@aswin.dev/editor 0.7.1 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +56 -0
- package/dist/{AiChatSidebar-DWGPVtvC.js → AiChatSidebar-Dt5pvG5t.js} +37 -51
- package/dist/{AiFeatureMenu-duUFSfDf.js → AiFeatureMenu-BipxcGap.js} +1 -1
- package/dist/{CloudEditor-CFldoCOb.js → CloudEditor-CnUX0IOW.js} +283 -239
- package/dist/{CollaboratorBar-Bw-lbt61.js → CollaboratorBar-NaaZTjbs.js} +2 -2
- package/dist/{CommentsSidebar-BaD5F53-.js → CommentsSidebar-DE6ZsM4D.js} +9 -10
- package/dist/CountdownBlock-Ba3T-i2X.js +1536 -0
- package/dist/{CountdownToolbar-Dg4F8MTk.js → CountdownToolbar-CbhSp_uq.js} +1 -1
- package/dist/{DesignReferenceSidebar-BSH7uNP_.js → DesignReferenceSidebar-CZg97bbj.js} +1 -1
- package/dist/{ModuleBrowserModal-6phxZSKI.js → ModuleBrowserModal-D2EVdexc.js} +4 -4
- package/dist/{ModulePreviewCanvas-BW8L3eQX.js → ModulePreviewCanvas-P3N-nxkU.js} +52 -47
- package/dist/{NumberWithSuffix-BpUzQOJt.js → NumberWithSuffix-CihczoAd.js} +1 -1
- package/dist/{ParagraphEditor-DNSzAB-I.js → ParagraphEditor-BbRuKhWv.js} +7 -7
- package/dist/{SaveModuleDialog-zMQTpez7.js → SaveModuleDialog-DLZa3m3O.js} +2 -2
- package/dist/{SnapshotHistory-Do-A5rYU.js → SnapshotHistory-Csg1_jXi.js} +3 -3
- package/dist/{TestEmailModal-DGj_9V1u.js → TestEmailModal-m3okLbJz.js} +2 -2
- package/dist/{TitleEditor-C7IDnAoS.js → TitleEditor-CtJIS5ER.js} +2 -2
- package/dist/{TplModal-BAsVzcTb.js → TplModal-CQCrKeKP.js} +1 -1
- package/dist/{blockTypeIcons-C6LDKvmd.js → blockTypeIcons-Dah0pgt-.js} +50 -19
- package/dist/bundle-stats.json +8 -8
- package/dist/cdn/chunks/AccessibilityPanel-Bt_fD7QT.js +97 -0
- package/dist/cdn/chunks/AccessibilityPanel-Bt_fD7QT.js.map +1 -0
- package/dist/cdn/chunks/AiFeatureMenu-Bn-0rgfr.js +59 -0
- package/dist/cdn/chunks/AiFeatureMenu-Bn-0rgfr.js.map +1 -0
- package/dist/cdn/chunks/BlockA11yBadge-Cj18Iw0p.js +33 -0
- package/dist/cdn/chunks/BlockA11yBadge-Cj18Iw0p.js.map +1 -0
- package/dist/cdn/chunks/CloudEditor-56lVcdot.js +1220 -0
- package/dist/cdn/chunks/CloudEditor-56lVcdot.js.map +1 -0
- package/dist/cdn/chunks/CollaboratorBar-B7DCV3xp.js +51 -0
- package/dist/cdn/chunks/CollaboratorBar-B7DCV3xp.js.map +1 -0
- package/dist/cdn/chunks/CountdownToolbar-BtaD3d3-.js +212 -0
- package/dist/cdn/chunks/CountdownToolbar-BtaD3d3-.js.map +1 -0
- package/dist/cdn/chunks/ModuleBrowserModal-CiIY7ZGv.js +195 -0
- package/dist/cdn/chunks/ModuleBrowserModal-CiIY7ZGv.js.map +1 -0
- package/dist/cdn/chunks/ModulePreviewCanvas-M7_OGV2m.js +113 -0
- package/dist/cdn/chunks/ModulePreviewCanvas-M7_OGV2m.js.map +1 -0
- package/dist/cdn/chunks/NumberWithSuffix-DfVBnsgc.js +423 -0
- package/dist/cdn/chunks/NumberWithSuffix-DfVBnsgc.js.map +1 -0
- package/dist/cdn/chunks/ParagraphEditor-1XJOpiLX.js +544 -0
- package/dist/cdn/chunks/ParagraphEditor-1XJOpiLX.js.map +1 -0
- package/dist/cdn/chunks/RichTextEditorContent-C2q8sbp2.js +106 -0
- package/dist/cdn/chunks/RichTextEditorContent-C2q8sbp2.js.map +1 -0
- package/dist/cdn/chunks/SaveModuleDialog-BNxh1jPT.js +119 -0
- package/dist/cdn/chunks/SaveModuleDialog-BNxh1jPT.js.map +1 -0
- package/dist/cdn/chunks/TitleEditor-IF7VzLTk.js +171 -0
- package/dist/cdn/chunks/TitleEditor-IF7VzLTk.js.map +1 -0
- package/dist/cdn/chunks/blockTypeIcons-tPBKQ8WC.js +24 -0
- package/dist/cdn/chunks/blockTypeIcons-tPBKQ8WC.js.map +1 -0
- package/dist/cdn/chunks/de-B05yW8Gi.js +840 -0
- package/dist/cdn/chunks/de-B05yW8Gi.js.map +1 -0
- package/dist/cdn/chunks/de-BPHtelu7.js +209 -0
- package/dist/cdn/chunks/de-BPHtelu7.js.map +1 -0
- package/dist/cdn/chunks/de-BRDqJwJe.js +89 -0
- package/dist/cdn/chunks/de-BRDqJwJe.js.map +1 -0
- package/dist/cdn/chunks/draggable-C-1_gch3.js +11572 -0
- package/dist/cdn/chunks/draggable-C-1_gch3.js.map +1 -0
- package/dist/cdn/chunks/emojiData-DUHzsh4j.js +19 -0
- package/dist/cdn/chunks/emojiData-DUHzsh4j.js.map +1 -0
- package/dist/cdn/chunks/en-BII7695P.js +840 -0
- package/dist/cdn/chunks/en-BII7695P.js.map +1 -0
- package/dist/cdn/chunks/en-Cdj_Ikl1.js +89 -0
- package/dist/cdn/chunks/en-Cdj_Ikl1.js.map +1 -0
- package/dist/cdn/chunks/en-DejwuJhw.js +209 -0
- package/dist/cdn/chunks/en-DejwuJhw.js.map +1 -0
- package/dist/cdn/chunks/extensions-B0eT-yjf.js +598 -0
- package/dist/cdn/chunks/extensions-B0eT-yjf.js.map +1 -0
- package/dist/cdn/chunks/features-BrvE2Fzv.js +9677 -0
- package/dist/cdn/chunks/features-BrvE2Fzv.js.map +1 -0
- package/dist/cdn/chunks/icons-C7wtAD8p.js +1043 -0
- package/dist/cdn/chunks/icons-C7wtAD8p.js.map +1 -0
- package/dist/cdn/chunks/liquid.browser-CllF-us3.js +3279 -0
- package/dist/cdn/chunks/liquid.browser-CllF-us3.js.map +1 -0
- package/dist/cdn/chunks/media-library-Cl5XuaKy.js +6030 -0
- package/dist/cdn/chunks/media-library-Cl5XuaKy.js.map +1 -0
- package/dist/cdn/chunks/pusher-i7-OBujc.js +2508 -0
- package/dist/cdn/chunks/pusher-i7-OBujc.js.map +1 -0
- package/dist/cdn/chunks/quality-Va91a3N8.js +1456 -0
- package/dist/cdn/chunks/quality-Va91a3N8.js.map +1 -0
- package/dist/cdn/chunks/readableTextColor-DhoK4XiZ.js +32 -0
- package/dist/cdn/chunks/readableTextColor-DhoK4XiZ.js.map +1 -0
- package/dist/cdn/chunks/renderer-si0Zgxeb.js +642 -0
- package/dist/cdn/chunks/renderer-si0Zgxeb.js.map +1 -0
- package/dist/cdn/chunks/rolldown-runtime-BNuo_Jkg.js +20 -0
- package/dist/cdn/chunks/src-BLyYIbdZ.js +497 -0
- package/dist/cdn/chunks/src-BLyYIbdZ.js.map +1 -0
- package/dist/cdn/chunks/styleConstants-DfcU8u_r.js +57 -0
- package/dist/cdn/chunks/styleConstants-DfcU8u_r.js.map +1 -0
- package/dist/cdn/chunks/styles-C6BQLT9F.js +5807 -0
- package/dist/cdn/chunks/styles-C6BQLT9F.js.map +1 -0
- package/dist/cdn/chunks/tiptap-D8whBv5F.js +14654 -0
- package/dist/cdn/chunks/tiptap-D8whBv5F.js.map +1 -0
- package/dist/cdn/editor.css +2 -0
- package/dist/cdn/editor.js +367 -0
- package/dist/cdn/editor.js.map +1 -0
- package/dist/{cloud-6ZmAvF0j.js → cloud-BoS0J0vs.js} +1 -1
- package/dist/{de-DWcgp-7T.js → de-C74F9xK3.js} +112 -3
- package/dist/dist-C2grMquk.js +1261 -0
- package/dist/{dist-CivF9P8b.js → dist-Djgi0A6k.js} +92 -77
- package/dist/{en-Cxd4fhNm.js → en-B24jVTeO.js} +112 -3
- package/dist/{extensions-D__hOlV1.js → extensions-DsmjHqBF.js} +14 -14
- package/dist/index.d.ts +21 -10
- package/dist/{pencil-BZJPNYWR.js → pencil-Bpimrzzw.js} +5 -2
- package/dist/style.css +1 -1
- package/dist/styles-BMFMtR9R.js +6341 -0
- package/dist/templatical-editor.js +197 -139
- package/dist/undo-2-m1EUDbUg.js +16 -0
- package/dist/{useEditorCore-wslttMH-.js → useEditorCore-CtNAo0uy.js} +2154 -2025
- package/dist/useMergeTag-2vTcVpNo.js +34 -0
- package/package.json +12 -12
- package/dist/CountdownBlock-DaYGxKqo.js +0 -92
- package/dist/check-DJrpDKO_.js +0 -7
- package/dist/dist-C04s_fLA.js +0 -563
- package/dist/styles-B4tjX5SP.js +0 -5224
- package/dist/useMergeTag-DX0XG5V9.js +0 -34
- /package/dist/{clock-ik2pRJKG.js → clock-Ba4p3rJM.js} +0 -0
- /package/dist/{readableTextColor-DVuzNX1y.js → readableTextColor-C_9OpzBw.js} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"styles-C6BQLT9F.js","names":["$emit","$emit"],"sources":["../../../src/utils/resolveAccessibilityOptions.ts","../../../src/utils/resolvePopupEmbed.ts","../../../src/components/Canvas.vue","../../../src/components/Canvas.vue","../../../src/components/CanvasPagesNav.vue","../../../src/components/CanvasPagesNav.vue","../../../src/components/PopupDesignSwitch.vue","../../../src/components/PopupDesignSwitch.vue","../../../src/components/PopupOverlayImagePanel.vue","../../../src/components/PopupOverlayImagePanel.vue","../../../src/components/PopupDesignView.vue","../../../src/components/PopupDesignView.vue","../../../src/composables/useBlockPalette.ts","../../../src/components/SidebarEmailRail.vue","../../../src/components/SidebarEmailRail.vue","../../../src/components/PopupLeftChrome.vue","../../../src/components/PopupLeftChrome.vue","../../../src/components/Sidebar.vue","../../../src/components/Sidebar.vue","../../../src/composables/useMergeTagField.ts","../../../src/components/MergeTagSegments.vue","../../../src/components/MergeTagSegments.vue","../../../src/components/MergeTagInsertButton.vue","../../../src/components/MergeTagInsertButton.vue","../../../src/components/MergeTagTextarea.vue","../../../src/components/MergeTagTextarea.vue","../../../src/components/SpacingControl.vue","../../../src/components/SpacingControl.vue","../../../src/components/TemplateSettings.vue","../../../src/components/TemplateSettings.vue","../../../src/components/MergeTagInput.vue","../../../src/components/MergeTagInput.vue","../../../src/components/toolbar/ButtonToolbar.vue","../../../src/components/toolbar/ButtonToolbar.vue","../../../src/components/toolbar/CollapsibleSection.vue","../../../src/components/toolbar/CollapsibleSection.vue","../../../src/components/toolbar/CommonBlockSettings.vue","../../../src/components/toolbar/CommonBlockSettings.vue","../../../src/components/toolbar/FormToolbar.vue","../../../src/components/toolbar/FormToolbar.vue","../../../src/components/toolbar/InputToolbar.vue","../../../src/components/toolbar/InputToolbar.vue","../../../src/components/toolbar/fields/BooleanField.vue","../../../src/components/toolbar/fields/BooleanField.vue","../../../src/components/toolbar/fields/FieldWrapper.vue","../../../src/components/toolbar/fields/FieldWrapper.vue","../../../src/components/toolbar/fields/ColorField.vue","../../../src/components/toolbar/fields/ColorField.vue","../../../src/components/toolbar/fields/ImageField.vue","../../../src/components/toolbar/fields/ImageField.vue","../../../src/components/toolbar/fields/NumberField.vue","../../../src/components/toolbar/fields/NumberField.vue","../../../src/components/toolbar/fields/RepeatableField.vue","../../../src/components/toolbar/fields/RepeatableField.vue","../../../src/components/toolbar/fields/SelectField.vue","../../../src/components/toolbar/fields/SelectField.vue","../../../src/components/toolbar/fields/TextField.vue","../../../src/components/toolbar/fields/TextField.vue","../../../src/components/toolbar/fields/TextareaField.vue","../../../src/components/toolbar/fields/TextareaField.vue","../../../src/components/toolbar/fields/index.ts","../../../src/components/toolbar/CustomBlockToolbar.vue","../../../src/components/toolbar/CustomBlockToolbar.vue","../../../src/components/toolbar/DividerToolbar.vue","../../../src/components/toolbar/DividerToolbar.vue","../../../src/components/toolbar/HtmlToolbar.vue","../../../src/components/toolbar/HtmlToolbar.vue","../../../src/components/toolbar/ImageToolbar.vue","../../../src/components/toolbar/ImageToolbar.vue","../../../src/components/toolbar/MenuToolbar.vue","../../../src/components/toolbar/MenuToolbar.vue","../../../src/components/toolbar/SectionToolbar.vue","../../../src/components/toolbar/SectionToolbar.vue","../../../src/components/toolbar/SocialToolbar.vue","../../../src/components/toolbar/SocialToolbar.vue","../../../src/components/toolbar/SpacerToolbar.vue","../../../src/components/toolbar/SpacerToolbar.vue","../../../src/components/toolbar/TableToolbar.vue","../../../src/components/toolbar/TableToolbar.vue","../../../src/components/toolbar/TitleToolbar.vue","../../../src/components/toolbar/TitleToolbar.vue","../../../src/components/toolbar/VideoToolbar.vue","../../../src/components/toolbar/VideoToolbar.vue","../../../src/components/Toolbar.vue","../../../src/components/Toolbar.vue","../../../src/components/RightSidebar.vue","../../../src/components/RightSidebar.vue","../../../src/components/HistoryUndoRedo.vue","../../../src/components/HistoryUndoRedo.vue","../../../src/components/ViewportToggle.vue","../../../src/components/ViewportToggle.vue","../../../src/components/PreviewToggle.vue","../../../src/components/PreviewToggle.vue","../../../src/components/DarkModeToggle.vue","../../../src/components/DarkModeToggle.vue","../../../src/components/EditorFooter.vue","../../../src/components/EditorFooter.vue","../../../src/types/editor-tour.ts","../../../src/utils/tourStorage.ts","../../../src/components/EditorTour.vue","../../../src/components/EditorTour.vue","../../../src/components/PopupTriggerConditionDetails.vue","../../../src/components/PopupTriggerConditionDetails.vue","../../../src/components/PopupDisplayRulesView.vue","../../../src/components/PopupDisplayRulesView.vue","../../../src/utils/syncEditorThemeToPortal.ts","../../../src/components/PopupScheduleDatePicker.vue","../../../src/components/PopupScheduleDatePicker.vue","../../../src/components/PopupScheduleView.vue","../../../src/components/PopupScheduleView.vue"],"sourcesContent":["import type { A11yOptions } from \"@aswin.dev/quality\";\n\n/**\n * Build the `A11yOptions` object the editor passes to the lint composable.\n *\n * In editor / cloud-editor mode the linter locale is forced to match the\n * editor UI locale — `accessibility.locale` set by the consumer is\n * ignored. Headless callers (`lintAccessibility(...)` directly) keep\n * full control.\n */\nexport function resolveAccessibilityOptions(config: {\n locale?: string;\n accessibility?: A11yOptions;\n}): A11yOptions {\n return {\n ...config.accessibility,\n locale: config.locale,\n };\n}\n","import type { PopupEmbedSettings, TemplateSettings } from \"@aswin.dev/types\";\nimport {\n createDefaultPopupEmbedSettings,\n normalizePopupTriggersSettings,\n} from \"@aswin.dev/types\";\n\n/** Deep-merge saved `settings.popup` onto defaults so UI always has full shape. */\nexport function resolvePopupEmbed(\n settings: TemplateSettings,\n): PopupEmbedSettings {\n const base = createDefaultPopupEmbedSettings();\n const p = settings.popup;\n if (!p) return base;\n\n const triggers = normalizePopupTriggersSettings(base.triggers, p.triggers);\n\n return {\n triggers,\n design: {\n ...base.design,\n ...(p.design ?? {}),\n contentPadding: {\n ...base.design.contentPadding,\n ...(p.design?.contentPadding ?? {}),\n },\n },\n schedule: {\n ...base.schedule,\n ...(p.schedule ?? {}),\n slots:\n p.schedule?.slots !== undefined\n ? p.schedule.slots\n : base.schedule.slots,\n },\n };\n}\n","<script setup lang=\"ts\">\nimport type {\n Block,\n Collaborator,\n CustomBlock as CustomBlockType,\n PopupDisplayAnimation,\n PopupScreenPosition,\n TemplateContent,\n ViewportSize,\n} from \"@aswin.dev/types\";\nimport { normalizeTemplateContentPages } from \"@aswin.dev/types\";\nimport { ImageUp, Sparkles, SquarePlus } from \"@lucide/vue\";\nimport {\n computed,\n inject,\n nextTick,\n onMounted,\n ref,\n watch,\n type Component,\n} from \"vue\";\nimport { useWindowSize } from \"@vueuse/core\";\nimport draggable from \"vuedraggable\";\nimport { useCloudI18n } from \"../composables/useCloudI18n\";\nimport { useI18n } from \"../composables/useI18n\";\nimport {\n BLOCK_REGISTRY_KEY,\n CAPABILITIES_KEY,\n CONDITION_PREVIEW_KEY,\n EDITOR_KEY,\n requireInject,\n} from \"../keys\";\nimport { resolveBlockComponent } from \"../utils/blockComponentResolver\";\nimport { readableTextColor } from \"../utils/readableTextColor\";\nimport { resolvePopupEmbed } from \"../utils/resolvePopupEmbed\";\n\nimport BlockWrapper from \"./blocks/BlockWrapper.vue\";\nimport ButtonBlock from \"./blocks/ButtonBlock.vue\";\nimport CountdownBlockComponent from \"./blocks/CountdownBlock.vue\";\nimport CustomBlock from \"./blocks/CustomBlock.vue\";\nimport DividerBlock from \"./blocks/DividerBlock.vue\";\nimport FormBlockComponent from \"./blocks/FormBlock.vue\";\nimport HtmlBlock from \"./blocks/HtmlBlock.vue\";\nimport ImageBlock from \"./blocks/ImageBlock.vue\";\nimport InputBlock from \"./blocks/InputBlock.vue\";\nimport MenuBlock from \"./blocks/MenuBlock.vue\";\nimport ParagraphBlock from \"./blocks/ParagraphBlock.vue\";\nimport SectionBlock from \"./blocks/SectionBlock.vue\";\nimport SocialIconsBlock from \"./blocks/SocialIconsBlock.vue\";\nimport SpacerBlock from \"./blocks/SpacerBlock.vue\";\nimport TableBlock from \"./blocks/TableBlock.vue\";\nimport TitleBlock from \"./blocks/TitleBlock.vue\";\nimport VideoBlock from \"./blocks/VideoBlock.vue\";\n\nconst blockComponentMap: Record<string, Component> = {\n section: SectionBlock,\n title: TitleBlock,\n paragraph: ParagraphBlock,\n image: ImageBlock,\n button: ButtonBlock,\n input: InputBlock,\n divider: DividerBlock,\n spacer: SpacerBlock,\n html: HtmlBlock,\n social: SocialIconsBlock,\n menu: MenuBlock,\n table: TableBlock,\n video: VideoBlock,\n countdown: CountdownBlockComponent,\n form: FormBlockComponent,\n custom: CustomBlock,\n};\n\nconst props = defineProps<{\n viewport: ViewportSize;\n content: TemplateContent;\n selectedBlockId: string | null;\n darkMode: boolean;\n previewMode: boolean;\n lockedBlocks?: Map<string, Collaborator>;\n /** Multi-step canvas (e.g. popup flows); enable via editor config `multiPageCanvas`. */\n multiPageCanvas?: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"select-block\", blockId: string | null): void;\n (e: \"open-ai-chat\"): void;\n (e: \"open-design-reference\"): void;\n}>();\n\nconst { t } = useI18n();\nconst { t: cloudT } = useCloudI18n();\n\nconst editor = requireInject(EDITOR_KEY, \"Canvas\");\nconst conditionPreview = inject(CONDITION_PREVIEW_KEY, null);\nconst blockRegistry = inject(BLOCK_REGISTRY_KEY, null);\n\nconst caps = inject(CAPABILITIES_KEY, {});\n\nconst canUseAiChat = computed(\n () =>\n (caps.plan?.hasFeature(\"ai_generation\") ?? false) &&\n (caps.ai?.isFeatureEnabled(\"chat\") ?? false),\n);\nconst canUseDesignToTemplate = computed(\n () =>\n (caps.plan?.hasFeature(\"ai_generation\") ?? false) &&\n (caps.ai?.isFeatureEnabled(\"designToTemplate\") ?? false),\n);\n\nconst blocks = computed({\n get: () => props.content.blocks,\n set: (value: Block[]) => {\n const c = props.content;\n const activeId = c.activeCanvasPageId;\n if (c.canvasPages?.length && activeId) {\n editor.setContent(\n normalizeTemplateContentPages({\n ...c,\n canvasPages: c.canvasPages.map((p) =>\n p.id === activeId ? { ...p, blocks: value } : p,\n ),\n blocks: value,\n activeCanvasPageId: activeId,\n }),\n );\n return;\n }\n editor.setContent({\n ...c,\n blocks: value,\n });\n },\n});\n\nonMounted(() => {\n if (props.multiPageCanvas) {\n editor.ensureCanvasPages();\n }\n});\n\nconst viewportWidth = computed(() => {\n switch (props.viewport) {\n case \"mobile\":\n return 375;\n case \"tablet\":\n return 768;\n default:\n return props.content.settings.width;\n }\n});\n\nconst hasCanvasPages = computed(\n () => (props.content.canvasPages ?? []).length > 0,\n);\n\n/**\n * Step strip is rendered externally by Editor.vue (via CanvasPagesNav) so the\n * canvas itself never owns the step UI. We just need to know whether to leave\n * vertical space at the top for it.\n */\nconst showStepNav = computed(\n () => props.multiPageCanvas === true && hasCanvasPages.value,\n);\n\nfunction clamp01(n: number): number {\n if (!Number.isFinite(n)) return 0;\n return Math.min(1, Math.max(0, n));\n}\n\n/**\n * Compose a CSS background colour value from a hex colour and an optional\n * alpha (0..1). Mirrors the popup runtime helper in `index.html` (search\n * `popupBackgroundCss`) so the editor preview reflects the same blending\n * the embed uses. Falls back to the raw colour string when the input isn't\n * a parseable 3- or 6-digit hex (e.g. CSS variable, named colour) — leaving\n * the existing rendering untouched in those cases.\n */\nfunction composeBackgroundColor(\n color: string | undefined,\n opacity: number | undefined,\n): string | undefined {\n if (!color) return color;\n const a = opacity === undefined || !Number.isFinite(opacity) ? 1 : opacity;\n const alpha = Math.min(1, Math.max(0, a));\n if (alpha === 1) return color;\n const raw = color.trim();\n let hex: string | null = null;\n if (/^#[0-9a-f]{6}$/i.test(raw)) {\n hex = raw.slice(1);\n } else if (/^#[0-9a-f]{3}$/i.test(raw)) {\n hex = raw[1] + raw[1] + raw[2] + raw[2] + raw[3] + raw[3];\n }\n if (!hex) return color;\n const n = Number.parseInt(hex, 16);\n if (!Number.isFinite(n)) return color;\n const r = (n >> 16) & 255;\n const g = (n >> 8) & 255;\n const b = n & 255;\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n}\n\n/**\n * Compose a soft drop shadow whose blur, offset, and alpha all scale with the\n * 0..1 `intensity` value driven by `backgroundShadow`. Returning `undefined`\n * means \"no opinion\" so the caller can fall back to its existing default\n * shadow (preserving legacy behaviour for templates that never set the field).\n */\nfunction composeBoxShadow(intensity: number | undefined): string | undefined {\n if (intensity === undefined || !Number.isFinite(intensity)) return undefined;\n const t = Math.min(1, Math.max(0, intensity));\n if (t === 0) return \"none\";\n const yOffset = Math.round(t * 12);\n const blur = Math.round(t * 60);\n const alpha = Number((t * 0.35).toFixed(3));\n return `0 ${yOffset}px ${blur}px rgba(0, 0, 0, ${alpha})`;\n}\n\nconst popupEmbedDesign = computed(() =>\n props.content.settings.popup\n ? resolvePopupEmbed(props.content.settings).design\n : null,\n);\n\n/** Default blur when no value is stored (mirrors {@link createDefaultPopupEmbedSettings}). */\nconst POPUP_OVERLAY_DEFAULT_BLUR_PX = 8;\n\nfunction clampBlurPx(n: number | undefined): number {\n if (n === undefined || !Number.isFinite(n)) {\n return POPUP_OVERLAY_DEFAULT_BLUR_PX;\n }\n return Math.min(30, Math.max(0, n));\n}\n\nconst popupOverlayBackdrop = computed(() => {\n const d = popupEmbedDesign.value;\n return d?.useOverlay ? d : null;\n});\n\nconst popupSizePreset = computed(\n () => popupEmbedDesign.value?.sizePreset ?? \"large\",\n);\n\n/** Tall edge-to-edge panel (fixed width, viewport height). */\nconst isPopupFullHeight = computed(\n () =>\n Boolean(popupOverlayBackdrop.value) &&\n popupSizePreset.value === \"fullHeight\",\n);\n\n/** Covers the entire preview viewport. */\nconst isPopupFullscreen = computed(\n () =>\n Boolean(popupOverlayBackdrop.value) &&\n popupSizePreset.value === \"fullscreen\",\n);\n\nconst isPopupViewportSized = computed(\n () => isPopupFullHeight.value || isPopupFullscreen.value,\n);\n\n// ---------------------------------------------------------------------------\n// Popup background image — auto-fit aspect-ratio probing\n// ---------------------------------------------------------------------------\n// Mirrors the runtime in `index.html` (search \"ADOPT IMAGE NATURAL ASPECT\n// RATIO\"): when the user picks `imageFit: \"auto\"` the popup card adopts the\n// image's natural aspect ratio so the artwork fills the card edge-to-edge\n// instead of sitting as a centred band on a wider card. We probe via an\n// in-memory `<img>` so the editor preview stays in sync with the URL the\n// user just picked, and bail gracefully if the probe fails.\nconst popupBgImageRatio = ref<number | null>(null);\n\nwatch(\n () => {\n const d = popupEmbedDesign.value;\n return d?.backgroundImage && d.backgroundImageUrl\n ? d.backgroundImageUrl\n : \"\";\n },\n (url) => {\n popupBgImageRatio.value = null;\n if (!url) return;\n const probe = new Image();\n const requestedUrl = url;\n probe.onload = () => {\n // Bail if a newer URL has been picked while we were loading.\n if (requestedUrl !== popupEmbedDesign.value?.backgroundImageUrl) return;\n if (!probe.naturalWidth || !probe.naturalHeight) return;\n popupBgImageRatio.value = probe.naturalWidth / probe.naturalHeight;\n };\n probe.onerror = () => {\n popupBgImageRatio.value = null;\n };\n probe.src = url;\n },\n { immediate: true },\n);\n\n// Reactive viewport height — drives the auto-fit cap so that a very tall\n// (portrait) background image can't push the popup card past the visible\n// canvas area. Matches what mainstream popup builders do: shrink the card\n// proportionally so the entire popup stays in view, instead of forcing the\n// editor to scroll vertically just to see the bottom of the design.\nconst { height: windowHeight } = useWindowSize();\n\n/**\n * Pixels of editor chrome above and below the popup canvas (top toolbar,\n * popup-frame padding, viewport toggle, etc). Subtracted from the window\n * height to derive the available space for the popup card itself. Tuned to\n * leave a small breathing margin so the card doesn't touch the toolbar.\n */\nconst POPUP_CARD_VIEWPORT_CHROME_PX = 160;\n/** Floor — never let the cap collapse below this even on tiny windows. */\nconst POPUP_CARD_MIN_AVAILABLE_HEIGHT_PX = 320;\n\nconst popupCardMaxHeight = computed(() =>\n Math.max(\n POPUP_CARD_MIN_AVAILABLE_HEIGHT_PX,\n windowHeight.value - POPUP_CARD_VIEWPORT_CHROME_PX,\n ),\n);\n\n/**\n * In popup overlay mode the canvas card is rendered inside a larger viewport-\n * like frame so the dark backdrop is visible around it. On desktop the frame\n * stretches to fill the container (so the browser-chrome wrapper feels like a\n * real screen); on mobile/tablet it stays at a fixed phone/tablet width.\n */\nconst popupFrameFillsContainer = computed(() => props.viewport === \"desktop\");\n\n/** Numeric fallback for tablet/mobile. */\nconst popupFrameWidth = computed(() => {\n switch (props.viewport) {\n case \"mobile\":\n return 375;\n case \"tablet\":\n return 768;\n default:\n return 0;\n }\n});\n\nconst popupFrameMinHeight = computed(() => {\n switch (props.viewport) {\n case \"mobile\":\n return 600;\n case \"tablet\":\n return 600;\n default:\n return 600;\n }\n});\n\nconst popupCardWidth = computed(() => {\n const settingsWidth = props.content.settings.width;\n if (popupFrameFillsContainer.value) {\n return settingsWidth;\n }\n return Math.min(settingsWidth, popupFrameWidth.value);\n});\n\nfunction popupHorizontalJustifyClass(position: PopupScreenPosition): string {\n switch (position) {\n case \"topLeft\":\n case \"middleLeft\":\n case \"bottomLeft\":\n return \"tpl:justify-start\";\n case \"topRight\":\n case \"middleRight\":\n case \"bottomRight\":\n return \"tpl:justify-end\";\n case \"topCenter\":\n case \"bottomCenter\":\n case \"center\":\n default:\n return \"tpl:justify-center\";\n }\n}\n\nconst popupCardAlignClass = computed(() => {\n const position = popupOverlayBackdrop.value?.popupPosition ?? \"center\";\n\n if (isPopupFullscreen.value) {\n return \"tpl:items-stretch tpl:justify-center\";\n }\n\n if (isPopupFullHeight.value) {\n return `tpl:items-stretch ${popupHorizontalJustifyClass(position)}`;\n }\n\n switch (position) {\n case \"topLeft\":\n return \"tpl:items-start tpl:justify-start\";\n case \"topCenter\":\n return \"tpl:items-start tpl:justify-center\";\n case \"topRight\":\n return \"tpl:items-start tpl:justify-end\";\n case \"middleLeft\":\n return \"tpl:items-center tpl:justify-start\";\n case \"middleRight\":\n return \"tpl:items-center tpl:justify-end\";\n case \"bottomLeft\":\n return \"tpl:items-end tpl:justify-start\";\n case \"bottomCenter\":\n return \"tpl:items-end tpl:justify-center\";\n case \"bottomRight\":\n return \"tpl:items-end tpl:justify-end\";\n case \"center\":\n default:\n return \"tpl:items-center tpl:justify-center\";\n }\n});\n\nconst popupFrameStyle = computed(() => {\n const base = {\n width: popupFrameFillsContainer.value\n ? \"100%\"\n : `${popupFrameWidth.value}px`,\n };\n if (isPopupViewportSized.value) {\n const h = popupCardMaxHeight.value;\n return {\n ...base,\n minHeight: `${h}px`,\n height: `${h}px`,\n };\n }\n return {\n ...base,\n minHeight: `${popupFrameMinHeight.value}px`,\n };\n});\n\nconst canvasCardStyle = computed(() => {\n const inPopupFrame = !!popupOverlayBackdrop.value;\n const width = inPopupFrame\n ? `${popupCardWidth.value}px`\n : showStepNav.value\n ? \"100%\"\n : `${viewportWidth.value}px`;\n\n // Popup background image — mirrors the embed runtime in\n // packages/editor/index.html (cssBackgroundUrl + imageFit). Only painted\n // when popup mode is active, the toggle is on, and a URL is set, so non-\n // popup canvases stay completely unaffected.\n const popupDesign = popupOverlayBackdrop.value;\n const popupBgUrl =\n popupDesign?.backgroundImage && popupDesign.backgroundImageUrl\n ? popupDesign.backgroundImageUrl\n : \"\";\n const popupBgFit = popupDesign?.backgroundImageFit ?? \"cover\";\n // For `auto` fit we let the popup card adopt the image's natural aspect\n // ratio (probed asynchronously in `popupBgImageRatio`) and stretch the\n // image to fill it perfectly. While the probe is pending we fall back to\n // `cover` so the artwork still fills the card instead of sitting at its\n // native pixel size as a tiny centred band.\n const autoFitWithRatio =\n popupBgUrl && popupBgFit === \"auto\" && popupBgImageRatio.value !== null;\n const popupBgSize = autoFitWithRatio\n ? \"100% 100%\"\n : popupBgFit === \"contain\"\n ? \"contain\"\n : \"cover\";\n\n // When auto-fit is active, derive both width AND height from the image's\n // natural ratio AND the available viewport, so the entire popup stays in\n // view without scrolling. If the natural height would push past the\n // viewport cap (typical with portrait images), scale the width down\n // proportionally instead — the image still fills the card cleanly because\n // background-size is `100% 100%` against a card whose ratio matches.\n let autoFitWidthPx: number | null = null;\n let autoFitHeightPx: number | null = null;\n // Portrait background auto-fit is skipped for viewport-sized presets — those\n // modes intentionally fill the preview height instead of shrinking to the\n // image's natural aspect ratio.\n if (autoFitWithRatio && inPopupFrame && !isPopupViewportSized.value) {\n const ratio = popupBgImageRatio.value as number;\n const baseWidth = popupCardWidth.value;\n const naturalHeight = baseWidth / ratio;\n if (naturalHeight > popupCardMaxHeight.value) {\n autoFitHeightPx = popupCardMaxHeight.value;\n autoFitWidthPx = autoFitHeightPx * ratio;\n } else {\n autoFitWidthPx = baseWidth;\n autoFitHeightPx = naturalHeight;\n }\n }\n\n const customShadow = composeBoxShadow(\n props.content.settings.backgroundShadow,\n );\n const style: Record<string, string | undefined> = {\n width: autoFitWidthPx !== null ? `${Math.round(autoFitWidthPx)}px` : width,\n backgroundColor: composeBackgroundColor(\n props.content.settings.backgroundColor,\n props.content.settings.backgroundOpacity,\n ),\n backgroundImage: popupBgUrl ? `url(\"${popupBgUrl}\")` : undefined,\n backgroundSize: popupBgUrl ? popupBgSize : undefined,\n backgroundPosition: popupBgUrl ? \"center\" : undefined,\n backgroundRepeat: popupBgUrl ? \"no-repeat\" : undefined,\n boxShadow: props.darkMode\n ? \"none\"\n : (customShadow ?? \"var(--tpl-shadow-xl)\"),\n filter: props.darkMode ? \"invert(1) hue-rotate(180deg)\" : \"none\",\n transition:\n \"width 300ms cubic-bezier(0.34, 1.56, 0.64, 1), filter 300ms ease\",\n };\n if (autoFitHeightPx !== null) {\n // Lock to the computed height and drop the min-height so the card\n // collapses cleanly to the image's footprint. Setting both width and\n // height directly (instead of `aspectRatio`) lets us cap the height\n // without losing the proportional width scale-down for tall images.\n style.height = `${Math.round(autoFitHeightPx)}px`;\n style.minHeight = \"0\";\n }\n\n if (inPopupFrame && isPopupFullscreen.value) {\n style.width = \"100%\";\n style.height = \"100%\";\n style.minHeight = \"100%\";\n style.maxHeight = \"none\";\n style.borderRadius = \"0\";\n } else if (inPopupFrame && isPopupFullHeight.value) {\n const viewportH = popupCardMaxHeight.value;\n style.width = `${popupCardWidth.value}px`;\n style.height = `${viewportH}px`;\n style.minHeight = `${viewportH}px`;\n style.maxHeight = `${viewportH}px`;\n style.borderRadius = \"0\";\n }\n\n return style;\n});\n\nconst canvasRootStyle = computed(() => {\n if (popupOverlayBackdrop.value) {\n return popupFrameFillsContainer.value\n ? { width: \"100%\" }\n : { width: `${popupFrameWidth.value}px` };\n }\n return showStepNav.value ? { width: `${viewportWidth.value}px` } : undefined;\n});\n\n// Canvas dark mode preview: simulates how the email will appear in recipients'\n// dark-themed email clients. Uses CSS filter inversion — independent of the\n// editor UI theme (light/dark/auto) which is controlled via uiTheme config.\nconst canvasStyle = computed(() => {\n // When a popup background image is showing, the outer wrapper paints it.\n // Keep this inner layer transparent so the artwork stays visible behind\n // block content; otherwise fall back to the template background colour.\n const popupDesign = popupOverlayBackdrop.value;\n const hasPopupBgImage = Boolean(\n popupDesign?.backgroundImage && popupDesign.backgroundImageUrl,\n );\n return {\n backgroundColor: hasPopupBgImage\n ? \"transparent\"\n : composeBackgroundColor(\n props.content.settings.backgroundColor,\n props.content.settings.backgroundOpacity,\n ),\n fontFamily: props.content.settings.fontFamily,\n };\n});\n\nconst popupCanvasPaddingStyle = computed(() => {\n if (!props.content.settings.popup) {\n return {};\n }\n const pad = resolvePopupEmbed(props.content.settings).design.contentPadding;\n return {\n paddingTop: `${pad.top}px`,\n paddingRight: `${pad.right}px`,\n paddingBottom: `${pad.bottom}px`,\n paddingLeft: `${pad.left}px`,\n };\n});\n\nconst popupOverlayImagePreview = computed(() => {\n const d = popupOverlayBackdrop.value;\n return Boolean(d?.overlayImage && d.overlayImageUrl);\n});\n\n/**\n * Backdrop blur is applied in both modes (overlay-only and image), so users\n * can combine a background image with blur if they want.\n * Tint is only added in overlay-only mode for contrast.\n */\nconst popupBackdropScrimStyle = computed((): Record<string, string> => {\n const d = popupOverlayBackdrop.value;\n if (!d) return {};\n const blur = clampBlurPx(d.overlayBlur);\n const filter = blur > 0 ? `blur(${blur}px)` : \"none\";\n return {\n backdropFilter: filter,\n WebkitBackdropFilter: filter,\n backgroundColor: popupOverlayImagePreview.value\n ? \"transparent\"\n : \"rgba(0, 0, 0, 0.1)\",\n };\n});\n\nconst popupOverlayImageUrl = computed(\n () => popupOverlayBackdrop.value?.overlayImageUrl ?? \"\",\n);\n\nconst popupOverlayImageLayerOpacity = computed(() =>\n clamp01(popupOverlayBackdrop.value?.overlayImageOpacity ?? 1),\n);\n\n/**\n * Overlay image inherits the same `overlayBlur` as the backdrop scrim so the\n * artwork itself gets blurred alongside the page behind it.\n */\nconst popupOverlayImageStyle = computed((): Record<string, string | number> => {\n const blur = clampBlurPx(popupOverlayBackdrop.value?.overlayBlur);\n const filter = blur > 0 ? `blur(${blur}px)` : \"none\";\n return {\n opacity: popupOverlayImageLayerOpacity.value,\n filter,\n WebkitFilter: filter,\n };\n});\n\n// ---------------------------------------------------------------------------\n// Popup open-animation preview\n// ---------------------------------------------------------------------------\n// Mirrors the runtime behaviour in packages/editor/index.html:\n// - `fadeIn` → single keyframe regardless of position\n// - `slideIn` → translate from a small offset matching the chosen position\n// - `smooth` → translate + slight scale, again driven by position\n// - `none` → no animation\n// The animation origin is implicit: the card sits at its final flex-aligned\n// position and the keyframe starts it offset toward the picked corner/edge,\n// so it \"appears\" from that corner. We re-trigger the animation whenever the\n// user changes `displayAnimation` or `popupPosition` by briefly setting\n// `animation: none` (forces a reflow) before reapplying the real name.\n\nconst POPUP_ANIMATION_DURATION = \"0.55s\";\nconst POPUP_ANIMATION_EASING = \"cubic-bezier(0.22, 1, 0.36, 1)\";\n/**\n * Small head-start so the popup briefly holds its \"from\" keyframe state before\n * sliding/fading in. `animation-fill-mode: both` (set in the shorthand below)\n * keeps the element at the offset/opacity-0 position during this delay so it\n * does not flash at its final position first.\n */\nconst POPUP_ANIMATION_DELAY = \"0.18s\";\n\nfunction getPopupAnimationName(\n animation: PopupDisplayAnimation,\n position: PopupScreenPosition,\n): string {\n if (animation === \"none\") return \"\";\n if (animation === \"fadeIn\") return \"tpl-popup-fadeIn\";\n\n const isSmooth = animation === \"smooth\";\n switch (position) {\n case \"topLeft\":\n return isSmooth ? \"tpl-popup-smoothTopLeft\" : \"tpl-popup-topLeft\";\n case \"topCenter\":\n return isSmooth ? \"tpl-popup-smoothTop\" : \"tpl-popup-topToBottom\";\n case \"topRight\":\n return isSmooth ? \"tpl-popup-smoothTopRight\" : \"tpl-popup-topRight\";\n case \"middleLeft\":\n return isSmooth ? \"tpl-popup-smoothLeft\" : \"tpl-popup-leftToRight\";\n case \"middleRight\":\n return isSmooth ? \"tpl-popup-smoothRight\" : \"tpl-popup-rightToLeft\";\n case \"bottomLeft\":\n return isSmooth ? \"tpl-popup-smoothBottomLeft\" : \"tpl-popup-bottomLeft\";\n case \"bottomCenter\":\n return isSmooth ? \"tpl-popup-smoothBottom\" : \"tpl-popup-bottomToTop\";\n case \"bottomRight\":\n return isSmooth ? \"tpl-popup-smoothBottomRight\" : \"tpl-popup-bottomRight\";\n case \"center\":\n default:\n return isSmooth ? \"tpl-popup-smoothTop\" : \"tpl-popup-topToBottom\";\n }\n}\n\nconst popupAnimationName = computed(() => {\n const d = popupOverlayBackdrop.value;\n if (!d) return \"\";\n return getPopupAnimationName(d.displayAnimation, d.popupPosition);\n});\n\n/**\n * Toggles to `false` briefly when the animation should restart, then back to\n * `true` on the next tick so Vue emits two distinct `animation` values and the\n * browser actually replays the keyframe.\n */\nconst popupAnimationPlaying = ref(true);\n\nwatch(\n () => {\n const d = popupOverlayBackdrop.value;\n if (!d) return \"\";\n return `${d.displayAnimation}|${d.popupPosition}`;\n },\n async () => {\n popupAnimationPlaying.value = false;\n await nextTick();\n popupAnimationPlaying.value = true;\n },\n);\n\nconst popupAnimationStyle = computed((): Record<string, string> => {\n if (!popupOverlayBackdrop.value) return {};\n const name = popupAnimationName.value;\n if (!name || !popupAnimationPlaying.value) {\n return { animation: \"none\" };\n }\n return {\n animation: `${name} ${POPUP_ANIMATION_DURATION} ${POPUP_ANIMATION_EASING} ${POPUP_ANIMATION_DELAY} both`,\n };\n});\n\nfunction handleCanvasClick(event: MouseEvent): void {\n if (props.previewMode) {\n return;\n }\n if (event.target === event.currentTarget) {\n emit(\"select-block\", null);\n }\n}\n\nfunction getBlockComponent(block: Block): Component | null {\n return resolveBlockComponent(block, blockRegistry, blockComponentMap);\n}\n\nfunction getBlockLock(blockId: string): Collaborator | null {\n return props.lockedBlocks?.get(blockId) ?? null;\n}\n\nfunction handleFetchData(\n block: Block,\n payload: {\n fieldValues: Record<string, unknown>;\n dataSourceFetched: boolean;\n },\n): void {\n if (block.type !== \"custom\") {\n return;\n }\n\n editor.updateBlock(block.id, {\n fieldValues: payload.fieldValues,\n dataSourceFetched: payload.dataSourceFetched,\n } as Partial<CustomBlockType>);\n}\n</script>\n\n<template>\n <div\n class=\"tpl-canvas-root tpl:box-border tpl:max-w-full\"\n :class=\"\n popupOverlayBackdrop ? 'tpl:flex tpl:min-h-0 tpl:flex-1 tpl:flex-col' : ''\n \"\n :style=\"canvasRootStyle\"\n >\n <div\n :class=\"\n popupOverlayBackdrop\n ? 'tpl-popup-frame tpl:relative tpl:flex tpl:flex-col tpl:max-w-full tpl:overflow-hidden'\n : 'tpl:contents'\n \"\n :style=\"popupOverlayBackdrop ? popupFrameStyle : undefined\"\n >\n <div\n v-if=\"popupOverlayBackdrop\"\n class=\"tpl:pointer-events-none tpl:absolute tpl:inset-0 tpl:z-0 tpl:overflow-hidden\"\n aria-hidden=\"true\"\n >\n <div\n class=\"tpl:absolute tpl:inset-0\"\n :style=\"popupBackdropScrimStyle\"\n />\n <img\n v-if=\"popupOverlayImagePreview\"\n :src=\"popupOverlayImageUrl\"\n alt=\"\"\n class=\"tpl:absolute tpl:inset-0 tpl:h-full tpl:w-full tpl:object-cover\"\n :style=\"popupOverlayImageStyle\"\n />\n </div>\n <div\n :class=\"\n popupOverlayBackdrop\n ? `tpl:relative tpl:z-[1] tpl:flex tpl:h-full tpl:min-h-0 tpl:flex-1 tpl:w-full ${popupCardAlignClass}`\n : 'tpl:contents'\n \"\n >\n <div\n data-testid=\"canvas-wrapper\"\n role=\"region\"\n :aria-label=\"t.landmarks.canvas\"\n class=\"tpl-canvas-wrapper tpl:rounded-lg\"\n :class=\"\n isPopupViewportSized\n ? 'tpl:flex tpl:h-full tpl:min-h-0 tpl:flex-col !tpl:rounded-none'\n : 'tpl:shrink-0'\n \"\n :style=\"[canvasCardStyle, popupAnimationStyle]\"\n >\n <div\n class=\"tpl-canvas tpl:rounded-lg\"\n :class=\"{\n 'tpl-canvas--dark-mode': darkMode,\n 'tpl-preview-mode': previewMode,\n 'tpl:min-h-0 tpl:flex-1 tpl:overflow-y-auto':\n isPopupViewportSized,\n '!tpl:rounded-none': isPopupViewportSized,\n }\"\n :style=\"{ ...canvasStyle, ...popupCanvasPaddingStyle }\"\n @click=\"handleCanvasClick\"\n >\n <draggable\n v-model=\"blocks\"\n group=\"blocks\"\n item-key=\"id\"\n :animation=\"150\"\n ghost-class=\"tpl-ghost\"\n drag-class=\"tpl-dragging\"\n handle=\".tpl-block-btn\"\n :invert-swap=\"true\"\n :inverted-swap-threshold=\"0.65\"\n :disabled=\"previewMode\"\n class=\"tpl-canvas-blocks\"\n >\n <template #item=\"{ element: block }\">\n <div v-show=\"!conditionPreview?.isHidden(block.id)\">\n <div class=\"tpl:relative\">\n <!-- Collaboration lock overlay -->\n <div\n v-if=\"getBlockLock(block.id)\"\n class=\"tpl-collab-lock tpl:pointer-events-none tpl:absolute tpl:inset-0 tpl:z-[4] tpl:rounded-sm\"\n :style=\"{\n outline: `2px solid ${getBlockLock(block.id)!.color}`,\n outlineOffset: '-1px',\n }\"\n >\n <span\n class=\"tpl:absolute tpl:-top-0.5 tpl:left-1/2 tpl:z-[5] tpl:flex tpl:-translate-x-1/2 tpl:-translate-y-full tpl:items-center tpl:gap-1 tpl:rounded-full tpl:px-2 tpl:py-0.5 tpl:text-[10px] tpl:font-medium tpl:whitespace-nowrap\"\n :style=\"{\n backgroundColor: getBlockLock(block.id)!.color,\n color: readableTextColor(\n getBlockLock(block.id)!.color,\n ),\n }\"\n >\n <span\n class=\"tpl:inline-flex tpl:size-3 tpl:items-center tpl:justify-center tpl:rounded-full tpl:text-[8px] tpl:font-bold\"\n style=\"\n background-color: color-mix(\n in srgb,\n var(--tpl-bg) 30%,\n transparent\n );\n \"\n >\n {{ getBlockLock(block.id)!.name.charAt(0) }}\n </span>\n {{ getBlockLock(block.id)!.name }}\n </span>\n </div>\n <BlockWrapper\n :block=\"block\"\n :is-selected=\"\n !previewMode &&\n selectedBlockId === block.id &&\n !getBlockLock(block.id)\n \"\n :viewport=\"viewport\"\n :preview-mode=\"previewMode\"\n @select=\"\n previewMode || getBlockLock(block.id)\n ? undefined\n : emit('select-block', block.id)\n \"\n >\n <component\n :is=\"getBlockComponent(block)\"\n :block=\"block\"\n :viewport=\"viewport\"\n @fetch-data=\"handleFetchData(block, $event)\"\n @update=\"\n (updates: Partial<Block>) =>\n editor.updateBlock(block.id, updates)\n \"\n />\n </BlockWrapper>\n </div>\n </div>\n </template>\n <template #footer>\n <div\n v-if=\"blocks.length === 0 && !previewMode\"\n class=\"tpl-canvas-empty tpl:m-6 tpl:flex tpl:min-h-[400px] tpl:flex-col tpl:items-center tpl:justify-center tpl:rounded-xl tpl:border-2 tpl:border-dashed tpl:px-10 tpl:py-12 tpl:text-center tpl:border-[var(--tpl-primary)] tpl:bg-[var(--tpl-bg-elevated)] tpl:font-[var(--tpl-font-family)]\"\n >\n <div\n class=\"tpl-canvas-empty-icon tpl:mb-4 tpl:text-[var(--tpl-primary)]\"\n >\n <SquarePlus :size=\"48\" :stroke-width=\"1\" />\n </div>\n <p\n class=\"tpl-canvas-empty-title tpl:m-0 tpl:mb-2 tpl:text-base tpl:font-semibold tpl:text-[var(--tpl-primary)]\"\n >\n {{ t.canvas.noBlocks }}\n </p>\n <p\n class=\"tpl-canvas-empty-text tpl:m-0 tpl:text-sm tpl:text-[var(--tpl-text-dim)]\"\n >\n {{ t.canvas.dragHint }}\n </p>\n <p\n v-if=\"canUseAiChat && cloudT\"\n class=\"tpl:m-0 tpl:mt-2 tpl:flex tpl:flex-wrap tpl:items-center tpl:justify-center tpl:gap-x-1 tpl:gap-y-0.5 tpl:text-sm tpl:text-[var(--tpl-text-dim)]\"\n >\n {{ t.canvas.aiHintChat }}\n <button\n class=\"tpl:inline-flex tpl:shrink-0 tpl:cursor-pointer tpl:items-center tpl:gap-1 tpl:whitespace-nowrap tpl:rounded-[var(--tpl-radius-sm)] tpl:border-none tpl:px-2 tpl:py-0.5 tpl:text-sm tpl:font-semibold tpl:transition-colors tpl:duration-150 tpl:bg-[var(--tpl-primary-light)] tpl:text-[var(--tpl-primary-hover)]\"\n @click=\"emit('open-ai-chat')\"\n >\n <Sparkles :size=\"14\" :stroke-width=\"2\" />\n {{ cloudT.aiMenu.aiAssistant }}\n </button>\n {{ t.canvas.aiHintChatSuffix }}\n </p>\n <p\n v-if=\"canUseDesignToTemplate && cloudT\"\n class=\"tpl:m-0 tpl:mt-4 tpl:flex tpl:flex-wrap tpl:items-center tpl:justify-center tpl:gap-x-1 tpl:gap-y-0.5 tpl:text-sm tpl:text-[var(--tpl-text-dim)]\"\n >\n {{ t.canvas.aiHintDesign }}\n <button\n class=\"tpl:inline-flex tpl:shrink-0 tpl:cursor-pointer tpl:items-center tpl:gap-1 tpl:whitespace-nowrap tpl:rounded-[var(--tpl-radius-sm)] tpl:border-none tpl:px-2 tpl:py-0.5 tpl:text-sm tpl:font-semibold tpl:transition-colors tpl:duration-150 tpl:bg-[var(--tpl-primary-light)] tpl:text-[var(--tpl-primary-hover)]\"\n @click=\"emit('open-design-reference')\"\n >\n <ImageUp :size=\"14\" :stroke-width=\"2\" />\n {{ cloudT.aiMenu.designToTemplate }}\n </button>\n {{ t.canvas.aiHintDesignSuffix }}\n </p>\n </div>\n </template>\n </draggable>\n </div>\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n.tpl-canvas-step {\n border-color: var(--tpl-border);\n background-color: color-mix(in srgb, var(--tpl-bg) 92%, transparent);\n}\n\n.tpl-canvas-step--active {\n border-color: color-mix(in srgb, var(--tpl-text) 55%, var(--tpl-border));\n background-color: var(--tpl-bg);\n box-shadow: 0 0 0 1px color-mix(in srgb, var(--tpl-text) 12%, transparent);\n}\n\n/* Counter-invert images so they look normal in dark mode */\n.tpl-canvas--dark-mode :deep(img) {\n filter: invert(1) hue-rotate(180deg);\n}\n\n/* Counter-invert editor UI chrome so controls stay readable */\n.tpl-canvas--dark-mode :deep(.tpl-block-actions),\n.tpl-canvas--dark-mode :deep(.tpl-condition-toggle),\n.tpl-canvas--dark-mode :deep(.tpl-block-hidden-overlay) {\n filter: invert(1) hue-rotate(180deg);\n}\n</style>\n\n<!--\n Popup open-animation keyframes. These are intentionally unscoped because the\n animation name is referenced from an inline `:style` binding, which would not\n resolve Vue's scoped-style suffixes. Mirrors the keyframes shipped with the\n runtime popup embed in `packages/editor/index.html`.\n-->\n<style>\n@keyframes tpl-popup-fadeIn {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n\n@keyframes tpl-popup-topToBottom {\n from {\n opacity: 0;\n transform: translateY(-90px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n@keyframes tpl-popup-bottomToTop {\n from {\n opacity: 0;\n transform: translateY(90px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n@keyframes tpl-popup-leftToRight {\n from {\n opacity: 0;\n transform: translateX(-90px);\n }\n to {\n opacity: 1;\n transform: translateX(0);\n }\n}\n\n@keyframes tpl-popup-rightToLeft {\n from {\n opacity: 0;\n transform: translateX(90px);\n }\n to {\n opacity: 1;\n transform: translateX(0);\n }\n}\n\n@keyframes tpl-popup-topLeft {\n from {\n opacity: 0;\n transform: translate(-90px, -90px);\n }\n to {\n opacity: 1;\n transform: translate(0, 0);\n }\n}\n\n@keyframes tpl-popup-topRight {\n from {\n opacity: 0;\n transform: translate(90px, -90px);\n }\n to {\n opacity: 1;\n transform: translate(0, 0);\n }\n}\n\n@keyframes tpl-popup-bottomLeft {\n from {\n opacity: 0;\n transform: translate(-90px, 90px);\n }\n to {\n opacity: 1;\n transform: translate(0, 0);\n }\n}\n\n@keyframes tpl-popup-bottomRight {\n from {\n opacity: 0;\n transform: translate(90px, 90px);\n }\n to {\n opacity: 1;\n transform: translate(0, 0);\n }\n}\n\n@keyframes tpl-popup-smoothTop {\n from {\n opacity: 0;\n transform: translateY(-40px) scale(0.94);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n\n@keyframes tpl-popup-smoothBottom {\n from {\n opacity: 0;\n transform: translateY(40px) scale(0.94);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n\n@keyframes tpl-popup-smoothLeft {\n from {\n opacity: 0;\n transform: translateX(-40px) scale(0.94);\n }\n to {\n opacity: 1;\n transform: translateX(0) scale(1);\n }\n}\n\n@keyframes tpl-popup-smoothRight {\n from {\n opacity: 0;\n transform: translateX(40px) scale(0.94);\n }\n to {\n opacity: 1;\n transform: translateX(0) scale(1);\n }\n}\n\n@keyframes tpl-popup-smoothTopLeft {\n from {\n opacity: 0;\n transform: translate(-40px, -40px) scale(0.94);\n }\n to {\n opacity: 1;\n transform: translate(0, 0) scale(1);\n }\n}\n\n@keyframes tpl-popup-smoothTopRight {\n from {\n opacity: 0;\n transform: translate(40px, -40px) scale(0.94);\n }\n to {\n opacity: 1;\n transform: translate(0, 0) scale(1);\n }\n}\n\n@keyframes tpl-popup-smoothBottomLeft {\n from {\n opacity: 0;\n transform: translate(-40px, 40px) scale(0.94);\n }\n to {\n opacity: 1;\n transform: translate(0, 0) scale(1);\n }\n}\n\n@keyframes tpl-popup-smoothBottomRight {\n from {\n opacity: 0;\n transform: translate(40px, 40px) scale(0.94);\n }\n to {\n opacity: 1;\n transform: translate(0, 0) scale(1);\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport type {\n Block,\n Collaborator,\n CustomBlock as CustomBlockType,\n PopupDisplayAnimation,\n PopupScreenPosition,\n TemplateContent,\n ViewportSize,\n} from \"@aswin.dev/types\";\nimport { normalizeTemplateContentPages } from \"@aswin.dev/types\";\nimport { ImageUp, Sparkles, SquarePlus } from \"@lucide/vue\";\nimport {\n computed,\n inject,\n nextTick,\n onMounted,\n ref,\n watch,\n type Component,\n} from \"vue\";\nimport { useWindowSize } from \"@vueuse/core\";\nimport draggable from \"vuedraggable\";\nimport { useCloudI18n } from \"../composables/useCloudI18n\";\nimport { useI18n } from \"../composables/useI18n\";\nimport {\n BLOCK_REGISTRY_KEY,\n CAPABILITIES_KEY,\n CONDITION_PREVIEW_KEY,\n EDITOR_KEY,\n requireInject,\n} from \"../keys\";\nimport { resolveBlockComponent } from \"../utils/blockComponentResolver\";\nimport { readableTextColor } from \"../utils/readableTextColor\";\nimport { resolvePopupEmbed } from \"../utils/resolvePopupEmbed\";\n\nimport BlockWrapper from \"./blocks/BlockWrapper.vue\";\nimport ButtonBlock from \"./blocks/ButtonBlock.vue\";\nimport CountdownBlockComponent from \"./blocks/CountdownBlock.vue\";\nimport CustomBlock from \"./blocks/CustomBlock.vue\";\nimport DividerBlock from \"./blocks/DividerBlock.vue\";\nimport FormBlockComponent from \"./blocks/FormBlock.vue\";\nimport HtmlBlock from \"./blocks/HtmlBlock.vue\";\nimport ImageBlock from \"./blocks/ImageBlock.vue\";\nimport InputBlock from \"./blocks/InputBlock.vue\";\nimport MenuBlock from \"./blocks/MenuBlock.vue\";\nimport ParagraphBlock from \"./blocks/ParagraphBlock.vue\";\nimport SectionBlock from \"./blocks/SectionBlock.vue\";\nimport SocialIconsBlock from \"./blocks/SocialIconsBlock.vue\";\nimport SpacerBlock from \"./blocks/SpacerBlock.vue\";\nimport TableBlock from \"./blocks/TableBlock.vue\";\nimport TitleBlock from \"./blocks/TitleBlock.vue\";\nimport VideoBlock from \"./blocks/VideoBlock.vue\";\n\nconst blockComponentMap: Record<string, Component> = {\n section: SectionBlock,\n title: TitleBlock,\n paragraph: ParagraphBlock,\n image: ImageBlock,\n button: ButtonBlock,\n input: InputBlock,\n divider: DividerBlock,\n spacer: SpacerBlock,\n html: HtmlBlock,\n social: SocialIconsBlock,\n menu: MenuBlock,\n table: TableBlock,\n video: VideoBlock,\n countdown: CountdownBlockComponent,\n form: FormBlockComponent,\n custom: CustomBlock,\n};\n\nconst props = defineProps<{\n viewport: ViewportSize;\n content: TemplateContent;\n selectedBlockId: string | null;\n darkMode: boolean;\n previewMode: boolean;\n lockedBlocks?: Map<string, Collaborator>;\n /** Multi-step canvas (e.g. popup flows); enable via editor config `multiPageCanvas`. */\n multiPageCanvas?: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"select-block\", blockId: string | null): void;\n (e: \"open-ai-chat\"): void;\n (e: \"open-design-reference\"): void;\n}>();\n\nconst { t } = useI18n();\nconst { t: cloudT } = useCloudI18n();\n\nconst editor = requireInject(EDITOR_KEY, \"Canvas\");\nconst conditionPreview = inject(CONDITION_PREVIEW_KEY, null);\nconst blockRegistry = inject(BLOCK_REGISTRY_KEY, null);\n\nconst caps = inject(CAPABILITIES_KEY, {});\n\nconst canUseAiChat = computed(\n () =>\n (caps.plan?.hasFeature(\"ai_generation\") ?? false) &&\n (caps.ai?.isFeatureEnabled(\"chat\") ?? false),\n);\nconst canUseDesignToTemplate = computed(\n () =>\n (caps.plan?.hasFeature(\"ai_generation\") ?? false) &&\n (caps.ai?.isFeatureEnabled(\"designToTemplate\") ?? false),\n);\n\nconst blocks = computed({\n get: () => props.content.blocks,\n set: (value: Block[]) => {\n const c = props.content;\n const activeId = c.activeCanvasPageId;\n if (c.canvasPages?.length && activeId) {\n editor.setContent(\n normalizeTemplateContentPages({\n ...c,\n canvasPages: c.canvasPages.map((p) =>\n p.id === activeId ? { ...p, blocks: value } : p,\n ),\n blocks: value,\n activeCanvasPageId: activeId,\n }),\n );\n return;\n }\n editor.setContent({\n ...c,\n blocks: value,\n });\n },\n});\n\nonMounted(() => {\n if (props.multiPageCanvas) {\n editor.ensureCanvasPages();\n }\n});\n\nconst viewportWidth = computed(() => {\n switch (props.viewport) {\n case \"mobile\":\n return 375;\n case \"tablet\":\n return 768;\n default:\n return props.content.settings.width;\n }\n});\n\nconst hasCanvasPages = computed(\n () => (props.content.canvasPages ?? []).length > 0,\n);\n\n/**\n * Step strip is rendered externally by Editor.vue (via CanvasPagesNav) so the\n * canvas itself never owns the step UI. We just need to know whether to leave\n * vertical space at the top for it.\n */\nconst showStepNav = computed(\n () => props.multiPageCanvas === true && hasCanvasPages.value,\n);\n\nfunction clamp01(n: number): number {\n if (!Number.isFinite(n)) return 0;\n return Math.min(1, Math.max(0, n));\n}\n\n/**\n * Compose a CSS background colour value from a hex colour and an optional\n * alpha (0..1). Mirrors the popup runtime helper in `index.html` (search\n * `popupBackgroundCss`) so the editor preview reflects the same blending\n * the embed uses. Falls back to the raw colour string when the input isn't\n * a parseable 3- or 6-digit hex (e.g. CSS variable, named colour) — leaving\n * the existing rendering untouched in those cases.\n */\nfunction composeBackgroundColor(\n color: string | undefined,\n opacity: number | undefined,\n): string | undefined {\n if (!color) return color;\n const a = opacity === undefined || !Number.isFinite(opacity) ? 1 : opacity;\n const alpha = Math.min(1, Math.max(0, a));\n if (alpha === 1) return color;\n const raw = color.trim();\n let hex: string | null = null;\n if (/^#[0-9a-f]{6}$/i.test(raw)) {\n hex = raw.slice(1);\n } else if (/^#[0-9a-f]{3}$/i.test(raw)) {\n hex = raw[1] + raw[1] + raw[2] + raw[2] + raw[3] + raw[3];\n }\n if (!hex) return color;\n const n = Number.parseInt(hex, 16);\n if (!Number.isFinite(n)) return color;\n const r = (n >> 16) & 255;\n const g = (n >> 8) & 255;\n const b = n & 255;\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n}\n\n/**\n * Compose a soft drop shadow whose blur, offset, and alpha all scale with the\n * 0..1 `intensity` value driven by `backgroundShadow`. Returning `undefined`\n * means \"no opinion\" so the caller can fall back to its existing default\n * shadow (preserving legacy behaviour for templates that never set the field).\n */\nfunction composeBoxShadow(intensity: number | undefined): string | undefined {\n if (intensity === undefined || !Number.isFinite(intensity)) return undefined;\n const t = Math.min(1, Math.max(0, intensity));\n if (t === 0) return \"none\";\n const yOffset = Math.round(t * 12);\n const blur = Math.round(t * 60);\n const alpha = Number((t * 0.35).toFixed(3));\n return `0 ${yOffset}px ${blur}px rgba(0, 0, 0, ${alpha})`;\n}\n\nconst popupEmbedDesign = computed(() =>\n props.content.settings.popup\n ? resolvePopupEmbed(props.content.settings).design\n : null,\n);\n\n/** Default blur when no value is stored (mirrors {@link createDefaultPopupEmbedSettings}). */\nconst POPUP_OVERLAY_DEFAULT_BLUR_PX = 8;\n\nfunction clampBlurPx(n: number | undefined): number {\n if (n === undefined || !Number.isFinite(n)) {\n return POPUP_OVERLAY_DEFAULT_BLUR_PX;\n }\n return Math.min(30, Math.max(0, n));\n}\n\nconst popupOverlayBackdrop = computed(() => {\n const d = popupEmbedDesign.value;\n return d?.useOverlay ? d : null;\n});\n\nconst popupSizePreset = computed(\n () => popupEmbedDesign.value?.sizePreset ?? \"large\",\n);\n\n/** Tall edge-to-edge panel (fixed width, viewport height). */\nconst isPopupFullHeight = computed(\n () =>\n Boolean(popupOverlayBackdrop.value) &&\n popupSizePreset.value === \"fullHeight\",\n);\n\n/** Covers the entire preview viewport. */\nconst isPopupFullscreen = computed(\n () =>\n Boolean(popupOverlayBackdrop.value) &&\n popupSizePreset.value === \"fullscreen\",\n);\n\nconst isPopupViewportSized = computed(\n () => isPopupFullHeight.value || isPopupFullscreen.value,\n);\n\n// ---------------------------------------------------------------------------\n// Popup background image — auto-fit aspect-ratio probing\n// ---------------------------------------------------------------------------\n// Mirrors the runtime in `index.html` (search \"ADOPT IMAGE NATURAL ASPECT\n// RATIO\"): when the user picks `imageFit: \"auto\"` the popup card adopts the\n// image's natural aspect ratio so the artwork fills the card edge-to-edge\n// instead of sitting as a centred band on a wider card. We probe via an\n// in-memory `<img>` so the editor preview stays in sync with the URL the\n// user just picked, and bail gracefully if the probe fails.\nconst popupBgImageRatio = ref<number | null>(null);\n\nwatch(\n () => {\n const d = popupEmbedDesign.value;\n return d?.backgroundImage && d.backgroundImageUrl\n ? d.backgroundImageUrl\n : \"\";\n },\n (url) => {\n popupBgImageRatio.value = null;\n if (!url) return;\n const probe = new Image();\n const requestedUrl = url;\n probe.onload = () => {\n // Bail if a newer URL has been picked while we were loading.\n if (requestedUrl !== popupEmbedDesign.value?.backgroundImageUrl) return;\n if (!probe.naturalWidth || !probe.naturalHeight) return;\n popupBgImageRatio.value = probe.naturalWidth / probe.naturalHeight;\n };\n probe.onerror = () => {\n popupBgImageRatio.value = null;\n };\n probe.src = url;\n },\n { immediate: true },\n);\n\n// Reactive viewport height — drives the auto-fit cap so that a very tall\n// (portrait) background image can't push the popup card past the visible\n// canvas area. Matches what mainstream popup builders do: shrink the card\n// proportionally so the entire popup stays in view, instead of forcing the\n// editor to scroll vertically just to see the bottom of the design.\nconst { height: windowHeight } = useWindowSize();\n\n/**\n * Pixels of editor chrome above and below the popup canvas (top toolbar,\n * popup-frame padding, viewport toggle, etc). Subtracted from the window\n * height to derive the available space for the popup card itself. Tuned to\n * leave a small breathing margin so the card doesn't touch the toolbar.\n */\nconst POPUP_CARD_VIEWPORT_CHROME_PX = 160;\n/** Floor — never let the cap collapse below this even on tiny windows. */\nconst POPUP_CARD_MIN_AVAILABLE_HEIGHT_PX = 320;\n\nconst popupCardMaxHeight = computed(() =>\n Math.max(\n POPUP_CARD_MIN_AVAILABLE_HEIGHT_PX,\n windowHeight.value - POPUP_CARD_VIEWPORT_CHROME_PX,\n ),\n);\n\n/**\n * In popup overlay mode the canvas card is rendered inside a larger viewport-\n * like frame so the dark backdrop is visible around it. On desktop the frame\n * stretches to fill the container (so the browser-chrome wrapper feels like a\n * real screen); on mobile/tablet it stays at a fixed phone/tablet width.\n */\nconst popupFrameFillsContainer = computed(() => props.viewport === \"desktop\");\n\n/** Numeric fallback for tablet/mobile. */\nconst popupFrameWidth = computed(() => {\n switch (props.viewport) {\n case \"mobile\":\n return 375;\n case \"tablet\":\n return 768;\n default:\n return 0;\n }\n});\n\nconst popupFrameMinHeight = computed(() => {\n switch (props.viewport) {\n case \"mobile\":\n return 600;\n case \"tablet\":\n return 600;\n default:\n return 600;\n }\n});\n\nconst popupCardWidth = computed(() => {\n const settingsWidth = props.content.settings.width;\n if (popupFrameFillsContainer.value) {\n return settingsWidth;\n }\n return Math.min(settingsWidth, popupFrameWidth.value);\n});\n\nfunction popupHorizontalJustifyClass(position: PopupScreenPosition): string {\n switch (position) {\n case \"topLeft\":\n case \"middleLeft\":\n case \"bottomLeft\":\n return \"tpl:justify-start\";\n case \"topRight\":\n case \"middleRight\":\n case \"bottomRight\":\n return \"tpl:justify-end\";\n case \"topCenter\":\n case \"bottomCenter\":\n case \"center\":\n default:\n return \"tpl:justify-center\";\n }\n}\n\nconst popupCardAlignClass = computed(() => {\n const position = popupOverlayBackdrop.value?.popupPosition ?? \"center\";\n\n if (isPopupFullscreen.value) {\n return \"tpl:items-stretch tpl:justify-center\";\n }\n\n if (isPopupFullHeight.value) {\n return `tpl:items-stretch ${popupHorizontalJustifyClass(position)}`;\n }\n\n switch (position) {\n case \"topLeft\":\n return \"tpl:items-start tpl:justify-start\";\n case \"topCenter\":\n return \"tpl:items-start tpl:justify-center\";\n case \"topRight\":\n return \"tpl:items-start tpl:justify-end\";\n case \"middleLeft\":\n return \"tpl:items-center tpl:justify-start\";\n case \"middleRight\":\n return \"tpl:items-center tpl:justify-end\";\n case \"bottomLeft\":\n return \"tpl:items-end tpl:justify-start\";\n case \"bottomCenter\":\n return \"tpl:items-end tpl:justify-center\";\n case \"bottomRight\":\n return \"tpl:items-end tpl:justify-end\";\n case \"center\":\n default:\n return \"tpl:items-center tpl:justify-center\";\n }\n});\n\nconst popupFrameStyle = computed(() => {\n const base = {\n width: popupFrameFillsContainer.value\n ? \"100%\"\n : `${popupFrameWidth.value}px`,\n };\n if (isPopupViewportSized.value) {\n const h = popupCardMaxHeight.value;\n return {\n ...base,\n minHeight: `${h}px`,\n height: `${h}px`,\n };\n }\n return {\n ...base,\n minHeight: `${popupFrameMinHeight.value}px`,\n };\n});\n\nconst canvasCardStyle = computed(() => {\n const inPopupFrame = !!popupOverlayBackdrop.value;\n const width = inPopupFrame\n ? `${popupCardWidth.value}px`\n : showStepNav.value\n ? \"100%\"\n : `${viewportWidth.value}px`;\n\n // Popup background image — mirrors the embed runtime in\n // packages/editor/index.html (cssBackgroundUrl + imageFit). Only painted\n // when popup mode is active, the toggle is on, and a URL is set, so non-\n // popup canvases stay completely unaffected.\n const popupDesign = popupOverlayBackdrop.value;\n const popupBgUrl =\n popupDesign?.backgroundImage && popupDesign.backgroundImageUrl\n ? popupDesign.backgroundImageUrl\n : \"\";\n const popupBgFit = popupDesign?.backgroundImageFit ?? \"cover\";\n // For `auto` fit we let the popup card adopt the image's natural aspect\n // ratio (probed asynchronously in `popupBgImageRatio`) and stretch the\n // image to fill it perfectly. While the probe is pending we fall back to\n // `cover` so the artwork still fills the card instead of sitting at its\n // native pixel size as a tiny centred band.\n const autoFitWithRatio =\n popupBgUrl && popupBgFit === \"auto\" && popupBgImageRatio.value !== null;\n const popupBgSize = autoFitWithRatio\n ? \"100% 100%\"\n : popupBgFit === \"contain\"\n ? \"contain\"\n : \"cover\";\n\n // When auto-fit is active, derive both width AND height from the image's\n // natural ratio AND the available viewport, so the entire popup stays in\n // view without scrolling. If the natural height would push past the\n // viewport cap (typical with portrait images), scale the width down\n // proportionally instead — the image still fills the card cleanly because\n // background-size is `100% 100%` against a card whose ratio matches.\n let autoFitWidthPx: number | null = null;\n let autoFitHeightPx: number | null = null;\n // Portrait background auto-fit is skipped for viewport-sized presets — those\n // modes intentionally fill the preview height instead of shrinking to the\n // image's natural aspect ratio.\n if (autoFitWithRatio && inPopupFrame && !isPopupViewportSized.value) {\n const ratio = popupBgImageRatio.value as number;\n const baseWidth = popupCardWidth.value;\n const naturalHeight = baseWidth / ratio;\n if (naturalHeight > popupCardMaxHeight.value) {\n autoFitHeightPx = popupCardMaxHeight.value;\n autoFitWidthPx = autoFitHeightPx * ratio;\n } else {\n autoFitWidthPx = baseWidth;\n autoFitHeightPx = naturalHeight;\n }\n }\n\n const customShadow = composeBoxShadow(\n props.content.settings.backgroundShadow,\n );\n const style: Record<string, string | undefined> = {\n width: autoFitWidthPx !== null ? `${Math.round(autoFitWidthPx)}px` : width,\n backgroundColor: composeBackgroundColor(\n props.content.settings.backgroundColor,\n props.content.settings.backgroundOpacity,\n ),\n backgroundImage: popupBgUrl ? `url(\"${popupBgUrl}\")` : undefined,\n backgroundSize: popupBgUrl ? popupBgSize : undefined,\n backgroundPosition: popupBgUrl ? \"center\" : undefined,\n backgroundRepeat: popupBgUrl ? \"no-repeat\" : undefined,\n boxShadow: props.darkMode\n ? \"none\"\n : (customShadow ?? \"var(--tpl-shadow-xl)\"),\n filter: props.darkMode ? \"invert(1) hue-rotate(180deg)\" : \"none\",\n transition:\n \"width 300ms cubic-bezier(0.34, 1.56, 0.64, 1), filter 300ms ease\",\n };\n if (autoFitHeightPx !== null) {\n // Lock to the computed height and drop the min-height so the card\n // collapses cleanly to the image's footprint. Setting both width and\n // height directly (instead of `aspectRatio`) lets us cap the height\n // without losing the proportional width scale-down for tall images.\n style.height = `${Math.round(autoFitHeightPx)}px`;\n style.minHeight = \"0\";\n }\n\n if (inPopupFrame && isPopupFullscreen.value) {\n style.width = \"100%\";\n style.height = \"100%\";\n style.minHeight = \"100%\";\n style.maxHeight = \"none\";\n style.borderRadius = \"0\";\n } else if (inPopupFrame && isPopupFullHeight.value) {\n const viewportH = popupCardMaxHeight.value;\n style.width = `${popupCardWidth.value}px`;\n style.height = `${viewportH}px`;\n style.minHeight = `${viewportH}px`;\n style.maxHeight = `${viewportH}px`;\n style.borderRadius = \"0\";\n }\n\n return style;\n});\n\nconst canvasRootStyle = computed(() => {\n if (popupOverlayBackdrop.value) {\n return popupFrameFillsContainer.value\n ? { width: \"100%\" }\n : { width: `${popupFrameWidth.value}px` };\n }\n return showStepNav.value ? { width: `${viewportWidth.value}px` } : undefined;\n});\n\n// Canvas dark mode preview: simulates how the email will appear in recipients'\n// dark-themed email clients. Uses CSS filter inversion — independent of the\n// editor UI theme (light/dark/auto) which is controlled via uiTheme config.\nconst canvasStyle = computed(() => {\n // When a popup background image is showing, the outer wrapper paints it.\n // Keep this inner layer transparent so the artwork stays visible behind\n // block content; otherwise fall back to the template background colour.\n const popupDesign = popupOverlayBackdrop.value;\n const hasPopupBgImage = Boolean(\n popupDesign?.backgroundImage && popupDesign.backgroundImageUrl,\n );\n return {\n backgroundColor: hasPopupBgImage\n ? \"transparent\"\n : composeBackgroundColor(\n props.content.settings.backgroundColor,\n props.content.settings.backgroundOpacity,\n ),\n fontFamily: props.content.settings.fontFamily,\n };\n});\n\nconst popupCanvasPaddingStyle = computed(() => {\n if (!props.content.settings.popup) {\n return {};\n }\n const pad = resolvePopupEmbed(props.content.settings).design.contentPadding;\n return {\n paddingTop: `${pad.top}px`,\n paddingRight: `${pad.right}px`,\n paddingBottom: `${pad.bottom}px`,\n paddingLeft: `${pad.left}px`,\n };\n});\n\nconst popupOverlayImagePreview = computed(() => {\n const d = popupOverlayBackdrop.value;\n return Boolean(d?.overlayImage && d.overlayImageUrl);\n});\n\n/**\n * Backdrop blur is applied in both modes (overlay-only and image), so users\n * can combine a background image with blur if they want.\n * Tint is only added in overlay-only mode for contrast.\n */\nconst popupBackdropScrimStyle = computed((): Record<string, string> => {\n const d = popupOverlayBackdrop.value;\n if (!d) return {};\n const blur = clampBlurPx(d.overlayBlur);\n const filter = blur > 0 ? `blur(${blur}px)` : \"none\";\n return {\n backdropFilter: filter,\n WebkitBackdropFilter: filter,\n backgroundColor: popupOverlayImagePreview.value\n ? \"transparent\"\n : \"rgba(0, 0, 0, 0.1)\",\n };\n});\n\nconst popupOverlayImageUrl = computed(\n () => popupOverlayBackdrop.value?.overlayImageUrl ?? \"\",\n);\n\nconst popupOverlayImageLayerOpacity = computed(() =>\n clamp01(popupOverlayBackdrop.value?.overlayImageOpacity ?? 1),\n);\n\n/**\n * Overlay image inherits the same `overlayBlur` as the backdrop scrim so the\n * artwork itself gets blurred alongside the page behind it.\n */\nconst popupOverlayImageStyle = computed((): Record<string, string | number> => {\n const blur = clampBlurPx(popupOverlayBackdrop.value?.overlayBlur);\n const filter = blur > 0 ? `blur(${blur}px)` : \"none\";\n return {\n opacity: popupOverlayImageLayerOpacity.value,\n filter,\n WebkitFilter: filter,\n };\n});\n\n// ---------------------------------------------------------------------------\n// Popup open-animation preview\n// ---------------------------------------------------------------------------\n// Mirrors the runtime behaviour in packages/editor/index.html:\n// - `fadeIn` → single keyframe regardless of position\n// - `slideIn` → translate from a small offset matching the chosen position\n// - `smooth` → translate + slight scale, again driven by position\n// - `none` → no animation\n// The animation origin is implicit: the card sits at its final flex-aligned\n// position and the keyframe starts it offset toward the picked corner/edge,\n// so it \"appears\" from that corner. We re-trigger the animation whenever the\n// user changes `displayAnimation` or `popupPosition` by briefly setting\n// `animation: none` (forces a reflow) before reapplying the real name.\n\nconst POPUP_ANIMATION_DURATION = \"0.55s\";\nconst POPUP_ANIMATION_EASING = \"cubic-bezier(0.22, 1, 0.36, 1)\";\n/**\n * Small head-start so the popup briefly holds its \"from\" keyframe state before\n * sliding/fading in. `animation-fill-mode: both` (set in the shorthand below)\n * keeps the element at the offset/opacity-0 position during this delay so it\n * does not flash at its final position first.\n */\nconst POPUP_ANIMATION_DELAY = \"0.18s\";\n\nfunction getPopupAnimationName(\n animation: PopupDisplayAnimation,\n position: PopupScreenPosition,\n): string {\n if (animation === \"none\") return \"\";\n if (animation === \"fadeIn\") return \"tpl-popup-fadeIn\";\n\n const isSmooth = animation === \"smooth\";\n switch (position) {\n case \"topLeft\":\n return isSmooth ? \"tpl-popup-smoothTopLeft\" : \"tpl-popup-topLeft\";\n case \"topCenter\":\n return isSmooth ? \"tpl-popup-smoothTop\" : \"tpl-popup-topToBottom\";\n case \"topRight\":\n return isSmooth ? \"tpl-popup-smoothTopRight\" : \"tpl-popup-topRight\";\n case \"middleLeft\":\n return isSmooth ? \"tpl-popup-smoothLeft\" : \"tpl-popup-leftToRight\";\n case \"middleRight\":\n return isSmooth ? \"tpl-popup-smoothRight\" : \"tpl-popup-rightToLeft\";\n case \"bottomLeft\":\n return isSmooth ? \"tpl-popup-smoothBottomLeft\" : \"tpl-popup-bottomLeft\";\n case \"bottomCenter\":\n return isSmooth ? \"tpl-popup-smoothBottom\" : \"tpl-popup-bottomToTop\";\n case \"bottomRight\":\n return isSmooth ? \"tpl-popup-smoothBottomRight\" : \"tpl-popup-bottomRight\";\n case \"center\":\n default:\n return isSmooth ? \"tpl-popup-smoothTop\" : \"tpl-popup-topToBottom\";\n }\n}\n\nconst popupAnimationName = computed(() => {\n const d = popupOverlayBackdrop.value;\n if (!d) return \"\";\n return getPopupAnimationName(d.displayAnimation, d.popupPosition);\n});\n\n/**\n * Toggles to `false` briefly when the animation should restart, then back to\n * `true` on the next tick so Vue emits two distinct `animation` values and the\n * browser actually replays the keyframe.\n */\nconst popupAnimationPlaying = ref(true);\n\nwatch(\n () => {\n const d = popupOverlayBackdrop.value;\n if (!d) return \"\";\n return `${d.displayAnimation}|${d.popupPosition}`;\n },\n async () => {\n popupAnimationPlaying.value = false;\n await nextTick();\n popupAnimationPlaying.value = true;\n },\n);\n\nconst popupAnimationStyle = computed((): Record<string, string> => {\n if (!popupOverlayBackdrop.value) return {};\n const name = popupAnimationName.value;\n if (!name || !popupAnimationPlaying.value) {\n return { animation: \"none\" };\n }\n return {\n animation: `${name} ${POPUP_ANIMATION_DURATION} ${POPUP_ANIMATION_EASING} ${POPUP_ANIMATION_DELAY} both`,\n };\n});\n\nfunction handleCanvasClick(event: MouseEvent): void {\n if (props.previewMode) {\n return;\n }\n if (event.target === event.currentTarget) {\n emit(\"select-block\", null);\n }\n}\n\nfunction getBlockComponent(block: Block): Component | null {\n return resolveBlockComponent(block, blockRegistry, blockComponentMap);\n}\n\nfunction getBlockLock(blockId: string): Collaborator | null {\n return props.lockedBlocks?.get(blockId) ?? null;\n}\n\nfunction handleFetchData(\n block: Block,\n payload: {\n fieldValues: Record<string, unknown>;\n dataSourceFetched: boolean;\n },\n): void {\n if (block.type !== \"custom\") {\n return;\n }\n\n editor.updateBlock(block.id, {\n fieldValues: payload.fieldValues,\n dataSourceFetched: payload.dataSourceFetched,\n } as Partial<CustomBlockType>);\n}\n</script>\n\n<template>\n <div\n class=\"tpl-canvas-root tpl:box-border tpl:max-w-full\"\n :class=\"\n popupOverlayBackdrop ? 'tpl:flex tpl:min-h-0 tpl:flex-1 tpl:flex-col' : ''\n \"\n :style=\"canvasRootStyle\"\n >\n <div\n :class=\"\n popupOverlayBackdrop\n ? 'tpl-popup-frame tpl:relative tpl:flex tpl:flex-col tpl:max-w-full tpl:overflow-hidden'\n : 'tpl:contents'\n \"\n :style=\"popupOverlayBackdrop ? popupFrameStyle : undefined\"\n >\n <div\n v-if=\"popupOverlayBackdrop\"\n class=\"tpl:pointer-events-none tpl:absolute tpl:inset-0 tpl:z-0 tpl:overflow-hidden\"\n aria-hidden=\"true\"\n >\n <div\n class=\"tpl:absolute tpl:inset-0\"\n :style=\"popupBackdropScrimStyle\"\n />\n <img\n v-if=\"popupOverlayImagePreview\"\n :src=\"popupOverlayImageUrl\"\n alt=\"\"\n class=\"tpl:absolute tpl:inset-0 tpl:h-full tpl:w-full tpl:object-cover\"\n :style=\"popupOverlayImageStyle\"\n />\n </div>\n <div\n :class=\"\n popupOverlayBackdrop\n ? `tpl:relative tpl:z-[1] tpl:flex tpl:h-full tpl:min-h-0 tpl:flex-1 tpl:w-full ${popupCardAlignClass}`\n : 'tpl:contents'\n \"\n >\n <div\n data-testid=\"canvas-wrapper\"\n role=\"region\"\n :aria-label=\"t.landmarks.canvas\"\n class=\"tpl-canvas-wrapper tpl:rounded-lg\"\n :class=\"\n isPopupViewportSized\n ? 'tpl:flex tpl:h-full tpl:min-h-0 tpl:flex-col !tpl:rounded-none'\n : 'tpl:shrink-0'\n \"\n :style=\"[canvasCardStyle, popupAnimationStyle]\"\n >\n <div\n class=\"tpl-canvas tpl:rounded-lg\"\n :class=\"{\n 'tpl-canvas--dark-mode': darkMode,\n 'tpl-preview-mode': previewMode,\n 'tpl:min-h-0 tpl:flex-1 tpl:overflow-y-auto':\n isPopupViewportSized,\n '!tpl:rounded-none': isPopupViewportSized,\n }\"\n :style=\"{ ...canvasStyle, ...popupCanvasPaddingStyle }\"\n @click=\"handleCanvasClick\"\n >\n <draggable\n v-model=\"blocks\"\n group=\"blocks\"\n item-key=\"id\"\n :animation=\"150\"\n ghost-class=\"tpl-ghost\"\n drag-class=\"tpl-dragging\"\n handle=\".tpl-block-btn\"\n :invert-swap=\"true\"\n :inverted-swap-threshold=\"0.65\"\n :disabled=\"previewMode\"\n class=\"tpl-canvas-blocks\"\n >\n <template #item=\"{ element: block }\">\n <div v-show=\"!conditionPreview?.isHidden(block.id)\">\n <div class=\"tpl:relative\">\n <!-- Collaboration lock overlay -->\n <div\n v-if=\"getBlockLock(block.id)\"\n class=\"tpl-collab-lock tpl:pointer-events-none tpl:absolute tpl:inset-0 tpl:z-[4] tpl:rounded-sm\"\n :style=\"{\n outline: `2px solid ${getBlockLock(block.id)!.color}`,\n outlineOffset: '-1px',\n }\"\n >\n <span\n class=\"tpl:absolute tpl:-top-0.5 tpl:left-1/2 tpl:z-[5] tpl:flex tpl:-translate-x-1/2 tpl:-translate-y-full tpl:items-center tpl:gap-1 tpl:rounded-full tpl:px-2 tpl:py-0.5 tpl:text-[10px] tpl:font-medium tpl:whitespace-nowrap\"\n :style=\"{\n backgroundColor: getBlockLock(block.id)!.color,\n color: readableTextColor(\n getBlockLock(block.id)!.color,\n ),\n }\"\n >\n <span\n class=\"tpl:inline-flex tpl:size-3 tpl:items-center tpl:justify-center tpl:rounded-full tpl:text-[8px] tpl:font-bold\"\n style=\"\n background-color: color-mix(\n in srgb,\n var(--tpl-bg) 30%,\n transparent\n );\n \"\n >\n {{ getBlockLock(block.id)!.name.charAt(0) }}\n </span>\n {{ getBlockLock(block.id)!.name }}\n </span>\n </div>\n <BlockWrapper\n :block=\"block\"\n :is-selected=\"\n !previewMode &&\n selectedBlockId === block.id &&\n !getBlockLock(block.id)\n \"\n :viewport=\"viewport\"\n :preview-mode=\"previewMode\"\n @select=\"\n previewMode || getBlockLock(block.id)\n ? undefined\n : emit('select-block', block.id)\n \"\n >\n <component\n :is=\"getBlockComponent(block)\"\n :block=\"block\"\n :viewport=\"viewport\"\n @fetch-data=\"handleFetchData(block, $event)\"\n @update=\"\n (updates: Partial<Block>) =>\n editor.updateBlock(block.id, updates)\n \"\n />\n </BlockWrapper>\n </div>\n </div>\n </template>\n <template #footer>\n <div\n v-if=\"blocks.length === 0 && !previewMode\"\n class=\"tpl-canvas-empty tpl:m-6 tpl:flex tpl:min-h-[400px] tpl:flex-col tpl:items-center tpl:justify-center tpl:rounded-xl tpl:border-2 tpl:border-dashed tpl:px-10 tpl:py-12 tpl:text-center tpl:border-[var(--tpl-primary)] tpl:bg-[var(--tpl-bg-elevated)] tpl:font-[var(--tpl-font-family)]\"\n >\n <div\n class=\"tpl-canvas-empty-icon tpl:mb-4 tpl:text-[var(--tpl-primary)]\"\n >\n <SquarePlus :size=\"48\" :stroke-width=\"1\" />\n </div>\n <p\n class=\"tpl-canvas-empty-title tpl:m-0 tpl:mb-2 tpl:text-base tpl:font-semibold tpl:text-[var(--tpl-primary)]\"\n >\n {{ t.canvas.noBlocks }}\n </p>\n <p\n class=\"tpl-canvas-empty-text tpl:m-0 tpl:text-sm tpl:text-[var(--tpl-text-dim)]\"\n >\n {{ t.canvas.dragHint }}\n </p>\n <p\n v-if=\"canUseAiChat && cloudT\"\n class=\"tpl:m-0 tpl:mt-2 tpl:flex tpl:flex-wrap tpl:items-center tpl:justify-center tpl:gap-x-1 tpl:gap-y-0.5 tpl:text-sm tpl:text-[var(--tpl-text-dim)]\"\n >\n {{ t.canvas.aiHintChat }}\n <button\n class=\"tpl:inline-flex tpl:shrink-0 tpl:cursor-pointer tpl:items-center tpl:gap-1 tpl:whitespace-nowrap tpl:rounded-[var(--tpl-radius-sm)] tpl:border-none tpl:px-2 tpl:py-0.5 tpl:text-sm tpl:font-semibold tpl:transition-colors tpl:duration-150 tpl:bg-[var(--tpl-primary-light)] tpl:text-[var(--tpl-primary-hover)]\"\n @click=\"emit('open-ai-chat')\"\n >\n <Sparkles :size=\"14\" :stroke-width=\"2\" />\n {{ cloudT.aiMenu.aiAssistant }}\n </button>\n {{ t.canvas.aiHintChatSuffix }}\n </p>\n <p\n v-if=\"canUseDesignToTemplate && cloudT\"\n class=\"tpl:m-0 tpl:mt-4 tpl:flex tpl:flex-wrap tpl:items-center tpl:justify-center tpl:gap-x-1 tpl:gap-y-0.5 tpl:text-sm tpl:text-[var(--tpl-text-dim)]\"\n >\n {{ t.canvas.aiHintDesign }}\n <button\n class=\"tpl:inline-flex tpl:shrink-0 tpl:cursor-pointer tpl:items-center tpl:gap-1 tpl:whitespace-nowrap tpl:rounded-[var(--tpl-radius-sm)] tpl:border-none tpl:px-2 tpl:py-0.5 tpl:text-sm tpl:font-semibold tpl:transition-colors tpl:duration-150 tpl:bg-[var(--tpl-primary-light)] tpl:text-[var(--tpl-primary-hover)]\"\n @click=\"emit('open-design-reference')\"\n >\n <ImageUp :size=\"14\" :stroke-width=\"2\" />\n {{ cloudT.aiMenu.designToTemplate }}\n </button>\n {{ t.canvas.aiHintDesignSuffix }}\n </p>\n </div>\n </template>\n </draggable>\n </div>\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n.tpl-canvas-step {\n border-color: var(--tpl-border);\n background-color: color-mix(in srgb, var(--tpl-bg) 92%, transparent);\n}\n\n.tpl-canvas-step--active {\n border-color: color-mix(in srgb, var(--tpl-text) 55%, var(--tpl-border));\n background-color: var(--tpl-bg);\n box-shadow: 0 0 0 1px color-mix(in srgb, var(--tpl-text) 12%, transparent);\n}\n\n/* Counter-invert images so they look normal in dark mode */\n.tpl-canvas--dark-mode :deep(img) {\n filter: invert(1) hue-rotate(180deg);\n}\n\n/* Counter-invert editor UI chrome so controls stay readable */\n.tpl-canvas--dark-mode :deep(.tpl-block-actions),\n.tpl-canvas--dark-mode :deep(.tpl-condition-toggle),\n.tpl-canvas--dark-mode :deep(.tpl-block-hidden-overlay) {\n filter: invert(1) hue-rotate(180deg);\n}\n</style>\n\n<!--\n Popup open-animation keyframes. These are intentionally unscoped because the\n animation name is referenced from an inline `:style` binding, which would not\n resolve Vue's scoped-style suffixes. Mirrors the keyframes shipped with the\n runtime popup embed in `packages/editor/index.html`.\n-->\n<style>\n@keyframes tpl-popup-fadeIn {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n\n@keyframes tpl-popup-topToBottom {\n from {\n opacity: 0;\n transform: translateY(-90px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n@keyframes tpl-popup-bottomToTop {\n from {\n opacity: 0;\n transform: translateY(90px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n@keyframes tpl-popup-leftToRight {\n from {\n opacity: 0;\n transform: translateX(-90px);\n }\n to {\n opacity: 1;\n transform: translateX(0);\n }\n}\n\n@keyframes tpl-popup-rightToLeft {\n from {\n opacity: 0;\n transform: translateX(90px);\n }\n to {\n opacity: 1;\n transform: translateX(0);\n }\n}\n\n@keyframes tpl-popup-topLeft {\n from {\n opacity: 0;\n transform: translate(-90px, -90px);\n }\n to {\n opacity: 1;\n transform: translate(0, 0);\n }\n}\n\n@keyframes tpl-popup-topRight {\n from {\n opacity: 0;\n transform: translate(90px, -90px);\n }\n to {\n opacity: 1;\n transform: translate(0, 0);\n }\n}\n\n@keyframes tpl-popup-bottomLeft {\n from {\n opacity: 0;\n transform: translate(-90px, 90px);\n }\n to {\n opacity: 1;\n transform: translate(0, 0);\n }\n}\n\n@keyframes tpl-popup-bottomRight {\n from {\n opacity: 0;\n transform: translate(90px, 90px);\n }\n to {\n opacity: 1;\n transform: translate(0, 0);\n }\n}\n\n@keyframes tpl-popup-smoothTop {\n from {\n opacity: 0;\n transform: translateY(-40px) scale(0.94);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n\n@keyframes tpl-popup-smoothBottom {\n from {\n opacity: 0;\n transform: translateY(40px) scale(0.94);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n\n@keyframes tpl-popup-smoothLeft {\n from {\n opacity: 0;\n transform: translateX(-40px) scale(0.94);\n }\n to {\n opacity: 1;\n transform: translateX(0) scale(1);\n }\n}\n\n@keyframes tpl-popup-smoothRight {\n from {\n opacity: 0;\n transform: translateX(40px) scale(0.94);\n }\n to {\n opacity: 1;\n transform: translateX(0) scale(1);\n }\n}\n\n@keyframes tpl-popup-smoothTopLeft {\n from {\n opacity: 0;\n transform: translate(-40px, -40px) scale(0.94);\n }\n to {\n opacity: 1;\n transform: translate(0, 0) scale(1);\n }\n}\n\n@keyframes tpl-popup-smoothTopRight {\n from {\n opacity: 0;\n transform: translate(40px, -40px) scale(0.94);\n }\n to {\n opacity: 1;\n transform: translate(0, 0) scale(1);\n }\n}\n\n@keyframes tpl-popup-smoothBottomLeft {\n from {\n opacity: 0;\n transform: translate(-40px, 40px) scale(0.94);\n }\n to {\n opacity: 1;\n transform: translate(0, 0) scale(1);\n }\n}\n\n@keyframes tpl-popup-smoothBottomRight {\n from {\n opacity: 0;\n transform: translate(40px, 40px) scale(0.94);\n }\n to {\n opacity: 1;\n transform: translate(0, 0) scale(1);\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport {\n ChevronRight,\n MoreVertical,\n SquarePlus,\n TriangleAlert,\n} from \"@lucide/vue\";\nimport { onClickOutside } from \"@vueuse/core\";\nimport { computed, onMounted, ref } from \"vue\";\nimport type { Block } from \"@aswin.dev/types\";\n\nimport { useI18n } from \"../composables/useI18n\";\nimport { EDITOR_KEY, requireInject } from \"../keys\";\n\nconst props = defineProps<{\n previewMode: boolean;\n multiPageCanvas: boolean;\n}>();\n\nconst { t } = useI18n();\nconst editor = requireInject(EDITOR_KEY, \"CanvasPagesNav\");\n\nconst canvasPagesStrip = computed(() => editor.content.value.canvasPages ?? []);\n\nconst activeCanvasPageId = computed(\n () => editor.content.value.activeCanvasPageId ?? null,\n);\n\nconst stepStripRef = ref<HTMLElement | null>(null);\nconst openStepMenuPageId = ref<string | null>(null);\nconst hoveredUnreachablePageId = ref<string | null>(null);\nconst tooltipPos = ref<{ left: number; top: number } | null>(null);\n\nfunction showUnreachableTooltip(pageId: string, ev: Event): void {\n const target = ev.currentTarget as HTMLElement | null;\n if (!target) return;\n const rect = target.getBoundingClientRect();\n hoveredUnreachablePageId.value = pageId;\n tooltipPos.value = {\n left: rect.left + rect.width / 2,\n top: rect.top,\n };\n}\n\nfunction hideUnreachableTooltip(pageId: string): void {\n if (hoveredUnreachablePageId.value === pageId) {\n hoveredUnreachablePageId.value = null;\n tooltipPos.value = null;\n }\n}\n\nfunction hasNextStepButton(blocks: Block[]): boolean {\n for (const block of blocks) {\n if (block.type === \"button\" && block.clickAction === \"next_step\") {\n return true;\n }\n if (block.type === \"form\" && block.buttonAction === \"next_step\") {\n return true;\n }\n if (block.type === \"section\") {\n for (const column of block.children) {\n if (hasNextStepButton(column)) {\n return true;\n }\n }\n }\n }\n return false;\n}\n\nconst unreachablePageIds = computed<Set<string>>(() => {\n const pages = canvasPagesStrip.value;\n const result = new Set<string>();\n for (let i = 1; i < pages.length; i += 1) {\n if (!hasNextStepButton(pages[i - 1].blocks)) {\n result.add(pages[i].id);\n }\n }\n return result;\n});\n\nonClickOutside(stepStripRef, () => {\n openStepMenuPageId.value = null;\n});\n\nonMounted(() => {\n if (props.multiPageCanvas) {\n editor.ensureCanvasPages();\n }\n});\n\nconst show = computed(\n () => props.multiPageCanvas && canvasPagesStrip.value.length > 0,\n);\n\nfunction selectCanvasPage(pageId: string): void {\n if (pageId === activeCanvasPageId.value) {\n return;\n }\n openStepMenuPageId.value = null;\n editor.switchCanvasPage(pageId);\n}\n\nfunction toggleStepMenu(pageId: string): void {\n openStepMenuPageId.value =\n openStepMenuPageId.value === pageId ? null : pageId;\n}\n\nfunction handleAddCanvasPage(): void {\n openStepMenuPageId.value = null;\n editor.addCanvasPage();\n}\n\nfunction handleRenameCanvasPage(pageId: string, currentTitle: string): void {\n const next = window.prompt(t.canvas.pages.renamePrompt, currentTitle);\n if (next === null) {\n return;\n }\n editor.renameCanvasPage(pageId, next);\n openStepMenuPageId.value = null;\n}\n\nfunction handleRemoveCanvasPage(pageId: string): void {\n if (canvasPagesStrip.value.length <= 1) {\n return;\n }\n editor.removeCanvasPage(pageId);\n openStepMenuPageId.value = null;\n}\n</script>\n\n<template>\n <nav\n v-if=\"show\"\n ref=\"stepStripRef\"\n class=\"tpl-canvas-steps-nav tpl:flex tpl:w-full tpl:flex-wrap tpl:items-center tpl:gap-1 tpl:rounded-lg tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:px-3 tpl:py-2.5 tpl:shadow-[var(--tpl-shadow-md)] tpl:font-[var(--tpl-font-family)]\"\n role=\"tablist\"\n :aria-label=\"t.canvas.pages.ariaStrip\"\n >\n <template v-for=\"(page, idx) in canvasPagesStrip\" :key=\"page.id\">\n <ChevronRight\n v-if=\"idx > 0\"\n :size=\"16\"\n :stroke-width=\"2\"\n class=\"tpl:pointer-events-none tpl:mx-0.5 tpl:text-[var(--tpl-text-muted)]\"\n aria-hidden=\"true\"\n />\n <div class=\"tpl:relative tpl:inline-flex tpl:items-stretch\">\n <div\n class=\"tpl:inline-flex tpl:items-center tpl:overflow-hidden tpl:rounded-full tpl:border tpl:text-sm tpl:font-semibold tpl:transition-colors tpl:duration-150\"\n :class=\"\n activeCanvasPageId === page.id\n ? 'tpl-canvas-step tpl-canvas-step--active'\n : 'tpl-canvas-step'\n \"\n >\n <button\n type=\"button\"\n role=\"tab\"\n class=\"tpl:max-w-[200px] tpl:truncate tpl:border-none tpl:bg-transparent tpl:px-3 tpl:py-1.5 tpl:text-left tpl:text-[var(--tpl-text)] tpl:outline-none tpl:transition-colors tpl:duration-150 focus-visible:tpl-ring-2 focus-visible:tpl-ring-[var(--tpl-primary)] focus-visible:tpl-ring-offset-2\"\n :aria-selected=\"activeCanvasPageId === page.id\"\n :tabindex=\"activeCanvasPageId === page.id ? 0 : -1\"\n @click=\"selectCanvasPage(page.id)\"\n >\n {{ page.title }}\n </button>\n <span\n v-if=\"unreachablePageIds.has(page.id)\"\n class=\"tpl-canvas-step-warning tpl:flex tpl:shrink-0 tpl:cursor-help tpl:items-center tpl:justify-center tpl:border-0 tpl:border-l tpl:border-solid tpl:border-[var(--tpl-border)] tpl:px-2 tpl:outline-none\"\n tabindex=\"0\"\n role=\"img\"\n :aria-label=\"t.canvas.pages.unreachableLabel\"\n @mouseenter=\"showUnreachableTooltip(page.id, $event)\"\n @mouseleave=\"hideUnreachableTooltip(page.id)\"\n @focus=\"showUnreachableTooltip(page.id, $event)\"\n @blur=\"hideUnreachableTooltip(page.id)\"\n >\n <TriangleAlert :size=\"16\" :stroke-width=\"2\" />\n </span>\n <button\n v-if=\"!previewMode\"\n type=\"button\"\n class=\"tpl:flex tpl:shrink-0 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:border-0 tpl:border-l tpl:border-solid tpl:border-[var(--tpl-border)] tpl:bg-transparent tpl:px-2 tpl:text-[var(--tpl-text-muted)] tpl:outline-none tpl:transition-colors tpl:duration-150 hover:tpl:text-[var(--tpl-text)] focus-visible:tpl-ring-2 focus-visible:tpl-ring-inset focus-visible:tpl-ring-[var(--tpl-primary)]\"\n :aria-expanded=\"openStepMenuPageId === page.id\"\n :aria-haspopup=\"true\"\n :aria-label=\"`${page.title}: ${t.canvas.pages.stepActions}`\"\n @click.stop=\"toggleStepMenu(page.id)\"\n >\n <MoreVertical :size=\"16\" :stroke-width=\"2\" />\n </button>\n </div>\n <div\n v-if=\"openStepMenuPageId === page.id && !previewMode\"\n class=\"tpl:absolute tpl:top-full tpl:right-0 tpl:z-[60] tpl:mt-1 tpl:min-w-[10rem] tpl:rounded-lg tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:py-1 tpl:shadow-lg\"\n role=\"menu\"\n >\n <button\n type=\"button\"\n role=\"menuitem\"\n class=\"tpl:flex tpl:w-full tpl:cursor-pointer tpl:items-center tpl:border-none tpl:bg-transparent tpl:px-3 tpl:py-2 tpl:text-left tpl:text-sm tpl:text-[var(--tpl-text)] tpl:outline-none hover:tpl:bg-[var(--tpl-bg-elevated)]\"\n @click=\"handleRenameCanvasPage(page.id, page.title)\"\n >\n {{ t.canvas.pages.rename }}\n </button>\n <button\n v-if=\"canvasPagesStrip.length > 1\"\n type=\"button\"\n role=\"menuitem\"\n class=\"tpl:flex tpl:w-full tpl:cursor-pointer tpl:items-center tpl:border-none tpl:bg-transparent tpl:px-3 tpl:py-2 tpl:text-left tpl:text-sm tpl:text-[var(--tpl-danger)] tpl:outline-none hover:tpl:bg-[var(--tpl-bg-elevated)]\"\n @click=\"handleRemoveCanvasPage(page.id)\"\n >\n {{ t.canvas.pages.delete }}\n </button>\n </div>\n </div>\n </template>\n <button\n v-if=\"!previewMode\"\n type=\"button\"\n class=\"tpl:ml-1 tpl:inline-flex tpl:cursor-pointer tpl:items-center tpl:gap-1 tpl:rounded-full tpl:border tpl:border-dashed tpl:border-[var(--tpl-border)] tpl:bg-transparent tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-semibold tpl:text-[var(--tpl-primary)] tpl:outline-none tpl:transition-colors tpl:duration-150 hover:tpl:border-[var(--tpl-primary)] hover:tpl:bg-[var(--tpl-primary-light)] focus-visible:tpl-ring-2 focus-visible:tpl-ring-[var(--tpl-primary)]\"\n @click=\"handleAddCanvasPage\"\n >\n <SquarePlus :size=\"14\" :stroke-width=\"2\" />\n {{ t.canvas.pages.addStep }}\n </button>\n </nav>\n <Teleport to=\"body\">\n <div\n v-if=\"hoveredUnreachablePageId && tooltipPos\"\n role=\"tooltip\"\n class=\"tpl-canvas-step-warning-tooltip\"\n :style=\"{\n left: `${tooltipPos.left}px`,\n top: `${tooltipPos.top}px`,\n }\"\n >\n {{ t.canvas.pages.unreachableTooltip }}\n </div>\n </Teleport>\n</template>\n\n<style scoped>\n.tpl-canvas-step {\n border-color: var(--tpl-border);\n background-color: color-mix(in srgb, var(--tpl-bg) 92%, transparent);\n}\n\n.tpl-canvas-step--active {\n border-color: color-mix(in srgb, var(--tpl-text) 55%, var(--tpl-border));\n background-color: var(--tpl-bg);\n box-shadow: 0 0 0 1px color-mix(in srgb, var(--tpl-text) 12%, transparent);\n}\n\n.tpl-canvas-step-warning {\n color: #d97706;\n}\n\n.tpl-canvas-step-warning:focus-visible {\n outline: 2px solid var(--tpl-primary);\n outline-offset: -2px;\n border-radius: 9999px;\n}\n</style>\n\n<style>\n.tpl-canvas-step-warning-tooltip {\n position: fixed;\n z-index: 99999;\n width: 16rem;\n padding: 0.5rem 0.75rem;\n border-radius: 0.5rem;\n background-color: #1f2937;\n color: #ffffff;\n font-size: 0.75rem;\n font-weight: 500;\n line-height: 1.5;\n pointer-events: none;\n white-space: normal;\n transform: translate(-50%, calc(-100% - 8px));\n box-shadow:\n 0 10px 15px -3px rgba(0, 0, 0, 0.1),\n 0 4px 6px -4px rgba(0, 0, 0, 0.1);\n}\n\n.tpl-canvas-step-warning-tooltip::after {\n content: \"\";\n position: absolute;\n top: 100%;\n left: 50%;\n transform: translateX(-50%);\n border: 6px solid transparent;\n border-top-color: #1f2937;\n}\n</style>\n","<script setup lang=\"ts\">\nimport {\n ChevronRight,\n MoreVertical,\n SquarePlus,\n TriangleAlert,\n} from \"@lucide/vue\";\nimport { onClickOutside } from \"@vueuse/core\";\nimport { computed, onMounted, ref } from \"vue\";\nimport type { Block } from \"@aswin.dev/types\";\n\nimport { useI18n } from \"../composables/useI18n\";\nimport { EDITOR_KEY, requireInject } from \"../keys\";\n\nconst props = defineProps<{\n previewMode: boolean;\n multiPageCanvas: boolean;\n}>();\n\nconst { t } = useI18n();\nconst editor = requireInject(EDITOR_KEY, \"CanvasPagesNav\");\n\nconst canvasPagesStrip = computed(() => editor.content.value.canvasPages ?? []);\n\nconst activeCanvasPageId = computed(\n () => editor.content.value.activeCanvasPageId ?? null,\n);\n\nconst stepStripRef = ref<HTMLElement | null>(null);\nconst openStepMenuPageId = ref<string | null>(null);\nconst hoveredUnreachablePageId = ref<string | null>(null);\nconst tooltipPos = ref<{ left: number; top: number } | null>(null);\n\nfunction showUnreachableTooltip(pageId: string, ev: Event): void {\n const target = ev.currentTarget as HTMLElement | null;\n if (!target) return;\n const rect = target.getBoundingClientRect();\n hoveredUnreachablePageId.value = pageId;\n tooltipPos.value = {\n left: rect.left + rect.width / 2,\n top: rect.top,\n };\n}\n\nfunction hideUnreachableTooltip(pageId: string): void {\n if (hoveredUnreachablePageId.value === pageId) {\n hoveredUnreachablePageId.value = null;\n tooltipPos.value = null;\n }\n}\n\nfunction hasNextStepButton(blocks: Block[]): boolean {\n for (const block of blocks) {\n if (block.type === \"button\" && block.clickAction === \"next_step\") {\n return true;\n }\n if (block.type === \"form\" && block.buttonAction === \"next_step\") {\n return true;\n }\n if (block.type === \"section\") {\n for (const column of block.children) {\n if (hasNextStepButton(column)) {\n return true;\n }\n }\n }\n }\n return false;\n}\n\nconst unreachablePageIds = computed<Set<string>>(() => {\n const pages = canvasPagesStrip.value;\n const result = new Set<string>();\n for (let i = 1; i < pages.length; i += 1) {\n if (!hasNextStepButton(pages[i - 1].blocks)) {\n result.add(pages[i].id);\n }\n }\n return result;\n});\n\nonClickOutside(stepStripRef, () => {\n openStepMenuPageId.value = null;\n});\n\nonMounted(() => {\n if (props.multiPageCanvas) {\n editor.ensureCanvasPages();\n }\n});\n\nconst show = computed(\n () => props.multiPageCanvas && canvasPagesStrip.value.length > 0,\n);\n\nfunction selectCanvasPage(pageId: string): void {\n if (pageId === activeCanvasPageId.value) {\n return;\n }\n openStepMenuPageId.value = null;\n editor.switchCanvasPage(pageId);\n}\n\nfunction toggleStepMenu(pageId: string): void {\n openStepMenuPageId.value =\n openStepMenuPageId.value === pageId ? null : pageId;\n}\n\nfunction handleAddCanvasPage(): void {\n openStepMenuPageId.value = null;\n editor.addCanvasPage();\n}\n\nfunction handleRenameCanvasPage(pageId: string, currentTitle: string): void {\n const next = window.prompt(t.canvas.pages.renamePrompt, currentTitle);\n if (next === null) {\n return;\n }\n editor.renameCanvasPage(pageId, next);\n openStepMenuPageId.value = null;\n}\n\nfunction handleRemoveCanvasPage(pageId: string): void {\n if (canvasPagesStrip.value.length <= 1) {\n return;\n }\n editor.removeCanvasPage(pageId);\n openStepMenuPageId.value = null;\n}\n</script>\n\n<template>\n <nav\n v-if=\"show\"\n ref=\"stepStripRef\"\n class=\"tpl-canvas-steps-nav tpl:flex tpl:w-full tpl:flex-wrap tpl:items-center tpl:gap-1 tpl:rounded-lg tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:px-3 tpl:py-2.5 tpl:shadow-[var(--tpl-shadow-md)] tpl:font-[var(--tpl-font-family)]\"\n role=\"tablist\"\n :aria-label=\"t.canvas.pages.ariaStrip\"\n >\n <template v-for=\"(page, idx) in canvasPagesStrip\" :key=\"page.id\">\n <ChevronRight\n v-if=\"idx > 0\"\n :size=\"16\"\n :stroke-width=\"2\"\n class=\"tpl:pointer-events-none tpl:mx-0.5 tpl:text-[var(--tpl-text-muted)]\"\n aria-hidden=\"true\"\n />\n <div class=\"tpl:relative tpl:inline-flex tpl:items-stretch\">\n <div\n class=\"tpl:inline-flex tpl:items-center tpl:overflow-hidden tpl:rounded-full tpl:border tpl:text-sm tpl:font-semibold tpl:transition-colors tpl:duration-150\"\n :class=\"\n activeCanvasPageId === page.id\n ? 'tpl-canvas-step tpl-canvas-step--active'\n : 'tpl-canvas-step'\n \"\n >\n <button\n type=\"button\"\n role=\"tab\"\n class=\"tpl:max-w-[200px] tpl:truncate tpl:border-none tpl:bg-transparent tpl:px-3 tpl:py-1.5 tpl:text-left tpl:text-[var(--tpl-text)] tpl:outline-none tpl:transition-colors tpl:duration-150 focus-visible:tpl-ring-2 focus-visible:tpl-ring-[var(--tpl-primary)] focus-visible:tpl-ring-offset-2\"\n :aria-selected=\"activeCanvasPageId === page.id\"\n :tabindex=\"activeCanvasPageId === page.id ? 0 : -1\"\n @click=\"selectCanvasPage(page.id)\"\n >\n {{ page.title }}\n </button>\n <span\n v-if=\"unreachablePageIds.has(page.id)\"\n class=\"tpl-canvas-step-warning tpl:flex tpl:shrink-0 tpl:cursor-help tpl:items-center tpl:justify-center tpl:border-0 tpl:border-l tpl:border-solid tpl:border-[var(--tpl-border)] tpl:px-2 tpl:outline-none\"\n tabindex=\"0\"\n role=\"img\"\n :aria-label=\"t.canvas.pages.unreachableLabel\"\n @mouseenter=\"showUnreachableTooltip(page.id, $event)\"\n @mouseleave=\"hideUnreachableTooltip(page.id)\"\n @focus=\"showUnreachableTooltip(page.id, $event)\"\n @blur=\"hideUnreachableTooltip(page.id)\"\n >\n <TriangleAlert :size=\"16\" :stroke-width=\"2\" />\n </span>\n <button\n v-if=\"!previewMode\"\n type=\"button\"\n class=\"tpl:flex tpl:shrink-0 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:border-0 tpl:border-l tpl:border-solid tpl:border-[var(--tpl-border)] tpl:bg-transparent tpl:px-2 tpl:text-[var(--tpl-text-muted)] tpl:outline-none tpl:transition-colors tpl:duration-150 hover:tpl:text-[var(--tpl-text)] focus-visible:tpl-ring-2 focus-visible:tpl-ring-inset focus-visible:tpl-ring-[var(--tpl-primary)]\"\n :aria-expanded=\"openStepMenuPageId === page.id\"\n :aria-haspopup=\"true\"\n :aria-label=\"`${page.title}: ${t.canvas.pages.stepActions}`\"\n @click.stop=\"toggleStepMenu(page.id)\"\n >\n <MoreVertical :size=\"16\" :stroke-width=\"2\" />\n </button>\n </div>\n <div\n v-if=\"openStepMenuPageId === page.id && !previewMode\"\n class=\"tpl:absolute tpl:top-full tpl:right-0 tpl:z-[60] tpl:mt-1 tpl:min-w-[10rem] tpl:rounded-lg tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:py-1 tpl:shadow-lg\"\n role=\"menu\"\n >\n <button\n type=\"button\"\n role=\"menuitem\"\n class=\"tpl:flex tpl:w-full tpl:cursor-pointer tpl:items-center tpl:border-none tpl:bg-transparent tpl:px-3 tpl:py-2 tpl:text-left tpl:text-sm tpl:text-[var(--tpl-text)] tpl:outline-none hover:tpl:bg-[var(--tpl-bg-elevated)]\"\n @click=\"handleRenameCanvasPage(page.id, page.title)\"\n >\n {{ t.canvas.pages.rename }}\n </button>\n <button\n v-if=\"canvasPagesStrip.length > 1\"\n type=\"button\"\n role=\"menuitem\"\n class=\"tpl:flex tpl:w-full tpl:cursor-pointer tpl:items-center tpl:border-none tpl:bg-transparent tpl:px-3 tpl:py-2 tpl:text-left tpl:text-sm tpl:text-[var(--tpl-danger)] tpl:outline-none hover:tpl:bg-[var(--tpl-bg-elevated)]\"\n @click=\"handleRemoveCanvasPage(page.id)\"\n >\n {{ t.canvas.pages.delete }}\n </button>\n </div>\n </div>\n </template>\n <button\n v-if=\"!previewMode\"\n type=\"button\"\n class=\"tpl:ml-1 tpl:inline-flex tpl:cursor-pointer tpl:items-center tpl:gap-1 tpl:rounded-full tpl:border tpl:border-dashed tpl:border-[var(--tpl-border)] tpl:bg-transparent tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-semibold tpl:text-[var(--tpl-primary)] tpl:outline-none tpl:transition-colors tpl:duration-150 hover:tpl:border-[var(--tpl-primary)] hover:tpl:bg-[var(--tpl-primary-light)] focus-visible:tpl-ring-2 focus-visible:tpl-ring-[var(--tpl-primary)]\"\n @click=\"handleAddCanvasPage\"\n >\n <SquarePlus :size=\"14\" :stroke-width=\"2\" />\n {{ t.canvas.pages.addStep }}\n </button>\n </nav>\n <Teleport to=\"body\">\n <div\n v-if=\"hoveredUnreachablePageId && tooltipPos\"\n role=\"tooltip\"\n class=\"tpl-canvas-step-warning-tooltip\"\n :style=\"{\n left: `${tooltipPos.left}px`,\n top: `${tooltipPos.top}px`,\n }\"\n >\n {{ t.canvas.pages.unreachableTooltip }}\n </div>\n </Teleport>\n</template>\n\n<style scoped>\n.tpl-canvas-step {\n border-color: var(--tpl-border);\n background-color: color-mix(in srgb, var(--tpl-bg) 92%, transparent);\n}\n\n.tpl-canvas-step--active {\n border-color: color-mix(in srgb, var(--tpl-text) 55%, var(--tpl-border));\n background-color: var(--tpl-bg);\n box-shadow: 0 0 0 1px color-mix(in srgb, var(--tpl-text) 12%, transparent);\n}\n\n.tpl-canvas-step-warning {\n color: #d97706;\n}\n\n.tpl-canvas-step-warning:focus-visible {\n outline: 2px solid var(--tpl-primary);\n outline-offset: -2px;\n border-radius: 9999px;\n}\n</style>\n\n<style>\n.tpl-canvas-step-warning-tooltip {\n position: fixed;\n z-index: 99999;\n width: 16rem;\n padding: 0.5rem 0.75rem;\n border-radius: 0.5rem;\n background-color: #1f2937;\n color: #ffffff;\n font-size: 0.75rem;\n font-weight: 500;\n line-height: 1.5;\n pointer-events: none;\n white-space: normal;\n transform: translate(-50%, calc(-100% - 8px));\n box-shadow:\n 0 10px 15px -3px rgba(0, 0, 0, 0.1),\n 0 4px 6px -4px rgba(0, 0, 0, 0.1);\n}\n\n.tpl-canvas-step-warning-tooltip::after {\n content: \"\";\n position: absolute;\n top: 100%;\n left: 50%;\n transform: translateX(-50%);\n border: 6px solid transparent;\n border-top-color: #1f2937;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { useId } from \"vue\";\n\nwithDefaults(\n defineProps<{\n checked: boolean;\n /** `neutral`: charcoal track when on (schedule-style). Default orange (design). */\n tone?: \"brand\" | \"neutral\";\n }>(),\n { tone: \"brand\" },\n);\n\nconst emit = defineEmits<{\n toggle: [];\n}>();\n\nconst switchId = useId();\n\nfunction onNativeChange(): void {\n emit(\"toggle\");\n}\n</script>\n\n<template>\n <!-- Structure adapted from Uiverse.io by zanina-yassine (checkbox + track + thumb) -->\n <div\n class=\"popup-design-switch__shell\"\n :class=\"{ 'popup-design-switch__shell--neutral': tone === 'neutral' }\"\n >\n <input\n :id=\"switchId\"\n type=\"checkbox\"\n class=\"popup-design-switch__native\"\n :checked=\"checked\"\n @change=\"onNativeChange\"\n />\n <label class=\"popup-design-switch__track\" :for=\"switchId\">\n <span class=\"popup-design-switch__thumb\" aria-hidden=\"true\" />\n </label>\n </div>\n</template>\n\n<style scoped>\n/* From Uiverse.io by zanina-yassine — scoped + BEM class names for this component */\n\n.popup-design-switch__shell {\n width: 44px;\n height: 26px;\n position: relative;\n flex-shrink: 0;\n vertical-align: middle;\n}\n\n.popup-design-switch__native {\n opacity: 0;\n width: 0;\n height: 0;\n position: absolute;\n}\n\n.popup-design-switch__track {\n width: 100%;\n height: 100%;\n display: block;\n background-color: #e9e9eb;\n border-radius: 13px;\n cursor: pointer;\n transition: all 0.2s ease-out;\n}\n\n.popup-design-switch__thumb {\n width: 22px;\n height: 22px;\n position: absolute;\n left: calc(50% - 22px / 2 - 8px);\n top: calc(50% - 22px / 2);\n border-radius: 50%;\n background: #ffffff;\n box-shadow:\n 0px 2px 6px rgba(0, 0, 0, 0.14),\n 0px 2px 1px rgba(0, 0, 0, 0.06);\n transition: all 0.2s ease-out;\n cursor: pointer;\n pointer-events: none;\n}\n\n.popup-design-switch__native:checked + .popup-design-switch__track {\n background-color: #e87e26;\n}\n\n.popup-design-switch__native:checked\n + .popup-design-switch__track\n .popup-design-switch__thumb {\n left: calc(50% - 22px / 2 + 8px);\n top: calc(50% - 22px / 2);\n}\n\n.popup-design-switch__native:focus {\n outline: none;\n}\n\n.popup-design-switch__native:focus-visible + .popup-design-switch__track {\n outline: 2px solid #e87e26;\n outline-offset: 2px;\n}\n\n.popup-design-switch__shell--neutral\n .popup-design-switch__native:checked\n + .popup-design-switch__track {\n background-color: #3b3b3e;\n}\n\n.popup-design-switch__shell--neutral\n .popup-design-switch__native:focus-visible\n + .popup-design-switch__track {\n outline: 2px solid #6a6a6f;\n outline-offset: 2px;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { useId } from \"vue\";\n\nwithDefaults(\n defineProps<{\n checked: boolean;\n /** `neutral`: charcoal track when on (schedule-style). Default orange (design). */\n tone?: \"brand\" | \"neutral\";\n }>(),\n { tone: \"brand\" },\n);\n\nconst emit = defineEmits<{\n toggle: [];\n}>();\n\nconst switchId = useId();\n\nfunction onNativeChange(): void {\n emit(\"toggle\");\n}\n</script>\n\n<template>\n <!-- Structure adapted from Uiverse.io by zanina-yassine (checkbox + track + thumb) -->\n <div\n class=\"popup-design-switch__shell\"\n :class=\"{ 'popup-design-switch__shell--neutral': tone === 'neutral' }\"\n >\n <input\n :id=\"switchId\"\n type=\"checkbox\"\n class=\"popup-design-switch__native\"\n :checked=\"checked\"\n @change=\"onNativeChange\"\n />\n <label class=\"popup-design-switch__track\" :for=\"switchId\">\n <span class=\"popup-design-switch__thumb\" aria-hidden=\"true\" />\n </label>\n </div>\n</template>\n\n<style scoped>\n/* From Uiverse.io by zanina-yassine — scoped + BEM class names for this component */\n\n.popup-design-switch__shell {\n width: 44px;\n height: 26px;\n position: relative;\n flex-shrink: 0;\n vertical-align: middle;\n}\n\n.popup-design-switch__native {\n opacity: 0;\n width: 0;\n height: 0;\n position: absolute;\n}\n\n.popup-design-switch__track {\n width: 100%;\n height: 100%;\n display: block;\n background-color: #e9e9eb;\n border-radius: 13px;\n cursor: pointer;\n transition: all 0.2s ease-out;\n}\n\n.popup-design-switch__thumb {\n width: 22px;\n height: 22px;\n position: absolute;\n left: calc(50% - 22px / 2 - 8px);\n top: calc(50% - 22px / 2);\n border-radius: 50%;\n background: #ffffff;\n box-shadow:\n 0px 2px 6px rgba(0, 0, 0, 0.14),\n 0px 2px 1px rgba(0, 0, 0, 0.06);\n transition: all 0.2s ease-out;\n cursor: pointer;\n pointer-events: none;\n}\n\n.popup-design-switch__native:checked + .popup-design-switch__track {\n background-color: #e87e26;\n}\n\n.popup-design-switch__native:checked\n + .popup-design-switch__track\n .popup-design-switch__thumb {\n left: calc(50% - 22px / 2 + 8px);\n top: calc(50% - 22px / 2);\n}\n\n.popup-design-switch__native:focus {\n outline: none;\n}\n\n.popup-design-switch__native:focus-visible + .popup-design-switch__track {\n outline: 2px solid #e87e26;\n outline-offset: 2px;\n}\n\n.popup-design-switch__shell--neutral\n .popup-design-switch__native:checked\n + .popup-design-switch__track {\n background-color: #3b3b3e;\n}\n\n.popup-design-switch__shell--neutral\n .popup-design-switch__native:focus-visible\n + .popup-design-switch__track {\n outline: 2px solid #6a6a6f;\n outline-offset: 2px;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { Image, Pencil, X } from \"@lucide/vue\";\nimport { computed, inject, ref } from \"vue\";\nimport { useI18n } from \"../composables/useI18n\";\nimport { ON_REQUEST_MEDIA_KEY } from \"../keys\";\n\nconst props = defineProps<{\n imageUrl: string;\n}>();\n\nconst emit = defineEmits<{\n \"set-url\": [url: string, templateWidth?: number];\n}>();\n\nconst { t } = useI18n();\n\n/**\n * Host-supplied media picker (set via editor `init({ onRequestMedia })`).\n * When wired, all overlay-image picking is delegated to the host so consumers\n * can upload to their own storage and return a public URL — no data-URL\n * round-trip is stored in the template JSON. Falls back to a local\n * FileReader → data-URL flow when no callback is provided so the picker still\n * works in standalone setups.\n */\nconst onRequestMedia = inject(ON_REQUEST_MEDIA_KEY, null);\nconst useHostPicker = computed(() => onRequestMedia !== null);\n\nconst fileRef = ref<HTMLInputElement | null>(null);\nconst isDragging = ref(false);\n\nconst MAX_DATA_URL_CHARS = 2_400_000;\n\nfunction pickFiles(files: FileList | File[] | null): void {\n const file = files?.[0];\n if (!file || !file.type.startsWith(\"image/\")) return;\n const reader = new FileReader();\n reader.onload = () => {\n const url = typeof reader.result === \"string\" ? reader.result : \"\";\n if (url.length > MAX_DATA_URL_CHARS) return;\n emit(\"set-url\", url);\n if (fileRef.value) fileRef.value.value = \"\";\n };\n reader.readAsDataURL(file);\n}\n\nasync function browseMedia(): Promise<void> {\n const result = await onRequestMedia?.({ accept: [\"images\"] });\n if (result?.url) {\n emit(\"set-url\", result.url, result.templateWidth);\n }\n}\n\nfunction onDrop(e: DragEvent): void {\n isDragging.value = false;\n if (useHostPicker.value) {\n // Host owns the upload pipeline — defer to it instead of reading a local\n // data URL we'd then need to discard.\n void browseMedia();\n return;\n }\n pickFiles(e.dataTransfer?.files ?? null);\n}\n\nfunction onBrowse(): void {\n if (useHostPicker.value) {\n void browseMedia();\n return;\n }\n fileRef.value?.click();\n}\n\nfunction onReplace(): void {\n if (useHostPicker.value) {\n void browseMedia();\n return;\n }\n fileRef.value?.click();\n}\n\nfunction onRemove(): void {\n emit(\"set-url\", \"\");\n}\n</script>\n\n<template>\n <div\n class=\"tpl:mt-4 tpl:rounded-lg tpl:border tpl:border-dashed tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:p-3\"\n role=\"region\"\n :aria-label=\"t.popupDesign.overlayImagePanelAriaLabel\"\n >\n <input\n ref=\"fileRef\"\n type=\"file\"\n class=\"tpl:sr-only\"\n tabindex=\"-1\"\n accept=\"image/jpeg,image/png,image/gif,image/webp,image/avif,image/svg+xml\"\n @change=\"pickFiles(($event.target as HTMLInputElement).files)\"\n />\n\n <template v-if=\"!props.imageUrl\">\n <button\n type=\"button\"\n class=\"tpl:flex tpl:w-full tpl:flex-col tpl:items-center tpl:justify-center tpl:gap-2 tpl:rounded-md tpl:border-none tpl:bg-transparent tpl:px-3 tpl:py-8 tpl:text-center tpl:text-[var(--tpl-text)] tpl:outline-none tpl:transition-colors hover:tpl:bg-[var(--tpl-bg-hover)] focus-visible:tpl:ring-2 focus-visible:tpl:ring-[var(--tpl-primary)] focus-visible:tpl:ring-offset-2 focus-visible:tpl:ring-offset-[var(--tpl-bg)]\"\n :class=\"\n isDragging\n ? 'tpl:bg-[var(--tpl-primary-light)] tpl:text-[var(--tpl-primary)]'\n : ''\n \"\n @dragenter.prevent=\"isDragging = true\"\n @dragleave.prevent=\"isDragging = false\"\n @dragover.prevent=\"isDragging = true\"\n @drop.prevent=\"onDrop\"\n @click=\"onBrowse\"\n >\n <Image\n :size=\"28\"\n :stroke-width=\"1.5\"\n class=\"tpl:text-[var(--tpl-text-muted)]\"\n />\n <span class=\"tpl:text-xs tpl:leading-snug tpl:text-[var(--tpl-text)]\">\n {{ t.popupDesign.overlayImageDropLineBefore }}\n <span class=\"tpl:underline tpl:decoration-[var(--tpl-text-muted)]\">{{\n t.popupDesign.overlayImageBrowse\n }}</span>\n {{ t.popupDesign.overlayImageDropLineAfter }}\n </span>\n <span class=\"tpl:text-[11px] tpl:text-[var(--tpl-text-muted)]\">{{\n t.popupDesign.overlayImageFormats\n }}</span>\n </button>\n </template>\n\n <template v-else>\n <div class=\"tpl:mb-3 tpl:flex tpl:gap-2\">\n <div\n class=\"tpl:flex tpl:min-h-0 tpl:flex-1 tpl:flex-col tpl:overflow-hidden tpl:rounded-lg tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-elevated)]\"\n role=\"img\"\n :aria-label=\"t.popupDesign.overlayImageDesktopPreview\"\n >\n <div\n class=\"tpl:flex tpl:gap-1 tpl:border-b tpl:border-[var(--tpl-border)] tpl:px-2 tpl:py-1.5\"\n >\n <span\n class=\"tpl:h-1.5 tpl:w-1.5 tpl:rounded-full tpl:bg-[var(--tpl-border-light)]\"\n />\n <span\n class=\"tpl:h-1.5 tpl:w-1.5 tpl:rounded-full tpl:bg-[var(--tpl-border-light)]\"\n />\n <span\n class=\"tpl:h-1.5 tpl:w-1.5 tpl:rounded-full tpl:bg-[var(--tpl-border-light)]\"\n />\n </div>\n <div\n class=\"tpl:flex tpl:aspect-[16/9] tpl:max-h-[72px] tpl:items-center tpl:justify-center tpl:bg-[var(--tpl-bg)] tpl:p-1\"\n >\n <img\n :src=\"props.imageUrl\"\n alt=\"\"\n class=\"tpl:max-h-full tpl:max-w-full tpl:rounded tpl:object-contain\"\n />\n </div>\n </div>\n <div\n class=\"tpl:flex tpl:w-[52px] tpl:shrink-0 tpl:flex-col tpl:overflow-hidden tpl:rounded-lg tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-elevated)]\"\n role=\"img\"\n :aria-label=\"t.popupDesign.overlayImageMobilePreview\"\n >\n <div\n class=\"tpl:flex tpl:gap-0.5 tpl:border-b tpl:border-[var(--tpl-border)] tpl:px-1 tpl:py-1\"\n >\n <span\n class=\"tpl:h-1 tpl:w-1 tpl:rounded-full tpl:bg-[var(--tpl-border-light)]\"\n />\n <span\n class=\"tpl:h-1 tpl:w-1 tpl:rounded-full tpl:bg-[var(--tpl-border-light)]\"\n />\n <span\n class=\"tpl:h-1 tpl:w-1 tpl:rounded-full tpl:bg-[var(--tpl-border-light)]\"\n />\n </div>\n <div\n class=\"tpl:flex tpl:aspect-[9/14] tpl:max-h-[72px] tpl:items-center tpl:justify-center tpl:bg-[var(--tpl-bg)] tpl:p-0.5\"\n >\n <img\n :src=\"props.imageUrl\"\n alt=\"\"\n class=\"tpl:max-h-full tpl:max-w-full tpl:rounded-sm tpl:object-cover\"\n />\n </div>\n </div>\n </div>\n\n <div class=\"tpl:flex tpl:flex-wrap tpl:gap-2\">\n <!-- <button\n type=\"button\"\n class=\"tpl:inline-flex tpl:flex-1 tpl:items-center tpl:justify-center tpl:gap-1.5 tpl:rounded-lg tpl:border tpl:border-[var(--tpl-border)] tpl:bg-transparent tpl:px-2 tpl:py-2 tpl:text-[11px] tpl:font-medium tpl:text-[var(--tpl-text-muted)] tpl:transition-colors hover:tpl:border-[var(--tpl-text-muted)] hover:tpl:text-[var(--tpl-text)]\"\n @click=\"onCrop\"\n >\n <Crop :size=\"14\" :stroke-width=\"1.75\" />\n {{ t.popupDesign.overlayImageCrop }}\n </button> -->\n <button\n type=\"button\"\n class=\"tpl:inline-flex tpl:flex-1 tpl:items-center tpl:justify-center tpl:gap-1.5 tpl:rounded-lg tpl:border tpl:border-[var(--tpl-border)] tpl:bg-transparent tpl:px-2 tpl:py-2 tpl:text-[11px] tpl:font-medium tpl:text-[var(--tpl-text-muted)] tpl:transition-colors hover:tpl:border-[var(--tpl-text-muted)] hover:tpl:text-[var(--tpl-text)]\"\n @click=\"onReplace\"\n >\n <Pencil :size=\"14\" :stroke-width=\"1.75\" />\n {{ t.popupDesign.overlayImageReplace }}\n </button>\n <button\n type=\"button\"\n class=\"tpl:inline-flex tpl:flex-1 tpl:items-center tpl:justify-center tpl:gap-1.5 tpl:rounded-lg tpl:border tpl:border-[var(--tpl-border)] tpl:bg-transparent tpl:px-2 tpl:py-2 tpl:text-[11px] tpl:font-medium tpl:text-[var(--tpl-text-muted)] tpl:transition-colors hover:tpl:border-[var(--tpl-danger)] hover:tpl:text-[var(--tpl-danger)]\"\n @click=\"onRemove\"\n >\n <X :size=\"14\" :stroke-width=\"1.75\" />\n {{ t.popupDesign.overlayImageRemove }}\n </button>\n </div>\n </template>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { Image, Pencil, X } from \"@lucide/vue\";\nimport { computed, inject, ref } from \"vue\";\nimport { useI18n } from \"../composables/useI18n\";\nimport { ON_REQUEST_MEDIA_KEY } from \"../keys\";\n\nconst props = defineProps<{\n imageUrl: string;\n}>();\n\nconst emit = defineEmits<{\n \"set-url\": [url: string, templateWidth?: number];\n}>();\n\nconst { t } = useI18n();\n\n/**\n * Host-supplied media picker (set via editor `init({ onRequestMedia })`).\n * When wired, all overlay-image picking is delegated to the host so consumers\n * can upload to their own storage and return a public URL — no data-URL\n * round-trip is stored in the template JSON. Falls back to a local\n * FileReader → data-URL flow when no callback is provided so the picker still\n * works in standalone setups.\n */\nconst onRequestMedia = inject(ON_REQUEST_MEDIA_KEY, null);\nconst useHostPicker = computed(() => onRequestMedia !== null);\n\nconst fileRef = ref<HTMLInputElement | null>(null);\nconst isDragging = ref(false);\n\nconst MAX_DATA_URL_CHARS = 2_400_000;\n\nfunction pickFiles(files: FileList | File[] | null): void {\n const file = files?.[0];\n if (!file || !file.type.startsWith(\"image/\")) return;\n const reader = new FileReader();\n reader.onload = () => {\n const url = typeof reader.result === \"string\" ? reader.result : \"\";\n if (url.length > MAX_DATA_URL_CHARS) return;\n emit(\"set-url\", url);\n if (fileRef.value) fileRef.value.value = \"\";\n };\n reader.readAsDataURL(file);\n}\n\nasync function browseMedia(): Promise<void> {\n const result = await onRequestMedia?.({ accept: [\"images\"] });\n if (result?.url) {\n emit(\"set-url\", result.url, result.templateWidth);\n }\n}\n\nfunction onDrop(e: DragEvent): void {\n isDragging.value = false;\n if (useHostPicker.value) {\n // Host owns the upload pipeline — defer to it instead of reading a local\n // data URL we'd then need to discard.\n void browseMedia();\n return;\n }\n pickFiles(e.dataTransfer?.files ?? null);\n}\n\nfunction onBrowse(): void {\n if (useHostPicker.value) {\n void browseMedia();\n return;\n }\n fileRef.value?.click();\n}\n\nfunction onReplace(): void {\n if (useHostPicker.value) {\n void browseMedia();\n return;\n }\n fileRef.value?.click();\n}\n\nfunction onRemove(): void {\n emit(\"set-url\", \"\");\n}\n</script>\n\n<template>\n <div\n class=\"tpl:mt-4 tpl:rounded-lg tpl:border tpl:border-dashed tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:p-3\"\n role=\"region\"\n :aria-label=\"t.popupDesign.overlayImagePanelAriaLabel\"\n >\n <input\n ref=\"fileRef\"\n type=\"file\"\n class=\"tpl:sr-only\"\n tabindex=\"-1\"\n accept=\"image/jpeg,image/png,image/gif,image/webp,image/avif,image/svg+xml\"\n @change=\"pickFiles(($event.target as HTMLInputElement).files)\"\n />\n\n <template v-if=\"!props.imageUrl\">\n <button\n type=\"button\"\n class=\"tpl:flex tpl:w-full tpl:flex-col tpl:items-center tpl:justify-center tpl:gap-2 tpl:rounded-md tpl:border-none tpl:bg-transparent tpl:px-3 tpl:py-8 tpl:text-center tpl:text-[var(--tpl-text)] tpl:outline-none tpl:transition-colors hover:tpl:bg-[var(--tpl-bg-hover)] focus-visible:tpl:ring-2 focus-visible:tpl:ring-[var(--tpl-primary)] focus-visible:tpl:ring-offset-2 focus-visible:tpl:ring-offset-[var(--tpl-bg)]\"\n :class=\"\n isDragging\n ? 'tpl:bg-[var(--tpl-primary-light)] tpl:text-[var(--tpl-primary)]'\n : ''\n \"\n @dragenter.prevent=\"isDragging = true\"\n @dragleave.prevent=\"isDragging = false\"\n @dragover.prevent=\"isDragging = true\"\n @drop.prevent=\"onDrop\"\n @click=\"onBrowse\"\n >\n <Image\n :size=\"28\"\n :stroke-width=\"1.5\"\n class=\"tpl:text-[var(--tpl-text-muted)]\"\n />\n <span class=\"tpl:text-xs tpl:leading-snug tpl:text-[var(--tpl-text)]\">\n {{ t.popupDesign.overlayImageDropLineBefore }}\n <span class=\"tpl:underline tpl:decoration-[var(--tpl-text-muted)]\">{{\n t.popupDesign.overlayImageBrowse\n }}</span>\n {{ t.popupDesign.overlayImageDropLineAfter }}\n </span>\n <span class=\"tpl:text-[11px] tpl:text-[var(--tpl-text-muted)]\">{{\n t.popupDesign.overlayImageFormats\n }}</span>\n </button>\n </template>\n\n <template v-else>\n <div class=\"tpl:mb-3 tpl:flex tpl:gap-2\">\n <div\n class=\"tpl:flex tpl:min-h-0 tpl:flex-1 tpl:flex-col tpl:overflow-hidden tpl:rounded-lg tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-elevated)]\"\n role=\"img\"\n :aria-label=\"t.popupDesign.overlayImageDesktopPreview\"\n >\n <div\n class=\"tpl:flex tpl:gap-1 tpl:border-b tpl:border-[var(--tpl-border)] tpl:px-2 tpl:py-1.5\"\n >\n <span\n class=\"tpl:h-1.5 tpl:w-1.5 tpl:rounded-full tpl:bg-[var(--tpl-border-light)]\"\n />\n <span\n class=\"tpl:h-1.5 tpl:w-1.5 tpl:rounded-full tpl:bg-[var(--tpl-border-light)]\"\n />\n <span\n class=\"tpl:h-1.5 tpl:w-1.5 tpl:rounded-full tpl:bg-[var(--tpl-border-light)]\"\n />\n </div>\n <div\n class=\"tpl:flex tpl:aspect-[16/9] tpl:max-h-[72px] tpl:items-center tpl:justify-center tpl:bg-[var(--tpl-bg)] tpl:p-1\"\n >\n <img\n :src=\"props.imageUrl\"\n alt=\"\"\n class=\"tpl:max-h-full tpl:max-w-full tpl:rounded tpl:object-contain\"\n />\n </div>\n </div>\n <div\n class=\"tpl:flex tpl:w-[52px] tpl:shrink-0 tpl:flex-col tpl:overflow-hidden tpl:rounded-lg tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-elevated)]\"\n role=\"img\"\n :aria-label=\"t.popupDesign.overlayImageMobilePreview\"\n >\n <div\n class=\"tpl:flex tpl:gap-0.5 tpl:border-b tpl:border-[var(--tpl-border)] tpl:px-1 tpl:py-1\"\n >\n <span\n class=\"tpl:h-1 tpl:w-1 tpl:rounded-full tpl:bg-[var(--tpl-border-light)]\"\n />\n <span\n class=\"tpl:h-1 tpl:w-1 tpl:rounded-full tpl:bg-[var(--tpl-border-light)]\"\n />\n <span\n class=\"tpl:h-1 tpl:w-1 tpl:rounded-full tpl:bg-[var(--tpl-border-light)]\"\n />\n </div>\n <div\n class=\"tpl:flex tpl:aspect-[9/14] tpl:max-h-[72px] tpl:items-center tpl:justify-center tpl:bg-[var(--tpl-bg)] tpl:p-0.5\"\n >\n <img\n :src=\"props.imageUrl\"\n alt=\"\"\n class=\"tpl:max-h-full tpl:max-w-full tpl:rounded-sm tpl:object-cover\"\n />\n </div>\n </div>\n </div>\n\n <div class=\"tpl:flex tpl:flex-wrap tpl:gap-2\">\n <!-- <button\n type=\"button\"\n class=\"tpl:inline-flex tpl:flex-1 tpl:items-center tpl:justify-center tpl:gap-1.5 tpl:rounded-lg tpl:border tpl:border-[var(--tpl-border)] tpl:bg-transparent tpl:px-2 tpl:py-2 tpl:text-[11px] tpl:font-medium tpl:text-[var(--tpl-text-muted)] tpl:transition-colors hover:tpl:border-[var(--tpl-text-muted)] hover:tpl:text-[var(--tpl-text)]\"\n @click=\"onCrop\"\n >\n <Crop :size=\"14\" :stroke-width=\"1.75\" />\n {{ t.popupDesign.overlayImageCrop }}\n </button> -->\n <button\n type=\"button\"\n class=\"tpl:inline-flex tpl:flex-1 tpl:items-center tpl:justify-center tpl:gap-1.5 tpl:rounded-lg tpl:border tpl:border-[var(--tpl-border)] tpl:bg-transparent tpl:px-2 tpl:py-2 tpl:text-[11px] tpl:font-medium tpl:text-[var(--tpl-text-muted)] tpl:transition-colors hover:tpl:border-[var(--tpl-text-muted)] hover:tpl:text-[var(--tpl-text)]\"\n @click=\"onReplace\"\n >\n <Pencil :size=\"14\" :stroke-width=\"1.75\" />\n {{ t.popupDesign.overlayImageReplace }}\n </button>\n <button\n type=\"button\"\n class=\"tpl:inline-flex tpl:flex-1 tpl:items-center tpl:justify-center tpl:gap-1.5 tpl:rounded-lg tpl:border tpl:border-[var(--tpl-border)] tpl:bg-transparent tpl:px-2 tpl:py-2 tpl:text-[11px] tpl:font-medium tpl:text-[var(--tpl-text-muted)] tpl:transition-colors hover:tpl:border-[var(--tpl-danger)] hover:tpl:text-[var(--tpl-danger)]\"\n @click=\"onRemove\"\n >\n <X :size=\"14\" :stroke-width=\"1.75\" />\n {{ t.popupDesign.overlayImageRemove }}\n </button>\n </div>\n </template>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, inject, ref } from \"vue\";\nimport type {\n PopupDisplayAnimation,\n PopupEmbedSettings,\n PopupScreenPosition,\n PopupSizePreset,\n} from \"@aswin.dev/types\";\nimport { POPUP_SIZE_PRESET_WIDTH } from \"@aswin.dev/types\";\nimport { useI18n } from \"../composables/useI18n\";\nimport { EDITOR_KEY } from \"../keys\";\nimport type { BaseEditorReturn } from \"../composables/useEditorCore\";\nimport { resolvePopupEmbed } from \"../utils/resolvePopupEmbed\";\nimport PopupDesignSwitch from \"./PopupDesignSwitch.vue\";\nimport PopupOverlayImagePanel from \"./PopupOverlayImagePanel.vue\";\n\nconst props = withDefaults(\n defineProps<{ layout?: \"standalone\" | \"popupAdjacent\" }>(),\n { layout: \"standalone\" },\n);\n\nconst editor = inject(EDITOR_KEY) as BaseEditorReturn | null;\nif (!editor) {\n throw new Error(\"PopupDesignView requires EDITOR_KEY\");\n}\n\nconst { t } = useI18n();\n\nconst rootClass = computed(() =>\n props.layout === \"popupAdjacent\"\n ? \"tpl-popup-design tpl:w-full tpl:overflow-x-hidden tpl:px-4 tpl:pb-6 tpl:pt-4 tpl:bg-[var(--tpl-bg)]\"\n : \"tpl-popup-design tpl:mx-auto tpl:max-w-md tpl:px-5 tpl:py-8 tpl:bg-[var(--tpl-bg)]\",\n);\n\nconst presetGridClass = computed(() =>\n props.layout === \"popupAdjacent\"\n ? \"tpl:mb-8 tpl:grid tpl:grid-cols-3 tpl:gap-2\"\n : \"tpl:mb-8 tpl:grid tpl:grid-cols-3 tpl:gap-3 sm:tpl:grid-cols-6\",\n);\n\nconst designTab = ref<\"style\" | \"position\" | \"closing\" | \"css\">(\"style\");\n\nconst designTabs = computed(() => [\n { id: \"style\" as const, label: t.popupDesign.tabStyle },\n { id: \"position\" as const, label: t.popupDesign.tabPosition },\n { id: \"closing\" as const, label: t.popupDesign.tabClosing },\n { id: \"css\" as const, label: t.popupDesign.tabCss },\n]);\n\nconst popup = computed(() => resolvePopupEmbed(editor!.content.value.settings));\n\nfunction patchDesign(patch: Partial<PopupEmbedSettings[\"design\"]>): void {\n const cur = popup.value;\n const design = { ...cur.design, ...patch };\n const updates: Record<string, unknown> = {\n popup: { ...cur, design },\n };\n if (patch.sizePreset !== undefined) {\n updates.width = POPUP_SIZE_PRESET_WIDTH[patch.sizePreset];\n }\n editor!.updateSettings(\n updates as Parameters<BaseEditorReturn[\"updateSettings\"]>[0],\n );\n}\n\nfunction toggleDesign<K extends keyof PopupEmbedSettings[\"design\"]>(\n key: K,\n value: PopupEmbedSettings[\"design\"][K],\n): void {\n patchDesign({ [key]: value } as Partial<PopupEmbedSettings[\"design\"]>);\n}\n\nfunction toggleOverlayImage(): void {\n const next = !popup.value.design.overlayImage;\n patchDesign({\n overlayImage: next,\n ...(!next ? { overlayImageUrl: \"\" } : {}),\n });\n}\n\nfunction setUseOverlay(enabled: boolean): void {\n if (!enabled) {\n patchDesign({\n useOverlay: false,\n overlayImage: false,\n overlayImageUrl: \"\",\n });\n } else {\n patchDesign({ useOverlay: true });\n }\n}\n\nfunction setOverlayImageUrl(url: string): void {\n patchDesign({ overlayImageUrl: url });\n}\n\nfunction toggleBackgroundImage(): void {\n const next = !popup.value.design.backgroundImage;\n patchDesign({\n backgroundImage: next,\n // Drop the stored URL when the user turns the feature off so re-enabling\n // surfaces the empty picker instead of a stale image.\n ...(!next ? { backgroundImageUrl: \"\" } : {}),\n });\n}\n\nfunction setBackgroundImageUrl(url: string, templateWidth?: number): void {\n patchDesign({ backgroundImageUrl: url });\n if (templateWidth !== undefined) {\n editor!.updateSettings({ width: templateWidth });\n }\n}\n\nfunction onBackgroundImageFitChange(e: Event): void {\n patchDesign({\n backgroundImageFit: (e.target as HTMLSelectElement).value as\n | \"auto\"\n | \"cover\"\n | \"contain\",\n });\n}\n\nfunction clamp01(n: number): number {\n if (!Number.isFinite(n)) return 0;\n return Math.min(1, Math.max(0, n));\n}\n\nconst OVERLAY_BLUR_MIN = 0;\nconst OVERLAY_BLUR_MAX = 30;\n\nfunction clampBlur(n: number): number {\n if (!Number.isFinite(n)) return OVERLAY_BLUR_MIN;\n return Math.min(OVERLAY_BLUR_MAX, Math.max(OVERLAY_BLUR_MIN, n));\n}\n\nfunction onOverlayBlurInput(e: Event): void {\n const raw = Number((e.target as HTMLInputElement).value);\n patchDesign({ overlayBlur: clampBlur(raw) });\n}\n\nfunction onOverlayImageOpacityInput(e: Event): void {\n const raw = Number((e.target as HTMLInputElement).value);\n patchDesign({ overlayImageOpacity: clamp01(raw / 100) });\n}\n\nconst POSITION_GRID: { id: PopupScreenPosition; cellAlign: string }[] = [\n {\n id: \"topLeft\",\n cellAlign:\n \"tpl:flex tpl:h-full tpl:w-full tpl:justify-start tpl:items-start\",\n },\n {\n id: \"topCenter\",\n cellAlign:\n \"tpl:flex tpl:h-full tpl:w-full tpl:justify-center tpl:items-start\",\n },\n {\n id: \"topRight\",\n cellAlign: \"tpl:flex tpl:h-full tpl:w-full tpl:justify-end tpl:items-start\",\n },\n {\n id: \"middleLeft\",\n cellAlign:\n \"tpl:flex tpl:h-full tpl:w-full tpl:justify-start tpl:items-center\",\n },\n {\n id: \"center\",\n cellAlign:\n \"tpl:flex tpl:h-full tpl:w-full tpl:justify-center tpl:items-center\",\n },\n {\n id: \"middleRight\",\n cellAlign:\n \"tpl:flex tpl:h-full tpl:w-full tpl:justify-end tpl:items-center\",\n },\n {\n id: \"bottomLeft\",\n cellAlign: \"tpl:flex tpl:h-full tpl:w-full tpl:justify-start tpl:items-end\",\n },\n {\n id: \"bottomCenter\",\n cellAlign:\n \"tpl:flex tpl:h-full tpl:w-full tpl:justify-center tpl:items-end\",\n },\n {\n id: \"bottomRight\",\n cellAlign: \"tpl:flex tpl:h-full tpl:w-full tpl:justify-end tpl:items-end\",\n },\n];\n\nconst animationSelectOptions = computed(() => [\n { id: \"smooth\" as const, label: t.popupDesign.animationSmooth },\n { id: \"fadeIn\" as const, label: t.popupDesign.animationFadeIn },\n { id: \"slideIn\" as const, label: t.popupDesign.animationSlideIn },\n { id: \"none\" as const, label: t.popupDesign.animationNone },\n]);\n\nfunction onAnimationChange(e: Event): void {\n patchDesign({\n displayAnimation: (e.target as HTMLSelectElement)\n .value as PopupDisplayAnimation,\n });\n}\n\nfunction onCloseButtonPositionChange(e: Event): void {\n patchDesign({\n closeButtonPosition: (e.target as HTMLSelectElement).value as\n | \"left\"\n | \"right\",\n });\n}\n\nconst sizePresets = computed(() =>\n (\n [\n \"small\",\n \"medium\",\n \"large\",\n \"fullscreen\",\n \"fullHeight\",\n \"custom\",\n ] as PopupSizePreset[]\n ).map((id) => ({\n id,\n label: t.popupDesign.sizeLabels[id],\n })),\n);\n</script>\n\n<template>\n <div :class=\"rootClass\">\n <div\n class=\"tpl:mb-6 tpl:flex tpl:flex-wrap tpl:gap-1 tpl:rounded-lg tpl:bg-[var(--tpl-bg-elevated)] tpl:p-1\"\n >\n <button\n v-for=\"tab in designTabs\"\n :key=\"tab.id\"\n type=\"button\"\n class=\"tpl:rounded-md tpl:border-none tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-colors\"\n :class=\"\n designTab === tab.id\n ? 'tpl:bg-[var(--tpl-bg)] tpl:text-[var(--tpl-text)] tpl:shadow-sm'\n : 'tpl:bg-transparent tpl:text-[var(--tpl-text-muted)] hover:tpl:text-[var(--tpl-text)]'\n \"\n @click=\"designTab = tab.id\"\n >\n {{ tab.label }}\n </button>\n </div>\n\n <template v-if=\"designTab === 'style'\">\n <p\n class=\"tpl:mb-4 tpl:text-[11px] tpl:font-semibold tpl:uppercase tpl:tracking-wide tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ t.popupDesign.standardSize }}\n </p>\n <div :class=\"presetGridClass\">\n <button\n v-for=\"preset in sizePresets\"\n :key=\"preset.id\"\n type=\"button\"\n class=\"tpl:flex tpl:flex-col tpl:items-center tpl:gap-2 tpl:rounded-lg tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:p-3 tpl:text-center tpl:transition-shadow hover:tpl:border-[var(--tpl-primary)]\"\n :class=\"\n popup.design.sizePreset === preset.id\n ? 'tpl:ring-2 tpl:ring-[var(--tpl-text)] tpl:ring-offset-2 tpl:ring-offset-[var(--tpl-bg)]'\n : ''\n \"\n @click=\"patchDesign({ sizePreset: preset.id })\"\n >\n <span\n class=\"tpl:flex tpl:h-12 tpl:w-full tpl:items-center tpl:justify-center tpl:rounded-md tpl:bg-[var(--tpl-bg-elevated)]\"\n >\n <span\n class=\"tpl:block tpl:rounded tpl:bg-[var(--tpl-text-muted)] tpl:opacity-80\"\n :class=\"{\n 'tpl:h-6 tpl:w-10': preset.id === 'small',\n 'tpl:h-8 tpl:w-12': preset.id === 'medium',\n 'tpl:h-10 tpl:w-14': preset.id === 'large',\n 'tpl:h-12 tpl:w-full': preset.id === 'fullscreen',\n 'tpl:h-12 tpl:w-8': preset.id === 'fullHeight',\n 'tpl:h-8 tpl:w-10 tpl:border tpl:border-dashed tpl:border-[var(--tpl-border)] tpl:bg-transparent':\n preset.id === 'custom',\n }\"\n />\n </span>\n <span\n class=\"tpl:text-[11px] tpl:font-medium tpl:text-[var(--tpl-text)]\"\n >{{ preset.label }}</span\n >\n </button>\n </div>\n\n <p\n class=\"tpl:mb-3 tpl:text-[11px] tpl:font-semibold tpl:uppercase tpl:tracking-wide tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ t.popupDesign.options }}\n </p>\n <div\n class=\"tpl:mb-8 tpl:space-y-5 tpl:rounded-[10px] tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-elevated)] tpl:px-4 tpl:py-5\"\n >\n <div\n class=\"tpl:flex tpl:min-h-[28px] tpl:items-center tpl:justify-between tpl:gap-4\"\n >\n <span\n class=\"tpl:text-sm tpl:leading-snug tpl:text-[var(--tpl-text)]\"\n >{{ t.popupDesign.useOverlay }}</span\n >\n <PopupDesignSwitch\n :checked=\"popup.design.useOverlay\"\n @toggle=\"setUseOverlay(!popup.design.useOverlay)\"\n />\n </div>\n <template v-if=\"popup.design.useOverlay\">\n <div\n class=\"tpl:flex tpl:min-h-[28px] tpl:items-center tpl:justify-between tpl:gap-4\"\n >\n <span\n class=\"tpl:text-sm tpl:leading-snug tpl:text-[var(--tpl-text)]\"\n >{{ t.popupDesign.overlayImage }}</span\n >\n <PopupDesignSwitch\n :checked=\"popup.design.overlayImage\"\n @toggle=\"toggleOverlayImage()\"\n />\n </div>\n\n <PopupOverlayImagePanel\n v-if=\"popup.design.overlayImage\"\n :image-url=\"popup.design.overlayImageUrl ?? ''\"\n @set-url=\"setOverlayImageUrl\"\n />\n\n <div\n v-if=\"\n popup.design.overlayImage &&\n (popup.design.overlayImageUrl ?? '').length > 0\n \"\n class=\"tpl:space-y-2\"\n >\n <div class=\"tpl:flex tpl:items-center tpl:justify-between\">\n <label\n class=\"tpl:block tpl:text-sm tpl:leading-snug tpl:text-[var(--tpl-text)]\"\n for=\"popup-overlay-image-opacity\"\n >\n {{ t.popupDesign.overlayImageOpacity }}\n </label>\n <span\n class=\"tpl:text-[11px] tpl:font-medium tpl:text-[var(--tpl-text-muted)] tpl:tabular-nums\"\n >\n {{ Math.round((popup.design.overlayImageOpacity ?? 1) * 100) }}%\n </span>\n </div>\n <input\n id=\"popup-overlay-image-opacity\"\n type=\"range\"\n class=\"tpl:w-full tpl:accent-[var(--tpl-primary)]\"\n min=\"0\"\n max=\"100\"\n step=\"5\"\n :value=\"Math.round((popup.design.overlayImageOpacity ?? 1) * 100)\"\n @input=\"onOverlayImageOpacityInput\"\n />\n </div>\n\n <div class=\"tpl:space-y-2\">\n <div class=\"tpl:flex tpl:items-center tpl:justify-between\">\n <label\n class=\"tpl:block tpl:text-sm tpl:leading-snug tpl:text-[var(--tpl-text)]\"\n for=\"popup-overlay-blur\"\n >\n {{ t.popupDesign.overlayBlur }}\n </label>\n <span\n class=\"tpl:text-[11px] tpl:font-medium tpl:text-[var(--tpl-text-muted)] tpl:tabular-nums\"\n >\n {{ Math.round(popup.design.overlayBlur ?? 8) }}px\n </span>\n </div>\n <input\n id=\"popup-overlay-blur\"\n type=\"range\"\n class=\"tpl:w-full tpl:accent-[var(--tpl-primary)]\"\n :min=\"0\"\n :max=\"30\"\n step=\"1\"\n :value=\"Math.round(popup.design.overlayBlur ?? 8)\"\n @input=\"onOverlayBlurInput\"\n />\n <p class=\"tpl:m-0 tpl:text-[11px] tpl:text-[var(--tpl-text-muted)]\">\n {{ t.popupDesign.overlayBlurHint }}\n </p>\n </div>\n </template>\n <div\n class=\"tpl:flex tpl:min-h-[28px] tpl:items-center tpl:justify-between tpl:gap-4\"\n >\n <span\n class=\"tpl:text-sm tpl:leading-snug tpl:text-[var(--tpl-text)]\"\n >{{ t.popupDesign.backgroundImage }}</span\n >\n <PopupDesignSwitch\n :checked=\"popup.design.backgroundImage\"\n @toggle=\"toggleBackgroundImage()\"\n />\n </div>\n\n <template v-if=\"popup.design.backgroundImage\">\n <PopupOverlayImagePanel\n :image-url=\"popup.design.backgroundImageUrl ?? ''\"\n @set-url=\"setBackgroundImageUrl\"\n />\n\n <div\n v-if=\"(popup.design.backgroundImageUrl ?? '').length > 0\"\n class=\"tpl:space-y-2\"\n >\n <label\n class=\"tpl:block tpl:text-sm tpl:leading-snug tpl:text-[var(--tpl-text)]\"\n for=\"popup-background-image-fit\"\n >\n {{ t.popupDesign.backgroundImageFitHeading }}\n </label>\n <select\n id=\"popup-background-image-fit\"\n class=\"tpl:w-full tpl:rounded-lg tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:px-3 tpl:py-2.5 tpl:text-sm tpl:text-[var(--tpl-text)] tpl:outline-none focus-visible:tpl:ring-2 focus-visible:tpl:ring-[var(--tpl-primary)] focus-visible:tpl:ring-offset-2 focus-visible:tpl:ring-offset-[var(--tpl-bg)]\"\n :value=\"popup.design.backgroundImageFit ?? 'cover'\"\n @change=\"onBackgroundImageFitChange\"\n >\n <option value=\"auto\">{{ t.popupDesign.fitAuto }}</option>\n <option value=\"cover\">{{ t.popupDesign.fitCover }}</option>\n <option value=\"contain\">{{ t.popupDesign.fitContain }}</option>\n </select>\n </div>\n\n <div\n v-if=\"(popup.design.backgroundImageUrl ?? '').length > 0\"\n class=\"tpl:flex tpl:min-h-[28px] tpl:items-center tpl:justify-between tpl:gap-4\"\n >\n <span\n class=\"tpl:text-sm tpl:leading-snug tpl:text-[var(--tpl-text)]\"\n >{{ t.popupDesign.backgroundImageOnly }}</span\n >\n <PopupDesignSwitch\n :checked=\"popup.design.backgroundImageOnly ?? false\"\n @toggle=\"\n toggleDesign(\n 'backgroundImageOnly',\n !(popup.design.backgroundImageOnly ?? false),\n )\n \"\n />\n </div>\n </template>\n </div>\n\n <!-- <div\n class=\"tpl:mb-8 tpl:rounded-[10px] tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-elevated)] tpl:px-4 tpl:py-5\"\n >\n <div\n class=\"tpl:mb-3 tpl:flex tpl:min-h-[28px] tpl:items-center tpl:justify-between tpl:gap-4\"\n >\n <span\n class=\"tpl:text-sm tpl:font-medium tpl:leading-snug tpl:text-[var(--tpl-text)]\"\n >\n {{ t.popupDesign.multiColumn }}\n </span>\n <PopupDesignSwitch\n :checked=\"popup.design.multiColumnEnabled\"\n @toggle=\"\n toggleDesign(\n 'multiColumnEnabled',\n !popup.design.multiColumnEnabled,\n )\n \"\n />\n </div>\n <div v-if=\"popup.design.multiColumnEnabled\" class=\"tpl:flex tpl:gap-3\">\n <button\n type=\"button\"\n class=\"tpl:flex-1 tpl:rounded-lg tpl:border tpl:px-3 tpl:py-4\"\n :class=\"\n popup.design.multiColumnPreset === 'two'\n ? 'tpl:border-[var(--tpl-text)] tpl:bg-[var(--tpl-bg-elevated)]'\n : 'tpl:border-[var(--tpl-border)]'\n \"\n @click=\"patchDesign({ multiColumnPreset: 'two' })\"\n >\n <span class=\"tpl:flex tpl:justify-center tpl:gap-0.5\">\n <span\n class=\"tpl:h-10 tpl:flex-1 tpl:rounded tpl:bg-[var(--tpl-text-muted)] tpl:opacity-80\"\n />\n <span\n class=\"tpl:h-10 tpl:flex-1 tpl:rounded tpl:bg-[var(--tpl-text-muted)] tpl:opacity-80\"\n />\n </span>\n </button>\n <button\n type=\"button\"\n class=\"tpl:flex-1 tpl:rounded-lg tpl:border tpl:px-3 tpl:py-4\"\n :class=\"\n popup.design.multiColumnPreset === 'three'\n ? 'tpl:border-[var(--tpl-text)] tpl:bg-[var(--tpl-bg-elevated)]'\n : 'tpl:border-[var(--tpl-border)]'\n \"\n @click=\"patchDesign({ multiColumnPreset: 'three' })\"\n >\n <span class=\"tpl:flex tpl:justify-center tpl:gap-0.5\">\n <span\n class=\"tpl:h-10 tpl:flex-1 tpl:rounded tpl:bg-[var(--tpl-bg-hover)]\"\n />\n <span\n class=\"tpl:h-10 tpl:flex-1 tpl:rounded tpl:bg-[var(--tpl-bg-hover)]\"\n />\n <span\n class=\"tpl:h-10 tpl:flex-1 tpl:rounded tpl:bg-[var(--tpl-bg-hover)]\"\n />\n </span>\n </button>\n </div>\n </div> -->\n\n <!-- <div\n class=\"tpl:flex tpl:min-h-[28px] tpl:items-center tpl:justify-between tpl:gap-4\"\n >\n <span class=\"tpl:text-sm tpl:leading-snug tpl:text-[var(--tpl-text)]\">{{\n t.popupDesign.rtl\n }}</span>\n <PopupDesignSwitch\n :checked=\"popup.design.rtl\"\n @toggle=\"toggleDesign('rtl', !popup.design.rtl)\"\n />\n </div> -->\n </template>\n\n <template v-else-if=\"designTab === 'position'\">\n <p\n class=\"tpl:mb-3 tpl:text-[11px] tpl:font-semibold tpl:uppercase tpl:tracking-wide tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ t.popupDesign.positionHeading }}\n </p>\n <div\n class=\"tpl:mb-8 tpl:grid tpl:grid-cols-3 tpl:gap-2\"\n role=\"group\"\n :aria-label=\"t.popupDesign.positionHeading\"\n >\n <button\n v-for=\"cell in POSITION_GRID\"\n :key=\"cell.id\"\n type=\"button\"\n class=\"tpl:flex tpl:min-h-[52px] tpl:w-full tpl:rounded-lg tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:p-2 tpl:transition-all tpl:outline-none hover:tpl:border-[var(--tpl-text-muted)] focus-visible:tpl:ring-2 focus-visible:tpl:ring-[var(--tpl-primary)] focus-visible:tpl:ring-offset-2 focus-visible:tpl:ring-offset-[var(--tpl-bg)]\"\n :class=\"\n popup.design.popupPosition === cell.id\n ? 'tpl:border-2 tpl:border-[var(--tpl-text)] tpl:bg-[var(--tpl-bg-elevated)]'\n : ''\n \"\n :aria-pressed=\"popup.design.popupPosition === cell.id\"\n :aria-label=\"t.popupDesign.positionLabels[cell.id]\"\n @click=\"patchDesign({ popupPosition: cell.id })\"\n >\n <div :class=\"cell.cellAlign\">\n <span\n class=\"tpl:block tpl:h-4 tpl:w-7 tpl:rounded-sm tpl:transition-colors\"\n :class=\"\n popup.design.popupPosition === cell.id\n ? 'tpl:bg-[var(--tpl-text)]'\n : 'tpl:bg-[var(--tpl-text-muted)] tpl:opacity-70'\n \"\n />\n </div>\n </button>\n </div>\n <p\n class=\"tpl:mb-2 tpl:text-[11px] tpl:font-semibold tpl:uppercase tpl:tracking-wide tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ t.popupDesign.displayAnimationHeading }}\n </p>\n <label class=\"tpl:sr-only\" for=\"popup-display-animation\">{{\n t.popupDesign.displayAnimationHeading\n }}</label>\n <select\n id=\"popup-display-animation\"\n class=\"tpl:w-full tpl:rounded-lg tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:px-3 tpl:py-2.5 tpl:text-sm tpl:text-[var(--tpl-text)] tpl:outline-none focus-visible:tpl:ring-2 focus-visible:tpl:ring-[var(--tpl-primary)] focus-visible:tpl:ring-offset-2 focus-visible:tpl:ring-offset-[var(--tpl-bg)]\"\n :value=\"popup.design.displayAnimation\"\n @change=\"onAnimationChange\"\n >\n <option\n v-for=\"opt in animationSelectOptions\"\n :key=\"opt.id\"\n :value=\"opt.id\"\n >\n {{ opt.label }}\n </option>\n </select>\n </template>\n\n <template v-else-if=\"designTab === 'closing'\">\n <div\n class=\"tpl:space-y-5 tpl:rounded-[10px] tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-elevated)] tpl:px-4 tpl:py-5\"\n >\n <div\n class=\"tpl:flex tpl:min-h-[28px] tpl:items-center tpl:justify-between tpl:gap-4\"\n >\n <span\n class=\"tpl:text-sm tpl:leading-snug tpl:text-[var(--tpl-text)]\"\n >{{ t.popupDesign.closingDisplayCloseButton }}</span\n >\n <PopupDesignSwitch\n :checked=\"popup.design.displayCloseButton\"\n @toggle=\"\n toggleDesign(\n 'displayCloseButton',\n !popup.design.displayCloseButton,\n )\n \"\n />\n </div>\n <div v-if=\"popup.design.displayCloseButton\" class=\"tpl:space-y-2\">\n <label\n class=\"tpl:block tpl:text-sm tpl:leading-snug tpl:text-[var(--tpl-text)]\"\n for=\"popup-close-button-position\"\n >\n {{ t.popupDesign.closingButtonPosition }}\n </label>\n <select\n id=\"popup-close-button-position\"\n class=\"tpl:w-full tpl:rounded-lg tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:px-3 tpl:py-2.5 tpl:text-sm tpl:text-[var(--tpl-text)] tpl:outline-none focus-visible:tpl:ring-2 focus-visible:tpl:ring-[var(--tpl-primary)] focus-visible:tpl:ring-offset-2 focus-visible:tpl:ring-offset-[var(--tpl-bg)]\"\n :value=\"popup.design.closeButtonPosition\"\n @change=\"onCloseButtonPositionChange\"\n >\n <option value=\"left\">\n {{ t.popupDesign.closingButtonPositionLeft }}\n </option>\n <option value=\"right\">\n {{ t.popupDesign.closingButtonPositionRight }}\n </option>\n </select>\n </div>\n <div\n class=\"tpl:flex tpl:min-h-[28px] tpl:items-center tpl:justify-between tpl:gap-4\"\n >\n <span\n class=\"tpl:text-sm tpl:leading-snug tpl:text-[var(--tpl-text)]\"\n >{{ t.popupDesign.closingOnOverlayClick }}</span\n >\n <PopupDesignSwitch\n :checked=\"popup.design.closeOnOverlayClick\"\n @toggle=\"\n toggleDesign(\n 'closeOnOverlayClick',\n !popup.design.closeOnOverlayClick,\n )\n \"\n />\n </div>\n </div>\n </template>\n\n <template v-else>\n <p class=\"tpl:text-sm tpl:text-[var(--tpl-text-muted)]\">\n {{ t.popupDesign.placeholderSubtab }}\n </p>\n </template>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, inject, ref } from \"vue\";\nimport type {\n PopupDisplayAnimation,\n PopupEmbedSettings,\n PopupScreenPosition,\n PopupSizePreset,\n} from \"@aswin.dev/types\";\nimport { POPUP_SIZE_PRESET_WIDTH } from \"@aswin.dev/types\";\nimport { useI18n } from \"../composables/useI18n\";\nimport { EDITOR_KEY } from \"../keys\";\nimport type { BaseEditorReturn } from \"../composables/useEditorCore\";\nimport { resolvePopupEmbed } from \"../utils/resolvePopupEmbed\";\nimport PopupDesignSwitch from \"./PopupDesignSwitch.vue\";\nimport PopupOverlayImagePanel from \"./PopupOverlayImagePanel.vue\";\n\nconst props = withDefaults(\n defineProps<{ layout?: \"standalone\" | \"popupAdjacent\" }>(),\n { layout: \"standalone\" },\n);\n\nconst editor = inject(EDITOR_KEY) as BaseEditorReturn | null;\nif (!editor) {\n throw new Error(\"PopupDesignView requires EDITOR_KEY\");\n}\n\nconst { t } = useI18n();\n\nconst rootClass = computed(() =>\n props.layout === \"popupAdjacent\"\n ? \"tpl-popup-design tpl:w-full tpl:overflow-x-hidden tpl:px-4 tpl:pb-6 tpl:pt-4 tpl:bg-[var(--tpl-bg)]\"\n : \"tpl-popup-design tpl:mx-auto tpl:max-w-md tpl:px-5 tpl:py-8 tpl:bg-[var(--tpl-bg)]\",\n);\n\nconst presetGridClass = computed(() =>\n props.layout === \"popupAdjacent\"\n ? \"tpl:mb-8 tpl:grid tpl:grid-cols-3 tpl:gap-2\"\n : \"tpl:mb-8 tpl:grid tpl:grid-cols-3 tpl:gap-3 sm:tpl:grid-cols-6\",\n);\n\nconst designTab = ref<\"style\" | \"position\" | \"closing\" | \"css\">(\"style\");\n\nconst designTabs = computed(() => [\n { id: \"style\" as const, label: t.popupDesign.tabStyle },\n { id: \"position\" as const, label: t.popupDesign.tabPosition },\n { id: \"closing\" as const, label: t.popupDesign.tabClosing },\n { id: \"css\" as const, label: t.popupDesign.tabCss },\n]);\n\nconst popup = computed(() => resolvePopupEmbed(editor!.content.value.settings));\n\nfunction patchDesign(patch: Partial<PopupEmbedSettings[\"design\"]>): void {\n const cur = popup.value;\n const design = { ...cur.design, ...patch };\n const updates: Record<string, unknown> = {\n popup: { ...cur, design },\n };\n if (patch.sizePreset !== undefined) {\n updates.width = POPUP_SIZE_PRESET_WIDTH[patch.sizePreset];\n }\n editor!.updateSettings(\n updates as Parameters<BaseEditorReturn[\"updateSettings\"]>[0],\n );\n}\n\nfunction toggleDesign<K extends keyof PopupEmbedSettings[\"design\"]>(\n key: K,\n value: PopupEmbedSettings[\"design\"][K],\n): void {\n patchDesign({ [key]: value } as Partial<PopupEmbedSettings[\"design\"]>);\n}\n\nfunction toggleOverlayImage(): void {\n const next = !popup.value.design.overlayImage;\n patchDesign({\n overlayImage: next,\n ...(!next ? { overlayImageUrl: \"\" } : {}),\n });\n}\n\nfunction setUseOverlay(enabled: boolean): void {\n if (!enabled) {\n patchDesign({\n useOverlay: false,\n overlayImage: false,\n overlayImageUrl: \"\",\n });\n } else {\n patchDesign({ useOverlay: true });\n }\n}\n\nfunction setOverlayImageUrl(url: string): void {\n patchDesign({ overlayImageUrl: url });\n}\n\nfunction toggleBackgroundImage(): void {\n const next = !popup.value.design.backgroundImage;\n patchDesign({\n backgroundImage: next,\n // Drop the stored URL when the user turns the feature off so re-enabling\n // surfaces the empty picker instead of a stale image.\n ...(!next ? { backgroundImageUrl: \"\" } : {}),\n });\n}\n\nfunction setBackgroundImageUrl(url: string, templateWidth?: number): void {\n patchDesign({ backgroundImageUrl: url });\n if (templateWidth !== undefined) {\n editor!.updateSettings({ width: templateWidth });\n }\n}\n\nfunction onBackgroundImageFitChange(e: Event): void {\n patchDesign({\n backgroundImageFit: (e.target as HTMLSelectElement).value as\n | \"auto\"\n | \"cover\"\n | \"contain\",\n });\n}\n\nfunction clamp01(n: number): number {\n if (!Number.isFinite(n)) return 0;\n return Math.min(1, Math.max(0, n));\n}\n\nconst OVERLAY_BLUR_MIN = 0;\nconst OVERLAY_BLUR_MAX = 30;\n\nfunction clampBlur(n: number): number {\n if (!Number.isFinite(n)) return OVERLAY_BLUR_MIN;\n return Math.min(OVERLAY_BLUR_MAX, Math.max(OVERLAY_BLUR_MIN, n));\n}\n\nfunction onOverlayBlurInput(e: Event): void {\n const raw = Number((e.target as HTMLInputElement).value);\n patchDesign({ overlayBlur: clampBlur(raw) });\n}\n\nfunction onOverlayImageOpacityInput(e: Event): void {\n const raw = Number((e.target as HTMLInputElement).value);\n patchDesign({ overlayImageOpacity: clamp01(raw / 100) });\n}\n\nconst POSITION_GRID: { id: PopupScreenPosition; cellAlign: string }[] = [\n {\n id: \"topLeft\",\n cellAlign:\n \"tpl:flex tpl:h-full tpl:w-full tpl:justify-start tpl:items-start\",\n },\n {\n id: \"topCenter\",\n cellAlign:\n \"tpl:flex tpl:h-full tpl:w-full tpl:justify-center tpl:items-start\",\n },\n {\n id: \"topRight\",\n cellAlign: \"tpl:flex tpl:h-full tpl:w-full tpl:justify-end tpl:items-start\",\n },\n {\n id: \"middleLeft\",\n cellAlign:\n \"tpl:flex tpl:h-full tpl:w-full tpl:justify-start tpl:items-center\",\n },\n {\n id: \"center\",\n cellAlign:\n \"tpl:flex tpl:h-full tpl:w-full tpl:justify-center tpl:items-center\",\n },\n {\n id: \"middleRight\",\n cellAlign:\n \"tpl:flex tpl:h-full tpl:w-full tpl:justify-end tpl:items-center\",\n },\n {\n id: \"bottomLeft\",\n cellAlign: \"tpl:flex tpl:h-full tpl:w-full tpl:justify-start tpl:items-end\",\n },\n {\n id: \"bottomCenter\",\n cellAlign:\n \"tpl:flex tpl:h-full tpl:w-full tpl:justify-center tpl:items-end\",\n },\n {\n id: \"bottomRight\",\n cellAlign: \"tpl:flex tpl:h-full tpl:w-full tpl:justify-end tpl:items-end\",\n },\n];\n\nconst animationSelectOptions = computed(() => [\n { id: \"smooth\" as const, label: t.popupDesign.animationSmooth },\n { id: \"fadeIn\" as const, label: t.popupDesign.animationFadeIn },\n { id: \"slideIn\" as const, label: t.popupDesign.animationSlideIn },\n { id: \"none\" as const, label: t.popupDesign.animationNone },\n]);\n\nfunction onAnimationChange(e: Event): void {\n patchDesign({\n displayAnimation: (e.target as HTMLSelectElement)\n .value as PopupDisplayAnimation,\n });\n}\n\nfunction onCloseButtonPositionChange(e: Event): void {\n patchDesign({\n closeButtonPosition: (e.target as HTMLSelectElement).value as\n | \"left\"\n | \"right\",\n });\n}\n\nconst sizePresets = computed(() =>\n (\n [\n \"small\",\n \"medium\",\n \"large\",\n \"fullscreen\",\n \"fullHeight\",\n \"custom\",\n ] as PopupSizePreset[]\n ).map((id) => ({\n id,\n label: t.popupDesign.sizeLabels[id],\n })),\n);\n</script>\n\n<template>\n <div :class=\"rootClass\">\n <div\n class=\"tpl:mb-6 tpl:flex tpl:flex-wrap tpl:gap-1 tpl:rounded-lg tpl:bg-[var(--tpl-bg-elevated)] tpl:p-1\"\n >\n <button\n v-for=\"tab in designTabs\"\n :key=\"tab.id\"\n type=\"button\"\n class=\"tpl:rounded-md tpl:border-none tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-colors\"\n :class=\"\n designTab === tab.id\n ? 'tpl:bg-[var(--tpl-bg)] tpl:text-[var(--tpl-text)] tpl:shadow-sm'\n : 'tpl:bg-transparent tpl:text-[var(--tpl-text-muted)] hover:tpl:text-[var(--tpl-text)]'\n \"\n @click=\"designTab = tab.id\"\n >\n {{ tab.label }}\n </button>\n </div>\n\n <template v-if=\"designTab === 'style'\">\n <p\n class=\"tpl:mb-4 tpl:text-[11px] tpl:font-semibold tpl:uppercase tpl:tracking-wide tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ t.popupDesign.standardSize }}\n </p>\n <div :class=\"presetGridClass\">\n <button\n v-for=\"preset in sizePresets\"\n :key=\"preset.id\"\n type=\"button\"\n class=\"tpl:flex tpl:flex-col tpl:items-center tpl:gap-2 tpl:rounded-lg tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:p-3 tpl:text-center tpl:transition-shadow hover:tpl:border-[var(--tpl-primary)]\"\n :class=\"\n popup.design.sizePreset === preset.id\n ? 'tpl:ring-2 tpl:ring-[var(--tpl-text)] tpl:ring-offset-2 tpl:ring-offset-[var(--tpl-bg)]'\n : ''\n \"\n @click=\"patchDesign({ sizePreset: preset.id })\"\n >\n <span\n class=\"tpl:flex tpl:h-12 tpl:w-full tpl:items-center tpl:justify-center tpl:rounded-md tpl:bg-[var(--tpl-bg-elevated)]\"\n >\n <span\n class=\"tpl:block tpl:rounded tpl:bg-[var(--tpl-text-muted)] tpl:opacity-80\"\n :class=\"{\n 'tpl:h-6 tpl:w-10': preset.id === 'small',\n 'tpl:h-8 tpl:w-12': preset.id === 'medium',\n 'tpl:h-10 tpl:w-14': preset.id === 'large',\n 'tpl:h-12 tpl:w-full': preset.id === 'fullscreen',\n 'tpl:h-12 tpl:w-8': preset.id === 'fullHeight',\n 'tpl:h-8 tpl:w-10 tpl:border tpl:border-dashed tpl:border-[var(--tpl-border)] tpl:bg-transparent':\n preset.id === 'custom',\n }\"\n />\n </span>\n <span\n class=\"tpl:text-[11px] tpl:font-medium tpl:text-[var(--tpl-text)]\"\n >{{ preset.label }}</span\n >\n </button>\n </div>\n\n <p\n class=\"tpl:mb-3 tpl:text-[11px] tpl:font-semibold tpl:uppercase tpl:tracking-wide tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ t.popupDesign.options }}\n </p>\n <div\n class=\"tpl:mb-8 tpl:space-y-5 tpl:rounded-[10px] tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-elevated)] tpl:px-4 tpl:py-5\"\n >\n <div\n class=\"tpl:flex tpl:min-h-[28px] tpl:items-center tpl:justify-between tpl:gap-4\"\n >\n <span\n class=\"tpl:text-sm tpl:leading-snug tpl:text-[var(--tpl-text)]\"\n >{{ t.popupDesign.useOverlay }}</span\n >\n <PopupDesignSwitch\n :checked=\"popup.design.useOverlay\"\n @toggle=\"setUseOverlay(!popup.design.useOverlay)\"\n />\n </div>\n <template v-if=\"popup.design.useOverlay\">\n <div\n class=\"tpl:flex tpl:min-h-[28px] tpl:items-center tpl:justify-between tpl:gap-4\"\n >\n <span\n class=\"tpl:text-sm tpl:leading-snug tpl:text-[var(--tpl-text)]\"\n >{{ t.popupDesign.overlayImage }}</span\n >\n <PopupDesignSwitch\n :checked=\"popup.design.overlayImage\"\n @toggle=\"toggleOverlayImage()\"\n />\n </div>\n\n <PopupOverlayImagePanel\n v-if=\"popup.design.overlayImage\"\n :image-url=\"popup.design.overlayImageUrl ?? ''\"\n @set-url=\"setOverlayImageUrl\"\n />\n\n <div\n v-if=\"\n popup.design.overlayImage &&\n (popup.design.overlayImageUrl ?? '').length > 0\n \"\n class=\"tpl:space-y-2\"\n >\n <div class=\"tpl:flex tpl:items-center tpl:justify-between\">\n <label\n class=\"tpl:block tpl:text-sm tpl:leading-snug tpl:text-[var(--tpl-text)]\"\n for=\"popup-overlay-image-opacity\"\n >\n {{ t.popupDesign.overlayImageOpacity }}\n </label>\n <span\n class=\"tpl:text-[11px] tpl:font-medium tpl:text-[var(--tpl-text-muted)] tpl:tabular-nums\"\n >\n {{ Math.round((popup.design.overlayImageOpacity ?? 1) * 100) }}%\n </span>\n </div>\n <input\n id=\"popup-overlay-image-opacity\"\n type=\"range\"\n class=\"tpl:w-full tpl:accent-[var(--tpl-primary)]\"\n min=\"0\"\n max=\"100\"\n step=\"5\"\n :value=\"Math.round((popup.design.overlayImageOpacity ?? 1) * 100)\"\n @input=\"onOverlayImageOpacityInput\"\n />\n </div>\n\n <div class=\"tpl:space-y-2\">\n <div class=\"tpl:flex tpl:items-center tpl:justify-between\">\n <label\n class=\"tpl:block tpl:text-sm tpl:leading-snug tpl:text-[var(--tpl-text)]\"\n for=\"popup-overlay-blur\"\n >\n {{ t.popupDesign.overlayBlur }}\n </label>\n <span\n class=\"tpl:text-[11px] tpl:font-medium tpl:text-[var(--tpl-text-muted)] tpl:tabular-nums\"\n >\n {{ Math.round(popup.design.overlayBlur ?? 8) }}px\n </span>\n </div>\n <input\n id=\"popup-overlay-blur\"\n type=\"range\"\n class=\"tpl:w-full tpl:accent-[var(--tpl-primary)]\"\n :min=\"0\"\n :max=\"30\"\n step=\"1\"\n :value=\"Math.round(popup.design.overlayBlur ?? 8)\"\n @input=\"onOverlayBlurInput\"\n />\n <p class=\"tpl:m-0 tpl:text-[11px] tpl:text-[var(--tpl-text-muted)]\">\n {{ t.popupDesign.overlayBlurHint }}\n </p>\n </div>\n </template>\n <div\n class=\"tpl:flex tpl:min-h-[28px] tpl:items-center tpl:justify-between tpl:gap-4\"\n >\n <span\n class=\"tpl:text-sm tpl:leading-snug tpl:text-[var(--tpl-text)]\"\n >{{ t.popupDesign.backgroundImage }}</span\n >\n <PopupDesignSwitch\n :checked=\"popup.design.backgroundImage\"\n @toggle=\"toggleBackgroundImage()\"\n />\n </div>\n\n <template v-if=\"popup.design.backgroundImage\">\n <PopupOverlayImagePanel\n :image-url=\"popup.design.backgroundImageUrl ?? ''\"\n @set-url=\"setBackgroundImageUrl\"\n />\n\n <div\n v-if=\"(popup.design.backgroundImageUrl ?? '').length > 0\"\n class=\"tpl:space-y-2\"\n >\n <label\n class=\"tpl:block tpl:text-sm tpl:leading-snug tpl:text-[var(--tpl-text)]\"\n for=\"popup-background-image-fit\"\n >\n {{ t.popupDesign.backgroundImageFitHeading }}\n </label>\n <select\n id=\"popup-background-image-fit\"\n class=\"tpl:w-full tpl:rounded-lg tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:px-3 tpl:py-2.5 tpl:text-sm tpl:text-[var(--tpl-text)] tpl:outline-none focus-visible:tpl:ring-2 focus-visible:tpl:ring-[var(--tpl-primary)] focus-visible:tpl:ring-offset-2 focus-visible:tpl:ring-offset-[var(--tpl-bg)]\"\n :value=\"popup.design.backgroundImageFit ?? 'cover'\"\n @change=\"onBackgroundImageFitChange\"\n >\n <option value=\"auto\">{{ t.popupDesign.fitAuto }}</option>\n <option value=\"cover\">{{ t.popupDesign.fitCover }}</option>\n <option value=\"contain\">{{ t.popupDesign.fitContain }}</option>\n </select>\n </div>\n\n <div\n v-if=\"(popup.design.backgroundImageUrl ?? '').length > 0\"\n class=\"tpl:flex tpl:min-h-[28px] tpl:items-center tpl:justify-between tpl:gap-4\"\n >\n <span\n class=\"tpl:text-sm tpl:leading-snug tpl:text-[var(--tpl-text)]\"\n >{{ t.popupDesign.backgroundImageOnly }}</span\n >\n <PopupDesignSwitch\n :checked=\"popup.design.backgroundImageOnly ?? false\"\n @toggle=\"\n toggleDesign(\n 'backgroundImageOnly',\n !(popup.design.backgroundImageOnly ?? false),\n )\n \"\n />\n </div>\n </template>\n </div>\n\n <!-- <div\n class=\"tpl:mb-8 tpl:rounded-[10px] tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-elevated)] tpl:px-4 tpl:py-5\"\n >\n <div\n class=\"tpl:mb-3 tpl:flex tpl:min-h-[28px] tpl:items-center tpl:justify-between tpl:gap-4\"\n >\n <span\n class=\"tpl:text-sm tpl:font-medium tpl:leading-snug tpl:text-[var(--tpl-text)]\"\n >\n {{ t.popupDesign.multiColumn }}\n </span>\n <PopupDesignSwitch\n :checked=\"popup.design.multiColumnEnabled\"\n @toggle=\"\n toggleDesign(\n 'multiColumnEnabled',\n !popup.design.multiColumnEnabled,\n )\n \"\n />\n </div>\n <div v-if=\"popup.design.multiColumnEnabled\" class=\"tpl:flex tpl:gap-3\">\n <button\n type=\"button\"\n class=\"tpl:flex-1 tpl:rounded-lg tpl:border tpl:px-3 tpl:py-4\"\n :class=\"\n popup.design.multiColumnPreset === 'two'\n ? 'tpl:border-[var(--tpl-text)] tpl:bg-[var(--tpl-bg-elevated)]'\n : 'tpl:border-[var(--tpl-border)]'\n \"\n @click=\"patchDesign({ multiColumnPreset: 'two' })\"\n >\n <span class=\"tpl:flex tpl:justify-center tpl:gap-0.5\">\n <span\n class=\"tpl:h-10 tpl:flex-1 tpl:rounded tpl:bg-[var(--tpl-text-muted)] tpl:opacity-80\"\n />\n <span\n class=\"tpl:h-10 tpl:flex-1 tpl:rounded tpl:bg-[var(--tpl-text-muted)] tpl:opacity-80\"\n />\n </span>\n </button>\n <button\n type=\"button\"\n class=\"tpl:flex-1 tpl:rounded-lg tpl:border tpl:px-3 tpl:py-4\"\n :class=\"\n popup.design.multiColumnPreset === 'three'\n ? 'tpl:border-[var(--tpl-text)] tpl:bg-[var(--tpl-bg-elevated)]'\n : 'tpl:border-[var(--tpl-border)]'\n \"\n @click=\"patchDesign({ multiColumnPreset: 'three' })\"\n >\n <span class=\"tpl:flex tpl:justify-center tpl:gap-0.5\">\n <span\n class=\"tpl:h-10 tpl:flex-1 tpl:rounded tpl:bg-[var(--tpl-bg-hover)]\"\n />\n <span\n class=\"tpl:h-10 tpl:flex-1 tpl:rounded tpl:bg-[var(--tpl-bg-hover)]\"\n />\n <span\n class=\"tpl:h-10 tpl:flex-1 tpl:rounded tpl:bg-[var(--tpl-bg-hover)]\"\n />\n </span>\n </button>\n </div>\n </div> -->\n\n <!-- <div\n class=\"tpl:flex tpl:min-h-[28px] tpl:items-center tpl:justify-between tpl:gap-4\"\n >\n <span class=\"tpl:text-sm tpl:leading-snug tpl:text-[var(--tpl-text)]\">{{\n t.popupDesign.rtl\n }}</span>\n <PopupDesignSwitch\n :checked=\"popup.design.rtl\"\n @toggle=\"toggleDesign('rtl', !popup.design.rtl)\"\n />\n </div> -->\n </template>\n\n <template v-else-if=\"designTab === 'position'\">\n <p\n class=\"tpl:mb-3 tpl:text-[11px] tpl:font-semibold tpl:uppercase tpl:tracking-wide tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ t.popupDesign.positionHeading }}\n </p>\n <div\n class=\"tpl:mb-8 tpl:grid tpl:grid-cols-3 tpl:gap-2\"\n role=\"group\"\n :aria-label=\"t.popupDesign.positionHeading\"\n >\n <button\n v-for=\"cell in POSITION_GRID\"\n :key=\"cell.id\"\n type=\"button\"\n class=\"tpl:flex tpl:min-h-[52px] tpl:w-full tpl:rounded-lg tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:p-2 tpl:transition-all tpl:outline-none hover:tpl:border-[var(--tpl-text-muted)] focus-visible:tpl:ring-2 focus-visible:tpl:ring-[var(--tpl-primary)] focus-visible:tpl:ring-offset-2 focus-visible:tpl:ring-offset-[var(--tpl-bg)]\"\n :class=\"\n popup.design.popupPosition === cell.id\n ? 'tpl:border-2 tpl:border-[var(--tpl-text)] tpl:bg-[var(--tpl-bg-elevated)]'\n : ''\n \"\n :aria-pressed=\"popup.design.popupPosition === cell.id\"\n :aria-label=\"t.popupDesign.positionLabels[cell.id]\"\n @click=\"patchDesign({ popupPosition: cell.id })\"\n >\n <div :class=\"cell.cellAlign\">\n <span\n class=\"tpl:block tpl:h-4 tpl:w-7 tpl:rounded-sm tpl:transition-colors\"\n :class=\"\n popup.design.popupPosition === cell.id\n ? 'tpl:bg-[var(--tpl-text)]'\n : 'tpl:bg-[var(--tpl-text-muted)] tpl:opacity-70'\n \"\n />\n </div>\n </button>\n </div>\n <p\n class=\"tpl:mb-2 tpl:text-[11px] tpl:font-semibold tpl:uppercase tpl:tracking-wide tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ t.popupDesign.displayAnimationHeading }}\n </p>\n <label class=\"tpl:sr-only\" for=\"popup-display-animation\">{{\n t.popupDesign.displayAnimationHeading\n }}</label>\n <select\n id=\"popup-display-animation\"\n class=\"tpl:w-full tpl:rounded-lg tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:px-3 tpl:py-2.5 tpl:text-sm tpl:text-[var(--tpl-text)] tpl:outline-none focus-visible:tpl:ring-2 focus-visible:tpl:ring-[var(--tpl-primary)] focus-visible:tpl:ring-offset-2 focus-visible:tpl:ring-offset-[var(--tpl-bg)]\"\n :value=\"popup.design.displayAnimation\"\n @change=\"onAnimationChange\"\n >\n <option\n v-for=\"opt in animationSelectOptions\"\n :key=\"opt.id\"\n :value=\"opt.id\"\n >\n {{ opt.label }}\n </option>\n </select>\n </template>\n\n <template v-else-if=\"designTab === 'closing'\">\n <div\n class=\"tpl:space-y-5 tpl:rounded-[10px] tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-elevated)] tpl:px-4 tpl:py-5\"\n >\n <div\n class=\"tpl:flex tpl:min-h-[28px] tpl:items-center tpl:justify-between tpl:gap-4\"\n >\n <span\n class=\"tpl:text-sm tpl:leading-snug tpl:text-[var(--tpl-text)]\"\n >{{ t.popupDesign.closingDisplayCloseButton }}</span\n >\n <PopupDesignSwitch\n :checked=\"popup.design.displayCloseButton\"\n @toggle=\"\n toggleDesign(\n 'displayCloseButton',\n !popup.design.displayCloseButton,\n )\n \"\n />\n </div>\n <div v-if=\"popup.design.displayCloseButton\" class=\"tpl:space-y-2\">\n <label\n class=\"tpl:block tpl:text-sm tpl:leading-snug tpl:text-[var(--tpl-text)]\"\n for=\"popup-close-button-position\"\n >\n {{ t.popupDesign.closingButtonPosition }}\n </label>\n <select\n id=\"popup-close-button-position\"\n class=\"tpl:w-full tpl:rounded-lg tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:px-3 tpl:py-2.5 tpl:text-sm tpl:text-[var(--tpl-text)] tpl:outline-none focus-visible:tpl:ring-2 focus-visible:tpl:ring-[var(--tpl-primary)] focus-visible:tpl:ring-offset-2 focus-visible:tpl:ring-offset-[var(--tpl-bg)]\"\n :value=\"popup.design.closeButtonPosition\"\n @change=\"onCloseButtonPositionChange\"\n >\n <option value=\"left\">\n {{ t.popupDesign.closingButtonPositionLeft }}\n </option>\n <option value=\"right\">\n {{ t.popupDesign.closingButtonPositionRight }}\n </option>\n </select>\n </div>\n <div\n class=\"tpl:flex tpl:min-h-[28px] tpl:items-center tpl:justify-between tpl:gap-4\"\n >\n <span\n class=\"tpl:text-sm tpl:leading-snug tpl:text-[var(--tpl-text)]\"\n >{{ t.popupDesign.closingOnOverlayClick }}</span\n >\n <PopupDesignSwitch\n :checked=\"popup.design.closeOnOverlayClick\"\n @toggle=\"\n toggleDesign(\n 'closeOnOverlayClick',\n !popup.design.closeOnOverlayClick,\n )\n \"\n />\n </div>\n </div>\n </template>\n\n <template v-else>\n <p class=\"tpl:text-sm tpl:text-[var(--tpl-text-muted)]\">\n {{ t.popupDesign.placeholderSubtab }}\n </p>\n </template>\n </div>\n</template>\n","import type { Block, BlockType } from \"@aswin.dev/types\";\nimport { createBlock, createCustomBlock } from \"@aswin.dev/types\";\nimport { computed, inject } from \"vue\";\nimport {\n BLOCK_DEFAULTS_KEY,\n CAPABILITIES_KEY,\n CUSTOM_BLOCK_DEFINITIONS_KEY,\n EDITOR_KEY,\n EDITOR_TYPE_KEY,\n} from \"../keys\";\nimport { getBlockTypeLabel } from \"../utils/blockTypeLabels\";\nimport { useI18n } from \"./useI18n\";\n\n// Popups use the Form block (multi-field opt-in) instead of the single\n// `input` field, so hide it from the palette there.\nconst POPUP_HIDDEN_BLOCK_TYPES = new Set<string>([\n \"social\",\n \"menu\",\n \"table\",\n \"input\",\n]);\n\n// The Form block is currently popup-only (it ships sync integrations and a\n// multi-step submit flow that don't apply to email exports). Hide it from\n// non-popup palettes for now.\nconst NON_POPUP_HIDDEN_BLOCK_TYPES = new Set<string>([\"form\"]);\n\nexport interface BlockPaletteItem {\n type: BlockType | string;\n label: string;\n isCustom?: boolean;\n icon?: string;\n}\n\nexport function useBlockPalette() {\n const { t, format } = useI18n();\n const customBlockDefinitions = inject(CUSTOM_BLOCK_DEFINITIONS_KEY, []);\n const blockDefaults = inject(BLOCK_DEFAULTS_KEY, undefined);\n const editor = inject(EDITOR_KEY, null);\n const caps = inject(CAPABILITIES_KEY, {});\n const editorType = inject(EDITOR_TYPE_KEY, \"email\");\n\n const showModulesSection = computed(\n () => (caps.savedModules?.moduleCount.value ?? 0) > 0,\n );\n\n const builtInBlockTypeOrder: string[] = [\n \"section\",\n \"image\",\n \"title\",\n \"paragraph\",\n \"button\",\n \"form\",\n \"input\",\n \"divider\",\n \"video\",\n \"social\",\n \"menu\",\n \"table\",\n \"countdown\",\n \"spacer\",\n \"html\",\n ];\n\n const builtInBlockTypes = computed<BlockPaletteItem[]>(() => {\n const hidden =\n editorType === \"popup\"\n ? POPUP_HIDDEN_BLOCK_TYPES\n : NON_POPUP_HIDDEN_BLOCK_TYPES;\n const order = builtInBlockTypeOrder.filter((type) => !hidden.has(type));\n\n return order.map((type) => ({\n type,\n label: getBlockTypeLabel(type, t),\n }));\n });\n\n const customBlockItems = computed<BlockPaletteItem[]>(() => {\n return customBlockDefinitions.map((def) => ({\n type: `custom:${def.type}`,\n label: def.name,\n isCustom: true,\n icon: def.icon,\n }));\n });\n\n const blockTypes = computed<BlockPaletteItem[]>(() => [\n ...builtInBlockTypes.value,\n ...customBlockItems.value,\n ]);\n\n function createBlockFromItem(item: BlockPaletteItem): Block {\n if (item.isCustom) {\n const customType = item.type.replace(\"custom:\", \"\");\n const definition = customBlockDefinitions.find(\n (d) => d.type === customType,\n );\n if (definition) {\n return createCustomBlock(definition);\n }\n }\n\n return createBlock(item.type as BlockType, blockDefaults);\n }\n\n function insertBlockFromItem(item: BlockPaletteItem): void {\n if (!editor) return;\n const block = createBlockFromItem(item);\n editor.addBlock(block);\n editor.selectBlock(block.id);\n }\n\n function handlePaletteKeydown(\n event: KeyboardEvent,\n item: BlockPaletteItem,\n ): void {\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n insertBlockFromItem(item);\n }\n }\n\n return {\n t,\n format,\n blockTypes,\n createBlockFromItem,\n insertBlockFromItem,\n handlePaletteKeydown,\n caps,\n showModulesSection,\n };\n}\n","<script setup lang=\"ts\">\nimport { Package } from \"@lucide/vue\";\nimport { computed, ref } from \"vue\";\nimport draggable from \"vuedraggable\";\nimport CustomBlockIcon from \"./CustomBlockIcon.vue\";\nimport { blockTypeIcons } from \"../utils/blockTypeIcons\";\nimport { useBlockPalette } from \"../composables/useBlockPalette\";\nimport { useCloudI18n } from \"../composables/useCloudI18n\";\n\n/** `standalone`: docked to viewport left. `popupAdjacent`: beside popup icon rail. */\nconst props = withDefaults(\n defineProps<{ layout?: \"standalone\" | \"popupAdjacent\" }>(),\n { layout: \"standalone\" },\n);\n\nconst hoverExpanded = ref(false);\n\n/** Popup chrome uses a narrow strip; keep labels visible without hover. Standalone email sidebar stays hover/focus-expand. */\nconst isExpanded = computed(\n () => props.layout === \"popupAdjacent\" || hoverExpanded.value,\n);\n\nfunction setAsideExpanded(next: boolean): void {\n if (props.layout === \"popupAdjacent\") return;\n hoverExpanded.value = next;\n}\n\nconst {\n t,\n format,\n blockTypes,\n createBlockFromItem,\n insertBlockFromItem,\n handlePaletteKeydown,\n showModulesSection,\n caps,\n} = useBlockPalette();\n\nconst { t: cloudT } = useCloudI18n();\n\nfunction openModulesBrowser(): void {\n caps.savedModules?.openBrowser();\n}\n</script>\n\n<template>\n <aside\n :aria-label=\"t.sidebarNav.palette\"\n :class=\"\n props.layout === 'standalone'\n ? 'tpl-sidebar-rail tpl:absolute tpl:top-14 tpl:bottom-0 tpl:left-0 tpl:z-40 tpl:overflow-hidden'\n : 'tpl-sidebar-rail tpl:relative tpl:z-0 tpl:h-full tpl:min-h-0 tpl:shrink-0 tpl:overflow-hidden'\n \"\n :style=\"{\n width: isExpanded ? '200px' : '48px',\n backgroundColor: 'var(--tpl-bg-elevated)',\n borderRight: '1px solid var(--tpl-border)',\n boxShadow: isExpanded ? 'var(--tpl-shadow-lg)' : 'none',\n transition:\n 'width 200ms cubic-bezier(0.16, 1, 0.3, 1), box-shadow 200ms cubic-bezier(0.16, 1, 0.3, 1)',\n }\"\n @mouseenter=\"setAsideExpanded(true)\"\n @mouseleave=\"setAsideExpanded(false)\"\n @focusin=\"setAsideExpanded(true)\"\n @focusout=\"setAsideExpanded(false)\"\n >\n <!-- Saved Modules browser trigger (cloud only) -->\n <div\n v-if=\"showModulesSection && cloudT\"\n class=\"tpl:border-b tpl:px-1 tpl:pb-1 tpl:border-[var(--tpl-border)]\"\n >\n <button\n type=\"button\"\n :aria-label=\"t.sidebarNav.browseModules\"\n class=\"tpl:flex tpl:h-10 tpl:w-full tpl:cursor-pointer tpl:items-center tpl:gap-3 tpl:rounded-[var(--tpl-radius-sm)] tpl:border-none tpl:bg-transparent tpl:px-3 tpl:text-[var(--tpl-text-muted)] tpl:transition-all tpl:duration-[120ms] hover:tpl:bg-[var(--tpl-primary-light)] hover:tpl:text-[var(--tpl-primary)]\"\n :style=\"{\n justifyContent: isExpanded ? 'flex-start' : 'center',\n }\"\n @click=\"openModulesBrowser\"\n >\n <Package :size=\"20\" :stroke-width=\"1.5\" class=\"tpl:shrink-0\" />\n <span\n v-if=\"isExpanded\"\n class=\"tpl:flex-1 tpl:truncate tpl:text-sm tpl:font-medium\"\n >\n {{ cloudT.modules.title }}\n </span>\n <span\n v-if=\"isExpanded\"\n class=\"tpl:shrink-0 tpl:rounded-full tpl:px-1.5 tpl:py-0.5 tpl:text-[10px] tpl:font-medium tpl:bg-[var(--tpl-bg-hover)] tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ caps.savedModules?.moduleCount.value ?? 0 }}\n </span>\n </button>\n </div>\n <draggable\n :list=\"blockTypes\"\n :group=\"{ name: 'blocks', pull: 'clone', put: false }\"\n :clone=\"createBlockFromItem\"\n :sort=\"false\"\n item-key=\"type\"\n :animation=\"150\"\n ghost-class=\"tpl-ghost\"\n class=\"tpl:flex tpl:flex-col tpl:gap-0.5 tpl:p-1\"\n >\n <template #item=\"{ element: blockType }\">\n <button\n type=\"button\"\n :data-palette-type=\"blockType.type\"\n :aria-label=\"\n format(t.sidebarNav.insertBlock, { block: blockType.label })\n \"\n class=\"tpl:flex tpl:h-10 tpl:w-full tpl:cursor-grab tpl:items-center tpl:gap-3 tpl:rounded-[var(--tpl-radius-sm)] tpl:border-none tpl:bg-transparent tpl:px-3 tpl:text-[var(--tpl-text-muted)] tpl:transition-all tpl:duration-[120ms] tpl:ease-[cubic-bezier(0.16,1,0.3,1)] hover:tpl:bg-[var(--tpl-primary-light)] hover:tpl:text-[var(--tpl-primary)] active:tpl:cursor-grabbing\"\n :style=\"{\n justifyContent: isExpanded ? 'flex-start' : 'center',\n }\"\n @click=\"insertBlockFromItem(blockType)\"\n @keydown=\"handlePaletteKeydown($event, blockType)\"\n >\n <div\n class=\"tpl:flex tpl:shrink-0 tpl:items-center tpl:justify-center tpl:transition-transform tpl:duration-[120ms] tpl:ease-[cubic-bezier(0.16,1,0.3,1)] hover:tpl:scale-105\"\n >\n <component\n :is=\"blockTypeIcons[blockType.type]\"\n v-if=\"blockTypeIcons[blockType.type]\"\n :size=\"20\"\n :stroke-width=\"1.5\"\n />\n <CustomBlockIcon\n v-else-if=\"blockType.isCustom\"\n :icon=\"blockType.icon\"\n :size=\"20\"\n />\n </div>\n <span\n v-if=\"isExpanded\"\n class=\"tpl:truncate tpl:text-sm tpl:font-medium\"\n >\n {{ blockType.label }}\n </span>\n </button>\n </template>\n </draggable>\n </aside>\n</template>\n","<script setup lang=\"ts\">\nimport { Package } from \"@lucide/vue\";\nimport { computed, ref } from \"vue\";\nimport draggable from \"vuedraggable\";\nimport CustomBlockIcon from \"./CustomBlockIcon.vue\";\nimport { blockTypeIcons } from \"../utils/blockTypeIcons\";\nimport { useBlockPalette } from \"../composables/useBlockPalette\";\nimport { useCloudI18n } from \"../composables/useCloudI18n\";\n\n/** `standalone`: docked to viewport left. `popupAdjacent`: beside popup icon rail. */\nconst props = withDefaults(\n defineProps<{ layout?: \"standalone\" | \"popupAdjacent\" }>(),\n { layout: \"standalone\" },\n);\n\nconst hoverExpanded = ref(false);\n\n/** Popup chrome uses a narrow strip; keep labels visible without hover. Standalone email sidebar stays hover/focus-expand. */\nconst isExpanded = computed(\n () => props.layout === \"popupAdjacent\" || hoverExpanded.value,\n);\n\nfunction setAsideExpanded(next: boolean): void {\n if (props.layout === \"popupAdjacent\") return;\n hoverExpanded.value = next;\n}\n\nconst {\n t,\n format,\n blockTypes,\n createBlockFromItem,\n insertBlockFromItem,\n handlePaletteKeydown,\n showModulesSection,\n caps,\n} = useBlockPalette();\n\nconst { t: cloudT } = useCloudI18n();\n\nfunction openModulesBrowser(): void {\n caps.savedModules?.openBrowser();\n}\n</script>\n\n<template>\n <aside\n :aria-label=\"t.sidebarNav.palette\"\n :class=\"\n props.layout === 'standalone'\n ? 'tpl-sidebar-rail tpl:absolute tpl:top-14 tpl:bottom-0 tpl:left-0 tpl:z-40 tpl:overflow-hidden'\n : 'tpl-sidebar-rail tpl:relative tpl:z-0 tpl:h-full tpl:min-h-0 tpl:shrink-0 tpl:overflow-hidden'\n \"\n :style=\"{\n width: isExpanded ? '200px' : '48px',\n backgroundColor: 'var(--tpl-bg-elevated)',\n borderRight: '1px solid var(--tpl-border)',\n boxShadow: isExpanded ? 'var(--tpl-shadow-lg)' : 'none',\n transition:\n 'width 200ms cubic-bezier(0.16, 1, 0.3, 1), box-shadow 200ms cubic-bezier(0.16, 1, 0.3, 1)',\n }\"\n @mouseenter=\"setAsideExpanded(true)\"\n @mouseleave=\"setAsideExpanded(false)\"\n @focusin=\"setAsideExpanded(true)\"\n @focusout=\"setAsideExpanded(false)\"\n >\n <!-- Saved Modules browser trigger (cloud only) -->\n <div\n v-if=\"showModulesSection && cloudT\"\n class=\"tpl:border-b tpl:px-1 tpl:pb-1 tpl:border-[var(--tpl-border)]\"\n >\n <button\n type=\"button\"\n :aria-label=\"t.sidebarNav.browseModules\"\n class=\"tpl:flex tpl:h-10 tpl:w-full tpl:cursor-pointer tpl:items-center tpl:gap-3 tpl:rounded-[var(--tpl-radius-sm)] tpl:border-none tpl:bg-transparent tpl:px-3 tpl:text-[var(--tpl-text-muted)] tpl:transition-all tpl:duration-[120ms] hover:tpl:bg-[var(--tpl-primary-light)] hover:tpl:text-[var(--tpl-primary)]\"\n :style=\"{\n justifyContent: isExpanded ? 'flex-start' : 'center',\n }\"\n @click=\"openModulesBrowser\"\n >\n <Package :size=\"20\" :stroke-width=\"1.5\" class=\"tpl:shrink-0\" />\n <span\n v-if=\"isExpanded\"\n class=\"tpl:flex-1 tpl:truncate tpl:text-sm tpl:font-medium\"\n >\n {{ cloudT.modules.title }}\n </span>\n <span\n v-if=\"isExpanded\"\n class=\"tpl:shrink-0 tpl:rounded-full tpl:px-1.5 tpl:py-0.5 tpl:text-[10px] tpl:font-medium tpl:bg-[var(--tpl-bg-hover)] tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ caps.savedModules?.moduleCount.value ?? 0 }}\n </span>\n </button>\n </div>\n <draggable\n :list=\"blockTypes\"\n :group=\"{ name: 'blocks', pull: 'clone', put: false }\"\n :clone=\"createBlockFromItem\"\n :sort=\"false\"\n item-key=\"type\"\n :animation=\"150\"\n ghost-class=\"tpl-ghost\"\n class=\"tpl:flex tpl:flex-col tpl:gap-0.5 tpl:p-1\"\n >\n <template #item=\"{ element: blockType }\">\n <button\n type=\"button\"\n :data-palette-type=\"blockType.type\"\n :aria-label=\"\n format(t.sidebarNav.insertBlock, { block: blockType.label })\n \"\n class=\"tpl:flex tpl:h-10 tpl:w-full tpl:cursor-grab tpl:items-center tpl:gap-3 tpl:rounded-[var(--tpl-radius-sm)] tpl:border-none tpl:bg-transparent tpl:px-3 tpl:text-[var(--tpl-text-muted)] tpl:transition-all tpl:duration-[120ms] tpl:ease-[cubic-bezier(0.16,1,0.3,1)] hover:tpl:bg-[var(--tpl-primary-light)] hover:tpl:text-[var(--tpl-primary)] active:tpl:cursor-grabbing\"\n :style=\"{\n justifyContent: isExpanded ? 'flex-start' : 'center',\n }\"\n @click=\"insertBlockFromItem(blockType)\"\n @keydown=\"handlePaletteKeydown($event, blockType)\"\n >\n <div\n class=\"tpl:flex tpl:shrink-0 tpl:items-center tpl:justify-center tpl:transition-transform tpl:duration-[120ms] tpl:ease-[cubic-bezier(0.16,1,0.3,1)] hover:tpl:scale-105\"\n >\n <component\n :is=\"blockTypeIcons[blockType.type]\"\n v-if=\"blockTypeIcons[blockType.type]\"\n :size=\"20\"\n :stroke-width=\"1.5\"\n />\n <CustomBlockIcon\n v-else-if=\"blockType.isCustom\"\n :icon=\"blockType.icon\"\n :size=\"20\"\n />\n </div>\n <span\n v-if=\"isExpanded\"\n class=\"tpl:truncate tpl:text-sm tpl:font-medium\"\n >\n {{ blockType.label }}\n </span>\n </button>\n </template>\n </draggable>\n </aside>\n</template>\n","<script setup lang=\"ts\">\nimport { Calendar, LayoutGrid, Palette, ScrollText } from \"@lucide/vue\";\nimport { computed, inject, ref } from \"vue\";\nimport { useI18n } from \"../composables/useI18n\";\nimport { POPUP_RAIL_TAB_KEY } from \"../keys\";\nimport type { PopupRailTabId } from \"../types/popup-rail-tab\";\nimport PopupDesignView from \"./PopupDesignView.vue\";\nimport SidebarEmailRail from \"./SidebarEmailRail.vue\";\n\nconst fallbackRailTab = ref<PopupRailTabId>(\"blocks\");\nconst popupRailTab = inject(POPUP_RAIL_TAB_KEY, fallbackRailTab);\n\nconst { t } = useI18n();\n\nconst railTabs = computed(() => [\n { id: \"design\" as const, icon: Palette, label: t.popupSidebar.railDesign },\n { id: \"blocks\" as const, icon: LayoutGrid, label: t.popupSidebar.railBlocks },\n {\n id: \"displayRules\" as const,\n icon: ScrollText,\n label: t.popupSidebar.railDisplayRules,\n },\n {\n id: \"schedule\" as const,\n icon: Calendar,\n label: t.popupSidebar.railSchedule,\n },\n]);\n</script>\n\n<template>\n <div\n class=\"tpl-popup-left-chrome tpl:absolute tpl:top-14 tpl:bottom-0 tpl:left-0 tpl:z-40 tpl:flex tpl:overflow-hidden\"\n style=\"\n border-right: 1px solid var(--tpl-border);\n box-shadow: var(--tpl-shadow-sm);\n \"\n :aria-label=\"t.popupSidebar.workspaceLabel\"\n >\n <nav\n class=\"tpl:flex tpl:w-[72px] tpl:shrink-0 tpl:flex-col tpl:items-stretch tpl:gap-0.5 tpl:overflow-y-auto tpl:py-2 tpl:px-1\"\n style=\"\n background-color: color-mix(\n in srgb,\n var(--tpl-bg-elevated) 92%,\n transparent\n );\n \"\n :aria-label=\"t.popupSidebar.railNavLabel\"\n >\n <button\n v-for=\"tab in railTabs\"\n :key=\"tab.id\"\n type=\"button\"\n class=\"tpl:flex tpl:flex-col tpl:items-center tpl:justify-center tpl:gap-0.5 tpl:rounded-[var(--tpl-radius-sm)] tpl:border-none tpl:py-2 tpl:px-1 tpl:text-[var(--tpl-text-muted)] tpl:transition-all tpl:duration-150 focus-visible:tpl:outline focus-visible:tpl:outline-2 focus-visible:tpl:outline-offset-[-2px] focus-visible:tpl:outline-[var(--tpl-primary)]\"\n :class=\"\n popupRailTab === tab.id\n ? 'tpl:bg-[var(--tpl-bg)] tpl:text-[var(--tpl-primary)] tpl:shadow-sm'\n : 'tpl:bg-transparent hover:tpl:bg-[var(--tpl-bg-hover)] hover:tpl:text-[var(--tpl-text)]'\n \"\n :aria-current=\"popupRailTab === tab.id ? 'page' : undefined\"\n @click=\"popupRailTab = tab.id\"\n >\n <component\n :is=\"tab.icon\"\n :size=\"18\"\n :stroke-width=\"1.75\"\n class=\"tpl:shrink-0\"\n />\n <span\n class=\"tpl:max-w-[4.25rem] tpl:text-center tpl:text-[10px] tpl:font-medium tpl:leading-tight\"\n >\n {{ tab.label }}\n </span>\n </button>\n </nav>\n\n <SidebarEmailRail v-if=\"popupRailTab === 'blocks'\" layout=\"popupAdjacent\" />\n\n <div\n v-else-if=\"popupRailTab === 'design'\"\n class=\"tpl:flex tpl:h-full tpl:w-[320px] tpl:min-h-0 tpl:shrink-0 tpl:flex-col tpl:overflow-y-auto tpl:overflow-x-hidden tpl:border-l tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)]\"\n role=\"region\"\n :aria-label=\"t.popupSidebar.railDesign\"\n >\n <PopupDesignView layout=\"popupAdjacent\" />\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { Calendar, LayoutGrid, Palette, ScrollText } from \"@lucide/vue\";\nimport { computed, inject, ref } from \"vue\";\nimport { useI18n } from \"../composables/useI18n\";\nimport { POPUP_RAIL_TAB_KEY } from \"../keys\";\nimport type { PopupRailTabId } from \"../types/popup-rail-tab\";\nimport PopupDesignView from \"./PopupDesignView.vue\";\nimport SidebarEmailRail from \"./SidebarEmailRail.vue\";\n\nconst fallbackRailTab = ref<PopupRailTabId>(\"blocks\");\nconst popupRailTab = inject(POPUP_RAIL_TAB_KEY, fallbackRailTab);\n\nconst { t } = useI18n();\n\nconst railTabs = computed(() => [\n { id: \"design\" as const, icon: Palette, label: t.popupSidebar.railDesign },\n { id: \"blocks\" as const, icon: LayoutGrid, label: t.popupSidebar.railBlocks },\n {\n id: \"displayRules\" as const,\n icon: ScrollText,\n label: t.popupSidebar.railDisplayRules,\n },\n {\n id: \"schedule\" as const,\n icon: Calendar,\n label: t.popupSidebar.railSchedule,\n },\n]);\n</script>\n\n<template>\n <div\n class=\"tpl-popup-left-chrome tpl:absolute tpl:top-14 tpl:bottom-0 tpl:left-0 tpl:z-40 tpl:flex tpl:overflow-hidden\"\n style=\"\n border-right: 1px solid var(--tpl-border);\n box-shadow: var(--tpl-shadow-sm);\n \"\n :aria-label=\"t.popupSidebar.workspaceLabel\"\n >\n <nav\n class=\"tpl:flex tpl:w-[72px] tpl:shrink-0 tpl:flex-col tpl:items-stretch tpl:gap-0.5 tpl:overflow-y-auto tpl:py-2 tpl:px-1\"\n style=\"\n background-color: color-mix(\n in srgb,\n var(--tpl-bg-elevated) 92%,\n transparent\n );\n \"\n :aria-label=\"t.popupSidebar.railNavLabel\"\n >\n <button\n v-for=\"tab in railTabs\"\n :key=\"tab.id\"\n type=\"button\"\n class=\"tpl:flex tpl:flex-col tpl:items-center tpl:justify-center tpl:gap-0.5 tpl:rounded-[var(--tpl-radius-sm)] tpl:border-none tpl:py-2 tpl:px-1 tpl:text-[var(--tpl-text-muted)] tpl:transition-all tpl:duration-150 focus-visible:tpl:outline focus-visible:tpl:outline-2 focus-visible:tpl:outline-offset-[-2px] focus-visible:tpl:outline-[var(--tpl-primary)]\"\n :class=\"\n popupRailTab === tab.id\n ? 'tpl:bg-[var(--tpl-bg)] tpl:text-[var(--tpl-primary)] tpl:shadow-sm'\n : 'tpl:bg-transparent hover:tpl:bg-[var(--tpl-bg-hover)] hover:tpl:text-[var(--tpl-text)]'\n \"\n :aria-current=\"popupRailTab === tab.id ? 'page' : undefined\"\n @click=\"popupRailTab = tab.id\"\n >\n <component\n :is=\"tab.icon\"\n :size=\"18\"\n :stroke-width=\"1.75\"\n class=\"tpl:shrink-0\"\n />\n <span\n class=\"tpl:max-w-[4.25rem] tpl:text-center tpl:text-[10px] tpl:font-medium tpl:leading-tight\"\n >\n {{ tab.label }}\n </span>\n </button>\n </nav>\n\n <SidebarEmailRail v-if=\"popupRailTab === 'blocks'\" layout=\"popupAdjacent\" />\n\n <div\n v-else-if=\"popupRailTab === 'design'\"\n class=\"tpl:flex tpl:h-full tpl:w-[320px] tpl:min-h-0 tpl:shrink-0 tpl:flex-col tpl:overflow-y-auto tpl:overflow-x-hidden tpl:border-l tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)]\"\n role=\"region\"\n :aria-label=\"t.popupSidebar.railDesign\"\n >\n <PopupDesignView layout=\"popupAdjacent\" />\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { inject } from \"vue\";\nimport { EDITOR_TYPE_KEY } from \"../keys\";\nimport PopupLeftChrome from \"./PopupLeftChrome.vue\";\nimport SidebarEmailRail from \"./SidebarEmailRail.vue\";\n\nconst editorType = inject(EDITOR_TYPE_KEY, \"email\");\n</script>\n\n<template>\n <PopupLeftChrome v-if=\"editorType === 'popup'\" />\n <SidebarEmailRail v-else />\n</template>\n","<script setup lang=\"ts\">\nimport { inject } from \"vue\";\nimport { EDITOR_TYPE_KEY } from \"../keys\";\nimport PopupLeftChrome from \"./PopupLeftChrome.vue\";\nimport SidebarEmailRail from \"./SidebarEmailRail.vue\";\n\nconst editorType = inject(EDITOR_TYPE_KEY, \"email\");\n</script>\n\n<template>\n <PopupLeftChrome v-if=\"editorType === 'popup'\" />\n <SidebarEmailRail v-else />\n</template>\n","import {\n getLogicMergeTagKeyword,\n isLogicMergeTagValue,\n} from \"@aswin.dev/types\";\nimport { computed, nextTick, ref, type ComputedRef, type Ref } from \"vue\";\nimport { useMergeTag } from \"./useMergeTag\";\n\nexport type MergeTagSegment =\n | { type: \"text\"; value: string }\n | { type: \"mergeTag\"; value: string; label: string }\n | { type: \"logicMergeTag\"; value: string; keyword: string };\n\nexport interface UseMergeTagFieldOptions {\n modelValue: () => string;\n emit: (value: string) => void;\n elementRef: Ref<HTMLInputElement | HTMLTextAreaElement | null>;\n}\n\nexport interface UseMergeTagFieldReturn {\n segments: ComputedRef<MergeTagSegment[]>;\n hasMergeTags: ComputedRef<boolean>;\n canRequestMergeTag: boolean;\n isRequestingMergeTag: Ref<boolean>;\n isEditing: Ref<boolean>;\n startEditing: () => void;\n stopEditing: () => void;\n handleInput: (event: Event) => void;\n clearValue: () => void;\n insertMergeTag: () => Promise<void>;\n}\n\nexport function useMergeTagField(\n options: UseMergeTagFieldOptions,\n): UseMergeTagFieldReturn {\n const { modelValue, emit, elementRef } = options;\n\n const {\n canRequestMergeTag,\n isRequesting: isRequestingMergeTag,\n isMergeTagValue,\n getMergeTagLabel,\n requestMergeTag,\n syntax,\n } = useMergeTag();\n\n const isEditing = ref(false);\n let insertingMergeTag = false;\n\n const segments = computed((): MergeTagSegment[] => {\n const val = modelValue();\n if (!val) return [];\n\n const result: MergeTagSegment[] = [];\n const combinedSource = `(${syntax.value.source}|${syntax.logic.source})`;\n const regex = new RegExp(combinedSource, \"g\");\n let lastIndex = 0;\n let match;\n\n while ((match = regex.exec(val)) !== null) {\n if (match.index > lastIndex) {\n result.push({\n type: \"text\",\n value: val.slice(lastIndex, match.index),\n });\n }\n\n const matched = match[0];\n if (isMergeTagValue(matched)) {\n result.push({\n type: \"mergeTag\",\n value: matched,\n label: getMergeTagLabel(matched),\n });\n } else if (isLogicMergeTagValue(matched, syntax)) {\n result.push({\n type: \"logicMergeTag\",\n value: matched,\n keyword: getLogicMergeTagKeyword(matched, syntax),\n });\n } else {\n result.push({ type: \"text\", value: matched });\n }\n\n lastIndex = match.index + matched.length;\n }\n\n if (lastIndex < val.length) {\n result.push({ type: \"text\", value: val.slice(lastIndex) });\n }\n\n return result;\n });\n\n const hasMergeTags = computed(() =>\n segments.value.some(\n (s) => s.type === \"mergeTag\" || s.type === \"logicMergeTag\",\n ),\n );\n\n function startEditing(): void {\n isEditing.value = true;\n nextTick(() => {\n elementRef.value?.focus();\n const len = modelValue()?.length || 0;\n elementRef.value?.setSelectionRange(len, len);\n });\n }\n\n function stopEditing(): void {\n if (insertingMergeTag) return;\n isEditing.value = false;\n }\n\n function handleInput(event: Event): void {\n emit((event.target as HTMLInputElement | HTMLTextAreaElement).value);\n }\n\n function clearValue(): void {\n emit(\"\");\n }\n\n async function insertMergeTag(): Promise<void> {\n const cursorPos =\n isEditing.value && elementRef.value\n ? (elementRef.value.selectionStart ?? modelValue().length)\n : modelValue().length;\n\n insertingMergeTag = true;\n let mergeTag: Awaited<ReturnType<typeof requestMergeTag>>;\n try {\n mergeTag = await requestMergeTag();\n } finally {\n insertingMergeTag = false;\n }\n\n if (mergeTag) {\n const before = modelValue().slice(0, cursorPos);\n const after = modelValue().slice(cursorPos);\n const newValue = before + mergeTag.value + after;\n emit(newValue);\n\n isEditing.value = true;\n nextTick(() => {\n const newPos = cursorPos + mergeTag.value.length;\n elementRef.value?.focus();\n elementRef.value?.setSelectionRange(newPos, newPos);\n });\n }\n }\n\n return {\n segments,\n hasMergeTags,\n canRequestMergeTag,\n isRequestingMergeTag,\n isEditing,\n startEditing,\n stopEditing,\n handleInput,\n clearValue,\n insertMergeTag,\n };\n}\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../composables/useI18n\";\nimport type { MergeTagSegment } from \"../composables/useMergeTagField\";\nimport { X } from \"@lucide/vue\";\n\ndefineProps<{\n segments: MergeTagSegment[];\n displayClass: string;\n pulse?: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"edit\"): void;\n (e: \"clear\"): void;\n}>();\n\nconst { t } = useI18n();\n\nfunction onEdit(): void {\n emit(\"edit\");\n}\n</script>\n\n<template>\n <div\n role=\"button\"\n tabindex=\"0\"\n :aria-label=\"t.mergeTag.clickToEdit\"\n :class=\"[displayClass, { 'tpl-pulse-fill': pulse }]\"\n @click=\"onEdit\"\n @keydown.enter=\"onEdit\"\n @keydown.space.prevent=\"onEdit\"\n >\n <template\n v-for=\"(seg, i) in segments\"\n :key=\"`${seg.type}-${i}-${seg.value}`\"\n >\n <span\n v-if=\"seg.type === 'mergeTag'\"\n class=\"tpl-tooltip tpl:inline-flex tpl:items-center tpl:gap-1 tpl:rounded tpl:px-1.5 tpl:py-0.5 tpl:text-[0.9em] tpl:font-medium\"\n :data-tooltip=\"seg.value\"\n style=\"\n background-color: color-mix(\n in srgb,\n var(--tpl-primary) 20%,\n transparent\n );\n color: var(--tpl-primary);\n \"\n >\n {{ seg.label }}\n </span>\n <span\n v-else-if=\"seg.type === 'logicMergeTag'\"\n class=\"tpl-tooltip tpl:inline-flex tpl:items-center tpl:rounded tpl:px-1.5 tpl:py-0.5 tpl:text-[0.8em] tpl:font-bold tpl:tracking-wide tpl:uppercase\"\n :data-tooltip=\"seg.value\"\n style=\"\n background-color: transparent;\n border: 1.5px solid\n color-mix(in srgb, var(--tpl-primary) 50%, transparent);\n color: var(--tpl-primary);\n \"\n >\n {{ seg.keyword }}\n </span>\n <span v-else class=\"tpl:text-sm tpl:text-[var(--tpl-text)]\">{{\n seg.value\n }}</span>\n </template>\n <button\n type=\"button\"\n class=\"tpl:ml-auto tpl:flex tpl:size-6 tpl:shrink-0 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-full tpl:border-none tpl:bg-transparent tpl:p-0 tpl:text-[var(--tpl-text-dim)] tpl:opacity-60 tpl:transition-all hover:tpl:text-[var(--tpl-danger)] hover:tpl:opacity-100\"\n :aria-label=\"t.mergeTag.remove\"\n :title=\"t.mergeTag.remove\"\n @click.stop=\"emit('clear')\"\n >\n <X :size=\"12\" :stroke-width=\"2.5\" />\n </button>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../composables/useI18n\";\nimport type { MergeTagSegment } from \"../composables/useMergeTagField\";\nimport { X } from \"@lucide/vue\";\n\ndefineProps<{\n segments: MergeTagSegment[];\n displayClass: string;\n pulse?: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"edit\"): void;\n (e: \"clear\"): void;\n}>();\n\nconst { t } = useI18n();\n\nfunction onEdit(): void {\n emit(\"edit\");\n}\n</script>\n\n<template>\n <div\n role=\"button\"\n tabindex=\"0\"\n :aria-label=\"t.mergeTag.clickToEdit\"\n :class=\"[displayClass, { 'tpl-pulse-fill': pulse }]\"\n @click=\"onEdit\"\n @keydown.enter=\"onEdit\"\n @keydown.space.prevent=\"onEdit\"\n >\n <template\n v-for=\"(seg, i) in segments\"\n :key=\"`${seg.type}-${i}-${seg.value}`\"\n >\n <span\n v-if=\"seg.type === 'mergeTag'\"\n class=\"tpl-tooltip tpl:inline-flex tpl:items-center tpl:gap-1 tpl:rounded tpl:px-1.5 tpl:py-0.5 tpl:text-[0.9em] tpl:font-medium\"\n :data-tooltip=\"seg.value\"\n style=\"\n background-color: color-mix(\n in srgb,\n var(--tpl-primary) 20%,\n transparent\n );\n color: var(--tpl-primary);\n \"\n >\n {{ seg.label }}\n </span>\n <span\n v-else-if=\"seg.type === 'logicMergeTag'\"\n class=\"tpl-tooltip tpl:inline-flex tpl:items-center tpl:rounded tpl:px-1.5 tpl:py-0.5 tpl:text-[0.8em] tpl:font-bold tpl:tracking-wide tpl:uppercase\"\n :data-tooltip=\"seg.value\"\n style=\"\n background-color: transparent;\n border: 1.5px solid\n color-mix(in srgb, var(--tpl-primary) 50%, transparent);\n color: var(--tpl-primary);\n \"\n >\n {{ seg.keyword }}\n </span>\n <span v-else class=\"tpl:text-sm tpl:text-[var(--tpl-text)]\">{{\n seg.value\n }}</span>\n </template>\n <button\n type=\"button\"\n class=\"tpl:ml-auto tpl:flex tpl:size-6 tpl:shrink-0 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-full tpl:border-none tpl:bg-transparent tpl:p-0 tpl:text-[var(--tpl-text-dim)] tpl:opacity-60 tpl:transition-all hover:tpl:text-[var(--tpl-danger)] hover:tpl:opacity-100\"\n :aria-label=\"t.mergeTag.remove\"\n :title=\"t.mergeTag.remove\"\n @click.stop=\"emit('clear')\"\n >\n <X :size=\"12\" :stroke-width=\"2.5\" />\n </button>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../composables/useI18n\";\nimport { ScanLine } from \"@lucide/vue\";\n\ndefineProps<{\n disabled?: boolean;\n}>();\n\ndefineEmits<{\n (e: \"insert\"): void;\n}>();\n\nconst { t } = useI18n();\n\nconst mergeTagBtnClass =\n \"tpl:flex tpl:items-center tpl:justify-center tpl:gap-1 tpl:rounded-[var(--tpl-radius-sm)] tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-hover)] tpl:px-2 tpl:py-1 tpl:text-xs tpl:text-[var(--tpl-text-muted)] tpl:transition-all tpl:duration-[120ms] hover:tpl:bg-[var(--tpl-primary-light)] hover:tpl:text-[var(--tpl-primary)] hover:tpl:border-[var(--tpl-primary)]\";\n</script>\n\n<template>\n <button\n type=\"button\"\n :class=\"[mergeTagBtnClass, 'tpl:mt-1.5']\"\n :aria-label=\"t.mergeTag.insert\"\n :title=\"t.mergeTag.insert\"\n :disabled=\"disabled\"\n @click=\"$emit('insert')\"\n >\n <ScanLine :size=\"12\" :stroke-width=\"2\" />\n {{ t.mergeTag.insert }}\n </button>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../composables/useI18n\";\nimport { ScanLine } from \"@lucide/vue\";\n\ndefineProps<{\n disabled?: boolean;\n}>();\n\ndefineEmits<{\n (e: \"insert\"): void;\n}>();\n\nconst { t } = useI18n();\n\nconst mergeTagBtnClass =\n \"tpl:flex tpl:items-center tpl:justify-center tpl:gap-1 tpl:rounded-[var(--tpl-radius-sm)] tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-hover)] tpl:px-2 tpl:py-1 tpl:text-xs tpl:text-[var(--tpl-text-muted)] tpl:transition-all tpl:duration-[120ms] hover:tpl:bg-[var(--tpl-primary-light)] hover:tpl:text-[var(--tpl-primary)] hover:tpl:border-[var(--tpl-primary)]\";\n</script>\n\n<template>\n <button\n type=\"button\"\n :class=\"[mergeTagBtnClass, 'tpl:mt-1.5']\"\n :aria-label=\"t.mergeTag.insert\"\n :title=\"t.mergeTag.insert\"\n :disabled=\"disabled\"\n @click=\"$emit('insert')\"\n >\n <ScanLine :size=\"12\" :stroke-width=\"2\" />\n {{ t.mergeTag.insert }}\n </button>\n</template>\n","<script setup lang=\"ts\">\nimport { useMergeTagField } from \"../composables/useMergeTagField\";\nimport MergeTagSegments from \"./MergeTagSegments.vue\";\nimport MergeTagInsertButton from \"./MergeTagInsertButton.vue\";\nimport { ref } from \"vue\";\n\nconst props = withDefaults(\n defineProps<{\n modelValue: string;\n placeholder?: string;\n rows?: number;\n }>(),\n {\n placeholder: \"\",\n rows: 3,\n },\n);\n\nconst emit = defineEmits<{\n (e: \"update:modelValue\", value: string): void;\n}>();\n\nconst textareaRef = ref<HTMLTextAreaElement | null>(null);\n\nconst {\n segments,\n hasMergeTags,\n canRequestMergeTag,\n isRequestingMergeTag,\n isEditing,\n startEditing,\n stopEditing,\n handleInput,\n clearValue,\n insertMergeTag,\n} = useMergeTagField({\n modelValue: () => props.modelValue,\n emit: (value) => emit(\"update:modelValue\", value),\n elementRef: textareaRef,\n});\n\nconst textareaClass =\n \"tpl:w-full tpl:resize-y tpl:rounded-[var(--tpl-radius-sm)] tpl:border tpl:shadow-xs tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:px-3 tpl:py-2 tpl:text-sm tpl:text-[var(--tpl-text)] tpl:outline-none tpl:transition-all tpl:duration-[120ms] tpl:ease-[cubic-bezier(0.16,1,0.3,1)] tpl:placeholder:text-[var(--tpl-text-dim)] tpl:focus:border-[var(--tpl-primary)] tpl:focus:shadow-[var(--tpl-ring)]\";\nconst displayClass =\n \"tpl:flex tpl:w-full tpl:min-h-[5rem] tpl:cursor-pointer tpl:items-start tpl:flex-wrap tpl:gap-1 tpl:rounded-[var(--tpl-radius-sm)] tpl:border tpl:shadow-xs tpl:bg-[var(--tpl-bg)] tpl:border-[var(--tpl-border)] tpl:px-3 tpl:py-2 tpl:transition-all tpl:duration-[120ms] tpl:ease-[cubic-bezier(0.16,1,0.3,1)]\";\n</script>\n\n<template>\n <div v-if=\"hasMergeTags && !isEditing\">\n <MergeTagSegments\n :segments=\"segments\"\n :display-class=\"displayClass\"\n @edit=\"startEditing\"\n @clear=\"clearValue\"\n />\n <MergeTagInsertButton\n v-if=\"canRequestMergeTag\"\n :disabled=\"isRequestingMergeTag\"\n @insert=\"insertMergeTag\"\n />\n </div>\n <div v-else>\n <textarea\n ref=\"textareaRef\"\n :class=\"textareaClass\"\n :value=\"modelValue\"\n :placeholder=\"placeholder\"\n :rows=\"rows\"\n @input=\"handleInput\"\n @blur=\"stopEditing\"\n @keydown.escape=\"stopEditing\"\n />\n <MergeTagInsertButton\n v-if=\"canRequestMergeTag\"\n :disabled=\"isRequestingMergeTag\"\n @insert=\"insertMergeTag\"\n />\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { useMergeTagField } from \"../composables/useMergeTagField\";\nimport MergeTagSegments from \"./MergeTagSegments.vue\";\nimport MergeTagInsertButton from \"./MergeTagInsertButton.vue\";\nimport { ref } from \"vue\";\n\nconst props = withDefaults(\n defineProps<{\n modelValue: string;\n placeholder?: string;\n rows?: number;\n }>(),\n {\n placeholder: \"\",\n rows: 3,\n },\n);\n\nconst emit = defineEmits<{\n (e: \"update:modelValue\", value: string): void;\n}>();\n\nconst textareaRef = ref<HTMLTextAreaElement | null>(null);\n\nconst {\n segments,\n hasMergeTags,\n canRequestMergeTag,\n isRequestingMergeTag,\n isEditing,\n startEditing,\n stopEditing,\n handleInput,\n clearValue,\n insertMergeTag,\n} = useMergeTagField({\n modelValue: () => props.modelValue,\n emit: (value) => emit(\"update:modelValue\", value),\n elementRef: textareaRef,\n});\n\nconst textareaClass =\n \"tpl:w-full tpl:resize-y tpl:rounded-[var(--tpl-radius-sm)] tpl:border tpl:shadow-xs tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:px-3 tpl:py-2 tpl:text-sm tpl:text-[var(--tpl-text)] tpl:outline-none tpl:transition-all tpl:duration-[120ms] tpl:ease-[cubic-bezier(0.16,1,0.3,1)] tpl:placeholder:text-[var(--tpl-text-dim)] tpl:focus:border-[var(--tpl-primary)] tpl:focus:shadow-[var(--tpl-ring)]\";\nconst displayClass =\n \"tpl:flex tpl:w-full tpl:min-h-[5rem] tpl:cursor-pointer tpl:items-start tpl:flex-wrap tpl:gap-1 tpl:rounded-[var(--tpl-radius-sm)] tpl:border tpl:shadow-xs tpl:bg-[var(--tpl-bg)] tpl:border-[var(--tpl-border)] tpl:px-3 tpl:py-2 tpl:transition-all tpl:duration-[120ms] tpl:ease-[cubic-bezier(0.16,1,0.3,1)]\";\n</script>\n\n<template>\n <div v-if=\"hasMergeTags && !isEditing\">\n <MergeTagSegments\n :segments=\"segments\"\n :display-class=\"displayClass\"\n @edit=\"startEditing\"\n @clear=\"clearValue\"\n />\n <MergeTagInsertButton\n v-if=\"canRequestMergeTag\"\n :disabled=\"isRequestingMergeTag\"\n @insert=\"insertMergeTag\"\n />\n </div>\n <div v-else>\n <textarea\n ref=\"textareaRef\"\n :class=\"textareaClass\"\n :value=\"modelValue\"\n :placeholder=\"placeholder\"\n :rows=\"rows\"\n @input=\"handleInput\"\n @blur=\"stopEditing\"\n @keydown.escape=\"stopEditing\"\n />\n <MergeTagInsertButton\n v-if=\"canRequestMergeTag\"\n :disabled=\"isRequestingMergeTag\"\n @insert=\"insertMergeTag\"\n />\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../composables/useI18n\";\nimport { Lock, LockOpen, Minus, Plus } from \"@lucide/vue\";\nimport { computed, ref, watch } from \"vue\";\n\nconst props = defineProps<{\n modelValue: SpacingValue;\n label: string;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update:modelValue\", value: SpacingValue): void;\n}>();\n\nconst { t } = useI18n();\n\ninterface SpacingValue {\n top: number;\n right: number;\n bottom: number;\n left: number;\n}\n\nconst isUniform = computed(\n () =>\n props.modelValue.top === props.modelValue.right &&\n props.modelValue.right === props.modelValue.bottom &&\n props.modelValue.bottom === props.modelValue.left,\n);\n\nconst locked = ref(isUniform.value);\n\nwatch(isUniform, (uniform) => {\n if (!uniform && locked.value) {\n locked.value = false;\n }\n});\n\nfunction updateValue(direction: keyof SpacingValue, delta: number): void {\n const currentValue = props.modelValue[direction];\n const newValue = Math.max(0, currentValue + delta);\n\n if (locked.value) {\n emit(\"update:modelValue\", {\n top: newValue,\n right: newValue,\n bottom: newValue,\n left: newValue,\n });\n } else {\n emit(\"update:modelValue\", {\n ...props.modelValue,\n [direction]: newValue,\n });\n }\n}\n\nfunction setDirectValue(direction: keyof SpacingValue, value: number): void {\n const newValue = Math.max(0, value);\n\n if (locked.value) {\n emit(\"update:modelValue\", {\n top: newValue,\n right: newValue,\n bottom: newValue,\n left: newValue,\n });\n } else {\n emit(\"update:modelValue\", {\n ...props.modelValue,\n [direction]: newValue,\n });\n }\n}\n\nfunction toggleLock(): void {\n locked.value = !locked.value;\n if (locked.value) {\n const value = props.modelValue.top;\n emit(\"update:modelValue\", {\n top: value,\n right: value,\n bottom: value,\n left: value,\n });\n }\n}\n\nconst stepperBtnClass =\n \"tpl:flex tpl:items-center tpl:justify-center tpl:w-8 tpl:h-8 tpl:text-[var(--tpl-text-muted)] tpl:bg-[var(--tpl-bg)] tpl:border tpl:border-[var(--tpl-border)] tpl:cursor-pointer tpl:transition-all tpl:duration-[120ms] tpl:ease-[cubic-bezier(0.16,1,0.3,1)] hover:tpl:bg-[var(--tpl-bg-hover)] hover:tpl:text-[var(--tpl-text)] active:tpl:bg-[var(--tpl-bg-active)]\";\nconst inputClass =\n \"tpl:w-10 tpl:h-8 tpl:text-center tpl:text-xs tpl:font-medium tpl:border-y tpl:border-x-0 tpl:border-[var(--tpl-border)] tpl:text-[var(--tpl-text)] tpl:bg-[var(--tpl-bg)] tpl:outline-none tpl:transition-all tpl:duration-[120ms] focus:tpl:border-[var(--tpl-primary)] focus:tpl:shadow-[var(--tpl-ring)]\";\n</script>\n\n<template>\n <div class=\"spacing-control\">\n <label\n class=\"tpl:mb-2 tpl:block tpl:text-sm tpl:font-medium tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ label }}\n </label>\n\n <div class=\"tpl:flex tpl:flex-col tpl:items-center tpl:gap-1.5\">\n <!-- Top -->\n <div class=\"tpl:flex tpl:items-center\">\n <button\n :aria-label=\"t.spacingControl.decreaseTop\"\n :class=\"[stepperBtnClass, 'tpl:rounded-l-[var(--tpl-radius-sm)]']\"\n @click=\"updateValue('top', -1)\"\n >\n <Minus :size=\"12\" :stroke-width=\"2\" />\n </button>\n <input\n type=\"number\"\n :class=\"inputClass\"\n :value=\"modelValue.top\"\n :aria-label=\"t.spacingControl.top\"\n min=\"0\"\n @input=\"\n setDirectValue(\n 'top',\n Number(($event.target as HTMLInputElement).value),\n )\n \"\n />\n <button\n :aria-label=\"t.spacingControl.increaseTop\"\n :class=\"[stepperBtnClass, 'tpl:rounded-r-[var(--tpl-radius-sm)]']\"\n @click=\"updateValue('top', 1)\"\n >\n <Plus :size=\"12\" :stroke-width=\"2\" />\n </button>\n </div>\n\n <!-- Middle row: Left - Lock - Right -->\n <div class=\"tpl:flex tpl:items-center tpl:gap-2\">\n <!-- Left -->\n <div class=\"tpl:flex tpl:items-center\">\n <button\n :aria-label=\"t.spacingControl.decreaseLeft\"\n :class=\"[stepperBtnClass, 'tpl:rounded-l-[var(--tpl-radius-sm)]']\"\n @click=\"updateValue('left', -1)\"\n >\n <Minus :size=\"12\" :stroke-width=\"2\" />\n </button>\n <input\n type=\"number\"\n :class=\"inputClass\"\n :value=\"modelValue.left\"\n :aria-label=\"t.spacingControl.left\"\n min=\"0\"\n @input=\"\n setDirectValue(\n 'left',\n Number(($event.target as HTMLInputElement).value),\n )\n \"\n />\n <button\n :aria-label=\"t.spacingControl.increaseLeft\"\n :class=\"[stepperBtnClass, 'tpl:rounded-r-[var(--tpl-radius-sm)]']\"\n @click=\"updateValue('left', 1)\"\n >\n <Plus :size=\"12\" :stroke-width=\"2\" />\n </button>\n </div>\n\n <!-- Lock button -->\n <button\n class=\"tpl:flex tpl:h-8 tpl:w-8 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-[var(--tpl-radius-sm)] tpl:border tpl:transition-all tpl:duration-[120ms] tpl:ease-[cubic-bezier(0.16,1,0.3,1)]\"\n :class=\"\n locked\n ? 'tpl:border-[var(--tpl-primary)] tpl:bg-[var(--tpl-primary-light)] tpl:text-[var(--tpl-primary)]'\n : 'tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:text-[var(--tpl-text-muted)] hover:tpl:bg-[var(--tpl-bg-hover)]'\n \"\n :aria-label=\"\n locked ? t.spacingControl.unlock : t.spacingControl.lockAll\n \"\n :title=\"locked ? t.spacingControl.unlock : t.spacingControl.lockAll\"\n @click=\"toggleLock\"\n >\n <Lock v-if=\"locked\" :size=\"14\" :stroke-width=\"2\" />\n <LockOpen v-else :size=\"14\" :stroke-width=\"2\" />\n </button>\n\n <!-- Right -->\n <div class=\"tpl:flex tpl:items-center\">\n <button\n :aria-label=\"t.spacingControl.decreaseRight\"\n :class=\"[stepperBtnClass, 'tpl:rounded-l-[var(--tpl-radius-sm)]']\"\n @click=\"updateValue('right', -1)\"\n >\n <Minus :size=\"12\" :stroke-width=\"2\" />\n </button>\n <input\n type=\"number\"\n :class=\"inputClass\"\n :value=\"modelValue.right\"\n :aria-label=\"t.spacingControl.right\"\n min=\"0\"\n @input=\"\n setDirectValue(\n 'right',\n Number(($event.target as HTMLInputElement).value),\n )\n \"\n />\n <button\n :aria-label=\"t.spacingControl.increaseRight\"\n :class=\"[stepperBtnClass, 'tpl:rounded-r-[var(--tpl-radius-sm)]']\"\n @click=\"updateValue('right', 1)\"\n >\n <Plus :size=\"12\" :stroke-width=\"2\" />\n </button>\n </div>\n </div>\n\n <!-- Bottom -->\n <div class=\"tpl:flex tpl:items-center\">\n <button\n :aria-label=\"t.spacingControl.decreaseBottom\"\n :class=\"[stepperBtnClass, 'tpl:rounded-l-[var(--tpl-radius-sm)]']\"\n @click=\"updateValue('bottom', -1)\"\n >\n <Minus :size=\"12\" :stroke-width=\"2\" />\n </button>\n <input\n type=\"number\"\n :class=\"inputClass\"\n :value=\"modelValue.bottom\"\n :aria-label=\"t.spacingControl.bottom\"\n min=\"0\"\n @input=\"\n setDirectValue(\n 'bottom',\n Number(($event.target as HTMLInputElement).value),\n )\n \"\n />\n <button\n :aria-label=\"t.spacingControl.increaseBottom\"\n :class=\"[stepperBtnClass, 'tpl:rounded-r-[var(--tpl-radius-sm)]']\"\n @click=\"updateValue('bottom', 1)\"\n >\n <Plus :size=\"12\" :stroke-width=\"2\" />\n </button>\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\ninput[type=\"number\"]::-webkit-outer-spin-button,\ninput[type=\"number\"]::-webkit-inner-spin-button {\n -webkit-appearance: none;\n margin: 0;\n}\n\ninput[type=\"number\"] {\n -moz-appearance: textfield;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../composables/useI18n\";\nimport { Lock, LockOpen, Minus, Plus } from \"@lucide/vue\";\nimport { computed, ref, watch } from \"vue\";\n\nconst props = defineProps<{\n modelValue: SpacingValue;\n label: string;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update:modelValue\", value: SpacingValue): void;\n}>();\n\nconst { t } = useI18n();\n\ninterface SpacingValue {\n top: number;\n right: number;\n bottom: number;\n left: number;\n}\n\nconst isUniform = computed(\n () =>\n props.modelValue.top === props.modelValue.right &&\n props.modelValue.right === props.modelValue.bottom &&\n props.modelValue.bottom === props.modelValue.left,\n);\n\nconst locked = ref(isUniform.value);\n\nwatch(isUniform, (uniform) => {\n if (!uniform && locked.value) {\n locked.value = false;\n }\n});\n\nfunction updateValue(direction: keyof SpacingValue, delta: number): void {\n const currentValue = props.modelValue[direction];\n const newValue = Math.max(0, currentValue + delta);\n\n if (locked.value) {\n emit(\"update:modelValue\", {\n top: newValue,\n right: newValue,\n bottom: newValue,\n left: newValue,\n });\n } else {\n emit(\"update:modelValue\", {\n ...props.modelValue,\n [direction]: newValue,\n });\n }\n}\n\nfunction setDirectValue(direction: keyof SpacingValue, value: number): void {\n const newValue = Math.max(0, value);\n\n if (locked.value) {\n emit(\"update:modelValue\", {\n top: newValue,\n right: newValue,\n bottom: newValue,\n left: newValue,\n });\n } else {\n emit(\"update:modelValue\", {\n ...props.modelValue,\n [direction]: newValue,\n });\n }\n}\n\nfunction toggleLock(): void {\n locked.value = !locked.value;\n if (locked.value) {\n const value = props.modelValue.top;\n emit(\"update:modelValue\", {\n top: value,\n right: value,\n bottom: value,\n left: value,\n });\n }\n}\n\nconst stepperBtnClass =\n \"tpl:flex tpl:items-center tpl:justify-center tpl:w-8 tpl:h-8 tpl:text-[var(--tpl-text-muted)] tpl:bg-[var(--tpl-bg)] tpl:border tpl:border-[var(--tpl-border)] tpl:cursor-pointer tpl:transition-all tpl:duration-[120ms] tpl:ease-[cubic-bezier(0.16,1,0.3,1)] hover:tpl:bg-[var(--tpl-bg-hover)] hover:tpl:text-[var(--tpl-text)] active:tpl:bg-[var(--tpl-bg-active)]\";\nconst inputClass =\n \"tpl:w-10 tpl:h-8 tpl:text-center tpl:text-xs tpl:font-medium tpl:border-y tpl:border-x-0 tpl:border-[var(--tpl-border)] tpl:text-[var(--tpl-text)] tpl:bg-[var(--tpl-bg)] tpl:outline-none tpl:transition-all tpl:duration-[120ms] focus:tpl:border-[var(--tpl-primary)] focus:tpl:shadow-[var(--tpl-ring)]\";\n</script>\n\n<template>\n <div class=\"spacing-control\">\n <label\n class=\"tpl:mb-2 tpl:block tpl:text-sm tpl:font-medium tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ label }}\n </label>\n\n <div class=\"tpl:flex tpl:flex-col tpl:items-center tpl:gap-1.5\">\n <!-- Top -->\n <div class=\"tpl:flex tpl:items-center\">\n <button\n :aria-label=\"t.spacingControl.decreaseTop\"\n :class=\"[stepperBtnClass, 'tpl:rounded-l-[var(--tpl-radius-sm)]']\"\n @click=\"updateValue('top', -1)\"\n >\n <Minus :size=\"12\" :stroke-width=\"2\" />\n </button>\n <input\n type=\"number\"\n :class=\"inputClass\"\n :value=\"modelValue.top\"\n :aria-label=\"t.spacingControl.top\"\n min=\"0\"\n @input=\"\n setDirectValue(\n 'top',\n Number(($event.target as HTMLInputElement).value),\n )\n \"\n />\n <button\n :aria-label=\"t.spacingControl.increaseTop\"\n :class=\"[stepperBtnClass, 'tpl:rounded-r-[var(--tpl-radius-sm)]']\"\n @click=\"updateValue('top', 1)\"\n >\n <Plus :size=\"12\" :stroke-width=\"2\" />\n </button>\n </div>\n\n <!-- Middle row: Left - Lock - Right -->\n <div class=\"tpl:flex tpl:items-center tpl:gap-2\">\n <!-- Left -->\n <div class=\"tpl:flex tpl:items-center\">\n <button\n :aria-label=\"t.spacingControl.decreaseLeft\"\n :class=\"[stepperBtnClass, 'tpl:rounded-l-[var(--tpl-radius-sm)]']\"\n @click=\"updateValue('left', -1)\"\n >\n <Minus :size=\"12\" :stroke-width=\"2\" />\n </button>\n <input\n type=\"number\"\n :class=\"inputClass\"\n :value=\"modelValue.left\"\n :aria-label=\"t.spacingControl.left\"\n min=\"0\"\n @input=\"\n setDirectValue(\n 'left',\n Number(($event.target as HTMLInputElement).value),\n )\n \"\n />\n <button\n :aria-label=\"t.spacingControl.increaseLeft\"\n :class=\"[stepperBtnClass, 'tpl:rounded-r-[var(--tpl-radius-sm)]']\"\n @click=\"updateValue('left', 1)\"\n >\n <Plus :size=\"12\" :stroke-width=\"2\" />\n </button>\n </div>\n\n <!-- Lock button -->\n <button\n class=\"tpl:flex tpl:h-8 tpl:w-8 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-[var(--tpl-radius-sm)] tpl:border tpl:transition-all tpl:duration-[120ms] tpl:ease-[cubic-bezier(0.16,1,0.3,1)]\"\n :class=\"\n locked\n ? 'tpl:border-[var(--tpl-primary)] tpl:bg-[var(--tpl-primary-light)] tpl:text-[var(--tpl-primary)]'\n : 'tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:text-[var(--tpl-text-muted)] hover:tpl:bg-[var(--tpl-bg-hover)]'\n \"\n :aria-label=\"\n locked ? t.spacingControl.unlock : t.spacingControl.lockAll\n \"\n :title=\"locked ? t.spacingControl.unlock : t.spacingControl.lockAll\"\n @click=\"toggleLock\"\n >\n <Lock v-if=\"locked\" :size=\"14\" :stroke-width=\"2\" />\n <LockOpen v-else :size=\"14\" :stroke-width=\"2\" />\n </button>\n\n <!-- Right -->\n <div class=\"tpl:flex tpl:items-center\">\n <button\n :aria-label=\"t.spacingControl.decreaseRight\"\n :class=\"[stepperBtnClass, 'tpl:rounded-l-[var(--tpl-radius-sm)]']\"\n @click=\"updateValue('right', -1)\"\n >\n <Minus :size=\"12\" :stroke-width=\"2\" />\n </button>\n <input\n type=\"number\"\n :class=\"inputClass\"\n :value=\"modelValue.right\"\n :aria-label=\"t.spacingControl.right\"\n min=\"0\"\n @input=\"\n setDirectValue(\n 'right',\n Number(($event.target as HTMLInputElement).value),\n )\n \"\n />\n <button\n :aria-label=\"t.spacingControl.increaseRight\"\n :class=\"[stepperBtnClass, 'tpl:rounded-r-[var(--tpl-radius-sm)]']\"\n @click=\"updateValue('right', 1)\"\n >\n <Plus :size=\"12\" :stroke-width=\"2\" />\n </button>\n </div>\n </div>\n\n <!-- Bottom -->\n <div class=\"tpl:flex tpl:items-center\">\n <button\n :aria-label=\"t.spacingControl.decreaseBottom\"\n :class=\"[stepperBtnClass, 'tpl:rounded-l-[var(--tpl-radius-sm)]']\"\n @click=\"updateValue('bottom', -1)\"\n >\n <Minus :size=\"12\" :stroke-width=\"2\" />\n </button>\n <input\n type=\"number\"\n :class=\"inputClass\"\n :value=\"modelValue.bottom\"\n :aria-label=\"t.spacingControl.bottom\"\n min=\"0\"\n @input=\"\n setDirectValue(\n 'bottom',\n Number(($event.target as HTMLInputElement).value),\n )\n \"\n />\n <button\n :aria-label=\"t.spacingControl.increaseBottom\"\n :class=\"[stepperBtnClass, 'tpl:rounded-r-[var(--tpl-radius-sm)]']\"\n @click=\"updateValue('bottom', 1)\"\n >\n <Plus :size=\"12\" :stroke-width=\"2\" />\n </button>\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\ninput[type=\"number\"]::-webkit-outer-spin-button,\ninput[type=\"number\"]::-webkit-inner-spin-button {\n -webkit-appearance: none;\n margin: 0;\n}\n\ninput[type=\"number\"] {\n -moz-appearance: textfield;\n}\n</style>\n","<script setup lang=\"ts\">\nimport ColorPicker from \"./ColorPicker.vue\";\nimport MergeTagTextarea from \"./MergeTagTextarea.vue\";\nimport SpacingControl from \"./SpacingControl.vue\";\nimport { useI18n } from \"../composables/useI18n\";\nimport type {\n PopupDesignSettings,\n SpacingValue,\n TemplateSettings,\n} from \"@aswin.dev/types\";\nimport { resolvePopupEmbed } from \"../utils/resolvePopupEmbed\";\nimport {\n cardClass,\n inputClass,\n inputGroupInputClass,\n inputSuffixClass,\n labelClass,\n DEFAULT_BG_COLOR,\n} from \"../constants/styleConstants\";\nimport { Circle, Eye, Globe, Info, Square } from \"@lucide/vue\";\nimport { computed } from \"vue\";\nimport { FONTS_MANAGER_KEY, requireInject } from \"../keys\";\n\nconst props = defineProps<{\n settings: TemplateSettings;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update\", settings: Partial<TemplateSettings>): void;\n}>();\n\nconst PREHEADER_MAX_LENGTH = 150;\n\nconst { t } = useI18n();\n\nconst fontsManager = requireInject(FONTS_MANAGER_KEY, \"TemplateSettings\");\nconst fontFamilies = computed(() => fontsManager.fonts.value);\n\n// If current font is not in available list (e.g., custom font when disabled), use default\nconst displayedFontFamily = computed(() => {\n const isAvailable = fontFamilies.value.some(\n (font) => font.value === props.settings.fontFamily,\n );\n return isAvailable\n ? props.settings.fontFamily\n : fontsManager.defaultFont.value;\n});\n\nconst widthPresets = [\n { value: 480, label: \"480px\" },\n { value: 600, label: \"600px\" },\n { value: 700, label: \"700px\" },\n { value: 800, label: \"800px\" },\n];\n\nconst hasPopupSettings = computed(() => Boolean(props.settings.popup));\n\nconst popupContentPadding = computed(() =>\n hasPopupSettings.value\n ? resolvePopupEmbed(props.settings).design.contentPadding\n : null,\n);\n\nfunction patchPopupDesign(patch: Partial<PopupDesignSettings>): void {\n const resolved = resolvePopupEmbed(props.settings);\n emit(\"update\", {\n popup: {\n ...resolved,\n design: {\n ...resolved.design,\n ...patch,\n },\n },\n });\n}\n\nfunction onPopupContentPaddingUpdate(value: SpacingValue): void {\n patchPopupDesign({ contentPadding: value });\n}\n\nconst backgroundOpacityPct = computed(() => {\n const raw = props.settings.backgroundOpacity;\n const n = typeof raw === \"number\" && Number.isFinite(raw) ? raw : 1;\n return Math.round(Math.min(1, Math.max(0, n)) * 100);\n});\n\nfunction onBackgroundOpacityInput(event: Event): void {\n const raw = Number((event.target as HTMLInputElement).value);\n const pct = Math.min(100, Math.max(0, Number.isFinite(raw) ? raw : 100));\n emit(\"update\", { backgroundOpacity: pct / 100 });\n}\n\n/**\n * When `backgroundShadow` is unset we surface the legacy `--tpl-shadow-xl`\n * default as ~100% in the slider so users see a sensible starting point that\n * matches what's currently on screen, instead of \"0%\" with no visible shadow.\n */\nconst backgroundShadowPct = computed(() => {\n const raw = props.settings.backgroundShadow;\n const n = typeof raw === \"number\" && Number.isFinite(raw) ? raw : 1;\n return Math.round(Math.min(1, Math.max(0, n)) * 100);\n});\n\nfunction onBackgroundShadowInput(event: Event): void {\n const raw = Number((event.target as HTMLInputElement).value);\n const pct = Math.min(100, Math.max(0, Number.isFinite(raw) ? raw : 100));\n emit(\"update\", { backgroundShadow: pct / 100 });\n}\n</script>\n\n<template>\n <aside\n class=\"tpl:flex tpl:w-full tpl:flex-1 tpl:flex-col tpl:bg-[var(--tpl-bg-elevated)]\"\n >\n <div\n class=\"tpl:flex tpl:flex-1 tpl:flex-col tpl:gap-3 tpl:overflow-y-auto tpl:p-4\"\n >\n <!-- Layout card -->\n <div :class=\"cardClass\">\n <div\n class=\"tpl:mb-3.5 tpl:flex tpl:items-center tpl:gap-2 tpl:text-sm tpl:font-semibold tpl:text-[var(--tpl-text)]\"\n >\n <Square\n class=\"tpl:text-[var(--tpl-text-muted)]\"\n :size=\"14\"\n :stroke-width=\"2\"\n />\n <span>{{ t.templateSettings.layout }}</span>\n </div>\n\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{\n t.templateSettings.widthPreset\n }}</label>\n <div\n class=\"tpl:grid tpl:grid-cols-4 tpl:gap-1 tpl:rounded-[var(--tpl-radius-sm)] tpl:p-1 tpl:bg-[var(--tpl-bg-hover)]\"\n >\n <button\n v-for=\"preset in widthPresets\"\n :key=\"preset.value\"\n class=\"tpl:cursor-pointer tpl:rounded-[var(--tpl-radius-sm)] tpl:border-none tpl:px-2 tpl:py-1.5 tpl:text-sm tpl:font-medium tpl:transition-all tpl:duration-[120ms] tpl:ease-[cubic-bezier(0.16,1,0.3,1)]\"\n :style=\"{\n backgroundColor:\n settings.width === preset.value\n ? 'var(--tpl-bg)'\n : 'transparent',\n color:\n settings.width === preset.value\n ? 'var(--tpl-primary)'\n : 'var(--tpl-text-muted)',\n boxShadow:\n settings.width === preset.value\n ? 'var(--tpl-shadow)'\n : 'none',\n }\"\n @click=\"emit('update', { width: preset.value })\"\n >\n {{ preset.label }}\n </button>\n </div>\n </div>\n\n <div>\n <label :class=\"labelClass\">{{\n t.templateSettings.customWidth\n }}</label>\n <div class=\"tpl:flex tpl:items-stretch\">\n <input\n type=\"number\"\n :class=\"inputGroupInputClass\"\n :value=\"settings.width\"\n min=\"300\"\n max=\"900\"\n @input=\"\n emit('update', {\n width: Number(($event.target as HTMLInputElement).value),\n })\n \"\n />\n <span :class=\"inputSuffixClass\">px</span>\n </div>\n </div>\n\n <div v-if=\"hasPopupSettings && popupContentPadding\" class=\"tpl:mt-4\">\n <SpacingControl\n :model-value=\"popupContentPadding\"\n :label=\"t.templateSettings.contentPadding\"\n @update:model-value=\"onPopupContentPaddingUpdate\"\n />\n </div>\n </div>\n\n <!-- Appearance card -->\n <div :class=\"cardClass\">\n <div\n class=\"tpl:mb-3.5 tpl:flex tpl:items-center tpl:gap-2 tpl:text-sm tpl:font-semibold tpl:text-[var(--tpl-text)]\"\n >\n <Circle\n class=\"tpl:text-[var(--tpl-text-muted)]\"\n :size=\"14\"\n :stroke-width=\"2\"\n />\n <span>{{ t.templateSettings.appearance }}</span>\n </div>\n\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{\n t.templateSettings.backgroundColor\n }}</label>\n <ColorPicker\n :model-value=\"settings.backgroundColor\"\n :placeholder=\"DEFAULT_BG_COLOR\"\n @update:model-value=\"emit('update', { backgroundColor: $event })\"\n />\n </div>\n\n <div class=\"tpl:mb-3.5 tpl:space-y-2\">\n <div class=\"tpl:flex tpl:items-center tpl:justify-between\">\n <label :class=\"labelClass\" for=\"template-bg-opacity\">\n {{ t.templateSettings.backgroundOpacity }}\n </label>\n <span\n class=\"tpl:text-[11px] tpl:font-medium tpl:text-[var(--tpl-text-muted)] tpl:tabular-nums\"\n >\n {{ backgroundOpacityPct }}%\n </span>\n </div>\n <input\n id=\"template-bg-opacity\"\n type=\"range\"\n class=\"tpl:w-full tpl:accent-[var(--tpl-primary)]\"\n min=\"0\"\n max=\"100\"\n step=\"5\"\n :value=\"backgroundOpacityPct\"\n @input=\"onBackgroundOpacityInput\"\n />\n </div>\n\n <div class=\"tpl:mb-3.5 tpl:space-y-2\">\n <div class=\"tpl:flex tpl:items-center tpl:justify-between\">\n <label :class=\"labelClass\" for=\"template-bg-shadow\">\n {{ t.templateSettings.backgroundShadow }}\n </label>\n <span\n class=\"tpl:text-[11px] tpl:font-medium tpl:text-[var(--tpl-text-muted)] tpl:tabular-nums\"\n >\n {{ backgroundShadowPct }}%\n </span>\n </div>\n <input\n id=\"template-bg-shadow\"\n type=\"range\"\n class=\"tpl:w-full tpl:accent-[var(--tpl-primary)]\"\n min=\"0\"\n max=\"100\"\n step=\"5\"\n :value=\"backgroundShadowPct\"\n @input=\"onBackgroundShadowInput\"\n />\n </div>\n\n <div>\n <label :class=\"labelClass\">{{ t.templateSettings.fontFamily }}</label>\n <select\n :class=\"inputClass\"\n :value=\"displayedFontFamily\"\n @change=\"\n emit('update', {\n fontFamily: ($event.target as HTMLSelectElement).value,\n })\n \"\n >\n <option\n v-for=\"font in fontFamilies\"\n :key=\"font.value\"\n :value=\"font.value\"\n >\n {{ font.label }}\n </option>\n </select>\n </div>\n </div>\n\n <!-- Language card -->\n <div :class=\"cardClass\">\n <div\n class=\"tpl:mb-3.5 tpl:flex tpl:items-center tpl:gap-2 tpl:text-sm tpl:font-semibold tpl:text-[var(--tpl-text)]\"\n >\n <Globe\n class=\"tpl:text-[var(--tpl-text-muted)]\"\n :size=\"14\"\n :stroke-width=\"2\"\n />\n <span>{{ t.templateSettings.language }}</span>\n </div>\n\n <div>\n <label :class=\"labelClass\">{{\n t.templateSettings.contentLocale\n }}</label>\n <input\n type=\"text\"\n :class=\"inputClass\"\n :value=\"settings.locale ?? ''\"\n placeholder=\"en\"\n spellcheck=\"false\"\n autocapitalize=\"off\"\n autocomplete=\"off\"\n @input=\"\n emit('update', {\n locale:\n ($event.target as HTMLInputElement).value.trim() || undefined,\n })\n \"\n />\n <p\n class=\"tpl:mt-1 tpl:text-xs tpl:leading-relaxed tpl:text-[var(--tpl-text-dim)]\"\n >\n {{ t.templateSettings.contentLocaleHint }}\n </p>\n </div>\n </div>\n\n <!-- Preheader card -->\n <div :class=\"cardClass\">\n <div\n class=\"tpl:mb-3.5 tpl:flex tpl:items-center tpl:gap-2 tpl:text-sm tpl:font-semibold tpl:text-[var(--tpl-text)]\"\n >\n <Eye\n class=\"tpl:text-[var(--tpl-text-muted)]\"\n :size=\"14\"\n :stroke-width=\"2\"\n />\n <span>{{ t.templateSettings.preheaderText }}</span>\n </div>\n\n <div>\n <MergeTagTextarea\n :model-value=\"settings.preheaderText ?? ''\"\n :placeholder=\"t.templateSettings.preheaderTextPlaceholder\"\n :rows=\"2\"\n @update:model-value=\"\n emit('update', {\n preheaderText: $event.replace(/[\\r\\n]/g, ' ') || undefined,\n })\n \"\n />\n <div\n class=\"tpl:mt-1 tpl:flex tpl:items-start tpl:justify-between tpl:gap-2\"\n >\n <span\n class=\"tpl:text-xs tpl:leading-relaxed tpl:text-[var(--tpl-text-dim)]\"\n >\n {{ t.templateSettings.preheaderTextHint }}\n </span>\n <span\n class=\"tpl:shrink-0 tpl:text-xs tpl:tabular-nums tpl:text-[var(--tpl-text-dim)]\"\n >\n {{ (settings.preheaderText ?? \"\").length }}/{{\n PREHEADER_MAX_LENGTH\n }}\n </span>\n </div>\n </div>\n </div>\n\n <!-- Tips card -->\n <div\n class=\"tpl:rounded-[var(--tpl-radius)] tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:p-3\"\n >\n <div\n class=\"tpl:mb-2.5 tpl:flex tpl:items-center tpl:gap-1.5 tpl:text-sm tpl:font-semibold tpl:text-[var(--tpl-text-muted)]\"\n >\n <Info :size=\"14\" :stroke-width=\"2\" />\n <span>{{ t.templateSettings.tips }}</span>\n </div>\n <ul\n class=\"tpl:m-0 tpl:pl-[18px] tpl:text-xs tpl:leading-relaxed tpl:text-[var(--tpl-text-dim)]\"\n >\n <li class=\"tpl:mb-1 tpl:last:mb-0\">\n {{ t.templateSettings.tip1 }}\n </li>\n <li class=\"tpl:mb-1 tpl:last:mb-0\">\n {{ t.templateSettings.tip2 }}\n </li>\n <li class=\"tpl:mb-1 tpl:last:mb-0\">\n {{ t.templateSettings.tip3 }}\n </li>\n </ul>\n </div>\n </div>\n </aside>\n</template>\n","<script setup lang=\"ts\">\nimport ColorPicker from \"./ColorPicker.vue\";\nimport MergeTagTextarea from \"./MergeTagTextarea.vue\";\nimport SpacingControl from \"./SpacingControl.vue\";\nimport { useI18n } from \"../composables/useI18n\";\nimport type {\n PopupDesignSettings,\n SpacingValue,\n TemplateSettings,\n} from \"@aswin.dev/types\";\nimport { resolvePopupEmbed } from \"../utils/resolvePopupEmbed\";\nimport {\n cardClass,\n inputClass,\n inputGroupInputClass,\n inputSuffixClass,\n labelClass,\n DEFAULT_BG_COLOR,\n} from \"../constants/styleConstants\";\nimport { Circle, Eye, Globe, Info, Square } from \"@lucide/vue\";\nimport { computed } from \"vue\";\nimport { FONTS_MANAGER_KEY, requireInject } from \"../keys\";\n\nconst props = defineProps<{\n settings: TemplateSettings;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update\", settings: Partial<TemplateSettings>): void;\n}>();\n\nconst PREHEADER_MAX_LENGTH = 150;\n\nconst { t } = useI18n();\n\nconst fontsManager = requireInject(FONTS_MANAGER_KEY, \"TemplateSettings\");\nconst fontFamilies = computed(() => fontsManager.fonts.value);\n\n// If current font is not in available list (e.g., custom font when disabled), use default\nconst displayedFontFamily = computed(() => {\n const isAvailable = fontFamilies.value.some(\n (font) => font.value === props.settings.fontFamily,\n );\n return isAvailable\n ? props.settings.fontFamily\n : fontsManager.defaultFont.value;\n});\n\nconst widthPresets = [\n { value: 480, label: \"480px\" },\n { value: 600, label: \"600px\" },\n { value: 700, label: \"700px\" },\n { value: 800, label: \"800px\" },\n];\n\nconst hasPopupSettings = computed(() => Boolean(props.settings.popup));\n\nconst popupContentPadding = computed(() =>\n hasPopupSettings.value\n ? resolvePopupEmbed(props.settings).design.contentPadding\n : null,\n);\n\nfunction patchPopupDesign(patch: Partial<PopupDesignSettings>): void {\n const resolved = resolvePopupEmbed(props.settings);\n emit(\"update\", {\n popup: {\n ...resolved,\n design: {\n ...resolved.design,\n ...patch,\n },\n },\n });\n}\n\nfunction onPopupContentPaddingUpdate(value: SpacingValue): void {\n patchPopupDesign({ contentPadding: value });\n}\n\nconst backgroundOpacityPct = computed(() => {\n const raw = props.settings.backgroundOpacity;\n const n = typeof raw === \"number\" && Number.isFinite(raw) ? raw : 1;\n return Math.round(Math.min(1, Math.max(0, n)) * 100);\n});\n\nfunction onBackgroundOpacityInput(event: Event): void {\n const raw = Number((event.target as HTMLInputElement).value);\n const pct = Math.min(100, Math.max(0, Number.isFinite(raw) ? raw : 100));\n emit(\"update\", { backgroundOpacity: pct / 100 });\n}\n\n/**\n * When `backgroundShadow` is unset we surface the legacy `--tpl-shadow-xl`\n * default as ~100% in the slider so users see a sensible starting point that\n * matches what's currently on screen, instead of \"0%\" with no visible shadow.\n */\nconst backgroundShadowPct = computed(() => {\n const raw = props.settings.backgroundShadow;\n const n = typeof raw === \"number\" && Number.isFinite(raw) ? raw : 1;\n return Math.round(Math.min(1, Math.max(0, n)) * 100);\n});\n\nfunction onBackgroundShadowInput(event: Event): void {\n const raw = Number((event.target as HTMLInputElement).value);\n const pct = Math.min(100, Math.max(0, Number.isFinite(raw) ? raw : 100));\n emit(\"update\", { backgroundShadow: pct / 100 });\n}\n</script>\n\n<template>\n <aside\n class=\"tpl:flex tpl:w-full tpl:flex-1 tpl:flex-col tpl:bg-[var(--tpl-bg-elevated)]\"\n >\n <div\n class=\"tpl:flex tpl:flex-1 tpl:flex-col tpl:gap-3 tpl:overflow-y-auto tpl:p-4\"\n >\n <!-- Layout card -->\n <div :class=\"cardClass\">\n <div\n class=\"tpl:mb-3.5 tpl:flex tpl:items-center tpl:gap-2 tpl:text-sm tpl:font-semibold tpl:text-[var(--tpl-text)]\"\n >\n <Square\n class=\"tpl:text-[var(--tpl-text-muted)]\"\n :size=\"14\"\n :stroke-width=\"2\"\n />\n <span>{{ t.templateSettings.layout }}</span>\n </div>\n\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{\n t.templateSettings.widthPreset\n }}</label>\n <div\n class=\"tpl:grid tpl:grid-cols-4 tpl:gap-1 tpl:rounded-[var(--tpl-radius-sm)] tpl:p-1 tpl:bg-[var(--tpl-bg-hover)]\"\n >\n <button\n v-for=\"preset in widthPresets\"\n :key=\"preset.value\"\n class=\"tpl:cursor-pointer tpl:rounded-[var(--tpl-radius-sm)] tpl:border-none tpl:px-2 tpl:py-1.5 tpl:text-sm tpl:font-medium tpl:transition-all tpl:duration-[120ms] tpl:ease-[cubic-bezier(0.16,1,0.3,1)]\"\n :style=\"{\n backgroundColor:\n settings.width === preset.value\n ? 'var(--tpl-bg)'\n : 'transparent',\n color:\n settings.width === preset.value\n ? 'var(--tpl-primary)'\n : 'var(--tpl-text-muted)',\n boxShadow:\n settings.width === preset.value\n ? 'var(--tpl-shadow)'\n : 'none',\n }\"\n @click=\"emit('update', { width: preset.value })\"\n >\n {{ preset.label }}\n </button>\n </div>\n </div>\n\n <div>\n <label :class=\"labelClass\">{{\n t.templateSettings.customWidth\n }}</label>\n <div class=\"tpl:flex tpl:items-stretch\">\n <input\n type=\"number\"\n :class=\"inputGroupInputClass\"\n :value=\"settings.width\"\n min=\"300\"\n max=\"900\"\n @input=\"\n emit('update', {\n width: Number(($event.target as HTMLInputElement).value),\n })\n \"\n />\n <span :class=\"inputSuffixClass\">px</span>\n </div>\n </div>\n\n <div v-if=\"hasPopupSettings && popupContentPadding\" class=\"tpl:mt-4\">\n <SpacingControl\n :model-value=\"popupContentPadding\"\n :label=\"t.templateSettings.contentPadding\"\n @update:model-value=\"onPopupContentPaddingUpdate\"\n />\n </div>\n </div>\n\n <!-- Appearance card -->\n <div :class=\"cardClass\">\n <div\n class=\"tpl:mb-3.5 tpl:flex tpl:items-center tpl:gap-2 tpl:text-sm tpl:font-semibold tpl:text-[var(--tpl-text)]\"\n >\n <Circle\n class=\"tpl:text-[var(--tpl-text-muted)]\"\n :size=\"14\"\n :stroke-width=\"2\"\n />\n <span>{{ t.templateSettings.appearance }}</span>\n </div>\n\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{\n t.templateSettings.backgroundColor\n }}</label>\n <ColorPicker\n :model-value=\"settings.backgroundColor\"\n :placeholder=\"DEFAULT_BG_COLOR\"\n @update:model-value=\"emit('update', { backgroundColor: $event })\"\n />\n </div>\n\n <div class=\"tpl:mb-3.5 tpl:space-y-2\">\n <div class=\"tpl:flex tpl:items-center tpl:justify-between\">\n <label :class=\"labelClass\" for=\"template-bg-opacity\">\n {{ t.templateSettings.backgroundOpacity }}\n </label>\n <span\n class=\"tpl:text-[11px] tpl:font-medium tpl:text-[var(--tpl-text-muted)] tpl:tabular-nums\"\n >\n {{ backgroundOpacityPct }}%\n </span>\n </div>\n <input\n id=\"template-bg-opacity\"\n type=\"range\"\n class=\"tpl:w-full tpl:accent-[var(--tpl-primary)]\"\n min=\"0\"\n max=\"100\"\n step=\"5\"\n :value=\"backgroundOpacityPct\"\n @input=\"onBackgroundOpacityInput\"\n />\n </div>\n\n <div class=\"tpl:mb-3.5 tpl:space-y-2\">\n <div class=\"tpl:flex tpl:items-center tpl:justify-between\">\n <label :class=\"labelClass\" for=\"template-bg-shadow\">\n {{ t.templateSettings.backgroundShadow }}\n </label>\n <span\n class=\"tpl:text-[11px] tpl:font-medium tpl:text-[var(--tpl-text-muted)] tpl:tabular-nums\"\n >\n {{ backgroundShadowPct }}%\n </span>\n </div>\n <input\n id=\"template-bg-shadow\"\n type=\"range\"\n class=\"tpl:w-full tpl:accent-[var(--tpl-primary)]\"\n min=\"0\"\n max=\"100\"\n step=\"5\"\n :value=\"backgroundShadowPct\"\n @input=\"onBackgroundShadowInput\"\n />\n </div>\n\n <div>\n <label :class=\"labelClass\">{{ t.templateSettings.fontFamily }}</label>\n <select\n :class=\"inputClass\"\n :value=\"displayedFontFamily\"\n @change=\"\n emit('update', {\n fontFamily: ($event.target as HTMLSelectElement).value,\n })\n \"\n >\n <option\n v-for=\"font in fontFamilies\"\n :key=\"font.value\"\n :value=\"font.value\"\n >\n {{ font.label }}\n </option>\n </select>\n </div>\n </div>\n\n <!-- Language card -->\n <div :class=\"cardClass\">\n <div\n class=\"tpl:mb-3.5 tpl:flex tpl:items-center tpl:gap-2 tpl:text-sm tpl:font-semibold tpl:text-[var(--tpl-text)]\"\n >\n <Globe\n class=\"tpl:text-[var(--tpl-text-muted)]\"\n :size=\"14\"\n :stroke-width=\"2\"\n />\n <span>{{ t.templateSettings.language }}</span>\n </div>\n\n <div>\n <label :class=\"labelClass\">{{\n t.templateSettings.contentLocale\n }}</label>\n <input\n type=\"text\"\n :class=\"inputClass\"\n :value=\"settings.locale ?? ''\"\n placeholder=\"en\"\n spellcheck=\"false\"\n autocapitalize=\"off\"\n autocomplete=\"off\"\n @input=\"\n emit('update', {\n locale:\n ($event.target as HTMLInputElement).value.trim() || undefined,\n })\n \"\n />\n <p\n class=\"tpl:mt-1 tpl:text-xs tpl:leading-relaxed tpl:text-[var(--tpl-text-dim)]\"\n >\n {{ t.templateSettings.contentLocaleHint }}\n </p>\n </div>\n </div>\n\n <!-- Preheader card -->\n <div :class=\"cardClass\">\n <div\n class=\"tpl:mb-3.5 tpl:flex tpl:items-center tpl:gap-2 tpl:text-sm tpl:font-semibold tpl:text-[var(--tpl-text)]\"\n >\n <Eye\n class=\"tpl:text-[var(--tpl-text-muted)]\"\n :size=\"14\"\n :stroke-width=\"2\"\n />\n <span>{{ t.templateSettings.preheaderText }}</span>\n </div>\n\n <div>\n <MergeTagTextarea\n :model-value=\"settings.preheaderText ?? ''\"\n :placeholder=\"t.templateSettings.preheaderTextPlaceholder\"\n :rows=\"2\"\n @update:model-value=\"\n emit('update', {\n preheaderText: $event.replace(/[\\r\\n]/g, ' ') || undefined,\n })\n \"\n />\n <div\n class=\"tpl:mt-1 tpl:flex tpl:items-start tpl:justify-between tpl:gap-2\"\n >\n <span\n class=\"tpl:text-xs tpl:leading-relaxed tpl:text-[var(--tpl-text-dim)]\"\n >\n {{ t.templateSettings.preheaderTextHint }}\n </span>\n <span\n class=\"tpl:shrink-0 tpl:text-xs tpl:tabular-nums tpl:text-[var(--tpl-text-dim)]\"\n >\n {{ (settings.preheaderText ?? \"\").length }}/{{\n PREHEADER_MAX_LENGTH\n }}\n </span>\n </div>\n </div>\n </div>\n\n <!-- Tips card -->\n <div\n class=\"tpl:rounded-[var(--tpl-radius)] tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:p-3\"\n >\n <div\n class=\"tpl:mb-2.5 tpl:flex tpl:items-center tpl:gap-1.5 tpl:text-sm tpl:font-semibold tpl:text-[var(--tpl-text-muted)]\"\n >\n <Info :size=\"14\" :stroke-width=\"2\" />\n <span>{{ t.templateSettings.tips }}</span>\n </div>\n <ul\n class=\"tpl:m-0 tpl:pl-[18px] tpl:text-xs tpl:leading-relaxed tpl:text-[var(--tpl-text-dim)]\"\n >\n <li class=\"tpl:mb-1 tpl:last:mb-0\">\n {{ t.templateSettings.tip1 }}\n </li>\n <li class=\"tpl:mb-1 tpl:last:mb-0\">\n {{ t.templateSettings.tip2 }}\n </li>\n <li class=\"tpl:mb-1 tpl:last:mb-0\">\n {{ t.templateSettings.tip3 }}\n </li>\n </ul>\n </div>\n </div>\n </aside>\n</template>\n","<script setup lang=\"ts\">\nimport { useMergeTagField } from \"../composables/useMergeTagField\";\nimport { inputClass } from \"../constants/styleConstants\";\nimport MergeTagSegments from \"./MergeTagSegments.vue\";\nimport MergeTagInsertButton from \"./MergeTagInsertButton.vue\";\nimport { ref } from \"vue\";\n\nconst props = withDefaults(\n defineProps<{\n modelValue: string;\n type?: \"text\" | \"url\";\n placeholder?: string;\n pulse?: boolean;\n }>(),\n {\n type: \"text\",\n placeholder: \"\",\n pulse: false,\n },\n);\n\nconst emit = defineEmits<{\n (e: \"update:modelValue\", value: string): void;\n}>();\n\nconst inputRef = ref<HTMLInputElement | null>(null);\n\nconst {\n segments,\n hasMergeTags,\n canRequestMergeTag,\n isRequestingMergeTag,\n isEditing,\n startEditing,\n stopEditing,\n handleInput,\n clearValue,\n insertMergeTag,\n} = useMergeTagField({\n modelValue: () => props.modelValue,\n emit: (value) => emit(\"update:modelValue\", value),\n elementRef: inputRef,\n});\n\nconst displayClass =\n \"tpl:flex tpl:w-full tpl:min-h-10 tpl:cursor-pointer tpl:items-center tpl:flex-wrap tpl:gap-1 tpl:rounded-[var(--tpl-radius-sm)] tpl:border tpl:shadow-xs tpl:bg-[var(--tpl-bg)] tpl:border-[var(--tpl-border)] tpl:px-3.5 tpl:py-1.5 tpl:transition-all tpl:duration-[120ms] tpl:ease-[cubic-bezier(0.16,1,0.3,1)]\";\n</script>\n\n<template>\n <div v-if=\"hasMergeTags && !isEditing\">\n <MergeTagSegments\n :segments=\"segments\"\n :display-class=\"displayClass\"\n :pulse=\"pulse\"\n @edit=\"startEditing\"\n @clear=\"clearValue\"\n />\n <MergeTagInsertButton\n v-if=\"canRequestMergeTag\"\n :disabled=\"isRequestingMergeTag\"\n @insert=\"insertMergeTag\"\n />\n </div>\n <div v-else>\n <input\n ref=\"inputRef\"\n :type=\"type\"\n :class=\"[inputClass, { 'tpl-pulse-fill': pulse }]\"\n :value=\"modelValue\"\n :placeholder=\"placeholder\"\n @input=\"handleInput\"\n @blur=\"stopEditing\"\n @keydown.escape=\"stopEditing\"\n />\n <MergeTagInsertButton\n v-if=\"canRequestMergeTag\"\n :disabled=\"isRequestingMergeTag\"\n @insert=\"insertMergeTag\"\n />\n </div>\n</template>\n\n<style scoped>\n.tpl-pulse-fill {\n animation: tpl-field-pulse 1s ease-out;\n}\n\n@keyframes tpl-field-pulse {\n 0% {\n box-shadow: 0 0 0 0 color-mix(in srgb, var(--tpl-warning) 30%, transparent);\n background-color: color-mix(in srgb, var(--tpl-warning) 6%, transparent);\n }\n 40% {\n box-shadow: 0 0 0 3px\n color-mix(in srgb, var(--tpl-warning) 15%, transparent);\n background-color: color-mix(in srgb, var(--tpl-warning) 5%, transparent);\n }\n 100% {\n box-shadow: 0 0 0 0 transparent;\n background-color: transparent;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .tpl-pulse-fill {\n animation: none;\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { useMergeTagField } from \"../composables/useMergeTagField\";\nimport { inputClass } from \"../constants/styleConstants\";\nimport MergeTagSegments from \"./MergeTagSegments.vue\";\nimport MergeTagInsertButton from \"./MergeTagInsertButton.vue\";\nimport { ref } from \"vue\";\n\nconst props = withDefaults(\n defineProps<{\n modelValue: string;\n type?: \"text\" | \"url\";\n placeholder?: string;\n pulse?: boolean;\n }>(),\n {\n type: \"text\",\n placeholder: \"\",\n pulse: false,\n },\n);\n\nconst emit = defineEmits<{\n (e: \"update:modelValue\", value: string): void;\n}>();\n\nconst inputRef = ref<HTMLInputElement | null>(null);\n\nconst {\n segments,\n hasMergeTags,\n canRequestMergeTag,\n isRequestingMergeTag,\n isEditing,\n startEditing,\n stopEditing,\n handleInput,\n clearValue,\n insertMergeTag,\n} = useMergeTagField({\n modelValue: () => props.modelValue,\n emit: (value) => emit(\"update:modelValue\", value),\n elementRef: inputRef,\n});\n\nconst displayClass =\n \"tpl:flex tpl:w-full tpl:min-h-10 tpl:cursor-pointer tpl:items-center tpl:flex-wrap tpl:gap-1 tpl:rounded-[var(--tpl-radius-sm)] tpl:border tpl:shadow-xs tpl:bg-[var(--tpl-bg)] tpl:border-[var(--tpl-border)] tpl:px-3.5 tpl:py-1.5 tpl:transition-all tpl:duration-[120ms] tpl:ease-[cubic-bezier(0.16,1,0.3,1)]\";\n</script>\n\n<template>\n <div v-if=\"hasMergeTags && !isEditing\">\n <MergeTagSegments\n :segments=\"segments\"\n :display-class=\"displayClass\"\n :pulse=\"pulse\"\n @edit=\"startEditing\"\n @clear=\"clearValue\"\n />\n <MergeTagInsertButton\n v-if=\"canRequestMergeTag\"\n :disabled=\"isRequestingMergeTag\"\n @insert=\"insertMergeTag\"\n />\n </div>\n <div v-else>\n <input\n ref=\"inputRef\"\n :type=\"type\"\n :class=\"[inputClass, { 'tpl-pulse-fill': pulse }]\"\n :value=\"modelValue\"\n :placeholder=\"placeholder\"\n @input=\"handleInput\"\n @blur=\"stopEditing\"\n @keydown.escape=\"stopEditing\"\n />\n <MergeTagInsertButton\n v-if=\"canRequestMergeTag\"\n :disabled=\"isRequestingMergeTag\"\n @insert=\"insertMergeTag\"\n />\n </div>\n</template>\n\n<style scoped>\n.tpl-pulse-fill {\n animation: tpl-field-pulse 1s ease-out;\n}\n\n@keyframes tpl-field-pulse {\n 0% {\n box-shadow: 0 0 0 0 color-mix(in srgb, var(--tpl-warning) 30%, transparent);\n background-color: color-mix(in srgb, var(--tpl-warning) 6%, transparent);\n }\n 40% {\n box-shadow: 0 0 0 3px\n color-mix(in srgb, var(--tpl-warning) 15%, transparent);\n background-color: color-mix(in srgb, var(--tpl-warning) 5%, transparent);\n }\n 100% {\n box-shadow: 0 0 0 0 transparent;\n background-color: transparent;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .tpl-pulse-fill {\n animation: none;\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed } from \"vue\";\nimport ColorPicker from \"../ColorPicker.vue\";\nimport MergeTagInput from \"../MergeTagInput.vue\";\nimport { useI18n } from \"../../composables/useI18n\";\nimport {\n inputClass,\n inputGroupInputClass,\n inputSuffixClass,\n labelClass,\n} from \"../../constants/styleConstants\";\nimport type {\n ButtonBlock,\n ButtonBorderSides,\n ButtonClickAction,\n} from \"@aswin.dev/types\";\n\nconst props = defineProps<{\n block: ButtonBlock;\n fontFamilies: Array<{ value: string; label: string }>;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update\", updates: Partial<ButtonBlock>): void;\n}>();\n\nconst { t } = useI18n();\n\nconst effectiveClickAction = computed(\n (): ButtonClickAction => props.block.clickAction ?? \"none\",\n);\n\nconst showLinkUrlFields = computed(() => {\n const a = effectiveClickAction.value;\n return a === \"none\" || a === \"load_page\";\n});\n\nconst urlFieldLabel = computed(() =>\n effectiveClickAction.value === \"load_page\" ? t.button.pageUrl : t.button.url,\n);\n\nconst selectActionModel = computed(() =>\n effectiveClickAction.value === \"none\" ? \"\" : effectiveClickAction.value,\n);\n\nfunction updateField(field: string, value: unknown): void {\n emit(\"update\", { [field]: value } as Partial<ButtonBlock>);\n}\n\nconst BORDER_SIDE_IDS: ButtonBorderSides[] = [\n \"all\",\n \"top\",\n \"right\",\n \"bottom\",\n \"left\",\n];\n\nconst borderSideOptions = computed(() =>\n BORDER_SIDE_IDS.map((id) => ({\n id,\n label:\n t.button.borderSidesLabels[id as keyof typeof t.button.borderSidesLabels],\n })),\n);\n\nfunction setClickActionFromSelect(ev: Event): void {\n const value = (ev.target as HTMLSelectElement).value;\n const action = (value === \"\" ? \"none\" : value) as ButtonClickAction;\n const patch: Partial<ButtonBlock> = {};\n if (action === \"none\") {\n patch.clickAction = undefined;\n patch.clickActionAnchorId = undefined;\n } else {\n patch.clickAction = action;\n if (action !== \"scroll_anchor\") {\n patch.clickActionAnchorId = undefined;\n }\n }\n emit(\"update\", patch);\n}\n</script>\n\n<template>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.button.fontFamily }}</label>\n <select\n :class=\"inputClass\"\n :value=\"block.fontFamily || ''\"\n @change=\"\n updateField(\n 'fontFamily',\n ($event.target as HTMLSelectElement).value || undefined,\n )\n \"\n >\n <option value=\"\">{{ t.button.inheritFont }}</option>\n <option\n v-for=\"font in fontFamilies\"\n :key=\"font.value\"\n :value=\"font.value\"\n >\n {{ font.label }}\n </option>\n </select>\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.button.text }}</label>\n <MergeTagInput\n :model-value=\"block.text\"\n type=\"text\"\n @update:model-value=\"updateField('text', $event)\"\n />\n </div>\n\n <div class=\"tpl:mb-3.5\">\n <label\n :class=\"[\n labelClass,\n 'tpl:!mb-1 tpl:!text-xs tpl:!font-semibold tpl:!uppercase tpl:!tracking-wide',\n ]\"\n >\n {{ t.button.actionOnClick }}\n </label>\n <select\n :class=\"inputClass\"\n :value=\"selectActionModel\"\n @change=\"setClickActionFromSelect\"\n >\n <option value=\"\">{{ t.button.clickActionNone }}</option>\n <option value=\"next_step\">{{ t.button.clickActionNextStep }}</option>\n <option value=\"previous_step\">\n {{ t.button.clickActionPreviousStep }}\n </option>\n <option value=\"load_page\">{{ t.button.clickActionLoadPage }}</option>\n <option value=\"scroll_anchor\">\n {{ t.button.clickActionScrollAnchor }}\n </option>\n <option value=\"close_popup\">{{ t.button.clickActionClosePopup }}</option>\n </select>\n </div>\n\n <div v-if=\"effectiveClickAction === 'scroll_anchor'\" class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.button.anchorId }}</label>\n <input\n type=\"text\"\n :class=\"inputClass\"\n :value=\"block.clickActionAnchorId ?? ''\"\n :placeholder=\"t.button.anchorIdPlaceholder\"\n autocapitalize=\"off\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n @input=\"\n updateField(\n 'clickActionAnchorId',\n ($event.target as HTMLInputElement).value.trim() || undefined,\n )\n \"\n />\n </div>\n\n <div v-if=\"showLinkUrlFields\" class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ urlFieldLabel }}</label>\n <MergeTagInput\n :model-value=\"block.url\"\n type=\"url\"\n :placeholder=\"t.button.urlPlaceholder\"\n @update:model-value=\"updateField('url', $event)\"\n />\n <label\n v-if=\"block.url\"\n class=\"tpl:mt-2 tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:text-[12px] tpl:text-[var(--tpl-text-muted)]\"\n >\n <input\n type=\"checkbox\"\n class=\"tpl:size-3.5 tpl:cursor-pointer tpl:accent-[var(--tpl-primary)]\"\n :checked=\"block.openInNewTab ?? false\"\n @change=\"\n updateField(\n 'openInNewTab',\n ($event.target as HTMLInputElement).checked,\n )\n \"\n />\n {{ t.button.openInNewTab }}\n </label>\n </div>\n\n <div class=\"tpl:flex tpl:flex-col tpl:gap-3\">\n <div class=\"tpl:w-full tpl:min-w-0\">\n <label :class=\"labelClass\">{{ t.button.background }}</label>\n <ColorPicker\n :model-value=\"block.backgroundColor\"\n @update:model-value=\"updateField('backgroundColor', $event)\"\n />\n </div>\n <div class=\"tpl:w-full tpl:min-w-0\">\n <label :class=\"labelClass\">{{ t.button.textColor }}</label>\n <ColorPicker\n :model-value=\"block.textColor\"\n @update:model-value=\"updateField('textColor', $event)\"\n />\n </div>\n </div>\n\n <div class=\"tpl:mt-3 tpl:flex tpl:flex-col tpl:gap-3\">\n <p\n :class=\"[\n labelClass,\n 'tpl:!mb-0 tpl:!text-xs tpl:!font-semibold tpl:!uppercase tpl:!tracking-wide',\n ]\"\n >\n {{ t.button.border }}\n </p>\n <div class=\"tpl:w-full tpl:min-w-0\">\n <label :class=\"labelClass\">{{ t.button.borderColor }}</label>\n <ColorPicker\n :model-value=\"block.borderColor ?? '#000000'\"\n @update:model-value=\"updateField('borderColor', $event)\"\n />\n </div>\n <div>\n <label :class=\"labelClass\">{{ t.button.borderWidth }}</label>\n <div class=\"tpl:flex tpl:items-stretch\">\n <input\n type=\"number\"\n :class=\"inputGroupInputClass\"\n :value=\"block.borderWidth ?? 0\"\n min=\"0\"\n max=\"20\"\n @input=\"\n updateField(\n 'borderWidth',\n Math.max(\n 0,\n Number(($event.target as HTMLInputElement).value) || 0,\n ),\n )\n \"\n />\n <span :class=\"inputSuffixClass\">px</span>\n </div>\n </div>\n <div>\n <label :class=\"labelClass\">{{ t.button.borderSides }}</label>\n <select\n :class=\"inputClass\"\n :value=\"block.borderSides ?? 'all'\"\n @change=\"\n updateField(\n 'borderSides',\n ($event.target as HTMLSelectElement).value as ButtonBorderSides,\n )\n \"\n >\n <option v-for=\"opt in borderSideOptions\" :key=\"opt.id\" :value=\"opt.id\">\n {{ opt.label }}\n </option>\n </select>\n </div>\n </div>\n\n <div class=\"tpl:mt-3 tpl:flex tpl:flex-col tpl:gap-3\">\n <div>\n <label :class=\"labelClass\">{{ t.button.borderRadius }}</label>\n <div class=\"tpl:flex tpl:items-stretch\">\n <input\n type=\"number\"\n :class=\"inputGroupInputClass\"\n :value=\"block.borderRadius\"\n min=\"0\"\n max=\"50\"\n @input=\"\n updateField(\n 'borderRadius',\n Number(($event.target as HTMLInputElement).value),\n )\n \"\n />\n <span :class=\"inputSuffixClass\">px</span>\n </div>\n </div>\n <div>\n <label :class=\"labelClass\">{{ t.button.fontSize }}</label>\n <div class=\"tpl:flex tpl:items-stretch\">\n <input\n type=\"number\"\n :class=\"inputGroupInputClass\"\n :value=\"block.fontSize\"\n min=\"10\"\n max=\"36\"\n @input=\"\n updateField(\n 'fontSize',\n Number(($event.target as HTMLInputElement).value),\n )\n \"\n />\n <span :class=\"inputSuffixClass\">px</span>\n </div>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from \"vue\";\nimport ColorPicker from \"../ColorPicker.vue\";\nimport MergeTagInput from \"../MergeTagInput.vue\";\nimport { useI18n } from \"../../composables/useI18n\";\nimport {\n inputClass,\n inputGroupInputClass,\n inputSuffixClass,\n labelClass,\n} from \"../../constants/styleConstants\";\nimport type {\n ButtonBlock,\n ButtonBorderSides,\n ButtonClickAction,\n} from \"@aswin.dev/types\";\n\nconst props = defineProps<{\n block: ButtonBlock;\n fontFamilies: Array<{ value: string; label: string }>;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update\", updates: Partial<ButtonBlock>): void;\n}>();\n\nconst { t } = useI18n();\n\nconst effectiveClickAction = computed(\n (): ButtonClickAction => props.block.clickAction ?? \"none\",\n);\n\nconst showLinkUrlFields = computed(() => {\n const a = effectiveClickAction.value;\n return a === \"none\" || a === \"load_page\";\n});\n\nconst urlFieldLabel = computed(() =>\n effectiveClickAction.value === \"load_page\" ? t.button.pageUrl : t.button.url,\n);\n\nconst selectActionModel = computed(() =>\n effectiveClickAction.value === \"none\" ? \"\" : effectiveClickAction.value,\n);\n\nfunction updateField(field: string, value: unknown): void {\n emit(\"update\", { [field]: value } as Partial<ButtonBlock>);\n}\n\nconst BORDER_SIDE_IDS: ButtonBorderSides[] = [\n \"all\",\n \"top\",\n \"right\",\n \"bottom\",\n \"left\",\n];\n\nconst borderSideOptions = computed(() =>\n BORDER_SIDE_IDS.map((id) => ({\n id,\n label:\n t.button.borderSidesLabels[id as keyof typeof t.button.borderSidesLabels],\n })),\n);\n\nfunction setClickActionFromSelect(ev: Event): void {\n const value = (ev.target as HTMLSelectElement).value;\n const action = (value === \"\" ? \"none\" : value) as ButtonClickAction;\n const patch: Partial<ButtonBlock> = {};\n if (action === \"none\") {\n patch.clickAction = undefined;\n patch.clickActionAnchorId = undefined;\n } else {\n patch.clickAction = action;\n if (action !== \"scroll_anchor\") {\n patch.clickActionAnchorId = undefined;\n }\n }\n emit(\"update\", patch);\n}\n</script>\n\n<template>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.button.fontFamily }}</label>\n <select\n :class=\"inputClass\"\n :value=\"block.fontFamily || ''\"\n @change=\"\n updateField(\n 'fontFamily',\n ($event.target as HTMLSelectElement).value || undefined,\n )\n \"\n >\n <option value=\"\">{{ t.button.inheritFont }}</option>\n <option\n v-for=\"font in fontFamilies\"\n :key=\"font.value\"\n :value=\"font.value\"\n >\n {{ font.label }}\n </option>\n </select>\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.button.text }}</label>\n <MergeTagInput\n :model-value=\"block.text\"\n type=\"text\"\n @update:model-value=\"updateField('text', $event)\"\n />\n </div>\n\n <div class=\"tpl:mb-3.5\">\n <label\n :class=\"[\n labelClass,\n 'tpl:!mb-1 tpl:!text-xs tpl:!font-semibold tpl:!uppercase tpl:!tracking-wide',\n ]\"\n >\n {{ t.button.actionOnClick }}\n </label>\n <select\n :class=\"inputClass\"\n :value=\"selectActionModel\"\n @change=\"setClickActionFromSelect\"\n >\n <option value=\"\">{{ t.button.clickActionNone }}</option>\n <option value=\"next_step\">{{ t.button.clickActionNextStep }}</option>\n <option value=\"previous_step\">\n {{ t.button.clickActionPreviousStep }}\n </option>\n <option value=\"load_page\">{{ t.button.clickActionLoadPage }}</option>\n <option value=\"scroll_anchor\">\n {{ t.button.clickActionScrollAnchor }}\n </option>\n <option value=\"close_popup\">{{ t.button.clickActionClosePopup }}</option>\n </select>\n </div>\n\n <div v-if=\"effectiveClickAction === 'scroll_anchor'\" class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.button.anchorId }}</label>\n <input\n type=\"text\"\n :class=\"inputClass\"\n :value=\"block.clickActionAnchorId ?? ''\"\n :placeholder=\"t.button.anchorIdPlaceholder\"\n autocapitalize=\"off\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n @input=\"\n updateField(\n 'clickActionAnchorId',\n ($event.target as HTMLInputElement).value.trim() || undefined,\n )\n \"\n />\n </div>\n\n <div v-if=\"showLinkUrlFields\" class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ urlFieldLabel }}</label>\n <MergeTagInput\n :model-value=\"block.url\"\n type=\"url\"\n :placeholder=\"t.button.urlPlaceholder\"\n @update:model-value=\"updateField('url', $event)\"\n />\n <label\n v-if=\"block.url\"\n class=\"tpl:mt-2 tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:text-[12px] tpl:text-[var(--tpl-text-muted)]\"\n >\n <input\n type=\"checkbox\"\n class=\"tpl:size-3.5 tpl:cursor-pointer tpl:accent-[var(--tpl-primary)]\"\n :checked=\"block.openInNewTab ?? false\"\n @change=\"\n updateField(\n 'openInNewTab',\n ($event.target as HTMLInputElement).checked,\n )\n \"\n />\n {{ t.button.openInNewTab }}\n </label>\n </div>\n\n <div class=\"tpl:flex tpl:flex-col tpl:gap-3\">\n <div class=\"tpl:w-full tpl:min-w-0\">\n <label :class=\"labelClass\">{{ t.button.background }}</label>\n <ColorPicker\n :model-value=\"block.backgroundColor\"\n @update:model-value=\"updateField('backgroundColor', $event)\"\n />\n </div>\n <div class=\"tpl:w-full tpl:min-w-0\">\n <label :class=\"labelClass\">{{ t.button.textColor }}</label>\n <ColorPicker\n :model-value=\"block.textColor\"\n @update:model-value=\"updateField('textColor', $event)\"\n />\n </div>\n </div>\n\n <div class=\"tpl:mt-3 tpl:flex tpl:flex-col tpl:gap-3\">\n <p\n :class=\"[\n labelClass,\n 'tpl:!mb-0 tpl:!text-xs tpl:!font-semibold tpl:!uppercase tpl:!tracking-wide',\n ]\"\n >\n {{ t.button.border }}\n </p>\n <div class=\"tpl:w-full tpl:min-w-0\">\n <label :class=\"labelClass\">{{ t.button.borderColor }}</label>\n <ColorPicker\n :model-value=\"block.borderColor ?? '#000000'\"\n @update:model-value=\"updateField('borderColor', $event)\"\n />\n </div>\n <div>\n <label :class=\"labelClass\">{{ t.button.borderWidth }}</label>\n <div class=\"tpl:flex tpl:items-stretch\">\n <input\n type=\"number\"\n :class=\"inputGroupInputClass\"\n :value=\"block.borderWidth ?? 0\"\n min=\"0\"\n max=\"20\"\n @input=\"\n updateField(\n 'borderWidth',\n Math.max(\n 0,\n Number(($event.target as HTMLInputElement).value) || 0,\n ),\n )\n \"\n />\n <span :class=\"inputSuffixClass\">px</span>\n </div>\n </div>\n <div>\n <label :class=\"labelClass\">{{ t.button.borderSides }}</label>\n <select\n :class=\"inputClass\"\n :value=\"block.borderSides ?? 'all'\"\n @change=\"\n updateField(\n 'borderSides',\n ($event.target as HTMLSelectElement).value as ButtonBorderSides,\n )\n \"\n >\n <option v-for=\"opt in borderSideOptions\" :key=\"opt.id\" :value=\"opt.id\">\n {{ opt.label }}\n </option>\n </select>\n </div>\n </div>\n\n <div class=\"tpl:mt-3 tpl:flex tpl:flex-col tpl:gap-3\">\n <div>\n <label :class=\"labelClass\">{{ t.button.borderRadius }}</label>\n <div class=\"tpl:flex tpl:items-stretch\">\n <input\n type=\"number\"\n :class=\"inputGroupInputClass\"\n :value=\"block.borderRadius\"\n min=\"0\"\n max=\"50\"\n @input=\"\n updateField(\n 'borderRadius',\n Number(($event.target as HTMLInputElement).value),\n )\n \"\n />\n <span :class=\"inputSuffixClass\">px</span>\n </div>\n </div>\n <div>\n <label :class=\"labelClass\">{{ t.button.fontSize }}</label>\n <div class=\"tpl:flex tpl:items-stretch\">\n <input\n type=\"number\"\n :class=\"inputGroupInputClass\"\n :value=\"block.fontSize\"\n min=\"10\"\n max=\"36\"\n @input=\"\n updateField(\n 'fontSize',\n Number(($event.target as HTMLInputElement).value),\n )\n \"\n />\n <span :class=\"inputSuffixClass\">px</span>\n </div>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { ChevronDown } from \"@lucide/vue\";\n\ndefineProps<{\n title: string;\n open: boolean;\n noBorder?: boolean;\n}>();\n\ndefineEmits<{\n (e: \"toggle\"): void;\n}>();\n</script>\n\n<template>\n <div\n class=\"tpl:py-3\"\n :class=\"noBorder ? '' : 'tpl:border-t tpl:border-[var(--tpl-border)]'\"\n >\n <button\n type=\"button\"\n class=\"tpl:flex tpl:w-full tpl:cursor-pointer tpl:items-center tpl:gap-1.5 tpl:border-none tpl:bg-transparent tpl:p-0 tpl:text-sm tpl:font-medium tpl:text-[var(--tpl-text-muted)]\"\n @click=\"$emit('toggle')\"\n >\n <ChevronDown\n class=\"tpl:transition-transform tpl:duration-200\"\n :class=\"open ? 'tpl:rotate-0' : 'tpl:-rotate-90'\"\n :size=\"12\"\n :stroke-width=\"2\"\n />\n <span>{{ title }}</span>\n </button>\n <div v-show=\"open\" class=\"tpl:mt-3\">\n <slot />\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { ChevronDown } from \"@lucide/vue\";\n\ndefineProps<{\n title: string;\n open: boolean;\n noBorder?: boolean;\n}>();\n\ndefineEmits<{\n (e: \"toggle\"): void;\n}>();\n</script>\n\n<template>\n <div\n class=\"tpl:py-3\"\n :class=\"noBorder ? '' : 'tpl:border-t tpl:border-[var(--tpl-border)]'\"\n >\n <button\n type=\"button\"\n class=\"tpl:flex tpl:w-full tpl:cursor-pointer tpl:items-center tpl:gap-1.5 tpl:border-none tpl:bg-transparent tpl:p-0 tpl:text-sm tpl:font-medium tpl:text-[var(--tpl-text-muted)]\"\n @click=\"$emit('toggle')\"\n >\n <ChevronDown\n class=\"tpl:transition-transform tpl:duration-200\"\n :class=\"open ? 'tpl:rotate-0' : 'tpl:-rotate-90'\"\n :size=\"12\"\n :stroke-width=\"2\"\n />\n <span>{{ title }}</span>\n </button>\n <div v-show=\"open\" class=\"tpl:mt-3\">\n <slot />\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport ColorPicker from \"../ColorPicker.vue\";\nimport SpacingControl from \"../SpacingControl.vue\";\nimport CollapsibleSection from \"./CollapsibleSection.vue\";\nimport { useI18n } from \"../../composables/useI18n\";\nimport {\n labelClass,\n monoTextareaClass,\n DEFAULT_BG_COLOR,\n} from \"../../constants/styleConstants\";\nimport type { Block, DisplayCondition } from \"@aswin.dev/types\";\nimport { Monitor, Smartphone, Tablet } from \"@lucide/vue\";\nimport { computed, inject, reactive, ref, watch, type Component } from \"vue\";\nimport {\n DISPLAY_CONDITIONS_KEY,\n ALLOW_CUSTOM_CONDITIONS_KEY,\n} from \"../../keys\";\n\ntype SectionKey = \"spacing\" | \"bg\" | \"display\" | \"css\" | \"condition\";\ntype VisibilityKey = \"desktop\" | \"tablet\" | \"mobile\";\n\nconst props = defineProps<{\n block: Block;\n isFirstSection?: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update\", updates: Partial<Block>): void;\n}>();\n\nconst { t } = useI18n();\n\nconst displayConditions = inject(DISPLAY_CONDITIONS_KEY, []);\nconst allowCustomConditions = inject(ALLOW_CUSTOM_CONDITIONS_KEY, false);\n\nconst openSections = reactive(new Set<SectionKey>());\nconst customConditionMode = ref(false);\nconst customBefore = ref(\"\");\nconst customAfter = ref(\"\");\n\nconst VISIBILITY_ITEMS: {\n key: VisibilityKey;\n icon: Component;\n labelKey: \"showOnDesktop\" | \"showOnTablet\" | \"showOnMobile\";\n}[] = [\n { key: \"desktop\", icon: Monitor, labelKey: \"showOnDesktop\" },\n { key: \"tablet\", icon: Tablet, labelKey: \"showOnTablet\" },\n { key: \"mobile\", icon: Smartphone, labelKey: \"showOnMobile\" },\n];\n\nfunction toggleSection(key: SectionKey): void {\n if (openSections.has(key)) openSections.delete(key);\n else openSections.add(key);\n}\n\nconst hasDisplayConditions = computed(\n () => displayConditions.length > 0 || allowCustomConditions,\n);\n\nconst isCustomCondition = computed(() => {\n if (!props.block.displayCondition) return false;\n return !displayConditions.some(\n (c) => c.label === props.block.displayCondition?.label,\n );\n});\n\nfunction startCustomCondition(): void {\n customConditionMode.value = true;\n if (isCustomCondition.value && props.block.displayCondition) {\n customBefore.value = props.block.displayCondition.before;\n customAfter.value = props.block.displayCondition.after ?? \"\";\n } else {\n customBefore.value = \"\";\n customAfter.value = \"\";\n }\n}\n\nfunction applyCustomCondition(): void {\n if (!customBefore.value.trim()) return;\n emit(\"update\", {\n displayCondition: {\n label: t.blockSettings.customCondition,\n before: customBefore.value.trim(),\n after: customAfter.value.trim(),\n },\n });\n customConditionMode.value = false;\n customBefore.value = \"\";\n customAfter.value = \"\";\n}\n\nwatch(\n () => props.block.displayCondition,\n (condition) => {\n if (!condition) {\n customConditionMode.value = false;\n customBefore.value = \"\";\n customAfter.value = \"\";\n return;\n }\n if (isCustomCondition.value) {\n customBefore.value = condition.before;\n customAfter.value = condition.after ?? \"\";\n }\n },\n { immediate: true },\n);\n\nconst groupedDisplayConditions = computed(() => {\n const groups: Record<string, DisplayCondition[]> = {};\n for (const condition of displayConditions) {\n const group = condition.group ?? \"\";\n if (!groups[group]) groups[group] = [];\n groups[group].push(condition);\n }\n return groups;\n});\n\nfunction updateStyle(field: string, value: unknown): void {\n emit(\"update\", {\n styles: { ...props.block.styles, [field]: value },\n });\n}\n\nfunction isVisible(key: VisibilityKey): boolean {\n return props.block.visibility?.[key] !== false;\n}\n\nfunction toggleVisibility(key: VisibilityKey): void {\n const next: Record<VisibilityKey, boolean> = {\n desktop: isVisible(\"desktop\"),\n tablet: isVisible(\"tablet\"),\n mobile: isVisible(\"mobile\"),\n };\n next[key] = !next[key];\n emit(\"update\", { visibility: next });\n}\n</script>\n\n<template>\n <div class=\"tpl:flex tpl:flex-col\" :class=\"isFirstSection ? '' : 'tpl:mt-4'\">\n <CollapsibleSection\n :title=\"t.blockSettings.spacing\"\n :open=\"openSections.has('spacing')\"\n :no-border=\"isFirstSection\"\n @toggle=\"toggleSection('spacing')\"\n >\n <SpacingControl\n :label=\"t.blockSettings.padding\"\n :model-value=\"block.styles.padding\"\n @update:model-value=\"updateStyle('padding', $event)\"\n />\n <div class=\"tpl:mt-4\">\n <SpacingControl\n :label=\"t.blockSettings.margin\"\n :model-value=\"block.styles.margin\"\n @update:model-value=\"updateStyle('margin', $event)\"\n />\n </div>\n </CollapsibleSection>\n\n <CollapsibleSection\n :title=\"t.blockSettings.background\"\n :open=\"openSections.has('bg')\"\n @toggle=\"toggleSection('bg')\"\n >\n <label :class=\"labelClass\">{{ t.blockSettings.color }}</label>\n <ColorPicker\n size=\"large\"\n :model-value=\"block.styles.backgroundColor || DEFAULT_BG_COLOR\"\n @update:model-value=\"updateStyle('backgroundColor', $event)\"\n />\n </CollapsibleSection>\n\n <CollapsibleSection\n :title=\"t.blockSettings.display\"\n :open=\"openSections.has('display')\"\n @toggle=\"toggleSection('display')\"\n >\n <div class=\"tpl:space-y-2\">\n <label\n v-for=\"item in VISIBILITY_ITEMS\"\n :key=\"item.key\"\n class=\"tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:text-xs tpl:text-[var(--tpl-text)]\"\n >\n <input\n type=\"checkbox\"\n class=\"tpl:accent-[var(--tpl-primary)]\"\n :checked=\"isVisible(item.key)\"\n @change=\"toggleVisibility(item.key)\"\n />\n <component :is=\"item.icon\" :size=\"14\" :stroke-width=\"1.5\" />\n {{ t.blockSettings[item.labelKey] }}\n </label>\n </div>\n </CollapsibleSection>\n\n <CollapsibleSection\n :title=\"t.blockSettings.customCss\"\n :open=\"openSections.has('css')\"\n @toggle=\"toggleSection('css')\"\n >\n <label :class=\"labelClass\">{{ t.blockSettings.css }}</label>\n <textarea\n :value=\"block.customCss || ''\"\n :placeholder=\"t.blockSettings.cssPlaceholder\"\n rows=\"3\"\n :class=\"monoTextareaClass\"\n @input=\"\n emit('update', {\n customCss: ($event.target as HTMLTextAreaElement).value,\n })\n \"\n />\n </CollapsibleSection>\n\n <CollapsibleSection\n v-if=\"hasDisplayConditions\"\n :title=\"t.blockSettings.displayCondition\"\n :open=\"openSections.has('condition')\"\n @toggle=\"toggleSection('condition')\"\n >\n <div class=\"tpl:space-y-2\">\n <select\n class=\"tpl:w-full tpl:rounded-md tpl:border tpl:px-2.5 tpl:py-2 tpl:text-xs tpl:outline-none tpl:transition-all tpl:duration-150 tpl:focus:border-[var(--tpl-primary)] tpl:focus:shadow-[0_0_0_3px_var(--tpl-primary-light)]\"\n :class=\"\n block.displayCondition\n ? 'tpl:border-[var(--tpl-primary)] tpl:bg-[var(--tpl-primary-light)] tpl:text-[var(--tpl-text)]'\n : 'tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:text-[var(--tpl-text)]'\n \"\n :value=\"\n customConditionMode || isCustomCondition\n ? '__custom__'\n : (block.displayCondition?.label ?? '')\n \"\n @change=\"\n (e: Event) => {\n const label = (e.target as HTMLSelectElement).value;\n if (label === '__custom__') {\n startCustomCondition();\n return;\n }\n customConditionMode = false;\n if (!label) {\n emit('update', { displayCondition: undefined });\n return;\n }\n const condition = displayConditions.find(\n (c) => c.label === label,\n );\n if (condition) {\n emit('update', { displayCondition: condition });\n }\n }\n \"\n >\n <option value=\"\">{{ t.blockSettings.noCondition }}</option>\n <template\n v-for=\"(conditions, group) in groupedDisplayConditions\"\n :key=\"group\"\n >\n <optgroup v-if=\"group\" :label=\"String(group)\">\n <option\n v-for=\"condition in conditions\"\n :key=\"condition.label\"\n :value=\"condition.label\"\n >\n {{ condition.label }}\n </option>\n </optgroup>\n <template v-else>\n <option\n v-for=\"condition in conditions\"\n :key=\"condition.label\"\n :value=\"condition.label\"\n >\n {{ condition.label }}\n </option>\n </template>\n </template>\n <option v-if=\"allowCustomConditions\" value=\"__custom__\">\n {{ t.blockSettings.customCondition }}\n </option>\n </select>\n\n <template v-if=\"customConditionMode || isCustomCondition\">\n <div class=\"tpl:space-y-2\">\n <div>\n <label\n class=\"tpl:mb-1 tpl:block tpl:text-[11px] tpl:font-medium tpl:text-[var(--tpl-text-muted)]\"\n >{{ t.blockSettings.customConditionBefore }}</label\n >\n <textarea\n v-model=\"customBefore\"\n rows=\"2\"\n :class=\"monoTextareaClass\"\n />\n </div>\n <div>\n <label\n class=\"tpl:mb-1 tpl:block tpl:text-[11px] tpl:font-medium tpl:text-[var(--tpl-text-muted)]\"\n >{{ t.blockSettings.customConditionAfter }}</label\n >\n <textarea\n v-model=\"customAfter\"\n rows=\"2\"\n :class=\"monoTextareaClass\"\n />\n </div>\n <div class=\"tpl:flex tpl:justify-end\">\n <button\n type=\"button\"\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:border-none tpl:bg-[var(--tpl-primary)] tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-bg)] tpl:transition-all tpl:duration-150 tpl:hover:opacity-90 tpl:disabled:opacity-50\"\n :disabled=\"!customBefore.trim()\"\n @click=\"applyCustomCondition\"\n >\n {{ t.blockSettings.applyCondition }}\n </button>\n </div>\n </div>\n </template>\n\n <template v-else-if=\"block.displayCondition && !isCustomCondition\">\n <p\n v-if=\"block.displayCondition.description\"\n class=\"tpl:text-[11px] tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ block.displayCondition.description }}\n </p>\n <div class=\"tpl:space-y-1\">\n <pre\n class=\"tpl:m-0 tpl:overflow-x-auto tpl:rounded tpl:bg-[var(--tpl-bg)] tpl:p-2 tpl:font-mono tpl:text-[10px] tpl:text-[var(--tpl-text-muted)]\"\n >{{ block.displayCondition.before }}</pre\n >\n <pre\n v-if=\"block.displayCondition.after\"\n class=\"tpl:m-0 tpl:overflow-x-auto tpl:rounded tpl:bg-[var(--tpl-bg)] tpl:p-2 tpl:font-mono tpl:text-[10px] tpl:text-[var(--tpl-text-muted)]\"\n >{{ block.displayCondition.after }}</pre\n >\n </div>\n </template>\n </div>\n </CollapsibleSection>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport ColorPicker from \"../ColorPicker.vue\";\nimport SpacingControl from \"../SpacingControl.vue\";\nimport CollapsibleSection from \"./CollapsibleSection.vue\";\nimport { useI18n } from \"../../composables/useI18n\";\nimport {\n labelClass,\n monoTextareaClass,\n DEFAULT_BG_COLOR,\n} from \"../../constants/styleConstants\";\nimport type { Block, DisplayCondition } from \"@aswin.dev/types\";\nimport { Monitor, Smartphone, Tablet } from \"@lucide/vue\";\nimport { computed, inject, reactive, ref, watch, type Component } from \"vue\";\nimport {\n DISPLAY_CONDITIONS_KEY,\n ALLOW_CUSTOM_CONDITIONS_KEY,\n} from \"../../keys\";\n\ntype SectionKey = \"spacing\" | \"bg\" | \"display\" | \"css\" | \"condition\";\ntype VisibilityKey = \"desktop\" | \"tablet\" | \"mobile\";\n\nconst props = defineProps<{\n block: Block;\n isFirstSection?: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update\", updates: Partial<Block>): void;\n}>();\n\nconst { t } = useI18n();\n\nconst displayConditions = inject(DISPLAY_CONDITIONS_KEY, []);\nconst allowCustomConditions = inject(ALLOW_CUSTOM_CONDITIONS_KEY, false);\n\nconst openSections = reactive(new Set<SectionKey>());\nconst customConditionMode = ref(false);\nconst customBefore = ref(\"\");\nconst customAfter = ref(\"\");\n\nconst VISIBILITY_ITEMS: {\n key: VisibilityKey;\n icon: Component;\n labelKey: \"showOnDesktop\" | \"showOnTablet\" | \"showOnMobile\";\n}[] = [\n { key: \"desktop\", icon: Monitor, labelKey: \"showOnDesktop\" },\n { key: \"tablet\", icon: Tablet, labelKey: \"showOnTablet\" },\n { key: \"mobile\", icon: Smartphone, labelKey: \"showOnMobile\" },\n];\n\nfunction toggleSection(key: SectionKey): void {\n if (openSections.has(key)) openSections.delete(key);\n else openSections.add(key);\n}\n\nconst hasDisplayConditions = computed(\n () => displayConditions.length > 0 || allowCustomConditions,\n);\n\nconst isCustomCondition = computed(() => {\n if (!props.block.displayCondition) return false;\n return !displayConditions.some(\n (c) => c.label === props.block.displayCondition?.label,\n );\n});\n\nfunction startCustomCondition(): void {\n customConditionMode.value = true;\n if (isCustomCondition.value && props.block.displayCondition) {\n customBefore.value = props.block.displayCondition.before;\n customAfter.value = props.block.displayCondition.after ?? \"\";\n } else {\n customBefore.value = \"\";\n customAfter.value = \"\";\n }\n}\n\nfunction applyCustomCondition(): void {\n if (!customBefore.value.trim()) return;\n emit(\"update\", {\n displayCondition: {\n label: t.blockSettings.customCondition,\n before: customBefore.value.trim(),\n after: customAfter.value.trim(),\n },\n });\n customConditionMode.value = false;\n customBefore.value = \"\";\n customAfter.value = \"\";\n}\n\nwatch(\n () => props.block.displayCondition,\n (condition) => {\n if (!condition) {\n customConditionMode.value = false;\n customBefore.value = \"\";\n customAfter.value = \"\";\n return;\n }\n if (isCustomCondition.value) {\n customBefore.value = condition.before;\n customAfter.value = condition.after ?? \"\";\n }\n },\n { immediate: true },\n);\n\nconst groupedDisplayConditions = computed(() => {\n const groups: Record<string, DisplayCondition[]> = {};\n for (const condition of displayConditions) {\n const group = condition.group ?? \"\";\n if (!groups[group]) groups[group] = [];\n groups[group].push(condition);\n }\n return groups;\n});\n\nfunction updateStyle(field: string, value: unknown): void {\n emit(\"update\", {\n styles: { ...props.block.styles, [field]: value },\n });\n}\n\nfunction isVisible(key: VisibilityKey): boolean {\n return props.block.visibility?.[key] !== false;\n}\n\nfunction toggleVisibility(key: VisibilityKey): void {\n const next: Record<VisibilityKey, boolean> = {\n desktop: isVisible(\"desktop\"),\n tablet: isVisible(\"tablet\"),\n mobile: isVisible(\"mobile\"),\n };\n next[key] = !next[key];\n emit(\"update\", { visibility: next });\n}\n</script>\n\n<template>\n <div class=\"tpl:flex tpl:flex-col\" :class=\"isFirstSection ? '' : 'tpl:mt-4'\">\n <CollapsibleSection\n :title=\"t.blockSettings.spacing\"\n :open=\"openSections.has('spacing')\"\n :no-border=\"isFirstSection\"\n @toggle=\"toggleSection('spacing')\"\n >\n <SpacingControl\n :label=\"t.blockSettings.padding\"\n :model-value=\"block.styles.padding\"\n @update:model-value=\"updateStyle('padding', $event)\"\n />\n <div class=\"tpl:mt-4\">\n <SpacingControl\n :label=\"t.blockSettings.margin\"\n :model-value=\"block.styles.margin\"\n @update:model-value=\"updateStyle('margin', $event)\"\n />\n </div>\n </CollapsibleSection>\n\n <CollapsibleSection\n :title=\"t.blockSettings.background\"\n :open=\"openSections.has('bg')\"\n @toggle=\"toggleSection('bg')\"\n >\n <label :class=\"labelClass\">{{ t.blockSettings.color }}</label>\n <ColorPicker\n size=\"large\"\n :model-value=\"block.styles.backgroundColor || DEFAULT_BG_COLOR\"\n @update:model-value=\"updateStyle('backgroundColor', $event)\"\n />\n </CollapsibleSection>\n\n <CollapsibleSection\n :title=\"t.blockSettings.display\"\n :open=\"openSections.has('display')\"\n @toggle=\"toggleSection('display')\"\n >\n <div class=\"tpl:space-y-2\">\n <label\n v-for=\"item in VISIBILITY_ITEMS\"\n :key=\"item.key\"\n class=\"tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:text-xs tpl:text-[var(--tpl-text)]\"\n >\n <input\n type=\"checkbox\"\n class=\"tpl:accent-[var(--tpl-primary)]\"\n :checked=\"isVisible(item.key)\"\n @change=\"toggleVisibility(item.key)\"\n />\n <component :is=\"item.icon\" :size=\"14\" :stroke-width=\"1.5\" />\n {{ t.blockSettings[item.labelKey] }}\n </label>\n </div>\n </CollapsibleSection>\n\n <CollapsibleSection\n :title=\"t.blockSettings.customCss\"\n :open=\"openSections.has('css')\"\n @toggle=\"toggleSection('css')\"\n >\n <label :class=\"labelClass\">{{ t.blockSettings.css }}</label>\n <textarea\n :value=\"block.customCss || ''\"\n :placeholder=\"t.blockSettings.cssPlaceholder\"\n rows=\"3\"\n :class=\"monoTextareaClass\"\n @input=\"\n emit('update', {\n customCss: ($event.target as HTMLTextAreaElement).value,\n })\n \"\n />\n </CollapsibleSection>\n\n <CollapsibleSection\n v-if=\"hasDisplayConditions\"\n :title=\"t.blockSettings.displayCondition\"\n :open=\"openSections.has('condition')\"\n @toggle=\"toggleSection('condition')\"\n >\n <div class=\"tpl:space-y-2\">\n <select\n class=\"tpl:w-full tpl:rounded-md tpl:border tpl:px-2.5 tpl:py-2 tpl:text-xs tpl:outline-none tpl:transition-all tpl:duration-150 tpl:focus:border-[var(--tpl-primary)] tpl:focus:shadow-[0_0_0_3px_var(--tpl-primary-light)]\"\n :class=\"\n block.displayCondition\n ? 'tpl:border-[var(--tpl-primary)] tpl:bg-[var(--tpl-primary-light)] tpl:text-[var(--tpl-text)]'\n : 'tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:text-[var(--tpl-text)]'\n \"\n :value=\"\n customConditionMode || isCustomCondition\n ? '__custom__'\n : (block.displayCondition?.label ?? '')\n \"\n @change=\"\n (e: Event) => {\n const label = (e.target as HTMLSelectElement).value;\n if (label === '__custom__') {\n startCustomCondition();\n return;\n }\n customConditionMode = false;\n if (!label) {\n emit('update', { displayCondition: undefined });\n return;\n }\n const condition = displayConditions.find(\n (c) => c.label === label,\n );\n if (condition) {\n emit('update', { displayCondition: condition });\n }\n }\n \"\n >\n <option value=\"\">{{ t.blockSettings.noCondition }}</option>\n <template\n v-for=\"(conditions, group) in groupedDisplayConditions\"\n :key=\"group\"\n >\n <optgroup v-if=\"group\" :label=\"String(group)\">\n <option\n v-for=\"condition in conditions\"\n :key=\"condition.label\"\n :value=\"condition.label\"\n >\n {{ condition.label }}\n </option>\n </optgroup>\n <template v-else>\n <option\n v-for=\"condition in conditions\"\n :key=\"condition.label\"\n :value=\"condition.label\"\n >\n {{ condition.label }}\n </option>\n </template>\n </template>\n <option v-if=\"allowCustomConditions\" value=\"__custom__\">\n {{ t.blockSettings.customCondition }}\n </option>\n </select>\n\n <template v-if=\"customConditionMode || isCustomCondition\">\n <div class=\"tpl:space-y-2\">\n <div>\n <label\n class=\"tpl:mb-1 tpl:block tpl:text-[11px] tpl:font-medium tpl:text-[var(--tpl-text-muted)]\"\n >{{ t.blockSettings.customConditionBefore }}</label\n >\n <textarea\n v-model=\"customBefore\"\n rows=\"2\"\n :class=\"monoTextareaClass\"\n />\n </div>\n <div>\n <label\n class=\"tpl:mb-1 tpl:block tpl:text-[11px] tpl:font-medium tpl:text-[var(--tpl-text-muted)]\"\n >{{ t.blockSettings.customConditionAfter }}</label\n >\n <textarea\n v-model=\"customAfter\"\n rows=\"2\"\n :class=\"monoTextareaClass\"\n />\n </div>\n <div class=\"tpl:flex tpl:justify-end\">\n <button\n type=\"button\"\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:border-none tpl:bg-[var(--tpl-primary)] tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-bg)] tpl:transition-all tpl:duration-150 tpl:hover:opacity-90 tpl:disabled:opacity-50\"\n :disabled=\"!customBefore.trim()\"\n @click=\"applyCustomCondition\"\n >\n {{ t.blockSettings.applyCondition }}\n </button>\n </div>\n </div>\n </template>\n\n <template v-else-if=\"block.displayCondition && !isCustomCondition\">\n <p\n v-if=\"block.displayCondition.description\"\n class=\"tpl:text-[11px] tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ block.displayCondition.description }}\n </p>\n <div class=\"tpl:space-y-1\">\n <pre\n class=\"tpl:m-0 tpl:overflow-x-auto tpl:rounded tpl:bg-[var(--tpl-bg)] tpl:p-2 tpl:font-mono tpl:text-[10px] tpl:text-[var(--tpl-text-muted)]\"\n >{{ block.displayCondition.before }}</pre\n >\n <pre\n v-if=\"block.displayCondition.after\"\n class=\"tpl:m-0 tpl:overflow-x-auto tpl:rounded tpl:bg-[var(--tpl-bg)] tpl:p-2 tpl:font-mono tpl:text-[10px] tpl:text-[var(--tpl-text-muted)]\"\n >{{ block.displayCondition.after }}</pre\n >\n </div>\n </template>\n </div>\n </CollapsibleSection>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, ref } from \"vue\";\nimport {\n Check,\n ChevronDown,\n Mail,\n Phone,\n Plus,\n Trash2,\n User,\n} from \"@lucide/vue\";\nimport type {\n FormBlock,\n FormButtonAction,\n FormButtonLayout,\n FormFieldData,\n FormFieldType,\n FormSyncIntegrationId,\n} from \"@aswin.dev/types\";\nimport { createFormField } from \"@aswin.dev/types\";\nimport {\n addItemBtnClass,\n inputClass,\n labelClass,\n} from \"../../constants/styleConstants\";\nimport { useI18n } from \"../../composables/useI18n\";\nimport ColorPicker from \"../ColorPicker.vue\";\n\nconst props = defineProps<{\n block: FormBlock;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update\", updates: Partial<FormBlock>): void;\n}>();\n\nconst { t } = useI18n();\n\ntype Tab = \"sync\" | \"fields\" | \"button\" | \"terms\";\nconst activeTab = ref<Tab>(\"fields\");\n\nconst tabs: { id: Tab; label: string }[] = [\n { id: \"sync\", label: t.form.tabs.sync },\n { id: \"fields\", label: t.form.tabs.fields },\n { id: \"button\", label: t.form.tabs.button },\n { id: \"terms\", label: t.form.tabs.terms },\n];\n\nfunction tabClass(tab: Tab): string {\n const base =\n \"tpl:flex-1 tpl:cursor-pointer tpl:rounded-[var(--tpl-radius-sm)] tpl:border-none tpl:px-3 tpl:py-2 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-[120ms]\";\n if (activeTab.value === tab) {\n return `${base} tpl:text-[var(--tpl-text)] tpl:bg-[var(--tpl-bg)] tpl:shadow-[var(--tpl-shadow-md)]`;\n }\n return `${base} tpl:text-[var(--tpl-text-muted)] tpl:bg-transparent hover:tpl:text-[var(--tpl-text)]`;\n}\n\nfunction updateField<K extends keyof FormBlock>(\n key: K,\n value: FormBlock[K],\n): void {\n emit(\"update\", { [key]: value } as Partial<FormBlock>);\n}\n\n// ---------------------------------------------------------------------------\n// Sync tab — list of marketing platforms (curated, static for now).\n// ---------------------------------------------------------------------------\n\ninterface IntegrationItem {\n id: FormSyncIntegrationId;\n label: string;\n initial: string;\n color: string;\n}\n\nconst integrations: IntegrationItem[] = [\n { id: \"acoustic\", label: \"Acoustic\", initial: \"A\", color: \"#5b21b6\" },\n { id: \"actito\", label: \"Actito\", initial: \"a\", color: \"#16a34a\" },\n {\n id: \"active_campaign\",\n label: \"Active Campaign\",\n initial: \">\",\n color: \"#1e40af\",\n },\n { id: \"attentive\", label: \"Attentive\", initial: \"A\", color: \"#facc15\" },\n { id: \"braze\", label: \"Braze\", initial: \"b\", color: \"#111827\" },\n { id: \"brevo\", label: \"Brevo\", initial: \"B\", color: \"#10b981\" },\n {\n id: \"campaign_monitor\",\n label: \"Campaign Monitor\",\n initial: \"M\",\n color: \"#0f172a\",\n },\n {\n id: \"constant_contact\",\n label: \"Constant Contact\",\n initial: \"≈\",\n color: \"#ea580c\",\n },\n { id: \"customer_io\", label: \"Customer.io\", initial: \"•\", color: \"#a855f7\" },\n { id: \"dotdigital\", label: \"Dotdigital\", initial: \"◎\", color: \"#dc2626\" },\n { id: \"drip\", label: \"Drip\", initial: \"⋯\", color: \"#0f172a\" },\n { id: \"emarsys\", label: \"Emarsys\", initial: \"≣\", color: \"#16a34a\" },\n { id: \"klaviyo\", label: \"Klaviyo\", initial: \"K\", color: \"#1e1b4b\" },\n { id: \"mailchimp\", label: \"Mailchimp\", initial: \"M\", color: \"#38bdf8\" },\n { id: \"hubspot\", label: \"HubSpot\", initial: \"H\", color: \"#f97316\" },\n];\n\nfunction selectIntegration(id: FormSyncIntegrationId): void {\n if (props.block.syncIntegrationId === id) {\n updateField(\"syncIntegrationId\", null);\n } else {\n updateField(\"syncIntegrationId\", id);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Fields tab — list editor + add menu.\n// ---------------------------------------------------------------------------\n\nconst FIELD_TYPE_META: Record<\n FormFieldType,\n { label: string; icon: typeof Mail }\n> = {\n firstName: { label: t.form.fieldTypes.firstName, icon: User },\n lastName: { label: t.form.fieldTypes.lastName, icon: User },\n email: { label: t.form.fieldTypes.email, icon: Mail },\n phone: { label: t.form.fieldTypes.phone, icon: Phone },\n};\n\nconst ALL_FIELD_TYPES: FormFieldType[] = [\n \"firstName\",\n \"lastName\",\n \"email\",\n \"phone\",\n];\n\nconst expandedFieldId = ref<string | null>(null);\nconst showAddFieldMenu = ref(false);\n\nconst availableFieldTypes = computed(() => {\n // First name, last name, email and phone can each be added once for now.\n const used = new Set(props.block.fields.map((f) => f.type));\n return ALL_FIELD_TYPES.filter((type) => !used.has(type));\n});\n\nfunction toggleField(id: string): void {\n expandedFieldId.value = expandedFieldId.value === id ? null : id;\n}\n\nfunction addField(type: FormFieldType): void {\n const newField = createFormField(type);\n emit(\"update\", { fields: [...props.block.fields, newField] });\n expandedFieldId.value = newField.id;\n showAddFieldMenu.value = false;\n}\n\nfunction deleteField(id: string): void {\n emit(\"update\", {\n fields: props.block.fields.filter((f) => f.id !== id),\n });\n}\n\nfunction patchField(id: string, patch: Partial<FormFieldData>): void {\n emit(\"update\", {\n fields: props.block.fields.map((f) =>\n f.id === id ? ({ ...f, ...patch } as FormFieldData) : f,\n ),\n });\n}\n\nfunction fieldIcon(type: FormFieldType) {\n return FIELD_TYPE_META[type].icon;\n}\n\n// ---------------------------------------------------------------------------\n// Button tab.\n// ---------------------------------------------------------------------------\n\nconst buttonActionOptions: { value: FormButtonAction; label: string }[] = [\n { value: \"next_step\", label: t.form.buttonActions.nextStep },\n { value: \"close_popup\", label: t.form.buttonActions.closePopup },\n { value: \"load_page\", label: t.form.buttonActions.loadPage },\n { value: \"none\", label: t.form.buttonActions.none },\n];\n\nfunction setButtonLayout(layout: FormButtonLayout): void {\n updateField(\"buttonLayout\", layout);\n}\n</script>\n\n<template>\n <div>\n <!-- Tabs strip -->\n <div\n role=\"tablist\"\n class=\"tpl:mb-4 tpl:flex tpl:gap-1 tpl:rounded-[var(--tpl-radius-sm)] tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-active)] tpl:p-1\"\n >\n <button\n v-for=\"tab in tabs\"\n :key=\"tab.id\"\n role=\"tab\"\n :aria-selected=\"activeTab === tab.id\"\n :class=\"tabClass(tab.id)\"\n @click=\"activeTab = tab.id\"\n >\n {{ tab.label }}\n </button>\n </div>\n\n <!-- Sync tab -->\n <div v-if=\"activeTab === 'sync'\">\n <p\n class=\"tpl:mb-3 tpl:text-[10px] tpl:font-semibold tpl:tracking-wider tpl:uppercase tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ t.form.sync.heading }}\n </p>\n <div class=\"tpl:grid tpl:grid-cols-3 tpl:gap-2\">\n <button\n v-for=\"item in integrations\"\n :key=\"item.id\"\n type=\"button\"\n class=\"tpl:relative tpl:flex tpl:cursor-pointer tpl:flex-col tpl:items-center tpl:justify-center tpl:gap-1.5 tpl:rounded-[var(--tpl-radius-sm)] tpl:border tpl:border-transparent tpl:bg-[var(--tpl-bg-active)] tpl:px-2 tpl:py-3 tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-text)] tpl:transition-all tpl:duration-150 hover:tpl:border-[var(--tpl-border)] hover:tpl:bg-[var(--tpl-bg-hover)]\"\n :class=\"\n block.syncIntegrationId === item.id\n ? 'tpl:!border-[var(--tpl-primary)] tpl:!bg-[var(--tpl-primary-light)]'\n : ''\n \"\n @click=\"selectIntegration(item.id)\"\n >\n <span\n class=\"tpl:flex tpl:size-9 tpl:items-center tpl:justify-center tpl:rounded-md tpl:text-base tpl:font-bold tpl:text-white\"\n :style=\"{ backgroundColor: item.color }\"\n >\n {{ item.initial }}\n </span>\n <span class=\"tpl:text-center tpl:leading-tight\">{{\n item.label\n }}</span>\n <Check\n v-if=\"block.syncIntegrationId === item.id\"\n class=\"tpl:absolute tpl:right-1 tpl:top-1 tpl:text-[var(--tpl-primary)]\"\n :size=\"14\"\n :stroke-width=\"2.5\"\n />\n </button>\n </div>\n </div>\n\n <!-- Fields tab -->\n <div v-else-if=\"activeTab === 'fields'\">\n <div class=\"tpl:flex tpl:flex-col tpl:gap-2\">\n <div\n v-for=\"field in block.fields\"\n :key=\"field.id\"\n class=\"tpl:overflow-hidden tpl:rounded-[var(--tpl-radius-sm)] tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-active)]\"\n >\n <button\n type=\"button\"\n class=\"tpl:flex tpl:w-full tpl:cursor-pointer tpl:items-center tpl:justify-between tpl:gap-2 tpl:border-none tpl:bg-transparent tpl:px-3 tpl:py-2.5 tpl:text-left\"\n @click=\"toggleField(field.id)\"\n >\n <span\n class=\"tpl:flex tpl:items-center tpl:gap-2 tpl:text-sm tpl:font-medium tpl:text-[var(--tpl-text)]\"\n >\n <component\n :is=\"fieldIcon(field.type)\"\n :size=\"14\"\n :stroke-width=\"1.75\"\n />\n {{ FIELD_TYPE_META[field.type].label }}\n </span>\n <span class=\"tpl:flex tpl:items-center tpl:gap-1\">\n <button\n type=\"button\"\n class=\"tpl:flex tpl:size-7 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-md tpl:border-none tpl:bg-transparent tpl:text-[var(--tpl-text-muted)] hover:tpl:bg-[var(--tpl-bg-hover)] hover:tpl:text-[var(--tpl-danger)]\"\n :title=\"t.form.fields.remove\"\n @click.stop=\"deleteField(field.id)\"\n >\n <Trash2 :size=\"14\" :stroke-width=\"1.75\" />\n </button>\n <ChevronDown\n class=\"tpl:transition-transform tpl:duration-200\"\n :class=\"\n expandedFieldId === field.id\n ? 'tpl:rotate-0'\n : 'tpl:-rotate-90'\n \"\n :size=\"14\"\n :stroke-width=\"2\"\n />\n </span>\n </button>\n\n <div\n v-if=\"expandedFieldId === field.id\"\n class=\"tpl:border-t tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:px-3 tpl:pt-3 tpl:pb-3\"\n >\n <div class=\"tpl:mb-3\">\n <label :class=\"labelClass\">\n {{ t.form.fields.label }}\n <span\n class=\"tpl:ml-1 tpl:text-[var(--tpl-text-dim)] tpl:font-normal\"\n >({{ t.form.fields.optional }})</span\n >\n </label>\n <input\n type=\"text\"\n :class=\"inputClass\"\n :value=\"field.label\"\n :placeholder=\"t.form.fields.labelPlaceholder\"\n @input=\"\n patchField(field.id, {\n label: ($event.target as HTMLInputElement).value,\n })\n \"\n />\n </div>\n\n <div class=\"tpl:mb-3\">\n <label :class=\"labelClass\">{{ t.form.fields.placeholder }}</label>\n <input\n type=\"text\"\n :class=\"inputClass\"\n :value=\"field.placeholder\"\n @input=\"\n patchField(field.id, {\n placeholder: ($event.target as HTMLInputElement).value,\n })\n \"\n />\n </div>\n\n <div class=\"tpl:mb-3\">\n <label :class=\"labelClass\">{{ t.form.fields.name }}</label>\n <input\n type=\"text\"\n :class=\"inputClass\"\n :value=\"field.name\"\n spellcheck=\"false\"\n autocomplete=\"off\"\n @input=\"\n patchField(field.id, {\n name: ($event.target as HTMLInputElement).value.trim(),\n })\n \"\n />\n </div>\n\n <!-- Email-only: block existing emails -->\n <template v-if=\"field.type === 'email'\">\n <label\n class=\"tpl:mb-2 tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:text-sm tpl:text-[var(--tpl-text)]\"\n >\n <input\n type=\"checkbox\"\n class=\"tpl:size-3.5 tpl:cursor-pointer tpl:accent-[var(--tpl-primary)]\"\n :checked=\"field.blockExisting ?? false\"\n @change=\"\n patchField(field.id, {\n blockExisting: ($event.target as HTMLInputElement)\n .checked,\n })\n \"\n />\n {{ t.form.fields.blockExisting }}\n </label>\n <p\n class=\"tpl:mb-3 tpl:text-xs tpl:text-[var(--tpl-text-muted)] tpl:leading-snug\"\n >\n {{ t.form.fields.blockExistingHint }}\n </p>\n\n <template v-if=\"field.blockExisting\">\n <label\n class=\"tpl:mb-1.5 tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:text-sm tpl:text-[var(--tpl-text)]\"\n >\n <input\n type=\"radio\"\n class=\"tpl:size-3.5 tpl:cursor-pointer tpl:accent-[var(--tpl-primary)]\"\n :checked=\"\n (field.blockExistingScope ?? 'all_campaigns') ===\n 'all_campaigns'\n \"\n @change=\"\n patchField(field.id, {\n blockExistingScope: 'all_campaigns',\n })\n \"\n />\n {{ t.form.fields.blockExistingAll }}\n </label>\n <label\n class=\"tpl:mb-3 tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:text-sm tpl:text-[var(--tpl-text)]\"\n >\n <input\n type=\"radio\"\n class=\"tpl:size-3.5 tpl:cursor-pointer tpl:accent-[var(--tpl-primary)]\"\n :checked=\"field.blockExistingScope === 'this_campaign'\"\n @change=\"\n patchField(field.id, {\n blockExistingScope: 'this_campaign',\n })\n \"\n />\n {{ t.form.fields.blockExistingThis }}\n </label>\n\n <div class=\"tpl:mb-3\">\n <label :class=\"labelClass\">{{\n t.form.fields.errorMessage\n }}</label>\n <input\n type=\"text\"\n :class=\"inputClass\"\n :value=\"field.blockExistingMessage ?? ''\"\n @input=\"\n patchField(field.id, {\n blockExistingMessage: (\n $event.target as HTMLInputElement\n ).value,\n })\n \"\n />\n </div>\n </template>\n </template>\n\n <label\n class=\"tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:text-sm tpl:text-[var(--tpl-text)]\"\n :class=\"\n field.type === 'email' && field.blockExisting\n ? 'tpl:opacity-60'\n : ''\n \"\n >\n <input\n type=\"checkbox\"\n class=\"tpl:size-3.5 tpl:cursor-pointer tpl:accent-[var(--tpl-primary)]\"\n :checked=\"field.required\"\n :disabled=\"field.type === 'email' && field.blockExisting\"\n @change=\"\n patchField(field.id, {\n required: ($event.target as HTMLInputElement).checked,\n })\n \"\n />\n {{ t.form.fields.required }}\n </label>\n </div>\n </div>\n </div>\n\n <!-- Add field -->\n <div class=\"tpl:relative tpl:mt-3\">\n <button\n v-if=\"!showAddFieldMenu\"\n type=\"button\"\n :class=\"addItemBtnClass\"\n :disabled=\"availableFieldTypes.length === 0\"\n @click=\"showAddFieldMenu = true\"\n >\n <Plus :size=\"14\" :stroke-width=\"2\" />\n {{ t.form.fields.addField }}\n </button>\n\n <div\n v-else\n class=\"tpl:rounded-[var(--tpl-radius-sm)] tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-elevated)] tpl:p-1 tpl:shadow-[var(--tpl-shadow-md)]\"\n >\n <button\n v-for=\"type in availableFieldTypes\"\n :key=\"type\"\n type=\"button\"\n class=\"tpl:flex tpl:w-full tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:rounded-[var(--tpl-radius-sm)] tpl:border-none tpl:bg-transparent tpl:px-2.5 tpl:py-2 tpl:text-left tpl:text-sm tpl:text-[var(--tpl-text)] hover:tpl:bg-[var(--tpl-bg-hover)]\"\n @click=\"addField(type)\"\n >\n <component\n :is=\"FIELD_TYPE_META[type].icon\"\n :size=\"14\"\n :stroke-width=\"1.75\"\n />\n {{ FIELD_TYPE_META[type].label }}\n </button>\n <button\n v-if=\"availableFieldTypes.length === 0\"\n type=\"button\"\n class=\"tpl:px-2.5 tpl:py-2 tpl:text-xs tpl:text-[var(--tpl-text-muted)]\"\n disabled\n >\n {{ t.form.fields.noMoreFields }}\n </button>\n <button\n type=\"button\"\n class=\"tpl:mt-1 tpl:flex tpl:w-full tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-[var(--tpl-radius-sm)] tpl:border-none tpl:bg-transparent tpl:px-2.5 tpl:py-1.5 tpl:text-xs tpl:text-[var(--tpl-text-muted)] hover:tpl:text-[var(--tpl-text)]\"\n @click=\"showAddFieldMenu = false\"\n >\n {{ t.form.fields.cancel }}\n </button>\n </div>\n </div>\n </div>\n\n <!-- Button tab -->\n <div v-else-if=\"activeTab === 'button'\">\n <div class=\"tpl:mb-4\">\n <label :class=\"labelClass\">{{ t.form.button.submitText }}</label>\n <input\n type=\"text\"\n :class=\"inputClass\"\n :value=\"block.submitButtonText\"\n @input=\"\n updateField(\n 'submitButtonText',\n ($event.target as HTMLInputElement).value,\n )\n \"\n />\n </div>\n\n <div class=\"tpl:mb-4\">\n <label :class=\"labelClass\">{{ t.form.button.actionOnClick }}</label>\n <select\n :class=\"inputClass\"\n :value=\"block.buttonAction\"\n @change=\"\n updateField(\n 'buttonAction',\n ($event.target as HTMLSelectElement).value as FormButtonAction,\n )\n \"\n >\n <option\n v-for=\"opt in buttonActionOptions\"\n :key=\"opt.value\"\n :value=\"opt.value\"\n >\n {{ opt.label }}\n </option>\n </select>\n </div>\n\n <div v-if=\"block.buttonAction === 'load_page'\" class=\"tpl:mb-4\">\n <label :class=\"labelClass\">{{ t.form.button.url }}</label>\n <input\n type=\"text\"\n :class=\"inputClass\"\n placeholder=\"https://...\"\n :value=\"block.buttonUrl ?? ''\"\n @input=\"\n updateField('buttonUrl', ($event.target as HTMLInputElement).value)\n \"\n />\n </div>\n\n <div class=\"tpl:mb-4\">\n <label\n class=\"tpl:mb-2 tpl:block tpl:text-[10px] tpl:font-semibold tpl:tracking-wider tpl:uppercase tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ t.form.button.position }}\n </label>\n <div class=\"tpl:grid tpl:grid-cols-2 tpl:gap-2\">\n <button\n type=\"button\"\n class=\"tpl:flex tpl:cursor-pointer tpl:flex-col tpl:items-center tpl:gap-2 tpl:rounded-[var(--tpl-radius-sm)] tpl:border tpl:px-3 tpl:py-3 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n :class=\"\n block.buttonLayout === 'one_line'\n ? 'tpl:border-[var(--tpl-primary)] tpl:bg-[var(--tpl-primary-light)] tpl:text-[var(--tpl-text)]'\n : 'tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:text-[var(--tpl-text-muted)] hover:tpl:border-[var(--tpl-text-muted)]'\n \"\n @click=\"setButtonLayout('one_line')\"\n >\n <svg\n width=\"56\"\n height=\"20\"\n viewBox=\"0 0 56 20\"\n fill=\"none\"\n aria-hidden=\"true\"\n >\n <rect\n x=\"2\"\n y=\"6\"\n width=\"34\"\n height=\"8\"\n rx=\"2\"\n fill=\"currentColor\"\n opacity=\"0.4\"\n />\n <rect\n x=\"38\"\n y=\"4\"\n width=\"16\"\n height=\"12\"\n rx=\"2\"\n fill=\"currentColor\"\n opacity=\"0.9\"\n />\n </svg>\n {{ t.form.button.oneLine }}\n </button>\n <button\n type=\"button\"\n class=\"tpl:flex tpl:cursor-pointer tpl:flex-col tpl:items-center tpl:gap-2 tpl:rounded-[var(--tpl-radius-sm)] tpl:border tpl:px-3 tpl:py-3 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n :class=\"\n block.buttonLayout === 'two_lines'\n ? 'tpl:border-[var(--tpl-primary)] tpl:bg-[var(--tpl-primary-light)] tpl:text-[var(--tpl-text)]'\n : 'tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:text-[var(--tpl-text-muted)] hover:tpl:border-[var(--tpl-text-muted)]'\n \"\n @click=\"setButtonLayout('two_lines')\"\n >\n <svg\n width=\"56\"\n height=\"28\"\n viewBox=\"0 0 56 28\"\n fill=\"none\"\n aria-hidden=\"true\"\n >\n <rect\n x=\"2\"\n y=\"2\"\n width=\"52\"\n height=\"8\"\n rx=\"2\"\n fill=\"currentColor\"\n opacity=\"0.4\"\n />\n <rect\n x=\"2\"\n y=\"14\"\n width=\"52\"\n height=\"10\"\n rx=\"2\"\n fill=\"currentColor\"\n opacity=\"0.9\"\n />\n </svg>\n {{ t.form.button.twoLines }}\n </button>\n </div>\n </div>\n\n <div class=\"tpl:mb-3\">\n <label :class=\"labelClass\">{{ t.form.button.background }}</label>\n <ColorPicker\n :model-value=\"block.buttonBackgroundColor\"\n @update:model-value=\"updateField('buttonBackgroundColor', $event)\"\n />\n </div>\n <div class=\"tpl:mb-3\">\n <label :class=\"labelClass\">{{ t.form.button.textColor }}</label>\n <ColorPicker\n :model-value=\"block.buttonTextColor\"\n @update:model-value=\"updateField('buttonTextColor', $event)\"\n />\n </div>\n <div class=\"tpl:mb-3\">\n <label :class=\"labelClass\">{{ t.form.button.borderRadius }}</label>\n <input\n type=\"number\"\n :class=\"inputClass\"\n min=\"0\"\n max=\"40\"\n :value=\"block.buttonBorderRadius\"\n @input=\"\n updateField(\n 'buttonBorderRadius',\n Number(($event.target as HTMLInputElement).value) || 0,\n )\n \"\n />\n </div>\n </div>\n\n <!-- Terms tab -->\n <div v-else-if=\"activeTab === 'terms'\">\n <label\n class=\"tpl:mb-3 tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:text-sm tpl:text-[var(--tpl-text)]\"\n >\n <input\n type=\"checkbox\"\n class=\"tpl:size-3.5 tpl:cursor-pointer tpl:accent-[var(--tpl-primary)]\"\n :checked=\"block.termsEnabled\"\n @change=\"\n updateField(\n 'termsEnabled',\n ($event.target as HTMLInputElement).checked,\n )\n \"\n />\n {{ t.form.terms.enable }}\n </label>\n\n <div v-if=\"block.termsEnabled\">\n <label :class=\"labelClass\">{{ t.form.terms.content }}</label>\n <textarea\n rows=\"6\"\n class=\"tpl:w-full tpl:resize-y tpl:rounded-[var(--tpl-radius-sm)] tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:px-3 tpl:py-2 tpl:text-sm tpl:text-[var(--tpl-text)] tpl:outline-none focus:tpl:border-[var(--tpl-primary)]\"\n :value=\"block.termsContent\"\n @input=\"\n updateField(\n 'termsContent',\n ($event.target as HTMLTextAreaElement).value,\n )\n \"\n ></textarea>\n <p class=\"tpl:mt-1 tpl:text-xs tpl:text-[var(--tpl-text-muted)]\">\n {{ t.form.terms.htmlHint }}\n </p>\n </div>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, ref } from \"vue\";\nimport {\n Check,\n ChevronDown,\n Mail,\n Phone,\n Plus,\n Trash2,\n User,\n} from \"@lucide/vue\";\nimport type {\n FormBlock,\n FormButtonAction,\n FormButtonLayout,\n FormFieldData,\n FormFieldType,\n FormSyncIntegrationId,\n} from \"@aswin.dev/types\";\nimport { createFormField } from \"@aswin.dev/types\";\nimport {\n addItemBtnClass,\n inputClass,\n labelClass,\n} from \"../../constants/styleConstants\";\nimport { useI18n } from \"../../composables/useI18n\";\nimport ColorPicker from \"../ColorPicker.vue\";\n\nconst props = defineProps<{\n block: FormBlock;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update\", updates: Partial<FormBlock>): void;\n}>();\n\nconst { t } = useI18n();\n\ntype Tab = \"sync\" | \"fields\" | \"button\" | \"terms\";\nconst activeTab = ref<Tab>(\"fields\");\n\nconst tabs: { id: Tab; label: string }[] = [\n { id: \"sync\", label: t.form.tabs.sync },\n { id: \"fields\", label: t.form.tabs.fields },\n { id: \"button\", label: t.form.tabs.button },\n { id: \"terms\", label: t.form.tabs.terms },\n];\n\nfunction tabClass(tab: Tab): string {\n const base =\n \"tpl:flex-1 tpl:cursor-pointer tpl:rounded-[var(--tpl-radius-sm)] tpl:border-none tpl:px-3 tpl:py-2 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-[120ms]\";\n if (activeTab.value === tab) {\n return `${base} tpl:text-[var(--tpl-text)] tpl:bg-[var(--tpl-bg)] tpl:shadow-[var(--tpl-shadow-md)]`;\n }\n return `${base} tpl:text-[var(--tpl-text-muted)] tpl:bg-transparent hover:tpl:text-[var(--tpl-text)]`;\n}\n\nfunction updateField<K extends keyof FormBlock>(\n key: K,\n value: FormBlock[K],\n): void {\n emit(\"update\", { [key]: value } as Partial<FormBlock>);\n}\n\n// ---------------------------------------------------------------------------\n// Sync tab — list of marketing platforms (curated, static for now).\n// ---------------------------------------------------------------------------\n\ninterface IntegrationItem {\n id: FormSyncIntegrationId;\n label: string;\n initial: string;\n color: string;\n}\n\nconst integrations: IntegrationItem[] = [\n { id: \"acoustic\", label: \"Acoustic\", initial: \"A\", color: \"#5b21b6\" },\n { id: \"actito\", label: \"Actito\", initial: \"a\", color: \"#16a34a\" },\n {\n id: \"active_campaign\",\n label: \"Active Campaign\",\n initial: \">\",\n color: \"#1e40af\",\n },\n { id: \"attentive\", label: \"Attentive\", initial: \"A\", color: \"#facc15\" },\n { id: \"braze\", label: \"Braze\", initial: \"b\", color: \"#111827\" },\n { id: \"brevo\", label: \"Brevo\", initial: \"B\", color: \"#10b981\" },\n {\n id: \"campaign_monitor\",\n label: \"Campaign Monitor\",\n initial: \"M\",\n color: \"#0f172a\",\n },\n {\n id: \"constant_contact\",\n label: \"Constant Contact\",\n initial: \"≈\",\n color: \"#ea580c\",\n },\n { id: \"customer_io\", label: \"Customer.io\", initial: \"•\", color: \"#a855f7\" },\n { id: \"dotdigital\", label: \"Dotdigital\", initial: \"◎\", color: \"#dc2626\" },\n { id: \"drip\", label: \"Drip\", initial: \"⋯\", color: \"#0f172a\" },\n { id: \"emarsys\", label: \"Emarsys\", initial: \"≣\", color: \"#16a34a\" },\n { id: \"klaviyo\", label: \"Klaviyo\", initial: \"K\", color: \"#1e1b4b\" },\n { id: \"mailchimp\", label: \"Mailchimp\", initial: \"M\", color: \"#38bdf8\" },\n { id: \"hubspot\", label: \"HubSpot\", initial: \"H\", color: \"#f97316\" },\n];\n\nfunction selectIntegration(id: FormSyncIntegrationId): void {\n if (props.block.syncIntegrationId === id) {\n updateField(\"syncIntegrationId\", null);\n } else {\n updateField(\"syncIntegrationId\", id);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Fields tab — list editor + add menu.\n// ---------------------------------------------------------------------------\n\nconst FIELD_TYPE_META: Record<\n FormFieldType,\n { label: string; icon: typeof Mail }\n> = {\n firstName: { label: t.form.fieldTypes.firstName, icon: User },\n lastName: { label: t.form.fieldTypes.lastName, icon: User },\n email: { label: t.form.fieldTypes.email, icon: Mail },\n phone: { label: t.form.fieldTypes.phone, icon: Phone },\n};\n\nconst ALL_FIELD_TYPES: FormFieldType[] = [\n \"firstName\",\n \"lastName\",\n \"email\",\n \"phone\",\n];\n\nconst expandedFieldId = ref<string | null>(null);\nconst showAddFieldMenu = ref(false);\n\nconst availableFieldTypes = computed(() => {\n // First name, last name, email and phone can each be added once for now.\n const used = new Set(props.block.fields.map((f) => f.type));\n return ALL_FIELD_TYPES.filter((type) => !used.has(type));\n});\n\nfunction toggleField(id: string): void {\n expandedFieldId.value = expandedFieldId.value === id ? null : id;\n}\n\nfunction addField(type: FormFieldType): void {\n const newField = createFormField(type);\n emit(\"update\", { fields: [...props.block.fields, newField] });\n expandedFieldId.value = newField.id;\n showAddFieldMenu.value = false;\n}\n\nfunction deleteField(id: string): void {\n emit(\"update\", {\n fields: props.block.fields.filter((f) => f.id !== id),\n });\n}\n\nfunction patchField(id: string, patch: Partial<FormFieldData>): void {\n emit(\"update\", {\n fields: props.block.fields.map((f) =>\n f.id === id ? ({ ...f, ...patch } as FormFieldData) : f,\n ),\n });\n}\n\nfunction fieldIcon(type: FormFieldType) {\n return FIELD_TYPE_META[type].icon;\n}\n\n// ---------------------------------------------------------------------------\n// Button tab.\n// ---------------------------------------------------------------------------\n\nconst buttonActionOptions: { value: FormButtonAction; label: string }[] = [\n { value: \"next_step\", label: t.form.buttonActions.nextStep },\n { value: \"close_popup\", label: t.form.buttonActions.closePopup },\n { value: \"load_page\", label: t.form.buttonActions.loadPage },\n { value: \"none\", label: t.form.buttonActions.none },\n];\n\nfunction setButtonLayout(layout: FormButtonLayout): void {\n updateField(\"buttonLayout\", layout);\n}\n</script>\n\n<template>\n <div>\n <!-- Tabs strip -->\n <div\n role=\"tablist\"\n class=\"tpl:mb-4 tpl:flex tpl:gap-1 tpl:rounded-[var(--tpl-radius-sm)] tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-active)] tpl:p-1\"\n >\n <button\n v-for=\"tab in tabs\"\n :key=\"tab.id\"\n role=\"tab\"\n :aria-selected=\"activeTab === tab.id\"\n :class=\"tabClass(tab.id)\"\n @click=\"activeTab = tab.id\"\n >\n {{ tab.label }}\n </button>\n </div>\n\n <!-- Sync tab -->\n <div v-if=\"activeTab === 'sync'\">\n <p\n class=\"tpl:mb-3 tpl:text-[10px] tpl:font-semibold tpl:tracking-wider tpl:uppercase tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ t.form.sync.heading }}\n </p>\n <div class=\"tpl:grid tpl:grid-cols-3 tpl:gap-2\">\n <button\n v-for=\"item in integrations\"\n :key=\"item.id\"\n type=\"button\"\n class=\"tpl:relative tpl:flex tpl:cursor-pointer tpl:flex-col tpl:items-center tpl:justify-center tpl:gap-1.5 tpl:rounded-[var(--tpl-radius-sm)] tpl:border tpl:border-transparent tpl:bg-[var(--tpl-bg-active)] tpl:px-2 tpl:py-3 tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-text)] tpl:transition-all tpl:duration-150 hover:tpl:border-[var(--tpl-border)] hover:tpl:bg-[var(--tpl-bg-hover)]\"\n :class=\"\n block.syncIntegrationId === item.id\n ? 'tpl:!border-[var(--tpl-primary)] tpl:!bg-[var(--tpl-primary-light)]'\n : ''\n \"\n @click=\"selectIntegration(item.id)\"\n >\n <span\n class=\"tpl:flex tpl:size-9 tpl:items-center tpl:justify-center tpl:rounded-md tpl:text-base tpl:font-bold tpl:text-white\"\n :style=\"{ backgroundColor: item.color }\"\n >\n {{ item.initial }}\n </span>\n <span class=\"tpl:text-center tpl:leading-tight\">{{\n item.label\n }}</span>\n <Check\n v-if=\"block.syncIntegrationId === item.id\"\n class=\"tpl:absolute tpl:right-1 tpl:top-1 tpl:text-[var(--tpl-primary)]\"\n :size=\"14\"\n :stroke-width=\"2.5\"\n />\n </button>\n </div>\n </div>\n\n <!-- Fields tab -->\n <div v-else-if=\"activeTab === 'fields'\">\n <div class=\"tpl:flex tpl:flex-col tpl:gap-2\">\n <div\n v-for=\"field in block.fields\"\n :key=\"field.id\"\n class=\"tpl:overflow-hidden tpl:rounded-[var(--tpl-radius-sm)] tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-active)]\"\n >\n <button\n type=\"button\"\n class=\"tpl:flex tpl:w-full tpl:cursor-pointer tpl:items-center tpl:justify-between tpl:gap-2 tpl:border-none tpl:bg-transparent tpl:px-3 tpl:py-2.5 tpl:text-left\"\n @click=\"toggleField(field.id)\"\n >\n <span\n class=\"tpl:flex tpl:items-center tpl:gap-2 tpl:text-sm tpl:font-medium tpl:text-[var(--tpl-text)]\"\n >\n <component\n :is=\"fieldIcon(field.type)\"\n :size=\"14\"\n :stroke-width=\"1.75\"\n />\n {{ FIELD_TYPE_META[field.type].label }}\n </span>\n <span class=\"tpl:flex tpl:items-center tpl:gap-1\">\n <button\n type=\"button\"\n class=\"tpl:flex tpl:size-7 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-md tpl:border-none tpl:bg-transparent tpl:text-[var(--tpl-text-muted)] hover:tpl:bg-[var(--tpl-bg-hover)] hover:tpl:text-[var(--tpl-danger)]\"\n :title=\"t.form.fields.remove\"\n @click.stop=\"deleteField(field.id)\"\n >\n <Trash2 :size=\"14\" :stroke-width=\"1.75\" />\n </button>\n <ChevronDown\n class=\"tpl:transition-transform tpl:duration-200\"\n :class=\"\n expandedFieldId === field.id\n ? 'tpl:rotate-0'\n : 'tpl:-rotate-90'\n \"\n :size=\"14\"\n :stroke-width=\"2\"\n />\n </span>\n </button>\n\n <div\n v-if=\"expandedFieldId === field.id\"\n class=\"tpl:border-t tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:px-3 tpl:pt-3 tpl:pb-3\"\n >\n <div class=\"tpl:mb-3\">\n <label :class=\"labelClass\">\n {{ t.form.fields.label }}\n <span\n class=\"tpl:ml-1 tpl:text-[var(--tpl-text-dim)] tpl:font-normal\"\n >({{ t.form.fields.optional }})</span\n >\n </label>\n <input\n type=\"text\"\n :class=\"inputClass\"\n :value=\"field.label\"\n :placeholder=\"t.form.fields.labelPlaceholder\"\n @input=\"\n patchField(field.id, {\n label: ($event.target as HTMLInputElement).value,\n })\n \"\n />\n </div>\n\n <div class=\"tpl:mb-3\">\n <label :class=\"labelClass\">{{ t.form.fields.placeholder }}</label>\n <input\n type=\"text\"\n :class=\"inputClass\"\n :value=\"field.placeholder\"\n @input=\"\n patchField(field.id, {\n placeholder: ($event.target as HTMLInputElement).value,\n })\n \"\n />\n </div>\n\n <div class=\"tpl:mb-3\">\n <label :class=\"labelClass\">{{ t.form.fields.name }}</label>\n <input\n type=\"text\"\n :class=\"inputClass\"\n :value=\"field.name\"\n spellcheck=\"false\"\n autocomplete=\"off\"\n @input=\"\n patchField(field.id, {\n name: ($event.target as HTMLInputElement).value.trim(),\n })\n \"\n />\n </div>\n\n <!-- Email-only: block existing emails -->\n <template v-if=\"field.type === 'email'\">\n <label\n class=\"tpl:mb-2 tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:text-sm tpl:text-[var(--tpl-text)]\"\n >\n <input\n type=\"checkbox\"\n class=\"tpl:size-3.5 tpl:cursor-pointer tpl:accent-[var(--tpl-primary)]\"\n :checked=\"field.blockExisting ?? false\"\n @change=\"\n patchField(field.id, {\n blockExisting: ($event.target as HTMLInputElement)\n .checked,\n })\n \"\n />\n {{ t.form.fields.blockExisting }}\n </label>\n <p\n class=\"tpl:mb-3 tpl:text-xs tpl:text-[var(--tpl-text-muted)] tpl:leading-snug\"\n >\n {{ t.form.fields.blockExistingHint }}\n </p>\n\n <template v-if=\"field.blockExisting\">\n <label\n class=\"tpl:mb-1.5 tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:text-sm tpl:text-[var(--tpl-text)]\"\n >\n <input\n type=\"radio\"\n class=\"tpl:size-3.5 tpl:cursor-pointer tpl:accent-[var(--tpl-primary)]\"\n :checked=\"\n (field.blockExistingScope ?? 'all_campaigns') ===\n 'all_campaigns'\n \"\n @change=\"\n patchField(field.id, {\n blockExistingScope: 'all_campaigns',\n })\n \"\n />\n {{ t.form.fields.blockExistingAll }}\n </label>\n <label\n class=\"tpl:mb-3 tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:text-sm tpl:text-[var(--tpl-text)]\"\n >\n <input\n type=\"radio\"\n class=\"tpl:size-3.5 tpl:cursor-pointer tpl:accent-[var(--tpl-primary)]\"\n :checked=\"field.blockExistingScope === 'this_campaign'\"\n @change=\"\n patchField(field.id, {\n blockExistingScope: 'this_campaign',\n })\n \"\n />\n {{ t.form.fields.blockExistingThis }}\n </label>\n\n <div class=\"tpl:mb-3\">\n <label :class=\"labelClass\">{{\n t.form.fields.errorMessage\n }}</label>\n <input\n type=\"text\"\n :class=\"inputClass\"\n :value=\"field.blockExistingMessage ?? ''\"\n @input=\"\n patchField(field.id, {\n blockExistingMessage: (\n $event.target as HTMLInputElement\n ).value,\n })\n \"\n />\n </div>\n </template>\n </template>\n\n <label\n class=\"tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:text-sm tpl:text-[var(--tpl-text)]\"\n :class=\"\n field.type === 'email' && field.blockExisting\n ? 'tpl:opacity-60'\n : ''\n \"\n >\n <input\n type=\"checkbox\"\n class=\"tpl:size-3.5 tpl:cursor-pointer tpl:accent-[var(--tpl-primary)]\"\n :checked=\"field.required\"\n :disabled=\"field.type === 'email' && field.blockExisting\"\n @change=\"\n patchField(field.id, {\n required: ($event.target as HTMLInputElement).checked,\n })\n \"\n />\n {{ t.form.fields.required }}\n </label>\n </div>\n </div>\n </div>\n\n <!-- Add field -->\n <div class=\"tpl:relative tpl:mt-3\">\n <button\n v-if=\"!showAddFieldMenu\"\n type=\"button\"\n :class=\"addItemBtnClass\"\n :disabled=\"availableFieldTypes.length === 0\"\n @click=\"showAddFieldMenu = true\"\n >\n <Plus :size=\"14\" :stroke-width=\"2\" />\n {{ t.form.fields.addField }}\n </button>\n\n <div\n v-else\n class=\"tpl:rounded-[var(--tpl-radius-sm)] tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-elevated)] tpl:p-1 tpl:shadow-[var(--tpl-shadow-md)]\"\n >\n <button\n v-for=\"type in availableFieldTypes\"\n :key=\"type\"\n type=\"button\"\n class=\"tpl:flex tpl:w-full tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:rounded-[var(--tpl-radius-sm)] tpl:border-none tpl:bg-transparent tpl:px-2.5 tpl:py-2 tpl:text-left tpl:text-sm tpl:text-[var(--tpl-text)] hover:tpl:bg-[var(--tpl-bg-hover)]\"\n @click=\"addField(type)\"\n >\n <component\n :is=\"FIELD_TYPE_META[type].icon\"\n :size=\"14\"\n :stroke-width=\"1.75\"\n />\n {{ FIELD_TYPE_META[type].label }}\n </button>\n <button\n v-if=\"availableFieldTypes.length === 0\"\n type=\"button\"\n class=\"tpl:px-2.5 tpl:py-2 tpl:text-xs tpl:text-[var(--tpl-text-muted)]\"\n disabled\n >\n {{ t.form.fields.noMoreFields }}\n </button>\n <button\n type=\"button\"\n class=\"tpl:mt-1 tpl:flex tpl:w-full tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-[var(--tpl-radius-sm)] tpl:border-none tpl:bg-transparent tpl:px-2.5 tpl:py-1.5 tpl:text-xs tpl:text-[var(--tpl-text-muted)] hover:tpl:text-[var(--tpl-text)]\"\n @click=\"showAddFieldMenu = false\"\n >\n {{ t.form.fields.cancel }}\n </button>\n </div>\n </div>\n </div>\n\n <!-- Button tab -->\n <div v-else-if=\"activeTab === 'button'\">\n <div class=\"tpl:mb-4\">\n <label :class=\"labelClass\">{{ t.form.button.submitText }}</label>\n <input\n type=\"text\"\n :class=\"inputClass\"\n :value=\"block.submitButtonText\"\n @input=\"\n updateField(\n 'submitButtonText',\n ($event.target as HTMLInputElement).value,\n )\n \"\n />\n </div>\n\n <div class=\"tpl:mb-4\">\n <label :class=\"labelClass\">{{ t.form.button.actionOnClick }}</label>\n <select\n :class=\"inputClass\"\n :value=\"block.buttonAction\"\n @change=\"\n updateField(\n 'buttonAction',\n ($event.target as HTMLSelectElement).value as FormButtonAction,\n )\n \"\n >\n <option\n v-for=\"opt in buttonActionOptions\"\n :key=\"opt.value\"\n :value=\"opt.value\"\n >\n {{ opt.label }}\n </option>\n </select>\n </div>\n\n <div v-if=\"block.buttonAction === 'load_page'\" class=\"tpl:mb-4\">\n <label :class=\"labelClass\">{{ t.form.button.url }}</label>\n <input\n type=\"text\"\n :class=\"inputClass\"\n placeholder=\"https://...\"\n :value=\"block.buttonUrl ?? ''\"\n @input=\"\n updateField('buttonUrl', ($event.target as HTMLInputElement).value)\n \"\n />\n </div>\n\n <div class=\"tpl:mb-4\">\n <label\n class=\"tpl:mb-2 tpl:block tpl:text-[10px] tpl:font-semibold tpl:tracking-wider tpl:uppercase tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ t.form.button.position }}\n </label>\n <div class=\"tpl:grid tpl:grid-cols-2 tpl:gap-2\">\n <button\n type=\"button\"\n class=\"tpl:flex tpl:cursor-pointer tpl:flex-col tpl:items-center tpl:gap-2 tpl:rounded-[var(--tpl-radius-sm)] tpl:border tpl:px-3 tpl:py-3 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n :class=\"\n block.buttonLayout === 'one_line'\n ? 'tpl:border-[var(--tpl-primary)] tpl:bg-[var(--tpl-primary-light)] tpl:text-[var(--tpl-text)]'\n : 'tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:text-[var(--tpl-text-muted)] hover:tpl:border-[var(--tpl-text-muted)]'\n \"\n @click=\"setButtonLayout('one_line')\"\n >\n <svg\n width=\"56\"\n height=\"20\"\n viewBox=\"0 0 56 20\"\n fill=\"none\"\n aria-hidden=\"true\"\n >\n <rect\n x=\"2\"\n y=\"6\"\n width=\"34\"\n height=\"8\"\n rx=\"2\"\n fill=\"currentColor\"\n opacity=\"0.4\"\n />\n <rect\n x=\"38\"\n y=\"4\"\n width=\"16\"\n height=\"12\"\n rx=\"2\"\n fill=\"currentColor\"\n opacity=\"0.9\"\n />\n </svg>\n {{ t.form.button.oneLine }}\n </button>\n <button\n type=\"button\"\n class=\"tpl:flex tpl:cursor-pointer tpl:flex-col tpl:items-center tpl:gap-2 tpl:rounded-[var(--tpl-radius-sm)] tpl:border tpl:px-3 tpl:py-3 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n :class=\"\n block.buttonLayout === 'two_lines'\n ? 'tpl:border-[var(--tpl-primary)] tpl:bg-[var(--tpl-primary-light)] tpl:text-[var(--tpl-text)]'\n : 'tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:text-[var(--tpl-text-muted)] hover:tpl:border-[var(--tpl-text-muted)]'\n \"\n @click=\"setButtonLayout('two_lines')\"\n >\n <svg\n width=\"56\"\n height=\"28\"\n viewBox=\"0 0 56 28\"\n fill=\"none\"\n aria-hidden=\"true\"\n >\n <rect\n x=\"2\"\n y=\"2\"\n width=\"52\"\n height=\"8\"\n rx=\"2\"\n fill=\"currentColor\"\n opacity=\"0.4\"\n />\n <rect\n x=\"2\"\n y=\"14\"\n width=\"52\"\n height=\"10\"\n rx=\"2\"\n fill=\"currentColor\"\n opacity=\"0.9\"\n />\n </svg>\n {{ t.form.button.twoLines }}\n </button>\n </div>\n </div>\n\n <div class=\"tpl:mb-3\">\n <label :class=\"labelClass\">{{ t.form.button.background }}</label>\n <ColorPicker\n :model-value=\"block.buttonBackgroundColor\"\n @update:model-value=\"updateField('buttonBackgroundColor', $event)\"\n />\n </div>\n <div class=\"tpl:mb-3\">\n <label :class=\"labelClass\">{{ t.form.button.textColor }}</label>\n <ColorPicker\n :model-value=\"block.buttonTextColor\"\n @update:model-value=\"updateField('buttonTextColor', $event)\"\n />\n </div>\n <div class=\"tpl:mb-3\">\n <label :class=\"labelClass\">{{ t.form.button.borderRadius }}</label>\n <input\n type=\"number\"\n :class=\"inputClass\"\n min=\"0\"\n max=\"40\"\n :value=\"block.buttonBorderRadius\"\n @input=\"\n updateField(\n 'buttonBorderRadius',\n Number(($event.target as HTMLInputElement).value) || 0,\n )\n \"\n />\n </div>\n </div>\n\n <!-- Terms tab -->\n <div v-else-if=\"activeTab === 'terms'\">\n <label\n class=\"tpl:mb-3 tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:text-sm tpl:text-[var(--tpl-text)]\"\n >\n <input\n type=\"checkbox\"\n class=\"tpl:size-3.5 tpl:cursor-pointer tpl:accent-[var(--tpl-primary)]\"\n :checked=\"block.termsEnabled\"\n @change=\"\n updateField(\n 'termsEnabled',\n ($event.target as HTMLInputElement).checked,\n )\n \"\n />\n {{ t.form.terms.enable }}\n </label>\n\n <div v-if=\"block.termsEnabled\">\n <label :class=\"labelClass\">{{ t.form.terms.content }}</label>\n <textarea\n rows=\"6\"\n class=\"tpl:w-full tpl:resize-y tpl:rounded-[var(--tpl-radius-sm)] tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:px-3 tpl:py-2 tpl:text-sm tpl:text-[var(--tpl-text)] tpl:outline-none focus:tpl:border-[var(--tpl-primary)]\"\n :value=\"block.termsContent\"\n @input=\"\n updateField(\n 'termsContent',\n ($event.target as HTMLTextAreaElement).value,\n )\n \"\n ></textarea>\n <p class=\"tpl:mt-1 tpl:text-xs tpl:text-[var(--tpl-text-muted)]\">\n {{ t.form.terms.htmlHint }}\n </p>\n </div>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, ref } from \"vue\";\nimport ColorPicker from \"../ColorPicker.vue\";\nimport MergeTagInput from \"../MergeTagInput.vue\";\nimport CollapsibleSection from \"./CollapsibleSection.vue\";\nimport SpacingControl from \"../SpacingControl.vue\";\nimport { useI18n } from \"../../composables/useI18n\";\nimport {\n inputClass,\n inputGroupInputClass,\n inputSuffixClass,\n labelClass,\n} from \"../../constants/styleConstants\";\nimport type {\n ButtonBorderSides,\n InputBlock,\n InputFieldType,\n SpacingValue,\n} from \"@aswin.dev/types\";\n\nconst props = defineProps<{\n block: InputBlock;\n fontFamilies: Array<{ value: string; label: string }>;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update\", updates: Partial<InputBlock>): void;\n}>();\n\nconst { t } = useI18n();\n\nconst openLabelSection = ref(true);\nconst openInputSection = ref(true);\n\nfunction updateField<K extends keyof InputBlock>(\n field: K,\n value: InputBlock[K],\n): void {\n emit(\"update\", { [field]: value } as Partial<InputBlock>);\n}\n\nconst BORDER_SIDE_IDS: ButtonBorderSides[] = [\n \"all\",\n \"top\",\n \"right\",\n \"bottom\",\n \"left\",\n];\n\nconst borderSideOptions = BORDER_SIDE_IDS.map((id) => ({\n id,\n label:\n t.button.borderSidesLabels[id as keyof typeof t.button.borderSidesLabels],\n}));\n\nconst inputTypes: { value: InputFieldType; label: string }[] = [\n { value: \"text\", label: t.input.types.text },\n { value: \"email\", label: t.input.types.email },\n { value: \"date\", label: t.input.types.date },\n];\n\nfunction patchSpacing<\n K extends \"labelMargin\" | \"labelPadding\" | \"inputMargin\" | \"inputPadding\",\n>(key: K, value: SpacingValue): void {\n emit(\"update\", { [key]: value } as Partial<InputBlock>);\n}\n\nconst widthMode = computed(() =>\n props.block.inputWidth === \"full\" ? \"full\" : \"fixed\",\n);\n\nfunction setWidthMode(ev: Event): void {\n const v = (ev.target as HTMLSelectElement).value;\n if (v === \"full\") {\n updateField(\"inputWidth\", \"full\");\n } else {\n const n =\n typeof props.block.inputWidth === \"number\" ? props.block.inputWidth : 280;\n updateField(\"inputWidth\", n);\n }\n}\n\nfunction toggleLabelSection(): void {\n openLabelSection.value = !openLabelSection.value;\n}\n\nfunction toggleInputSection(): void {\n openInputSection.value = !openInputSection.value;\n}\n</script>\n\n<template>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.input.fieldType }}</label>\n <select\n :class=\"inputClass\"\n :value=\"block.inputType\"\n @change=\"\n updateField(\n 'inputType',\n ($event.target as HTMLSelectElement).value as InputFieldType,\n )\n \"\n >\n <option v-for=\"opt in inputTypes\" :key=\"opt.value\" :value=\"opt.value\">\n {{ opt.label }}\n </option>\n </select>\n </div>\n\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.input.name }}</label>\n <input\n type=\"text\"\n :class=\"inputClass\"\n :value=\"block.name\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n @input=\"\n updateField('name', ($event.target as HTMLInputElement).value.trim())\n \"\n />\n </div>\n\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.input.label }}</label>\n <MergeTagInput\n :model-value=\"block.label\"\n type=\"text\"\n @update:model-value=\"updateField('label', $event)\"\n />\n </div>\n\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.input.placeholder }}</label>\n <MergeTagInput\n :model-value=\"block.placeholder\"\n type=\"text\"\n @update:model-value=\"updateField('placeholder', $event)\"\n />\n </div>\n\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.input.defaultValue }}</label>\n <MergeTagInput\n :model-value=\"block.defaultValue ?? ''\"\n type=\"text\"\n @update:model-value=\"updateField('defaultValue', $event)\"\n />\n </div>\n\n <label\n class=\"tpl:mb-3.5 tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:text-[13px] tpl:text-[var(--tpl-text)]\"\n >\n <input\n type=\"checkbox\"\n class=\"tpl:size-3.5 tpl:cursor-pointer tpl:accent-[var(--tpl-primary)]\"\n :checked=\"block.required ?? false\"\n @change=\"\n updateField('required', ($event.target as HTMLInputElement).checked)\n \"\n />\n {{ t.input.required }}\n </label>\n\n <CollapsibleSection\n :title=\"t.input.labelAppearance\"\n :open=\"openLabelSection\"\n no-border\n @toggle=\"toggleLabelSection\"\n >\n <SpacingControl\n :model-value=\"block.labelMargin\"\n :label=\"t.input.labelMargin\"\n @update:model-value=\"patchSpacing('labelMargin', $event)\"\n />\n <SpacingControl\n :model-value=\"block.labelPadding\"\n :label=\"t.input.labelPadding\"\n @update:model-value=\"patchSpacing('labelPadding', $event)\"\n />\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.input.fontFamily }}</label>\n <select\n :class=\"inputClass\"\n :value=\"block.labelFontFamily || ''\"\n @change=\"\n updateField(\n 'labelFontFamily',\n ($event.target as HTMLSelectElement).value || undefined,\n )\n \"\n >\n <option value=\"\">{{ t.input.inheritFont }}</option>\n <option\n v-for=\"font in fontFamilies\"\n :key=\"font.value\"\n :value=\"font.value\"\n >\n {{ font.label }}\n </option>\n </select>\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.button.fontSize }}</label>\n <div class=\"tpl:flex tpl:items-stretch\">\n <input\n type=\"number\"\n :class=\"inputGroupInputClass\"\n :value=\"block.labelFontSize\"\n min=\"8\"\n max=\"48\"\n @input=\"\n updateField(\n 'labelFontSize',\n Number(($event.target as HTMLInputElement).value) || 14,\n )\n \"\n />\n <span :class=\"inputSuffixClass\">px</span>\n </div>\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.button.textColor }}</label>\n <ColorPicker\n :model-value=\"block.labelColor\"\n @update:model-value=\"updateField('labelColor', $event)\"\n />\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.input.fontWeight }}</label>\n <select\n :class=\"inputClass\"\n :value=\"block.labelFontWeight\"\n @change=\"\n updateField(\n 'labelFontWeight',\n ($event.target as HTMLSelectElement).value as 'normal' | 'bold',\n )\n \"\n >\n <option value=\"normal\">{{ t.input.weightNormal }}</option>\n <option value=\"bold\">{{ t.input.weightBold }}</option>\n </select>\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.menu.textAlign }}</label>\n <select\n :class=\"inputClass\"\n :value=\"block.labelTextAlign\"\n @change=\"\n updateField(\n 'labelTextAlign',\n ($event.target as HTMLSelectElement).value as\n | 'left'\n | 'center'\n | 'right',\n )\n \"\n >\n <option value=\"left\">{{ t.input.alignLeft }}</option>\n <option value=\"center\">{{ t.input.alignCenter }}</option>\n <option value=\"right\">{{ t.input.alignRight }}</option>\n </select>\n </div>\n </CollapsibleSection>\n\n <CollapsibleSection\n :title=\"t.input.inputAppearance\"\n :open=\"openInputSection\"\n @toggle=\"toggleInputSection\"\n >\n <SpacingControl\n :model-value=\"block.inputMargin\"\n :label=\"t.input.inputMargin\"\n @update:model-value=\"patchSpacing('inputMargin', $event)\"\n />\n <SpacingControl\n :model-value=\"block.inputPadding\"\n :label=\"t.input.inputPadding\"\n @update:model-value=\"patchSpacing('inputPadding', $event)\"\n />\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.input.fieldWidth }}</label>\n <select :class=\"inputClass\" :value=\"widthMode\" @change=\"setWidthMode\">\n <option value=\"full\">{{ t.video.fullWidth }}</option>\n <option value=\"fixed\">{{ t.input.fixedWidth }}</option>\n </select>\n </div>\n <div v-if=\"block.inputWidth !== 'full'\" class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.video.width }}</label>\n <div class=\"tpl:flex tpl:items-stretch\">\n <input\n type=\"number\"\n :class=\"inputGroupInputClass\"\n :value=\"typeof block.inputWidth === 'number' ? block.inputWidth : 280\"\n min=\"80\"\n max=\"1200\"\n @input=\"\n updateField(\n 'inputWidth',\n Math.max(\n 80,\n Number(($event.target as HTMLInputElement).value) || 280,\n ),\n )\n \"\n />\n <span :class=\"inputSuffixClass\">px</span>\n </div>\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.input.fontFamily }}</label>\n <select\n :class=\"inputClass\"\n :value=\"block.inputFontFamily || ''\"\n @change=\"\n updateField(\n 'inputFontFamily',\n ($event.target as HTMLSelectElement).value || undefined,\n )\n \"\n >\n <option value=\"\">{{ t.input.inheritFont }}</option>\n <option\n v-for=\"font in fontFamilies\"\n :key=\"font.value\"\n :value=\"font.value\"\n >\n {{ font.label }}\n </option>\n </select>\n </div>\n <div class=\"tpl:mb-3.5 tpl:flex tpl:flex-col tpl:gap-3\">\n <div>\n <label :class=\"labelClass\">{{ t.button.background }}</label>\n <ColorPicker\n :model-value=\"block.inputBackgroundColor\"\n @update:model-value=\"updateField('inputBackgroundColor', $event)\"\n />\n </div>\n <div>\n <label :class=\"labelClass\">{{ t.button.textColor }}</label>\n <ColorPicker\n :model-value=\"block.inputTextColor\"\n @update:model-value=\"updateField('inputTextColor', $event)\"\n />\n </div>\n <div>\n <label :class=\"labelClass\">{{ t.input.placeholderColor }}</label>\n <ColorPicker\n :model-value=\"block.inputPlaceholderColor\"\n @update:model-value=\"updateField('inputPlaceholderColor', $event)\"\n />\n </div>\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.button.fontSize }}</label>\n <div class=\"tpl:flex tpl:items-stretch\">\n <input\n type=\"number\"\n :class=\"inputGroupInputClass\"\n :value=\"block.inputFontSize\"\n min=\"10\"\n max=\"36\"\n @input=\"\n updateField(\n 'inputFontSize',\n Number(($event.target as HTMLInputElement).value) || 15,\n )\n \"\n />\n <span :class=\"inputSuffixClass\">px</span>\n </div>\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.button.borderRadius }}</label>\n <div class=\"tpl:flex tpl:items-stretch\">\n <input\n type=\"number\"\n :class=\"inputGroupInputClass\"\n :value=\"block.inputBorderRadius\"\n min=\"0\"\n max=\"40\"\n @input=\"\n updateField(\n 'inputBorderRadius',\n Number(($event.target as HTMLInputElement).value) || 0,\n )\n \"\n />\n <span :class=\"inputSuffixClass\">px</span>\n </div>\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.button.border }}</label>\n <div class=\"tpl:flex tpl:flex-col tpl:gap-3\">\n <div>\n <label :class=\"labelClass\">{{ t.button.borderColor }}</label>\n <ColorPicker\n :model-value=\"block.inputBorderColor ?? '#d1d5db'\"\n @update:model-value=\"updateField('inputBorderColor', $event)\"\n />\n </div>\n <div>\n <label :class=\"labelClass\">{{ t.button.borderWidth }}</label>\n <div class=\"tpl:flex tpl:items-stretch\">\n <input\n type=\"number\"\n :class=\"inputGroupInputClass\"\n :value=\"block.inputBorderWidth ?? 0\"\n min=\"0\"\n max=\"20\"\n @input=\"\n updateField(\n 'inputBorderWidth',\n Math.max(\n 0,\n Number(($event.target as HTMLInputElement).value) || 0,\n ),\n )\n \"\n />\n <span :class=\"inputSuffixClass\">px</span>\n </div>\n </div>\n <div>\n <label :class=\"labelClass\">{{ t.button.borderSides }}</label>\n <select\n :class=\"inputClass\"\n :value=\"block.inputBorderSides ?? 'all'\"\n @change=\"\n updateField(\n 'inputBorderSides',\n ($event.target as HTMLSelectElement).value as ButtonBorderSides,\n )\n \"\n >\n <option\n v-for=\"opt in borderSideOptions\"\n :key=\"opt.id\"\n :value=\"opt.id\"\n >\n {{ opt.label }}\n </option>\n </select>\n </div>\n </div>\n </div>\n </CollapsibleSection>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, ref } from \"vue\";\nimport ColorPicker from \"../ColorPicker.vue\";\nimport MergeTagInput from \"../MergeTagInput.vue\";\nimport CollapsibleSection from \"./CollapsibleSection.vue\";\nimport SpacingControl from \"../SpacingControl.vue\";\nimport { useI18n } from \"../../composables/useI18n\";\nimport {\n inputClass,\n inputGroupInputClass,\n inputSuffixClass,\n labelClass,\n} from \"../../constants/styleConstants\";\nimport type {\n ButtonBorderSides,\n InputBlock,\n InputFieldType,\n SpacingValue,\n} from \"@aswin.dev/types\";\n\nconst props = defineProps<{\n block: InputBlock;\n fontFamilies: Array<{ value: string; label: string }>;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update\", updates: Partial<InputBlock>): void;\n}>();\n\nconst { t } = useI18n();\n\nconst openLabelSection = ref(true);\nconst openInputSection = ref(true);\n\nfunction updateField<K extends keyof InputBlock>(\n field: K,\n value: InputBlock[K],\n): void {\n emit(\"update\", { [field]: value } as Partial<InputBlock>);\n}\n\nconst BORDER_SIDE_IDS: ButtonBorderSides[] = [\n \"all\",\n \"top\",\n \"right\",\n \"bottom\",\n \"left\",\n];\n\nconst borderSideOptions = BORDER_SIDE_IDS.map((id) => ({\n id,\n label:\n t.button.borderSidesLabels[id as keyof typeof t.button.borderSidesLabels],\n}));\n\nconst inputTypes: { value: InputFieldType; label: string }[] = [\n { value: \"text\", label: t.input.types.text },\n { value: \"email\", label: t.input.types.email },\n { value: \"date\", label: t.input.types.date },\n];\n\nfunction patchSpacing<\n K extends \"labelMargin\" | \"labelPadding\" | \"inputMargin\" | \"inputPadding\",\n>(key: K, value: SpacingValue): void {\n emit(\"update\", { [key]: value } as Partial<InputBlock>);\n}\n\nconst widthMode = computed(() =>\n props.block.inputWidth === \"full\" ? \"full\" : \"fixed\",\n);\n\nfunction setWidthMode(ev: Event): void {\n const v = (ev.target as HTMLSelectElement).value;\n if (v === \"full\") {\n updateField(\"inputWidth\", \"full\");\n } else {\n const n =\n typeof props.block.inputWidth === \"number\" ? props.block.inputWidth : 280;\n updateField(\"inputWidth\", n);\n }\n}\n\nfunction toggleLabelSection(): void {\n openLabelSection.value = !openLabelSection.value;\n}\n\nfunction toggleInputSection(): void {\n openInputSection.value = !openInputSection.value;\n}\n</script>\n\n<template>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.input.fieldType }}</label>\n <select\n :class=\"inputClass\"\n :value=\"block.inputType\"\n @change=\"\n updateField(\n 'inputType',\n ($event.target as HTMLSelectElement).value as InputFieldType,\n )\n \"\n >\n <option v-for=\"opt in inputTypes\" :key=\"opt.value\" :value=\"opt.value\">\n {{ opt.label }}\n </option>\n </select>\n </div>\n\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.input.name }}</label>\n <input\n type=\"text\"\n :class=\"inputClass\"\n :value=\"block.name\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n @input=\"\n updateField('name', ($event.target as HTMLInputElement).value.trim())\n \"\n />\n </div>\n\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.input.label }}</label>\n <MergeTagInput\n :model-value=\"block.label\"\n type=\"text\"\n @update:model-value=\"updateField('label', $event)\"\n />\n </div>\n\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.input.placeholder }}</label>\n <MergeTagInput\n :model-value=\"block.placeholder\"\n type=\"text\"\n @update:model-value=\"updateField('placeholder', $event)\"\n />\n </div>\n\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.input.defaultValue }}</label>\n <MergeTagInput\n :model-value=\"block.defaultValue ?? ''\"\n type=\"text\"\n @update:model-value=\"updateField('defaultValue', $event)\"\n />\n </div>\n\n <label\n class=\"tpl:mb-3.5 tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:text-[13px] tpl:text-[var(--tpl-text)]\"\n >\n <input\n type=\"checkbox\"\n class=\"tpl:size-3.5 tpl:cursor-pointer tpl:accent-[var(--tpl-primary)]\"\n :checked=\"block.required ?? false\"\n @change=\"\n updateField('required', ($event.target as HTMLInputElement).checked)\n \"\n />\n {{ t.input.required }}\n </label>\n\n <CollapsibleSection\n :title=\"t.input.labelAppearance\"\n :open=\"openLabelSection\"\n no-border\n @toggle=\"toggleLabelSection\"\n >\n <SpacingControl\n :model-value=\"block.labelMargin\"\n :label=\"t.input.labelMargin\"\n @update:model-value=\"patchSpacing('labelMargin', $event)\"\n />\n <SpacingControl\n :model-value=\"block.labelPadding\"\n :label=\"t.input.labelPadding\"\n @update:model-value=\"patchSpacing('labelPadding', $event)\"\n />\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.input.fontFamily }}</label>\n <select\n :class=\"inputClass\"\n :value=\"block.labelFontFamily || ''\"\n @change=\"\n updateField(\n 'labelFontFamily',\n ($event.target as HTMLSelectElement).value || undefined,\n )\n \"\n >\n <option value=\"\">{{ t.input.inheritFont }}</option>\n <option\n v-for=\"font in fontFamilies\"\n :key=\"font.value\"\n :value=\"font.value\"\n >\n {{ font.label }}\n </option>\n </select>\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.button.fontSize }}</label>\n <div class=\"tpl:flex tpl:items-stretch\">\n <input\n type=\"number\"\n :class=\"inputGroupInputClass\"\n :value=\"block.labelFontSize\"\n min=\"8\"\n max=\"48\"\n @input=\"\n updateField(\n 'labelFontSize',\n Number(($event.target as HTMLInputElement).value) || 14,\n )\n \"\n />\n <span :class=\"inputSuffixClass\">px</span>\n </div>\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.button.textColor }}</label>\n <ColorPicker\n :model-value=\"block.labelColor\"\n @update:model-value=\"updateField('labelColor', $event)\"\n />\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.input.fontWeight }}</label>\n <select\n :class=\"inputClass\"\n :value=\"block.labelFontWeight\"\n @change=\"\n updateField(\n 'labelFontWeight',\n ($event.target as HTMLSelectElement).value as 'normal' | 'bold',\n )\n \"\n >\n <option value=\"normal\">{{ t.input.weightNormal }}</option>\n <option value=\"bold\">{{ t.input.weightBold }}</option>\n </select>\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.menu.textAlign }}</label>\n <select\n :class=\"inputClass\"\n :value=\"block.labelTextAlign\"\n @change=\"\n updateField(\n 'labelTextAlign',\n ($event.target as HTMLSelectElement).value as\n | 'left'\n | 'center'\n | 'right',\n )\n \"\n >\n <option value=\"left\">{{ t.input.alignLeft }}</option>\n <option value=\"center\">{{ t.input.alignCenter }}</option>\n <option value=\"right\">{{ t.input.alignRight }}</option>\n </select>\n </div>\n </CollapsibleSection>\n\n <CollapsibleSection\n :title=\"t.input.inputAppearance\"\n :open=\"openInputSection\"\n @toggle=\"toggleInputSection\"\n >\n <SpacingControl\n :model-value=\"block.inputMargin\"\n :label=\"t.input.inputMargin\"\n @update:model-value=\"patchSpacing('inputMargin', $event)\"\n />\n <SpacingControl\n :model-value=\"block.inputPadding\"\n :label=\"t.input.inputPadding\"\n @update:model-value=\"patchSpacing('inputPadding', $event)\"\n />\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.input.fieldWidth }}</label>\n <select :class=\"inputClass\" :value=\"widthMode\" @change=\"setWidthMode\">\n <option value=\"full\">{{ t.video.fullWidth }}</option>\n <option value=\"fixed\">{{ t.input.fixedWidth }}</option>\n </select>\n </div>\n <div v-if=\"block.inputWidth !== 'full'\" class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.video.width }}</label>\n <div class=\"tpl:flex tpl:items-stretch\">\n <input\n type=\"number\"\n :class=\"inputGroupInputClass\"\n :value=\"typeof block.inputWidth === 'number' ? block.inputWidth : 280\"\n min=\"80\"\n max=\"1200\"\n @input=\"\n updateField(\n 'inputWidth',\n Math.max(\n 80,\n Number(($event.target as HTMLInputElement).value) || 280,\n ),\n )\n \"\n />\n <span :class=\"inputSuffixClass\">px</span>\n </div>\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.input.fontFamily }}</label>\n <select\n :class=\"inputClass\"\n :value=\"block.inputFontFamily || ''\"\n @change=\"\n updateField(\n 'inputFontFamily',\n ($event.target as HTMLSelectElement).value || undefined,\n )\n \"\n >\n <option value=\"\">{{ t.input.inheritFont }}</option>\n <option\n v-for=\"font in fontFamilies\"\n :key=\"font.value\"\n :value=\"font.value\"\n >\n {{ font.label }}\n </option>\n </select>\n </div>\n <div class=\"tpl:mb-3.5 tpl:flex tpl:flex-col tpl:gap-3\">\n <div>\n <label :class=\"labelClass\">{{ t.button.background }}</label>\n <ColorPicker\n :model-value=\"block.inputBackgroundColor\"\n @update:model-value=\"updateField('inputBackgroundColor', $event)\"\n />\n </div>\n <div>\n <label :class=\"labelClass\">{{ t.button.textColor }}</label>\n <ColorPicker\n :model-value=\"block.inputTextColor\"\n @update:model-value=\"updateField('inputTextColor', $event)\"\n />\n </div>\n <div>\n <label :class=\"labelClass\">{{ t.input.placeholderColor }}</label>\n <ColorPicker\n :model-value=\"block.inputPlaceholderColor\"\n @update:model-value=\"updateField('inputPlaceholderColor', $event)\"\n />\n </div>\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.button.fontSize }}</label>\n <div class=\"tpl:flex tpl:items-stretch\">\n <input\n type=\"number\"\n :class=\"inputGroupInputClass\"\n :value=\"block.inputFontSize\"\n min=\"10\"\n max=\"36\"\n @input=\"\n updateField(\n 'inputFontSize',\n Number(($event.target as HTMLInputElement).value) || 15,\n )\n \"\n />\n <span :class=\"inputSuffixClass\">px</span>\n </div>\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.button.borderRadius }}</label>\n <div class=\"tpl:flex tpl:items-stretch\">\n <input\n type=\"number\"\n :class=\"inputGroupInputClass\"\n :value=\"block.inputBorderRadius\"\n min=\"0\"\n max=\"40\"\n @input=\"\n updateField(\n 'inputBorderRadius',\n Number(($event.target as HTMLInputElement).value) || 0,\n )\n \"\n />\n <span :class=\"inputSuffixClass\">px</span>\n </div>\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.button.border }}</label>\n <div class=\"tpl:flex tpl:flex-col tpl:gap-3\">\n <div>\n <label :class=\"labelClass\">{{ t.button.borderColor }}</label>\n <ColorPicker\n :model-value=\"block.inputBorderColor ?? '#d1d5db'\"\n @update:model-value=\"updateField('inputBorderColor', $event)\"\n />\n </div>\n <div>\n <label :class=\"labelClass\">{{ t.button.borderWidth }}</label>\n <div class=\"tpl:flex tpl:items-stretch\">\n <input\n type=\"number\"\n :class=\"inputGroupInputClass\"\n :value=\"block.inputBorderWidth ?? 0\"\n min=\"0\"\n max=\"20\"\n @input=\"\n updateField(\n 'inputBorderWidth',\n Math.max(\n 0,\n Number(($event.target as HTMLInputElement).value) || 0,\n ),\n )\n \"\n />\n <span :class=\"inputSuffixClass\">px</span>\n </div>\n </div>\n <div>\n <label :class=\"labelClass\">{{ t.button.borderSides }}</label>\n <select\n :class=\"inputClass\"\n :value=\"block.inputBorderSides ?? 'all'\"\n @change=\"\n updateField(\n 'inputBorderSides',\n ($event.target as HTMLSelectElement).value as ButtonBorderSides,\n )\n \"\n >\n <option\n v-for=\"opt in borderSideOptions\"\n :key=\"opt.id\"\n :value=\"opt.id\"\n >\n {{ opt.label }}\n </option>\n </select>\n </div>\n </div>\n </div>\n </CollapsibleSection>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../../composables/useI18n\";\nimport type { CustomBlockBooleanField } from \"@aswin.dev/types\";\nimport { Lock } from \"@lucide/vue\";\n\ndefineProps<{\n field: CustomBlockBooleanField;\n modelValue: boolean;\n readOnly?: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update:modelValue\", value: boolean): void;\n}>();\n\nconst { t } = useI18n();\n</script>\n\n<template>\n <div\n class=\"tpl:mb-3.5\"\n :title=\"readOnly ? t.customBlocks.dataSource.readOnlyTooltip : undefined\"\n >\n <label\n :class=\"[\n 'tpl:flex tpl:items-center tpl:justify-between tpl:gap-2',\n readOnly ? 'tpl:cursor-not-allowed' : 'tpl:cursor-pointer',\n ]\"\n >\n <span\n class=\"tpl:text-sm tpl:font-medium tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ field.label }}\n <Lock\n v-if=\"readOnly\"\n :size=\"12\"\n class=\"tpl:inline tpl:text-[var(--tpl-text-dim)]\"\n />\n <span v-if=\"field.required\" class=\"tpl:text-[var(--tpl-danger)]\">\n *\n </span>\n </span>\n <button\n type=\"button\"\n role=\"switch\"\n :aria-checked=\"modelValue\"\n :aria-label=\"field.label\"\n :class=\"[\n 'tpl:relative tpl:inline-flex tpl:h-5 tpl:w-9 tpl:shrink-0 tpl:rounded-full tpl:border-2 tpl:border-transparent tpl:transition-colors tpl:duration-200',\n modelValue\n ? 'tpl:bg-[var(--tpl-primary)]'\n : 'tpl:bg-[var(--tpl-border)]',\n readOnly\n ? 'tpl:opacity-60 tpl:cursor-not-allowed'\n : 'tpl:cursor-pointer',\n ]\"\n :disabled=\"readOnly\"\n @click=\"!readOnly && emit('update:modelValue', !modelValue)\"\n >\n <span\n class=\"tpl:pointer-events-none tpl:inline-block tpl:size-4 tpl:rounded-full tpl:bg-[var(--tpl-bg)] tpl:shadow tpl:transition-transform tpl:duration-200\"\n :class=\"modelValue ? 'tpl:translate-x-4' : 'tpl:translate-x-0'\"\n />\n </button>\n </label>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../../composables/useI18n\";\nimport type { CustomBlockBooleanField } from \"@aswin.dev/types\";\nimport { Lock } from \"@lucide/vue\";\n\ndefineProps<{\n field: CustomBlockBooleanField;\n modelValue: boolean;\n readOnly?: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update:modelValue\", value: boolean): void;\n}>();\n\nconst { t } = useI18n();\n</script>\n\n<template>\n <div\n class=\"tpl:mb-3.5\"\n :title=\"readOnly ? t.customBlocks.dataSource.readOnlyTooltip : undefined\"\n >\n <label\n :class=\"[\n 'tpl:flex tpl:items-center tpl:justify-between tpl:gap-2',\n readOnly ? 'tpl:cursor-not-allowed' : 'tpl:cursor-pointer',\n ]\"\n >\n <span\n class=\"tpl:text-sm tpl:font-medium tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ field.label }}\n <Lock\n v-if=\"readOnly\"\n :size=\"12\"\n class=\"tpl:inline tpl:text-[var(--tpl-text-dim)]\"\n />\n <span v-if=\"field.required\" class=\"tpl:text-[var(--tpl-danger)]\">\n *\n </span>\n </span>\n <button\n type=\"button\"\n role=\"switch\"\n :aria-checked=\"modelValue\"\n :aria-label=\"field.label\"\n :class=\"[\n 'tpl:relative tpl:inline-flex tpl:h-5 tpl:w-9 tpl:shrink-0 tpl:rounded-full tpl:border-2 tpl:border-transparent tpl:transition-colors tpl:duration-200',\n modelValue\n ? 'tpl:bg-[var(--tpl-primary)]'\n : 'tpl:bg-[var(--tpl-border)]',\n readOnly\n ? 'tpl:opacity-60 tpl:cursor-not-allowed'\n : 'tpl:cursor-pointer',\n ]\"\n :disabled=\"readOnly\"\n @click=\"!readOnly && emit('update:modelValue', !modelValue)\"\n >\n <span\n class=\"tpl:pointer-events-none tpl:inline-block tpl:size-4 tpl:rounded-full tpl:bg-[var(--tpl-bg)] tpl:shadow tpl:transition-transform tpl:duration-200\"\n :class=\"modelValue ? 'tpl:translate-x-4' : 'tpl:translate-x-0'\"\n />\n </button>\n </label>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { labelClass } from \"../../../constants/styleConstants\";\nimport { Lock } from \"@lucide/vue\";\n\ndefineProps<{\n label: string;\n required?: boolean;\n readOnly?: boolean;\n}>();\n</script>\n\n<template>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">\n {{ label }}\n <Lock\n v-if=\"readOnly\"\n :size=\"12\"\n class=\"tpl:inline tpl:text-[var(--tpl-text-dim)]\"\n />\n <span v-if=\"required\" class=\"tpl:text-[var(--tpl-danger)]\">*</span>\n </label>\n <slot />\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { labelClass } from \"../../../constants/styleConstants\";\nimport { Lock } from \"@lucide/vue\";\n\ndefineProps<{\n label: string;\n required?: boolean;\n readOnly?: boolean;\n}>();\n</script>\n\n<template>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">\n {{ label }}\n <Lock\n v-if=\"readOnly\"\n :size=\"12\"\n class=\"tpl:inline tpl:text-[var(--tpl-text-dim)]\"\n />\n <span v-if=\"required\" class=\"tpl:text-[var(--tpl-danger)]\">*</span>\n </label>\n <slot />\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../../composables/useI18n\";\nimport type { CustomBlockColorField } from \"@aswin.dev/types\";\nimport { DEFAULT_TEXT_COLOR } from \"../../../constants/styleConstants\";\nimport FieldWrapper from \"./FieldWrapper.vue\";\nimport ColorPicker from \"../../ColorPicker.vue\";\n\ndefineProps<{\n field: CustomBlockColorField;\n modelValue: string;\n readOnly?: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update:modelValue\", value: string): void;\n}>();\n\nconst { t } = useI18n();\n</script>\n\n<template>\n <FieldWrapper\n :label=\"field.label\"\n :required=\"field.required\"\n :read-only=\"readOnly\"\n >\n <ColorPicker\n :model-value=\"modelValue || DEFAULT_TEXT_COLOR\"\n :placeholder=\"field.placeholder || DEFAULT_TEXT_COLOR\"\n :disabled=\"readOnly\"\n :title=\"readOnly ? t.customBlocks.dataSource.readOnlyTooltip : undefined\"\n @update:model-value=\"emit('update:modelValue', $event)\"\n />\n </FieldWrapper>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../../composables/useI18n\";\nimport type { CustomBlockColorField } from \"@aswin.dev/types\";\nimport { DEFAULT_TEXT_COLOR } from \"../../../constants/styleConstants\";\nimport FieldWrapper from \"./FieldWrapper.vue\";\nimport ColorPicker from \"../../ColorPicker.vue\";\n\ndefineProps<{\n field: CustomBlockColorField;\n modelValue: string;\n readOnly?: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update:modelValue\", value: string): void;\n}>();\n\nconst { t } = useI18n();\n</script>\n\n<template>\n <FieldWrapper\n :label=\"field.label\"\n :required=\"field.required\"\n :read-only=\"readOnly\"\n >\n <ColorPicker\n :model-value=\"modelValue || DEFAULT_TEXT_COLOR\"\n :placeholder=\"field.placeholder || DEFAULT_TEXT_COLOR\"\n :disabled=\"readOnly\"\n :title=\"readOnly ? t.customBlocks.dataSource.readOnlyTooltip : undefined\"\n @update:model-value=\"emit('update:modelValue', $event)\"\n />\n </FieldWrapper>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../../composables/useI18n\";\nimport type { CustomBlockImageField } from \"@aswin.dev/types\";\nimport { inputClass } from \"../../../constants/styleConstants\";\nimport { Image } from \"@lucide/vue\";\nimport { computed, inject } from \"vue\";\nimport { ON_REQUEST_MEDIA_KEY } from \"../../../keys\";\nimport FieldWrapper from \"./FieldWrapper.vue\";\n\ndefineProps<{\n field: CustomBlockImageField;\n modelValue: string;\n readOnly?: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update:modelValue\", value: string): void;\n}>();\n\nconst { t } = useI18n();\nconst onRequestMedia = inject(ON_REQUEST_MEDIA_KEY, null);\n\nconst canBrowseMedia = computed(() => !!onRequestMedia);\n\nasync function browseMedia(): Promise<void> {\n const result = await onRequestMedia?.({ accept: [\"images\"] });\n if (result) {\n emit(\"update:modelValue\", result.url);\n }\n}\n</script>\n\n<template>\n <FieldWrapper\n :label=\"field.label\"\n :required=\"field.required\"\n :read-only=\"readOnly\"\n >\n <input\n v-if=\"readOnly\"\n type=\"url\"\n :class=\"[inputClass, 'tpl:opacity-60 tpl:cursor-not-allowed']\"\n :value=\"modelValue\"\n :placeholder=\"field.placeholder || 'https://...'\"\n disabled\n :title=\"t.customBlocks.dataSource.readOnlyTooltip\"\n />\n <input\n v-else\n type=\"url\"\n :class=\"inputClass\"\n :value=\"modelValue\"\n :placeholder=\"field.placeholder || 'https://...'\"\n @input=\"\n emit('update:modelValue', ($event.target as HTMLInputElement).value)\n \"\n />\n <button\n v-if=\"canBrowseMedia && !readOnly\"\n class=\"tpl:mt-2 tpl:flex tpl:w-full tpl:items-center tpl:justify-center tpl:gap-1.5 tpl:rounded-md tpl:border tpl:px-3 tpl:py-2 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150 tpl:border-[var(--tpl-border)] tpl:text-[var(--tpl-primary)] tpl:bg-[var(--tpl-bg)]\"\n @click=\"browseMedia()\"\n >\n <Image :size=\"14\" :stroke-width=\"1.5\" />\n {{ t.image.browseMedia }}\n </button>\n </FieldWrapper>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../../composables/useI18n\";\nimport type { CustomBlockImageField } from \"@aswin.dev/types\";\nimport { inputClass } from \"../../../constants/styleConstants\";\nimport { Image } from \"@lucide/vue\";\nimport { computed, inject } from \"vue\";\nimport { ON_REQUEST_MEDIA_KEY } from \"../../../keys\";\nimport FieldWrapper from \"./FieldWrapper.vue\";\n\ndefineProps<{\n field: CustomBlockImageField;\n modelValue: string;\n readOnly?: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update:modelValue\", value: string): void;\n}>();\n\nconst { t } = useI18n();\nconst onRequestMedia = inject(ON_REQUEST_MEDIA_KEY, null);\n\nconst canBrowseMedia = computed(() => !!onRequestMedia);\n\nasync function browseMedia(): Promise<void> {\n const result = await onRequestMedia?.({ accept: [\"images\"] });\n if (result) {\n emit(\"update:modelValue\", result.url);\n }\n}\n</script>\n\n<template>\n <FieldWrapper\n :label=\"field.label\"\n :required=\"field.required\"\n :read-only=\"readOnly\"\n >\n <input\n v-if=\"readOnly\"\n type=\"url\"\n :class=\"[inputClass, 'tpl:opacity-60 tpl:cursor-not-allowed']\"\n :value=\"modelValue\"\n :placeholder=\"field.placeholder || 'https://...'\"\n disabled\n :title=\"t.customBlocks.dataSource.readOnlyTooltip\"\n />\n <input\n v-else\n type=\"url\"\n :class=\"inputClass\"\n :value=\"modelValue\"\n :placeholder=\"field.placeholder || 'https://...'\"\n @input=\"\n emit('update:modelValue', ($event.target as HTMLInputElement).value)\n \"\n />\n <button\n v-if=\"canBrowseMedia && !readOnly\"\n class=\"tpl:mt-2 tpl:flex tpl:w-full tpl:items-center tpl:justify-center tpl:gap-1.5 tpl:rounded-md tpl:border tpl:px-3 tpl:py-2 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150 tpl:border-[var(--tpl-border)] tpl:text-[var(--tpl-primary)] tpl:bg-[var(--tpl-bg)]\"\n @click=\"browseMedia()\"\n >\n <Image :size=\"14\" :stroke-width=\"1.5\" />\n {{ t.image.browseMedia }}\n </button>\n </FieldWrapper>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../../composables/useI18n\";\nimport type { CustomBlockNumberField } from \"@aswin.dev/types\";\nimport { inputClass } from \"../../../constants/styleConstants\";\nimport FieldWrapper from \"./FieldWrapper.vue\";\n\ndefineProps<{\n field: CustomBlockNumberField;\n modelValue: number;\n readOnly?: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update:modelValue\", value: number): void;\n}>();\n\nconst { t } = useI18n();\n</script>\n\n<template>\n <FieldWrapper\n :label=\"field.label\"\n :required=\"field.required\"\n :read-only=\"readOnly\"\n >\n <input\n type=\"number\"\n :class=\"[inputClass, readOnly && 'tpl:opacity-60 tpl:cursor-not-allowed']\"\n :value=\"modelValue\"\n :placeholder=\"field.placeholder\"\n :min=\"field.min\"\n :max=\"field.max\"\n :step=\"field.step\"\n :disabled=\"readOnly\"\n :title=\"readOnly ? t.customBlocks.dataSource.readOnlyTooltip : undefined\"\n @input=\"\n !readOnly &&\n emit(\n 'update:modelValue',\n Number(($event.target as HTMLInputElement).value),\n )\n \"\n />\n </FieldWrapper>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../../composables/useI18n\";\nimport type { CustomBlockNumberField } from \"@aswin.dev/types\";\nimport { inputClass } from \"../../../constants/styleConstants\";\nimport FieldWrapper from \"./FieldWrapper.vue\";\n\ndefineProps<{\n field: CustomBlockNumberField;\n modelValue: number;\n readOnly?: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update:modelValue\", value: number): void;\n}>();\n\nconst { t } = useI18n();\n</script>\n\n<template>\n <FieldWrapper\n :label=\"field.label\"\n :required=\"field.required\"\n :read-only=\"readOnly\"\n >\n <input\n type=\"number\"\n :class=\"[inputClass, readOnly && 'tpl:opacity-60 tpl:cursor-not-allowed']\"\n :value=\"modelValue\"\n :placeholder=\"field.placeholder\"\n :min=\"field.min\"\n :max=\"field.max\"\n :step=\"field.step\"\n :disabled=\"readOnly\"\n :title=\"readOnly ? t.customBlocks.dataSource.readOnlyTooltip : undefined\"\n @input=\"\n !readOnly &&\n emit(\n 'update:modelValue',\n Number(($event.target as HTMLInputElement).value),\n )\n \"\n />\n </FieldWrapper>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../../composables/useI18n\";\nimport type { CustomBlockRepeatableField } from \"@aswin.dev/types\";\nimport { Plus, Trash2 } from \"@lucide/vue\";\nimport { computed } from \"vue\";\nimport { resolveFieldComponent } from \"./index\";\nimport FieldWrapper from \"./FieldWrapper.vue\";\nimport { addItemBtnClass } from \"../../../constants/styleConstants\";\n\nconst props = defineProps<{\n field: CustomBlockRepeatableField;\n modelValue: Record<string, unknown>[];\n readOnly?: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update:modelValue\", value: Record<string, unknown>[]): void;\n}>();\n\nconst { t } = useI18n();\n\nconst items = computed(() => props.modelValue || []);\n\nconst canAdd = computed(\n () => !props.field.maxItems || items.value.length < props.field.maxItems,\n);\n\nconst canRemove = computed(\n () => !props.field.minItems || items.value.length > props.field.minItems,\n);\n\nfunction addItem(): void {\n if (!canAdd.value || props.readOnly) {\n return;\n }\n\n const newItem: Record<string, unknown> = {};\n for (const subField of props.field.fields) {\n newItem[subField.key] = subField.default ?? \"\";\n }\n\n emit(\"update:modelValue\", [...items.value, newItem]);\n}\n\nfunction removeItem(index: number): void {\n if (!canRemove.value || props.readOnly) {\n return;\n }\n\n const updated = [...items.value];\n updated.splice(index, 1);\n emit(\"update:modelValue\", updated);\n}\n\nfunction updateItemField(index: number, key: string, value: unknown): void {\n const updated = items.value.map((item, i) =>\n i === index ? { ...item, [key]: value } : item,\n );\n emit(\"update:modelValue\", updated);\n}\n</script>\n\n<template>\n <FieldWrapper\n :label=\"field.label\"\n :required=\"field.required\"\n :read-only=\"readOnly\"\n >\n <div class=\"tpl:flex tpl:flex-col tpl:gap-2\">\n <div\n v-for=\"(item, index) in items\"\n :key=\"`${field.key}-${index}`\"\n class=\"tpl:rounded-md tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-hover)] tpl:p-3\"\n >\n <div class=\"tpl:mb-2 tpl:flex tpl:items-center tpl:justify-between\">\n <span\n class=\"tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-text-dim)]\"\n >\n #{{ index + 1 }}\n </span>\n <button\n v-if=\"canRemove && !readOnly\"\n type=\"button\"\n class=\"tpl:flex tpl:size-6 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:text-[var(--tpl-text-muted)] tpl:transition-all tpl:duration-150 tpl:hover:border-[var(--tpl-danger)] tpl:hover:bg-[var(--tpl-danger-light)] tpl:hover:text-[var(--tpl-danger)]\"\n :title=\"t.customBlocks.fields.removeItem\"\n @click=\"removeItem(index)\"\n >\n <Trash2 :size=\"12\" :stroke-width=\"2\" />\n </button>\n </div>\n\n <template v-for=\"subField in field.fields\" :key=\"subField.key\">\n <component\n :is=\"resolveFieldComponent(subField.type)\"\n :field=\"subField\"\n :model-value=\"item[subField.key]\"\n :read-only=\"readOnly\"\n @update:model-value=\"updateItemField(index, subField.key, $event)\"\n />\n </template>\n </div>\n\n <button\n v-if=\"canAdd && !readOnly\"\n type=\"button\"\n :class=\"addItemBtnClass\"\n @click=\"addItem\"\n >\n <Plus :size=\"14\" :stroke-width=\"2\" />\n {{ t.customBlocks.fields.addItem }}\n </button>\n\n <p\n v-if=\"!canAdd && !readOnly\"\n class=\"tpl:m-0 tpl:text-center tpl:text-xs tpl:text-[var(--tpl-text-dim)]\"\n >\n {{ t.customBlocks.fields.maxItemsReached }}\n </p>\n </div>\n </FieldWrapper>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../../composables/useI18n\";\nimport type { CustomBlockRepeatableField } from \"@aswin.dev/types\";\nimport { Plus, Trash2 } from \"@lucide/vue\";\nimport { computed } from \"vue\";\nimport { resolveFieldComponent } from \"./index\";\nimport FieldWrapper from \"./FieldWrapper.vue\";\nimport { addItemBtnClass } from \"../../../constants/styleConstants\";\n\nconst props = defineProps<{\n field: CustomBlockRepeatableField;\n modelValue: Record<string, unknown>[];\n readOnly?: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update:modelValue\", value: Record<string, unknown>[]): void;\n}>();\n\nconst { t } = useI18n();\n\nconst items = computed(() => props.modelValue || []);\n\nconst canAdd = computed(\n () => !props.field.maxItems || items.value.length < props.field.maxItems,\n);\n\nconst canRemove = computed(\n () => !props.field.minItems || items.value.length > props.field.minItems,\n);\n\nfunction addItem(): void {\n if (!canAdd.value || props.readOnly) {\n return;\n }\n\n const newItem: Record<string, unknown> = {};\n for (const subField of props.field.fields) {\n newItem[subField.key] = subField.default ?? \"\";\n }\n\n emit(\"update:modelValue\", [...items.value, newItem]);\n}\n\nfunction removeItem(index: number): void {\n if (!canRemove.value || props.readOnly) {\n return;\n }\n\n const updated = [...items.value];\n updated.splice(index, 1);\n emit(\"update:modelValue\", updated);\n}\n\nfunction updateItemField(index: number, key: string, value: unknown): void {\n const updated = items.value.map((item, i) =>\n i === index ? { ...item, [key]: value } : item,\n );\n emit(\"update:modelValue\", updated);\n}\n</script>\n\n<template>\n <FieldWrapper\n :label=\"field.label\"\n :required=\"field.required\"\n :read-only=\"readOnly\"\n >\n <div class=\"tpl:flex tpl:flex-col tpl:gap-2\">\n <div\n v-for=\"(item, index) in items\"\n :key=\"`${field.key}-${index}`\"\n class=\"tpl:rounded-md tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-hover)] tpl:p-3\"\n >\n <div class=\"tpl:mb-2 tpl:flex tpl:items-center tpl:justify-between\">\n <span\n class=\"tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-text-dim)]\"\n >\n #{{ index + 1 }}\n </span>\n <button\n v-if=\"canRemove && !readOnly\"\n type=\"button\"\n class=\"tpl:flex tpl:size-6 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:text-[var(--tpl-text-muted)] tpl:transition-all tpl:duration-150 tpl:hover:border-[var(--tpl-danger)] tpl:hover:bg-[var(--tpl-danger-light)] tpl:hover:text-[var(--tpl-danger)]\"\n :title=\"t.customBlocks.fields.removeItem\"\n @click=\"removeItem(index)\"\n >\n <Trash2 :size=\"12\" :stroke-width=\"2\" />\n </button>\n </div>\n\n <template v-for=\"subField in field.fields\" :key=\"subField.key\">\n <component\n :is=\"resolveFieldComponent(subField.type)\"\n :field=\"subField\"\n :model-value=\"item[subField.key]\"\n :read-only=\"readOnly\"\n @update:model-value=\"updateItemField(index, subField.key, $event)\"\n />\n </template>\n </div>\n\n <button\n v-if=\"canAdd && !readOnly\"\n type=\"button\"\n :class=\"addItemBtnClass\"\n @click=\"addItem\"\n >\n <Plus :size=\"14\" :stroke-width=\"2\" />\n {{ t.customBlocks.fields.addItem }}\n </button>\n\n <p\n v-if=\"!canAdd && !readOnly\"\n class=\"tpl:m-0 tpl:text-center tpl:text-xs tpl:text-[var(--tpl-text-dim)]\"\n >\n {{ t.customBlocks.fields.maxItemsReached }}\n </p>\n </div>\n </FieldWrapper>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../../composables/useI18n\";\nimport type { CustomBlockSelectField } from \"@aswin.dev/types\";\nimport { inputClass } from \"../../../constants/styleConstants\";\nimport FieldWrapper from \"./FieldWrapper.vue\";\n\ndefineProps<{\n field: CustomBlockSelectField;\n modelValue: string;\n readOnly?: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update:modelValue\", value: string): void;\n}>();\n\nconst { t } = useI18n();\n</script>\n\n<template>\n <FieldWrapper\n :label=\"field.label\"\n :required=\"field.required\"\n :read-only=\"readOnly\"\n >\n <select\n :class=\"[inputClass, readOnly && 'tpl:opacity-60 tpl:cursor-not-allowed']\"\n :value=\"modelValue\"\n :disabled=\"readOnly\"\n :title=\"readOnly ? t.customBlocks.dataSource.readOnlyTooltip : undefined\"\n @change=\"\n !readOnly &&\n emit('update:modelValue', ($event.target as HTMLSelectElement).value)\n \"\n >\n <option\n v-for=\"option in field.options\"\n :key=\"option.value\"\n :value=\"option.value\"\n >\n {{ option.label }}\n </option>\n </select>\n </FieldWrapper>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../../composables/useI18n\";\nimport type { CustomBlockSelectField } from \"@aswin.dev/types\";\nimport { inputClass } from \"../../../constants/styleConstants\";\nimport FieldWrapper from \"./FieldWrapper.vue\";\n\ndefineProps<{\n field: CustomBlockSelectField;\n modelValue: string;\n readOnly?: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update:modelValue\", value: string): void;\n}>();\n\nconst { t } = useI18n();\n</script>\n\n<template>\n <FieldWrapper\n :label=\"field.label\"\n :required=\"field.required\"\n :read-only=\"readOnly\"\n >\n <select\n :class=\"[inputClass, readOnly && 'tpl:opacity-60 tpl:cursor-not-allowed']\"\n :value=\"modelValue\"\n :disabled=\"readOnly\"\n :title=\"readOnly ? t.customBlocks.dataSource.readOnlyTooltip : undefined\"\n @change=\"\n !readOnly &&\n emit('update:modelValue', ($event.target as HTMLSelectElement).value)\n \"\n >\n <option\n v-for=\"option in field.options\"\n :key=\"option.value\"\n :value=\"option.value\"\n >\n {{ option.label }}\n </option>\n </select>\n </FieldWrapper>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../../composables/useI18n\";\nimport type { CustomBlockTextField } from \"@aswin.dev/types\";\nimport { inputClass } from \"../../../constants/styleConstants\";\nimport FieldWrapper from \"./FieldWrapper.vue\";\nimport MergeTagInput from \"../../MergeTagInput.vue\";\n\ndefineProps<{\n field: CustomBlockTextField;\n modelValue: string;\n readOnly?: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update:modelValue\", value: string): void;\n}>();\n\nconst { t } = useI18n();\n</script>\n\n<template>\n <FieldWrapper\n :label=\"field.label\"\n :required=\"field.required\"\n :read-only=\"readOnly\"\n >\n <input\n v-if=\"readOnly\"\n type=\"text\"\n :class=\"[inputClass, 'tpl:opacity-60 tpl:cursor-not-allowed']\"\n :value=\"modelValue\"\n :placeholder=\"field.placeholder\"\n disabled\n :title=\"t.customBlocks.dataSource.readOnlyTooltip\"\n />\n <MergeTagInput\n v-else\n :model-value=\"modelValue\"\n :placeholder=\"field.placeholder\"\n @update:model-value=\"emit('update:modelValue', $event)\"\n />\n </FieldWrapper>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../../composables/useI18n\";\nimport type { CustomBlockTextField } from \"@aswin.dev/types\";\nimport { inputClass } from \"../../../constants/styleConstants\";\nimport FieldWrapper from \"./FieldWrapper.vue\";\nimport MergeTagInput from \"../../MergeTagInput.vue\";\n\ndefineProps<{\n field: CustomBlockTextField;\n modelValue: string;\n readOnly?: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update:modelValue\", value: string): void;\n}>();\n\nconst { t } = useI18n();\n</script>\n\n<template>\n <FieldWrapper\n :label=\"field.label\"\n :required=\"field.required\"\n :read-only=\"readOnly\"\n >\n <input\n v-if=\"readOnly\"\n type=\"text\"\n :class=\"[inputClass, 'tpl:opacity-60 tpl:cursor-not-allowed']\"\n :value=\"modelValue\"\n :placeholder=\"field.placeholder\"\n disabled\n :title=\"t.customBlocks.dataSource.readOnlyTooltip\"\n />\n <MergeTagInput\n v-else\n :model-value=\"modelValue\"\n :placeholder=\"field.placeholder\"\n @update:model-value=\"emit('update:modelValue', $event)\"\n />\n </FieldWrapper>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../../composables/useI18n\";\nimport type { CustomBlockTextareaField } from \"@aswin.dev/types\";\nimport FieldWrapper from \"./FieldWrapper.vue\";\nimport MergeTagTextarea from \"../../MergeTagTextarea.vue\";\n\ndefineProps<{\n field: CustomBlockTextareaField;\n modelValue: string;\n readOnly?: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update:modelValue\", value: string): void;\n}>();\n\nconst { t } = useI18n();\n\nconst readOnlyTextareaClass =\n \"tpl:w-full tpl:resize-y tpl:rounded-md tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:px-3 tpl:py-2 tpl:text-sm tpl:text-[var(--tpl-text)] tpl:outline-none tpl:opacity-60 tpl:cursor-not-allowed\";\n</script>\n\n<template>\n <FieldWrapper\n :label=\"field.label\"\n :required=\"field.required\"\n :read-only=\"readOnly\"\n >\n <textarea\n v-if=\"readOnly\"\n :value=\"modelValue\"\n :placeholder=\"field.placeholder\"\n rows=\"3\"\n disabled\n :title=\"t.customBlocks.dataSource.readOnlyTooltip\"\n :class=\"readOnlyTextareaClass\"\n />\n <MergeTagTextarea\n v-else\n :model-value=\"modelValue\"\n :placeholder=\"field.placeholder\"\n @update:model-value=\"emit('update:modelValue', $event)\"\n />\n </FieldWrapper>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../../composables/useI18n\";\nimport type { CustomBlockTextareaField } from \"@aswin.dev/types\";\nimport FieldWrapper from \"./FieldWrapper.vue\";\nimport MergeTagTextarea from \"../../MergeTagTextarea.vue\";\n\ndefineProps<{\n field: CustomBlockTextareaField;\n modelValue: string;\n readOnly?: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update:modelValue\", value: string): void;\n}>();\n\nconst { t } = useI18n();\n\nconst readOnlyTextareaClass =\n \"tpl:w-full tpl:resize-y tpl:rounded-md tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:px-3 tpl:py-2 tpl:text-sm tpl:text-[var(--tpl-text)] tpl:outline-none tpl:opacity-60 tpl:cursor-not-allowed\";\n</script>\n\n<template>\n <FieldWrapper\n :label=\"field.label\"\n :required=\"field.required\"\n :read-only=\"readOnly\"\n >\n <textarea\n v-if=\"readOnly\"\n :value=\"modelValue\"\n :placeholder=\"field.placeholder\"\n rows=\"3\"\n disabled\n :title=\"t.customBlocks.dataSource.readOnlyTooltip\"\n :class=\"readOnlyTextareaClass\"\n />\n <MergeTagTextarea\n v-else\n :model-value=\"modelValue\"\n :placeholder=\"field.placeholder\"\n @update:model-value=\"emit('update:modelValue', $event)\"\n />\n </FieldWrapper>\n</template>\n","import type { CustomBlockFieldType } from \"@aswin.dev/types\";\nimport type { Component } from \"vue\";\nimport BooleanField from \"./BooleanField.vue\";\nimport ColorField from \"./ColorField.vue\";\nimport ImageField from \"./ImageField.vue\";\nimport NumberField from \"./NumberField.vue\";\nimport RepeatableField from \"./RepeatableField.vue\";\nimport SelectField from \"./SelectField.vue\";\nimport TextField from \"./TextField.vue\";\nimport TextareaField from \"./TextareaField.vue\";\n\nexport const fieldComponentMap: Record<CustomBlockFieldType, Component> = {\n text: TextField,\n textarea: TextareaField,\n image: ImageField,\n color: ColorField,\n number: NumberField,\n select: SelectField,\n boolean: BooleanField,\n repeatable: RepeatableField,\n};\n\nexport function resolveFieldComponent(type: CustomBlockFieldType): Component {\n return fieldComponentMap[type] ?? TextField;\n}\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport { useDataSourceFetch } from \"@aswin.dev/core\";\nimport type { CustomBlock, CustomBlockField } from \"@aswin.dev/types\";\nimport { CircleAlert, RefreshCw } from \"@lucide/vue\";\nimport { computed, inject } from \"vue\";\nimport { CUSTOM_BLOCK_DEFINITIONS_KEY } from \"../../keys\";\nimport { resolveFieldComponent } from \"./fields\";\n\nconst props = defineProps<{\n block: CustomBlock;\n}>();\n\nconst emit = defineEmits<{\n (e: \"updateFieldValues\", values: Record<string, unknown>): void;\n (e: \"updateDataSourceFetched\", fetched: boolean): void;\n}>();\n\nconst { t } = useI18n();\n\nconst customBlockDefinitions = inject(CUSTOM_BLOCK_DEFINITIONS_KEY, []);\n\nconst definition = computed(() =>\n customBlockDefinitions.find((d) => d.type === props.block.customType),\n);\n\nconst blockRef = computed(() => props.block);\n\nconst {\n isFetching,\n fetchError,\n fetch: fetchData,\n hasDataSource,\n needsFetch,\n} = useDataSourceFetch({\n definition,\n block: blockRef,\n onUpdate: (fieldValues, fetched) => {\n emit(\"updateFieldValues\", fieldValues);\n emit(\"updateDataSourceFetched\", fetched);\n },\n});\n\nfunction isFieldReadOnly(field: CustomBlockField): boolean {\n return (\n field.readOnly === true &&\n hasDataSource.value &&\n !!props.block.dataSourceFetched\n );\n}\n\nfunction updateField(key: string, value: unknown): void {\n emit(\"updateFieldValues\", {\n ...props.block.fieldValues,\n [key]: value,\n });\n}\n</script>\n\n<template>\n <div v-if=\"!definition\" class=\"tpl:p-4\">\n <p\n class=\"tpl:m-0 tpl:text-center tpl:text-sm tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ t.customBlocks.toolbar.noDefinition }}\n </p>\n </div>\n\n <div v-else>\n <p\n v-if=\"definition.description\"\n class=\"tpl:m-0 tpl:mb-3 tpl:text-xs tpl:text-[var(--tpl-text-dim)]\"\n >\n {{ definition.description }}\n </p>\n\n <div v-if=\"hasDataSource\" class=\"tpl:mb-4\">\n <!-- Before fetch: prominent button -->\n <button\n v-if=\"needsFetch && !isFetching\"\n type=\"button\"\n class=\"tpl:flex tpl:w-full tpl:items-center tpl:justify-center tpl:gap-2 tpl:rounded-md tpl:px-3 tpl:py-2.5 tpl:text-sm tpl:font-medium tpl:text-[var(--tpl-bg)] tpl:transition-all tpl:duration-150 tpl:bg-[var(--tpl-primary)]\"\n @click=\"fetchData\"\n >\n {{\n definition?.dataSource?.label || t.customBlocks.dataSource.fetchButton\n }}\n </button>\n\n <!-- Loading / Change button -->\n <div v-else class=\"tpl:flex tpl:h-[32px] tpl:items-center\">\n <div\n v-if=\"isFetching\"\n class=\"tpl:w-full tpl:text-center tpl:text-xs tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ t.customBlocks.dataSource.fetching }}\n </div>\n <button\n v-else\n type=\"button\"\n class=\"tpl:flex tpl:w-full tpl:items-center tpl:justify-center tpl:gap-1.5 tpl:rounded-md tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-text-muted)] tpl:transition-all tpl:duration-150 tpl:hover:border-[var(--tpl-primary)] tpl:hover:text-[var(--tpl-primary)]\"\n @click=\"fetchData\"\n >\n <RefreshCw :size=\"12\" />\n {{ t.customBlocks.dataSource.changeButton }}\n </button>\n </div>\n\n <p\n v-if=\"fetchError\"\n class=\"tpl:m-0 tpl:mt-2 tpl:flex tpl:items-center tpl:gap-1.5 tpl:text-xs tpl:text-[var(--tpl-danger)]\"\n >\n <CircleAlert :size=\"14\" class=\"tpl:shrink-0\" />\n {{ t.customBlocks.dataSource.fetchError }}\n </p>\n </div>\n\n <template v-for=\"field in definition.fields\" :key=\"field.key\">\n <component\n :is=\"resolveFieldComponent(field.type)\"\n :field=\"field\"\n :model-value=\"block.fieldValues[field.key]\"\n :read-only=\"isFieldReadOnly(field)\"\n @update:model-value=\"updateField(field.key, $event)\"\n />\n </template>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport { useDataSourceFetch } from \"@aswin.dev/core\";\nimport type { CustomBlock, CustomBlockField } from \"@aswin.dev/types\";\nimport { CircleAlert, RefreshCw } from \"@lucide/vue\";\nimport { computed, inject } from \"vue\";\nimport { CUSTOM_BLOCK_DEFINITIONS_KEY } from \"../../keys\";\nimport { resolveFieldComponent } from \"./fields\";\n\nconst props = defineProps<{\n block: CustomBlock;\n}>();\n\nconst emit = defineEmits<{\n (e: \"updateFieldValues\", values: Record<string, unknown>): void;\n (e: \"updateDataSourceFetched\", fetched: boolean): void;\n}>();\n\nconst { t } = useI18n();\n\nconst customBlockDefinitions = inject(CUSTOM_BLOCK_DEFINITIONS_KEY, []);\n\nconst definition = computed(() =>\n customBlockDefinitions.find((d) => d.type === props.block.customType),\n);\n\nconst blockRef = computed(() => props.block);\n\nconst {\n isFetching,\n fetchError,\n fetch: fetchData,\n hasDataSource,\n needsFetch,\n} = useDataSourceFetch({\n definition,\n block: blockRef,\n onUpdate: (fieldValues, fetched) => {\n emit(\"updateFieldValues\", fieldValues);\n emit(\"updateDataSourceFetched\", fetched);\n },\n});\n\nfunction isFieldReadOnly(field: CustomBlockField): boolean {\n return (\n field.readOnly === true &&\n hasDataSource.value &&\n !!props.block.dataSourceFetched\n );\n}\n\nfunction updateField(key: string, value: unknown): void {\n emit(\"updateFieldValues\", {\n ...props.block.fieldValues,\n [key]: value,\n });\n}\n</script>\n\n<template>\n <div v-if=\"!definition\" class=\"tpl:p-4\">\n <p\n class=\"tpl:m-0 tpl:text-center tpl:text-sm tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ t.customBlocks.toolbar.noDefinition }}\n </p>\n </div>\n\n <div v-else>\n <p\n v-if=\"definition.description\"\n class=\"tpl:m-0 tpl:mb-3 tpl:text-xs tpl:text-[var(--tpl-text-dim)]\"\n >\n {{ definition.description }}\n </p>\n\n <div v-if=\"hasDataSource\" class=\"tpl:mb-4\">\n <!-- Before fetch: prominent button -->\n <button\n v-if=\"needsFetch && !isFetching\"\n type=\"button\"\n class=\"tpl:flex tpl:w-full tpl:items-center tpl:justify-center tpl:gap-2 tpl:rounded-md tpl:px-3 tpl:py-2.5 tpl:text-sm tpl:font-medium tpl:text-[var(--tpl-bg)] tpl:transition-all tpl:duration-150 tpl:bg-[var(--tpl-primary)]\"\n @click=\"fetchData\"\n >\n {{\n definition?.dataSource?.label || t.customBlocks.dataSource.fetchButton\n }}\n </button>\n\n <!-- Loading / Change button -->\n <div v-else class=\"tpl:flex tpl:h-[32px] tpl:items-center\">\n <div\n v-if=\"isFetching\"\n class=\"tpl:w-full tpl:text-center tpl:text-xs tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ t.customBlocks.dataSource.fetching }}\n </div>\n <button\n v-else\n type=\"button\"\n class=\"tpl:flex tpl:w-full tpl:items-center tpl:justify-center tpl:gap-1.5 tpl:rounded-md tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-text-muted)] tpl:transition-all tpl:duration-150 tpl:hover:border-[var(--tpl-primary)] tpl:hover:text-[var(--tpl-primary)]\"\n @click=\"fetchData\"\n >\n <RefreshCw :size=\"12\" />\n {{ t.customBlocks.dataSource.changeButton }}\n </button>\n </div>\n\n <p\n v-if=\"fetchError\"\n class=\"tpl:m-0 tpl:mt-2 tpl:flex tpl:items-center tpl:gap-1.5 tpl:text-xs tpl:text-[var(--tpl-danger)]\"\n >\n <CircleAlert :size=\"14\" class=\"tpl:shrink-0\" />\n {{ t.customBlocks.dataSource.fetchError }}\n </p>\n </div>\n\n <template v-for=\"field in definition.fields\" :key=\"field.key\">\n <component\n :is=\"resolveFieldComponent(field.type)\"\n :field=\"field\"\n :model-value=\"block.fieldValues[field.key]\"\n :read-only=\"isFieldReadOnly(field)\"\n @update:model-value=\"updateField(field.key, $event)\"\n />\n </template>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport ColorPicker from \"../ColorPicker.vue\";\nimport SlidingPillSelect from \"../SlidingPillSelect.vue\";\nimport { useI18n } from \"../../composables/useI18n\";\nimport {\n inputGroupInputClass,\n inputSuffixClass,\n labelClass,\n} from \"../../constants/styleConstants\";\nimport type { DividerBlock } from \"@aswin.dev/types\";\n\ndefineProps<{\n block: DividerBlock;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update\", updates: Partial<DividerBlock>): void;\n}>();\n\nconst { t } = useI18n();\n\nfunction updateField(field: string, value: unknown): void {\n emit(\"update\", { [field]: value } as Partial<DividerBlock>);\n}\n</script>\n\n<template>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.divider.style }}</label>\n <SlidingPillSelect\n :options=\"[\n { value: 'solid', label: t.divider.solid },\n { value: 'dashed', label: t.divider.dashed },\n { value: 'dotted', label: t.divider.dotted },\n ]\"\n :model-value=\"block.lineStyle\"\n @update:model-value=\"updateField('lineStyle', $event)\"\n />\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.divider.color }}</label>\n <ColorPicker\n :model-value=\"block.color\"\n @update:model-value=\"updateField('color', $event)\"\n />\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.divider.thickness }}</label>\n <div class=\"tpl:flex tpl:items-stretch\">\n <input\n type=\"number\"\n :class=\"inputGroupInputClass\"\n :value=\"block.thickness\"\n min=\"1\"\n max=\"10\"\n @input=\"\n updateField(\n 'thickness',\n Number(($event.target as HTMLInputElement).value),\n )\n \"\n />\n <span :class=\"inputSuffixClass\">px</span>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport ColorPicker from \"../ColorPicker.vue\";\nimport SlidingPillSelect from \"../SlidingPillSelect.vue\";\nimport { useI18n } from \"../../composables/useI18n\";\nimport {\n inputGroupInputClass,\n inputSuffixClass,\n labelClass,\n} from \"../../constants/styleConstants\";\nimport type { DividerBlock } from \"@aswin.dev/types\";\n\ndefineProps<{\n block: DividerBlock;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update\", updates: Partial<DividerBlock>): void;\n}>();\n\nconst { t } = useI18n();\n\nfunction updateField(field: string, value: unknown): void {\n emit(\"update\", { [field]: value } as Partial<DividerBlock>);\n}\n</script>\n\n<template>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.divider.style }}</label>\n <SlidingPillSelect\n :options=\"[\n { value: 'solid', label: t.divider.solid },\n { value: 'dashed', label: t.divider.dashed },\n { value: 'dotted', label: t.divider.dotted },\n ]\"\n :model-value=\"block.lineStyle\"\n @update:model-value=\"updateField('lineStyle', $event)\"\n />\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.divider.color }}</label>\n <ColorPicker\n :model-value=\"block.color\"\n @update:model-value=\"updateField('color', $event)\"\n />\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.divider.thickness }}</label>\n <div class=\"tpl:flex tpl:items-stretch\">\n <input\n type=\"number\"\n :class=\"inputGroupInputClass\"\n :value=\"block.thickness\"\n min=\"1\"\n max=\"10\"\n @input=\"\n updateField(\n 'thickness',\n Number(($event.target as HTMLInputElement).value),\n )\n \"\n />\n <span :class=\"inputSuffixClass\">px</span>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport { labelClass, monoTextareaClass } from \"../../constants/styleConstants\";\nimport type { HtmlBlock } from \"@aswin.dev/types\";\nimport { Info } from \"@lucide/vue\";\n\ndefineProps<{\n block: HtmlBlock;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update\", updates: Partial<HtmlBlock>): void;\n}>();\n\nconst { t } = useI18n();\n</script>\n\n<template>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.html.content }}</label>\n <textarea\n :value=\"block.content\"\n :placeholder=\"'<div>...</div>'\"\n rows=\"10\"\n :class=\"monoTextareaClass\"\n @input=\"\n emit('update', {\n content: ($event.target as HTMLTextAreaElement).value,\n })\n \"\n />\n <p\n class=\"tpl:mt-1.5 tpl:flex tpl:items-start tpl:gap-1.5 tpl:text-[11px] tpl:text-[var(--tpl-text-dim)]\"\n >\n <Info :size=\"12\" class=\"tpl:mt-0.5 tpl:shrink-0\" />\n {{ t.html.sanitizationHint }}\n </p>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport { labelClass, monoTextareaClass } from \"../../constants/styleConstants\";\nimport type { HtmlBlock } from \"@aswin.dev/types\";\nimport { Info } from \"@lucide/vue\";\n\ndefineProps<{\n block: HtmlBlock;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update\", updates: Partial<HtmlBlock>): void;\n}>();\n\nconst { t } = useI18n();\n</script>\n\n<template>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.html.content }}</label>\n <textarea\n :value=\"block.content\"\n :placeholder=\"'<div>...</div>'\"\n rows=\"10\"\n :class=\"monoTextareaClass\"\n @input=\"\n emit('update', {\n content: ($event.target as HTMLTextAreaElement).value,\n })\n \"\n />\n <p\n class=\"tpl:mt-1.5 tpl:flex tpl:items-start tpl:gap-1.5 tpl:text-[11px] tpl:text-[var(--tpl-text-dim)]\"\n >\n <Info :size=\"12\" class=\"tpl:mt-0.5 tpl:shrink-0\" />\n {{ t.html.sanitizationHint }}\n </p>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport MergeTagInput from \"../MergeTagInput.vue\";\nimport SlidingPillSelect from \"../SlidingPillSelect.vue\";\nimport { useI18n } from \"../../composables/useI18n\";\nimport { inputClass, labelClass } from \"../../constants/styleConstants\";\nimport type { ImageBlock } from \"@aswin.dev/types\";\nimport { containsMergeTag, SYNTAX_PRESETS } from \"@aswin.dev/types\";\nimport { Image } from \"@lucide/vue\";\nimport { computed, inject, ref } from \"vue\";\nimport { ON_REQUEST_MEDIA_KEY, MERGE_TAG_SYNTAX_KEY } from \"../../keys\";\nimport { useTimeoutFn } from \"@vueuse/core\";\n\ndefineProps<{\n block: ImageBlock;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update\", updates: Partial<ImageBlock>): void;\n}>();\n\nconst { t } = useI18n();\nconst onRequestMedia = inject(ON_REQUEST_MEDIA_KEY, null);\nconst mergeTagSyntax = inject(MERGE_TAG_SYNTAX_KEY, SYNTAX_PRESETS.liquid);\n\nconst canBrowseMedia = computed(() => !!onRequestMedia);\n\nconst pulseSrc = ref(false);\nconst pulseAlt = ref(false);\n\nconst { start: startPulseSrc } = useTimeoutFn(\n () => {\n pulseSrc.value = false;\n },\n 1000,\n { immediate: false },\n);\n\nfunction updateField(field: string, value: unknown): void {\n emit(\"update\", { [field]: value } as Partial<ImageBlock>);\n}\n\nasync function openMediaBrowser(): Promise<void> {\n const result = await onRequestMedia?.({ accept: [\"images\"] });\n if (result) {\n updateField(\"src\", result.url);\n if (result.alt) {\n updateField(\"alt\", result.alt);\n pulseAlt.value = true;\n }\n pulseSrc.value = true;\n startPulseSrc();\n }\n}\n</script>\n\n<template>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.image.imageUrl }}</label>\n <MergeTagInput\n :model-value=\"block.src\"\n type=\"url\"\n :placeholder=\"t.image.imageUrlPlaceholder\"\n :pulse=\"pulseSrc\"\n @update:model-value=\"updateField('src', $event)\"\n />\n <button\n v-if=\"canBrowseMedia\"\n class=\"tpl:mt-2 tpl:flex tpl:w-full tpl:items-center tpl:justify-center tpl:gap-1.5 tpl:rounded-md tpl:border tpl:px-3 tpl:py-2 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-border);\n color: var(--tpl-primary);\n background-color: var(--tpl-bg);\n \"\n @click=\"openMediaBrowser\"\n >\n <Image :size=\"14\" :stroke-width=\"1.5\" />\n {{ t.image.browseMedia }}\n </button>\n </div>\n <div v-if=\"containsMergeTag(block.src, mergeTagSyntax)\" class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\"\n >{{ t.image.placeholderUrl }}\n <span class=\"tpl:font-normal tpl:text-[var(--tpl-text-dim)]\">{{\n \"(optional)\"\n }}</span>\n </label>\n <input\n type=\"url\"\n :class=\"inputClass\"\n :value=\"block.placeholderUrl || ''\"\n :placeholder=\"t.image.placeholderUrlPlaceholder\"\n @input=\"\n updateField('placeholderUrl', ($event.target as HTMLInputElement).value)\n \"\n />\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.image.altText }}</label>\n <MergeTagInput\n :model-value=\"block.alt\"\n type=\"text\"\n :placeholder=\"t.image.altTextPlaceholder\"\n :pulse=\"pulseAlt\"\n :disabled=\"block.decorative === true\"\n @update:model-value=\"updateField('alt', $event)\"\n />\n <label\n class=\"tpl:mt-2 tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:text-[12px] tpl:text-[var(--tpl-text-muted)]\"\n >\n <input\n type=\"checkbox\"\n class=\"tpl:size-3.5 tpl:cursor-pointer tpl:accent-[var(--tpl-primary)]\"\n :checked=\"block.decorative === true\"\n @change=\"\n updateField('decorative', ($event.target as HTMLInputElement).checked)\n \"\n />\n <span>\n {{ t.image.decorative }}\n <span class=\"tpl:block tpl:text-[var(--tpl-text-dim)]\">\n {{ t.image.decorativeHint }}\n </span>\n </span>\n </label>\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.image.width }}</label>\n <select\n :class=\"inputClass\"\n :value=\"block.width\"\n @change=\"\n updateField(\n 'width',\n ($event.target as HTMLSelectElement).value === 'full'\n ? 'full'\n : Number(($event.target as HTMLSelectElement).value),\n )\n \"\n >\n <option value=\"full\">{{ t.image.fullWidth }}</option>\n <option value=\"300\">300px</option>\n <option value=\"400\">400px</option>\n <option value=\"500\">500px</option>\n </select>\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.title.align }}</label>\n <SlidingPillSelect\n :options=\"[\n { value: 'left', label: t.title.alignLeft },\n { value: 'center', label: t.title.alignCenter },\n { value: 'right', label: t.title.alignRight },\n ]\"\n :model-value=\"block.align\"\n @update:model-value=\"updateField('align', $event)\"\n />\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.image.linkUrl }}</label>\n <MergeTagInput\n :model-value=\"block.linkUrl || ''\"\n type=\"url\"\n :placeholder=\"t.image.imageUrlPlaceholder\"\n @update:model-value=\"updateField('linkUrl', $event)\"\n />\n <label\n v-if=\"block.linkUrl\"\n class=\"tpl:mt-2 tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:text-[12px] tpl:text-[var(--tpl-text-muted)]\"\n >\n <input\n type=\"checkbox\"\n class=\"tpl:size-3.5 tpl:cursor-pointer tpl:accent-[var(--tpl-primary)]\"\n :checked=\"block.linkOpenInNewTab ?? false\"\n @change=\"\n updateField(\n 'linkOpenInNewTab',\n ($event.target as HTMLInputElement).checked,\n )\n \"\n />\n {{ t.image.openInNewTab }}\n </label>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport MergeTagInput from \"../MergeTagInput.vue\";\nimport SlidingPillSelect from \"../SlidingPillSelect.vue\";\nimport { useI18n } from \"../../composables/useI18n\";\nimport { inputClass, labelClass } from \"../../constants/styleConstants\";\nimport type { ImageBlock } from \"@aswin.dev/types\";\nimport { containsMergeTag, SYNTAX_PRESETS } from \"@aswin.dev/types\";\nimport { Image } from \"@lucide/vue\";\nimport { computed, inject, ref } from \"vue\";\nimport { ON_REQUEST_MEDIA_KEY, MERGE_TAG_SYNTAX_KEY } from \"../../keys\";\nimport { useTimeoutFn } from \"@vueuse/core\";\n\ndefineProps<{\n block: ImageBlock;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update\", updates: Partial<ImageBlock>): void;\n}>();\n\nconst { t } = useI18n();\nconst onRequestMedia = inject(ON_REQUEST_MEDIA_KEY, null);\nconst mergeTagSyntax = inject(MERGE_TAG_SYNTAX_KEY, SYNTAX_PRESETS.liquid);\n\nconst canBrowseMedia = computed(() => !!onRequestMedia);\n\nconst pulseSrc = ref(false);\nconst pulseAlt = ref(false);\n\nconst { start: startPulseSrc } = useTimeoutFn(\n () => {\n pulseSrc.value = false;\n },\n 1000,\n { immediate: false },\n);\n\nfunction updateField(field: string, value: unknown): void {\n emit(\"update\", { [field]: value } as Partial<ImageBlock>);\n}\n\nasync function openMediaBrowser(): Promise<void> {\n const result = await onRequestMedia?.({ accept: [\"images\"] });\n if (result) {\n updateField(\"src\", result.url);\n if (result.alt) {\n updateField(\"alt\", result.alt);\n pulseAlt.value = true;\n }\n pulseSrc.value = true;\n startPulseSrc();\n }\n}\n</script>\n\n<template>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.image.imageUrl }}</label>\n <MergeTagInput\n :model-value=\"block.src\"\n type=\"url\"\n :placeholder=\"t.image.imageUrlPlaceholder\"\n :pulse=\"pulseSrc\"\n @update:model-value=\"updateField('src', $event)\"\n />\n <button\n v-if=\"canBrowseMedia\"\n class=\"tpl:mt-2 tpl:flex tpl:w-full tpl:items-center tpl:justify-center tpl:gap-1.5 tpl:rounded-md tpl:border tpl:px-3 tpl:py-2 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-border);\n color: var(--tpl-primary);\n background-color: var(--tpl-bg);\n \"\n @click=\"openMediaBrowser\"\n >\n <Image :size=\"14\" :stroke-width=\"1.5\" />\n {{ t.image.browseMedia }}\n </button>\n </div>\n <div v-if=\"containsMergeTag(block.src, mergeTagSyntax)\" class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\"\n >{{ t.image.placeholderUrl }}\n <span class=\"tpl:font-normal tpl:text-[var(--tpl-text-dim)]\">{{\n \"(optional)\"\n }}</span>\n </label>\n <input\n type=\"url\"\n :class=\"inputClass\"\n :value=\"block.placeholderUrl || ''\"\n :placeholder=\"t.image.placeholderUrlPlaceholder\"\n @input=\"\n updateField('placeholderUrl', ($event.target as HTMLInputElement).value)\n \"\n />\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.image.altText }}</label>\n <MergeTagInput\n :model-value=\"block.alt\"\n type=\"text\"\n :placeholder=\"t.image.altTextPlaceholder\"\n :pulse=\"pulseAlt\"\n :disabled=\"block.decorative === true\"\n @update:model-value=\"updateField('alt', $event)\"\n />\n <label\n class=\"tpl:mt-2 tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:text-[12px] tpl:text-[var(--tpl-text-muted)]\"\n >\n <input\n type=\"checkbox\"\n class=\"tpl:size-3.5 tpl:cursor-pointer tpl:accent-[var(--tpl-primary)]\"\n :checked=\"block.decorative === true\"\n @change=\"\n updateField('decorative', ($event.target as HTMLInputElement).checked)\n \"\n />\n <span>\n {{ t.image.decorative }}\n <span class=\"tpl:block tpl:text-[var(--tpl-text-dim)]\">\n {{ t.image.decorativeHint }}\n </span>\n </span>\n </label>\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.image.width }}</label>\n <select\n :class=\"inputClass\"\n :value=\"block.width\"\n @change=\"\n updateField(\n 'width',\n ($event.target as HTMLSelectElement).value === 'full'\n ? 'full'\n : Number(($event.target as HTMLSelectElement).value),\n )\n \"\n >\n <option value=\"full\">{{ t.image.fullWidth }}</option>\n <option value=\"300\">300px</option>\n <option value=\"400\">400px</option>\n <option value=\"500\">500px</option>\n </select>\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.title.align }}</label>\n <SlidingPillSelect\n :options=\"[\n { value: 'left', label: t.title.alignLeft },\n { value: 'center', label: t.title.alignCenter },\n { value: 'right', label: t.title.alignRight },\n ]\"\n :model-value=\"block.align\"\n @update:model-value=\"updateField('align', $event)\"\n />\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.image.linkUrl }}</label>\n <MergeTagInput\n :model-value=\"block.linkUrl || ''\"\n type=\"url\"\n :placeholder=\"t.image.imageUrlPlaceholder\"\n @update:model-value=\"updateField('linkUrl', $event)\"\n />\n <label\n v-if=\"block.linkUrl\"\n class=\"tpl:mt-2 tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:text-[12px] tpl:text-[var(--tpl-text-muted)]\"\n >\n <input\n type=\"checkbox\"\n class=\"tpl:size-3.5 tpl:cursor-pointer tpl:accent-[var(--tpl-primary)]\"\n :checked=\"block.linkOpenInNewTab ?? false\"\n @change=\"\n updateField(\n 'linkOpenInNewTab',\n ($event.target as HTMLInputElement).checked,\n )\n \"\n />\n {{ t.image.openInNewTab }}\n </label>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport ColorPicker from \"../ColorPicker.vue\";\nimport MergeTagInput from \"../MergeTagInput.vue\";\nimport SlidingPillSelect from \"../SlidingPillSelect.vue\";\nimport FieldRow from \"./FieldRow.vue\";\nimport NumberWithSuffix from \"./NumberWithSuffix.vue\";\nimport { useI18n } from \"../../composables/useI18n\";\nimport {\n inputClass,\n labelClass,\n removeItemBtnClass,\n addItemBtnClass,\n} from \"../../constants/styleConstants\";\nimport type { MenuBlock, MenuItemData } from \"@aswin.dev/types\";\nimport { generateId } from \"@aswin.dev/types\";\nimport { AlignCenter, AlignLeft, AlignRight, Plus, X } from \"@lucide/vue\";\nimport { computed } from \"vue\";\n\nconst props = defineProps<{\n block: MenuBlock;\n fontFamilies: Array<{ value: string; label: string }>;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update\", updates: Partial<MenuBlock>): void;\n}>();\n\nconst { t } = useI18n();\n\nconst ITEM_TOGGLES = computed(() => [\n { key: \"openInNewTab\" as const, label: t.menu.openInNewTab },\n { key: \"bold\" as const, label: t.menu.bold },\n { key: \"underline\" as const, label: t.menu.underline },\n]);\n\nconst ALIGN_OPTIONS = computed(() => [\n { value: \"left\", label: t.title.alignLeft, icon: AlignLeft },\n { value: \"center\", label: t.title.alignCenter, icon: AlignCenter },\n { value: \"right\", label: t.title.alignRight, icon: AlignRight },\n]);\n\nfunction updateField(field: keyof MenuBlock, value: unknown): void {\n emit(\"update\", { [field]: value } as Partial<MenuBlock>);\n}\n\nfunction addMenuItem(): void {\n const newItem: MenuItemData = {\n id: generateId(),\n text: \"\",\n url: \"\",\n openInNewTab: false,\n bold: false,\n underline: false,\n };\n emit(\"update\", { items: [...props.block.items, newItem] });\n}\n\nfunction updateMenuItem(\n itemId: string,\n field: keyof MenuItemData,\n value: unknown,\n): void {\n const updatedItems = props.block.items.map((item) =>\n item.id === itemId ? { ...item, [field]: value } : item,\n );\n emit(\"update\", { items: updatedItems });\n}\n\nfunction removeMenuItem(itemId: string): void {\n emit(\"update\", {\n items: props.block.items.filter((item) => item.id !== itemId),\n });\n}\n</script>\n\n<template>\n <FieldRow :label=\"t.menu.items\">\n <div class=\"tpl:flex tpl:flex-col tpl:gap-2\">\n <div\n v-for=\"item in block.items\"\n :key=\"item.id\"\n class=\"tpl:flex tpl:flex-col tpl:gap-1.5 tpl:rounded-md tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-hover)] tpl:p-2\"\n >\n <div class=\"tpl:flex tpl:items-center tpl:gap-2\">\n <input\n type=\"text\"\n :class=\"inputClass\"\n class=\"tpl:flex-1\"\n :value=\"item.text\"\n :placeholder=\"t.menu.text\"\n @input=\"\n updateMenuItem(\n item.id,\n 'text',\n ($event.target as HTMLInputElement).value,\n )\n \"\n />\n <button\n :class=\"removeItemBtnClass\"\n :title=\"t.menu.removeItem\"\n @click=\"removeMenuItem(item.id)\"\n >\n <X :size=\"14\" :stroke-width=\"2\" />\n </button>\n </div>\n <MergeTagInput\n :model-value=\"item.url\"\n type=\"url\"\n :placeholder=\"t.menu.urlPlaceholder\"\n @update:model-value=\"updateMenuItem(item.id, 'url', $event)\"\n />\n <div\n class=\"tpl:flex tpl:items-center tpl:gap-3 tpl:text-xs tpl:text-[var(--tpl-text-muted)]\"\n >\n <label\n v-for=\"toggle in ITEM_TOGGLES\"\n :key=\"toggle.key\"\n class=\"tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-1\"\n >\n <input\n type=\"checkbox\"\n :checked=\"item[toggle.key]\"\n class=\"tpl:accent-[var(--tpl-primary)]\"\n @change=\"\n updateMenuItem(\n item.id,\n toggle.key,\n ($event.target as HTMLInputElement).checked,\n )\n \"\n />\n {{ toggle.label }}\n </label>\n </div>\n <div class=\"tpl:flex tpl:items-center tpl:gap-2\">\n <label :class=\"labelClass\" class=\"tpl:!mb-0\">{{\n t.menu.color\n }}</label>\n <ColorPicker\n swatch-only\n :model-value=\"item.color || block.linkColor || block.color\"\n @update:model-value=\"updateMenuItem(item.id, 'color', $event)\"\n />\n </div>\n </div>\n <button :class=\"addItemBtnClass\" @click=\"addMenuItem\">\n <Plus :size=\"14\" :stroke-width=\"2\" />\n {{ t.menu.addItem }}\n </button>\n </div>\n </FieldRow>\n\n <FieldRow :label=\"t.menu.fontFamily\">\n <select\n :class=\"inputClass\"\n :value=\"block.fontFamily || ''\"\n @change=\"\n updateField(\n 'fontFamily',\n ($event.target as HTMLSelectElement).value || undefined,\n )\n \"\n >\n <option value=\"\">{{ t.title.inheritFont }}</option>\n <option\n v-for=\"font in fontFamilies\"\n :key=\"font.value\"\n :value=\"font.value\"\n >\n {{ font.label }}\n </option>\n </select>\n </FieldRow>\n\n <FieldRow :label=\"t.menu.fontSize\">\n <NumberWithSuffix\n :model-value=\"block.fontSize\"\n :min=\"8\"\n :max=\"48\"\n suffix=\"px\"\n @update:model-value=\"updateField('fontSize', $event)\"\n />\n </FieldRow>\n\n <FieldRow :label=\"t.menu.color\">\n <ColorPicker\n :model-value=\"block.color\"\n @update:model-value=\"updateField('color', $event)\"\n />\n </FieldRow>\n\n <FieldRow :label=\"t.menu.linkColor\">\n <ColorPicker\n :model-value=\"block.linkColor || block.color\"\n @update:model-value=\"updateField('linkColor', $event || undefined)\"\n />\n </FieldRow>\n\n <FieldRow :label=\"t.menu.textAlign\">\n <SlidingPillSelect\n :options=\"ALIGN_OPTIONS\"\n :model-value=\"block.textAlign\"\n @update:model-value=\"updateField('textAlign', $event)\"\n />\n </FieldRow>\n\n <FieldRow :label=\"t.menu.separator\">\n <input\n type=\"text\"\n :class=\"inputClass\"\n :value=\"block.separator\"\n @input=\"\n updateField('separator', ($event.target as HTMLInputElement).value)\n \"\n />\n </FieldRow>\n\n <FieldRow :label=\"t.menu.separatorColor\">\n <ColorPicker\n :model-value=\"block.separatorColor\"\n @update:model-value=\"updateField('separatorColor', $event)\"\n />\n </FieldRow>\n\n <FieldRow :label=\"t.menu.spacing\">\n <NumberWithSuffix\n :model-value=\"block.spacing\"\n :min=\"0\"\n :max=\"50\"\n suffix=\"px\"\n @update:model-value=\"updateField('spacing', $event)\"\n />\n </FieldRow>\n</template>\n","<script setup lang=\"ts\">\nimport ColorPicker from \"../ColorPicker.vue\";\nimport MergeTagInput from \"../MergeTagInput.vue\";\nimport SlidingPillSelect from \"../SlidingPillSelect.vue\";\nimport FieldRow from \"./FieldRow.vue\";\nimport NumberWithSuffix from \"./NumberWithSuffix.vue\";\nimport { useI18n } from \"../../composables/useI18n\";\nimport {\n inputClass,\n labelClass,\n removeItemBtnClass,\n addItemBtnClass,\n} from \"../../constants/styleConstants\";\nimport type { MenuBlock, MenuItemData } from \"@aswin.dev/types\";\nimport { generateId } from \"@aswin.dev/types\";\nimport { AlignCenter, AlignLeft, AlignRight, Plus, X } from \"@lucide/vue\";\nimport { computed } from \"vue\";\n\nconst props = defineProps<{\n block: MenuBlock;\n fontFamilies: Array<{ value: string; label: string }>;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update\", updates: Partial<MenuBlock>): void;\n}>();\n\nconst { t } = useI18n();\n\nconst ITEM_TOGGLES = computed(() => [\n { key: \"openInNewTab\" as const, label: t.menu.openInNewTab },\n { key: \"bold\" as const, label: t.menu.bold },\n { key: \"underline\" as const, label: t.menu.underline },\n]);\n\nconst ALIGN_OPTIONS = computed(() => [\n { value: \"left\", label: t.title.alignLeft, icon: AlignLeft },\n { value: \"center\", label: t.title.alignCenter, icon: AlignCenter },\n { value: \"right\", label: t.title.alignRight, icon: AlignRight },\n]);\n\nfunction updateField(field: keyof MenuBlock, value: unknown): void {\n emit(\"update\", { [field]: value } as Partial<MenuBlock>);\n}\n\nfunction addMenuItem(): void {\n const newItem: MenuItemData = {\n id: generateId(),\n text: \"\",\n url: \"\",\n openInNewTab: false,\n bold: false,\n underline: false,\n };\n emit(\"update\", { items: [...props.block.items, newItem] });\n}\n\nfunction updateMenuItem(\n itemId: string,\n field: keyof MenuItemData,\n value: unknown,\n): void {\n const updatedItems = props.block.items.map((item) =>\n item.id === itemId ? { ...item, [field]: value } : item,\n );\n emit(\"update\", { items: updatedItems });\n}\n\nfunction removeMenuItem(itemId: string): void {\n emit(\"update\", {\n items: props.block.items.filter((item) => item.id !== itemId),\n });\n}\n</script>\n\n<template>\n <FieldRow :label=\"t.menu.items\">\n <div class=\"tpl:flex tpl:flex-col tpl:gap-2\">\n <div\n v-for=\"item in block.items\"\n :key=\"item.id\"\n class=\"tpl:flex tpl:flex-col tpl:gap-1.5 tpl:rounded-md tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-hover)] tpl:p-2\"\n >\n <div class=\"tpl:flex tpl:items-center tpl:gap-2\">\n <input\n type=\"text\"\n :class=\"inputClass\"\n class=\"tpl:flex-1\"\n :value=\"item.text\"\n :placeholder=\"t.menu.text\"\n @input=\"\n updateMenuItem(\n item.id,\n 'text',\n ($event.target as HTMLInputElement).value,\n )\n \"\n />\n <button\n :class=\"removeItemBtnClass\"\n :title=\"t.menu.removeItem\"\n @click=\"removeMenuItem(item.id)\"\n >\n <X :size=\"14\" :stroke-width=\"2\" />\n </button>\n </div>\n <MergeTagInput\n :model-value=\"item.url\"\n type=\"url\"\n :placeholder=\"t.menu.urlPlaceholder\"\n @update:model-value=\"updateMenuItem(item.id, 'url', $event)\"\n />\n <div\n class=\"tpl:flex tpl:items-center tpl:gap-3 tpl:text-xs tpl:text-[var(--tpl-text-muted)]\"\n >\n <label\n v-for=\"toggle in ITEM_TOGGLES\"\n :key=\"toggle.key\"\n class=\"tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-1\"\n >\n <input\n type=\"checkbox\"\n :checked=\"item[toggle.key]\"\n class=\"tpl:accent-[var(--tpl-primary)]\"\n @change=\"\n updateMenuItem(\n item.id,\n toggle.key,\n ($event.target as HTMLInputElement).checked,\n )\n \"\n />\n {{ toggle.label }}\n </label>\n </div>\n <div class=\"tpl:flex tpl:items-center tpl:gap-2\">\n <label :class=\"labelClass\" class=\"tpl:!mb-0\">{{\n t.menu.color\n }}</label>\n <ColorPicker\n swatch-only\n :model-value=\"item.color || block.linkColor || block.color\"\n @update:model-value=\"updateMenuItem(item.id, 'color', $event)\"\n />\n </div>\n </div>\n <button :class=\"addItemBtnClass\" @click=\"addMenuItem\">\n <Plus :size=\"14\" :stroke-width=\"2\" />\n {{ t.menu.addItem }}\n </button>\n </div>\n </FieldRow>\n\n <FieldRow :label=\"t.menu.fontFamily\">\n <select\n :class=\"inputClass\"\n :value=\"block.fontFamily || ''\"\n @change=\"\n updateField(\n 'fontFamily',\n ($event.target as HTMLSelectElement).value || undefined,\n )\n \"\n >\n <option value=\"\">{{ t.title.inheritFont }}</option>\n <option\n v-for=\"font in fontFamilies\"\n :key=\"font.value\"\n :value=\"font.value\"\n >\n {{ font.label }}\n </option>\n </select>\n </FieldRow>\n\n <FieldRow :label=\"t.menu.fontSize\">\n <NumberWithSuffix\n :model-value=\"block.fontSize\"\n :min=\"8\"\n :max=\"48\"\n suffix=\"px\"\n @update:model-value=\"updateField('fontSize', $event)\"\n />\n </FieldRow>\n\n <FieldRow :label=\"t.menu.color\">\n <ColorPicker\n :model-value=\"block.color\"\n @update:model-value=\"updateField('color', $event)\"\n />\n </FieldRow>\n\n <FieldRow :label=\"t.menu.linkColor\">\n <ColorPicker\n :model-value=\"block.linkColor || block.color\"\n @update:model-value=\"updateField('linkColor', $event || undefined)\"\n />\n </FieldRow>\n\n <FieldRow :label=\"t.menu.textAlign\">\n <SlidingPillSelect\n :options=\"ALIGN_OPTIONS\"\n :model-value=\"block.textAlign\"\n @update:model-value=\"updateField('textAlign', $event)\"\n />\n </FieldRow>\n\n <FieldRow :label=\"t.menu.separator\">\n <input\n type=\"text\"\n :class=\"inputClass\"\n :value=\"block.separator\"\n @input=\"\n updateField('separator', ($event.target as HTMLInputElement).value)\n \"\n />\n </FieldRow>\n\n <FieldRow :label=\"t.menu.separatorColor\">\n <ColorPicker\n :model-value=\"block.separatorColor\"\n @update:model-value=\"updateField('separatorColor', $event)\"\n />\n </FieldRow>\n\n <FieldRow :label=\"t.menu.spacing\">\n <NumberWithSuffix\n :model-value=\"block.spacing\"\n :min=\"0\"\n :max=\"50\"\n suffix=\"px\"\n @update:model-value=\"updateField('spacing', $event)\"\n />\n </FieldRow>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport { inputClass, labelClass } from \"../../constants/styleConstants\";\nimport type { ColumnLayout, SectionBlock } from \"@aswin.dev/types\";\nimport { computed } from \"vue\";\n\ndefineProps<{\n block: SectionBlock;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update\", updates: Partial<SectionBlock>): void;\n}>();\n\nconst { t } = useI18n();\n\nconst columnOptions = computed(() => [\n { value: \"1\" as ColumnLayout, label: t.section.column1 },\n { value: \"2\" as ColumnLayout, label: t.section.column2 },\n { value: \"3\" as ColumnLayout, label: t.section.column3 },\n { value: \"1-2\" as ColumnLayout, label: t.section.ratio12 },\n { value: \"2-1\" as ColumnLayout, label: t.section.ratio21 },\n]);\n</script>\n\n<template>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.section.columns }}</label>\n <select\n :class=\"inputClass\"\n :value=\"block.columns\"\n @change=\"\n emit('update', {\n columns: ($event.target as HTMLSelectElement).value as ColumnLayout,\n })\n \"\n >\n <option\n v-for=\"option in columnOptions\"\n :key=\"option.value\"\n :value=\"option.value\"\n >\n {{ option.label }}\n </option>\n </select>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport { inputClass, labelClass } from \"../../constants/styleConstants\";\nimport type { ColumnLayout, SectionBlock } from \"@aswin.dev/types\";\nimport { computed } from \"vue\";\n\ndefineProps<{\n block: SectionBlock;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update\", updates: Partial<SectionBlock>): void;\n}>();\n\nconst { t } = useI18n();\n\nconst columnOptions = computed(() => [\n { value: \"1\" as ColumnLayout, label: t.section.column1 },\n { value: \"2\" as ColumnLayout, label: t.section.column2 },\n { value: \"3\" as ColumnLayout, label: t.section.column3 },\n { value: \"1-2\" as ColumnLayout, label: t.section.ratio12 },\n { value: \"2-1\" as ColumnLayout, label: t.section.ratio21 },\n]);\n</script>\n\n<template>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.section.columns }}</label>\n <select\n :class=\"inputClass\"\n :value=\"block.columns\"\n @change=\"\n emit('update', {\n columns: ($event.target as HTMLSelectElement).value as ColumnLayout,\n })\n \"\n >\n <option\n v-for=\"option in columnOptions\"\n :key=\"option.value\"\n :value=\"option.value\"\n >\n {{ option.label }}\n </option>\n </select>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport MergeTagInput from \"../MergeTagInput.vue\";\nimport SlidingPillSelect from \"../SlidingPillSelect.vue\";\nimport { useI18n } from \"../../composables/useI18n\";\nimport {\n inputClass,\n inputGroupInputClass,\n inputSuffixClass,\n labelClass,\n removeItemBtnClass,\n addItemBtnClass,\n} from \"../../constants/styleConstants\";\nimport {\n socialIcons,\n socialPlatformOptions,\n} from \"../../constants/socialIcons\";\nimport type {\n SocialIcon,\n SocialIconsBlock,\n SocialPlatform,\n} from \"@aswin.dev/types\";\nimport { generateId } from \"@aswin.dev/types\";\nimport { AlignCenter, AlignLeft, AlignRight, Plus, X } from \"@lucide/vue\";\n\nconst props = defineProps<{\n block: SocialIconsBlock;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update\", updates: Partial<SocialIconsBlock>): void;\n}>();\n\nconst { t } = useI18n();\n\nfunction updateField(field: string, value: unknown): void {\n emit(\"update\", { [field]: value } as Partial<SocialIconsBlock>);\n}\n\nfunction addSocialIcon(): void {\n const newIcon: SocialIcon = {\n id: generateId(),\n platform: \"facebook\",\n url: \"\",\n };\n emit(\"update\", { icons: [...props.block.icons, newIcon] });\n}\n\nfunction updateSocialIcon(\n iconId: string,\n field: keyof SocialIcon,\n value: string,\n): void {\n const updatedIcons = props.block.icons.map((icon) =>\n icon.id === iconId ? { ...icon, [field]: value } : icon,\n );\n emit(\"update\", { icons: updatedIcons });\n}\n\nfunction removeSocialIcon(iconId: string): void {\n emit(\"update\", {\n icons: props.block.icons.filter((icon) => icon.id !== iconId),\n });\n}\n</script>\n\n<template>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.social.icons }}</label>\n <div class=\"tpl:flex tpl:flex-col tpl:gap-2\">\n <div\n v-for=\"icon in block.icons\"\n :key=\"icon.id\"\n class=\"tpl:flex tpl:flex-col tpl:gap-1.5 tpl:rounded-md tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-hover)] tpl:p-2\"\n >\n <div class=\"tpl:flex tpl:items-center tpl:gap-2\">\n <select\n :class=\"inputClass\"\n class=\"tpl:flex-1\"\n :value=\"icon.platform\"\n @change=\"\n updateSocialIcon(\n icon.id,\n 'platform',\n ($event.target as HTMLSelectElement).value as SocialPlatform,\n )\n \"\n >\n <option\n v-for=\"platform in socialPlatformOptions\"\n :key=\"platform\"\n :value=\"platform\"\n >\n {{ socialIcons[platform].name }}\n </option>\n </select>\n <button\n :class=\"removeItemBtnClass\"\n :title=\"t.social.removeIcon\"\n @click=\"removeSocialIcon(icon.id)\"\n >\n <X :size=\"14\" :stroke-width=\"2\" />\n </button>\n </div>\n <MergeTagInput\n :model-value=\"icon.url\"\n type=\"url\"\n :placeholder=\"t.social.urlPlaceholder\"\n @update:model-value=\"updateSocialIcon(icon.id, 'url', $event)\"\n />\n </div>\n <button :class=\"addItemBtnClass\" @click=\"addSocialIcon\">\n <Plus :size=\"14\" :stroke-width=\"2\" />\n {{ t.social.addIcon }}\n </button>\n </div>\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.social.style }}</label>\n <SlidingPillSelect\n :options=\"[\n { value: 'solid', label: t.social.styleSolid },\n { value: 'outlined', label: t.social.styleOutlined },\n { value: 'rounded', label: t.social.styleRounded },\n { value: 'square', label: t.social.styleSquare },\n { value: 'circle', label: t.social.styleCircle },\n ]\"\n :model-value=\"block.iconStyle\"\n @update:model-value=\"updateField('iconStyle', $event)\"\n />\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.social.size }}</label>\n <SlidingPillSelect\n :options=\"[\n { value: 'small', label: t.social.sizeSmall },\n { value: 'medium', label: t.social.sizeMedium },\n { value: 'large', label: t.social.sizeLarge },\n ]\"\n :model-value=\"block.iconSize\"\n @update:model-value=\"updateField('iconSize', $event)\"\n />\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.social.spacing }}</label>\n <div class=\"tpl:flex tpl:items-stretch\">\n <input\n type=\"number\"\n :class=\"inputGroupInputClass\"\n :value=\"block.spacing\"\n min=\"0\"\n max=\"50\"\n @input=\"\n updateField(\n 'spacing',\n Number(($event.target as HTMLInputElement).value),\n )\n \"\n />\n <span :class=\"inputSuffixClass\">px</span>\n </div>\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.social.align }}</label>\n <SlidingPillSelect\n :options=\"[\n { value: 'left', label: t.title.alignLeft, icon: AlignLeft },\n { value: 'center', label: t.title.alignCenter, icon: AlignCenter },\n { value: 'right', label: t.title.alignRight, icon: AlignRight },\n ]\"\n :model-value=\"block.align\"\n @update:model-value=\"updateField('align', $event)\"\n />\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport MergeTagInput from \"../MergeTagInput.vue\";\nimport SlidingPillSelect from \"../SlidingPillSelect.vue\";\nimport { useI18n } from \"../../composables/useI18n\";\nimport {\n inputClass,\n inputGroupInputClass,\n inputSuffixClass,\n labelClass,\n removeItemBtnClass,\n addItemBtnClass,\n} from \"../../constants/styleConstants\";\nimport {\n socialIcons,\n socialPlatformOptions,\n} from \"../../constants/socialIcons\";\nimport type {\n SocialIcon,\n SocialIconsBlock,\n SocialPlatform,\n} from \"@aswin.dev/types\";\nimport { generateId } from \"@aswin.dev/types\";\nimport { AlignCenter, AlignLeft, AlignRight, Plus, X } from \"@lucide/vue\";\n\nconst props = defineProps<{\n block: SocialIconsBlock;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update\", updates: Partial<SocialIconsBlock>): void;\n}>();\n\nconst { t } = useI18n();\n\nfunction updateField(field: string, value: unknown): void {\n emit(\"update\", { [field]: value } as Partial<SocialIconsBlock>);\n}\n\nfunction addSocialIcon(): void {\n const newIcon: SocialIcon = {\n id: generateId(),\n platform: \"facebook\",\n url: \"\",\n };\n emit(\"update\", { icons: [...props.block.icons, newIcon] });\n}\n\nfunction updateSocialIcon(\n iconId: string,\n field: keyof SocialIcon,\n value: string,\n): void {\n const updatedIcons = props.block.icons.map((icon) =>\n icon.id === iconId ? { ...icon, [field]: value } : icon,\n );\n emit(\"update\", { icons: updatedIcons });\n}\n\nfunction removeSocialIcon(iconId: string): void {\n emit(\"update\", {\n icons: props.block.icons.filter((icon) => icon.id !== iconId),\n });\n}\n</script>\n\n<template>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.social.icons }}</label>\n <div class=\"tpl:flex tpl:flex-col tpl:gap-2\">\n <div\n v-for=\"icon in block.icons\"\n :key=\"icon.id\"\n class=\"tpl:flex tpl:flex-col tpl:gap-1.5 tpl:rounded-md tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-hover)] tpl:p-2\"\n >\n <div class=\"tpl:flex tpl:items-center tpl:gap-2\">\n <select\n :class=\"inputClass\"\n class=\"tpl:flex-1\"\n :value=\"icon.platform\"\n @change=\"\n updateSocialIcon(\n icon.id,\n 'platform',\n ($event.target as HTMLSelectElement).value as SocialPlatform,\n )\n \"\n >\n <option\n v-for=\"platform in socialPlatformOptions\"\n :key=\"platform\"\n :value=\"platform\"\n >\n {{ socialIcons[platform].name }}\n </option>\n </select>\n <button\n :class=\"removeItemBtnClass\"\n :title=\"t.social.removeIcon\"\n @click=\"removeSocialIcon(icon.id)\"\n >\n <X :size=\"14\" :stroke-width=\"2\" />\n </button>\n </div>\n <MergeTagInput\n :model-value=\"icon.url\"\n type=\"url\"\n :placeholder=\"t.social.urlPlaceholder\"\n @update:model-value=\"updateSocialIcon(icon.id, 'url', $event)\"\n />\n </div>\n <button :class=\"addItemBtnClass\" @click=\"addSocialIcon\">\n <Plus :size=\"14\" :stroke-width=\"2\" />\n {{ t.social.addIcon }}\n </button>\n </div>\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.social.style }}</label>\n <SlidingPillSelect\n :options=\"[\n { value: 'solid', label: t.social.styleSolid },\n { value: 'outlined', label: t.social.styleOutlined },\n { value: 'rounded', label: t.social.styleRounded },\n { value: 'square', label: t.social.styleSquare },\n { value: 'circle', label: t.social.styleCircle },\n ]\"\n :model-value=\"block.iconStyle\"\n @update:model-value=\"updateField('iconStyle', $event)\"\n />\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.social.size }}</label>\n <SlidingPillSelect\n :options=\"[\n { value: 'small', label: t.social.sizeSmall },\n { value: 'medium', label: t.social.sizeMedium },\n { value: 'large', label: t.social.sizeLarge },\n ]\"\n :model-value=\"block.iconSize\"\n @update:model-value=\"updateField('iconSize', $event)\"\n />\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.social.spacing }}</label>\n <div class=\"tpl:flex tpl:items-stretch\">\n <input\n type=\"number\"\n :class=\"inputGroupInputClass\"\n :value=\"block.spacing\"\n min=\"0\"\n max=\"50\"\n @input=\"\n updateField(\n 'spacing',\n Number(($event.target as HTMLInputElement).value),\n )\n \"\n />\n <span :class=\"inputSuffixClass\">px</span>\n </div>\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.social.align }}</label>\n <SlidingPillSelect\n :options=\"[\n { value: 'left', label: t.title.alignLeft, icon: AlignLeft },\n { value: 'center', label: t.title.alignCenter, icon: AlignCenter },\n { value: 'right', label: t.title.alignRight, icon: AlignRight },\n ]\"\n :model-value=\"block.align\"\n @update:model-value=\"updateField('align', $event)\"\n />\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport {\n inputGroupInputClass,\n inputSuffixClass,\n labelClass,\n} from \"../../constants/styleConstants\";\nimport type { SpacerBlock } from \"@aswin.dev/types\";\n\ndefineProps<{\n block: SpacerBlock;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update\", updates: Partial<SpacerBlock>): void;\n}>();\n\nconst { t } = useI18n();\n</script>\n\n<template>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.spacer.height }}</label>\n <div class=\"tpl:flex tpl:items-stretch\">\n <input\n type=\"number\"\n :class=\"inputGroupInputClass\"\n :value=\"block.height\"\n min=\"10\"\n max=\"100\"\n @input=\"\n emit('update', {\n height: Number(($event.target as HTMLInputElement).value),\n })\n \"\n />\n <span :class=\"inputSuffixClass\">px</span>\n </div>\n <input\n type=\"range\"\n class=\"tpl:mt-2 tpl:w-full tpl:accent-[var(--tpl-primary)]\"\n :value=\"block.height\"\n min=\"10\"\n max=\"100\"\n @input=\"\n emit('update', {\n height: Number(($event.target as HTMLInputElement).value),\n })\n \"\n />\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport {\n inputGroupInputClass,\n inputSuffixClass,\n labelClass,\n} from \"../../constants/styleConstants\";\nimport type { SpacerBlock } from \"@aswin.dev/types\";\n\ndefineProps<{\n block: SpacerBlock;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update\", updates: Partial<SpacerBlock>): void;\n}>();\n\nconst { t } = useI18n();\n</script>\n\n<template>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.spacer.height }}</label>\n <div class=\"tpl:flex tpl:items-stretch\">\n <input\n type=\"number\"\n :class=\"inputGroupInputClass\"\n :value=\"block.height\"\n min=\"10\"\n max=\"100\"\n @input=\"\n emit('update', {\n height: Number(($event.target as HTMLInputElement).value),\n })\n \"\n />\n <span :class=\"inputSuffixClass\">px</span>\n </div>\n <input\n type=\"range\"\n class=\"tpl:mt-2 tpl:w-full tpl:accent-[var(--tpl-primary)]\"\n :value=\"block.height\"\n min=\"10\"\n max=\"100\"\n @input=\"\n emit('update', {\n height: Number(($event.target as HTMLInputElement).value),\n })\n \"\n />\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport ColorPicker from \"../ColorPicker.vue\";\nimport SlidingPillSelect from \"../SlidingPillSelect.vue\";\nimport { useI18n } from \"../../composables/useI18n\";\nimport {\n inputClass,\n inputGroupInputClass,\n inputSuffixClass,\n labelClass,\n DEFAULT_TABLE_ROW_BG,\n} from \"../../constants/styleConstants\";\nimport type { TableBlock, TableCellData, TableRowData } from \"@aswin.dev/types\";\nimport { generateId } from \"@aswin.dev/types\";\nimport { AlignCenter, AlignLeft, AlignRight, Minus, Plus } from \"@lucide/vue\";\nimport { computed } from \"vue\";\n\nconst props = defineProps<{\n block: TableBlock;\n fontFamilies: Array<{ value: string; label: string }>;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update\", updates: Partial<TableBlock>): void;\n}>();\n\nconst { t } = useI18n();\n\nconst tableColumnCount = computed(() => {\n return props.block.rows.length > 0 ? props.block.rows[0].cells.length : 0;\n});\n\nfunction updateField(field: string, value: unknown): void {\n emit(\"update\", { [field]: value } as Partial<TableBlock>);\n}\n\nfunction addTableRow(): void {\n const columnCount =\n props.block.rows.length > 0 ? props.block.rows[0].cells.length : 3;\n const newRow: TableRowData = {\n id: generateId(),\n cells: Array.from(\n { length: columnCount },\n (): TableCellData => ({\n id: generateId(),\n content: \"\",\n }),\n ),\n };\n emit(\"update\", { rows: [...props.block.rows, newRow] });\n}\n\nfunction removeTableRow(rowId: string): void {\n emit(\"update\", {\n rows: props.block.rows.filter((row) => row.id !== rowId),\n });\n}\n\nfunction addTableColumn(): void {\n const updatedRows = props.block.rows.map((row) => ({\n ...row,\n cells: [...row.cells, { id: generateId(), content: \"\" } as TableCellData],\n }));\n emit(\"update\", { rows: updatedRows });\n}\n\nfunction removeTableColumn(colIndex: number): void {\n const updatedRows = props.block.rows.map((row) => ({\n ...row,\n cells: row.cells.filter((_, i) => i !== colIndex),\n }));\n emit(\"update\", { rows: updatedRows });\n}\n</script>\n\n<template>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.table.dimensions }}</label>\n <div class=\"tpl:flex tpl:items-center tpl:gap-3\">\n <div class=\"tpl:flex tpl:flex-1 tpl:items-center tpl:gap-1.5\">\n <span class=\"tpl:text-xs tpl:text-[var(--tpl-text-muted)]\">{{\n t.table.rows\n }}</span>\n <div\n class=\"tpl:flex tpl:items-center tpl:rounded-md tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)]\"\n >\n <button\n class=\"tpl:flex tpl:items-center tpl:justify-center tpl:px-1.5 tpl:py-1 tpl:text-[var(--tpl-text-muted)] tpl:transition-colors tpl:duration-150 tpl:hover:text-[var(--tpl-primary)] tpl:disabled:opacity-30\"\n :disabled=\"block.rows.length <= 1\"\n @click=\"removeTableRow(block.rows[block.rows.length - 1].id)\"\n >\n <Minus :size=\"12\" :stroke-width=\"2\" />\n </button>\n <span\n class=\"tpl:min-w-[20px] tpl:text-center tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-text)]\"\n >{{ block.rows.length }}</span\n >\n <button\n class=\"tpl:flex tpl:items-center tpl:justify-center tpl:px-1.5 tpl:py-1 tpl:text-[var(--tpl-text-muted)] tpl:transition-colors tpl:duration-150 tpl:hover:text-[var(--tpl-primary)]\"\n @click=\"addTableRow\"\n >\n <Plus :size=\"12\" :stroke-width=\"2\" />\n </button>\n </div>\n </div>\n <div class=\"tpl:flex tpl:flex-1 tpl:items-center tpl:gap-1.5\">\n <span class=\"tpl:text-xs tpl:text-[var(--tpl-text-muted)]\">{{\n t.table.columns\n }}</span>\n <div\n class=\"tpl:flex tpl:items-center tpl:rounded-md tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)]\"\n >\n <button\n class=\"tpl:flex tpl:items-center tpl:justify-center tpl:px-1.5 tpl:py-1 tpl:text-[var(--tpl-text-muted)] tpl:transition-colors tpl:duration-150 tpl:hover:text-[var(--tpl-primary)] tpl:disabled:opacity-30\"\n :disabled=\"tableColumnCount <= 1\"\n @click=\"removeTableColumn(tableColumnCount - 1)\"\n >\n <Minus :size=\"12\" :stroke-width=\"2\" />\n </button>\n <span\n class=\"tpl:min-w-[20px] tpl:text-center tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-text)]\"\n >{{ tableColumnCount }}</span\n >\n <button\n class=\"tpl:flex tpl:items-center tpl:justify-center tpl:px-1.5 tpl:py-1 tpl:text-[var(--tpl-text-muted)] tpl:transition-colors tpl:duration-150 tpl:hover:text-[var(--tpl-primary)]\"\n @click=\"addTableColumn\"\n >\n <Plus :size=\"12\" :stroke-width=\"2\" />\n </button>\n </div>\n </div>\n </div>\n </div>\n <div class=\"tpl:mb-3.5\">\n <label\n class=\"tpl:mb-1.5 tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-text-muted)]\"\n >\n <input\n type=\"checkbox\"\n :checked=\"block.hasHeaderRow\"\n class=\"tpl:accent-[var(--tpl-primary)]\"\n @change=\"\n updateField(\n 'hasHeaderRow',\n ($event.target as HTMLInputElement).checked,\n )\n \"\n />\n {{ t.table.hasHeaderRow }}\n </label>\n </div>\n <div v-if=\"block.hasHeaderRow\" class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.table.headerBackgroundColor }}</label>\n <ColorPicker\n :model-value=\"block.headerBackgroundColor || DEFAULT_TABLE_ROW_BG\"\n :placeholder=\"t.table.noHeaderBg\"\n @update:model-value=\"updateField('headerBackgroundColor', $event || null)\"\n />\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.table.borderColor }}</label>\n <ColorPicker\n :model-value=\"block.borderColor\"\n @update:model-value=\"updateField('borderColor', $event)\"\n />\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.table.borderWidth }}</label>\n <div class=\"tpl:flex tpl:items-stretch\">\n <input\n type=\"number\"\n :class=\"inputGroupInputClass\"\n :value=\"block.borderWidth\"\n min=\"0\"\n max=\"10\"\n @input=\"\n updateField(\n 'borderWidth',\n Number(($event.target as HTMLInputElement).value),\n )\n \"\n />\n <span :class=\"inputSuffixClass\">px</span>\n </div>\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.table.cellPadding }}</label>\n <div class=\"tpl:flex tpl:items-stretch\">\n <input\n type=\"number\"\n :class=\"inputGroupInputClass\"\n :value=\"block.cellPadding\"\n min=\"0\"\n max=\"30\"\n @input=\"\n updateField(\n 'cellPadding',\n Number(($event.target as HTMLInputElement).value),\n )\n \"\n />\n <span :class=\"inputSuffixClass\">px</span>\n </div>\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.table.fontFamily }}</label>\n <select\n :class=\"inputClass\"\n :value=\"block.fontFamily || ''\"\n @change=\"\n updateField(\n 'fontFamily',\n ($event.target as HTMLSelectElement).value || undefined,\n )\n \"\n >\n <option value=\"\">{{ t.title.inheritFont }}</option>\n <option\n v-for=\"font in fontFamilies\"\n :key=\"font.value\"\n :value=\"font.value\"\n >\n {{ font.label }}\n </option>\n </select>\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.table.fontSize }}</label>\n <div class=\"tpl:flex tpl:items-stretch\">\n <input\n type=\"number\"\n :class=\"inputGroupInputClass\"\n :value=\"block.fontSize\"\n min=\"10\"\n max=\"32\"\n @input=\"\n updateField(\n 'fontSize',\n Number(($event.target as HTMLInputElement).value),\n )\n \"\n />\n <span :class=\"inputSuffixClass\">px</span>\n </div>\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.table.color }}</label>\n <ColorPicker\n :model-value=\"block.color\"\n @update:model-value=\"updateField('color', $event)\"\n />\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.table.textAlign }}</label>\n <SlidingPillSelect\n :options=\"[\n { value: 'left', label: t.title.alignLeft, icon: AlignLeft },\n { value: 'center', label: t.title.alignCenter, icon: AlignCenter },\n { value: 'right', label: t.title.alignRight, icon: AlignRight },\n ]\"\n :model-value=\"block.textAlign\"\n @update:model-value=\"updateField('textAlign', $event)\"\n />\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport ColorPicker from \"../ColorPicker.vue\";\nimport SlidingPillSelect from \"../SlidingPillSelect.vue\";\nimport { useI18n } from \"../../composables/useI18n\";\nimport {\n inputClass,\n inputGroupInputClass,\n inputSuffixClass,\n labelClass,\n DEFAULT_TABLE_ROW_BG,\n} from \"../../constants/styleConstants\";\nimport type { TableBlock, TableCellData, TableRowData } from \"@aswin.dev/types\";\nimport { generateId } from \"@aswin.dev/types\";\nimport { AlignCenter, AlignLeft, AlignRight, Minus, Plus } from \"@lucide/vue\";\nimport { computed } from \"vue\";\n\nconst props = defineProps<{\n block: TableBlock;\n fontFamilies: Array<{ value: string; label: string }>;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update\", updates: Partial<TableBlock>): void;\n}>();\n\nconst { t } = useI18n();\n\nconst tableColumnCount = computed(() => {\n return props.block.rows.length > 0 ? props.block.rows[0].cells.length : 0;\n});\n\nfunction updateField(field: string, value: unknown): void {\n emit(\"update\", { [field]: value } as Partial<TableBlock>);\n}\n\nfunction addTableRow(): void {\n const columnCount =\n props.block.rows.length > 0 ? props.block.rows[0].cells.length : 3;\n const newRow: TableRowData = {\n id: generateId(),\n cells: Array.from(\n { length: columnCount },\n (): TableCellData => ({\n id: generateId(),\n content: \"\",\n }),\n ),\n };\n emit(\"update\", { rows: [...props.block.rows, newRow] });\n}\n\nfunction removeTableRow(rowId: string): void {\n emit(\"update\", {\n rows: props.block.rows.filter((row) => row.id !== rowId),\n });\n}\n\nfunction addTableColumn(): void {\n const updatedRows = props.block.rows.map((row) => ({\n ...row,\n cells: [...row.cells, { id: generateId(), content: \"\" } as TableCellData],\n }));\n emit(\"update\", { rows: updatedRows });\n}\n\nfunction removeTableColumn(colIndex: number): void {\n const updatedRows = props.block.rows.map((row) => ({\n ...row,\n cells: row.cells.filter((_, i) => i !== colIndex),\n }));\n emit(\"update\", { rows: updatedRows });\n}\n</script>\n\n<template>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.table.dimensions }}</label>\n <div class=\"tpl:flex tpl:items-center tpl:gap-3\">\n <div class=\"tpl:flex tpl:flex-1 tpl:items-center tpl:gap-1.5\">\n <span class=\"tpl:text-xs tpl:text-[var(--tpl-text-muted)]\">{{\n t.table.rows\n }}</span>\n <div\n class=\"tpl:flex tpl:items-center tpl:rounded-md tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)]\"\n >\n <button\n class=\"tpl:flex tpl:items-center tpl:justify-center tpl:px-1.5 tpl:py-1 tpl:text-[var(--tpl-text-muted)] tpl:transition-colors tpl:duration-150 tpl:hover:text-[var(--tpl-primary)] tpl:disabled:opacity-30\"\n :disabled=\"block.rows.length <= 1\"\n @click=\"removeTableRow(block.rows[block.rows.length - 1].id)\"\n >\n <Minus :size=\"12\" :stroke-width=\"2\" />\n </button>\n <span\n class=\"tpl:min-w-[20px] tpl:text-center tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-text)]\"\n >{{ block.rows.length }}</span\n >\n <button\n class=\"tpl:flex tpl:items-center tpl:justify-center tpl:px-1.5 tpl:py-1 tpl:text-[var(--tpl-text-muted)] tpl:transition-colors tpl:duration-150 tpl:hover:text-[var(--tpl-primary)]\"\n @click=\"addTableRow\"\n >\n <Plus :size=\"12\" :stroke-width=\"2\" />\n </button>\n </div>\n </div>\n <div class=\"tpl:flex tpl:flex-1 tpl:items-center tpl:gap-1.5\">\n <span class=\"tpl:text-xs tpl:text-[var(--tpl-text-muted)]\">{{\n t.table.columns\n }}</span>\n <div\n class=\"tpl:flex tpl:items-center tpl:rounded-md tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)]\"\n >\n <button\n class=\"tpl:flex tpl:items-center tpl:justify-center tpl:px-1.5 tpl:py-1 tpl:text-[var(--tpl-text-muted)] tpl:transition-colors tpl:duration-150 tpl:hover:text-[var(--tpl-primary)] tpl:disabled:opacity-30\"\n :disabled=\"tableColumnCount <= 1\"\n @click=\"removeTableColumn(tableColumnCount - 1)\"\n >\n <Minus :size=\"12\" :stroke-width=\"2\" />\n </button>\n <span\n class=\"tpl:min-w-[20px] tpl:text-center tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-text)]\"\n >{{ tableColumnCount }}</span\n >\n <button\n class=\"tpl:flex tpl:items-center tpl:justify-center tpl:px-1.5 tpl:py-1 tpl:text-[var(--tpl-text-muted)] tpl:transition-colors tpl:duration-150 tpl:hover:text-[var(--tpl-primary)]\"\n @click=\"addTableColumn\"\n >\n <Plus :size=\"12\" :stroke-width=\"2\" />\n </button>\n </div>\n </div>\n </div>\n </div>\n <div class=\"tpl:mb-3.5\">\n <label\n class=\"tpl:mb-1.5 tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-text-muted)]\"\n >\n <input\n type=\"checkbox\"\n :checked=\"block.hasHeaderRow\"\n class=\"tpl:accent-[var(--tpl-primary)]\"\n @change=\"\n updateField(\n 'hasHeaderRow',\n ($event.target as HTMLInputElement).checked,\n )\n \"\n />\n {{ t.table.hasHeaderRow }}\n </label>\n </div>\n <div v-if=\"block.hasHeaderRow\" class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.table.headerBackgroundColor }}</label>\n <ColorPicker\n :model-value=\"block.headerBackgroundColor || DEFAULT_TABLE_ROW_BG\"\n :placeholder=\"t.table.noHeaderBg\"\n @update:model-value=\"updateField('headerBackgroundColor', $event || null)\"\n />\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.table.borderColor }}</label>\n <ColorPicker\n :model-value=\"block.borderColor\"\n @update:model-value=\"updateField('borderColor', $event)\"\n />\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.table.borderWidth }}</label>\n <div class=\"tpl:flex tpl:items-stretch\">\n <input\n type=\"number\"\n :class=\"inputGroupInputClass\"\n :value=\"block.borderWidth\"\n min=\"0\"\n max=\"10\"\n @input=\"\n updateField(\n 'borderWidth',\n Number(($event.target as HTMLInputElement).value),\n )\n \"\n />\n <span :class=\"inputSuffixClass\">px</span>\n </div>\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.table.cellPadding }}</label>\n <div class=\"tpl:flex tpl:items-stretch\">\n <input\n type=\"number\"\n :class=\"inputGroupInputClass\"\n :value=\"block.cellPadding\"\n min=\"0\"\n max=\"30\"\n @input=\"\n updateField(\n 'cellPadding',\n Number(($event.target as HTMLInputElement).value),\n )\n \"\n />\n <span :class=\"inputSuffixClass\">px</span>\n </div>\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.table.fontFamily }}</label>\n <select\n :class=\"inputClass\"\n :value=\"block.fontFamily || ''\"\n @change=\"\n updateField(\n 'fontFamily',\n ($event.target as HTMLSelectElement).value || undefined,\n )\n \"\n >\n <option value=\"\">{{ t.title.inheritFont }}</option>\n <option\n v-for=\"font in fontFamilies\"\n :key=\"font.value\"\n :value=\"font.value\"\n >\n {{ font.label }}\n </option>\n </select>\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.table.fontSize }}</label>\n <div class=\"tpl:flex tpl:items-stretch\">\n <input\n type=\"number\"\n :class=\"inputGroupInputClass\"\n :value=\"block.fontSize\"\n min=\"10\"\n max=\"32\"\n @input=\"\n updateField(\n 'fontSize',\n Number(($event.target as HTMLInputElement).value),\n )\n \"\n />\n <span :class=\"inputSuffixClass\">px</span>\n </div>\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.table.color }}</label>\n <ColorPicker\n :model-value=\"block.color\"\n @update:model-value=\"updateField('color', $event)\"\n />\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.table.textAlign }}</label>\n <SlidingPillSelect\n :options=\"[\n { value: 'left', label: t.title.alignLeft, icon: AlignLeft },\n { value: 'center', label: t.title.alignCenter, icon: AlignCenter },\n { value: 'right', label: t.title.alignRight, icon: AlignRight },\n ]\"\n :model-value=\"block.textAlign\"\n @update:model-value=\"updateField('textAlign', $event)\"\n />\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport ColorPicker from \"../ColorPicker.vue\";\nimport SlidingPillSelect from \"../SlidingPillSelect.vue\";\nimport { useI18n } from \"../../composables/useI18n\";\nimport { inputClass, labelClass } from \"../../constants/styleConstants\";\nimport type { TitleBlock } from \"@aswin.dev/types\";\nimport { AlignCenter, AlignLeft, AlignRight } from \"@lucide/vue\";\n\ndefineProps<{\n block: TitleBlock;\n fontFamilies: Array<{ value: string; label: string }>;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update\", updates: Partial<TitleBlock>): void;\n}>();\n\nconst { t } = useI18n();\n\nfunction updateField(field: string, value: unknown): void {\n emit(\"update\", { [field]: value } as Partial<TitleBlock>);\n}\n</script>\n\n<template>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.title.level }}</label>\n <select\n :class=\"inputClass\"\n :value=\"block.level\"\n @change=\"\n updateField('level', Number(($event.target as HTMLSelectElement).value))\n \"\n >\n <option :value=\"1\">{{ t.title.heading1 }}</option>\n <option :value=\"2\">{{ t.title.heading2 }}</option>\n <option :value=\"3\">{{ t.title.heading3 }}</option>\n <option :value=\"4\">{{ t.title.heading4 }}</option>\n </select>\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.title.fontFamily }}</label>\n <select\n :class=\"inputClass\"\n :value=\"block.fontFamily || ''\"\n @change=\"\n updateField(\n 'fontFamily',\n ($event.target as HTMLSelectElement).value || undefined,\n )\n \"\n >\n <option value=\"\">{{ t.title.inheritFont }}</option>\n <option\n v-for=\"font in fontFamilies\"\n :key=\"font.value\"\n :value=\"font.value\"\n >\n {{ font.label }}\n </option>\n </select>\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.title.color }}</label>\n <ColorPicker\n :model-value=\"block.color\"\n @update:model-value=\"updateField('color', $event)\"\n />\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.title.align }}</label>\n <SlidingPillSelect\n :options=\"[\n { value: 'left', label: t.title.alignLeft, icon: AlignLeft },\n { value: 'center', label: t.title.alignCenter, icon: AlignCenter },\n { value: 'right', label: t.title.alignRight, icon: AlignRight },\n ]\"\n :model-value=\"block.textAlign\"\n @update:model-value=\"updateField('textAlign', $event)\"\n />\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport ColorPicker from \"../ColorPicker.vue\";\nimport SlidingPillSelect from \"../SlidingPillSelect.vue\";\nimport { useI18n } from \"../../composables/useI18n\";\nimport { inputClass, labelClass } from \"../../constants/styleConstants\";\nimport type { TitleBlock } from \"@aswin.dev/types\";\nimport { AlignCenter, AlignLeft, AlignRight } from \"@lucide/vue\";\n\ndefineProps<{\n block: TitleBlock;\n fontFamilies: Array<{ value: string; label: string }>;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update\", updates: Partial<TitleBlock>): void;\n}>();\n\nconst { t } = useI18n();\n\nfunction updateField(field: string, value: unknown): void {\n emit(\"update\", { [field]: value } as Partial<TitleBlock>);\n}\n</script>\n\n<template>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.title.level }}</label>\n <select\n :class=\"inputClass\"\n :value=\"block.level\"\n @change=\"\n updateField('level', Number(($event.target as HTMLSelectElement).value))\n \"\n >\n <option :value=\"1\">{{ t.title.heading1 }}</option>\n <option :value=\"2\">{{ t.title.heading2 }}</option>\n <option :value=\"3\">{{ t.title.heading3 }}</option>\n <option :value=\"4\">{{ t.title.heading4 }}</option>\n </select>\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.title.fontFamily }}</label>\n <select\n :class=\"inputClass\"\n :value=\"block.fontFamily || ''\"\n @change=\"\n updateField(\n 'fontFamily',\n ($event.target as HTMLSelectElement).value || undefined,\n )\n \"\n >\n <option value=\"\">{{ t.title.inheritFont }}</option>\n <option\n v-for=\"font in fontFamilies\"\n :key=\"font.value\"\n :value=\"font.value\"\n >\n {{ font.label }}\n </option>\n </select>\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.title.color }}</label>\n <ColorPicker\n :model-value=\"block.color\"\n @update:model-value=\"updateField('color', $event)\"\n />\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.title.align }}</label>\n <SlidingPillSelect\n :options=\"[\n { value: 'left', label: t.title.alignLeft, icon: AlignLeft },\n { value: 'center', label: t.title.alignCenter, icon: AlignCenter },\n { value: 'right', label: t.title.alignRight, icon: AlignRight },\n ]\"\n :model-value=\"block.textAlign\"\n @update:model-value=\"updateField('textAlign', $event)\"\n />\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport MergeTagInput from \"../MergeTagInput.vue\";\nimport SlidingPillSelect from \"../SlidingPillSelect.vue\";\nimport { useI18n } from \"../../composables/useI18n\";\nimport { inputClass, labelClass } from \"../../constants/styleConstants\";\nimport type { VideoBlock } from \"@aswin.dev/types\";\nimport { containsMergeTag, SYNTAX_PRESETS } from \"@aswin.dev/types\";\nimport { Image } from \"@lucide/vue\";\nimport { computed, inject, ref } from \"vue\";\nimport { ON_REQUEST_MEDIA_KEY, MERGE_TAG_SYNTAX_KEY } from \"../../keys\";\nimport { useTimeoutFn } from \"@vueuse/core\";\n\nconst props = defineProps<{\n block: VideoBlock;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update\", updates: Partial<VideoBlock>): void;\n}>();\n\nconst { t } = useI18n();\nconst onRequestMedia = inject(ON_REQUEST_MEDIA_KEY, null);\nconst mergeTagSyntax = inject(MERGE_TAG_SYNTAX_KEY, SYNTAX_PRESETS.liquid);\n\nconst canBrowseMedia = computed(() => !!onRequestMedia);\nconst urlHasMergeTag = computed(() =>\n containsMergeTag(props.block.url, mergeTagSyntax),\n);\n\nconst pulseThumbnail = ref(false);\nconst { start: startPulseThumbnail } = useTimeoutFn(\n () => {\n pulseThumbnail.value = false;\n },\n 1000,\n { immediate: false },\n);\n\nfunction updateField(field: keyof VideoBlock, value: unknown): void {\n emit(\"update\", { [field]: value } as Partial<VideoBlock>);\n}\n\nasync function openMediaBrowser(): Promise<void> {\n const result = await onRequestMedia?.({ accept: [\"images\"] });\n if (result) {\n updateField(\"thumbnailUrl\", result.url);\n pulseThumbnail.value = true;\n startPulseThumbnail();\n }\n}\n</script>\n\n<template>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.video.videoUrl }}</label>\n <MergeTagInput\n :model-value=\"block.url\"\n type=\"url\"\n :placeholder=\"t.video.videoUrlPlaceholder\"\n @update:model-value=\"updateField('url', $event)\"\n />\n <label\n v-if=\"block.url\"\n class=\"tpl:mt-2 tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:text-[12px] tpl:text-[var(--tpl-text-muted)]\"\n >\n <input\n type=\"checkbox\"\n class=\"tpl:size-3.5 tpl:cursor-pointer tpl:accent-[var(--tpl-primary)]\"\n :checked=\"block.openInNewTab ?? false\"\n @change=\"\n updateField(\n 'openInNewTab',\n ($event.target as HTMLInputElement).checked,\n )\n \"\n />\n {{ t.video.openInNewTab }}\n </label>\n </div>\n <div v-if=\"urlHasMergeTag\" class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">\n {{ t.video.placeholderUrl }}\n <span class=\"tpl:font-normal tpl:text-[var(--tpl-text-dim)]\">\n {{ t.video.optional }}\n </span>\n </label>\n <input\n type=\"url\"\n :class=\"inputClass\"\n :value=\"block.placeholderUrl || ''\"\n :placeholder=\"t.video.placeholderUrlPlaceholder\"\n :title=\"t.video.placeholderUrlTooltip\"\n @input=\"\n updateField('placeholderUrl', ($event.target as HTMLInputElement).value)\n \"\n />\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">\n {{ t.video.customThumbnail }}\n <span class=\"tpl:font-normal tpl:text-[var(--tpl-text-dim)]\">\n {{ t.video.optional }}\n </span>\n </label>\n <MergeTagInput\n :model-value=\"block.thumbnailUrl\"\n type=\"url\"\n :placeholder=\"t.video.thumbnailPlaceholder\"\n :pulse=\"pulseThumbnail\"\n @update:model-value=\"updateField('thumbnailUrl', $event)\"\n />\n <button\n v-if=\"canBrowseMedia\"\n class=\"tpl:mt-2 tpl:flex tpl:w-full tpl:items-center tpl:justify-center tpl:gap-1.5 tpl:rounded-md tpl:border tpl:px-3 tpl:py-2 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-border);\n color: var(--tpl-primary);\n background-color: var(--tpl-bg);\n \"\n @click=\"openMediaBrowser\"\n >\n <Image :size=\"14\" :stroke-width=\"1.5\" />\n {{ t.image.browseMedia }}\n </button>\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.video.altText }}</label>\n <MergeTagInput\n :model-value=\"block.alt\"\n type=\"text\"\n :placeholder=\"t.video.altTextPlaceholder\"\n @update:model-value=\"updateField('alt', $event)\"\n />\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.video.width }}</label>\n <select\n :class=\"inputClass\"\n :value=\"block.width\"\n @change=\"\n updateField(\n 'width',\n ($event.target as HTMLSelectElement).value === 'full'\n ? 'full'\n : Number(($event.target as HTMLSelectElement).value),\n )\n \"\n >\n <option value=\"full\">{{ t.video.fullWidth }}</option>\n <option value=\"300\">300px</option>\n <option value=\"400\">400px</option>\n <option value=\"500\">500px</option>\n </select>\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.title.align }}</label>\n <SlidingPillSelect\n :options=\"[\n { value: 'left', label: t.title.alignLeft },\n { value: 'center', label: t.title.alignCenter },\n { value: 'right', label: t.title.alignRight },\n ]\"\n :model-value=\"block.align\"\n @update:model-value=\"updateField('align', $event)\"\n />\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport MergeTagInput from \"../MergeTagInput.vue\";\nimport SlidingPillSelect from \"../SlidingPillSelect.vue\";\nimport { useI18n } from \"../../composables/useI18n\";\nimport { inputClass, labelClass } from \"../../constants/styleConstants\";\nimport type { VideoBlock } from \"@aswin.dev/types\";\nimport { containsMergeTag, SYNTAX_PRESETS } from \"@aswin.dev/types\";\nimport { Image } from \"@lucide/vue\";\nimport { computed, inject, ref } from \"vue\";\nimport { ON_REQUEST_MEDIA_KEY, MERGE_TAG_SYNTAX_KEY } from \"../../keys\";\nimport { useTimeoutFn } from \"@vueuse/core\";\n\nconst props = defineProps<{\n block: VideoBlock;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update\", updates: Partial<VideoBlock>): void;\n}>();\n\nconst { t } = useI18n();\nconst onRequestMedia = inject(ON_REQUEST_MEDIA_KEY, null);\nconst mergeTagSyntax = inject(MERGE_TAG_SYNTAX_KEY, SYNTAX_PRESETS.liquid);\n\nconst canBrowseMedia = computed(() => !!onRequestMedia);\nconst urlHasMergeTag = computed(() =>\n containsMergeTag(props.block.url, mergeTagSyntax),\n);\n\nconst pulseThumbnail = ref(false);\nconst { start: startPulseThumbnail } = useTimeoutFn(\n () => {\n pulseThumbnail.value = false;\n },\n 1000,\n { immediate: false },\n);\n\nfunction updateField(field: keyof VideoBlock, value: unknown): void {\n emit(\"update\", { [field]: value } as Partial<VideoBlock>);\n}\n\nasync function openMediaBrowser(): Promise<void> {\n const result = await onRequestMedia?.({ accept: [\"images\"] });\n if (result) {\n updateField(\"thumbnailUrl\", result.url);\n pulseThumbnail.value = true;\n startPulseThumbnail();\n }\n}\n</script>\n\n<template>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.video.videoUrl }}</label>\n <MergeTagInput\n :model-value=\"block.url\"\n type=\"url\"\n :placeholder=\"t.video.videoUrlPlaceholder\"\n @update:model-value=\"updateField('url', $event)\"\n />\n <label\n v-if=\"block.url\"\n class=\"tpl:mt-2 tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:text-[12px] tpl:text-[var(--tpl-text-muted)]\"\n >\n <input\n type=\"checkbox\"\n class=\"tpl:size-3.5 tpl:cursor-pointer tpl:accent-[var(--tpl-primary)]\"\n :checked=\"block.openInNewTab ?? false\"\n @change=\"\n updateField(\n 'openInNewTab',\n ($event.target as HTMLInputElement).checked,\n )\n \"\n />\n {{ t.video.openInNewTab }}\n </label>\n </div>\n <div v-if=\"urlHasMergeTag\" class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">\n {{ t.video.placeholderUrl }}\n <span class=\"tpl:font-normal tpl:text-[var(--tpl-text-dim)]\">\n {{ t.video.optional }}\n </span>\n </label>\n <input\n type=\"url\"\n :class=\"inputClass\"\n :value=\"block.placeholderUrl || ''\"\n :placeholder=\"t.video.placeholderUrlPlaceholder\"\n :title=\"t.video.placeholderUrlTooltip\"\n @input=\"\n updateField('placeholderUrl', ($event.target as HTMLInputElement).value)\n \"\n />\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">\n {{ t.video.customThumbnail }}\n <span class=\"tpl:font-normal tpl:text-[var(--tpl-text-dim)]\">\n {{ t.video.optional }}\n </span>\n </label>\n <MergeTagInput\n :model-value=\"block.thumbnailUrl\"\n type=\"url\"\n :placeholder=\"t.video.thumbnailPlaceholder\"\n :pulse=\"pulseThumbnail\"\n @update:model-value=\"updateField('thumbnailUrl', $event)\"\n />\n <button\n v-if=\"canBrowseMedia\"\n class=\"tpl:mt-2 tpl:flex tpl:w-full tpl:items-center tpl:justify-center tpl:gap-1.5 tpl:rounded-md tpl:border tpl:px-3 tpl:py-2 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-border);\n color: var(--tpl-primary);\n background-color: var(--tpl-bg);\n \"\n @click=\"openMediaBrowser\"\n >\n <Image :size=\"14\" :stroke-width=\"1.5\" />\n {{ t.image.browseMedia }}\n </button>\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.video.altText }}</label>\n <MergeTagInput\n :model-value=\"block.alt\"\n type=\"text\"\n :placeholder=\"t.video.altTextPlaceholder\"\n @update:model-value=\"updateField('alt', $event)\"\n />\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.video.width }}</label>\n <select\n :class=\"inputClass\"\n :value=\"block.width\"\n @change=\"\n updateField(\n 'width',\n ($event.target as HTMLSelectElement).value === 'full'\n ? 'full'\n : Number(($event.target as HTMLSelectElement).value),\n )\n \"\n >\n <option value=\"full\">{{ t.video.fullWidth }}</option>\n <option value=\"300\">300px</option>\n <option value=\"400\">400px</option>\n <option value=\"500\">500px</option>\n </select>\n </div>\n <div class=\"tpl:mb-3.5\">\n <label :class=\"labelClass\">{{ t.title.align }}</label>\n <SlidingPillSelect\n :options=\"[\n { value: 'left', label: t.title.alignLeft },\n { value: 'center', label: t.title.alignCenter },\n { value: 'right', label: t.title.alignRight },\n ]\"\n :model-value=\"block.align\"\n @update:model-value=\"updateField('align', $event)\"\n />\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { defineAsyncComponent } from \"vue\";\nimport ButtonToolbar from \"./toolbar/ButtonToolbar.vue\";\nimport CommonBlockSettings from \"./toolbar/CommonBlockSettings.vue\";\nimport FormToolbar from \"./toolbar/FormToolbar.vue\";\nimport InputToolbar from \"./toolbar/InputToolbar.vue\";\nconst CountdownToolbar = defineAsyncComponent(\n () => import(\"./toolbar/CountdownToolbar.vue\"),\n);\nimport CustomBlockToolbar from \"./toolbar/CustomBlockToolbar.vue\";\nimport DividerToolbar from \"./toolbar/DividerToolbar.vue\";\nimport HtmlToolbar from \"./toolbar/HtmlToolbar.vue\";\nimport ImageToolbar from \"./toolbar/ImageToolbar.vue\";\nimport MenuToolbar from \"./toolbar/MenuToolbar.vue\";\nimport SectionToolbar from \"./toolbar/SectionToolbar.vue\";\nimport SocialToolbar from \"./toolbar/SocialToolbar.vue\";\nimport SpacerToolbar from \"./toolbar/SpacerToolbar.vue\";\nimport TableToolbar from \"./toolbar/TableToolbar.vue\";\nimport TitleToolbar from \"./toolbar/TitleToolbar.vue\";\nimport VideoToolbar from \"./toolbar/VideoToolbar.vue\";\nimport { useI18n } from \"../composables/useI18n\";\nimport type {\n Block,\n ButtonBlock,\n CountdownBlock,\n CustomBlock,\n DividerBlock,\n FormBlock,\n HtmlBlock,\n ImageBlock,\n InputBlock,\n MenuBlock,\n SectionBlock,\n SocialIconsBlock,\n SpacerBlock,\n TableBlock,\n TitleBlock,\n VideoBlock,\n} from \"@aswin.dev/types\";\nimport { isCustomBlock } from \"@aswin.dev/types\";\nimport { Code, Copy, Trash2 } from \"@lucide/vue\";\nimport { computed, inject } from \"vue\";\nimport { blockTypeIcons } from \"../utils/blockTypeIcons\";\nimport { getBlockTypeLabel } from \"../utils/blockTypeLabels\";\nimport {\n FONTS_MANAGER_KEY,\n CUSTOM_BLOCK_DEFINITIONS_KEY,\n requireInject,\n} from \"../keys\";\n\nconst props = defineProps<{\n block: Block;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update\", updates: Partial<Block>): void;\n (e: \"delete\"): void;\n (e: \"duplicate\"): void;\n}>();\n\nconst { t } = useI18n();\n\nconst fontsManager = requireInject(FONTS_MANAGER_KEY, \"Toolbar\");\nconst customBlockDefinitions = inject(CUSTOM_BLOCK_DEFINITIONS_KEY, []);\n\nconst blockType = computed(() => props.block.type);\n\nconst isCustom = computed(() => isCustomBlock(props.block));\n\nconst customBlockDefinition = computed(() => {\n if (!isCustom.value) {\n return undefined;\n }\n return customBlockDefinitions.find(\n (d) => d.type === (props.block as CustomBlock).customType,\n );\n});\n\nconst blockTypeLabel = computed(() => {\n if (isCustom.value) {\n return (\n customBlockDefinition.value?.name ??\n (props.block as CustomBlock).customType\n );\n }\n\n return getBlockTypeLabel(blockType.value, t);\n});\n\n// Font families from shared fontsManager (provided by Editor.vue / CloudEditor.vue)\nconst fontFamilies = fontsManager.fonts;\n\nfunction handleUpdate(updates: Partial<Block>): void {\n emit(\"update\", updates);\n}\n</script>\n\n<template>\n <aside\n :aria-label=\"t.landmarks.blockToolbar\"\n class=\"tpl:flex tpl:w-full tpl:flex-1 tpl:flex-col tpl:bg-[var(--tpl-bg-elevated)]\"\n >\n <div\n class=\"tpl:flex tpl:items-center tpl:justify-between tpl:border-b tpl:border-[var(--tpl-border)] tpl:px-4 tpl:py-3.5\"\n >\n <div\n class=\"tpl:flex tpl:items-center tpl:gap-2 tpl:text-[var(--tpl-primary)]\"\n >\n <component\n :is=\"blockTypeIcons[blockType]\"\n v-if=\"blockTypeIcons[blockType]\"\n :size=\"16\"\n :stroke-width=\"1.5\"\n />\n <Code v-else-if=\"isCustom\" :size=\"16\" :stroke-width=\"1.5\" />\n <h3\n class=\"tpl:m-0 tpl:text-sm tpl:font-semibold tpl:text-[var(--tpl-text)]\"\n >\n {{ blockTypeLabel }}\n </h3>\n </div>\n <div class=\"tpl:flex tpl:gap-1\">\n <button\n class=\"tpl:flex tpl:size-7 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-md tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-hover)] tpl:text-[var(--tpl-text-muted)] tpl:transition-all tpl:duration-150 tpl:hover:bg-[var(--tpl-bg-active)] tpl:hover:text-[var(--tpl-text)]\"\n :title=\"t.toolbar.duplicate\"\n @click=\"emit('duplicate')\"\n >\n <Copy :size=\"14\" :stroke-width=\"2\" />\n </button>\n <button\n class=\"tpl:flex tpl:size-7 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-md tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-hover)] tpl:text-[var(--tpl-text-muted)] tpl:transition-all tpl:duration-150 tpl:hover:border-[var(--tpl-danger)] tpl:hover:bg-[var(--tpl-danger-light)] tpl:hover:text-[var(--tpl-danger)]\"\n :title=\"t.toolbar.delete\"\n @click=\"emit('delete')\"\n >\n <Trash2 :size=\"14\" :stroke-width=\"2\" />\n </button>\n </div>\n </div>\n\n <div class=\"tpl:flex-1 tpl:overflow-y-auto tpl:p-4\">\n <template v-if=\"isCustom\">\n <CustomBlockToolbar\n :block=\"block as CustomBlock\"\n @update-field-values=\"emit('update', { fieldValues: $event })\"\n @update-data-source-fetched=\"\n emit('update', { dataSourceFetched: $event })\n \"\n />\n </template>\n\n <SectionToolbar\n v-else-if=\"blockType === 'section'\"\n :block=\"block as SectionBlock\"\n @update=\"handleUpdate\"\n />\n\n <TitleToolbar\n v-else-if=\"blockType === 'title'\"\n :block=\"block as TitleBlock\"\n :font-families=\"fontFamilies\"\n @update=\"handleUpdate\"\n />\n\n <!-- Paragraph block: no text-specific sidebar controls — all formatting is in the TipTap toolbar -->\n <template v-else-if=\"blockType === 'paragraph'\" />\n\n <ImageToolbar\n v-else-if=\"blockType === 'image'\"\n :block=\"block as ImageBlock\"\n @update=\"handleUpdate\"\n />\n\n <VideoToolbar\n v-else-if=\"blockType === 'video'\"\n :block=\"block as VideoBlock\"\n @update=\"handleUpdate\"\n />\n\n <ButtonToolbar\n v-else-if=\"blockType === 'button'\"\n :block=\"block as ButtonBlock\"\n :font-families=\"fontFamilies\"\n @update=\"handleUpdate\"\n />\n\n <InputToolbar\n v-else-if=\"blockType === 'input'\"\n :block=\"block as InputBlock\"\n :font-families=\"fontFamilies\"\n @update=\"handleUpdate\"\n />\n\n <DividerToolbar\n v-else-if=\"blockType === 'divider'\"\n :block=\"block as DividerBlock\"\n @update=\"handleUpdate\"\n />\n\n <SocialToolbar\n v-else-if=\"blockType === 'social'\"\n :block=\"block as SocialIconsBlock\"\n @update=\"handleUpdate\"\n />\n\n <MenuToolbar\n v-else-if=\"blockType === 'menu'\"\n :block=\"block as MenuBlock\"\n :font-families=\"fontFamilies\"\n @update=\"handleUpdate\"\n />\n\n <TableToolbar\n v-else-if=\"blockType === 'table'\"\n :block=\"block as TableBlock\"\n :font-families=\"fontFamilies\"\n @update=\"handleUpdate\"\n />\n\n <SpacerToolbar\n v-else-if=\"blockType === 'spacer'\"\n :block=\"block as SpacerBlock\"\n @update=\"handleUpdate\"\n />\n\n <HtmlToolbar\n v-else-if=\"blockType === 'html'\"\n :block=\"block as HtmlBlock\"\n @update=\"handleUpdate\"\n />\n\n <CountdownToolbar\n v-else-if=\"blockType === 'countdown'\"\n :block=\"block as CountdownBlock\"\n :font-families=\"fontFamilies\"\n @update=\"handleUpdate\"\n />\n\n <FormToolbar\n v-else-if=\"blockType === 'form'\"\n :block=\"block as FormBlock\"\n @update=\"handleUpdate\"\n />\n\n <!-- Common block settings -->\n <CommonBlockSettings\n :block=\"block\"\n :is-first-section=\"blockType === 'paragraph'\"\n @update=\"handleUpdate\"\n />\n </div>\n </aside>\n</template>\n\n<style scoped>\n.tpl-collapsible {\n display: grid;\n grid-template-rows: 0fr;\n transition: grid-template-rows 200ms cubic-bezier(0.16, 1, 0.3, 1);\n}\n\n.tpl-collapsible--open {\n grid-template-rows: 1fr;\n}\n\n.tpl-collapsible > div {\n overflow: hidden;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { defineAsyncComponent } from \"vue\";\nimport ButtonToolbar from \"./toolbar/ButtonToolbar.vue\";\nimport CommonBlockSettings from \"./toolbar/CommonBlockSettings.vue\";\nimport FormToolbar from \"./toolbar/FormToolbar.vue\";\nimport InputToolbar from \"./toolbar/InputToolbar.vue\";\nconst CountdownToolbar = defineAsyncComponent(\n () => import(\"./toolbar/CountdownToolbar.vue\"),\n);\nimport CustomBlockToolbar from \"./toolbar/CustomBlockToolbar.vue\";\nimport DividerToolbar from \"./toolbar/DividerToolbar.vue\";\nimport HtmlToolbar from \"./toolbar/HtmlToolbar.vue\";\nimport ImageToolbar from \"./toolbar/ImageToolbar.vue\";\nimport MenuToolbar from \"./toolbar/MenuToolbar.vue\";\nimport SectionToolbar from \"./toolbar/SectionToolbar.vue\";\nimport SocialToolbar from \"./toolbar/SocialToolbar.vue\";\nimport SpacerToolbar from \"./toolbar/SpacerToolbar.vue\";\nimport TableToolbar from \"./toolbar/TableToolbar.vue\";\nimport TitleToolbar from \"./toolbar/TitleToolbar.vue\";\nimport VideoToolbar from \"./toolbar/VideoToolbar.vue\";\nimport { useI18n } from \"../composables/useI18n\";\nimport type {\n Block,\n ButtonBlock,\n CountdownBlock,\n CustomBlock,\n DividerBlock,\n FormBlock,\n HtmlBlock,\n ImageBlock,\n InputBlock,\n MenuBlock,\n SectionBlock,\n SocialIconsBlock,\n SpacerBlock,\n TableBlock,\n TitleBlock,\n VideoBlock,\n} from \"@aswin.dev/types\";\nimport { isCustomBlock } from \"@aswin.dev/types\";\nimport { Code, Copy, Trash2 } from \"@lucide/vue\";\nimport { computed, inject } from \"vue\";\nimport { blockTypeIcons } from \"../utils/blockTypeIcons\";\nimport { getBlockTypeLabel } from \"../utils/blockTypeLabels\";\nimport {\n FONTS_MANAGER_KEY,\n CUSTOM_BLOCK_DEFINITIONS_KEY,\n requireInject,\n} from \"../keys\";\n\nconst props = defineProps<{\n block: Block;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update\", updates: Partial<Block>): void;\n (e: \"delete\"): void;\n (e: \"duplicate\"): void;\n}>();\n\nconst { t } = useI18n();\n\nconst fontsManager = requireInject(FONTS_MANAGER_KEY, \"Toolbar\");\nconst customBlockDefinitions = inject(CUSTOM_BLOCK_DEFINITIONS_KEY, []);\n\nconst blockType = computed(() => props.block.type);\n\nconst isCustom = computed(() => isCustomBlock(props.block));\n\nconst customBlockDefinition = computed(() => {\n if (!isCustom.value) {\n return undefined;\n }\n return customBlockDefinitions.find(\n (d) => d.type === (props.block as CustomBlock).customType,\n );\n});\n\nconst blockTypeLabel = computed(() => {\n if (isCustom.value) {\n return (\n customBlockDefinition.value?.name ??\n (props.block as CustomBlock).customType\n );\n }\n\n return getBlockTypeLabel(blockType.value, t);\n});\n\n// Font families from shared fontsManager (provided by Editor.vue / CloudEditor.vue)\nconst fontFamilies = fontsManager.fonts;\n\nfunction handleUpdate(updates: Partial<Block>): void {\n emit(\"update\", updates);\n}\n</script>\n\n<template>\n <aside\n :aria-label=\"t.landmarks.blockToolbar\"\n class=\"tpl:flex tpl:w-full tpl:flex-1 tpl:flex-col tpl:bg-[var(--tpl-bg-elevated)]\"\n >\n <div\n class=\"tpl:flex tpl:items-center tpl:justify-between tpl:border-b tpl:border-[var(--tpl-border)] tpl:px-4 tpl:py-3.5\"\n >\n <div\n class=\"tpl:flex tpl:items-center tpl:gap-2 tpl:text-[var(--tpl-primary)]\"\n >\n <component\n :is=\"blockTypeIcons[blockType]\"\n v-if=\"blockTypeIcons[blockType]\"\n :size=\"16\"\n :stroke-width=\"1.5\"\n />\n <Code v-else-if=\"isCustom\" :size=\"16\" :stroke-width=\"1.5\" />\n <h3\n class=\"tpl:m-0 tpl:text-sm tpl:font-semibold tpl:text-[var(--tpl-text)]\"\n >\n {{ blockTypeLabel }}\n </h3>\n </div>\n <div class=\"tpl:flex tpl:gap-1\">\n <button\n class=\"tpl:flex tpl:size-7 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-md tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-hover)] tpl:text-[var(--tpl-text-muted)] tpl:transition-all tpl:duration-150 tpl:hover:bg-[var(--tpl-bg-active)] tpl:hover:text-[var(--tpl-text)]\"\n :title=\"t.toolbar.duplicate\"\n @click=\"emit('duplicate')\"\n >\n <Copy :size=\"14\" :stroke-width=\"2\" />\n </button>\n <button\n class=\"tpl:flex tpl:size-7 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-md tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-hover)] tpl:text-[var(--tpl-text-muted)] tpl:transition-all tpl:duration-150 tpl:hover:border-[var(--tpl-danger)] tpl:hover:bg-[var(--tpl-danger-light)] tpl:hover:text-[var(--tpl-danger)]\"\n :title=\"t.toolbar.delete\"\n @click=\"emit('delete')\"\n >\n <Trash2 :size=\"14\" :stroke-width=\"2\" />\n </button>\n </div>\n </div>\n\n <div class=\"tpl:flex-1 tpl:overflow-y-auto tpl:p-4\">\n <template v-if=\"isCustom\">\n <CustomBlockToolbar\n :block=\"block as CustomBlock\"\n @update-field-values=\"emit('update', { fieldValues: $event })\"\n @update-data-source-fetched=\"\n emit('update', { dataSourceFetched: $event })\n \"\n />\n </template>\n\n <SectionToolbar\n v-else-if=\"blockType === 'section'\"\n :block=\"block as SectionBlock\"\n @update=\"handleUpdate\"\n />\n\n <TitleToolbar\n v-else-if=\"blockType === 'title'\"\n :block=\"block as TitleBlock\"\n :font-families=\"fontFamilies\"\n @update=\"handleUpdate\"\n />\n\n <!-- Paragraph block: no text-specific sidebar controls — all formatting is in the TipTap toolbar -->\n <template v-else-if=\"blockType === 'paragraph'\" />\n\n <ImageToolbar\n v-else-if=\"blockType === 'image'\"\n :block=\"block as ImageBlock\"\n @update=\"handleUpdate\"\n />\n\n <VideoToolbar\n v-else-if=\"blockType === 'video'\"\n :block=\"block as VideoBlock\"\n @update=\"handleUpdate\"\n />\n\n <ButtonToolbar\n v-else-if=\"blockType === 'button'\"\n :block=\"block as ButtonBlock\"\n :font-families=\"fontFamilies\"\n @update=\"handleUpdate\"\n />\n\n <InputToolbar\n v-else-if=\"blockType === 'input'\"\n :block=\"block as InputBlock\"\n :font-families=\"fontFamilies\"\n @update=\"handleUpdate\"\n />\n\n <DividerToolbar\n v-else-if=\"blockType === 'divider'\"\n :block=\"block as DividerBlock\"\n @update=\"handleUpdate\"\n />\n\n <SocialToolbar\n v-else-if=\"blockType === 'social'\"\n :block=\"block as SocialIconsBlock\"\n @update=\"handleUpdate\"\n />\n\n <MenuToolbar\n v-else-if=\"blockType === 'menu'\"\n :block=\"block as MenuBlock\"\n :font-families=\"fontFamilies\"\n @update=\"handleUpdate\"\n />\n\n <TableToolbar\n v-else-if=\"blockType === 'table'\"\n :block=\"block as TableBlock\"\n :font-families=\"fontFamilies\"\n @update=\"handleUpdate\"\n />\n\n <SpacerToolbar\n v-else-if=\"blockType === 'spacer'\"\n :block=\"block as SpacerBlock\"\n @update=\"handleUpdate\"\n />\n\n <HtmlToolbar\n v-else-if=\"blockType === 'html'\"\n :block=\"block as HtmlBlock\"\n @update=\"handleUpdate\"\n />\n\n <CountdownToolbar\n v-else-if=\"blockType === 'countdown'\"\n :block=\"block as CountdownBlock\"\n :font-families=\"fontFamilies\"\n @update=\"handleUpdate\"\n />\n\n <FormToolbar\n v-else-if=\"blockType === 'form'\"\n :block=\"block as FormBlock\"\n @update=\"handleUpdate\"\n />\n\n <!-- Common block settings -->\n <CommonBlockSettings\n :block=\"block\"\n :is-first-section=\"blockType === 'paragraph'\"\n @update=\"handleUpdate\"\n />\n </div>\n </aside>\n</template>\n\n<style scoped>\n.tpl-collapsible {\n display: grid;\n grid-template-rows: 0fr;\n transition: grid-template-rows 200ms cubic-bezier(0.16, 1, 0.3, 1);\n}\n\n.tpl-collapsible--open {\n grid-template-rows: 1fr;\n}\n\n.tpl-collapsible > div {\n overflow: hidden;\n}\n</style>\n","<script setup lang=\"ts\">\nimport TemplateSettingsPanel from \"./TemplateSettings.vue\";\nimport Toolbar from \"./Toolbar.vue\";\nimport { useI18n } from \"../composables/useI18n\";\nimport { ACCESSIBILITY_LINT_KEY } from \"../keys\";\nimport type { Block, TemplateSettings } from \"@aswin.dev/types\";\nimport { Accessibility, LayoutTemplate, PanelTop, Settings } from \"@lucide/vue\";\nimport { computed, defineAsyncComponent, inject, ref, watch } from \"vue\";\n\nconst AccessibilityPanel = defineAsyncComponent(\n () => import(\"./sidebar/AccessibilityPanel.vue\"),\n);\n\nconst props = defineProps<{\n selectedBlock: Block | null;\n settings: TemplateSettings;\n shiftedLeft?: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update-block\", updates: Partial<Block>): void;\n (e: \"delete-block\"): void;\n (e: \"duplicate-block\"): void;\n (e: \"update-settings\", settings: Partial<TemplateSettings>): void;\n}>();\n\nconst { t } = useI18n();\n\ntype Tab = \"content\" | \"settings\" | \"accessibility\";\nconst activeTab = ref<Tab>(\"content\");\n\nconst lint = inject(ACCESSIBILITY_LINT_KEY, null);\nconst a11yEnabled = computed(() => lint !== null);\nconst a11yIssueCount = computed(() => lint?.issues.value.length ?? 0);\n\nfunction tabClass(tab: Tab): string {\n const isActive = activeTab.value === tab;\n if (isActive) {\n return \"tpl:flex-1 tpl:text-[var(--tpl-primary)]\";\n }\n return \"tpl:shrink-0 tpl:text-[var(--tpl-text-muted)] hover:tpl:text-[var(--tpl-text)]\";\n}\n\nfunction tabStyle(tab: Tab): Record<string, string> {\n const isActive = activeTab.value === tab;\n if (isActive) {\n return {\n backgroundColor: \"var(--tpl-bg)\",\n boxShadow: \"var(--tpl-shadow-md)\",\n };\n }\n return { backgroundColor: \"transparent\" };\n}\n\nwatch(\n () => props.selectedBlock,\n (newBlock) => {\n if (newBlock) {\n activeTab.value = \"content\";\n }\n },\n);\n</script>\n\n<template>\n <aside\n :aria-label=\"t.landmarks.rightSidebar\"\n class=\"tpl-right-sidebar tpl:absolute tpl:top-14 tpl:bottom-0 tpl:z-40 tpl:flex tpl:w-[320px] tpl:flex-col tpl:bg-[var(--tpl-bg-elevated)] tpl:transition-all tpl:duration-200 tpl:border-l tpl:border-[var(--tpl-border)]\"\n :class=\"shiftedLeft ? 'tpl:right-[360px]' : 'tpl:right-0'\"\n >\n <div\n role=\"tablist\"\n class=\"tpl:relative tpl:flex tpl:gap-1 tpl:border-b tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-active)] tpl:p-1.5\"\n >\n <button\n id=\"tpl-tab-content\"\n role=\"tab\"\n :aria-selected=\"activeTab === 'content'\"\n aria-controls=\"tpl-tabpanel-content\"\n :aria-label=\"t.sidebar.content\"\n :title=\"t.sidebar.content\"\n class=\"tpl:flex tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:gap-1.5 tpl:rounded-[var(--tpl-radius-sm)] tpl:border-none tpl:px-3 tpl:py-2 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-[120ms] tpl:ease-[cubic-bezier(0.16,1,0.3,1)]\"\n :class=\"tabClass('content')\"\n :style=\"tabStyle('content')\"\n @click=\"activeTab = 'content'\"\n >\n <PanelTop :size=\"14\" :stroke-width=\"2\" />\n <span v-if=\"activeTab === 'content'\">{{ t.sidebar.content }}</span>\n </button>\n <button\n id=\"tpl-tab-settings\"\n role=\"tab\"\n :aria-selected=\"activeTab === 'settings'\"\n aria-controls=\"tpl-tabpanel-settings\"\n :aria-label=\"t.sidebar.settings\"\n :title=\"t.sidebar.settings\"\n class=\"tpl:flex tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:gap-1.5 tpl:rounded-[var(--tpl-radius-sm)] tpl:border-none tpl:px-3 tpl:py-2 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-[120ms] tpl:ease-[cubic-bezier(0.16,1,0.3,1)]\"\n :class=\"tabClass('settings')\"\n :style=\"tabStyle('settings')\"\n @click=\"activeTab = 'settings'\"\n >\n <Settings :size=\"14\" :stroke-width=\"1.5\" />\n <span v-if=\"activeTab === 'settings'\">{{ t.sidebar.settings }}</span>\n </button>\n <button\n v-if=\"a11yEnabled\"\n id=\"tpl-tab-accessibility\"\n role=\"tab\"\n :aria-selected=\"activeTab === 'accessibility'\"\n aria-controls=\"tpl-tabpanel-accessibility\"\n :aria-label=\"t.accessibility.panelTabLabel\"\n :title=\"t.accessibility.panelTabLabel\"\n class=\"tpl:flex tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:gap-1.5 tpl:rounded-[var(--tpl-radius-sm)] tpl:border-none tpl:px-3 tpl:py-2 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-[120ms] tpl:ease-[cubic-bezier(0.16,1,0.3,1)]\"\n :class=\"tabClass('accessibility')\"\n :style=\"tabStyle('accessibility')\"\n @click=\"activeTab = 'accessibility'\"\n >\n <Accessibility :size=\"14\" :stroke-width=\"1.5\" />\n <span v-if=\"activeTab === 'accessibility'\">\n {{ t.accessibility.panelTabLabel }}\n </span>\n <span\n v-if=\"a11yIssueCount > 0\"\n class=\"tpl:ml-1 tpl:rounded-full tpl:bg-[var(--tpl-bg-hover)] tpl:px-1.5 tpl:text-[10px]\"\n >\n {{ a11yIssueCount }}\n </span>\n </button>\n </div>\n\n <div\n v-if=\"activeTab === 'content'\"\n id=\"tpl-tabpanel-content\"\n role=\"tabpanel\"\n aria-labelledby=\"tpl-tab-content\"\n class=\"tpl:flex tpl:flex-1 tpl:flex-col tpl:overflow-y-auto\"\n >\n <Toolbar\n v-if=\"selectedBlock\"\n :block=\"selectedBlock\"\n @update=\"emit('update-block', $event)\"\n @delete=\"emit('delete-block')\"\n @duplicate=\"emit('duplicate-block')\"\n />\n <div\n v-else\n class=\"tpl:flex tpl:flex-col tpl:items-center tpl:justify-center tpl:px-6 tpl:py-10 tpl:text-center tpl:text-[var(--tpl-text-muted)]\"\n >\n <div class=\"tpl:mb-4 tpl:text-[var(--tpl-text-dim)]\">\n <LayoutTemplate :size=\"40\" :stroke-width=\"1.5\" />\n </div>\n <h3\n class=\"tpl:m-0 tpl:mb-2 tpl:text-sm tpl:font-semibold tpl:text-[var(--tpl-text)]\"\n >\n {{ t.sidebar.noSelection }}\n </h3>\n <p class=\"tpl:m-0 tpl:text-sm tpl:leading-normal\">\n {{ t.sidebar.noSelectionHint }}\n </p>\n </div>\n </div>\n\n <div\n v-if=\"activeTab === 'settings'\"\n id=\"tpl-tabpanel-settings\"\n role=\"tabpanel\"\n aria-labelledby=\"tpl-tab-settings\"\n class=\"tpl:flex tpl:flex-1 tpl:flex-col tpl:overflow-y-auto\"\n >\n <TemplateSettingsPanel\n :settings=\"settings\"\n @update=\"emit('update-settings', $event)\"\n />\n </div>\n\n <div\n v-if=\"activeTab === 'accessibility' && a11yEnabled\"\n id=\"tpl-tabpanel-accessibility\"\n role=\"tabpanel\"\n aria-labelledby=\"tpl-tab-accessibility\"\n class=\"tpl:flex tpl:flex-1 tpl:flex-col tpl:overflow-y-auto\"\n >\n <AccessibilityPanel />\n </div>\n </aside>\n</template>\n","<script setup lang=\"ts\">\nimport TemplateSettingsPanel from \"./TemplateSettings.vue\";\nimport Toolbar from \"./Toolbar.vue\";\nimport { useI18n } from \"../composables/useI18n\";\nimport { ACCESSIBILITY_LINT_KEY } from \"../keys\";\nimport type { Block, TemplateSettings } from \"@aswin.dev/types\";\nimport { Accessibility, LayoutTemplate, PanelTop, Settings } from \"@lucide/vue\";\nimport { computed, defineAsyncComponent, inject, ref, watch } from \"vue\";\n\nconst AccessibilityPanel = defineAsyncComponent(\n () => import(\"./sidebar/AccessibilityPanel.vue\"),\n);\n\nconst props = defineProps<{\n selectedBlock: Block | null;\n settings: TemplateSettings;\n shiftedLeft?: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update-block\", updates: Partial<Block>): void;\n (e: \"delete-block\"): void;\n (e: \"duplicate-block\"): void;\n (e: \"update-settings\", settings: Partial<TemplateSettings>): void;\n}>();\n\nconst { t } = useI18n();\n\ntype Tab = \"content\" | \"settings\" | \"accessibility\";\nconst activeTab = ref<Tab>(\"content\");\n\nconst lint = inject(ACCESSIBILITY_LINT_KEY, null);\nconst a11yEnabled = computed(() => lint !== null);\nconst a11yIssueCount = computed(() => lint?.issues.value.length ?? 0);\n\nfunction tabClass(tab: Tab): string {\n const isActive = activeTab.value === tab;\n if (isActive) {\n return \"tpl:flex-1 tpl:text-[var(--tpl-primary)]\";\n }\n return \"tpl:shrink-0 tpl:text-[var(--tpl-text-muted)] hover:tpl:text-[var(--tpl-text)]\";\n}\n\nfunction tabStyle(tab: Tab): Record<string, string> {\n const isActive = activeTab.value === tab;\n if (isActive) {\n return {\n backgroundColor: \"var(--tpl-bg)\",\n boxShadow: \"var(--tpl-shadow-md)\",\n };\n }\n return { backgroundColor: \"transparent\" };\n}\n\nwatch(\n () => props.selectedBlock,\n (newBlock) => {\n if (newBlock) {\n activeTab.value = \"content\";\n }\n },\n);\n</script>\n\n<template>\n <aside\n :aria-label=\"t.landmarks.rightSidebar\"\n class=\"tpl-right-sidebar tpl:absolute tpl:top-14 tpl:bottom-0 tpl:z-40 tpl:flex tpl:w-[320px] tpl:flex-col tpl:bg-[var(--tpl-bg-elevated)] tpl:transition-all tpl:duration-200 tpl:border-l tpl:border-[var(--tpl-border)]\"\n :class=\"shiftedLeft ? 'tpl:right-[360px]' : 'tpl:right-0'\"\n >\n <div\n role=\"tablist\"\n class=\"tpl:relative tpl:flex tpl:gap-1 tpl:border-b tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-active)] tpl:p-1.5\"\n >\n <button\n id=\"tpl-tab-content\"\n role=\"tab\"\n :aria-selected=\"activeTab === 'content'\"\n aria-controls=\"tpl-tabpanel-content\"\n :aria-label=\"t.sidebar.content\"\n :title=\"t.sidebar.content\"\n class=\"tpl:flex tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:gap-1.5 tpl:rounded-[var(--tpl-radius-sm)] tpl:border-none tpl:px-3 tpl:py-2 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-[120ms] tpl:ease-[cubic-bezier(0.16,1,0.3,1)]\"\n :class=\"tabClass('content')\"\n :style=\"tabStyle('content')\"\n @click=\"activeTab = 'content'\"\n >\n <PanelTop :size=\"14\" :stroke-width=\"2\" />\n <span v-if=\"activeTab === 'content'\">{{ t.sidebar.content }}</span>\n </button>\n <button\n id=\"tpl-tab-settings\"\n role=\"tab\"\n :aria-selected=\"activeTab === 'settings'\"\n aria-controls=\"tpl-tabpanel-settings\"\n :aria-label=\"t.sidebar.settings\"\n :title=\"t.sidebar.settings\"\n class=\"tpl:flex tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:gap-1.5 tpl:rounded-[var(--tpl-radius-sm)] tpl:border-none tpl:px-3 tpl:py-2 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-[120ms] tpl:ease-[cubic-bezier(0.16,1,0.3,1)]\"\n :class=\"tabClass('settings')\"\n :style=\"tabStyle('settings')\"\n @click=\"activeTab = 'settings'\"\n >\n <Settings :size=\"14\" :stroke-width=\"1.5\" />\n <span v-if=\"activeTab === 'settings'\">{{ t.sidebar.settings }}</span>\n </button>\n <button\n v-if=\"a11yEnabled\"\n id=\"tpl-tab-accessibility\"\n role=\"tab\"\n :aria-selected=\"activeTab === 'accessibility'\"\n aria-controls=\"tpl-tabpanel-accessibility\"\n :aria-label=\"t.accessibility.panelTabLabel\"\n :title=\"t.accessibility.panelTabLabel\"\n class=\"tpl:flex tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:gap-1.5 tpl:rounded-[var(--tpl-radius-sm)] tpl:border-none tpl:px-3 tpl:py-2 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-[120ms] tpl:ease-[cubic-bezier(0.16,1,0.3,1)]\"\n :class=\"tabClass('accessibility')\"\n :style=\"tabStyle('accessibility')\"\n @click=\"activeTab = 'accessibility'\"\n >\n <Accessibility :size=\"14\" :stroke-width=\"1.5\" />\n <span v-if=\"activeTab === 'accessibility'\">\n {{ t.accessibility.panelTabLabel }}\n </span>\n <span\n v-if=\"a11yIssueCount > 0\"\n class=\"tpl:ml-1 tpl:rounded-full tpl:bg-[var(--tpl-bg-hover)] tpl:px-1.5 tpl:text-[10px]\"\n >\n {{ a11yIssueCount }}\n </span>\n </button>\n </div>\n\n <div\n v-if=\"activeTab === 'content'\"\n id=\"tpl-tabpanel-content\"\n role=\"tabpanel\"\n aria-labelledby=\"tpl-tab-content\"\n class=\"tpl:flex tpl:flex-1 tpl:flex-col tpl:overflow-y-auto\"\n >\n <Toolbar\n v-if=\"selectedBlock\"\n :block=\"selectedBlock\"\n @update=\"emit('update-block', $event)\"\n @delete=\"emit('delete-block')\"\n @duplicate=\"emit('duplicate-block')\"\n />\n <div\n v-else\n class=\"tpl:flex tpl:flex-col tpl:items-center tpl:justify-center tpl:px-6 tpl:py-10 tpl:text-center tpl:text-[var(--tpl-text-muted)]\"\n >\n <div class=\"tpl:mb-4 tpl:text-[var(--tpl-text-dim)]\">\n <LayoutTemplate :size=\"40\" :stroke-width=\"1.5\" />\n </div>\n <h3\n class=\"tpl:m-0 tpl:mb-2 tpl:text-sm tpl:font-semibold tpl:text-[var(--tpl-text)]\"\n >\n {{ t.sidebar.noSelection }}\n </h3>\n <p class=\"tpl:m-0 tpl:text-sm tpl:leading-normal\">\n {{ t.sidebar.noSelectionHint }}\n </p>\n </div>\n </div>\n\n <div\n v-if=\"activeTab === 'settings'\"\n id=\"tpl-tabpanel-settings\"\n role=\"tabpanel\"\n aria-labelledby=\"tpl-tab-settings\"\n class=\"tpl:flex tpl:flex-1 tpl:flex-col tpl:overflow-y-auto\"\n >\n <TemplateSettingsPanel\n :settings=\"settings\"\n @update=\"emit('update-settings', $event)\"\n />\n </div>\n\n <div\n v-if=\"activeTab === 'accessibility' && a11yEnabled\"\n id=\"tpl-tabpanel-accessibility\"\n role=\"tabpanel\"\n aria-labelledby=\"tpl-tab-accessibility\"\n class=\"tpl:flex tpl:flex-1 tpl:flex-col tpl:overflow-y-auto\"\n >\n <AccessibilityPanel />\n </div>\n </aside>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, inject } from \"vue\";\nimport { Undo2, Redo2 } from \"@lucide/vue\";\n\nimport { HISTORY_KEY } from \"../keys\";\nimport { useI18n } from \"../composables/useI18n\";\n\nconst props = defineProps<{\n /** Cloud: show collaboration undo toast before applying undo (matches keyboard shortcut). */\n onBeforeUndo?: () => void;\n}>();\n\nconst history = inject(HISTORY_KEY, null);\nconst { t } = useI18n();\n\nconst canUndo = computed(() => history?.canUndo.value ?? false);\nconst canRedo = computed(() => history?.canRedo.value ?? false);\n\nfunction undo(): void {\n if (!history || !canUndo.value) return;\n props.onBeforeUndo?.();\n history.undo();\n}\n\nfunction redo(): void {\n if (!history || !canRedo.value) return;\n history.redo();\n}\n</script>\n\n<template>\n <div\n v-if=\"history\"\n class=\"tpl:flex tpl:items-center tpl:gap-0.5\"\n role=\"group\"\n :aria-label=\"`${t.history.undo} / ${t.history.redo}`\"\n >\n <button\n type=\"button\"\n class=\"tpl-history-btn tpl:relative tpl:flex tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-[var(--tpl-radius-sm)] tpl:border-none tpl:p-2 tpl:text-[var(--tpl-text-muted)] tpl:transition-all tpl:duration-150\"\n :disabled=\"!canUndo\"\n :aria-label=\"t.history.undo\"\n :title=\"t.history.undo\"\n @click=\"undo\"\n >\n <Undo2 :size=\"18\" :stroke-width=\"1.5\" />\n </button>\n <button\n type=\"button\"\n class=\"tpl-history-btn tpl:relative tpl:flex tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-[var(--tpl-radius-sm)] tpl:border-none tpl:p-2 tpl:text-[var(--tpl-text-muted)] tpl:transition-all tpl:duration-150\"\n :disabled=\"!canRedo\"\n :aria-label=\"t.history.redo\"\n :title=\"t.history.redo\"\n @click=\"redo\"\n >\n <Redo2 :size=\"18\" :stroke-width=\"1.5\" />\n </button>\n </div>\n</template>\n\n<style scoped>\n.tpl-history-btn:hover:not(:disabled) {\n background-color: var(--tpl-bg-hover);\n}\n\n.tpl-history-btn:active:not(:disabled) {\n transform: scale(0.92);\n}\n\n.tpl-history-btn:disabled {\n cursor: not-allowed;\n opacity: 0.35;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, inject } from \"vue\";\nimport { Undo2, Redo2 } from \"@lucide/vue\";\n\nimport { HISTORY_KEY } from \"../keys\";\nimport { useI18n } from \"../composables/useI18n\";\n\nconst props = defineProps<{\n /** Cloud: show collaboration undo toast before applying undo (matches keyboard shortcut). */\n onBeforeUndo?: () => void;\n}>();\n\nconst history = inject(HISTORY_KEY, null);\nconst { t } = useI18n();\n\nconst canUndo = computed(() => history?.canUndo.value ?? false);\nconst canRedo = computed(() => history?.canRedo.value ?? false);\n\nfunction undo(): void {\n if (!history || !canUndo.value) return;\n props.onBeforeUndo?.();\n history.undo();\n}\n\nfunction redo(): void {\n if (!history || !canRedo.value) return;\n history.redo();\n}\n</script>\n\n<template>\n <div\n v-if=\"history\"\n class=\"tpl:flex tpl:items-center tpl:gap-0.5\"\n role=\"group\"\n :aria-label=\"`${t.history.undo} / ${t.history.redo}`\"\n >\n <button\n type=\"button\"\n class=\"tpl-history-btn tpl:relative tpl:flex tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-[var(--tpl-radius-sm)] tpl:border-none tpl:p-2 tpl:text-[var(--tpl-text-muted)] tpl:transition-all tpl:duration-150\"\n :disabled=\"!canUndo\"\n :aria-label=\"t.history.undo\"\n :title=\"t.history.undo\"\n @click=\"undo\"\n >\n <Undo2 :size=\"18\" :stroke-width=\"1.5\" />\n </button>\n <button\n type=\"button\"\n class=\"tpl-history-btn tpl:relative tpl:flex tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-[var(--tpl-radius-sm)] tpl:border-none tpl:p-2 tpl:text-[var(--tpl-text-muted)] tpl:transition-all tpl:duration-150\"\n :disabled=\"!canRedo\"\n :aria-label=\"t.history.redo\"\n :title=\"t.history.redo\"\n @click=\"redo\"\n >\n <Redo2 :size=\"18\" :stroke-width=\"1.5\" />\n </button>\n </div>\n</template>\n\n<style scoped>\n.tpl-history-btn:hover:not(:disabled) {\n background-color: var(--tpl-bg-hover);\n}\n\n.tpl-history-btn:active:not(:disabled) {\n transform: scale(0.92);\n}\n\n.tpl-history-btn:disabled {\n cursor: not-allowed;\n opacity: 0.35;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../composables/useI18n\";\nimport type { ViewportSize } from \"@aswin.dev/types\";\nimport { Monitor, Smartphone } from \"@lucide/vue\";\nimport { computed, watch } from \"vue\";\n\nconst props = defineProps<{\n viewport: ViewportSize;\n}>();\n\nconst emit = defineEmits<{\n (e: \"change\", viewport: ViewportSize): void;\n}>();\n\nconst { t } = useI18n();\n\nconst viewports = computed(() => [\n { value: \"desktop\" as ViewportSize, label: t.viewport.desktop },\n { value: \"mobile\" as ViewportSize, label: t.viewport.mobile },\n]);\n\n/** Avoid broken pill animation while \"tablet\" is being migrated off. */\nconst viewportForUi = computed(() =>\n props.viewport === \"tablet\" ? \"desktop\" : props.viewport,\n);\n\n/** Tablet preview is hidden; normalize any persisted \"tablet\" selection. */\nwatch(\n () => props.viewport,\n (v) => {\n if (v === \"tablet\") {\n emit(\"change\", \"desktop\");\n }\n },\n { immediate: true },\n);\n\nconst pillOffset = computed(() => {\n const index = viewports.value.findIndex(\n (vp) => vp.value === viewportForUi.value,\n );\n return `translateX(${Math.max(0, index) * 100}%)`;\n});\n</script>\n\n<template>\n <div\n role=\"radiogroup\"\n :aria-label=\"t.viewport.label\"\n class=\"tpl:relative tpl:grid tpl:rounded-[var(--tpl-radius-sm)] tpl:p-1\"\n :style=\"{\n gridTemplateColumns: `repeat(${viewports.length}, 1fr)`,\n backgroundColor: 'var(--tpl-bg-hover)',\n }\"\n >\n <!-- Sliding pill -->\n <div\n class=\"tpl:absolute tpl:inset-y-1 tpl:rounded-[var(--tpl-radius-sm)]\"\n :style=\"{\n left: '4px',\n width: `calc((100% - 8px) / ${viewports.length})`,\n transform: pillOffset,\n backgroundColor: 'var(--tpl-bg)',\n boxShadow: 'var(--tpl-shadow)',\n transition: 'transform 120ms cubic-bezier(0.16, 1, 0.3, 1)',\n }\"\n ></div>\n\n <button\n v-for=\"vp in viewports\"\n :key=\"vp.value\"\n role=\"radio\"\n :aria-checked=\"viewportForUi === vp.value\"\n :aria-label=\"vp.label\"\n class=\"tpl:relative tpl:z-10 tpl:flex tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:gap-1.5 tpl:rounded-md tpl:border-none tpl:bg-transparent tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium\"\n :style=\"{\n color:\n viewportForUi === vp.value\n ? 'var(--tpl-primary)'\n : 'var(--tpl-text-muted)',\n transition: 'color 120ms cubic-bezier(0.16, 1, 0.3, 1)',\n }\"\n :title=\"vp.label\"\n @click=\"emit('change', vp.value)\"\n >\n <Monitor v-if=\"vp.value === 'desktop'\" :size=\"18\" :stroke-width=\"1.5\" />\n <Smartphone v-else :size=\"18\" :stroke-width=\"1.5\" />\n <span>{{ vp.label }}</span>\n </button>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../composables/useI18n\";\nimport type { ViewportSize } from \"@aswin.dev/types\";\nimport { Monitor, Smartphone } from \"@lucide/vue\";\nimport { computed, watch } from \"vue\";\n\nconst props = defineProps<{\n viewport: ViewportSize;\n}>();\n\nconst emit = defineEmits<{\n (e: \"change\", viewport: ViewportSize): void;\n}>();\n\nconst { t } = useI18n();\n\nconst viewports = computed(() => [\n { value: \"desktop\" as ViewportSize, label: t.viewport.desktop },\n { value: \"mobile\" as ViewportSize, label: t.viewport.mobile },\n]);\n\n/** Avoid broken pill animation while \"tablet\" is being migrated off. */\nconst viewportForUi = computed(() =>\n props.viewport === \"tablet\" ? \"desktop\" : props.viewport,\n);\n\n/** Tablet preview is hidden; normalize any persisted \"tablet\" selection. */\nwatch(\n () => props.viewport,\n (v) => {\n if (v === \"tablet\") {\n emit(\"change\", \"desktop\");\n }\n },\n { immediate: true },\n);\n\nconst pillOffset = computed(() => {\n const index = viewports.value.findIndex(\n (vp) => vp.value === viewportForUi.value,\n );\n return `translateX(${Math.max(0, index) * 100}%)`;\n});\n</script>\n\n<template>\n <div\n role=\"radiogroup\"\n :aria-label=\"t.viewport.label\"\n class=\"tpl:relative tpl:grid tpl:rounded-[var(--tpl-radius-sm)] tpl:p-1\"\n :style=\"{\n gridTemplateColumns: `repeat(${viewports.length}, 1fr)`,\n backgroundColor: 'var(--tpl-bg-hover)',\n }\"\n >\n <!-- Sliding pill -->\n <div\n class=\"tpl:absolute tpl:inset-y-1 tpl:rounded-[var(--tpl-radius-sm)]\"\n :style=\"{\n left: '4px',\n width: `calc((100% - 8px) / ${viewports.length})`,\n transform: pillOffset,\n backgroundColor: 'var(--tpl-bg)',\n boxShadow: 'var(--tpl-shadow)',\n transition: 'transform 120ms cubic-bezier(0.16, 1, 0.3, 1)',\n }\"\n ></div>\n\n <button\n v-for=\"vp in viewports\"\n :key=\"vp.value\"\n role=\"radio\"\n :aria-checked=\"viewportForUi === vp.value\"\n :aria-label=\"vp.label\"\n class=\"tpl:relative tpl:z-10 tpl:flex tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:gap-1.5 tpl:rounded-md tpl:border-none tpl:bg-transparent tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium\"\n :style=\"{\n color:\n viewportForUi === vp.value\n ? 'var(--tpl-primary)'\n : 'var(--tpl-text-muted)',\n transition: 'color 120ms cubic-bezier(0.16, 1, 0.3, 1)',\n }\"\n :title=\"vp.label\"\n @click=\"emit('change', vp.value)\"\n >\n <Monitor v-if=\"vp.value === 'desktop'\" :size=\"18\" :stroke-width=\"1.5\" />\n <Smartphone v-else :size=\"18\" :stroke-width=\"1.5\" />\n <span>{{ vp.label }}</span>\n </button>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../composables/useI18n\";\nimport { Eye, EyeOff } from \"@lucide/vue\";\n\ndefineProps<{\n previewMode: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"change\", previewMode: boolean): void;\n}>();\n\nconst { t } = useI18n();\n</script>\n\n<template>\n <button\n class=\"tpl-preview-toggle tpl:relative tpl:flex tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-[var(--tpl-radius-sm)] tpl:border-none tpl:p-2 tpl:transition-all tpl:duration-150\"\n :style=\"{\n color: previewMode ? 'var(--tpl-primary)' : 'var(--tpl-text-muted)',\n backgroundColor: previewMode ? 'var(--tpl-primary-light)' : 'transparent',\n }\"\n :aria-label=\"previewMode ? t.previewMode.disable : t.previewMode.enable\"\n :title=\"previewMode ? t.previewMode.disable : t.previewMode.enable\"\n :aria-pressed=\"previewMode\"\n @click=\"emit('change', !previewMode)\"\n >\n <Transition\n enter-active-class=\"tpl-icon-enter-active\"\n leave-active-class=\"tpl-icon-leave-active\"\n enter-from-class=\"tpl-icon-enter-from\"\n leave-to-class=\"tpl-icon-leave-to\"\n mode=\"out-in\"\n >\n <Eye v-if=\"previewMode\" key=\"eye\" :size=\"18\" :stroke-width=\"1.5\" />\n <EyeOff v-else key=\"eye-off\" :size=\"18\" :stroke-width=\"1.5\" />\n </Transition>\n </button>\n</template>\n\n<style scoped>\n.tpl-icon-enter-active,\n.tpl-icon-leave-active {\n transition:\n opacity 120ms cubic-bezier(0.16, 1, 0.3, 1),\n transform 120ms cubic-bezier(0.16, 1, 0.3, 1);\n}\n\n.tpl-icon-enter-from {\n opacity: 0;\n transform: scale(0.8);\n}\n\n.tpl-icon-leave-to {\n opacity: 0;\n transform: scale(0.8);\n}\n\n.tpl-preview-toggle:hover {\n background-color: var(--tpl-bg-hover);\n}\n\n.tpl-preview-toggle:active {\n transform: scale(0.92);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../composables/useI18n\";\nimport { Eye, EyeOff } from \"@lucide/vue\";\n\ndefineProps<{\n previewMode: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"change\", previewMode: boolean): void;\n}>();\n\nconst { t } = useI18n();\n</script>\n\n<template>\n <button\n class=\"tpl-preview-toggle tpl:relative tpl:flex tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-[var(--tpl-radius-sm)] tpl:border-none tpl:p-2 tpl:transition-all tpl:duration-150\"\n :style=\"{\n color: previewMode ? 'var(--tpl-primary)' : 'var(--tpl-text-muted)',\n backgroundColor: previewMode ? 'var(--tpl-primary-light)' : 'transparent',\n }\"\n :aria-label=\"previewMode ? t.previewMode.disable : t.previewMode.enable\"\n :title=\"previewMode ? t.previewMode.disable : t.previewMode.enable\"\n :aria-pressed=\"previewMode\"\n @click=\"emit('change', !previewMode)\"\n >\n <Transition\n enter-active-class=\"tpl-icon-enter-active\"\n leave-active-class=\"tpl-icon-leave-active\"\n enter-from-class=\"tpl-icon-enter-from\"\n leave-to-class=\"tpl-icon-leave-to\"\n mode=\"out-in\"\n >\n <Eye v-if=\"previewMode\" key=\"eye\" :size=\"18\" :stroke-width=\"1.5\" />\n <EyeOff v-else key=\"eye-off\" :size=\"18\" :stroke-width=\"1.5\" />\n </Transition>\n </button>\n</template>\n\n<style scoped>\n.tpl-icon-enter-active,\n.tpl-icon-leave-active {\n transition:\n opacity 120ms cubic-bezier(0.16, 1, 0.3, 1),\n transform 120ms cubic-bezier(0.16, 1, 0.3, 1);\n}\n\n.tpl-icon-enter-from {\n opacity: 0;\n transform: scale(0.8);\n}\n\n.tpl-icon-leave-to {\n opacity: 0;\n transform: scale(0.8);\n}\n\n.tpl-preview-toggle:hover {\n background-color: var(--tpl-bg-hover);\n}\n\n.tpl-preview-toggle:active {\n transform: scale(0.92);\n}\n</style>\n","<!--\n Canvas Dark Mode Preview Toggle\n\n This toggle simulates how the email template will look when the recipient's\n email client uses dark mode (e.g. Gmail, Outlook, Apple Mail in dark theme).\n It applies a CSS filter (invert + hue-rotate) to the canvas area only.\n\n This is NOT the editor UI theme toggle. The editor UI theme (light/dark/auto)\n is controlled externally via the `uiTheme` config option or `editor.setTheme()`.\n-->\n<script setup lang=\"ts\">\nimport { useI18n } from \"../composables/useI18n\";\nimport { Moon, Sun } from \"@lucide/vue\";\n\ndefineProps<{\n darkMode: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"change\", darkMode: boolean): void;\n}>();\n\nconst { t } = useI18n();\n</script>\n\n<template>\n <button\n class=\"tpl-dark-mode-toggle tpl:relative tpl:flex tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-[var(--tpl-radius-sm)] tpl:border-none tpl:p-2 tpl:transition-all tpl:duration-150\"\n :style=\"{\n color: darkMode ? 'var(--tpl-primary)' : 'var(--tpl-text-muted)',\n backgroundColor: darkMode ? 'var(--tpl-primary-light)' : 'transparent',\n }\"\n :aria-label=\"darkMode ? t.darkMode.disable : t.darkMode.enable\"\n :title=\"darkMode ? t.darkMode.disable : t.darkMode.enable\"\n :aria-pressed=\"darkMode\"\n @click=\"emit('change', !darkMode)\"\n >\n <Transition\n enter-active-class=\"tpl-icon-enter-active\"\n leave-active-class=\"tpl-icon-leave-active\"\n enter-from-class=\"tpl-icon-enter-from\"\n leave-to-class=\"tpl-icon-leave-to\"\n mode=\"out-in\"\n >\n <Moon v-if=\"darkMode\" key=\"moon\" :size=\"18\" :stroke-width=\"1.5\" />\n <Sun v-else key=\"sun\" :size=\"18\" :stroke-width=\"1.5\" />\n </Transition>\n </button>\n</template>\n\n<style scoped>\n.tpl-icon-enter-active,\n.tpl-icon-leave-active {\n transition:\n opacity 120ms cubic-bezier(0.16, 1, 0.3, 1),\n transform 120ms cubic-bezier(0.16, 1, 0.3, 1);\n}\n\n.tpl-icon-enter-from {\n opacity: 0;\n transform: scale(0.8);\n}\n\n.tpl-icon-leave-to {\n opacity: 0;\n transform: scale(0.8);\n}\n\n.tpl-dark-mode-toggle:hover {\n background-color: var(--tpl-bg-hover);\n}\n\n.tpl-dark-mode-toggle:active {\n transform: scale(0.92);\n}\n</style>\n","<!--\n Canvas Dark Mode Preview Toggle\n\n This toggle simulates how the email template will look when the recipient's\n email client uses dark mode (e.g. Gmail, Outlook, Apple Mail in dark theme).\n It applies a CSS filter (invert + hue-rotate) to the canvas area only.\n\n This is NOT the editor UI theme toggle. The editor UI theme (light/dark/auto)\n is controlled externally via the `uiTheme` config option or `editor.setTheme()`.\n-->\n<script setup lang=\"ts\">\nimport { useI18n } from \"../composables/useI18n\";\nimport { Moon, Sun } from \"@lucide/vue\";\n\ndefineProps<{\n darkMode: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"change\", darkMode: boolean): void;\n}>();\n\nconst { t } = useI18n();\n</script>\n\n<template>\n <button\n class=\"tpl-dark-mode-toggle tpl:relative tpl:flex tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-[var(--tpl-radius-sm)] tpl:border-none tpl:p-2 tpl:transition-all tpl:duration-150\"\n :style=\"{\n color: darkMode ? 'var(--tpl-primary)' : 'var(--tpl-text-muted)',\n backgroundColor: darkMode ? 'var(--tpl-primary-light)' : 'transparent',\n }\"\n :aria-label=\"darkMode ? t.darkMode.disable : t.darkMode.enable\"\n :title=\"darkMode ? t.darkMode.disable : t.darkMode.enable\"\n :aria-pressed=\"darkMode\"\n @click=\"emit('change', !darkMode)\"\n >\n <Transition\n enter-active-class=\"tpl-icon-enter-active\"\n leave-active-class=\"tpl-icon-leave-active\"\n enter-from-class=\"tpl-icon-enter-from\"\n leave-to-class=\"tpl-icon-leave-to\"\n mode=\"out-in\"\n >\n <Moon v-if=\"darkMode\" key=\"moon\" :size=\"18\" :stroke-width=\"1.5\" />\n <Sun v-else key=\"sun\" :size=\"18\" :stroke-width=\"1.5\" />\n </Transition>\n </button>\n</template>\n\n<style scoped>\n.tpl-icon-enter-active,\n.tpl-icon-leave-active {\n transition:\n opacity 120ms cubic-bezier(0.16, 1, 0.3, 1),\n transform 120ms cubic-bezier(0.16, 1, 0.3, 1);\n}\n\n.tpl-icon-enter-from {\n opacity: 0;\n transform: scale(0.8);\n}\n\n.tpl-icon-leave-to {\n opacity: 0;\n transform: scale(0.8);\n}\n\n.tpl-dark-mode-toggle:hover {\n background-color: var(--tpl-bg-hover);\n}\n\n.tpl-dark-mode-toggle:active {\n transform: scale(0.92);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../composables/useI18n\";\n\ndefineProps<{\n /** Positioning classes for the footer (left/right offsets). */\n positionClass?: string | string[];\n}>();\n\nconst { t } = useI18n();\n</script>\n\n<template>\n <footer\n class=\"tpl:pointer-events-none tpl:absolute tpl:bottom-0 tpl:z-50 tpl:flex tpl:h-8 tpl:items-center tpl:justify-end tpl:pr-4 tpl:text-[9px] tpl:opacity-90 tpl:transition-all tpl:duration-300 tpl:text-[var(--tpl-text-dim)]\"\n :class=\"positionClass\"\n >\n <div\n class=\"tpl:pointer-events-auto tpl:flex tpl:items-center tpl:gap-1.5 tpl:rounded-tl-lg tpl:p-1\"\n style=\"\n background-color: color-mix(\n in srgb,\n var(--tpl-canvas-bg) 85%,\n transparent\n );\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n \"\n >\n <span>{{ t.footer.poweredBy }}</span>\n <a\n href=\"https://templatical.com\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"tpl:inline-flex tpl:items-center tpl:gap-1 tpl:font-medium tpl:transition-colors tpl:duration-150 hover:tpl:opacity-80 tpl:text-[var(--tpl-text-muted)]\"\n style=\"text-decoration: none\"\n >\n <img\n width=\"14\"\n height=\"14\"\n src=\"https://templatical.com/logo.svg\"\n alt=\"\"\n />\n Templatical\n </a>\n <span class=\"tpl:text-[var(--tpl-border)]\">·</span>\n <a\n href=\"https://github.com/templatical/sdk\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"tpl:transition-colors tpl:duration-150 hover:tpl:opacity-80 tpl:text-[var(--tpl-text-dim)]\"\n style=\"text-decoration: none\"\n >\n {{ t.footer.openSource }}\n </a>\n </div>\n </footer>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../composables/useI18n\";\n\ndefineProps<{\n /** Positioning classes for the footer (left/right offsets). */\n positionClass?: string | string[];\n}>();\n\nconst { t } = useI18n();\n</script>\n\n<template>\n <footer\n class=\"tpl:pointer-events-none tpl:absolute tpl:bottom-0 tpl:z-50 tpl:flex tpl:h-8 tpl:items-center tpl:justify-end tpl:pr-4 tpl:text-[9px] tpl:opacity-90 tpl:transition-all tpl:duration-300 tpl:text-[var(--tpl-text-dim)]\"\n :class=\"positionClass\"\n >\n <div\n class=\"tpl:pointer-events-auto tpl:flex tpl:items-center tpl:gap-1.5 tpl:rounded-tl-lg tpl:p-1\"\n style=\"\n background-color: color-mix(\n in srgb,\n var(--tpl-canvas-bg) 85%,\n transparent\n );\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n \"\n >\n <span>{{ t.footer.poweredBy }}</span>\n <a\n href=\"https://templatical.com\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"tpl:inline-flex tpl:items-center tpl:gap-1 tpl:font-medium tpl:transition-colors tpl:duration-150 hover:tpl:opacity-80 tpl:text-[var(--tpl-text-muted)]\"\n style=\"text-decoration: none\"\n >\n <img\n width=\"14\"\n height=\"14\"\n src=\"https://templatical.com/logo.svg\"\n alt=\"\"\n />\n Templatical\n </a>\n <span class=\"tpl:text-[var(--tpl-border)]\">·</span>\n <a\n href=\"https://github.com/templatical/sdk\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"tpl:transition-colors tpl:duration-150 hover:tpl:opacity-80 tpl:text-[var(--tpl-text-dim)]\"\n style=\"text-decoration: none\"\n >\n {{ t.footer.openSource }}\n </a>\n </div>\n </footer>\n</template>\n","/**\n * Guided tour types for Templatical Editor `init()` / `initCloud()`.\n */\n\n/** Reserved tooltip placement hints for layout inside the viewport. */\nexport type EditorTourStepPlacement =\n | \"canvas\"\n | \"sidebar\"\n | \"rightSidebar\"\n | \"below\";\n\nexport interface EditorTourStep {\n /** Any valid `document.querySelector` selector (editor internals or host DOM). */\n target: string;\n title: string;\n text: string;\n placement?: EditorTourStepPlacement;\n /**\n * When the target is absent, skip this step. Default `true`.\n * Set `false` to keep the step even if the node is missing (rect will be null — avoided).\n */\n skipIfMissing?: boolean;\n}\n\nexport interface EditorTourConfig {\n /**\n * Steps to show in order. Omit to use built-in defaults (canvas, block rail, right sidebar).\n */\n steps?: EditorTourStep[];\n /** Start automatically after mount when the tour has not been dismissed. Default `false`. */\n autoStart?: boolean;\n /**\n * `localStorage` key for dismissal persistence.\n * Set to `false` to disable persistence (tour can restart every load).\n * Default key is `DEFAULT_EDITOR_TOUR_STORAGE_KEY` when omitted.\n */\n storageKey?: string | false;\n}\n\nexport interface EditorTourStartOptions {\n /** Zero-based index into the active (filtered) step list */\n stepIndex?: number;\n}\n\nexport const DEFAULT_EDITOR_TOUR_STORAGE_KEY =\n \"templatical-editor-tour-dismissed\";\n","import type { EditorTourConfig } from \"../types/editor-tour\";\nimport { DEFAULT_EDITOR_TOUR_STORAGE_KEY } from \"../types/editor-tour\";\n\nexport function resolveTourStorageKey(\n storageKey: EditorTourConfig[\"storageKey\"],\n): string | null {\n if (storageKey === false) return null;\n return storageKey ?? DEFAULT_EDITOR_TOUR_STORAGE_KEY;\n}\n\nfunction safeGetItem(key: string): string | null {\n try {\n return localStorage.getItem(key);\n } catch {\n return null;\n }\n}\n\nfunction safeSetItem(key: string, value: string): void {\n try {\n localStorage.setItem(key, value);\n } catch {\n /* ignore quota / private mode */\n }\n}\n\nfunction safeRemoveItem(key: string): void {\n try {\n localStorage.removeItem(key);\n } catch {\n /* ignore */\n }\n}\n\n/** Whether the user has dismissed the tour (only when persistence is enabled). */\nexport function readTourDismissedState(\n tour: EditorTourConfig | undefined,\n): boolean {\n if (!tour) return true;\n const key = resolveTourStorageKey(tour.storageKey);\n if (key === null) return false;\n const raw = safeGetItem(key);\n if (raw === null) return false;\n try {\n return JSON.parse(raw) === true;\n } catch {\n return raw === \"true\";\n }\n}\n\nexport function writeTourDismissedState(\n tour: EditorTourConfig | undefined,\n): void {\n if (!tour) return;\n const key = resolveTourStorageKey(tour.storageKey);\n if (key === null) return;\n safeSetItem(key, JSON.stringify(true));\n}\n\nexport function clearTourDismissedState(\n tour: EditorTourConfig | undefined,\n): void {\n if (!tour) return;\n const key = resolveTourStorageKey(tour.storageKey);\n if (key === null) return;\n safeRemoveItem(key);\n}\n","<script setup lang=\"ts\">\nimport { ChevronRight } from \"@lucide/vue\";\nimport {\n useIntervalFn,\n usePreferredReducedMotion,\n useScrollLock,\n} from \"@vueuse/core\";\nimport {\n computed,\n nextTick,\n onMounted,\n onUnmounted,\n ref,\n shallowRef,\n watch,\n} from \"vue\";\nimport { useI18n } from \"../composables/useI18n\";\nimport type {\n EditorTourConfig,\n EditorTourStartOptions,\n EditorTourStep,\n EditorTourStepPlacement,\n} from \"../types/editor-tour\";\nimport {\n clearTourDismissedState,\n readTourDismissedState,\n writeTourDismissedState,\n} from \"../utils/tourStorage\";\n\nconst props = withDefaults(\n defineProps<{\n tourConfig: EditorTourConfig;\n /**\n * Align tooltip palette with the editor chrome (Teleport is outside `.tpl`,\n * so theme CSS variables do not apply unless we mirror light/dark here).\n */\n darkMode?: boolean;\n }>(),\n { darkMode: false },\n);\n\nconst { t, format } = useI18n();\n\nconst tourActive = ref(false);\nconst stepIndex = ref(0);\nconst targetRect = ref<DOMRect | null>(null);\nconst typedText = ref(\"\");\nconst prefersReducedMotion = usePreferredReducedMotion();\nconst activeSteps = shallowRef<EditorTourStep[]>([]);\n\nconst bodyScrollLocked = useScrollLock(document.body);\n\nwatch(\n tourActive,\n (active, _was, onCleanup) => {\n bodyScrollLocked.value = active;\n if (!active) return;\n const onKey = (e: KeyboardEvent): void => {\n if (e.key === \"Escape\") {\n e.preventDefault();\n dismiss(true);\n }\n };\n document.addEventListener(\"keydown\", onKey, true);\n onCleanup(() => document.removeEventListener(\"keydown\", onKey, true));\n },\n { immediate: true },\n);\n\nlet typewriterCharIndex = 0;\nconst { pause: stopTypewriter, resume: resumeTypewriter } = useIntervalFn(\n () => {\n const fullText = currentStepText.value;\n if (typewriterCharIndex < fullText.length) {\n typedText.value = fullText.slice(0, typewriterCharIndex + 1);\n typewriterCharIndex++;\n } else {\n stopTypewriter();\n }\n },\n 25,\n { immediate: false },\n);\n\nconst defaultSteps = computed<EditorTourStep[]>(() => [\n {\n target: \".tpl-body\",\n placement: \"canvas\",\n title: t.tour.defaults.canvas.title,\n text: t.tour.defaults.canvas.text,\n },\n {\n target: \".tpl-sidebar-rail\",\n placement: \"sidebar\",\n title: t.tour.defaults.sidebar.title,\n text: t.tour.defaults.sidebar.text,\n },\n {\n target: \".tpl-right-sidebar\",\n placement: \"rightSidebar\",\n title: t.tour.defaults.rightSidebar.title,\n text: t.tour.defaults.rightSidebar.text,\n },\n]);\n\nfunction filterResolvableSteps(steps: EditorTourStep[]): EditorTourStep[] {\n return steps.filter((s) => {\n const el = document.querySelector(s.target);\n if (el) return true;\n return s.skipIfMissing === false;\n });\n}\n\nconst currentStep = computed(() => activeSteps.value[stepIndex.value] ?? null);\n\nconst currentStepTitle = computed(() => currentStep.value?.title ?? \"\");\nconst currentStepText = computed(() => currentStep.value?.text ?? \"\");\n\nfunction resolvePlacement(\n step: EditorTourStep | null,\n): EditorTourStepPlacement {\n return step?.placement ?? \"below\";\n}\n\nfunction updateTargetRect(): void {\n const step = currentStep.value;\n if (!step) {\n targetRect.value = null;\n return;\n }\n const el = document.querySelector<HTMLElement>(step.target);\n if (!el) {\n targetRect.value = null;\n return;\n }\n const placement = resolvePlacement(step);\n if (placement === \"below\") {\n el.scrollIntoView({\n behavior: \"instant\",\n block: \"nearest\",\n inline: \"nearest\",\n });\n }\n targetRect.value = el.getBoundingClientRect();\n}\n\nfunction startTypewriter(): void {\n stopTypewriter();\n const fullText = currentStepText.value;\n typedText.value = \"\";\n if (prefersReducedMotion.value === \"reduce\") {\n typedText.value = fullText;\n return;\n }\n typewriterCharIndex = 0;\n resumeTypewriter();\n}\n\nfunction buildActiveStepsForRun(): EditorTourStep[] {\n const raw =\n props.tourConfig.steps !== undefined\n ? props.tourConfig.steps\n : defaultSteps.value;\n return filterResolvableSteps(raw);\n}\n\nfunction start(options?: EditorTourStartOptions): void {\n const steps = buildActiveStepsForRun();\n activeSteps.value = steps;\n if (steps.length === 0) {\n tourActive.value = false;\n return;\n }\n const nextIdx = Math.min(\n Math.max(0, options?.stepIndex ?? 0),\n steps.length - 1,\n );\n stepIndex.value = nextIdx;\n tourActive.value = true;\n nextTick(() => {\n updateTargetRect();\n startTypewriter();\n });\n}\n\nfunction dismiss(persist: boolean): void {\n stopTypewriter();\n tourActive.value = false;\n targetRect.value = null;\n if (persist) {\n writeTourDismissedState(props.tourConfig);\n }\n}\n\nfunction nextStep(): void {\n if (stepIndex.value < activeSteps.value.length - 1) {\n stepIndex.value++;\n nextTick(() => {\n updateTargetRect();\n startTypewriter();\n });\n } else {\n dismiss(true);\n }\n}\n\nfunction isDismissed(): boolean {\n return readTourDismissedState(props.tourConfig);\n}\n\nfunction resetDismissed(): void {\n clearTourDismissedState(props.tourConfig);\n}\n\nconst tooltipStyle = computed(() => {\n const rect = targetRect.value;\n if (!rect) return {};\n const placement = resolvePlacement(currentStep.value);\n const pad = 12;\n\n if (placement === \"canvas\") {\n return {\n top: `${rect.top + rect.height / 3}px`,\n left: `${rect.left + rect.width / 2}px`,\n transform: \"translate(-50%, -50%)\",\n };\n }\n if (placement === \"sidebar\") {\n return {\n top: `${rect.top + rect.height / 3}px`,\n left: `${rect.right + pad}px`,\n };\n }\n if (placement === \"rightSidebar\") {\n return {\n top: `${rect.top + rect.height / 3}px`,\n left: `${rect.left - 300 - pad}px`,\n };\n }\n return {\n top: `${rect.bottom + pad}px`,\n left: `${Math.min(rect.left, typeof window !== \"undefined\" ? window.innerWidth - 320 : rect.left)}px`,\n };\n});\n\nconst spotlightStyle = computed(() => {\n const rect = targetRect.value;\n if (!rect) return {};\n const placement = resolvePlacement(currentStep.value);\n const pad = 6;\n const isLargePanel =\n placement === \"canvas\" ||\n placement === \"sidebar\" ||\n placement === \"rightSidebar\";\n const w = rect.width + pad * 2;\n const h = rect.height + pad * 2;\n const x = rect.left - pad;\n const y = rect.top - pad;\n return {\n width: `${w}px`,\n height: `${h}px`,\n transform: `translate(${x}px, ${y}px)`,\n borderRadius: isLargePanel ? \"14px\" : \"8px\",\n };\n});\n\nonMounted(() => {\n if (props.tourConfig.autoStart && !readTourDismissedState(props.tourConfig)) {\n start();\n }\n});\n\nonUnmounted(() => {\n stopTypewriter();\n bodyScrollLocked.value = false;\n});\n\ndefineExpose({\n start,\n dismiss: () => dismiss(true),\n resetDismissed,\n isDismissed,\n});\n</script>\n\n<template>\n <Teleport to=\"body\">\n <Transition name=\"tpl-editor-tour\">\n <div\n v-if=\"tourActive\"\n class=\"tpl-editor-tour-overlay tpl:fixed tpl:inset-0 tpl:z-[10001]\"\n :class=\"{ 'tpl-editor-tour-overlay--dark': darkMode }\"\n data-testid=\"editor-tour-overlay\"\n @click.self=\"dismiss(true)\"\n >\n <div\n v-if=\"targetRect\"\n data-testid=\"editor-tour-spotlight\"\n class=\"tpl-editor-tour-spotlight tpl:absolute tpl:top-0 tpl:left-0\"\n :style=\"spotlightStyle\"\n @click=\"dismiss(true)\"\n />\n\n <div\n v-if=\"targetRect && currentStep\"\n role=\"dialog\"\n aria-modal=\"true\"\n :aria-label=\"currentStepTitle\"\n data-testid=\"editor-tour-tooltip\"\n class=\"tpl-editor-tour-tooltip tpl-editor-tour-tooltip-shell tpl:fixed tpl:z-[10002]\"\n :style=\"tooltipStyle\"\n >\n <div class=\"tpl-editor-tour-tooltip-body\">\n <div class=\"tpl-editor-tour-step-meta\">\n {{\n format(t.tour.stepCounter, {\n current: String(stepIndex + 1),\n total: String(activeSteps.length),\n })\n }}\n </div>\n <div class=\"tpl-editor-tour-title\">\n {{ currentStepTitle }}\n </div>\n <div class=\"tpl-editor-tour-text\">\n {{ typedText\n }}<span\n v-if=\"typedText.length < currentStepText.length\"\n class=\"tpl-editor-tour-cursor\"\n >|</span\n >\n </div>\n </div>\n <div class=\"tpl-editor-tour-footer\">\n <button\n type=\"button\"\n data-testid=\"editor-tour-skip\"\n class=\"tpl-editor-tour-skip\"\n @click=\"dismiss(true)\"\n >\n {{ t.tour.skip }}\n </button>\n <button\n type=\"button\"\n data-testid=\"editor-tour-next\"\n class=\"tpl-editor-tour-next\"\n @click=\"nextStep\"\n >\n {{\n stepIndex < activeSteps.length - 1 ? t.tour.next : t.tour.done\n }}\n <ChevronRight\n v-if=\"stepIndex < activeSteps.length - 1\"\n :size=\"12\"\n aria-hidden=\"true\"\n />\n </button>\n </div>\n </div>\n </div>\n </Transition>\n </Teleport>\n</template>\n\n<style>\n/* Playground-aligned palette: Teleport targets `body`, outside `.tpl` theme vars. */\n\n.tpl-editor-tour-overlay {\n --tour-primary: oklch(70% 0.16 55);\n --tour-primary-hover: oklch(63% 0.17 55);\n --tour-surface: #ffffff;\n --tour-title: #111827;\n --tour-body: #6b7280;\n --tour-skip: #9ca3af;\n --tour-skip-hover: #4b5563;\n --tour-border: #e5e7eb;\n --tour-footer-bg: rgb(249 250 251 / 0.5);\n --tour-next-fg: #ffffff;\n --tour-shadow-card: 0 16px 48px rgb(0 0 0 / 0.15);\n}\n\n.tpl-editor-tour-overlay--dark {\n --tour-primary: oklch(78% 0.14 55);\n --tour-primary-hover: oklch(70% 0.16 55);\n --tour-surface: #1f2937;\n --tour-title: #f9fafb;\n --tour-body: #9ca3af;\n --tour-skip: #6b7280;\n --tour-skip-hover: #d1d5db;\n --tour-border: #374151;\n --tour-footer-bg: rgb(31 41 55 / 0.5);\n --tour-next-fg: #ffffff;\n --tour-shadow-card: 0 16px 48px rgb(0 0 0 / 0.35);\n}\n\n.tpl-editor-tour-overlay:focus {\n outline: none;\n}\n\n.tpl-editor-tour-enter-active,\n.tpl-editor-tour-leave-active {\n transition: opacity 200ms ease;\n}\n\n.tpl-editor-tour-enter-from,\n.tpl-editor-tour-leave-to {\n opacity: 0;\n}\n\n/* Same ring/glow as legacy playground `.pg-onboarding-spotlight` */\n.tpl-editor-tour-spotlight {\n box-shadow:\n 0 0 0 2px oklch(70% 0.16 55 / 0.5),\n 0 0 20px 6px oklch(70% 0.16 55 / 0.12),\n 0 0 0 9999px rgba(0, 0, 0, 0.5);\n transition:\n transform 500ms cubic-bezier(0.4, 0, 0.2, 1),\n border-radius 500ms cubic-bezier(0.4, 0, 0.2, 1),\n opacity 400ms ease;\n pointer-events: auto;\n}\n\n@keyframes tpl-editor-tour-tooltip-in {\n from {\n opacity: 0;\n transform: translateY(10px) scale(0.98);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n\n.tpl-editor-tour-tooltip-shell {\n width: 300px;\n overflow: hidden;\n border-radius: 12px;\n border: 1px solid var(--tour-border);\n background-color: var(--tour-surface);\n box-shadow: var(--tour-shadow-card);\n font-family:\n ui-sans-serif,\n system-ui,\n -apple-system,\n Segoe UI,\n Roboto,\n Helvetica,\n Arial,\n sans-serif;\n animation: tpl-editor-tour-tooltip-in 400ms cubic-bezier(0.4, 0, 0.2, 1) both;\n}\n\n.tpl-editor-tour-tooltip-body {\n padding: 16px 16px 12px;\n}\n\n.tpl-editor-tour-step-meta {\n margin-bottom: 4px;\n font-size: 12px;\n font-weight: 500;\n letter-spacing: 0.05em;\n text-transform: uppercase;\n color: var(--tour-primary);\n}\n\n.tpl-editor-tour-title {\n margin-bottom: 6px;\n font-size: 15px;\n font-weight: 600;\n color: var(--tour-title);\n}\n\n.tpl-editor-tour-text {\n min-height: 40px;\n font-size: 13px;\n line-height: 1.625;\n color: var(--tour-body);\n}\n\n.tpl-editor-tour-footer {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 16px;\n border-top: 1px solid var(--tour-border);\n background-color: var(--tour-footer-bg);\n}\n\n.tpl-editor-tour-skip {\n cursor: pointer;\n border: none;\n background: transparent;\n padding: 0;\n font-size: 13px;\n font-family: inherit;\n color: var(--tour-skip);\n transition: color 150ms ease;\n}\n\n.tpl-editor-tour-skip:hover {\n color: var(--tour-skip-hover);\n}\n\n.tpl-editor-tour-next {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n height: 32px;\n cursor: pointer;\n border: none;\n border-radius: 6px;\n padding: 0 16px;\n font-size: 13px;\n font-weight: 500;\n font-family: inherit;\n color: var(--tour-next-fg);\n background-color: var(--tour-primary);\n transition:\n background-color 150ms ease,\n box-shadow 150ms ease;\n}\n\n.tpl-editor-tour-next:hover {\n background-color: var(--tour-primary-hover);\n box-shadow:\n 0 0 0 3px oklch(70% 0.16 55 / 0.15),\n 0 4px 16px oklch(70% 0.16 55 / 0.25);\n}\n\n.tpl-editor-tour-overlay--dark .tpl-editor-tour-next:hover {\n box-shadow:\n 0 0 0 3px oklch(78% 0.14 55 / 0.2),\n 0 4px 16px oklch(70% 0.16 55 / 0.2);\n}\n\n@keyframes tpl-editor-tour-cursor-blink {\n 0%,\n 100% {\n opacity: 1;\n }\n 50% {\n opacity: 0;\n }\n}\n\n.tpl-editor-tour-cursor {\n animation: tpl-editor-tour-cursor-blink 800ms ease-in-out infinite;\n font-weight: 300;\n color: oklch(70% 0.16 55);\n}\n\n.tpl-editor-tour-overlay--dark .tpl-editor-tour-cursor {\n color: oklch(78% 0.14 55);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .tpl-editor-tour-spotlight {\n transition-duration: 0.01ms !important;\n }\n\n .tpl-editor-tour-tooltip-shell {\n animation-duration: 0.01ms !important;\n }\n\n .tpl-editor-tour-cursor {\n animation: none !important;\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { ChevronRight } from \"@lucide/vue\";\nimport {\n useIntervalFn,\n usePreferredReducedMotion,\n useScrollLock,\n} from \"@vueuse/core\";\nimport {\n computed,\n nextTick,\n onMounted,\n onUnmounted,\n ref,\n shallowRef,\n watch,\n} from \"vue\";\nimport { useI18n } from \"../composables/useI18n\";\nimport type {\n EditorTourConfig,\n EditorTourStartOptions,\n EditorTourStep,\n EditorTourStepPlacement,\n} from \"../types/editor-tour\";\nimport {\n clearTourDismissedState,\n readTourDismissedState,\n writeTourDismissedState,\n} from \"../utils/tourStorage\";\n\nconst props = withDefaults(\n defineProps<{\n tourConfig: EditorTourConfig;\n /**\n * Align tooltip palette with the editor chrome (Teleport is outside `.tpl`,\n * so theme CSS variables do not apply unless we mirror light/dark here).\n */\n darkMode?: boolean;\n }>(),\n { darkMode: false },\n);\n\nconst { t, format } = useI18n();\n\nconst tourActive = ref(false);\nconst stepIndex = ref(0);\nconst targetRect = ref<DOMRect | null>(null);\nconst typedText = ref(\"\");\nconst prefersReducedMotion = usePreferredReducedMotion();\nconst activeSteps = shallowRef<EditorTourStep[]>([]);\n\nconst bodyScrollLocked = useScrollLock(document.body);\n\nwatch(\n tourActive,\n (active, _was, onCleanup) => {\n bodyScrollLocked.value = active;\n if (!active) return;\n const onKey = (e: KeyboardEvent): void => {\n if (e.key === \"Escape\") {\n e.preventDefault();\n dismiss(true);\n }\n };\n document.addEventListener(\"keydown\", onKey, true);\n onCleanup(() => document.removeEventListener(\"keydown\", onKey, true));\n },\n { immediate: true },\n);\n\nlet typewriterCharIndex = 0;\nconst { pause: stopTypewriter, resume: resumeTypewriter } = useIntervalFn(\n () => {\n const fullText = currentStepText.value;\n if (typewriterCharIndex < fullText.length) {\n typedText.value = fullText.slice(0, typewriterCharIndex + 1);\n typewriterCharIndex++;\n } else {\n stopTypewriter();\n }\n },\n 25,\n { immediate: false },\n);\n\nconst defaultSteps = computed<EditorTourStep[]>(() => [\n {\n target: \".tpl-body\",\n placement: \"canvas\",\n title: t.tour.defaults.canvas.title,\n text: t.tour.defaults.canvas.text,\n },\n {\n target: \".tpl-sidebar-rail\",\n placement: \"sidebar\",\n title: t.tour.defaults.sidebar.title,\n text: t.tour.defaults.sidebar.text,\n },\n {\n target: \".tpl-right-sidebar\",\n placement: \"rightSidebar\",\n title: t.tour.defaults.rightSidebar.title,\n text: t.tour.defaults.rightSidebar.text,\n },\n]);\n\nfunction filterResolvableSteps(steps: EditorTourStep[]): EditorTourStep[] {\n return steps.filter((s) => {\n const el = document.querySelector(s.target);\n if (el) return true;\n return s.skipIfMissing === false;\n });\n}\n\nconst currentStep = computed(() => activeSteps.value[stepIndex.value] ?? null);\n\nconst currentStepTitle = computed(() => currentStep.value?.title ?? \"\");\nconst currentStepText = computed(() => currentStep.value?.text ?? \"\");\n\nfunction resolvePlacement(\n step: EditorTourStep | null,\n): EditorTourStepPlacement {\n return step?.placement ?? \"below\";\n}\n\nfunction updateTargetRect(): void {\n const step = currentStep.value;\n if (!step) {\n targetRect.value = null;\n return;\n }\n const el = document.querySelector<HTMLElement>(step.target);\n if (!el) {\n targetRect.value = null;\n return;\n }\n const placement = resolvePlacement(step);\n if (placement === \"below\") {\n el.scrollIntoView({\n behavior: \"instant\",\n block: \"nearest\",\n inline: \"nearest\",\n });\n }\n targetRect.value = el.getBoundingClientRect();\n}\n\nfunction startTypewriter(): void {\n stopTypewriter();\n const fullText = currentStepText.value;\n typedText.value = \"\";\n if (prefersReducedMotion.value === \"reduce\") {\n typedText.value = fullText;\n return;\n }\n typewriterCharIndex = 0;\n resumeTypewriter();\n}\n\nfunction buildActiveStepsForRun(): EditorTourStep[] {\n const raw =\n props.tourConfig.steps !== undefined\n ? props.tourConfig.steps\n : defaultSteps.value;\n return filterResolvableSteps(raw);\n}\n\nfunction start(options?: EditorTourStartOptions): void {\n const steps = buildActiveStepsForRun();\n activeSteps.value = steps;\n if (steps.length === 0) {\n tourActive.value = false;\n return;\n }\n const nextIdx = Math.min(\n Math.max(0, options?.stepIndex ?? 0),\n steps.length - 1,\n );\n stepIndex.value = nextIdx;\n tourActive.value = true;\n nextTick(() => {\n updateTargetRect();\n startTypewriter();\n });\n}\n\nfunction dismiss(persist: boolean): void {\n stopTypewriter();\n tourActive.value = false;\n targetRect.value = null;\n if (persist) {\n writeTourDismissedState(props.tourConfig);\n }\n}\n\nfunction nextStep(): void {\n if (stepIndex.value < activeSteps.value.length - 1) {\n stepIndex.value++;\n nextTick(() => {\n updateTargetRect();\n startTypewriter();\n });\n } else {\n dismiss(true);\n }\n}\n\nfunction isDismissed(): boolean {\n return readTourDismissedState(props.tourConfig);\n}\n\nfunction resetDismissed(): void {\n clearTourDismissedState(props.tourConfig);\n}\n\nconst tooltipStyle = computed(() => {\n const rect = targetRect.value;\n if (!rect) return {};\n const placement = resolvePlacement(currentStep.value);\n const pad = 12;\n\n if (placement === \"canvas\") {\n return {\n top: `${rect.top + rect.height / 3}px`,\n left: `${rect.left + rect.width / 2}px`,\n transform: \"translate(-50%, -50%)\",\n };\n }\n if (placement === \"sidebar\") {\n return {\n top: `${rect.top + rect.height / 3}px`,\n left: `${rect.right + pad}px`,\n };\n }\n if (placement === \"rightSidebar\") {\n return {\n top: `${rect.top + rect.height / 3}px`,\n left: `${rect.left - 300 - pad}px`,\n };\n }\n return {\n top: `${rect.bottom + pad}px`,\n left: `${Math.min(rect.left, typeof window !== \"undefined\" ? window.innerWidth - 320 : rect.left)}px`,\n };\n});\n\nconst spotlightStyle = computed(() => {\n const rect = targetRect.value;\n if (!rect) return {};\n const placement = resolvePlacement(currentStep.value);\n const pad = 6;\n const isLargePanel =\n placement === \"canvas\" ||\n placement === \"sidebar\" ||\n placement === \"rightSidebar\";\n const w = rect.width + pad * 2;\n const h = rect.height + pad * 2;\n const x = rect.left - pad;\n const y = rect.top - pad;\n return {\n width: `${w}px`,\n height: `${h}px`,\n transform: `translate(${x}px, ${y}px)`,\n borderRadius: isLargePanel ? \"14px\" : \"8px\",\n };\n});\n\nonMounted(() => {\n if (props.tourConfig.autoStart && !readTourDismissedState(props.tourConfig)) {\n start();\n }\n});\n\nonUnmounted(() => {\n stopTypewriter();\n bodyScrollLocked.value = false;\n});\n\ndefineExpose({\n start,\n dismiss: () => dismiss(true),\n resetDismissed,\n isDismissed,\n});\n</script>\n\n<template>\n <Teleport to=\"body\">\n <Transition name=\"tpl-editor-tour\">\n <div\n v-if=\"tourActive\"\n class=\"tpl-editor-tour-overlay tpl:fixed tpl:inset-0 tpl:z-[10001]\"\n :class=\"{ 'tpl-editor-tour-overlay--dark': darkMode }\"\n data-testid=\"editor-tour-overlay\"\n @click.self=\"dismiss(true)\"\n >\n <div\n v-if=\"targetRect\"\n data-testid=\"editor-tour-spotlight\"\n class=\"tpl-editor-tour-spotlight tpl:absolute tpl:top-0 tpl:left-0\"\n :style=\"spotlightStyle\"\n @click=\"dismiss(true)\"\n />\n\n <div\n v-if=\"targetRect && currentStep\"\n role=\"dialog\"\n aria-modal=\"true\"\n :aria-label=\"currentStepTitle\"\n data-testid=\"editor-tour-tooltip\"\n class=\"tpl-editor-tour-tooltip tpl-editor-tour-tooltip-shell tpl:fixed tpl:z-[10002]\"\n :style=\"tooltipStyle\"\n >\n <div class=\"tpl-editor-tour-tooltip-body\">\n <div class=\"tpl-editor-tour-step-meta\">\n {{\n format(t.tour.stepCounter, {\n current: String(stepIndex + 1),\n total: String(activeSteps.length),\n })\n }}\n </div>\n <div class=\"tpl-editor-tour-title\">\n {{ currentStepTitle }}\n </div>\n <div class=\"tpl-editor-tour-text\">\n {{ typedText\n }}<span\n v-if=\"typedText.length < currentStepText.length\"\n class=\"tpl-editor-tour-cursor\"\n >|</span\n >\n </div>\n </div>\n <div class=\"tpl-editor-tour-footer\">\n <button\n type=\"button\"\n data-testid=\"editor-tour-skip\"\n class=\"tpl-editor-tour-skip\"\n @click=\"dismiss(true)\"\n >\n {{ t.tour.skip }}\n </button>\n <button\n type=\"button\"\n data-testid=\"editor-tour-next\"\n class=\"tpl-editor-tour-next\"\n @click=\"nextStep\"\n >\n {{\n stepIndex < activeSteps.length - 1 ? t.tour.next : t.tour.done\n }}\n <ChevronRight\n v-if=\"stepIndex < activeSteps.length - 1\"\n :size=\"12\"\n aria-hidden=\"true\"\n />\n </button>\n </div>\n </div>\n </div>\n </Transition>\n </Teleport>\n</template>\n\n<style>\n/* Playground-aligned palette: Teleport targets `body`, outside `.tpl` theme vars. */\n\n.tpl-editor-tour-overlay {\n --tour-primary: oklch(70% 0.16 55);\n --tour-primary-hover: oklch(63% 0.17 55);\n --tour-surface: #ffffff;\n --tour-title: #111827;\n --tour-body: #6b7280;\n --tour-skip: #9ca3af;\n --tour-skip-hover: #4b5563;\n --tour-border: #e5e7eb;\n --tour-footer-bg: rgb(249 250 251 / 0.5);\n --tour-next-fg: #ffffff;\n --tour-shadow-card: 0 16px 48px rgb(0 0 0 / 0.15);\n}\n\n.tpl-editor-tour-overlay--dark {\n --tour-primary: oklch(78% 0.14 55);\n --tour-primary-hover: oklch(70% 0.16 55);\n --tour-surface: #1f2937;\n --tour-title: #f9fafb;\n --tour-body: #9ca3af;\n --tour-skip: #6b7280;\n --tour-skip-hover: #d1d5db;\n --tour-border: #374151;\n --tour-footer-bg: rgb(31 41 55 / 0.5);\n --tour-next-fg: #ffffff;\n --tour-shadow-card: 0 16px 48px rgb(0 0 0 / 0.35);\n}\n\n.tpl-editor-tour-overlay:focus {\n outline: none;\n}\n\n.tpl-editor-tour-enter-active,\n.tpl-editor-tour-leave-active {\n transition: opacity 200ms ease;\n}\n\n.tpl-editor-tour-enter-from,\n.tpl-editor-tour-leave-to {\n opacity: 0;\n}\n\n/* Same ring/glow as legacy playground `.pg-onboarding-spotlight` */\n.tpl-editor-tour-spotlight {\n box-shadow:\n 0 0 0 2px oklch(70% 0.16 55 / 0.5),\n 0 0 20px 6px oklch(70% 0.16 55 / 0.12),\n 0 0 0 9999px rgba(0, 0, 0, 0.5);\n transition:\n transform 500ms cubic-bezier(0.4, 0, 0.2, 1),\n border-radius 500ms cubic-bezier(0.4, 0, 0.2, 1),\n opacity 400ms ease;\n pointer-events: auto;\n}\n\n@keyframes tpl-editor-tour-tooltip-in {\n from {\n opacity: 0;\n transform: translateY(10px) scale(0.98);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n\n.tpl-editor-tour-tooltip-shell {\n width: 300px;\n overflow: hidden;\n border-radius: 12px;\n border: 1px solid var(--tour-border);\n background-color: var(--tour-surface);\n box-shadow: var(--tour-shadow-card);\n font-family:\n ui-sans-serif,\n system-ui,\n -apple-system,\n Segoe UI,\n Roboto,\n Helvetica,\n Arial,\n sans-serif;\n animation: tpl-editor-tour-tooltip-in 400ms cubic-bezier(0.4, 0, 0.2, 1) both;\n}\n\n.tpl-editor-tour-tooltip-body {\n padding: 16px 16px 12px;\n}\n\n.tpl-editor-tour-step-meta {\n margin-bottom: 4px;\n font-size: 12px;\n font-weight: 500;\n letter-spacing: 0.05em;\n text-transform: uppercase;\n color: var(--tour-primary);\n}\n\n.tpl-editor-tour-title {\n margin-bottom: 6px;\n font-size: 15px;\n font-weight: 600;\n color: var(--tour-title);\n}\n\n.tpl-editor-tour-text {\n min-height: 40px;\n font-size: 13px;\n line-height: 1.625;\n color: var(--tour-body);\n}\n\n.tpl-editor-tour-footer {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 16px;\n border-top: 1px solid var(--tour-border);\n background-color: var(--tour-footer-bg);\n}\n\n.tpl-editor-tour-skip {\n cursor: pointer;\n border: none;\n background: transparent;\n padding: 0;\n font-size: 13px;\n font-family: inherit;\n color: var(--tour-skip);\n transition: color 150ms ease;\n}\n\n.tpl-editor-tour-skip:hover {\n color: var(--tour-skip-hover);\n}\n\n.tpl-editor-tour-next {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n height: 32px;\n cursor: pointer;\n border: none;\n border-radius: 6px;\n padding: 0 16px;\n font-size: 13px;\n font-weight: 500;\n font-family: inherit;\n color: var(--tour-next-fg);\n background-color: var(--tour-primary);\n transition:\n background-color 150ms ease,\n box-shadow 150ms ease;\n}\n\n.tpl-editor-tour-next:hover {\n background-color: var(--tour-primary-hover);\n box-shadow:\n 0 0 0 3px oklch(70% 0.16 55 / 0.15),\n 0 4px 16px oklch(70% 0.16 55 / 0.25);\n}\n\n.tpl-editor-tour-overlay--dark .tpl-editor-tour-next:hover {\n box-shadow:\n 0 0 0 3px oklch(78% 0.14 55 / 0.2),\n 0 4px 16px oklch(70% 0.16 55 / 0.2);\n}\n\n@keyframes tpl-editor-tour-cursor-blink {\n 0%,\n 100% {\n opacity: 1;\n }\n 50% {\n opacity: 0;\n }\n}\n\n.tpl-editor-tour-cursor {\n animation: tpl-editor-tour-cursor-blink 800ms ease-in-out infinite;\n font-weight: 300;\n color: oklch(70% 0.16 55);\n}\n\n.tpl-editor-tour-overlay--dark .tpl-editor-tour-cursor {\n color: oklch(78% 0.14 55);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .tpl-editor-tour-spotlight {\n transition-duration: 0.01ms !important;\n }\n\n .tpl-editor-tour-tooltip-shell {\n animation-duration: 0.01ms !important;\n }\n\n .tpl-editor-tour-cursor {\n animation: none !important;\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { Plus, X } from \"@lucide/vue\";\nimport type { PopupTriggerConditions } from \"@aswin.dev/types\";\nimport { ref, watch } from \"vue\";\nimport { useI18n } from \"../composables/useI18n\";\n\nconst props = defineProps<{\n conditions: PopupTriggerConditions;\n}>();\n\nconst emit = defineEmits<{\n patch: [partial: Partial<PopupTriggerConditions>];\n}>();\n\nconst { t } = useI18n();\n\n/** Second row (AND/OR + page views) — matches “Add rule” / delete UX in the reference UI. */\nconst showPageViewsRow = ref(props.conditions.pageViews > 0);\n\nwatch(\n () => props.conditions.pageViews,\n (n) => {\n if (n > 0) {\n showPageViewsRow.value = true;\n }\n },\n);\n\nfunction patchDelay(ev: Event): void {\n const raw = (ev.target as HTMLInputElement).value;\n emit(\"patch\", {\n delaySeconds: Math.max(0, Number(raw) || 0),\n });\n}\n\nfunction patchPageViews(ev: Event): void {\n const raw = (ev.target as HTMLInputElement).value.trim();\n emit(\"patch\", {\n pageViews: raw === \"\" ? 0 : Math.max(0, Number(raw) || 0),\n });\n}\n\nfunction setOperator(op: \"and\" | \"or\"): void {\n emit(\"patch\", { operator: op });\n}\n\nfunction addPageViewsRule(): void {\n showPageViewsRow.value = true;\n}\n\nfunction removePageViewsRule(): void {\n showPageViewsRow.value = false;\n emit(\"patch\", { pageViews: 0 });\n}\n</script>\n\n<template>\n <div\n class=\"tpl:mt-3 tpl:space-y-3 tpl:border-t tpl:border-[var(--tpl-border)] tpl:pt-4\"\n >\n <!-- Row 1: delay + unit (dropdown like reference) -->\n <div\n class=\"tpl:flex tpl:flex-wrap tpl:items-center tpl:gap-x-2 tpl:gap-y-2 tpl:text-sm tpl:text-[var(--tpl-text-muted)]\"\n >\n <span class=\"tpl:shrink-0 tpl:text-[var(--tpl-text)]\">{{\n t.popupDisplayRules.triggerShowOnlyAfter\n }}</span>\n <input\n type=\"number\"\n min=\"0\"\n inputmode=\"numeric\"\n class=\"tpl:h-9 tpl:w-14 tpl:shrink-0 tpl:rounded-lg tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:px-2 tpl:text-center tpl:text-sm tpl:text-[var(--tpl-text)] tpl:tabular-nums tpl:outline-none focus:tpl:ring-2 focus:tpl:ring-[var(--tpl-primary)]/25\"\n :value=\"props.conditions.delaySeconds\"\n @change=\"patchDelay\"\n />\n <select\n class=\"tpl:h-9 tpl:min-w-[10.5rem] tpl:shrink-0 tpl:cursor-pointer tpl:appearance-none tpl:rounded-lg tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:pl-3 tpl:pr-8 tpl:text-left tpl:text-sm tpl:text-[var(--tpl-text)] tpl:outline-none focus:tpl:ring-2 focus:tpl:ring-[var(--tpl-primary)]/25\"\n aria-label=\"Time condition unit\"\n >\n <option value=\"seconds\">\n {{ t.popupDisplayRules.triggerSecondsOnPage }}\n </option>\n </select>\n </div>\n\n <!-- Row 2: AND/OR + page views + delete -->\n <div\n v-if=\"showPageViewsRow\"\n class=\"tpl:flex tpl:flex-wrap tpl:items-center tpl:gap-x-2 tpl:gap-y-2\"\n >\n <div\n class=\"trigger-operator-toggle\"\n role=\"group\"\n :aria-label=\"t.popupDisplayRules.triggerOperatorGroupAria\"\n >\n <button\n type=\"button\"\n class=\"trigger-operator-toggle__btn\"\n :class=\"{\n 'trigger-operator-toggle__btn--selected':\n props.conditions.operator === 'and',\n }\"\n @click=\"setOperator('and')\"\n >\n {{ t.popupDisplayRules.operatorAndShort }}\n </button>\n <button\n type=\"button\"\n class=\"trigger-operator-toggle__btn\"\n :class=\"{\n 'trigger-operator-toggle__btn--selected':\n props.conditions.operator === 'or',\n }\"\n @click=\"setOperator('or')\"\n >\n {{ t.popupDisplayRules.operatorOrShort }}\n </button>\n </div>\n <input\n type=\"number\"\n min=\"0\"\n inputmode=\"numeric\"\n class=\"tpl:h-9 tpl:w-14 tpl:shrink-0 tpl:rounded-lg tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:px-2 tpl:text-center tpl:text-sm tpl:text-[var(--tpl-text)] tpl:tabular-nums tpl:outline-none focus:tpl:ring-2 focus:tpl:ring-[var(--tpl-primary)]/25\"\n :value=\"\n props.conditions.pageViews > 0 ? props.conditions.pageViews : ''\n \"\n :placeholder=\"'0'\"\n @change=\"patchPageViews\"\n />\n <span class=\"tpl:text-sm tpl:text-[var(--tpl-text-muted)]\">{{\n t.popupDisplayRules.triggerPageViewsSuffix\n }}</span>\n <button\n type=\"button\"\n class=\"tpl:ml-auto tpl:flex tpl:h-8 tpl:w-8 tpl:shrink-0 tpl:items-center tpl:justify-center tpl:rounded-lg tpl:text-[var(--tpl-text-muted)] tpl:transition-colors hover:tpl:bg-[var(--tpl-bg-hover)] hover:tpl:text-[var(--tpl-text)]\"\n :aria-label=\"t.popupDisplayRules.triggerRemoveRuleAria\"\n @click=\"removePageViewsRule\"\n >\n <X :size=\"16\" :stroke-width=\"2\" />\n </button>\n </div>\n\n <!-- Add rule (when page-views row is hidden) -->\n <button\n v-if=\"!showPageViewsRow\"\n type=\"button\"\n class=\"trigger-add-rule-btn\"\n @click=\"addPageViewsRule\"\n >\n <span class=\"trigger-add-rule-btn__icon\" aria-hidden=\"true\">\n <Plus :size=\"15\" :stroke-width=\"2.25\" />\n </span>\n {{ t.popupDisplayRules.triggerAddRule }}\n </button>\n </div>\n</template>\n\n<style scoped>\n/* Segmented AND / OR — light rail, dark selected pill, white unselected with subtle border */\n.trigger-operator-toggle {\n display: inline-flex;\n flex-shrink: 0;\n align-items: stretch;\n gap: 0.25rem;\n padding: 0.1875rem;\n background: var(--tpl-bg, #ffffff);\n border: 1px solid var(--tpl-border, #d8d8d8);\n border-radius: 9999px;\n box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05);\n}\n\n.trigger-operator-toggle__btn {\n margin: 0;\n padding: 0.4375rem 0.8125rem;\n font-family: inherit;\n font-size: 0.6875rem;\n font-weight: 600;\n line-height: 1.2;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n color: var(--tpl-text-muted, #6b6b6b);\n background: transparent;\n border: 1px solid transparent;\n border-radius: 9999px;\n cursor: pointer;\n transition:\n background-color 0.15s ease,\n color 0.15s ease,\n border-color 0.15s ease,\n box-shadow 0.15s ease;\n}\n\n.trigger-operator-toggle__btn:hover:not(\n .trigger-operator-toggle__btn--selected\n ) {\n color: var(--tpl-text, #333333);\n background: rgba(15, 23, 42, 0.04);\n border-color: var(--tpl-border, #c8c8c8);\n}\n\n.trigger-operator-toggle__btn--selected {\n color: #ffffff;\n background: var(--tpl-text, #333333);\n border-color: transparent;\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);\n}\n\n.trigger-operator-toggle__btn:not(.trigger-operator-toggle__btn--selected) {\n background: var(--tpl-bg, #ffffff);\n border-color: var(--tpl-border, #d8d8d8);\n}\n\n.trigger-operator-toggle__btn:focus {\n outline: none;\n}\n\n.trigger-operator-toggle__btn:focus-visible {\n outline: 2px solid var(--tpl-primary, #6366f1);\n outline-offset: 2px;\n}\n\n/* Matches reference “+ Add rule”: white pill, hairline border, soft shadow, muted + */\n.trigger-add-rule-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 0.375rem;\n margin: 0;\n padding: 0.5rem 0.875rem;\n font-family: inherit;\n font-size: 0.8125rem;\n font-weight: 500;\n line-height: 1.25;\n letter-spacing: 0.01em;\n color: var(--tpl-text, #3a3a3a);\n background: var(--tpl-bg, #ffffff);\n border: 1px solid var(--tpl-border, #d8d8d8);\n border-radius: 8px;\n cursor: pointer;\n box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05);\n transition:\n border-color 0.15s ease,\n background-color 0.15s ease,\n box-shadow 0.15s ease,\n color 0.15s ease;\n}\n\n.trigger-add-rule-btn:hover {\n background: var(--tpl-bg-hover, #f7f7f7);\n border-color: var(--tpl-border, #c8c8c8);\n box-shadow: 0 1px 3px rgba(15, 23, 42, 0.08);\n}\n\n.trigger-add-rule-btn:active {\n box-shadow: 0 0 0 1px rgba(15, 23, 42, 0.06) inset;\n}\n\n.trigger-add-rule-btn:focus {\n outline: none;\n}\n\n.trigger-add-rule-btn:focus-visible {\n outline: 2px solid var(--tpl-primary, #6366f1);\n outline-offset: 2px;\n}\n\n.trigger-add-rule-btn__icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: var(--tpl-text-muted, #6b6b6b);\n line-height: 0;\n}\n\n.trigger-add-rule-btn:hover .trigger-add-rule-btn__icon {\n color: var(--tpl-text, #3a3a3a);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { Plus, X } from \"@lucide/vue\";\nimport type { PopupTriggerConditions } from \"@aswin.dev/types\";\nimport { ref, watch } from \"vue\";\nimport { useI18n } from \"../composables/useI18n\";\n\nconst props = defineProps<{\n conditions: PopupTriggerConditions;\n}>();\n\nconst emit = defineEmits<{\n patch: [partial: Partial<PopupTriggerConditions>];\n}>();\n\nconst { t } = useI18n();\n\n/** Second row (AND/OR + page views) — matches “Add rule” / delete UX in the reference UI. */\nconst showPageViewsRow = ref(props.conditions.pageViews > 0);\n\nwatch(\n () => props.conditions.pageViews,\n (n) => {\n if (n > 0) {\n showPageViewsRow.value = true;\n }\n },\n);\n\nfunction patchDelay(ev: Event): void {\n const raw = (ev.target as HTMLInputElement).value;\n emit(\"patch\", {\n delaySeconds: Math.max(0, Number(raw) || 0),\n });\n}\n\nfunction patchPageViews(ev: Event): void {\n const raw = (ev.target as HTMLInputElement).value.trim();\n emit(\"patch\", {\n pageViews: raw === \"\" ? 0 : Math.max(0, Number(raw) || 0),\n });\n}\n\nfunction setOperator(op: \"and\" | \"or\"): void {\n emit(\"patch\", { operator: op });\n}\n\nfunction addPageViewsRule(): void {\n showPageViewsRow.value = true;\n}\n\nfunction removePageViewsRule(): void {\n showPageViewsRow.value = false;\n emit(\"patch\", { pageViews: 0 });\n}\n</script>\n\n<template>\n <div\n class=\"tpl:mt-3 tpl:space-y-3 tpl:border-t tpl:border-[var(--tpl-border)] tpl:pt-4\"\n >\n <!-- Row 1: delay + unit (dropdown like reference) -->\n <div\n class=\"tpl:flex tpl:flex-wrap tpl:items-center tpl:gap-x-2 tpl:gap-y-2 tpl:text-sm tpl:text-[var(--tpl-text-muted)]\"\n >\n <span class=\"tpl:shrink-0 tpl:text-[var(--tpl-text)]\">{{\n t.popupDisplayRules.triggerShowOnlyAfter\n }}</span>\n <input\n type=\"number\"\n min=\"0\"\n inputmode=\"numeric\"\n class=\"tpl:h-9 tpl:w-14 tpl:shrink-0 tpl:rounded-lg tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:px-2 tpl:text-center tpl:text-sm tpl:text-[var(--tpl-text)] tpl:tabular-nums tpl:outline-none focus:tpl:ring-2 focus:tpl:ring-[var(--tpl-primary)]/25\"\n :value=\"props.conditions.delaySeconds\"\n @change=\"patchDelay\"\n />\n <select\n class=\"tpl:h-9 tpl:min-w-[10.5rem] tpl:shrink-0 tpl:cursor-pointer tpl:appearance-none tpl:rounded-lg tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:pl-3 tpl:pr-8 tpl:text-left tpl:text-sm tpl:text-[var(--tpl-text)] tpl:outline-none focus:tpl:ring-2 focus:tpl:ring-[var(--tpl-primary)]/25\"\n aria-label=\"Time condition unit\"\n >\n <option value=\"seconds\">\n {{ t.popupDisplayRules.triggerSecondsOnPage }}\n </option>\n </select>\n </div>\n\n <!-- Row 2: AND/OR + page views + delete -->\n <div\n v-if=\"showPageViewsRow\"\n class=\"tpl:flex tpl:flex-wrap tpl:items-center tpl:gap-x-2 tpl:gap-y-2\"\n >\n <div\n class=\"trigger-operator-toggle\"\n role=\"group\"\n :aria-label=\"t.popupDisplayRules.triggerOperatorGroupAria\"\n >\n <button\n type=\"button\"\n class=\"trigger-operator-toggle__btn\"\n :class=\"{\n 'trigger-operator-toggle__btn--selected':\n props.conditions.operator === 'and',\n }\"\n @click=\"setOperator('and')\"\n >\n {{ t.popupDisplayRules.operatorAndShort }}\n </button>\n <button\n type=\"button\"\n class=\"trigger-operator-toggle__btn\"\n :class=\"{\n 'trigger-operator-toggle__btn--selected':\n props.conditions.operator === 'or',\n }\"\n @click=\"setOperator('or')\"\n >\n {{ t.popupDisplayRules.operatorOrShort }}\n </button>\n </div>\n <input\n type=\"number\"\n min=\"0\"\n inputmode=\"numeric\"\n class=\"tpl:h-9 tpl:w-14 tpl:shrink-0 tpl:rounded-lg tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:px-2 tpl:text-center tpl:text-sm tpl:text-[var(--tpl-text)] tpl:tabular-nums tpl:outline-none focus:tpl:ring-2 focus:tpl:ring-[var(--tpl-primary)]/25\"\n :value=\"\n props.conditions.pageViews > 0 ? props.conditions.pageViews : ''\n \"\n :placeholder=\"'0'\"\n @change=\"patchPageViews\"\n />\n <span class=\"tpl:text-sm tpl:text-[var(--tpl-text-muted)]\">{{\n t.popupDisplayRules.triggerPageViewsSuffix\n }}</span>\n <button\n type=\"button\"\n class=\"tpl:ml-auto tpl:flex tpl:h-8 tpl:w-8 tpl:shrink-0 tpl:items-center tpl:justify-center tpl:rounded-lg tpl:text-[var(--tpl-text-muted)] tpl:transition-colors hover:tpl:bg-[var(--tpl-bg-hover)] hover:tpl:text-[var(--tpl-text)]\"\n :aria-label=\"t.popupDisplayRules.triggerRemoveRuleAria\"\n @click=\"removePageViewsRule\"\n >\n <X :size=\"16\" :stroke-width=\"2\" />\n </button>\n </div>\n\n <!-- Add rule (when page-views row is hidden) -->\n <button\n v-if=\"!showPageViewsRow\"\n type=\"button\"\n class=\"trigger-add-rule-btn\"\n @click=\"addPageViewsRule\"\n >\n <span class=\"trigger-add-rule-btn__icon\" aria-hidden=\"true\">\n <Plus :size=\"15\" :stroke-width=\"2.25\" />\n </span>\n {{ t.popupDisplayRules.triggerAddRule }}\n </button>\n </div>\n</template>\n\n<style scoped>\n/* Segmented AND / OR — light rail, dark selected pill, white unselected with subtle border */\n.trigger-operator-toggle {\n display: inline-flex;\n flex-shrink: 0;\n align-items: stretch;\n gap: 0.25rem;\n padding: 0.1875rem;\n background: var(--tpl-bg, #ffffff);\n border: 1px solid var(--tpl-border, #d8d8d8);\n border-radius: 9999px;\n box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05);\n}\n\n.trigger-operator-toggle__btn {\n margin: 0;\n padding: 0.4375rem 0.8125rem;\n font-family: inherit;\n font-size: 0.6875rem;\n font-weight: 600;\n line-height: 1.2;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n color: var(--tpl-text-muted, #6b6b6b);\n background: transparent;\n border: 1px solid transparent;\n border-radius: 9999px;\n cursor: pointer;\n transition:\n background-color 0.15s ease,\n color 0.15s ease,\n border-color 0.15s ease,\n box-shadow 0.15s ease;\n}\n\n.trigger-operator-toggle__btn:hover:not(\n .trigger-operator-toggle__btn--selected\n ) {\n color: var(--tpl-text, #333333);\n background: rgba(15, 23, 42, 0.04);\n border-color: var(--tpl-border, #c8c8c8);\n}\n\n.trigger-operator-toggle__btn--selected {\n color: #ffffff;\n background: var(--tpl-text, #333333);\n border-color: transparent;\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);\n}\n\n.trigger-operator-toggle__btn:not(.trigger-operator-toggle__btn--selected) {\n background: var(--tpl-bg, #ffffff);\n border-color: var(--tpl-border, #d8d8d8);\n}\n\n.trigger-operator-toggle__btn:focus {\n outline: none;\n}\n\n.trigger-operator-toggle__btn:focus-visible {\n outline: 2px solid var(--tpl-primary, #6366f1);\n outline-offset: 2px;\n}\n\n/* Matches reference “+ Add rule”: white pill, hairline border, soft shadow, muted + */\n.trigger-add-rule-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 0.375rem;\n margin: 0;\n padding: 0.5rem 0.875rem;\n font-family: inherit;\n font-size: 0.8125rem;\n font-weight: 500;\n line-height: 1.25;\n letter-spacing: 0.01em;\n color: var(--tpl-text, #3a3a3a);\n background: var(--tpl-bg, #ffffff);\n border: 1px solid var(--tpl-border, #d8d8d8);\n border-radius: 8px;\n cursor: pointer;\n box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05);\n transition:\n border-color 0.15s ease,\n background-color 0.15s ease,\n box-shadow 0.15s ease,\n color 0.15s ease;\n}\n\n.trigger-add-rule-btn:hover {\n background: var(--tpl-bg-hover, #f7f7f7);\n border-color: var(--tpl-border, #c8c8c8);\n box-shadow: 0 1px 3px rgba(15, 23, 42, 0.08);\n}\n\n.trigger-add-rule-btn:active {\n box-shadow: 0 0 0 1px rgba(15, 23, 42, 0.06) inset;\n}\n\n.trigger-add-rule-btn:focus {\n outline: none;\n}\n\n.trigger-add-rule-btn:focus-visible {\n outline: 2px solid var(--tpl-primary, #6366f1);\n outline-offset: 2px;\n}\n\n.trigger-add-rule-btn__icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: var(--tpl-text-muted, #6b6b6b);\n line-height: 0;\n}\n\n.trigger-add-rule-btn:hover .trigger-add-rule-btn__icon {\n color: var(--tpl-text, #3a3a3a);\n}\n</style>\n","<script setup lang=\"ts\">\nimport {\n ArrowDownFromLine,\n Cog,\n LogOut,\n MousePointerClick,\n MousePointer2,\n LayoutTemplate,\n} from \"@lucide/vue\";\nimport { computed, inject, ref } from \"vue\";\nimport type {\n PopupEmbedSettings,\n PopupTriggerConditions,\n PopupTriggersSettings,\n} from \"@aswin.dev/types\";\nimport {\n createDefaultTriggerConditions,\n normalizeTriggerConditions,\n toPopupTriggersEmbedJson,\n} from \"@aswin.dev/types\";\nimport { useI18n } from \"../composables/useI18n\";\nimport { EDITOR_KEY } from \"../keys\";\nimport type { BaseEditorReturn } from \"../composables/useEditorCore\";\nimport { resolvePopupEmbed } from \"../utils/resolvePopupEmbed\";\nimport PopupTriggerConditionDetails from \"./PopupTriggerConditionDetails.vue\";\n\nconst editor = inject(EDITOR_KEY) as BaseEditorReturn | null;\nif (!editor) {\n throw new Error(\"PopupDisplayRulesView requires EDITOR_KEY\");\n}\n\nconst { t } = useI18n();\n\nconst rulesTab = ref<\n \"trigger\" | \"pages\" | \"audience\" | \"frequency\" | \"advanced\"\n>(\"trigger\");\n\nconst rulesTabs = computed(() => [\n { id: \"trigger\" as const, label: t.popupDisplayRules.tabTrigger },\n { id: \"pages\" as const, label: t.popupDisplayRules.tabPages },\n { id: \"audience\" as const, label: t.popupDisplayRules.tabAudience },\n { id: \"frequency\" as const, label: t.popupDisplayRules.tabFrequency },\n { id: \"advanced\" as const, label: t.popupDisplayRules.tabAdvanced },\n]);\n\nconst popup = computed(() => resolvePopupEmbed(editor!.content.value.settings));\n\nfunction commit(next: PopupEmbedSettings): void {\n editor!.updateSettings({ popup: next });\n}\n\nfunction patchTrigger<K extends keyof PopupTriggersSettings>(\n key: K,\n patch: Partial<PopupTriggersSettings[K]>,\n): void {\n const cur = popup.value;\n commit({\n ...cur,\n triggers: {\n ...cur.triggers,\n [key]: { ...cur.triggers[key], ...patch },\n },\n });\n}\n\nconst triggerDefs = computed(() => [\n {\n key: \"onLanding\" as const,\n icon: LayoutTemplate,\n title: t.popupDisplayRules.triggerOnLandingTitle,\n desc: t.popupDisplayRules.triggerOnLandingDesc,\n kind: \"landing\" as const,\n },\n {\n key: \"onExit\" as const,\n icon: LogOut,\n title: t.popupDisplayRules.triggerOnExitTitle,\n desc: t.popupDisplayRules.triggerOnExitDesc,\n kind: \"exit\" as const,\n },\n {\n key: \"onScroll\" as const,\n icon: ArrowDownFromLine,\n title: t.popupDisplayRules.triggerOnScrollTitle,\n desc: t.popupDisplayRules.triggerOnScrollDesc,\n kind: \"scroll\" as const,\n },\n {\n key: \"onClick\" as const,\n icon: MousePointerClick,\n title: t.popupDisplayRules.triggerOnClickTitle,\n desc: t.popupDisplayRules.triggerOnClickDesc,\n kind: \"click\" as const,\n },\n {\n key: \"onHover\" as const,\n icon: MousePointer2,\n title: t.popupDisplayRules.triggerOnHoverTitle,\n desc: t.popupDisplayRules.triggerOnHoverDesc,\n kind: \"hover\" as const,\n },\n {\n key: \"onCustomEvent\" as const,\n icon: Cog,\n title: t.popupDisplayRules.triggerOnCustomTitle,\n desc: t.popupDisplayRules.triggerOnCustomDesc,\n kind: \"custom\" as const,\n },\n]);\n\nconst debugTriggersOpen = ref(false);\nconst debugCopyDone = ref(false);\n\nfunction triggerSlotConditions<K extends keyof PopupTriggersSettings>(\n key: K,\n): PopupTriggerConditions {\n return normalizeTriggerConditions(\n popup.value.triggers[key].triggerConditions,\n );\n}\n\nfunction patchConditions<K extends keyof PopupTriggersSettings>(\n key: K,\n partial: Partial<PopupTriggerConditions>,\n): void {\n const prev = triggerSlotConditions(key);\n patchTrigger(key, {\n triggerConditions: { ...prev, ...partial },\n } as Partial<PopupTriggersSettings[K]>);\n}\n\nfunction setConditionsPackEnabled<K extends keyof PopupTriggersSettings>(\n key: K,\n enabled: boolean,\n): void {\n if (enabled) {\n patchTrigger(key, {\n triggerConditions: {\n ...createDefaultTriggerConditions(),\n enabled: true,\n delaySeconds: 5,\n pageViews: 3,\n operator: \"and\",\n },\n } as Partial<PopupTriggersSettings[K]>);\n } else {\n patchTrigger(key, {\n triggerConditions: {\n ...triggerSlotConditions(key),\n enabled: false,\n },\n } as Partial<PopupTriggersSettings[K]>);\n }\n}\n\nconst embedTriggersWrappedJson = computed(() =>\n JSON.stringify(\n { triggers: toPopupTriggersEmbedJson(popup.value.triggers) },\n null,\n 2,\n ),\n);\n\nasync function copyEmbedTriggersJson(): Promise<void> {\n try {\n await navigator.clipboard.writeText(embedTriggersWrappedJson.value);\n debugCopyDone.value = true;\n window.setTimeout(() => {\n debugCopyDone.value = false;\n }, 2000);\n } catch {\n debugCopyDone.value = false;\n }\n}\n</script>\n\n<template>\n <div\n class=\"tpl-popup-display-rules tpl:mx-auto tpl:max-w-3xl tpl:px-6 tpl:py-8 tpl:pb-14 tpl:bg-[var(--tpl-bg)]\"\n >\n <div\n class=\"tpl:mb-6 tpl:flex tpl:flex-wrap tpl:items-center tpl:justify-between tpl:gap-3\"\n >\n <div\n class=\"tpl:flex tpl:flex-wrap tpl:gap-1 tpl:rounded-lg tpl:bg-[var(--tpl-bg-elevated)] tpl:p-1\"\n >\n <button\n v-for=\"tab in rulesTabs\"\n :key=\"tab.id\"\n type=\"button\"\n class=\"tpl:rounded-md tpl:border-none tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-colors\"\n :class=\"\n rulesTab === tab.id\n ? 'tpl:bg-[var(--tpl-bg)] tpl:text-[var(--tpl-text)] tpl:shadow-sm'\n : 'tpl:bg-transparent tpl:text-[var(--tpl-text-muted)] hover:tpl:text-[var(--tpl-text)]'\n \"\n @click=\"rulesTab = tab.id\"\n >\n {{ tab.label }}\n </button>\n </div>\n <button\n type=\"button\"\n class=\"tpl:rounded-md tpl:border tpl:border-[var(--tpl-border)] tpl:bg-transparent tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-text-muted)] tpl:hover:bg-[var(--tpl-bg-hover)]\"\n @click=\"debugTriggersOpen = true\"\n >\n {{ t.popupDisplayRules.debug }}\n </button>\n </div>\n\n <template v-if=\"rulesTab !== 'trigger'\">\n <p class=\"tpl:text-sm tpl:text-[var(--tpl-text-muted)]\">\n {{ t.popupDisplayRules.placeholderSubtab }}\n </p>\n </template>\n\n <template v-else>\n <h1\n class=\"tpl:mb-2 tpl:text-2xl tpl:font-semibold tpl:text-[var(--tpl-text)]\"\n >\n {{ t.popupDisplayRules.triggerHeading }}\n </h1>\n <p class=\"tpl:mb-1 tpl:text-sm tpl:text-[var(--tpl-text-muted)]\">\n {{ t.popupDisplayRules.triggerIntro }}\n </p>\n <p class=\"tpl:mb-8 tpl:text-sm tpl:text-[var(--tpl-text-muted)]\">\n {{ t.popupDisplayRules.triggerIntroSecondary }}\n <a\n href=\"#\"\n class=\"tpl:text-[var(--tpl-primary)] tpl:underline tpl:underline-offset-2\"\n >{{ t.popupDisplayRules.learnMore }}</a\n >\n </p>\n\n <div class=\"tpl:flex tpl:flex-col tpl:gap-4\">\n <div\n v-for=\"def in triggerDefs\"\n :key=\"def.key\"\n class=\"tpl:rounded-xl tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:p-4 tpl:shadow-sm\"\n >\n <div class=\"tpl:flex tpl:gap-4 tpl:items-start\">\n <input\n type=\"checkbox\"\n class=\"tpl:mt-1 tpl:h-4 tpl:w-4 tpl:accent-[var(--tpl-primary)] tpl:shrink-0 tpl:rounded\"\n :checked=\"popup.triggers[def.key].enabled\"\n @change=\"\n patchTrigger(def.key, {\n enabled: ($event.target as HTMLInputElement).checked,\n })\n \"\n />\n <component\n :is=\"def.icon\"\n :size=\"22\"\n :stroke-width=\"1.75\"\n class=\"tpl:mt-0.5 tpl:shrink-0 tpl:text-[var(--tpl-text-muted)]\"\n />\n <div class=\"tpl:min-w-0 tpl:flex-1\">\n <h2\n class=\"tpl:text-[15px] tpl:font-semibold tpl:text-[var(--tpl-text)]\"\n >\n {{ def.title }}\n </h2>\n <p\n class=\"tpl:mt-1 tpl:text-sm tpl:text-[var(--tpl-text-muted)] tpl:leading-relaxed\"\n >\n {{ def.desc }}\n <template v-if=\"def.kind === 'exit'\">\n {{ \" \" }}\n <a\n href=\"#\"\n class=\"tpl:text-[var(--tpl-primary)] tpl:underline tpl:underline-offset-2\"\n >{{ t.popupDisplayRules.learnMore }}</a\n >\n </template>\n </p>\n\n <div\n v-if=\"popup.triggers[def.key].enabled && def.kind === 'landing'\"\n class=\"tpl:mt-4 tpl:rounded-lg tpl:bg-[var(--tpl-bg-elevated)] tpl:px-3 tpl:py-3\"\n >\n <label\n class=\"tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:text-sm tpl:text-[var(--tpl-text)]\"\n >\n <input\n type=\"checkbox\"\n class=\"tpl:h-4 tpl:w-4 tpl:accent-[var(--tpl-primary)] tpl:rounded\"\n :checked=\"triggerSlotConditions('onLanding').enabled\"\n @change=\"\n setConditionsPackEnabled(\n 'onLanding',\n ($event.target as HTMLInputElement).checked,\n )\n \"\n />\n {{ t.popupDisplayRules.addTriggerConditions }}\n </label>\n <PopupTriggerConditionDetails\n v-if=\"triggerSlotConditions('onLanding').enabled\"\n :conditions=\"triggerSlotConditions('onLanding')\"\n @patch=\"(p) => patchConditions('onLanding', p)\"\n />\n </div>\n\n <div\n v-if=\"popup.triggers[def.key].enabled && def.kind === 'exit'\"\n class=\"tpl:mt-4 tpl:space-y-3 tpl:rounded-lg tpl:bg-[var(--tpl-bg-elevated)] tpl:px-3 tpl:py-3\"\n >\n <label\n class=\"tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:text-sm tpl:text-[var(--tpl-text)]\"\n >\n <input\n type=\"checkbox\"\n class=\"tpl:h-4 tpl:w-4 tpl:accent-[var(--tpl-primary)] tpl:rounded\"\n :checked=\"popup.triggers.onExit.triggerOnScrollUp === true\"\n @change=\"\n patchTrigger('onExit', {\n triggerOnScrollUp: ($event.target as HTMLInputElement)\n .checked,\n })\n \"\n />\n {{ t.popupDisplayRules.triggerOnScrollUpLabel }}\n </label>\n <label\n class=\"tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:text-sm tpl:text-[var(--tpl-text)]\"\n >\n <input\n type=\"checkbox\"\n class=\"tpl:h-4 tpl:w-4 tpl:accent-[var(--tpl-primary)] tpl:rounded\"\n :checked=\"\n popup.triggers.onExit.triggerOnBackButton === true\n \"\n @change=\"\n patchTrigger('onExit', {\n triggerOnBackButton: ($event.target as HTMLInputElement)\n .checked,\n })\n \"\n />\n {{ t.popupDisplayRules.triggerOnBackButtonLabel }}\n </label>\n <hr class=\"tpl:border-[var(--tpl-border)]\" />\n <label\n class=\"tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:text-sm tpl:text-[var(--tpl-text)]\"\n >\n <input\n type=\"checkbox\"\n class=\"tpl:h-4 tpl:w-4 tpl:accent-[var(--tpl-primary)] tpl:rounded\"\n :checked=\"triggerSlotConditions('onExit').enabled\"\n @change=\"\n setConditionsPackEnabled(\n 'onExit',\n ($event.target as HTMLInputElement).checked,\n )\n \"\n />\n {{ t.popupDisplayRules.addTriggerConditions }}\n </label>\n <PopupTriggerConditionDetails\n v-if=\"triggerSlotConditions('onExit').enabled\"\n :conditions=\"triggerSlotConditions('onExit')\"\n @patch=\"(p) => patchConditions('onExit', p)\"\n />\n </div>\n\n <div\n v-if=\"popup.triggers[def.key].enabled && def.kind === 'scroll'\"\n class=\"tpl:mt-4 tpl:space-y-3 tpl:rounded-lg tpl:bg-[var(--tpl-bg-elevated)] tpl:px-3 tpl:py-3\"\n >\n <label\n class=\"tpl:flex tpl:flex-wrap tpl:items-center tpl:gap-2 tpl:text-sm tpl:text-[var(--tpl-text)]\"\n >\n {{ t.popupDisplayRules.scrollShowAfter }}\n <input\n type=\"number\"\n min=\"0\"\n max=\"100\"\n class=\"tpl:w-16 tpl:rounded-md tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:px-2 tpl:py-1 tpl:text-sm\"\n :value=\"popup.triggers.onScroll.scrollPercent ?? 50\"\n @change=\"\n patchTrigger('onScroll', {\n scrollPercent: Number(\n ($event.target as HTMLInputElement).value,\n ),\n })\n \"\n />\n {{ t.popupDisplayRules.scrollPercentSuffix }}\n </label>\n <hr class=\"tpl:border-[var(--tpl-border)]\" />\n <label\n class=\"tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:text-sm tpl:text-[var(--tpl-text)]\"\n >\n <input\n type=\"checkbox\"\n class=\"tpl:h-4 tpl:w-4 tpl:accent-[var(--tpl-primary)] tpl:rounded\"\n :checked=\"triggerSlotConditions('onScroll').enabled\"\n @change=\"\n setConditionsPackEnabled(\n 'onScroll',\n ($event.target as HTMLInputElement).checked,\n )\n \"\n />\n {{ t.popupDisplayRules.addTriggerConditions }}\n </label>\n <PopupTriggerConditionDetails\n v-if=\"triggerSlotConditions('onScroll').enabled\"\n :conditions=\"triggerSlotConditions('onScroll')\"\n @patch=\"(p) => patchConditions('onScroll', p)\"\n />\n </div>\n\n <div\n v-if=\"popup.triggers[def.key].enabled && def.kind === 'click'\"\n class=\"tpl:mt-4 tpl:flex tpl:flex-col tpl:gap-3 tpl:rounded-lg tpl:bg-[var(--tpl-bg-elevated)] tpl:px-3 tpl:py-3\"\n >\n <div\n class=\"tpl:inline-flex tpl:rounded-full tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:px-3 tpl:py-1 tpl:text-sm tpl:font-mono tpl:text-[var(--tpl-text)]\"\n >\n <input\n type=\"text\"\n class=\"tpl:w-36 tpl:border-none tpl:bg-transparent tpl:p-0 tpl:text-sm tpl:outline-none\"\n :value=\"popup.triggers.onClick.clickAnchor ?? ''\"\n @change=\"\n patchTrigger('onClick', {\n clickAnchor: (\n $event.target as HTMLInputElement\n ).value.trim(),\n })\n \"\n />\n </div>\n <p\n class=\"tpl:text-xs tpl:leading-relaxed tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ t.popupDisplayRules.clickHint }}\n </p>\n <a\n href=\"#\"\n class=\"tpl:text-xs tpl:text-[var(--tpl-primary)] tpl:underline\"\n >{{ t.popupDisplayRules.learnMore }}</a\n >\n <hr class=\"tpl:border-[var(--tpl-border)]\" />\n <label\n class=\"tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:text-sm tpl:text-[var(--tpl-text)]\"\n >\n <input\n type=\"checkbox\"\n class=\"tpl:h-4 tpl:w-4 tpl:accent-[var(--tpl-primary)] tpl:rounded\"\n :checked=\"triggerSlotConditions('onClick').enabled\"\n @change=\"\n setConditionsPackEnabled(\n 'onClick',\n ($event.target as HTMLInputElement).checked,\n )\n \"\n />\n {{ t.popupDisplayRules.addTriggerConditions }}\n </label>\n <PopupTriggerConditionDetails\n v-if=\"triggerSlotConditions('onClick').enabled\"\n :conditions=\"triggerSlotConditions('onClick')\"\n @patch=\"(p) => patchConditions('onClick', p)\"\n />\n </div>\n\n <div\n v-if=\"popup.triggers[def.key].enabled && def.kind === 'hover'\"\n class=\"tpl:mt-4 tpl:flex tpl:flex-col tpl:gap-3 tpl:rounded-lg tpl:bg-[var(--tpl-bg-elevated)] tpl:px-3 tpl:py-3\"\n >\n <div class=\"tpl:flex tpl:flex-wrap tpl:items-center tpl:gap-2\">\n <input\n type=\"text\"\n class=\"tpl:min-w-[12rem] tpl:flex-1 tpl:rounded-md tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:px-3 tpl:py-2 tpl:text-sm\"\n :placeholder=\"t.popupDisplayRules.hoverPlaceholder\"\n :value=\"popup.triggers.onHover.hoverElementId ?? ''\"\n @change=\"\n patchTrigger('onHover', {\n hoverElementId: (\n $event.target as HTMLInputElement\n ).value.trim(),\n })\n \"\n />\n <a\n href=\"#\"\n class=\"tpl:text-sm tpl:text-[var(--tpl-primary)] tpl:underline\"\n >{{ t.popupDisplayRules.help }}</a\n >\n </div>\n <hr class=\"tpl:border-[var(--tpl-border)]\" />\n <label\n class=\"tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:text-sm tpl:text-[var(--tpl-text)]\"\n >\n <input\n type=\"checkbox\"\n class=\"tpl:h-4 tpl:w-4 tpl:accent-[var(--tpl-primary)] tpl:rounded\"\n :checked=\"triggerSlotConditions('onHover').enabled\"\n @change=\"\n setConditionsPackEnabled(\n 'onHover',\n ($event.target as HTMLInputElement).checked,\n )\n \"\n />\n {{ t.popupDisplayRules.addTriggerConditions }}\n </label>\n <PopupTriggerConditionDetails\n v-if=\"triggerSlotConditions('onHover').enabled\"\n :conditions=\"triggerSlotConditions('onHover')\"\n @patch=\"(p) => patchConditions('onHover', p)\"\n />\n </div>\n\n <div\n v-if=\"popup.triggers[def.key].enabled && def.kind === 'custom'\"\n class=\"tpl:mt-4 tpl:rounded-lg tpl:bg-[var(--tpl-bg-elevated)] tpl:px-3 tpl:py-3\"\n >\n <label\n class=\"tpl:block tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-text-muted)]\"\n >{{ t.popupDisplayRules.customEventLabel }}</label\n >\n <input\n type=\"text\"\n class=\"tpl:mt-2 tpl:w-full tpl:rounded-md tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:px-3 tpl:py-2 tpl:text-sm\"\n :placeholder=\"t.popupDisplayRules.customEventPlaceholder\"\n :value=\"popup.triggers.onCustomEvent.customEventName ?? ''\"\n @change=\"\n patchTrigger('onCustomEvent', {\n customEventName: (\n $event.target as HTMLInputElement\n ).value.trim(),\n })\n \"\n />\n <hr class=\"tpl:mt-3 tpl:border-[var(--tpl-border)]\" />\n <label\n class=\"tpl:mt-3 tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:text-sm tpl:text-[var(--tpl-text)]\"\n >\n <input\n type=\"checkbox\"\n class=\"tpl:h-4 tpl:w-4 tpl:accent-[var(--tpl-primary)] tpl:rounded\"\n :checked=\"triggerSlotConditions('onCustomEvent').enabled\"\n @change=\"\n setConditionsPackEnabled(\n 'onCustomEvent',\n ($event.target as HTMLInputElement).checked,\n )\n \"\n />\n {{ t.popupDisplayRules.addTriggerConditions }}\n </label>\n <PopupTriggerConditionDetails\n v-if=\"triggerSlotConditions('onCustomEvent').enabled\"\n :conditions=\"triggerSlotConditions('onCustomEvent')\"\n @patch=\"(p) => patchConditions('onCustomEvent', p)\"\n />\n </div>\n </div>\n </div>\n </div>\n </div>\n </template>\n </div>\n\n <Teleport to=\"body\">\n <div\n v-if=\"debugTriggersOpen\"\n class=\"tpl:fixed tpl:inset-0 tpl:z-[1000] tpl:flex tpl:items-center tpl:justify-center tpl:bg-black/50 tpl:p-4\"\n role=\"dialog\"\n aria-modal=\"true\"\n :aria-label=\"t.popupDisplayRules.debugJsonTitle\"\n @click.self=\"debugTriggersOpen = false\"\n >\n <div\n class=\"tpl:flex tpl:max-h-[85vh] tpl:w-full tpl:max-w-2xl tpl:flex-col tpl:overflow-hidden tpl:rounded-xl tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:shadow-xl\"\n >\n <div\n class=\"tpl:flex tpl:flex-wrap tpl:items-center tpl:justify-between tpl:gap-2 tpl:border-b tpl:border-[var(--tpl-border)] tpl:px-4 tpl:py-3\"\n >\n <h2 class=\"tpl:text-sm tpl:font-semibold tpl:text-[var(--tpl-text)]\">\n {{ t.popupDisplayRules.debugJsonTitle }}\n </h2>\n <div class=\"tpl:flex tpl:items-center tpl:gap-2\">\n <button\n type=\"button\"\n class=\"tpl:rounded-md tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-elevated)] tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-text)] tpl:hover:bg-[var(--tpl-bg-hover)]\"\n @click=\"copyEmbedTriggersJson\"\n >\n {{\n debugCopyDone\n ? t.popupDisplayRules.copyJsonDone\n : t.popupDisplayRules.copyJson\n }}\n </button>\n <button\n type=\"button\"\n class=\"tpl:rounded-md tpl:border tpl:border-[var(--tpl-border)] tpl:bg-transparent tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-text-muted)] tpl:hover:bg-[var(--tpl-bg-hover)]\"\n @click=\"debugTriggersOpen = false\"\n >\n {{ t.popupDisplayRules.debugJsonClose }}\n </button>\n </div>\n </div>\n <pre\n class=\"tpl:m-0 tpl:flex-1 tpl:overflow-auto tpl:p-4 tpl:text-left tpl:font-mono tpl:text-[11px] tpl:leading-relaxed tpl:text-[var(--tpl-text)]\"\n >{{ embedTriggersWrappedJson }}</pre\n >\n <p\n class=\"tpl:m-0 tpl:border-t tpl:border-[var(--tpl-border)] tpl:px-4 tpl:py-2 tpl:text-xs tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ t.popupDisplayRules.debugJsonHint }}\n </p>\n </div>\n </div>\n </Teleport>\n</template>\n","<script setup lang=\"ts\">\nimport {\n ArrowDownFromLine,\n Cog,\n LogOut,\n MousePointerClick,\n MousePointer2,\n LayoutTemplate,\n} from \"@lucide/vue\";\nimport { computed, inject, ref } from \"vue\";\nimport type {\n PopupEmbedSettings,\n PopupTriggerConditions,\n PopupTriggersSettings,\n} from \"@aswin.dev/types\";\nimport {\n createDefaultTriggerConditions,\n normalizeTriggerConditions,\n toPopupTriggersEmbedJson,\n} from \"@aswin.dev/types\";\nimport { useI18n } from \"../composables/useI18n\";\nimport { EDITOR_KEY } from \"../keys\";\nimport type { BaseEditorReturn } from \"../composables/useEditorCore\";\nimport { resolvePopupEmbed } from \"../utils/resolvePopupEmbed\";\nimport PopupTriggerConditionDetails from \"./PopupTriggerConditionDetails.vue\";\n\nconst editor = inject(EDITOR_KEY) as BaseEditorReturn | null;\nif (!editor) {\n throw new Error(\"PopupDisplayRulesView requires EDITOR_KEY\");\n}\n\nconst { t } = useI18n();\n\nconst rulesTab = ref<\n \"trigger\" | \"pages\" | \"audience\" | \"frequency\" | \"advanced\"\n>(\"trigger\");\n\nconst rulesTabs = computed(() => [\n { id: \"trigger\" as const, label: t.popupDisplayRules.tabTrigger },\n { id: \"pages\" as const, label: t.popupDisplayRules.tabPages },\n { id: \"audience\" as const, label: t.popupDisplayRules.tabAudience },\n { id: \"frequency\" as const, label: t.popupDisplayRules.tabFrequency },\n { id: \"advanced\" as const, label: t.popupDisplayRules.tabAdvanced },\n]);\n\nconst popup = computed(() => resolvePopupEmbed(editor!.content.value.settings));\n\nfunction commit(next: PopupEmbedSettings): void {\n editor!.updateSettings({ popup: next });\n}\n\nfunction patchTrigger<K extends keyof PopupTriggersSettings>(\n key: K,\n patch: Partial<PopupTriggersSettings[K]>,\n): void {\n const cur = popup.value;\n commit({\n ...cur,\n triggers: {\n ...cur.triggers,\n [key]: { ...cur.triggers[key], ...patch },\n },\n });\n}\n\nconst triggerDefs = computed(() => [\n {\n key: \"onLanding\" as const,\n icon: LayoutTemplate,\n title: t.popupDisplayRules.triggerOnLandingTitle,\n desc: t.popupDisplayRules.triggerOnLandingDesc,\n kind: \"landing\" as const,\n },\n {\n key: \"onExit\" as const,\n icon: LogOut,\n title: t.popupDisplayRules.triggerOnExitTitle,\n desc: t.popupDisplayRules.triggerOnExitDesc,\n kind: \"exit\" as const,\n },\n {\n key: \"onScroll\" as const,\n icon: ArrowDownFromLine,\n title: t.popupDisplayRules.triggerOnScrollTitle,\n desc: t.popupDisplayRules.triggerOnScrollDesc,\n kind: \"scroll\" as const,\n },\n {\n key: \"onClick\" as const,\n icon: MousePointerClick,\n title: t.popupDisplayRules.triggerOnClickTitle,\n desc: t.popupDisplayRules.triggerOnClickDesc,\n kind: \"click\" as const,\n },\n {\n key: \"onHover\" as const,\n icon: MousePointer2,\n title: t.popupDisplayRules.triggerOnHoverTitle,\n desc: t.popupDisplayRules.triggerOnHoverDesc,\n kind: \"hover\" as const,\n },\n {\n key: \"onCustomEvent\" as const,\n icon: Cog,\n title: t.popupDisplayRules.triggerOnCustomTitle,\n desc: t.popupDisplayRules.triggerOnCustomDesc,\n kind: \"custom\" as const,\n },\n]);\n\nconst debugTriggersOpen = ref(false);\nconst debugCopyDone = ref(false);\n\nfunction triggerSlotConditions<K extends keyof PopupTriggersSettings>(\n key: K,\n): PopupTriggerConditions {\n return normalizeTriggerConditions(\n popup.value.triggers[key].triggerConditions,\n );\n}\n\nfunction patchConditions<K extends keyof PopupTriggersSettings>(\n key: K,\n partial: Partial<PopupTriggerConditions>,\n): void {\n const prev = triggerSlotConditions(key);\n patchTrigger(key, {\n triggerConditions: { ...prev, ...partial },\n } as Partial<PopupTriggersSettings[K]>);\n}\n\nfunction setConditionsPackEnabled<K extends keyof PopupTriggersSettings>(\n key: K,\n enabled: boolean,\n): void {\n if (enabled) {\n patchTrigger(key, {\n triggerConditions: {\n ...createDefaultTriggerConditions(),\n enabled: true,\n delaySeconds: 5,\n pageViews: 3,\n operator: \"and\",\n },\n } as Partial<PopupTriggersSettings[K]>);\n } else {\n patchTrigger(key, {\n triggerConditions: {\n ...triggerSlotConditions(key),\n enabled: false,\n },\n } as Partial<PopupTriggersSettings[K]>);\n }\n}\n\nconst embedTriggersWrappedJson = computed(() =>\n JSON.stringify(\n { triggers: toPopupTriggersEmbedJson(popup.value.triggers) },\n null,\n 2,\n ),\n);\n\nasync function copyEmbedTriggersJson(): Promise<void> {\n try {\n await navigator.clipboard.writeText(embedTriggersWrappedJson.value);\n debugCopyDone.value = true;\n window.setTimeout(() => {\n debugCopyDone.value = false;\n }, 2000);\n } catch {\n debugCopyDone.value = false;\n }\n}\n</script>\n\n<template>\n <div\n class=\"tpl-popup-display-rules tpl:mx-auto tpl:max-w-3xl tpl:px-6 tpl:py-8 tpl:pb-14 tpl:bg-[var(--tpl-bg)]\"\n >\n <div\n class=\"tpl:mb-6 tpl:flex tpl:flex-wrap tpl:items-center tpl:justify-between tpl:gap-3\"\n >\n <div\n class=\"tpl:flex tpl:flex-wrap tpl:gap-1 tpl:rounded-lg tpl:bg-[var(--tpl-bg-elevated)] tpl:p-1\"\n >\n <button\n v-for=\"tab in rulesTabs\"\n :key=\"tab.id\"\n type=\"button\"\n class=\"tpl:rounded-md tpl:border-none tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-colors\"\n :class=\"\n rulesTab === tab.id\n ? 'tpl:bg-[var(--tpl-bg)] tpl:text-[var(--tpl-text)] tpl:shadow-sm'\n : 'tpl:bg-transparent tpl:text-[var(--tpl-text-muted)] hover:tpl:text-[var(--tpl-text)]'\n \"\n @click=\"rulesTab = tab.id\"\n >\n {{ tab.label }}\n </button>\n </div>\n <button\n type=\"button\"\n class=\"tpl:rounded-md tpl:border tpl:border-[var(--tpl-border)] tpl:bg-transparent tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-text-muted)] tpl:hover:bg-[var(--tpl-bg-hover)]\"\n @click=\"debugTriggersOpen = true\"\n >\n {{ t.popupDisplayRules.debug }}\n </button>\n </div>\n\n <template v-if=\"rulesTab !== 'trigger'\">\n <p class=\"tpl:text-sm tpl:text-[var(--tpl-text-muted)]\">\n {{ t.popupDisplayRules.placeholderSubtab }}\n </p>\n </template>\n\n <template v-else>\n <h1\n class=\"tpl:mb-2 tpl:text-2xl tpl:font-semibold tpl:text-[var(--tpl-text)]\"\n >\n {{ t.popupDisplayRules.triggerHeading }}\n </h1>\n <p class=\"tpl:mb-1 tpl:text-sm tpl:text-[var(--tpl-text-muted)]\">\n {{ t.popupDisplayRules.triggerIntro }}\n </p>\n <p class=\"tpl:mb-8 tpl:text-sm tpl:text-[var(--tpl-text-muted)]\">\n {{ t.popupDisplayRules.triggerIntroSecondary }}\n <a\n href=\"#\"\n class=\"tpl:text-[var(--tpl-primary)] tpl:underline tpl:underline-offset-2\"\n >{{ t.popupDisplayRules.learnMore }}</a\n >\n </p>\n\n <div class=\"tpl:flex tpl:flex-col tpl:gap-4\">\n <div\n v-for=\"def in triggerDefs\"\n :key=\"def.key\"\n class=\"tpl:rounded-xl tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:p-4 tpl:shadow-sm\"\n >\n <div class=\"tpl:flex tpl:gap-4 tpl:items-start\">\n <input\n type=\"checkbox\"\n class=\"tpl:mt-1 tpl:h-4 tpl:w-4 tpl:accent-[var(--tpl-primary)] tpl:shrink-0 tpl:rounded\"\n :checked=\"popup.triggers[def.key].enabled\"\n @change=\"\n patchTrigger(def.key, {\n enabled: ($event.target as HTMLInputElement).checked,\n })\n \"\n />\n <component\n :is=\"def.icon\"\n :size=\"22\"\n :stroke-width=\"1.75\"\n class=\"tpl:mt-0.5 tpl:shrink-0 tpl:text-[var(--tpl-text-muted)]\"\n />\n <div class=\"tpl:min-w-0 tpl:flex-1\">\n <h2\n class=\"tpl:text-[15px] tpl:font-semibold tpl:text-[var(--tpl-text)]\"\n >\n {{ def.title }}\n </h2>\n <p\n class=\"tpl:mt-1 tpl:text-sm tpl:text-[var(--tpl-text-muted)] tpl:leading-relaxed\"\n >\n {{ def.desc }}\n <template v-if=\"def.kind === 'exit'\">\n {{ \" \" }}\n <a\n href=\"#\"\n class=\"tpl:text-[var(--tpl-primary)] tpl:underline tpl:underline-offset-2\"\n >{{ t.popupDisplayRules.learnMore }}</a\n >\n </template>\n </p>\n\n <div\n v-if=\"popup.triggers[def.key].enabled && def.kind === 'landing'\"\n class=\"tpl:mt-4 tpl:rounded-lg tpl:bg-[var(--tpl-bg-elevated)] tpl:px-3 tpl:py-3\"\n >\n <label\n class=\"tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:text-sm tpl:text-[var(--tpl-text)]\"\n >\n <input\n type=\"checkbox\"\n class=\"tpl:h-4 tpl:w-4 tpl:accent-[var(--tpl-primary)] tpl:rounded\"\n :checked=\"triggerSlotConditions('onLanding').enabled\"\n @change=\"\n setConditionsPackEnabled(\n 'onLanding',\n ($event.target as HTMLInputElement).checked,\n )\n \"\n />\n {{ t.popupDisplayRules.addTriggerConditions }}\n </label>\n <PopupTriggerConditionDetails\n v-if=\"triggerSlotConditions('onLanding').enabled\"\n :conditions=\"triggerSlotConditions('onLanding')\"\n @patch=\"(p) => patchConditions('onLanding', p)\"\n />\n </div>\n\n <div\n v-if=\"popup.triggers[def.key].enabled && def.kind === 'exit'\"\n class=\"tpl:mt-4 tpl:space-y-3 tpl:rounded-lg tpl:bg-[var(--tpl-bg-elevated)] tpl:px-3 tpl:py-3\"\n >\n <label\n class=\"tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:text-sm tpl:text-[var(--tpl-text)]\"\n >\n <input\n type=\"checkbox\"\n class=\"tpl:h-4 tpl:w-4 tpl:accent-[var(--tpl-primary)] tpl:rounded\"\n :checked=\"popup.triggers.onExit.triggerOnScrollUp === true\"\n @change=\"\n patchTrigger('onExit', {\n triggerOnScrollUp: ($event.target as HTMLInputElement)\n .checked,\n })\n \"\n />\n {{ t.popupDisplayRules.triggerOnScrollUpLabel }}\n </label>\n <label\n class=\"tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:text-sm tpl:text-[var(--tpl-text)]\"\n >\n <input\n type=\"checkbox\"\n class=\"tpl:h-4 tpl:w-4 tpl:accent-[var(--tpl-primary)] tpl:rounded\"\n :checked=\"\n popup.triggers.onExit.triggerOnBackButton === true\n \"\n @change=\"\n patchTrigger('onExit', {\n triggerOnBackButton: ($event.target as HTMLInputElement)\n .checked,\n })\n \"\n />\n {{ t.popupDisplayRules.triggerOnBackButtonLabel }}\n </label>\n <hr class=\"tpl:border-[var(--tpl-border)]\" />\n <label\n class=\"tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:text-sm tpl:text-[var(--tpl-text)]\"\n >\n <input\n type=\"checkbox\"\n class=\"tpl:h-4 tpl:w-4 tpl:accent-[var(--tpl-primary)] tpl:rounded\"\n :checked=\"triggerSlotConditions('onExit').enabled\"\n @change=\"\n setConditionsPackEnabled(\n 'onExit',\n ($event.target as HTMLInputElement).checked,\n )\n \"\n />\n {{ t.popupDisplayRules.addTriggerConditions }}\n </label>\n <PopupTriggerConditionDetails\n v-if=\"triggerSlotConditions('onExit').enabled\"\n :conditions=\"triggerSlotConditions('onExit')\"\n @patch=\"(p) => patchConditions('onExit', p)\"\n />\n </div>\n\n <div\n v-if=\"popup.triggers[def.key].enabled && def.kind === 'scroll'\"\n class=\"tpl:mt-4 tpl:space-y-3 tpl:rounded-lg tpl:bg-[var(--tpl-bg-elevated)] tpl:px-3 tpl:py-3\"\n >\n <label\n class=\"tpl:flex tpl:flex-wrap tpl:items-center tpl:gap-2 tpl:text-sm tpl:text-[var(--tpl-text)]\"\n >\n {{ t.popupDisplayRules.scrollShowAfter }}\n <input\n type=\"number\"\n min=\"0\"\n max=\"100\"\n class=\"tpl:w-16 tpl:rounded-md tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:px-2 tpl:py-1 tpl:text-sm\"\n :value=\"popup.triggers.onScroll.scrollPercent ?? 50\"\n @change=\"\n patchTrigger('onScroll', {\n scrollPercent: Number(\n ($event.target as HTMLInputElement).value,\n ),\n })\n \"\n />\n {{ t.popupDisplayRules.scrollPercentSuffix }}\n </label>\n <hr class=\"tpl:border-[var(--tpl-border)]\" />\n <label\n class=\"tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:text-sm tpl:text-[var(--tpl-text)]\"\n >\n <input\n type=\"checkbox\"\n class=\"tpl:h-4 tpl:w-4 tpl:accent-[var(--tpl-primary)] tpl:rounded\"\n :checked=\"triggerSlotConditions('onScroll').enabled\"\n @change=\"\n setConditionsPackEnabled(\n 'onScroll',\n ($event.target as HTMLInputElement).checked,\n )\n \"\n />\n {{ t.popupDisplayRules.addTriggerConditions }}\n </label>\n <PopupTriggerConditionDetails\n v-if=\"triggerSlotConditions('onScroll').enabled\"\n :conditions=\"triggerSlotConditions('onScroll')\"\n @patch=\"(p) => patchConditions('onScroll', p)\"\n />\n </div>\n\n <div\n v-if=\"popup.triggers[def.key].enabled && def.kind === 'click'\"\n class=\"tpl:mt-4 tpl:flex tpl:flex-col tpl:gap-3 tpl:rounded-lg tpl:bg-[var(--tpl-bg-elevated)] tpl:px-3 tpl:py-3\"\n >\n <div\n class=\"tpl:inline-flex tpl:rounded-full tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:px-3 tpl:py-1 tpl:text-sm tpl:font-mono tpl:text-[var(--tpl-text)]\"\n >\n <input\n type=\"text\"\n class=\"tpl:w-36 tpl:border-none tpl:bg-transparent tpl:p-0 tpl:text-sm tpl:outline-none\"\n :value=\"popup.triggers.onClick.clickAnchor ?? ''\"\n @change=\"\n patchTrigger('onClick', {\n clickAnchor: (\n $event.target as HTMLInputElement\n ).value.trim(),\n })\n \"\n />\n </div>\n <p\n class=\"tpl:text-xs tpl:leading-relaxed tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ t.popupDisplayRules.clickHint }}\n </p>\n <a\n href=\"#\"\n class=\"tpl:text-xs tpl:text-[var(--tpl-primary)] tpl:underline\"\n >{{ t.popupDisplayRules.learnMore }}</a\n >\n <hr class=\"tpl:border-[var(--tpl-border)]\" />\n <label\n class=\"tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:text-sm tpl:text-[var(--tpl-text)]\"\n >\n <input\n type=\"checkbox\"\n class=\"tpl:h-4 tpl:w-4 tpl:accent-[var(--tpl-primary)] tpl:rounded\"\n :checked=\"triggerSlotConditions('onClick').enabled\"\n @change=\"\n setConditionsPackEnabled(\n 'onClick',\n ($event.target as HTMLInputElement).checked,\n )\n \"\n />\n {{ t.popupDisplayRules.addTriggerConditions }}\n </label>\n <PopupTriggerConditionDetails\n v-if=\"triggerSlotConditions('onClick').enabled\"\n :conditions=\"triggerSlotConditions('onClick')\"\n @patch=\"(p) => patchConditions('onClick', p)\"\n />\n </div>\n\n <div\n v-if=\"popup.triggers[def.key].enabled && def.kind === 'hover'\"\n class=\"tpl:mt-4 tpl:flex tpl:flex-col tpl:gap-3 tpl:rounded-lg tpl:bg-[var(--tpl-bg-elevated)] tpl:px-3 tpl:py-3\"\n >\n <div class=\"tpl:flex tpl:flex-wrap tpl:items-center tpl:gap-2\">\n <input\n type=\"text\"\n class=\"tpl:min-w-[12rem] tpl:flex-1 tpl:rounded-md tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:px-3 tpl:py-2 tpl:text-sm\"\n :placeholder=\"t.popupDisplayRules.hoverPlaceholder\"\n :value=\"popup.triggers.onHover.hoverElementId ?? ''\"\n @change=\"\n patchTrigger('onHover', {\n hoverElementId: (\n $event.target as HTMLInputElement\n ).value.trim(),\n })\n \"\n />\n <a\n href=\"#\"\n class=\"tpl:text-sm tpl:text-[var(--tpl-primary)] tpl:underline\"\n >{{ t.popupDisplayRules.help }}</a\n >\n </div>\n <hr class=\"tpl:border-[var(--tpl-border)]\" />\n <label\n class=\"tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:text-sm tpl:text-[var(--tpl-text)]\"\n >\n <input\n type=\"checkbox\"\n class=\"tpl:h-4 tpl:w-4 tpl:accent-[var(--tpl-primary)] tpl:rounded\"\n :checked=\"triggerSlotConditions('onHover').enabled\"\n @change=\"\n setConditionsPackEnabled(\n 'onHover',\n ($event.target as HTMLInputElement).checked,\n )\n \"\n />\n {{ t.popupDisplayRules.addTriggerConditions }}\n </label>\n <PopupTriggerConditionDetails\n v-if=\"triggerSlotConditions('onHover').enabled\"\n :conditions=\"triggerSlotConditions('onHover')\"\n @patch=\"(p) => patchConditions('onHover', p)\"\n />\n </div>\n\n <div\n v-if=\"popup.triggers[def.key].enabled && def.kind === 'custom'\"\n class=\"tpl:mt-4 tpl:rounded-lg tpl:bg-[var(--tpl-bg-elevated)] tpl:px-3 tpl:py-3\"\n >\n <label\n class=\"tpl:block tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-text-muted)]\"\n >{{ t.popupDisplayRules.customEventLabel }}</label\n >\n <input\n type=\"text\"\n class=\"tpl:mt-2 tpl:w-full tpl:rounded-md tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:px-3 tpl:py-2 tpl:text-sm\"\n :placeholder=\"t.popupDisplayRules.customEventPlaceholder\"\n :value=\"popup.triggers.onCustomEvent.customEventName ?? ''\"\n @change=\"\n patchTrigger('onCustomEvent', {\n customEventName: (\n $event.target as HTMLInputElement\n ).value.trim(),\n })\n \"\n />\n <hr class=\"tpl:mt-3 tpl:border-[var(--tpl-border)]\" />\n <label\n class=\"tpl:mt-3 tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:text-sm tpl:text-[var(--tpl-text)]\"\n >\n <input\n type=\"checkbox\"\n class=\"tpl:h-4 tpl:w-4 tpl:accent-[var(--tpl-primary)] tpl:rounded\"\n :checked=\"triggerSlotConditions('onCustomEvent').enabled\"\n @change=\"\n setConditionsPackEnabled(\n 'onCustomEvent',\n ($event.target as HTMLInputElement).checked,\n )\n \"\n />\n {{ t.popupDisplayRules.addTriggerConditions }}\n </label>\n <PopupTriggerConditionDetails\n v-if=\"triggerSlotConditions('onCustomEvent').enabled\"\n :conditions=\"triggerSlotConditions('onCustomEvent')\"\n @patch=\"(p) => patchConditions('onCustomEvent', p)\"\n />\n </div>\n </div>\n </div>\n </div>\n </div>\n </template>\n </div>\n\n <Teleport to=\"body\">\n <div\n v-if=\"debugTriggersOpen\"\n class=\"tpl:fixed tpl:inset-0 tpl:z-[1000] tpl:flex tpl:items-center tpl:justify-center tpl:bg-black/50 tpl:p-4\"\n role=\"dialog\"\n aria-modal=\"true\"\n :aria-label=\"t.popupDisplayRules.debugJsonTitle\"\n @click.self=\"debugTriggersOpen = false\"\n >\n <div\n class=\"tpl:flex tpl:max-h-[85vh] tpl:w-full tpl:max-w-2xl tpl:flex-col tpl:overflow-hidden tpl:rounded-xl tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:shadow-xl\"\n >\n <div\n class=\"tpl:flex tpl:flex-wrap tpl:items-center tpl:justify-between tpl:gap-2 tpl:border-b tpl:border-[var(--tpl-border)] tpl:px-4 tpl:py-3\"\n >\n <h2 class=\"tpl:text-sm tpl:font-semibold tpl:text-[var(--tpl-text)]\">\n {{ t.popupDisplayRules.debugJsonTitle }}\n </h2>\n <div class=\"tpl:flex tpl:items-center tpl:gap-2\">\n <button\n type=\"button\"\n class=\"tpl:rounded-md tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-elevated)] tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-text)] tpl:hover:bg-[var(--tpl-bg-hover)]\"\n @click=\"copyEmbedTriggersJson\"\n >\n {{\n debugCopyDone\n ? t.popupDisplayRules.copyJsonDone\n : t.popupDisplayRules.copyJson\n }}\n </button>\n <button\n type=\"button\"\n class=\"tpl:rounded-md tpl:border tpl:border-[var(--tpl-border)] tpl:bg-transparent tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-text-muted)] tpl:hover:bg-[var(--tpl-bg-hover)]\"\n @click=\"debugTriggersOpen = false\"\n >\n {{ t.popupDisplayRules.debugJsonClose }}\n </button>\n </div>\n </div>\n <pre\n class=\"tpl:m-0 tpl:flex-1 tpl:overflow-auto tpl:p-4 tpl:text-left tpl:font-mono tpl:text-[11px] tpl:leading-relaxed tpl:text-[var(--tpl-text)]\"\n >{{ embedTriggersWrappedJson }}</pre\n >\n <p\n class=\"tpl:m-0 tpl:border-t tpl:border-[var(--tpl-border)] tpl:px-4 tpl:py-2 tpl:text-xs tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ t.popupDisplayRules.debugJsonHint }}\n </p>\n </div>\n </div>\n </Teleport>\n</template>\n","/**\n * Copies `--tpl-*` CSS variables and base typography from the nearest editor\n * theme root so portaled / fixed UI matches the editor surface (see MergeTagSuggestion).\n */\nexport function syncEditorThemeToPortal(\n trigger: HTMLElement | null | undefined,\n panel: HTMLElement | null | undefined,\n): void {\n if (!trigger || !panel) return;\n const themeRoot = trigger.closest<HTMLElement>(\"[data-tpl-theme]\");\n if (!themeRoot) return;\n const themeValue = themeRoot.getAttribute(\"data-tpl-theme\");\n if (themeValue) panel.setAttribute(\"data-tpl-theme\", themeValue);\n else panel.removeAttribute(\"data-tpl-theme\");\n\n const computed = window.getComputedStyle(themeRoot);\n for (let i = 0; i < computed.length; i++) {\n const prop = computed[i];\n if (prop.startsWith(\"--tpl-\")) {\n panel.style.setProperty(prop, computed.getPropertyValue(prop));\n }\n }\n panel.style.fontFamily = computed.fontFamily;\n panel.style.fontSize = computed.fontSize;\n panel.style.lineHeight = computed.lineHeight;\n}\n","<script setup lang=\"ts\">\nimport { CalendarDays, ChevronLeft, ChevronRight } from \"@lucide/vue\";\nimport {\n onClickOutside,\n useElementBounding,\n useEventListener,\n} from \"@vueuse/core\";\nimport { computed, nextTick, ref, watch } from \"vue\";\nimport { useI18n } from \"../composables/useI18n\";\nimport { syncEditorThemeToPortal } from \"../utils/syncEditorThemeToPortal\";\n\nconst props = defineProps<{\n modelValue: string;\n placeholder: string;\n inputId: string;\n clearLabel: string;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update:modelValue\", value: string): void;\n}>();\n\nconst { t } = useI18n();\n\nconst open = ref(false);\nconst triggerRef = ref<HTMLElement | null>(null);\nconst popoverRef = ref<HTMLElement | null>(null);\n\nconst viewYear = ref(new Date().getFullYear());\nconst viewMonth = ref(new Date().getMonth());\n\nconst { bottom, left, width, update } = useElementBounding(triggerRef);\n\nconst popoverStyle = computed(() => ({\n top: `${bottom.value + 4}px`,\n left: `${left.value}px`,\n width: `${Math.max(width.value, 268)}px`,\n}));\n\nfunction pad2(n: number): string {\n return String(n).padStart(2, \"0\");\n}\n\nfunction toISO(y: number, monthIndex: number, day: number): string {\n return `${y}-${pad2(monthIndex + 1)}-${pad2(day)}`;\n}\n\nfunction parseISO(s: string): { y: number; m: number; d: number } | null {\n const m = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(s.trim());\n if (!m) return null;\n const y = Number(m[1]);\n const mo = Number(m[2]) - 1;\n const d = Number(m[3]);\n const dt = new Date(y, mo, d);\n if (dt.getFullYear() !== y || dt.getMonth() !== mo || dt.getDate() !== d) {\n return null;\n }\n return { y, m: mo, d };\n}\n\nfunction isoToday(): string {\n const n = new Date();\n return toISO(n.getFullYear(), n.getMonth(), n.getDate());\n}\n\nfunction buildCalendarCells(y: number, monthIndex: number) {\n type Cell = { iso: string; day: number; inMonth: boolean };\n const cells: Cell[] = [];\n const first = new Date(y, monthIndex, 1);\n const startPad = first.getDay();\n const daysInMonth = new Date(y, monthIndex + 1, 0).getDate();\n\n const prevLast = new Date(y, monthIndex, 0);\n const prevDays = prevLast.getDate();\n const prevM = monthIndex === 0 ? 11 : monthIndex - 1;\n const prevY = monthIndex === 0 ? y - 1 : y;\n\n for (let i = startPad - 1; i >= 0; i--) {\n const day = prevDays - i;\n cells.push({\n iso: toISO(prevY, prevM, day),\n day,\n inMonth: false,\n });\n }\n\n for (let d = 1; d <= daysInMonth; d++) {\n cells.push({\n iso: toISO(y, monthIndex, d),\n day: d,\n inMonth: true,\n });\n }\n\n const nextY = monthIndex === 11 ? y + 1 : y;\n const nextM = monthIndex === 11 ? 0 : monthIndex + 1;\n let nextD = 1;\n while (cells.length % 7 !== 0) {\n cells.push({\n iso: toISO(nextY, nextM, nextD),\n day: nextD++,\n inMonth: false,\n });\n }\n return cells;\n}\n\nconst weekdayLabels = computed(() => {\n const labels: string[] = [];\n const sun = new Date(2024, 0, 7);\n for (let i = 0; i < 7; i++) {\n const d = new Date(sun);\n d.setDate(sun.getDate() + i);\n labels.push(\n new Intl.DateTimeFormat(undefined, { weekday: \"short\" }).format(d),\n );\n }\n return labels;\n});\n\nconst monthTitle = computed(() =>\n new Intl.DateTimeFormat(undefined, {\n month: \"long\",\n year: \"numeric\",\n }).format(new Date(viewYear.value, viewMonth.value, 1)),\n);\n\nconst cells = computed(() =>\n buildCalendarCells(viewYear.value, viewMonth.value),\n);\n\nconst todayStr = computed(() => isoToday());\n\nfunction formatDisplay(iso: string): string {\n const p = parseISO(iso);\n if (!p) return iso;\n return new Intl.DateTimeFormat(undefined, {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n }).format(new Date(p.y, p.m, p.d));\n}\n\nfunction syncViewToModel(): void {\n const p = parseISO(props.modelValue);\n if (p) {\n viewYear.value = p.y;\n viewMonth.value = p.m;\n } else {\n const n = new Date();\n viewYear.value = n.getFullYear();\n viewMonth.value = n.getMonth();\n }\n}\n\nfunction toggleOpen(): void {\n open.value = !open.value;\n}\n\nwatch(open, async (isOpen) => {\n if (!isOpen) return;\n syncViewToModel();\n await nextTick();\n await nextTick();\n update();\n syncEditorThemeToPortal(triggerRef.value, popoverRef.value);\n});\n\nonClickOutside(\n popoverRef,\n () => {\n open.value = false;\n },\n { ignore: [triggerRef] },\n);\n\nuseEventListener(window, \"keydown\", (e: KeyboardEvent) => {\n if (e.key === \"Escape\") open.value = false;\n});\n\nuseEventListener(window, \"scroll\", () => open.value && update(), {\n capture: true,\n});\n\nuseEventListener(window, \"resize\", () => open.value && update());\n\nfunction prevMonth(): void {\n if (viewMonth.value === 0) {\n viewMonth.value = 11;\n viewYear.value--;\n } else {\n viewMonth.value--;\n }\n}\n\nfunction nextMonth(): void {\n if (viewMonth.value === 11) {\n viewMonth.value = 0;\n viewYear.value++;\n } else {\n viewMonth.value++;\n }\n}\n\nfunction pick(iso: string): void {\n emit(\"update:modelValue\", iso);\n open.value = false;\n}\n\nfunction clear(): void {\n emit(\"update:modelValue\", \"\");\n open.value = false;\n}\n\nfunction pickToday(): void {\n pick(isoToday());\n}\n</script>\n\n<template>\n <div\n class=\"tpl:flex tpl:items-center tpl:gap-0.5 tpl:rounded-lg tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:pr-1\"\n >\n <button\n :id=\"inputId\"\n ref=\"triggerRef\"\n type=\"button\"\n class=\"tpl:flex tpl:min-w-0 tpl:flex-1 tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:rounded-lg tpl:border-none tpl:bg-transparent tpl:px-3 tpl:py-2.5 tpl:text-left tpl:text-sm tpl:outline-none focus-visible:tpl:ring-2 focus-visible:tpl:ring-[var(--tpl-primary)] focus-visible:tpl:ring-offset-2 focus-visible:tpl:ring-offset-[var(--tpl-bg)]\"\n :aria-expanded=\"open\"\n aria-haspopup=\"dialog\"\n @click=\"toggleOpen\"\n >\n <span\n :class=\"\n modelValue\n ? 'tpl:text-[var(--tpl-text)]'\n : 'tpl:text-[var(--tpl-text-muted)]'\n \"\n >\n {{ modelValue ? formatDisplay(modelValue) : placeholder }}\n </span>\n <CalendarDays\n :size=\"16\"\n :stroke-width=\"1.5\"\n class=\"tpl:ml-auto tpl:shrink-0 tpl:opacity-60\"\n aria-hidden=\"true\"\n />\n </button>\n <button\n v-if=\"modelValue\"\n type=\"button\"\n class=\"tpl:flex tpl:h-8 tpl:w-8 tpl:shrink-0 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-md tpl:border-none tpl:bg-transparent tpl:text-[var(--tpl-text-muted)] hover:tpl:bg-[var(--tpl-bg-hover)] hover:tpl:text-[var(--tpl-text)] focus-visible:tpl:outline focus-visible:tpl:outline-2 focus-visible:tpl:outline-[var(--tpl-primary)]\"\n :aria-label=\"clearLabel\"\n @click=\"clear\"\n >\n ×\n </button>\n </div>\n\n <Teleport to=\"body\">\n <div\n v-if=\"open\"\n ref=\"popoverRef\"\n class=\"tpl:fixed tpl:z-[100] tpl:rounded-lg tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:p-3 tpl:shadow-[var(--tpl-shadow-md)]\"\n :style=\"popoverStyle\"\n role=\"dialog\"\n aria-modal=\"true\"\n :aria-label=\"t.popupSchedule.calendarDialogLabel\"\n >\n <div\n class=\"tpl:mb-3 tpl:flex tpl:items-center tpl:justify-between tpl:gap-2\"\n >\n <button\n type=\"button\"\n class=\"tpl:flex tpl:h-8 tpl:w-8 tpl:shrink-0 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-md tpl:border-none tpl:bg-transparent tpl:text-[var(--tpl-text-muted)] hover:tpl:bg-[var(--tpl-bg-hover)] hover:tpl:text-[var(--tpl-text)] focus-visible:tpl:outline focus-visible:tpl:outline-2 focus-visible:tpl:outline-[var(--tpl-primary)]\"\n :aria-label=\"t.popupSchedule.calendarPrevMonth\"\n @click=\"prevMonth\"\n >\n <ChevronLeft :size=\"18\" :stroke-width=\"1.75\" aria-hidden=\"true\" />\n </button>\n <span\n class=\"tpl:flex-1 tpl:text-center tpl:text-sm tpl:font-medium tpl:text-[var(--tpl-text)]\"\n >\n {{ monthTitle }}\n </span>\n <button\n type=\"button\"\n class=\"tpl:flex tpl:h-8 tpl:w-8 tpl:shrink-0 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-md tpl:border-none tpl:bg-transparent tpl:text-[var(--tpl-text-muted)] hover:tpl:bg-[var(--tpl-bg-hover)] hover:tpl:text-[var(--tpl-text)] focus-visible:tpl:outline focus-visible:tpl:outline-2 focus-visible:tpl:outline-[var(--tpl-primary)]\"\n :aria-label=\"t.popupSchedule.calendarNextMonth\"\n @click=\"nextMonth\"\n >\n <ChevronRight :size=\"18\" :stroke-width=\"1.75\" aria-hidden=\"true\" />\n </button>\n </div>\n\n <div\n class=\"tpl:grid tpl:grid-cols-7 tpl:gap-x-0 tpl:gap-y-1 tpl:text-[11px] tpl:font-medium tpl:uppercase tpl:tracking-wide tpl:text-[var(--tpl-text-muted)]\"\n >\n <span\n v-for=\"(wd, i) in weekdayLabels\"\n :key=\"`wd-${i}`\"\n class=\"tpl:block tpl:text-center\"\n >\n {{ wd }}\n </span>\n </div>\n\n <div class=\"tpl:mt-1 tpl:grid tpl:grid-cols-7 tpl:gap-y-1\">\n <button\n v-for=\"(cell, i) in cells\"\n :key=\"`${cell.iso}-${i}`\"\n type=\"button\"\n class=\"tpl:flex tpl:h-9 tpl:w-full tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-md tpl:border-none tpl:bg-transparent tpl:text-sm tpl:outline-none focus-visible:tpl:ring-2 focus-visible:tpl:ring-[var(--tpl-primary)] focus-visible:tpl:ring-offset-2 focus-visible:tpl:ring-offset-[var(--tpl-bg)]\"\n :class=\"[\n cell.inMonth\n ? 'tpl:text-[var(--tpl-text)]'\n : 'tpl:text-[var(--tpl-text-muted)] tpl:opacity-70',\n modelValue === cell.iso\n ? 'tpl:bg-[var(--tpl-primary)] tpl:font-medium tpl:text-white hover:tpl:bg-[var(--tpl-primary)]'\n : cell.iso === todayStr\n ? 'tpl:ring-1 tpl:ring-[var(--tpl-border)] tpl:ring-inset'\n : 'hover:tpl:bg-[var(--tpl-bg-hover)]',\n ]\"\n @click=\"pick(cell.iso)\"\n >\n {{ cell.day }}\n </button>\n </div>\n\n <div\n class=\"tpl:mt-3 tpl:flex tpl:flex-wrap tpl:items-center tpl:justify-between tpl:gap-2 tpl:border-t tpl:border-[var(--tpl-border)] tpl:pt-3\"\n >\n <button\n type=\"button\"\n class=\"tpl:rounded-md tpl:border-none tpl:bg-transparent tpl:px-2 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-primary)] tpl:underline tpl:underline-offset-2 hover:tpl:bg-[var(--tpl-bg-hover)] focus-visible:tpl:outline focus-visible:tpl:outline-2 focus-visible:tpl:outline-[var(--tpl-primary)]\"\n @click=\"pickToday\"\n >\n {{ t.popupSchedule.calendarToday }}\n </button>\n <button\n v-if=\"modelValue\"\n type=\"button\"\n class=\"tpl:rounded-md tpl:border-none tpl:bg-transparent tpl:px-2 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-text-muted)] hover:tpl:bg-[var(--tpl-bg-hover)] hover:tpl:text-[var(--tpl-text)] focus-visible:tpl:outline focus-visible:tpl:outline-2 focus-visible:tpl:outline-[var(--tpl-primary)]\"\n @click=\"clear\"\n >\n {{ t.popupSchedule.calendarClear }}\n </button>\n </div>\n </div>\n </Teleport>\n</template>\n","<script setup lang=\"ts\">\nimport { CalendarDays, ChevronLeft, ChevronRight } from \"@lucide/vue\";\nimport {\n onClickOutside,\n useElementBounding,\n useEventListener,\n} from \"@vueuse/core\";\nimport { computed, nextTick, ref, watch } from \"vue\";\nimport { useI18n } from \"../composables/useI18n\";\nimport { syncEditorThemeToPortal } from \"../utils/syncEditorThemeToPortal\";\n\nconst props = defineProps<{\n modelValue: string;\n placeholder: string;\n inputId: string;\n clearLabel: string;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update:modelValue\", value: string): void;\n}>();\n\nconst { t } = useI18n();\n\nconst open = ref(false);\nconst triggerRef = ref<HTMLElement | null>(null);\nconst popoverRef = ref<HTMLElement | null>(null);\n\nconst viewYear = ref(new Date().getFullYear());\nconst viewMonth = ref(new Date().getMonth());\n\nconst { bottom, left, width, update } = useElementBounding(triggerRef);\n\nconst popoverStyle = computed(() => ({\n top: `${bottom.value + 4}px`,\n left: `${left.value}px`,\n width: `${Math.max(width.value, 268)}px`,\n}));\n\nfunction pad2(n: number): string {\n return String(n).padStart(2, \"0\");\n}\n\nfunction toISO(y: number, monthIndex: number, day: number): string {\n return `${y}-${pad2(monthIndex + 1)}-${pad2(day)}`;\n}\n\nfunction parseISO(s: string): { y: number; m: number; d: number } | null {\n const m = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(s.trim());\n if (!m) return null;\n const y = Number(m[1]);\n const mo = Number(m[2]) - 1;\n const d = Number(m[3]);\n const dt = new Date(y, mo, d);\n if (dt.getFullYear() !== y || dt.getMonth() !== mo || dt.getDate() !== d) {\n return null;\n }\n return { y, m: mo, d };\n}\n\nfunction isoToday(): string {\n const n = new Date();\n return toISO(n.getFullYear(), n.getMonth(), n.getDate());\n}\n\nfunction buildCalendarCells(y: number, monthIndex: number) {\n type Cell = { iso: string; day: number; inMonth: boolean };\n const cells: Cell[] = [];\n const first = new Date(y, monthIndex, 1);\n const startPad = first.getDay();\n const daysInMonth = new Date(y, monthIndex + 1, 0).getDate();\n\n const prevLast = new Date(y, monthIndex, 0);\n const prevDays = prevLast.getDate();\n const prevM = monthIndex === 0 ? 11 : monthIndex - 1;\n const prevY = monthIndex === 0 ? y - 1 : y;\n\n for (let i = startPad - 1; i >= 0; i--) {\n const day = prevDays - i;\n cells.push({\n iso: toISO(prevY, prevM, day),\n day,\n inMonth: false,\n });\n }\n\n for (let d = 1; d <= daysInMonth; d++) {\n cells.push({\n iso: toISO(y, monthIndex, d),\n day: d,\n inMonth: true,\n });\n }\n\n const nextY = monthIndex === 11 ? y + 1 : y;\n const nextM = monthIndex === 11 ? 0 : monthIndex + 1;\n let nextD = 1;\n while (cells.length % 7 !== 0) {\n cells.push({\n iso: toISO(nextY, nextM, nextD),\n day: nextD++,\n inMonth: false,\n });\n }\n return cells;\n}\n\nconst weekdayLabels = computed(() => {\n const labels: string[] = [];\n const sun = new Date(2024, 0, 7);\n for (let i = 0; i < 7; i++) {\n const d = new Date(sun);\n d.setDate(sun.getDate() + i);\n labels.push(\n new Intl.DateTimeFormat(undefined, { weekday: \"short\" }).format(d),\n );\n }\n return labels;\n});\n\nconst monthTitle = computed(() =>\n new Intl.DateTimeFormat(undefined, {\n month: \"long\",\n year: \"numeric\",\n }).format(new Date(viewYear.value, viewMonth.value, 1)),\n);\n\nconst cells = computed(() =>\n buildCalendarCells(viewYear.value, viewMonth.value),\n);\n\nconst todayStr = computed(() => isoToday());\n\nfunction formatDisplay(iso: string): string {\n const p = parseISO(iso);\n if (!p) return iso;\n return new Intl.DateTimeFormat(undefined, {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n }).format(new Date(p.y, p.m, p.d));\n}\n\nfunction syncViewToModel(): void {\n const p = parseISO(props.modelValue);\n if (p) {\n viewYear.value = p.y;\n viewMonth.value = p.m;\n } else {\n const n = new Date();\n viewYear.value = n.getFullYear();\n viewMonth.value = n.getMonth();\n }\n}\n\nfunction toggleOpen(): void {\n open.value = !open.value;\n}\n\nwatch(open, async (isOpen) => {\n if (!isOpen) return;\n syncViewToModel();\n await nextTick();\n await nextTick();\n update();\n syncEditorThemeToPortal(triggerRef.value, popoverRef.value);\n});\n\nonClickOutside(\n popoverRef,\n () => {\n open.value = false;\n },\n { ignore: [triggerRef] },\n);\n\nuseEventListener(window, \"keydown\", (e: KeyboardEvent) => {\n if (e.key === \"Escape\") open.value = false;\n});\n\nuseEventListener(window, \"scroll\", () => open.value && update(), {\n capture: true,\n});\n\nuseEventListener(window, \"resize\", () => open.value && update());\n\nfunction prevMonth(): void {\n if (viewMonth.value === 0) {\n viewMonth.value = 11;\n viewYear.value--;\n } else {\n viewMonth.value--;\n }\n}\n\nfunction nextMonth(): void {\n if (viewMonth.value === 11) {\n viewMonth.value = 0;\n viewYear.value++;\n } else {\n viewMonth.value++;\n }\n}\n\nfunction pick(iso: string): void {\n emit(\"update:modelValue\", iso);\n open.value = false;\n}\n\nfunction clear(): void {\n emit(\"update:modelValue\", \"\");\n open.value = false;\n}\n\nfunction pickToday(): void {\n pick(isoToday());\n}\n</script>\n\n<template>\n <div\n class=\"tpl:flex tpl:items-center tpl:gap-0.5 tpl:rounded-lg tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:pr-1\"\n >\n <button\n :id=\"inputId\"\n ref=\"triggerRef\"\n type=\"button\"\n class=\"tpl:flex tpl:min-w-0 tpl:flex-1 tpl:cursor-pointer tpl:items-center tpl:gap-2 tpl:rounded-lg tpl:border-none tpl:bg-transparent tpl:px-3 tpl:py-2.5 tpl:text-left tpl:text-sm tpl:outline-none focus-visible:tpl:ring-2 focus-visible:tpl:ring-[var(--tpl-primary)] focus-visible:tpl:ring-offset-2 focus-visible:tpl:ring-offset-[var(--tpl-bg)]\"\n :aria-expanded=\"open\"\n aria-haspopup=\"dialog\"\n @click=\"toggleOpen\"\n >\n <span\n :class=\"\n modelValue\n ? 'tpl:text-[var(--tpl-text)]'\n : 'tpl:text-[var(--tpl-text-muted)]'\n \"\n >\n {{ modelValue ? formatDisplay(modelValue) : placeholder }}\n </span>\n <CalendarDays\n :size=\"16\"\n :stroke-width=\"1.5\"\n class=\"tpl:ml-auto tpl:shrink-0 tpl:opacity-60\"\n aria-hidden=\"true\"\n />\n </button>\n <button\n v-if=\"modelValue\"\n type=\"button\"\n class=\"tpl:flex tpl:h-8 tpl:w-8 tpl:shrink-0 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-md tpl:border-none tpl:bg-transparent tpl:text-[var(--tpl-text-muted)] hover:tpl:bg-[var(--tpl-bg-hover)] hover:tpl:text-[var(--tpl-text)] focus-visible:tpl:outline focus-visible:tpl:outline-2 focus-visible:tpl:outline-[var(--tpl-primary)]\"\n :aria-label=\"clearLabel\"\n @click=\"clear\"\n >\n ×\n </button>\n </div>\n\n <Teleport to=\"body\">\n <div\n v-if=\"open\"\n ref=\"popoverRef\"\n class=\"tpl:fixed tpl:z-[100] tpl:rounded-lg tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:p-3 tpl:shadow-[var(--tpl-shadow-md)]\"\n :style=\"popoverStyle\"\n role=\"dialog\"\n aria-modal=\"true\"\n :aria-label=\"t.popupSchedule.calendarDialogLabel\"\n >\n <div\n class=\"tpl:mb-3 tpl:flex tpl:items-center tpl:justify-between tpl:gap-2\"\n >\n <button\n type=\"button\"\n class=\"tpl:flex tpl:h-8 tpl:w-8 tpl:shrink-0 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-md tpl:border-none tpl:bg-transparent tpl:text-[var(--tpl-text-muted)] hover:tpl:bg-[var(--tpl-bg-hover)] hover:tpl:text-[var(--tpl-text)] focus-visible:tpl:outline focus-visible:tpl:outline-2 focus-visible:tpl:outline-[var(--tpl-primary)]\"\n :aria-label=\"t.popupSchedule.calendarPrevMonth\"\n @click=\"prevMonth\"\n >\n <ChevronLeft :size=\"18\" :stroke-width=\"1.75\" aria-hidden=\"true\" />\n </button>\n <span\n class=\"tpl:flex-1 tpl:text-center tpl:text-sm tpl:font-medium tpl:text-[var(--tpl-text)]\"\n >\n {{ monthTitle }}\n </span>\n <button\n type=\"button\"\n class=\"tpl:flex tpl:h-8 tpl:w-8 tpl:shrink-0 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-md tpl:border-none tpl:bg-transparent tpl:text-[var(--tpl-text-muted)] hover:tpl:bg-[var(--tpl-bg-hover)] hover:tpl:text-[var(--tpl-text)] focus-visible:tpl:outline focus-visible:tpl:outline-2 focus-visible:tpl:outline-[var(--tpl-primary)]\"\n :aria-label=\"t.popupSchedule.calendarNextMonth\"\n @click=\"nextMonth\"\n >\n <ChevronRight :size=\"18\" :stroke-width=\"1.75\" aria-hidden=\"true\" />\n </button>\n </div>\n\n <div\n class=\"tpl:grid tpl:grid-cols-7 tpl:gap-x-0 tpl:gap-y-1 tpl:text-[11px] tpl:font-medium tpl:uppercase tpl:tracking-wide tpl:text-[var(--tpl-text-muted)]\"\n >\n <span\n v-for=\"(wd, i) in weekdayLabels\"\n :key=\"`wd-${i}`\"\n class=\"tpl:block tpl:text-center\"\n >\n {{ wd }}\n </span>\n </div>\n\n <div class=\"tpl:mt-1 tpl:grid tpl:grid-cols-7 tpl:gap-y-1\">\n <button\n v-for=\"(cell, i) in cells\"\n :key=\"`${cell.iso}-${i}`\"\n type=\"button\"\n class=\"tpl:flex tpl:h-9 tpl:w-full tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-md tpl:border-none tpl:bg-transparent tpl:text-sm tpl:outline-none focus-visible:tpl:ring-2 focus-visible:tpl:ring-[var(--tpl-primary)] focus-visible:tpl:ring-offset-2 focus-visible:tpl:ring-offset-[var(--tpl-bg)]\"\n :class=\"[\n cell.inMonth\n ? 'tpl:text-[var(--tpl-text)]'\n : 'tpl:text-[var(--tpl-text-muted)] tpl:opacity-70',\n modelValue === cell.iso\n ? 'tpl:bg-[var(--tpl-primary)] tpl:font-medium tpl:text-white hover:tpl:bg-[var(--tpl-primary)]'\n : cell.iso === todayStr\n ? 'tpl:ring-1 tpl:ring-[var(--tpl-border)] tpl:ring-inset'\n : 'hover:tpl:bg-[var(--tpl-bg-hover)]',\n ]\"\n @click=\"pick(cell.iso)\"\n >\n {{ cell.day }}\n </button>\n </div>\n\n <div\n class=\"tpl:mt-3 tpl:flex tpl:flex-wrap tpl:items-center tpl:justify-between tpl:gap-2 tpl:border-t tpl:border-[var(--tpl-border)] tpl:pt-3\"\n >\n <button\n type=\"button\"\n class=\"tpl:rounded-md tpl:border-none tpl:bg-transparent tpl:px-2 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-primary)] tpl:underline tpl:underline-offset-2 hover:tpl:bg-[var(--tpl-bg-hover)] focus-visible:tpl:outline focus-visible:tpl:outline-2 focus-visible:tpl:outline-[var(--tpl-primary)]\"\n @click=\"pickToday\"\n >\n {{ t.popupSchedule.calendarToday }}\n </button>\n <button\n v-if=\"modelValue\"\n type=\"button\"\n class=\"tpl:rounded-md tpl:border-none tpl:bg-transparent tpl:px-2 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-text-muted)] hover:tpl:bg-[var(--tpl-bg-hover)] hover:tpl:text-[var(--tpl-text)] focus-visible:tpl:outline focus-visible:tpl:outline-2 focus-visible:tpl:outline-[var(--tpl-primary)]\"\n @click=\"clear\"\n >\n {{ t.popupSchedule.calendarClear }}\n </button>\n </div>\n </div>\n </Teleport>\n</template>\n","<script setup lang=\"ts\">\nimport { Trash2 } from \"@lucide/vue\";\nimport { computed, inject } from \"vue\";\nimport type {\n PopupEmbedSettings,\n PopupScheduleDayPreset,\n PopupScheduleSlot,\n} from \"@aswin.dev/types\";\nimport { POPUP_SCHEDULE_TIMEZONES } from \"@aswin.dev/types\";\nimport { useI18n } from \"../composables/useI18n\";\nimport { EDITOR_KEY } from \"../keys\";\nimport type { BaseEditorReturn } from \"../composables/useEditorCore\";\nimport { resolvePopupEmbed } from \"../utils/resolvePopupEmbed\";\nimport PopupDesignSwitch from \"./PopupDesignSwitch.vue\";\nimport PopupScheduleDatePicker from \"./PopupScheduleDatePicker.vue\";\n\nconst props = withDefaults(\n defineProps<{ layout?: \"standalone\" | \"popupAdjacent\" }>(),\n { layout: \"standalone\" },\n);\n\nconst editor = inject(EDITOR_KEY) as BaseEditorReturn | null;\nif (!editor) {\n throw new Error(\"PopupScheduleView requires EDITOR_KEY\");\n}\n\nconst { t } = useI18n();\n\nconst rootClass = computed(() =>\n props.layout === \"popupAdjacent\"\n ? \"tpl-popup-schedule tpl:w-full tpl:overflow-x-hidden tpl:px-4 tpl:pb-6 tpl:pt-4 tpl:bg-[var(--tpl-bg)]\"\n : \"tpl-popup-schedule tpl:mx-auto tpl:w-full tpl:max-w-3xl tpl:px-6 tpl:py-8 tpl:pb-14 tpl:bg-[var(--tpl-bg)]\",\n);\n\nconst popup = computed(() => resolvePopupEmbed(editor!.content.value.settings));\n\nconst selectClass =\n \"tpl:w-full tpl:min-w-0 tpl:rounded-lg tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:px-3 tpl:py-2.5 tpl:text-sm tpl:text-[var(--tpl-text)] tpl:outline-none focus-visible:tpl:ring-2 focus-visible:tpl:ring-[var(--tpl-primary)] focus-visible:tpl:ring-offset-2 focus-visible:tpl:ring-offset-[var(--tpl-bg)]\";\n\nconst labelClass =\n \"tpl:block tpl:mb-1.5 tpl:text-[13px] tpl:leading-snug tpl:text-[var(--tpl-text-muted)]\";\n\nfunction patchSchedule(patch: Partial<PopupEmbedSettings[\"schedule\"]>): void {\n const cur = popup.value;\n const schedule = { ...cur.schedule, ...patch };\n editor!.updateSettings({ popup: { ...cur, schedule } } as Parameters<\n BaseEditorReturn[\"updateSettings\"]\n >[0]);\n}\n\nfunction replaceSlots(slots: PopupScheduleSlot[]): void {\n patchSchedule({ slots });\n}\n\nfunction updateSlot(id: string, patch: Partial<PopupScheduleSlot>): void {\n replaceSlots(\n popup.value.schedule.slots.map((s) =>\n s.id === id ? { ...s, ...patch } : s,\n ),\n );\n}\n\nfunction removeSlot(id: string): void {\n replaceSlots(popup.value.schedule.slots.filter((s) => s.id !== id));\n}\n\nfunction addSlot(): void {\n const id =\n globalThis.crypto?.randomUUID?.() ?? `slot-${Date.now().toString(36)}`;\n replaceSlots([\n ...popup.value.schedule.slots,\n {\n id,\n dayPreset: \"everyday\",\n startTime: \"00:00\",\n endTime: \"23:59\",\n },\n ]);\n}\n\nconst quarterHourTimes = computed(() => {\n const out: string[] = [];\n for (let h = 0; h < 24; h++) {\n for (const m of [0, 15, 30, 45]) {\n out.push(`${String(h).padStart(2, \"0\")}:${String(m).padStart(2, \"0\")}`);\n }\n }\n if (!out.includes(\"23:59\")) out.push(\"23:59\");\n return out;\n});\n\nconst timezoneChoices = computed(() => {\n const cur = popup.value.schedule.timeZone;\n const known = POPUP_SCHEDULE_TIMEZONES.some((z) => z.id === cur);\n if (known) return [...POPUP_SCHEDULE_TIMEZONES];\n return [{ id: cur, label: cur }, ...POPUP_SCHEDULE_TIMEZONES];\n});\n\nconst dayOptions = computed(() =>\n (\n [\n \"everyday\",\n \"monday\",\n \"tuesday\",\n \"wednesday\",\n \"thursday\",\n \"friday\",\n \"saturday\",\n \"sunday\",\n ] as PopupScheduleDayPreset[]\n ).map((id) => ({\n id,\n label:\n t.popupSchedule.dayLabels[id as keyof typeof t.popupSchedule.dayLabels],\n })),\n);\n\nfunction onTimezoneChange(e: Event): void {\n patchSchedule({\n timeZone: (e.target as HTMLSelectElement).value,\n });\n}\n\nfunction onSlotDayChange(id: string, e: Event): void {\n updateSlot(id, {\n dayPreset: (e.target as HTMLSelectElement).value as PopupScheduleDayPreset,\n });\n}\n\nfunction onSlotStartChange(id: string, e: Event): void {\n updateSlot(id, { startTime: (e.target as HTMLSelectElement).value });\n}\n\nfunction onSlotEndChange(id: string, e: Event): void {\n updateSlot(id, { endTime: (e.target as HTMLSelectElement).value });\n}\n</script>\n\n<template>\n <div :class=\"rootClass\">\n <div class=\"tpl:mb-5\">\n <label class=\"tpl:sr-only\" for=\"popup-schedule-timezone\">{{\n t.popupSchedule.timeZone\n }}</label>\n <span :class=\"labelClass\">{{ t.popupSchedule.timeZone }}</span>\n <select\n id=\"popup-schedule-timezone\"\n :class=\"selectClass\"\n :value=\"popup.schedule.timeZone\"\n @change=\"onTimezoneChange\"\n >\n <option v-for=\"z in timezoneChoices\" :key=\"z.id\" :value=\"z.id\">\n {{ z.label }}\n </option>\n </select>\n </div>\n\n <div class=\"tpl:mb-3 tpl:grid tpl:grid-cols-2 tpl:gap-3\">\n <div>\n <label class=\"tpl:sr-only\" for=\"popup-schedule-start-date\">{{\n t.popupSchedule.startDate\n }}</label>\n <span :class=\"labelClass\">{{ t.popupSchedule.startDate }}</span>\n <PopupScheduleDatePicker\n input-id=\"popup-schedule-start-date\"\n :model-value=\"popup.schedule.startDate\"\n :placeholder=\"t.popupSchedule.startImmediate\"\n :clear-label=\"t.popupSchedule.clearStartDate\"\n @update:model-value=\"patchSchedule({ startDate: $event })\"\n />\n </div>\n <div>\n <label class=\"tpl:sr-only\" for=\"popup-schedule-end-date\">{{\n t.popupSchedule.endDate\n }}</label>\n <span :class=\"labelClass\">{{ t.popupSchedule.endDate }}</span>\n <PopupScheduleDatePicker\n input-id=\"popup-schedule-end-date\"\n :model-value=\"popup.schedule.endDate\"\n :placeholder=\"t.popupSchedule.endNone\"\n :clear-label=\"t.popupSchedule.clearEndDate\"\n @update:model-value=\"patchSchedule({ endDate: $event })\"\n />\n </div>\n </div>\n\n <a\n href=\"https://docs.templatical.com\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"tpl:mb-6 tpl:inline-block tpl:text-[13px] tpl:underline tpl:text-[var(--tpl-text-muted)] hover:tpl:text-[var(--tpl-text)]\"\n >\n {{ t.popupSchedule.needHelp }}\n </a>\n\n <div class=\"tpl:mb-5 tpl:flex tpl:items-center tpl:gap-3\">\n <PopupDesignSwitch\n tone=\"neutral\"\n :checked=\"popup.schedule.detailedScheduleEnabled\"\n @toggle=\"\n patchSchedule({\n detailedScheduleEnabled: !popup.schedule.detailedScheduleEnabled,\n })\n \"\n />\n <span class=\"tpl:text-sm tpl:leading-snug tpl:text-[var(--tpl-text)]\">{{\n t.popupSchedule.detailedSchedule\n }}</span>\n </div>\n\n <template v-if=\"popup.schedule.detailedScheduleEnabled\">\n <p :class=\"`${labelClass} tpl:mb-2 tpl:mt-1`\">\n {{ t.popupSchedule.detailedScheduleHeading }}\n </p>\n\n <div class=\"tpl:space-y-2 tpl:mb-4\">\n <div\n v-for=\"slot in popup.schedule.slots\"\n :key=\"slot.id\"\n class=\"tpl:grid tpl:grid-cols-[auto_minmax(0,1fr)_4.5rem_4.5rem] tpl:items-center tpl:gap-2\"\n >\n <button\n type=\"button\"\n class=\"tpl:flex tpl:h-9 tpl:w-[26px] tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-md tpl:border-none tpl:bg-transparent tpl:text-[var(--tpl-text-muted)] hover:tpl:bg-[var(--tpl-bg-hover)] hover:tpl:text-[var(--tpl-text)] focus-visible:tpl:outline focus-visible:tpl:outline-2 focus-visible:tpl:outline-[var(--tpl-primary)]\"\n :aria-label=\"t.popupSchedule.removeSlot\"\n @click=\"removeSlot(slot.id)\"\n >\n <Trash2 :size=\"16\" :stroke-width=\"1.75\" aria-hidden=\"true\" />\n </button>\n <select\n :class=\"selectClass\"\n :value=\"slot.dayPreset\"\n @change=\"onSlotDayChange(slot.id, $event)\"\n >\n <option v-for=\"opt in dayOptions\" :key=\"opt.id\" :value=\"opt.id\">\n {{ opt.label }}\n </option>\n </select>\n <select\n :class=\"selectClass\"\n :value=\"slot.startTime\"\n @change=\"onSlotStartChange(slot.id, $event)\"\n >\n <option\n v-for=\"tm in quarterHourTimes\"\n :key=\"`s-${slot.id}-${tm}`\"\n :value=\"tm\"\n >\n {{ tm }}\n </option>\n </select>\n <select\n :class=\"selectClass\"\n :value=\"slot.endTime\"\n @change=\"onSlotEndChange(slot.id, $event)\"\n >\n <option\n v-for=\"tm in quarterHourTimes\"\n :key=\"`e-${slot.id}-${tm}`\"\n :value=\"tm\"\n >\n {{ tm }}\n </option>\n </select>\n </div>\n </div>\n\n <button\n type=\"button\"\n class=\"tpl:inline-flex tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-lg tpl:border tpl:border-[var(--tpl-text)] tpl:bg-transparent tpl:px-4 tpl:py-2.5 tpl:text-sm tpl:font-medium tpl:text-[var(--tpl-text)] hover:tpl:bg-[var(--tpl-bg-hover)] focus-visible:tpl:outline focus-visible:tpl:outline-2 focus-visible:tpl:outline-offset-2 focus-visible:tpl:outline-[var(--tpl-primary)]\"\n @click=\"addSlot\"\n >\n {{ t.popupSchedule.addTimeSlot }}\n </button>\n </template>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { Trash2 } from \"@lucide/vue\";\nimport { computed, inject } from \"vue\";\nimport type {\n PopupEmbedSettings,\n PopupScheduleDayPreset,\n PopupScheduleSlot,\n} from \"@aswin.dev/types\";\nimport { POPUP_SCHEDULE_TIMEZONES } from \"@aswin.dev/types\";\nimport { useI18n } from \"../composables/useI18n\";\nimport { EDITOR_KEY } from \"../keys\";\nimport type { BaseEditorReturn } from \"../composables/useEditorCore\";\nimport { resolvePopupEmbed } from \"../utils/resolvePopupEmbed\";\nimport PopupDesignSwitch from \"./PopupDesignSwitch.vue\";\nimport PopupScheduleDatePicker from \"./PopupScheduleDatePicker.vue\";\n\nconst props = withDefaults(\n defineProps<{ layout?: \"standalone\" | \"popupAdjacent\" }>(),\n { layout: \"standalone\" },\n);\n\nconst editor = inject(EDITOR_KEY) as BaseEditorReturn | null;\nif (!editor) {\n throw new Error(\"PopupScheduleView requires EDITOR_KEY\");\n}\n\nconst { t } = useI18n();\n\nconst rootClass = computed(() =>\n props.layout === \"popupAdjacent\"\n ? \"tpl-popup-schedule tpl:w-full tpl:overflow-x-hidden tpl:px-4 tpl:pb-6 tpl:pt-4 tpl:bg-[var(--tpl-bg)]\"\n : \"tpl-popup-schedule tpl:mx-auto tpl:w-full tpl:max-w-3xl tpl:px-6 tpl:py-8 tpl:pb-14 tpl:bg-[var(--tpl-bg)]\",\n);\n\nconst popup = computed(() => resolvePopupEmbed(editor!.content.value.settings));\n\nconst selectClass =\n \"tpl:w-full tpl:min-w-0 tpl:rounded-lg tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:px-3 tpl:py-2.5 tpl:text-sm tpl:text-[var(--tpl-text)] tpl:outline-none focus-visible:tpl:ring-2 focus-visible:tpl:ring-[var(--tpl-primary)] focus-visible:tpl:ring-offset-2 focus-visible:tpl:ring-offset-[var(--tpl-bg)]\";\n\nconst labelClass =\n \"tpl:block tpl:mb-1.5 tpl:text-[13px] tpl:leading-snug tpl:text-[var(--tpl-text-muted)]\";\n\nfunction patchSchedule(patch: Partial<PopupEmbedSettings[\"schedule\"]>): void {\n const cur = popup.value;\n const schedule = { ...cur.schedule, ...patch };\n editor!.updateSettings({ popup: { ...cur, schedule } } as Parameters<\n BaseEditorReturn[\"updateSettings\"]\n >[0]);\n}\n\nfunction replaceSlots(slots: PopupScheduleSlot[]): void {\n patchSchedule({ slots });\n}\n\nfunction updateSlot(id: string, patch: Partial<PopupScheduleSlot>): void {\n replaceSlots(\n popup.value.schedule.slots.map((s) =>\n s.id === id ? { ...s, ...patch } : s,\n ),\n );\n}\n\nfunction removeSlot(id: string): void {\n replaceSlots(popup.value.schedule.slots.filter((s) => s.id !== id));\n}\n\nfunction addSlot(): void {\n const id =\n globalThis.crypto?.randomUUID?.() ?? `slot-${Date.now().toString(36)}`;\n replaceSlots([\n ...popup.value.schedule.slots,\n {\n id,\n dayPreset: \"everyday\",\n startTime: \"00:00\",\n endTime: \"23:59\",\n },\n ]);\n}\n\nconst quarterHourTimes = computed(() => {\n const out: string[] = [];\n for (let h = 0; h < 24; h++) {\n for (const m of [0, 15, 30, 45]) {\n out.push(`${String(h).padStart(2, \"0\")}:${String(m).padStart(2, \"0\")}`);\n }\n }\n if (!out.includes(\"23:59\")) out.push(\"23:59\");\n return out;\n});\n\nconst timezoneChoices = computed(() => {\n const cur = popup.value.schedule.timeZone;\n const known = POPUP_SCHEDULE_TIMEZONES.some((z) => z.id === cur);\n if (known) return [...POPUP_SCHEDULE_TIMEZONES];\n return [{ id: cur, label: cur }, ...POPUP_SCHEDULE_TIMEZONES];\n});\n\nconst dayOptions = computed(() =>\n (\n [\n \"everyday\",\n \"monday\",\n \"tuesday\",\n \"wednesday\",\n \"thursday\",\n \"friday\",\n \"saturday\",\n \"sunday\",\n ] as PopupScheduleDayPreset[]\n ).map((id) => ({\n id,\n label:\n t.popupSchedule.dayLabels[id as keyof typeof t.popupSchedule.dayLabels],\n })),\n);\n\nfunction onTimezoneChange(e: Event): void {\n patchSchedule({\n timeZone: (e.target as HTMLSelectElement).value,\n });\n}\n\nfunction onSlotDayChange(id: string, e: Event): void {\n updateSlot(id, {\n dayPreset: (e.target as HTMLSelectElement).value as PopupScheduleDayPreset,\n });\n}\n\nfunction onSlotStartChange(id: string, e: Event): void {\n updateSlot(id, { startTime: (e.target as HTMLSelectElement).value });\n}\n\nfunction onSlotEndChange(id: string, e: Event): void {\n updateSlot(id, { endTime: (e.target as HTMLSelectElement).value });\n}\n</script>\n\n<template>\n <div :class=\"rootClass\">\n <div class=\"tpl:mb-5\">\n <label class=\"tpl:sr-only\" for=\"popup-schedule-timezone\">{{\n t.popupSchedule.timeZone\n }}</label>\n <span :class=\"labelClass\">{{ t.popupSchedule.timeZone }}</span>\n <select\n id=\"popup-schedule-timezone\"\n :class=\"selectClass\"\n :value=\"popup.schedule.timeZone\"\n @change=\"onTimezoneChange\"\n >\n <option v-for=\"z in timezoneChoices\" :key=\"z.id\" :value=\"z.id\">\n {{ z.label }}\n </option>\n </select>\n </div>\n\n <div class=\"tpl:mb-3 tpl:grid tpl:grid-cols-2 tpl:gap-3\">\n <div>\n <label class=\"tpl:sr-only\" for=\"popup-schedule-start-date\">{{\n t.popupSchedule.startDate\n }}</label>\n <span :class=\"labelClass\">{{ t.popupSchedule.startDate }}</span>\n <PopupScheduleDatePicker\n input-id=\"popup-schedule-start-date\"\n :model-value=\"popup.schedule.startDate\"\n :placeholder=\"t.popupSchedule.startImmediate\"\n :clear-label=\"t.popupSchedule.clearStartDate\"\n @update:model-value=\"patchSchedule({ startDate: $event })\"\n />\n </div>\n <div>\n <label class=\"tpl:sr-only\" for=\"popup-schedule-end-date\">{{\n t.popupSchedule.endDate\n }}</label>\n <span :class=\"labelClass\">{{ t.popupSchedule.endDate }}</span>\n <PopupScheduleDatePicker\n input-id=\"popup-schedule-end-date\"\n :model-value=\"popup.schedule.endDate\"\n :placeholder=\"t.popupSchedule.endNone\"\n :clear-label=\"t.popupSchedule.clearEndDate\"\n @update:model-value=\"patchSchedule({ endDate: $event })\"\n />\n </div>\n </div>\n\n <a\n href=\"https://docs.templatical.com\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"tpl:mb-6 tpl:inline-block tpl:text-[13px] tpl:underline tpl:text-[var(--tpl-text-muted)] hover:tpl:text-[var(--tpl-text)]\"\n >\n {{ t.popupSchedule.needHelp }}\n </a>\n\n <div class=\"tpl:mb-5 tpl:flex tpl:items-center tpl:gap-3\">\n <PopupDesignSwitch\n tone=\"neutral\"\n :checked=\"popup.schedule.detailedScheduleEnabled\"\n @toggle=\"\n patchSchedule({\n detailedScheduleEnabled: !popup.schedule.detailedScheduleEnabled,\n })\n \"\n />\n <span class=\"tpl:text-sm tpl:leading-snug tpl:text-[var(--tpl-text)]\">{{\n t.popupSchedule.detailedSchedule\n }}</span>\n </div>\n\n <template v-if=\"popup.schedule.detailedScheduleEnabled\">\n <p :class=\"`${labelClass} tpl:mb-2 tpl:mt-1`\">\n {{ t.popupSchedule.detailedScheduleHeading }}\n </p>\n\n <div class=\"tpl:space-y-2 tpl:mb-4\">\n <div\n v-for=\"slot in popup.schedule.slots\"\n :key=\"slot.id\"\n class=\"tpl:grid tpl:grid-cols-[auto_minmax(0,1fr)_4.5rem_4.5rem] tpl:items-center tpl:gap-2\"\n >\n <button\n type=\"button\"\n class=\"tpl:flex tpl:h-9 tpl:w-[26px] tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-md tpl:border-none tpl:bg-transparent tpl:text-[var(--tpl-text-muted)] hover:tpl:bg-[var(--tpl-bg-hover)] hover:tpl:text-[var(--tpl-text)] focus-visible:tpl:outline focus-visible:tpl:outline-2 focus-visible:tpl:outline-[var(--tpl-primary)]\"\n :aria-label=\"t.popupSchedule.removeSlot\"\n @click=\"removeSlot(slot.id)\"\n >\n <Trash2 :size=\"16\" :stroke-width=\"1.75\" aria-hidden=\"true\" />\n </button>\n <select\n :class=\"selectClass\"\n :value=\"slot.dayPreset\"\n @change=\"onSlotDayChange(slot.id, $event)\"\n >\n <option v-for=\"opt in dayOptions\" :key=\"opt.id\" :value=\"opt.id\">\n {{ opt.label }}\n </option>\n </select>\n <select\n :class=\"selectClass\"\n :value=\"slot.startTime\"\n @change=\"onSlotStartChange(slot.id, $event)\"\n >\n <option\n v-for=\"tm in quarterHourTimes\"\n :key=\"`s-${slot.id}-${tm}`\"\n :value=\"tm\"\n >\n {{ tm }}\n </option>\n </select>\n <select\n :class=\"selectClass\"\n :value=\"slot.endTime\"\n @change=\"onSlotEndChange(slot.id, $event)\"\n >\n <option\n v-for=\"tm in quarterHourTimes\"\n :key=\"`e-${slot.id}-${tm}`\"\n :value=\"tm\"\n >\n {{ tm }}\n </option>\n </select>\n </div>\n </div>\n\n <button\n type=\"button\"\n class=\"tpl:inline-flex tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-lg tpl:border tpl:border-[var(--tpl-text)] tpl:bg-transparent tpl:px-4 tpl:py-2.5 tpl:text-sm tpl:font-medium tpl:text-[var(--tpl-text)] hover:tpl:bg-[var(--tpl-bg-hover)] focus-visible:tpl:outline focus-visible:tpl:outline-2 focus-visible:tpl:outline-offset-2 focus-visible:tpl:outline-[var(--tpl-primary)]\"\n @click=\"addSlot\"\n >\n {{ t.popupSchedule.addTimeSlot }}\n </button>\n </template>\n </div>\n</template>\n"],"mappings":"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"}
|