首页 需求分析 界面布局 图标获取 网页编辑 关于

创意无限

AI创意无限

精准的,用AI还原创意

全新AI创意工作流,从需求分析到界面落地,让每一个创意都被精准还原

在创意的世界里,每一个想法都值得被精准还原。AI创意无限平台重新定义了从灵感到产品的旅程——无需学习复杂的设计工具,无需编写一行代码,只需用最自然的方式表达你的想法。需求分析技能深入理解产品愿景,将模糊的概念转化为结构化的界面需求文档,自动梳理页面层级、用户角色、功能模块与交互逻辑,让产品经理和设计师站在同一起跑线上高效协作。文本无限是创意流程的核心引擎,用纯文本描述即可生成专业的界面线框图,从经典的后台管理布局到沉浸式的移动端落地页,从电商产品展示到数据可视化看板,只需几行文字就能勾勒出完整的界面框架,让创意在最短的时间内可视化。AI智能体是连接设计与实现的桥梁,它读取需求文档和线框图,自动生成高保真页面,从色彩搭配到字体排版,从交互动效到响应式适配,每一个细节都经过精心打磨,一键输出可预览、可交付的完整页面。图像无限让AI生成精准匹配的视觉素材,无论是产品配图还是品牌视觉,都能与界面风格完美融合,省去漫长的设计沟通周期。页面流程设计确保用户体验的完整性,用文本描述页面跳转逻辑和操作流程,自动生成清晰的流程图,标注每一个入口、出口和关键交互节点。这不是简单的工具堆砌,而是一套完整的AI创意工作流。从需求分析到布局绘制,从页面生成到流程设计,从视觉素材到交互实现,每一步都精准可控,每一步都环环相扣。让创意不再受限于技术壁垒,让每一个想法都能被精准地还原为现实,让设计师、产品经理和开发者真正站在同一个创意起点上。在创意的世界里,每一个想法都值得被精准还原。AI创意无限平台重新定义了从灵感到产品的旅程——无需学习复杂的设计工具,无需编写一行代码,只需用最自然的方式表达你的想法。需求分析技能深入理解产品愿景,将模糊的概念转化为结构化的界面需求文档,自动梳理页面层级、用户角色、功能模块与交互逻辑,让产品经理和设计师站在同一起跑线上高效协作。文本无限是创意流程的核心引擎,用纯文本描述即可生成专业的界面线框图,从经典的后台管理布局到沉浸式的移动端落地页,从电商产品展示到数据可视化看板,只需几行文字就能勾勒出完整的界面框架,让创意在最短的时间内可视化。AI智能体是连接设计与实现的桥梁,它读取需求文档和线框图,自动生成高保真页面,从色彩搭配到字体排版,从交互动效到响应式适配,每一个细节都经过精心打磨,一键输出可预览、可交付的完整页面。图像无限让AI生成精准匹配的视觉素材,无论是产品配图还是品牌视觉,都能与界面风格完美融合,省去漫长的设计沟通周期。页面流程设计确保用户体验的完整性,用文本描述页面跳转逻辑和操作流程,自动生成清晰的流程图,标注每一个入口、出口和关键交互节点。这不是简单的工具堆砌,而是一套完整的AI创意工作流。从需求分析到布局绘制,从页面生成到流程设计,从视觉素材到交互实现,每一步都精准可控,每一步都环环相扣。让创意不再受限于技术壁垒,让每一个想法都能被精准地还原为现实,让设计师、产品经理和开发者真正站在同一个创意起点上。
在创意的世界里,每一个想法都值得被精准还原。AI创意无限平台重新定义了从灵感到产品的旅程——无需学习复杂的设计工具,无需编写一行代码,只需用最自然的方式表达你的想法。需求分析技能深入理解产品愿景,将模糊的概念转化为结构化的界面需求文档,自动梳理页面层级、用户角色、功能模块与交互逻辑,让产品经理和设计师站在同一起跑线上高效协作。文本无限是创意流程的核心引擎,用纯文本描述即可生成专业的界面线框图,从经典的后台管理布局到沉浸式的移动端落地页,从电商产品展示到数据可视化看板,只需几行文字就能勾勒出完整的界面框架,让创意在最短的时间内可视化。AI智能体是连接设计与实现的桥梁,它读取需求文档和线框图,自动生成高保真页面,从色彩搭配到字体排版,从交互动效到响应式适配,每一个细节都经过精心打磨,一键输出可预览、可交付的完整页面。图像无限让AI生成精准匹配的视觉素材,无论是产品配图还是品牌视觉,都能与界面风格完美融合,省去漫长的设计沟通周期。页面流程设计确保用户体验的完整性,用文本描述页面跳转逻辑和操作流程,自动生成清晰的流程图,标注每一个入口、出口和关键交互节点。这不是简单的工具堆砌,而是一套完整的AI创意工作流。从需求分析到布局绘制,从页面生成到流程设计,从视觉素材到交互实现,每一步都精准可控,每一步都环环相扣。让创意不再受限于技术壁垒,让每一个想法都能被精准地还原为现实,让设计师、产品经理和开发者真正站在同一个创意起点上。在创意的世界里,每一个想法都值得被精准还原。AI创意无限平台重新定义了从灵感到产品的旅程——无需学习复杂的设计工具,无需编写一行代码,只需用最自然的方式表达你的想法。需求分析技能深入理解产品愿景,将模糊的概念转化为结构化的界面需求文档,自动梳理页面层级、用户角色、功能模块与交互逻辑,让产品经理和设计师站在同一起跑线上高效协作。文本无限是创意流程的核心引擎,用纯文本描述即可生成专业的界面线框图,从经典的后台管理布局到沉浸式的移动端落地页,从电商产品展示到数据可视化看板,只需几行文字就能勾勒出完整的界面框架,让创意在最短的时间内可视化。AI智能体是连接设计与实现的桥梁,它读取需求文档和线框图,自动生成高保真页面,从色彩搭配到字体排版,从交互动效到响应式适配,每一个细节都经过精心打磨,一键输出可预览、可交付的完整页面。图像无限让AI生成精准匹配的视觉素材,无论是产品配图还是品牌视觉,都能与界面风格完美融合,省去漫长的设计沟通周期。页面流程设计确保用户体验的完整性,用文本描述页面跳转逻辑和操作流程,自动生成清晰的流程图,标注每一个入口、出口和关键交互节点。这不是简单的工具堆砌,而是一套完整的AI创意工作流。从需求分析到布局绘制,从页面生成到流程设计,从视觉素材到交互实现,每一步都精准可控,每一步都环环相扣。让创意不再受限于技术壁垒,让每一个想法都能被精准地还原为现实,让设计师、产品经理和开发者真正站在同一个创意起点上。

全新的AI创意流程

六大环节,环环相扣,让AI精准理解你的每一个创意需求

需求分析
需求分析
绘制布局
文本无限
生成页面
AI智能体
页面流程
文本无限
生成交互
AI智能体
编辑页面
网页编辑器
01

需求分析

需求分析技能

将模糊的产品想法转化为结构化的界面需求。通过专业的需求分析技能,AI能够理解你的业务逻辑,自动梳理页面结构、交互流程和功能模块,输出设计师可以直接使用的需求文档。

02

绘制布局

文本无限

使用文本描述即可快速生成界面线框图。无需学习任何设计工具,只需用自然语言描述你想要的布局,文本无限就能将其转化为专业的框架图,让创意以最直观的方式呈现。

03

生成页面

AI智能体

基于框架图和需求文档,AI智能体自动生成高保真页面。从布局结构到视觉细节,从静态展示到基础交互,一键生成可预览的完整页面,大幅缩短从设计到原型的周期。

04

页面流程设计

文本无限

用文本描述页面之间的跳转逻辑和用户操作流程。文本无限将你的流程描述转化为清晰的流程图,标注每个页面的入口、出口和关键交互节点,确保用户体验的完整性。

05

生成可交互页面

AI智能体

将静态页面升级为完整的可交互原型。AI智能体根据流程图自动添加页面跳转、表单交互、动画效果等,生成真正可以点击操作的高保真原型,用于演示和用户测试。

06

编辑页面

网页编辑器

使用可视化网页编辑器对生成的页面进行精细调整。修改文案、调整样式、替换图片、优化布局,所见即所得的编辑体验让非技术人员也能轻松完成页面的最终调优。

效果演示

1 从口语需求到结构化界面文档
2 文本绘制线框图全流程
3 AI一键生成可交互原型
4 网页编辑器精细调优

开始你的AI创意之旅

免费体验全套AI创意工具,让每一个想法都能被精准实现

界面需求分析

把口语化的想法,变成结构化的界面需求文档

无需专业的产品知识,只需用日常语言描述你想要的界面,AI就能自动梳理页面结构、功能模块和交互逻辑,输出设计师可以直接使用的需求文档。让需求传达更精准,减少沟通成本。

界面需求分析 · 效果预览

使用示例

"我想做一个宠物社交App,用户可以在上面晒自己的宠物,还能给别人的宠物点赞和评论,最好有个地图功能可以看到附近的宠物。"

界面需求分析报告

  • 首页Feed流 — 瀑布流卡片布局,每张卡片包含宠物图片/视频、宠物信息、点赞与评论入口
  • 发布页 — 支持多图/视频上传,文本编辑,宠物标签选择,位置定位
  • 附近宠物 — 地图视图 + 列表视图切换,基于LBS展示周边宠物动态
  • 宠物主页 — 宠物档案、成长记录、粉丝关注列表
  • 消息中心 — 点赞通知、评论回复、新粉丝提醒

"我们公司需要一个内部知识库系统,员工可以搜索和浏览技术文档,也可以自己写文档发布,需要有权限管理。"

界面需求分析报告

  • 知识库首页 — 分类导航 + 全文搜索,热门文档推荐,最近更新列表
  • 文档阅读页 — Markdown渲染,目录锚点导航,代码高亮,评论与收藏
  • 文档编辑器 — 所见即所得编辑器,支持Markdown、表格、图片插入,自动保存
  • 权限管理 — 角色分级(管理员/编辑者/阅读者),文档级别的访问控制
  • 个人中心 — 我创建的、我收藏的、浏览历史、编辑草稿

开始使用

下载界面需求分析技能,让AI帮你把想法变成专业的需求文档

文本无限

用文字描述,无限绘制界面布局与页面流程

告别拖拽式的设计工具,只需用文本描述你想要的界面布局和页面流程,文本无限就能将其转化为专业的线框图和流程图。支持多种布局模式,从简单的列表到复杂的Bento Grid,一句话即可生成。

文本无限效果预览

布局示例

经典后台布局

侧边导航 + 顶部工具栏 + 内容区域,一句话生成完整的后台管理界面框架,支持多级菜单和面包屑导航。

落地页布局

Hero区、特性展示、CTA按钮,标准的营销页面结构。

移动端布局

底部Tab导航、卡片列表、浮动操作按钮。

数据看板布局

多指标卡片、图表区域、筛选器面板,快速搭建数据分析看板,支持拖拽调整和数据实时刷新展示。

流程示例

"用户注册流程:输入手机号 → 获取验证码 → 设置密码 → 完善个人信息 → 完成注册进入首页"

页面流程图

  • 注册页1 — 手机号输入框 + "获取验证码"按钮,60秒倒计时
  • 注册页2 — 6位验证码输入,倒计时重发入口
  • 注册页3 — 密码设置,强度检测,确认密码
  • 注册页4 — 头像上传、昵称、性别选择(可跳过)
  • 首页 — 注册成功toast提示,引导完成首次任务

释放文本的创造力

用自然语言描述,让文本无限为你绘制专业的界面布局

图标获取

一句话描述,精准匹配你需要的图标

无需在图标库中逐个翻找,只需用自然语言描述你想要的图标风格和内容,AI就能从海量图标库中精准匹配,或直接生成定制图标。支持SVG、PNG等多种格式导出,完美适配你的项目。

图标获取 · 效果预览

使用示例

"我需要一个电商App的全套图标,风格要圆润可爱,线条稍粗,包括:首页、购物车、订单、个人中心、搜索、收藏、分享、设置"

图标匹配结果

  • 首页 — 小房子图标,圆润线条风格,2px描边
  • 购物车 — 购物车+数字角标,支持空/满两种状态
  • 订单 — 文件箱图标,带小标签装饰
  • 个人中心 — 微笑头像轮廓,极简风格
  • 搜索/收藏/分享/设置 — 统一风格的配套图标,已打包为SVG

"给我找一些科技感的仪表盘图标,用来做数据大屏,要那种霓虹发光效果的"

图标匹配结果

  • 数据趋势 — 霓虹蓝折线图图标,带发光效果
  • 用户活跃 — 渐变色人形图标,呼吸灯效果
  • 服务器状态 — 科技风服务器图标,指示灯闪烁
  • 告警通知 — 红色脉冲铃铛图标,支持动效

找到完美图标

告别繁琐搜索,用自然语言获取最匹配的图标

网页编辑器

所见即所得,轻松精修每一个页面

AI生成的页面只是起点,网页编辑器让你对每一个细节进行精细调整。可视化编辑界面,支持拖拽调整布局、修改文案样式、替换图片资源、添加交互动效,非技术人员也能轻松完成页面的最终调优。

网页编辑器 · 效果预览

功能亮点

可视化编辑

点击即编辑,所见即所得的操作体验,修改文案、样式、布局,一切都在直觉中完成。

拖拽布局

自由拖拽调整组件位置,支持网格对齐和自由布局两种模式。

样式面板

颜色、字体、间距、阴影,所有样式属性一目了然,精细调整每个像素。

实时预览与导出

编辑过程中随时预览效果,支持多设备预览。完成后一键导出HTML/CSS代码或直接发布上线,与AI生成流程无缝衔接。

精修你的创意

所见即所得的编辑体验,让每个页面都达到完美

关于创作者

在AI技术飞速发展的今天,我发现很多人虽然有很好的产品创意,却因为不会使用专业的设计工具,无法将自己的想法清晰地表达出来。于是我开始思考:能不能用AI来弥合"创意"和"实现"之间的鸿沟?


AI创意无限工具集就是这个问题的答案。从需求分析到界面设计,从布局绘制到代码编辑,每一个工具都是为了让创意能够更精准地被理解和实现。我相信,当AI真正理解了人的创意意图,从想法到产品的距离将变得无限短。


希望这套工具能帮助到每一个有创意的人,无论你是产品经理、设计师、开发者,还是一个怀揣想法的创业者。

highlightBox.style.cssText = 'position:absolute;pointer-events:none;border:2px solid hsl(258,72%,56%);background:hsl(258,72%,56%,0.08);border-radius:2px;transition:all 0.1s ease;display:none;box-sizing:border-box;z-index:2147483647;'; overlay.appendChild(highlightBox); const selectBox = document.createElement('div'); selectBox.id = 've-select'; selectBox.style.cssText = 'position:absolute;pointer-events:none;border:2px dashed hsl(258,72%,56%);background:hsl(258,72%,56%,0.04);border-radius:2px;display:none;box-sizing:border-box;z-index:2147483647;'; overlay.appendChild(selectBox); let isSelecting = false; function getSelector(el) { if (el === document.body) return 'body'; if (el === document.documentElement) return 'html'; if (el.id) return el.tagName.toLowerCase() + '#' + el.id; const path = []; let current = el; while (current && current !== document.body && current !== document.documentElement) { let segment = current.tagName.toLowerCase(); if (current.id) { segment += '#' + current.id; path.unshift(segment); break; } if (current.className && typeof current.className === 'string') { const cls = current.className.trim().split(/\s+/).slice(0, 2).join('.'); if (cls) segment += '.' + cls; } // Add :nth-of-type to disambiguate siblings with the same tag+class var parent = current.parentElement; if (parent) { var siblings = parent.children; var sameCount = 0; var idx = 0; for (var i = 0; i < siblings.length; i++) { if (siblings[i].tagName === current.tagName) { sameCount++; if (siblings[i] === current) idx = sameCount; } } if (sameCount > 1) segment += ':nth-of-type(' + idx + ')'; } path.unshift(segment); current = current.parentElement; } return path.join(' > '); } function getRect(el) { const r = el.getBoundingClientRect(); return { top: r.top, left: r.left, width: r.width, height: r.height, }; } // Store original styles for revert const originalStyles = new Map(); const originalGlobalVars = new Map(); var originalTexts = new WeakMap(); // ── Tab+Space chord shortcut to toggle mode ──────────────────── // Tracks Tab keydown state; when Space is pressed while Tab is held, // notify parent to flip selection/interaction mode. Both keys' default // behaviours (focus traversal, page scroll) are prevented. var veTabDown = false; window.addEventListener('keydown', function(e) { if (e.key === 'Tab' || e.code === 'Tab') { veTabDown = true; // Don't preventDefault on bare Tab so existing Tab focus behaviour // remains for forms / inputs. } if (veTabDown && (e.code === 'Space' || e.key === ' ')) { e.preventDefault(); e.stopPropagation(); window.parent.postMessage({ type: 'request-toggle-mode' }, '*'); } }, true); window.addEventListener('keyup', function(e) { if (e.key === 'Tab' || e.code === 'Tab') veTabDown = false; }, true); // Lose Tab state if window blurs (avoids sticky chord) window.addEventListener('blur', function() { veTabDown = false; }); // Preview-mode anchor click guard: when not in selecting mode, intercept // clicks. If the href can't possibly resolve inside the srcDoc // iframe (relative path with no matching uploaded file), block navigation // and notify the parent so it can show a "缺少文件,跳转失败" toast. document.addEventListener('click', function(e) { if (isSelecting) return; // selection mode is handled by the next listener var node = e.target; var anchor = null; while (node && node !== document.body && node !== document.documentElement) { if (node.tagName === 'A' && node.getAttribute && node.getAttribute('href')) { anchor = node; break; } node = node.parentNode; } if (!anchor) return; var href = anchor.getAttribute('href'); if (!href) return; // Allow same-page anchors and pseudo-protocols if (/^(#|javascript:|mailto:|tel:|sms:)/i.test(href)) return; // Strip fragment + query to get the path var path = href.split('#')[0].split('?')[0]; if (!path) return; // pure fragment after strip var isAbsolute = /^(https?:|data:|blob:|file:)/i.test(path); var found = false; if (!isAbsolute) { var filename = path.split('/').pop(); var known = window.__veKnownFiles || []; for (var i = 0; i < known.length; i++) { if (known[i] === filename || known[i] === path) { found = true; break; } } } // Inside a srcDoc iframe relative URLs cannot resolve, and absolute URLs // are blocked by the sandbox. Either way, the navigation will fail — we // intercept it and surface a friendly message instead. if (!found) { e.preventDefault(); e.stopPropagation(); window.parent.postMessage({ type: 'nav-failed', href: href }, '*'); } }, true); // Click handler document.addEventListener('click', function(e) { if (!isSelecting) return; e.preventDefault(); e.stopPropagation(); var el = e.target; if (!el || el === overlay || el === highlightBox || el === selectBox) return; // Avoid drilling into SVG internals — lift to nearest var svgAncestor = el.closest('svg'); if (svgAncestor) { el = svgAncestor; } const selector = getSelector(el); const rect = getRect(el); const computed = window.getComputedStyle(el); const styles = {}; const keyProps = ['color','background-color','width','height','margin-top','margin-right','margin-bottom','margin-left','padding-top','padding-right','padding-bottom','padding-left','border-radius','border-width','border-style','border-color','box-shadow','opacity','display','column-gap','cursor','z-index','font-weight','font-size','line-height','text-align','letter-spacing','overflow','object-fit','visibility','position','flex-direction','justify-content','align-items','fill','fill-opacity','stroke','stroke-width','stroke-opacity','stroke-linecap','stroke-linejoin']; keyProps.forEach(function(p) { styles[p] = computed.getPropertyValue(p); }); // Detect original specified mode for width/height (auto / % / px) by // inspecting inline style first, then walking matching CSS rules in cascade // order. getComputedStyle always returns resolved px values, so it cannot // tell us whether the author wrote 'auto' or a percentage. function parseSizeSpec(raw) { if (!raw) return null; var v = String(raw).trim().toLowerCase(); if (!v) return null; if (v === 'auto' || v === 'inherit' || v === 'initial' || v === 'unset' || v === 'fit-content' || v === 'max-content' || v === 'min-content') { return { mode: 'auto', numeric: 0 }; } if (v.indexOf('%') !== -1 && /^-?[\d.]+%$/.test(v)) { return { mode: 'percent', numeric: parseFloat(v) || 0 }; } if (/^-?[\d.]+px$/.test(v)) { return { mode: 'px', numeric: parseFloat(v) || 0 }; } return null; } function detectSizeMode(node, prop) { // 1. Inline style takes highest priority try { var inline = node.style && node.style.getPropertyValue(prop); var inlineParsed = parseSizeSpec(inline); if (inlineParsed) return inlineParsed; } catch (e) {} // 2. Walk all matching stylesheet rules in cascade order var lastSpec = null; try { var sheets = document.styleSheets; for (var i = 0; i < sheets.length; i++) { var rules; try { rules = sheets[i].cssRules || sheets[i].rules; } catch (e) { continue; } if (!rules) continue; for (var j = 0; j < rules.length; j++) { var rule = rules[j]; if (!rule || !rule.selectorText || !rule.style) continue; var spec; try { if (node.matches && node.matches(rule.selectorText)) { spec = rule.style.getPropertyValue(prop); } } catch (e) { continue; } if (spec) { var parsed = parseSizeSpec(spec); if (parsed) lastSpec = parsed; } } } } catch (e) {} if (lastSpec) return lastSpec; return { mode: 'auto', numeric: 0 }; } var widthSpec = detectSizeMode(el, 'width'); var heightSpec = detectSizeMode(el, 'height'); styles['__width_mode__'] = widthSpec.mode; styles['__width_numeric__'] = String(widthSpec.numeric); styles['__height_mode__'] = heightSpec.mode; styles['__height_numeric__'] = String(heightSpec.numeric); // Capture the actual rendered pixel size and the parent's content-box // size so the parent panel can pre-fill realistic defaults when the // user switches between auto / 百分比 / 数值 modes. try { var elRect = el.getBoundingClientRect(); styles['__width_actual_px__'] = String(Math.round(elRect.width)); styles['__height_actual_px__'] = String(Math.round(elRect.height)); var parentEl = el.parentElement; if (parentEl) { var pCs = window.getComputedStyle(parentEl); var pRect = parentEl.getBoundingClientRect(); // Subtract padding + border to get the content-box width/height // that percentages resolve against (per CSS spec, percent resolves // to the containing block's content box for in-flow elements). var pPadL = parseFloat(pCs.paddingLeft) || 0; var pPadR = parseFloat(pCs.paddingRight) || 0; var pBorL = parseFloat(pCs.borderLeftWidth) || 0; var pBorR = parseFloat(pCs.borderRightWidth) || 0; var pPadT = parseFloat(pCs.paddingTop) || 0; var pPadB = parseFloat(pCs.paddingBottom) || 0; var pBorT = parseFloat(pCs.borderTopWidth) || 0; var pBorB = parseFloat(pCs.borderBottomWidth) || 0; var pInnerW = Math.max(0, pRect.width - pPadL - pPadR - pBorL - pBorR); var pInnerH = Math.max(0, pRect.height - pPadT - pPadB - pBorT - pBorB); styles['__width_parent_px__'] = String(Math.round(pInnerW)); styles['__height_parent_px__'] = String(Math.round(pInnerH)); } else { styles['__width_parent_px__'] = '0'; styles['__height_parent_px__'] = '0'; } } catch (e) {} // Detect if element contains multiple text areas (multiple text nodes // or multiple text-containing children). When true, the parent should // hide the "内容" text editor and text-color property since editing // concatenated text is meaningless. var hasMultipleTextAreas = false; try { var textAreaCount = 0; for (var mi = 0; mi < el.childNodes.length; mi++) { var mc = el.childNodes[mi]; if (mc.nodeType === 3 && mc.nodeValue && mc.nodeValue.trim().length > 0) { textAreaCount++; } else if (mc.nodeType === 1 && mc.textContent && mc.textContent.trim().length > 0) { textAreaCount++; } if (textAreaCount >= 2) { hasMultipleTextAreas = true; break; } } } catch (e) {} window.parent.postMessage({ type: 'element-clicked', selector: selector, tagName: el.tagName.toLowerCase(), id: el.id || null, className: (typeof el.className === 'string') ? el.className : null, rect: rect, computedStyles: styles, inlineStyle: el.getAttribute('style') || null, textContent: el.textContent ? el.textContent.trim().substring(0, 100) : null, childElementCount: el.childElementCount, hasMultipleTextAreas: hasMultipleTextAreas, }, '*'); }, true); // Hover handler let hoveredEl = null; document.addEventListener('mousemove', function(e) { if (!isSelecting) { if (highlightBox.style.display !== 'none') highlightBox.style.display = 'none'; return; } const el = document.elementFromPoint(e.clientX, e.clientY); if (!el || el === overlay || el === highlightBox || el === selectBox || el === document.body || el === document.documentElement) { if (highlightBox.style.display !== 'none') highlightBox.style.display = 'none'; return; } if (el === hoveredEl) return; hoveredEl = el; const rect = getRect(el); highlightBox.style.display = 'block'; highlightBox.style.top = rect.top + 'px'; highlightBox.style.left = rect.left + 'px'; highlightBox.style.width = rect.width + 'px'; highlightBox.style.height = rect.height + 'px'; window.parent.postMessage({ type: 'element-hovered', selector: getSelector(el), rect: rect, }, '*'); }, { passive: true }); // Clear highlight when mouse leaves the iframe entirely document.documentElement.addEventListener('mouseleave', function() { highlightBox.style.display = 'none'; hoveredEl = null; }); // Listen for parent commands window.addEventListener('message', function(e) { var msg = e.data; if (!msg || !msg.type) return; // ── Shared utilities (accessible from all handlers) ────────────── function getManagedSheet() { var styleEl = document.getElementById('__ve_managed_styles__'); if (!styleEl) { styleEl = document.createElement('style'); styleEl.id = '__ve_managed_styles__'; styleEl.setAttribute('data-ve-managed', '1'); document.head.appendChild(styleEl); } else if (styleEl.parentNode !== document.head || styleEl.nextSibling) { document.head.appendChild(styleEl); } return styleEl.sheet; } function cssEscape(s) { if (typeof CSS !== 'undefined' && CSS.escape) return CSS.escape(s); return String(s).replace(/[^a-zA-Z0-9_-]/g, function(c) { return '\\' + c; }); } function getClassSyncSelector(el) { if (!el || !el.tagName) return null; if (typeof el.className !== 'string') return null; var cls = el.className.trim(); if (!cls) return null; var parts = cls.split(/\s+/).filter(Boolean); if (!parts.length) return null; return el.tagName.toLowerCase() + '.' + parts.map(cssEscape).join('.'); } function upsertManagedRule(selector, prop, value) { var sheet = getManagedSheet(); if (!sheet) return false; var rules = sheet.cssRules; for (var i = 0; i < rules.length; i++) { var r = rules[i]; if (r && r.selectorText === selector) { r.style.setProperty(prop, value, 'important'); return true; } } try { sheet.insertRule(selector + ' { ' + prop + ': ' + value + ' !important; }', rules.length); return true; } catch (e) { return false; } } if (msg.type === 'start-selecting') { isSelecting = true; document.body.style.cursor = 'crosshair'; } if (msg.type === 'stop-selecting') { isSelecting = false; document.body.style.cursor = ''; highlightBox.style.display = 'none'; selectBox.style.display = 'none'; } if (msg.type === 'highlight-element' && msg.selector) { try { var el = document.querySelector(msg.selector); if (el) { var rect = getRect(el); highlightBox.style.display = 'block'; highlightBox.style.top = rect.top + 'px'; highlightBox.style.left = rect.left + 'px'; highlightBox.style.width = rect.width + 'px'; highlightBox.style.height = rect.height + 'px'; highlightBox.style.borderColor = msg.color || 'hsl(258,72%,56%)'; } } catch(e) {} } if (msg.type === 'select-element' && msg.selector) { try { var el = document.querySelector(msg.selector); if (el) { var rect = getRect(el); selectBox.style.display = 'block'; selectBox.style.top = rect.top + 'px'; selectBox.style.left = rect.left + 'px'; selectBox.style.width = rect.width + 'px'; selectBox.style.height = rect.height + 'px'; } } catch(e) {} } if (msg.type === 'clear-highlight') { highlightBox.style.display = 'none'; selectBox.style.display = 'none'; } if (msg.type === 'apply-style' && msg.selector) { try { // SVG paint properties — when applied to , must also be // propagated to descendant shape elements because their inline // presentation attributes (e.g. fill="black" on a ) block // CSS inheritance from the root . var SVG_PAINT_PROPS = { 'fill': 1, 'stroke': 1, 'fill-opacity': 1, 'stroke-opacity': 1, 'stroke-width': 1, 'stroke-linecap': 1, 'stroke-linejoin': 1, 'stroke-dasharray': 1, 'stroke-dashoffset': 1, 'stroke-miterlimit': 1, 'fill-rule': 1, 'clip-rule': 1, }; var SVG_SHAPES_SELECTOR = 'path,circle,rect,ellipse,line,polygon,polyline,g,use,text,tspan'; // Inline fallback (no className OR SVG paint that must override // shape-level presentation attributes). function applyToOne(target) { if (!originalStyles.has(target)) { originalStyles.set(target, target.getAttribute('style') || ''); } target.style.setProperty(msg.property, msg.value, 'important'); } // SVG paint propagation: even when the change goes through a class // rule, descendant shapes carry presentation attributes that block // CSS inheritance, so we must also write inline overrides on each // shape across ALL elements matching the class signature. function propagateSvgPaint(classSelOrEl, prop, value, isClassMode) { var roots = isClassMode ? document.querySelectorAll(classSelOrEl) : [classSelOrEl]; for (var r = 0; r < roots.length; r++) { var root = roots[r]; if (!root || !root.tagName) continue; if (root.tagName.toLowerCase() !== 'svg') continue; var shapes = root.querySelectorAll(SVG_SHAPES_SELECTOR); for (var s = 0; s < shapes.length; s++) { var sh = shapes[s]; if (!originalStyles.has(sh)) { originalStyles.set(sh, sh.getAttribute('style') || ''); } if (prop === 'color') { var sc = window.getComputedStyle(sh); if (sc.getPropertyValue('fill') !== 'none') { sh.style.setProperty('fill', value, 'important'); } if (sc.getPropertyValue('stroke') !== 'none') { sh.style.setProperty('stroke', value, 'important'); } } else { sh.style.setProperty(prop, value, 'important'); } } } } var els = document.querySelectorAll(msg.selector); // Track class selectors already updated to avoid duplicate work // when multiple elements in els share the same className. var processedClassSel = (typeof Set !== 'undefined') ? new Set() : null; // Track elements already inline-applied (no-className branch). var processedInline = (typeof Set !== 'undefined') ? new Set() : null; // ── Hover-pseudo branch ─────────────────────────────────── // When msg.pseudo === ':hover' (hover-tab edit), we write the // value into the :hover variant of the element's class rule in // the managed stylesheet. Inline styles are untouched so the // default appearance stays intact — the change only shows on // real hover (or when the hover-preview rule is active). if (msg.pseudo === ':hover') { for (var hi = 0; hi < els.length; hi++) { var hel = els[hi]; var hClassSel = getClassSyncSelector(hel); var hIsSvg = hel.tagName && hel.tagName.toLowerCase() === 'svg'; if (hClassSel && (!processedClassSel || !processedClassSel.has(hClassSel))) { if (processedClassSel) processedClassSel.add(hClassSel); upsertManagedRule(hClassSel + ':hover', msg.property, msg.value); if (hIsSvg && msg.property === 'color') { upsertManagedRule(hClassSel + ':hover', 'fill', msg.value); upsertManagedRule(hClassSel + ':hover', 'stroke', msg.value); } if (hIsSvg && (msg.property === 'color' || SVG_PAINT_PROPS[msg.property])) { var shapes = SVG_SHAPES_SELECTOR.split(','); for (var shi = 0; shi < shapes.length; shi++) { var compound = hClassSel + ':hover ' + shapes[shi]; if (msg.property === 'color') { upsertManagedRule(compound, 'fill', msg.value); upsertManagedRule(compound, 'stroke', msg.value); } else { upsertManagedRule(compound, msg.property, msg.value); } } } } } return; } for (var i = 0; i < els.length; i++) { var el = els[i]; var classSel = getClassSyncSelector(el); var isSvg = el.tagName && el.tagName.toLowerCase() === 'svg'; if (classSel) { // Class-bearing element → write to shared managed rule if (!processedClassSel || !processedClassSel.has(classSel)) { if (processedClassSel) processedClassSel.add(classSel); upsertManagedRule(classSel, msg.property, msg.value); // SVG color shorthand → also write fill/stroke into the rule if (isSvg && msg.property === 'color') { upsertManagedRule(classSel, 'fill', msg.value); upsertManagedRule(classSel, 'stroke', msg.value); } // SVG paint props OR color: propagate to shapes inline if (isSvg && (msg.property === 'color' || SVG_PAINT_PROPS[msg.property])) { propagateSvgPaint(classSel, msg.property, msg.value, true); } } } else { // No className → fall back to per-element inline if (!processedInline || !processedInline.has(el)) { if (processedInline) processedInline.add(el); applyToOne(el); if (isSvg && msg.property === 'color') { el.style.setProperty('fill', msg.value, 'important'); el.style.setProperty('stroke', msg.value, 'important'); propagateSvgPaint(el, 'color', msg.value, false); } else if (isSvg && SVG_PAINT_PROPS[msg.property]) { propagateSvgPaint(el, msg.property, msg.value, false); } } } } } catch(e) {} } if (msg.type === 'apply-text' && msg.selector) { try { var textEls = document.querySelectorAll(msg.selector); for (var ti = 0; ti < textEls.length; ti++) { var textEl = textEls[ti]; if (!originalTexts.has(textEl)) { originalTexts.set(textEl, textEl.textContent || ''); } // Recursively find all text nodes to preserve child elements (e.g. SVG icons) function collectTextNodes(node) { var result = []; for (var i = 0; i < node.childNodes.length; i++) { var child = node.childNodes[i]; if (child.nodeType === 3) result.push(child); else if (child.nodeType === 1) result = result.concat(collectTextNodes(child)); } return result; } // Only update the first non-whitespace text node; preserve all others // (including whitespace-only nodes that affect layout/spacing) var allTextNodes = collectTextNodes(textEl); var targetNode = null; for (var ni = 0; ni < allTextNodes.length; ni++) { if (allTextNodes[ni].nodeValue && allTextNodes[ni].nodeValue.trim().length > 0) { targetNode = allTextNodes[ni]; break; } } if (targetNode) { targetNode.nodeValue = msg.value; } else if (allTextNodes.length > 0) { allTextNodes[0].nodeValue = msg.value; } else { textEl.appendChild(document.createTextNode(msg.value)); } } } catch(e) {} } if (msg.type === 'replace-content' && msg.selector) { try { var el = document.querySelector(msg.selector); if (el && typeof msg.content === 'string') { // Capture original outerHTML before replacement so the parent // can record it as a tracked style change (for save/export). var oldOuter = el.outerHTML; // Parse new SVG content var temp = document.createElement('div'); temp.innerHTML = msg.content.trim(); var newSvg = temp.querySelector('svg'); if (newSvg) { // === "Image replacement" approach === // Keep the original element in the DOM (preserving its class, style, // event listeners, CSS :hover/:active/.selected rules, etc.). // Only replace its internal drawing content (paths, shapes, defs, etc.) // and update the viewBox to match the new icon's coordinate system. // 1. Update viewBox from new SVG (coordinate system of the new icon) var newViewBox = newSvg.getAttribute('viewBox'); if (newViewBox) { el.setAttribute('viewBox', newViewBox); } // 2. Sync paint-mode attributes on the root SVG. // The new icon determines whether it's fill-based or stroke-based. // Remove old paint attributes that could conflict (e.g., original had // stroke="currentColor" but new icon uses fill only). var PAINT_ATTRS = ['fill','stroke','stroke-width','stroke-linecap', 'stroke-linejoin','stroke-dasharray','stroke-dashoffset','stroke-miterlimit', 'fill-rule','clip-rule','fill-opacity','stroke-opacity']; for (var pa = 0; pa < PAINT_ATTRS.length; pa++) { el.removeAttribute(PAINT_ATTRS[pa]); } // Copy paint attributes from the new SVG root (if any) for (var pa2 = 0; pa2 < PAINT_ATTRS.length; pa2++) { var pVal = newSvg.getAttribute(PAINT_ATTRS[pa2]); if (pVal != null) { el.setAttribute(PAINT_ATTRS[pa2], pVal); } } // Ensure the root uses currentColor for fill/stroke so CSS color cascades var rootFill = el.getAttribute('fill'); if (rootFill && rootFill !== 'none' && rootFill !== 'currentColor') { el.setAttribute('fill', 'currentColor'); } else if (!rootFill) { el.setAttribute('fill', 'currentColor'); } var rootStroke = el.getAttribute('stroke'); if (rootStroke && rootStroke !== 'none' && rootStroke !== 'currentColor') { el.setAttribute('stroke', 'currentColor'); } // 3. Clear all existing children of the original SVG while (el.firstChild) { el.removeChild(el.firstChild); } // 4. Move all children from the new SVG into the original element while (newSvg.firstChild) { el.appendChild(newSvg.firstChild); } // 5. Set fill="currentColor" on shape elements so they inherit // the CSS 'color' property (which handles hover/active/selected states). // Shapes with explicit fill="none" / stroke="none" are preserved as-is. var shapes = el.querySelectorAll('path,circle,rect,ellipse,line,polygon,polyline,g,use,text,tspan'); for (var ns = 0; ns < shapes.length; ns++) { var nsEl = shapes[ns]; var nsFill = nsEl.getAttribute('fill'); var nsStroke = nsEl.getAttribute('stroke'); if (nsFill !== 'none') { nsEl.setAttribute('fill', 'currentColor'); } if (nsStroke && nsStroke !== 'none') { nsEl.setAttribute('stroke', 'currentColor'); } } // Notify parent so it can record this as a tracked change for // save HTML / export AI instructions. try { var newOuter = el.outerHTML; window.parent.postMessage({ type: 'svg-replaced', selector: msg.selector, oldOuterHTML: oldOuter, newOuterHTML: newOuter }, '*'); } catch(e2) {} } } } catch(e) {} } if (msg.type === 'revert-all') { originalStyles.forEach(function(orig, el) { if (orig) { el.setAttribute('style', orig); } else { el.removeAttribute('style'); } }); originalStyles.clear(); // Also clear the managed stylesheet so class-rule overrides are gone. var managed = document.getElementById('__ve_managed_styles__'); if (managed && managed.parentNode) { managed.parentNode.removeChild(managed); } var hoverPreview = document.getElementById('__ve_hover_preview__'); if (hoverPreview && hoverPreview.parentNode) { hoverPreview.parentNode.removeChild(hoverPreview); } // Restore original global CSS variables originalGlobalVars.forEach(function(origVal, varName) { if (origVal) { document.documentElement.style.setProperty(varName, origVal); } else { document.documentElement.style.removeProperty(varName); } }); originalGlobalVars.clear(); // Restore original text content originalTexts.forEach(function(origText, el) { if (el && el.parentNode) { el.textContent = origText; } }); originalTexts = new WeakMap(); } if (msg.type === 'request-hover-styles') { // Collect :hover rule declarations for the element matching // msg.selector and reply. We walk every stylesheet recursively, // including rules nested inside @media, @supports, @layer etc. var hDecls = {}; try { var hEl = document.querySelector(msg.selector); if (hEl) { // Recursive function to walk into at-rules function collectHoverDecls(rules) { for (var ri = 0; ri < rules.length; ri++) { var hRule = rules[ri]; if (!hRule) continue; // At-rule (CSSMediaRule, CSSSupportsRule, etc.) — recurse if (hRule.cssRules && !hRule.selectorText) { try { collectHoverDecls(hRule.cssRules); } catch (e) {} continue; } if (!hRule.selectorText || !hRule.style) continue; var selText = hRule.selectorText; if (selText.indexOf(':hover') === -1) continue; var hSelectors = selText.split(','); for (var ski = 0; ski < hSelectors.length; ski++) { var hSel = hSelectors[ski].trim(); if (hSel.indexOf(':hover') === -1) continue; var hTestSel = hSel.replace(/:hover/g, ''); if (!hTestSel) continue; var hMatched = false; try { hMatched = !!(hEl.matches && hEl.matches(hTestSel)); } catch (e) { hMatched = false; } if (!hMatched) continue; for (var mi = 0; mi < hRule.style.length; mi++) { var hp = hRule.style[mi]; if (!hp) continue; hDecls[hp] = hRule.style.getPropertyValue(hp); } } } } var hSheets = document.styleSheets; for (var si = 0; si < hSheets.length; si++) { var hRules; try { hRules = hSheets[si].cssRules || hSheets[si].rules; } catch (e) { continue; } if (!hRules) continue; collectHoverDecls(hRules); } } } catch (e) {} window.parent.postMessage({ type: 'hover-styles', selector: msg.selector, hoverStyles: hDecls, }, '*'); } if (msg.type === 'hover-preview') { // Mirror ALL managed :hover rules into the preview sheet as // plain rules so the selected element shows hover appearance even // without a real :hover event. Mirroring every :hover rule (not // just those for msg.selector) keeps the preview in sync after // each apply-style change without extra round-trips. var preview = document.getElementById('__ve_hover_preview__'); if (!msg.selector) { if (preview && preview.parentNode) preview.parentNode.removeChild(preview); } else { if (!preview) { preview = document.createElement('style'); preview.id = '__ve_hover_preview__'; preview.setAttribute('data-ve-hover-preview', '1'); document.head.appendChild(preview); } var pvSheet = preview.sheet; while (pvSheet && pvSheet.cssRules.length > 0) pvSheet.deleteRule(0); if (pvSheet) { var pvManaged = document.getElementById('__ve_managed_styles__'); if (pvManaged && pvManaged.sheet) { var pvRules = pvManaged.sheet.cssRules; for (var pvi = 0; pvi < pvRules.length; pvi++) { var pvR = pvRules[pvi]; if (!pvR || !pvR.selectorText) continue; if (pvR.selectorText.indexOf(':hover') === -1) continue; var pvPlain = pvR.selectorText.replace(/:hover/g, ''); try { pvSheet.insertRule(pvPlain + ' { ' + pvR.style.cssText + ' }', pvSheet.cssRules.length); } catch (e) {} } } } } } // ── Global Styles: extract & apply ───────────────────────────── if (msg.type === 'extract-global-styles') { var gp = function(v, cv) { return { value: v || '', cssVariable: cv || null }; }; var emptyGp = function() { return { value: '', cssVariable: null }; }; var emptyTypo = function() { return { fontSize: emptyGp(), lineHeight: emptyGp(), fontWeight: emptyGp(), color: emptyGp() }; }; var data = { colors: { primary: emptyGp(), success: emptyGp(), danger: emptyGp(), warning: emptyGp() }, backgrounds: { background: emptyGp(), sidebar: emptyGp() }, borderRadius: { panel: emptyGp(), button: emptyGp() }, shadows: { card: emptyGp(), popup: emptyGp() }, typography: { heading1: emptyTypo(), heading2: emptyTypo(), body: emptyTypo(), auxiliary: emptyTypo() } }; // Phase 1: collect CSS custom properties from :root var cssVars = new Map(); var sheets = document.styleSheets; for (var gsi = 0; gsi < sheets.length; gsi++) { var gRules; try { gRules = sheets[gsi].cssRules || sheets[gsi].rules; } catch(e) { continue; } if (!gRules) continue; for (var gri = 0; gri < gRules.length; gri++) { var gR = gRules[gri]; if (!gR || !gR.selectorText) continue; if (gR.selectorText.trim() === ':root') { for (var pi = 0; pi < gR.style.length; pi++) { var pn = gR.style[pi]; if (pn && pn.indexOf('--') === 0) { cssVars.set(pn, gR.style.getPropertyValue(pn).trim()); } } } } } // Phase 2: heuristic variable matching var usedVars = new Set(); function matchVar(keywords) { for (var i = 0; i < keywords.length; i++) { var kw = keywords[i]; var found = null; cssVars.forEach(function(val, name) { if (!usedVars.has(name) && name.toLowerCase().indexOf(kw) !== -1) { found = name; } }); if (found) { usedVars.add(found); return { value: cssVars.get(found), cssVariable: found }; } } return null; } var colorParams = [ ['colors', 'primary', ['primary', 'accent', 'brand', 'theme', 'main-color']], ['colors', 'success', ['success', 'positive', 'confirm']], ['colors', 'danger', ['danger', 'error', 'failure', 'destructive']], ['colors', 'warning', ['warning', 'warn', 'caution', 'alert']] ]; for (var ci = 0; ci < colorParams.length; ci++) { var m = matchVar(colorParams[ci][2]); if (m) data.colors[colorParams[ci][1]] = m; } // Background color CSS variable matching var bgParams = [ ['background', ['bg-color', 'bg', 'background', 'background-color', 'page-bg']], ['sidebar', ['sidebar-bg', 'sidebar-color', 'sidebar', 'side-bg', 'nav-bg']] ]; for (var bgi = 0; bgi < bgParams.length; bgi++) { var bgm = matchVar(bgParams[bgi][1]); if (bgm) data.backgrounds[bgParams[bgi][0]] = bgm; } // Background DOM fallback: read computed background-color from elements function rgbToHex(rgbStr) { if (!rgbStr || rgbStr === 'transparent' || rgbStr === 'rgba(0, 0, 0, 0)') return ''; var parts = rgbStr.match(/[d.]+/g); if (!parts || parts.length < 3) return ''; var r = parseInt(parts[0]), g = parseInt(parts[1]), b = parseInt(parts[2]); return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); } if (!data.backgrounds.background.value) { var bodyBg = getComputedStyle(document.body).backgroundColor; var bodyHex = rgbToHex(bodyBg); if (bodyHex) data.backgrounds.background = gp(bodyHex, null); } if (!data.backgrounds.sidebar.value) { var sidebarEl = document.querySelector('aside, nav, .sidebar, [class*="sidebar"], [class*="side-nav"]'); if (sidebarEl) { var sideBg = getComputedStyle(sidebarEl).backgroundColor; var sideHex = rgbToHex(sideBg); if (sideHex) data.backgrounds.sidebar = gp(sideHex, null); } } var radP = [['panel', ['radius-card', 'radius-panel', 'radius-lg', 'radius']], ['button', ['radius-btn', 'radius-button', 'radius-sm']]]; for (var ri2 = 0; ri2 < radP.length; ri2++) { var rm = matchVar(radP[ri2][1]); if (rm) data.borderRadius[radP[ri2][0]] = rm; } var shdP = [['card', ['shadow-card', 'shadow', 'elevation', 'box-shadow']], ['popup', ['shadow-popup', 'shadow-dropdown', 'shadow-modal', 'shadow-lg']]]; for (var si2 = 0; si2 < shdP.length; si2++) { var sm = matchVar(shdP[si2][1]); if (sm) data.shadows[shdP[si2][0]] = sm; } var typoKeys = [ ['heading1', ['font-size-h1', 'text-h1', 'fs-h1']], ['heading2', ['font-size-h2', 'text-h2', 'fs-h2']], ['body', ['font-size-body', 'font-size-base', 'text-base', 'fs-body']], ['auxiliary', ['font-size-sm', 'font-size-aux', 'text-sm', 'fs-sm']] ]; for (var ti = 0; ti < typoKeys.length; ti++) { var tm = matchVar(typoKeys[ti][1]); if (tm) data.typography[typoKeys[ti][0]].fontSize = tm; } // Try to match typography colors var typoColorKeys = [['heading1', ['heading-color', 'text-h1-color']], ['heading2', ['text-h2-color']], ['body', ['text-color', 'body-color']], ['auxiliary', ['text-muted', 'text-secondary']]]; for (var tci = 0; tci < typoColorKeys.length; tci++) { var tcm = matchVar(typoColorKeys[tci][1]); if (tcm) data.typography[typoColorKeys[tci][0]].color = tcm; } // Typography fallback: query DOM elements var typoSelectors = [['heading1', 'h1'], ['heading2', 'h2'], ['body', 'p'], ['auxiliary', 'small']]; for (var tsi = 0; tsi < typoSelectors.length; tsi++) { var tKey = typoSelectors[tsi][0]; var tSel = typoSelectors[tsi][1]; var tEl = document.querySelector(tSel); if (!tEl && tSel === 'p') tEl = document.body; if (tEl) { var cs = getComputedStyle(tEl); if (!data.typography[tKey].fontSize.value) data.typography[tKey].fontSize = gp(cs.fontSize, null); if (!data.typography[tKey].lineHeight.value) data.typography[tKey].lineHeight = gp(cs.lineHeight, null); if (!data.typography[tKey].fontWeight.value) data.typography[tKey].fontWeight = gp(cs.fontWeight, null); if (!data.typography[tKey].color.value) data.typography[tKey].color = gp(cs.color, null); } } window.parent.postMessage({ type: 'global-styles-extracted', data: data }, '*'); } if (msg.type === 'apply-global-style') { var GLOBAL_PARAM_MAPPING = { 'colors.primary': { generatedVar: '--ve-primary-color', injectionSelectors: ['a', '.btn-primary', '[class*="primary"]'], injectionProperty: 'color' }, 'colors.success': { generatedVar: '--ve-success-color', injectionSelectors: ['.btn-success', '[class*="success"]'], injectionProperty: 'color' }, 'colors.danger': { generatedVar: '--ve-danger-color', injectionSelectors: ['.btn-danger', '[class*="danger"]', '[class*="error"]'], injectionProperty: 'color' }, 'colors.warning': { generatedVar: '--ve-warning-color', injectionSelectors: ['.btn-warning', '[class*="warning"]'], injectionProperty: 'color' }, 'backgrounds.background': { generatedVar: '--ve-bg-color', injectionSelectors: ['body', 'main', '.main', '[class*="content"]', '#app'], injectionProperty: 'background-color' }, 'backgrounds.sidebar': { generatedVar: '--ve-sidebar-color', injectionSelectors: ['aside', 'nav', '.sidebar', '[class*="sidebar"]', '[class*="side-nav"]', '[class*="sidenav"]'], injectionProperty: 'background-color' }, 'borderRadius.panel': { generatedVar: '--ve-panel-radius', injectionSelectors: ['.card', '[class*="card"]', '[class*="panel"]'], injectionProperty: 'border-radius' }, 'borderRadius.button': { generatedVar: '--ve-button-radius', injectionSelectors: ['.btn', 'button', '[class*="button"]', 'input[type="submit"]'], injectionProperty: 'border-radius' }, 'shadows.card': { generatedVar: '--ve-card-shadow', injectionSelectors: ['.card', '[class*="card"]'], injectionProperty: 'box-shadow' }, 'shadows.popup': { generatedVar: '--ve-popup-shadow', injectionSelectors: ['.modal', '.popup', '.dropdown', '[class*="modal"]', '[class*="popup"]', '[class*="dropdown"]'], injectionProperty: 'box-shadow' }, 'typography.heading1.fontSize': { generatedVar: '--ve-h1-font-size', injectionSelectors: ['h1'], injectionProperty: 'font-size' }, 'typography.heading1.lineHeight': { generatedVar: '--ve-h1-line-height', injectionSelectors: ['h1'], injectionProperty: 'line-height' }, 'typography.heading1.fontWeight': { generatedVar: '--ve-h1-font-weight', injectionSelectors: ['h1'], injectionProperty: 'font-weight' }, 'typography.heading1.color': { generatedVar: '--ve-h1-color', injectionSelectors: ['h1'], injectionProperty: 'color' }, 'typography.heading2.fontSize': { generatedVar: '--ve-h2-font-size', injectionSelectors: ['h2'], injectionProperty: 'font-size' }, 'typography.heading2.lineHeight': { generatedVar: '--ve-h2-line-height', injectionSelectors: ['h2'], injectionProperty: 'line-height' }, 'typography.heading2.fontWeight': { generatedVar: '--ve-h2-font-weight', injectionSelectors: ['h2'], injectionProperty: 'font-weight' }, 'typography.heading2.color': { generatedVar: '--ve-h2-color', injectionSelectors: ['h2'], injectionProperty: 'color' }, 'typography.body.fontSize': { generatedVar: '--ve-body-font-size', injectionSelectors: ['p', 'body'], injectionProperty: 'font-size' }, 'typography.body.lineHeight': { generatedVar: '--ve-body-line-height', injectionSelectors: ['p', 'body'], injectionProperty: 'line-height' }, 'typography.body.fontWeight': { generatedVar: '--ve-body-font-weight', injectionSelectors: ['p', 'body'], injectionProperty: 'font-weight' }, 'typography.body.color': { generatedVar: '--ve-body-color', injectionSelectors: ['p', 'body'], injectionProperty: 'color' }, 'typography.auxiliary.fontSize': { generatedVar: '--ve-aux-font-size', injectionSelectors: ['small', '.text-sm', '[class*="aux"]'], injectionProperty: 'font-size' }, 'typography.auxiliary.lineHeight': { generatedVar: '--ve-aux-line-height', injectionSelectors: ['small', '.text-sm', '[class*="aux"]'], injectionProperty: 'line-height' }, 'typography.auxiliary.fontWeight': { generatedVar: '--ve-aux-font-weight', injectionSelectors: ['small', '.text-sm', '[class*="aux"]'], injectionProperty: 'font-weight' }, 'typography.auxiliary.color': { generatedVar: '--ve-aux-color', injectionSelectors: ['small', '.text-sm', '[class*="aux"]'], injectionProperty: 'color' } }; if (msg.cssVariable) { if (!originalGlobalVars.has(msg.cssVariable)) { originalGlobalVars.set(msg.cssVariable, getComputedStyle(document.documentElement).getPropertyValue(msg.cssVariable).trim()); } upsertManagedRule(':root', msg.cssVariable, msg.value); } else { var mapping = GLOBAL_PARAM_MAPPING[msg.paramKey]; if (mapping) { if (!originalGlobalVars.has(mapping.generatedVar)) { originalGlobalVars.set(mapping.generatedVar, getComputedStyle(document.documentElement).getPropertyValue(mapping.generatedVar).trim()); } upsertManagedRule(':root', mapping.generatedVar, msg.value); // For box-shadow: only inject on elements that already have a // box-shadow (not 'none') to avoid applying shadow to nested elements. if (mapping.injectionProperty === 'box-shadow') { for (var injI = 0; injI < mapping.injectionSelectors.length; injI++) { var injSel = mapping.injectionSelectors[injI]; var shEls = document.querySelectorAll(injSel); for (var shi = 0; shi < shEls.length; shi++) { var shEl = shEls[shi]; var curShadow = getComputedStyle(shEl).getPropertyValue('box-shadow').trim(); if (curShadow && curShadow !== 'none') { if (!originalStyles.has(shEl)) { originalStyles.set(shEl, shEl.getAttribute('style') || ''); } shEl.style.setProperty('box-shadow', msg.value, 'important'); } } } } else { for (var injI2 = 0; injI2 < mapping.injectionSelectors.length; injI2++) { var injSel2 = mapping.injectionSelectors[injI2]; if (document.querySelector(injSel2)) { upsertManagedRule(injSel2, mapping.injectionProperty, 'var(' + mapping.generatedVar + ')'); } } } } } } }); window.parent.postMessage({ type: 'iframe-ready', pageWidth: document.documentElement.scrollWidth, pageHeight: document.documentElement.scrollHeight, }, '*'); })();