@invisibleloop/pulse 0.20.10 → 0.20.19

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 (266) hide show
  1. package/package.json +6 -5
  2. package/public/.pulse-ui-version +1 -1
  3. package/scripts/build.js +26 -0
  4. package/scripts/release-version.js +17 -8
  5. package/scripts/strip-server.test.js +60 -0
  6. package/src/agent/checklist.md +8 -12
  7. package/src/agent/commands/pulse-start.md +2 -0
  8. package/src/agent/commands/verify.md +30 -8
  9. package/src/agent/guide-components.md +7 -25
  10. package/src/agent/guide-routing.md +0 -12
  11. package/src/agent/guide-server.md +102 -1
  12. package/src/agent/guide-spec.md +8 -1
  13. package/src/agent/guide-styles.md +23 -0
  14. package/src/agent/identity.md +9 -0
  15. package/src/agent/workflow.md +4 -23
  16. package/src/cli/check-bundles.js +113 -0
  17. package/src/cli/check-bundles.test.js +137 -0
  18. package/src/cli/cli.test.js +105 -0
  19. package/src/cli/dev.js +48 -3
  20. package/src/cli/diagnose.js +105 -0
  21. package/src/cli/diagnose.test.js +124 -0
  22. package/src/cli/discover.js +9 -2
  23. package/src/cli/index.js +171 -9
  24. package/src/cli/process-utils.js +57 -0
  25. package/src/cli/process-utils.test.js +114 -0
  26. package/src/cli/review.js +391 -0
  27. package/src/cli/review.test.js +143 -0
  28. package/src/cli/scaffold.js +9 -4
  29. package/src/cli/stamp.js +102 -0
  30. package/src/cli/stamp.test.js +149 -0
  31. package/src/cli/start.js +15 -2
  32. package/src/cli/validate.js +184 -0
  33. package/src/cli/validate.test.js +153 -0
  34. package/src/mcp/mcp.test.js +261 -30
  35. package/src/mcp/server.js +207 -515
  36. package/src/mcp/validate-worker.js +18 -1
  37. package/src/runtime/index.js +144 -11
  38. package/src/runtime/runtime.test.js +199 -0
  39. package/src/runtime/ssr.js +14 -7
  40. package/src/runtime/ssr.test.js +31 -0
  41. package/src/server/index.js +163 -10
  42. package/src/server/server.test.js +225 -2
  43. package/src/spec/schema.js +1 -1
  44. package/src/ui/index.js +0 -5
  45. package/src/ui/ui.test.js +0 -75
  46. package/types/ui.d.ts +0 -21
  47. package/.claude/commands/build-page.md +0 -63
  48. package/.claude/commands/new-doc-page.md +0 -45
  49. package/.claude/commands/pulse-start.md +0 -11
  50. package/.claude/commands/pulse-stop.md +0 -5
  51. package/.claude/commands/verify.md +0 -118
  52. package/.claude/pulse-checklist.md +0 -189
  53. package/.github/workflows/ci.yml +0 -25
  54. package/.github/workflows/publish.yml +0 -45
  55. package/.pulse/load-reports/home/1773432711417.json +0 -22
  56. package/.pulse-verified +0 -1
  57. package/AGENTS.md +0 -697
  58. package/CLAUDE.md +0 -697
  59. package/README.md +0 -95
  60. package/benchmark/public/dist/counter.boot-PO6SNSJZ.js +0 -8
  61. package/benchmark/public/dist/home.boot-YK6VAEQI.js +0 -6
  62. package/benchmark/public/dist/manifest.json +0 -5
  63. package/benchmark/public/dist/runtime-5Y7ESAIB.js +0 -2
  64. package/benchmark/public/dist/runtime-KO4BHUQ3.js +0 -49
  65. package/benchmark/results/2026-03-27-09-20.json +0 -188
  66. package/benchmark/results/2026-03-27-09-21.json +0 -188
  67. package/benchmark/results/2026-03-27-09-29.json +0 -188
  68. package/benchmark/results/2026-03-27-10-14.json +0 -188
  69. package/benchmark/results/2026-03-27-10-18.json +0 -232
  70. package/benchmark/src/pages/counter.js +0 -37
  71. package/benchmark/src/pages/home.js +0 -21
  72. package/docs/.claude/commands/pulse-build.md +0 -7
  73. package/docs/.claude/commands/pulse-dev.md +0 -7
  74. package/docs/.claude/commands/pulse-start.md +0 -9
  75. package/docs/.claude/commands/pulse-stop.md +0 -5
  76. package/docs/.claude/commands/verify.md +0 -84
  77. package/docs/.claude/coverage-check.js +0 -196
  78. package/docs/.claude/pulse-checklist.md +0 -189
  79. package/docs/public/.pulse-ui-version +0 -1
  80. package/docs/public/andypixel.css +0 -237
  81. package/docs/public/bookworms.css +0 -422
  82. package/docs/public/coutumes.css +0 -561
  83. package/docs/public/docs.css +0 -2388
  84. package/docs/public/favicon.svg +0 -4
  85. package/docs/public/home-brut.css +0 -982
  86. package/docs/public/images/bookworms/hero-books.jpg +0 -0
  87. package/docs/public/images/bookworms/how-we-read-now.jpg +0 -0
  88. package/docs/public/images/bookworms/lantern-keepers.jpg +0 -0
  89. package/docs/public/images/bookworms/provincial-bookseller.jpg +0 -0
  90. package/docs/public/images/journey/178.jpg +0 -0
  91. package/docs/public/images/journey/29.jpg +0 -0
  92. package/docs/public/images/retrowheels/hero.jpg +0 -0
  93. package/docs/public/images/shop/devbook-pro.jpg +0 -0
  94. package/docs/public/images/suzukibento/1015.jpg +0 -0
  95. package/docs/public/images/suzukibento/1025.jpg +0 -0
  96. package/docs/public/images/suzukibento/1043.jpg +0 -0
  97. package/docs/public/images/suzukibento/1059.jpg +0 -0
  98. package/docs/public/images/suzukibento/1062.jpg +0 -0
  99. package/docs/public/images/suzukibento/1084.jpg +0 -0
  100. package/docs/public/images/swish/1469334031218-e382a71b716b.jpg +0 -0
  101. package/docs/public/images/swish/1483985988355-763728e1935b.jpg +0 -0
  102. package/docs/public/images/swish/1485518882345-15568b007407.jpg +0 -0
  103. package/docs/public/images/swish/1490481651871-ab68de25d43d.jpg +0 -0
  104. package/docs/public/images/swish/1496747611176-843222e1e57c.jpg +0 -0
  105. package/docs/public/images/swish/1502716119720-b23a93e5fe1b.jpg +0 -0
  106. package/docs/public/images/swish/1539109136881-3be0616acf4b.jpg +0 -0
  107. package/docs/public/images/swish/1551803091-e20673f15770.jpg +0 -0
  108. package/docs/public/images/swish/1558769132-cb1aea458c5e.jpg +0 -0
  109. package/docs/public/images/swish/1572804013309-59a88b7e92f1.jpg +0 -0
  110. package/docs/public/img/heic1501a.jpg +0 -0
  111. package/docs/public/img/placeholder-landscape.svg +0 -18
  112. package/docs/public/img/placeholder-square.svg +0 -14
  113. package/docs/public/img/placeholder-tall.svg +0 -22
  114. package/docs/public/journey.css +0 -528
  115. package/docs/public/menu.js +0 -327
  116. package/docs/public/pulse-ui.css +0 -3028
  117. package/docs/public/pulse-ui.js +0 -257
  118. package/docs/public/retrowheels.css +0 -425
  119. package/docs/public/shop.css +0 -269
  120. package/docs/public/sparkys.css +0 -382
  121. package/docs/public/suzukibento.css +0 -517
  122. package/docs/public/swish.css +0 -394
  123. package/docs/public/theme.css +0 -52
  124. package/docs/public/themes/lumio.css +0 -50
  125. package/docs/public/themes/observatory.css +0 -104
  126. package/docs/server.js +0 -161
  127. package/docs/src/lib/component-page.js +0 -49
  128. package/docs/src/lib/highlight.js +0 -2
  129. package/docs/src/lib/layout.js +0 -190
  130. package/docs/src/lib/metrics-store.js +0 -6
  131. package/docs/src/lib/nav.js +0 -171
  132. package/docs/src/lib/stats.js +0 -81
  133. package/docs/src/pages/accessibility.js +0 -157
  134. package/docs/src/pages/actions.js +0 -194
  135. package/docs/src/pages/auth.js +0 -176
  136. package/docs/src/pages/caching.js +0 -95
  137. package/docs/src/pages/components/accordion.js +0 -48
  138. package/docs/src/pages/components/alert.js +0 -35
  139. package/docs/src/pages/components/app-badge.js +0 -41
  140. package/docs/src/pages/components/avatar.js +0 -35
  141. package/docs/src/pages/components/badge.js +0 -36
  142. package/docs/src/pages/components/breadcrumbs.js +0 -94
  143. package/docs/src/pages/components/button.js +0 -84
  144. package/docs/src/pages/components/card.js +0 -225
  145. package/docs/src/pages/components/charts.js +0 -283
  146. package/docs/src/pages/components/checkbox.js +0 -129
  147. package/docs/src/pages/components/cluster.js +0 -47
  148. package/docs/src/pages/components/container.js +0 -40
  149. package/docs/src/pages/components/cta.js +0 -54
  150. package/docs/src/pages/components/divider.js +0 -37
  151. package/docs/src/pages/components/empty.js +0 -36
  152. package/docs/src/pages/components/feature.js +0 -60
  153. package/docs/src/pages/components/fieldset.js +0 -65
  154. package/docs/src/pages/components/fileupload.js +0 -128
  155. package/docs/src/pages/components/footer.js +0 -58
  156. package/docs/src/pages/components/grid.js +0 -169
  157. package/docs/src/pages/components/heading.js +0 -107
  158. package/docs/src/pages/components/hero.js +0 -123
  159. package/docs/src/pages/components/icons.js +0 -410
  160. package/docs/src/pages/components/image.js +0 -71
  161. package/docs/src/pages/components/input.js +0 -51
  162. package/docs/src/pages/components/media.js +0 -51
  163. package/docs/src/pages/components/modal.js +0 -111
  164. package/docs/src/pages/components/nav.js +0 -136
  165. package/docs/src/pages/components/phone-frame.js +0 -74
  166. package/docs/src/pages/components/progress.js +0 -102
  167. package/docs/src/pages/components/prose.js +0 -111
  168. package/docs/src/pages/components/pullquote.js +0 -71
  169. package/docs/src/pages/components/radio.js +0 -194
  170. package/docs/src/pages/components/rating.js +0 -106
  171. package/docs/src/pages/components/search.js +0 -61
  172. package/docs/src/pages/components/section.js +0 -98
  173. package/docs/src/pages/components/segmented.js +0 -121
  174. package/docs/src/pages/components/select.js +0 -45
  175. package/docs/src/pages/components/slider.js +0 -115
  176. package/docs/src/pages/components/spinner.js +0 -73
  177. package/docs/src/pages/components/stack.js +0 -48
  178. package/docs/src/pages/components/stat.js +0 -55
  179. package/docs/src/pages/components/stepper.js +0 -66
  180. package/docs/src/pages/components/table.js +0 -45
  181. package/docs/src/pages/components/textarea.js +0 -31
  182. package/docs/src/pages/components/toggle.js +0 -84
  183. package/docs/src/pages/components/tooltip.js +0 -48
  184. package/docs/src/pages/components.js +0 -200
  185. package/docs/src/pages/config.js +0 -193
  186. package/docs/src/pages/constraints.js +0 -100
  187. package/docs/src/pages/deploy.js +0 -233
  188. package/docs/src/pages/error-pages.js +0 -99
  189. package/docs/src/pages/extending.js +0 -198
  190. package/docs/src/pages/faq.js +0 -99
  191. package/docs/src/pages/forms.js +0 -129
  192. package/docs/src/pages/getting-started.js +0 -147
  193. package/docs/src/pages/guard.js +0 -120
  194. package/docs/src/pages/home.js +0 -345
  195. package/docs/src/pages/how-it-works.js +0 -244
  196. package/docs/src/pages/hydration.js +0 -108
  197. package/docs/src/pages/images.js +0 -120
  198. package/docs/src/pages/markdown.js +0 -156
  199. package/docs/src/pages/meta.js +0 -193
  200. package/docs/src/pages/mutations.js +0 -107
  201. package/docs/src/pages/navigation.js +0 -85
  202. package/docs/src/pages/performance.js +0 -157
  203. package/docs/src/pages/persist.js +0 -89
  204. package/docs/src/pages/project-structure.js +0 -91
  205. package/docs/src/pages/prompt-examples.js +0 -186
  206. package/docs/src/pages/raw-responses.js +0 -122
  207. package/docs/src/pages/routing.js +0 -120
  208. package/docs/src/pages/server-api.js +0 -291
  209. package/docs/src/pages/server-data.js +0 -203
  210. package/docs/src/pages/slash-commands.js +0 -55
  211. package/docs/src/pages/spec.js +0 -207
  212. package/docs/src/pages/state.js +0 -102
  213. package/docs/src/pages/store.js +0 -203
  214. package/docs/src/pages/streaming.js +0 -107
  215. package/docs/src/pages/stripe.js +0 -194
  216. package/docs/src/pages/supabase.js +0 -324
  217. package/docs/src/pages/templates/andypixel.js +0 -260
  218. package/docs/src/pages/templates/blog-post.js +0 -226
  219. package/docs/src/pages/templates/bookworms.js +0 -227
  220. package/docs/src/pages/templates/coutumes.js +0 -237
  221. package/docs/src/pages/templates/journey.js +0 -186
  222. package/docs/src/pages/templates/mobile-app.js +0 -371
  223. package/docs/src/pages/templates/retrowheels.js +0 -355
  224. package/docs/src/pages/templates/shop.js +0 -268
  225. package/docs/src/pages/templates/sparkys.js +0 -264
  226. package/docs/src/pages/templates/suzukibento.js +0 -200
  227. package/docs/src/pages/templates/swish.js +0 -313
  228. package/docs/src/pages/templates.js +0 -534
  229. package/docs/src/pages/testing.js +0 -199
  230. package/docs/src/pages/validation.js +0 -138
  231. package/examples/.claude/commands/pulse-build.md +0 -7
  232. package/examples/.claude/commands/pulse-dev.md +0 -7
  233. package/examples/.claude/commands/pulse-start.md +0 -9
  234. package/examples/.claude/commands/pulse-stop.md +0 -5
  235. package/examples/.claude/commands/verify.md +0 -84
  236. package/examples/.claude/coverage-check.js +0 -196
  237. package/examples/.claude/pulse-checklist.md +0 -189
  238. package/examples/andypixel.css +0 -237
  239. package/examples/andypixel.js +0 -258
  240. package/examples/bookworms.css +0 -422
  241. package/examples/bookworms.js +0 -225
  242. package/examples/contact.js +0 -166
  243. package/examples/counter.js +0 -94
  244. package/examples/dev.server.js +0 -110
  245. package/examples/examples.test.js +0 -394
  246. package/examples/journey.css +0 -528
  247. package/examples/journey.js +0 -186
  248. package/examples/pricing.js +0 -262
  249. package/examples/products.js +0 -190
  250. package/examples/public/.pulse-ui-version +0 -1
  251. package/examples/public/pulse-ui.css +0 -3028
  252. package/examples/public/pulse-ui.js +0 -257
  253. package/examples/quiz.js +0 -208
  254. package/examples/retrowheels.css +0 -425
  255. package/examples/retrowheels.js +0 -353
  256. package/examples/shared.js +0 -84
  257. package/examples/suzukibento.css +0 -513
  258. package/examples/suzukibento.js +0 -200
  259. package/examples/swish.css +0 -394
  260. package/examples/swish.js +0 -311
  261. package/examples/todos.js +0 -162
  262. package/src/ui/fieldset.js +0 -37
  263. package/src/ui/phone-frame.js +0 -40
  264. package/src/ui/search.js +0 -78
  265. package/src/ui/segmented.js +0 -50
  266. package/src/ui/tooltip.js +0 -28
@@ -1,327 +0,0 @@
1
- /**
2
- * Pulse Docs — Mobile sidebar menu, search, and demo theme toggles
3
- * Loaded as an external script so it passes the CSP (no unsafe-inline needed).
4
- */
5
- (function () {
6
- // ── Scroll active nav item into view ─────────────────────────────────────
7
-
8
- function centerNavLink(sidebar, link) {
9
- if (!sidebar || !link) return
10
- var navRect = sidebar.getBoundingClientRect()
11
- var linkRect = link.getBoundingClientRect()
12
- sidebar.scrollTop = sidebar.scrollTop + (linkRect.top - navRect.top) - sidebar.clientHeight / 2 + link.clientHeight / 2
13
- }
14
-
15
- // ── Sidebar nav filter ────────────────────────────────────────────────────
16
-
17
- function initSidebarSearch(sidebar) {
18
- var input = sidebar && sidebar.querySelector('#sidebar-search')
19
- var nav = sidebar && sidebar.querySelector('#sidebar-nav')
20
- var noResult = sidebar && sidebar.querySelector('.sidebar-no-results')
21
- if (!input || !nav || input._searchBound) return
22
- input._searchBound = true
23
-
24
- function filter(q) {
25
- var query = q.trim().toLowerCase()
26
- var sections = nav.querySelectorAll('.nav-section')
27
- var anyVisible = false
28
-
29
- if (!query) {
30
- sections.forEach(function (s) {
31
- s.hidden = false
32
- s.querySelectorAll('.nav-link').forEach(function (l) { l.hidden = false })
33
- })
34
- if (noResult) noResult.hidden = true
35
- return
36
- }
37
-
38
- sections.forEach(function (section) {
39
- var sectionLinks = section.querySelectorAll('.nav-link')
40
- var sectionHasMatch = false
41
- sectionLinks.forEach(function (link) {
42
- var matches = link.textContent.toLowerCase().includes(query)
43
- link.hidden = !matches
44
- if (matches) { sectionHasMatch = true; anyVisible = true }
45
- })
46
- section.hidden = !sectionHasMatch
47
- })
48
-
49
- if (noResult) noResult.hidden = anyVisible
50
- }
51
-
52
- input.addEventListener('input', function () { filter(this.value) })
53
-
54
- input.addEventListener('keydown', function (e) {
55
- if (e.key === 'Escape') {
56
- input.value = ''
57
- filter('')
58
- input.blur()
59
- }
60
- })
61
- }
62
-
63
- // ── Full-text search overlay ──────────────────────────────────────────────
64
-
65
- var _searchIndex = null
66
-
67
- function loadSearchIndex(cb) {
68
- if (_searchIndex) { cb(_searchIndex); return }
69
- fetch('/search-index.json')
70
- .then(function (r) { return r.json() })
71
- .then(function (data) { _searchIndex = data; cb(data) })
72
- .catch(function () { cb([]) })
73
- }
74
-
75
- function searchIndex(query, index) {
76
- var q = query.trim().toLowerCase()
77
- if (!q) return []
78
- return index.filter(function (item) {
79
- return item.title.toLowerCase().includes(q) ||
80
- item.section.toLowerCase().includes(q) ||
81
- (item.body && item.body.toLowerCase().includes(q))
82
- }).slice(0, 12)
83
- }
84
-
85
- function highlight(text, query) {
86
- if (!query) return text
87
- var re = new RegExp('(' + query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + ')', 'gi')
88
- return text.replace(re, '<mark>$1</mark>')
89
- }
90
-
91
- function snippet(body, query) {
92
- if (!body || !query) return ''
93
- var idx = body.toLowerCase().indexOf(query.toLowerCase())
94
- if (idx === -1) return ''
95
- var start = Math.max(0, idx - 40)
96
- var end = Math.min(body.length, idx + query.length + 80)
97
- var s = (start > 0 ? '…' : '') + body.slice(start, end) + (end < body.length ? '…' : '')
98
- return highlight(s, query)
99
- }
100
-
101
- function renderResults(results, query, container, selectedIdx) {
102
- while (container.firstChild) container.removeChild(container.firstChild)
103
-
104
- if (!results.length) {
105
- var empty = document.createElement('p')
106
- empty.className = 'search-overlay-empty'
107
- empty.textContent = 'No results for '
108
- var strong = document.createElement('strong')
109
- strong.textContent = query
110
- empty.appendChild(strong)
111
- container.appendChild(empty)
112
- return
113
- }
114
-
115
- results.forEach(function (item, i) {
116
- var a = document.createElement('a')
117
- a.href = item.href
118
- a.className = 'search-result' + (i === selectedIdx ? ' search-result--selected' : '')
119
- a.setAttribute('role', 'option')
120
- if (i === selectedIdx) a.setAttribute('aria-selected', 'true')
121
-
122
- var meta = document.createElement('div')
123
- meta.className = 'search-result-meta'
124
- var section = document.createElement('span')
125
- section.className = 'search-result-section'
126
- section.textContent = item.section
127
- meta.appendChild(section)
128
- a.appendChild(meta)
129
-
130
- var title = document.createElement('div')
131
- title.className = 'search-result-title'
132
- setHighlighted(title, item.title, query)
133
- a.appendChild(title)
134
-
135
- var snip = snippet(item.body, query)
136
- if (snip) {
137
- var snipEl = document.createElement('div')
138
- snipEl.className = 'search-result-snip'
139
- setHighlighted(snipEl, snip, null) // snip already has highlight markers stripped
140
- a.appendChild(snipEl)
141
- }
142
-
143
- container.appendChild(a)
144
- })
145
- }
146
-
147
- function setHighlighted(el, text, query) {
148
- if (!query) { el.textContent = text; return }
149
- var lower = text.toLowerCase()
150
- var q = query.toLowerCase()
151
- var idx = lower.indexOf(q)
152
- if (idx === -1) { el.textContent = text; return }
153
- var before = document.createTextNode(text.slice(0, idx))
154
- var mark = document.createElement('mark')
155
- mark.textContent = text.slice(idx, idx + query.length)
156
- var after = document.createTextNode(text.slice(idx + query.length))
157
- el.appendChild(before)
158
- el.appendChild(mark)
159
- el.appendChild(after)
160
- }
161
-
162
- function initSearchOverlay() {
163
- var overlay = document.getElementById('search-overlay')
164
- var input = document.getElementById('search-overlay-input')
165
- var results = document.getElementById('search-overlay-results')
166
- var fullBtns = document.querySelectorAll('.sidebar-search-full')
167
- if (!overlay || !input || !results) return
168
-
169
- var selectedIdx = -1
170
- var currentResults = []
171
- var currentQuery = ''
172
-
173
- function open() {
174
- overlay.hidden = false
175
- document.body.style.overflow = 'hidden'
176
- setTimeout(function () { input.focus() }, 50)
177
- selectedIdx = -1
178
- }
179
-
180
- function close() {
181
- overlay.hidden = true
182
- document.body.style.overflow = ''
183
- input.value = ''
184
- results.innerHTML = ''
185
- currentResults = []
186
- currentQuery = ''
187
- selectedIdx = -1
188
- }
189
-
190
- function navigate(dir) {
191
- selectedIdx = Math.max(-1, Math.min(currentResults.length - 1, selectedIdx + dir))
192
- renderResults(currentResults, currentQuery, results, selectedIdx)
193
- var sel = results.querySelector('.search-result--selected')
194
- if (sel) sel.scrollIntoView({ block: 'nearest' })
195
- }
196
-
197
- fullBtns.forEach(function (btn) { btn.addEventListener('click', open) })
198
-
199
- overlay.querySelector('.search-overlay-backdrop').addEventListener('click', close)
200
- overlay.querySelector('.search-overlay-close').addEventListener('click', close)
201
-
202
- input.addEventListener('input', function () {
203
- var q = this.value.trim()
204
- currentQuery = q
205
- selectedIdx = -1
206
- if (!q) { results.innerHTML = ''; currentResults = []; return }
207
- loadSearchIndex(function (index) {
208
- currentResults = searchIndex(q, index)
209
- renderResults(currentResults, q, results, selectedIdx)
210
- })
211
- })
212
-
213
- input.addEventListener('keydown', function (e) {
214
- if (e.key === 'Escape') { close(); return }
215
- if (e.key === 'ArrowDown') { e.preventDefault(); navigate(1); return }
216
- if (e.key === 'ArrowUp') { e.preventDefault(); navigate(-1); return }
217
- if (e.key === 'Enter') {
218
- if (selectedIdx >= 0 && currentResults[selectedIdx]) {
219
- window.location.href = currentResults[selectedIdx].href
220
- close()
221
- }
222
- }
223
- })
224
-
225
- document.addEventListener('keydown', function (e) {
226
- if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
227
- e.preventDefault()
228
- overlay.hidden ? open() : close()
229
- }
230
- if (e.key === 'Escape' && !overlay.hidden) close()
231
- })
232
- }
233
-
234
- // ── Bind sidebar, mobile menu, and centre the active link ─────────────────
235
- // Called on first load and after every client-side navigation, because
236
- // #pulse-root is replaced on navigate — all element references go stale.
237
-
238
- function init() {
239
- var sidebar = document.querySelector('.docs-sidebar')
240
- var btn = document.querySelector('.mobile-menu-btn')
241
- var overlay = document.querySelector('.sidebar-overlay')
242
-
243
- // Mobile open/close
244
- if (btn && sidebar) {
245
- function open() {
246
- sidebar.classList.add('open')
247
- overlay && overlay.classList.add('visible')
248
- document.body.style.overflow = 'hidden'
249
- }
250
-
251
- function close() {
252
- sidebar.classList.remove('open')
253
- overlay && overlay.classList.remove('visible')
254
- document.body.style.overflow = ''
255
- }
256
-
257
- btn.addEventListener('click', function () {
258
- sidebar.classList.contains('open') ? close() : open()
259
- })
260
-
261
- overlay && overlay.addEventListener('click', close)
262
-
263
- sidebar.querySelectorAll('.nav-link').forEach(function (link) {
264
- link.addEventListener('click', close)
265
- })
266
- }
267
-
268
- // Centre the active link
269
- var activeLink = sidebar && sidebar.querySelector('.nav-link.active')
270
- if (activeLink) centerNavLink(sidebar, activeLink)
271
-
272
- // Sidebar filter
273
- initSidebarSearch(sidebar)
274
-
275
- // Full-text search overlay (only bind once — overlay persists across navigations)
276
- if (!document.getElementById('search-overlay')?._searchBound) {
277
- initSearchOverlay()
278
- var o = document.getElementById('search-overlay')
279
- if (o) o._searchBound = true
280
- }
281
- }
282
-
283
- // On initial load — defer slightly to ensure layout paint before scrolling.
284
- // Use both paths: if load already fired (common with defer scripts), run now;
285
- // otherwise wait for it.
286
- if (document.readyState === 'complete' || document.readyState === 'interactive') {
287
- setTimeout(init, 50)
288
- } else {
289
- window.addEventListener('load', function () { setTimeout(init, 50) })
290
- }
291
-
292
- // After client-side navigation — re-bind everything against the fresh DOM
293
- document.addEventListener('pulse:navigate', init)
294
-
295
- // ── Slider fill ──────────────────────────────────────────────────────────
296
- function updateSliderFill(input) {
297
- var pct = ((input.value - input.min) / (input.max - input.min) * 100) + '%'
298
- input.style.setProperty('--slider-fill', pct)
299
- }
300
-
301
- function bindDemoControls() {
302
- document.querySelectorAll('.ui-slider').forEach(function (input) {
303
- input.addEventListener('input', function () { updateSliderFill(this) })
304
- })
305
-
306
- // ── Demo theme toggles ──────────────────────────────────────────────────
307
- document.querySelectorAll('.demo-theme-toggle').forEach(function (toggle) {
308
- var preview = toggle.closest('.demo-preview')
309
- if (!preview) return
310
- toggle.addEventListener('click', function () {
311
- var isLight = preview.classList.toggle('is-light')
312
- var inner = preview.querySelector('.demo-preview-inner')
313
- if (inner) {
314
- inner.classList.toggle('ui-theme-light', isLight)
315
- }
316
- })
317
- })
318
- }
319
-
320
- document.addEventListener('pulse:navigate', bindDemoControls)
321
-
322
- if (document.readyState === 'complete' || document.readyState === 'interactive') {
323
- setTimeout(bindDemoControls, 50)
324
- } else {
325
- window.addEventListener('load', function () { setTimeout(bindDemoControls, 50) })
326
- }
327
- })()