@bettercms-ai/kit 0.1.1 → 2.0.0

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 (1100) hide show
  1. package/README.md +22 -16
  2. package/dist/_virtual/_rolldown/runtime.js +13 -0
  3. package/dist/adapters/dock-values.d.ts +28 -0
  4. package/dist/adapters/dock-values.js +41 -0
  5. package/dist/adapters/dock-values.js.map +1 -0
  6. package/dist/adapters/icons.d.ts +15 -0
  7. package/dist/adapters/icons.js +27 -0
  8. package/dist/adapters/icons.js.map +1 -0
  9. package/dist/adapters/lossy.d.ts +326 -0
  10. package/dist/adapters/lossy.js +414 -0
  11. package/dist/adapters/lossy.js.map +1 -0
  12. package/dist/adapters/prod-types.d.ts +546 -0
  13. package/dist/adapters/prod-types.js +714 -0
  14. package/dist/adapters/prod-types.js.map +1 -0
  15. package/dist/adapters/schema.d.ts +154 -0
  16. package/dist/adapters/schema.js +3020 -0
  17. package/dist/adapters/schema.js.map +1 -0
  18. package/dist/adapters/values.d.ts +221 -0
  19. package/dist/adapters/values.js +623 -0
  20. package/dist/adapters/values.js.map +1 -0
  21. package/dist/brand/logo.d.ts +17 -0
  22. package/dist/brand/logo.js +96 -0
  23. package/dist/brand/logo.js.map +1 -0
  24. package/dist/context.d.ts +359 -0
  25. package/dist/context.js +217 -0
  26. package/dist/context.js.map +1 -0
  27. package/dist/controls/button.d.ts +72 -0
  28. package/dist/controls/button.js +578 -0
  29. package/dist/controls/button.js.map +1 -0
  30. package/dist/controls/c1.d.ts +11 -0
  31. package/dist/controls/c1.js +24 -0
  32. package/dist/controls/c1.js.map +1 -0
  33. package/dist/controls/c2.d.ts +10 -0
  34. package/dist/controls/c2.js +19 -0
  35. package/dist/controls/c2.js.map +1 -0
  36. package/dist/controls/choice.d.ts +44 -0
  37. package/dist/controls/choice.js +463 -0
  38. package/dist/controls/choice.js.map +1 -0
  39. package/dist/controls/code-register.d.ts +1 -0
  40. package/dist/controls/code-register.js +8 -0
  41. package/dist/controls/code-register.js.map +1 -0
  42. package/dist/controls/code.d.ts +42 -0
  43. package/dist/controls/code.js +397 -0
  44. package/dist/controls/code.js.map +1 -0
  45. package/dist/controls/color.d.ts +5 -0
  46. package/dist/controls/color.js +130 -0
  47. package/dist/controls/color.js.map +1 -0
  48. package/dist/controls/component-services.d.ts +156 -0
  49. package/dist/controls/component-services.js +7 -0
  50. package/dist/controls/component-services.js.map +1 -0
  51. package/dist/controls/date-register.d.ts +1 -0
  52. package/dist/controls/date-register.js +9 -0
  53. package/dist/controls/date-register.js.map +1 -0
  54. package/dist/controls/date-zone.d.ts +104 -0
  55. package/dist/controls/date-zone.js +278 -0
  56. package/dist/controls/date-zone.js.map +1 -0
  57. package/dist/controls/date.d.ts +7 -0
  58. package/dist/controls/date.js +466 -0
  59. package/dist/controls/date.js.map +1 -0
  60. package/dist/controls/form-picker-register.d.ts +1 -0
  61. package/dist/controls/form-picker-register.js +19 -0
  62. package/dist/controls/form-picker-register.js.map +1 -0
  63. package/dist/controls/form-picker.d.ts +43 -0
  64. package/dist/controls/form-picker.js +85 -0
  65. package/dist/controls/form-picker.js.map +1 -0
  66. package/dist/controls/icon-register.d.ts +1 -0
  67. package/dist/controls/icon-register.js +8 -0
  68. package/dist/controls/icon-register.js.map +1 -0
  69. package/dist/controls/icon.d.ts +40 -0
  70. package/dist/controls/icon.js +360 -0
  71. package/dist/controls/icon.js.map +1 -0
  72. package/dist/controls/index.d.ts +5 -0
  73. package/dist/controls/index.js +38 -0
  74. package/dist/controls/index.js.map +1 -0
  75. package/dist/controls/json-register.d.ts +1 -0
  76. package/dist/controls/json-register.js +8 -0
  77. package/dist/controls/json-register.js.map +1 -0
  78. package/dist/controls/json.d.ts +2 -0
  79. package/dist/controls/json.js +82 -0
  80. package/dist/controls/json.js.map +1 -0
  81. package/dist/controls/link.d.ts +260 -0
  82. package/dist/controls/link.js +1186 -0
  83. package/dist/controls/link.js.map +1 -0
  84. package/dist/controls/location-register.d.ts +1 -0
  85. package/dist/controls/location-register.js +8 -0
  86. package/dist/controls/location-register.js.map +1 -0
  87. package/dist/controls/location.d.ts +16 -0
  88. package/dist/controls/location.js +221 -0
  89. package/dist/controls/location.js.map +1 -0
  90. package/dist/controls/media-register.d.ts +1 -0
  91. package/dist/controls/media-register.js +12 -0
  92. package/dist/controls/media-register.js.map +1 -0
  93. package/dist/controls/media-services.d.ts +46 -0
  94. package/dist/controls/media-services.js +0 -0
  95. package/dist/controls/media.d.ts +104 -0
  96. package/dist/controls/media.js +1396 -0
  97. package/dist/controls/media.js.map +1 -0
  98. package/dist/controls/number.d.ts +22 -0
  99. package/dist/controls/number.js +196 -0
  100. package/dist/controls/number.js.map +1 -0
  101. package/dist/controls/reference-register.d.ts +1 -0
  102. package/dist/controls/reference-register.js +10 -0
  103. package/dist/controls/reference-register.js.map +1 -0
  104. package/dist/controls/reference-services.d.ts +58 -0
  105. package/dist/controls/reference-services.js +0 -0
  106. package/dist/controls/reference.d.ts +23 -0
  107. package/dist/controls/reference.js +777 -0
  108. package/dist/controls/reference.js.map +1 -0
  109. package/dist/controls/rich-text/blocks.d.ts +49 -0
  110. package/dist/controls/rich-text/blocks.js +1859 -0
  111. package/dist/controls/rich-text/blocks.js.map +1 -0
  112. package/dist/controls/rich-text/catalog.d.ts +101 -0
  113. package/dist/controls/rich-text/catalog.js +623 -0
  114. package/dist/controls/rich-text/catalog.js.map +1 -0
  115. package/dist/controls/rich-text/codec.d.ts +64 -0
  116. package/dist/controls/rich-text/codec.js +34 -0
  117. package/dist/controls/rich-text/codec.js.map +1 -0
  118. package/dist/controls/rich-text/component-card.d.ts +14 -0
  119. package/dist/controls/rich-text/component-card.js +1024 -0
  120. package/dist/controls/rich-text/component-card.js.map +1 -0
  121. package/dist/controls/rich-text/component-panel.d.ts +47 -0
  122. package/dist/controls/rich-text/component-panel.js +141 -0
  123. package/dist/controls/rich-text/component-panel.js.map +1 -0
  124. package/dist/controls/rich-text/control.d.ts +20 -0
  125. package/dist/controls/rich-text/control.js +296 -0
  126. package/dist/controls/rich-text/control.js.map +1 -0
  127. package/dist/controls/rich-text/dom.d.ts +233 -0
  128. package/dist/controls/rich-text/dom.js +1019 -0
  129. package/dist/controls/rich-text/dom.js.map +1 -0
  130. package/dist/controls/rich-text/editor.d.ts +57 -0
  131. package/dist/controls/rich-text/editor.js +1630 -0
  132. package/dist/controls/rich-text/editor.js.map +1 -0
  133. package/dist/controls/rich-text/floating.d.ts +28 -0
  134. package/dist/controls/rich-text/floating.js +154 -0
  135. package/dist/controls/rich-text/floating.js.map +1 -0
  136. package/dist/controls/rich-text/index.d.ts +1 -0
  137. package/dist/controls/rich-text/index.js +12 -0
  138. package/dist/controls/rich-text/index.js.map +1 -0
  139. package/dist/controls/rich-text/link-form.d.ts +13 -0
  140. package/dist/controls/rich-text/link-form.js +147 -0
  141. package/dist/controls/rich-text/link-form.js.map +1 -0
  142. package/dist/controls/rich-text/markdown.d.ts +6 -0
  143. package/dist/controls/rich-text/markdown.js +181 -0
  144. package/dist/controls/rich-text/markdown.js.map +1 -0
  145. package/dist/controls/rich-text/ops.d.ts +35 -0
  146. package/dist/controls/rich-text/ops.js +316 -0
  147. package/dist/controls/rich-text/ops.js.map +1 -0
  148. package/dist/controls/rich-text/plain.d.ts +17 -0
  149. package/dist/controls/rich-text/plain.js +44 -0
  150. package/dist/controls/rich-text/plain.js.map +1 -0
  151. package/dist/controls/rich-text/toolbar.d.ts +89 -0
  152. package/dist/controls/rich-text/toolbar.js +343 -0
  153. package/dist/controls/rich-text/toolbar.js.map +1 -0
  154. package/dist/controls/scale.d.ts +56 -0
  155. package/dist/controls/scale.js +563 -0
  156. package/dist/controls/scale.js.map +1 -0
  157. package/dist/controls/slot-register.d.ts +1 -0
  158. package/dist/controls/slot-register.js +16 -0
  159. package/dist/controls/slot-register.js.map +1 -0
  160. package/dist/controls/slot-services.d.ts +122 -0
  161. package/dist/controls/slot-services.js +155 -0
  162. package/dist/controls/slot-services.js.map +1 -0
  163. package/dist/controls/slot.d.ts +33 -0
  164. package/dist/controls/slot.js +366 -0
  165. package/dist/controls/slot.js.map +1 -0
  166. package/dist/controls/table/index.d.ts +1 -0
  167. package/dist/controls/table/index.js +12 -0
  168. package/dist/controls/table/index.js.map +1 -0
  169. package/dist/controls/table/prod.d.ts +21 -0
  170. package/dist/controls/table/prod.js +79 -0
  171. package/dist/controls/table/prod.js.map +1 -0
  172. package/dist/controls/table/refs.d.ts +50 -0
  173. package/dist/controls/table/refs.js +79 -0
  174. package/dist/controls/table/refs.js.map +1 -0
  175. package/dist/controls/table/stored.d.ts +15 -0
  176. package/dist/controls/table/stored.js +44 -0
  177. package/dist/controls/table/stored.js.map +1 -0
  178. package/dist/controls/table/table-editor.d.ts +103 -0
  179. package/dist/controls/table/table-editor.js +2725 -0
  180. package/dist/controls/table/table-editor.js.map +1 -0
  181. package/dist/controls/table/table-link.d.ts +24 -0
  182. package/dist/controls/table/table-link.js +180 -0
  183. package/dist/controls/table/table-link.js.map +1 -0
  184. package/dist/controls/table/table.d.ts +9 -0
  185. package/dist/controls/table/table.js +103 -0
  186. package/dist/controls/table/table.js.map +1 -0
  187. package/dist/controls/text.d.ts +17 -0
  188. package/dist/controls/text.js +322 -0
  189. package/dist/controls/text.js.map +1 -0
  190. package/dist/controls/unknown-value.d.ts +17 -0
  191. package/dist/controls/unknown-value.js +66 -0
  192. package/dist/controls/unknown-value.js.map +1 -0
  193. package/dist/core/core.d.ts +63 -0
  194. package/dist/core/core.js +171 -0
  195. package/dist/core/core.js.map +1 -0
  196. package/dist/core/facets.d.ts +347 -0
  197. package/dist/core/facets.js +334 -0
  198. package/dist/core/facets.js.map +1 -0
  199. package/dist/core/index.d.ts +3 -0
  200. package/dist/core/index.js +3 -0
  201. package/dist/engine/array.d.ts +101 -0
  202. package/dist/engine/array.js +906 -0
  203. package/dist/engine/array.js.map +1 -0
  204. package/dist/engine/bits.d.ts +24 -0
  205. package/dist/engine/bits.js +60 -0
  206. package/dist/engine/bits.js.map +1 -0
  207. package/dist/engine/breadcrumb.d.ts +45 -0
  208. package/dist/engine/breadcrumb.js +63 -0
  209. package/dist/engine/breadcrumb.js.map +1 -0
  210. package/dist/engine/expand.d.ts +66 -0
  211. package/dist/engine/expand.js +153 -0
  212. package/dist/engine/expand.js.map +1 -0
  213. package/dist/engine/field-list.d.ts +20 -0
  214. package/dist/engine/field-list.js +19 -0
  215. package/dist/engine/field-list.js.map +1 -0
  216. package/dist/engine/field-shell.d.ts +49 -0
  217. package/dist/engine/field-shell.js +988 -0
  218. package/dist/engine/field-shell.js.map +1 -0
  219. package/dist/engine/frame.d.ts +122 -0
  220. package/dist/engine/frame.js +601 -0
  221. package/dist/engine/frame.js.map +1 -0
  222. package/dist/engine/group-summary.d.ts +5 -0
  223. package/dist/engine/group-summary.js +70 -0
  224. package/dist/engine/group-summary.js.map +1 -0
  225. package/dist/engine/grouping.d.ts +238 -0
  226. package/dist/engine/grouping.js +996 -0
  227. package/dist/engine/grouping.js.map +1 -0
  228. package/dist/engine/item-list.d.ts +99 -0
  229. package/dist/engine/item-list.js +725 -0
  230. package/dist/engine/item-list.js.map +1 -0
  231. package/dist/engine/kit-field.d.ts +72 -0
  232. package/dist/engine/kit-field.js +558 -0
  233. package/dist/engine/kit-field.js.map +1 -0
  234. package/dist/engine/schema-form.d.ts +116 -0
  235. package/dist/engine/schema-form.js +462 -0
  236. package/dist/engine/schema-form.js.map +1 -0
  237. package/dist/engine/slot.d.ts +13 -0
  238. package/dist/engine/slot.js +216 -0
  239. package/dist/engine/slot.js.map +1 -0
  240. package/dist/index.d.ts +45 -3139
  241. package/dist/index.js +47 -2036
  242. package/dist/index.js.map +1 -0
  243. package/dist/kit.css +75 -0
  244. package/dist/lib/cn.d.ts +4 -0
  245. package/dist/lib/cn.js +56 -0
  246. package/dist/lib/cn.js.map +1 -0
  247. package/dist/lib/columns.d.ts +14 -0
  248. package/dist/lib/columns.js +30 -0
  249. package/dist/lib/columns.js.map +1 -0
  250. package/dist/lib/frames.d.ts +36 -0
  251. package/dist/lib/frames.js +214 -0
  252. package/dist/lib/frames.js.map +1 -0
  253. package/dist/lib/icon-image.d.ts +10 -0
  254. package/dist/lib/icon-image.js +8 -0
  255. package/dist/lib/icon-image.js.map +1 -0
  256. package/dist/lib/icons.d.ts +16 -0
  257. package/dist/lib/icons.js +173 -0
  258. package/dist/lib/icons.js.map +1 -0
  259. package/dist/lib/input-modality.d.ts +11 -0
  260. package/dist/lib/input-modality.js +47 -0
  261. package/dist/lib/input-modality.js.map +1 -0
  262. package/dist/lib/key-event.d.ts +11 -0
  263. package/dist/lib/key-event.js +16 -0
  264. package/dist/lib/key-event.js.map +1 -0
  265. package/dist/lib/path.d.ts +29 -0
  266. package/dist/lib/path.js +66 -0
  267. package/dist/lib/path.js.map +1 -0
  268. package/dist/lib/schema.d.ts +80 -0
  269. package/dist/lib/schema.js +384 -0
  270. package/dist/lib/schema.js.map +1 -0
  271. package/dist/lib/utils.d.ts +7 -0
  272. package/dist/lib/utils.js +37 -0
  273. package/dist/lib/utils.js.map +1 -0
  274. package/dist/lib/validate.d.ts +30 -0
  275. package/dist/lib/validate.js +224 -0
  276. package/dist/lib/validate.js.map +1 -0
  277. package/dist/library/analysis.d.ts +23 -0
  278. package/dist/library/analysis.js +160 -0
  279. package/dist/library/analysis.js.map +1 -0
  280. package/dist/library/catalog.d.ts +57 -0
  281. package/dist/library/catalog.js +307 -0
  282. package/dist/library/catalog.js.map +1 -0
  283. package/dist/library/coreNotes.d.ts +8 -0
  284. package/dist/library/coreNotes.js +111 -0
  285. package/dist/library/coreNotes.js.map +1 -0
  286. package/dist/library/coverage.d.ts +40 -0
  287. package/dist/library/coverage.js +59 -0
  288. package/dist/library/coverage.js.map +1 -0
  289. package/dist/library/entries/advanced.entries.d.ts +2 -0
  290. package/dist/library/entries/advanced.entries.js +6579 -0
  291. package/dist/library/entries/advanced.entries.js.map +1 -0
  292. package/dist/library/entries/commerce.entries.d.ts +2 -0
  293. package/dist/library/entries/commerce.entries.js +4140 -0
  294. package/dist/library/entries/commerce.entries.js.map +1 -0
  295. package/dist/library/entries/company.entries.d.ts +2 -0
  296. package/dist/library/entries/company.entries.js +2731 -0
  297. package/dist/library/entries/company.entries.js.map +1 -0
  298. package/dist/library/entries/conversion.entries.d.ts +2 -0
  299. package/dist/library/entries/conversion.entries.js +3707 -0
  300. package/dist/library/entries/conversion.entries.js.map +1 -0
  301. package/dist/library/entries/data.entries.d.ts +7 -0
  302. package/dist/library/entries/data.entries.js +4613 -0
  303. package/dist/library/entries/data.entries.js.map +1 -0
  304. package/dist/library/entries/events.entries.d.ts +2 -0
  305. package/dist/library/entries/events.entries.js +3428 -0
  306. package/dist/library/entries/events.entries.js.map +1 -0
  307. package/dist/library/entries/features.entries.d.ts +2 -0
  308. package/dist/library/entries/features.entries.js +3157 -0
  309. package/dist/library/entries/features.entries.js.map +1 -0
  310. package/dist/library/entries/heroes.entries.d.ts +2 -0
  311. package/dist/library/entries/heroes.entries.js +3401 -0
  312. package/dist/library/entries/heroes.entries.js.map +1 -0
  313. package/dist/library/entries/index.d.ts +3 -0
  314. package/dist/library/entries/index.js +35 -0
  315. package/dist/library/entries/index.js.map +1 -0
  316. package/dist/library/entries/listings.entries.d.ts +2 -0
  317. package/dist/library/entries/listings.entries.js +2976 -0
  318. package/dist/library/entries/listings.entries.js.map +1 -0
  319. package/dist/library/entries/longform.entries.d.ts +2 -0
  320. package/dist/library/entries/longform.entries.js +4227 -0
  321. package/dist/library/entries/longform.entries.js.map +1 -0
  322. package/dist/library/entries/media.entries.d.ts +2 -0
  323. package/dist/library/entries/media.entries.js +2781 -0
  324. package/dist/library/entries/media.entries.js.map +1 -0
  325. package/dist/library/entries/navigation.entries.d.ts +2 -0
  326. package/dist/library/entries/navigation.entries.js +4011 -0
  327. package/dist/library/entries/navigation.entries.js.map +1 -0
  328. package/dist/library/entries/pricing.entries.d.ts +2 -0
  329. package/dist/library/entries/pricing.entries.js +2383 -0
  330. package/dist/library/entries/pricing.entries.js.map +1 -0
  331. package/dist/library/entries/social-proof.entries.d.ts +2 -0
  332. package/dist/library/entries/social-proof.entries.js +2297 -0
  333. package/dist/library/entries/social-proof.entries.js.map +1 -0
  334. package/dist/library/examples/bento.d.ts +92 -0
  335. package/dist/library/examples/bento.js +201 -0
  336. package/dist/library/examples/bento.js.map +1 -0
  337. package/dist/library/examples/blocks.d.ts +14 -0
  338. package/dist/library/examples/blocks.js +313 -0
  339. package/dist/library/examples/blocks.js.map +1 -0
  340. package/dist/library/examples/faq.d.ts +28 -0
  341. package/dist/library/examples/faq.js +230 -0
  342. package/dist/library/examples/faq.js.map +1 -0
  343. package/dist/library/examples/hero.d.ts +43 -0
  344. package/dist/library/examples/hero.js +222 -0
  345. package/dist/library/examples/hero.js.map +1 -0
  346. package/dist/library/examples/index.d.ts +36 -0
  347. package/dist/library/examples/index.js +109 -0
  348. package/dist/library/examples/index.js.map +1 -0
  349. package/dist/library/examples/journalStory.d.ts +93 -0
  350. package/dist/library/examples/journalStory.js +338 -0
  351. package/dist/library/examples/journalStory.js.map +1 -0
  352. package/dist/library/examples/megaMenu.d.ts +134 -0
  353. package/dist/library/examples/megaMenu.js +442 -0
  354. package/dist/library/examples/megaMenu.js.map +1 -0
  355. package/dist/library/examples/pricing.d.ts +103 -0
  356. package/dist/library/examples/pricing.js +479 -0
  357. package/dist/library/examples/pricing.js.map +1 -0
  358. package/dist/library/examples/tabs.d.ts +87 -0
  359. package/dist/library/examples/tabs.js +180 -0
  360. package/dist/library/examples/tabs.js.map +1 -0
  361. package/dist/library/export.d.ts +42 -0
  362. package/dist/library/export.js +80 -0
  363. package/dist/library/export.js.map +1 -0
  364. package/dist/library/index.d.ts +13 -0
  365. package/dist/library/index.js +6 -0
  366. package/dist/library/kit-catalog.json +1510 -0
  367. package/dist/library/types.d.ts +42 -0
  368. package/dist/library/types.js +0 -0
  369. package/dist/schema/adapters.d.ts +28 -0
  370. package/dist/schema/adapters.js +492 -0
  371. package/dist/schema/adapters.js.map +1 -0
  372. package/dist/schema/help.d.ts +27 -0
  373. package/dist/schema/help.js +56 -0
  374. package/dist/schema/help.js.map +1 -0
  375. package/dist/schema/scale.d.ts +69 -0
  376. package/dist/schema/scale.js +250 -0
  377. package/dist/schema/scale.js.map +1 -0
  378. package/dist/schema/sources.d.ts +103 -0
  379. package/dist/schema/sources.js +65 -0
  380. package/dist/schema/sources.js.map +1 -0
  381. package/dist/schema/table.d.ts +319 -0
  382. package/dist/schema/table.js +1199 -0
  383. package/dist/schema/table.js.map +1 -0
  384. package/dist/schema/types.d.ts +297 -0
  385. package/dist/schema/types.js +14 -0
  386. package/dist/schema/types.js.map +1 -0
  387. package/dist/styles.css +2 -0
  388. package/dist/table/table-view.d.ts +14 -0
  389. package/dist/table/table-view.js +140 -0
  390. package/dist/table/table-view.js.map +1 -0
  391. package/dist/table/table.css +73 -0
  392. package/dist/testing/corpus.d.ts +63 -0
  393. package/dist/testing/corpus.js +129 -0
  394. package/dist/testing/corpus.js.map +1 -0
  395. package/dist/testing/test-form.d.ts +11 -0
  396. package/dist/testing/test-form.js +38 -0
  397. package/dist/testing/test-form.js.map +1 -0
  398. package/dist/theme.css +345 -0
  399. package/dist/theme.d.ts +20 -1504
  400. package/dist/theme.js +43 -4126
  401. package/dist/theme.js.map +1 -0
  402. package/dist/tokens.css +542 -0
  403. package/dist/types.d.ts +17 -0
  404. package/dist/types.js +0 -0
  405. package/dist/ui/action-tray.d.ts +54 -0
  406. package/dist/ui/action-tray.js +154 -0
  407. package/dist/ui/action-tray.js.map +1 -0
  408. package/dist/ui/autocomplete.d.ts +30 -0
  409. package/dist/ui/autocomplete.js +139 -0
  410. package/dist/ui/autocomplete.js.map +1 -0
  411. package/dist/ui/button.d.ts +57 -0
  412. package/dist/ui/button.js +201 -0
  413. package/dist/ui/button.js.map +1 -0
  414. package/dist/ui/choice-switch.d.ts +36 -0
  415. package/dist/ui/choice-switch.js +57 -0
  416. package/dist/ui/choice-switch.js.map +1 -0
  417. package/dist/ui/controls.d.ts +83 -0
  418. package/dist/ui/controls.js +331 -0
  419. package/dist/ui/controls.js.map +1 -0
  420. package/dist/ui/display.d.ts +163 -0
  421. package/dist/ui/display.js +455 -0
  422. package/dist/ui/display.js.map +1 -0
  423. package/dist/ui/expanded-editor-modal.d.ts +26 -0
  424. package/dist/ui/expanded-editor-modal.js +140 -0
  425. package/dist/ui/expanded-editor-modal.js.map +1 -0
  426. package/dist/ui/field-context.d.ts +12 -0
  427. package/dist/ui/field-context.js +23 -0
  428. package/dist/ui/field-context.js.map +1 -0
  429. package/dist/ui/floating-panel.d.ts +152 -0
  430. package/dist/ui/floating-panel.js +928 -0
  431. package/dist/ui/floating-panel.js.map +1 -0
  432. package/dist/ui/index.d.ts +16 -0
  433. package/dist/ui/index.js +18 -0
  434. package/dist/ui/input.d.ts +78 -0
  435. package/dist/ui/input.js +250 -0
  436. package/dist/ui/input.js.map +1 -0
  437. package/dist/ui/mode-bar.d.ts +31 -0
  438. package/dist/ui/mode-bar.js +65 -0
  439. package/dist/ui/mode-bar.js.map +1 -0
  440. package/dist/ui/overlay.d.ts +195 -0
  441. package/dist/ui/overlay.js +697 -0
  442. package/dist/ui/overlay.js.map +1 -0
  443. package/dist/ui/path-crumbs.d.ts +90 -0
  444. package/dist/ui/path-crumbs.js +566 -0
  445. package/dist/ui/path-crumbs.js.map +1 -0
  446. package/dist/ui/presence.d.ts +16 -0
  447. package/dist/ui/presence.js +65 -0
  448. package/dist/ui/presence.js.map +1 -0
  449. package/dist/ui/select.d.ts +66 -0
  450. package/dist/ui/select.js +407 -0
  451. package/dist/ui/select.js.map +1 -0
  452. package/dist/ui/shortcut-hint.d.ts +14 -0
  453. package/dist/ui/shortcut-hint.js +35 -0
  454. package/dist/ui/shortcut-hint.js.map +1 -0
  455. package/dist/ui/spinner.d.ts +24 -0
  456. package/dist/ui/spinner.js +42 -0
  457. package/dist/ui/spinner.js.map +1 -0
  458. package/dist/ui/split-button.d.ts +32 -0
  459. package/dist/ui/split-button.js +60 -0
  460. package/dist/ui/split-button.js.map +1 -0
  461. package/dist/ui/surfaces.d.ts +23 -0
  462. package/dist/ui/surfaces.js +26 -0
  463. package/dist/ui/surfaces.js.map +1 -0
  464. package/dist/ui/tab.d.ts +38 -0
  465. package/dist/ui/tab.js +87 -0
  466. package/dist/ui/tab.js.map +1 -0
  467. package/dist/ui/toast.d.ts +20 -0
  468. package/dist/ui/toast.js +115 -0
  469. package/dist/ui/toast.js.map +1 -0
  470. package/dist/ui/tooltip-root.d.ts +13 -0
  471. package/dist/ui/tooltip-root.js +77 -0
  472. package/dist/ui/tooltip-root.js.map +1 -0
  473. package/dist/ui/variants.d.ts +15 -0
  474. package/dist/ui/variants.js +14 -0
  475. package/dist/ui/variants.js.map +1 -0
  476. package/package.json +76 -50
  477. package/LICENSE +0 -21
  478. package/dist/_chunks-cjs/_visual-editing.js +0 -5898
  479. package/dist/_chunks-cjs/refractor.js +0 -22
  480. package/dist/_chunks-es/_visual-editing.mjs +0 -5904
  481. package/dist/_chunks-es/refractor.mjs +0 -25
  482. package/dist/_color.d.mts +0 -119
  483. package/dist/_color.d.ts +0 -119
  484. package/dist/_color.js +0 -1412
  485. package/dist/_color.mjs +0 -1411
  486. package/dist/_visual-editing.d.mts +0 -1126
  487. package/dist/_visual-editing.d.ts +0 -1126
  488. package/dist/_visual-editing.js +0 -30
  489. package/dist/_visual-editing.mjs +0 -30
  490. package/dist/index.d.mts +0 -3140
  491. package/dist/index.mjs +0 -2044
  492. package/dist/theme.d.mts +0 -1506
  493. package/dist/theme.mjs +0 -4126
  494. package/exports/_visual-editing.ts +0 -31
  495. package/exports/index.ts +0 -1
  496. package/exports/theme.ts +0 -1
  497. package/src/color/index.d.mts +0 -119
  498. package/src/color/index.mjs +0 -1411
  499. package/src/core/__workshop__/constants.ts +0 -293
  500. package/src/core/_compat.ts +0 -264
  501. package/src/core/components/autocomplete/__mocks__/apiStore.ts +0 -49
  502. package/src/core/components/autocomplete/__mocks__/countries.ts +0 -245
  503. package/src/core/components/autocomplete/__workshop__/async.tsx +0 -156
  504. package/src/core/components/autocomplete/__workshop__/constrainedHeight.tsx +0 -137
  505. package/src/core/components/autocomplete/__workshop__/custom.tsx +0 -89
  506. package/src/core/components/autocomplete/__workshop__/example.tsx +0 -79
  507. package/src/core/components/autocomplete/__workshop__/focusAndBlur.tsx +0 -36
  508. package/src/core/components/autocomplete/__workshop__/fullscreen.tsx +0 -294
  509. package/src/core/components/autocomplete/__workshop__/index.ts +0 -39
  510. package/src/core/components/autocomplete/__workshop__/types.ts +0 -4
  511. package/src/core/components/autocomplete/autocomplete.styles.tsx +0 -39
  512. package/src/core/components/autocomplete/autocomplete.tsx +0 -767
  513. package/src/core/components/autocomplete/autocompleteOption.tsx +0 -45
  514. package/src/core/components/autocomplete/autocompleteReducer.ts +0 -47
  515. package/src/core/components/autocomplete/constants.ts +0 -28
  516. package/src/core/components/autocomplete/index.ts +0 -2
  517. package/src/core/components/autocomplete/types.ts +0 -108
  518. package/src/core/components/breadcrumbs/__workshop__/example.tsx +0 -62
  519. package/src/core/components/breadcrumbs/__workshop__/index.ts +0 -14
  520. package/src/core/components/breadcrumbs/breadcrumbs.styles.ts +0 -18
  521. package/src/core/components/breadcrumbs/breadcrumbs.test.tsx +0 -51
  522. package/src/core/components/breadcrumbs/breadcrumbs.tsx +0 -133
  523. package/src/core/components/breadcrumbs/index.ts +0 -1
  524. package/src/core/components/dialog/__workshop__/activate.tsx +0 -134
  525. package/src/core/components/dialog/__workshop__/autoFocus.tsx +0 -29
  526. package/src/core/components/dialog/__workshop__/index.ts +0 -19
  527. package/src/core/components/dialog/__workshop__/layering.tsx +0 -41
  528. package/src/core/components/dialog/__workshop__/nested.tsx +0 -72
  529. package/src/core/components/dialog/__workshop__/onScroll.tsx +0 -39
  530. package/src/core/components/dialog/__workshop__/panes.tsx +0 -82
  531. package/src/core/components/dialog/__workshop__/position.tsx +0 -30
  532. package/src/core/components/dialog/__workshop__/props.tsx +0 -75
  533. package/src/core/components/dialog/__workshop__/provider.tsx +0 -11
  534. package/src/core/components/dialog/__workshop__/wrapped.tsx +0 -76
  535. package/src/core/components/dialog/dialog.tsx +0 -430
  536. package/src/core/components/dialog/dialogContext.ts +0 -20
  537. package/src/core/components/dialog/dialogProvider.tsx +0 -35
  538. package/src/core/components/dialog/index.ts +0 -4
  539. package/src/core/components/dialog/styles.ts +0 -77
  540. package/src/core/components/dialog/useDialog.ts +0 -11
  541. package/src/core/components/hotkeys/__workshop__/index.ts +0 -14
  542. package/src/core/components/hotkeys/__workshop__/plain.tsx +0 -9
  543. package/src/core/components/hotkeys/hotkeys.test.tsx +0 -42
  544. package/src/core/components/hotkeys/hotkeys.tsx +0 -66
  545. package/src/core/components/hotkeys/index.ts +0 -1
  546. package/src/core/components/index.ts +0 -9
  547. package/src/core/components/menu/__workshop__/asComponent.tsx +0 -45
  548. package/src/core/components/menu/__workshop__/avatarMenu.tsx +0 -51
  549. package/src/core/components/menu/__workshop__/closableMenuButton.tsx +0 -46
  550. package/src/core/components/menu/__workshop__/constrainedInBoundary.tsx +0 -134
  551. package/src/core/components/menu/__workshop__/customMenuItem.tsx +0 -45
  552. package/src/core/components/menu/__workshop__/customSelectedState.tsx +0 -39
  553. package/src/core/components/menu/__workshop__/disableFocusOnClose.tsx +0 -40
  554. package/src/core/components/menu/__workshop__/groups.tsx +0 -156
  555. package/src/core/components/menu/__workshop__/index.ts +0 -89
  556. package/src/core/components/menu/__workshop__/menuButton.tsx +0 -80
  557. package/src/core/components/menu/__workshop__/menuGroupRight.tsx +0 -58
  558. package/src/core/components/menu/__workshop__/nestedMenu.tsx +0 -22
  559. package/src/core/components/menu/__workshop__/onCloseMenuButton.tsx +0 -41
  560. package/src/core/components/menu/__workshop__/selectedItem.tsx +0 -69
  561. package/src/core/components/menu/__workshop__/shouldFocus.tsx +0 -47
  562. package/src/core/components/menu/__workshop__/tones.tsx +0 -25
  563. package/src/core/components/menu/__workshop__/withoutArrow.tsx +0 -32
  564. package/src/core/components/menu/helpers.ts +0 -82
  565. package/src/core/components/menu/index.ts +0 -5
  566. package/src/core/components/menu/menu.spacing.test.tsx +0 -63
  567. package/src/core/components/menu/menu.test.tsx +0 -125
  568. package/src/core/components/menu/menu.tsx +0 -178
  569. package/src/core/components/menu/menuButton.tsx +0 -268
  570. package/src/core/components/menu/menuContext.ts +0 -18
  571. package/src/core/components/menu/menuDivider.ts +0 -23
  572. package/src/core/components/menu/menuGroup.spacing.test.tsx +0 -63
  573. package/src/core/components/menu/menuGroup.tsx +0 -233
  574. package/src/core/components/menu/menuItem.spacing.test.tsx +0 -59
  575. package/src/core/components/menu/menuItem.tsx +0 -197
  576. package/src/core/components/menu/useMenu.ts +0 -21
  577. package/src/core/components/menu/useMenuController.ts +0 -231
  578. package/src/core/components/skeleton/__workshop__/delay.tsx +0 -70
  579. package/src/core/components/skeleton/__workshop__/index.ts +0 -11
  580. package/src/core/components/skeleton/__workshop__/skeleton.tsx +0 -64
  581. package/src/core/components/skeleton/index.ts +0 -2
  582. package/src/core/components/skeleton/skeleton.tsx +0 -57
  583. package/src/core/components/skeleton/styles.ts +0 -50
  584. package/src/core/components/skeleton/textSkeleton.tsx +0 -122
  585. package/src/core/components/tab/__workshop__/example.tsx +0 -79
  586. package/src/core/components/tab/__workshop__/index.ts +0 -14
  587. package/src/core/components/tab/index.ts +0 -3
  588. package/src/core/components/tab/tab.tsx +0 -101
  589. package/src/core/components/tab/tabList.test.tsx +0 -48
  590. package/src/core/components/tab/tabList.tsx +0 -70
  591. package/src/core/components/tab/tabPanel.tsx +0 -39
  592. package/src/core/components/toast/__workshop__/hook.tsx +0 -71
  593. package/src/core/components/toast/__workshop__/index.ts +0 -19
  594. package/src/core/components/toast/__workshop__/toast.tsx +0 -26
  595. package/src/core/components/toast/index.ts +0 -4
  596. package/src/core/components/toast/styles.ts +0 -77
  597. package/src/core/components/toast/toast.test.tsx +0 -101
  598. package/src/core/components/toast/toast.tsx +0 -198
  599. package/src/core/components/toast/toastContext.ts +0 -7
  600. package/src/core/components/toast/toastLayer.tsx +0 -51
  601. package/src/core/components/toast/toastProvider.tsx +0 -113
  602. package/src/core/components/toast/toastState.ts +0 -6
  603. package/src/core/components/toast/types.ts +0 -26
  604. package/src/core/components/toast/useToast.ts +0 -25
  605. package/src/core/components/tree/__workshop__/basic.perf.ts +0 -19
  606. package/src/core/components/tree/__workshop__/basic.tsx +0 -105
  607. package/src/core/components/tree/__workshop__/index.ts +0 -21
  608. package/src/core/components/tree/__workshop__/tabFromElement.tsx +0 -84
  609. package/src/core/components/tree/helpers.ts +0 -105
  610. package/src/core/components/tree/index.ts +0 -4
  611. package/src/core/components/tree/style.ts +0 -105
  612. package/src/core/components/tree/tree.test.tsx +0 -55
  613. package/src/core/components/tree/tree.tsx +0 -240
  614. package/src/core/components/tree/treeContext.ts +0 -10
  615. package/src/core/components/tree/treeGroup.tsx +0 -28
  616. package/src/core/components/tree/treeItem.test.tsx +0 -65
  617. package/src/core/components/tree/treeItem.tsx +0 -224
  618. package/src/core/components/tree/types.ts +0 -25
  619. package/src/core/components/tree/useTree.ts +0 -17
  620. package/src/core/constants.ts +0 -79
  621. package/src/core/helpers/animation.ts +0 -27
  622. package/src/core/helpers/element.ts +0 -55
  623. package/src/core/helpers/focus.ts +0 -100
  624. package/src/core/helpers/index.ts +0 -4
  625. package/src/core/helpers/scroll.ts +0 -15
  626. package/src/core/hooks/index.ts +0 -12
  627. package/src/core/hooks/useArrayProp.ts +0 -17
  628. package/src/core/hooks/useClickOutside.test.tsx +0 -482
  629. package/src/core/hooks/useClickOutside.ts +0 -118
  630. package/src/core/hooks/useClickOutsideEvent.test.tsx +0 -115
  631. package/src/core/hooks/useClickOutsideEvent.ts +0 -73
  632. package/src/core/hooks/useCustomValidity.ts +0 -13
  633. package/src/core/hooks/useDelayed.test.ts +0 -88
  634. package/src/core/hooks/useDelayedState.ts +0 -36
  635. package/src/core/hooks/useElementRect/__workshop__/example.tsx +0 -32
  636. package/src/core/hooks/useElementRect/__workshop__/index.ts +0 -14
  637. package/src/core/hooks/useElementRect/index.ts +0 -1
  638. package/src/core/hooks/useElementRect/useElementRect.ts +0 -13
  639. package/src/core/hooks/useElementSize.ts +0 -19
  640. package/src/core/hooks/useForwardedRef.ts +0 -19
  641. package/src/core/hooks/useGlobalKeyDown.ts +0 -24
  642. package/src/core/hooks/useIsomorphicEffect.ts +0 -7
  643. package/src/core/hooks/useMatchMedia.ts +0 -28
  644. package/src/core/hooks/useMediaIndex/__workshop__/index.ts +0 -14
  645. package/src/core/hooks/useMediaIndex/__workshop__/test.tsx +0 -14
  646. package/src/core/hooks/useMediaIndex/index.ts +0 -1
  647. package/src/core/hooks/useMediaIndex/useMediaIndex.test.tsx +0 -36
  648. package/src/core/hooks/useMediaIndex/useMediaIndex.ts +0 -102
  649. package/src/core/hooks/useMounted.ts +0 -15
  650. package/src/core/hooks/usePrefersDark.hydration.test.tsx +0 -56
  651. package/src/core/hooks/usePrefersDark.test.tsx +0 -19
  652. package/src/core/hooks/usePrefersDark.ts +0 -16
  653. package/src/core/hooks/usePrefersReducedMotion.hydration.test.tsx +0 -56
  654. package/src/core/hooks/usePrefersReducedMotion.test.tsx +0 -19
  655. package/src/core/hooks/usePrefersReducedMotion.ts +0 -16
  656. package/src/core/index.ts +0 -10
  657. package/src/core/lib/createGlobalScopedContext.ts +0 -36
  658. package/src/core/lib/globalScope.ts +0 -19
  659. package/src/core/lib/isRecord.ts +0 -3
  660. package/src/core/middleware/origin.ts +0 -47
  661. package/src/core/observers/elementSizeObserver.ts +0 -116
  662. package/src/core/observers/index.ts +0 -2
  663. package/src/core/observers/resizeObserver.ts +0 -9
  664. package/src/core/primitives/_selectable/index.ts +0 -1
  665. package/src/core/primitives/_selectable/selectable.tsx +0 -15
  666. package/src/core/primitives/_selectable/style.ts +0 -115
  667. package/src/core/primitives/avatar/__workshop__/asButton.tsx +0 -14
  668. package/src/core/primitives/avatar/__workshop__/index.ts +0 -29
  669. package/src/core/primitives/avatar/__workshop__/stack.tsx +0 -25
  670. package/src/core/primitives/avatar/__workshop__/withinButton.tsx +0 -63
  671. package/src/core/primitives/avatar/__workshop__/withinMenuItem.tsx +0 -70
  672. package/src/core/primitives/avatar/avatar.tsx +0 -200
  673. package/src/core/primitives/avatar/avatarCounter.tsx +0 -91
  674. package/src/core/primitives/avatar/avatarStack.tsx +0 -100
  675. package/src/core/primitives/avatar/index.ts +0 -4
  676. package/src/core/primitives/avatar/styles.ts +0 -167
  677. package/src/core/primitives/avatar/types.ts +0 -17
  678. package/src/core/primitives/badge/__workshop__/index.ts +0 -19
  679. package/src/core/primitives/badge/__workshop__/props.tsx +0 -30
  680. package/src/core/primitives/badge/__workshop__/tones.tsx +0 -21
  681. package/src/core/primitives/badge/badge.test.tsx +0 -16
  682. package/src/core/primitives/badge/badge.tsx +0 -70
  683. package/src/core/primitives/badge/index.ts +0 -1
  684. package/src/core/primitives/badge/styles.ts +0 -20
  685. package/src/core/primitives/badge/types.ts +0 -8
  686. package/src/core/primitives/box/__workshop__/asComponent.tsx +0 -31
  687. package/src/core/primitives/box/__workshop__/index.ts +0 -24
  688. package/src/core/primitives/box/__workshop__/props.tsx +0 -20
  689. package/src/core/primitives/box/__workshop__/responsive.tsx +0 -20
  690. package/src/core/primitives/box/box.test.tsx +0 -142
  691. package/src/core/primitives/box/box.tsx +0 -154
  692. package/src/core/primitives/box/index.ts +0 -1
  693. package/src/core/primitives/button/__workshop__/custom.tsx +0 -51
  694. package/src/core/primitives/button/__workshop__/customIcons.tsx +0 -30
  695. package/src/core/primitives/button/__workshop__/disabled.tsx +0 -74
  696. package/src/core/primitives/button/__workshop__/index.ts +0 -59
  697. package/src/core/primitives/button/__workshop__/labelUploadButton.tsx +0 -36
  698. package/src/core/primitives/button/__workshop__/mixedChildren.tsx +0 -12
  699. package/src/core/primitives/button/__workshop__/props.tsx +0 -55
  700. package/src/core/primitives/button/__workshop__/stacked.tsx +0 -58
  701. package/src/core/primitives/button/__workshop__/styled1.tsx +0 -17
  702. package/src/core/primitives/button/__workshop__/styled2.tsx +0 -19
  703. package/src/core/primitives/button/__workshop__/uploadButton.tsx +0 -28
  704. package/src/core/primitives/button/button.test.tsx +0 -82
  705. package/src/core/primitives/button/button.tsx +0 -210
  706. package/src/core/primitives/button/index.ts +0 -1
  707. package/src/core/primitives/button/styles.ts +0 -117
  708. package/src/core/primitives/card/__workshop__/allTones.tsx +0 -25
  709. package/src/core/primitives/card/__workshop__/asButton.tsx +0 -107
  710. package/src/core/primitives/card/__workshop__/asComponent.tsx +0 -27
  711. package/src/core/primitives/card/__workshop__/checkered.tsx +0 -26
  712. package/src/core/primitives/card/__workshop__/index.ts +0 -18
  713. package/src/core/primitives/card/__workshop__/interactive.tsx +0 -30
  714. package/src/core/primitives/card/__workshop__/listNavigation.tsx +0 -80
  715. package/src/core/primitives/card/__workshop__/props.tsx +0 -53
  716. package/src/core/primitives/card/__workshop__/selected.tsx +0 -94
  717. package/src/core/primitives/card/__workshop__/styled.tsx +0 -14
  718. package/src/core/primitives/card/card.tsx +0 -128
  719. package/src/core/primitives/card/index.ts +0 -2
  720. package/src/core/primitives/card/styles.ts +0 -147
  721. package/src/core/primitives/card/types.ts +0 -11
  722. package/src/core/primitives/checkbox/__workshop__/example.tsx +0 -25
  723. package/src/core/primitives/checkbox/__workshop__/index.ts +0 -13
  724. package/src/core/primitives/checkbox/__workshop__/props.tsx +0 -33
  725. package/src/core/primitives/checkbox/__workshop__/readOnly.tsx +0 -16
  726. package/src/core/primitives/checkbox/__workshop__/tones.tsx +0 -28
  727. package/src/core/primitives/checkbox/checkbox.tsx +0 -73
  728. package/src/core/primitives/checkbox/index.ts +0 -1
  729. package/src/core/primitives/checkbox/styles.ts +0 -128
  730. package/src/core/primitives/code/__workshop__/index.ts +0 -19
  731. package/src/core/primitives/code/__workshop__/opticalAlignment.tsx +0 -48
  732. package/src/core/primitives/code/__workshop__/props.tsx +0 -21
  733. package/src/core/primitives/code/code.tsx +0 -55
  734. package/src/core/primitives/code/index.ts +0 -1
  735. package/src/core/primitives/code/refractor.tsx +0 -20
  736. package/src/core/primitives/code/styles.ts +0 -77
  737. package/src/core/primitives/container/__workshop__/example.tsx +0 -20
  738. package/src/core/primitives/container/__workshop__/index.ts +0 -8
  739. package/src/core/primitives/container/container.tsx +0 -54
  740. package/src/core/primitives/container/index.ts +0 -2
  741. package/src/core/primitives/container/styles.ts +0 -23
  742. package/src/core/primitives/container/types.ts +0 -6
  743. package/src/core/primitives/flex/__workshop__/example.tsx +0 -34
  744. package/src/core/primitives/flex/__workshop__/gap.tsx +0 -25
  745. package/src/core/primitives/flex/__workshop__/index.ts +0 -11
  746. package/src/core/primitives/flex/flex.tsx +0 -64
  747. package/src/core/primitives/flex/index.ts +0 -1
  748. package/src/core/primitives/grid/__workshop__/index.ts +0 -10
  749. package/src/core/primitives/grid/__workshop__/responsive.tsx +0 -54
  750. package/src/core/primitives/grid/grid.test.tsx +0 -80
  751. package/src/core/primitives/grid/grid.tsx +0 -74
  752. package/src/core/primitives/grid/index.ts +0 -1
  753. package/src/core/primitives/heading/__workshop__/index.ts +0 -19
  754. package/src/core/primitives/heading/__workshop__/opticalAlignment.tsx +0 -54
  755. package/src/core/primitives/heading/__workshop__/plain.tsx +0 -32
  756. package/src/core/primitives/heading/heading.tsx +0 -88
  757. package/src/core/primitives/heading/index.ts +0 -1
  758. package/src/core/primitives/heading/styles.ts +0 -64
  759. package/src/core/primitives/heading/types.ts +0 -7
  760. package/src/core/primitives/index.ts +0 -27
  761. package/src/core/primitives/inline/__workshop__/index.ts +0 -8
  762. package/src/core/primitives/inline/__workshop__/plain.tsx +0 -27
  763. package/src/core/primitives/inline/index.ts +0 -1
  764. package/src/core/primitives/inline/inline.test.tsx +0 -51
  765. package/src/core/primitives/inline/inline.tsx +0 -63
  766. package/src/core/primitives/inline/styles.ts +0 -32
  767. package/src/core/primitives/inline/types.ts +0 -6
  768. package/src/core/primitives/kbd/__workshop__/index.ts +0 -8
  769. package/src/core/primitives/kbd/__workshop__/plain.tsx +0 -9
  770. package/src/core/primitives/kbd/index.ts +0 -1
  771. package/src/core/primitives/kbd/kbd.tsx +0 -69
  772. package/src/core/primitives/label/__workshop__/index.ts +0 -19
  773. package/src/core/primitives/label/__workshop__/opticalAlignment.tsx +0 -48
  774. package/src/core/primitives/label/__workshop__/plain.tsx +0 -24
  775. package/src/core/primitives/label/index.ts +0 -1
  776. package/src/core/primitives/label/label.tsx +0 -87
  777. package/src/core/primitives/label/styles.ts +0 -45
  778. package/src/core/primitives/popover/__workshop__/AlignedStory.tsx +0 -77
  779. package/src/core/primitives/popover/__workshop__/MarginsStory.tsx +0 -31
  780. package/src/core/primitives/popover/__workshop__/MatchReferenceWidthStory.tsx +0 -32
  781. package/src/core/primitives/popover/__workshop__/OpenOnMountStory.tsx +0 -11
  782. package/src/core/primitives/popover/__workshop__/PlainStory.tsx +0 -71
  783. package/src/core/primitives/popover/__workshop__/RecursiveStory.tsx +0 -71
  784. package/src/core/primitives/popover/__workshop__/SidePanelStory.tsx +0 -89
  785. package/src/core/primitives/popover/__workshop__/TestStory.tsx +0 -97
  786. package/src/core/primitives/popover/__workshop__/index.ts +0 -49
  787. package/src/core/primitives/popover/constants.ts +0 -22
  788. package/src/core/primitives/popover/floating-ui/size.ts +0 -99
  789. package/src/core/primitives/popover/helpers.ts +0 -31
  790. package/src/core/primitives/popover/index.ts +0 -2
  791. package/src/core/primitives/popover/popover.tsx +0 -511
  792. package/src/core/primitives/popover/popoverCard.tsx +0 -166
  793. package/src/core/primitives/popover/types.ts +0 -7
  794. package/src/core/primitives/radio/__workshop__/example.tsx +0 -63
  795. package/src/core/primitives/radio/__workshop__/index.ts +0 -11
  796. package/src/core/primitives/radio/__workshop__/plain.tsx +0 -28
  797. package/src/core/primitives/radio/index.ts +0 -1
  798. package/src/core/primitives/radio/radio.tsx +0 -51
  799. package/src/core/primitives/radio/styles.ts +0 -117
  800. package/src/core/primitives/select/__workshop__/index.ts +0 -11
  801. package/src/core/primitives/select/__workshop__/plain.tsx +0 -27
  802. package/src/core/primitives/select/__workshop__/readOnly.tsx +0 -23
  803. package/src/core/primitives/select/index.ts +0 -1
  804. package/src/core/primitives/select/select.test.tsx +0 -52
  805. package/src/core/primitives/select/select.tsx +0 -94
  806. package/src/core/primitives/select/styles.ts +0 -166
  807. package/src/core/primitives/spinner/__workshop__/Props.tsx +0 -15
  808. package/src/core/primitives/spinner/__workshop__/index.ts +0 -14
  809. package/src/core/primitives/spinner/index.ts +0 -1
  810. package/src/core/primitives/spinner/spinner.tsx +0 -46
  811. package/src/core/primitives/stack/__workshop__/index.ts +0 -14
  812. package/src/core/primitives/stack/__workshop__/plain.tsx +0 -32
  813. package/src/core/primitives/stack/index.ts +0 -1
  814. package/src/core/primitives/stack/stack.test.tsx +0 -57
  815. package/src/core/primitives/stack/stack.tsx +0 -57
  816. package/src/core/primitives/stack/styles.ts +0 -32
  817. package/src/core/primitives/switch/__workshop__/example.tsx +0 -23
  818. package/src/core/primitives/switch/__workshop__/index.ts +0 -11
  819. package/src/core/primitives/switch/__workshop__/props.tsx +0 -21
  820. package/src/core/primitives/switch/index.ts +0 -1
  821. package/src/core/primitives/switch/styles.ts +0 -173
  822. package/src/core/primitives/switch/switch.tsx +0 -68
  823. package/src/core/primitives/text/__workshop__/colored.tsx +0 -30
  824. package/src/core/primitives/text/__workshop__/example.tsx +0 -39
  825. package/src/core/primitives/text/__workshop__/index.ts +0 -24
  826. package/src/core/primitives/text/__workshop__/opticalAlignment.tsx +0 -48
  827. package/src/core/primitives/text/index.ts +0 -1
  828. package/src/core/primitives/text/styles.ts +0 -74
  829. package/src/core/primitives/text/text.tsx +0 -89
  830. package/src/core/primitives/textArea/__workshop__/index.ts +0 -14
  831. package/src/core/primitives/textArea/__workshop__/plain.tsx +0 -50
  832. package/src/core/primitives/textArea/index.ts +0 -1
  833. package/src/core/primitives/textArea/textArea.tsx +0 -120
  834. package/src/core/primitives/textInput/__workshop__/clearButton.tsx +0 -30
  835. package/src/core/primitives/textInput/__workshop__/customValidity.tsx +0 -17
  836. package/src/core/primitives/textInput/__workshop__/index.ts +0 -44
  837. package/src/core/primitives/textInput/__workshop__/plain.tsx +0 -98
  838. package/src/core/primitives/textInput/__workshop__/readOnly.tsx +0 -9
  839. package/src/core/primitives/textInput/__workshop__/states.tsx +0 -53
  840. package/src/core/primitives/textInput/__workshop__/tones.tsx +0 -277
  841. package/src/core/primitives/textInput/__workshop__/typed.tsx +0 -23
  842. package/src/core/primitives/textInput/index.ts +0 -1
  843. package/src/core/primitives/textInput/textInput.test.tsx +0 -48
  844. package/src/core/primitives/textInput/textInput.tsx +0 -395
  845. package/src/core/primitives/tooltip/__workshop__/customPortal.tsx +0 -102
  846. package/src/core/primitives/tooltip/__workshop__/index.ts +0 -29
  847. package/src/core/primitives/tooltip/__workshop__/overflowingBoundary.tsx +0 -74
  848. package/src/core/primitives/tooltip/__workshop__/props.tsx +0 -107
  849. package/src/core/primitives/tooltip/__workshop__/resizableBoundary.tsx +0 -86
  850. package/src/core/primitives/tooltip/constants.ts +0 -23
  851. package/src/core/primitives/tooltip/index.ts +0 -2
  852. package/src/core/primitives/tooltip/tooltip.test.tsx +0 -452
  853. package/src/core/primitives/tooltip/tooltip.tsx +0 -495
  854. package/src/core/primitives/tooltip/tooltipCard.tsx +0 -109
  855. package/src/core/primitives/tooltip/tooltipDelayGroup/index.ts +0 -4
  856. package/src/core/primitives/tooltip/tooltipDelayGroup/tooltipDelayGroupContext.tsx +0 -11
  857. package/src/core/primitives/tooltip/tooltipDelayGroup/tooltipDelayGroupProvider.tsx +0 -57
  858. package/src/core/primitives/tooltip/tooltipDelayGroup/types.ts +0 -10
  859. package/src/core/primitives/tooltip/tooltipDelayGroup/useTooltipDelayGroup.ts +0 -13
  860. package/src/core/primitives/types.ts +0 -179
  861. package/src/core/styles/border/borderStyle.ts +0 -56
  862. package/src/core/styles/border/index.ts +0 -2
  863. package/src/core/styles/border/types.ts +0 -10
  864. package/src/core/styles/box/boxStyle.ts +0 -69
  865. package/src/core/styles/box/index.ts +0 -2
  866. package/src/core/styles/box/types.ts +0 -11
  867. package/src/core/styles/card/_cardColorStyle.ts +0 -108
  868. package/src/core/styles/card/index.ts +0 -1
  869. package/src/core/styles/flex/flexItemStyle.ts +0 -27
  870. package/src/core/styles/flex/flexStyle.ts +0 -70
  871. package/src/core/styles/flex/index.ts +0 -3
  872. package/src/core/styles/flex/types.ts +0 -25
  873. package/src/core/styles/focusRing/index.ts +0 -25
  874. package/src/core/styles/font/codeFontStyle.ts +0 -13
  875. package/src/core/styles/font/headingFontStyle.ts +0 -13
  876. package/src/core/styles/font/index.ts +0 -6
  877. package/src/core/styles/font/labelFontStyle.ts +0 -13
  878. package/src/core/styles/font/responsiveFont.ts +0 -103
  879. package/src/core/styles/font/textAlignStyle.ts +0 -19
  880. package/src/core/styles/font/textFontStyle.ts +0 -13
  881. package/src/core/styles/font/types.ts +0 -35
  882. package/src/core/styles/grid/gridItemStyle.ts +0 -82
  883. package/src/core/styles/grid/gridStyle.ts +0 -108
  884. package/src/core/styles/grid/index.ts +0 -3
  885. package/src/core/styles/grid/types.ts +0 -37
  886. package/src/core/styles/helpers.ts +0 -72
  887. package/src/core/styles/index.ts +0 -5
  888. package/src/core/styles/input/index.ts +0 -2
  889. package/src/core/styles/input/responsiveInputPaddingStyle.ts +0 -78
  890. package/src/core/styles/input/textInputStyle.ts +0 -263
  891. package/src/core/styles/internal.ts +0 -12
  892. package/src/core/styles/margin/index.ts +0 -2
  893. package/src/core/styles/margin/marginStyle.ts +0 -21
  894. package/src/core/styles/margin/marginsStyle.test.ts +0 -33
  895. package/src/core/styles/margin/types.ts +0 -9
  896. package/src/core/styles/padding/index.ts +0 -2
  897. package/src/core/styles/padding/paddingStyle.test.ts +0 -33
  898. package/src/core/styles/padding/paddingStyle.ts +0 -21
  899. package/src/core/styles/padding/types.ts +0 -12
  900. package/src/core/styles/radius/index.ts +0 -2
  901. package/src/core/styles/radius/radiusStyle.ts +0 -23
  902. package/src/core/styles/radius/types.ts +0 -8
  903. package/src/core/styles/shadow/index.ts +0 -2
  904. package/src/core/styles/shadow/shadowStyle.ts +0 -29
  905. package/src/core/styles/shadow/types.ts +0 -6
  906. package/src/core/styles/types.ts +0 -8
  907. package/src/core/theme/__workshop__/build/Root.tsx +0 -367
  908. package/src/core/theme/__workshop__/build/helpers.ts +0 -46
  909. package/src/core/theme/__workshop__/build/story.tsx +0 -465
  910. package/src/core/theme/__workshop__/canvas.tsx +0 -352
  911. package/src/core/theme/__workshop__/color.tsx +0 -62
  912. package/src/core/theme/__workshop__/debug/story.tsx +0 -408
  913. package/src/core/theme/__workshop__/index.ts +0 -39
  914. package/src/core/theme/__workshop__/layer.tsx +0 -78
  915. package/src/core/theme/__workshop__/nestedProvider.tsx +0 -15
  916. package/src/core/theme/index.ts +0 -5
  917. package/src/core/theme/theme.test.tsx +0 -73
  918. package/src/core/theme/themeColorProvider.tsx +0 -29
  919. package/src/core/theme/themeContext.ts +0 -10
  920. package/src/core/theme/themeProvider.tsx +0 -62
  921. package/src/core/theme/types.ts +0 -12
  922. package/src/core/theme/useRootTheme.ts +0 -17
  923. package/src/core/theme/useTheme.ts +0 -16
  924. package/src/core/types/avatar.ts +0 -14
  925. package/src/core/types/badge.ts +0 -12
  926. package/src/core/types/box.ts +0 -19
  927. package/src/core/types/button.ts +0 -21
  928. package/src/core/types/card.ts +0 -6
  929. package/src/core/types/component.ts +0 -32
  930. package/src/core/types/dialog.ts +0 -4
  931. package/src/core/types/flex.ts +0 -30
  932. package/src/core/types/grid.ts +0 -14
  933. package/src/core/types/gridItem.ts +0 -29
  934. package/src/core/types/index.ts +0 -15
  935. package/src/core/types/placement.ts +0 -18
  936. package/src/core/types/popover.ts +0 -4
  937. package/src/core/types/radius.ts +0 -4
  938. package/src/core/types/selectable.ts +0 -6
  939. package/src/core/types/text.ts +0 -4
  940. package/src/core/utils/arrow/arrow.tsx +0 -123
  941. package/src/core/utils/arrow/cmds.ts +0 -91
  942. package/src/core/utils/arrow/index.ts +0 -1
  943. package/src/core/utils/boundaryElement/__workshop__/index.ts +0 -14
  944. package/src/core/utils/boundaryElement/__workshop__/plain.tsx +0 -19
  945. package/src/core/utils/boundaryElement/boundaryElement.test.tsx +0 -100
  946. package/src/core/utils/boundaryElement/boundaryElementContext.ts +0 -7
  947. package/src/core/utils/boundaryElement/boundaryElementProvider.tsx +0 -24
  948. package/src/core/utils/boundaryElement/index.ts +0 -3
  949. package/src/core/utils/boundaryElement/types.ts +0 -7
  950. package/src/core/utils/boundaryElement/useBoundaryElement.ts +0 -26
  951. package/src/core/utils/conditionalWrapper/conditionalWrapper.tsx +0 -21
  952. package/src/core/utils/conditionalWrapper/index.ts +0 -1
  953. package/src/core/utils/elementQuery/__workshop__/customMedia.tsx +0 -30
  954. package/src/core/utils/elementQuery/__workshop__/index.ts +0 -14
  955. package/src/core/utils/elementQuery/elementQuery.tsx +0 -53
  956. package/src/core/utils/elementQuery/helpers.ts +0 -27
  957. package/src/core/utils/elementQuery/index.ts +0 -1
  958. package/src/core/utils/errorBoundary.tsx +0 -48
  959. package/src/core/utils/getElementRef.ts +0 -26
  960. package/src/core/utils/index.ts +0 -9
  961. package/src/core/utils/layer/__workshop__/_debug.tsx +0 -17
  962. package/src/core/utils/layer/__workshop__/index.ts +0 -24
  963. package/src/core/utils/layer/__workshop__/multipleRoots.tsx +0 -49
  964. package/src/core/utils/layer/__workshop__/nested.tsx +0 -98
  965. package/src/core/utils/layer/__workshop__/responsiveZOffset.tsx +0 -13
  966. package/src/core/utils/layer/getLayerContext.test.ts +0 -30
  967. package/src/core/utils/layer/getLayerContext.ts +0 -21
  968. package/src/core/utils/layer/index.ts +0 -4
  969. package/src/core/utils/layer/layer.test.tsx +0 -108
  970. package/src/core/utils/layer/layer.tsx +0 -98
  971. package/src/core/utils/layer/layerContext.ts +0 -7
  972. package/src/core/utils/layer/layerProvider.tsx +0 -111
  973. package/src/core/utils/layer/types.ts +0 -9
  974. package/src/core/utils/layer/useLayer.ts +0 -26
  975. package/src/core/utils/portal/__workshop__/index.ts +0 -14
  976. package/src/core/utils/portal/__workshop__/named.tsx +0 -63
  977. package/src/core/utils/portal/index.ts +0 -4
  978. package/src/core/utils/portal/portal.test.tsx +0 -103
  979. package/src/core/utils/portal/portal.ts +0 -32
  980. package/src/core/utils/portal/portalContext.ts +0 -31
  981. package/src/core/utils/portal/portalProvider.tsx +0 -47
  982. package/src/core/utils/portal/types.ts +0 -9
  983. package/src/core/utils/portal/usePortal.ts +0 -25
  984. package/src/core/utils/spanWithTextOverflow.tsx +0 -10
  985. package/src/core/utils/srOnly/index.ts +0 -1
  986. package/src/core/utils/srOnly/srOnly.tsx +0 -36
  987. package/src/core/utils/virtualList/__workshop__/changingProps.tsx +0 -36
  988. package/src/core/utils/virtualList/__workshop__/elementScroll.tsx +0 -24
  989. package/src/core/utils/virtualList/__workshop__/index.ts +0 -29
  990. package/src/core/utils/virtualList/__workshop__/infiniteList.tsx +0 -48
  991. package/src/core/utils/virtualList/__workshop__/windowScrolll.tsx +0 -22
  992. package/src/core/utils/virtualList/index.ts +0 -1
  993. package/src/core/utils/virtualList/virtualList.tsx +0 -200
  994. package/src/theme/build/_deprecated/color/_selectable/createSelectableTones.ts +0 -76
  995. package/src/theme/build/_deprecated/color/_solid/createSolidTones.ts +0 -54
  996. package/src/theme/build/_deprecated/color/button/createButtonModes.ts +0 -22
  997. package/src/theme/build/_deprecated/color/button/createButtonTones.ts +0 -55
  998. package/src/theme/build/_deprecated/color/card/createCardStates.ts +0 -60
  999. package/src/theme/build/_deprecated/color/color.test.ts +0 -63
  1000. package/src/theme/build/_deprecated/color/defaults.ts +0 -390
  1001. package/src/theme/build/_deprecated/color/factory.ts +0 -142
  1002. package/src/theme/build/_deprecated/color/index.ts +0 -1
  1003. package/src/theme/build/_deprecated/color/input/createInputModes.ts +0 -81
  1004. package/src/theme/build/_deprecated/color/muted/createMuted.ts +0 -54
  1005. package/src/theme/build/_deprecated/color/spot/createSpot.ts +0 -20
  1006. package/src/theme/build/buildColorTheme.test.ts +0 -183
  1007. package/src/theme/build/buildColorTheme.ts +0 -499
  1008. package/src/theme/build/buildTheme.test.ts +0 -87
  1009. package/src/theme/build/buildTheme.ts +0 -36
  1010. package/src/theme/build/colorToken/colorToken.ts +0 -19
  1011. package/src/theme/build/colorToken/compileColorToken.ts +0 -21
  1012. package/src/theme/build/colorToken/index.ts +0 -2
  1013. package/src/theme/build/colorToken/types.ts +0 -6
  1014. package/src/theme/build/index.ts +0 -3
  1015. package/src/theme/build/lib/color-fns/blend/index.ts +0 -3
  1016. package/src/theme/build/lib/color-fns/blend/mix.ts +0 -21
  1017. package/src/theme/build/lib/color-fns/blend/multiply.ts +0 -18
  1018. package/src/theme/build/lib/color-fns/blend/screen.ts +0 -18
  1019. package/src/theme/build/lib/color-fns/color-fns.test.ts +0 -68
  1020. package/src/theme/build/lib/color-fns/contrastRatio.ts +0 -31
  1021. package/src/theme/build/lib/color-fns/convert.ts +0 -150
  1022. package/src/theme/build/lib/color-fns/index.ts +0 -6
  1023. package/src/theme/build/lib/color-fns/parse.ts +0 -60
  1024. package/src/theme/build/lib/color-fns/rgba.ts +0 -10
  1025. package/src/theme/build/lib/color-fns/types.ts +0 -28
  1026. package/src/theme/build/lib/isRecord.ts +0 -3
  1027. package/src/theme/build/lib/lazy.ts +0 -30
  1028. package/src/theme/build/lib/utils.ts +0 -19
  1029. package/src/theme/build/merge.ts +0 -26
  1030. package/src/theme/build/mixThemeColor.test.ts +0 -21
  1031. package/src/theme/build/mixThemeColor.ts +0 -53
  1032. package/src/theme/build/renderColorTheme.ts +0 -532
  1033. package/src/theme/build/renderColorValue.ts +0 -85
  1034. package/src/theme/build/resolveColorTokens.ts +0 -268
  1035. package/src/theme/build/theme.test.ts +0 -27
  1036. package/src/theme/config/helpers.ts +0 -55
  1037. package/src/theme/config/index.ts +0 -4
  1038. package/src/theme/config/system.ts +0 -115
  1039. package/src/theme/config/tokens/color/index.ts +0 -1
  1040. package/src/theme/config/tokens/color/types.ts +0 -133
  1041. package/src/theme/config/tokens/index.ts +0 -4
  1042. package/src/theme/config/tokens/parseTokenKey.test.ts +0 -64
  1043. package/src/theme/config/tokens/parseTokenKey.ts +0 -67
  1044. package/src/theme/config/tokens/parseTokenValue.test.ts +0 -42
  1045. package/src/theme/config/tokens/parseTokenValue.ts +0 -133
  1046. package/src/theme/config/tokens/types.ts +0 -53
  1047. package/src/theme/config/types.ts +0 -46
  1048. package/src/theme/defaults/colorPalette.ts +0 -5
  1049. package/src/theme/defaults/colorTokens.ts +0 -648
  1050. package/src/theme/defaults/config.ts +0 -84
  1051. package/src/theme/defaults/fonts.ts +0 -227
  1052. package/src/theme/getScopedTheme.ts +0 -98
  1053. package/src/theme/index.ts +0 -5
  1054. package/src/theme/system/avatar.ts +0 -14
  1055. package/src/theme/system/color/_constants.ts +0 -45
  1056. package/src/theme/system/color/_helpers.ts +0 -24
  1057. package/src/theme/system/color/_system.ts +0 -40
  1058. package/src/theme/system/color/avatar.ts +0 -15
  1059. package/src/theme/system/color/badge.ts +0 -16
  1060. package/src/theme/system/color/button.ts +0 -17
  1061. package/src/theme/system/color/color.ts +0 -34
  1062. package/src/theme/system/color/index.ts +0 -13
  1063. package/src/theme/system/color/input.ts +0 -25
  1064. package/src/theme/system/color/kbd.ts +0 -8
  1065. package/src/theme/system/color/selectable.ts +0 -12
  1066. package/src/theme/system/color/shadow.ts +0 -7
  1067. package/src/theme/system/color/state.ts +0 -43
  1068. package/src/theme/system/color/syntax.ts +0 -41
  1069. package/src/theme/system/css.ts +0 -14
  1070. package/src/theme/system/focusRing.ts +0 -7
  1071. package/src/theme/system/font.ts +0 -52
  1072. package/src/theme/system/index.ts +0 -11
  1073. package/src/theme/system/input.ts +0 -33
  1074. package/src/theme/system/layer.ts +0 -8
  1075. package/src/theme/system/shadow.ts +0 -19
  1076. package/src/theme/system/styles.ts +0 -13
  1077. package/src/theme/system/theme.ts +0 -138
  1078. package/src/theme/system/v0/avatar.ts +0 -10
  1079. package/src/theme/system/v0/color/_generic.ts +0 -35
  1080. package/src/theme/system/v0/color/_system.ts +0 -17
  1081. package/src/theme/system/v0/color/base.ts +0 -21
  1082. package/src/theme/system/v0/color/button.ts +0 -42
  1083. package/src/theme/system/v0/color/card.ts +0 -19
  1084. package/src/theme/system/v0/color/color.ts +0 -40
  1085. package/src/theme/system/v0/color/index.ts +0 -11
  1086. package/src/theme/system/v0/color/input.ts +0 -34
  1087. package/src/theme/system/v0/color/muted.ts +0 -26
  1088. package/src/theme/system/v0/color/selectable.ts +0 -31
  1089. package/src/theme/system/v0/color/solid.ts +0 -26
  1090. package/src/theme/system/v0/color/spot.ts +0 -13
  1091. package/src/theme/system/v0/index.ts +0 -3
  1092. package/src/theme/system/v0/input.ts +0 -23
  1093. package/src/theme/versioning/getTheme_v2.ts +0 -57
  1094. package/src/theme/versioning/index.ts +0 -5
  1095. package/src/theme/versioning/is_v0.ts +0 -6
  1096. package/src/theme/versioning/is_v2.ts +0 -6
  1097. package/src/theme/versioning/themeColor_v0_v2.ts +0 -243
  1098. package/src/theme/versioning/themeColor_v2_v2_9.ts +0 -49
  1099. package/src/theme/versioning/v0_v2.ts +0 -94
  1100. package/src/theme/versioning/v2_v0.ts +0 -135
@@ -0,0 +1,4140 @@
1
+ import { tableFrom } from "../../schema/table.js";
2
+ //#region src/library/entries/commerce.entries.ts
3
+ var img = (seed, w = 1200, h = 900, alt = "") => ({
4
+ url: `https://picsum.photos/seed/${seed}/${w}/${h}`,
5
+ alt,
6
+ name: `${seed}.jpg`,
7
+ width: w,
8
+ height: h
9
+ });
10
+ /** Products entry references (Content › Products). Price and stock resolve through the entry's Shopify link. */
11
+ var P = {
12
+ grill: "en_p1",
13
+ dutch: "en_p2",
14
+ skillet: "en_p3",
15
+ kettle: "en_p4"
16
+ };
17
+ var productRef = (name, title, description) => ({
18
+ name,
19
+ title,
20
+ type: "reference",
21
+ reference: { to: ["col_products"] },
22
+ ...description ? { description } : {}
23
+ });
24
+ var themeField = {
25
+ name: "theme",
26
+ title: "Theme",
27
+ type: "select",
28
+ group: "design",
29
+ layout: "segmented",
30
+ initialValue: "light",
31
+ options: [
32
+ {
33
+ value: "light",
34
+ label: "Light"
35
+ },
36
+ {
37
+ value: "dark",
38
+ label: "Dark"
39
+ },
40
+ {
41
+ value: "brand",
42
+ label: "Brand"
43
+ }
44
+ ]
45
+ };
46
+ var ENTRIES = [
47
+ {
48
+ id: "commerce-product-grid-query",
49
+ label: "Product grid (query)",
50
+ proves: "A saved query (collection · filters · sort · limit) vs hand-picked products · per-breakpoint columns · card display toggles",
51
+ category: "Commerce",
52
+ brand: "Caldera",
53
+ previewLayout: "grid-4",
54
+ tags: [
55
+ "dynamic-query",
56
+ "commerce",
57
+ "conditional",
58
+ "references",
59
+ "repeater",
60
+ "validation",
61
+ "variants",
62
+ "responsive-layout"
63
+ ],
64
+ schema: {
65
+ name: "productGrid",
66
+ title: "Product grid",
67
+ kind: "block",
68
+ icon: "grid-dots",
69
+ category: "Commerce",
70
+ description: "Products from a live query or picked by hand",
71
+ variants: [{
72
+ id: "grid",
73
+ name: "Grid",
74
+ description: "Rows of cards"
75
+ }, {
76
+ id: "carousel",
77
+ name: "Carousel",
78
+ description: "One scrolling row with a peek"
79
+ }],
80
+ preview: {
81
+ title: "heading",
82
+ subtitle: "eyebrow",
83
+ icon: "layout-grid"
84
+ },
85
+ groups: [
86
+ {
87
+ name: "content",
88
+ title: "Content",
89
+ default: true
90
+ },
91
+ {
92
+ name: "products",
93
+ title: "Products"
94
+ },
95
+ {
96
+ name: "design",
97
+ title: "Design"
98
+ }
99
+ ],
100
+ fieldsets: [{
101
+ name: "card",
102
+ title: "Card",
103
+ description: "What each product card shows."
104
+ }, {
105
+ name: "columns",
106
+ title: "Columns"
107
+ }],
108
+ fields: [
109
+ {
110
+ name: "eyebrow",
111
+ title: "Eyebrow",
112
+ type: "string",
113
+ validation: { max: 40 }
114
+ },
115
+ {
116
+ name: "heading",
117
+ title: "Heading",
118
+ type: "string",
119
+ validation: {
120
+ required: true,
121
+ recommendedMax: 48
122
+ },
123
+ ai: true,
124
+ localized: true
125
+ },
126
+ {
127
+ name: "intro",
128
+ title: "Intro",
129
+ type: "text",
130
+ rows: 2,
131
+ validation: { recommendedMax: 160 },
132
+ ai: true
133
+ },
134
+ {
135
+ name: "source",
136
+ title: "Products come from",
137
+ type: "select",
138
+ group: "products",
139
+ layout: "segmented",
140
+ initialValue: "query",
141
+ options: [{
142
+ value: "query",
143
+ label: "A query",
144
+ description: "Updates on its own as products change"
145
+ }, {
146
+ value: "manual",
147
+ label: "Picked by hand",
148
+ description: "A fixed list in your order"
149
+ }]
150
+ },
151
+ {
152
+ name: "query",
153
+ title: "Query",
154
+ type: "object",
155
+ icon: "filter",
156
+ group: "products",
157
+ hidden: {
158
+ field: "source",
159
+ equals: "query"
160
+ },
161
+ description: "Re-runs on every publish and when stock changes.",
162
+ fields: [
163
+ {
164
+ name: "collection",
165
+ title: "Collection",
166
+ type: "reference",
167
+ reference: {
168
+ to: ["collection"],
169
+ filter: "singular == 'Product'"
170
+ },
171
+ validation: { required: true }
172
+ },
173
+ {
174
+ name: "filters",
175
+ title: "Filters",
176
+ type: "array",
177
+ itemNoun: "filter",
178
+ openMode: "inline",
179
+ validation: { max: 4 },
180
+ description: "Every filter must match. Leave empty to show the whole collection.",
181
+ of: [{
182
+ name: "filter",
183
+ title: "Filter",
184
+ type: "object",
185
+ icon: "filter",
186
+ preview: {
187
+ title: "field",
188
+ subtitle: "value",
189
+ fallback: "Untitled filter"
190
+ },
191
+ fields: [
192
+ {
193
+ name: "field",
194
+ title: "Field",
195
+ type: "select",
196
+ validation: { required: true },
197
+ options: [
198
+ {
199
+ value: "material",
200
+ label: "Material"
201
+ },
202
+ {
203
+ value: "tags",
204
+ label: "Tag"
205
+ },
206
+ {
207
+ value: "productType",
208
+ label: "Product type"
209
+ },
210
+ {
211
+ value: "price",
212
+ label: "Price"
213
+ },
214
+ {
215
+ value: "rating",
216
+ label: "Rating"
217
+ },
218
+ {
219
+ value: "createdAt",
220
+ label: "Date added"
221
+ }
222
+ ]
223
+ },
224
+ {
225
+ name: "operator",
226
+ title: "Condition",
227
+ type: "select",
228
+ initialValue: "eq",
229
+ options: [
230
+ {
231
+ value: "eq",
232
+ label: "Is"
233
+ },
234
+ {
235
+ value: "neq",
236
+ label: "Is not"
237
+ },
238
+ {
239
+ value: "contains",
240
+ label: "Contains"
241
+ },
242
+ {
243
+ value: "gte",
244
+ label: "Is at least"
245
+ },
246
+ {
247
+ value: "lte",
248
+ label: "Is at most"
249
+ },
250
+ {
251
+ value: "within",
252
+ label: "In the last N days"
253
+ }
254
+ ]
255
+ },
256
+ {
257
+ name: "value",
258
+ title: "Value",
259
+ type: "string",
260
+ validation: {
261
+ required: true,
262
+ max: 48
263
+ },
264
+ description: "Matches exactly. Prices in dollars, dates in days."
265
+ }
266
+ ]
267
+ }]
268
+ },
269
+ {
270
+ name: "sort",
271
+ title: "Sort by",
272
+ type: "select",
273
+ initialValue: "bestselling",
274
+ options: [
275
+ {
276
+ value: "bestselling",
277
+ label: "Best selling",
278
+ description: "Shopify sales, last 30 days"
279
+ },
280
+ {
281
+ value: "newest",
282
+ label: "Newest first"
283
+ },
284
+ {
285
+ value: "price-asc",
286
+ label: "Price, low to high"
287
+ },
288
+ {
289
+ value: "price-desc",
290
+ label: "Price, high to low"
291
+ },
292
+ {
293
+ value: "rating",
294
+ label: "Highest rated"
295
+ },
296
+ {
297
+ value: "collection",
298
+ label: "Collection order",
299
+ description: "The order set in Content › Products"
300
+ }
301
+ ]
302
+ },
303
+ {
304
+ name: "limit",
305
+ title: "How many",
306
+ type: "number",
307
+ initialValue: 8,
308
+ numeric: {
309
+ min: 1,
310
+ max: 24,
311
+ step: 1,
312
+ unit: "products"
313
+ },
314
+ validation: {
315
+ required: true,
316
+ min: 1,
317
+ max: 24
318
+ }
319
+ },
320
+ {
321
+ name: "hideSoldOut",
322
+ title: "Hide sold-out products",
323
+ type: "boolean",
324
+ initialValue: true
325
+ },
326
+ {
327
+ name: "excludeCurrent",
328
+ title: "Leave out this page",
329
+ type: "boolean",
330
+ initialValue: true
331
+ }
332
+ ]
333
+ },
334
+ {
335
+ name: "products",
336
+ title: "Products",
337
+ type: "array",
338
+ group: "products",
339
+ hidden: {
340
+ field: "source",
341
+ equals: "manual"
342
+ },
343
+ validation: {
344
+ min: 1,
345
+ max: 12
346
+ },
347
+ of: [productRef("product", "Product")]
348
+ },
349
+ {
350
+ name: "viewAllButton",
351
+ title: "View all button",
352
+ type: "button",
353
+ description: "Leave empty to hide it."
354
+ },
355
+ {
356
+ name: "showPrice",
357
+ title: "Price",
358
+ type: "boolean",
359
+ group: "design",
360
+ fieldset: "card",
361
+ initialValue: true
362
+ },
363
+ {
364
+ name: "showRating",
365
+ title: "Show rating",
366
+ type: "boolean",
367
+ group: "design",
368
+ fieldset: "card",
369
+ initialValue: true,
370
+ description: "Hidden for products with fewer than 3 reviews."
371
+ },
372
+ {
373
+ name: "showSwatches",
374
+ title: "Color swatches",
375
+ type: "boolean",
376
+ group: "design",
377
+ fieldset: "card"
378
+ },
379
+ {
380
+ name: "showQuickAdd",
381
+ title: "Show quick add",
382
+ type: "boolean",
383
+ group: "design",
384
+ fieldset: "card"
385
+ },
386
+ {
387
+ name: "imageRatio",
388
+ title: "Image shape",
389
+ type: "select",
390
+ group: "design",
391
+ fieldset: "card",
392
+ layout: "segmented",
393
+ initialValue: "4:5",
394
+ options: [
395
+ {
396
+ value: "1:1",
397
+ label: "Square"
398
+ },
399
+ {
400
+ value: "4:5",
401
+ label: "Portrait"
402
+ },
403
+ {
404
+ value: "4:3",
405
+ label: "Landscape"
406
+ }
407
+ ]
408
+ },
409
+ {
410
+ name: "columnsDesktop",
411
+ title: "Desktop",
412
+ type: "number",
413
+ group: "design",
414
+ fieldset: "columns",
415
+ layout: "slider",
416
+ initialValue: 4,
417
+ numeric: {
418
+ min: 2,
419
+ max: 5,
420
+ step: 1
421
+ }
422
+ },
423
+ {
424
+ name: "columnsMobile",
425
+ title: "Phone",
426
+ type: "select",
427
+ group: "design",
428
+ fieldset: "columns",
429
+ layout: "segmented",
430
+ initialValue: "2",
431
+ options: [{
432
+ value: "1",
433
+ label: "1"
434
+ }, {
435
+ value: "2",
436
+ label: "2"
437
+ }]
438
+ },
439
+ themeField
440
+ ]
441
+ },
442
+ value: {
443
+ _variant: "grid",
444
+ eyebrow: "Built for the fire",
445
+ heading: "Cast iron, best sellers first",
446
+ intro: "Pre-seasoned in Bend, Oregon and backed by a lifetime warranty.",
447
+ source: "query",
448
+ query: {
449
+ collection: "col_products",
450
+ filters: [{
451
+ _key: "f1",
452
+ field: "material",
453
+ operator: "eq",
454
+ value: "Cast iron"
455
+ }, {
456
+ _key: "f2",
457
+ field: "price",
458
+ operator: "lte",
459
+ value: "200"
460
+ }],
461
+ sort: "bestselling",
462
+ limit: 8,
463
+ hideSoldOut: true,
464
+ excludeCurrent: true
465
+ },
466
+ products: [
467
+ P.dutch,
468
+ P.skillet,
469
+ P.grill
470
+ ],
471
+ viewAllButton: {
472
+ label: "Shop all cast iron",
473
+ href: "/collections/cast-iron",
474
+ type: "page",
475
+ style: "outline",
476
+ arrow: true
477
+ },
478
+ showPrice: true,
479
+ showRating: true,
480
+ showSwatches: true,
481
+ showQuickAdd: false,
482
+ imageRatio: "4:5",
483
+ columnsDesktop: 4,
484
+ columnsMobile: "2",
485
+ theme: "light"
486
+ },
487
+ notes: {
488
+ why: "A grid is a QUERY, not a list: most merch grids should keep themselves current (new arrivals, best sellers, a material), so the default `source` is a saved query object — collection reference + filter rows + sort + limit — and hand-picking is the conditional alternative. Filters are an array of tiny objects (field · rule · value) so they render inline and stay readable; price and stock are never stored here, the card reads them live through each Products entry's Shopify link. Card toggles and columns are design, so they live in the Design group (columns per breakpoint = responsive-layout).",
489
+ sanity: "Sanity has no saved-query field: you'd model `productGrid` with a `mode` string, `products: array<reference to product>` and either a hard-coded GROQ in the frontend per mode or a free-text `filter` string. With Sanity Connect for Shopify, products are `product` documents whose commerce data sits under `store` (`store.priceRange.minVariantPrice`, `store.status`, `store.variants[]->`), so the grid queries `*[_type=='product' && store.status=='active' && !store.isDeleted]`.",
490
+ htmlSignals: [
491
+ "a repeated card list (ul/div grid) where each card has an <img>, a product title link to /products/…, and a price with a currency symbol",
492
+ "a struck-through price (<s>, <del>, .compare-at, .price--sale) next to a second price",
493
+ "'Add to cart' / 'Quick add' buttons or forms posting to /cart/add",
494
+ "a heading plus a 'View all' / 'Shop all' link to /collections/…",
495
+ "Shopify markup: data-product-id, .product-card, .card--product, /cdn/shop/products/ image URLs"
496
+ ],
497
+ mcp: "Map the heading/eyebrow/intro text. For each card, resolve the /products/<handle> link to a Products entry (create a stub entry linked to the Shopify product if missing) — never store the card's price, title or image as section values. If the cards share an obvious rule (all one collection URL, 'New', 'Best sellers'), emit source:'query' with the matching filter/sort and limit = number of cards; otherwise source:'manual' with the entry ids in DOM order. Count grid-template-columns / Tailwind grid-cols-* at the md+ breakpoint for columnsDesktop and the base breakpoint for columnsMobile. Carousel markup (overflow-x, swiper, flickity, scroll-snap) → _variant:'carousel'.",
498
+ pitfalls: [
499
+ "Copying prices into the section: they go stale the moment Shopify changes them.",
500
+ "A query that matches nothing renders an empty section — the renderer hides the block and the editor warns, so don't add placeholder products.",
501
+ "Filter values are untyped strings today (price '200' vs 200); the converter must normalize units.",
502
+ "excludeCurrent only applies inside a product template; on a static page it is a no-op."
503
+ ]
504
+ }
505
+ },
506
+ {
507
+ id: "commerce-product-detail",
508
+ label: "Product detail page",
509
+ proves: "A Products ENTRY (not a block): Shopify-synced vs native commerce · polymorphic gallery tied to options · options → variants matrix · grouped specs · policy accordions",
510
+ category: "Commerce",
511
+ brand: "Caldera",
512
+ previewLayout: "split",
513
+ tags: [
514
+ "commerce",
515
+ "deep-nesting",
516
+ "polymorphic-array",
517
+ "conditional",
518
+ "references",
519
+ "media-heavy",
520
+ "repeater",
521
+ "rich-text",
522
+ "validation",
523
+ "seo-structured",
524
+ "a11y"
525
+ ],
526
+ docTitle: "Ember Dutch Oven 5.5qt",
527
+ urlPrefix: "caldera.com/products/",
528
+ schema: {
529
+ name: "product",
530
+ title: "Product",
531
+ kind: "entry",
532
+ icon: "package",
533
+ category: "Commerce",
534
+ description: "One product page: story, gallery, options, specs and policies",
535
+ preview: {
536
+ title: "title",
537
+ subtitle: "tagline",
538
+ media: "gallery.0.image"
539
+ },
540
+ groups: [
541
+ {
542
+ name: "content",
543
+ title: "Story",
544
+ default: true
545
+ },
546
+ {
547
+ name: "media",
548
+ title: "Gallery"
549
+ },
550
+ {
551
+ name: "commerce",
552
+ title: "Options & price"
553
+ },
554
+ {
555
+ name: "details",
556
+ title: "Specs & care"
557
+ },
558
+ {
559
+ name: "policies",
560
+ title: "Shipping & returns"
561
+ }
562
+ ],
563
+ fieldsets: [{
564
+ name: "price",
565
+ title: "Price",
566
+ columns: 2
567
+ }, {
568
+ name: "stock",
569
+ title: "Stock",
570
+ columns: 2
571
+ }],
572
+ fields: [
573
+ {
574
+ name: "title",
575
+ title: "Product name",
576
+ type: "string",
577
+ validation: {
578
+ required: true,
579
+ max: 70
580
+ },
581
+ localized: true,
582
+ description: ""
583
+ },
584
+ {
585
+ name: "slug",
586
+ title: "URL",
587
+ type: "slug",
588
+ validation: { required: true }
589
+ },
590
+ {
591
+ name: "tagline",
592
+ title: "Tagline",
593
+ type: "string",
594
+ validation: { recommendedMax: 80 },
595
+ ai: true,
596
+ localized: true
597
+ },
598
+ {
599
+ name: "description",
600
+ title: "Description",
601
+ type: "richtext",
602
+ ai: true,
603
+ localized: true
604
+ },
605
+ {
606
+ name: "badges",
607
+ title: "Badges",
608
+ type: "multiselect",
609
+ layout: "checkbox",
610
+ options: [
611
+ {
612
+ value: "new",
613
+ label: "New"
614
+ },
615
+ {
616
+ value: "bestseller",
617
+ label: "Best seller"
618
+ },
619
+ {
620
+ value: "limited",
621
+ label: "Limited run"
622
+ },
623
+ {
624
+ value: "made-usa",
625
+ label: "Made in USA"
626
+ }
627
+ ],
628
+ description: "Also shown on product cards everywhere."
629
+ },
630
+ {
631
+ name: "source",
632
+ title: "Price and stock come from",
633
+ type: "select",
634
+ group: "commerce",
635
+ layout: "segmented",
636
+ initialValue: "shopify",
637
+ options: [{
638
+ value: "shopify",
639
+ label: "Shopify",
640
+ description: "Synced every few minutes, read-only here"
641
+ }, {
642
+ value: "native",
643
+ label: "BetterCMS",
644
+ description: "You manage price, options and stock here"
645
+ }]
646
+ },
647
+ {
648
+ name: "shopifyProduct",
649
+ title: "Shopify product",
650
+ type: "reference",
651
+ group: "commerce",
652
+ reference: { to: ["shopify"] },
653
+ hidden: {
654
+ field: "source",
655
+ equals: "shopify"
656
+ },
657
+ validation: { required: true }
658
+ },
659
+ {
660
+ name: "price",
661
+ title: "Price",
662
+ type: "number",
663
+ group: "commerce",
664
+ fieldset: "price",
665
+ numeric: {
666
+ unit: "USD",
667
+ min: 0,
668
+ step: .01
669
+ },
670
+ hidden: {
671
+ field: "source",
672
+ equals: "native"
673
+ },
674
+ validation: {
675
+ required: true,
676
+ min: 0
677
+ },
678
+ description: "A variant can override it."
679
+ },
680
+ {
681
+ name: "compareAt",
682
+ title: "Compare-at price",
683
+ type: "number",
684
+ group: "commerce",
685
+ fieldset: "price",
686
+ numeric: {
687
+ unit: "USD",
688
+ min: 0,
689
+ step: .01
690
+ },
691
+ hidden: {
692
+ field: "source",
693
+ equals: "native"
694
+ },
695
+ description: "Must be higher than the price."
696
+ },
697
+ {
698
+ name: "options",
699
+ title: "Options",
700
+ type: "array",
701
+ group: "commerce",
702
+ itemNoun: "option",
703
+ hidden: {
704
+ field: "source",
705
+ equals: "native"
706
+ },
707
+ validation: { max: 3 },
708
+ description: "Up to 3. Each combination becomes a variant.",
709
+ of: [{
710
+ name: "option",
711
+ title: "Option",
712
+ type: "object",
713
+ icon: "adjustments",
714
+ preview: {
715
+ title: "name",
716
+ fallback: "Untitled option"
717
+ },
718
+ fields: [
719
+ {
720
+ name: "name",
721
+ title: "Option name",
722
+ type: "string",
723
+ validation: {
724
+ required: true,
725
+ max: 20
726
+ },
727
+ placeholder: "Color"
728
+ },
729
+ {
730
+ name: "display",
731
+ title: "Show as",
732
+ type: "select",
733
+ layout: "segmented",
734
+ initialValue: "buttons",
735
+ options: [
736
+ {
737
+ value: "buttons",
738
+ label: "Buttons"
739
+ },
740
+ {
741
+ value: "swatches",
742
+ label: "Swatches"
743
+ },
744
+ {
745
+ value: "dropdown",
746
+ label: "Dropdown"
747
+ }
748
+ ]
749
+ },
750
+ {
751
+ name: "values",
752
+ title: "Values",
753
+ type: "array",
754
+ itemNoun: "value",
755
+ validation: {
756
+ min: 1,
757
+ max: 12
758
+ },
759
+ of: [{
760
+ name: "optionValue",
761
+ title: "Value",
762
+ type: "object",
763
+ icon: "point",
764
+ preview: {
765
+ title: "label",
766
+ media: "image",
767
+ fallback: "Untitled value"
768
+ },
769
+ fields: [
770
+ {
771
+ name: "label",
772
+ title: "Label",
773
+ type: "string",
774
+ validation: {
775
+ required: true,
776
+ max: 24
777
+ }
778
+ },
779
+ {
780
+ name: "swatch",
781
+ title: "Swatch color",
782
+ type: "color"
783
+ },
784
+ {
785
+ name: "image",
786
+ title: "Image",
787
+ type: "image"
788
+ }
789
+ ]
790
+ }]
791
+ }
792
+ ]
793
+ }]
794
+ },
795
+ {
796
+ name: "variants",
797
+ title: "Variants",
798
+ type: "array",
799
+ group: "commerce",
800
+ itemNoun: "variant",
801
+ hidden: {
802
+ field: "source",
803
+ equals: "native"
804
+ },
805
+ validation: { max: 100 },
806
+ helpMore: "Option values exactly as above, joined with \" / \", like \"5.5 qt / Ember red\".",
807
+ of: [{
808
+ name: "variant",
809
+ title: "Variant",
810
+ type: "object",
811
+ icon: "box",
812
+ preview: {
813
+ title: "combination",
814
+ subtitle: "sku",
815
+ fallback: "Untitled variant"
816
+ },
817
+ fields: [
818
+ {
819
+ name: "combination",
820
+ title: "Combination",
821
+ type: "string",
822
+ validation: {
823
+ required: true,
824
+ max: 60
825
+ },
826
+ placeholder: "5.5 qt / Ember red"
827
+ },
828
+ {
829
+ name: "sku",
830
+ title: "SKU",
831
+ type: "string",
832
+ validation: {
833
+ required: true,
834
+ pattern: "^[A-Z0-9-]{3,24}$",
835
+ patternMessage: "Use capital letters, numbers and hyphens, like CF-12.",
836
+ unique: true
837
+ }
838
+ },
839
+ {
840
+ name: "price",
841
+ title: "Price override",
842
+ type: "number",
843
+ numeric: {
844
+ unit: "USD",
845
+ min: 0,
846
+ step: .01
847
+ },
848
+ description: "Leave empty to use the product price."
849
+ },
850
+ {
851
+ name: "inventory",
852
+ title: "In stock",
853
+ type: "number",
854
+ numeric: {
855
+ min: 0,
856
+ step: 1,
857
+ unit: "units"
858
+ }
859
+ },
860
+ {
861
+ name: "weight",
862
+ title: "Shipping weight",
863
+ type: "number",
864
+ numeric: {
865
+ min: 0,
866
+ step: .1,
867
+ unit: "lb"
868
+ }
869
+ }
870
+ ]
871
+ }]
872
+ },
873
+ {
874
+ name: "inventoryPolicy",
875
+ title: "When it sells out",
876
+ type: "select",
877
+ group: "commerce",
878
+ fieldset: "stock",
879
+ initialValue: "deny",
880
+ options: [
881
+ {
882
+ value: "deny",
883
+ label: "Stop selling"
884
+ },
885
+ {
886
+ value: "preorder",
887
+ label: "Take pre-orders",
888
+ description: "Shows the restock date below"
889
+ },
890
+ {
891
+ value: "notify",
892
+ label: "Collect back-in-stock emails"
893
+ }
894
+ ]
895
+ },
896
+ {
897
+ name: "restockDate",
898
+ title: "Back in stock",
899
+ type: "date",
900
+ group: "commerce",
901
+ fieldset: "stock",
902
+ hidden: {
903
+ field: "inventoryPolicy",
904
+ equals: "preorder"
905
+ }
906
+ },
907
+ {
908
+ name: "lowStockAt",
909
+ title: "Low-stock warning at",
910
+ type: "number",
911
+ group: "commerce",
912
+ numeric: {
913
+ min: 0,
914
+ max: 50,
915
+ step: 1,
916
+ unit: "units"
917
+ },
918
+ description: "0 turns it off."
919
+ },
920
+ {
921
+ name: "gallery",
922
+ title: "Gallery",
923
+ type: "array",
924
+ group: "media",
925
+ arrayLayout: "grid",
926
+ itemNoun: "media",
927
+ validation: {
928
+ min: 1,
929
+ max: 16
930
+ },
931
+ description: "The first item is the main image on cards and in shared links.",
932
+ of: [
933
+ {
934
+ name: "galleryImage",
935
+ title: "Image",
936
+ type: "object",
937
+ icon: "photo",
938
+ preview: {
939
+ title: "image.alt",
940
+ media: "image",
941
+ fallback: "Untitled image"
942
+ },
943
+ fields: [{
944
+ name: "image",
945
+ title: "Image",
946
+ type: "image",
947
+ validation: { required: true }
948
+ }, {
949
+ name: "forValues",
950
+ title: "Show for",
951
+ type: "tags",
952
+ description: "Leave empty for every variant."
953
+ }]
954
+ },
955
+ {
956
+ name: "galleryVideo",
957
+ title: "Video",
958
+ type: "object",
959
+ icon: "player-play",
960
+ preview: {
961
+ title: "caption",
962
+ media: "poster",
963
+ fallback: "Untitled video"
964
+ },
965
+ fields: [
966
+ {
967
+ name: "video",
968
+ title: "Video",
969
+ type: "video",
970
+ validation: { required: true }
971
+ },
972
+ {
973
+ name: "poster",
974
+ title: "Poster",
975
+ type: "image",
976
+ validation: { required: true }
977
+ },
978
+ {
979
+ name: "caption",
980
+ title: "Caption",
981
+ type: "string",
982
+ validation: { max: 80 },
983
+ description: "Also the video's accessible name."
984
+ }
985
+ ]
986
+ },
987
+ {
988
+ name: "gallery3d",
989
+ title: "3D model",
990
+ type: "object",
991
+ icon: "box",
992
+ preview: {
993
+ title: "caption",
994
+ media: "poster",
995
+ fallback: "Untitled 3D model"
996
+ },
997
+ fields: [
998
+ {
999
+ name: "model",
1000
+ title: "Model file",
1001
+ type: "file",
1002
+ validation: { required: true },
1003
+ description: "A .glb file under 15 MB."
1004
+ },
1005
+ {
1006
+ name: "poster",
1007
+ title: "Poster",
1008
+ type: "image",
1009
+ validation: { required: true }
1010
+ },
1011
+ {
1012
+ name: "caption",
1013
+ title: "Caption",
1014
+ type: "string",
1015
+ validation: { max: 80 }
1016
+ }
1017
+ ]
1018
+ }
1019
+ ]
1020
+ },
1021
+ {
1022
+ name: "highlights",
1023
+ title: "Highlights",
1024
+ type: "array",
1025
+ group: "details",
1026
+ itemNoun: "highlight",
1027
+ openMode: "inline",
1028
+ validation: { max: 4 },
1029
+ of: [{
1030
+ name: "highlight",
1031
+ title: "Highlight",
1032
+ type: "object",
1033
+ icon: "sparkles",
1034
+ preview: {
1035
+ title: "label",
1036
+ fallback: "Untitled highlight"
1037
+ },
1038
+ fields: [{
1039
+ name: "icon",
1040
+ title: "Icon",
1041
+ type: "icon"
1042
+ }, {
1043
+ name: "label",
1044
+ title: "Label",
1045
+ type: "string",
1046
+ validation: {
1047
+ required: true,
1048
+ max: 40
1049
+ }
1050
+ }]
1051
+ }]
1052
+ },
1053
+ {
1054
+ name: "specs",
1055
+ title: "Specs",
1056
+ type: "array",
1057
+ group: "details",
1058
+ itemNoun: "spec group",
1059
+ validation: { max: 6 },
1060
+ of: [{
1061
+ name: "specGroup",
1062
+ title: "Spec group",
1063
+ type: "object",
1064
+ icon: "list-details",
1065
+ preview: {
1066
+ title: "title",
1067
+ fallback: "Untitled group"
1068
+ },
1069
+ fields: [{
1070
+ name: "title",
1071
+ title: "Group title",
1072
+ type: "string",
1073
+ validation: {
1074
+ required: true,
1075
+ max: 32
1076
+ },
1077
+ placeholder: "Dimensions"
1078
+ }, {
1079
+ name: "rows",
1080
+ title: "Rows",
1081
+ type: "array",
1082
+ itemNoun: "row",
1083
+ openMode: "inline",
1084
+ validation: {
1085
+ min: 1,
1086
+ max: 12
1087
+ },
1088
+ of: [{
1089
+ name: "specRow",
1090
+ title: "Row",
1091
+ type: "object",
1092
+ icon: "table",
1093
+ preview: {
1094
+ title: "label",
1095
+ subtitle: "value",
1096
+ fallback: "Untitled row"
1097
+ },
1098
+ fields: [{
1099
+ name: "label",
1100
+ title: "Label",
1101
+ type: "string",
1102
+ validation: {
1103
+ required: true,
1104
+ max: 32
1105
+ }
1106
+ }, {
1107
+ name: "value",
1108
+ title: "Value",
1109
+ type: "string",
1110
+ validation: {
1111
+ required: true,
1112
+ max: 60
1113
+ }
1114
+ }]
1115
+ }]
1116
+ }]
1117
+ }]
1118
+ },
1119
+ {
1120
+ name: "care",
1121
+ title: "Care",
1122
+ type: "richtext",
1123
+ group: "details",
1124
+ ai: true
1125
+ },
1126
+ {
1127
+ name: "useStorePolicies",
1128
+ title: "Use the store's shipping and returns",
1129
+ type: "boolean",
1130
+ group: "policies",
1131
+ initialValue: true,
1132
+ description: "Turn off to write product-specific ones."
1133
+ },
1134
+ {
1135
+ name: "policies",
1136
+ title: "Product policies",
1137
+ type: "array",
1138
+ group: "policies",
1139
+ itemNoun: "policy",
1140
+ hidden: {
1141
+ field: "useStorePolicies",
1142
+ equals: false
1143
+ },
1144
+ validation: { max: 4 },
1145
+ of: [{
1146
+ name: "policy",
1147
+ title: "Policy",
1148
+ type: "object",
1149
+ icon: "truck-delivery",
1150
+ preview: {
1151
+ title: "title",
1152
+ subtitle: "summary",
1153
+ fallback: "Untitled policy"
1154
+ },
1155
+ fields: [
1156
+ {
1157
+ name: "title",
1158
+ title: "Title",
1159
+ type: "string",
1160
+ validation: {
1161
+ required: true,
1162
+ max: 40
1163
+ }
1164
+ },
1165
+ {
1166
+ name: "summary",
1167
+ title: "Summary",
1168
+ type: "string",
1169
+ validation: { max: 80 },
1170
+ placeholder: "Free over $80"
1171
+ },
1172
+ {
1173
+ name: "body",
1174
+ title: "Body",
1175
+ type: "richtext",
1176
+ validation: { required: true }
1177
+ }
1178
+ ]
1179
+ }]
1180
+ },
1181
+ {
1182
+ name: "related",
1183
+ title: "Pairs well with",
1184
+ type: "array",
1185
+ group: "details",
1186
+ validation: {
1187
+ max: 4,
1188
+ unique: true
1189
+ },
1190
+ of: [productRef("relatedProduct", "Product")]
1191
+ },
1192
+ {
1193
+ name: "reviewsId",
1194
+ title: "Reviews product ID",
1195
+ type: "string",
1196
+ group: "details",
1197
+ advanced: true,
1198
+ access: { edit: "schema.manage" },
1199
+ lockReason: "Set by the reviews app integration",
1200
+ description: "The product's ID in Okendo."
1201
+ }
1202
+ ]
1203
+ },
1204
+ value: {
1205
+ title: "Ember Dutch Oven 5.5qt",
1206
+ slug: "ember-dutch-oven",
1207
+ tagline: "Bakes bread at 500°F, braises for eight, and the lid is a skillet.",
1208
+ description: "<p>Poured in our Bend foundry and pre-seasoned three times with flaxseed oil, the Ember is the pot you'll reach for at home and at camp. The flanged lid holds coals for top heat, then flips into a 10-inch skillet.</p><p>The enamel interior on the Ember red finish cleans up with soap and water; the bare iron finish gets better every time you cook.</p>",
1209
+ badges: ["bestseller", "made-usa"],
1210
+ source: "shopify",
1211
+ shopifyProduct: "gid://shopify/Product/8813",
1212
+ price: 165,
1213
+ compareAt: 195,
1214
+ options: [{
1215
+ _key: "o1",
1216
+ name: "Size",
1217
+ display: "buttons",
1218
+ values: [{
1219
+ _key: "v1",
1220
+ label: "3.5 qt"
1221
+ }, {
1222
+ _key: "v2",
1223
+ label: "5.5 qt"
1224
+ }]
1225
+ }, {
1226
+ _key: "o2",
1227
+ name: "Finish",
1228
+ display: "swatches",
1229
+ values: [
1230
+ {
1231
+ _key: "v3",
1232
+ label: "Bare iron",
1233
+ swatch: "#1E1B18"
1234
+ },
1235
+ {
1236
+ _key: "v4",
1237
+ label: "Ember red",
1238
+ swatch: "#B33A24"
1239
+ },
1240
+ {
1241
+ _key: "v5",
1242
+ label: "Speckled sage",
1243
+ image: img("caldera-sage-speckle", 200, 200, "Sage green enamel with dark speckles")
1244
+ }
1245
+ ]
1246
+ }],
1247
+ variants: [
1248
+ {
1249
+ _key: "va1",
1250
+ combination: "5.5 qt / Bare iron",
1251
+ sku: "ED-55-BI",
1252
+ inventory: 38,
1253
+ weight: 13.2
1254
+ },
1255
+ {
1256
+ _key: "va2",
1257
+ combination: "5.5 qt / Ember red",
1258
+ sku: "ED-55-ER",
1259
+ price: 185,
1260
+ inventory: 12,
1261
+ weight: 13.6
1262
+ },
1263
+ {
1264
+ _key: "va3",
1265
+ combination: "3.5 qt / Bare iron",
1266
+ sku: "ED-35-BI",
1267
+ price: 129,
1268
+ inventory: 0,
1269
+ weight: 9.4
1270
+ }
1271
+ ],
1272
+ inventoryPolicy: "preorder",
1273
+ restockDate: "2026-11-12",
1274
+ lowStockAt: 10,
1275
+ gallery: [
1276
+ {
1277
+ _key: "g1",
1278
+ _type: "galleryImage",
1279
+ image: img("caldera-dutch-coals", 1600, 2e3, "Ember Dutch Oven sitting in glowing coals with a lid full of embers"),
1280
+ forValues: ["Bare iron"]
1281
+ },
1282
+ {
1283
+ _key: "g2",
1284
+ _type: "galleryImage",
1285
+ image: img("caldera-dutch-red", 1600, 2e3, "Ember red Dutch oven on a wooden counter next to a sourdough loaf"),
1286
+ forValues: ["Ember red"]
1287
+ },
1288
+ {
1289
+ _key: "g3",
1290
+ _type: "galleryVideo",
1291
+ video: "https://www.youtube.com/watch?v=caldera-ember-bread",
1292
+ poster: img("caldera-dutch-bread-poster", 1600, 900, "Hands lifting a lid off a fresh loaf"),
1293
+ caption: "Baking a no-knead loaf over coals"
1294
+ },
1295
+ {
1296
+ _key: "g4",
1297
+ _type: "galleryImage",
1298
+ image: img("caldera-dutch-lid-skillet", 1600, 2e3, "The lid flipped over and used as a skillet to fry eggs")
1299
+ }
1300
+ ],
1301
+ highlights: [
1302
+ {
1303
+ _key: "h1",
1304
+ icon: "flame",
1305
+ label: "Safe to 650°F and over open fire"
1306
+ },
1307
+ {
1308
+ _key: "h2",
1309
+ icon: "shield-check",
1310
+ label: "Lifetime warranty"
1311
+ },
1312
+ {
1313
+ _key: "h3",
1314
+ icon: "truck",
1315
+ label: "Free shipping over $80"
1316
+ }
1317
+ ],
1318
+ specs: [{
1319
+ _key: "s1",
1320
+ title: "Dimensions",
1321
+ rows: [
1322
+ {
1323
+ _key: "r1",
1324
+ label: "Capacity",
1325
+ value: "5.5 qt (5.2 L)"
1326
+ },
1327
+ {
1328
+ _key: "r2",
1329
+ label: "Diameter",
1330
+ value: "10.25 in"
1331
+ },
1332
+ {
1333
+ _key: "r3",
1334
+ label: "Weight",
1335
+ value: "13.2 lb with lid"
1336
+ }
1337
+ ]
1338
+ }, {
1339
+ _key: "s2",
1340
+ title: "Materials",
1341
+ rows: [
1342
+ {
1343
+ _key: "r4",
1344
+ label: "Body",
1345
+ value: "Cast iron, 3.8 mm wall"
1346
+ },
1347
+ {
1348
+ _key: "r5",
1349
+ label: "Seasoning",
1350
+ value: "Flaxseed oil, 3 coats"
1351
+ },
1352
+ {
1353
+ _key: "r6",
1354
+ label: "Heat sources",
1355
+ value: "Gas, electric, induction, oven, coals"
1356
+ }
1357
+ ]
1358
+ }],
1359
+ care: "<p>Rinse warm, scrub with the chainmail pad, dry over low heat and wipe a few drops of oil inside. Never soak bare iron or run it through the dishwasher.</p>",
1360
+ useStorePolicies: false,
1361
+ policies: [{
1362
+ _key: "pl1",
1363
+ title: "Shipping",
1364
+ summary: "Free over $80 · ships in 1–2 days",
1365
+ body: "<p>Orders over $80 ship free in the contiguous US. Cast iron ships in a recycled-fibre box with no plastic.</p>"
1366
+ }, {
1367
+ _key: "pl2",
1368
+ title: "Returns",
1369
+ summary: "60 days, even if you've cooked in it",
1370
+ body: "<p>Cook with it for 60 days. If it isn't for you, send it back for a full refund — seasoning and all.</p>"
1371
+ }],
1372
+ related: [P.skillet, P.grill],
1373
+ reviewsId: "okendo-ED-55"
1374
+ },
1375
+ notes: {
1376
+ why: "D27: a product page is a Products ENTRY, so this is `kind: 'entry'`, not a block. The one decision that shapes everything is `source`: when Shopify owns commerce, price/options/variants/stock are hidden and the entry only stores the Shopify GID plus EDITORIAL content (story, gallery, highlights, specs, care, policies); the 'native' path shows the full options → variants matrix for stores without Shopify. Options are an array of objects whose values are themselves objects (label + swatch color/image) because swatches need data, not just strings. The gallery is a polymorphic array (image | video | 3D) and each image can be scoped to option values so picking 'Ember red' swaps the photos. Specs are groups of rows (rendered as a table with th scope) instead of rich text so they can be compared, filtered and emitted in Product JSON-LD `additionalProperty`. Policies default to the store-wide ones (one source of truth) with a per-product override.",
1377
+ sanity: "Sanity Connect creates `product` and `productVariant` documents; the synced data lives under a read-only `store` object (`store.title`, `store.priceRange`, `store.options[]`, `store.variants[] -> productVariant.store.price / inventory`). Editorial fields are added beside `store` on the same `product` type, e.g. `body: portableText`, `gallery: array<image|mux.video>`, `specs: array<object{title, rows: array<object>}>`. A native (non-Shopify) catalogue would need its own `variant` object type and custom validation for price vs compare-at.",
1378
+ htmlSignals: [
1379
+ "a single page with one <h1> product name, a price block and a form posting to /cart/add with a hidden variant id",
1380
+ "option pickers: radio groups / fieldsets named Size, Color, Finish; swatch buttons with background colors or images",
1381
+ "a thumbnail strip + main image (or a <video>/<model-viewer>) — the media gallery",
1382
+ "a <table> or <dl> of label/value pairs under 'Specs', 'Specifications', 'Details', 'Dimensions'",
1383
+ "accordions/<details> titled Shipping, Returns, Care, Warranty",
1384
+ "existing JSON-LD with @type Product and offers"
1385
+ ],
1386
+ mcp: "Create/locate the Products entry by the URL handle; set source:'shopify' and shopifyProduct when Shopify markers exist (window.ShopifyAnalytics, /products/<handle>.js, cdn.shopify.com), and then DO NOT import price, compare-at, options, variants or stock — they sync. Otherwise source:'native' and parse options from the pickers, variants from the variant JSON (often a <script type=application/json> with variants[]). Gallery: each media → galleryImage/galleryVideo/gallery3d, alt from the img alt (never the filename), forValues from data-variant/data-color attributes. Specs: each table/dl → one specGroup (caption or preceding heading → title). Shipping/returns accordions: if the text is identical across products, set useStorePolicies:true and put the text in Settings once; otherwise emit policies. Never create meta/og fields — page meta is native.",
1387
+ pitfalls: [
1388
+ "Duplicating Shopify price/stock into the entry — two sources drift within a day.",
1389
+ "compare-at must be higher than price; the schema can't express a cross-field rule yet (gap), so the editor validates on save.",
1390
+ "Variant combinations are typed strings; renaming an option value orphans gallery `forValues` and variant rows.",
1391
+ "Video posters are required: autoplaying video without a poster hurts LCP on the product page."
1392
+ ]
1393
+ }
1394
+ },
1395
+ {
1396
+ id: "commerce-featured-product",
1397
+ label: "Featured product spotlight",
1398
+ proves: "One product reference + per-placement overrides (headline, image) that fall back to the entry · inline add-to-cart vs link",
1399
+ category: "Commerce",
1400
+ brand: "Caldera",
1401
+ previewLayout: "split",
1402
+ tags: [
1403
+ "references",
1404
+ "commerce",
1405
+ "variants",
1406
+ "conditional",
1407
+ "media-heavy",
1408
+ "repeater"
1409
+ ],
1410
+ schema: {
1411
+ name: "featuredProduct",
1412
+ title: "Featured product",
1413
+ kind: "block",
1414
+ icon: "star",
1415
+ category: "Commerce",
1416
+ description: "One product, told as a story, with a buy button",
1417
+ variants: [
1418
+ {
1419
+ id: "image-left",
1420
+ name: "Image left"
1421
+ },
1422
+ {
1423
+ id: "image-right",
1424
+ name: "Image right"
1425
+ },
1426
+ {
1427
+ id: "full-bleed",
1428
+ name: "Full-bleed image",
1429
+ description: "Text over a full-width photo"
1430
+ }
1431
+ ],
1432
+ preview: {
1433
+ title: "heading",
1434
+ subtitle: "eyebrow",
1435
+ media: "image"
1436
+ },
1437
+ groups: [{
1438
+ name: "content",
1439
+ title: "Content",
1440
+ default: true
1441
+ }, {
1442
+ name: "design",
1443
+ title: "Design"
1444
+ }],
1445
+ fields: [
1446
+ productRef("product", "Product", "Name, price, rating and stock come from this product."),
1447
+ {
1448
+ name: "eyebrow",
1449
+ title: "Eyebrow",
1450
+ type: "string",
1451
+ validation: { max: 32 },
1452
+ placeholder: "New for fall"
1453
+ },
1454
+ {
1455
+ name: "heading",
1456
+ title: "Heading",
1457
+ type: "string",
1458
+ validation: { recommendedMax: 70 },
1459
+ ai: true,
1460
+ localized: true,
1461
+ description: "Leave empty to use the product name."
1462
+ },
1463
+ {
1464
+ name: "body",
1465
+ title: "Body",
1466
+ type: "text",
1467
+ rows: 3,
1468
+ validation: { recommendedMax: 280 },
1469
+ ai: true,
1470
+ localized: true,
1471
+ description: "Leave empty to use the product tagline."
1472
+ },
1473
+ {
1474
+ name: "points",
1475
+ title: "Selling points",
1476
+ type: "array",
1477
+ itemNoun: "point",
1478
+ openMode: "inline",
1479
+ validation: { max: 4 },
1480
+ of: [{
1481
+ name: "point",
1482
+ title: "Point",
1483
+ type: "object",
1484
+ icon: "check",
1485
+ preview: {
1486
+ title: "label",
1487
+ fallback: "Untitled point"
1488
+ },
1489
+ fields: [{
1490
+ name: "icon",
1491
+ title: "Icon",
1492
+ type: "icon"
1493
+ }, {
1494
+ name: "label",
1495
+ title: "Label",
1496
+ type: "string",
1497
+ validation: {
1498
+ required: true,
1499
+ max: 48
1500
+ }
1501
+ }]
1502
+ }]
1503
+ },
1504
+ {
1505
+ name: "image",
1506
+ title: "Image",
1507
+ type: "image",
1508
+ description: "Leave empty to use the product's first gallery image."
1509
+ },
1510
+ {
1511
+ name: "action",
1512
+ title: "Button does",
1513
+ type: "select",
1514
+ layout: "segmented",
1515
+ initialValue: "add",
1516
+ options: [{
1517
+ value: "add",
1518
+ label: "Add to cart",
1519
+ description: "Variant picker + add button in place"
1520
+ }, {
1521
+ value: "link",
1522
+ label: "Open the product"
1523
+ }]
1524
+ },
1525
+ {
1526
+ name: "addLabel",
1527
+ title: "Add button label",
1528
+ type: "string",
1529
+ hidden: {
1530
+ field: "action",
1531
+ equals: "add"
1532
+ },
1533
+ validation: { max: 24 },
1534
+ placeholder: "Add to cart",
1535
+ description: "Changes to Pre-order or Sold out on its own."
1536
+ },
1537
+ {
1538
+ name: "button",
1539
+ title: "Button",
1540
+ type: "button",
1541
+ hidden: {
1542
+ field: "action",
1543
+ equals: "link"
1544
+ }
1545
+ },
1546
+ {
1547
+ name: "showPrice",
1548
+ title: "Show price",
1549
+ type: "boolean",
1550
+ initialValue: true,
1551
+ group: "design"
1552
+ },
1553
+ {
1554
+ name: "showRating",
1555
+ title: "Show rating",
1556
+ type: "boolean",
1557
+ initialValue: true,
1558
+ group: "design"
1559
+ },
1560
+ themeField
1561
+ ]
1562
+ },
1563
+ value: {
1564
+ _variant: "image-left",
1565
+ product: P.grill,
1566
+ eyebrow: "Folds to 2 cm",
1567
+ heading: "The grill grate that lives in your pack",
1568
+ body: "Stainless steel, 1.1 kg, and it opens to cook for four over any fire ring. Cable hinges mean nothing to lose and nothing to rattle.",
1569
+ points: [
1570
+ {
1571
+ _key: "pt1",
1572
+ icon: "weight",
1573
+ label: "1.1 kg, folds flat to 2 cm"
1574
+ },
1575
+ {
1576
+ _key: "pt2",
1577
+ icon: "flame",
1578
+ label: "Rated to 900°F direct flame"
1579
+ },
1580
+ {
1581
+ _key: "pt3",
1582
+ icon: "tool",
1583
+ label: "No loose parts, no tools"
1584
+ }
1585
+ ],
1586
+ image: img("caldera-cable-fold-fire", 1600, 1200, "Cable Fold grill grate open over a campfire with two skewers of vegetables"),
1587
+ action: "add",
1588
+ addLabel: "Add to cart",
1589
+ button: {
1590
+ label: "See the details",
1591
+ href: "/products/cable-fold",
1592
+ type: "entry",
1593
+ title: "Cable Fold Grill",
1594
+ style: "outline"
1595
+ },
1596
+ showPrice: true,
1597
+ showRating: true,
1598
+ theme: "light"
1599
+ },
1600
+ notes: {
1601
+ why: "The spotlight is a REFERENCE plus optional overrides: name, price, rating, image and stock all come from the Products entry, and the section only stores what is placement-specific (headline, story, image override, selling points). Every override says 'leave empty to use…' so the fallback is explicit. The action is a conditional pair: 'Add to cart' needs only a label (the renderer adds the variant picker and swaps to Pre-order/Sold out itself); 'Open the product' is a normal button unit.",
1602
+ sanity: "`featuredProduct` object with `product: reference to product` (Shopify-synced via Sanity Connect), `heading`/`body` optional strings, `image` optional, and frontend coalescing `coalesce(heading, product->store.title)` and `product->store.previewImageUrl`.",
1603
+ htmlSignals: [
1604
+ "a two-column section with one large product image and one product name + price + add-to-cart/'Shop now'",
1605
+ "a single /products/<handle> link or a single add-to-cart form in the section",
1606
+ "short check-icon bullet list beside the image",
1607
+ "an eyebrow like 'New', 'Featured', 'Product of the month'"
1608
+ ],
1609
+ mcp: "Resolve the single product link to a Products entry → product. Compare the visible name/description/image with the entry: only emit headline/body/image when they DIFFER (that's an override); otherwise leave them empty. A form posting to /cart/add → action:'add' (+ addLabel from the submit text); a plain link → action:'link' with a button unit. Image position decides the variant (image first in DOM / on the left → image-left).",
1610
+ pitfalls: ["Copying the product name into `heading` freezes it; renames in Shopify won't flow through.", "A deleted or archived product leaves the section empty — the renderer hides it and the editor flags the broken reference."]
1611
+ }
1612
+ },
1613
+ {
1614
+ id: "commerce-collection-header-facets",
1615
+ label: "Collection header + filters",
1616
+ proves: "Collection reference with header overrides · facet config array (source × display) with conditional swatch/range settings · sort options",
1617
+ category: "Commerce",
1618
+ brand: "Caldera",
1619
+ previewLayout: "centered",
1620
+ tags: [
1621
+ "commerce",
1622
+ "dynamic-query",
1623
+ "references",
1624
+ "repeater",
1625
+ "conditional",
1626
+ "validation",
1627
+ "media-heavy"
1628
+ ],
1629
+ schema: {
1630
+ name: "collectionHeader",
1631
+ title: "Collection header",
1632
+ kind: "block",
1633
+ icon: "category-2",
1634
+ category: "Commerce",
1635
+ description: "The top of a shop page: title, banner, filters and sort",
1636
+ variants: [{
1637
+ id: "banner",
1638
+ name: "Banner image"
1639
+ }, {
1640
+ id: "plain",
1641
+ name: "Plain title"
1642
+ }],
1643
+ preview: {
1644
+ title: "title",
1645
+ subtitle: "description",
1646
+ media: "banner"
1647
+ },
1648
+ groups: [{
1649
+ name: "content",
1650
+ title: "Content",
1651
+ default: true
1652
+ }, {
1653
+ name: "filters",
1654
+ title: "Filters & sort"
1655
+ }],
1656
+ fields: [
1657
+ {
1658
+ name: "collection",
1659
+ title: "Collection",
1660
+ type: "reference",
1661
+ reference: { to: ["collection"] },
1662
+ validation: { required: true }
1663
+ },
1664
+ {
1665
+ name: "title",
1666
+ title: "Title override",
1667
+ type: "string",
1668
+ validation: { max: 60 },
1669
+ localized: true,
1670
+ description: "Leave empty to use the collection name."
1671
+ },
1672
+ {
1673
+ name: "description",
1674
+ title: "Intro",
1675
+ type: "text",
1676
+ rows: 2,
1677
+ validation: { recommendedMax: 200 },
1678
+ ai: true,
1679
+ localized: true
1680
+ },
1681
+ {
1682
+ name: "banner",
1683
+ title: "Banner",
1684
+ type: "image",
1685
+ description: "Shown at 1600×500."
1686
+ },
1687
+ {
1688
+ name: "facets",
1689
+ title: "Filters",
1690
+ type: "array",
1691
+ group: "filters",
1692
+ itemNoun: "filter",
1693
+ validation: { max: 8 },
1694
+ description: "Filters with one value hide themselves.",
1695
+ of: [{
1696
+ name: "facet",
1697
+ title: "Filter",
1698
+ type: "object",
1699
+ icon: "adjustments-horizontal",
1700
+ preview: {
1701
+ title: "label",
1702
+ subtitle: "source",
1703
+ fallback: "Untitled filter"
1704
+ },
1705
+ fields: [
1706
+ {
1707
+ name: "label",
1708
+ title: "Label",
1709
+ type: "string",
1710
+ validation: {
1711
+ required: true,
1712
+ max: 24
1713
+ }
1714
+ },
1715
+ {
1716
+ name: "source",
1717
+ title: "Filters on",
1718
+ type: "select",
1719
+ validation: { required: true },
1720
+ options: [
1721
+ {
1722
+ value: "option",
1723
+ label: "A product option",
1724
+ description: "Size, Color, Finish"
1725
+ },
1726
+ {
1727
+ value: "field",
1728
+ label: "A product field",
1729
+ description: "Material, Capacity"
1730
+ },
1731
+ {
1732
+ value: "tag",
1733
+ label: "Tags"
1734
+ },
1735
+ {
1736
+ value: "price",
1737
+ label: "Price"
1738
+ },
1739
+ {
1740
+ value: "availability",
1741
+ label: "In stock"
1742
+ }
1743
+ ]
1744
+ },
1745
+ {
1746
+ name: "key",
1747
+ title: "Option or field name",
1748
+ type: "string",
1749
+ hidden: {
1750
+ field: "source",
1751
+ equals: ["option", "field"]
1752
+ },
1753
+ validation: {
1754
+ required: true,
1755
+ max: 32
1756
+ },
1757
+ description: "Exactly as named on the products, e.g. Finish."
1758
+ },
1759
+ {
1760
+ name: "display",
1761
+ title: "Show as",
1762
+ type: "select",
1763
+ layout: "segmented",
1764
+ initialValue: "checkbox",
1765
+ options: [
1766
+ {
1767
+ value: "checkbox",
1768
+ label: "Checkboxes"
1769
+ },
1770
+ {
1771
+ value: "swatch",
1772
+ label: "Swatches"
1773
+ },
1774
+ {
1775
+ value: "pills",
1776
+ label: "Pills"
1777
+ },
1778
+ {
1779
+ value: "range",
1780
+ label: "Range"
1781
+ }
1782
+ ]
1783
+ },
1784
+ {
1785
+ name: "rangeStep",
1786
+ title: "Range step",
1787
+ type: "number",
1788
+ hidden: {
1789
+ field: "display",
1790
+ equals: "range"
1791
+ },
1792
+ numeric: {
1793
+ min: 1,
1794
+ max: 100,
1795
+ step: 1,
1796
+ unit: "USD"
1797
+ }
1798
+ },
1799
+ {
1800
+ name: "swatches",
1801
+ title: "Swatch colors",
1802
+ type: "array",
1803
+ itemNoun: "swatch",
1804
+ openMode: "inline",
1805
+ hidden: {
1806
+ field: "display",
1807
+ equals: "swatch"
1808
+ },
1809
+ validation: { max: 16 },
1810
+ description: "Values without a color show as a text pill.",
1811
+ of: [{
1812
+ name: "swatch",
1813
+ title: "Swatch",
1814
+ type: "object",
1815
+ icon: "palette",
1816
+ preview: {
1817
+ title: "value",
1818
+ fallback: "Untitled swatch"
1819
+ },
1820
+ fields: [{
1821
+ name: "value",
1822
+ title: "Value",
1823
+ type: "string",
1824
+ validation: {
1825
+ required: true,
1826
+ max: 24
1827
+ }
1828
+ }, {
1829
+ name: "color",
1830
+ title: "Color",
1831
+ type: "color",
1832
+ validation: { required: true }
1833
+ }]
1834
+ }]
1835
+ },
1836
+ {
1837
+ name: "isOpen",
1838
+ title: "Open by default",
1839
+ type: "boolean"
1840
+ },
1841
+ {
1842
+ name: "showCounts",
1843
+ title: "Show counts",
1844
+ type: "boolean",
1845
+ initialValue: true
1846
+ }
1847
+ ]
1848
+ }]
1849
+ },
1850
+ {
1851
+ name: "sortOptions",
1852
+ title: "Sort options",
1853
+ type: "multiselect",
1854
+ group: "filters",
1855
+ layout: "checkbox",
1856
+ validation: { min: 1 },
1857
+ options: [
1858
+ {
1859
+ value: "featured",
1860
+ label: "Featured"
1861
+ },
1862
+ {
1863
+ value: "bestselling",
1864
+ label: "Best selling"
1865
+ },
1866
+ {
1867
+ value: "newest",
1868
+ label: "Newest"
1869
+ },
1870
+ {
1871
+ value: "price-asc",
1872
+ label: "Price, low to high"
1873
+ },
1874
+ {
1875
+ value: "price-desc",
1876
+ label: "Price, high to low"
1877
+ },
1878
+ {
1879
+ value: "rating",
1880
+ label: "Highest rated"
1881
+ }
1882
+ ]
1883
+ },
1884
+ {
1885
+ name: "defaultSort",
1886
+ title: "Default sort",
1887
+ type: "select",
1888
+ group: "filters",
1889
+ initialValue: "featured",
1890
+ options: [
1891
+ {
1892
+ value: "featured",
1893
+ label: "Featured"
1894
+ },
1895
+ {
1896
+ value: "bestselling",
1897
+ label: "Best selling"
1898
+ },
1899
+ {
1900
+ value: "newest",
1901
+ label: "Newest"
1902
+ }
1903
+ ],
1904
+ description: "Must be one of the sort options above."
1905
+ },
1906
+ {
1907
+ name: "perPage",
1908
+ title: "Products per page",
1909
+ type: "number",
1910
+ group: "filters",
1911
+ layout: "slider",
1912
+ initialValue: 24,
1913
+ numeric: {
1914
+ min: 12,
1915
+ max: 48,
1916
+ step: 12
1917
+ }
1918
+ },
1919
+ {
1920
+ name: "showCount",
1921
+ title: "Show result count",
1922
+ type: "boolean",
1923
+ group: "filters",
1924
+ initialValue: true
1925
+ }
1926
+ ]
1927
+ },
1928
+ value: {
1929
+ _variant: "banner",
1930
+ collection: "col_products",
1931
+ title: "Cast iron",
1932
+ description: "Skillets, Dutch ovens and griddles poured in Bend, Oregon. Pre-seasoned, ready for coals, guaranteed for life.",
1933
+ banner: img("caldera-castiron-banner", 1600, 500, "A row of cast iron skillets hanging on a workshop wall"),
1934
+ facets: [
1935
+ {
1936
+ _key: "fc1",
1937
+ label: "Product type",
1938
+ source: "field",
1939
+ key: "productType",
1940
+ display: "pills",
1941
+ isOpen: true,
1942
+ showCounts: true
1943
+ },
1944
+ {
1945
+ _key: "fc2",
1946
+ label: "Finish",
1947
+ source: "option",
1948
+ key: "Finish",
1949
+ display: "swatch",
1950
+ isOpen: true,
1951
+ showCounts: false,
1952
+ swatches: [
1953
+ {
1954
+ _key: "sw1",
1955
+ value: "Bare iron",
1956
+ color: "#1E1B18"
1957
+ },
1958
+ {
1959
+ _key: "sw2",
1960
+ value: "Ember red",
1961
+ color: "#B33A24"
1962
+ },
1963
+ {
1964
+ _key: "sw3",
1965
+ value: "Speckled sage",
1966
+ color: "#7D8F74"
1967
+ }
1968
+ ]
1969
+ },
1970
+ {
1971
+ _key: "fc3",
1972
+ label: "Price",
1973
+ source: "price",
1974
+ display: "range",
1975
+ rangeStep: 10
1976
+ },
1977
+ {
1978
+ _key: "fc4",
1979
+ label: "In stock only",
1980
+ source: "availability",
1981
+ display: "checkbox",
1982
+ showCounts: true
1983
+ }
1984
+ ],
1985
+ sortOptions: [
1986
+ "featured",
1987
+ "bestselling",
1988
+ "newest",
1989
+ "price-asc",
1990
+ "price-desc"
1991
+ ],
1992
+ defaultSort: "featured",
1993
+ perPage: 24,
1994
+ showCount: true
1995
+ },
1996
+ notes: {
1997
+ why: "The header is presentation over a collection reference (title/intro fall back to the collection), and the filter sidebar is CONFIGURATION, not content: an array of facet objects, each saying what it filters on (option, field, tag, price, availability) and how it shows (checkbox, swatch, pills, range). Display-specific settings are conditional siblings — swatch colors only for swatches, step only for ranges — so a facet stays a short form. Facet values themselves are never listed: they're computed from the products, so new colors appear without an edit.",
1998
+ sanity: "A `collection` document (Sanity Connect syncs Shopify collections) plus a `collectionPage` object with `facets: array<object{label, source, key, display, swatches[]}>`. Filtering itself would run in the frontend via Shopify Storefront API `products(filters:)` — Sanity stores only the config.",
1999
+ htmlSignals: [
2000
+ "a page title + description followed by a filter sidebar/drawer with grouped checkbox lists, color swatches and a price slider",
2001
+ "a 'Sort by' <select> with options like Featured, Best selling, Price low to high",
2002
+ "URL params like ?filter.v.option.color= or ?sort_by= (Shopify), ?min_price= (WooCommerce)",
2003
+ "a 'n products' count and pagination/'Load more' below the grid"
2004
+ ],
2005
+ mcp: "Header text → title (only if it differs from the collection name) / description; hero image → banner. For each filter group: label = group heading; infer source from the param name (filter.v.option.* → option + key, filter.p.m.* metafield → field, filter.v.price → price + range, filter.v.availability → availability). Swatch groups → display:'swatch' and copy each swatch's background color into swatches[]. Do NOT import the filter VALUES — they are computed from products. Sort <select> options → sortOptions (map labels to the enum), selected option → defaultSort.",
2006
+ pitfalls: ["defaultSort must be one of sortOptions; select options can't depend on another field yet (gap), so it's validated on save.", "Importing facet values as content makes new colors invisible until someone edits the page."]
2007
+ }
2008
+ },
2009
+ {
2010
+ id: "commerce-bundle-builder",
2011
+ label: "Bundle builder (choose N)",
2012
+ proves: "Eligible products (query or picked) · pick min/max · tiered discount OR fixed bundle price as a conditional pair · tier rows with validation",
2013
+ category: "Commerce",
2014
+ brand: "Caldera",
2015
+ previewLayout: "grid-3",
2016
+ tags: [
2017
+ "commerce",
2018
+ "conditional",
2019
+ "references",
2020
+ "repeater",
2021
+ "validation"
2022
+ ],
2023
+ schema: {
2024
+ name: "bundleBuilder",
2025
+ title: "Bundle builder",
2026
+ kind: "block",
2027
+ icon: "packages",
2028
+ category: "Commerce",
2029
+ description: "Shoppers pick a set of products and save as they add more",
2030
+ preview: {
2031
+ title: "heading",
2032
+ subtitle: "eyebrow"
2033
+ },
2034
+ groups: [{
2035
+ name: "content",
2036
+ title: "Content",
2037
+ default: true
2038
+ }, {
2039
+ name: "rules",
2040
+ title: "Rules & savings"
2041
+ }],
2042
+ fieldsets: [{
2043
+ name: "pick",
2044
+ title: "How many",
2045
+ columns: 2
2046
+ }],
2047
+ fields: [
2048
+ {
2049
+ name: "eyebrow",
2050
+ title: "Eyebrow",
2051
+ type: "string",
2052
+ validation: { max: 32 }
2053
+ },
2054
+ {
2055
+ name: "heading",
2056
+ title: "Heading",
2057
+ type: "string",
2058
+ validation: {
2059
+ required: true,
2060
+ recommendedMax: 48
2061
+ },
2062
+ ai: true
2063
+ },
2064
+ {
2065
+ name: "body",
2066
+ title: "Body",
2067
+ type: "text",
2068
+ rows: 2,
2069
+ validation: { recommendedMax: 160 },
2070
+ ai: true
2071
+ },
2072
+ {
2073
+ name: "eligibleProducts",
2074
+ title: "Products to choose from",
2075
+ type: "array",
2076
+ validation: {
2077
+ min: 2,
2078
+ max: 12,
2079
+ unique: true
2080
+ },
2081
+ of: [productRef("eligibleProduct", "Product")]
2082
+ },
2083
+ {
2084
+ name: "min",
2085
+ title: "At least",
2086
+ type: "number",
2087
+ group: "rules",
2088
+ fieldset: "pick",
2089
+ initialValue: 2,
2090
+ numeric: {
2091
+ min: 1,
2092
+ max: 10,
2093
+ step: 1,
2094
+ unit: "items"
2095
+ },
2096
+ validation: { required: true }
2097
+ },
2098
+ {
2099
+ name: "max",
2100
+ title: "At most",
2101
+ type: "number",
2102
+ group: "rules",
2103
+ fieldset: "pick",
2104
+ initialValue: 4,
2105
+ numeric: {
2106
+ min: 1,
2107
+ max: 12,
2108
+ step: 1,
2109
+ unit: "items"
2110
+ },
2111
+ validation: { required: true },
2112
+ description: "Must be at least the minimum."
2113
+ },
2114
+ {
2115
+ name: "allowRepeats",
2116
+ title: "Allow the same product twice",
2117
+ type: "boolean",
2118
+ group: "rules"
2119
+ },
2120
+ {
2121
+ name: "pricing",
2122
+ title: "Savings",
2123
+ type: "select",
2124
+ group: "rules",
2125
+ layout: "cards",
2126
+ initialValue: "tiers",
2127
+ options: [{
2128
+ value: "tiers",
2129
+ label: "Save more as they add",
2130
+ icon: "stairs-up",
2131
+ description: "e.g. 10% for 2, 15% for 3"
2132
+ }, {
2133
+ value: "fixed",
2134
+ label: "One bundle price",
2135
+ icon: "tag",
2136
+ description: "e.g. any 3 for $199"
2137
+ }]
2138
+ },
2139
+ {
2140
+ name: "tiers",
2141
+ title: "Discount steps",
2142
+ type: "array",
2143
+ group: "rules",
2144
+ itemNoun: "step",
2145
+ openMode: "inline",
2146
+ hidden: {
2147
+ field: "pricing",
2148
+ equals: "tiers"
2149
+ },
2150
+ validation: {
2151
+ min: 1,
2152
+ max: 4
2153
+ },
2154
+ description: "The highest step reached applies.",
2155
+ of: [{
2156
+ name: "tier",
2157
+ title: "Step",
2158
+ type: "object",
2159
+ icon: "discount",
2160
+ preview: {
2161
+ title: "label",
2162
+ subtitle: "quantity",
2163
+ fallback: "Untitled step"
2164
+ },
2165
+ fields: [
2166
+ {
2167
+ name: "quantity",
2168
+ title: "Items",
2169
+ type: "number",
2170
+ numeric: {
2171
+ min: 1,
2172
+ max: 12,
2173
+ step: 1
2174
+ },
2175
+ validation: { required: true }
2176
+ },
2177
+ {
2178
+ name: "percent",
2179
+ title: "Off",
2180
+ type: "number",
2181
+ numeric: {
2182
+ min: 1,
2183
+ max: 50,
2184
+ step: 1,
2185
+ unit: "%"
2186
+ },
2187
+ validation: { required: true }
2188
+ },
2189
+ {
2190
+ name: "label",
2191
+ title: "Label",
2192
+ type: "string",
2193
+ validation: { max: 24 },
2194
+ placeholder: "Save 15%"
2195
+ }
2196
+ ]
2197
+ }]
2198
+ },
2199
+ {
2200
+ name: "bundlePrice",
2201
+ title: "Bundle price",
2202
+ type: "number",
2203
+ group: "rules",
2204
+ hidden: {
2205
+ field: "pricing",
2206
+ equals: "fixed"
2207
+ },
2208
+ numeric: {
2209
+ unit: "USD",
2210
+ min: 0,
2211
+ step: 1
2212
+ },
2213
+ validation: { required: true },
2214
+ description: "Charged for exactly the maximum number of items."
2215
+ },
2216
+ {
2217
+ name: "discountCode",
2218
+ title: "Shopify discount",
2219
+ type: "string",
2220
+ group: "rules",
2221
+ access: { edit: "schema.manage" },
2222
+ lockReason: "Created by the bundle app in Shopify",
2223
+ description: "The Shopify automatic discount that applies these savings."
2224
+ },
2225
+ {
2226
+ name: "addLabel",
2227
+ title: "Add bundle button",
2228
+ type: "string",
2229
+ validation: {
2230
+ required: true,
2231
+ max: 28
2232
+ },
2233
+ placeholder: "Add kit to cart"
2234
+ },
2235
+ {
2236
+ name: "note",
2237
+ title: "Small print",
2238
+ type: "string",
2239
+ validation: { max: 120 }
2240
+ }
2241
+ ]
2242
+ },
2243
+ value: {
2244
+ eyebrow: "Build your camp kit",
2245
+ heading: "Pick 3, save 15%",
2246
+ body: "Choose any three pieces from the camp range. Save 10% on two, 15% on three, 20% on four.",
2247
+ eligibleProducts: [
2248
+ P.grill,
2249
+ P.dutch,
2250
+ P.skillet,
2251
+ P.kettle
2252
+ ],
2253
+ min: 2,
2254
+ max: 4,
2255
+ allowRepeats: false,
2256
+ pricing: "tiers",
2257
+ tiers: [
2258
+ {
2259
+ _key: "t1",
2260
+ quantity: 2,
2261
+ percent: 10,
2262
+ label: "Save 10%"
2263
+ },
2264
+ {
2265
+ _key: "t2",
2266
+ quantity: 3,
2267
+ percent: 15,
2268
+ label: "Save 15%"
2269
+ },
2270
+ {
2271
+ _key: "t3",
2272
+ quantity: 4,
2273
+ percent: 20,
2274
+ label: "Save 20%"
2275
+ }
2276
+ ],
2277
+ bundlePrice: 279,
2278
+ discountCode: "BUNDLE-CAMPKIT-AUTO",
2279
+ addLabel: "Add kit to cart",
2280
+ note: "Gift cards and the Ember Pizza Oven aren't included."
2281
+ },
2282
+ notes: {
2283
+ why: "A bundle is RULES around a product set: the eligible list is references (price and stock stay live), the pick limits are two numbers side by side, and the savings model is a conditional pair — tiered steps (an inline array of quantity/percent/label) or one fixed bundle price — because those two are the deals real stores run. The actual checkout discount lives in Shopify; the section shows its id developer-locked so marketers can see what enforces the promise without breaking it.",
2284
+ sanity: "`bundleBuilder` object with `products: array<reference to product>`, `min/max: number` with a custom `Rule.custom` for min ≤ max, `pricing: string` + `tiers: array<object>` hidden via `hidden: ({parent}) => parent.pricing !== 'tiers'`. The discount itself would be a Shopify automatic discount referenced by ID.",
2285
+ htmlSignals: [
2286
+ "copy like 'Pick 3', 'Build your bundle', 'Mix & match', 'Save x% when you buy n'",
2287
+ "product cards with Add/Remove toggles and a running selection tray or progress bar (1 of 3 selected)",
2288
+ "a discount ladder ('2 items: 10% · 3 items: 15%')",
2289
+ "a single 'Add bundle to cart' button that stays disabled until enough items are chosen"
2290
+ ],
2291
+ mcp: "Resolve each selectable card to a Products entry → eligible[]. Parse the deal copy: 'n for $X' → pricing:'fixed' + bundlePrice + max=n; ladder copy → pricing:'tiers' with one row per step. min = the first step's quantity (or the disabled-until count). Never invent a discount code — leave discountCode empty for a developer to wire.",
2292
+ pitfalls: ["min ≤ max and ascending tier quantities can't be enforced by the schema yet (no cross-field rules).", "The page promise and the Shopify discount can disagree; the converter must flag bundles without a discountCode."]
2293
+ }
2294
+ },
2295
+ {
2296
+ id: "commerce-cart-cross-sell",
2297
+ label: "Cart upsell & cross-sell",
2298
+ proves: "Rules array: when (condition type) → then (offer references) with condition-specific inputs · fallback list · placement",
2299
+ category: "Commerce",
2300
+ brand: "Caldera",
2301
+ previewLayout: "list",
2302
+ tags: [
2303
+ "commerce",
2304
+ "references",
2305
+ "conditional",
2306
+ "deep-nesting",
2307
+ "personalization",
2308
+ "repeater",
2309
+ "validation"
2310
+ ],
2311
+ schema: {
2312
+ name: "cartOffers",
2313
+ title: "Cart offers",
2314
+ kind: "block",
2315
+ icon: "shopping-cart-plus",
2316
+ category: "Commerce",
2317
+ description: "Suggestions in the cart, chosen by what's already in it",
2318
+ preview: {
2319
+ title: "heading",
2320
+ subtitle: "placements"
2321
+ },
2322
+ groups: [{
2323
+ name: "content",
2324
+ title: "Content",
2325
+ default: true
2326
+ }, {
2327
+ name: "rules",
2328
+ title: "Rules"
2329
+ }],
2330
+ fields: [
2331
+ {
2332
+ name: "heading",
2333
+ title: "Heading",
2334
+ type: "string",
2335
+ validation: {
2336
+ required: true,
2337
+ max: 40
2338
+ },
2339
+ placeholder: "Goes well with",
2340
+ localized: true
2341
+ },
2342
+ {
2343
+ name: "placements",
2344
+ title: "Shows in",
2345
+ type: "multiselect",
2346
+ layout: "checkbox",
2347
+ validation: { min: 1 },
2348
+ options: [{
2349
+ value: "drawer",
2350
+ label: "Cart drawer"
2351
+ }, {
2352
+ value: "page",
2353
+ label: "Cart page"
2354
+ }]
2355
+ },
2356
+ {
2357
+ name: "maxShown",
2358
+ title: "Show at most",
2359
+ type: "number",
2360
+ layout: "slider",
2361
+ initialValue: 3,
2362
+ numeric: {
2363
+ min: 1,
2364
+ max: 4,
2365
+ step: 1,
2366
+ unit: "products"
2367
+ }
2368
+ },
2369
+ {
2370
+ name: "hideInCart",
2371
+ title: "Hide products already in the cart",
2372
+ type: "boolean",
2373
+ initialValue: true
2374
+ },
2375
+ {
2376
+ name: "rules",
2377
+ title: "Rules",
2378
+ type: "array",
2379
+ group: "rules",
2380
+ itemNoun: "rule",
2381
+ validation: { max: 10 },
2382
+ description: "Checked top to bottom. The first match decides the offers.",
2383
+ of: [{
2384
+ name: "rule",
2385
+ title: "Rule",
2386
+ type: "object",
2387
+ icon: "arrow-guide",
2388
+ preview: {
2389
+ title: "name",
2390
+ subtitle: "when",
2391
+ fallback: "Untitled rule"
2392
+ },
2393
+ fields: [
2394
+ {
2395
+ name: "name",
2396
+ title: "Rule name",
2397
+ type: "string",
2398
+ validation: {
2399
+ required: true,
2400
+ max: 48
2401
+ },
2402
+ description: "Only your team sees this."
2403
+ },
2404
+ {
2405
+ name: "when",
2406
+ title: "When the cart",
2407
+ type: "select",
2408
+ validation: { required: true },
2409
+ options: [
2410
+ {
2411
+ value: "contains-product",
2412
+ label: "contains a product"
2413
+ },
2414
+ {
2415
+ value: "contains-tag",
2416
+ label: "contains a product tagged"
2417
+ },
2418
+ {
2419
+ value: "subtotal-below",
2420
+ label: "is under an amount",
2421
+ description: "Good for free-shipping nudges"
2422
+ }
2423
+ ]
2424
+ },
2425
+ {
2426
+ name: "triggerProducts",
2427
+ title: "Any of these products",
2428
+ type: "array",
2429
+ hidden: {
2430
+ field: "when",
2431
+ equals: "contains-product"
2432
+ },
2433
+ validation: {
2434
+ min: 1,
2435
+ max: 6
2436
+ },
2437
+ of: [productRef("triggerProduct", "Product")]
2438
+ },
2439
+ {
2440
+ name: "triggerTag",
2441
+ title: "Tag",
2442
+ type: "string",
2443
+ hidden: {
2444
+ field: "when",
2445
+ equals: "contains-tag"
2446
+ },
2447
+ validation: {
2448
+ required: true,
2449
+ max: 32
2450
+ },
2451
+ description: "Exactly as written on the products."
2452
+ },
2453
+ {
2454
+ name: "threshold",
2455
+ title: "Amount",
2456
+ type: "number",
2457
+ hidden: {
2458
+ field: "when",
2459
+ equals: "subtotal-below"
2460
+ },
2461
+ numeric: {
2462
+ unit: "USD",
2463
+ min: 1,
2464
+ step: 1
2465
+ },
2466
+ validation: { required: true },
2467
+ description: "The offer shows while the subtotal is below this."
2468
+ },
2469
+ {
2470
+ name: "offer",
2471
+ title: "Then suggest",
2472
+ type: "object",
2473
+ icon: "gift",
2474
+ openMode: "drill",
2475
+ fields: [
2476
+ {
2477
+ name: "products",
2478
+ title: "Products",
2479
+ type: "array",
2480
+ validation: {
2481
+ min: 1,
2482
+ max: 4
2483
+ },
2484
+ of: [productRef("offerProduct", "Product")]
2485
+ },
2486
+ {
2487
+ name: "message",
2488
+ title: "Message",
2489
+ type: "string",
2490
+ validation: { max: 80 },
2491
+ placeholder: "You're {{remaining}} from free shipping",
2492
+ description: "{{remaining}} becomes the amount left."
2493
+ },
2494
+ {
2495
+ name: "percentOff",
2496
+ title: "Discount on these",
2497
+ type: "number",
2498
+ numeric: {
2499
+ min: 0,
2500
+ max: 30,
2501
+ step: 5,
2502
+ unit: "%"
2503
+ },
2504
+ description: "0 = full price."
2505
+ }
2506
+ ]
2507
+ },
2508
+ {
2509
+ name: "isActive",
2510
+ title: "Active",
2511
+ type: "boolean",
2512
+ initialValue: true
2513
+ }
2514
+ ]
2515
+ }]
2516
+ },
2517
+ {
2518
+ name: "fallbackOffers",
2519
+ title: "When no rule matches",
2520
+ type: "array",
2521
+ group: "rules",
2522
+ validation: { max: 4 },
2523
+ description: "Leave empty to show nothing.",
2524
+ of: [productRef("fallbackProduct", "Product")]
2525
+ }
2526
+ ]
2527
+ },
2528
+ value: {
2529
+ heading: "Goes well with",
2530
+ placements: ["drawer", "page"],
2531
+ maxShown: 3,
2532
+ hideInCart: true,
2533
+ rules: [
2534
+ {
2535
+ _key: "ru1",
2536
+ name: "Free-shipping nudge",
2537
+ when: "subtotal-below",
2538
+ threshold: 80,
2539
+ offer: {
2540
+ products: [P.kettle],
2541
+ message: "You're {{remaining}} from free shipping",
2542
+ percentOff: 0
2543
+ },
2544
+ isActive: true
2545
+ },
2546
+ {
2547
+ _key: "ru2",
2548
+ name: "Dutch oven buyers",
2549
+ when: "contains-product",
2550
+ triggerProducts: [P.dutch],
2551
+ offer: {
2552
+ products: [P.skillet, P.grill],
2553
+ message: "The lid fits the Ridge Skillet too.",
2554
+ percentOff: 10
2555
+ },
2556
+ isActive: true
2557
+ },
2558
+ {
2559
+ _key: "ru3",
2560
+ name: "Anything cast iron",
2561
+ when: "contains-tag",
2562
+ triggerTag: "cast-iron",
2563
+ offer: {
2564
+ products: [P.grill],
2565
+ message: "Cook over coals with the Cable Fold."
2566
+ },
2567
+ isActive: false
2568
+ }
2569
+ ],
2570
+ fallbackOffers: [P.kettle, P.grill]
2571
+ },
2572
+ notes: {
2573
+ why: "Cart offers are RULES, not a product list: an ordered array where each rule is `when` (one select) → condition-specific input (product refs, a tag, or an amount, each a conditional sibling) → `offer` (a small drill-in object of product refs, a message with a {{remaining}} token, and an optional discount). First match wins, which is how merchandisers reason about it, and a fallbackOffers list covers the rest. Rules can be paused with `isActive` so experiments don't destroy setup.",
2574
+ sanity: "`cartOffers` document (singleton) with `rules: array<object{when: string, triggerProducts: array<reference to product>, triggerTag: string, threshold: number, offer: object{products: array<reference>, message, percentOff}}>` and `hidden: ({parent}) => parent.when !== '…'` per input. Product references point at Sanity Connect `product` docs.",
2575
+ htmlSignals: [
2576
+ "inside a cart drawer/page (form action=/cart, .cart-drawer): a heading like 'You may also like', 'Complete the set', 'Goes well with'",
2577
+ "a small horizontal product list with '+ Add' buttons inside the cart",
2578
+ "free-shipping progress copy: 'You're $12 away from free shipping'",
2579
+ "data attributes from upsell apps (Rebuy, ReConvert, Shopify product recommendations intent=complementary)"
2580
+ ],
2581
+ mcp: "Static cross-sell markup only shows ONE state, so emit a single fallback list from the visible products and, if a free-shipping bar exists, one 'subtotal-below' rule with the threshold parsed from the copy. If an app config is reachable (Rebuy JSON, recommendations endpoint intent=complementary), translate its rules into rules[]. Never fabricate rules from a single screenshot; leave rules empty and set fallback.",
2582
+ pitfalls: [
2583
+ "Rule order matters (first match wins); the converter must preserve source order.",
2584
+ "percentOff needs a matching Shopify discount — otherwise the price promised in the cart won't apply at checkout.",
2585
+ "A rule's trigger can't reference products already in the offer set; 'hideInCart' handles it at render."
2586
+ ]
2587
+ }
2588
+ },
2589
+ {
2590
+ id: "commerce-promo-countdown",
2591
+ label: "Promo banner with code + countdown",
2592
+ proves: "Discount code with pattern validation · fixed-deadline vs per-visitor countdown (conditional) · what happens after it ends · schedule + audience",
2593
+ category: "Commerce",
2594
+ brand: "Caldera",
2595
+ previewLayout: "banner",
2596
+ tags: [
2597
+ "commerce",
2598
+ "conditional",
2599
+ "validation",
2600
+ "personalization",
2601
+ "variants"
2602
+ ],
2603
+ schema: {
2604
+ name: "promoBanner",
2605
+ title: "Promo banner",
2606
+ kind: "block",
2607
+ icon: "discount",
2608
+ category: "Commerce",
2609
+ description: "A sale message with a code to copy and a countdown",
2610
+ variants: [{
2611
+ id: "bar",
2612
+ name: "Top bar",
2613
+ description: "One line above the navigation"
2614
+ }, {
2615
+ id: "band",
2616
+ name: "Band",
2617
+ description: "A full-width section on the page"
2618
+ }],
2619
+ preview: {
2620
+ title: "message",
2621
+ subtitle: "code",
2622
+ icon: "discount"
2623
+ },
2624
+ groups: [
2625
+ {
2626
+ name: "content",
2627
+ title: "Content",
2628
+ default: true
2629
+ },
2630
+ {
2631
+ name: "timing",
2632
+ title: "Timing"
2633
+ },
2634
+ {
2635
+ name: "design",
2636
+ title: "Design"
2637
+ }
2638
+ ],
2639
+ fieldsets: [{
2640
+ name: "schedule",
2641
+ title: "Schedule"
2642
+ }],
2643
+ fields: [
2644
+ {
2645
+ name: "message",
2646
+ title: "Message",
2647
+ type: "string",
2648
+ validation: {
2649
+ required: true,
2650
+ max: 90,
2651
+ recommendedMax: 60
2652
+ },
2653
+ ai: true,
2654
+ localized: true
2655
+ },
2656
+ {
2657
+ name: "code",
2658
+ title: "Discount code",
2659
+ type: "string",
2660
+ validation: {
2661
+ pattern: "^[A-Z0-9]{3,20}$",
2662
+ patternMessage: "Use 3–20 capital letters and numbers, like SAVE20."
2663
+ },
2664
+ description: "Must already exist in Shopify."
2665
+ },
2666
+ {
2667
+ name: "isAutoApplied",
2668
+ title: "Apply the code for them",
2669
+ type: "boolean",
2670
+ hidden: {
2671
+ field: "code",
2672
+ notEmpty: true
2673
+ }
2674
+ },
2675
+ {
2676
+ name: "button",
2677
+ title: "Button",
2678
+ type: "button"
2679
+ },
2680
+ {
2681
+ name: "countdown",
2682
+ title: "Countdown",
2683
+ type: "select",
2684
+ group: "timing",
2685
+ layout: "segmented",
2686
+ initialValue: "none",
2687
+ options: [
2688
+ {
2689
+ value: "none",
2690
+ label: "None"
2691
+ },
2692
+ {
2693
+ value: "deadline",
2694
+ label: "To a date",
2695
+ description: "Everyone sees the same end time"
2696
+ },
2697
+ {
2698
+ value: "evergreen",
2699
+ label: "Per visitor",
2700
+ description: "Starts on their first visit"
2701
+ }
2702
+ ]
2703
+ },
2704
+ {
2705
+ name: "endsAt",
2706
+ title: "Ends",
2707
+ type: "datetime",
2708
+ group: "timing",
2709
+ hidden: {
2710
+ field: "countdown",
2711
+ equals: "deadline"
2712
+ },
2713
+ validation: { required: true }
2714
+ },
2715
+ {
2716
+ name: "durationHours",
2717
+ title: "Counts down from",
2718
+ type: "number",
2719
+ group: "timing",
2720
+ hidden: {
2721
+ field: "countdown",
2722
+ equals: "evergreen"
2723
+ },
2724
+ numeric: {
2725
+ min: 1,
2726
+ max: 72,
2727
+ step: 1,
2728
+ unit: "hours"
2729
+ },
2730
+ validation: { required: true }
2731
+ },
2732
+ {
2733
+ name: "afterEnd",
2734
+ title: "When the timer ends",
2735
+ type: "select",
2736
+ group: "timing",
2737
+ hidden: {
2738
+ field: "countdown",
2739
+ equals: ["deadline", "evergreen"]
2740
+ },
2741
+ initialValue: "hide",
2742
+ options: [{
2743
+ value: "hide",
2744
+ label: "Hide the banner"
2745
+ }, {
2746
+ value: "message",
2747
+ label: "Show a different message"
2748
+ }]
2749
+ },
2750
+ {
2751
+ name: "endedMessage",
2752
+ title: "Message after it ends",
2753
+ type: "string",
2754
+ group: "timing",
2755
+ hidden: {
2756
+ field: "afterEnd",
2757
+ equals: "message"
2758
+ },
2759
+ validation: { max: 90 },
2760
+ placeholder: "The sale's over. Free shipping still applies over $80."
2761
+ },
2762
+ {
2763
+ name: "startsAt",
2764
+ title: "Show from",
2765
+ type: "datetime",
2766
+ group: "timing",
2767
+ fieldset: "schedule"
2768
+ },
2769
+ {
2770
+ name: "hideAfter",
2771
+ title: "Hide after",
2772
+ type: "datetime",
2773
+ group: "timing",
2774
+ fieldset: "schedule",
2775
+ description: "Leave empty to keep it until you remove it."
2776
+ },
2777
+ {
2778
+ name: "audience",
2779
+ title: "Who sees it",
2780
+ type: "select",
2781
+ group: "timing",
2782
+ initialValue: "everyone",
2783
+ options: [
2784
+ {
2785
+ value: "everyone",
2786
+ label: "Everyone"
2787
+ },
2788
+ {
2789
+ value: "new",
2790
+ label: "First-time visitors"
2791
+ },
2792
+ {
2793
+ value: "returning",
2794
+ label: "Returning visitors"
2795
+ },
2796
+ {
2797
+ value: "utm",
2798
+ label: "Visitors from a campaign link"
2799
+ }
2800
+ ]
2801
+ },
2802
+ {
2803
+ name: "utmCampaign",
2804
+ title: "Campaign",
2805
+ type: "string",
2806
+ group: "timing",
2807
+ hidden: {
2808
+ field: "audience",
2809
+ equals: "utm"
2810
+ },
2811
+ validation: {
2812
+ required: true,
2813
+ max: 48
2814
+ },
2815
+ description: "Matches utm_campaign in the link."
2816
+ },
2817
+ {
2818
+ name: "isDismissible",
2819
+ title: "Visitors can close it",
2820
+ type: "boolean",
2821
+ group: "design",
2822
+ initialValue: true
2823
+ },
2824
+ themeField
2825
+ ]
2826
+ },
2827
+ value: {
2828
+ _variant: "bar",
2829
+ message: "Fireside Sale: 20% off all cast iron",
2830
+ code: "FIRESIDE20",
2831
+ isAutoApplied: true,
2832
+ button: {
2833
+ label: "Shop the sale",
2834
+ href: "/collections/cast-iron",
2835
+ type: "page",
2836
+ style: "link",
2837
+ arrow: true
2838
+ },
2839
+ countdown: "deadline",
2840
+ endsAt: "2026-11-02T23:59:00-08:00[America/Los_Angeles]",
2841
+ afterEnd: "message",
2842
+ endedMessage: "The sale's over — free shipping still applies over $80.",
2843
+ startsAt: "2026-10-24T06:00:00-07:00[America/Los_Angeles]",
2844
+ hideAfter: "2026-11-09T23:59:00-08:00[America/Los_Angeles]",
2845
+ audience: "everyone",
2846
+ isDismissible: true,
2847
+ theme: "brand"
2848
+ },
2849
+ notes: {
2850
+ why: "Three ideas, each a conditional cluster: the CODE (pattern-validated string, plus 'apply for them' only when a code exists — `notEmpty` condition), the COUNTDOWN (none / a real deadline / a per-visitor evergreen timer, each showing only its own input), and what happens AFTER it ends (hide or swap the message). Schedule and audience are separate from the countdown on purpose: the banner can be visible before the timer starts. The evergreen help text keeps it honest — fake urgency is a dark pattern.",
2851
+ sanity: "`promoBanner` object: `code: string` with `Rule.regex(/^[A-Z0-9]{3,20}$/)`, `countdown: string` + `endsAt: datetime` / `durationHours: number` with `hidden` callbacks, `startsAt`/`hideAfter` datetimes. Often a singleton `announcement` document referenced by the layout.",
2852
+ htmlSignals: [
2853
+ "a thin full-width bar above the header with sale copy and a code in <code>/<strong>/a 'Copy' button",
2854
+ "a ticking timer: elements for days/hours/minutes/seconds, data-countdown / data-end attributes or a script with a target timestamp",
2855
+ "a close (×) button with aria-label='Close announcement'",
2856
+ "a link that applies a discount: /discount/<CODE>?redirect=…"
2857
+ ],
2858
+ mcp: "Copy text → message; the code → code (upper-case it); a /discount/<CODE> link → isAutoApplied: true. A timer with a fixed timestamp in markup/script → countdown:'deadline' + endsAt (keep the offset); a timer seeded from localStorage/cookie → 'evergreen' + durationHours. Position above the nav → _variant:'bar', otherwise 'band'. Close button → isDismissible: true.",
2859
+ pitfalls: [
2860
+ "Time zones: an ISO string without an offset ends the sale at the wrong hour for half the country.",
2861
+ "hideAfter earlier than endsAt can't be caught by the schema (no cross-field rules).",
2862
+ "Evergreen timers that reset on refresh are deceptive; the renderer persists the start per visitor."
2863
+ ]
2864
+ }
2865
+ },
2866
+ {
2867
+ id: "commerce-shop-the-look",
2868
+ label: "Shop the look (hotspots)",
2869
+ proves: "Looks › look › hotspots › hotspot: image coordinates as numbers + a product reference per point · accessible labels for every dot",
2870
+ category: "Commerce",
2871
+ brand: "Caldera",
2872
+ previewLayout: "gallery",
2873
+ tags: [
2874
+ "media-heavy",
2875
+ "references",
2876
+ "deep-nesting",
2877
+ "repeater",
2878
+ "commerce",
2879
+ "a11y",
2880
+ "validation"
2881
+ ],
2882
+ schema: {
2883
+ name: "shopTheLook",
2884
+ title: "Shop the look",
2885
+ kind: "block",
2886
+ icon: "hanger",
2887
+ category: "Commerce",
2888
+ description: "Photos with dots that open the products in them",
2889
+ variants: [{
2890
+ id: "single",
2891
+ name: "One look"
2892
+ }, {
2893
+ id: "carousel",
2894
+ name: "Several looks in a carousel"
2895
+ }],
2896
+ preview: {
2897
+ title: "heading",
2898
+ media: "looks.0.image"
2899
+ },
2900
+ fields: [
2901
+ {
2902
+ name: "heading",
2903
+ title: "Heading",
2904
+ type: "string",
2905
+ validation: { recommendedMax: 48 },
2906
+ ai: true
2907
+ },
2908
+ {
2909
+ name: "looks",
2910
+ title: "Looks",
2911
+ type: "array",
2912
+ itemNoun: "look",
2913
+ arrayLayout: "grid",
2914
+ validation: {
2915
+ min: 1,
2916
+ max: 6
2917
+ },
2918
+ of: [{
2919
+ name: "look",
2920
+ title: "Look",
2921
+ type: "object",
2922
+ icon: "photo",
2923
+ preview: {
2924
+ title: "title",
2925
+ subtitle: "image.alt",
2926
+ media: "image",
2927
+ fallback: "Untitled look"
2928
+ },
2929
+ fields: [
2930
+ {
2931
+ name: "title",
2932
+ title: "Look name",
2933
+ type: "string",
2934
+ validation: { max: 40 }
2935
+ },
2936
+ {
2937
+ name: "image",
2938
+ title: "Image",
2939
+ type: "image",
2940
+ validation: { required: true }
2941
+ },
2942
+ {
2943
+ name: "hotspots",
2944
+ title: "Dots",
2945
+ type: "array",
2946
+ itemNoun: "dot",
2947
+ validation: {
2948
+ min: 1,
2949
+ max: 8
2950
+ },
2951
+ of: [{
2952
+ name: "hotspot",
2953
+ title: "Dot",
2954
+ type: "object",
2955
+ icon: "point",
2956
+ preview: {
2957
+ title: "label",
2958
+ subtitle: "product",
2959
+ fallback: "Untitled dot"
2960
+ },
2961
+ fields: [
2962
+ productRef("product", "Product"),
2963
+ {
2964
+ name: "label",
2965
+ title: "Label for screen readers",
2966
+ type: "string",
2967
+ validation: {
2968
+ required: true,
2969
+ max: 60
2970
+ },
2971
+ description: "Not shown. Read by screen readers."
2972
+ },
2973
+ {
2974
+ name: "x",
2975
+ title: "Across",
2976
+ type: "number",
2977
+ layout: "slider",
2978
+ numeric: {
2979
+ min: 0,
2980
+ max: 100,
2981
+ step: .5,
2982
+ unit: "%"
2983
+ },
2984
+ validation: { required: true },
2985
+ description: "From the left edge of the photo."
2986
+ },
2987
+ {
2988
+ name: "y",
2989
+ title: "Down",
2990
+ type: "number",
2991
+ layout: "slider",
2992
+ numeric: {
2993
+ min: 0,
2994
+ max: 100,
2995
+ step: .5,
2996
+ unit: "%"
2997
+ },
2998
+ validation: { required: true },
2999
+ description: "From the top edge of the photo."
3000
+ },
3001
+ {
3002
+ name: "variant",
3003
+ title: "Variant",
3004
+ type: "string",
3005
+ validation: { max: 40 },
3006
+ description: "Leave empty for the default."
3007
+ }
3008
+ ]
3009
+ }]
3010
+ }
3011
+ ]
3012
+ }]
3013
+ },
3014
+ {
3015
+ name: "showProductList",
3016
+ title: "List the products beside the photo",
3017
+ type: "boolean",
3018
+ initialValue: true
3019
+ }
3020
+ ]
3021
+ },
3022
+ value: {
3023
+ _variant: "single",
3024
+ heading: "Sunday breakfast at camp",
3025
+ looks: [{
3026
+ _key: "lk1",
3027
+ title: "Coals, coffee, cornbread",
3028
+ image: img("caldera-camp-breakfast", 1600, 2e3, "A campfire breakfast: a Dutch oven of cornbread on a grill grate, a kettle steaming beside it"),
3029
+ hotspots: [
3030
+ {
3031
+ _key: "hs1",
3032
+ product: P.dutch,
3033
+ label: "Ember Dutch Oven on the grate",
3034
+ x: 42,
3035
+ y: 58,
3036
+ variant: "Bare iron"
3037
+ },
3038
+ {
3039
+ _key: "hs2",
3040
+ product: P.grill,
3041
+ label: "Cable Fold Grill over the fire",
3042
+ x: 50,
3043
+ y: 72
3044
+ },
3045
+ {
3046
+ _key: "hs3",
3047
+ product: P.kettle,
3048
+ label: "Trail Kettle on the rock",
3049
+ x: 78,
3050
+ y: 46
3051
+ }
3052
+ ]
3053
+ }],
3054
+ showProductList: true
3055
+ },
3056
+ notes: {
3057
+ why: "Four levels: section › looks › look › hotspots › hotspot. A hotspot is a product REFERENCE plus two percentage coordinates (so a re-crop or responsive resize keeps the dot on the object) plus a REQUIRED screen-reader label — a dot is a button with no visible text, so the label is not optional. `showProductList` guarantees the products are reachable without precise tapping on a phone.",
3058
+ sanity: "`shopTheLook` with `looks: array<object{image, hotspots: array<object{product: reference, x: number, y: number}>}>`; most teams install `sanity-plugin-hotspot-array` to place dots on the image visually. Products are Sanity Connect `product` references.",
3059
+ htmlSignals: [
3060
+ "an image wrapper with absolutely positioned buttons/dots (style='left:42%;top:58%')",
3061
+ "popovers/tooltips that show a product name, price and 'Shop' link on dot click",
3062
+ "a 'Shop the look' / 'Get the look' heading with a product list beside the image",
3063
+ "data attributes like data-hotspot, data-x/data-y, .lookbook__dot"
3064
+ ],
3065
+ mcp: "Each image with positioned dots → one look. For each dot: read left/top percentages (convert px to % using the image's natural size), resolve the popover's product link to a Products entry, and write label from the dot's aria-label or the product name + where it is. Never drop a dot because it lacks an aria-label — generate one and flag it for review.",
3066
+ pitfalls: [
3067
+ "Pixel coordinates break on resize; always store percentages.",
3068
+ "Coordinates are raw numbers today — there's no visual point picker bound to the image yet (gap).",
3069
+ "Dots without labels fail WCAG 4.1.2; the label is required for that reason."
3070
+ ]
3071
+ }
3072
+ },
3073
+ {
3074
+ id: "commerce-size-guide",
3075
+ label: "Size guide table",
3076
+ proves: "`table` type: sizes as row headers, a column group per unit (both measurements stored, never converted) · measuring steps · applies-to references",
3077
+ category: "Commerce",
3078
+ brand: "Caldera",
3079
+ previewLayout: "table",
3080
+ tags: [
3081
+ "repeater",
3082
+ "validation",
3083
+ "a11y",
3084
+ "references",
3085
+ "media-heavy"
3086
+ ],
3087
+ schema: {
3088
+ name: "sizeGuide",
3089
+ title: "Size guide",
3090
+ kind: "block",
3091
+ icon: "ruler-measure",
3092
+ category: "Commerce",
3093
+ description: "Size tables with inch and centimetre values, and how to measure",
3094
+ preview: {
3095
+ title: "title",
3096
+ subtitle: "intro"
3097
+ },
3098
+ groups: [{
3099
+ name: "content",
3100
+ title: "Tables",
3101
+ default: true
3102
+ }, {
3103
+ name: "measure",
3104
+ title: "How to measure"
3105
+ }],
3106
+ fields: [
3107
+ {
3108
+ name: "title",
3109
+ title: "Title",
3110
+ type: "string",
3111
+ validation: {
3112
+ required: true,
3113
+ max: 48
3114
+ }
3115
+ },
3116
+ {
3117
+ name: "intro",
3118
+ title: "Intro",
3119
+ type: "text",
3120
+ rows: 2,
3121
+ validation: { recommendedMax: 160 }
3122
+ },
3123
+ {
3124
+ name: "appliesTo",
3125
+ title: "Used on",
3126
+ type: "array",
3127
+ validation: { max: 20 },
3128
+ of: [productRef("guideProduct", "Product")]
3129
+ },
3130
+ {
3131
+ name: "units",
3132
+ title: "Units",
3133
+ type: "select",
3134
+ layout: "segmented",
3135
+ initialValue: "both",
3136
+ options: [
3137
+ {
3138
+ value: "in",
3139
+ label: "Inches only"
3140
+ },
3141
+ {
3142
+ value: "cm",
3143
+ label: "Centimeters only"
3144
+ },
3145
+ {
3146
+ value: "both",
3147
+ label: "Both, with a switch"
3148
+ }
3149
+ ]
3150
+ },
3151
+ {
3152
+ name: "tables",
3153
+ title: "Tables",
3154
+ type: "array",
3155
+ itemNoun: "table",
3156
+ validation: {
3157
+ min: 1,
3158
+ max: 4
3159
+ },
3160
+ of: [{
3161
+ name: "sizeTable",
3162
+ title: "Table",
3163
+ type: "object",
3164
+ icon: "table",
3165
+ preview: {
3166
+ title: "table.caption",
3167
+ fallback: "Untitled table"
3168
+ },
3169
+ fields: [{
3170
+ name: "table",
3171
+ title: "Sizes",
3172
+ type: "table",
3173
+ validation: { required: true },
3174
+ table: {
3175
+ maxColumns: 9,
3176
+ maxRows: 10,
3177
+ columnTypes: ["text", "number"],
3178
+ allowGroups: false,
3179
+ settings: {
3180
+ totals: false,
3181
+ highlight: false
3182
+ }
3183
+ },
3184
+ description: "Give each measurement in inches and centimeters.",
3185
+ helpMore: "Ranges like 3.5–3.75 are fine."
3186
+ }]
3187
+ }]
3188
+ },
3189
+ {
3190
+ name: "measureImage",
3191
+ title: "Measuring diagram",
3192
+ type: "image",
3193
+ group: "measure"
3194
+ },
3195
+ {
3196
+ name: "steps",
3197
+ title: "Steps",
3198
+ type: "array",
3199
+ group: "measure",
3200
+ itemNoun: "step",
3201
+ openMode: "inline",
3202
+ validation: { max: 4 },
3203
+ of: [{
3204
+ name: "step",
3205
+ title: "Step",
3206
+ type: "object",
3207
+ icon: "list-numbers",
3208
+ preview: {
3209
+ title: "label",
3210
+ subtitle: "body",
3211
+ fallback: "Untitled step"
3212
+ },
3213
+ fields: [{
3214
+ name: "label",
3215
+ title: "What",
3216
+ type: "string",
3217
+ validation: {
3218
+ required: true,
3219
+ max: 32
3220
+ }
3221
+ }, {
3222
+ name: "body",
3223
+ title: "Body",
3224
+ type: "text",
3225
+ rows: 2,
3226
+ validation: { max: 160 }
3227
+ }]
3228
+ }]
3229
+ },
3230
+ {
3231
+ name: "fitNote",
3232
+ title: "Between sizes?",
3233
+ type: "richtext",
3234
+ group: "measure"
3235
+ }
3236
+ ]
3237
+ },
3238
+ value: {
3239
+ title: "Fire glove and apron sizes",
3240
+ intro: "Our gloves are cut snug for grip. If you wear liners, size up.",
3241
+ appliesTo: [P.grill],
3242
+ units: "both",
3243
+ tables: [{
3244
+ _key: "tb1",
3245
+ table: tableFrom("Leather fire gloves, hand size", [
3246
+ {
3247
+ _key: "size",
3248
+ label: "Size"
3249
+ },
3250
+ {
3251
+ _key: "palmIn",
3252
+ label: "Palm width",
3253
+ group: "in"
3254
+ },
3255
+ {
3256
+ _key: "lengthIn",
3257
+ label: "Hand length",
3258
+ group: "in"
3259
+ },
3260
+ {
3261
+ _key: "palmCm",
3262
+ label: "Palm width",
3263
+ group: "cm"
3264
+ },
3265
+ {
3266
+ _key: "lengthCm",
3267
+ label: "Hand length",
3268
+ group: "cm"
3269
+ }
3270
+ ], [
3271
+ [
3272
+ "S",
3273
+ "3–3.25",
3274
+ "6.75–7",
3275
+ "7.6–8.3",
3276
+ "17–18"
3277
+ ],
3278
+ [
3279
+ "M",
3280
+ "3.5–3.75",
3281
+ "7.25–7.5",
3282
+ "8.9–9.5",
3283
+ "18.5–19"
3284
+ ],
3285
+ [
3286
+ "L",
3287
+ "4–4.25",
3288
+ "7.75–8",
3289
+ "10.2–10.8",
3290
+ "19.7–20.3"
3291
+ ],
3292
+ [
3293
+ "XL",
3294
+ "4.5–4.75",
3295
+ "8.25–8.5",
3296
+ "11.4–12",
3297
+ "21–21.6"
3298
+ ]
3299
+ ], {
3300
+ id: "tbl_glove_sizes",
3301
+ headerColumn: true,
3302
+ columnGroups: [{
3303
+ _key: "in",
3304
+ label: "Inches"
3305
+ }, {
3306
+ _key: "cm",
3307
+ label: "Centimetres"
3308
+ }]
3309
+ })
3310
+ }, {
3311
+ _key: "tb2",
3312
+ table: tableFrom("Waxed canvas apron, length", [
3313
+ {
3314
+ _key: "size",
3315
+ label: "Size"
3316
+ },
3317
+ {
3318
+ _key: "lengthIn",
3319
+ label: "Length",
3320
+ type: "number",
3321
+ group: "in"
3322
+ },
3323
+ {
3324
+ _key: "chestIn",
3325
+ label: "Chest width",
3326
+ type: "number",
3327
+ group: "in"
3328
+ },
3329
+ {
3330
+ _key: "lengthCm",
3331
+ label: "Length",
3332
+ type: "number",
3333
+ group: "cm"
3334
+ },
3335
+ {
3336
+ _key: "chestCm",
3337
+ label: "Chest width",
3338
+ type: "number",
3339
+ group: "cm"
3340
+ }
3341
+ ], [[
3342
+ "Regular",
3343
+ 34,
3344
+ 24,
3345
+ 86,
3346
+ 61
3347
+ ], [
3348
+ "Long",
3349
+ 38,
3350
+ 26,
3351
+ 97,
3352
+ 66
3353
+ ]], {
3354
+ id: "tbl_apron_sizes",
3355
+ headerColumn: true,
3356
+ columnGroups: [{
3357
+ _key: "in",
3358
+ label: "Inches"
3359
+ }, {
3360
+ _key: "cm",
3361
+ label: "Centimetres"
3362
+ }]
3363
+ })
3364
+ }],
3365
+ measureImage: img("caldera-glove-measure", 1200, 800, "Line drawing of an open hand with arrows across the palm below the knuckles and from wrist crease to fingertip"),
3366
+ steps: [{
3367
+ _key: "st1",
3368
+ label: "Palm width",
3369
+ body: "Measure across your palm just below the knuckles, thumb excluded."
3370
+ }, {
3371
+ _key: "st2",
3372
+ label: "Hand length",
3373
+ body: "From the crease of your wrist to the tip of your middle finger."
3374
+ }],
3375
+ fitNote: "<p>Between sizes? Go up if you'll wear liners or handle cast iron straight from the coals; go down for camp-kitchen dexterity.</p>"
3376
+ },
3377
+ notes: {
3378
+ why: "Each size table is a `table` (CPO D42): sizes are row headers (th scope=row), and every measurement appears once per unit inside a column group (Inches, Centimetres → th scope=colgroup), so the unit switch only shows or hides a group and never computes conversions (rounding a size chart is how returns happen). Cells are keyed by column, so reordering never misaligns a size. The caption is required because a <table> without a caption is a guessing game for screen readers. The guide is referenced FROM products via `appliesTo`, so one guide serves the whole range and the product page links to it next to the size picker.",
3379
+ sanity: "Usually `@sanity/table` (`rows: array<{cells: string[]}>`) or the native Portable Text table (v6.6: rows › cells, `headerRows`) inside a `sizeGuide` document, referenced from `product`. Neither has column groups or keyed cells, so units become two tables.",
3380
+ htmlSignals: [
3381
+ "a <table> with a first column of sizes (XS/S/M/L/XL or numbers) and measurement columns",
3382
+ "a 'Size guide' / 'Size chart' link next to the size option that opens a modal/drawer",
3383
+ "an in/cm or US/EU toggle switching the table values",
3384
+ "a 'How to measure' illustration with numbered instructions"
3385
+ ],
3386
+ mcp: "For each table run the table detector (docs/TABLES.md): caption from <caption> or the preceding heading; first column → row headers (`headerColumn: true`); measurement headers → columns. If a unit toggle exists, read both states (data attributes often hold the other unit) and emit one column group per unit with the same measurements in the same order; never convert numerically. Images in the guide → measureImage with descriptive alt.",
3387
+ pitfalls: [
3388
+ "RESOLVED (D42): cells are keyed by column, so a missing value is an empty cell, never a shifted row.",
3389
+ "The unit switch is the renderer's job (show the active column group); both groups stay in the HTML for search and AI.",
3390
+ "Auto-converting inches to cm produces values the brand never published.",
3391
+ "A size guide pasted as an image fails accessibility; the converter should OCR it into rows and keep the image only as the diagram."
3392
+ ]
3393
+ }
3394
+ },
3395
+ {
3396
+ id: "commerce-store-locator",
3397
+ label: "Store locator",
3398
+ proves: "Locations collection query vs written-here list · geopoint · weekly hours + holiday exceptions with HH:MM validation · map provider settings",
3399
+ category: "Commerce",
3400
+ brand: "Caldera",
3401
+ previewLayout: "media",
3402
+ tags: [
3403
+ "references",
3404
+ "dynamic-query",
3405
+ "conditional",
3406
+ "deep-nesting",
3407
+ "repeater",
3408
+ "validation",
3409
+ "embed",
3410
+ "seo-structured"
3411
+ ],
3412
+ schema: {
3413
+ name: "storeLocator",
3414
+ title: "Store locator",
3415
+ kind: "block",
3416
+ icon: "map-pin",
3417
+ category: "Commerce",
3418
+ description: "A searchable map of shops and stockists, with hours",
3419
+ variants: [{
3420
+ id: "map-list",
3421
+ name: "Map with list"
3422
+ }, {
3423
+ id: "list",
3424
+ name: "List only"
3425
+ }],
3426
+ preview: {
3427
+ title: "heading",
3428
+ subtitle: "source",
3429
+ icon: "map-pin"
3430
+ },
3431
+ groups: [
3432
+ {
3433
+ name: "content",
3434
+ title: "Content",
3435
+ default: true
3436
+ },
3437
+ {
3438
+ name: "locations",
3439
+ title: "Locations"
3440
+ },
3441
+ {
3442
+ name: "map",
3443
+ title: "Map & search"
3444
+ }
3445
+ ],
3446
+ fields: [
3447
+ {
3448
+ name: "heading",
3449
+ title: "Heading",
3450
+ type: "string",
3451
+ validation: {
3452
+ required: true,
3453
+ recommendedMax: 48
3454
+ }
3455
+ },
3456
+ {
3457
+ name: "intro",
3458
+ title: "Intro",
3459
+ type: "text",
3460
+ rows: 2,
3461
+ validation: { recommendedMax: 160 }
3462
+ },
3463
+ {
3464
+ name: "source",
3465
+ title: "Locations come from",
3466
+ type: "select",
3467
+ group: "locations",
3468
+ layout: "segmented",
3469
+ initialValue: "collection",
3470
+ options: [{
3471
+ value: "collection",
3472
+ label: "Locations collection",
3473
+ description: "Each shop has its own page"
3474
+ }, {
3475
+ value: "inline",
3476
+ label: "Written here",
3477
+ description: "For two or three shops"
3478
+ }]
3479
+ },
3480
+ {
3481
+ name: "types",
3482
+ title: "Show these types",
3483
+ type: "multiselect",
3484
+ group: "locations",
3485
+ layout: "checkbox",
3486
+ hidden: {
3487
+ field: "source",
3488
+ equals: "collection"
3489
+ },
3490
+ options: [
3491
+ {
3492
+ value: "flagship",
3493
+ label: "Caldera stores"
3494
+ },
3495
+ {
3496
+ value: "stockist",
3497
+ label: "Stockists"
3498
+ },
3499
+ {
3500
+ value: "outfitter",
3501
+ label: "Outfitters with demo kits"
3502
+ }
3503
+ ],
3504
+ description: "Leave empty for every location."
3505
+ },
3506
+ {
3507
+ name: "locations",
3508
+ title: "Locations",
3509
+ type: "array",
3510
+ group: "locations",
3511
+ itemNoun: "location",
3512
+ hidden: {
3513
+ field: "source",
3514
+ equals: "inline"
3515
+ },
3516
+ validation: {
3517
+ min: 1,
3518
+ max: 12
3519
+ },
3520
+ of: [{
3521
+ name: "location",
3522
+ title: "Location",
3523
+ type: "object",
3524
+ icon: "building-store",
3525
+ preview: {
3526
+ title: "name",
3527
+ subtitle: "address",
3528
+ fallback: "Untitled location"
3529
+ },
3530
+ fields: [
3531
+ {
3532
+ name: "name",
3533
+ title: "Name",
3534
+ type: "string",
3535
+ validation: {
3536
+ required: true,
3537
+ max: 48
3538
+ }
3539
+ },
3540
+ {
3541
+ name: "type",
3542
+ title: "Type",
3543
+ type: "select",
3544
+ options: [
3545
+ {
3546
+ value: "flagship",
3547
+ label: "Caldera store"
3548
+ },
3549
+ {
3550
+ value: "stockist",
3551
+ label: "Stockist"
3552
+ },
3553
+ {
3554
+ value: "outfitter",
3555
+ label: "Outfitter"
3556
+ }
3557
+ ]
3558
+ },
3559
+ {
3560
+ name: "address",
3561
+ title: "Address",
3562
+ type: "text",
3563
+ rows: 2,
3564
+ validation: { required: true },
3565
+ description: "As it should appear, on two lines."
3566
+ },
3567
+ {
3568
+ name: "point",
3569
+ title: "Map pin",
3570
+ type: "geopoint",
3571
+ validation: { required: true },
3572
+ description: "Drop it on the door."
3573
+ },
3574
+ {
3575
+ name: "phone",
3576
+ title: "Phone",
3577
+ type: "link"
3578
+ },
3579
+ {
3580
+ name: "hours",
3581
+ title: "Opening hours",
3582
+ type: "array",
3583
+ itemNoun: "hours row",
3584
+ openMode: "inline",
3585
+ validation: { max: 7 },
3586
+ description: "Days not listed show as closed.",
3587
+ of: [{
3588
+ name: "hoursRow",
3589
+ title: "Hours",
3590
+ type: "object",
3591
+ icon: "clock",
3592
+ preview: {
3593
+ title: "opens",
3594
+ subtitle: "closes",
3595
+ fallback: "Untitled hours"
3596
+ },
3597
+ fields: [
3598
+ {
3599
+ name: "days",
3600
+ title: "Days",
3601
+ type: "multiselect",
3602
+ validation: { min: 1 },
3603
+ options: [
3604
+ "mon",
3605
+ "tue",
3606
+ "wed",
3607
+ "thu",
3608
+ "fri",
3609
+ "sat",
3610
+ "sun"
3611
+ ].map((d) => ({
3612
+ value: d,
3613
+ label: d[0].toUpperCase() + d.slice(1)
3614
+ }))
3615
+ },
3616
+ {
3617
+ name: "opens",
3618
+ title: "Opens",
3619
+ type: "string",
3620
+ validation: {
3621
+ required: true,
3622
+ pattern: "^([01]\\d|2[0-3]):[0-5]\\d$",
3623
+ patternMessage: "Use 24-hour time, like 09:00."
3624
+ },
3625
+ placeholder: "09:00"
3626
+ },
3627
+ {
3628
+ name: "closes",
3629
+ title: "Closes",
3630
+ type: "string",
3631
+ validation: {
3632
+ required: true,
3633
+ pattern: "^([01]\\d|2[0-3]):[0-5]\\d$",
3634
+ patternMessage: "Use 24-hour time, like 18:00."
3635
+ },
3636
+ placeholder: "18:00"
3637
+ }
3638
+ ]
3639
+ }]
3640
+ },
3641
+ {
3642
+ name: "exceptions",
3643
+ title: "Holiday hours",
3644
+ type: "array",
3645
+ itemNoun: "date",
3646
+ openMode: "inline",
3647
+ validation: { max: 12 },
3648
+ of: [{
3649
+ name: "exception",
3650
+ title: "Date",
3651
+ type: "object",
3652
+ icon: "calendar-off",
3653
+ preview: {
3654
+ title: "label",
3655
+ subtitle: "date",
3656
+ fallback: "Untitled date"
3657
+ },
3658
+ fields: [
3659
+ {
3660
+ name: "date",
3661
+ title: "Date",
3662
+ type: "date",
3663
+ validation: { required: true }
3664
+ },
3665
+ {
3666
+ name: "label",
3667
+ title: "Label",
3668
+ type: "string",
3669
+ validation: { max: 32 },
3670
+ placeholder: "Thanksgiving"
3671
+ },
3672
+ {
3673
+ name: "isClosed",
3674
+ title: "Closed all day",
3675
+ type: "boolean",
3676
+ initialValue: true
3677
+ }
3678
+ ]
3679
+ }]
3680
+ },
3681
+ {
3682
+ name: "services",
3683
+ title: "Services",
3684
+ type: "tags"
3685
+ }
3686
+ ]
3687
+ }]
3688
+ },
3689
+ {
3690
+ name: "provider",
3691
+ title: "Map",
3692
+ type: "select",
3693
+ group: "map",
3694
+ initialValue: "mapbox",
3695
+ options: [{
3696
+ value: "mapbox",
3697
+ label: "Mapbox"
3698
+ }, {
3699
+ value: "google",
3700
+ label: "Google Maps"
3701
+ }]
3702
+ },
3703
+ {
3704
+ name: "center",
3705
+ title: "Start at",
3706
+ type: "geopoint",
3707
+ group: "map",
3708
+ description: "Where the map opens before a search."
3709
+ },
3710
+ {
3711
+ name: "zoom",
3712
+ title: "Start zoom",
3713
+ type: "number",
3714
+ group: "map",
3715
+ layout: "slider",
3716
+ initialValue: 6,
3717
+ numeric: {
3718
+ min: 2,
3719
+ max: 14,
3720
+ step: 1
3721
+ }
3722
+ },
3723
+ {
3724
+ name: "showSearch",
3725
+ title: "Show search by city or ZIP",
3726
+ type: "boolean",
3727
+ group: "map",
3728
+ initialValue: true
3729
+ },
3730
+ {
3731
+ name: "radius",
3732
+ title: "Search radius",
3733
+ type: "select",
3734
+ group: "map",
3735
+ hidden: {
3736
+ field: "showSearch",
3737
+ equals: true
3738
+ },
3739
+ initialValue: "50",
3740
+ options: [
3741
+ {
3742
+ value: "25",
3743
+ label: "25 miles"
3744
+ },
3745
+ {
3746
+ value: "50",
3747
+ label: "50 miles"
3748
+ },
3749
+ {
3750
+ value: "100",
3751
+ label: "100 miles"
3752
+ }
3753
+ ]
3754
+ },
3755
+ {
3756
+ name: "useRichResults",
3757
+ title: "Local business rich results",
3758
+ type: "boolean",
3759
+ group: "map",
3760
+ initialValue: true,
3761
+ description: "Shows address and hours in search results."
3762
+ }
3763
+ ]
3764
+ },
3765
+ value: {
3766
+ _variant: "map-list",
3767
+ heading: "Find Caldera near you",
3768
+ intro: "Two Caldera stores in Oregon, and 140 outfitters across the US who stock the range.",
3769
+ source: "inline",
3770
+ types: ["flagship", "stockist"],
3771
+ locations: [{
3772
+ _key: "loc1",
3773
+ name: "Caldera Bend",
3774
+ type: "flagship",
3775
+ address: "1020 NW Wall St\nBend, OR 97703",
3776
+ point: {
3777
+ label: "Caldera Bend",
3778
+ address: "1020 NW Wall St, Bend, OR 97703",
3779
+ lat: 44.0596,
3780
+ lng: -121.3133
3781
+ },
3782
+ phone: {
3783
+ type: "phone",
3784
+ href: "tel:+15415550182",
3785
+ phone: "+1 541 555 0182",
3786
+ label: "(541) 555-0182"
3787
+ },
3788
+ hours: [
3789
+ {
3790
+ _key: "hr1",
3791
+ days: [
3792
+ "mon",
3793
+ "tue",
3794
+ "wed",
3795
+ "thu",
3796
+ "fri"
3797
+ ],
3798
+ opens: "10:00",
3799
+ closes: "18:00"
3800
+ },
3801
+ {
3802
+ _key: "hr2",
3803
+ days: ["sat"],
3804
+ opens: "09:00",
3805
+ closes: "18:00"
3806
+ },
3807
+ {
3808
+ _key: "hr3",
3809
+ days: ["sun"],
3810
+ opens: "11:00",
3811
+ closes: "16:00"
3812
+ }
3813
+ ],
3814
+ exceptions: [{
3815
+ _key: "ex1",
3816
+ date: "2026-11-26",
3817
+ label: "Thanksgiving",
3818
+ isClosed: true
3819
+ }, {
3820
+ _key: "ex2",
3821
+ date: "2026-12-25",
3822
+ label: "Christmas Day",
3823
+ isClosed: true
3824
+ }],
3825
+ services: [
3826
+ "Re-seasoning",
3827
+ "Engraving",
3828
+ "Warranty swaps"
3829
+ ]
3830
+ }, {
3831
+ _key: "loc2",
3832
+ name: "Caldera Portland",
3833
+ type: "flagship",
3834
+ address: "2311 SE Division St\nPortland, OR 97202",
3835
+ point: {
3836
+ label: "Caldera Portland",
3837
+ address: "2311 SE Division St, Portland, OR 97202",
3838
+ lat: 45.5047,
3839
+ lng: -122.6406
3840
+ },
3841
+ phone: {
3842
+ type: "phone",
3843
+ href: "tel:+15035550147",
3844
+ phone: "+1 503 555 0147",
3845
+ label: "(503) 555-0147"
3846
+ },
3847
+ hours: [{
3848
+ _key: "hr4",
3849
+ days: [
3850
+ "tue",
3851
+ "wed",
3852
+ "thu",
3853
+ "fri",
3854
+ "sat",
3855
+ "sun"
3856
+ ],
3857
+ opens: "10:00",
3858
+ closes: "19:00"
3859
+ }],
3860
+ exceptions: [],
3861
+ services: ["Re-seasoning"]
3862
+ }],
3863
+ provider: "mapbox",
3864
+ center: {
3865
+ label: "Oregon",
3866
+ lat: 44.5,
3867
+ lng: -121.5
3868
+ },
3869
+ zoom: 6,
3870
+ showSearch: true,
3871
+ radius: "50",
3872
+ useRichResults: true
3873
+ },
3874
+ notes: {
3875
+ why: "Locations are real-world entities that deserve their own pages, so the default source is a Locations collection; the inline list exists for small brands and is what the sample shows so the full shape is visible. Hours are modelled the way people say them (grouped days + opens/closes in 24-hour strings, pattern-validated) plus dated exceptions, which is exactly what LocalBusiness `openingHoursSpecification` needs — so rich results come from content, not a second copy. Map provider/center/zoom are settings; the API key is never a content field.",
3876
+ sanity: "A `location` document (`name`, `address`, `geopoint` type, `openingHours: array<object{days: array<string>, opens: string, closes: string}>`, `specialHours[]`) and a `storeLocator` object with `locations: array<reference to location>` or a GROQ `*[_type=='location' && type in $types]`. Sanity's `geopoint` is the same primitive.",
3877
+ htmlSignals: [
3878
+ "an embedded map (Mapbox GL canvas, maps.googleapis.com iframe/script) next to a scrolling list of addresses",
3879
+ "a 'Find a store' / 'Stockists' heading with a ZIP/city search input and 'Use my location'",
3880
+ "address blocks with phone links (tel:) and opening hours tables (Mon–Fri 10:00–18:00)",
3881
+ "JSON-LD LocalBusiness/Store, or data-lat/data-lng on list items",
3882
+ "store-locator app markers (Stockist, Storemapper, Storepoint)"
3883
+ ],
3884
+ mcp: "If >4 locations or each has its own URL, create Locations entries and set source:'collection'; else inline. Parse each address block; geocode only if lat/lng are absent from data attributes/JSON-LD (flag geocoded pins for review). Normalise hours to grouped days + HH:MM (12h '6pm' → '18:00'). Holiday notices → exceptions with ISO dates. Map vendor from the script URL → provider; never import API keys.",
3885
+ pitfalls: [
3886
+ "Time is a string with a pattern because there is no time-of-day primitive (gap).",
3887
+ "Overnight hours (closes 01:00) need care: closes < opens means past midnight.",
3888
+ "The Locations collection does not exist in the demo seed, so the collection path can't be previewed here."
3889
+ ]
3890
+ }
3891
+ },
3892
+ {
3893
+ id: "commerce-product-comparison",
3894
+ label: "Product comparison (2–4)",
3895
+ proves: "2–4 product references as columns · a `table` keyed by product id (reorder-safe) · row groups · Yes / no columns with per-row and per-cell types",
3896
+ category: "Commerce",
3897
+ brand: "Caldera",
3898
+ previewLayout: "table",
3899
+ tags: [
3900
+ "references",
3901
+ "commerce",
3902
+ "deep-nesting",
3903
+ "repeater",
3904
+ "validation",
3905
+ "conditional",
3906
+ "a11y",
3907
+ "responsive-layout"
3908
+ ],
3909
+ schema: {
3910
+ name: "productComparison",
3911
+ title: "Product comparison",
3912
+ kind: "block",
3913
+ icon: "columns-3",
3914
+ category: "Commerce",
3915
+ description: "Two to four products side by side, spec by spec",
3916
+ preview: {
3917
+ title: "heading",
3918
+ subtitle: "intro"
3919
+ },
3920
+ groups: [{
3921
+ name: "content",
3922
+ title: "Content",
3923
+ default: true
3924
+ }, {
3925
+ name: "design",
3926
+ title: "Design"
3927
+ }],
3928
+ fields: [
3929
+ {
3930
+ name: "heading",
3931
+ title: "Heading",
3932
+ type: "string",
3933
+ validation: {
3934
+ required: true,
3935
+ recommendedMax: 48
3936
+ },
3937
+ ai: true
3938
+ },
3939
+ {
3940
+ name: "intro",
3941
+ title: "Intro",
3942
+ type: "text",
3943
+ rows: 2,
3944
+ validation: { recommendedMax: 160 }
3945
+ },
3946
+ {
3947
+ name: "products",
3948
+ title: "Products",
3949
+ type: "array",
3950
+ validation: {
3951
+ min: 2,
3952
+ max: 4,
3953
+ unique: true
3954
+ },
3955
+ of: [productRef("comparedProduct", "Product")]
3956
+ },
3957
+ {
3958
+ name: "highlightLabel",
3959
+ title: "Highlight label",
3960
+ type: "string",
3961
+ validation: { max: 20 },
3962
+ placeholder: "Our pick"
3963
+ },
3964
+ {
3965
+ name: "specs",
3966
+ title: "Specs",
3967
+ type: "table",
3968
+ validation: { required: true },
3969
+ table: {
3970
+ maxColumns: 5,
3971
+ maxRows: 40,
3972
+ columnTypes: [
3973
+ "text",
3974
+ "boolean",
3975
+ "number",
3976
+ "money",
3977
+ "rating"
3978
+ ],
3979
+ jsonLd: ["ItemList"],
3980
+ settings: {
3981
+ totals: false,
3982
+ sticky: false
3983
+ }
3984
+ }
3985
+ },
3986
+ {
3987
+ name: "hideSame",
3988
+ title: "Offer Hide identical rows",
3989
+ type: "boolean",
3990
+ group: "design",
3991
+ initialValue: true
3992
+ },
3993
+ {
3994
+ name: "isHeaderSticky",
3995
+ title: "Keep the header row in view",
3996
+ type: "boolean",
3997
+ group: "design",
3998
+ initialValue: true
3999
+ }
4000
+ ]
4001
+ },
4002
+ value: {
4003
+ heading: "Which pan is right for you?",
4004
+ intro: "Three ways to cook over fire. Here's how they compare where it matters.",
4005
+ products: [
4006
+ P.skillet,
4007
+ P.dutch,
4008
+ P.grill
4009
+ ],
4010
+ highlightLabel: "Our pick",
4011
+ specs: tableFrom("Ridge Skillet, Ember Dutch Oven and Cable Fold Grill compared for cooking and carrying", [
4012
+ {
4013
+ _key: "spec",
4014
+ label: "Spec"
4015
+ },
4016
+ {
4017
+ _key: P.skillet,
4018
+ label: "Ridge Skillet 10\"",
4019
+ type: "boolean"
4020
+ },
4021
+ {
4022
+ _key: P.dutch,
4023
+ label: "Ember Dutch Oven",
4024
+ type: "boolean",
4025
+ highlight: true
4026
+ },
4027
+ {
4028
+ _key: P.grill,
4029
+ label: "Cable Fold Grill",
4030
+ type: "boolean"
4031
+ }
4032
+ ], [
4033
+ {
4034
+ cells: [
4035
+ "Best for",
4036
+ "Searing, eggs",
4037
+ "Bread, braises",
4038
+ "Grilling over flame"
4039
+ ],
4040
+ group: "cooking",
4041
+ type: "text"
4042
+ },
4043
+ {
4044
+ cells: [
4045
+ "Induction",
4046
+ true,
4047
+ true,
4048
+ false
4049
+ ],
4050
+ group: "cooking"
4051
+ },
4052
+ {
4053
+ cells: [
4054
+ "Open fire",
4055
+ true,
4056
+ true,
4057
+ true
4058
+ ],
4059
+ group: "cooking"
4060
+ },
4061
+ {
4062
+ cells: [
4063
+ "Oven-safe lid",
4064
+ false,
4065
+ true,
4066
+ {
4067
+ value: "No lid — use foil",
4068
+ type: "text"
4069
+ }
4070
+ ],
4071
+ group: "cooking"
4072
+ },
4073
+ {
4074
+ cells: [
4075
+ {
4076
+ value: "Weight",
4077
+ type: "text"
4078
+ },
4079
+ 5.1,
4080
+ 13.2,
4081
+ 2.4
4082
+ ],
4083
+ group: "carry",
4084
+ type: "number"
4085
+ },
4086
+ {
4087
+ cells: [
4088
+ "Packs flat",
4089
+ false,
4090
+ false,
4091
+ true
4092
+ ],
4093
+ group: "carry"
4094
+ }
4095
+ ], {
4096
+ id: "tbl_pan_compare",
4097
+ headerColumn: true,
4098
+ rowGroups: [{
4099
+ _key: "cooking",
4100
+ label: "Cooking"
4101
+ }, {
4102
+ _key: "carry",
4103
+ label: "Carry"
4104
+ }],
4105
+ notes: "Weights in pounds, without lids. Open fire = directly on coals or over a fire ring.",
4106
+ design: {
4107
+ responsive: "scroll",
4108
+ sticky: true,
4109
+ hideCaption: true
4110
+ },
4111
+ jsonLd: {
4112
+ type: "ItemList",
4113
+ axis: "columns"
4114
+ }
4115
+ }),
4116
+ hideSame: true,
4117
+ isHeaderSticky: true
4118
+ },
4119
+ notes: {
4120
+ why: "Columns are product REFERENCES (name, photo, price and Add button come from the entry); the specs are one `table` (CPO D42) whose product columns are KEYED BY THE PRODUCT ID, so reordering `products` re-orders the columns with their cells. Rows are editorial and grouped (Cooking, Carry → tbody + th scope=rowgroup). Product columns are Yes / no, so the renderer writes ✓ with the words 'Included' / 'Not included' (never a bare icon); a row can be Text or Number ('Best for', 'Weight') and one cell can differ ('No lid — use foil'). The highlighted column is a column flag, not an index.",
4121
+ sanity: "`productComparison` with `products: array<reference to product>` (Rule.min(2).max(4)) and either nested groups › rows › cells (aligned by index) or the v6.6 Portable Text table (rows › cells › blocks, `headerRows`) — neither keys a cell to its product.",
4122
+ htmlSignals: [
4123
+ "a <table> whose header row has 2–4 product images/names with prices and buttons",
4124
+ "rows of ✓/✕ icons or 'Yes/No' with an occasional text value",
4125
+ "a 'Compare' heading, 'Which one is right for you?' copy, or a 'Hide identical' toggle",
4126
+ "a highlighted column with a ribbon like 'Best seller' / 'Our pick'"
4127
+ ],
4128
+ mcp: "Header cells → resolve each product link to a Products entry; emit `products` in column order and give each spec column `_key` = that entry id. Body via the table detector (docs/TABLES.md): <tbody> per group or a full-width heading row → `rowGroups`; first cell → row header; ✓/✕ icons, aria-labels and sr-only text → Yes / no; a row of words or numbers → that row's `type`; a partial icon with a note → a Text cell. The ribbon column → `highlight: true` on that column, its words → highlightLabel.",
4129
+ pitfalls: [
4130
+ "RESOLVED (D42): cells are keyed by product id, not position; the highlight is a column flag.",
4131
+ "Keeping `products` and the spec columns in step (add a product → add its column) is the editor's job today; a column source ('one column per item of products') is the follow-up (gap 3).",
4132
+ "Icon-only cells need text alternatives; Yes / no cells render ✓ plus the words, which is what search and AI read."
4133
+ ]
4134
+ }
4135
+ }
4136
+ ];
4137
+ //#endregion
4138
+ export { ENTRIES };
4139
+
4140
+ //# sourceMappingURL=commerce.entries.js.map