@custardui/custardui 2.1.1 → 2.1.2-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/dist/custardui.js +754 -514
  2. package/dist/custardui.js.map +1 -1
  3. package/dist/custardui.min.js +2 -2
  4. package/dist/custardui.min.js.map +1 -1
  5. package/dist/types/src/browser.d.ts.map +1 -1
  6. package/dist/types/src/lib/app/ui-manager.d.ts.map +1 -1
  7. package/dist/types/src/lib/features/adaptation/types.d.ts +6 -1
  8. package/dist/types/src/lib/features/adaptation/types.d.ts.map +1 -1
  9. package/dist/types/src/lib/features/anchor/descriptor.d.ts.map +1 -1
  10. package/dist/types/src/lib/features/anchor/resolver.d.ts +1 -1
  11. package/dist/types/src/lib/features/anchor/resolver.d.ts.map +1 -1
  12. package/dist/types/src/lib/features/focus/services/focus-service.svelte.d.ts +1 -2
  13. package/dist/types/src/lib/features/focus/services/focus-service.svelte.d.ts.map +1 -1
  14. package/dist/types/src/lib/features/highlight/services/highlight-service.svelte.d.ts +1 -3
  15. package/dist/types/src/lib/features/highlight/services/highlight-service.svelte.d.ts.map +1 -1
  16. package/dist/types/src/lib/features/labels/label-manager.d.ts +19 -0
  17. package/dist/types/src/lib/features/labels/label-manager.d.ts.map +1 -0
  18. package/dist/types/src/lib/features/labels/label-registry-store.svelte.d.ts +27 -0
  19. package/dist/types/src/lib/features/labels/label-registry-store.svelte.d.ts.map +1 -0
  20. package/dist/types/src/lib/features/labels/label-utils.d.ts +12 -0
  21. package/dist/types/src/lib/features/labels/label-utils.d.ts.map +1 -0
  22. package/dist/types/src/lib/features/labels/types.d.ts +12 -0
  23. package/dist/types/src/lib/features/labels/types.d.ts.map +1 -0
  24. package/dist/types/src/lib/features/placeholder/placeholder-manager.d.ts +1 -1
  25. package/dist/types/src/lib/features/placeholder/types.d.ts +2 -2
  26. package/dist/types/src/lib/features/placeholder/types.d.ts.map +1 -1
  27. package/dist/types/src/lib/features/settings/intro-manager.svelte.d.ts +2 -2
  28. package/dist/types/src/lib/features/settings/intro-manager.svelte.d.ts.map +1 -1
  29. package/dist/types/src/lib/features/toggles/types.d.ts +5 -0
  30. package/dist/types/src/lib/features/toggles/types.d.ts.map +1 -1
  31. package/dist/types/src/lib/features/url/url-constants.d.ts +13 -0
  32. package/dist/types/src/lib/features/url/url-constants.d.ts.map +1 -0
  33. package/dist/types/src/lib/features/url/url-encoding-utils.d.ts +35 -0
  34. package/dist/types/src/lib/features/url/url-encoding-utils.d.ts.map +1 -0
  35. package/dist/types/src/lib/features/url/url-state-generator.d.ts +17 -0
  36. package/dist/types/src/lib/features/url/url-state-generator.d.ts.map +1 -0
  37. package/dist/types/src/lib/features/url/url-state-manager.d.ts +10 -26
  38. package/dist/types/src/lib/features/url/url-state-manager.d.ts.map +1 -1
  39. package/dist/types/src/lib/features/url/url-state-parser.d.ts +17 -0
  40. package/dist/types/src/lib/features/url/url-state-parser.d.ts.map +1 -0
  41. package/dist/types/src/lib/features/url/url-state-shaper.d.ts +49 -0
  42. package/dist/types/src/lib/features/url/url-state-shaper.d.ts.map +1 -0
  43. package/dist/types/src/lib/registry.d.ts +1 -0
  44. package/dist/types/src/lib/registry.d.ts.map +1 -1
  45. package/dist/types/src/lib/runtime.svelte.d.ts +1 -3
  46. package/dist/types/src/lib/runtime.svelte.d.ts.map +1 -1
  47. package/dist/types/src/lib/stores/active-state-store.svelte.d.ts +8 -3
  48. package/dist/types/src/lib/stores/active-state-store.svelte.d.ts.map +1 -1
  49. package/dist/types/src/lib/stores/color-scheme-store.svelte.d.ts +13 -0
  50. package/dist/types/src/lib/stores/color-scheme-store.svelte.d.ts.map +1 -0
  51. package/dist/types/src/lib/stores/derived-store.svelte.d.ts +2 -6
  52. package/dist/types/src/lib/stores/derived-store.svelte.d.ts.map +1 -1
  53. package/dist/types/src/lib/stores/element-store.svelte.d.ts +2 -2
  54. package/dist/types/src/lib/stores/element-store.svelte.d.ts.map +1 -1
  55. package/dist/types/src/lib/types/config.d.ts +5 -4
  56. package/dist/types/src/lib/types/config.d.ts.map +1 -1
  57. package/dist/types/src/lib/types/index.d.ts +1 -1
  58. package/dist/types/src/lib/types/index.d.ts.map +1 -1
  59. package/dist/types/src/lib/utils/init-utils.d.ts +2 -3
  60. package/dist/types/src/lib/utils/init-utils.d.ts.map +1 -1
  61. package/package.json +5 -5
  62. package/dist/types/src/lib/features/render/assets.d.ts +0 -12
  63. package/dist/types/src/lib/features/render/assets.d.ts.map +0 -1
  64. package/dist/types/src/lib/features/render/render.d.ts +0 -3
  65. package/dist/types/src/lib/features/render/render.d.ts.map +0 -1
  66. package/dist/types/src/lib/features/render/types.d.ts +0 -19
  67. package/dist/types/src/lib/features/render/types.d.ts.map +0 -1
  68. package/dist/types/tests/functional/fingerprint-compatibility.test.d.ts +0 -2
  69. package/dist/types/tests/functional/fingerprint-compatibility.test.d.ts.map +0 -1
  70. package/dist/types/tests/lib/features/adaptation/adaptation-manager.test.d.ts +0 -2
  71. package/dist/types/tests/lib/features/adaptation/adaptation-manager.test.d.ts.map +0 -1
  72. package/dist/types/tests/lib/features/adaptation/stores/adaptation-store.test.d.ts +0 -2
  73. package/dist/types/tests/lib/features/adaptation/stores/adaptation-store.test.d.ts.map +0 -1
  74. package/dist/types/tests/lib/features/anchor/anchor.test.d.ts +0 -2
  75. package/dist/types/tests/lib/features/anchor/anchor.test.d.ts.map +0 -1
  76. package/dist/types/tests/lib/features/focus/focus-logic.test.d.ts +0 -2
  77. package/dist/types/tests/lib/features/focus/focus-logic.test.d.ts.map +0 -1
  78. package/dist/types/tests/lib/features/focus/focus-store.test.d.ts +0 -2
  79. package/dist/types/tests/lib/features/focus/focus-store.test.d.ts.map +0 -1
  80. package/dist/types/tests/lib/features/highlight/highlight-logic.test.d.ts +0 -2
  81. package/dist/types/tests/lib/features/highlight/highlight-logic.test.d.ts.map +0 -1
  82. package/dist/types/tests/lib/features/notifications/stores/toast-store.test.d.ts +0 -2
  83. package/dist/types/tests/lib/features/notifications/stores/toast-store.test.d.ts.map +0 -1
  84. package/dist/types/tests/lib/features/placeholder/placeholder-binder.test.d.ts +0 -2
  85. package/dist/types/tests/lib/features/placeholder/placeholder-binder.test.d.ts.map +0 -1
  86. package/dist/types/tests/lib/features/placeholder/placeholder-manager.test.d.ts +0 -2
  87. package/dist/types/tests/lib/features/placeholder/placeholder-manager.test.d.ts.map +0 -1
  88. package/dist/types/tests/lib/features/settings/intro-manager.test.d.ts +0 -2
  89. package/dist/types/tests/lib/features/settings/intro-manager.test.d.ts.map +0 -1
  90. package/dist/types/tests/lib/features/settings/stores/icon-settings-store.test.d.ts +0 -2
  91. package/dist/types/tests/lib/features/settings/stores/icon-settings-store.test.d.ts.map +0 -1
  92. package/dist/types/tests/lib/features/share/share-logic.test.d.ts +0 -2
  93. package/dist/types/tests/lib/features/share/share-logic.test.d.ts.map +0 -1
  94. package/dist/types/tests/lib/features/share/share-store.test.d.ts +0 -2
  95. package/dist/types/tests/lib/features/share/share-store.test.d.ts.map +0 -1
  96. package/dist/types/tests/lib/features/url/url-action-handler.test.d.ts +0 -2
  97. package/dist/types/tests/lib/features/url/url-action-handler.test.d.ts.map +0 -1
  98. package/dist/types/tests/lib/features/url/url-state-manager.test.d.ts +0 -2
  99. package/dist/types/tests/lib/features/url/url-state-manager.test.d.ts.map +0 -1
  100. package/dist/types/tests/lib/services/url-action-router.test.d.ts +0 -2
  101. package/dist/types/tests/lib/services/url-action-router.test.d.ts.map +0 -1
  102. package/dist/types/tests/lib/stores/active-state-store.test.d.ts +0 -2
  103. package/dist/types/tests/lib/stores/active-state-store.test.d.ts.map +0 -1
  104. package/dist/types/tests/lib/stores/element-store.test.d.ts +0 -2
  105. package/dist/types/tests/lib/stores/element-store.test.d.ts.map +0 -1
  106. package/dist/types/tests/lib/stores/ui-store.test.d.ts +0 -2
  107. package/dist/types/tests/lib/stores/ui-store.test.d.ts.map +0 -1
  108. package/dist/types/tests/lib/utils/persistence.test.d.ts +0 -2
  109. package/dist/types/tests/lib/utils/persistence.test.d.ts.map +0 -1
  110. package/dist/types/tests/lib/utils/scroll-utils.test.d.ts +0 -2
  111. package/dist/types/tests/lib/utils/scroll-utils.test.d.ts.map +0 -1
  112. package/dist/types/tests/lib/utils/url-utils.test.d.ts +0 -2
  113. package/dist/types/tests/lib/utils/url-utils.test.d.ts.map +0 -1
  114. package/dist/types/tests/setup.d.ts +0 -2
  115. package/dist/types/tests/setup.d.ts.map +0 -1
package/dist/custardui.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @custardui/custardui v2.1.1
2
+ * @custardui/custardui v2.1.2-beta.1
3
3
  * (c) 2026 Chan Ger Teck
4
4
  * Released under the MIT License.
5
5
  */
@@ -28,58 +28,61 @@
28
28
  return cleanbaseUrl + cleanPath;
29
29
  }
30
30
 
31
+ const SCRIPT_ATTRIBUTE_DEFAULTS = {
32
+ baseURL: '/',
33
+ configPath: '/custardui.config.json',
34
+ };
35
+ const FALLBACK_CONFIG = {
36
+ config: {},
37
+ settings: { enabled: false },
38
+ };
31
39
  /**
32
40
  * Finds the script tag that loaded the library and extracts configuration attributes.
33
41
  * Looks for `data-base-url` and `data-config-path`.
34
42
  */
35
43
  function getScriptAttributes() {
36
- let scriptTag = document.currentScript;
37
- const defaults = { baseURL: '', configPath: '/custardui.config.json' };
38
- if (!scriptTag || !scriptTag.hasAttribute('data-base-url')) {
39
- const dataAttrMatch = document.querySelector('script[data-base-url]');
40
- if (dataAttrMatch) {
41
- scriptTag = dataAttrMatch;
42
- }
43
- else {
44
- // Fallback: try to find script by src pattern
45
- for (const script of document.scripts) {
46
- const src = script.src || '';
47
- if (/(?:custard(?:ui)?|@custardui\/custard(?:ui)?)(?:\.min)?\.(?:esm\.)?js($|\?)/i.test(src)) {
48
- scriptTag = script;
49
- break;
50
- }
51
- }
44
+ const scriptTag = findScriptTag();
45
+ if (!scriptTag)
46
+ return SCRIPT_ATTRIBUTE_DEFAULTS;
47
+ return {
48
+ baseURL: scriptTag.getAttribute('data-base-url') || SCRIPT_ATTRIBUTE_DEFAULTS.baseURL,
49
+ configPath: scriptTag.getAttribute('data-config-path') || SCRIPT_ATTRIBUTE_DEFAULTS.configPath,
50
+ };
51
+ }
52
+ function findScriptTag() {
53
+ const current = document.currentScript;
54
+ if (current?.hasAttribute('data-base-url'))
55
+ return current;
56
+ const byAttr = document.querySelector('script[data-base-url]');
57
+ if (byAttr)
58
+ return byAttr;
59
+ // Fallback: find script by src pattern
60
+ for (const script of document.scripts) {
61
+ if (/(?:custard(?:ui)?|@custardui\/custard(?:ui)?)(?:\.min)?\.(?:esm\.)?js($|\?)/i.test(script.src)) {
62
+ return script;
52
63
  }
53
64
  }
54
- if (scriptTag) {
55
- return {
56
- baseURL: scriptTag.getAttribute('data-base-url') || defaults.baseURL,
57
- configPath: scriptTag.getAttribute('data-config-path') || defaults.configPath,
58
- };
59
- }
60
- return defaults;
65
+ return null;
61
66
  }
62
67
  /**
63
68
  * Fetches and parses the configuration file.
69
+ * Returns the fallback config silently if the file is not found (404),
70
+ * since operating without a config file is a valid use case.
64
71
  */
65
72
  async function fetchConfig(configPath, baseURL) {
66
- const fallbackMinimalConfig = {
67
- config: {},
68
- settings: { enabled: true },
69
- };
70
73
  try {
71
74
  const fullConfigPath = prependBaseUrl(configPath, baseURL);
72
75
  const response = await fetch(fullConfigPath);
73
- if (!response.ok) {
74
- console.warn(`[CustardUI] Config file not found at ${fullConfigPath}. Using defaults.`);
75
- return fallbackMinimalConfig;
76
- }
77
- const config = await response.json();
78
- return config;
76
+ if (!response.ok)
77
+ return FALLBACK_CONFIG;
78
+ const text = await response.text();
79
+ if (!text.trim())
80
+ return FALLBACK_CONFIG;
81
+ return JSON.parse(text);
79
82
  }
80
83
  catch (error) {
81
84
  console.error('[CustardUI] Error loading config file:', error);
82
- return fallbackMinimalConfig;
85
+ return FALLBACK_CONFIG;
83
86
  }
84
87
  }
85
88
 
@@ -9203,8 +9206,8 @@
9203
9206
  config.placeholders.forEach((def) => {
9204
9207
  placeholderRegistryStore.register({
9205
9208
  ...def,
9206
- // adaptationPlaceholder implies hidden from user-facing settings
9207
- hiddenFromSettings: def.adaptationPlaceholder ? true : def.hiddenFromSettings,
9209
+ // siteManaged implies hidden from user-facing settings
9210
+ hiddenFromSettings: def.siteManaged ? true : def.hiddenFromSettings,
9208
9211
  source: 'config',
9209
9212
  });
9210
9213
  });
@@ -9264,7 +9267,7 @@
9264
9267
  }
9265
9268
  /**
9266
9269
  * Filters a record of incoming placeholders to only those that can be set by users.
9267
- * Extends filterValidPlaceholders() by also excluding adaptation-only placeholders.
9270
+ * Extends filterValidPlaceholders() by also excluding siteManaged placeholders.
9268
9271
  * Use this for persistence loads and URL delta application.
9269
9272
  */
9270
9273
  filterUserSettablePlaceholders(placeholders = {}) {
@@ -9272,8 +9275,8 @@
9272
9275
  const userSettable = {};
9273
9276
  for (const [key, value] of Object.entries(valid)) {
9274
9277
  const def = placeholderRegistryStore.get(key);
9275
- if (def?.adaptationPlaceholder) {
9276
- // Silently skip — adaptation-only placeholders cannot be user-set
9278
+ if (def?.siteManaged) {
9279
+ // Silently skip — site-managed placeholders cannot be user-set
9277
9280
  continue;
9278
9281
  }
9279
9282
  userSettable[key] = value;
@@ -9461,8 +9464,8 @@
9461
9464
  const defaults = this.computeDefaultState();
9462
9465
  const validatedTabs = this.filterValidTabs(newState.tabs ?? {});
9463
9466
  const validatedPlaceholders = placeholderManager.filterUserSettablePlaceholders(newState.placeholders ?? {});
9464
- const validatedShownToggles = this.filterValidToggles(newState.shownToggles ?? defaults.shownToggles ?? []);
9465
- const validatedPeekToggles = this.filterValidToggles(newState.peekToggles ?? defaults.peekToggles ?? []);
9467
+ const validatedShownToggles = this.filterNonSiteManagedToggleIds(this.filterValidToggles(newState.shownToggles ?? defaults.shownToggles ?? []));
9468
+ const validatedPeekToggles = this.filterNonSiteManagedToggleIds(this.filterValidToggles(newState.peekToggles ?? defaults.peekToggles ?? []));
9466
9469
 
9467
9470
  this.state = {
9468
9471
  shownToggles: validatedShownToggles,
@@ -9499,20 +9502,28 @@
9499
9502
  }
9500
9503
 
9501
9504
  /**
9502
- * Applies adaptation defaults on top of the config defaults, before persisted state.
9505
+ * Applies adaptation preset on top of the config defaults, before persisted state.
9503
9506
  * User choices applied later via applyState() will override these.
9504
9507
  *
9505
- * @param defaults The defaults section from the adaptation config
9508
+ * @param preset The preset section from the adaptation config
9506
9509
  */
9507
- applyAdaptationDefaults(defaults) {
9508
- if (!defaults) return;
9510
+ applyAdaptationDefaults(preset) {
9511
+ if (!preset) return;
9509
9512
 
9510
- if (defaults.toggles) {
9511
- this.applyToggleMap(defaults.toggles);
9513
+ if (preset.toggles) {
9514
+ this.applyToggleMap(preset.toggles);
9512
9515
  }
9513
9516
 
9514
- if (defaults.placeholders) {
9515
- const validated = placeholderManager.filterValidPlaceholders(defaults.placeholders);
9517
+ if (preset.tabs) {
9518
+ const validated = this.filterValidTabs(preset.tabs);
9519
+
9520
+ if (!this.state.tabs) this.state.tabs = {};
9521
+
9522
+ Object.assign(this.state.tabs, validated);
9523
+ }
9524
+
9525
+ if (preset.placeholders) {
9526
+ const validated = placeholderManager.filterValidPlaceholders(preset.placeholders);
9516
9527
 
9517
9528
  if (!this.state.placeholders) this.state.placeholders = {};
9518
9529
 
@@ -9569,11 +9580,11 @@
9569
9580
  }
9570
9581
  }
9571
9582
 
9572
- // 3. Seed author-controlled (adaptationPlaceholder) defaults.
9583
+ // 3. Seed site-managed (siteManaged) defaults.
9573
9584
  // These are set by adaptations when active, and fall back to defaultValue when no adaptation is active.
9574
9585
  // This is intentionally separate from regular user-settable placeholder defaults (see PR #206).
9575
9586
  for (const def of placeholderRegistryStore.definitions) {
9576
- if (def.adaptationPlaceholder && def.defaultValue !== undefined && def.defaultValue !== '') {
9587
+ if (def.siteManaged && def.defaultValue !== undefined && def.defaultValue !== '') {
9577
9588
  placeholders[def.name] = def.defaultValue;
9578
9589
  }
9579
9590
  }
@@ -9631,13 +9642,13 @@
9631
9642
  */
9632
9643
  applyToggleDelta(deltaState) {
9633
9644
  // eslint-disable-next-line svelte/prefer-svelte-reactivity
9634
- const toShow = new Set(this.filterValidToggles(deltaState.shownToggles ?? []));
9645
+ const toShow = new Set(this.filterNonSiteManagedToggleIds(this.filterValidToggles(deltaState.shownToggles ?? [])));
9635
9646
 
9636
9647
  // eslint-disable-next-line svelte/prefer-svelte-reactivity
9637
- const toPeek = new Set(this.filterValidToggles(deltaState.peekToggles ?? []));
9648
+ const toPeek = new Set(this.filterNonSiteManagedToggleIds(this.filterValidToggles(deltaState.peekToggles ?? [])));
9638
9649
 
9639
9650
  // eslint-disable-next-line svelte/prefer-svelte-reactivity
9640
- const toHide = new Set(this.filterValidToggles(deltaState.hiddenToggles ?? []));
9651
+ const toHide = new Set(this.filterNonSiteManagedToggleIds(this.filterValidToggles(deltaState.hiddenToggles ?? [])));
9641
9652
 
9642
9653
  // eslint-disable-next-line svelte/prefer-svelte-reactivity
9643
9654
  const allMentioned = new Set([...toShow, ...toPeek, ...toHide]);
@@ -9685,6 +9696,18 @@
9685
9696
  Object.assign(this.state.placeholders, validatedPlaceholders);
9686
9697
  }
9687
9698
 
9699
+ /**
9700
+ * Removes toggle IDs belonging to siteManaged toggles.
9701
+ * Used to prevent user-supplied state (URL, localStorage) from overriding site-controlled toggles.
9702
+ */
9703
+ filterNonSiteManagedToggleIds(ids) {
9704
+ return ids.filter((id) => {
9705
+ const toggle = this.config.toggles?.find((t) => t.toggleId === id);
9706
+
9707
+ return !toggle?.siteManaged;
9708
+ });
9709
+ }
9710
+
9688
9711
  /**
9689
9712
  * Validates an incoming tab record against the configuration.
9690
9713
  * Drops any groupId that doesn't exist in `config.tabGroups`,
@@ -9826,14 +9849,14 @@
9826
9849
  set(this.#detectedPlaceholders, value, true);
9827
9850
  }
9828
9851
 
9829
- #hasPageElements = user_derived(() => this.detectedToggles.size > 0 || this.detectedTabGroups.size > 0);
9852
+ #hasElementsOnCurrentPage = user_derived(() => this.detectedToggles.size > 0 || this.detectedTabGroups.size > 0 || this.detectedPlaceholders.size > 0);
9830
9853
 
9831
- get hasPageElements() {
9832
- return get(this.#hasPageElements);
9854
+ get hasElementsOnCurrentPage() {
9855
+ return get(this.#hasElementsOnCurrentPage);
9833
9856
  }
9834
9857
 
9835
- set hasPageElements(value) {
9836
- set(this.#hasPageElements, value);
9858
+ set hasElementsOnCurrentPage(value) {
9859
+ set(this.#hasElementsOnCurrentPage, value);
9837
9860
  }
9838
9861
 
9839
9862
  registerToggle(id) {
@@ -9937,21 +9960,15 @@
9937
9960
  /* derived-store.svelte.ts generated by Svelte v5.46.1 */
9938
9961
 
9939
9962
  class DerivedStateStore {
9940
- #assetsManager = state(undefined);
9941
-
9942
- get assetsManager() {
9943
- return get(this.#assetsManager);
9944
- }
9945
-
9946
- set assetsManager(value) {
9947
- set(this.#assetsManager, value, true);
9948
- }
9949
-
9950
- #menuToggles = user_derived(() => {
9951
- if (!activeStateStore.config.toggles) return [];
9963
+ #menuToggles = user_derived(
9964
+ // Menu toggles are those that are either global or
9965
+ // local but present and registered in the DOM
9966
+ () => {
9967
+ if (!activeStateStore.config.toggles) return [];
9952
9968
 
9953
- return activeStateStore.config.toggles.filter((t) => !t.isLocal || elementStore.detectedToggles.has(t.toggleId));
9954
- });
9969
+ return activeStateStore.config.toggles.filter((t) => (!t.isLocal || elementStore.detectedToggles.has(t.toggleId)) && !t.siteManaged);
9970
+ }
9971
+ );
9955
9972
 
9956
9973
  get menuToggles() {
9957
9974
  return get(this.#menuToggles);
@@ -10019,23 +10036,19 @@
10019
10036
  set hasMenuOptions(value) {
10020
10037
  set(this.#hasMenuOptions, value);
10021
10038
  }
10022
-
10023
- setAssetsManager(manager) {
10024
- this.assetsManager = manager;
10025
- }
10026
10039
  }
10027
10040
 
10028
10041
  const derivedStore = new DerivedStateStore();
10029
10042
 
10030
10043
  var root$w = from_html(`<div><div role="alert"><button type="button" class="close-btn svelte-ysaqmb" aria-label="Dismiss intro">×</button> <p class="text svelte-ysaqmb"> </p></div></div>`);
10031
10044
 
10032
- const $$css$o = {
10045
+ const $$css$p = {
10033
10046
  hash: 'svelte-ysaqmb',
10034
10047
  code: '\n /* Animation */\n @keyframes svelte-ysaqmb-popIn {\n 0% {\n opacity: 0;\n transform: scale(0.9) translateY(-50%);\n }\n 100% {\n opacity: 1;\n transform: scale(1) translateY(-50%);\n }\n }\n\n /* Reset transform for top/bottom positions */\n @keyframes svelte-ysaqmb-popInVertical {\n 0% {\n opacity: 0;\n transform: scale(0.9);\n }\n 100% {\n opacity: 1;\n transform: scale(1);\n }\n }\n\n /* Simplified Pulse Animation - Shadow Only */\n @keyframes svelte-ysaqmb-pulse {\n 0% {\n transform: scale(1);\n box-shadow:\n 0 4px 6px -1px rgba(0, 0, 0, 0.1),\n 0 0 0 0 rgba(62, 132, 244, 0.7);\n }\n 50% {\n transform: scale(1);\n box-shadow:\n 0 4px 6px -1px rgba(0, 0, 0, 0.1),\n 0 0 0 10px rgba(62, 132, 244, 0);\n }\n 100% {\n transform: scale(1);\n box-shadow:\n 0 4px 6px -1px rgba(0, 0, 0, 0.1),\n 0 0 0 0 rgba(62, 132, 244, 0);\n }\n }\n\n /* Wrapper handles Positioning & Entry Animation */.cv-callout-wrapper.svelte-ysaqmb {position:fixed;z-index:9999;\n\n /* Default animation (centered ones) */\n animation: svelte-ysaqmb-popIn 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards;}\n\n /* Inner handles Visuals & Pulse Animation */.cv-callout.svelte-ysaqmb {background:var(--cv-callout-bg, var(--cv-bg));padding:1rem 1.25rem;border-radius:0.5rem;box-shadow:0 4px 6px -1px var(--cv-shadow),\n 0 2px 4px -1px var(--cv-shadow); /* adapt shadow? */max-width:250px;font-size:0.9rem;line-height:1.5;color:var(--cv-callout-text, var(--cv-text));display:flex;align-items:flex-start;gap:0.75rem;font-family:inherit;border:2px solid var(--cv-border);}\n\n /* Apply pulse to inner callout if enabled */.cv-callout.cv-pulse.svelte-ysaqmb {\n animation: svelte-ysaqmb-pulse 2s infinite 0.5s;}\n\n /* Arrow Base */.cv-callout.svelte-ysaqmb::before {content:\'\';position:absolute;width:1rem;height:1rem;background:var(--cv-callout-bg, var(--cv-bg));transform:rotate(45deg);border:2px solid var(--cv-border);z-index:-1;}.close-btn.svelte-ysaqmb {background:transparent;border:none;color:currentColor;opacity:0.7;font-size:1.25rem;line-height:1;cursor:pointer;padding:0;margin:-0.25rem -0.5rem 0 0;transition:opacity 0.15s;flex-shrink:0;}.close-btn.svelte-ysaqmb:hover {color:currentColor;opacity:1;}.text.svelte-ysaqmb {margin:0;flex:1;font-weight:500;}\n\n /* \n Position Specifics (Applied to Wrapper)\n */\n\n /* Right-side positions (Icon on Right -> Callout on Left) */.pos-top-right.svelte-ysaqmb,\n .pos-middle-right.svelte-ysaqmb,\n .pos-bottom-right.svelte-ysaqmb {right:80px;}.pos-top-right.svelte-ysaqmb,\n .pos-bottom-right.svelte-ysaqmb {\n animation-name: svelte-ysaqmb-popInVertical;}\n\n /* X Button Spacing Adjustments */.pos-top-right.svelte-ysaqmb .close-btn:where(.svelte-ysaqmb),\n .pos-middle-right.svelte-ysaqmb .close-btn:where(.svelte-ysaqmb),\n .pos-bottom-right.svelte-ysaqmb .close-btn:where(.svelte-ysaqmb) {margin-right:0;margin-left:-0.5rem;}\n\n /* Left-side positions (Icon on Left -> Callout on Right) */.pos-top-left.svelte-ysaqmb,\n .pos-middle-left.svelte-ysaqmb,\n .pos-bottom-left.svelte-ysaqmb {left:80px;}.pos-top-left.svelte-ysaqmb .close-btn:where(.svelte-ysaqmb),\n .pos-middle-left.svelte-ysaqmb .close-btn:where(.svelte-ysaqmb),\n .pos-bottom-left.svelte-ysaqmb .close-btn:where(.svelte-ysaqmb) {order:2; /* Move to end */margin-right:-0.5rem;margin-left:0;}.pos-top-left.svelte-ysaqmb,\n .pos-bottom-left.svelte-ysaqmb {\n animation-name: svelte-ysaqmb-popInVertical;}\n\n /* Vertical Alignment */.pos-middle-right.svelte-ysaqmb,\n .pos-middle-left.svelte-ysaqmb {top:50%;\n /* transform handled by popIn animation (translateY -50%) */}.pos-top-right.svelte-ysaqmb,\n .pos-top-left.svelte-ysaqmb {top:20px;}.pos-bottom-right.svelte-ysaqmb,\n .pos-bottom-left.svelte-ysaqmb {bottom:20px;}\n\n /* Arrow Positioning (Child of .callout, dependent on Wrapper .pos-*) */\n\n /* Pointing Right */.pos-top-right.svelte-ysaqmb .cv-callout:where(.svelte-ysaqmb)::before,\n .pos-middle-right.svelte-ysaqmb .cv-callout:where(.svelte-ysaqmb)::before,\n .pos-bottom-right.svelte-ysaqmb .cv-callout:where(.svelte-ysaqmb)::before {right:-0.5rem;border-left:none;border-bottom:none;}\n\n /* Pointing Left */.pos-top-left.svelte-ysaqmb .cv-callout:where(.svelte-ysaqmb)::before,\n .pos-middle-left.svelte-ysaqmb .cv-callout:where(.svelte-ysaqmb)::before,\n .pos-bottom-left.svelte-ysaqmb .cv-callout:where(.svelte-ysaqmb)::before {left:-0.5rem;border-right:none;border-top:none;}\n\n /* Vertical placement of arrow */.pos-middle-right.svelte-ysaqmb .cv-callout:where(.svelte-ysaqmb)::before,\n .pos-middle-left.svelte-ysaqmb .cv-callout:where(.svelte-ysaqmb)::before {top:50%;margin-top:-0.5rem;}.pos-top-right.svelte-ysaqmb .cv-callout:where(.svelte-ysaqmb)::before,\n .pos-top-left.svelte-ysaqmb .cv-callout:where(.svelte-ysaqmb)::before {top:1.25rem;}.pos-bottom-right.svelte-ysaqmb .cv-callout:where(.svelte-ysaqmb)::before,\n .pos-bottom-left.svelte-ysaqmb .cv-callout:where(.svelte-ysaqmb)::before {bottom:1.25rem;}\n\n @media print {.cv-callout-wrapper.svelte-ysaqmb {display:none !important;}\n }'
10035
10048
  };
10036
10049
 
10037
10050
  function IntroCallout($$anchor, $$props) {
10038
- append_styles$1($$anchor, $$css$o);
10051
+ append_styles$1($$anchor, $$css$p);
10039
10052
 
10040
10053
  let position = prop($$props, 'position', 3, 'middle-left'),
10041
10054
  message = prop($$props, 'message', 3, 'Customize your reading experience here.'),
@@ -10103,17 +10116,17 @@
10103
10116
  append($$anchor, svg);
10104
10117
  }
10105
10118
 
10106
- var root_1$f = from_html(`<button type="button" class="cv-dismiss-btn svelte-122ln5" aria-label="Dismiss settings icon">✕</button>`);
10119
+ var root_1$g = from_html(`<button type="button" class="cv-dismiss-btn svelte-122ln5" aria-label="Dismiss settings icon">✕</button>`);
10107
10120
  var root$u = from_html(`<div role="none"><button type="button" class="cv-settings-main-btn svelte-122ln5"><span class="cv-gear svelte-122ln5"><!></span></button> <button type="button" class="cv-collapse-btn svelte-122ln5" aria-label="Collapse settings icon"> </button> <!></div>`);
10108
10121
 
10109
- const $$css$n = {
10122
+ const $$css$o = {
10110
10123
  hash: 'svelte-122ln5',
10111
10124
  code: '.cv-settings-main-btn.svelte-122ln5 {appearance:none;-webkit-appearance:none;background:transparent;border:none;padding:0;margin:0;flex:1;height:100%;width:100%;display:flex;align-items:inherit;justify-content:inherit;color:inherit;cursor:inherit;border-radius:inherit;}.cv-settings-main-btn.svelte-122ln5:focus-visible {outline:2px solid currentColor;outline-offset:-2px;}.cv-gear.svelte-122ln5 {display:flex;align-items:center;justify-content:center;width:18px;height:18px;}.cv-gear.svelte-122ln5 svg {width:18px;height:18px;}.cv-gear.svelte-122ln5 svg path {fill:currentColor;}.cv-settings-icon.svelte-122ln5 {position:fixed;background:var(--cv-icon-bg, rgba(255, 255, 255, 0.92));color:var(--cv-icon-color, rgba(0, 0, 0, 0.9));opacity:var(--cv-icon-opacity, 0.6);display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:bold;cursor:grab; /* Default cursor */box-shadow:0 4px 12px rgba(0, 0, 0, 0.15);border:2px solid rgba(0, 0, 0, 0.2);z-index:9998;transition:width 0.3s ease,\n background 0.3s ease,\n color 0.3s ease,\n opacity 0.3s ease,\n border-color 0.3s ease,\n transform 0.4s ease; /* transform transition drives the peek slide animation */touch-action:none; /* Crucial for touch dragging */font-family:-apple-system, BlinkMacSystemFont, \'Segoe UI\', Roboto, sans-serif;box-sizing:border-box;user-select:none; /* Prevent text selection while dragging */}.cv-settings-icon.svelte-122ln5:active {cursor:grabbing;}.cv-settings-icon.svelte-122ln5:hover {background:var(--cv-icon-bg, rgba(255, 255, 255, 1));color:var(--cv-icon-color, rgba(0, 0, 0, 1));opacity:1;border-color:rgba(0, 0, 0, 0.3);}\n\n /* Remove transform transition during drag so it tracks the pointer without lag */.cv-settings-icon.cv-is-dragging.svelte-122ln5 {transition:width 0.3s ease,\n background 0.3s ease,\n color 0.3s ease,\n opacity 0.3s ease,\n border-color 0.3s ease;}\n\n /* When collapsed, dim the strip */.cv-settings-icon.cv-is-collapsed.svelte-122ln5 {opacity:0.5;}.cv-settings-icon.cv-is-collapsed.svelte-122ln5:hover {opacity:0.85;}.cv-collapse-btn.svelte-122ln5 {position:absolute;top:0;bottom:0;width:16px;display:flex;align-items:center;justify-content:center;background:rgba(0, 0, 0, 0.12);border:none;padding:0;cursor:pointer;font-size:13px;line-height:1;color:inherit;opacity:0.5;transition:opacity 0.15s ease, background 0.15s ease;}.cv-collapse-btn[data-side=\'left\'].svelte-122ln5 {left:0; /* outer = screen-edge side for left icons */border-radius:0 6px 6px 0;}.cv-collapse-btn[data-side=\'right\'].svelte-122ln5 {right:0; /* outer = screen-edge side for right icons */border-radius:6px 0 0 6px;}.cv-collapse-btn.svelte-122ln5:hover {opacity:1;background:rgba(0, 0, 0, 0.22);}\n\n /* Hide collapse tab when already collapsed */.cv-settings-icon.cv-is-collapsed.svelte-122ln5 .cv-collapse-btn:where(.svelte-122ln5) {display:none;}.cv-dismiss-btn.svelte-122ln5 {position:absolute;bottom:calc(100% + 4px);width:16px;height:16px;display:flex;align-items:center;justify-content:center;background:rgba(0, 0, 0, 0.15);border:none;border-radius:50%;padding:0;cursor:pointer;font-size:9px;line-height:1;color:inherit;opacity:0.5;transition:opacity 0.15s ease, background 0.15s ease;}.cv-dismiss-btn[data-side=\'left\'].svelte-122ln5 {left:0;}.cv-dismiss-btn[data-side=\'right\'].svelte-122ln5 {right:0;}.cv-dismiss-btn.svelte-122ln5:hover {opacity:1;background:rgba(0, 0, 0, 0.25);}\n\n\n /* Top-right */.cv-settings-top-right.svelte-122ln5 {top:20px;right:0;border-radius:18px 0 0 18px;padding-left:6px;justify-content:flex-start;border-right:none;}\n\n /* Top-left */.cv-settings-top-left.svelte-122ln5 {top:20px;left:0;border-radius:0 18px 18px 0;padding-right:6px;justify-content:flex-end;border-left:none;}\n\n /* Bottom-right */.cv-settings-bottom-right.svelte-122ln5 {bottom:20px;right:0;border-radius:18px 0 0 18px;padding-left:6px;justify-content:flex-start;border-right:none;}\n\n /* Bottom-left */.cv-settings-bottom-left.svelte-122ln5 {bottom:20px;left:0;border-radius:0 18px 18px 0;padding-right:6px;justify-content:flex-end;border-left:none;}\n\n /* Middle-left */.cv-settings-middle-left.svelte-122ln5 {top:50%;left:0;\n /* transform handled by inline style now */border-radius:0 18px 18px 0;padding-right:6px;justify-content:flex-end;border-left:none;}\n\n /* Middle-right */.cv-settings-middle-right.svelte-122ln5 {top:50%;right:0;\n /* transform handled by inline style now */border-radius:18px 0 0 18px;padding-left:6px;justify-content:flex-start;border-right:none;}.cv-settings-top-right.svelte-122ln5,\n .cv-settings-middle-right.svelte-122ln5,\n .cv-settings-bottom-right.svelte-122ln5,\n .cv-settings-top-left.svelte-122ln5,\n .cv-settings-middle-left.svelte-122ln5,\n .cv-settings-bottom-left.svelte-122ln5 {height:36px;width:36px;}.cv-settings-middle-right.svelte-122ln5:hover,\n .cv-settings-top-right.svelte-122ln5:hover,\n .cv-settings-bottom-right.svelte-122ln5:hover,\n .cv-settings-top-left.svelte-122ln5:hover,\n .cv-settings-middle-left.svelte-122ln5:hover,\n .cv-settings-bottom-left.svelte-122ln5:hover {width:55px;}.cv-pulse {\n animation: svelte-122ln5-pulse 2s infinite;}\n\n @keyframes svelte-122ln5-pulse {\n 0% {\n box-shadow:\n 0 4px 12px rgba(0, 0, 0, 0.15),\n 0 0 0 0 rgba(62, 132, 244, 0.7);\n }\n 70% {\n box-shadow:\n 0 4px 12px rgba(0, 0, 0, 0.15),\n 0 0 0 10px rgba(62, 132, 244, 0);\n }\n 100% {\n box-shadow:\n 0 4px 12px rgba(0, 0, 0, 0.15),\n 0 0 0 0 rgba(62, 132, 244, 0);\n }\n }\n\n @media (max-width: 768px) {.cv-settings-top-right.svelte-122ln5,\n .cv-settings-top-left.svelte-122ln5 {top:10px;}.cv-settings-bottom-right.svelte-122ln5,\n .cv-settings-bottom-left.svelte-122ln5 {bottom:10px;}.cv-settings-top-right.svelte-122ln5,\n .cv-settings-bottom-right.svelte-122ln5,\n .cv-settings-middle-right.svelte-122ln5 {right:0;}.cv-settings-top-left.svelte-122ln5,\n .cv-settings-bottom-left.svelte-122ln5,\n .cv-settings-middle-left.svelte-122ln5 {left:0;}.cv-settings-icon.svelte-122ln5 {width:60px;height:32px;}.cv-settings-icon.svelte-122ln5:hover {width:75px;}\n }\n\n @media print {.cv-settings-icon.svelte-122ln5 {display:none !important;}\n }'
10112
10125
  };
10113
10126
 
10114
10127
  function SettingsIcon($$anchor, $$props) {
10115
10128
  push($$props, true);
10116
- append_styles$1($$anchor, $$css$n);
10129
+ append_styles$1($$anchor, $$css$o);
10117
10130
 
10118
10131
  /* eslint-disable @typescript-eslint/no-explicit-any */
10119
10132
  const iconSettingsStore = getContext(ICON_SETTINGS_CTX);
@@ -10358,7 +10371,7 @@
10358
10371
 
10359
10372
  {
10360
10373
  var consequent = ($$anchor) => {
10361
- var button_2 = root_1$f();
10374
+ var button_2 = root_1$g();
10362
10375
 
10363
10376
  button_2.__click = (e) => {
10364
10377
  e.stopPropagation();
@@ -10677,7 +10690,12 @@
10677
10690
  const PARAM_HIDE_TOGGLE = 't-hide';
10678
10691
  const PARAM_TABS = 'tabs';
10679
10692
  const PARAM_PH = 'ph';
10693
+ const PARAM_CV_SHOW = 'cv-show';
10694
+ const PARAM_CV_HIDE = 'cv-hide';
10695
+ const PARAM_CV_HIGHLIGHT = 'cv-highlight';
10696
+ /** Parameters owned by URLStateManager */
10680
10697
  const MANAGED_PARAMS = [PARAM_SHOW_TOGGLE, PARAM_PEEK_TOGGLE, PARAM_HIDE_TOGGLE, PARAM_TABS, PARAM_PH];
10698
+
10681
10699
  /**
10682
10700
  * Encodes a list of IDs into a comma-separated query-safe string.
10683
10701
  *
@@ -10752,12 +10770,12 @@
10752
10770
  }
10753
10771
  return result;
10754
10772
  }
10755
- // --- URL Parsing ---
10773
+
10756
10774
  /**
10757
10775
  * Parses toggle visibility state from the current URL search string.
10758
10776
  * Returns partial state containing only the toggle fields that are present.
10759
10777
  */
10760
- function parseTogglesFromSearch(search) {
10778
+ function parseTogglesFromURL(search) {
10761
10779
  const partial = {};
10762
10780
  const showIds = splitAndDecode(search, PARAM_SHOW_TOGGLE);
10763
10781
  if (showIds.length > 0)
@@ -10774,7 +10792,7 @@
10774
10792
  * Parses tab group selections from the current URL search string.
10775
10793
  * Returns partial state containing the `tabs` record, or empty object if absent.
10776
10794
  */
10777
- function parseTabsFromSearch(search) {
10795
+ function parseTabsFromURL(search) {
10778
10796
  const tabs = decodePairs(search, PARAM_TABS);
10779
10797
  return Object.keys(tabs).length > 0 ? { tabs } : {};
10780
10798
  }
@@ -10782,11 +10800,169 @@
10782
10800
  * Parses placeholder values from the current URL search string.
10783
10801
  * Returns partial state containing the `placeholders` record, or empty object if absent.
10784
10802
  */
10785
- function parsePlaceholdersFromSearch(search) {
10803
+ function parsePlaceholdersFromURL(search) {
10786
10804
  const placeholders = decodePairs(search, PARAM_PH);
10787
10805
  return Object.keys(placeholders).length > 0 ? { placeholders } : {};
10788
10806
  }
10789
- // --- URL Generation ---
10807
+
10808
+ /**
10809
+ * Strips placeholder entries that should not appear in shareable URLs:
10810
+ * - Tab-group-derived placeholders (source: 'tabgroup') — implied by ?tabs=
10811
+ * - Site-managed placeholders (siteManaged: true) — site-controlled, not shareable
10812
+ */
10813
+ function stripNonShareablePlaceholders(placeholders, config) {
10814
+ const shareable = {};
10815
+ for (const [key, value] of Object.entries(placeholders)) {
10816
+ const definition = placeholderRegistryStore.get(key) || config.placeholders?.find(p => p.name === key);
10817
+ if (!definition)
10818
+ continue; // skip unknown keys (injection prevention)
10819
+ // Note: PlaceholderDefinition from registry has 'source', but config doesn't.
10820
+ // That's fine as 'tabgroup' placeholders are mostly a runtime artifact.
10821
+ if ('source' in definition && definition.source === 'tabgroup')
10822
+ continue; // implied by ?tabs=
10823
+ if ('siteManaged' in definition && definition.siteManaged)
10824
+ continue; // site-managed, not shareable
10825
+ shareable[key] = value;
10826
+ }
10827
+ return shareable;
10828
+ }
10829
+ /**
10830
+ * Filters the current toggle state (shown, peeked) and derives the explicit
10831
+ * hidden list for the shareable URL.
10832
+ */
10833
+ function getShareableToggles(currentState, pageTogglesSet, config) {
10834
+ const currentShown = currentState.shownToggles ?? [];
10835
+ const currentPeek = currentState.peekToggles ?? [];
10836
+ const shouldInclude = (id) => {
10837
+ const toggleConfig = config.toggles?.find(t => t.toggleId === id);
10838
+ // Case 1: Not found in configuration
10839
+ if (!toggleConfig) {
10840
+ return pageTogglesSet.has(id);
10841
+ }
10842
+ // Site-managed toggles are never included in shareable URLs
10843
+ if (toggleConfig.siteManaged)
10844
+ return false;
10845
+ // Case 2: Configured as Global
10846
+ if (!toggleConfig.isLocal) {
10847
+ return true;
10848
+ }
10849
+ // Case 3: Configured as Local
10850
+ return pageTogglesSet.has(id);
10851
+ };
10852
+ const shareableShown = currentShown.filter(shouldInclude);
10853
+ const shareablePeek = currentPeek.filter(shouldInclude);
10854
+ const shownSet = new Set(shareableShown);
10855
+ const peekSet = new Set(shareablePeek);
10856
+ const absoluteHide = [];
10857
+ const relevantToggles = new Set(pageTogglesSet);
10858
+ for (const t of (config.toggles ?? [])) {
10859
+ if (!t.isLocal && !t.siteManaged)
10860
+ relevantToggles.add(t.toggleId);
10861
+ }
10862
+ // Build a set of siteManaged toggle IDs to exclude from absoluteHide
10863
+ const siteManagedToggleIds = new Set((config.toggles ?? []).filter(t => t.siteManaged).map(t => t.toggleId));
10864
+ for (const id of relevantToggles) {
10865
+ if (!shownSet.has(id) && !peekSet.has(id) && !siteManagedToggleIds.has(id)) {
10866
+ absoluteHide.push(id);
10867
+ }
10868
+ }
10869
+ const result = {};
10870
+ if (shareableShown.length > 0)
10871
+ result.shownToggles = shareableShown;
10872
+ if (shareablePeek.length > 0)
10873
+ result.peekToggles = shareablePeek;
10874
+ if (absoluteHide.length > 0)
10875
+ result.hiddenToggles = absoluteHide;
10876
+ return result;
10877
+ }
10878
+ /**
10879
+ * Filters the active tab selections for the shareable URL.
10880
+ */
10881
+ function getShareableTabs(currentState, pageTabGroupsSet, config) {
10882
+ if (!currentState.tabs)
10883
+ return {};
10884
+ const shareableTabs = {};
10885
+ for (const [groupId, tabId] of Object.entries(currentState.tabs)) {
10886
+ const groupConfig = config.tabGroups?.find(g => g.groupId === groupId);
10887
+ let shouldInclude = false;
10888
+ if (!groupConfig) {
10889
+ // Unknown group: Only include if explicitly detected on page to prevent injection
10890
+ shouldInclude = pageTabGroupsSet.has(groupId);
10891
+ }
10892
+ else if (!groupConfig.isLocal) {
10893
+ // Global group: Always include
10894
+ shouldInclude = true;
10895
+ }
10896
+ else {
10897
+ // Local group: Only include if detected on page
10898
+ shouldInclude = pageTabGroupsSet.has(groupId);
10899
+ }
10900
+ if (shouldInclude) {
10901
+ shareableTabs[groupId] = tabId;
10902
+ }
10903
+ }
10904
+ return Object.keys(shareableTabs).length > 0 ? { tabs: shareableTabs } : {};
10905
+ }
10906
+ /**
10907
+ * Filters the custom placeholder values for the shareable URL.
10908
+ */
10909
+ function getShareablePlaceholders(currentState, pagePlaceholdersSet, config) {
10910
+ if (!currentState.placeholders)
10911
+ return {};
10912
+ const strippedPlaceholders = stripNonShareablePlaceholders(currentState.placeholders, config);
10913
+ const shareablePlaceholders = {};
10914
+ for (const [key, value] of Object.entries(strippedPlaceholders)) {
10915
+ const definition = placeholderRegistryStore.get(key) || config.placeholders?.find(p => p.name === key);
10916
+ let shouldInclude = false;
10917
+ if (!definition) {
10918
+ // Note: definition is guaranteed to exist here due to stripNonShareablePlaceholders,
10919
+ // but we handle it explicitly for clarity and future-proofing.
10920
+ shouldInclude = pagePlaceholdersSet.has(key);
10921
+ }
10922
+ else if (!definition.isLocal) {
10923
+ // Global placeholder: Always include
10924
+ shouldInclude = true;
10925
+ }
10926
+ else {
10927
+ // Local placeholder: Only include if detected on page
10928
+ shouldInclude = pagePlaceholdersSet.has(key);
10929
+ }
10930
+ if (shouldInclude) {
10931
+ shareablePlaceholders[key] = value;
10932
+ }
10933
+ }
10934
+ return Object.keys(shareablePlaceholders).length > 0 ? { placeholders: shareablePlaceholders } : {};
10935
+ }
10936
+ /**
10937
+ * Computes a shareable state object from the current state.
10938
+ *
10939
+ * Toggles, tabs, and placeholders marked as `isLocal: true` are only included
10940
+ * if they are present on the current page.
10941
+ *
10942
+ * Global elements (not `isLocal`) are always included regardless of whether
10943
+ * they are detected on the current page.
10944
+ *
10945
+ * Every toggle known to the page (or global) is explicitly encoded as shown,
10946
+ * peeked, or hidden, so the recipient's view exactly matches the sender's
10947
+ * regardless of their local settings.
10948
+ *
10949
+ * Tab-group-derived placeholders are omitted — the `?tabs=` param is their source of truth.
10950
+ *
10951
+ * @param currentState The current application state.
10952
+ * @param elementsOnCurrentPage The active elements detected on the current page.
10953
+ * @param config The application configuration.
10954
+ */
10955
+ function computeShareableSettingState(currentState, elementsOnCurrentPage, config) {
10956
+ const pageTogglesSet = new Set(elementsOnCurrentPage.toggles);
10957
+ const pageTabGroupsSet = new Set(elementsOnCurrentPage.tabGroups);
10958
+ const pagePlaceholdersSet = new Set(elementsOnCurrentPage.placeholders);
10959
+ return {
10960
+ ...getShareableToggles(currentState, pageTogglesSet, config),
10961
+ ...getShareableTabs(currentState, pageTabGroupsSet, config),
10962
+ ...getShareablePlaceholders(currentState, pagePlaceholdersSet, config),
10963
+ };
10964
+ }
10965
+
10790
10966
  /**
10791
10967
  * Builds the query string fragment for the managed URL parameters from a state object.
10792
10968
  *
@@ -10797,7 +10973,7 @@
10797
10973
  * By building the string directly, each value is encoded exactly once,
10798
10974
  * and structural separators (`,` `:`) remain as literal characters in the URL.
10799
10975
  */
10800
- function buildManagedSearch(state) {
10976
+ function generateManagedQuery(state) {
10801
10977
  const parts = [];
10802
10978
  if (state.shownToggles && state.shownToggles.length > 0) {
10803
10979
  parts.push(`${PARAM_SHOW_TOGGLE}=${encodeList(state.shownToggles)}`);
@@ -10819,100 +10995,17 @@
10819
10995
  /**
10820
10996
  * Builds a full absolute URL string from the base URL and managed params.
10821
10997
  */
10822
- function buildFullUrl(url, managedSearch) {
10998
+ function buildFullUrl(url, managedQuery) {
10823
10999
  const preservedSearch = url.searchParams.toString();
10824
- const search = [preservedSearch, managedSearch].filter(Boolean).join('&');
11000
+ const search = [preservedSearch, managedQuery].filter(Boolean).join('&');
10825
11001
  return url.origin + url.pathname + (search ? `?${search}` : '') + (url.hash || '');
10826
11002
  }
10827
- /**
10828
- * Strips placeholder entries that should not appear in shareable URLs:
10829
- * - Tab-group-derived placeholders (source: 'tabgroup') — implied by ?tabs=
10830
- * - Adaptation-only placeholders (adaptationPlaceholder: true) — author-controlled, not shareable
10831
- */
10832
- function stripNonShareablePlaceholders(placeholders) {
10833
- const shareable = {};
10834
- for (const [key, value] of Object.entries(placeholders)) {
10835
- const definition = placeholderRegistryStore.get(key);
10836
- if (definition?.source === 'tabgroup')
10837
- continue; // implied by ?tabs=
10838
- if (definition?.adaptationPlaceholder)
10839
- continue; // author-only, not shareable
10840
- shareable[key] = value;
10841
- }
10842
- return shareable;
10843
- }
10844
- /**
10845
- * Computes a shareable state object from the current state.
10846
- *
10847
- * Every toggle known to the page is explicitly encoded as shown, peeked, or hidden,
10848
- * so the recipient's view exactly matches the sender's regardless of their local settings.
10849
- *
10850
- * Toggles, tabs, and placeholders NOT present on the current page are omitted,
10851
- * preventing cross-page state pollution.
10852
- *
10853
- * Tab-group-derived placeholders are omitted — the `?tabs=` param is their source of truth.
10854
- *
10855
- * @param currentState The current application state.
10856
- * @param pageElements The active elements detected on the current page.
10857
- */
10858
- function computeShareableState(currentState, pageElements) {
10859
- const currentShown = currentState.shownToggles ?? [];
10860
- const currentPeek = currentState.peekToggles ?? [];
10861
- const pageTogglesSet = new Set(pageElements.toggles);
10862
- const pageTabGroupsSet = new Set(pageElements.tabGroups);
10863
- const pagePlaceholdersSet = new Set(pageElements.placeholders);
10864
- // 1. Filter toggles to only those present on the page
10865
- const pageShown = currentShown.filter((id) => pageTogglesSet.has(id));
10866
- const pagePeek = currentPeek.filter((id) => pageTogglesSet.has(id));
10867
- const shownSet = new Set(pageShown);
10868
- const peekSet = new Set(pagePeek);
10869
- // Every toggle on the page that isn't shown or peeked must be explicitly hidden.
10870
- const absoluteHide = [];
10871
- for (const id of pageTogglesSet) {
10872
- if (!shownSet.has(id) && !peekSet.has(id)) {
10873
- absoluteHide.push(id);
10874
- }
10875
- }
10876
- const shareable = {};
10877
- if (pageShown.length > 0)
10878
- shareable.shownToggles = pageShown;
10879
- if (pagePeek.length > 0)
10880
- shareable.peekToggles = pagePeek;
10881
- if (absoluteHide.length > 0)
10882
- shareable.hiddenToggles = absoluteHide;
10883
- // 2. Filter tabs to only those present on the page
10884
- if (currentState.tabs) {
10885
- const pageTabs = {};
10886
- for (const [groupId, tabId] of Object.entries(currentState.tabs)) {
10887
- if (pageTabGroupsSet.has(groupId)) {
10888
- pageTabs[groupId] = tabId;
10889
- }
10890
- }
10891
- if (Object.keys(pageTabs).length > 0) {
10892
- shareable.tabs = pageTabs;
10893
- }
10894
- }
10895
- // 3. Filter placeholders to only those present on the page
10896
- if (currentState.placeholders) {
10897
- const shareablePlaceholders = stripNonShareablePlaceholders(currentState.placeholders);
10898
- const pagePlaceholders = {};
10899
- for (const [key, value] of Object.entries(shareablePlaceholders)) {
10900
- if (pagePlaceholdersSet.has(key)) {
10901
- pagePlaceholders[key] = value;
10902
- }
10903
- }
10904
- if (Object.keys(pagePlaceholders).length > 0) {
10905
- shareable.placeholders = pagePlaceholders;
10906
- }
10907
- }
10908
- return shareable;
10909
- }
10910
- // --- URL State Manager ---
11003
+
10911
11004
  /**
10912
11005
  * URL State Manager for CustardUI.
10913
11006
  * Handles encoding/decoding of view states as human-readable URL parameters.
10914
11007
  *
10915
- * URL Schema:
11008
+ * URL Schema (defined in url-constants.ts):
10916
11009
  * ?t-show=A,B — toggle IDs to explicitly show
10917
11010
  * ?t-peek=C — toggle IDs to explicitly peek
10918
11011
  * ?t-hide=D — toggle IDs to explicitly hide
@@ -10944,37 +11037,29 @@
10944
11037
  return null;
10945
11038
  const search = window.location.search;
10946
11039
  return {
10947
- ...parseTogglesFromSearch(search),
10948
- ...parseTabsFromSearch(search),
10949
- ...parsePlaceholdersFromSearch(search),
11040
+ ...parseTogglesFromURL(search),
11041
+ ...parseTabsFromURL(search),
11042
+ ...parsePlaceholdersFromURL(search),
10950
11043
  };
10951
11044
  }
10952
11045
  /**
10953
- * Generates a shareable URL that encodes the full current state.
10954
- *
10955
- * Encodes every toggle on the page explicitly (shown, peeked, or hidden)
10956
- * so the recipient sees the exact same view regardless of their local settings.
10957
- *
10958
- * Tab-group-derived placeholders are omitted from the URL — they are implied
10959
- * by the `?tabs=` parameter and will be re-derived by the recipient's store.
10960
- *
10961
- * Toggles, tabs, and placeholders NOT present on the current page are omitted,
10962
- * preventing cross-page state pollution.
11046
+ * Generates a shareable URL that encodes the current state based on Config and State.
10963
11047
  *
10964
11048
  * @param currentState The full application state to encode.
10965
- * @param pageElements The active elements detected on the current page.
11049
+ * @param config The application configuration.
11050
+ * @param elementsOnCurrentPage The active elements detected on the current page.
10966
11051
  */
10967
- static generateShareableURL(currentState, pageElements = { toggles: [], tabGroups: [], placeholders: [] }) {
11052
+ static generateShareableURL(currentState, config, elementsOnCurrentPage = { toggles: [], tabGroups: [], placeholders: [] }) {
10968
11053
  const url = new URL(window.location.href);
10969
11054
  for (const param of MANAGED_PARAMS) {
10970
11055
  url.searchParams.delete(param);
10971
11056
  }
10972
- let managedSearch = '';
11057
+ let managedQuery = '';
10973
11058
  if (currentState) {
10974
- const shareable = computeShareableState(currentState, pageElements);
10975
- managedSearch = buildManagedSearch(shareable);
11059
+ const shareable = computeShareableSettingState(currentState, elementsOnCurrentPage, config);
11060
+ managedQuery = generateManagedQuery(shareable);
10976
11061
  }
10977
- return buildFullUrl(url, managedSearch);
11062
+ return buildFullUrl(url, managedQuery);
10978
11063
  }
10979
11064
  /**
10980
11065
  * Clears all managed parameters from the current browser URL.
@@ -11176,19 +11261,19 @@
11176
11261
  append($$anchor, svg);
11177
11262
  }
11178
11263
 
11179
- var root_1$e = from_html(`<p class="description svelte-gwkhja"> </p>`);
11264
+ var root_1$f = from_html(`<p class="description svelte-gwkhja"> </p>`);
11180
11265
  var root_3$7 = from_html(`<span class="segment-icon svelte-gwkhja"><!></span>`);
11181
11266
  var root_2$a = from_html(`<button type="button"><!> <span class="segment-label svelte-gwkhja"> </span></button>`);
11182
11267
  var root$k = from_html(`<div class="card svelte-gwkhja"><div class="content svelte-gwkhja"><div><p class="title svelte-gwkhja"> </p> <!></div> <div class="segmented svelte-gwkhja" role="group" aria-label="Visibility"></div></div></div>`);
11183
11268
 
11184
- const $$css$m = {
11269
+ const $$css$n = {
11185
11270
  hash: 'svelte-gwkhja',
11186
11271
  code: '.card.svelte-gwkhja {background:var(--cv-bg);border:1px solid var(--cv-border);border-radius:var(--cv-card-radius, 0.5rem);transition:background 0.15s ease;}.card.svelte-gwkhja:hover {background:var(--cv-bg-hover);}.content.svelte-gwkhja {display:flex;align-items:center;justify-content:space-between;padding:0.75rem;}.title.svelte-gwkhja {font-weight:500;font-size:0.875rem;color:var(--cv-text);margin:0;}.description.svelte-gwkhja {font-size:0.75rem;color:var(--cv-text-secondary);margin:0.125rem 0 0 0;}.segmented.svelte-gwkhja {display:flex;border:1px solid var(--cv-border);border-radius:0.375rem;overflow:hidden;flex-shrink:0;}.segment-btn.svelte-gwkhja {display:flex;align-items:center;gap:0.25rem;background:transparent;border:none;border-left:1px solid var(--cv-border);padding:0.3rem 0.6rem;font-size:0.8rem;font-weight:500;color:var(--cv-text-secondary);cursor:pointer;transition:background 0.15s ease, color 0.15s ease;font-family:inherit;line-height:1;}.segment-icon.svelte-gwkhja {display:flex;align-items:center;justify-content:center;width:14px;height:14px;flex-shrink:0;}.segment-icon.svelte-gwkhja svg {width:100%;height:100%;}.segment-label.svelte-gwkhja {font-size:0.75rem;}.segment-btn.svelte-gwkhja:first-child {border-left:none;}.segment-btn.svelte-gwkhja:hover:not(.active) {background:var(--cv-bg-hover);color:var(--cv-text);}.segment-btn.active.svelte-gwkhja {background:var(--cv-primary);color:white;box-shadow:0 1px 3px rgba(0, 0, 0, 0.15);}'
11187
11272
  };
11188
11273
 
11189
11274
  function ToggleItem($$anchor, $$props) {
11190
11275
  push($$props, true);
11191
- append_styles$1($$anchor, $$css$m);
11276
+ append_styles$1($$anchor, $$css$n);
11192
11277
 
11193
11278
  let value = prop($$props, 'value', 15, 'show'),
11194
11279
  onchange = prop($$props, 'onchange', 3, () => {});
@@ -11206,7 +11291,7 @@
11206
11291
 
11207
11292
  {
11208
11293
  var consequent = ($$anchor) => {
11209
- var p_1 = root_1$e();
11294
+ var p_1 = root_1$f();
11210
11295
  var text_1 = child(p_1, true);
11211
11296
 
11212
11297
  reset(p_1);
@@ -11284,18 +11369,18 @@
11284
11369
 
11285
11370
  delegate(['click']);
11286
11371
 
11287
- var root_1$d = from_html(`<p class="description svelte-uub3h8"> </p>`);
11372
+ var root_1$e = from_html(`<p class="description svelte-uub3h8"> </p>`);
11288
11373
  var root_2$9 = from_html(`<option> </option>`);
11289
11374
  var root$j = from_html(`<div class="root svelte-uub3h8"><div class="header svelte-uub3h8"><label class="label svelte-uub3h8"> </label> <!></div> <select class="select svelte-uub3h8"></select></div>`);
11290
11375
 
11291
- const $$css$l = {
11376
+ const $$css$m = {
11292
11377
  hash: 'svelte-uub3h8',
11293
11378
  code: '.root.svelte-uub3h8 {display:flex;flex-direction:column;gap:0.5rem;padding:0.75rem;background:var(--cv-bg);border:1px solid var(--cv-border);border-radius:var(--cv-card-radius, 0.5rem);transition:background 0.15s ease;}.root.svelte-uub3h8:hover {background:var(--cv-bg-hover);}\n\n /* Remove special handling for last child since they are now separate cards */.root.svelte-uub3h8:last-child {border-bottom:1px solid var(--cv-border);}.header.svelte-uub3h8 {display:flex;flex-direction:column;gap:0.25rem;}.label.svelte-uub3h8 {font-size:0.875rem;color:var(--cv-text);margin:0;line-height:1.4;font-weight:500;display:block;cursor:pointer;}.description.svelte-uub3h8 {font-size:0.75rem;color:var(--cv-text-secondary);margin:0;line-height:1.4;}.select.svelte-uub3h8 {width:100%;border-radius:0.5rem;background:var(--cv-input-bg);border:1px solid var(--cv-input-border);color:var(--cv-text);padding:0.5rem 0.75rem;font-size:0.875rem;cursor:pointer;transition:all 0.15s ease;font-family:inherit;}.select.svelte-uub3h8:hover {border-color:var(--cv-text-secondary);}.select.svelte-uub3h8:focus {outline:none;border-color:var(--cv-primary);box-shadow:0 0 0 2px var(--cv-focus-ring);}'
11294
11379
  };
11295
11380
 
11296
11381
  function TabGroupItem($$anchor, $$props) {
11297
11382
  push($$props, true);
11298
- append_styles$1($$anchor, $$css$l);
11383
+ append_styles$1($$anchor, $$css$m);
11299
11384
 
11300
11385
  let activeTabId = prop($$props, 'activeTabId', 15, ''),
11301
11386
  onchange = prop($$props, 'onchange', 3, () => {});
@@ -11318,7 +11403,7 @@
11318
11403
 
11319
11404
  {
11320
11405
  var consequent = ($$anchor) => {
11321
- var p = root_1$d();
11406
+ var p = root_1$e();
11322
11407
  var text_1 = child(p, true);
11323
11408
 
11324
11409
  reset(p);
@@ -11384,14 +11469,14 @@
11384
11469
 
11385
11470
  var root$i = from_html(`<div class="placeholder-item svelte-1vp05mb"><label class="placeholder-label svelte-1vp05mb"> </label> <input class="placeholder-input svelte-1vp05mb" type="text"/></div>`);
11386
11471
 
11387
- const $$css$k = {
11472
+ const $$css$l = {
11388
11473
  hash: 'svelte-1vp05mb',
11389
11474
  code: '.placeholder-item.svelte-1vp05mb {display:flex;flex-direction:column;gap:0.25rem;}.placeholder-label.svelte-1vp05mb {font-size:0.85rem;font-weight:500;color:var(--cv-text);}.placeholder-input.svelte-1vp05mb {padding:0.5rem 0.75rem;border:1px solid var(--cv-input-border);border-radius:var(--cv-card-radius, 0.5rem);font-size:0.9rem;transition:border-color 0.2s;background:var(--cv-input-bg);color:var(--cv-text);}.placeholder-input.svelte-1vp05mb:focus {outline:none;border-color:var(--cv-primary);box-shadow:0 0 0 2px var(--cv-focus-ring);}'
11390
11475
  };
11391
11476
 
11392
11477
  function PlaceholderItem($$anchor, $$props) {
11393
11478
  push($$props, true);
11394
- append_styles$1($$anchor, $$css$k);
11479
+ append_styles$1($$anchor, $$css$l);
11395
11480
 
11396
11481
  let value = prop($$props, 'value', 15, ''),
11397
11482
  onchange = prop($$props, 'onchange', 3, () => {});
@@ -11471,7 +11556,7 @@
11471
11556
  }
11472
11557
  }
11473
11558
 
11474
- var root_1$c = from_html(`<button type="button">Customize</button>`);
11559
+ var root_1$d = from_html(`<button type="button">Customize</button>`);
11475
11560
  var root_3$6 = from_html(`<p class="description svelte-16uy9h6"> </p>`);
11476
11561
  var root_5$2 = from_html(`<div class="section svelte-16uy9h6"><div class="section-heading svelte-16uy9h6">Toggles</div> <div class="toggles-container svelte-16uy9h6"></div></div>`);
11477
11562
  var root_7$2 = from_html(`<div class="section svelte-16uy9h6"><div class="section-heading svelte-16uy9h6">Placeholders</div> <div class="placeholders-container svelte-16uy9h6"></div></div>`);
@@ -11487,14 +11572,14 @@
11487
11572
  var root_22 = from_html(`<div></div>`);
11488
11573
  var root$h = from_html(`<div class="modal-overlay svelte-16uy9h6" role="presentation"><div class="modal-box cv-custom-state-modal svelte-16uy9h6" role="dialog" aria-modal="true"><header class="header svelte-16uy9h6"><div class="header-content svelte-16uy9h6"><div class="modal-icon svelte-16uy9h6"><!></div> <div class="title svelte-16uy9h6"> </div></div> <button type="button" class="close-btn svelte-16uy9h6" aria-label="Close modal"><!></button></header> <main class="main svelte-16uy9h6"><div class="tabs svelte-16uy9h6"><!> <button type="button">Share</button></div> <!></main> <footer class="footer svelte-16uy9h6"><!> <div class="footer-attribution svelte-16uy9h6"><span class="footer-tagline svelte-16uy9h6">Browser-side page customisations provided by</span> <a href="https://custardui.js.org" target="_blank" rel="noopener noreferrer" class="footer-link svelte-16uy9h6">custardui.js.org</a></div> <button type="button" class="done-btn svelte-16uy9h6">Done</button></footer></div></div>`);
11489
11574
 
11490
- const $$css$j = {
11575
+ const $$css$k = {
11491
11576
  hash: 'svelte-16uy9h6',
11492
11577
  code: '\n /* Modal Overlay & Modal Frame */.modal-overlay.svelte-16uy9h6 {position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0, 0, 0, 0.5);display:flex;align-items:center;justify-content:center;z-index:10002;}.modal-box.svelte-16uy9h6 {background:var(--cv-bg);border-radius:var(--cv-modal-radius, 0.75rem);box-shadow:0 25px 50px -12px var(--cv-shadow);max-width:32rem;width:90vw;max-height:80vh;display:flex;flex-direction:column;}.header.svelte-16uy9h6 {display:flex;align-items:center;justify-content:space-between;padding:0.5rem 1rem;border-bottom:1px solid var(--cv-border);}.header-content.svelte-16uy9h6 {display:flex;align-items:center;gap:0.75rem;}.modal-icon.svelte-16uy9h6 {position:relative;width:1rem;height:1rem;display:flex;align-items:center;justify-content:center;border-radius:9999px;color:var(--cv-text);}.modal-icon.svelte-16uy9h6 svg {fill:currentColor;}.title.svelte-16uy9h6 {font-size:1.125rem;font-weight:bold;color:var(--cv-text);margin:0;}.close-btn.svelte-16uy9h6 {width:2rem;height:2rem;display:flex;align-items:center;justify-content:center;border-radius:9999px;background:transparent;border:none;color:var(--cv-text-secondary);cursor:pointer;transition:all 0.2s ease;}.close-btn.svelte-16uy9h6:hover {background:rgba(62, 132, 244, 0.1);color:var(--cv-primary);}.main.svelte-16uy9h6 {padding:1rem;flex:1;display:flex;flex-direction:column;overflow-y:auto;max-height:calc(80vh - 8rem);min-height:var(--cv-modal-min-height, 20rem);}.description.svelte-16uy9h6 {font-size:0.875rem;color:var(--cv-text);margin:0 0 1rem 0;line-height:1.4;}\n\n /* Tabs */.tabs.svelte-16uy9h6 {display:flex;margin-bottom:1rem;border-bottom:2px solid var(--cv-border);}.tab.svelte-16uy9h6 {background:transparent;border:none;padding:0.5rem 1rem;font-size:0.9rem;font-weight:600;color:var(--cv-text-secondary);cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-2px;}.tab.active.svelte-16uy9h6 {color:var(--cv-primary);border-bottom-color:var(--cv-primary);}.tab-content.svelte-16uy9h6 {display:none;}.tab-content.active.svelte-16uy9h6 {display:block;}\n\n /* Section Styling */.section.svelte-16uy9h6 {display:flex;flex-direction:column;gap:0.75rem;margin-bottom:1.5rem;}.section-heading.svelte-16uy9h6 {font-size:0.7rem;font-weight:600;color:var(--cv-text-secondary);text-transform:var(--cv-section-label-transform, uppercase);letter-spacing:0.08em;margin:0;}.toggles-container.svelte-16uy9h6 {display:flex;flex-direction:column;gap:0.5rem;overflow:hidden;}\n\n /* Tab Groups Section specific */.tabgroups-container.svelte-16uy9h6 {border-radius:0.5rem;}\n\n /* Nav Toggle Card */.tabgroup-card.svelte-16uy9h6 {background:var(--cv-bg);border-bottom:1px solid var(--cv-border);}.tabgroup-card.header-card.svelte-16uy9h6 {display:flex;align-items:center;justify-content:space-between;padding:0.75rem;border:1px solid var(--cv-border);border-radius:0.5rem;margin-bottom:0.75rem;}.tabgroup-row.svelte-16uy9h6 {display:flex;align-items:center;justify-content:space-between;width:100%;gap:1rem;}.logo-box.svelte-16uy9h6 {width:3rem;height:3rem;background:var(--cv-modal-icon-bg);border-radius:0.5rem;display:flex;align-items:center;justify-content:center;flex-shrink:0;}.nav-icon.svelte-16uy9h6 {width:2rem;height:2rem;color:var(--cv-text);display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:color 0.2s ease;}.tabgroup-info.svelte-16uy9h6 {flex:1;display:flex;flex-direction:column;gap:0.25rem;}.tabgroup-title.svelte-16uy9h6 {font-weight:500;font-size:0.875rem;color:var(--cv-text);margin:0 0 0 0;}.tabgroup-description.svelte-16uy9h6 {font-size:0.75rem;color:var(--cv-text-secondary);margin:0;line-height:1.3;}\n\n /* Toggle Switch */.toggle-switch.svelte-16uy9h6 {position:relative;display:inline-flex;align-items:center;width:44px;height:24px;background:var(--cv-switch-bg);border-radius:9999px;padding:2px;box-sizing:border-box;cursor:pointer;transition:background-color 0.2s ease;border:none;}.toggle-switch.svelte-16uy9h6 input:where(.svelte-16uy9h6) {display:none;}.toggle-switch.svelte-16uy9h6 .switch-bg:where(.svelte-16uy9h6) {position:absolute;inset:0;border-radius:9999px;background:var(--cv-switch-bg);transition:background-color 0.2s ease;pointer-events:none;}.toggle-switch.svelte-16uy9h6 .switch-knob:where(.svelte-16uy9h6) {position:relative;width:20px;height:20px;background:var(--cv-switch-knob);border-radius:50%;box-shadow:0 1px 2px rgba(0, 0, 0, 0.1);transition:transform 0.2s ease;transform:translateX(0);}.toggle-switch.svelte-16uy9h6 input:where(.svelte-16uy9h6):checked ~ .switch-knob:where(.svelte-16uy9h6) {transform:translateX(20px);}.toggle-switch.svelte-16uy9h6 input:where(.svelte-16uy9h6):checked ~ .switch-bg:where(.svelte-16uy9h6) {background:var(--cv-primary);}\n\n /* Tab Groups List */.tab-groups-list.svelte-16uy9h6 {display:flex;flex-direction:column;gap:0.75rem;}\n\n /* Footer */.footer.svelte-16uy9h6 {padding:0.75rem 1rem;border-top:1px solid var(--cv-border);display:flex;align-items:center;justify-content:space-between;background:var(--cv-bg);border-bottom-left-radius:var(--cv-modal-radius, 0.75rem);border-bottom-right-radius:var(--cv-modal-radius, 0.75rem);}.footer-attribution.svelte-16uy9h6 {display:flex;flex-direction:column;align-items:center;text-align:center;gap:0.1rem;opacity:0.5;transition:opacity 0.15s ease;}.footer-attribution.svelte-16uy9h6:hover {opacity:1;}.footer-tagline.svelte-16uy9h6 {font-size:0.6rem;color:var(--cv-text-secondary);letter-spacing:0.04em;}.footer-link.svelte-16uy9h6 {color:var(--cv-text-secondary);text-decoration:none;font-size:0.68rem;font-weight:600;letter-spacing:0.08em;transition:color 0.15s ease;}.footer-link.svelte-16uy9h6:hover {color:var(--cv-primary);}.reset-btn.svelte-16uy9h6 {display:flex;align-items:center;gap:0.4rem;background:transparent;border:none;font-size:0.875rem;font-weight:500;color:var(--cv-text-secondary);cursor:pointer;padding:0.4rem 0.5rem;border-radius:0.5rem;transition:all 0.2s ease;}.reset-btn.svelte-16uy9h6:hover {background:var(--cv-danger-bg);color:var(--cv-danger);}.done-btn.svelte-16uy9h6 {background:var(--cv-primary);color:white;border:none;padding:0.5rem 1.1rem;border-radius:0.5rem;font-weight:600;font-size:0.875rem;cursor:pointer;box-shadow:0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.08);transition:background-color 0.15s ease, box-shadow 0.15s ease;}.done-btn.svelte-16uy9h6:hover {background:var(--cv-primary-hover);box-shadow:0 3px 6px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.08);}\n\n /* Share Tab Styles */.share-content.svelte-16uy9h6 {display:flex;flex-direction:column;gap:1rem;align-items:center;text-align:center;padding:1rem 0;}.share-instruction.svelte-16uy9h6 {font-size:0.95rem;color:var(--cv-text-secondary);margin-bottom:0.5rem;}.share-action-btn.svelte-16uy9h6 {display:flex;align-items:center;justify-content:center;gap:0.75rem;width:100%;max-width:320px;padding:0.75rem 1rem;border-radius:0.5rem;font-weight:500;font-size:0.95rem;cursor:pointer;transition:all 0.2s ease;border:1px solid var(--cv-border);background:var(--cv-bg);color:var(--cv-text);}.share-action-btn.svelte-16uy9h6:hover {border-color:var(--cv-primary);color:var(--cv-primary);background:var(--cv-bg-hover);}.share-action-btn.primary.svelte-16uy9h6 {background:var(--cv-primary);border-color:var(--cv-primary);color:white;}.share-action-btn.primary.svelte-16uy9h6:hover {background:var(--cv-primary-hover);border-color:var(--cv-primary-hover);}.btn-icon.svelte-16uy9h6 {display:flex;align-items:center;justify-content:center;width:1.25rem;height:1.25rem;}\n\n /* Placeholder Inputs */.placeholders-container.svelte-16uy9h6 {display:flex;flex-direction:column;gap:0.75rem;}'
11493
11578
  };
11494
11579
 
11495
11580
  function Modal($$anchor, $$props) {
11496
11581
  push($$props, true);
11497
- append_styles$1($$anchor, $$css$j);
11582
+ append_styles$1($$anchor, $$css$k);
11498
11583
 
11499
11584
  /* eslint-disable @typescript-eslint/no-explicit-any */
11500
11585
  let onclose = prop($$props, 'onclose', 3, () => {}),
@@ -11593,7 +11678,7 @@
11593
11678
  }
11594
11679
 
11595
11680
  async function copyShareUrl() {
11596
- const url = URLStateManager.generateShareableURL(activeStateStore.state, {
11681
+ const url = URLStateManager.generateShareableURL(activeStateStore.state, activeStateStore.config, {
11597
11682
  toggles: elementStore.detectedToggles,
11598
11683
  tabGroups: elementStore.detectedTabGroups,
11599
11684
  placeholders: elementStore.detectedPlaceholders
@@ -11663,7 +11748,7 @@
11663
11748
 
11664
11749
  {
11665
11750
  var consequent = ($$anchor) => {
11666
- var button_1 = root_1$c();
11751
+ var button_1 = root_1$d();
11667
11752
 
11668
11753
  button_1.__click = () => set(activeTab, 'customize');
11669
11754
  template_effect(() => set_class(button_1, 1, `tab ${get(activeTab) === 'customize' ? 'active' : ''}`, 'svelte-16uy9h6'));
@@ -12128,33 +12213,45 @@
12128
12213
  return getStableTextContent(el).trim().replace(/\s+/g, ' ');
12129
12214
  }
12130
12215
 
12216
+ /**
12217
+ * Walks the ancestor chain of `el` to find the first element with a non-empty
12218
+ * `id` attribute. Returns both the id string and the ancestor element so the
12219
+ * caller can use the element itself as a query scope.
12220
+ * Returns `ancestorEl: null` when no id-bearing ancestor exists.
12221
+ */
12222
+ function findNearestIdAncestor(el) {
12223
+ let node = el.parentElement;
12224
+ while (node) {
12225
+ if (node.id)
12226
+ return { parentId: node.id, ancestorEl: node };
12227
+ node = node.parentElement;
12228
+ }
12229
+ return { parentId: undefined, ancestorEl: null };
12230
+ }
12131
12231
  /**
12132
12232
  * Creates an AnchorDescriptor for a given DOM element.
12133
12233
  */
12134
12234
  function createDescriptor(el) {
12135
12235
  const tag = el.tagName;
12136
12236
  const normalizedText = getStableNormalizedText(el);
12137
- // Find nearest parent with an ID
12138
- let parentId;
12139
- let parent = el.parentElement;
12140
- while (parent) {
12141
- if (parent.id) {
12142
- parentId = parent.id;
12143
- break;
12144
- }
12145
- parent = parent.parentElement;
12146
- }
12147
- // Calculate index relative to the container
12148
- const container = parent || document.body;
12237
+ const { parentId, ancestorEl: idAncestor } = findNearestIdAncestor(el);
12238
+ const container = idAncestor ?? document.body;
12149
12239
  const siblings = Array.from(container.querySelectorAll(tag));
12150
- const index = siblings.indexOf(el);
12240
+ const rawIndex = siblings.indexOf(el);
12241
+ if (rawIndex === -1) {
12242
+ console.error('[CustardUI] createDescriptor: element not found in container, ' +
12243
+ 'element may be detached from the DOM. Please open an issue.', el);
12244
+ }
12245
+ const index = rawIndex !== -1 ? rawIndex : 0;
12151
12246
  const descriptor = {
12152
12247
  tag,
12153
- index: index !== -1 ? index : 0,
12248
+ index,
12154
12249
  textSnippet: normalizedText.substring(0, 32),
12155
12250
  textHash: hashCode(normalizedText),
12156
- elementId: el.id,
12157
12251
  };
12252
+ if (el.id) {
12253
+ descriptor.elementId = el.id;
12254
+ }
12158
12255
  if (parentId) {
12159
12256
  descriptor.parentId = parentId;
12160
12257
  }
@@ -12356,7 +12453,7 @@
12356
12453
  * Returns an array of elements. For specific descriptors, usually contains 0 or 1 element.
12357
12454
  * For ID-only descriptors (tag='ANY'), may return multiple if duplicates exist.
12358
12455
  */
12359
- function resolve(root, descriptor) {
12456
+ function resolve(descriptor) {
12360
12457
  // 0. Direct ID Shortcut
12361
12458
  if (descriptor.elementId) {
12362
12459
  // Always support duplicate IDs for consistency, even if technically invalid HTML.
@@ -12368,19 +12465,15 @@
12368
12465
  return [];
12369
12466
  }
12370
12467
  // 1. Determine Scope
12371
- let scope = root;
12372
- // Optimization: If parentId exists, try to narrow scope immediately
12468
+ // Default to document.body so index pool matches descriptor.ts,
12469
+ // which also falls back to document.body when no id-bearing ancestor is found.
12470
+ let scope = document.body;
12471
+ // Optimization: If parentId exists, try to narrow scope immediately.
12373
12472
  if (descriptor.parentId) {
12374
- const foundParent = root.querySelector(`#${descriptor.parentId}`);
12473
+ const foundParent = document.getElementById(descriptor.parentId);
12375
12474
  if (foundParent instanceof HTMLElement) {
12376
12475
  scope = foundParent;
12377
12476
  }
12378
- else {
12379
- const globalParent = document.getElementById(descriptor.parentId);
12380
- if (globalParent) {
12381
- scope = globalParent;
12382
- }
12383
- }
12384
12477
  }
12385
12478
  // 2. Candidate Search & Scoring
12386
12479
  const candidates = scope.querySelectorAll(descriptor.tag);
@@ -12963,23 +13056,23 @@
12963
13056
  return zoom;
12964
13057
  }
12965
13058
 
12966
- var root_1$b = from_html(`<div role="alert" aria-live="polite"> </div>`);
13059
+ var root_1$c = from_html(`<div role="alert" aria-live="polite"> </div>`);
12967
13060
  var root$g = from_html(`<div class="toast-container svelte-14irt8g"></div>`);
12968
13061
 
12969
- const $$css$i = {
13062
+ const $$css$j = {
12970
13063
  hash: 'svelte-14irt8g',
12971
13064
  code: '.toast-container.svelte-14irt8g {position:fixed;top:20px;left:50%;transform:translateX(-50%);z-index:20000;display:flex;flex-direction:column;align-items:center;gap:10px;pointer-events:none; /* Let clicks pass through container */}.toast-item.svelte-14irt8g {background:rgba(0, 0, 0, 0.85);color:white;padding:10px 20px;border-radius:4px;font-size:14px;box-shadow:0 4px 6px rgba(0, 0, 0, 0.1);pointer-events:auto; /* Re-enable clicks on toasts */max-width:300px;text-align:center;}'
12972
13065
  };
12973
13066
 
12974
13067
  function Toast($$anchor, $$props) {
12975
13068
  push($$props, false);
12976
- append_styles$1($$anchor, $$css$i);
13069
+ append_styles$1($$anchor, $$css$j);
12977
13070
  init();
12978
13071
 
12979
13072
  var div = root$g();
12980
13073
 
12981
13074
  each(div, 13, () => toast.items, (t) => t.id, ($$anchor, t) => {
12982
- var div_1 = root_1$b();
13075
+ var div_1 = root_1$c();
12983
13076
  var text = child(div_1, true);
12984
13077
 
12985
13078
  reset(div_1);
@@ -13002,14 +13095,14 @@
13002
13095
 
13003
13096
  var root$f = from_html(`<div class="floating-bar svelte-bs8cbd"><div class="mode-toggle svelte-bs8cbd"><button type="button" title="Highlight selected elements">Highlight</button> <button type="button" title="Show only selected elements">Show</button> <button type="button" title="Hide selected elements">Hide</button></div> <span class="divider svelte-bs8cbd"></span> <span class="count svelte-bs8cbd"> </span> <button type="button" class="btn clear svelte-bs8cbd">Clear</button> <button type="button" class="btn preview svelte-bs8cbd">Preview</button> <button type="button" class="btn generate svelte-bs8cbd">Copy Link</button> <button type="button" class="btn exit svelte-bs8cbd">Exit</button></div>`);
13004
13097
 
13005
- const $$css$h = {
13098
+ const $$css$i = {
13006
13099
  hash: 'svelte-bs8cbd',
13007
13100
  code: '.floating-bar.svelte-bs8cbd {position:fixed;bottom:20px;left:50%;transform:translateX(-50%);background-color:#2c2c2c;color:#f1f1f1;border-radius:8px;padding:8px 12px;box-shadow:0 8px 20px rgba(0, 0, 0, 0.4);display:grid;grid-template-columns:auto auto 1fr auto auto auto auto;align-items:center;gap:12px;z-index:99999;font-family:system-ui,\n -apple-system,\n sans-serif;font-size:14px;border:1px solid #4a4a4a;pointer-events:auto;white-space:nowrap;min-width:500px;}.mode-toggle.svelte-bs8cbd {display:flex;background:#1a1a1a;border-radius:6px;padding:2px;border:1px solid #4a4a4a;}.mode-btn.svelte-bs8cbd {background:transparent;color:#aeaeae;border:none;padding:4px 10px;border-radius:4px;cursor:pointer;font-weight:500;font-size:13px;transition:all 0.2s;}.mode-btn.svelte-bs8cbd:hover {color:#fff;}.mode-btn.active.svelte-bs8cbd {background:#4a4a4a;color:#fff;box-shadow:0 1px 3px rgba(0, 0, 0, 0.2);}.divider.svelte-bs8cbd {width:1px;height:20px;background:#4a4a4a;margin:0 4px;}.count.svelte-bs8cbd {font-weight:500;min-width:120px;text-align:center;font-size:13px;color:#ccc;}.btn.svelte-bs8cbd {background-color:#0078d4;color:white;border:none;padding:6px 12px;border-radius:5px;cursor:pointer;font-weight:500;transition:background-color 0.2s;font-size:13px;}.btn.svelte-bs8cbd:hover {background-color:#005a9e;}.btn.clear.svelte-bs8cbd {background-color:transparent;border:1px solid #5a5a5a;color:#dadada;}.btn.clear.svelte-bs8cbd:hover {background-color:#3a3a3a;color:white;}.btn.preview.svelte-bs8cbd {background-color:#333;border:1px solid #555;}.btn.preview.svelte-bs8cbd:hover {background-color:#444;}.btn.exit.svelte-bs8cbd {background-color:transparent;color:#ff6b6b;padding:6px 10px;}.btn.exit.svelte-bs8cbd:hover {background-color:rgba(255, 107, 107, 0.1);}\n\n @media (max-width: 600px) {.floating-bar.svelte-bs8cbd {display:flex;flex-wrap:wrap;min-width:unset;width:90%;max-width:400px;height:auto;padding:12px;gap:10px;bottom:30px;}.mode-toggle.svelte-bs8cbd {margin-right:auto;order:1;}.btn.exit.svelte-bs8cbd {margin-left:auto;order:2;}.divider.svelte-bs8cbd {display:none;}.count.svelte-bs8cbd {width:100%;text-align:center;order:3;padding:8px 0;border-top:1px solid #3a3a3a;border-bottom:1px solid #3a3a3a;margin:4px 0;}.btn.clear.svelte-bs8cbd,\n .btn.preview.svelte-bs8cbd,\n .btn.generate.svelte-bs8cbd {flex:1;text-align:center;font-size:12px;padding:8px 4px;order:4;}.btn.generate.svelte-bs8cbd {flex:1.5;}\n }'
13008
13101
  };
13009
13102
 
13010
13103
  function ShareToolbar($$anchor, $$props) {
13011
13104
  push($$props, false);
13012
- append_styles$1($$anchor, $$css$h);
13105
+ append_styles$1($$anchor, $$css$i);
13013
13106
 
13014
13107
  function handleClear() {
13015
13108
  shareStore.clearAllSelections();
@@ -13089,16 +13182,16 @@
13089
13182
 
13090
13183
  var root_2$7 = from_html(`<span class="id-badge svelte-64gpkh" title="ID detection active"> </span>`);
13091
13184
  var root_3$5 = from_html(`<button type="button" class="action-btn up svelte-64gpkh" title="Select Parent">↰</button>`);
13092
- var root_1$a = from_html(`<div class="hover-helper svelte-64gpkh"><div class="info svelte-64gpkh"><span class="tag svelte-64gpkh"> </span> <!></div> <button type="button"> </button> <!></div>`);
13185
+ var root_1$b = from_html(`<div class="hover-helper svelte-64gpkh"><div class="info svelte-64gpkh"><span class="tag svelte-64gpkh"> </span> <!></div> <button type="button"> </button> <!></div>`);
13093
13186
 
13094
- const $$css$g = {
13187
+ const $$css$h = {
13095
13188
  hash: 'svelte-64gpkh',
13096
13189
  code: '.hover-helper.svelte-64gpkh {position:fixed;z-index:99999;background-color:#333;color:white;padding:4px 8px;border-radius:4px;display:flex;align-items:center;gap:8px;box-shadow:0 2px 5px rgba(0, 0, 0, 0.2);font-family:monospace;pointer-events:auto;}.info.svelte-64gpkh {display:flex;flex-direction:column;align-items:flex-start;line-height:1;gap:2px;}.tag.svelte-64gpkh {font-size:12px;font-weight:bold;color:#aeaeae;}.id-badge.svelte-64gpkh {font-size:10px;color:#64d2ff;max-width:100px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}.action-btn.svelte-64gpkh {background:#555;border:none;color:white;border-radius:3px;cursor:pointer;padding:2px 6px;font-size:14px;line-height:1;display:flex;align-items:center;justify-content:center;transition:background-color 0.1s;}.action-btn.svelte-64gpkh:hover {background:#777;}.action-btn.deselect.svelte-64gpkh {background-color:#d13438;}.action-btn.deselect.svelte-64gpkh:hover {background-color:#a42628;}'
13097
13190
  };
13098
13191
 
13099
13192
  function HoverHelper($$anchor, $$props) {
13100
13193
  push($$props, true);
13101
- append_styles$1($$anchor, $$css$g);
13194
+ append_styles$1($$anchor, $$css$h);
13102
13195
 
13103
13196
  // Derived state for easier access
13104
13197
  let target = user_derived(() => shareStore.currentHoverTarget);
@@ -13197,7 +13290,7 @@
13197
13290
 
13198
13291
  {
13199
13292
  var consequent_2 = ($$anchor) => {
13200
- var div = root_1$a();
13293
+ var div = root_1$b();
13201
13294
  var div_1 = child(div);
13202
13295
  var span = child(div_1);
13203
13296
  var text = child(span, true);
@@ -13273,17 +13366,17 @@
13273
13366
  delegate(['click']);
13274
13367
 
13275
13368
  var root_2$6 = from_html(`<button type="button"></button>`);
13276
- var root_1$9 = from_html(`<div class="cv-color-swatches svelte-1r78n4c" role="none"></div>`);
13369
+ var root_1$a = from_html(`<div class="cv-color-swatches svelte-1r78n4c" role="none"></div>`);
13277
13370
  var root$e = from_html(`<div class="cv-color-picker svelte-1r78n4c" role="none"><button type="button" class="cv-color-trigger svelte-1r78n4c" title="Choose highlight color" aria-label="Choose highlight color"><span class="cv-color-dot svelte-1r78n4c"></span></button> <!></div>`);
13278
13371
 
13279
- const $$css$f = {
13372
+ const $$css$g = {
13280
13373
  hash: 'svelte-1r78n4c',
13281
13374
  code: '.cv-color-picker.svelte-1r78n4c {position:fixed;transform:translateX(-50%) translateY(-100%);display:flex;flex-direction:column;align-items:center;gap:4px;pointer-events:auto;z-index:9500;\n /* Nudge down so the trigger peeks above the element edge */margin-top:8px;}.cv-color-trigger.svelte-1r78n4c {width:22px;height:16px;border-radius:100px;border:1.5px solid rgba(0, 0, 0, 0.18);background:white;cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0;box-shadow:0 2px 8px rgba(0, 0, 0, 0.15);transition:box-shadow 0.15s;}.cv-color-trigger.svelte-1r78n4c:hover {box-shadow:0 3px 12px rgba(0, 0, 0, 0.22);}.cv-color-dot.svelte-1r78n4c {width:10px;height:10px;border-radius:50%;display:block;border:1px solid rgba(0, 0, 0, 0.12);}.cv-color-swatches.svelte-1r78n4c {display:flex;flex-direction:row;gap:4px;background:white;border-radius:100px;padding:4px 6px;box-shadow:0 4px 16px rgba(0, 0, 0, 0.18);border:1px solid rgba(0, 0, 0, 0.1);}.cv-color-swatch.svelte-1r78n4c {width:16px;height:16px;border-radius:50%;border:2px solid transparent;cursor:pointer;padding:0;transition:transform 0.1s, border-color 0.1s;}.cv-color-swatch.svelte-1r78n4c:hover {transform:scale(1.2);border-color:rgba(0, 0, 0, 0.3);}.cv-color-swatch.active.svelte-1r78n4c {border-color:rgba(0, 0, 0, 0.5);transform:scale(1.15);}'
13282
13375
  };
13283
13376
 
13284
13377
  function HighlightColorPicker($$anchor, $$props) {
13285
13378
  push($$props, true);
13286
- append_styles$1($$anchor, $$css$f);
13379
+ append_styles$1($$anchor, $$css$g);
13287
13380
 
13288
13381
  let isExpanded = state(false);
13289
13382
  let rect = state(proxy({ top: 0, left: 0, width: 0 }));
@@ -13357,7 +13450,7 @@
13357
13450
 
13358
13451
  {
13359
13452
  var consequent = ($$anchor) => {
13360
- var div_1 = root_1$9();
13453
+ var div_1 = root_1$a();
13361
13454
 
13362
13455
  each(div_1, 21, () => HIGHLIGHT_COLORS, index, ($$anchor, color) => {
13363
13456
  var button_1 = root_2$6();
@@ -13400,20 +13493,20 @@
13400
13493
 
13401
13494
  delegate(['click', 'dblclick']);
13402
13495
 
13403
- var root_1$8 = from_html(`<span class="cv-annotation-tab-preview svelte-1r1spmr"> </span>`);
13496
+ var root_1$9 = from_html(`<span class="cv-annotation-tab-preview svelte-1r1spmr"> </span>`);
13404
13497
  var root_2$5 = from_html(`<span class="cv-annotation-tab-icon svelte-1r1spmr"> </span>`);
13405
13498
  var root_4$2 = from_html(`<button type="button"> </button>`);
13406
13499
  var root_3$4 = from_html(`<div class="cv-annotation-panel svelte-1r1spmr" role="none"><textarea class="cv-annotation-textarea svelte-1r1spmr" placeholder="Add a note…" rows="3"></textarea> <div class="cv-annotation-footer svelte-1r1spmr"><div class="cv-corner-selector svelte-1r1spmr" role="group" aria-label="Anchor corner"></div> <span class="cv-char-counter svelte-1r1spmr"> </span></div></div>`);
13407
13500
  var root$d = from_html(`<div class="cv-annotation-editor svelte-1r1spmr" role="none"><button type="button" aria-label="Annotation"><!></button> <!></div>`);
13408
13501
 
13409
- const $$css$e = {
13502
+ const $$css$f = {
13410
13503
  hash: 'svelte-1r1spmr',
13411
13504
  code: '.cv-annotation-editor.svelte-1r1spmr {position:fixed;z-index:9400;pointer-events:auto;display:flex;flex-direction:column;align-items:flex-start;gap:2px;}.cv-annotation-tab.svelte-1r1spmr {height:20px;padding:0 8px;border-radius:100px;border:1.5px solid rgba(0, 0, 0, 0.18);background:white;cursor:pointer;display:flex;align-items:center;gap:4px;box-shadow:0 2px 8px rgba(0, 0, 0, 0.15);transition:box-shadow 0.15s;max-width:160px;overflow:hidden;}.cv-annotation-tab.svelte-1r1spmr:hover {box-shadow:0 3px 12px rgba(0, 0, 0, 0.22);}.cv-annotation-tab--has-text.svelte-1r1spmr {background:#fffbe6;border-color:rgba(180, 83, 9, 0.4);}.cv-annotation-tab-icon.svelte-1r1spmr {font-size:10px;line-height:1;color:#6b7280;}.cv-annotation-tab-preview.svelte-1r1spmr {font-size:9px;font-weight:600;color:#1a1a1a;font-family:ui-sans-serif, system-ui, sans-serif;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:130px;}.cv-annotation-panel.svelte-1r1spmr {background:white;border-radius:8px;border:1px solid rgba(0, 0, 0, 0.12);box-shadow:0 4px 16px rgba(0, 0, 0, 0.18);padding:8px;width:220px;display:flex;flex-direction:column;gap:6px;}.cv-annotation-textarea.svelte-1r1spmr {width:100%;box-sizing:border-box;resize:vertical;border:1px solid rgba(0, 0, 0, 0.15);border-radius:4px;padding:5px 7px;font-size:11px;font-family:ui-sans-serif, system-ui, sans-serif;color:#1a1a1a;line-height:1.5;outline:none;min-height:56px;}.cv-annotation-textarea.svelte-1r1spmr:focus {border-color:#b45309;box-shadow:0 0 0 2px rgba(180, 83, 9, 0.15);}.cv-annotation-footer.svelte-1r1spmr {display:flex;align-items:center;justify-content:space-between;}.cv-corner-selector.svelte-1r1spmr {display:flex;gap:2px;}.cv-corner-btn.svelte-1r1spmr {width:20px;height:20px;border-radius:4px;border:1px solid rgba(0, 0, 0, 0.12);background:white;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:10px;color:#6b7280;padding:0;transition:background 0.1s, border-color 0.1s;}.cv-corner-btn.svelte-1r1spmr:hover {background:#fef3c7;border-color:#b45309;color:#1a1a1a;}.cv-corner-btn.active.svelte-1r1spmr {background:#fef3c7;border-color:#b45309;color:#92400e;font-weight:700;}.cv-char-counter.svelte-1r1spmr {font-size:9px;color:#9ca3af;font-family:ui-sans-serif, system-ui, sans-serif;font-variant-numeric:tabular-nums;}'
13412
13505
  };
13413
13506
 
13414
13507
  function HighlightAnnotationEditor($$anchor, $$props) {
13415
13508
  push($$props, true);
13416
- append_styles$1($$anchor, $$css$e);
13509
+ append_styles$1($$anchor, $$css$f);
13417
13510
 
13418
13511
  let isExpanded = state(false);
13419
13512
  let rect = state(proxy({ top: 0, left: 0, width: 0, height: 0, bottom: 0, right: 0 }));
@@ -13500,7 +13593,7 @@
13500
13593
 
13501
13594
  {
13502
13595
  var consequent = ($$anchor) => {
13503
- var span = root_1$8();
13596
+ var span = root_1$9();
13504
13597
  var text = child(span, true);
13505
13598
 
13506
13599
  reset(span);
@@ -13605,14 +13698,14 @@
13605
13698
  var root_3$3 = from_html(`<div><span class="selection-label svelte-1dbf58w"> </span></div>`);
13606
13699
  var root$c = from_html(`<div class="share-overlay-ui"><!> <!> <!> <!></div>`);
13607
13700
 
13608
- const $$css$d = {
13701
+ const $$css$e = {
13609
13702
  hash: 'svelte-1dbf58w',
13610
13703
  code: '\n /* Global styles injected when active */body.cv-share-active {cursor:default;user-select:none;-webkit-user-select:none;}\n\n /* Highlight outlines */.cv-highlight-target {outline:2px dashed #0078d4 !important;outline-offset:2px;cursor:crosshair;}.cv-share-selected {outline:3px solid #005a9e !important;outline-offset:2px;background-color:rgba(0, 120, 212, 0.05);}.cv-highlight-target-hide {outline:2px dashed #d13438 !important;outline-offset:2px;cursor:crosshair;}.cv-share-selected-hide {outline:3px solid #a4262c !important;outline-offset:2px;background-color:rgba(209, 52, 56, 0.05);}.cv-highlight-target-mode {outline:2px dashed #d97706 !important;outline-offset:2px;cursor:crosshair;}.cv-share-selected-highlight {outline:3px solid #b45309 !important;outline-offset:2px;background-color:rgba(245, 158, 11, 0.05);}.selection-box.svelte-1dbf58w {position:fixed;border:1px solid rgba(0, 120, 212, 0.4);background-color:rgba(0, 120, 212, 0.1);pointer-events:none;z-index:10000;box-sizing:border-box;}.selection-box.hide-mode.svelte-1dbf58w {border:1px solid rgba(209, 52, 56, 0.4);background-color:rgba(209, 52, 56, 0.1);}.selection-box.highlight-mode.svelte-1dbf58w {border:1px solid rgba(255, 140, 0, 0.6); /* Orange/Gold for highlight */background-color:rgba(255, 140, 0, 0.1);}.selection-label.svelte-1dbf58w {position:absolute;top:-24px;left:0;background:#0078d4;color:white;padding:2px 6px;font-size:11px;border-radius:3px;white-space:nowrap;font-family:sans-serif;opacity:0.9;}.hide-mode.svelte-1dbf58w .selection-label:where(.svelte-1dbf58w) {background:#d13438;}.highlight-mode.svelte-1dbf58w .selection-label:where(.svelte-1dbf58w) {background:#d97706; /* Darker orange for text bg */}'
13611
13704
  };
13612
13705
 
13613
13706
  function ShareOverlay($$anchor, $$props) {
13614
13707
  push($$props, true);
13615
- append_styles$1($$anchor, $$css$d);
13708
+ append_styles$1($$anchor, $$css$e);
13616
13709
 
13617
13710
  let excludedTags = prop($$props, 'excludedTags', 19, () => ['HEADER', 'NAV', 'FOOTER']),
13618
13711
  excludedIds = prop($$props, 'excludedIds', 19, () => []);
@@ -13935,16 +14028,16 @@
13935
14028
  pop();
13936
14029
  }
13937
14030
 
13938
- var root_1$7 = from_html(`<div class="cv-focus-banner-wrapper svelte-1yqpn7e"><div id="cv-exit-focus-banner" data-cv-scroll-offset="" class="svelte-1yqpn7e"><span>You are viewing a 'focused view' generated by CustardUI.</span> <button type="button" class="svelte-1yqpn7e">See Original Page</button></div></div>`);
14031
+ var root_1$8 = from_html(`<div class="cv-focus-banner-wrapper svelte-1yqpn7e"><div id="cv-exit-focus-banner" data-cv-scroll-offset="" class="svelte-1yqpn7e"><span>You are viewing a 'focused view' generated by CustardUI.</span> <button type="button" class="svelte-1yqpn7e">See Original Page</button></div></div>`);
13939
14032
 
13940
- const $$css$c = {
14033
+ const $$css$d = {
13941
14034
  hash: 'svelte-1yqpn7e',
13942
14035
  code: '.cv-focus-banner-wrapper.svelte-1yqpn7e {position:fixed;top:0;left:0;right:0;z-index:9000;background-color:#f3cb52;box-shadow:0 2px 8px rgba(44, 26, 14, 0.15);font-family:system-ui, sans-serif;}#cv-exit-focus-banner.svelte-1yqpn7e {color:#2c1a0e;padding:10px 20px;display:flex;align-items:center;justify-content:center;gap:16px;}button.svelte-1yqpn7e {background:#804b18;color:#fdf6e3;border:none;padding:4px 12px;border-radius:4px;cursor:pointer;font-weight:600;}button.svelte-1yqpn7e:hover {background:#c4853a;}'
13943
14036
  };
13944
14037
 
13945
14038
  function FocusBanner($$anchor, $$props) {
13946
14039
  push($$props, false);
13947
- append_styles$1($$anchor, $$css$c);
14040
+ append_styles$1($$anchor, $$css$d);
13948
14041
 
13949
14042
  function handleExit() {
13950
14043
  focusStore.exit();
@@ -13957,7 +14050,7 @@
13957
14050
 
13958
14051
  {
13959
14052
  var consequent = ($$anchor) => {
13960
- var div = root_1$7();
14053
+ var div = root_1$8();
13961
14054
  var div_1 = child(div);
13962
14055
  var button = sibling(child(div_1), 2);
13963
14056
 
@@ -14169,13 +14262,13 @@
14169
14262
 
14170
14263
  /**
14171
14264
  * Initializes the manager. Should be called when the component is ready
14172
- * and we know there are elements on the page.
14265
+ * and we know there are elements on the current page (toggles, tab groups, or placeholders).
14173
14266
  */
14174
- init(hasPageElements, settingsEnabled) {
14267
+ init(hasElementsOnCurrentPage, settingsEnabled) {
14175
14268
  const options = this.getOptions();
14176
14269
 
14177
14270
  if (settingsEnabled && !this.hasChecked && options?.show) {
14178
- if (hasPageElements) {
14271
+ if (hasElementsOnCurrentPage) {
14179
14272
  this.hasChecked = true;
14180
14273
  this.checkAndShow();
14181
14274
  }
@@ -14203,14 +14296,14 @@
14203
14296
 
14204
14297
  var root$b = from_html(`<div class="cv-widget-root" data-cv-share-ignore=""><!> <!> <!> <!> <!> <!></div>`);
14205
14298
 
14206
- const $$css$b = {
14299
+ const $$css$c = {
14207
14300
  hash: 'svelte-1vlfixd',
14208
14301
  code: '\n /* Root should allow clicks to pass through to the page unless hitting checking/interactive element */.cv-widget-root {position:fixed;top:0;left:0;width:0;height:0;z-index:9999;pointer-events:none; /* Crucial: Allow clicks to pass through */\n\n /* Light Theme Defaults */--cv-bg: white;--cv-text: rgba(0, 0, 0, 0.9);--cv-text-secondary: rgba(0, 0, 0, 0.6);--cv-border: rgba(0, 0, 0, 0.1);--cv-bg-hover: rgba(0, 0, 0, 0.05);--cv-primary: #3e84f4;--cv-primary-hover: #2563eb;--cv-danger: #dc2626;--cv-danger-bg: rgba(220, 38, 38, 0.1);--cv-shadow: rgba(0, 0, 0, 0.25);--cv-input-bg: white;--cv-input-border: rgba(0, 0, 0, 0.15);--cv-switch-bg: rgba(0, 0, 0, 0.1);--cv-switch-knob: white;--cv-modal-icon-bg: rgba(0, 0, 0, 0.08);--cv-icon-bg: rgba(255, 255, 255, 0.92);--cv-icon-color: rgba(0, 0, 0, 0.9);--cv-focus-ring: rgba(62, 132, 244, 0.2);--cv-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.1);--cv-modal-radius: 0.75rem;--cv-card-radius: 0.5rem;--cv-section-label-transform: uppercase;font-family:inherit; /* Inherit font from host */}\n\n /* But interactive children need pointer-events back */.cv-widget-root > * {pointer-events:auto;}\n\n /* Exception: ShareOverlay manages its own pointer events */.cv-widget-root .cv-share-overlay {pointer-events:none; /* Overlay often passes clicks until specialized handles active */}.cv-widget-root[data-theme=\'dark\'] {\n /* Dark Theme Overrides */--cv-bg: #101722;--cv-text: #e2e8f0;--cv-text-secondary: rgba(255, 255, 255, 0.6);--cv-border: rgba(255, 255, 255, 0.1);--cv-bg-hover: rgba(255, 255, 255, 0.05);--cv-primary: #3e84f4;--cv-primary-hover: #60a5fa;--cv-danger: #f87171;--cv-danger-bg: rgba(248, 113, 113, 0.1);--cv-shadow: rgba(0, 0, 0, 0.5);--cv-input-bg: #1e293b;--cv-input-border: rgba(255, 255, 255, 0.1);--cv-switch-bg: rgba(255, 255, 255, 0.1);--cv-switch-knob: #e2e8f0;--cv-modal-icon-bg: rgba(255, 255, 255, 0.08);--cv-icon-bg: #1e293b;--cv-icon-color: #e2e8f0;--cv-focus-ring: rgba(62, 132, 244, 0.5);--cv-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.5);--cv-modal-radius: 0.75rem;--cv-card-radius: 0.5rem;--cv-section-label-transform: uppercase;}.cv-hidden {display:none !important;}'
14209
14302
  };
14210
14303
 
14211
14304
  function UIRoot($$anchor, $$props) {
14212
14305
  push($$props, true);
14213
- append_styles$1($$anchor, $$css$b);
14306
+ append_styles$1($$anchor, $$css$c);
14214
14307
 
14215
14308
  const { persistenceManager, resetToDefault } = getContext(RUNTIME_CALLBACKS_CTX);
14216
14309
  const iconSettingsStore = getContext(ICON_SETTINGS_CTX);
@@ -14252,7 +14345,7 @@
14252
14345
 
14253
14346
  // --- Effects ---
14254
14347
  user_effect(() => {
14255
- introManager.init(elementStore.hasPageElements, get(settingsEnabled));
14348
+ introManager.init(elementStore.hasElementsOnCurrentPage, get(settingsEnabled));
14256
14349
  });
14257
14350
 
14258
14351
  // --- Modal Actions ---
@@ -14485,7 +14578,8 @@
14485
14578
  * Initializes the UI manager (settings and share UI) using the provided config.
14486
14579
  */
14487
14580
  function initUIManager(runtime, config) {
14488
- const settingsEnabled = config.settings?.enabled === true;
14581
+ const { enabled, ...widgetSettings } = config.settings ?? {};
14582
+ const settingsEnabled = enabled === true;
14489
14583
  const callbacks = {
14490
14584
  resetToDefault: () => runtime.resetToDefault(),
14491
14585
  iconSettings: runtime.iconSettingsStore,
@@ -14494,7 +14588,7 @@
14494
14588
  const uiManager = new CustardUIManager({
14495
14589
  callbacks,
14496
14590
  settingsEnabled,
14497
- ...config.settings,
14591
+ ...widgetSettings,
14498
14592
  });
14499
14593
  uiManager.render();
14500
14594
  return uiManager;
@@ -14795,14 +14889,14 @@
14795
14889
 
14796
14890
  var root$a = from_html(`<div class="cv-context-divider svelte-1a535nn" role="button" tabindex="0"> </div>`);
14797
14891
 
14798
- const $$css$a = {
14892
+ const $$css$b = {
14799
14893
  hash: 'svelte-1a535nn',
14800
14894
  code: '.cv-context-divider.svelte-1a535nn {padding:12px;margin:16px 0;background-color:#f8f8f8;border-top:1px dashed #ccc;border-bottom:1px dashed #ccc;color:#555;text-align:center;cursor:pointer;font-family:system-ui, sans-serif;font-size:13px;transition:background-color 0.2s;}.cv-context-divider.svelte-1a535nn:hover {background-color:#e8e8e8;color:#333;}'
14801
14895
  };
14802
14896
 
14803
14897
  function FocusDivider($$anchor, $$props) {
14804
14898
  push($$props, true);
14805
- append_styles$1($$anchor, $$css$a);
14899
+ append_styles$1($$anchor, $$css$b);
14806
14900
 
14807
14901
  // Component is mounted manually via `mount`, use props for communication.
14808
14902
  let hiddenCount = prop($$props, 'hiddenCount', 3, 0);
@@ -14948,18 +15042,18 @@
14948
15042
  var root_3$2 = from_html(`<!> <span class="cv-ribbon-text cv-ribbon-text--right svelte-1asb212"> </span> <span class="cv-ribbon-grip svelte-1asb212" aria-hidden="true"><span class="svelte-1asb212"></span><span class="svelte-1asb212"></span> <span class="svelte-1asb212"></span><span class="svelte-1asb212"></span> <span class="svelte-1asb212"></span><span class="svelte-1asb212"></span></span>`, 1);
14949
15043
  var root_6$1 = from_html(`<span>▾</span>`);
14950
15044
  var root_5$1 = from_html(`<span class="cv-ribbon-grip svelte-1asb212" aria-hidden="true"><span class="svelte-1asb212"></span><span class="svelte-1asb212"></span> <span class="svelte-1asb212"></span><span class="svelte-1asb212"></span> <span class="svelte-1asb212"></span><span class="svelte-1asb212"></span></span> <span class="cv-ribbon-text svelte-1asb212"> </span> <!>`, 1);
14951
- var root_1$6 = from_html(`<button type="button"><!></button>`);
15045
+ var root_1$7 = from_html(`<button type="button"><!></button>`);
14952
15046
  var root_7$1 = from_html(`<div class="cv-annotation-card svelte-1asb212" role="region" aria-label="Annotation"><button type="button" class="cv-card-close svelte-1asb212" aria-label="Collapse annotation">✕</button> <span class="cv-card-text svelte-1asb212"> </span></div>`);
14953
15047
  var root$9 = from_html(`<div><!></div>`);
14954
15048
 
14955
- const $$css$9 = {
15049
+ const $$css$a = {
14956
15050
  hash: 'svelte-1asb212',
14957
15051
  code: '\n /* ==============================\n CONTAINER (position, drag, opacity)\n ============================== */.cv-annotation-container.svelte-1asb212 {position:absolute;z-index:100;pointer-events:auto;touch-action:none;user-select:none;cursor:default;opacity:0.88;transition:opacity 0.2s ease, z-index 0s;}.cv-annotation-container.svelte-1asb212:hover {opacity:1;z-index:110;}\n\n /* ==============================\n RIBBON (home-plate)\n ============================== */.cv-annotation-ribbon.svelte-1asb212 {border:none;padding:6px 20px 6px 8px;min-width:28px;min-height:24px;background:var(--cv-highlight-color);cursor:default;box-shadow:0 2px 8px rgba(0, 0, 0, 0.15);display:flex;align-items:center;justify-content:flex-start;gap:5px;transform-origin:center center;}.cv-annotation-ribbon--intro.svelte-1asb212 {\n animation: svelte-1asb212-cv-wiggle-intro 0.75s ease-in-out forwards;}.cv-annotation-ribbon--periodic.svelte-1asb212 {\n animation: svelte-1asb212-cv-wiggle-periodic 5s ease-in-out infinite;}.cv-annotation-ribbon--right.svelte-1asb212 {padding:6px 8px 6px 20px;justify-content:flex-end;}.cv-annotation-ribbon--empty.svelte-1asb212 {min-width:24px;padding:6px 16px 6px 8px;}.cv-annotation-ribbon--expandable.svelte-1asb212 {cursor:pointer;}.cv-annotation-ribbon--expandable.svelte-1asb212:hover {filter:brightness(1.1);}\n\n /* ==============================\n RIBBON TEXT (single line)\n ============================== */.cv-ribbon-text.svelte-1asb212 {display:block;font-family:\'Segoe Print\', \'Bradley Hand\', \'Chilanka\', cursive;font-size:13px;font-weight:700;line-height:1.2;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:160px;color:#fff;text-shadow:0 1px 3px rgba(0, 0, 0, 0.25);}.cv-ribbon-text--right.svelte-1asb212 {text-align:right;}.cv-ribbon-chevron.svelte-1asb212 {font-size:22px;opacity:1;flex-shrink:0;line-height:1;color:#fff;text-shadow:0 1px 4px rgba(0, 0, 0, 0.4);}.cv-ribbon-chevron--bounce.svelte-1asb212 {\n animation: svelte-1asb212-cv-chevron-bounce 3s ease-in-out infinite;}\n\n /* ==============================\n DRAG GRIP (6-dot grid on flat side)\n ============================== */.cv-ribbon-grip.svelte-1asb212 {display:grid;grid-template-columns:repeat(2, 3px);gap:3px;flex-shrink:0;opacity:0.7;cursor:grab;padding:2px;}.cv-ribbon-grip.svelte-1asb212:active {cursor:grabbing;}.cv-ribbon-grip.svelte-1asb212 > span:where(.svelte-1asb212) {width:3px;height:3px;border-radius:50%;background:rgba(255, 255, 255, 0.9);box-shadow:0 0 1px rgba(0, 0, 0, 0.4);}\n\n /* ==============================\n CARD (sticky note)\n ============================== */.cv-annotation-card.svelte-1asb212 {background:#FFFDF5;border:1.5px solid var(--cv-highlight-color);border-radius:4px;padding:10px 12px;max-width:280px;min-width:120px;box-shadow:0 4px 20px rgba(0, 0, 0, 0.18);position:relative;\n animation: svelte-1asb212-cv-cardPop 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards;}.cv-card-close.svelte-1asb212 {position:absolute;top:3px;right:5px;border:none;background:transparent;cursor:pointer;font-size:12px;color:#aaa;padding:2px 4px;line-height:1;font-family:sans-serif;}.cv-card-close.svelte-1asb212:hover {color:#555;}.cv-card-text.svelte-1asb212 {display:block;font-family:\'Segoe Print\', \'Bradley Hand\', \'Chilanka\', cursive;font-size:13px;font-weight:600;color:#333;line-height:1.45;word-break:break-word;white-space:pre-wrap;padding-right:15px;}\n\n /* ==============================\n ANIMATIONS\n ============================== */\n @keyframes svelte-1asb212-cv-wiggle-intro {\n 0% { transform: rotate(0deg); }\n 10% { transform: rotate(-6deg); }\n 25% { transform: rotate(6deg); }\n 40% { transform: rotate(-5deg); }\n 55% { transform: rotate(5deg); }\n 68% { transform: rotate(-3deg); }\n 80% { transform: rotate(2.5deg); }\n 90% { transform: rotate(-1deg); }\n 100% { transform: rotate(0deg); }\n }\n\n @keyframes svelte-1asb212-cv-wiggle-periodic {\n 0%, 85%, 100% { transform: rotate(0deg); }\n 87% { transform: rotate(1.2deg); }\n 90% { transform: rotate(-1.2deg); }\n 93% { transform: rotate(0.8deg); }\n 96% { transform: rotate(-0.5deg); }\n }\n\n @keyframes svelte-1asb212-cv-cardPop {\n from { opacity: 0; transform: scale(0.9) translateY(5px); }\n to { opacity: 1; transform: scale(1) translateY(0); }\n }\n\n @keyframes svelte-1asb212-cv-chevron-bounce {\n 0%, 70%, 100% { transform: translateY(0); }\n 78% { transform: translateY(-3px); }\n 86% { transform: translateY(1px); }\n 93% { transform: translateY(-1.5px); }\n }'
14958
15052
  };
14959
15053
 
14960
15054
  function HighlightTextAnnotation($$anchor, $$props) {
14961
15055
  push($$props, true);
14962
- append_styles$1($$anchor, $$css$9);
15056
+ append_styles$1($$anchor, $$css$a);
14963
15057
 
14964
15058
  const corner = user_derived(() => $$props.annotationCorner ?? DEFAULT_ANNOTATION_CORNER);
14965
15059
  const hasText = user_derived(() => $$props.annotation.length > 0);
@@ -15121,9 +15215,9 @@
15121
15215
  const pointsRight = c === 'tl' || c === 'bl';
15122
15216
 
15123
15217
  if (pointsRight) {
15124
- return 'polygon(0% 0%, 80% 0%, 100% 50%, 80% 100%, 0% 100%)';
15218
+ return 'polygon(0% 0%, calc(100% - 14px) 0%, 100% 50%, calc(100% - 14px) 100%, 0% 100%)';
15125
15219
  } else {
15126
- return 'polygon(20% 0%, 100% 0%, 100% 100%, 20% 100%, 0% 50%)';
15220
+ return 'polygon(14px 0%, 100% 0%, 100% 100%, 14px 100%, 0% 50%)';
15127
15221
  }
15128
15222
  }
15129
15223
 
@@ -15138,7 +15232,7 @@
15138
15232
 
15139
15233
  {
15140
15234
  var consequent_4 = ($$anchor) => {
15141
- var button = root_1$6();
15235
+ var button = root_1$7();
15142
15236
  let classes_1;
15143
15237
 
15144
15238
  button.__click = handleInteraction;
@@ -15294,15 +15388,15 @@
15294
15388
 
15295
15389
  delegate(['pointerdown', 'pointermove', 'pointerup', 'click']);
15296
15390
 
15297
- var root_1$5 = from_html(`<div class="cv-annotation-container svelte-ii1txw"><div role="img" aria-label="Annotation marker"></div> <button type="button" class="cv-empty-dismiss svelte-ii1txw" aria-label="Dismiss marker">✕</button></div>`);
15391
+ var root_1$6 = from_html(`<div class="cv-annotation-container svelte-ii1txw"><div role="img" aria-label="Annotation marker"></div> <button type="button" class="cv-empty-dismiss svelte-ii1txw" aria-label="Dismiss marker">✕</button></div>`);
15298
15392
 
15299
- const $$css$8 = {
15393
+ const $$css$9 = {
15300
15394
  hash: 'svelte-ii1txw',
15301
15395
  code: '.cv-annotation-container.svelte-ii1txw {position:absolute;z-index:100;pointer-events:auto;touch-action:none;user-select:none;opacity:0.95;transition:opacity 0.2s ease, z-index 0s;}.cv-annotation-container.svelte-ii1txw:hover {opacity:1;z-index:110;}.cv-empty-ribbon.svelte-ii1txw {min-width:45px;min-height:22px;background:var(--cv-highlight-color);display:flex;align-items:center;justify-content:center;box-shadow:0 2px 6px rgba(0, 0, 0, 0.18), inset 0 1px 0 rgba(255, 255, 255, 0.22);transform-origin:center center;padding:5px 22px 5px 10px;opacity:0.95;transition:opacity 0.2s ease;}.cv-annotation-container.svelte-ii1txw:hover .cv-empty-ribbon:where(.svelte-ii1txw) {opacity:1;}.cv-empty-ribbon--right.svelte-ii1txw {padding:5px 10px 5px 22px;}.cv-empty-ribbon--intro.svelte-ii1txw {\n animation: svelte-ii1txw-cv-wiggle-intro 0.75s ease-in-out forwards;}.cv-empty-ribbon--periodic.svelte-ii1txw {\n animation: svelte-ii1txw-cv-wiggle-periodic 5s ease-in-out infinite;}\n\n /* Dismiss button — hidden until container is hovered */.cv-empty-dismiss.svelte-ii1txw {position:absolute;border:none;background:rgba(100, 100, 100, 0.55);color:#fff;font-size:8px;cursor:pointer;width:14px;height:14px;display:flex;align-items:center;justify-content:center;padding:0;line-height:1;border-radius:50%;box-shadow:0 1px 4px rgba(0, 0, 0, 0.2);opacity:0;pointer-events:none;transition:opacity 0.15s ease, background 0.15s ease, color 0.15s ease;}.cv-annotation-container.svelte-ii1txw:hover .cv-empty-dismiss:where(.svelte-ii1txw) {opacity:1;pointer-events:auto;}.cv-empty-dismiss.svelte-ii1txw:hover {background:rgba(80, 80, 80, 0.8);box-shadow:0 1px 6px rgba(0, 0, 0, 0.28);}\n\n @keyframes svelte-ii1txw-cv-wiggle-intro {\n 0% { transform: rotate(0deg); }\n 10% { transform: rotate(-6deg); }\n 25% { transform: rotate(6deg); }\n 40% { transform: rotate(-5deg); }\n 55% { transform: rotate(5deg); }\n 68% { transform: rotate(-3deg); }\n 80% { transform: rotate(2.5deg); }\n 90% { transform: rotate(-1deg); }\n 100% { transform: rotate(0deg); }\n }\n\n @keyframes svelte-ii1txw-cv-wiggle-periodic {\n 0%, 85%, 100% { transform: rotate(0deg); }\n 87% { transform: rotate(1.2deg); }\n 90% { transform: rotate(-1.2deg); }\n 93% { transform: rotate(0.8deg); }\n 96% { transform: rotate(-0.5deg); }\n }'
15302
15396
  };
15303
15397
 
15304
15398
  function HighlightEmptyAnnotation($$anchor, $$props) {
15305
- append_styles$1($$anchor, $$css$8);
15399
+ append_styles$1($$anchor, $$css$9);
15306
15400
 
15307
15401
  const corner = user_derived(() => $$props.annotationCorner ?? DEFAULT_ANNOTATION_CORNER);
15308
15402
  const isRightCorner = user_derived(() => get(corner) === 'tr' || get(corner) === 'br');
@@ -15367,7 +15461,7 @@
15367
15461
 
15368
15462
  {
15369
15463
  var consequent = ($$anchor) => {
15370
- var div = root_1$5();
15464
+ var div = root_1$6();
15371
15465
  var div_1 = child(div);
15372
15466
  let classes;
15373
15467
  var button = sibling(div_1, 2);
@@ -15410,17 +15504,17 @@
15410
15504
  delegate(['click']);
15411
15505
 
15412
15506
  var root_2$3 = from_html(`<button type="button" aria-label="Previous highlight">↑</button> <button type="button" aria-label="Next highlight">↓</button>`, 1);
15413
- var root_1$4 = from_html(`<div class="cv-highlight-group svelte-1mz0neo"><div class="cv-highlight-marker svelte-1mz0neo"></div> <!> <div class="cv-highlight-pill svelte-1mz0neo"><a href="https://custardui.js.org" target="_blank" rel="noopener noreferrer" class="svelte-1mz0neo">Annotated by: CustardUI↗</a></div> <!></div>`);
15507
+ var root_1$5 = from_html(`<div class="cv-highlight-group svelte-1mz0neo"><div class="cv-highlight-marker svelte-1mz0neo"></div> <!> <div class="cv-highlight-pill svelte-1mz0neo"><a href="https://custardui.js.org" target="_blank" rel="noopener noreferrer" class="svelte-1mz0neo">Annotated by: CustardUI↗</a></div> <!></div>`);
15414
15508
  var root$8 = from_html(`<div class="cv-highlight-overlay svelte-1mz0neo"></div>`);
15415
15509
 
15416
- const $$css$7 = {
15510
+ const $$css$8 = {
15417
15511
  hash: 'svelte-1mz0neo',
15418
15512
  code: '.cv-highlight-overlay.svelte-1mz0neo {position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:8000;}.cv-highlight-group.svelte-1mz0neo {position:absolute;pointer-events:none;}.cv-highlight-marker.svelte-1mz0neo {position:absolute;inset:0;pointer-events:none;\n \n /* Marker Style */border:3.5px solid var(--cv-highlight-color);border-radius:200px 15px 225px 15px / 15px 225px 15px 255px;transform:rotate(-0.5deg);\n \n /* 3D INTERNAL VOLUME:\n Adds depth to the yellow border itself so it looks rounded.\n */box-shadow:inset 0 1px 2px rgba(129, 73, 25, 0.2),\n inset 0 -1px 1px rgba(255, 255, 255, 0.7);\n\n /* 2A-3 DOUBLE LIGHT PROJECTION:\n Stacks multiple drop-shadows to cast into the box interior.\n */filter:/* Sharp contact shadow for grounding */\n drop-shadow(0 2px 2px rgba(44, 26, 14, 0.15)) \n /* Light Source A: Casts shadow from top-left to bottom-right */\n drop-shadow(-8px 12px 10px rgba(44, 26, 14, 0.12))\n /* Light Source B: Casts shadow from top-right to bottom-left */\n drop-shadow(8px 12px 10px rgba(44, 26, 14, 0.12));\n \n animation: svelte-1mz0neo-highlightFadeIn 0.3s ease-out forwards;}.cv-nav-arrow.svelte-1mz0neo {position:absolute;z-index:10;right:-5px;pointer-events:auto;width:14px;height:14px;border-radius:100px;border:1px solid var(--cv-highlight-color);background:white;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:7px;color:#814919;font-weight:700;font-family:ui-sans-serif, system-ui, sans-serif;line-height:1;padding:0;box-shadow:0 4px 12px rgba(44, 26, 14, 0.15);opacity:0.7;}.cv-nav-arrow.svelte-1mz0neo:hover {opacity:1;}.cv-nav-arrow--up.svelte-1mz0neo {top:0px;}.cv-nav-arrow--down.svelte-1mz0neo {bottom:0px;}.cv-nav-arrow--hidden.svelte-1mz0neo {visibility:hidden;pointer-events:none;}.cv-highlight-pill.svelte-1mz0neo {position:absolute;z-index:10;bottom:-2px;right:14px;background:white;height:14px;padding:0 8px;display:flex;align-items:center;border-radius:100px;border:1px solid var(--cv-highlight-color);pointer-events:auto;white-space:nowrap;\n \n /* Stronger shadow to match the frame\'s new altitude */box-shadow:0 4px 12px rgba(44, 26, 14, 0.15);}.cv-highlight-pill.svelte-1mz0neo a:where(.svelte-1mz0neo) {font-size:8px;font-weight:700;color:#814919;text-decoration:none;font-family:ui-sans-serif, system-ui, sans-serif;line-height:1;}.cv-highlight-pill.svelte-1mz0neo:hover a:where(.svelte-1mz0neo) {opacity:0.8;}\n\n @keyframes svelte-1mz0neo-highlightFadeIn {\n from { \n opacity: 0; \n transform: scale(0.98) rotate(-1deg); \n }\n to { \n opacity: 1; \n transform: scale(1) rotate(-0.5deg); \n }\n }'
15419
15513
  };
15420
15514
 
15421
15515
  function HighlightOverlay($$anchor, $$props) {
15422
15516
  push($$props, true);
15423
- append_styles$1($$anchor, $$css$7);
15517
+ append_styles$1($$anchor, $$css$8);
15424
15518
 
15425
15519
  let rects = user_derived(() => $$props.box.rects);
15426
15520
 
@@ -15437,7 +15531,7 @@
15437
15531
  var div = root$8();
15438
15532
 
15439
15533
  each(div, 23, () => get(rects), (rect) => rect.element, ($$anchor, rect, i) => {
15440
- var div_1 = root_1$4();
15534
+ var div_1 = root_1$5();
15441
15535
  var node = sibling(child(div_1), 2);
15442
15536
 
15443
15537
  {
@@ -15629,7 +15723,6 @@
15629
15723
 
15630
15724
  /* highlight-service.svelte.ts generated by Svelte v5.46.1 */
15631
15725
 
15632
- const HIGHLIGHT_PARAM = 'cv-highlight';
15633
15726
  const BODY_HIGHLIGHT_CLASS = 'cv-highlight-mode';
15634
15727
 
15635
15728
  const ARROW_OVERLAY_ID = 'cv-highlight-overlay';
@@ -15647,7 +15740,6 @@
15647
15740
  }
15648
15741
 
15649
15742
  class HighlightService {
15650
- rootEl;
15651
15743
  overlayApp;
15652
15744
  state = new HighlightState();
15653
15745
  resizeObserver;
@@ -15656,9 +15748,7 @@
15656
15748
  activeAnnotations = new Map();
15657
15749
  onWindowResize = () => this.updatePositions();
15658
15750
 
15659
- constructor(rootEl) {
15660
- this.rootEl = rootEl;
15661
-
15751
+ constructor() {
15662
15752
  this.resizeObserver = new ResizeObserver(() => {
15663
15753
  this.updatePositions();
15664
15754
  });
@@ -15672,7 +15762,7 @@
15672
15762
  const targets = [];
15673
15763
 
15674
15764
  descriptors.forEach((desc) => {
15675
- const matchingEls = resolve(this.rootEl, desc);
15765
+ const matchingEls = resolve(desc);
15676
15766
 
15677
15767
  if (matchingEls && matchingEls.length > 0) targets.push(...matchingEls);
15678
15768
  });
@@ -15690,7 +15780,7 @@
15690
15780
  const annotations = new Map();
15691
15781
 
15692
15782
  descriptors.forEach((desc) => {
15693
- const matchingEls = resolve(this.rootEl, desc);
15783
+ const matchingEls = resolve(desc);
15694
15784
 
15695
15785
  if (matchingEls && matchingEls.length > 0) {
15696
15786
  targets.push(...matchingEls);
@@ -15851,14 +15941,11 @@
15851
15941
 
15852
15942
  /* focus-service.svelte.ts generated by Svelte v5.46.1 */
15853
15943
 
15854
- const SHOW_PARAM = 'cv-show';
15855
- const HIDE_PARAM = 'cv-hide';
15856
15944
  const BODY_SHOW_CLASS = 'cv-show-mode';
15857
15945
  const HIDDEN_CLASS = 'cv-hidden';
15858
15946
  const SHOW_ELEMENT_CLASS = 'cv-show-element';
15859
15947
 
15860
15948
  class FocusService {
15861
- rootEl;
15862
15949
  hiddenElements = new SvelteSet();
15863
15950
  dividers = new SvelteSet(); // Store Svelte App instances
15864
15951
  excludedTags;
@@ -15869,15 +15956,13 @@
15869
15956
 
15870
15957
  highlightService;
15871
15958
 
15872
- constructor(rootEl, options) {
15873
- this.rootEl = rootEl;
15874
-
15959
+ constructor(options) {
15875
15960
  const userTags = options.shareExclusions?.tags || [];
15876
15961
  const userIds = options.shareExclusions?.ids || [];
15877
15962
 
15878
15963
  this.excludedTags = new SvelteSet([...DEFAULT_EXCLUDED_TAGS, ...userTags].map((t) => t.toUpperCase()));
15879
15964
  this.excludedIds = new SvelteSet([...DEFAULT_EXCLUDED_IDS, ...userIds]);
15880
- this.highlightService = new HighlightService(this.rootEl);
15965
+ this.highlightService = new HighlightService();
15881
15966
 
15882
15967
  // Subscribe to store for exit signal
15883
15968
  this.unsubscribe = effect_root(() => {
@@ -15910,9 +15995,9 @@
15910
15995
  // eslint-disable-next-line svelte/prefer-svelte-reactivity
15911
15996
  const url = new URL(window.location.href);
15912
15997
 
15913
- const showDescriptors = url.searchParams.get(SHOW_PARAM);
15914
- const hideDescriptors = url.searchParams.get(HIDE_PARAM);
15915
- const highlightDescriptors = url.searchParams.get(HIGHLIGHT_PARAM);
15998
+ const showDescriptors = url.searchParams.get(PARAM_CV_SHOW);
15999
+ const hideDescriptors = url.searchParams.get(PARAM_CV_HIDE);
16000
+ const highlightDescriptors = url.searchParams.get(PARAM_CV_HIGHLIGHT);
15916
16001
  const hasAnyMode = showDescriptors || hideDescriptors || highlightDescriptors;
15917
16002
 
15918
16003
  if (!hasAnyMode) {
@@ -15969,7 +16054,7 @@
15969
16054
  const targets = [];
15970
16055
 
15971
16056
  descriptors.forEach((desc) => {
15972
- const matchingEls = resolve(this.rootEl, desc);
16057
+ const matchingEls = resolve(desc);
15973
16058
 
15974
16059
  if (matchingEls && matchingEls.length > 0) {
15975
16060
  targets.push(...matchingEls);
@@ -16008,7 +16093,7 @@
16008
16093
  const targets = [];
16009
16094
 
16010
16095
  descriptors.forEach((desc) => {
16011
- const matchingEls = resolve(this.rootEl, desc);
16096
+ const matchingEls = resolve(desc);
16012
16097
 
16013
16098
  if (matchingEls && matchingEls.length > 0) {
16014
16099
  targets.push(...matchingEls);
@@ -16198,18 +16283,18 @@
16198
16283
 
16199
16284
  let changed = false;
16200
16285
 
16201
- if (url.searchParams.has(SHOW_PARAM)) {
16202
- url.searchParams.delete(SHOW_PARAM);
16286
+ if (url.searchParams.has(PARAM_CV_SHOW)) {
16287
+ url.searchParams.delete(PARAM_CV_SHOW);
16203
16288
  changed = true;
16204
16289
  }
16205
16290
 
16206
- if (url.searchParams.has(HIDE_PARAM)) {
16207
- url.searchParams.delete(HIDE_PARAM);
16291
+ if (url.searchParams.has(PARAM_CV_HIDE)) {
16292
+ url.searchParams.delete(PARAM_CV_HIDE);
16208
16293
  changed = true;
16209
16294
  }
16210
16295
 
16211
- if (url.searchParams.has(HIGHLIGHT_PARAM)) {
16212
- url.searchParams.delete(HIGHLIGHT_PARAM);
16296
+ if (url.searchParams.has(PARAM_CV_HIGHLIGHT)) {
16297
+ url.searchParams.delete(PARAM_CV_HIGHLIGHT);
16213
16298
  changed = true;
16214
16299
  }
16215
16300
 
@@ -16226,6 +16311,131 @@
16226
16311
  }
16227
16312
  }
16228
16313
 
16314
+ /* label-registry-store.svelte.ts generated by Svelte v5.46.1 */
16315
+
16316
+ class LabelRegistryStore {
16317
+ _labels = new SvelteMap();
16318
+
16319
+ /**
16320
+ * Registers a label definition. Overwrites any existing entry with the same name.
16321
+ */
16322
+ register(def) {
16323
+ this._labels.set(def.name, { ...def });
16324
+ }
16325
+
16326
+ /**
16327
+ * Merges overrides onto an existing label entry.
16328
+ * Warns and skips if the label name is not registered.
16329
+ */
16330
+ override(name, overrides) {
16331
+ const existing = this._labels.get(name);
16332
+
16333
+ if (!existing) {
16334
+ console.warn(`[CustardUI] Label "${name}" is not in the config and cannot be overridden.`);
16335
+
16336
+ return;
16337
+ }
16338
+
16339
+ this._labels.set(name, { ...existing, ...overrides });
16340
+ }
16341
+
16342
+ /**
16343
+ * Returns the label definition for the given name, or undefined if not registered.
16344
+ */
16345
+ get(name) {
16346
+ return this._labels.get(name);
16347
+ }
16348
+ }
16349
+
16350
+ const labelRegistryStore = new LabelRegistryStore();
16351
+
16352
+ /**
16353
+ * Manages registration and adaptation overrides for label definitions.
16354
+ */
16355
+ const labelManager = {
16356
+ /**
16357
+ * Registers all labels from the config into the label registry store.
16358
+ */
16359
+ registerConfigLabels(config) {
16360
+ for (const label of (config.labels ?? [])) {
16361
+ labelRegistryStore.register(label);
16362
+ }
16363
+ },
16364
+ /**
16365
+ * Applies adaptation preset overrides to registered labels.
16366
+ * Unknown label names produce a warning and are skipped.
16367
+ */
16368
+ applyAdaptationOverrides(overrides) {
16369
+ for (const [name, override] of Object.entries(overrides)) {
16370
+ labelRegistryStore.override(name, override);
16371
+ }
16372
+ },
16373
+ };
16374
+
16375
+ /* color-scheme-store.svelte.ts generated by Svelte v5.46.1 */
16376
+
16377
+ class ColorSchemeStore {
16378
+ #isDark = state(false);
16379
+
16380
+ get isDark() {
16381
+ return get(this.#isDark);
16382
+ }
16383
+
16384
+ set isDark(value) {
16385
+ set(this.#isDark, value, true);
16386
+ }
16387
+
16388
+ #mq = null;
16389
+ #handler = null;
16390
+
16391
+ #removeListener() {
16392
+ if (this.#mq && this.#handler) {
16393
+ this.#mq.removeEventListener('change', this.#handler);
16394
+ this.#mq = null;
16395
+ this.#handler = null;
16396
+ }
16397
+ }
16398
+
16399
+ init(scheme = 'light') {
16400
+ this.#removeListener();
16401
+
16402
+ if (scheme === 'dark') {
16403
+ this.isDark = true;
16404
+
16405
+ return;
16406
+ }
16407
+
16408
+ if (scheme === 'auto') {
16409
+ if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {
16410
+ // SSR or environment without matchMedia — default to light
16411
+ this.isDark = false;
16412
+
16413
+ return;
16414
+ }
16415
+
16416
+ this.#mq = window.matchMedia('(prefers-color-scheme: dark)');
16417
+
16418
+ this.#handler = (e) => {
16419
+ this.isDark = e.matches;
16420
+ };
16421
+
16422
+ this.isDark = this.#mq.matches;
16423
+ this.#mq.addEventListener('change', this.#handler);
16424
+
16425
+ return;
16426
+ }
16427
+
16428
+ // 'light' (default)
16429
+ this.isDark = false;
16430
+ }
16431
+
16432
+ destroy() {
16433
+ this.#removeListener();
16434
+ }
16435
+ }
16436
+
16437
+ const colorSchemeStore = new ColorSchemeStore();
16438
+
16229
16439
  /**
16230
16440
  * DOM Scanner for Variable Interpolation
16231
16441
  *
@@ -16526,18 +16736,23 @@
16526
16736
 
16527
16737
  /* runtime.svelte.ts generated by Svelte v5.46.1 */
16528
16738
 
16529
- function stripAdaptationPlaceholders(state) {
16530
- if (!state.placeholders) return state;
16739
+ function stripSiteManaged(state) {
16740
+ // Strip siteManaged toggle values
16741
+ const siteManagedToggleIds = new Set((activeStateStore.config.toggles ?? []).filter((t) => t.siteManaged).map((t) => t.toggleId));
16742
+
16743
+ const shownToggles = (state.shownToggles ?? []).filter((id) => !siteManagedToggleIds.has(id));
16744
+ const peekToggles = (state.peekToggles ?? []).filter((id) => !siteManagedToggleIds.has(id));
16531
16745
 
16532
- const filtered = {};
16746
+ // Strip siteManaged placeholder keys
16747
+ const placeholders = {};
16533
16748
 
16534
- for (const [key, value] of Object.entries(state.placeholders)) {
16749
+ for (const [key, value] of Object.entries(state.placeholders ?? {})) {
16535
16750
  const def = placeholderRegistryStore.get(key);
16536
16751
 
16537
- if (!def?.adaptationPlaceholder) filtered[key] = value;
16752
+ if (!def?.siteManaged) placeholders[key] = value;
16538
16753
  }
16539
16754
 
16540
- return { ...state, placeholders: filtered };
16755
+ return { ...state, shownToggles, peekToggles, placeholders };
16541
16756
  }
16542
16757
 
16543
16758
  /**
@@ -16565,15 +16780,12 @@
16565
16780
  // Initialize adaptation store
16566
16781
  adaptationStore.init(opt.adaptationConfig ?? null);
16567
16782
 
16568
- // Store assetsManager for component access
16569
- derivedStore.setAssetsManager(opt.assetsManager);
16570
-
16571
16783
  // Initial State Resolution:
16572
- // URL (Sparse Override) > Persistence (Full) > Adaptation Defaults > Config Default
16784
+ // URL (Sparse Override) > Persistence (Full) > Adaptation Preset > Config Default
16573
16785
  this.resolveInitialState(opt.adaptationConfig ?? null);
16574
16786
 
16575
16787
  // Resolve Exclusions
16576
- this.focusService = new FocusService(this.rootEl, {
16788
+ this.focusService = new FocusService({
16577
16789
  shareExclusions: opt.configFile.config?.shareExclusions || {}
16578
16790
  });
16579
16791
  }
@@ -16595,6 +16807,15 @@
16595
16807
  // Register tab-group placeholders AFTER global config placeholders to preserve precedence
16596
16808
  placeholderManager.registerTabGroupPlaceholders(config);
16597
16809
 
16810
+ // Register label definitions
16811
+ labelManager.registerConfigLabels(config);
16812
+
16813
+ // Initialize color scheme for site for general color resolution
16814
+ // Ensure any previous listeners cleaned up before re-initializing
16815
+ colorSchemeStore.destroy();
16816
+
16817
+ colorSchemeStore.init(configFile.colorScheme ?? 'light');
16818
+
16598
16819
  // Initialize UI Options from Settings
16599
16820
  uiStore.setUIOptions({
16600
16821
  showTabGroups: settings.showTabGroups ?? true,
@@ -16608,7 +16829,7 @@
16608
16829
  * Resolves the starting application state by layering sources:
16609
16830
  *
16610
16831
  * 1. **Baseline**: `ActiveStateStore` initializes with defaults from the config file.
16611
- * 2. **Adaptation Defaults**: If an adaptation is active, its defaults are applied
16832
+ * 2. **Adaptation Preset**: If an adaptation is active, its preset is applied
16612
16833
  * on top of the config defaults (before persisted state, so user choices can win).
16613
16834
  * 3. **Persistence**: If local storage has a saved state, it replaces the baseline (`applyState`).
16614
16835
  * 4. **URL Overrides**: If the URL contains parameters (`?t-show=X`), these are applied
@@ -16616,9 +16837,13 @@
16616
16837
  * retain their values from persistence/defaults.
16617
16838
  */
16618
16839
  resolveInitialState(adaptationConfig) {
16619
- // 1. Apply adaptation defaults on top of config defaults (before persisted state)
16620
- if (adaptationConfig?.defaults) {
16621
- activeStateStore.applyAdaptationDefaults(adaptationConfig.defaults);
16840
+ // 1. Apply adaptation preset on top of config defaults (before persisted state)
16841
+ if (adaptationConfig?.preset) {
16842
+ activeStateStore.applyAdaptationDefaults(adaptationConfig.preset);
16843
+
16844
+ if (adaptationConfig.preset.labels) {
16845
+ labelManager.applyAdaptationOverrides(adaptationConfig.preset.labels);
16846
+ }
16622
16847
  }
16623
16848
 
16624
16849
  // 2. Apply persisted base state on top of defaults (user choices win over adaptation defaults).
@@ -16674,7 +16899,7 @@
16674
16899
  this.destroyEffectRoot = effect_root(() => {
16675
16900
  // Automatic Persistence
16676
16901
  user_effect(() => {
16677
- this.persistenceManager.persistState(stripAdaptationPlaceholders(activeStateStore.state));
16902
+ this.persistenceManager.persistState(stripSiteManaged(activeStateStore.state));
16678
16903
  this.persistenceManager.persistTabNavVisibility(uiStore.isTabGroupNavHeadingVisible);
16679
16904
  });
16680
16905
 
@@ -16745,9 +16970,9 @@
16745
16970
  this.persistenceManager.clearAll();
16746
16971
  activeStateStore.reset();
16747
16972
 
16748
- // Re-apply adaptation defaults so adaptation-controlled placeholders are not wiped by reset.
16749
- if (adaptationStore.activeConfig?.defaults) {
16750
- activeStateStore.applyAdaptationDefaults(adaptationStore.activeConfig.defaults);
16973
+ // Re-apply adaptation preset so adaptation-controlled placeholders are not wiped by reset.
16974
+ if (adaptationStore.activeConfig?.preset) {
16975
+ activeStateStore.applyAdaptationDefaults(adaptationStore.activeConfig.preset);
16751
16976
  }
16752
16977
 
16753
16978
  uiStore.reset();
@@ -16758,6 +16983,7 @@
16758
16983
  this.observer?.disconnect();
16759
16984
  this.destroyEffectRoot?.();
16760
16985
  this.focusService.destroy();
16986
+ colorSchemeStore.destroy();
16761
16987
 
16762
16988
  if (this.onHashChange) {
16763
16989
  window.removeEventListener('hashchange', this.onHashChange);
@@ -16765,52 +16991,6 @@
16765
16991
  }
16766
16992
  }
16767
16993
 
16768
- class AssetsManager {
16769
- assets;
16770
- baseURL;
16771
- constructor(assets, baseURL = '') {
16772
- this.assets = assets;
16773
- this.baseURL = baseURL;
16774
- if (!this.validate()) {
16775
- console.warn('Invalid assets:', this.assets);
16776
- }
16777
- }
16778
- // Check each asset has content or src
16779
- validate() {
16780
- return Object.values(this.assets).every((a) => a.src || a.content);
16781
- }
16782
- get(assetId) {
16783
- const asset = this.assets[assetId];
16784
- if (!asset)
16785
- return undefined;
16786
- // If there's a baseURL and the asset has a src property, prepend the baseURL
16787
- if (this.baseURL && asset.src) {
16788
- // Create a shallow copy to avoid mutating the original asset
16789
- return {
16790
- ...asset,
16791
- src: this.prependBaseURL(asset.src),
16792
- };
16793
- }
16794
- return asset;
16795
- }
16796
- prependBaseURL(path) {
16797
- // Don't prepend if the path is already absolute (starts with http:// or https://)
16798
- if (path.startsWith('http://') || path.startsWith('https://')) {
16799
- return path;
16800
- }
16801
- // Ensure baseURL doesn't end with / and path starts with /
16802
- const cleanBaseURL = this.baseURL.endsWith('/') ? this.baseURL.slice(0, -1) : this.baseURL;
16803
- const cleanPath = path.startsWith('/') ? path : '/' + path;
16804
- return cleanBaseURL + cleanPath;
16805
- }
16806
- loadFromJSON(json) {
16807
- this.assets = json;
16808
- }
16809
- loadAdditionalAssets(additionalAssets) {
16810
- this.assets = { ...this.assets, ...additionalAssets };
16811
- }
16812
- }
16813
-
16814
16994
  var root$7 = from_svg(`<svg><polyline points="6 9 12 15 18 9"></polyline></svg>`);
16815
16995
 
16816
16996
  function IconChevronDown($$anchor, $$props) {
@@ -16853,86 +17033,9 @@
16853
17033
  append($$anchor, svg);
16854
17034
  }
16855
17035
 
16856
- /** --- Basic renderers --- */
16857
- function renderImage(el, asset) {
16858
- if (!asset.src)
16859
- return;
16860
- el.innerHTML = '';
16861
- const img = document.createElement('img');
16862
- img.src = asset.src;
16863
- img.alt = asset.alt || '';
16864
- // Apply custom styling if provided
16865
- if (asset.className) {
16866
- img.className = asset.className;
16867
- }
16868
- if (asset.style) {
16869
- img.setAttribute('style', asset.style);
16870
- }
16871
- // Default styles (can be overridden by asset.style)
16872
- img.style.maxWidth = img.style.maxWidth || '100%';
16873
- img.style.height = img.style.height || 'auto';
16874
- img.style.display = img.style.display || 'block';
16875
- el.appendChild(img);
16876
- }
16877
- function renderText(el, asset) {
16878
- if (asset.content != null) {
16879
- el.textContent = asset.content;
16880
- }
16881
- // Apply custom styling if provided
16882
- if (asset.className) {
16883
- el.className = asset.className;
16884
- }
16885
- if (asset.style) {
16886
- el.setAttribute('style', asset.style);
16887
- }
16888
- }
16889
- function renderHtml(el, asset) {
16890
- if (asset.content != null) {
16891
- el.innerHTML = asset.content;
16892
- }
16893
- // Apply custom styling if provided
16894
- if (asset.className) {
16895
- el.className = asset.className;
16896
- }
16897
- if (asset.style) {
16898
- el.setAttribute('style', asset.style);
16899
- }
16900
- }
16901
- /** --- Unified asset renderer --- */
16902
- function detectAssetType(asset) {
16903
- // If src exists, it's an image
16904
- if (asset.src)
16905
- return 'image';
16906
- // If content contains HTML tags, it's HTML
16907
- if (asset.content && /<[^>]+>/.test(asset.content)) {
16908
- return 'html';
16909
- }
16910
- return 'text';
16911
- }
16912
- function renderAssetInto(el, assetId, assetsManager) {
16913
- const asset = assetsManager.get(assetId);
16914
- if (!asset)
16915
- return;
16916
- const type = asset.type || detectAssetType(asset);
16917
- switch (type) {
16918
- case 'image':
16919
- renderImage(el, asset);
16920
- break;
16921
- case 'text':
16922
- renderText(el, asset);
16923
- break;
16924
- case 'html':
16925
- renderHtml(el, asset);
16926
- break;
16927
- default:
16928
- el.innerHTML = asset.content || String(asset);
16929
- console.warn('[Custard] Unknown asset type:', type);
16930
- }
16931
- }
16932
-
16933
17036
  var root_2$2 = from_html(`<span class="cv-placeholder-label svelte-1ka2eec"> </span>`);
16934
17037
  var root_3$1 = from_html(`<button type="button"></button>`);
16935
- var root_1$3 = from_html(`<div class="cv-toggle-placeholder svelte-1ka2eec" role="group"><!> <div class="cv-state-dots svelte-1ka2eec" role="group" aria-label="Visibility states"></div></div>`);
17038
+ var root_1$4 = from_html(`<div class="cv-toggle-placeholder svelte-1ka2eec" role="group"><!> <div class="cv-state-dots svelte-1ka2eec" role="group" aria-label="Visibility states"></div></div>`);
16936
17039
  var root_4 = from_html(`<div class="cv-toggle-label svelte-1ka2eec"> </div>`);
16937
17040
  var root_6 = from_html(`<button type="button"></button>`);
16938
17041
  var root_5 = from_html(`<div class="cv-state-dots cv-state-dots--floating svelte-1ka2eec" role="group" aria-label="Visibility states"></div>`);
@@ -16941,18 +17044,17 @@
16941
17044
  var root_7 = from_html(`<button type="button" class="cv-expand-btn svelte-1ka2eec"><!></button>`);
16942
17045
  var root$5 = from_html(`<!> <div><!> <!> <div class="cv-toggle-content svelte-1ka2eec"><div class="cv-toggle-inner svelte-1ka2eec"><!></div></div> <!></div>`, 1);
16943
17046
 
16944
- const $$css$6 = {
17047
+ const $$css$7 = {
16945
17048
  hash: 'svelte-1ka2eec',
16946
17049
  code: ':host {display:block;position:relative;z-index:1;overflow:visible;}\n\n /* Host visibility control */:host([hidden]) {display:none;}.cv-toggle-wrapper.svelte-1ka2eec {position:relative;width:100%;transition:all 0.35s cubic-bezier(0.4, 0, 0.2, 1);margin-bottom:4px;}.cv-toggle-wrapper.hidden.svelte-1ka2eec {margin-bottom:0;}.cv-toggle-wrapper.peek-mode.svelte-1ka2eec {margin-bottom:28px;}.cv-toggle-content.svelte-1ka2eec {overflow:hidden;transition:max-height 0.35s cubic-bezier(0.4, 0, 0.2, 1),\n opacity 0.3s ease,\n overflow 0s 0s;}.cv-toggle-inner.svelte-1ka2eec {display:flow-root; /* Ensures margins of children are contained */}\n\n /* Hidden State */.hidden.svelte-1ka2eec .cv-toggle-content:where(.svelte-1ka2eec) {opacity:0;pointer-events:none;}\n\n /* Bordered State */.has-border.svelte-1ka2eec {box-sizing:border-box;border:2px dashed rgba(0, 0, 0, 0.15);border-bottom:none;box-shadow:0 2px 8px rgba(0, 0, 0, 0.05),\n inset 0 -15px 10px -10px rgba(0, 0, 0, 0.1);border-radius:8px 8px 0 0;padding:12px 0 0 0;margin-top:4px;}\n\n /* Visible / Expanded State */.expanded.svelte-1ka2eec .cv-toggle-content:where(.svelte-1ka2eec) {opacity:1;transform:translateY(0);overflow:visible;transition:max-height 0.35s cubic-bezier(0.4, 0, 0.2, 1),\n opacity 0.3s ease,\n overflow 0s 0.35s;}\n\n /* When expanded, complete the border */.has-border.expanded.svelte-1ka2eec {border-bottom:2px dashed rgba(0, 0, 0, 0.15);border-radius:8px;padding-bottom:12px;box-shadow:0 2px 8px rgba(0, 0, 0, 0.05);}\n\n /* Peek State — smoother gradient */.peeking.svelte-1ka2eec .cv-toggle-content:where(.svelte-1ka2eec) {opacity:1;mask-image:linear-gradient(to bottom, black 30%, rgba(0,0,0,0.5) 70%, transparent 100%);-webkit-mask-image:linear-gradient(to bottom, black 30%, rgba(0,0,0,0.5) 70%, transparent 100%);}\n\n /* Label Style */.cv-toggle-label.svelte-1ka2eec {position:absolute;top:-12px;left:0;background:#e0e0e0;color:#333;font-size:0.75rem;font-weight:600;padding:2px 8px;border-radius:4px;z-index:10;pointer-events:auto;box-shadow:0 1px 2px rgba(0, 0, 0, 0.1);}\n\n /* Adjust label position if bordered */.has-border.svelte-1ka2eec .cv-toggle-label:where(.svelte-1ka2eec) {top:-10px;left:0;}\n\n /* Hidden-state placeholder bar */.cv-toggle-placeholder.svelte-1ka2eec {display:flex;align-items:center;justify-content:space-between;padding:4px 8px;min-height:24px;border:1px solid rgba(0, 0, 0, 0.06);border-radius:6px;background:rgba(0, 0, 0, 0.015);margin-bottom:4px;transition:border-color 0.2s ease, background 0.2s ease;}.cv-toggle-placeholder.svelte-1ka2eec:hover {border-color:rgba(0, 0, 0, 0.12);background:rgba(0, 0, 0, 0.025);}.cv-placeholder-label.svelte-1ka2eec {font-size:0.7rem;font-weight:500;color:rgba(0, 0, 0, 0.35);letter-spacing:0.02em;user-select:none;}\n\n /* 3-dot state indicator */.cv-state-dots.svelte-1ka2eec {display:flex;align-items:center;gap:5px;}\n\n /* Floating position (top-right of content) */.cv-state-dots--floating.svelte-1ka2eec {position:absolute;top:-2px;right:0;z-index:10;opacity:0;transition:opacity 0.2s ease;}.cv-toggle-wrapper.svelte-1ka2eec:hover .cv-state-dots--floating:where(.svelte-1ka2eec),\n .cv-state-dots--floating.svelte-1ka2eec:focus-within {opacity:1;}\n\n /* Adjust floating dots when bordered */.has-border.svelte-1ka2eec .cv-state-dots--floating:where(.svelte-1ka2eec) {top:4px;right:8px;}.cv-dot.svelte-1ka2eec {position:relative;width:7px;height:7px;border-radius:50%;border:1.5px solid rgba(0, 0, 0, 0.2);background:transparent;padding:0;cursor:pointer;transition:all 0.15s ease;flex-shrink:0;}\n\n /* Expand tap target to ~20px while keeping dot visually small */.cv-dot.svelte-1ka2eec::before {content:\'\';position:absolute;top:50%;left:50%;width:20px;height:20px;transform:translate(-50%, -50%);border-radius:50%;}.cv-dot.svelte-1ka2eec:hover {border-color:rgba(0, 0, 0, 0.5);transform:scale(1.3);}.cv-dot.active.svelte-1ka2eec {background:var(--cv-primary, #3E84F4);border-color:var(--cv-primary, #3E84F4);}.cv-dot.active.svelte-1ka2eec:hover {transform:scale(1.3);}\n\n /* Expand Button — upgraded to pill style */.cv-expand-btn.svelte-1ka2eec {position:absolute;bottom:-28px;left:50%;transform:translateX(-50%);display:flex;align-items:center;gap:4px;background:rgba(0, 0, 0, 0.04);border:1px solid rgba(0, 0, 0, 0.08);border-radius:999px;padding:3px 12px;cursor:pointer;z-index:100;color:#666;font-size:0.7rem;font-weight:500;font-family:inherit;line-height:1;transition:all 0.2s ease;}.cv-expand-btn.svelte-1ka2eec:hover {background:rgba(0, 0, 0, 0.08);border-color:rgba(0, 0, 0, 0.15);color:#333;transform:translateX(-50%) scale(1.02);}.cv-expand-btn.svelte-1ka2eec svg {display:block;width:14px;height:14px;opacity:0.6;flex-shrink:0;}.cv-expand-btn.svelte-1ka2eec:hover svg {opacity:1;}.cv-expand-label.svelte-1ka2eec {white-space:nowrap;}'
16947
17050
  };
16948
17051
 
16949
17052
  function Toggle($$anchor, $$props) {
16950
17053
  push($$props, true);
16951
- append_styles$1($$anchor, $$css$6);
17054
+ append_styles$1($$anchor, $$css$7);
16952
17055
 
16953
17056
  // Props using Svelte 5 runes
16954
17057
  let toggleId = prop($$props, 'toggleId', 7, ''),
16955
- assetId = prop($$props, 'assetId', 7, ''),
16956
17058
  showPeekBorder = prop($$props, 'showPeekBorder', 7, false),
16957
17059
  showLabel = prop($$props, 'showLabel', 7, false),
16958
17060
  showInlineControl = prop($$props, 'showInlineControl', 7, false),
@@ -16993,6 +17095,8 @@
16993
17095
  if (placeholderId()) elementStore.registerPlaceholder(get(placeholderName));
16994
17096
  });
16995
17097
 
17098
+ let isSiteManaged = user_derived(() => get(toggleConfig)?.siteManaged ?? false);
17099
+
16996
17100
  // Derive label text from config
16997
17101
  let labelText = user_derived(() => {
16998
17102
  if (!get(toggleConfig)) return '';
@@ -17002,7 +17106,6 @@
17002
17106
 
17003
17107
  let localExpanded = state(false);
17004
17108
  let isUnconstrained = state(false /* New state to track if we can release max-height */);
17005
- let hasRendered = state(false);
17006
17109
  let contentEl;
17007
17110
  let innerEl;
17008
17111
  let scrollHeight = state(0);
@@ -17121,14 +17224,6 @@
17121
17224
  get(toggleIds).forEach((id) => activeStateStore.updateToggleState(id, targetState));
17122
17225
  }
17123
17226
 
17124
- // Reactive asset rendering - renders assets when toggle becomes visible
17125
- user_effect(() => {
17126
- if (get(showFullContent) && assetId() && !get(hasRendered) && derivedStore.assetsManager && contentEl) {
17127
- renderAssetInto(contentEl, assetId(), derivedStore.assetsManager);
17128
- set(hasRendered, true);
17129
- }
17130
- });
17131
-
17132
17227
  var $$exports = {
17133
17228
  get toggleId() {
17134
17229
  return toggleId();
@@ -17139,15 +17234,6 @@
17139
17234
  flushSync();
17140
17235
  },
17141
17236
 
17142
- get assetId() {
17143
- return assetId();
17144
- },
17145
-
17146
- set assetId($$value = '') {
17147
- assetId($$value);
17148
- flushSync();
17149
- },
17150
-
17151
17237
  get showPeekBorder() {
17152
17238
  return showPeekBorder();
17153
17239
  },
@@ -17190,7 +17276,7 @@
17190
17276
 
17191
17277
  {
17192
17278
  var consequent_1 = ($$anchor) => {
17193
- var div = root_1$3();
17279
+ var div = root_1$4();
17194
17280
  var node_1 = child(div);
17195
17281
 
17196
17282
  {
@@ -17238,7 +17324,7 @@
17238
17324
  };
17239
17325
 
17240
17326
  if_block(node, ($$render) => {
17241
- if (showInlineControl() && !get(isPlaceholderMode) && get(isHidden)) $$render(consequent_1);
17327
+ if (showInlineControl() && !get(isPlaceholderMode) && !get(isSiteManaged) && get(isHidden)) $$render(consequent_1);
17242
17328
  });
17243
17329
  }
17244
17330
 
@@ -17293,7 +17379,7 @@
17293
17379
  };
17294
17380
 
17295
17381
  if_block(node_3, ($$render) => {
17296
- if (showInlineControl() && !get(isPlaceholderMode) && !get(isHidden)) $$render(consequent_3);
17382
+ if (showInlineControl() && !get(isPlaceholderMode) && !get(isSiteManaged) && !get(isHidden)) $$render(consequent_3);
17297
17383
  });
17298
17384
  }
17299
17385
 
@@ -17361,7 +17447,7 @@
17361
17447
  'peek-mode': get(peekState),
17362
17448
  hidden: get(isHidden),
17363
17449
  'has-border': showPeekBorder() && get(peekState),
17364
- 'has-inline-control': showInlineControl() && !get(isPlaceholderMode)
17450
+ 'has-inline-control': showInlineControl() && !get(isPlaceholderMode) && !get(isSiteManaged)
17365
17451
  });
17366
17452
 
17367
17453
  styles = set_style(div_5, '', styles, { 'max-height': get(currentMaxHeight) });
@@ -17379,7 +17465,6 @@
17379
17465
  Toggle,
17380
17466
  {
17381
17467
  toggleId: { attribute: 'toggle-id', reflect: true, type: 'String' },
17382
- assetId: { attribute: 'asset-id', reflect: true, type: 'String' },
17383
17468
 
17384
17469
  showPeekBorder: {
17385
17470
  attribute: 'show-peek-border',
@@ -17404,14 +17489,14 @@
17404
17489
 
17405
17490
  var root$4 = from_html(`<div><!></div>`);
17406
17491
 
17407
- const $$css$5 = {
17492
+ const $$css$6 = {
17408
17493
  hash: 'svelte-8qj5x2',
17409
17494
  code: ':host {display:block;}:host(.cv-hidden) {display:none !important;}:host(.cv-visible) {display:block !important;}:host([active=\'true\']) {display:block;}.cv-tab-content.svelte-8qj5x2 {display:none;\n animation: svelte-8qj5x2-fade-in 0.2s ease-in-out;padding-top:1rem;padding-bottom:0.5rem;padding-left:0;padding-right:0;}.cv-tab-content.active.svelte-8qj5x2 {display:block;}\n\n /* Hide cv-tab-header source element; content is extracted to nav link */.svelte-8qj5x2::slotted(cv-tab-header) {display:none !important;}\n\n /* Allow cv-tab-body to flow naturally */.svelte-8qj5x2::slotted(cv-tab-body) {display:block;}\n\n @keyframes svelte-8qj5x2-fade-in {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n }'
17410
17495
  };
17411
17496
 
17412
17497
  function Tab($$anchor, $$props) {
17413
17498
  push($$props, true);
17414
- append_styles$1($$anchor, $$css$5);
17499
+ append_styles$1($$anchor, $$css$6);
17415
17500
 
17416
17501
  // Props using Svelte 5 runes
17417
17502
  // tabId and header are used in TabGroup directly.
@@ -17453,7 +17538,7 @@
17453
17538
  true
17454
17539
  ));
17455
17540
 
17456
- var root_1$2 = from_svg(`<path d="M4 2v13l4-2.5L12 15V2H4z"></path>`);
17541
+ var root_1$3 = from_svg(`<path d="M4 2v13l4-2.5L12 15V2H4z"></path>`);
17457
17542
  var root_2$1 = from_svg(`<path d="M4 2v13l4-2.5L12 15V2H4zm1 1h6v10.5l-3-1.88L5 13.5V3z"></path>`);
17458
17543
  var root$3 = from_svg(`<svg><!></svg>`);
17459
17544
 
@@ -17480,7 +17565,7 @@
17480
17565
 
17481
17566
  {
17482
17567
  var consequent = ($$anchor) => {
17483
- var path = root_1$2();
17568
+ var path = root_1$3();
17484
17569
 
17485
17570
  append($$anchor, path);
17486
17571
  };
@@ -17501,18 +17586,18 @@
17501
17586
  }
17502
17587
 
17503
17588
  var root_2 = from_html(`<li class="cv-tabgroup-item svelte-1ujqpe3"><div><a role="tab" title="Double-click a tab to 'mark' it in all similar tab groups."><span class="cv-tab-header-text svelte-1ujqpe3"><!></span></a> <button type="button"><!></button></div></li>`);
17504
- var root_1$1 = from_html(`<ul class="cv-tabgroup-nav svelte-1ujqpe3" role="tablist"></ul>`);
17589
+ var root_1$2 = from_html(`<ul class="cv-tabgroup-nav svelte-1ujqpe3" role="tablist"></ul>`);
17505
17590
  var root_3 = from_html(`<link rel="stylesheet"/>`);
17506
17591
  var root$2 = from_html(`<div class="cv-tabgroup-container"><!> <!> <div class="cv-tabgroup-content"><!></div> <div class="cv-tabgroup-bottom-border svelte-1ujqpe3"></div></div>`);
17507
17592
 
17508
- const $$css$4 = {
17593
+ const $$css$5 = {
17509
17594
  hash: 'svelte-1ujqpe3',
17510
17595
  code: ':host {display:block;margin-bottom:24px;}\n\n /* Tab navigation styles */ul.cv-tabgroup-nav.svelte-1ujqpe3 {display:flex;flex-wrap:wrap;padding-left:0;margin-top:0.5rem;margin-bottom:0;list-style:none;border-bottom:1px solid var(--cv-border, rgba(128, 128, 128, 0.3));align-items:stretch;gap:0.5rem;}.cv-tabgroup-item.svelte-1ujqpe3 {margin-bottom:-1px;list-style:none;display:flex;align-items:stretch;}.cv-tabgroup-link.svelte-1ujqpe3 {display:flex;align-items:center;justify-content:center;padding:0.5rem 0.75rem;color:inherit;opacity:0.7;text-decoration:none;background-color:transparent !important;border:none;border-bottom:2px solid transparent;transition:opacity 0.15s ease-in-out,\n border-color 0.15s ease-in-out;cursor:pointer;min-height:2.5rem;box-sizing:border-box;font-weight:500;}.cv-tabgroup-link.svelte-1ujqpe3 p {margin:0;display:inline;}.cv-tabgroup-link.svelte-1ujqpe3:hover,\n .cv-tabgroup-link.svelte-1ujqpe3:focus {opacity:1;border-bottom-color:var(--cv-border, rgba(128, 128, 128, 0.3));isolation:isolate;}.cv-tabgroup-link.active.svelte-1ujqpe3 {opacity:1;background-color:transparent !important;}.cv-tabgroup-link.svelte-1ujqpe3:focus {outline:0;}.cv-tab-wrapper.svelte-1ujqpe3 {display:flex;align-items:center;border-bottom:2px solid transparent;transition:border-color 0.15s ease-in-out;}.cv-tab-wrapper.svelte-1ujqpe3:hover,\n .cv-tab-wrapper.svelte-1ujqpe3:focus-within {border-bottom-color:var(--cv-border, rgba(128, 128, 128, 0.3));}.cv-tab-wrapper.active.svelte-1ujqpe3 {border-bottom-color:currentColor;}.cv-tab-header-text.svelte-1ujqpe3 {line-height:1;}.cv-tab-marked-icon.svelte-1ujqpe3 {display:inline-flex;align-items:center;justify-content:center;line-height:0;flex-shrink:0;opacity:0;transition:opacity 0.15s ease-out;background:none;border:none;padding:0 8px 0 0;margin:0;cursor:pointer;color:inherit;height:100%;}.cv-tab-wrapper.svelte-1ujqpe3:hover .cv-tab-marked-icon:where(.svelte-1ujqpe3),\n .cv-tab-wrapper.svelte-1ujqpe3:focus-within .cv-tab-marked-icon:where(.svelte-1ujqpe3),\n .cv-tab-marked-icon.is-marked.svelte-1ujqpe3 {opacity:1;}.cv-tab-marked-icon.svelte-1ujqpe3 svg {vertical-align:middle;width:14px;height:14px;}.cv-tabgroup-bottom-border.svelte-1ujqpe3 {border-bottom:1px solid var(--cv-border, rgba(128, 128, 128, 0.3));}\n\n @media print {ul.cv-tabgroup-nav.svelte-1ujqpe3 {display:none !important;}\n }'
17511
17596
  };
17512
17597
 
17513
17598
  function TabGroup($$anchor, $$props) {
17514
17599
  push($$props, true);
17515
- append_styles$1($$anchor, $$css$4);
17600
+ append_styles$1($$anchor, $$css$5);
17516
17601
 
17517
17602
  // ID of the tabgroup Group
17518
17603
  let groupId = prop($$props, 'groupId', 7),
@@ -17745,7 +17830,7 @@
17745
17830
 
17746
17831
  {
17747
17832
  var consequent = ($$anchor) => {
17748
- var ul = root_1$1();
17833
+ var ul = root_1$2();
17749
17834
 
17750
17835
  each(ul, 21, () => get(tabs), (tab) => tab.id, ($$anchor, tab) => {
17751
17836
  const splitIds = user_derived(() => splitTabIds(get(tab).rawId));
@@ -17855,13 +17940,13 @@
17855
17940
  true
17856
17941
  ));
17857
17942
 
17858
- const $$css$3 = {
17943
+ const $$css$4 = {
17859
17944
  hash: 'svelte-1hl11lz',
17860
17945
  code: ':host {display:none; /* Semantic container only, usually read by parent and hidden */}'
17861
17946
  };
17862
17947
 
17863
17948
  function TabHeader($$anchor, $$props) {
17864
- append_styles$1($$anchor, $$css$3);
17949
+ append_styles$1($$anchor, $$css$4);
17865
17950
 
17866
17951
  var fragment = comment();
17867
17952
  var node = first_child(fragment);
@@ -17873,10 +17958,10 @@
17873
17958
 
17874
17959
  customElements.define('cv-tab-header', create_custom_element(TabHeader, {}, ['default'], [], true));
17875
17960
 
17876
- const $$css$2 = { hash: 'svelte-eizj8y', code: ':host {display:block;}' };
17961
+ const $$css$3 = { hash: 'svelte-eizj8y', code: ':host {display:block;}' };
17877
17962
 
17878
17963
  function TabBody($$anchor, $$props) {
17879
- append_styles$1($$anchor, $$css$2);
17964
+ append_styles$1($$anchor, $$css$3);
17880
17965
 
17881
17966
  var fragment = comment();
17882
17967
  var node = first_child(fragment);
@@ -17889,11 +17974,11 @@
17889
17974
  customElements.define('cv-tab-body', create_custom_element(TabBody, {}, ['default'], [], true));
17890
17975
 
17891
17976
  var root$1 = from_html(`<span class="cv-var"> </span>`);
17892
- const $$css$1 = { hash: 'svelte-1tffxwo', code: ':host {display:inline;}' };
17977
+ const $$css$2 = { hash: 'svelte-1tffxwo', code: ':host {display:inline;}' };
17893
17978
 
17894
17979
  function Placeholder($$anchor, $$props) {
17895
17980
  push($$props, true);
17896
- append_styles$1($$anchor, $$css$1);
17981
+ append_styles$1($$anchor, $$css$2);
17897
17982
 
17898
17983
  let name = prop($$props, 'name', 7),
17899
17984
  fallback = prop($$props, 'fallback', 7),
@@ -18017,17 +18102,17 @@
18017
18102
  true
18018
18103
  ));
18019
18104
 
18020
- var root_1 = from_html(`<label class="placeholder-label svelte-dpk3ag"> </label>`);
18105
+ var root_1$1 = from_html(`<label class="placeholder-label svelte-dpk3ag"> </label>`);
18021
18106
  var root = from_html(`<div><!> <input type="text"/></div>`);
18022
18107
 
18023
- const $$css = {
18108
+ const $$css$1 = {
18024
18109
  hash: 'svelte-dpk3ag',
18025
18110
  code: ':host {display:inline-block;width:auto;margin:0 0.25rem; /* Add breathing room for inline text */}\n\n /* Host display overrides based on layout */:host([layout=\'stacked\']),\n :host([layout=\'horizontal\']) {display:block;width:100%;margin:0 0 0.5rem 0; /* Reset margins for block layouts */}\n \n /* Wrapper Grid/Flex Layouts */.cv-input-wrapper.svelte-dpk3ag {display:flex;width:100%;box-sizing:border-box;}\n\n /* INLINE */.cv-input-wrapper.inline.svelte-dpk3ag {display:inline-block;width:auto;}\n\n /* STACKED */.cv-input-wrapper.stacked.svelte-dpk3ag {flex-direction:column;gap:0.25rem;}\n\n /* HORIZONTAL */.cv-input-wrapper.horizontal.svelte-dpk3ag {flex-direction:row;align-items:center;gap:0.75rem;}\n\n /* Label Styles */.placeholder-label.svelte-dpk3ag {font-size:0.85rem;font-weight:500;color:var(--cv-text, #333);white-space:nowrap;}.stacked.svelte-dpk3ag .placeholder-label:where(.svelte-dpk3ag) {margin-bottom:2px;width:100%; /* Ensure label context is full width */text-align:left; /* Reset text align */}\n\n /* Input Styles */.placeholder-input.svelte-dpk3ag {padding:0.5rem 0.75rem;border:1px solid var(--cv-input-border, rgba(0, 0, 0, 0.1));border-radius:0.375rem;font-size:0.9rem;transition:all 0.2s;background:var(--cv-input-bg, white);color:var(--cv-text, #333);box-sizing:border-box;width:100%;}.stacked.svelte-dpk3ag .placeholder-input:where(.svelte-dpk3ag) {width:100%;}.inline.svelte-dpk3ag .placeholder-input:where(.svelte-dpk3ag) {width:var(--cv-input-width, auto);padding:0.3rem 0.5rem;display:inline-block;text-align:center;}.horizontal.svelte-dpk3ag .placeholder-input:where(.svelte-dpk3ag) {width:var(--cv-input-width, auto);flex:1;}\n\n /* APPEARANCES */\n \n /* Outline (Default) - handled by base styles above */\n\n /* Underline */.placeholder-input.underline.svelte-dpk3ag {border:none;border-bottom:1px solid var(--cv-input-border, rgba(0, 0, 0, 0.2));border-radius:0;background:transparent;padding-left:0;padding-right:0;}.placeholder-input.underline.svelte-dpk3ag:focus {box-shadow:none;border-bottom-color:var(--cv-primary, #3e84f4);}\n\n /* Ghost */.placeholder-input.ghost.svelte-dpk3ag {border-color:transparent;background:transparent;}.placeholder-input.ghost.svelte-dpk3ag:hover {background:var(--cv-input-bg-hover, rgba(0,0,0,0.05));}.placeholder-input.ghost.svelte-dpk3ag:focus {background:var(--cv-input-bg, white);border-color:var(--cv-primary, #3e84f4);box-shadow:0 0 0 2px var(--cv-focus-ring, rgba(62, 132, 244, 0.2));}\n\n /* Focus states for standard inputs */.placeholder-input.svelte-dpk3ag:not(.underline):focus {outline:none;border-color:var(--cv-primary, #3e84f4);box-shadow:0 0 0 2px var(--cv-focus-ring, rgba(62, 132, 244, 0.2));}'
18026
18111
  };
18027
18112
 
18028
18113
  function PlaceholderInput($$anchor, $$props) {
18029
18114
  push($$props, true);
18030
- append_styles$1($$anchor, $$css);
18115
+ append_styles$1($$anchor, $$css$1);
18031
18116
 
18032
18117
  let name = prop($$props, 'name', 7),
18033
18118
  label = prop($$props, 'label', 7),
@@ -18139,7 +18224,7 @@
18139
18224
 
18140
18225
  {
18141
18226
  var consequent = ($$anchor) => {
18142
- var label_1 = root_1();
18227
+ var label_1 = root_1$1();
18143
18228
  var text = child(label_1, true);
18144
18229
 
18145
18230
  reset(label_1);
@@ -18200,6 +18285,178 @@
18200
18285
  true
18201
18286
  ));
18202
18287
 
18288
+ /**
18289
+ * Single-letter shorthand color aliases.
18290
+ * Each maps to a light-theme and dark-theme hex value.
18291
+ */
18292
+ const SHORTHAND_COLORS = {
18293
+ r: { light: '#fca5a5', dark: '#dc2626' }, // Pale red / Deep red
18294
+ g: { light: '#4ade80', dark: '#16a34a' }, // Neon green / Forest green
18295
+ b: { light: '#93c5fd', dark: '#2563eb' }, // Light blue / Royal blue
18296
+ c: { light: '#67e8f9', dark: '#0d9488' }, // Aqua cyan / Teal cyan
18297
+ m: { light: '#f0abfc', dark: '#a21caf' }, // Bright magenta / Deep magenta
18298
+ y: { light: '#fde047', dark: '#92400e' }, // Bright yellow / Golden yellow
18299
+ w: { light: '#f1f5f9', dark: '#94a3b8' }, // White / Silver grey
18300
+ k: { light: '#e2e8f0', dark: '#0f172a' }, // Light grey / Pure black
18301
+ };
18302
+ /**
18303
+ * Resolves a color value, expanding single-letter shorthands to hex.
18304
+ * Pass `isDark=true` to get the dark-theme variant of a shorthand.
18305
+ * Non-shorthand values are returned unchanged.
18306
+ */
18307
+ function resolveColor(color, isDark) {
18308
+ const trimmed = color.trim();
18309
+ const entry = SHORTHAND_COLORS[trimmed];
18310
+ if (entry)
18311
+ return isDark ? entry.dark : entry.light;
18312
+ return trimmed;
18313
+ }
18314
+ /**
18315
+ * Computes a contrasting text color (black or white) for a given background color.
18316
+ * Supports #rrggbb and #rgb hex strings. Falls back to white for non-hex inputs.
18317
+ */
18318
+ function computeTextColor(bgColor) {
18319
+ const hex = bgColor.replace(/^#/, '');
18320
+ let r, g, b;
18321
+ if (hex.length === 3) {
18322
+ r = parseInt((hex[0] ?? '') + (hex[0] ?? ''), 16);
18323
+ g = parseInt((hex[1] ?? '') + (hex[1] ?? ''), 16);
18324
+ b = parseInt((hex[2] ?? '') + (hex[2] ?? ''), 16);
18325
+ }
18326
+ else if (hex.length === 6) {
18327
+ r = parseInt(hex.slice(0, 2), 16);
18328
+ g = parseInt(hex.slice(2, 4), 16);
18329
+ b = parseInt(hex.slice(4, 6), 16);
18330
+ }
18331
+ else {
18332
+ return '#ffffff';
18333
+ }
18334
+ const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
18335
+ return luminance > 0.5 ? '#000000' : '#ffffff';
18336
+ }
18337
+
18338
+ var root_1 = from_html(`<span class="cv-label svelte-10fc57e"><!></span>`);
18339
+
18340
+ const $$css = {
18341
+ hash: 'svelte-10fc57e',
18342
+ code: ':host {display:inline;}.cv-label.svelte-10fc57e {display:inline-flex;align-items:center;padding:1px 8px;border-radius:999px;font-size:0.75em;font-weight:600;white-space:nowrap;line-height:1.5;letter-spacing:0.02em;vertical-align:middle;}'
18343
+ };
18344
+
18345
+ function Label($$anchor, $$props) {
18346
+ push($$props, true);
18347
+ append_styles$1($$anchor, $$css);
18348
+
18349
+ let name = prop($$props, 'name', 7, ''),
18350
+ color = prop($$props, 'color', 7, '');
18351
+
18352
+ const DEFAULT_COLOR = '#6b7280';
18353
+ let hasSlotContent = state(false);
18354
+
18355
+ user_effect(() => {
18356
+ const host = $$props.$$host;
18357
+
18358
+ function update() {
18359
+ set(hasSlotContent, Array.from(host.childNodes).some((n) => n.nodeType === Node.ELEMENT_NODE || (n.textContent?.trim() ?? '') !== ''), true);
18360
+ }
18361
+
18362
+ update();
18363
+
18364
+ const observer = new MutationObserver(update);
18365
+
18366
+ observer.observe(host, { childList: true, characterData: true, subtree: true });
18367
+
18368
+ return () => observer.disconnect();
18369
+ });
18370
+
18371
+ let labelDef = user_derived(() => labelRegistryStore.get(name()));
18372
+ let rawColor = user_derived(() => get(labelDef)?.color ?? (color() || DEFAULT_COLOR));
18373
+ let bgColor = user_derived(() => resolveColor(get(rawColor), colorSchemeStore.isDark));
18374
+ let textColor = user_derived(() => computeTextColor(get(bgColor)));
18375
+
18376
+ var $$exports = {
18377
+ get name() {
18378
+ return name();
18379
+ },
18380
+
18381
+ set name($$value = '') {
18382
+ name($$value);
18383
+ flushSync();
18384
+ },
18385
+
18386
+ get color() {
18387
+ return color();
18388
+ },
18389
+
18390
+ set color($$value = '') {
18391
+ color($$value);
18392
+ flushSync();
18393
+ }
18394
+ };
18395
+
18396
+ var fragment = comment();
18397
+ var node = first_child(fragment);
18398
+
18399
+ {
18400
+ var consequent_1 = ($$anchor) => {
18401
+ var span = root_1();
18402
+ let styles;
18403
+ var node_1 = child(span);
18404
+
18405
+ {
18406
+ var consequent = ($$anchor) => {
18407
+ var text$1 = text();
18408
+
18409
+ template_effect(() => set_text(text$1, get(labelDef).value));
18410
+ append($$anchor, text$1);
18411
+ };
18412
+
18413
+ var alternate = ($$anchor) => {
18414
+ var fragment_2 = comment();
18415
+ var node_2 = first_child(fragment_2);
18416
+
18417
+ slot(node_2, $$props, 'default', {});
18418
+ append($$anchor, fragment_2);
18419
+ };
18420
+
18421
+ if_block(node_1, ($$render) => {
18422
+ if (get(labelDef)?.value) $$render(consequent); else $$render(alternate, false);
18423
+ });
18424
+ }
18425
+
18426
+ reset(span);
18427
+ template_effect(() => styles = set_style(span, '', styles, { background: get(bgColor), color: get(textColor) }));
18428
+ append($$anchor, span);
18429
+ };
18430
+
18431
+ var alternate_1 = ($$anchor) => {
18432
+ var fragment_3 = comment();
18433
+ var node_3 = first_child(fragment_3);
18434
+
18435
+ slot(node_3, $$props, 'default', {});
18436
+ append($$anchor, fragment_3);
18437
+ };
18438
+
18439
+ if_block(node, ($$render) => {
18440
+ if (get(labelDef)?.value || get(hasSlotContent)) $$render(consequent_1); else $$render(alternate_1, false);
18441
+ });
18442
+ }
18443
+
18444
+ append($$anchor, fragment);
18445
+
18446
+ return pop($$exports);
18447
+ }
18448
+
18449
+ customElements.define('cv-label', create_custom_element(
18450
+ Label,
18451
+ {
18452
+ name: { attribute: 'name', reflect: true, type: 'String' },
18453
+ color: { attribute: 'color', reflect: true, type: 'String' }
18454
+ },
18455
+ ['default'],
18456
+ [],
18457
+ true
18458
+ ));
18459
+
18203
18460
  // --- No Public API Exports ---
18204
18461
  // The script auto-initializes via initializeFromScript().
18205
18462
  /**
@@ -18225,7 +18482,7 @@
18225
18482
  // Fetch Config first to retrieve storageKey prefix
18226
18483
  const configFile = await fetchConfig(configPath, baseURL);
18227
18484
  // Determine effective baseURL (data attribute takes precedence)
18228
- const effectiveBaseURL = baseURL || configFile.baseUrl || '';
18485
+ const effectiveBaseURL = baseURL;
18229
18486
  // Initialize Adaptation early (before AppRuntime):
18230
18487
  // - Theme CSS injected ASAP (FOUC prevention)
18231
18488
  // - ?adapt= param cleaned before URLStateManager.parseURL() runs
@@ -18234,24 +18491,7 @@
18234
18491
  if (adaptationConfig?.id) {
18235
18492
  AdaptationManager.rewriteUrlIndicator(adaptationConfig.id);
18236
18493
  }
18237
- // Initialize Assets
18238
- let assetsManager;
18239
- if (configFile.assetsJsonPath) {
18240
- const assetsPath = prependBaseUrl(configFile.assetsJsonPath, effectiveBaseURL);
18241
- try {
18242
- const assetsJson = await (await fetch(assetsPath)).json();
18243
- assetsManager = new AssetsManager(assetsJson, effectiveBaseURL);
18244
- }
18245
- catch (error) {
18246
- console.error(`[Custard] Failed to load assets JSON from ${assetsPath}:`, error);
18247
- assetsManager = new AssetsManager({}, effectiveBaseURL);
18248
- }
18249
- }
18250
- else {
18251
- assetsManager = new AssetsManager({}, effectiveBaseURL);
18252
- }
18253
18494
  const coreOptions = {
18254
- assetsManager,
18255
18495
  configFile,
18256
18496
  rootEl: document.body,
18257
18497
  storageKey: configFile.storageKey,