pagiera 0.2.0-alpha.33

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 (192) hide show
  1. package/README.md +528 -0
  2. package/dist/adapters.d.ts +89 -0
  3. package/dist/adapters.d.ts.map +1 -0
  4. package/dist/adapters.js +2 -0
  5. package/dist/adapters.js.map +1 -0
  6. package/dist/client.d.ts +22 -0
  7. package/dist/client.d.ts.map +1 -0
  8. package/dist/client.js +33 -0
  9. package/dist/client.js.map +1 -0
  10. package/dist/data.js +210 -0
  11. package/dist/data.js.map +7 -0
  12. package/dist/document.d.ts +63 -0
  13. package/dist/document.d.ts.map +1 -0
  14. package/dist/document.js +25 -0
  15. package/dist/document.js.map +1 -0
  16. package/dist/editor.d.ts +14 -0
  17. package/dist/editor.d.ts.map +1 -0
  18. package/dist/editor.js +59 -0
  19. package/dist/editor.js.map +1 -0
  20. package/dist/full-editor.d.ts +53 -0
  21. package/dist/full-editor.js +11117 -0
  22. package/dist/full-editor.js.map +7 -0
  23. package/dist/full.css +3498 -0
  24. package/dist/index.d.ts +8 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.js +7 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/internal/app/api/ai-design/route.d.ts +3 -0
  29. package/dist/internal/app/api/ai-design/route.d.ts.map +1 -0
  30. package/dist/internal/app/api/ai-design/route.js +165 -0
  31. package/dist/internal/app/api/ai-design/route.js.map +1 -0
  32. package/dist/internal/app/p/editor/ai-panel.d.ts +27 -0
  33. package/dist/internal/app/p/editor/ai-panel.d.ts.map +1 -0
  34. package/dist/internal/app/p/editor/ai-panel.js +77 -0
  35. package/dist/internal/app/p/editor/ai-panel.js.map +1 -0
  36. package/dist/internal/app/p/editor/data-modal.d.ts +26 -0
  37. package/dist/internal/app/p/editor/data-modal.d.ts.map +1 -0
  38. package/dist/internal/app/p/editor/data-modal.js +94 -0
  39. package/dist/internal/app/p/editor/data-modal.js.map +1 -0
  40. package/dist/internal/app/p/editor/data-panel.d.ts +18 -0
  41. package/dist/internal/app/p/editor/data-panel.d.ts.map +1 -0
  42. package/dist/internal/app/p/editor/data-panel.js +45 -0
  43. package/dist/internal/app/p/editor/data-panel.js.map +1 -0
  44. package/dist/internal/app/p/editor/editor.d.ts +51 -0
  45. package/dist/internal/app/p/editor/editor.d.ts.map +1 -0
  46. package/dist/internal/app/p/editor/editor.js +1551 -0
  47. package/dist/internal/app/p/editor/editor.js.map +1 -0
  48. package/dist/internal/app/p/editor/fields.d.ts +111 -0
  49. package/dist/internal/app/p/editor/fields.d.ts.map +1 -0
  50. package/dist/internal/app/p/editor/fields.js +141 -0
  51. package/dist/internal/app/p/editor/fields.js.map +1 -0
  52. package/dist/internal/app/p/editor/inspector.d.ts +38 -0
  53. package/dist/internal/app/p/editor/inspector.d.ts.map +1 -0
  54. package/dist/internal/app/p/editor/inspector.js +336 -0
  55. package/dist/internal/app/p/editor/inspector.js.map +1 -0
  56. package/dist/internal/app/p/editor/library.d.ts +18 -0
  57. package/dist/internal/app/p/editor/library.d.ts.map +1 -0
  58. package/dist/internal/app/p/editor/library.js +61 -0
  59. package/dist/internal/app/p/editor/library.js.map +1 -0
  60. package/dist/internal/app/p/editor/parts.d.ts +75 -0
  61. package/dist/internal/app/p/editor/parts.d.ts.map +1 -0
  62. package/dist/internal/app/p/editor/parts.js +261 -0
  63. package/dist/internal/app/p/editor/parts.js.map +1 -0
  64. package/dist/internal/app/p/editor/templates-panel.d.ts +6 -0
  65. package/dist/internal/app/p/editor/templates-panel.d.ts.map +1 -0
  66. package/dist/internal/app/p/editor/templates-panel.js +12 -0
  67. package/dist/internal/app/p/editor/templates-panel.js.map +1 -0
  68. package/dist/internal/app/p/editor/use-canvas-view.d.ts +24 -0
  69. package/dist/internal/app/p/editor/use-canvas-view.d.ts.map +1 -0
  70. package/dist/internal/app/p/editor/use-canvas-view.js +242 -0
  71. package/dist/internal/app/p/editor/use-canvas-view.js.map +1 -0
  72. package/dist/internal/app/p/editor/use-editor.d.ts +48 -0
  73. package/dist/internal/app/p/editor/use-editor.d.ts.map +1 -0
  74. package/dist/internal/app/p/editor/use-editor.js +224 -0
  75. package/dist/internal/app/p/editor/use-editor.js.map +1 -0
  76. package/dist/internal/app/p/editor/variables-panel.d.ts +8 -0
  77. package/dist/internal/app/p/editor/variables-panel.d.ts.map +1 -0
  78. package/dist/internal/app/p/editor/variables-panel.js +52 -0
  79. package/dist/internal/app/p/editor/variables-panel.js.map +1 -0
  80. package/dist/internal/components/ui/select.d.ts +12 -0
  81. package/dist/internal/components/ui/select.d.ts.map +1 -0
  82. package/dist/internal/components/ui/select.js +32 -0
  83. package/dist/internal/components/ui/select.js.map +1 -0
  84. package/dist/internal/drizzle/index.d.ts +6 -0
  85. package/dist/internal/drizzle/index.d.ts.map +1 -0
  86. package/dist/internal/drizzle/index.js +20 -0
  87. package/dist/internal/drizzle/index.js.map +1 -0
  88. package/dist/internal/drizzle/schema.d.ts +405 -0
  89. package/dist/internal/drizzle/schema.d.ts.map +1 -0
  90. package/dist/internal/drizzle/schema.js +70 -0
  91. package/dist/internal/drizzle/schema.js.map +1 -0
  92. package/dist/internal/lib/data/context.d.ts +8 -0
  93. package/dist/internal/lib/data/context.d.ts.map +1 -0
  94. package/dist/internal/lib/data/context.js +17 -0
  95. package/dist/internal/lib/data/context.js.map +1 -0
  96. package/dist/internal/lib/data/source.d.ts +54 -0
  97. package/dist/internal/lib/data/source.d.ts.map +1 -0
  98. package/dist/internal/lib/data/source.js +229 -0
  99. package/dist/internal/lib/data/source.js.map +1 -0
  100. package/dist/internal/lib/editor/ai-types.d.ts +44 -0
  101. package/dist/internal/lib/editor/ai-types.d.ts.map +1 -0
  102. package/dist/internal/lib/editor/ai-types.js +2 -0
  103. package/dist/internal/lib/editor/ai-types.js.map +1 -0
  104. package/dist/internal/lib/editor/arrange.d.ts +12 -0
  105. package/dist/internal/lib/editor/arrange.d.ts.map +1 -0
  106. package/dist/internal/lib/editor/arrange.js +72 -0
  107. package/dist/internal/lib/editor/arrange.js.map +1 -0
  108. package/dist/internal/lib/editor/cascade.d.ts +39 -0
  109. package/dist/internal/lib/editor/cascade.d.ts.map +1 -0
  110. package/dist/internal/lib/editor/cascade.js +86 -0
  111. package/dist/internal/lib/editor/cascade.js.map +1 -0
  112. package/dist/internal/lib/editor/icon.d.ts +6 -0
  113. package/dist/internal/lib/editor/icon.d.ts.map +1 -0
  114. package/dist/internal/lib/editor/icon.js +13 -0
  115. package/dist/internal/lib/editor/icon.js.map +1 -0
  116. package/dist/internal/lib/editor/presets.d.ts +10 -0
  117. package/dist/internal/lib/editor/presets.d.ts.map +1 -0
  118. package/dist/internal/lib/editor/presets.js +83 -0
  119. package/dist/internal/lib/editor/presets.js.map +1 -0
  120. package/dist/internal/lib/editor/showcase.d.ts +14 -0
  121. package/dist/internal/lib/editor/showcase.d.ts.map +1 -0
  122. package/dist/internal/lib/editor/showcase.js +219 -0
  123. package/dist/internal/lib/editor/showcase.js.map +1 -0
  124. package/dist/internal/lib/editor/site-templates.d.ts +4 -0
  125. package/dist/internal/lib/editor/site-templates.d.ts.map +1 -0
  126. package/dist/internal/lib/editor/site-templates.js +156 -0
  127. package/dist/internal/lib/editor/site-templates.js.map +1 -0
  128. package/dist/internal/lib/editor/snap.d.ts +34 -0
  129. package/dist/internal/lib/editor/snap.d.ts.map +1 -0
  130. package/dist/internal/lib/editor/snap.js +70 -0
  131. package/dist/internal/lib/editor/snap.js.map +1 -0
  132. package/dist/internal/lib/editor/style.d.ts +53 -0
  133. package/dist/internal/lib/editor/style.d.ts.map +1 -0
  134. package/dist/internal/lib/editor/style.js +313 -0
  135. package/dist/internal/lib/editor/style.js.map +1 -0
  136. package/dist/internal/lib/editor/tree.d.ts +72 -0
  137. package/dist/internal/lib/editor/tree.d.ts.map +1 -0
  138. package/dist/internal/lib/editor/tree.js +301 -0
  139. package/dist/internal/lib/editor/tree.js.map +1 -0
  140. package/dist/internal/lib/editor/types.d.ts +273 -0
  141. package/dist/internal/lib/editor/types.d.ts.map +1 -0
  142. package/dist/internal/lib/editor/types.js +411 -0
  143. package/dist/internal/lib/editor/types.js.map +1 -0
  144. package/dist/internal/lib/editor/validate.d.ts +20 -0
  145. package/dist/internal/lib/editor/validate.d.ts.map +1 -0
  146. package/dist/internal/lib/editor/validate.js +467 -0
  147. package/dist/internal/lib/editor/validate.js.map +1 -0
  148. package/dist/internal/lib/pages.d.ts +83 -0
  149. package/dist/internal/lib/pages.d.ts.map +1 -0
  150. package/dist/internal/lib/pages.js +294 -0
  151. package/dist/internal/lib/pages.js.map +1 -0
  152. package/dist/internal/lib/render/bind.d.ts +18 -0
  153. package/dist/internal/lib/render/bind.d.ts.map +1 -0
  154. package/dist/internal/lib/render/bind.js +28 -0
  155. package/dist/internal/lib/render/bind.js.map +1 -0
  156. package/dist/internal/lib/render/css.d.ts +31 -0
  157. package/dist/internal/lib/render/css.d.ts.map +1 -0
  158. package/dist/internal/lib/render/css.js +239 -0
  159. package/dist/internal/lib/render/css.js.map +1 -0
  160. package/dist/internal/lib/render/load-data.d.ts +15 -0
  161. package/dist/internal/lib/render/load-data.d.ts.map +1 -0
  162. package/dist/internal/lib/render/load-data.js +39 -0
  163. package/dist/internal/lib/render/load-data.js.map +1 -0
  164. package/dist/internal/lib/render/page-render.d.ts +17 -0
  165. package/dist/internal/lib/render/page-render.d.ts.map +1 -0
  166. package/dist/internal/lib/render/page-render.js +117 -0
  167. package/dist/internal/lib/render/page-render.js.map +1 -0
  168. package/dist/internal/lib/utils.d.ts +3 -0
  169. package/dist/internal/lib/utils.d.ts.map +1 -0
  170. package/dist/internal/lib/utils.js +6 -0
  171. package/dist/internal/lib/utils.js.map +1 -0
  172. package/dist/manrope-data.d.ts +3 -0
  173. package/dist/manrope-data.d.ts.map +1 -0
  174. package/dist/manrope-data.js +3 -0
  175. package/dist/manrope-data.js.map +1 -0
  176. package/dist/page.d.ts +9 -0
  177. package/dist/page.d.ts.map +1 -0
  178. package/dist/page.js +148 -0
  179. package/dist/page.js.map +1 -0
  180. package/dist/provider.d.ts +14 -0
  181. package/dist/provider.d.ts.map +1 -0
  182. package/dist/provider.js +45 -0
  183. package/dist/provider.js.map +1 -0
  184. package/dist/runtime.d.ts +8 -0
  185. package/dist/runtime.js +717 -0
  186. package/dist/runtime.js.map +7 -0
  187. package/dist/server.d.ts +31 -0
  188. package/dist/server.js +2267 -0
  189. package/dist/server.js.map +7 -0
  190. package/dist/styles.css +300 -0
  191. package/package.json +94 -0
  192. package/thumbnail.png +0 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/internal/lib/render/page-render.tsx", "../src/internal/lib/editor/types.ts", "../src/internal/lib/editor/cascade.ts", "../src/internal/lib/editor/style.ts", "../src/internal/lib/editor/tree.ts", "../src/internal/lib/data/source.ts", "../src/internal/lib/render/bind.ts", "../src/internal/lib/render/css.ts", "../src/internal/lib/editor/icon.tsx"],
4
+ "sourcesContent": ["import React from \"react\";\nimport { baseOf, cascadeOf } from \"@/lib/editor/cascade\";\nimport { isBand, resolveStyle } from \"@/lib/editor/style\";\nimport { childrenOf, indexById, noteIds } from \"@/lib/editor/tree\";\nimport type { CanvasElement, RootStyle } from \"@/lib/editor/types\";\nimport { bindElement, type PageData, type Row, rowsFor } from \"./bind\";\nimport { classFor, ENTRANCE_SCRIPT, hasEntrances, stylesheetFor } from \"./css\";\nimport { IconGlyph } from \"@/lib/editor/icon\";\n\n/**\n * Read-only rendering of a document, shared by the preview route and the\n * published site so what the author previews is what visitors get.\n */\nexport function RenderedPage({\n elements: all,\n rootStyle,\n data = {},\n}: {\n elements: CanvasElement[];\n rootStyle: RootStyle;\n /** Rows already fetched for each data source; see `loadPageData`. */\n data?: PageData;\n}) {\n // Anything parked beside the canvas is a note the author kept for\n // themselves; it never reaches the page.\n const frameWidth = rootStyle.fullWidth ? Number.POSITIVE_INFINITY : rootStyle.maxWidth;\n const cascade = cascadeOf(rootStyle.breakpoints, rootStyle.baseBreakpointId);\n const baseId = baseOf(cascade).id;\n const notes = noteIds(all, indexById(all), baseId, frameWidth, rootStyle.layout, cascade);\n const elements = all.filter((element) => !notes.has(element.id) && element.componentRole !== \"master\");\n\n return (\n <>\n <style\n // biome-ignore lint/security/noDangerouslySetInnerHtml: the sheet is generated from validated style values, never raw user markup\n dangerouslySetInnerHTML={{ __html: stylesheetFor(elements, rootStyle) }}\n />\n <main className=\"pg-root\">\n {childrenOf(elements, undefined).map((el) => (\n <RenderedNode\n key={el.id}\n element={el}\n elements={elements}\n rootStyle={rootStyle}\n data={data}\n />\n ))}\n </main>\n {hasEntrances(elements) && (\n <script\n // biome-ignore lint/security/noDangerouslySetInnerHtml: a fixed script with no interpolated content\n dangerouslySetInnerHTML={{ __html: ENTRANCE_SCRIPT }}\n />\n )}\n {elements.some((element) => element.draggable) && <script dangerouslySetInnerHTML={{ __html: DRAG_SCRIPT }} />}\n {elements.some((element) => element.interaction && [\"toggle-layer\", \"show-layer\", \"hide-layer\"].includes(element.interaction.action)) && <script dangerouslySetInnerHTML={{ __html: ACTION_SCRIPT }} />}\n </>\n );\n}\n\nfunction RenderedNode({\n element: raw,\n elements,\n rootStyle,\n data,\n row,\n}: {\n element: CanvasElement;\n elements: CanvasElement[];\n rootStyle: RootStyle;\n data: PageData;\n /** The data row this subtree is being rendered for, inside a Repeat. */\n row?: Row;\n}): React.ReactNode {\n // Derived here rather than threaded down: the node already has the page\n // style, and a second prop is one more thing that can fall out of step.\n const cascade = cascadeOf(rootStyle.breakpoints, rootStyle.baseBreakpointId);\n const baseId = baseOf(cascade).id;\n\n // Outside a Repeat, a bound element reads the first row/object from its\n // own source. Repeat descendants still receive the current list row.\n const directRow = row ?? (raw.sourceId ? data[raw.sourceId]?.[0] : undefined);\n const element = bindElement(raw, directRow);\n const children = childrenOf(elements, element.id);\n const style = resolveStyle(element, baseId, cascade);\n const band = isBand(element.type, style, rootStyle);\n const className =\n `pg-node ${classFor(element.id)}` +\n (band ? \" pg-band\" : \"\") +\n (style.entrance === \"none\" ? \"\" : \" pg-anim\");\n\n // Repeat iterates a list. Request renders once and provides the source's\n // first object as the binding context for its entire subtree.\n const renderChildren = () =>\n element.type === \"Repeat\"\n ? rowsFor(element, data, true).flatMap((dataRow, index) =>\n children.map((child) => (\n <RenderedNode\n key={`${index}:${child.id}`}\n element={child}\n elements={elements}\n rootStyle={rootStyle}\n data={data}\n row={dataRow}\n />\n )),\n )\n : children.map((child) => (\n <RenderedNode\n key={child.id}\n element={child}\n elements={elements}\n rootStyle={rootStyle}\n data={data}\n row={element.type === \"Request\" ? data[element.sourceId ?? \"\"]?.[0] : row}\n />\n ));\n\n const content = (\n <>\n <ElementContent element={element} />\n {renderChildren()}\n </>\n );\n\n // A band's background reaches both edges while its content stays centred\n // inside the page's content width.\n const body = band ? <div className=\"pg-inner\">{content}</div> : content;\n const interactionHref =\n element.interaction?.action === \"scroll-to\"\n ? `#${classFor(element.interaction.value)}`\n : element.interaction?.action === \"navigate\" ? element.interaction.value : undefined;\n const href = interactionHref || element.href;\n const target = element.interaction?.target || element.target;\n\n // A link wraps rather than replaces the box, so styling stays on one node.\n if (href) {\n return (\n <a\n id={classFor(element.id)}\n data-pg-drag={element.draggable ? \"true\" : undefined}\n data-pg-action={element.interaction?.action}\n data-pg-target={element.interaction && ![\"navigate\", \"scroll-to\"].includes(element.interaction.action) ? classFor(element.interaction.value) : undefined}\n className={`${className} pg-link`}\n href={href}\n target={target}\n rel={target === \"_blank\" ? \"noopener noreferrer\" : undefined}\n >\n {body}\n </a>\n );\n }\n\n const tag = semanticTag(element);\n return React.createElement(tag, {\n id: classFor(element.id),\n type: tag === \"button\" ? \"button\" : undefined,\n \"data-pg-drag\": element.draggable ? \"true\" : undefined,\n \"data-pg-action\": element.interaction?.action,\n \"data-pg-target\": element.interaction && ![\"navigate\", \"scroll-to\"].includes(element.interaction.action) ? classFor(element.interaction.value) : undefined,\n \"data-pg-display\": element.type === \"Grid\" ? \"grid\" : isContainerType(element.type) || element.type === \"Section\" ? \"flex\" : \"block\",\n className,\n }, body);\n}\n\nfunction semanticTag(element: CanvasElement) {\n const name = (element.name ?? \"\").toLowerCase();\n if (element.type === \"Button\") return \"button\";\n if (element.type === \"Heading\") {\n const level = name.match(/(?:^|\\s)h([1-6])(?:\\s|$)/)?.[1] ?? \"2\";\n return `h${level}` as const;\n }\n if (element.type === \"Text\") return \"p\";\n if (name.includes(\"navbar\") || name === \"nav\" || name.includes(\"navigation\")) return \"nav\";\n if (name.includes(\"footer\")) return \"footer\";\n if (name.includes(\"header\")) return \"header\";\n if (element.type === \"Section\") return \"section\";\n return \"div\";\n}\n\nconst DRAG_SCRIPT = `(function(){document.querySelectorAll('[data-pg-drag]').forEach(function(n){n.style.touchAction='none';n.style.cursor='grab';n.addEventListener('pointerdown',function(e){if(e.button!==0)return;e.preventDefault();var sx=e.clientX,sy=e.clientY,ox=Number(n.dataset.dx||0),oy=Number(n.dataset.dy||0);n.setPointerCapture(e.pointerId);n.style.cursor='grabbing';var move=function(p){var x=ox+p.clientX-sx,y=oy+p.clientY-sy;n.dataset.dx=String(x);n.dataset.dy=String(y);n.style.translate=x+'px '+y+'px'};var up=function(){n.style.cursor='grab';n.removeEventListener('pointermove',move);n.removeEventListener('pointerup',up)};n.addEventListener('pointermove',move);n.addEventListener('pointerup',up)})})})();`;\n\nconst ACTION_SCRIPT = `(function(){document.querySelectorAll('[data-pg-target]').forEach(function(trigger){trigger.style.cursor='pointer';trigger.addEventListener('click',function(event){var target=document.getElementById(trigger.dataset.pgTarget);if(!target)return;event.preventDefault();var action=trigger.dataset.pgAction;var open=target.dataset.pgOpen==='true';var next=action==='show-layer'?true:action==='hide-layer'?false:!open;target.dataset.pgOpen=next?'true':'false';target.style.setProperty('display',next?(target.dataset.pgDisplay||'flex'):'none','important');trigger.setAttribute('aria-expanded',String(next));target.setAttribute('aria-hidden',String(!next))})})})();`;\n\nfunction isContainerType(type: CanvasElement[\"type\"]) {\n return [\"Frame\", \"Stack\", \"Container\", \"Request\", \"Repeat\"].includes(type);\n}\n\nexport function ElementContent({ element }: { element: CanvasElement }) {\n if (element.code) return <iframe title={element.name ?? \"Code component\"} srcDoc={element.code} sandbox=\"\" style={{ width: \"100%\", height: \"100%\", border: 0, borderRadius: \"inherit\" }} />;\n if (element.type === \"Image\") {\n if (!element.src) return null;\n return (\n // biome-ignore lint/performance/noImgElement: the src is author-supplied at runtime and cannot be statically optimised\n <img\n src={element.src}\n alt={element.alt ?? \"\"}\n style={{ objectFit: element.objectFit ?? \"cover\", borderRadius: \"inherit\" }}\n />\n );\n }\n\n if (element.type === \"Video\") {\n if (!element.src) return null;\n return (\n <iframe\n src={element.src}\n title={element.name ?? \"Video\"}\n allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; picture-in-picture\"\n allowFullScreen\n style={{ width: \"100%\", height: \"100%\", border: 0, borderRadius: \"inherit\" }}\n />\n );\n }\n\n if (element.type === \"Icon\") return <IconGlyph element={element} />;\n\n if (!element.content) return null;\n return (\n <span style={{ display: \"block\", width: \"100%\", whiteSpace: \"pre-wrap\" }}>\n {element.content}\n </span>\n );\n}\n", "export const ELEMENT_TYPES = [\n \"Frame\",\n \"Stack\",\n \"Section\",\n \"Container\",\n \"Grid\",\n \"Heading\",\n \"Text\",\n \"Image\",\n \"Button\",\n \"Video\",\n \"Icon\",\n \"Request\",\n \"Repeat\",\n] as const;\n\nexport type ElementType = (typeof ELEMENT_TYPES)[number];\n\n/** One key/value pair on a request; values may carry `{{\u2026}}` tokens. */\nexport type RequestPair = { key: string; value: string };\n\nexport const HTTP_METHODS = [\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\"] as const;\nexport type HttpMethod = (typeof HTTP_METHODS)[number];\n\nexport const DATA_SOURCE_NOT_FOUND_BEHAVIORS = [\"empty\", \"page-404\"] as const;\nexport type DataSourceNotFoundBehavior = (typeof DATA_SOURCE_NOT_FOUND_BEHAVIORS)[number];\n\n/** Methods that carry a request body. */\nexport function sendsBody(method: HttpMethod) {\n return method === \"POST\" || method === \"PUT\" || method === \"PATCH\";\n}\n\n/**\n * A JSON endpoint the page pulls content from. Sources live on the page so a\n * Repeat block can name one without carrying the URL on every element.\n */\nexport type DataSource = {\n id: string;\n name: string;\n url: string;\n /** Dotted path to the array inside the payload; \"\" when it is the root. */\n path: string;\n method?: HttpMethod;\n /** JSON body for POST/PUT/PATCH; tokens are resolved before sending. */\n body?: string;\n /** Appended to the URL as a query string. */\n params?: RequestPair[];\n /** Sent as request headers \u2014 for API keys and the like. */\n headers?: RequestPair[];\n /** Controls whether an upstream 404 empties this source or rejects the whole page. */\n onNotFound?: DataSourceNotFoundBehavior;\n};\n\n/**\n * Values a request token can read. Everything else resolves to an empty\n * string, so a typo cannot leak an unrelated value into the URL.\n */\nexport type RequestContext = {\n /** The visitor's query string, e.g. `{{query.id}}` on /post?id=5. */\n query: Record<string, string>;\n /** Values captured by a dynamic page path, e.g. `{{params.slug}}`. */\n params: Record<string, string>;\n /** The page being rendered, e.g. `{{page.slug}}`. */\n page: { slug: string };\n};\n\nexport type ResizeHandle = \"nw\" | \"ne\" | \"sw\" | \"se\" | \"n\" | \"s\" | \"w\" | \"e\";\n\n/* ------------------------------------------------------------- breakpoints */\n\nexport const BREAKPOINTS = [\"desktop\", \"tablet\", \"mobile\"] as const;\nexport type Breakpoint = string;\n\nexport type BreakpointDefinition = {\n id: string;\n name: string;\n width: number;\n};\n\nexport const DEFAULT_BREAKPOINTS: BreakpointDefinition[] = [\n { id: \"desktop\", name: \"Desktop\", width: 1280 },\n { id: \"tablet\", name: \"Tablet\", width: 768 },\n { id: \"mobile\", name: \"Mobile\", width: 375 },\n];\n\nexport const BREAKPOINT_WIDTHS: Record<string, number> = {\n desktop: 1280,\n tablet: 768,\n mobile: 375,\n};\n\n/**\n * Styles cascade from the widest breakpoint down, so a value set on desktop\n * holds everywhere until a narrower breakpoint overrides it.\n */\nexport const BREAKPOINT_CHAIN: Record<string, Breakpoint[]> = {\n desktop: [\"desktop\"],\n tablet: [\"desktop\", \"tablet\"],\n mobile: [\"desktop\", \"tablet\", \"mobile\"],\n};\n\n/* ------------------------------------------------------------------ styles */\n\n/** `fill` stretches to the parent, `auto` shrinks to the content. */\nexport type SizeMode = \"fixed\" | \"fill\" | \"auto\";\nexport type Constraint = \"start\" | \"center\" | \"end\" | \"stretch\";\n/** `absolute` positions children by x/y; `stack` lays them out with flexbox. */\nexport type LayoutMode = \"absolute\" | \"stack\";\nexport type Direction = \"row\" | \"column\";\nexport type Justify = \"start\" | \"center\" | \"end\" | \"between\";\nexport type Align = \"start\" | \"center\" | \"end\" | \"stretch\";\nexport type TextAlign = \"left\" | \"center\" | \"right\" | \"justify\";\nexport type TextTransform = \"none\" | \"uppercase\" | \"lowercase\" | \"capitalize\";\nexport type ObjectFit = \"cover\" | \"contain\" | \"fill\" | \"none\";\nexport type Overflow = \"visible\" | \"hidden\" | \"auto\" | \"scroll\";\nexport type Entrance = \"none\" | \"fade\" | \"up\" | \"down\" | \"left\" | \"right\" | \"zoom\";\nexport type MotionCurve = \"ease\" | \"spring\";\nexport type CursorStyle = \"auto\" | \"default\" | \"pointer\" | \"text\" | \"grab\" | \"zoom-in\" | \"none\";\nexport type PositionMode = \"static\" | \"sticky\";\nexport type BgSize = \"cover\" | \"contain\" | \"auto\";\nexport type BlendMode =\n | \"normal\"\n | \"multiply\"\n | \"screen\"\n | \"overlay\"\n | \"darken\"\n | \"lighten\"\n | \"difference\"\n | \"luminosity\";\nexport type BorderStyle = \"solid\" | \"dashed\" | \"dotted\";\n\nexport type ElementStyle = {\n // Box \u2014 x/y only apply inside an `absolute` parent.\n x: number;\n y: number;\n constraintX: Constraint;\n constraintY: Constraint;\n w: number;\n h: number;\n widthMode: SizeMode;\n heightMode: SizeMode;\n\n // How this element arranges its own children.\n layout: LayoutMode;\n direction: Direction;\n gap: number;\n padT: number;\n padR: number;\n padB: number;\n padL: number;\n justify: Justify;\n align: Align;\n wrap: boolean;\n /** Grid columns; only read when `layout` is `stack` on a Grid element. */\n columns: number;\n\n // Appearance\n bg: string;\n /** A full CSS gradient value, or \"\" for none. Painted over `bg`. */\n gradient: string;\n color: string;\n radius: number;\n opacity: number;\n borderW: number;\n borderC: string;\n borderStyle: BorderStyle;\n /** A full CSS box-shadow value, or \"\" for none. */\n shadow: string;\n rotate: number;\n\n // Typography\n fontFamily: string;\n fontSize: number;\n fontWeight: string;\n lineHeight: number;\n letterSpacing: number;\n textAlign: TextAlign;\n textTransform: TextTransform;\n\n // Composition \u2014 the pieces that make a layout feel designed rather than\n // stacked: clipping, sticky rails, imagery, glass and blend effects.\n overflow: Overflow;\n position: PositionMode;\n /** Distance from the top edge while stuck; only read when position is sticky. */\n stickyOffset: number;\n /** Background image URL, or \"\" for none. Painted over `gradient`. */\n bgImage: string;\n bgSize: BgSize;\n bgPosition: string;\n /** Blurs the element's own content, in px. */\n blur: number;\n /** Blurs whatever sits behind the element, in px \u2014 the glass effect. */\n backdropBlur: number;\n blendMode: BlendMode;\n /** Percent; 100 leaves the element alone. */\n scale: number;\n /** A CSS ratio such as \"16/9\", or \"\" to leave height to the layout. */\n aspectRatio: string;\n\n /** Entrance effect, played once when the element scrolls into view. */\n entrance: Entrance;\n /** Milliseconds. */\n entranceDuration: number;\n entranceDelay: number;\n entranceCurve: MotionCurve;\n entranceBezier: string;\n springStiffness: number;\n springDamping: number;\n cursor: CursorStyle;\n\n /** Hidden at this breakpoint. */\n hidden: boolean;\n};\n\nexport const STYLE_KEYS = [\n \"x\",\n \"y\",\n \"constraintX\",\n \"constraintY\",\n \"w\",\n \"h\",\n \"widthMode\",\n \"heightMode\",\n \"layout\",\n \"direction\",\n \"gap\",\n \"padT\",\n \"padR\",\n \"padB\",\n \"padL\",\n \"justify\",\n \"align\",\n \"wrap\",\n \"columns\",\n \"bg\",\n \"gradient\",\n \"color\",\n \"radius\",\n \"opacity\",\n \"borderW\",\n \"borderC\",\n \"borderStyle\",\n \"shadow\",\n \"rotate\",\n \"fontFamily\",\n \"fontSize\",\n \"fontWeight\",\n \"lineHeight\",\n \"letterSpacing\",\n \"textAlign\",\n \"textTransform\",\n \"overflow\",\n \"position\",\n \"stickyOffset\",\n \"bgImage\",\n \"bgSize\",\n \"bgPosition\",\n \"blur\",\n \"backdropBlur\",\n \"blendMode\",\n \"scale\",\n \"aspectRatio\",\n \"entrance\",\n \"entranceDuration\",\n \"entranceDelay\",\n \"entranceCurve\",\n \"entranceBezier\",\n \"springStiffness\",\n \"springDamping\",\n \"cursor\",\n \"hidden\",\n] as const satisfies ReadonlyArray<keyof ElementStyle>;\n\nexport type StyleKey = (typeof STYLE_KEYS)[number];\n\nexport type CanvasElement = {\n id: string;\n type: ElementType;\n name?: string;\n parentId?: string;\n z: number;\n locked?: boolean;\n /** Page-local reusable component metadata. */\n componentRole?: \"master\" | \"instance\";\n componentId?: string;\n componentSourceId?: string;\n variant?: string;\n styleBindings?: Partial<Record<StyleKey, string>>;\n\n // Content is shared across breakpoints.\n content?: string;\n /** Sandboxed HTML/CSS used by code components. */\n code?: string;\n src?: string;\n alt?: string;\n objectFit?: ObjectFit;\n iconName?: \"star\" | \"heart\" | \"arrow-right\" | \"check\" | \"menu\" | \"search\";\n href?: string;\n target?: \"_self\" | \"_blank\";\n interaction?: {\n trigger: \"click\";\n action: \"navigate\" | \"scroll-to\" | \"toggle-layer\" | \"show-layer\" | \"hide-layer\";\n value: string;\n target?: \"_self\" | \"_blank\";\n };\n\n /** Data source read by a Request/Repeat block or a directly-bound element. */\n sourceId?: string;\n /**\n * Inside Request/Repeat, pulls this field off the current object instead\n * of using the element's content. Dotted paths work: \"author.name\".\n */\n binding?: string;\n\n /** Desktop values; every breakpoint falls back to these. */\n base: ElementStyle;\n /** Narrower-breakpoint deltas, applied over `base` in cascade order. */\n overrides?: Record<string, Partial<ElementStyle>>;\n /** Applied on pointer hover, on top of the resolved breakpoint style. */\n hover?: Partial<ElementStyle>;\n /** Applied while the pointer is pressed. */\n press?: Partial<ElementStyle>;\n loop?: { type: \"pulse\" | \"float\" | \"spin\"; duration: number };\n draggable?: boolean;\n};\n\n/* ---------------------------------------------------------------- defaults */\n\nexport const BASE_STYLE: ElementStyle = {\n x: 0,\n y: 0,\n constraintX: \"start\",\n constraintY: \"start\",\n w: 200,\n h: 100,\n widthMode: \"fixed\",\n heightMode: \"fixed\",\n\n layout: \"absolute\",\n direction: \"column\",\n gap: 0,\n padT: 0,\n padR: 0,\n padB: 0,\n padL: 0,\n justify: \"start\",\n align: \"start\",\n wrap: false,\n columns: 3,\n\n bg: \"transparent\",\n gradient: \"\",\n color: \"#27272a\",\n radius: 0,\n opacity: 100,\n borderW: 0,\n borderC: \"transparent\",\n borderStyle: \"solid\",\n shadow: \"\",\n rotate: 0,\n\n fontFamily: \"inherit\",\n fontSize: 16,\n fontWeight: \"normal\",\n lineHeight: 1.5,\n letterSpacing: 0,\n textAlign: \"left\",\n textTransform: \"none\",\n\n overflow: \"visible\",\n position: \"static\",\n stickyOffset: 0,\n bgImage: \"\",\n bgSize: \"cover\",\n bgPosition: \"center\",\n blur: 0,\n backdropBlur: 0,\n blendMode: \"normal\",\n scale: 100,\n aspectRatio: \"\",\n\n entrance: \"none\",\n entranceDuration: 600,\n entranceDelay: 0,\n entranceCurve: \"ease\",\n entranceBezier: \"0.44, 0, 0.56, 1\",\n springStiffness: 300,\n springDamping: 30,\n cursor: \"auto\",\n\n hidden: false,\n};\n\nexport function makeStyle(overrides: Partial<ElementStyle>): ElementStyle {\n return { ...BASE_STYLE, ...overrides };\n}\n\n/** Every field a freshly dropped element starts with. */\nexport const ELEMENT_DEFAULTS: Record<\n ElementType,\n { style: Partial<ElementStyle>; props?: Partial<CanvasElement> }\n> = {\n Frame: {\n style: {\n w: 640,\n h: 420,\n widthMode: \"fixed\",\n heightMode: \"fixed\",\n layout: \"absolute\",\n direction: \"column\",\n overflow: \"hidden\",\n bg: \"#ffffff\",\n borderW: 1,\n borderC: \"#e4e4e7\",\n radius: 12,\n },\n },\n Stack: {\n style: {\n w: 600,\n h: 160,\n widthMode: \"fill\",\n heightMode: \"auto\",\n layout: \"stack\",\n direction: \"column\",\n gap: 16,\n padT: 0,\n padR: 0,\n padB: 0,\n padL: 0,\n justify: \"start\",\n align: \"stretch\",\n bg: \"transparent\",\n },\n },\n Section: {\n style: {\n w: 1280,\n h: 480,\n widthMode: \"fill\",\n heightMode: \"auto\",\n layout: \"stack\",\n direction: \"column\",\n gap: 24,\n padT: 64,\n padR: 48,\n padB: 64,\n padL: 48,\n justify: \"start\",\n align: \"stretch\",\n bg: \"#ffffff\",\n },\n },\n Container: {\n style: {\n w: 600,\n h: 240,\n widthMode: \"fill\",\n heightMode: \"auto\",\n layout: \"stack\",\n direction: \"column\",\n gap: 16,\n padT: 24,\n padR: 24,\n padB: 24,\n padL: 24,\n align: \"stretch\",\n bg: \"transparent\",\n borderW: 1,\n borderC: \"#e4e4e7\",\n radius: 12,\n },\n },\n Grid: {\n style: {\n w: 900,\n h: 300,\n widthMode: \"fill\",\n heightMode: \"auto\",\n layout: \"stack\",\n direction: \"row\",\n columns: 3,\n gap: 24,\n align: \"stretch\",\n },\n },\n Heading: {\n style: {\n w: 480,\n h: 48,\n widthMode: \"fill\",\n heightMode: \"auto\",\n fontSize: 40,\n fontWeight: \"bold\",\n lineHeight: 1.2,\n letterSpacing: -0.5,\n color: \"#18181b\",\n },\n props: { content: \"Heading\" },\n },\n Text: {\n style: {\n w: 480,\n h: 24,\n widthMode: \"fill\",\n heightMode: \"auto\",\n fontSize: 16,\n lineHeight: 1.6,\n color: \"#52525b\",\n },\n props: { content: \"Text block content\" },\n },\n Image: {\n style: { w: 400, h: 260, widthMode: \"fill\", bg: \"#e4e4e7\", radius: 12 },\n props: { src: \"\", alt: \"\", objectFit: \"cover\" },\n },\n Button: {\n style: {\n w: 140,\n h: 44,\n widthMode: \"auto\",\n heightMode: \"auto\",\n layout: \"stack\",\n direction: \"row\",\n justify: \"center\",\n align: \"center\",\n padT: 12,\n padR: 22,\n padB: 12,\n padL: 22,\n bg: \"#2563eb\",\n color: \"#ffffff\",\n radius: 8,\n fontSize: 15,\n fontWeight: \"500\",\n textAlign: \"center\",\n },\n props: { content: \"Button\", href: \"\", target: \"_self\" },\n },\n Video: {\n style: { w: 640, h: 360, widthMode: \"fill\", bg: \"#18181b\", radius: 12 },\n props: { src: \"\" },\n },\n Icon: {\n style: { w: 24, h: 24, widthMode: \"fixed\", heightMode: \"fixed\", color: \"#5402E6\" },\n props: { iconName: \"star\" },\n },\n Request: {\n style: {\n w: 900,\n h: 200,\n widthMode: \"fill\",\n heightMode: \"auto\",\n layout: \"stack\",\n direction: \"column\",\n gap: 16,\n align: \"stretch\",\n },\n props: { sourceId: \"\" },\n },\n Repeat: {\n style: {\n w: 900,\n h: 200,\n widthMode: \"fill\",\n heightMode: \"auto\",\n layout: \"stack\",\n direction: \"row\",\n gap: 24,\n align: \"stretch\",\n wrap: true,\n },\n props: { sourceId: \"\" },\n },\n};\n\n/* ----------------------------------------------------------------- helpers */\n\nconst CONTAINER_TYPES: ReadonlySet<ElementType> = new Set([\n \"Frame\",\n \"Stack\",\n \"Section\",\n \"Container\",\n \"Grid\",\n \"Button\",\n \"Request\",\n \"Repeat\",\n]);\n\n/** Whether an element can hold children. */\nexport function isContainer(type: ElementType) {\n return CONTAINER_TYPES.has(type);\n}\n\nconst TEXTUAL_TYPES: ReadonlySet<ElementType> = new Set([\n \"Heading\",\n \"Text\",\n \"Button\",\n]);\n\n/** Whether an element renders editable text. */\nexport function isTextual(type: ElementType) {\n return TEXTUAL_TYPES.has(type);\n}\n\n/** Page-level settings; the canvas behaves as the root container. */\nexport type RootStyle = {\n documentMode: \"page\" | \"component\";\n /** Content is centred inside this width unless `fullWidth` is set. */\n maxWidth: number;\n /** Editable design-surface height; the public page can still grow with content. */\n canvasHeight: number;\n /** Let content run edge to edge instead of being capped at `maxWidth`. */\n fullWidth: boolean;\n bg: string;\n layout: LayoutMode;\n direction: Direction;\n gap: number;\n padT: number;\n padR: number;\n padB: number;\n padL: number;\n align: Align;\n fontFamily: string;\n /** User-defined viewport previews, ordered from widest to narrowest. */\n breakpoints?: BreakpointDefinition[];\n /** The breakpoint whose values live in every element's `base` style. */\n baseBreakpointId?: string;\n variables?: DesignVariable[];\n customFonts?: CustomFont[];\n};\n\nexport type CustomFont = { id: string; name: string; url: string; weight: number; style: \"normal\" | \"italic\" };\n\nexport type DesignVariable = {\n id: string;\n name: string;\n type: \"color\" | \"number\";\n value: string | number;\n};\n\nexport const DEFAULT_ROOT_STYLE: RootStyle = {\n documentMode: \"page\",\n maxWidth: 1280,\n canvasHeight: 800,\n fullWidth: false,\n bg: \"#ffffff\",\n layout: \"stack\",\n direction: \"column\",\n gap: 0,\n padT: 0,\n padR: 0,\n padB: 0,\n padL: 0,\n align: \"stretch\",\n fontFamily: \"inherit\",\n};\n\nexport const FONT_STACKS: Array<{ label: string; value: string }> = [\n { label: \"Inherit\", value: \"inherit\" },\n { label: \"Sans\", value: \"ui-sans-serif, system-ui, sans-serif\" },\n { label: \"Serif\", value: \"ui-serif, Georgia, serif\" },\n { label: \"Mono\", value: \"ui-monospace, SFMono-Regular, monospace\" },\n];\n\nexport const SHADOW_PRESETS: Array<{ label: string; value: string }> = [\n { label: \"None\", value: \"\" },\n { label: \"Small\", value: \"0 1px 2px rgba(0,0,0,0.06)\" },\n { label: \"Medium\", value: \"0 4px 12px rgba(0,0,0,0.08)\" },\n { label: \"Large\", value: \"0 12px 32px rgba(0,0,0,0.12)\" },\n { label: \"Glow\", value: \"0 0 0 4px rgba(37,99,235,0.15)\" },\n];\n\nexport const ASPECT_RATIOS: Array<{ label: string; value: string }> = [\n { label: \"Free\", value: \"\" },\n { label: \"Square 1:1\", value: \"1/1\" },\n { label: \"Photo 4:3\", value: \"4/3\" },\n { label: \"Wide 16:9\", value: \"16/9\" },\n { label: \"Ultra 21:9\", value: \"21/9\" },\n { label: \"Portrait 3:4\", value: \"3/4\" },\n];\n\nexport const ENTRANCES: Array<{ label: string; value: Entrance }> = [\n { label: \"None\", value: \"none\" },\n { label: \"Fade in\", value: \"fade\" },\n { label: \"Rise up\", value: \"up\" },\n { label: \"Drop down\", value: \"down\" },\n { label: \"Slide from left\", value: \"left\" },\n { label: \"Slide from right\", value: \"right\" },\n { label: \"Zoom in\", value: \"zoom\" },\n];\n\nexport const DRAG_MIME = \"application/pagiera-element-type\";\n/** Set when dragging an element that already exists on the canvas. */\nexport const MOVE_MIME = \"application/pagiera-element-id\";\n", "import {\n type BreakpointDefinition,\n DEFAULT_BREAKPOINTS,\n} from \"./types\";\n\n/**\n * The breakpoint layout of a page: which artboards exist and which one holds\n * the shared values.\n *\n * Styles live in two places \u2014 `element.base` carries the base breakpoint's\n * values, and `element.overrides[id]` carries the deltas for every other one.\n * Which overrides apply to a given artboard depends entirely on where it sits\n * relative to the base, so that ordering is computed here rather than being\n * hard-coded to desktop \u2192 tablet \u2192 mobile.\n */\nexport type Cascade = {\n breakpoints: BreakpointDefinition[];\n /** Whose values live in `element.base`. */\n baseId: string;\n};\n\nexport const DEFAULT_CASCADE: Cascade = {\n breakpoints: DEFAULT_BREAKPOINTS,\n baseId: \"desktop\",\n};\n\n/** Builds a cascade from whatever the page has stored, filling the gaps. */\nexport function cascadeOf(\n breakpoints: BreakpointDefinition[] | undefined,\n baseId: string | undefined,\n): Cascade {\n const list = breakpoints?.length ? breakpoints : DEFAULT_BREAKPOINTS;\n const base = list.find((item) => item.id === baseId) ?? widest(list);\n return { breakpoints: list, baseId: base.id };\n}\n\nfunction widest(list: BreakpointDefinition[]) {\n return list.reduce((a, b) => (b.width > a.width ? b : a), list[0]);\n}\n\nexport function baseOf(cascade: Cascade): BreakpointDefinition {\n return (\n cascade.breakpoints.find((item) => item.id === cascade.baseId) ??\n widest(cascade.breakpoints)\n );\n}\n\n/**\n * The overrides to apply, in order, to reach `targetId` from the base.\n *\n * Narrower artboards inherit downward from the base and wider ones inherit\n * upward, so a value set on the base reaches both directions until something\n * closer to the target overrides it.\n */\nexport function overrideChain(cascade: Cascade, targetId: string): string[] {\n const base = baseOf(cascade);\n if (targetId === base.id) return [];\n\n const target = cascade.breakpoints.find((item) => item.id === targetId);\n if (!target) return [];\n\n const goingNarrower = target.width < base.width;\n\n const between = cascade.breakpoints.filter((item) => {\n if (item.id === base.id) return false;\n return goingNarrower\n ? item.width < base.width && item.width >= target.width\n : item.width > base.width && item.width <= target.width;\n });\n\n // Apply from the artboard nearest the base outward, so the one closest to\n // the target has the last word.\n between.sort((a, b) => (goingNarrower ? b.width - a.width : a.width - b.width));\n return between.map((item) => item.id);\n}\n\n/**\n * Media queries for the published page, in the order they must appear so that\n * later rules win. Narrower artboards become `max-width`, wider ones\n * `min-width`; the base itself needs no query.\n */\nexport function mediaPlan(\n cascade: Cascade,\n): Array<{ id: string; query: string }> {\n const base = baseOf(cascade);\n\n // An artboard governs from its own width up to just below the next wider\n // one \u2014 the same reading the canvas gives, where a 1000px window falls to\n // the nearest artboard at or below it.\n const narrower = cascade.breakpoints\n .filter((item) => item.id !== base.id && item.width < base.width)\n .sort((a, b) => b.width - a.width)\n .map((item) => {\n const ceiling = cascade.breakpoints\n .filter((other) => other.width > item.width)\n .reduce(\n (lowest, other) => Math.min(lowest, other.width),\n Number.POSITIVE_INFINITY,\n );\n return { id: item.id, query: `(max-width: ${ceiling - 1}px)` };\n });\n\n const wider = cascade.breakpoints\n .filter((item) => item.id !== base.id && item.width > base.width)\n .sort((a, b) => a.width - b.width)\n .map((item) => ({ id: item.id, query: `(min-width: ${item.width}px)` }));\n\n return [...narrower, ...wider];\n}\n\n/** Memoised chains \u2014 resolution runs per element, the chain does not. */\nconst chainCache = new WeakMap<Cascade, Map<string, string[]>>();\n\nexport function chainFor(cascade: Cascade, targetId: string): string[] {\n let byTarget = chainCache.get(cascade);\n if (!byTarget) {\n byTarget = new Map();\n chainCache.set(cascade, byTarget);\n }\n const cached = byTarget.get(targetId);\n if (cached) return cached;\n\n const chain = overrideChain(cascade, targetId);\n byTarget.set(targetId, chain);\n return chain;\n}\n", "import type { CSSProperties } from \"react\";\nimport { baseOf, type Cascade, chainFor, DEFAULT_CASCADE } from \"./cascade\";\nimport {\n type Align,\n type Breakpoint,\n type CanvasElement,\n type ElementStyle,\n type Justify,\n type RootStyle,\n type StyleKey,\n} from \"./types\";\n\n/**\n * Flattens the cascade for one breakpoint: the base values, then each override\n * between the base and this artboard, nearest-to-base first.\n */\nexport function resolveStyle(\n element: CanvasElement,\n breakpoint: Breakpoint,\n cascade: Cascade = DEFAULT_CASCADE,\n): ElementStyle {\n let style = element.base;\n for (const step of chainFor(cascade, breakpoint)) {\n const override = element.overrides?.[step];\n if (override) style = { ...style, ...override };\n }\n return style;\n}\n\n/**\n * Which breakpoint actually supplies the value shown for `key` \u2014 used to mark\n * a field in the inspector as inherited rather than set here.\n */\nexport function originOf(\n element: CanvasElement,\n breakpoint: Breakpoint,\n key: StyleKey,\n cascade: Cascade = DEFAULT_CASCADE,\n): Breakpoint {\n let origin: Breakpoint = baseOf(cascade).id;\n for (const step of chainFor(cascade, breakpoint)) {\n if (element.overrides?.[step]?.[key] !== undefined) origin = step;\n }\n return origin;\n}\n\nexport function hasOverride(\n element: CanvasElement,\n breakpoint: Breakpoint,\n key: StyleKey,\n cascade: Cascade = DEFAULT_CASCADE,\n) {\n return (\n breakpoint !== baseOf(cascade).id &&\n element.overrides?.[breakpoint]?.[key] !== undefined\n );\n}\n\n/**\n * Writes style changes to the right layer: an edit on the base artboard\n * changes the shared values, anywhere else accumulates an override so the\n * other artboards keep theirs.\n */\nexport function applyStyle(\n element: CanvasElement,\n breakpoint: Breakpoint,\n patch: Partial<ElementStyle>,\n cascade: Cascade = DEFAULT_CASCADE,\n): CanvasElement {\n const baseId = baseOf(cascade).id;\n if (breakpoint === baseId) {\n const next = { ...element, base: { ...element.base, ...patch } };\n // The base owns `base`, so an override under its own id is dead weight\n // \u2014 usually left behind when the base moved. Clear it as we pass.\n if (next.overrides?.[baseId]) {\n const overrides = { ...next.overrides };\n delete overrides[baseId];\n next.overrides =\n Object.keys(overrides).length > 0 ? overrides : undefined;\n }\n return next;\n }\n return {\n ...element,\n overrides: {\n ...element.overrides,\n [breakpoint]: { ...element.overrides?.[breakpoint], ...patch },\n },\n };\n}\n\n/** Keeps a base-breakpoint edit local by snapshotting the affected values on\n * every other artboard before changing the shared desktop base. */\nexport function applyStyleIsolated(\n element: CanvasElement,\n breakpoint: Breakpoint,\n patch: Partial<ElementStyle>,\n breakpoints: readonly Breakpoint[],\n cascade: Cascade = DEFAULT_CASCADE,\n): CanvasElement {\n const keys = Object.keys(patch) as StyleKey[];\n const baseId = baseOf(cascade).id;\n const snapshots = new Map<Breakpoint, ElementStyle>();\n for (const target of breakpoints) {\n if (target !== breakpoint && target !== baseId) {\n snapshots.set(target, resolveStyle(element, target, cascade));\n }\n }\n const updated = applyStyle(element, breakpoint, patch, cascade);\n const overrides = { ...updated.overrides };\n for (const [target, effective] of snapshots) {\n const layer = { ...overrides[target] };\n for (const key of keys) layer[key] = effective[key] as never;\n overrides[target] = layer;\n }\n return { ...updated, overrides };\n}\n\n/** Drops overrides for `keys` at this breakpoint so they inherit again. */\nexport function clearOverrides(\n element: CanvasElement,\n breakpoint: Breakpoint,\n keys: StyleKey[],\n cascade: Cascade = DEFAULT_CASCADE,\n): CanvasElement {\n // The base artboard holds the shared values; there is nothing to clear.\n if (breakpoint === baseOf(cascade).id) return element;\n const current = element.overrides?.[breakpoint];\n if (!current) return element;\n\n const next = { ...current };\n for (const key of keys) delete next[key];\n\n const overrides = { ...element.overrides };\n if (Object.keys(next).length === 0) delete overrides[breakpoint];\n else overrides[breakpoint] = next;\n\n return { ...element, overrides };\n}\n\n/* ------------------------------------------------------------------ to CSS */\n\nconst JUSTIFY_MAP: Record<Justify, CSSProperties[\"justifyContent\"]> = {\n start: \"flex-start\",\n center: \"center\",\n end: \"flex-end\",\n between: \"space-between\",\n};\n\nconst ALIGN_MAP: Record<Align, CSSProperties[\"alignItems\"]> = {\n start: \"flex-start\",\n center: \"center\",\n end: \"flex-end\",\n stretch: \"stretch\",\n};\n\nexport type BoxContext = {\n /** How the parent arranges this element; drives positioning vs flex sizing. */\n parentLayout: \"absolute\" | \"stack\";\n parentDirection: \"row\" | \"column\";\n};\n\n/**\n * Turns a resolved style into inline CSS. Positioning depends on the parent:\n * inside an absolute parent the element is placed at x/y, inside a stack it is\n * a flex item whose sizing comes from widthMode/heightMode.\n */\nexport function styleToCss(\n style: ElementStyle,\n context: BoxContext,\n element?: Pick<CanvasElement, \"type\">,\n): CSSProperties {\n const css: CSSProperties = {\n boxSizing: \"border-box\",\n display: \"flex\",\n flexDirection: style.direction,\n gap: style.gap || undefined,\n justifyContent: JUSTIFY_MAP[style.justify],\n alignItems: ALIGN_MAP[style.align],\n flexWrap: style.wrap ? \"wrap\" : \"nowrap\",\n\n paddingTop: style.padT || undefined,\n paddingRight: style.padR || undefined,\n paddingBottom: style.padB || undefined,\n paddingLeft: style.padL || undefined,\n\n backgroundColor: style.bg || undefined,\n // A gradient and a photo can be layered; the gradient is painted first\n // so it reads as a scrim over the image.\n backgroundImage:\n [style.gradient, style.bgImage && `url(\"${style.bgImage}\")`]\n .filter(Boolean)\n .join(\", \") || undefined,\n backgroundSize: style.bgImage ? style.bgSize : undefined,\n backgroundPosition: style.bgImage ? style.bgPosition : undefined,\n backgroundRepeat: style.bgImage ? \"no-repeat\" : undefined,\n\n overflow: style.overflow === \"visible\" ? undefined : style.overflow,\n aspectRatio: style.aspectRatio || undefined,\n cursor: style.cursor === \"auto\" ? undefined : style.cursor,\n mixBlendMode: style.blendMode === \"normal\" ? undefined : style.blendMode,\n filter: style.blur ? `blur(${style.blur}px)` : undefined,\n backdropFilter: style.backdropBlur\n ? `blur(${style.backdropBlur}px)`\n : undefined,\n color: style.color,\n borderRadius: style.radius || undefined,\n opacity: style.opacity === 100 ? undefined : style.opacity / 100,\n borderWidth: style.borderW || undefined,\n borderColor: style.borderW ? style.borderC : undefined,\n borderStyle: style.borderW ? style.borderStyle : undefined,\n boxShadow: style.shadow || undefined,\n transform:\n [\n style.rotate ? `rotate(${style.rotate}deg)` : \"\",\n style.scale === 100 ? \"\" : `scale(${style.scale / 100})`,\n ]\n .filter(Boolean)\n .join(\" \") || undefined,\n\n fontFamily: style.fontFamily === \"inherit\" ? undefined : style.fontFamily,\n fontSize: style.fontSize,\n fontWeight: style.fontWeight,\n lineHeight: style.lineHeight,\n letterSpacing: style.letterSpacing || undefined,\n textAlign: style.textAlign,\n textTransform: style.textTransform,\n };\n\n // A Grid ignores the flex axis and lays its children out in columns.\n if (element?.type === \"Grid\" && style.layout === \"stack\") {\n css.display = \"grid\";\n css.gridTemplateColumns = `repeat(${Math.max(1, style.columns)}, minmax(0, 1fr))`;\n css.alignItems = ALIGN_MAP[style.align];\n }\n\n // An absolute container is still a positioning context for its children.\n if (style.layout === \"absolute\") {\n css.display = \"block\";\n }\n\n if (context.parentLayout === \"absolute\") {\n css.position = \"absolute\";\n css.left = style.x;\n css.top = style.y;\n css.width = style.widthMode === \"fixed\" ? style.w : \"max-content\";\n css.height = style.heightMode === \"fixed\" ? style.h : \"max-content\";\n if (style.widthMode === \"fill\") css.width = \"100%\";\n if (style.heightMode === \"fill\") css.height = \"100%\";\n\n const transforms: string[] = [];\n if (style.constraintX === \"center\") {\n css.left = `calc(50% + ${style.x}px)`;\n transforms.push(\"translateX(-50%)\");\n } else if (style.constraintX === \"end\") {\n css.left = undefined;\n css.right = style.x;\n } else if (style.constraintX === \"stretch\") {\n css.left = style.x;\n css.right = style.x;\n css.width = \"auto\";\n }\n\n if (style.constraintY === \"center\") {\n css.top = `calc(50% + ${style.y}px)`;\n transforms.push(\"translateY(-50%)\");\n } else if (style.constraintY === \"end\") {\n css.top = undefined;\n css.bottom = style.y;\n } else if (style.constraintY === \"stretch\") {\n css.top = style.y;\n css.bottom = style.y;\n css.height = \"auto\";\n }\n if (css.transform) transforms.push(String(css.transform));\n css.transform = transforms.join(\" \") || undefined;\n return css;\n }\n\n // Sticky only means anything inside a flowing parent; in an absolute one\n // the element is already taken out of the flow above.\n css.position = style.position === \"sticky\" ? \"sticky\" : \"relative\";\n if (style.position === \"sticky\") css.top = style.stickyOffset;\n\n const mainAxisIsWidth = context.parentDirection === \"row\";\n\n applySize(css, \"width\", style.widthMode, style.w, mainAxisIsWidth);\n applySize(css, \"height\", style.heightMode, style.h, !mainAxisIsWidth);\n\n return css;\n}\n\nfunction applySize(\n css: CSSProperties,\n axis: \"width\" | \"height\",\n mode: ElementStyle[\"widthMode\"],\n value: number,\n isMainAxis: boolean,\n) {\n if (mode === \"fixed\") {\n css[axis] = value;\n if (isMainAxis) css.flexShrink = 0;\n // An explicit size already wins over the parent's `stretch`.\n return;\n }\n\n if (mode === \"auto\") {\n css[axis] = \"auto\";\n if (isMainAxis) css.flexGrow = 0;\n // Without this the parent's `align-items: stretch` would blow an\n // auto-sized element out to the full cross axis instead of hugging.\n else css.alignSelf = \"flex-start\";\n return;\n }\n\n // fill\n css[axis] = \"auto\";\n if (isMainAxis) {\n css.flexGrow = 1;\n css.flexBasis = 0;\n } else {\n css.alignSelf = \"stretch\";\n }\n}\n\n/**\n * A Section is a full-bleed band: its background reaches both edges of the\n * viewport while the content inside stays within the page's content width.\n * Capping the Section itself instead would box the background too, which is\n * why the published page used to sit in a container the canvas never showed.\n *\n * Returns the outer shell and the centred inner box; the inner one keeps the\n * flex/grid layout so children lay out exactly as before.\n */\nexport function splitBand(\n css: CSSProperties,\n contentWidth: number,\n): { shell: CSSProperties; inner: CSSProperties } {\n const inner: CSSProperties = {\n boxSizing: \"border-box\",\n display: css.display,\n flexDirection: css.flexDirection,\n gap: css.gap,\n justifyContent: css.justifyContent,\n alignItems: css.alignItems,\n flexWrap: css.flexWrap,\n gridTemplateColumns: css.gridTemplateColumns,\n width: \"100%\",\n maxWidth: contentWidth,\n marginLeft: \"auto\",\n marginRight: \"auto\",\n };\n\n const shell: CSSProperties = { ...css };\n // The inner box owns the layout now.\n shell.flexDirection = undefined;\n shell.gap = undefined;\n shell.justifyContent = undefined;\n shell.alignItems = undefined;\n shell.flexWrap = undefined;\n shell.gridTemplateColumns = undefined;\n shell.display = \"block\";\n\n return { shell, inner };\n}\n\n/** Whether this element should render as a full-bleed band. */\nexport function isBand(\n type: string,\n style: ElementStyle,\n root: Pick<RootStyle, \"fullWidth\">,\n) {\n return type === \"Section\" && style.layout === \"stack\" && !root.fullWidth;\n}\n\nexport function rootStyleToCss(root: RootStyle): CSSProperties {\n return {\n boxSizing: \"border-box\",\n display: root.layout === \"stack\" ? \"flex\" : \"block\",\n flexDirection: root.direction,\n gap: root.gap || undefined,\n alignItems: ALIGN_MAP[root.align],\n paddingTop: root.padT || undefined,\n paddingRight: root.padR || undefined,\n paddingBottom: root.padB || undefined,\n paddingLeft: root.padL || undefined,\n backgroundColor: root.bg,\n fontFamily: root.fontFamily === \"inherit\" ? undefined : root.fontFamily,\n position: \"relative\",\n minHeight: \"100%\",\n width: \"100%\",\n };\n}\n", "import { type Cascade, DEFAULT_CASCADE } from \"./cascade\";\nimport { applyStyle, resolveStyle } from \"./style\";\nimport {\n type Breakpoint,\n type CanvasElement,\n ELEMENT_DEFAULTS,\n type ElementType,\n isContainer,\n type LayoutMode,\n makeStyle,\n} from \"./types\";\n\nexport function indexById(elements: CanvasElement[]) {\n return new Map(elements.map((el) => [el.id, el]));\n}\n\nexport function childrenOf(elements: CanvasElement[], parentId?: string) {\n return elements\n .filter((el) => (el.parentId ?? undefined) === parentId)\n .sort((a, b) => a.z - b.z);\n}\n\n/** The element plus every descendant. */\nexport function subtreeIds(elements: CanvasElement[], rootId: string) {\n const ids = new Set<string>([rootId]);\n let grew = true;\n while (grew) {\n grew = false;\n for (const el of elements) {\n if (el.parentId && ids.has(el.parentId) && !ids.has(el.id)) {\n ids.add(el.id);\n grew = true;\n }\n }\n }\n return ids;\n}\n\nexport function removeSubtree(elements: CanvasElement[], rootId: string) {\n const doomed = subtreeIds(elements, rootId);\n return elements.filter((el) => !doomed.has(el.id));\n}\n\nexport function nextZ(elements: CanvasElement[], parentId?: string) {\n const siblings = childrenOf(elements, parentId);\n return siblings.length ? Math.max(...siblings.map((s) => s.z)) + 1 : 0;\n}\n\nexport function newId() {\n // `crypto.randomUUID` needs a secure context, which localhost and https\n // both provide; the fallback covers anything else.\n if (typeof crypto !== \"undefined\" && \"randomUUID\" in crypto) {\n return crypto.randomUUID();\n }\n return `el-${Date.now().toString(36)}-${Math.floor(Math.random() * 1e9).toString(36)}`;\n}\n\nexport function createElement(\n type: ElementType,\n position: { x: number; y: number; z: number; parentId?: string },\n): CanvasElement {\n const defaults = ELEMENT_DEFAULTS[type];\n return {\n id: newId(),\n type,\n z: position.z,\n parentId: position.parentId,\n ...defaults.props,\n base: makeStyle({ ...defaults.style, x: position.x, y: position.y }),\n };\n}\n\n/**\n * Copies an element and everything under it, giving each node a fresh id while\n * keeping the parent/child wiring intact.\n */\nexport function cloneSubtree(\n elements: CanvasElement[],\n rootId: string,\n offset = { x: 16, y: 16 },\n): { elements: CanvasElement[]; rootId: string } | null {\n const byId = indexById(elements);\n const root = byId.get(rootId);\n if (!root) return null;\n\n const ids = subtreeIds(elements, rootId);\n const idMap = new Map<string, string>();\n for (const id of ids) idMap.set(id, newId());\n const remap = (id: string) => idMap.get(id) ?? id;\n\n const copies = elements\n .filter((el) => ids.has(el.id))\n .map<CanvasElement>((el) => ({\n ...el,\n id: remap(el.id),\n parentId: el.parentId ? idMap.get(el.parentId) : undefined,\n // Only the root shifts; descendants stay positioned relative to it.\n base:\n el.id === rootId\n ? { ...el.base, x: el.base.x + offset.x, y: el.base.y + offset.y }\n : el.base,\n }));\n\n const newRootId = remap(rootId);\n const topZ = nextZ(elements, root.parentId);\n return {\n elements: copies.map((el) =>\n el.id === newRootId ? { ...el, z: topZ } : el,\n ),\n rootId: newRootId,\n };\n}\n\n/** Absolute position on the canvas, accumulating every ancestor's offset. */\nexport function absolutePosition(\n byId: Map<string, CanvasElement>,\n element: CanvasElement,\n breakpoint: Breakpoint,\n cascade: Cascade = DEFAULT_CASCADE,\n) {\n const own = resolveStyle(element, breakpoint, cascade);\n let x = own.x;\n let y = own.y;\n let parent = element.parentId ? byId.get(element.parentId) : undefined;\n const guard = new Set<string>([element.id]);\n\n while (parent && !guard.has(parent.id)) {\n guard.add(parent.id);\n const style = resolveStyle(parent, breakpoint, cascade);\n x += style.x;\n y += style.y;\n parent = parent.parentId ? byId.get(parent.parentId) : undefined;\n }\n return { x, y };\n}\n\nexport function isAncestor(\n byId: Map<string, CanvasElement>,\n maybeAncestorId: string,\n elementId: string,\n) {\n let cursor = byId.get(elementId);\n const guard = new Set<string>();\n while (cursor?.parentId) {\n if (guard.has(cursor.id)) return false;\n guard.add(cursor.id);\n if (cursor.parentId === maybeAncestorId) return true;\n cursor = byId.get(cursor.parentId);\n }\n return false;\n}\n\nexport function displayName(element: CanvasElement) {\n if (element.name?.trim()) return element.name.trim();\n const content = element.content?.trim();\n if (content && !isContainer(element.type)) {\n return content.length > 24 ? `${content.slice(0, 24)}\u2026` : content;\n }\n return element.type;\n}\n\n/** Moves an element one step up or down among its siblings. */\nexport function reorder(\n elements: CanvasElement[],\n id: string,\n direction: \"up\" | \"down\",\n): CanvasElement[] {\n const target = elements.find((el) => el.id === id);\n if (!target) return elements;\n\n const siblings = childrenOf(elements, target.parentId);\n const index = siblings.findIndex((s) => s.id === id);\n const nextIndex = direction === \"up\" ? index + 1 : index - 1;\n if (!siblings[nextIndex]) return elements;\n\n // Re-index the whole sibling list so repeated swaps can't collide on ties.\n const ordered = [...siblings];\n ordered[index] = siblings[nextIndex];\n ordered[nextIndex] = target;\n const zById = new Map(ordered.map((el, i) => [el.id, i]));\n\n return elements.map((el) => {\n const z = zById.get(el.id);\n return z === undefined ? el : { ...el, z };\n });\n}\n\n/**\n * Inserts a Container between an element and its parent, sized to the element\n * and taking over its slot, so a loose element can be grouped in one step.\n */\nexport function wrapInContainer(\n elements: CanvasElement[],\n id: string,\n breakpoint: Breakpoint,\n): { elements: CanvasElement[]; wrapperId: string } | null {\n const target = elements.find((el) => el.id === id);\n if (!target) return null;\n\n const style = resolveStyle(target, breakpoint);\n const wrapper = createElement(\"Container\", {\n x: style.x,\n y: style.y,\n z: target.z,\n parentId: target.parentId,\n });\n wrapper.base = {\n ...wrapper.base,\n w: style.w,\n h: style.h,\n widthMode: style.widthMode,\n heightMode: \"auto\",\n padT: 0,\n padR: 0,\n padB: 0,\n padL: 0,\n borderW: 0,\n radius: 0,\n };\n\n const child = applyStyle({ ...target, parentId: wrapper.id, z: 0 }, breakpoint, {\n x: 0,\n y: 0,\n });\n\n return {\n elements: [...elements.map((el) => (el.id === id ? child : el)), wrapper],\n wrapperId: wrapper.id,\n };\n}\n\n/* -------------------------------------------------------------- reparenting */\n\n/**\n * Moves an element under a new parent, refusing the moves that would corrupt\n * the tree (into itself, into a descendant, or into a leaf).\n */\nexport function reparent(\n elements: CanvasElement[],\n id: string,\n newParentId: string | undefined,\n breakpoint: Breakpoint,\n /** Insert before this sibling; appended last when omitted. */\n beforeSiblingId?: string,\n): CanvasElement[] {\n const byId = indexById(elements);\n const element = byId.get(id);\n if (!element) return elements;\n if (newParentId === id) return elements;\n\n if (newParentId) {\n const parent = byId.get(newParentId);\n if (!parent || !isContainer(parent.type)) return elements;\n if (subtreeIds(elements, id).has(newParentId)) return elements;\n }\n\n // Keep the element where it looks like it is: convert its absolute position\n // into the new parent's coordinate space.\n const before = absolutePosition(byId, element, breakpoint);\n const newParent = newParentId ? byId.get(newParentId) : undefined;\n const parentOrigin = newParent\n ? absolutePosition(byId, newParent, breakpoint)\n : { x: 0, y: 0 };\n\n const moved = applyStyle(\n { ...element, parentId: newParentId },\n breakpoint,\n { x: before.x - parentOrigin.x, y: before.y - parentOrigin.y },\n );\n\n const rest = elements.map((el) => (el.id === id ? moved : el));\n return resequence(rest, newParentId, id, beforeSiblingId);\n}\n\n/** Rewrites z for one parent's children so the order is dense and explicit. */\nfunction resequence(\n elements: CanvasElement[],\n parentId: string | undefined,\n movedId: string,\n beforeSiblingId?: string,\n): CanvasElement[] {\n const siblings = childrenOf(elements, parentId).filter(\n (el) => el.id !== movedId,\n );\n const moved = elements.find((el) => el.id === movedId);\n if (!moved) return elements;\n\n const at = beforeSiblingId\n ? siblings.findIndex((s) => s.id === beforeSiblingId)\n : -1;\n const ordered =\n at === -1\n ? [...siblings, moved]\n : [...siblings.slice(0, at), moved, ...siblings.slice(at)];\n\n const zById = new Map(ordered.map((el, i) => [el.id, i]));\n return elements.map((el) => {\n const z = zById.get(el.id);\n return z === undefined ? el : { ...el, z };\n });\n}\n\n/**\n * The deepest container under a point that can accept `draggedId`, or\n * undefined for the page root.\n */\nexport function containerAt(\n elements: CanvasElement[],\n byId: Map<string, CanvasElement>,\n point: { x: number; y: number },\n breakpoint: Breakpoint,\n draggedId?: string,\n): string | undefined {\n const excluded = draggedId ? subtreeIds(elements, draggedId) : new Set<string>();\n\n const hits = elements\n .filter((el) => isContainer(el.type) && !excluded.has(el.id))\n .filter((el) => !resolveStyle(el, breakpoint).hidden)\n .filter((el) => {\n const style = resolveStyle(el, breakpoint);\n const origin = absolutePosition(byId, el, breakpoint);\n return (\n point.x >= origin.x &&\n point.x <= origin.x + style.w &&\n point.y >= origin.y &&\n point.y <= origin.y + style.h\n );\n });\n\n if (hits.length === 0) return undefined;\n\n // Prefer the most deeply nested hit, then the one painted on top.\n return hits.sort((a, b) => depthOf(byId, b) - depthOf(byId, a) || b.z - a.z)[0]\n .id;\n}\n\nfunction depthOf(byId: Map<string, CanvasElement>, element: CanvasElement) {\n let depth = 0;\n let cursor = element.parentId ? byId.get(element.parentId) : undefined;\n const guard = new Set<string>([element.id]);\n while (cursor && !guard.has(cursor.id)) {\n guard.add(cursor.id);\n depth += 1;\n cursor = cursor.parentId ? byId.get(cursor.parentId) : undefined;\n }\n return depth;\n}\n\n/**\n * An element parked beside the canvas is treated as a note: it stays visible\n * while designing but is left out of the published page.\n *\n * Only the horizontal axis counts. The canvas has a definite width \u2014 the\n * simulated viewport \u2014 while its height grows with the content, so \"below the\n * fold\" is a normal place for content to be, not a margin.\n */\nexport function isNote(\n element: CanvasElement,\n byId: Map<string, CanvasElement>,\n breakpoint: Breakpoint,\n frameWidth: number,\n /** How the page root arranges its own children. */\n rootLayout: LayoutMode = \"absolute\",\n cascade: Cascade = DEFAULT_CASCADE,\n): boolean {\n // Wherever a stack does the placing, x/y are not read at all \u2014 so a stale\n // coordinate must never be able to drop an element from the page.\n const parent = element.parentId ? byId.get(element.parentId) : undefined;\n const placedBy = parent\n ? resolveStyle(parent, breakpoint, cascade).layout\n : rootLayout;\n if (placedBy !== \"absolute\") return false;\n\n const style = resolveStyle(element, breakpoint, cascade);\n const { x } = absolutePosition(byId, element, breakpoint, cascade);\n const right = x + (style.widthMode === \"fixed\" ? style.w : 0);\n\n // Fully past either edge \u2014 a partial overlap is still part of the page.\n return right <= 0 || x >= frameWidth;\n}\n\n/** Ids of every note and everything inside one. */\nexport function noteIds(\n elements: CanvasElement[],\n byId: Map<string, CanvasElement>,\n breakpoint: Breakpoint,\n frameWidth: number,\n rootLayout: LayoutMode = \"absolute\",\n cascade: Cascade = DEFAULT_CASCADE,\n): Set<string> {\n const notes = new Set<string>();\n for (const element of elements) {\n if (isNote(element, byId, breakpoint, frameWidth, rootLayout, cascade)) {\n for (const id of subtreeIds(elements, element.id)) notes.add(id);\n }\n }\n return notes;\n}\n", "import {\n type DataSource,\n type HttpMethod,\n type RequestContext,\n type RequestPair,\n sendsBody,\n} from \"@/lib/editor/types\";\n\n/**\n * Fetching an author-supplied URL from our server is a server-side request\n * forgery vector: without checks it can reach cloud metadata endpoints, admin\n * services on localhost, or anything else inside the network perimeter. Every\n * hop is validated against this list, and redirects are followed by hand so a\n * public URL cannot bounce into private space.\n */\nconst BLOCKED_HOSTS = new Set([\n \"localhost\",\n \"127.0.0.1\",\n \"0.0.0.0\",\n \"::1\",\n \"[::1]\",\n \"metadata.google.internal\",\n \"metadata.goog\",\n]);\n\nconst PRIVATE_IPV4 =\n /^(10\\.|127\\.|0\\.|169\\.254\\.|192\\.168\\.|172\\.(1[6-9]|2\\d|3[01])\\.)/;\n\n/** Hostnames that only resolve inside a private network. */\nconst PRIVATE_SUFFIXES = [\".local\", \".internal\", \".localdomain\", \".home.arpa\"];\n\nexport const MAX_ROWS = 200;\nconst MAX_BYTES = 2_000_000;\nconst TIMEOUT_MS = 8000;\nconst MAX_REDIRECTS = 3;\n\nexport class DataSourceError extends Error {\n constructor(message: string, public readonly status?: number) {\n super(message);\n this.name = \"DataSourceError\";\n }\n}\n\nexport function assertFetchableUrl(raw: string): URL {\n let url: URL;\n try {\n url = new URL(raw);\n } catch {\n throw new DataSourceError(\"That is not a valid URL.\");\n }\n\n if (url.protocol !== \"https:\" && url.protocol !== \"http:\") {\n throw new DataSourceError(\"Only http and https URLs can be requested.\");\n }\n\n const host = url.hostname.toLowerCase();\n if (BLOCKED_HOSTS.has(host) || PRIVATE_IPV4.test(host)) {\n throw new DataSourceError(`Requests to ${host} are not allowed.`);\n }\n if (PRIVATE_SUFFIXES.some((suffix) => host.endsWith(suffix))) {\n throw new DataSourceError(`Requests to ${host} are not allowed.`);\n }\n // Bracketed IPv6 literals: anything link-local or unique-local.\n if (host.startsWith(\"[\") && /^\\[(fe80|fc|fd)/i.test(host)) {\n throw new DataSourceError(\"Requests to private addresses are not allowed.\");\n }\n\n return url;\n}\n\n/**\n * Walks a dotted path such as `data.items` or `results.0.tags`. An empty path\n * returns the payload itself, which is what a bare JSON array needs.\n */\nexport function selectPath(payload: unknown, path: string): unknown {\n const steps = path.split(\".\").map((s) => s.trim()).filter(Boolean);\n let cursor: unknown = payload;\n for (const step of steps) {\n if (cursor === null || typeof cursor !== \"object\") return undefined;\n cursor = (cursor as Record<string, unknown>)[step];\n }\n return cursor;\n}\n\n/** Reads a body with a hard cap so a huge response cannot exhaust memory. */\nasync function readCapped(response: Response): Promise<string> {\n const reader = response.body?.getReader();\n if (!reader) return \"\";\n\n const chunks: Uint8Array[] = [];\n let total = 0;\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n total += value.byteLength;\n if (total > MAX_BYTES) {\n await reader.cancel();\n throw new DataSourceError(\"That response is too large (over 2 MB).\");\n }\n chunks.push(value);\n }\n return new TextDecoder().decode(\n chunks.reduce<Uint8Array>((acc, chunk) => {\n const merged = new Uint8Array(acc.length + chunk.length);\n merged.set(acc);\n merged.set(chunk, acc.length);\n return merged;\n }, new Uint8Array()),\n );\n}\n\nexport type SourceResult = { rows: Array<Record<string, unknown>>; keys: string[] };\n\n/**\n * Requests a source and normalizes it to rows. A root object becomes one row\n * for Request; an array stays multiple rows for Repeat.\n */\nexport type LoadOptions = {\n /**\n * Seconds to cache the response for. `false` bypasses the cache \u2014 only for\n * the editor's Test button; on a rendered route it would force an\n * otherwise-static page to become dynamic on every request.\n */\n revalidate?: number | false;\n /** Supplies `{{query.\u2026}}` / `{{page.\u2026}}` values for this request. */\n context?: RequestContext;\n};\n\nexport const DEFAULT_REVALIDATE = 60;\n\nexport async function loadSource(\n source: DataSource,\n { revalidate = DEFAULT_REVALIDATE, context = EMPTY_CONTEXT }: LoadOptions = {},\n): Promise<SourceResult> {\n const request = buildRequest(source, context);\n let target = request.url;\n\n let response: Response | undefined;\n for (let hop = 0; hop <= MAX_REDIRECTS; hop++) {\n response = await fetch(target, {\n // Redirects are followed by hand so each hop is validated too.\n redirect: \"manual\",\n method: request.method,\n headers: request.headers,\n body: request.body,\n signal: AbortSignal.timeout(TIMEOUT_MS),\n // Only GET responses are cacheable; anything with side effects is\n // fetched fresh every time.\n ...(revalidate === false || request.method !== \"GET\"\n ? { cache: \"no-store\" as const }\n : { next: { revalidate } }),\n });\n\n if (response.status < 300 || response.status >= 400) break;\n\n const location = response.headers.get(\"location\");\n if (!location) break;\n target = assertFetchableUrl(new URL(location, target).toString());\n response = undefined;\n }\n\n if (!response) throw new DataSourceError(\"Too many redirects.\");\n if (!response.ok) {\n throw new DataSourceError(`The API replied ${response.status}.`, response.status);\n }\n\n const text = await readCapped(response);\n let payload: unknown;\n try {\n payload = JSON.parse(text);\n } catch {\n throw new DataSourceError(\"The response was not JSON.\");\n }\n\n const selected = selectPath(payload, source.path);\n const selectedRows = Array.isArray(selected)\n ? selected\n : selected !== null && typeof selected === \"object\"\n ? [selected]\n : undefined;\n if (!selectedRows) {\n throw new DataSourceError(\n source.path\n ? `\"${source.path}\" is not a list in that response.`\n : \"That response is neither an object nor a list.\",\n );\n }\n\n const rows = selectedRows\n .slice(0, MAX_ROWS)\n .map((row) =>\n row !== null && typeof row === \"object\" && !Array.isArray(row)\n ? (row as Record<string, unknown>)\n : { value: row },\n );\n\n // Union of the keys across the sample, so binding menus list everything.\n const keys = [...new Set(rows.flatMap((row) => Object.keys(row)))].sort();\n return { rows, keys };\n}\n\n/** Reads one binding path off a row and flattens it to something renderable. */\nexport function readBinding(row: Record<string, unknown>, path: string): string {\n const value = selectPath(row, path);\n if (value === null || value === undefined) return \"\";\n if (typeof value === \"object\") return \"\";\n return String(value);\n}\n\n/* ------------------------------------------------------------------ tokens */\n\n/**\n * Resolves `{{query.id}}`, `{{params.slug}}` and `{{page.slug}}`.\n *\n * Only these two namespaces exist: an unknown token becomes an empty string\n * rather than being left in the URL, so a typo cannot send a literal `{{\u2026}}`\n * to the API or accidentally expose something else.\n */\nexport function resolveTokens(value: string, context: RequestContext): string {\n return value.replace(/\\{\\{\\s*([a-zA-Z0-9_.]+)\\s*\\}\\}/g, (_, token: string) => {\n const [namespace, ...rest] = token.split(\".\");\n const key = rest.join(\".\");\n if (namespace === \"query\") return context.query[key] ?? \"\";\n if (namespace === \"params\") return context.params[key] ?? \"\";\n if (namespace === \"page\" && key === \"slug\") return context.page.slug;\n return \"\";\n });\n}\n\n/** Header names and values must not be able to inject extra headers. */\nfunction safeHeader(pair: RequestPair, context: RequestContext) {\n const name = pair.key.trim();\n if (!/^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/.test(name)) return null;\n // Strip anything that could terminate the header line.\n const value = resolveTokens(pair.value, context).replace(/[\\r\\n\\0]/g, \"\").trim();\n return value ? ([name, value] as const) : null;\n}\n\n/**\n * Builds the final URL: tokens resolved, params appended and percent-encoded\n * by `URLSearchParams` so a value can never break out into the query grammar.\n */\nexport function buildRequest(source: DataSource, context: RequestContext) {\n const url = assertFetchableUrl(resolveTokens(source.url, context));\n\n for (const pair of source.params ?? []) {\n const key = pair.key.trim();\n if (!key) continue;\n const value = resolveTokens(pair.value, context);\n // An empty optional filter is dropped rather than sent as `key=`.\n if (value === \"\") continue;\n url.searchParams.set(key, value);\n }\n\n const method: HttpMethod = source.method ?? \"GET\";\n\n const headers: Record<string, string> = {\n accept: \"application/json,text/plain;q=0.9,*/*;q=0.8\",\n };\n\n let body: string | undefined;\n if (sendsBody(method) && source.body?.trim()) {\n body = resolveTokens(source.body, context);\n headers[\"content-type\"] = \"application/json\";\n }\n\n // Author headers last, so they can override the defaults above.\n for (const pair of source.headers ?? []) {\n const safe = safeHeader(pair, context);\n if (safe) headers[safe[0]] = safe[1];\n }\n\n // Re-check after tokens: a token could have rewritten the host.\n return { url: assertFetchableUrl(url.toString()), headers, method, body };\n}\n\nexport const EMPTY_CONTEXT: RequestContext = { query: {}, params: {}, page: { slug: \"\" } };\n", "import { readBinding } from \"@/lib/data/source\";\nimport type { CanvasElement } from \"@/lib/editor/types\";\n\n/** One object of a data source, as seen inside Request or Repeat. */\nexport type Row = Record<string, unknown>;\n\n/** Resolved data for every source on the page, keyed by source id. */\nexport type PageData = Record<string, Row[]>;\n\n/**\n * Applies a row to an element. A bound Image or Video takes its `src` from the\n * field; anything else takes its text. Returns the element unchanged when\n * there is nothing to bind, so callers can use the result directly.\n */\nexport function bindElement(\n element: CanvasElement,\n row: Row | undefined,\n): CanvasElement {\n if (!row || !element.binding) return element;\n\n const value = readBinding(row, element.binding);\n if (element.type === \"Image\" || element.type === \"Video\") {\n // An empty result would blank the element; keep the authored fallback.\n return value ? { ...element, src: value } : element;\n }\n return { ...element, content: value };\n}\n\n/**\n * The rows a Repeat should render. An unconfigured or failed source yields a\n * single placeholder row so the template still shows up in the editor rather\n * than collapsing to nothing.\n */\nexport function rowsFor(\n element: CanvasElement,\n data: PageData,\n placeholderWhenEmpty: boolean,\n): Array<Row | undefined> {\n const rows = element.sourceId ? data[element.sourceId] : undefined;\n if (rows && rows.length > 0) return rows;\n return placeholderWhenEmpty ? [undefined] : [];\n}\n", "import type { CSSProperties } from \"react\";\nimport {\n isBand,\n resolveStyle,\n rootStyleToCss,\n splitBand,\n styleToCss,\n} from \"@/lib/editor/style\";\nimport {\n baseOf,\n type Cascade,\n cascadeOf,\n DEFAULT_CASCADE,\n mediaPlan,\n} from \"@/lib/editor/cascade\";\nimport { childrenOf } from \"@/lib/editor/tree\";\nimport {\n type Breakpoint,\n type CanvasElement,\n type RootStyle,\n} from \"@/lib/editor/types\";\n\n/** Properties that take a raw number rather than pixels. */\nconst UNITLESS = new Set([\n \"opacity\",\n \"zIndex\",\n \"flexGrow\",\n \"flexShrink\",\n \"flexBasis\",\n \"lineHeight\",\n \"order\",\n \"fontWeight\",\n \"columns\",\n]);\n\nfunction kebab(property: string) {\n return property.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);\n}\n\nexport function declarationsToCss(css: CSSProperties): string {\n return Object.entries(css)\n .filter(([, value]) => value !== undefined && value !== null && value !== \"\")\n .map(([property, value]) => {\n const rendered =\n typeof value === \"number\" && !UNITLESS.has(property)\n ? `${value}px`\n : String(value);\n return `${kebab(property)}:${rendered}`;\n })\n .join(\";\");\n}\n\n/** A CSS-identifier-safe class for one element. */\nexport function classFor(id: string) {\n return `pg-${id.replace(/[^a-zA-Z0-9_-]/g, \"-\")}`;\n}\n\n/**\n * \"inherit\" means \"the page default\" \u2014 which on a published page is a neutral\n * system stack, not whatever the surrounding app happens to set.\n */\nexport const DEFAULT_PAGE_FONT = \"ui-sans-serif, system-ui, sans-serif\";\n\nexport function resolveFont(fontFamily: string) {\n return fontFamily === \"inherit\" || fontFamily === \"\"\n ? DEFAULT_PAGE_FONT\n : fontFamily;\n}\n\n/**\n * Breakpoints are emitted as real media queries so a published page responds\n * to the viewport rather than to whatever the editor was previewing.\n */\n/**\n * The rules for one element. A full-bleed band produces two: the shell that\n * spans the viewport and the inner box that holds the content width.\n */\nfunction rulesFor(\n element: CanvasElement,\n byId: Map<string, CanvasElement>,\n breakpoint: Breakpoint,\n rootStyle: RootStyle,\n cascade: Cascade,\n): Array<[suffix: string, declarations: string]> {\n const parent = element.parentId ? byId.get(element.parentId) : undefined;\n const parentStyle = parent ? resolveStyle(parent, breakpoint, cascade) : undefined;\n const style = resolveStyle(element, breakpoint, cascade);\n\n const css = styleToCss(\n style,\n {\n parentLayout: parentStyle?.layout ?? \"stack\",\n parentDirection: parentStyle?.direction ?? \"column\",\n },\n element,\n );\n css.zIndex = element.z;\n if (style.hidden) css.display = \"none\";\n\n if (!isBand(element.type, style, rootStyle) || style.hidden) {\n return [[\"\", declarationsToCss(css)]];\n }\n\n const { shell, inner } = splitBand(css, rootStyle.maxWidth);\n return [\n [\"\", declarationsToCss(shell)],\n [\">.pg-inner\", declarationsToCss(inner)],\n ];\n}\n\n/**\n * Entrance keyframes animate `translate`/`scale` rather than `transform`, so\n * they compose with an element's own rotation or scale instead of clobbering\n * it. Emitted once, only when some element actually uses an entrance.\n */\nconst ENTRANCE_KEYFRAMES = `\n@keyframes pg-fade{from{opacity:0}to{opacity:1}}\n@keyframes pg-up{from{opacity:0;translate:0 28px}to{opacity:1;translate:none}}\n@keyframes pg-down{from{opacity:0;translate:0 -28px}to{opacity:1;translate:none}}\n@keyframes pg-left{from{opacity:0;translate:-32px 0}to{opacity:1;translate:none}}\n@keyframes pg-right{from{opacity:0;translate:32px 0}to{opacity:1;translate:none}}\n@keyframes pg-zoom{from{opacity:0;scale:.94}to{opacity:1;scale:none}}\n/* The hidden start state is applied only once the script is running, so a\n visitor without JavaScript still sees every element. */\n.pg-ready .pg-anim{opacity:0}\n/* Revealed elements are visible as a resting value, not only as the end of a\n keyframe: if animations are disabled or never get to run, the content still\n shows rather than staying at zero opacity. */\n.pg-ready .pg-anim.pg-in{opacity:1}\n@media (prefers-reduced-motion:reduce){.pg-ready .pg-anim{opacity:1;animation:none!important}}\n`.trim();\n\n/**\n * The reveal script; static, with no interpolated content.\n *\n * Hiding content until a callback fires is a risky trade on a public page, so\n * this fails open three ways: whatever is already on screen is revealed\n * synchronously, anything still hidden is revealed after a timeout, and the\n * whole hidden state is skipped when IntersectionObserver or motion\n * preferences say so.\n */\nexport const ENTRANCE_SCRIPT = `\n(function(){\n var nodes=document.querySelectorAll('.pg-anim');\n if(!nodes.length)return;\n var reveal=function(n){n.classList.add('pg-in');};\n var all=function(){for(var i=0;i<nodes.length;i++)reveal(nodes[i]);};\n\n if(!('IntersectionObserver' in window)||!window.matchMedia||matchMedia('(prefers-reduced-motion:reduce)').matches){\n all();return;\n }\n document.documentElement.classList.add('pg-ready');\n\n // Anything already in view animates straight away rather than waiting for\n // the observer's first delivery.\n var pending=[];\n for(var i=0;i<nodes.length;i++){\n var r=nodes[i].getBoundingClientRect();\n if(r.top<innerHeight&&r.bottom>0)reveal(nodes[i]);else pending.push(nodes[i]);\n }\n\n var io=new IntersectionObserver(function(entries){\n for(var j=0;j<entries.length;j++){\n if(entries[j].isIntersecting){reveal(entries[j].target);io.unobserve(entries[j].target);}\n }\n },{threshold:0.15,rootMargin:'0px 0px -8% 0px'});\n for(var k=0;k<pending.length;k++)io.observe(pending[k]);\n\n // Last resort: never leave content invisible because a callback did not run.\n setTimeout(all,3000);\n})();\n`.trim();\n\nexport function hasEntrances(\n elements: CanvasElement[],\n cascade: Cascade = DEFAULT_CASCADE,\n) {\n const baseId = baseOf(cascade).id;\n return elements.some(\n (el) => resolveStyle(el, baseId, cascade).entrance !== \"none\",\n );\n}\n\n/**\n * Builds the stylesheet for a published document: one base rule per element,\n * plus a media block for each breakpoint that actually overrides something,\n * plus hover rules.\n */\nexport function stylesheetFor(\n elements: CanvasElement[],\n rootStyle: RootStyle,\n): string {\n const byId = new Map(elements.map((el) => [el.id, el]));\n const cascade = cascadeOf(rootStyle.breakpoints, rootStyle.baseBreakpointId);\n const baseId = baseOf(cascade).id;\n const parts: string[] = [];\n for (const font of rootStyle.customFonts ?? []) {\n const family = font.name.replace(/[\"'{};]/g, \"\");\n const rawUrl = font.url.replace(/[\"'()\\\\]/g, \"\");\n const url = rawUrl === \"/fonts/manrope-variable.woff2\"\n ? \"/api/pagiera/assets/manrope-variable.woff2\"\n : rawUrl;\n parts.push(`@font-face{font-family:\"${family}\";src:url(\"${url}\") format(\"woff2\");font-weight:${font.weight};font-style:${font.style};font-display:swap}`);\n }\n\n // The published page lives inside the app's root layout, which paints its\n // own theme background on <body>. Take that over, or the area beside a\n // width-capped page shows the editor's chrome colour rather than the\n // page's own background.\n parts.push(`html,body{margin:0;padding:0;background:${rootStyle.bg}}`);\n\n parts.push(`html{scroll-behavior:smooth}.pg-root{${declarationsToCss(rootStyleToCss(rootStyle))}}`);\n parts.push(\n // The font is pinned on `.pg-root` rather than on <body>: the app's\n // layout puts a font class on <body>, which would out-specify an\n // element selector and leak the editor's typeface into the site.\n // The shell spans the viewport; the content width is applied by each\n // band's inner box, so section backgrounds reach both edges.\n `.pg-root{font-family:${resolveFont(rootStyle.fontFamily)}}` +\n \".pg-node{box-sizing:border-box}\" +\n \".pg-node img{display:block;width:100%;height:100%}\" +\n \".pg-link{text-decoration:none;color:inherit}\",\n );\n\n for (const element of elements) {\n const emitted = rulesFor(element, byId, baseId, rootStyle, cascade)\n .map(([suffix, decl]) => `.${classFor(element.id)}${suffix}{${decl}}`)\n .join(\"\");\n parts.push(emitted);\n }\n\n for (const plan of mediaPlan(cascade)) {\n const breakpoint = plan.id;\n // A parent switching layout at this breakpoint changes how its children\n // position themselves, so re-emit descendants of any changed container.\n const affected = elements.filter((el) => {\n if (el.overrides?.[breakpoint]) return true;\n const parent = el.parentId ? byId.get(el.parentId) : undefined;\n return Boolean(parent?.overrides?.[breakpoint]);\n });\n if (affected.length === 0) continue;\n\n const rules = affected\n .flatMap((el) =>\n rulesFor(el, byId, breakpoint, rootStyle, cascade).map(\n ([suffix, decl]) => `.${classFor(el.id)}${suffix}{${decl}}`,\n ),\n )\n .join(\"\");\n parts.push(`@media ${plan.query}{${rules}}`);\n }\n\n if (hasEntrances(elements, cascade)) {\n parts.push(ENTRANCE_KEYFRAMES);\n for (const element of elements) {\n const style = resolveStyle(element, baseId, cascade);\n if (style.entrance === \"none\") continue;\n parts.push(\n `.pg-ready .${classFor(element.id)}.pg-in{` +\n `animation:pg-${style.entrance} ${style.entranceDuration}ms ` +\n `${style.entranceCurve === \"spring\" ? `cubic-bezier(.16,${1 + Math.max(0, 45 - style.springDamping) / 100},${Math.max(0.12, Math.min(0.52, 120 / style.springStiffness))},1)` : `cubic-bezier(${style.entranceBezier})`} ${style.entranceDelay}ms both}`,\n );\n }\n }\n\n for (const element of elements) {\n if (element.hover || element.press) parts.push(`.${classFor(element.id)}{transition:transform .42s cubic-bezier(.16,1,.3,1),scale .42s cubic-bezier(.16,1,.3,1),rotate .42s cubic-bezier(.16,1,.3,1),translate .42s cubic-bezier(.16,1,.3,1),background-color .32s ease,color .32s ease,border-color .32s ease,box-shadow .42s cubic-bezier(.16,1,.3,1),opacity .32s ease,filter .42s ease;will-change:transform}`);\n if (!element.hover || Object.keys(element.hover).length === 0) continue;\n const merged = { ...resolveStyle(element, baseId, cascade), ...element.hover };\n const parent = element.parentId ? byId.get(element.parentId) : undefined;\n const parentStyle = parent ? resolveStyle(parent, baseId, cascade) : undefined;\n const css = styleToCss(\n merged,\n {\n parentLayout: parentStyle?.layout ?? \"stack\",\n parentDirection: parentStyle?.direction ?? \"column\",\n },\n element,\n );\n parts.push(\n `.${classFor(element.id)}:hover{${declarationsToCss(css)}}`,\n );\n }\n\n for (const element of elements) {\n if (element.press && Object.keys(element.press).length) {\n const merged = { ...resolveStyle(element, baseId, cascade), ...element.press };\n const parent = element.parentId ? byId.get(element.parentId) : undefined;\n const parentStyle = parent ? resolveStyle(parent, baseId, cascade) : undefined;\n const css = styleToCss(merged, { parentLayout: parentStyle?.layout ?? \"stack\", parentDirection: parentStyle?.direction ?? \"column\" }, element);\n parts.push(`.${classFor(element.id)}:active{${declarationsToCss(css)}}`);\n }\n if (element.loop) {\n const name = element.loop.type;\n parts.push(`.${classFor(element.id)}{animation:pg-loop-${name} ${element.loop.duration}ms ease-in-out infinite}`);\n }\n }\n if (elements.some((element) => element.loop)) parts.push(`@keyframes pg-loop-pulse{0%,100%{scale:1}50%{scale:1.06}}@keyframes pg-loop-float{0%,100%{translate:0 0}50%{translate:0 -12px}}@keyframes pg-loop-spin{to{rotate:360deg}}@media(prefers-reduced-motion:reduce){.pg-node{animation:none!important}}`);\n\n return parts.join(\"\\n\");\n}\n\nexport function rootChildren(elements: CanvasElement[]) {\n return childrenOf(elements, undefined);\n}\n", "import type React from \"react\";\nimport type { CanvasElement } from \"./types\";\n\nconst paths: Record<NonNullable<CanvasElement[\"iconName\"]>, React.ReactNode> = {\n star: <path d=\"m12 2.8 2.8 5.7 6.2.9-4.5 4.4 1.1 6.2-5.6-2.9L6.4 20l1.1-6.2L3 9.4l6.2-.9L12 2.8Z\" />,\n heart: <path d=\"M20.8 5.8c-2.2-2.2-5.7-2.2-7.9 0L12 6.7l-.9-.9a5.6 5.6 0 0 0-7.9 7.9L12 22l8.8-8.3a5.6 5.6 0 0 0 0-7.9Z\" />,\n \"arrow-right\": <><path d=\"M5 12h14\" /><path d=\"m14 7 5 5-5 5\" /></>,\n check: <path d=\"m5 12 4 4L19 6\" />,\n menu: <><path d=\"M4 7h16\" /><path d=\"M4 12h16\" /><path d=\"M4 17h16\" /></>,\n search: <><circle cx=\"11\" cy=\"11\" r=\"7\" /><path d=\"m20 20-4-4\" /></>,\n};\n\nexport function IconGlyph({ element }: { element: CanvasElement }) {\n return <svg viewBox=\"0 0 24 24\" width=\"100%\" height=\"100%\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">{paths[element.iconName ?? \"star\"]}</svg>;\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["Fragment", "jsx", "jsxs"]
7
+ }
@@ -0,0 +1,31 @@
1
+ import type { Pool } from "pg";
2
+ import type { RedisClientType } from "redis";
3
+
4
+ export type PagieraServerConfig = {
5
+ postgresUrl: string;
6
+ redisUrl: string;
7
+ openRouterApiKey: string;
8
+ openRouterModel: string;
9
+ basePath?: string;
10
+ aiRateLimitPerMinute?: number;
11
+ };
12
+
13
+ export function pagieraConfigFromEnv(env?: NodeJS.ProcessEnv): PagieraServerConfig;
14
+ export function createPagieraServer(config: PagieraServerConfig): Promise<{
15
+ handle(request: Request): Promise<Response>;
16
+ getEditorBootstrap(pageId?: string): Promise<any | undefined>;
17
+ getPreviewPage(pageId: string, context?: Record<string, unknown>): Promise<any | undefined>;
18
+ getPublishedDocument(slug: string): Promise<any | undefined>;
19
+ getPublishedPage(slug: string, context?: Record<string, unknown>): Promise<(any & { data: Record<string, unknown> }) | undefined>;
20
+ close(): Promise<void>;
21
+ redis: RedisClientType;
22
+ pool: Pool;
23
+ }>;
24
+ export function getPagieraServer(config: PagieraServerConfig): ReturnType<typeof createPagieraServer>;
25
+ export function createPagieraRouteHandlers(config: PagieraServerConfig): {
26
+ GET(request: Request): Promise<Response>;
27
+ POST(request: Request): Promise<Response>;
28
+ PUT(request: Request): Promise<Response>;
29
+ PATCH(request: Request): Promise<Response>;
30
+ DELETE(request: Request): Promise<Response>;
31
+ };