a.top+a.height&&(c=a.top+a.height),l-=a.left,c-=a.top}else n&&(l=n.x*a.width,c=n.y*a.height);\"h\"!==i&&(s.style.left=`calc(${l/a.width*100}% - ${s.offsetWidth/2}px)`),\"v\"!==i&&(s.style.top=`calc(${c/a.height*100}% - ${s.offsetHeight/2}px)`),e.cache={x:l/a.width,y:c/a.height};const p=C(l/a.width),u=C(c/a.height);switch(i){case\"v\":return o.onchange(p);case\"h\":return o.onchange(u);default:return o.onchange(p,u)}},_tapstop(){e.options.onstop(),s(document,[\"mouseup\",\"touchend\",\"touchcancel\"],e._tapstop),s(document,[\"mousemove\",\"touchmove\"],e._tapmove)},trigger(){e._tapmove()},update(t=0,o=0){const{left:n,top:i,width:s,height:r}=e.options.wrapper.getBoundingClientRect();\"h\"===e.options.lock&&(o=t),e._tapmove({clientX:n+s*t,clientY:i+r*o})},destroy(){const{options:t,_tapstart:o,_keyboard:n}=e;s(document,[\"keydown\",\"keyup\"],n),s([t.wrapper,t.element],\"mousedown\",o),s([t.wrapper,t.element],\"touchstart\",o,{passive:!1})}},{options:o,_tapstart:n,_keyboard:r}=e;return i([o.wrapper,o.element],\"mousedown\",n),i([o.wrapper,o.element],\"touchstart\",n,{passive:!1}),i(document,[\"keydown\",\"keyup\"],r),e}function k(t={}){t=Object.assign({onchange:()=>0,className:\"\",elements:[]},t);const e=i(t.elements,\"click\",(e=>{t.elements.forEach((o=>o.classList[e.target===o?\"add\":\"remove\"](t.className))),t.onchange(e),e.stopPropagation()}));return{destroy:()=>s(...e)}}const S={variantFlipOrder:{start:\"sme\",middle:\"mse\",end:\"ems\"},positionFlipOrder:{top:\"tbrl\",right:\"rltb\",bottom:\"btrl\",left:\"lrbt\"},position:\"bottom\",margin:8},O=(t,e,o)=>{const{container:n,margin:i,position:s,variantFlipOrder:r,positionFlipOrder:a}={container:document.documentElement.getBoundingClientRect(),...S,...o},{left:l,top:c}=e.style;e.style.left=\"0\",e.style.top=\"0\";const p=t.getBoundingClientRect(),u=e.getBoundingClientRect(),h={t:p.top-u.height-i,b:p.bottom+i,r:p.right+i,l:p.left-u.width-i},d={vs:p.left,vm:p.left+p.width/2+-u.width/2,ve:p.left+p.width-u.width,hs:p.top,hm:p.bottom-p.height/2-u.height/2,he:p.bottom-u.height},[m,f=\"middle\"]=s.split(\"-\"),v=a[m],b=r[f],{top:y,left:g,bottom:_,right:w}=n;for(const t of v){const o=\"t\"===t||\"b\"===t,n=h[t],[i,s]=o?[\"top\",\"left\"]:[\"left\",\"top\"],[r,a]=o?[u.height,u.width]:[u.width,u.height],[l,c]=o?[_,w]:[w,_],[p,m]=o?[y,g]:[g,y];if(!(nl))for(const r of b){const l=d[(o?\"v\":\"h\")+r];if(!(lc))return e.style[s]=l-u[s]+\"px\",e.style[i]=n-u[i]+\"px\",t+r}}return e.style.left=l,e.style.top=c,null};function E(t,e,o){return e in t?Object.defineProperty(t,e,{value:o,enumerable:!0,configurable:!0,writable:!0}):t[e]=o,t}class L{constructor(t){E(this,\"_initializingActive\",!0),E(this,\"_recalc\",!0),E(this,\"_nanopop\",null),E(this,\"_root\",null),E(this,\"_color\",A()),E(this,\"_lastColor\",A()),E(this,\"_swatchColors\",[]),E(this,\"_setupAnimationFrame\",null),E(this,\"_eventListener\",{init:[],save:[],hide:[],show:[],clear:[],change:[],changestop:[],cancel:[],swatchselect:[]}),this.options=t=Object.assign({...L.DEFAULT_OPTIONS},t);const{swatches:e,components:o,theme:n,sliders:i,lockOpacity:s,padding:r}=t;[\"nano\",\"monolith\"].includes(n)&&!i&&(t.sliders=\"h\"),o.interaction||(o.interaction={});const{preview:a,opacity:l,hue:c,palette:p}=o;o.opacity=!s&&l,o.palette=p||a||l||c,this._preBuild(),this._buildComponents(),this._bindEvents(),this._finalBuild(),e&&e.length&&e.forEach((t=>this.addSwatch(t)));const{button:u,app:h}=this._root;this._nanopop=((t,e,o)=>{const n=\"object\"!=typeof t||t instanceof HTMLElement?{reference:t,popper:e,...o}:t;return{update(t=n){const{reference:e,popper:o}=Object.assign(n,t);if(!o||!e)throw new Error(\"Popper- or reference-element missing.\");return O(e,o,n)}}})(u,h,{margin:r}),u.setAttribute(\"role\",\"button\"),u.setAttribute(\"aria-label\",this._t(\"btn:toggle\"));const d=this;this._setupAnimationFrame=requestAnimationFrame((function e(){if(!h.offsetWidth)return requestAnimationFrame(e);d.setColor(t.default),d._rePositioningPicker(),t.defaultRepresentation&&(d._representation=t.defaultRepresentation,d.setColorRepresentation(d._representation)),t.showAlways&&d.show(),d._initializingActive=!1,d._emit(\"init\")}))}_preBuild(){const{options:t}=this;for(const e of[\"el\",\"container\"])t[e]=c(t[e]);this._root=(t=>{const{components:e,useAsButton:o,inline:n,appClass:i,theme:s,lockOpacity:r}=t.options,l=t=>t?\"\":'style=\"display:none\" hidden',c=e=>t._t(e),p=a(`\\n \\n\\n ${o?\"\":'
'}\\n\\n
\\n
\\n `),u=p.interaction;return u.options.find((t=>!t.hidden&&!t.classList.add(\"active\"))),u.type=()=>u.options.find((t=>t.classList.contains(\"active\"))),p})(this),t.useAsButton&&(this._root.button=t.el),t.container.appendChild(this._root.root)}_finalBuild(){const t=this.options,e=this._root;if(t.container.removeChild(e.root),t.inline){const o=t.el.parentElement;t.el.nextSibling?o.insertBefore(e.app,t.el.nextSibling):o.appendChild(e.app)}else t.container.appendChild(e.app);t.useAsButton?t.inline&&t.el.remove():t.el.parentNode.replaceChild(e.root,t.el),t.disabled&&this.disable(),t.comparison||(e.button.style.transition=\"none\",t.useAsButton||(e.preview.lastColor.style.transition=\"none\")),this.hide()}_buildComponents(){const t=this,e=this.options.components,o=(t.options.sliders||\"v\").repeat(2),[n,i]=o.match(/^[vh]+$/g)?o:[],s=()=>this._color||(this._color=this._lastColor.clone()),r={palette:$({element:t._root.palette.picker,wrapper:t._root.palette.palette,onstop:()=>t._emit(\"changestop\",\"slider\",t),onchange(o,n){if(!e.palette)return;const i=s(),{_root:r,options:a}=t,{lastColor:l,currentColor:c}=r.preview;t._recalc&&(i.s=100*o,i.v=100-100*n,i.v<0&&(i.v=0),t._updateOutput(\"slider\"));const p=i.toRGBA().toString(0);this.element.style.background=p,this.wrapper.style.background=`\\n linear-gradient(to top, rgba(0, 0, 0, ${i.a}), transparent),\\n linear-gradient(to left, hsla(${i.h}, 100%, 50%, ${i.a}), rgba(255, 255, 255, ${i.a}))\\n `,a.comparison?a.useAsButton||t._lastColor||l.style.setProperty(\"--pcr-color\",p):(r.button.style.setProperty(\"--pcr-color\",p),r.button.classList.remove(\"clear\"));const u=i.toHEXA().toString();for(const{el:e,color:o}of t._swatchColors)e.classList[u===o.toHEXA().toString()?\"add\":\"remove\"](\"pcr-active\");c.style.setProperty(\"--pcr-color\",p)}}),hue:$({lock:\"v\"===i?\"h\":\"v\",element:t._root.hue.picker,wrapper:t._root.hue.slider,onstop:()=>t._emit(\"changestop\",\"slider\",t),onchange(o){if(!e.hue||!e.palette)return;const n=s();t._recalc&&(n.h=360*o),this.element.style.backgroundColor=`hsl(${n.h}, 100%, 50%)`,r.palette.trigger()}}),opacity:$({lock:\"v\"===n?\"h\":\"v\",element:t._root.opacity.picker,wrapper:t._root.opacity.slider,onstop:()=>t._emit(\"changestop\",\"slider\",t),onchange(o){if(!e.opacity||!e.palette)return;const n=s();t._recalc&&(n.a=Math.round(100*o)/100),this.element.style.background=`rgba(0, 0, 0, ${n.a})`,r.palette.trigger()}}),selectable:k({elements:t._root.interaction.options,className:\"active\",onchange(e){t._representation=e.target.getAttribute(\"data-type\").toUpperCase(),t._recalc&&t._updateOutput(\"swatch\")}})};this._components=r}_bindEvents(){const{_root:t,options:e}=this,o=[i(t.interaction.clear,\"click\",(()=>this._clearColor())),i([t.interaction.cancel,t.preview.lastColor],\"click\",(()=>{this.setHSVA(...(this._lastColor||this._color).toHSVA(),!0),this._emit(\"cancel\")})),i(t.interaction.save,\"click\",(()=>{!this.applyColor()&&!e.showAlways&&this.hide()})),i(t.interaction.result,[\"keyup\",\"input\"],(t=>{this.setColor(t.target.value,!0)&&!this._initializingActive&&(this._emit(\"change\",this._color,\"input\",this),this._emit(\"changestop\",\"input\",this)),t.stopImmediatePropagation()})),i(t.interaction.result,[\"focus\",\"blur\"],(t=>{this._recalc=\"blur\"===t.type,this._recalc&&this._updateOutput(null)})),i([t.palette.palette,t.palette.picker,t.hue.slider,t.hue.picker,t.opacity.slider,t.opacity.picker],[\"mousedown\",\"touchstart\"],(()=>this._recalc=!0),{passive:!0})];if(!e.showAlways){const n=e.closeWithKey;o.push(i(t.button,\"click\",(()=>this.isOpen()?this.hide():this.show())),i(document,\"keyup\",(t=>this.isOpen()&&(t.key===n||t.code===n)&&this.hide())),i(document,[\"touchstart\",\"mousedown\"],(e=>{this.isOpen()&&!l(e).some((e=>e===t.app||e===t.button))&&this.hide()}),{capture:!0}))}if(e.adjustableNumbers){const e={rgba:[255,255,255,1],hsva:[360,100,100,1],hsla:[360,100,100,1],cmyk:[100,100,100,100]};p(t.interaction.result,((t,o,n)=>{const i=e[this.getColorRepresentation().toLowerCase()];if(i){const e=i[n],s=t+(e>=100?1e3*o:o);return s<=0?0:Number((s{n.isOpen()&&(e.closeOnScroll&&n.hide(),null===t?(t=setTimeout((()=>t=null),100),requestAnimationFrame((function e(){n._rePositioningPicker(),null!==t&&requestAnimationFrame(e)}))):(clearTimeout(t),t=setTimeout((()=>t=null),100)))}),{capture:!0}))}this._eventBindings=o}_rePositioningPicker(){const{options:t}=this;if(!t.inline){if(!this._nanopop.update({container:document.body.getBoundingClientRect(),position:t.position})){const t=this._root.app,e=t.getBoundingClientRect();t.style.top=(window.innerHeight-e.height)/2+\"px\",t.style.left=(window.innerWidth-e.width)/2+\"px\"}}}_updateOutput(t){const{_root:e,_color:o,options:n}=this;if(e.interaction.type()){const t=`to${e.interaction.type().getAttribute(\"data-type\")}`;e.interaction.result.value=\"function\"==typeof o[t]?o[t]().toString(n.outputPrecision):\"\"}!this._initializingActive&&this._recalc&&this._emit(\"change\",o,t,this)}_clearColor(t=!1){const{_root:e,options:o}=this;o.useAsButton||e.button.style.setProperty(\"--pcr-color\",\"rgba(0, 0, 0, 0.15)\"),e.button.classList.add(\"clear\"),o.showAlways||this.hide(),this._lastColor=null,this._initializingActive||t||(this._emit(\"save\",null),this._emit(\"clear\"))}_parseLocalColor(t){const{values:e,type:o,a:n}=w(t),{lockOpacity:i}=this.options,s=void 0!==n&&1!==n;return e&&3===e.length&&(e[3]=void 0),{values:!e||i&&s?null:e,type:o}}_t(t){return this.options.i18n[t]||L.I18N_DEFAULTS[t]}_emit(t,...e){this._eventListener[t].forEach((t=>t(...e,this)))}on(t,e){return this._eventListener[t].push(e),this}off(t,e){const o=this._eventListener[t]||[],n=o.indexOf(e);return~n&&o.splice(n,1),this}addSwatch(t){const{values:e}=this._parseLocalColor(t);if(e){const{_swatchColors:t,_root:o}=this,n=A(...e),s=r(`
`;\n });\n\n Modal.render({\n title: \"Add An Attribute\",\n content: attributesModal,\n onLoad() {\n document.getElementById('hyfb8mxg0').focus();\n },\n onClose() {\n \n },\n onConfirm() {\n addAttribute(document.getElementById('hyfb8mxg0').value.trim());\n }\n });\n}\nfunction LayerTree() {\n // Function to render each layer recursively\n function renderLayer(layer) {\n const { id, name, children, state } = layer;\n const hasChildren = children && children.length > 0;\n const isVisible = state.visible;\n if (data.shiftKey && data.cmdKey && layer.style === data.stylesTarget) {\n layer.state.selected = true;\n }\n\n // HTML structure for each layer\n const listItem = `\n \n \n \n \n ${hasChildren ? icons.arrowDown : ''}\n \n \n ${isVisible ? icons.eye : icons.eyeSlash}\n \n \n \n ${name}\n \n
\n ${hasChildren ? `` + children.map(child => renderLayer(child)).join('') + '
' : ''}\n \n `;\n\n return listItem;\n }\n\n return project.html.map(layer => renderLayer(layer)).join('');\n}\nfunction Inspector() {\n if (data.editorNavState) return;\n\n // Helper to find common layer tags & attributes\n function findCommonLayerTags(layers) {\n if (layers.length === 0) return [];\n const firstTag = layers[0].tag;\n return layers.every(layer => layer.tag === firstTag) ? layers : [];\n }\n function findCommonAttributes(layers) {\n if (layers.length === 0) return {};\n const attributeCounts = {};\n\n layers.forEach(layer => {\n Object.keys(layer.props || {}).forEach(prop => {\n attributeCounts[prop] = (attributeCounts[prop] || 0) + 1;\n });\n });\n\n return Object.fromEntries(\n Object.entries(attributeCounts)\n .filter(([key, count]) => count === layers.length)\n .map(([key]) => [key, layers[0].props[key]])\n );\n }\n\n // Constants and helper functions\n let buttonItemClass = 'bg-transparent border-0 text-[.6rem] p-0 m-0 h-full capitalize text-left';\n const buttonAddItemClass = 'bg-transparent border-0 p-0 text-right';\n const RenameOrDeleteButtonClass = 'bg-transparent text-[.6rem] p-0 m-0 h-full capitalize text-center p-2 border';\n const selectClass = 'm-0 w-auto rounded-md capitalize text-[.6rem]';\n const selectStyle = 'padding: .5rem; background-image: none;';\n const inputClass = 'w-auto rounded-md normal-case text-[.6rem]';\n const inputStyle = 'height: auto; margin: 0; padding: .4rem;';\n const textareaClass = 'w-auto rounded-md normal-case text-[.6rem] resize-vertical';\n const textareaStyle = 'height: 5rem; margin: 0; padding: .4rem;';\n const mediaClass = 'cursor-pointer w-full my-2';\n\n const languages = {\n 'en': 'English', // English\n 'es': 'Espa\u00F1ol', // Spanish\n 'zh': '\u4E2D\u6587', // Chinese\n 'hi': '\u0939\u093F\u0928\u094D\u0926\u0940', // Hindi\n 'ar': '\u0627\u0644\u0639\u0631\u0628\u064A\u0629', // Arabic\n 'fr': 'Fran\u00E7ais', // French\n 'ru': '\u0420\u0443\u0441\u0441\u043A\u0438\u0439', // Russian\n 'pt': 'Portugu\u00EAs', // Portuguese\n 'de': 'Deutsch', // German\n 'ja': '\u65E5\u672C\u8A9E', // Japanese\n 'ko': '\uD55C\uAD6D\uC5B4', // Korean\n 'it': 'Italiano', // Italian\n 'tr': 'T\u00FCrk\u00E7e', // Turkish\n 'vi': 'Ti\u1EBFng Vi\u1EC7t', // Vietnamese\n 'pl': 'Polski' // Polish\n };\n const sizeOptions = {\n Phones: {\n '320x480': 'iPhone 3GS',\n '375x667': 'iPhone 6/7/8',\n '414x736': 'iPhone 6/7/8 Plus',\n '375x812': 'iPhone X/XS/11 Pro',\n '414x896': 'iPhone XR/XS Max/11/11 Pro Max',\n '360x640': 'Samsung Galaxy S5',\n '360x740': 'Samsung Galaxy S8+',\n '1440x3200': 'Samsung Galaxy S21 Ultra',\n '1080x2340': 'Google Pixel 5',\n '1080x2400': 'OnePlus 8 Pro',\n '1440x3200': 'Xiaomi Mi 11 Ultra',\n '1644x3840': 'Sony Xperia 1 III'\n },\n Tablets: {\n '2048x2732': 'iPad Pro 12.9\" (3rd/4th Gen)',\n '2388x1668': 'iPad Pro 11\" (1st/2nd/3rd Gen)',\n '2736x1824': 'Microsoft Surface Pro 7',\n '2800x1752': 'Samsung Galaxy Tab S7+',\n '2560x1600': 'Huawei MatePad Pro',\n '2000x1200': 'Lenovo Tab P11 Pro',\n '1920x1200': 'Amazon Fire HD 10',\n '1536x2048': 'iPad Air (3rd Gen)',\n '1620x2160': 'iPad Air (4th Gen)',\n '1620x2160': 'iPad 10.2\" (8th Gen)',\n '1668x2224': 'iPad Pro 11\" (2021)'\n },\n Desktops: {\n '3840x2160': '4K UHD (3840x2160)',\n '2560x1440': 'WQHD (2560x1440)',\n '1920x1080': 'Full HD (1920x1080)',\n '1366x768': 'Laptop (1366x768)',\n '3440x1440': 'UltraWide QHD (3440x1440)',\n '5120x2880': '5K Retina (5120x2880)',\n '1280x800': 'MacBook (1280x800)',\n '2560x1600': 'MacBook Pro (2560x1600)',\n '2880x1800': 'MacBook Pro Retina (2880x1800)'\n }\n };\n const booleanAttributes = [\n \"autofocus\", \"autoplay\", \"checked\", \"controls\", \"default\", \"defer\", \"disabled\", \n \"formnovalidate\", \"hidden\", \"loop\", \"multiple\", \"muted\", \"novalidate\", \"open\", \n \"contenteditable\", \"readonly\", \"required\", \"reversed\", \"scoped\", \"seamless\", \"selected\"\n ];\n const inputTypes = [\n \"button\", \"checkbox\", \"color\", \"date\", \"datetime-local\", \"email\", \"file\", \"hidden\",\n \"image\", \"month\", \"number\", \"password\", \"radio\", \"range\", \"reset\", \"search\", \"submit\",\n \"tel\", \"text\", \"time\", \"url\", \"week\"\n ];\n const numberAttributes = [\"max\", \"maxlength\", \"min\", \"minlength\", \"multiple\", \"range\", \"size\", \"step\"];\n const stringAttributes = [\"accept\", \"acceptCharset\", \"accesskey\", \"action\", \"align\", \"alt\", \"autocomplete\", \"form\", \"list\", \"pattern\", \"placeholder\", \"src\", \"tabindex\", \"title\", \"type\", \"usemap\", \"value\"];\n const selectedLayers = data.selectedLayerIds.map(id => findLayerById(id, project.html).layer).filter(layer => layer);\n const commonLayerTag = findCommonLayerTags(selectedLayers);\n const commonAttributes = findCommonAttributes(selectedLayers);\n const cssFixedValueProperties = data.cssFixedValueProperties;\n const cssRangedValueProperties = data.cssRangedValueProperties;\n const boxElements = data.boxElements;\n const textElements = data.textElements;\n const breakElements = data.breakElements;\n\n // Simplify rendering functions\n function renderBooleanAttribute(name, value) {\n return `\n ${name}\n ${value ? 'Yes' : 'No'}\n `;\n }\n function renderInput(name, type, value, min, max, step) {\n return `\n ${name}\n \n `;\n }\n function renderTextarea(name, value) {\n return `\n ${name}\n \n `;\n }\n \n // Functions to generate HTML sections\n const generatePreviewSize = () => `\n \n \n \n ${icons.rotate}\n \n
\n\n \n language\n \n
\n `;\n\n const processStyles = (stylesObject, selectorPrefix, key, detect = null) => {\n let styles = '';\n\n // Regular expression to detect color values\n const colorRegex = /^(#[0-9a-f]{3,6}|rgb?(.+)|hsl?(.+)|hsv?(.+))$/i;\n \n // List of properties that should use a textarea\n const complexProperties = [\n 'background', 'background-image', 'box-shadow', 'text-shadow',\n 'border', 'border-radius', 'border-image', 'filter', 'transform'\n ];\n\n Object.keys(stylesObject).forEach(prop => {\n let value = stylesObject[prop];\n let selector = `${selectorPrefix}['${prop}']`;\n\n // Check if the property has fixed values\n const predefinedValues = cssFixedValueProperties[prop];\n if (predefinedValues) {\n let options = predefinedValues.map(val => \n ``\n ).join('');\n\n styles += `\n \n ${prop}\n \n `;\n } else if (cssRangedValueProperties[prop]) {\n const { min, max, step } = cssRangedValueProperties[prop];\n \n // Ensure valueParts and remainingParts are arrays, even if value is null or doesn't match\n const valueParts = value ? value.match(/-?\\d*\\.?\\d+([a-z%]+|)/g) || [] : [];\n const remainingParts = value ? value.split(/-?\\d*\\.?\\d+[a-z%]*/g).filter(Boolean) || [] : [];\n \n // Determine the appropriate grid column class based on the presence of value parts\n const gridColsClass = valueParts.length > 0 ? 'grid-cols-2' : 'grid-cols-1';\n \n styles += `\n \n ${prop}\n \n `;\n \n valueParts.forEach((part, index) => {\n const numericValue = parseFloat(part);\n const unitMatch = part.match(/[a-zA-Z%]+/);\n const unit = unitMatch ? unitMatch[0] : '';\n \n // Define valid units based on property\n let validUnits;\n switch (prop) {\n case 'scale':\n case 'rotate':\n case 'translate':\n case 'perspective':\n case 'skew':\n validUnits = ['', 'deg', 'rad']; // Example units for transform properties\n break;\n case 'animation-duration':\n case 'transition-duration':\n validUnits = ['', 'ms', 's']; // Example units for duration properties\n break;\n default:\n validUnits = ['', 'px', '%', 'rem', 'em', 'vh', 'lvh', 'svh', 'dvh', 'vw', 'lvw', 'svw', 'dvw']; // Default units\n break;\n }\n \n const selectElement = ``;\n \n const rangeElement = ` 0 ? ` + ' ' + '${remainingParts.join(' ')}'` : ''};\"\n onfocus=\"saveState();\" onblur=\"saveState();\">`;\n \n styles += `\n 0 ? ` + ' ' + '${remainingParts.join(' ')}'` : ''};\"\n onfocus=\"saveState();\" onblur=\"saveState();\">\n ${prop === 'opacity' || prop === 'z-index' ? rangeElement : selectElement}`;\n });\n \n // Add a backup text input for cases where units aren't defined\n if (remainingParts.length > 0 || valueParts.length === 0) {\n styles += `\n \n `;\n }\n \n styles += `
`;\n } else if (complexProperties.includes(prop)) {\n // Use a textarea for complex multi-line properties\n styles += `\n \n ${prop}\n \n `;\n } else {\n // Check if the property is a color property\n const isColorProperty = colorRegex.test(value) || value === null;\n const fallbackColor = isColorProperty && value === null ? '#000000' : value;\n\n styles += `\n \n ${prop}\n \n `;\n }\n });\n\n return styles;\n };\n\n const generateRootVariablesSection = () => {\n let styles = '';\n \n // Regular expression to detect color values\n const colorRegex = /^(#[0-9a-f]{3,6}|rgb?(.+)|hsl?(.+)|hsv?(.+))$/i;\n\n // Iterate over each root variable\n Object.keys(project.css.rootVariables).forEach(key => {\n const value = project.css.rootVariables[key];\n const selector = `project.css.rootVariables['${key}']`;\n\n // Determine input type based on value\n const isColor = colorRegex.test(value);\n const isNumeric = !isNaN(parseFloat(value)) && isFinite(value);\n\n let inputType = 'text';\n let inputStyle = 'height: auto; margin: 0; padding: .4rem;';\n\n if (isNumeric) inputType = 'number';\n\n // Use processStyles function to generate styles for root variables\n styles += `\n \n ${key}\n \n \n `;\n });\n\n return `\n \n
\n
\n root css variables\n \n
\n \n \n
\n
\n ${styles}\n
\n
\n `;\n };\n\n const generateStylesSection = () => {\n if (!commonLayerTag) data.stylesTarget = null;\n let targets = null;\n let styles = '';\n let activeStyle = null;\n if (commonLayerTag) {\n Object.keys(commonLayerTag).forEach(layerKey => {\n const layer = commonLayerTag[layerKey];\n });\n }\n \n let obj = null;\n if (data.breakpointKey) {\n obj = project.css.breakpoints[`${data.breakpointKey}px`];\n } else {\n obj = project.css.styles;\n }\n\n // Render targets within style\n let dropdown = ``;\n\n let buttonClass = '';\n Object.keys(obj).forEach(key => {\n if (data.stylesTarget && data.stylesTarget === key) {\n buttonClass = buttonItemClass.split('bg-transparent border-0').join('');\n activeStyle = key;\n } else {\n buttonClass = 'bg-transparent text-[.6rem] p-0 m-0 h-full capitalize text-left';\n activeStyle = null;\n }\n\n styles += `${key}`;\n });\n\n return `\n
\n
\n styles\n \n
\n \n \n
\n
\n ${data.stylesTarget ? `
\n ${dropdown}\n
` : ''}\n
\n ${styles}\n
\n ${data.stylesTarget ? `
\n \n rename\n \n \n delete\n \n
` : ''}\n ${data.stylesTarget ? `
\n \n duplicate\n \n \n de-select\n \n
` : ''}\n
\n
`;\n };\n\n const generatePseudosSection = () => {\n if (!commonLayerTag) data.stylesTarget = null;\n let styles = '';\n let selector = '';\n let activeStyle = null;\n if (commonLayerTag) {\n Object.keys(commonLayerTag).forEach(layerKey => {\n const layer = commonLayerTag[layerKey];\n if (!data.stylesTarget) {\n data.stylesTarget = layer.style;\n }\n });\n }\n\n // Target specific pseudo style\n if (data.stylesTarget && data.stylesPropTarget === \"pseudos\") {\n if (project.css.styles[data.stylesTarget].pseudos) {\n Object.keys(project.css.styles[data.stylesTarget].pseudos).forEach(index => {\n selector = project.css.styles[data.stylesTarget].pseudos[index].selector;\n if (data.pseudosSelector === selector) {\n buttonClass = buttonItemClass.split('bg-transparent border-0').join('');\n activeStyle = true;\n data.pseudosSelectorIndex = index;\n } else {\n buttonClass = 'bg-transparent text-[.6rem] p-0 m-0 h-full text-left';\n activeStyle = null;\n }\n styles += `${selector}`;\n });\n }\n }\n\n return `\n
\n
\n pseudos\n \n
\n \n \n
\n
\n ${data.stylesTarget ? `
\n ${styles}\n
` : ''}\n ${data.pseudosSelector && data.stylesTarget ? `\n
\n \n Rename\n \n \n Delete\n \n
\n ` : ''}\n ${data.pseudosSelector && data.stylesTarget ? `\n
\n \n De-select\n \n
\n ` : ''}\n
\n
`;\n };\n\n const generateStylePropertiesSection = () => {\n let styles = '';\n let styleKey = null;\n\n if (commonLayerTag) {\n Object.keys(commonLayerTag).forEach(layerKey => {\n const layer = commonLayerTag[layerKey];\n styleKey = layer.style;\n });\n }\n\n let obj = project.css.styles;\n const detectStylesPropTarget = ['base', 'pseudos'];\n if (detectStylesPropTarget.includes(data.stylesPropTarget)) {\n Object.keys(obj).forEach(key => {\n if (styleKey === key || data.stylesTarget == key) {\n if (data.stylesPropTarget === 'pseudos') {\n const index = data.pseudosSelectorIndex;\n if (data.pseudosSelector) {\n if (obj[key].pseudos[index].styles) {\n styles += processStyles(obj[key].pseudos[index].styles, `project.css.styles['${key}'].pseudos['${index}'].styles`, key);\n }\n }\n } else {\n if (obj[key][data.stylesPropTarget]) {\n styles += processStyles(obj[key][data.stylesPropTarget], `project.css.styles['${key}']['${data.stylesPropTarget}']`, key);\n }\n }\n }\n });\n }\n\n let stylesObj = 'project.css.styles[data.stylesTarget][data.stylesPropTarget]';\n if (data.stylesPropTarget === \"pseudos\") {\n stylesObj = 'project.css.styles[data.stylesTarget][data.stylesPropTarget][data.pseudosSelectorIndex].styles';\n }\n\n return `\n
\n
\n style properties\n \n
\n \n \n
\n
\n ${styles}\n
\n
`;\n };\n\n const generateBreakpointsSection = () => {\n if (!commonLayerTag) data.stylesTarget = null;\n let styles = '';\n let activeStyle = null;\n if (commonLayerTag) {\n Object.keys(commonLayerTag).forEach(layerKey => {\n const layer = commonLayerTag[layerKey];\n if (!data.stylesTarget) {\n data.stylesTarget = layer.style;\n }\n });\n }\n\n let buttonClass = '';\n if (data.stylesTarget && project.css.breakpoints) {\n Object.keys(project.css.breakpoints).forEach(key => {\n if (data.breakpointKey === key.split('px').join('')) {\n buttonClass = buttonItemClass.split('bg-transparent border-0').join('');\n activeStyle = key;\n } else {\n buttonClass = 'bg-transparent text-[.6rem] p-0 m-0 h-full capitalize text-center';\n activeStyle = null;\n }\n styles += `${key.split('px').join('')}`;\n });\n }\n\n return `\n
\n
\n breakpoints\n \n
\n \n \n
\n
\n ${data.stylesTarget && project.css.breakpoints ? `
\n ${styles}\n
` : ''}\n ${data.breakpointKey && data.stylesTarget && project.css.breakpoints ? `
\n \n rename\n \n \n delete\n \n
` : ''}\n ${data.breakpointKey && data.stylesTarget && project.css.breakpoints ? `
\n \n de-select\n \n
` : ''}\n
\n
`;\n };\n\n const generateBreakpointStylesSection = () => {\n if (!data.breakpointKey) return;\n let styles = '';\n let styleKey = null;\n\n if (commonLayerTag) {\n Object.keys(commonLayerTag).forEach(layerKey => {\n const layer = commonLayerTag[layerKey];\n styleKey = layer.style;\n });\n }\n\n let obj = project.css.breakpoints[`${data.breakpointKey}px`];\n\n const detectStylesPropTarget = ['base', 'pseudos'];\n if (detectStylesPropTarget.includes(data.stylesPropTarget)) {\n Object.keys(obj).forEach(key => {\n if (styleKey === key || data.stylesTarget == key) {\n if (data.stylesPropTarget === 'pseudos') {\n const index = data.pseudosSelectorIndex;\n if (data.pseudosSelector) {\n if (obj[key].pseudos[index].styles) {\n styles += processStyles(obj[key].pseudos[index].styles, `project.css.breakpoints['${data.breakpointKey}px']['${key}'].pseudos['${index}'].styles`, key, 'breakpoints');\n }\n }\n } else {\n if (obj[key][data.stylesPropTarget]) {\n styles += processStyles(obj[key][data.stylesPropTarget], `project.css.breakpoints['${data.breakpointKey}px']['${key}']['${data.stylesPropTarget}']`, key, 'breakpoints');\n }\n }\n }\n });\n }\n\n return `\n
\n
\n breakpoint styles\n \n
\n \n \n
\n
\n ${styles}\n
\n
`;\n };\n\n const generateAnimationsSection = () => {\n if (!commonLayerTag) data.stylesTarget = null;\n let styles = '';\n let activeStyle = null;\n if (commonLayerTag) {\n Object.keys(commonLayerTag).forEach(layerKey => {\n const layer = commonLayerTag[layerKey];\n if (!data.stylesTarget) {\n data.stylesTarget = layer.style;\n }\n });\n }\n\n if (data.stylesTarget && project.css.animations) {\n Object.keys(project.css.animations).forEach(key => {\n if (data.animationTarget === key) {\n buttonClass = buttonItemClass.split('bg-transparent border-0').join('');\n buttonClass = buttonClass.split('capitalize').join('normal-case');\n activeStyle = key;\n } else {\n buttonClass = 'bg-transparent text-[.6rem] p-0 m-0 h-full normal-case text-center';\n activeStyle = null;\n }\n styles += `${key}`;\n });\n }\n\n return `\n
\n
\n animations\n \n
\n \n \n
\n
\n ${data.stylesTarget && project.css.animations ? `
\n ${styles}\n
` : ''}\n ${data.animationTarget && project.css.animations ? `
\n \n rename\n \n \n delete\n \n
` : ''}\n ${data.animationTarget && project.css.animations ? `
\n \n de-select\n \n
` : ''}\n
\n
`;\n };\n\n const generateAnimationPropertySection = () => {\n if (!commonLayerTag) data.stylesTarget = null;\n let content = '';\n let keyframes = '';\n let activeStyle = null;\n if (commonLayerTag) {\n Object.keys(commonLayerTag).forEach(layerKey => {\n const layer = commonLayerTag[layerKey];\n if (!data.stylesTarget) {\n data.stylesTarget = layer.style;\n }\n });\n }\n\n // Generate keyframes buttons\n if (data.animationTarget && project.css.animations && project.css.animations[data.animationTarget].keyframes) {\n Object.keys(project.css.animations[data.animationTarget].keyframes).forEach(key => {\n let buttonClass = '';\n if (data.animationKeyframe === key) {\n buttonClass = buttonItemClass.split('bg-transparent border-0').join('');\n isActive = true;\n } else {\n buttonClass = 'bg-transparent text-[.6rem] p-0 m-0 h-full capitalize text-center';\n isActive = null;\n }\n \n keyframes += `${key}`;\n });\n }\n\n // Generate the styles for the active keyframe\n if (data.animationKeyframe && data.animationTarget && project.css.animations && project.css.animations[data.animationTarget].keyframes) {\n const activeKeyframe = project.css.animations[data.animationTarget].keyframes[data.animationKeyframe];\n content += processStyles(activeKeyframe, `project.css.animations['${data.animationTarget}'].keyframes['${data.animationKeyframe}']`, data.animationKeyframe, 'animations');\n }\n\n return `\n
\n
\n animation keyframes\n \n
\n \n \n
\n
\n ${data.animationTarget && project.css.animations ? `
\n ${keyframes}\n
` : ''}\n ${data.animationKeyframe && project.css.animations && project.css.animations[data.animationTarget].keyframes ? `
\n ${content}\n
` : ''}\n ${data.animationKeyframe && project.css.animations && project.css.animations[data.animationTarget].keyframes ? `
\n \n rename\n \n \n delete\n \n \n ${icons.plus}\n \n \n de-select\n \n
` : ''}\n
\n
`;\n };\n\n const generateAttributesSection = () => {\n if (selectedLayers.length === 0) return '';\n \n // Display common attributes\n let attributes = \"\",\n attributeTag = \"\",\n svgImage = \"\";\n if (commonLayerTag || selectedLayers.length === 1) {\n Object.keys(commonLayerTag).forEach(layerKey => {\n attributeTag = \"\";\n const layer = commonLayerTag[layerKey];\n const tag = layer.tag;\n \n // block name\n attributeTag += `\n Block Name\n \n `;\n \n // style reference\n attributeTag += `\n Style Ref\n \n `;\n \n // Determine block type and render the appropriate options\n let options = \"\";\n if (boxElements.includes(tag)) {\n options = boxElements.map(element => \n ``\n ).join('');\n } else if (textElements.includes(tag)) {\n options = textElements.map(element => \n ``\n ).join('');\n } else if (breakElements.includes(tag)) {\n options = breakElements.map(element => \n ``\n ).join('');\n } else {\n options = ``;\n }\n \n if (options) {\n attributeTag += `\n tag\n `;\n }\n \n if (tag === \"svg\" && selectedLayers.length === 1) {\n const elm = document.createElement(\"template\");\n elm.innerHTML = json2html(layer);\n const element = elm.content.firstElementChild;\n \n if (element) {\n if (element.hasAttribute(\"style\")) element.removeAttribute('style');\n element.removeAttribute(\"width\");\n element.removeAttribute(\"height\");\n element.setAttribute(\"class\", mediaClass);\n element.setAttribute(\"onclick\", `updateSvgMedia('${layer.id}', 'svg')`);\n \n svgImage += `\n svg\n \n ${element.outerHTML}\n
\n `;\n }\n \n elm.remove();\n }\n });\n }\n \n if (Object.keys(commonAttributes).length > 0 || selectedLayers.length === 1) {\n svgImage ? attributes = svgImage + \"\" : attributes = \"\";\n const layer = selectedLayers[0];\n const tag = layer.tag;\n\n if (tag === \"audio\") {\n attributes += `\n Replace Audio\n \n
\n
\n `;\n }\n \n if (layer.props) {\n // Generate the attributes section dynamically based on layer\n Object.keys(commonAttributes).forEach(propKey => {\n let name = propKey;\n const value = commonAttributes[propKey];\n \n textAreaAttributes = [\"on\", '@', \"x-\", \":\"];\n const lowerPropKey = propKey.toLowerCase();\n \n // Handle different types of attributes\n if (booleanAttributes.includes(name)) {\n attributes += renderBooleanAttribute(propKey, commonAttributes[propKey]);\n } else if (lowerPropKey === \"style\" || lowerPropKey === \"class\") {\n attributes += renderTextarea(propKey, commonAttributes[propKey]);\n } else if (textAreaAttributes.some(attr => lowerPropKey.startsWith(attr))) {\n attributes += renderTextarea(propKey, commonAttributes[propKey]);\n } else if (tag === \"input\") {\n if (name === \"required\") {\n attributes += renderBooleanAttribute(propKey, commonAttributes[propKey]);\n }\n \n let nodeType = \"text\";\n for (let numAttr of numberAttributes) {\n if (name === numAttr) {\n nodeType = \"number\";\n }\n }\n for (let string of stringAttributes) {\n if (name === string) {\n nodeType = \"text\";\n }\n }\n if (name === \"value\") {\n for (let inputType of inputTypes) {\n if (layer.props.type) {\n if (layer.props.type === inputType) {\n nodeType = layer.props.type.toLowerCase();\n }\n if (layer.props.type === \"range\") {\n nodeType = \"number\";\n }\n }\n }\n }\n if (name === \"type\") {\n let options = '';\n for (let string of inputTypes) {\n options += ``;\n }\n \n attributes += `\n ${propKey}\n \n `;\n } else {\n attributes += renderInput(propKey, 'text', commonAttributes[propKey]);\n }\n } else if (tag === \"button\") {\n if (name === \"type\") {\n attributes += `\n ${propKey}\n \n `;\n } else if (name === \"role\") {\n attributes += renderBooleanAttribute(propKey, commonAttributes[propKey]);\n } else {\n attributes += renderInput(propKey, 'text', commonAttributes[propKey]);\n }\n } else if (tag === \"a\") {\n if (name === \"target\") {\n attributes += `\n ${propKey}\n \n `;\n } else {\n attributes += renderInput(propKey, 'text', commonAttributes[propKey]);\n }\n } else if (tag === \"img\") {\n let numTypes = [\"width\", \"height\"];\n if (numTypes.includes(name)) {\n let type = \"text\";\n for (let numType of numTypes) {\n if (name === numType) {\n type = \"number\";\n }\n }\n attributes += renderInput(propKey, 'text', commonAttributes[propKey]);\n } else if (name === \"src\") {\n attributes += `\n ${propKey}\n \n
\n
\n
\n `;\n } else {\n attributes += renderInput(propKey, 'text', commonAttributes[propKey]);\n }\n } else if (tag === \"svg\") {\n let numTypes = [\"stroke-width\"];\n let type = \"text\";\n for (let numType of numTypes) {\n if (name === numType) {\n type = \"number\";\n }\n }\n attributes += renderInput(propKey, 'text', commonAttributes[propKey]);\n } else if (tag === \"form\") {\n if (name === \"method\") {\n attributes += `\n ${propKey}\n \n `;\n } else {\n attributes += renderInput(propKey, 'text', commonAttributes[propKey]);\n }\n } else {\n attributes += renderInput(propKey, 'text', commonAttributes[propKey]);\n }\n });\n }\n }\n\n if (Object.keys(commonAttributes).length > 0 || selectedLayers.length >= 0) {\n const layer = selectedLayers[0];\n const tag = layer.tag;\n\n if (\"text\" in layer || layer.text) {\n if (tag === \"style\" || tag === \"script\") {\n attributes += `\n text\n \n `;\n } else {\n attributes += `\n text\n \n `;\n }\n }\n }\n \n if (selectedLayers.length > 0) {\n return `\n
\n
\n attributes\n \n
\n \n \n \n \n
\n
\n ${attributeTag + attributes}\n
\n
`;\n }\n }\n\n // Combine all sections\n const inspectorHtml = `\n \n ${generatePreviewSize()}\n ${generateRootVariablesSection()}\n ${generateStylesSection()}\n ${data.stylesTarget && data.stylesPropTarget === \"pseudos\" ? generatePseudosSection() : ''}\n ${data.stylesTarget ? generateStylePropertiesSection() : ''}\n ${data.stylesTarget ? generateBreakpointsSection() : ''}\n ${data.breakpointKey ? generateBreakpointStylesSection() : ''}\n ${data.stylesTarget ? generateAnimationsSection() : ''}\n ${data.animationTarget ? generateAnimationPropertySection() : ''}\n ${generateAttributesSection()}\n
\n `;\n\n return inspectorHtml;\n}\nfunction editorNav() {\n const buttonClass = \"border-0 bg-transparent py-1\";\n\n return `\n ${icons.trash}\n \n \n ${icons.clone}\n \n \n ${icons.cut}\n \n \n ${icons.copy}\n \n \n ${icons.paste}\n `;\n}\nwindow.Modal = {\n render({\n large,\n title = \"Are you sure you want to proceed?\",\n content,\n CloseLabel,\n ConfirmLabel,\n onLoad,\n onClose,\n onConfirm\n }) {\n // if (!options) return false;\n const hClass = \"text-lg font-thin m-0\";\n const buttonClass = \"text-xs w-auto px-3 py-2 m-0 capitalize rounded-md\";\n const svgClass = \"w-3\";\n const times = `\n \n `;\n\n const html = `\n \n ${title}
\n \n ${times}\n \n \n \n ${content ? content : ''}\n \n \n `;\n\n const modal = document.createElement('dialog');\n modal.open = true;\n modal.innerHTML = html;\n\n document.body.appendChild(modal);\n if (onLoad && typeof onLoad === 'function') {\n onLoad();\n }\n\n const timesBtn = modal.querySelector('header button');\n const closeBtn = modal.querySelector('footer button:first-child');\n const confirmBtn = modal.querySelector('footer button:last-child');\n\n // Confirm handler function\n timesBtn.onclick = function() {\n if (onClose && typeof onClose === 'function') {\n onClose();\n }\n document.body.removeChild(modal);\n }\n closeBtn.onclick = function() {\n if (onClose && typeof onClose === 'function') {\n onClose();\n }\n document.body.removeChild(modal);\n }\n confirmBtn.onclick = function() {\n if (onConfirm && typeof onConfirm === 'function') {\n onConfirm();\n }\n document.body.removeChild(modal);\n }\n }\n}\nwindow.Blocks = () => {\n const btnClass = `bg-transparent p-4 text-xs cursor-pointer capitalize`;\n\n let blockItem = '', componentItem = '';\n\n data.blocks.items.forEach((block, index) => {\n blockItem += `\n \n ${block.type}\n `;\n });\n\n project.components.forEach((component, index) => {\n componentItem += `\n \n \n ${component.name}\n \n\n \n ${icons.trash}\n \n
`;\n });\n\n let modalContent = `\n \n \n \n \n \n ${data.blocks.name}\n
\n \n ${blockItem}\n\n \n Custom\n \n
\n \n
\n \n \n \n \n \n \n Components\n
\n \n ${componentItem}\n\n \n ${icons.plus}\n \n
\n \n
\n `;\n\n // Render the modal\n Modal.render({\n title: `Add A Block`,\n content: modalContent\n });\n}\nwindow.App = {\n initialRender: true,\n render(container) {\n if (data.doNotRender) return;\n const buttonClass = \"border-0 bg-transparent py-1\";\n // Calculate zoom transform based on viewport size and iframe size\n const size = data.selectedSize;\n let viewportWidth, viewportHeight;\n const previewElm = document.getElementById('previewElm');\n if (document.getElementById('previewElm')) {\n viewportWidth = previewElm.clientWidth;\n viewportHeight = previewElm.clientHeight;\n }\n let [width, height] = size.split('x').map(Number);\n \n const html = `\n \n
\n ${LeftMenubar()}\n
\n \n \n
\n
\n
\n
\n
\n
\n
\n 0 ? '' : 'disabled=\"true\"'}>\n ${icons.undo}\n \n \n ctrl\n \n \n ${icons.shift}\n \n
\n
\n ${editorNav()}\n
\n
\n \n ${icons.commandKey}\n \n \n ${icons.redo}\n \n
\n
\n
\n
\n
\n
\n
\n
\n
\n 0 ? '' : 'disabled=\"true\"'}>\n ${icons.undo}\n \n
\n
\n \n ctrl\n \n \n ${icons.shift}\n \n \n ${icons.redo}\n \n
\n
\n
\n
\n
\n\n
\n
\n
\n
\n
\n \n\n \n ${data.iframeSize}\n \n
\n
\n
\n
\n
\n 0 ? '' : 'disabled=\"true\"'}>\n ${icons.undo}\n \n
\n \n
\n ${editorNav()}\n
\n\n
\n ${icons.redo}\n \n
\n
\n
\n
\n
\n \n
\n
\n ${Inspector()}\n
\n
\n
\n\n ${Menu()}\n ${Settings()}\n
`;\n \n const element = document.querySelector(container);\n if (!element) return;\n\n // Create a new temporary element to compare\n const parser = new DOMParser();\n const doc = parser.parseFromString(html, 'text/html');\n if (doc.body.innerHTML.trim() === html.trim()) return;\n if (App.initialRender) {\n element.innerHTML = html;\n renderPreview(true);\n App.initialRender = false;\n return false;\n }\n\n const oldPickers = document.querySelectorAll('.pcr-app');\n if (oldPickers) oldPickers.forEach(picker => picker.remove());\n\n // Compare and update only the changed parts\n const currentDoc = element.firstElementChild;\n const newDoc = doc.body.firstElementChild;\n diffNodes(currentDoc, newDoc);\n\n // Select all elements with the data-color-picker attribute\n const pickers = document.querySelectorAll('[data-iscolor]');\n\n if (pickers) {\n pickers.forEach((picker, index) => {\n // Extract the initial color value from the data-iscolor attribute\n const initialColor = picker.getAttribute('data-iscolor');\n \n const pickr = Pickr.create({\n el: picker,\n theme: 'nano', // or 'monolith', or 'nano'\n default: initialColor,\n inline: true,\n components: {\n // Main components\n preview: true,\n opacity: true,\n hue: true,\n \n // Input / output Options\n interaction: {\n input: true\n }\n }\n });\n\n // Set the initial color\n pickr.setColor(initialColor);\n \n // Update color display and state on color change\n pickr.on('show', () => {\n data.doNotRender = true;\n })\n .on('change', color => {\n const colorString = color.toHEXA().toString();\n \n // Get the oninput attribute value\n const onInputCode = pickers[index].getAttribute('oninput');\n \n if (onInputCode) {\n // Replace 'this.value' with the actual color string\n const updatedCode = onInputCode.replace(/this.value/g, `\"${colorString}\"`);\n \n // Create a new function using the Function constructor and execute it\n const func = new Function(updatedCode);\n func(); // Execute the dynamically created function\n }\n \n // Apply color\n pickr.applyColor();\n })\n .on('hide', () => {\n data.doNotRender = null;\n pickr.applyColor();\n App.render('#app');\n })\n });\n }\n }\n}\n\n// Inspector functions\nwindow.modifyRootVariable = id => {\n let modalContent = `\n
\n
\n
\n
Value:
\n
button:last-child').onclick();\n }\n \">\n
\n
\n
\n
\n button:last-child').onclick();\n \">\n Delete Variable\n \n
\n
`;\n\n Modal.render({\n title: `Are you sure you want to rename the \"${id.substring(2)}\" root variable?`,\n content: modalContent,\n onLoad() {\n document.getElementById('m7t85jokv').focus();\n document.getElementById('m7t85jokv').select();\n },\n onConfirm() {\n let newValue = document.getElementById('hbo1luvti').value;\n let name = document.getElementById('m7t85jokv').value;\n // Convert the first character to lowercase\n name = name.charAt(0).toLowerCase() + name.slice(1);\n\n if (name) {\n if (!name.startsWith('--')) {\n name = '--' + name;\n }\n // Convert the first character after '--' to lowercase\n let newName = name.substring(0, 2) + name.charAt(2).toLowerCase() + name.slice(3);\n \n if (project.css.rootVariables[newName]) {\n // Update existsing value\n project.css.rootVariables[newName] = newValue;\n App.render('#app');\n } else {\n saveState();\n\n // Clone the style object\n project.css.rootVariables[newName] = JSON.parse(JSON.stringify(project.css.rootVariables[`${id}`]));\n \n // Update the variable with the new value\n project.css.rootVariables[newName] = newValue; // Assign the new value to the variable\n\n // Now delete the old style object\n delete project.css.rootVariables[`${id}`];\n localStorage.setItem('Polyrise', JSON.stringify(project));\n\n saveState();\n }\n } else {\n Modal.render({\n title: `Unable to rename variable`,\n content: \"No value detected!\"\n });\n }\n }\n });\n}\nwindow.addStyle = () => {\n let modalContent = `\n button:last-child').onclick();\n }\n \">\n `;\n \n // Render the modal\n Modal.render({\n title: `Add A Style`,\n content: modalContent,\n onLoad() {\n document.getElementById('vvrh9nxwk').focus();\n document.getElementById('vvrh9nxwk').select();\n },\n onConfirm() {\n let value = document.getElementById('vvrh9nxwk').value;\n if (value) {\n // Convert the first character to lowercase\n value = value.charAt(0).toLowerCase() + value.slice(1);\n \n let obj = project.css.styles;\n if (data.breakpointKey && data.stylesTarget) {\n obj = project.css.breakpoints[`${data.breakpointKey}px`];\n }\n\n if (obj[`${value}`]) {\n Modal.render({\n title: `Unable to add style!`,\n content: \"Style already exists!\"\n });\n } else {\n obj[value] = {\n \"base\": {},\n \"pseudos\": []\n };\n }\n } else {\n Modal.render({\n title: `Unable to add style`,\n content: \"No value detected!\"\n });\n }\n }\n });\n}\nwindow.duplicateStyle = () => {\n let modalContent = `\n button:last-child').onclick();\n }\n \">\n `;\n \n // Render the modal\n Modal.render({\n title: `Name your style`,\n content: modalContent,\n onLoad() {\n document.getElementById('vvrh9nxwk').focus();\n document.getElementById('vvrh9nxwk').select();\n },\n onConfirm() {\n let value = document.getElementById('vvrh9nxwk').value;\n if (value) {\n // Convert the first character to lowercase\n value = value.charAt(0).toLowerCase() + value.slice(1);\n \n let obj = project.css.styles;\n if (data.breakpointKey && data.stylesTarget) {\n obj = project.css.breakpoints[`${data.breakpointKey}px`];\n }\n\n if (obj[`${value}`]) {\n Modal.render({\n title: `Unable to add style!`,\n content: \"Style already exists!\"\n });\n } else {\n obj[value] = obj[data.stylesTarget];\n }\n } else {\n Modal.render({\n title: `Unable to add style`,\n content: \"No value detected!\"\n });\n }\n }\n });\n}\nwindow.addStylePropModal = (id, obj) => {\n // Define default values for each property type\n const defaultValues = data.defaultValues;\n\n // Sort and categorize properties\n const rootVariables = Object.keys(project.css.rootVariables).sort();\n const fixedValueProperties = Object.keys(data.cssFixedValueProperties).sort();\n const rangedValueProperties = Object.keys(data.cssRangedValueProperties).sort();\n\n // Generate options grouped by category\n const rootVariableOptions = rootVariables.map(prop => `\n \n `).join('');\n\n const fixedValuePropertyOptions = fixedValueProperties.map(prop => `\n \n `).join('');\n\n const rangedValuePropertyOptions = rangedValueProperties.map(prop => `\n \n `).join('');\n\n const modalContent = `\n \n
\n
\n
\n
\n \n \n
\n
\n
button:last-child').onclick();\n }\n \"/>\n
\n ${data.canUseQuickCommands ? `\n You can also apply styles using
tailwind classes as quick commands!\n
` : ''}`;\n\n Modal.render({\n title: `Add New Style to \"${id}\"`,\n content: modalContent,\n onLoad() {\n document.getElementById('ool1zyibs').focus();\n const propertyTypeSelect = document.getElementById('property-type');\n const unitSelect = document.getElementById('property-unit');\n const unitSection = document.getElementById('unit-section');\n\n function updatePropertyDetails(selectedType) {\n unitSelect.innerHTML = '';\n\n if (data.cssRangedValueProperties[selectedType]) {\n unitSection.style.display = noUnitProperties.includes(selectedType) ? 'none' : 'block';\n\n if (selectedType.startsWith('animation')) {\n unitOptions.animation.forEach(unit => {\n unitSelect.innerHTML += ``;\n });\n } else if (transformUnits.includes(selectedType)) {\n unitOptions.transform.forEach(unit => {\n unitSelect.innerHTML += ``;\n });\n } else {\n unitOptions.default.forEach(unit => {\n unitSelect.innerHTML += ``;\n });\n }\n } else if (data.cssFixedValueProperties[selectedType]) {\n unitSection.style.display = 'none';\n } else {\n unitSection.style.display = 'none';\n }\n }\n\n propertyTypeSelect.onchange = function() {\n const selectedType = this.value;\n updatePropertyDetails(selectedType);\n document.getElementById('ool1zyibs').value = selectedType;\n };\n },\n onConfirm() {\n let propertyTypeInput = document.getElementById('ool1zyibs').value.trim();\n const unit = document.getElementById('property-unit') ? document.getElementById('property-unit').value : '';\n const noUnit = ['opacity', 'z-index'];\n const cssQuickCommands = data.cssQuickCommands;\n \n // Normalize the input\n const properties = propertyTypeInput.split(',').map(prop => prop.trim());\n \n properties.forEach(propertyString => {\n let [propertyType, userDefinedValue] = propertyString.split('=').map(str => str.trim());\n propertyType = propertyType.toLowerCase();\n \n // Check if propertyType is a Tailwind quick command\n if (Object.keys(cssQuickCommands).includes(propertyType)) {\n const quickCommand = cssQuickCommands[propertyType];\n const quickCommandProperties = quickCommand.split(';').filter(Boolean);\n \n quickCommandProperties.forEach(propertyString => {\n let [quickPropertyType, quickUserDefinedValue] = propertyString.split(':').map(str => str.trim());\n quickPropertyType = quickPropertyType.toLowerCase();\n obj[quickPropertyType] = quickUserDefinedValue;\n });\n } else {\n // Handle custom properties\n let finalValue;\n \n if (userDefinedValue) {\n // Use the user-defined value\n finalValue = userDefinedValue + (unit && !noUnit.includes(propertyType) ? unit : '');\n } else {\n // Use the default value if no value was provided\n const defaultValue = defaultValues[propertyType] || defaultValues['default'];\n finalValue = unit ? `${defaultValue}${unit}` : defaultValue;\n }\n \n // Apply the final value to the property\n if (noUnit.includes(propertyType)) {\n obj[propertyType] = userDefinedValue || \"1\";\n } else {\n obj[propertyType] = finalValue;\n }\n }\n });\n \n saveState();\n } \n });\n}\nwindow.renameStyleTarget = target => {\n let modalContent = `\n button:last-child').onclick();\n }\n \">\n
`;\n \n // Render the modal\n Modal.render({\n title: `Are you sure you want to rename the \"${target}\" style?`,\n content: modalContent,\n onLoad() {\n document.getElementById('lnjvy3iz2').focus();\n },\n onConfirm() {\n let value = document.getElementById('lnjvy3iz2').value;\n if (value) {\n // Convert the first character to lowercase\n value = value.charAt(0).toLowerCase() + value.slice(1);\n \n if (project.css.styles[value]) {\n Modal.render({\n title: `Unable to add style!`,\n content: \"Style already exists!\"\n });\n } else if (project.css.styles[`${target}`]) {\n // Remove the storage of the styles target before changing\n data.stylesTarget = null;\n\n // Clone the style object\n project.css.styles[value] = JSON.parse(JSON.stringify(project.css.styles[target]));\n \n // Now delete the old style object\n delete project.css.styles[target];\n\n // Target the new style\n data.stylesTarget = value;\n\n saveState();\n }\n } else {\n Modal.render({\n title: `Unable to rename style`,\n content: \"No value detected!\"\n });\n }\n }\n });\n}\nwindow.deleteStyleTarget = target => {\n let modalContent = `You will still be able to undo.
`;\n \n // Render the modal\n Modal.render({\n title: `Are you sure you want to delete the \"${target}\" style?`,\n content: modalContent,\n onConfirm() {\n if (data.stylesTarget) {\n clearStyles(project.html, data.stylesTarget);\n delete project.css.styles[data.stylesTarget];\n data.stylesTarget = null;\n saveState();\n }\n }\n });\n}\nwindow.addBreakpoint = () => {\n let modalContent = `\n \n button:last-child').onclick();\n }\n \">\n `;\n \n // Render the modal\n Modal.render({\n title: `Add A Breakpoint`,\n content: modalContent,\n onLoad() {\n document.getElementById('vvrh9nxwk').focus();\n },\n onConfirm() {\n const value = document.getElementById('vvrh9nxwk').value;\n if (value) {\n if (project.css.breakpoints[`${value}px`]) {\n Modal.render({\n title: `Unable to add breakpoint!`,\n content: \"Breakpoint already exists!\"\n });\n } else {\n project.css.breakpoints[`${value}px`] = {};\n project.css.breakpoints[`${value}px`][`${data.stylesTarget}`] = {\n \"base\": {},\n \"pseudos\": []\n };\n }\n } else {\n Modal.render({\n title: `Unable to add breakpoint`,\n content: \"No value detected!\"\n });\n }\n }\n });\n}\nwindow.renameBreakpointKey = size => {\n let modalContent = `\n button:last-child').onclick();\n }\n \">\n
`;\n \n // Render the modal\n Modal.render({\n title: `Are you sure you want to rename the \"${size}\" style?`,\n content: modalContent,\n onLoad() {\n document.getElementById('mow5ep6l7').focus();\n },\n onConfirm() {\n const value = document.getElementById('mow5ep6l7').value;\n if (value) {\n if (project.css.breakpoints[`${value}px`]) {\n Modal.render({\n title: `Unable to rename breakpoint key!`,\n content: \"Key already exists!\"\n });\n } else {\n // Remove the storage of the styles target before changing\n data.breakpointKey = null;\n\n // Clone the style object\n project.css.breakpoints[`${value}px`] = JSON.parse(JSON.stringify(project.css.breakpoints[size]));\n \n // Now delete the old style object\n delete project.css.breakpoints[size];\n }\n } else {\n Modal.render({\n title: `Unable to rename breakpoint key`,\n content: \"No value detected!\"\n });\n }\n }\n });\n}\nwindow.deleteBreakpointKey = size => {\n let modalContent = `You will still be able to undo.
`;\n \n // Render the modal\n Modal.render({\n title: `Are you sure you want to delete the \"${size}\" style?`,\n content: modalContent,\n onConfirm() {\n if (size in project.css.breakpoints) {\n data.breakpointKey = null;\n delete project.css.breakpoints[size];\n saveState();\n }\n }\n });\n}\nwindow.addAnimation = () => {\n let modalContent = `\n button:last-child').onclick();\n }\n \">\n `;\n \n // Render the modal\n Modal.render({\n title: `Add An Animation`,\n content: modalContent,\n onLoad() {\n document.getElementById('vvrh9nxwk').focus();\n },\n onConfirm() {\n let value = document.getElementById('vvrh9nxwk').value;\n if (value) {\n // Convert the first character to lowercase\n value = value.charAt(0).toLowerCase() + value.slice(1);\n if (project.css.animations[`${value}`]) {\n Modal.render({\n title: `Unable to add animation!`,\n content: \"Animation already exists!\"\n });\n } else {\n project.css.animations[value] = {\n \"keyframes\": {}\n };\n project.css.animations[value].keyframes = {\n \"0%\": {},\n \"100%\": {}\n };\n }\n } else {\n Modal.render({\n title: `Unable to add animation`,\n content: \"No value detected!\"\n });\n }\n }\n });\n}\nwindow.renameAnimation = name => {\n let modalContent = `\n button:last-child').onclick();\n }\n \">\n
`;\n \n // Render the modal\n Modal.render({\n title: `Are you sure you want to rename the \"${name}\" animation?`,\n content: modalContent,\n onLoad() {\n document.getElementById('mow5ep6l7').focus();\n },\n onConfirm() {\n const value = document.getElementById('mow5ep6l7').value;\n if (value) {\n if (project.css.animations[value]) {\n Modal.render({\n title: `Unable to rename animation!`,\n content: \"Animation name already exists!\"\n });\n } else {\n // Remove the storage of the styles target before changing\n data.animationTarget = null;\n\n // Clone the style object\n project.css.animations[value] = JSON.parse(JSON.stringify(project.css.animations[name]));\n \n // Now delete the old style object\n delete project.css.animations[name];\n\n // Make the new name the target\n data.animationTarget = value;\n }\n } else {\n Modal.render({\n title: `Unable to rename animation`,\n content: \"No value detected!\"\n });\n }\n }\n });\n}\nwindow.deleteAnimation = name => {\n let modalContent = `You will still be able to undo.
`;\n \n // Render the modal\n Modal.render({\n title: `Are you sure you want to delete the \"${name}\" animation?`,\n content: modalContent,\n onConfirm() {\n if (data.animationKeyframe) data.animationKeyframe = null;\n // Remove the storage of the styles target before changing\n data.animationTarget = null;\n data.animationTarget = null;\n delete project.css.animations[name];\n App.render(\"#app\");\n }\n });\n}\nwindow.addKeyFrame = () => {\n if (!data.animationTarget) return;\n let modalContent = `\n button:last-child').onclick();\n }\n \">\n `;\n \n // Render the modal\n Modal.render({\n title: `Add An Animation`,\n content: modalContent,\n onLoad() {\n document.getElementById('vvrh9nxwk').focus();\n },\n onConfirm() {\n const value = document.getElementById('vvrh9nxwk').value;\n if (value) {\n if (project.css.animations[data.animationTarget].keyframes[`${value}`]) {\n Modal.render({\n title: `Unable to add keyframe!`,\n content: \"Keyframe already exists!\"\n });\n } else {\n project.css.animations[data.animationTarget].keyframes[`${value}`] = {};\n saveState();\n }\n } else {\n Modal.render({\n title: `Unable to add keyframe`,\n content: \"No value detected!\"\n });\n }\n }\n });\n}\nwindow.renameKeyFrame = name => {\n let modalContent = `\n button:last-child').onclick();\n }\n \">\n
`;\n \n // Render the modal\n Modal.render({\n title: `Are you sure you want to rename the \"${name}\" keyframe?`,\n content: modalContent,\n onLoad() {\n document.getElementById('mow5ep6l7').focus();\n },\n onConfirm() {\n const value = document.getElementById('mow5ep6l7').value;\n if (value) {\n if (project.css.animations[data.animationTarget].keyframes[value]) {\n Modal.render({\n title: `Unable to rename keyframe!`,\n content: \"Animation keyframe already exists!\"\n });\n } else {\n // Remove the storage of the styles target before changing\n data.animationKeyframe = null;\n\n // Clone the style object\n project.css.animations[data.animationTarget].keyframes[value] = JSON.parse(JSON.stringify(project.css.animations[data.animationTarget].keyframes[name]));\n \n // Now delete the old style object\n delete project.css.animations[data.animationTarget].keyframes[name];\n\n // Make the new name the target\n data.animationKeyframe = value;\n saveState();\n }\n } else {\n Modal.render({\n title: `Unable to rename keyframe key`,\n content: \"No value detected!\"\n });\n }\n }\n });\n}\nwindow.deleteKeyFrame = name => {\n let modalContent = `You will still be able to undo.
`;\n \n // Render the modal\n Modal.render({\n title: `Are you sure you want to delete the \"${name}\" keyframe?`,\n content: modalContent,\n onConfirm() {\n if (data.animationKeyframe) data.animationKeyframe = null;\n // Remove the storage of the styles target before changing\n data.animationKeyframe = null;\n delete project.css.animations[data.animationTarget].keyframes[name];\n saveState();\n App.render(\"#app\");\n }\n });\n}\nwindow.addToKeyframe = () => {\n if (!data.animationTarget || !data.animationKeyframe) return;\n let modalContent = `\n button:last-child').onclick();\n }\n \">\n `;\n \n // Render the modal\n Modal.render({\n title: `Add keyframe property`,\n content: modalContent,\n onLoad() {\n document.getElementById('vvrh9nxwk').focus();\n },\n onConfirm() {\n const value = document.getElementById('vvrh9nxwk').value;\n if (value) {\n if (project.css.animations[data.animationTarget].keyframes[`${value}`]) {\n Modal.render({\n title: `Unable to add keyframe!`,\n content: \"Keyframe already exists!\"\n });\n } else {\n project.css.animations[data.animationTarget].keyframes[value] = {};\n saveState();\n }\n } else {\n Modal.render({\n title: `Unable to add keyframe`,\n content: \"No value detected!\"\n });\n }\n }\n });\n}\nwindow.deleteStyleProp = (id, prop, e, detect = null) => {\n let obj = null;\n if (detect) {\n if (detect === \"breakpoints\") {\n obj = project.css.breakpoints[`${data.breakpointKey}px`][id][data.stylesPropTarget];\n }\n if (detect === \"animations\") {\n obj = project.css.animations[data.animationTarget].keyframes[data.animationKeyframe]\n }\n } else {\n obj = project.css.styles[id][data.stylesPropTarget];\n }\n // Delete the property\n if (prop in obj) delete obj[`${prop}`];\n saveState();\n\n // Remove the modal\n e.closest('dialog[open]').remove();\n}\nwindow.clearStyles = (layers, query, callback) => {\n // first delete the style object\n if (project.css.styles[query]) {\n delete project.css[query];\n }\n\n // Track whether we found and cleared the styles in any layer\n let found = false;\n\n // then let's remove the style from layers\n for (const layer of layers) {\n if (layer.style === query) {\n layer.style = \"\";\n found = true;\n };\n // Recurse through child layers\n if (layer.children && layer.children.length > 0) {\n clearStyles(layer.children, query, () => {\n found = true;\n });\n }\n // If we processed any layers, renderPreview and invoke the callback\n if (found) {\n if (typeof callback === 'function') {\n callback();\n }\n }\n }\n}\nwindow.styleModal = (id, prop, currentValue, detect = null) => {\n const cssFixedValueProperties = data.cssFixedValueProperties;\n\n let detected = null;\n if (detect) detected = detect;\n\n // Initialize the modal content based on the property type\n let modalContent = '';\n\n if (cssFixedValueProperties[prop]) {\n // Handle fixed values\n const options = cssFixedValueProperties[prop].map(val => `\n \n `).join('');\n\n modalContent = `\n \n \n \n
`;\n } else {\n // Handle other types of properties (e.g., text) with a single input\n modalContent = `\n \n \n button:last-child').onclick();\n }\n \"/>\n
`;\n }\n\n // Add a delete option\n modalContent += `\n \n Delete Property\n
`;\n\n // Render the modal\n Modal.render({\n title: `Modify \"${prop}\" Style`,\n content: modalContent,\n onLoad() {\n if (document.getElementById('new-value')) {\n const element = document.getElementById('new-value');\n element.focus();\n if (element.tagName.toLowerCase() === 'input') {\n element.select();\n }\n }\n },\n onConfirm() {\n saveState();\n\n // Get the new value from the modal\n const newValue = document.getElementById('new-value').value;\n\n let obj = null;\n if (detect) {\n if (detect === \"breakpoints\") {\n if (project.css.breakpoints[`${data.breakpointKey}px`][id][data.stylesPropTarget]) {\n obj = project.css.breakpoints[`${data.breakpointKey}px`][id][data.stylesPropTarget];\n }\n }\n if (detect === \"animations\") {\n if (project.css.animations[data.animationTarget].keyframes[data.animationKeyframe]) {\n obj = project.css.animations[data.animationTarget].keyframes[data.animationKeyframe];\n }\n }\n } else {\n if (data.stylesPropTarget) {\n obj = project.css.styles[id][data.stylesPropTarget];\n }\n }\n\n // Update or delete the style\n if (newValue === '') {\n // Delete the property if empty\n delete obj[prop];\n } else {\n // Update the property with the new value\n obj[prop] = `${newValue}`;\n }\n\n saveState();\n }\n });\n}\nwindow.addPseudo = selector => {\n // Ensure the selector exists and initialize pseudos if not already present\n if (!project.css.styles[selector]) return;\n if (!project.css.styles[selector].pseudos) {\n project.css.styles[selector].pseudos = [];\n }\n\n // Define available pseudo-classes and pseudo-elements\n const pseudos = [\n 'none',\n ':active',\n ':after',\n ':before',\n ':first-child',\n ':focus',\n ':focus-visible',\n ':focus-within',\n ':hover',\n ':last-child',\n ':nth-child',\n ':target',\n ':visited',\n '::-webkit-scrollbar',\n '::-webkit-scrollbar-thumb',\n '::-webkit-scrollbar-track',\n '::before',\n '::after'\n ];\n\n let pseudoOptions = pseudos.map(pseudo => `\n \n `).join('');\n\n let modalContent = `\n \n \n \n button:last-child').onclick();\n }\n \">\n
\n `;\n\n // Render the modal\n Modal.render({\n title: `Add A Pseudo-Class/Element`,\n content: modalContent,\n onLoad() {\n document.getElementById('pseudo-input').focus();\n },\n onConfirm() {\n const pseudoSelector = document.getElementById('pseudo-selector').value.trim();\n const pseudoStyles = document.getElementById('pseudo-input').value.trim();\n\n if (pseudoStyles) {\n // Convert pseudoStyles into an object\n const styles = pseudoStyles.split(';').reduce((acc, rule) => {\n const [property, value] = rule.split(':').map(s => s.trim());\n if (property && value) acc[property] = value;\n return acc;\n }, {});\n\n const existingPseudo = project.css.styles[selector].pseudos.find(pseudo => pseudo.selector === pseudoStyles);\n\n if (existingPseudo) {\n // Merge new styles with existing styles if pseudo already exists\n existingPseudo.styles = {\n ...existingPseudo.styles,\n ...styles\n };\n } else {\n // Add a new pseudo object\n let obj = {\n \"selector\": pseudoStyles,\n \"styles\": styles\n };\n project.css.styles[selector].pseudos.push(obj);\n }\n\n saveState();\n } else {\n Modal.render({\n title: `Unable to add pseudo`,\n content: \"Please select a pseudo and enter valid CSS properties and values.\"\n });\n }\n }\n });\n}\nwindow.renamePseudo = oldName => {\n // Define available pseudo-classes and pseudo-elements\n const pseudos = [\n 'none',\n ':active',\n ':after',\n ':before',\n ':first-child',\n ':focus',\n ':focus-visible',\n ':focus-within',\n ':hover',\n ':last-child',\n ':nth-child',\n ':target',\n ':visited',\n '::-webkit-scrollbar',\n '::-webkit-scrollbar-thumb',\n '::-webkit-scrollbar-track',\n '::before',\n '::after'\n ];\n\n let pseudoOptions = pseudos.map(pseudo => `\n \n `).join('');\n\n let modalContent = `\n \n \n \n button:last-child').onclick();\n }\n \">\n
\n `;\n\n // Render the modal\n Modal.render({\n title: `Are you sure you want to rename the \"${oldName}\" pseudo-class/element?`,\n content: modalContent,\n onLoad() {\n document.getElementById('pseudo-name-input').focus();\n },\n onConfirm() {\n const newName = document.getElementById('pseudo-name-input').value.trim();\n if (newName) {\n const style = project.css.styles[data.stylesTarget];\n if (!style || !style.pseudos) return;\n\n const existingPseudo = style.pseudos.find(pseudo => pseudo.selector === newName);\n if (existingPseudo) {\n Modal.render({\n title: `Unable to rename pseudo!`,\n content: \"Pseudo with the new name already exists!\"\n });\n return;\n }\n\n const pseudoIndex = style.pseudos.findIndex(pseudo => pseudo.selector === oldName);\n if (pseudoIndex === -1) {\n Modal.render({\n title: `Pseudo not found!`,\n content: `No pseudo with the name \"${oldName}\" found!`\n });\n return;\n }\n\n // Rename the pseudo\n style.pseudos[pseudoIndex].selector = newName;\n\n App.render(\"#app\");\n saveState();\n } else {\n Modal.render({\n title: `Unable to rename pseudo`,\n content: \"No value detected!\"\n });\n }\n }\n });\n}\nwindow.deletePseudo = () => {\n const name = data.pseudosSelector;\n const pseudoIndex = data.pseudosSelectorIndex;\n let modalContent = `You will still be able to undo.
`;\n\n // Render the modal\n Modal.render({\n title: `Are you sure you want to delete the \"${name}\" pseudo-class/element?`,\n content: modalContent,\n onConfirm() {\n const style = project.css.styles[data.stylesTarget];\n if (!style || !style.pseudos) return;\n\n data.pseudosSelector = null;\n data.pseudosSelectorIndex = 0;\n style.pseudos.splice(pseudoIndex, 1);\n saveState();\n }\n });\n}\nwindow.fetchCssQuickCommands = async url => {\n try {\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error('Network response was not ok');\n }\n data.canUseQuickCommands = true;\n const obj = await response.json();\n return obj;\n } catch (error) {\n console.error('Error fetching CSS quick commands:', error);\n }\n}\nwindow.applyCssQuickCommands = async url => {\n const cssQuickCommands = await fetchCssQuickCommands(url);\n\n if (cssQuickCommands) {\n // Assuming `data` is a global object where `cssQuickCommands` should be applied\n data.cssQuickCommands = cssQuickCommands;\n }\n}\n// Helper function to add an attribute to the element\nwindow.addAttribute = attr => {\n if (!attr) return;\n const incrementPattern = /{n}/g; // Pattern to detect increment placeholder\n\n // Split the attributes into individual attributes\n const attrs = attr.toLowerCase().split(',').map(q => q.trim().toLowerCase());\n\n // Get the current increment values for each attribute\n const incrementValues = {};\n\n saveState();\n data.selectedLayerIds.forEach(id => {\n const { layer } = findLayerById(id, project.html);\n if (layer) {\n // Initialize layer.props if it's undefined\n if (!layer.props) layer.props = {};\n\n // Iterate over each attribute\n attrs.forEach(attribute => {\n let [key, value] = attribute.split('=').map(s => s.trim());\n if (key === 'id') value = generateId();\n\n if (incrementPattern.test(value)) {\n // Handle incrementing values\n let baseValue = value.replace(incrementPattern, '');\n let increment = incrementValues[key] || 1;\n value = baseValue + increment;\n incrementValues[key] = increment + 1;\n }\n\n if (!(key in layer.props)) {\n layer.props[key] = value !== undefined ? value : \"\";\n } else if (value !== undefined) {\n // If the attribute already exists, update its value\n layer.props[key] = value;\n }\n });\n }\n });\n saveState();\n}\n\n// editor functions\nwindow.html2json = input => {\n function elementToJson(element) {\n const boxElements = data.boxElements;\n const textElements = data.textElements;\n const noTextElements = [\n \"br\",\n \"hr\",\n \"input\",\n \"progress\",\n \"optgroup\",\n \"input\",\n \"link\",\n \"img\",\n \"svg\",\n \"path\",\n \"polygon\",\n \"rect\",\n \"circle\",\n \"ellipse\",\n \"g\",\n \"defs\",\n \"clipPath\"\n ];\n const tagName = element.tagName.toLowerCase();\n const obj = {\n tag: element.tagName.toLowerCase(),\n id: generateId(),\n style: \"\",\n state: {\n \"collapsed\": false,\n \"visible\": true,\n \"selected\": false\n }\n };\n\n obj.name = tagName;\n if (boxElements.includes(tagName)) {\n obj.type = \"box\";\n obj.text = \"\";\n } else if (textElements.includes(tagName)) {\n obj.type = \"text\";\n obj.text = \"\";\n } else {\n obj.type = tagName;\n if (!noTextElements.includes(tagName)) {\n obj.text = \"\";\n }\n }\n \n // Add props only if not empty\n if (element.hasAttributes()) {\n const props = {};\n Array.from(element.attributes).forEach(attr => {\n props[`${attr.name.toLowerCase()}`] = `${attr.value}`;\n });\n obj.props = props;\n }\n \n if (element.childNodes.length > 0) {\n obj.children = [];\n element.childNodes.forEach(child => {\n if (child.nodeType === Node.ELEMENT_NODE) {\n obj.children.push(elementToJson(child));\n } else if (child.nodeType === Node.TEXT_NODE && child.nodeValue.trim()) {\n obj.text = child.nodeValue.trim();\n }\n });\n }\n return obj;\n }\n\n const parser = new DOMParser();\n const doc = parser.parseFromString(input, 'text/html');\n const json = Array.from(doc.body.children).map(child => elementToJson(child));\n return json;\n}\nwindow.json2html = input => {\n function jsonToElement(json) {\n const renderElement = element => {\n let html = '';\n\n // Skip elements that are not visible\n if (element.state && !element.state.visible) return html;\n \n if (!element.tag) {\n html += element.text || '';\n return html;\n }\n \n html += `<${element.tag}`;\n\n if (element.props) {\n for (let [key, value] of Object.entries(element.props)) {\n html += ` ${key}=\"${value}\"`;\n }\n }\n \n html += '>';\n \n if (element.text) {\n if (element.tag === 'style' || element.tag === 'script') {\n html += element.text;\n } else {\n html += escapeHtml(element.text);\n }\n }\n \n if (element.children) {\n for (const childElement of element.children) {\n html += renderElement(childElement);\n }\n }\n \n html += `${element.tag}>`;\n return html;\n }\n \n let html = '';\n\n // If the input is an object, wrap it in an array\n if (!Array.isArray(json)) {\n json = [json];\n }\n\n if (Array.isArray(json)) {\n json.forEach(element => {\n html += renderElement(element);\n });\n }\n return html;\n }\n function escapeHtml(text) {\n const map = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": '''\n };\n return text.replace(/[&<>\"']/g, function(m) { return map[m]; });\n }\n function beautifyHtml(json) {\n const html = jsonToElement(json);\n let tab = ' ';\n let result = '';\n let indent = '';\n \n html.split(/>\\s*).forEach(function(element) {\n if (element.match(/^\\/\\w/)) {\n indent = indent.substring(tab.length);\n }\n \n result += indent + '<' + element.trim() + '>\\r\\n';\n \n if (element.match(/^\\w[^>]*[^\\/]$/) && !element.startsWith(\" {\n if (typeof css !== 'string') {\n throw new Error(\"Input must be a CSS string\");\n }\n\n const json = {\n rootVariables: {},\n styles: {},\n animations: {},\n breakpoints: {}\n };\n\n css = minifyCSS(css)\n\n // Handle @import statements\n const importRegex = /@import\\s+url\\(['\"]([^'\"]+)['\"]\\);/g;\n let importMatch;\n\n while ((importMatch = importRegex.exec(css)) !== null) {\n const importUrl = importMatch[1].trim();\n if (project.libraries && !project.libraries.includes(importUrl)) {\n project.libraries.push(importUrl);\n }\n }\n\n // Remove @import statements from CSS\n css = css.replace(importRegex, '');\n\n const keyframesRegex = /@keyframes\\s+([^{\\s]+)\\s*\\{([^}]*(\\{[^}]*\\})[^}]*)\\}/g;\n const mediaQueryRegex = /@media\\s*([^{]+)\\s*\\{([\\s\\S]*?\\{[\\s\\S]*?\\})\\s*}/g;\n const selectorRegex = /([^{]+?)\\s*(\\{([^}]+)\\})/g;\n\n // Decode URL-encoded characters\n function decodeURIComponentSafe(str) {\n try {\n return decodeURIComponent(str);\n } catch {\n return str;\n }\n }\n\n function processSelector(selector, properties, target) {\n selector = selector.trim();\n if (selector.startsWith('@keyframes')) return;\n\n if (selector === \":root\") {\n properties.split(';').forEach(prop => {\n const [varName, varValue] = prop.split(\":\").map(part => part.trim());\n if (varName && varValue) {\n json.rootVariables[varName] = decodeURIComponentSafe(varValue);\n }\n });\n return;\n }\n\n const pseudoMatch = selector.match(/^(.*?)(::?[a-zA-Z0-9-]+)$/);\n let baseSelector = selector;\n let pseudo = null;\n\n if (pseudoMatch) {\n baseSelector = pseudoMatch[1].trim();\n pseudo = pseudoMatch[2];\n }\n\n if (!target[baseSelector]) {\n target[baseSelector] = {};\n }\n\n let currentTarget = target[baseSelector];\n if (pseudo) {\n currentTarget['pseudos'] = currentTarget['pseudos'] || [];\n const pseudoObj = {\n selector: pseudo,\n styles: {}\n };\n currentTarget['pseudos'].push(pseudoObj);\n currentTarget = pseudoObj.styles;\n } else {\n currentTarget['base'] = currentTarget['base'] || {};\n currentTarget = currentTarget['base'];\n }\n\n // Use regex to handle property-value pairs\n const propertyRegex = /([a-zA-Z-]+)\\s*:\\s*(.*?)(?=;|$)/g;\n let match;\n while ((match = propertyRegex.exec(properties)) !== null) {\n const property = match[1].trim();\n const value = match[2].trim();\n\n // Special handling for url(...) values\n const urlRegex = /^url\\(['\"]?(.*?)['\"]?\\)$/i;\n const urlMatch = value.match(urlRegex);\n\n if (urlMatch) {\n const urlContent = urlMatch[1].trim(); // Extract URL content and trim\n currentTarget[property] = `url(\"${decodeURIComponentSafe(urlContent)}\")`;\n } else {\n currentTarget[property] = decodeURIComponentSafe(value);\n }\n }\n }\n\n function processRules(cssRules, target) {\n let match;\n while ((match = selectorRegex.exec(cssRules)) !== null) {\n const selector = match[1].trim();\n const properties = match[3].trim();\n processSelector(selector, properties, target);\n }\n }\n\n function processKeyframes(keyframesName, keyframesRules) {\n const keyframes = {};\n\n keyframesRules.split('}').forEach(segment => {\n segment = segment.trim();\n if (!segment) return;\n\n const [keyframeName, propertiesPart] = segment.split('{').map(part => part.trim());\n if (keyframeName && (keyframeName.includes('to') || keyframeName.includes('from') || keyframeName.includes('%'))) {\n propertiesPart.split(';').forEach(prop => {\n const [property, value] = prop.split(':').map(p => p.trim());\n if (property && value) {\n keyframes[keyframeName] = keyframes[keyframeName] || {};\n keyframes[keyframeName][property] = decodeURIComponentSafe(value);\n }\n });\n }\n });\n\n json.animations[keyframesName] = { keyframes };\n }\n\n // Process keyframes\n let keyframesMatch;\n while ((keyframesMatch = keyframesRegex.exec(css)) !== null) {\n const keyframesName = keyframesMatch[1].trim();\n const keyframesRules = keyframesMatch[2].trim();\n processKeyframes(keyframesName, keyframesRules);\n }\n\n // Process media queries\n let mediaMatch;\n while ((mediaMatch = mediaQueryRegex.exec(css)) !== null) {\n const mediaCondition = mediaMatch[1].trim().split(\")\")[0].split(\":\")[1].trim();\n const mediaRules = mediaMatch[2].trim();\n const mediaTarget = {};\n\n processRules(mediaRules, mediaTarget);\n\n json.breakpoints[mediaCondition] = mediaTarget;\n }\n\n // Remove media queries from CSS\n const cssWithoutMedia = css.replace(mediaQueryRegex, '');\n\n // Process remaining CSS rules\n processRules(cssWithoutMedia, json.styles);\n\n // Remove keyframes from CSS\n css = css.replace(keyframesRegex, '');\n\n // Remove any empty selectors or unnecessary properties\n Object.keys(json.styles).forEach(selector => {\n if (Object.keys(json.styles[selector]).length === 0) {\n delete json.styles[selector];\n }\n });\n\n return json;\n};\n\nwindow.json2css = styles => {\n let css = '';\n let symbol = \"\";\n let semicolon = \";\";\n let openBrace = \"{\";\n let closeBrace = \"}\";\n\n // Function to check if a value contains CSS variables\n function containCssVar(value) {\n return /var\\(--/.test(value);\n }\n\n // Function to process styles recursively\n function processStyles(selector, style, indentLevel = 0) {\n let indent = ' '.repeat(indentLevel);\n let innerCss = '';\n\n const variables = style.variables || {};\n const baseStyles = style.base || {};\n const pseudos = style.pseudos || [];\n const children = style.children || {}; // Account for children\n\n // Add the base selector\n innerCss += `${indent}${selector} ${openBrace}\\n`;\n\n // Variables\n for (const [variable, value] of Object.entries(variables)) {\n innerCss += `${indent} ${symbol}${variable}: ${value}${semicolon}\\n`;\n }\n\n // Base styles\n for (let [property, value] of Object.entries(baseStyles)) {\n if (containCssVar(value)) {\n // Replace CSS variables with CSS variables, handling mixed content\n value = value.replace(/var\\(--([a-zA-Z0-9-_]+)\\)/g, (match, varName) => {\n return `var(--${varName})`;\n });\n }\n innerCss += `${indent} ${property}: ${value}${semicolon}\\n`;\n }\n\n innerCss += `${indent}${closeBrace}\\n`;\n\n // Pseudo-classes/styles\n pseudos.forEach(({ selector: pseudoSelector, styles: pseudoStyles }) => {\n innerCss += `${indent}${selector}${pseudoSelector} ${openBrace}\\n`;\n for (let [property, value] of Object.entries(pseudoStyles)) {\n if (containCssVar(value)) {\n value = value.replace(/var\\(--([a-zA-Z0-9-_]+)\\)/g, (match, varName) => {\n return `var(--${varName})`;\n });\n }\n innerCss += `${indent} ${property}: ${value}${semicolon}\\n`;\n }\n innerCss += `${indent}${closeBrace}\\n`;\n });\n\n // Recursively process children\n for (const [childSelector, childStyle] of Object.entries(children)) {\n innerCss += processStyles(`${selector} ${childSelector}`, childStyle, indentLevel + 1);\n }\n\n return innerCss;\n }\n\n // Function to process animations\n function processAnimations(animations, indentLevel = 0) {\n let indent = ' '.repeat(indentLevel);\n let animationCss = '';\n\n for (const [animationName, animation] of Object.entries(animations)) {\n animationCss += `${indent}@keyframes ${animationName} ${openBrace}\\n`;\n\n for (const [keyframe, styles] of Object.entries(animation.keyframes)) {\n animationCss += `${indent} ${keyframe} ${openBrace}\\n`;\n for (let [property, value] of Object.entries(styles)) {\n if (containCssVar(value)) {\n value = value.replace(/var\\(--([a-zA-Z0-9-_]+)\\)/g, (match, varName) => {\n return `var(--${varName})`;\n });\n }\n animationCss += `${indent} ${property}: ${value}${semicolon}\\n`;\n }\n animationCss += `${indent} ${closeBrace}\\n`;\n }\n\n animationCss += `${indent}${closeBrace}\\n`;\n }\n\n return animationCss;\n }\n\n // Function to process breakpoints\n function processBreakpoints(breakpoints, indentLevel = 0) {\n let indent = ' '.repeat(indentLevel);\n let breakpointCss = '';\n\n for (const [breakpoint, styles] of Object.entries(breakpoints)) {\n breakpointCss += `${indent}@media (min-width: ${breakpoint}) ${openBrace}\\n`;\n for (const [selector, style] of Object.entries(styles)) {\n breakpointCss += processStyles(selector, style, indentLevel + 1);\n }\n breakpointCss += `${indent}${closeBrace}\\n`;\n }\n\n return breakpointCss;\n }\n\n // Define :root variables\n if (styles.rootVariables && Object.keys(styles.rootVariables).length) {\n css += \":root {\\n\";\n for (const [variable, value] of Object.entries(styles.rootVariables)) {\n css += ` ${variable}: ${value}${semicolon}\\n`;\n }\n css += \"}\\n\\n\";\n }\n\n // Define styles for each class\n for (const [classId, style] of Object.entries(styles.styles)) {\n if (!style || (!Object.keys(style.variables || {}).length &&\n !Object.keys(style.base || {}).length &&\n !Object.keys(style.pseudos || {}).length &&\n !Object.keys(style.children || {}).length)) {\n continue; // Skip empty styles\n }\n\n const selector = classId;\n css += processStyles(selector, style);\n }\n\n // Process animations\n if (Object.keys(styles.animations || {}).length) {\n css += processAnimations(styles.animations);\n }\n\n // Process breakpoints (media queries)\n if (Object.keys(styles.breakpoints || {}).length) {\n css += processBreakpoints(styles.breakpoints);\n }\n\n return css;\n}\nwindow.json2preprocessor = styles => {\n let css = '';\n let symbol = \"\";\n let semicolon = \";\";\n let openBrace = \"{\";\n let closeBrace = \"}\";\n\n // set proper symbols\n if (data.preprocessors.includes(project.convertTo)) {\n if (project.convertTo === \"sass\" || project.convertTo === \"scss\") symbol = \"$\";\n if (project.convertTo === \"sass\") {\n semicolon = \"\";\n openBrace = \"\";\n closeBrace = \"\";\n }\n if (project.convertTo === \"less\") symbol = \"@\";\n }\n\n // Function to check if a value contains CSS variables\n function containcssVar(value) {\n return /var\\(--/.test(value);\n }\n\n // Function to process styles recursively\n function processStyles(selector, style, indentLevel = 0) {\n let indent = ' '.repeat(indentLevel);\n let innercss = '';\n\n const variables = style.variables || {};\n const baseStyles = style.base || {};\n const pseudos = style.pseudos || [];\n const children = style.children || {}; // Account for children\n\n innercss += `${indent}${selector} ${openBrace}\\n`;\n\n // Variables (convert CSS variables to css variables)\n for (const [variable, value] of Object.entries(variables)) {\n innercss += `${indent} ${symbol}${variable}: ${value}${semicolon}\\n`;\n }\n\n // Base styles\n for (let [property, value] of Object.entries(baseStyles)) {\n if (property.startsWith('--')) {\n property = property.split('--').join(symbol);\n }\n\n // Check if value contains a CSS variable\n if (containcssVar(value)) {\n // Replace CSS variables with css variables, handling mixed content\n value = value.replace(/var\\(--([a-zA-Z0-9-_]+)\\)/g, (match, varName) => {\n if (!property.startsWith('--')) {\n return `${symbol}${varName}`;\n } else {\n return `${symbol}${varName}`;\n }\n });\n }\n innercss += `${indent} ${property}: ${value}${semicolon}\\n`;\n }\n\n // Pseudo-classes/styles\n pseudos.forEach(({ selector: pseudoSelector, styles: pseudoStyles }) => {\n innercss += `${indent} &${pseudoSelector} ${openBrace}\\n`;\n for (let [property, value] of Object.entries(pseudoStyles)) {\n // Check if value contains a CSS variable\n if (containcssVar(value)) {\n value = value.replace(/var\\(--([a-zA-Z0-9-_]+)\\)/g, (match, varName) => {\n return `${symbol}${varName}`;\n });\n }\n innercss += `${indent} ${property}: ${value}${semicolon}\\n`;\n }\n innercss += `${indent} ${closeBrace}\\n`;\n });\n\n // Recursively process children\n if (children) {\n for (const [childSelector, childStyle] of Object.entries(children)) {\n innercss += processStyles(`${selector} ${childSelector}`, childStyle, indentLevel + 1);\n }\n }\n\n innercss += `${indent}${closeBrace}\\n`;\n\n return innercss;\n }\n\n // Function to process animations\n function processAnimations(animations, indentLevel = 0) {\n let indent = ' '.repeat(indentLevel);\n let animationCSS = '';\n\n for (const [animationName, animation] of Object.entries(animations)) {\n animationCSS += `${indent}@keyframes ${animationName} ${openBrace}\\n`;\n\n for (const [keyframe, styles] of Object.entries(animation.keyframes)) {\n animationCSS += `${indent} ${keyframe} ${openBrace}\\n`;\n for (let [property, value] of Object.entries(styles)) {\n // Replace CSS variables with preprocessor variables if needed\n if (containcssVar(value)) {\n value = value.replace(/var\\(--([a-zA-Z0-9-_]+)\\)/g, (match, varName) => {\n return `${symbol}${varName}`;\n });\n }\n animationCSS += `${indent} ${property}: ${value}${semicolon}\\n`;\n }\n animationCSS += `${indent} ${closeBrace}\\n`;\n }\n\n animationCSS += `${indent}${closeBrace}\\n`;\n }\n\n return animationCSS;\n }\n\n // Function to process breakpoints\n function processBreakpoints(breakpoints, indentLevel = 0) {\n let indent = ' '.repeat(indentLevel);\n let breakpointCSS = '';\n\n for (const [breakpoint, styles] of Object.entries(breakpoints)) {\n breakpointCSS += `${indent}@media (max-width: ${breakpoint}) ${openBrace}\\n`;\n for (const [selector, style] of Object.entries(styles.base || {})) {\n breakpointCSS += processStyles(selector, style, indentLevel + 1);\n }\n breakpointCSS += `${indent}${closeBrace}\\n`;\n }\n\n return breakpointCSS;\n }\n\n // Define :root variables (css supports variables using $)\n let rootVariables = [];\n if (styles.rootVariables && Object.keys(styles.rootVariables).length) {\n for (const [variable, value] of Object.entries(styles.rootVariables)) {\n rootVariables.push(variable);\n css += `${symbol}${variable.split('--').join('')}: ${value}${semicolon}\\n`;\n }\n css += '\\n';\n }\n\n // Define styles for each class\n for (const [classId, style] of Object.entries(styles.styles)) {\n if (!style || (!Object.keys(style.variables || {}).length &&\n !Object.keys(style.base || {}).length &&\n !Object.keys(style.pseudos || {}).length &&\n !Object.keys(style.children || {}).length)) {\n continue; // Skip empty styles\n }\n\n const selector = classId;\n css += processStyles(selector, style);\n }\n\n // Process animations\n if (Object.keys(styles.animations || {}).length) {\n css += processAnimations(styles.animations);\n }\n\n // Process breakpoints (media queries)\n if (Object.keys(styles.breakpoints || {}).length) {\n css += processBreakpoints(styles.breakpoints);\n }\n\n return css;\n}\nwindow.mergeCSSJSON = (existingJSON, newJSON) => {\n if (typeof existingJSON === 'string') {\n throw new Error(\"Input's must be JSON\");\n }\n\n // Merge root variables\n Object.assign(existingJSON.rootVariables, newJSON.rootVariables);\n\n // Merge styles\n Object.keys(newJSON.styles).forEach(selector => {\n if (!existingJSON.styles[selector]) {\n existingJSON.styles[selector] = newJSON.styles[selector];\n } else {\n if (newJSON.styles[selector].base) {\n existingJSON.styles[selector].base = {\n ...existingJSON.styles[selector].base,\n ...newJSON.styles[selector].base\n };\n }\n if (newJSON.styles[selector].pseudos) {\n existingJSON.styles[selector].pseudos = [\n ...(existingJSON.styles[selector].pseudos || []),\n ...newJSON.styles[selector].pseudos\n ];\n }\n }\n });\n\n // Merge animations\n Object.keys(newJSON.animations).forEach(animationName => {\n if (!existingJSON.animations[animationName]) {\n existingJSON.animations[animationName] = newJSON.animations[animationName];\n } else {\n existingJSON.animations[animationName].keyframes = {\n ...existingJSON.animations[animationName].keyframes,\n ...newJSON.animations[animationName].keyframes\n };\n existingJSON.animations[animationName].properties = {\n ...existingJSON.animations[animationName].properties,\n ...newJSON.animations[animationName].properties\n };\n }\n });\n\n // Merge breakpoints\n Object.keys(newJSON.breakpoints).forEach(breakpoint => {\n if (!existingJSON.breakpoints[breakpoint]) {\n existingJSON.breakpoints[breakpoint] = newJSON.breakpoints[breakpoint];\n } else {\n Object.keys(newJSON.breakpoints[breakpoint]).forEach(selector => {\n if (!existingJSON.breakpoints[breakpoint][selector]) {\n existingJSON.breakpoints[breakpoint][selector] = newJSON.breakpoints[breakpoint][selector];\n } else {\n existingJSON.breakpoints[breakpoint][selector].base = {\n ...existingJSON.breakpoints[breakpoint][selector].base,\n ...newJSON.breakpoints[breakpoint][selector].base\n };\n }\n });\n }\n });\n\n return existingJSON;\n}\nwindow.fetchCssFile = async url => {\n const response = await fetch(url);\n return response.text();\n}\nwindow.generateCssQuickCommands = async url => {\n const css = await fetchCssFile(url);\n\n // Create a new CSSStyleSheet object\n const stylesheet = new CSSStyleSheet();\n await stylesheet.replace(css); // Replace with the CSS content\n\n const cssQuickCommands = {};\n\n // Iterate over all rules in the stylesheet\n for (const rule of stylesheet.cssRules) {\n // Skip pseudo-classes and animations\n if (rule.type === CSSRule.STYLE_RULE &&\n !rule.selectorText.includes(':') &&\n !rule.selectorText.includes('@keyframes')) {\n\n const className = rule.selectorText.replace('.', '');\n if (className) {\n const declarations = Array.from(rule.style)\n .filter(prop => !prop.startsWith('animation') && !prop.startsWith('transition'))\n .map(prop => `${prop}: ${rule.style[prop]};`)\n .join(' ');\n cssQuickCommands[className] = declarations;\n }\n }\n }\n\n return cssQuickCommands;\n}\nwindow.saveState = () => {\n // Save the current state to history\n const currentState = {\n rootVariables: project.css.rootVariables,\n styles: project.css,\n html: project.html,\n selectedLayerIds: data.selectedLayerIds\n };\n\n // Store the state as a stringified object\n const stateString = JSON.stringify(currentState);\n\n // Check if the last saved state is different from the current state\n if (data.history.length === 0 || data.history[data.historyIndex] !== stateString) {\n data.history = data.history.slice(0, data.historyIndex + 1); // Trim any redo history\n data.history.push(stateString); // Save the new state\n data.historyIndex++;\n localStorage.setItem('Polyrise', JSON.stringify(project));\n }\n}\nwindow.undo = () => {\n if (data.historyIndex > 0) {\n data.editorNavState = true;\n data.historyIndex--;\n const previousState = JSON.parse(data.history[data.historyIndex]);\n // Restore the previous state\n project.css.rootVariables = previousState.rootVariables;\n project.css = previousState.styles;\n project.html = previousState.html;\n data.selectedLayerIds = previousState.selectedLayerIds;\n data.editorNavState = null;\n }\n}\nwindow.redo = () => {\n if (data.historyIndex < data.history.length - 1) {\n data.editorNavState = true;\n data.historyIndex++;\n const nextState = JSON.parse(data.history[data.historyIndex]);\n // Restore the next state\n project.css.rootVariables = nextState.rootVariables;\n project.css = previousState.styles;\n project.html = nextState.html;\n data.selectedLayerIds = nextState.selectedLayerIds;\n data.editorNavState = null;\n }\n}\nwindow.customCode = () => {\n Modal.render({\n title: \"Paste Custom Code\",\n content: `\n \n
\n \n
\n
\n \n
\n
\n `,\n onLoad() {\n document.getElementById('op95hyy3l').focus();\n document.getElementById('op95hyy3l').select();\n },\n onConfirm() {\n const selection = document.getElementById('bvk1c6j4o').value;\n let code = document.getElementById('op95hyy3l').value;\n if (selection === 'html') {\n addBlock(code);\n } else {\n code = minifyCSS(code);\n const newJSON = css2json(code);\n mergeCSSJSON(project.css, newJSON);\n document.querySelector('dialog[open]').querySelector('header > button').onclick();\n }\n }\n });\n}\nwindow.addLibrary = url => {\n if (!url) {\n project.libraries.push('');\n document.getElementById('librariesBox').innerHTML = renderLibraries();\n return false;\n }\n\n if (!project.libraries.includes(url)) {\n project.libraries.push(url);\n } else {\n console.error(`Library already exists: ${url}`);\n }\n\n if (document.getElementById('librariesBox')) {\n document.getElementById('librariesBox').innerHTML = renderLibraries();\n }\n};\nwindow.renderLibraries = () => {\n return project.libraries.map((library, index) => `\n \n `).join('')\n}\nwindow.fetchSuggestions = key => {\n fetch(\n `https://api.cdnjs.com/libraries?search=${key}&fields=filename,description,version`\n )\n .then(response => {\n if (!response.ok) {\n throw new Error(\"Network response was not ok\");\n }\n return response.json();\n })\n .then(item => {\n if (item && item.results && item.results.length > 0) {\n const suggestions = item.results.map(result => result);\n\n document.getElementById('pruz9lb2p').innerHTML = suggestions.map(result => {\n return `\n button').onclick();\n \">\n \n ${result.name}\n ${result.version}\n
\n ${result.description}
\n `;\n }).join('');\n }\n })\n .catch(error => {\n console.error(\"Error fetching data:\", error);\n });\n}\nwindow.removeScript = src => {\n const script = document.querySelector(`script[src=\"${src}\"]`);\n if (script) script.remove();\n}\nwindow.removeScripts = scripts => {\n scripts.forEach(src => {\n const script = document.querySelector(`script[src=\"${src}\"]`);\n if (script) script.remove();\n });\n}\nwindow.loadScript = async scriptUrl => {\n return new Promise((resolve, reject) => {\n // Check if the script is already loaded\n const existingScript = document.querySelector(`script[src=\"${scriptUrl}\"]`);\n if (existingScript) {\n resolve(); // If the script is already present, resolve immediately\n return;\n }\n\n // Create a new script element if not present\n const scriptElement = document.createElement('script');\n scriptElement.src = scriptUrl;\n scriptElement.onload = resolve; // Resolve when the script is successfully loaded\n scriptElement.onerror = () => reject(new Error(`Failed to load script: ${scriptUrl}`)); // Reject on error\n document.body.appendChild(scriptElement); // Append the script to the body\n });\n}\nwindow.loadScripts = async srcArray => {\n return Promise.all(srcArray.map(loadScript));\n}\n\n// layers functions\nwindow.executeQuery = (queriesString, replaceSelection = true) => {\n if (!queriesString) {\n clearAllSelections();\n return;\n }\n\n const queries = queriesString.split(',').map(q => q.trim());\n\n function handleSpecialCommand(command) {\n switch (command) {\n case 'f':\n foldAllLayers(true); // Collapse all layers\n break;\n case 'u':\n foldAllLayers(false); // Uncollapse all layers\n break;\n case 'h':\n hideAllLayers(true); // Hide all layers\n break;\n case 's':\n hideAllLayers(false); // Show all layers\n break;\n case 'e':\n emptyChildren(); // Empty all children from selections\n break;\n case 'cas':\n project.css = {\n \"rootVariables\": {},\n \"styles\": {},\n \"animations\": {},\n \"breakpoints\": {}\n };\n break;\n default:\n console.warn('Unknown command:', command);\n }\n }\n\n function matchesPseudoClass(layer, pseudoClass, index, total) {\n switch (pseudoClass) {\n case 'first-child': return index === 0;\n case 'last-child': return index === total - 1;\n case 'nth-child': return (index + 1) === parseInt(pseudoClass.split('(')[1], 10);\n case 'nth-last-child': return (total - index) === parseInt(pseudoClass.split('(')[1], 10);\n case 'only-child': return total === 1;\n case 'empty': return !(layer.children && layer.children.length > 0);\n case 'first-of-type': return layer.tagOccurrences.index === 0;\n case 'last-of-type': return layer.tagOccurrences.reverseIndex === 0;\n case 'nth-of-type': return layer.tagOccurrences.index === parseInt(pseudoClass.split('(')[1], 10) - 1;\n case 'nth-last-of-type': return layer.tagOccurrences.reverseIndex === parseInt(pseudoClass.split('(')[1], 10) - 1;\n case 'only-of-type': return layer.tagOccurrences.total === 1;\n default: return false;\n }\n }\n\n function selectLayersRecursive(layers, query, callback) {\n let activeCalls = 0; // Track active recursive calls\n \n function processLayers(layers) {\n activeCalls++; // Increment the active call count\n \n layers.forEach((layer, index) => {\n let match = false;\n \n // Extract selector and pseudo-class\n const [selector, pseudoClassPart] = query.split(':');\n const pseudoClass = pseudoClassPart || null;\n \n // Parse selector\n let [tag, classNames, attribute, value] = [null, [], null, null];\n const attributeMatch = /\\[([^\\]]+)\\]/.exec(selector);\n if (attributeMatch) {\n [attribute, value] = attributeMatch[1].split('=');\n }\n const classMatches = /\\.([^.\\[]+)/g;\n let matchResult;\n while ((matchResult = classMatches.exec(selector)) !== null) {\n classNames.push(matchResult[1]);\n }\n tag = selector.split(/[\\.\\[]/)[0];\n \n // Check tag match\n if (tag && layer.tag !== tag) match = false;\n else match = true;\n \n // Check class match\n if (classNames.length > 0) {\n if (!layer.props || !layer.props.class) match = false;\n else {\n const layerClasses = layer.props.class.split(' ');\n match = classNames.every(className => layerClasses.includes(className));\n }\n }\n \n // Check attribute match\n if (attribute) {\n if (value) {\n // Ensure attribute is matched specifically, not just any property\n if (layer.props && layer.props[attribute] !== value) match = false;\n } else {\n // Ensure attribute is matched specifically, not just any property\n if (!layer.props || !layer.props.hasOwnProperty(attribute)) match = false;\n }\n }\n \n // Check pseudo-class match\n if (match && pseudoClass) {\n const total = layers.length;\n if (!matchesPseudoClass(layer, pseudoClass, index, total)) match = false;\n }\n \n // Apply selection\n if (match) {\n layer.state.selected = true;\n if (!data.selectedLayerIds.includes(layer.id)) {\n data.selectedLayerIds.push(layer.id);\n }\n }\n \n // Recursively apply to children\n if (layer.children && layer.children.length > 0) {\n processLayers(layer.children);\n }\n });\n \n activeCalls--; // Decrement the active call count\n \n // If this was the last active call, invoke the callback\n if (activeCalls === 0 && callback && typeof callback === 'function') {\n callback();\n }\n }\n \n // Start processing layers\n processLayers(layers);\n }\n\n function targetChildrenOfSelections(query, callback) {\n if (data.selectedLayerIds.length > 0) {\n // Find the layers by IDs and target their children\n const selectedLayers = data.selectedLayerIds.map(id => findLayerById(id, project.html));\n if (selectedLayers.length > 0) {\n // Collect children of selected layers\n const children = selectedLayers.flatMap(group => group.layer.children || []);\n // Select layers from children\n selectLayersRecursive(children, query);\n }\n\n // Invoke the callback if provided\n if (typeof callback === 'function') {\n callback();\n }\n }\n }\n\n function processQuery(query) {\n // Find the index of the '=' symbol\n const equalsIndex = query.indexOf('=');\n \n // If '=' is found, convert the portion before it to lowercase\n if (equalsIndex !== -1) {\n const prefix = query.slice(0, equalsIndex).toLowerCase();\n query = prefix + query.slice(equalsIndex);\n }\n \n if (query.startsWith('t=')) {\n if (replaceSelection) clearAllSelections();\n selectLayersRecursive(project.html, query.slice(2));\n } else if (query.startsWith('r=')) {\n clearAllSelections();\n selectLayersRecursive(project.html, query.slice(2), () => {\n deleteLayers();\n });\n } else if (query.startsWith('e=')) {\n clearAllSelections();\n selectLayersRecursive(project.html, query.slice(2), () => {\n emptyChildren();\n });\n } else if (query.startsWith('c=')) {\n if (data.selectedLayerIds.length > 0) {\n if (data.replaceCurrentSelection) {\n let currentIDs = [...data.selectedLayerIds];\n targetChildrenOfSelections(query.slice(2), () => {\n currentIDs.forEach(id => {\n const { layer } = findLayerById(id, project.html);\n layer.state.selected = false;\n });\n });\n } else {\n targetChildrenOfSelections(query.slice(2));\n }\n } else {\n console.error('no layers selected');\n }\n } else if (query.startsWith('mv=')) {\n if (data.selectedLayerIds.length > 0) {\n if (replaceSelection) clearAllSelections();\n cutLayers(() => {\n selectLayersRecursive(project.html, query.slice(3), () => {\n pasteLayers();\n });\n });\n }\n } else if (query.startsWith('rs=')) {\n if (data.selectedLayerIds.length > 0) {\n if (replaceSelection) clearAllSelections();\n clearStyles(project.html, query.slice(3), () => {\n saveState(); // Callback after clearStyles completes\n });\n }\n } else {\n handleSpecialCommand(query);\n }\n }\n\n queries.forEach(processQuery);\n}\nwindow.toggleCollapse = layerId => {\n if (project.activePanel !== 'layers') project.activePanel = 'layers';\n let targetLayer = null;\n let parentLayer = null;\n\n // Function to recursively find the target layer and its parent\n function findLayerAndParent(layer, parent = null) {\n if (layer.id === layerId) {\n targetLayer = layer;\n parentLayer = parent;\n return true; // Found the layer\n }\n\n if (layer.children) {\n for (let i = 0; i < layer.children.length; i++) {\n if (findLayerAndParent(layer.children[i], layer)) {\n return true; // Found the layer in children\n }\n }\n }\n\n return false; // Layer not found\n }\n\n // Function to collapse or uncollapse all siblings to match the target layer's state\n function applyCollapseStateToSiblings(layers, collapseState) {\n layers.forEach(layer => {\n if (layer !== targetLayer) {\n layer.state.collapsed = collapseState;\n }\n });\n }\n\n // Check top-level layers directly\n for (let i = 0; i < project.html.length; i++) {\n let layer = project.html[i];\n if (layer.id === layerId) {\n targetLayer = layer;\n parentLayer = null; // No parent for top-level layers\n break;\n } else {\n findLayerAndParent(layer);\n }\n }\n\n if (targetLayer) {\n // Toggle the target layer's collapse state\n const newCollapseState = !targetLayer.state.collapsed;\n targetLayer.state.collapsed = newCollapseState;\n\n if (data.shiftKey) {\n if (parentLayer) {\n // Apply to siblings within the same parent layer\n applyCollapseStateToSiblings(parentLayer.children, newCollapseState);\n } else {\n // Apply to all top-level layers\n applyCollapseStateToSiblings(project.html, newCollapseState);\n }\n }\n\n // Render the application (if needed)\n App.render(\"#app\");\n }\n}\nwindow.foldAllLayers = (state = false) => {\n if (project.activePanel !== 'layers') project.activePanel = 'layers';\n function collapseLayer(layer) {\n layer.state.collapsed = state;\n if (layer.children) layer.children.forEach(child => collapseLayer(child));\n }\n\n project.html.forEach(layer => collapseLayer(layer));\n}\nwindow.hideAllLayers = (state = false) => {\n if (project.activePanel !== 'layers') project.activePanel = 'layers';\n function hideLayer(layer) {\n layer.state.visible = !state;\n if (layer.children) layer.children.forEach(child => hideLayer(child));\n }\n\n project.html.forEach(layer => hideLayer(layer));\n}\nwindow.toggleVisible = layerId => {\n if (project.activePanel !== 'layers') project.activePanel = 'layers';\n let targetLayer = null;\n let parentLayer = null;\n\n // Function to recursively find the target layer and its parent\n function findLayerAndParent(layer, parent = null) {\n if (layer.id === layerId) {\n targetLayer = layer;\n parentLayer = parent;\n return true; // Found the layer\n }\n\n if (layer.children) {\n for (let i = 0; i < layer.children.length; i++) {\n if (findLayerAndParent(layer.children[i], layer)) {\n return true; // Found the layer in children\n }\n }\n }\n\n return false; // Layer not found\n }\n\n // Function to set visibility for all siblings to match the target layer's state\n function applyVisibilityToSiblings(layers, visibilityState) {\n layers.forEach(layer => {\n if (layer !== targetLayer) {\n layer.state.visible = visibilityState;\n }\n });\n }\n\n // Check top-level layers directly\n for (let i = 0; i < project.html.length; i++) {\n let layer = project.html[i];\n if (layer.id === layerId) {\n targetLayer = layer;\n parentLayer = null; // No parent for top-level layers\n break;\n } else {\n findLayerAndParent(layer);\n }\n }\n\n if (targetLayer) {\n // Toggle the target layer's visibility state\n const newVisibilityState = !targetLayer.state.visible;\n targetLayer.state.visible = newVisibilityState;\n\n // Apply the new visibility state to all siblings\n if (data.shiftKey) {\n if (parentLayer) {\n // Apply to siblings within the same parent layer\n applyVisibilityToSiblings(parentLayer.children, newVisibilityState);\n } else {\n // Apply to all top-level layers\n applyVisibilityToSiblings(project.html, newVisibilityState);\n }\n }\n }\n}\nwindow.selectedBlock = layerId => {\n if (project.activePanel !== 'layers') project.activePanel = 'layers';\n let targetLayer = null;\n let parentLayer = null;\n\n // Function to find the layer and its parent\n function findLayerAndParent(layer, parent = null) {\n if (layer.id === layerId) {\n targetLayer = layer;\n parentLayer = parent;\n return true; // Found the layer\n }\n\n if (layer.children) {\n for (let i = 0; i < layer.children.length; i++) {\n if (findLayerAndParent(layer.children[i], layer)) {\n return true; // Found the layer in children\n }\n }\n }\n\n return false; // Layer not found\n }\n\n // Apply selection state to all siblings\n function applySelectionToSiblings(layers, selectionState) {\n layers.forEach(layer => {\n if (layer !== targetLayer) {\n const childIndex = data.selectedLayerIds.indexOf(layer.id);\n if (selectionState) {\n if (childIndex === -1) {\n data.selectedLayerIds.push(layer.id);\n layer.state.selected = true;\n }\n } else {\n if (childIndex > -1) {\n data.selectedLayerIds.splice(childIndex, 1);\n layer.state.selected = false;\n }\n }\n }\n });\n }\n\n // Check top-level layers directly\n for (let i = 0; i < project.html.length; i++) {\n let layer = project.html[i];\n if (layer.id === layerId) {\n targetLayer = layer;\n parentLayer = null; // No parent for top-level layers\n break;\n } else {\n findLayerAndParent(layer);\n }\n }\n\n if (targetLayer) {\n const isSelected = data.selectedLayerIds.includes(layerId);\n const newSelectionState = !isSelected;\n\n // If replaceCurrentSelection is true and shiftKey is not pressed, clear all selections\n if (data.cmdKey && !data.shiftKey) {\n data.selectedLayerIds.forEach(id => {\n const layer = findLayerById(id);\n if (layer) {\n layer.state.selected = false;\n }\n });\n data.selectedLayerIds = []; // Clear all selections\n }\n\n // Toggle selection state of the target layer\n if (newSelectionState) {\n data.selectedLayerIds.push(layerId);\n const lastSelectedLayerId = data.selectedLayerIds[data.selectedLayerIds.length - 1];\n const layer = findLayerById(lastSelectedLayerId);\n data.stylesTarget = layer.style;\n data.breakpointKey = null;\n } else {\n const index = data.selectedLayerIds.indexOf(layerId);\n if (index > -1) {\n data.selectedLayerIds.splice(index, 1);\n }\n }\n targetLayer.state.selected = newSelectionState;\n\n // Apply selection state to siblings\n if (data.shiftKey) {\n if (parentLayer) {\n // Apply to siblings within the same parent layer\n applySelectionToSiblings(parentLayer.children, newSelectionState);\n } else {\n // Apply to all top-level layers\n applySelectionToSiblings(project.html, newSelectionState);\n }\n }\n }\n\n function findLayerById(id) {\n let foundLayer = null;\n for (let i = 0; i < project.html.length; i++) {\n function searchLayer(layer) {\n if (layer.id === id) {\n foundLayer = layer;\n return true;\n }\n if (layer.children) {\n for (let j = 0; j < layer.children.length; j++) {\n if (searchLayer(layer.children[j])) {\n return true;\n }\n }\n }\n return false;\n }\n searchLayer(project.html[i]);\n if (foundLayer) break;\n }\n return foundLayer;\n }\n}\nwindow.collectSelectedIDs = layers => {\n layers.forEach(layer => {\n if (layer.state.selected) {\n data.selectedLayerIds.push(layer.id);\n }\n if (layer.children && layer.children.length > 0) {\n collectSelectedIDs(layer.children);\n }\n });\n}\nwindow.clearAllSelections = () => {\n data.selectedLayerIds = [];\n data.stylesTarget = null;\n clearSelection(project.html);\n}\nwindow.clearSelection = layers => {\n layers.forEach(layer => {\n layer.state.selected = false;\n if (layer.children) clearSelection(layer.children);\n });\n}\nwindow.clearSelectionExcept = (excludeId, layers) => {\n layers.forEach(layer => {\n if (layer.id !== excludeId) {\n layer.state.selected = false;\n } else {\n layer.state.selected = true;\n }\n if (layer.children) clearSelectionExcept(excludeId, layer.children);\n });\n}\nwindow.findLayerById = (id, layers, parent = null) => {\n for (const layer of layers) {\n if (layer.id === id) return { layer, parent };\n if (layer.children) {\n const found = findLayerById(id, layer.children, layer);\n if (found) return found;\n }\n }\n return null;\n}\nwindow.canAcceptChildren = layer => {\n const elementsThatDontAcceptChildren = [\n 'audio',\n 'datalist',\n 'iframe',\n 'img',\n 'input',\n 'meter',\n 'option',\n 'progress',\n 'select',\n 'textarea',\n 'video'\n ]; \n \n return !elementsThatDontAcceptChildren.includes(layer.tag);\n}\nwindow.addBlock = html => {\n saveState(); // Save state before making changes\n\n // Function to assign an ID to each new block\n const assignIds = (blocks, callback) => {\n blocks.forEach(block => {\n block.id = generateId(); // Assign a new ID\n if (block.children) {\n assignIds(block.children); // Recursively assign IDs to children if they exist\n }\n });\n\n if (callback && typeof callback === 'function') {\n callback(); // Call the callback function after all IDs have been assigned\n }\n };\n\n // Function to handle processing of HTML string or object\n const processHtmlOrObject = html => {\n if (typeof html === 'string') {\n return html2json(html); // Convert HTML string to JSON\n } else if (typeof html === 'object') {\n // Assume it's already a block object or an array of block objects\n return Array.isArray(html) ? html : [html];\n } else {\n console.error('Invalid HTML input. Expected a string or an object.');\n return [];\n }\n };\n\n // Process the input HTML or object\n const newBlocks = processHtmlOrObject(html);\n\n if (data.selectedLayerIds.length > 0) {\n data.selectedLayerIds.forEach(id => {\n const result = findLayerById(id, project.html);\n if (result) {\n const { layer, parent } = result;\n\n // Set the selected state to false and remove the ID from selectedLayerIds\n layer.selected = false;\n data.selectedLayerIds = data.selectedLayerIds.filter(layerId => layerId !== id);\n\n if (canAcceptChildren(layer)) {\n if (data.blockWrap) {\n // Ensure the new block can hold children\n const parentBlock = newBlocks[0];\n if (canAcceptChildren(parentBlock)) {\n // Ensure `parentBlock.children` is initialized\n parentBlock.children = parentBlock.children || [];\n parentBlock.children.push(layer); // Make the selected layer a child of the new block\n\n // Assign IDs to the new parent block and its children\n assignIds(newBlocks, () => {\n // Push the new parent block to the original parent's children\n if (parent && parent.children) {\n parent.children = parent.children.map(child =>\n child.id === layer.id ? parentBlock : child\n );\n } else {\n project.html = project.html.map(child =>\n child.id === layer.id ? parentBlock : child\n );\n }\n });\n }\n } else {\n // Ensure `layer.children` is initialized\n layer.children = layer.children || [];\n\n // Assign IDs and then push new blocks\n assignIds(newBlocks, () => {\n newBlocks.forEach(newBlock => {\n layer.children.push(newBlock); // Push new block after ID assignment\n });\n });\n }\n }\n }\n });\n } else {\n // If user has no layers selected, add to the root layer structure\n assignIds(newBlocks, () => {\n newBlocks.forEach(newBlock => project.html.push(newBlock)); // Push new block after ID assignment\n });\n }\n\n clearAllSelections();\n saveState(); // Save state after making changes\n document.querySelector('dialog[open]').querySelector('header > button').onclick();\n};\n\n\nwindow.selectLayersByStyleRef = (style, layers) => {\n for (const layer of layers) {\n // Deselect all layers\n layer.state.selected = false;\n\n // Check if the current layer matches the style reference\n if (layer.style === style) {\n data.selectedLayerIds.push(layer.id);\n layer.state.selected = true;\n // Continue searching in children even if the parent is selected\n }\n\n // Recursively check children if they exist\n if (layer.children && layer.children.length > 0) {\n selectLayersByStyleRef(style, layer.children);\n }\n }\n};\nwindow.deleteLayers = () => {\n saveState(); // Save state before making changes\n data.editorNavState = true;\n data.selectedLayerIds.forEach(id => {\n removeLayerById(id, project.html);\n });\n data.selectedLayerIds = []; // Clear selection after deletion\n data.editorNavState = null;\n saveState(); // Save state after making changes\n}\nwindow.removeLayerById = (id, layers) => {\n for (const layer of layers) {\n if (layer.id === id) {\n const index = layers.findIndex(l => l.id === id);\n layers.splice(index, 1); // Remove layer from the main layers array\n return;\n }\n\n if (layer.children) {\n const index = layer.children.findIndex(child => child.id === id);\n if (index !== -1) {\n layer.children.splice(index, 1); // Remove from children\n return;\n } else {\n removeLayerById(id, layer.children); // Recursively remove from nested layers\n }\n }\n }\n}\nwindow.cloneLayers = () => {\n let modalContent = `\n button:last-child').onclick();\n }\n \"\n />\n
`;\n\n Modal.render({\n title: `How many times do you want to clone this block?`,\n content: modalContent,\n onLoad() {\n document.getElementById('b40h7qc6d').focus();\n document.getElementById('b40h7qc6d').select();\n },\n onConfirm() {\n saveState(); // Save state before making changes\n\n const cloneCount = parseInt(data.increment, 10);\n \n if (isNaN(cloneCount) || cloneCount <= 0) {\n console.error('Invalid clone count:', cloneCount);\n return;\n }\n\n data.selectedLayerIds.forEach(id => {\n const { layer, parent } = findLayerById(id, project.html);\n \n if (layer) {\n for (let i = 0; i < cloneCount; i++) {\n const clonedLayer = cloneLayerObject(layer);\n \n if (parent && Array.isArray(parent.children)) {\n const index = parent.children.findIndex(child => child.id === layer.id);\n if (index !== -1) {\n parent.children.splice(index + 1, 0, clonedLayer);\n } else {\n console.error(\"Selected layer not found in parent's children:\", layer);\n }\n } else if (!parent) {\n const index = project.html.findIndex(rootLayer => rootLayer.id === layer.id);\n if (index !== -1) {\n project.html.splice(index + 1, 0, clonedLayer);\n } else {\n console.error('Selected layer not found in root layer structure:', layer);\n }\n }\n }\n } else {\n console.error('Layer not found for ID:', id);\n }\n });\n \n clearAllSelections(); // Clear selection after cloning\n saveState(); // Save state after making changes\n }\n });\n}\n\nwindow.cloneLayerObject = layer => {\n const clonedLayer = JSON.parse(JSON.stringify(layer)); // Deep clone\n clonedLayer.id = generateId(); // Assign a new ID\n\n if (clonedLayer.children) {\n clonedLayer.children = clonedLayer.children.map(child => cloneLayerObject(child)); // Clone children recursively\n }\n return clonedLayer;\n}\nwindow.cutLayers = callback => {\n saveState(); // Save state before making changes\n data.editorNavState = true;\n copyLayers();\n data.selectedLayerIds.forEach(id => {\n removeLayerById(id, project.html);\n });\n data.selectedLayerIds = []; // Clear selection after deletion\n saveState(); // Save state after making changes\n data.editorNavState = null;\n\n // Call the callback function if provided\n if (callback && typeof callback === 'function') {\n callback();\n }\n}\nwindow.copyLayers = () => {\n data.clipboard = data.selectedLayerIds.map(id => {\n const { layer } = findLayerById(id, project.html);\n return cloneLayerObject(layer); // Clone layer without deleting\n });\n}\nwindow.pasteLayers = () => {\n saveState(); // Save state before making changes\n if (data.clipboard.length > 0) {\n const pastedLayers = data.clipboard.map(layer => {\n return cloneLayerObject(layer); // Clone layer with new IDs\n });\n\n if (data.selectedLayerIds.length > 0) {\n data.selectedLayerIds.forEach(id => {\n const { layer } = findLayerById(id, project.html);\n if (layer && canAcceptChildren(layer)) {\n layer.children = layer.children || [];\n layer.children.push(...pastedLayers);\n }\n });\n } else {\n project.html.push(...pastedLayers); // Paste to root if no layer selected\n }\n\n data.clipboard = []; // Clear clipboard after pasting\n clearAllSelections(); // Clear selection after pasting\n saveState(); // Save state after making changes\n }\n}\nwindow.removeAttributeFromLayers = property => {\n saveState();\n data.selectedLayerIds.forEach(id => {\n const { layer } = findLayerById(id, project.html);\n // Delete the key from the props object\n if (layer) delete layer.props[property];\n });\n saveState();\n}\nwindow.removeProp = key => {\n Modal.render({\n title: `Are you sure you want to delete the ${key} attribute?`,\n content: `\n You will still be able to undo.
\n `,\n onConfirm() {\n removeAttributeFromLayers(key);\n }\n });\n}\nwindow.emptyChildren = () => {\n saveState(); // Save state before making changes\n if (data.selectedLayerIds.length > 0) {\n data.selectedLayerIds.forEach(id => {\n const { layer } = findLayerById(id, project.html);\n if (layer.children) layer.children = [];\n if (layer.text) {\n layer.text = '';\n }\n });\n }\n saveState(); // Save state after making changes\n}\nwindow.updateElement = (key, propKey, value, initIncrement = false) => {\n const incrementPattern = /{n}/g; // Pattern to detect increment placeholder\n \n saveState();\n data.selectedLayerIds.forEach((id, index) => {\n const { layer } = findLayerById(id, project.html);\n if (layer) {\n if (key !== 'props') {\n if (key === 'text') {\n if (!value) {\n layer.text = \"\";\n } else {\n if (initIncrement && incrementPattern.test(value)) {\n // If initIncrement is true, increment the {n} pattern\n layer.text = value.replace(incrementPattern, index + 1);\n } else {\n layer.text = value; // Otherwise, just use the given value\n }\n }\n } else {\n // General case for non-text keys\n layer[`${key}`] = value;\n }\n } else {\n // Handling props (e.g., attributes)\n if (initIncrement && incrementPattern.test(value)) {\n // If initIncrement is true, increment the {n} pattern in props\n layer.props[`${propKey}`] = value.replace(incrementPattern, index + 1);\n } else {\n layer.props[`${propKey}`] = value; // Otherwise, just use the given value\n }\n }\n }\n });\n saveState();\n};\nwindow.updateImageMedia = (id, type) => {\n let target = findLayerById(id, project.html).layer.props['src'];\n let modalContent = `\n
\n
\n
\n
\n
\n
`;\n \n Modal.render({\n title: \"Are you sure you want to replace the image source?\",\n content: modalContent,\n onLoad() {\n const searchField = document.getElementById('search-input');\n searchField.focus();\n \n const handleSearch = async () => {\n if (searchField.value) {\n const results = await searchOpenverseImage(searchField.value);\n displayResults(results);\n } else {\n document.getElementById('search-results').innerHTML = '';\n return false;\n }\n };\n \n searchField.oninput = handleSearch;\n document.getElementById('search-btn').onclick = handleSearch;\n\n function displayResults(results) {\n const resultsContainer = document.getElementById('search-results');\n resultsContainer.innerHTML = results.map(result => `\n \n `).join('');\n }\n },\n onConfirm() {\n data.selectedLayerIds.forEach(id => {\n const { layer } = findLayerById(id, project.html);\n if (layer) {\n if (layer.tag === \"img\") {\n saveState();\n layer.props[`src`] = document.getElementById('p8gnvn4o7').src;\n saveState();\n }\n }\n });\n }\n });\n}\nwindow.searchOpenverseImage = async query => {\n const url = `https://api.openverse.org/v1/images?q=${encodeURIComponent(query)}`;\n const response = await fetch(url);\n if (response.ok) {\n const data = await response.json();\n return data.results;\n } else {\n console.error(\"API request failed:\", response.status);\n return [];\n }\n}\nwindow.updateAudioMedia = (id, type) => {\n let target = findLayerById(id, project.html).layer;\n if (target.tag !== 'audio' || type !== 'audio') return;\n let uniqueId = generateId();\n if (!target.props) target.props = {};\n if (target.props.id) target.props.id = uniqueId;\n if (!target.props.id) target.props['id'] = uniqueId;\n \n const audioHTML = json2html([target]);\n let modalContent = `\n \n \n
\n
\n
\n
\n
\n
`;\n \n Modal.render({\n title: \"Are you sure you want to replace the audio element?\",\n content: modalContent,\n onLoad() {\n const searchInput = document.getElementById('search-input');\n const searchBtn = document.getElementById('search-btn');\n const resultsContainer = document.getElementById('search-results');\n searchInput.focus();\n \n const handleSearch = async () => {\n if (searchInput.value) {\n const results = await searchOpenverseAudio(searchInput.value);\n displayResults(results);\n } else {\n resultsContainer.innerHTML = '';\n return false;\n }\n };\n \n searchInput.oninput = handleSearch;\n searchBtn.onclick = handleSearch;\n\n function displayResults(results) {\n if (results.length === 0) {\n resultsContainer.innerHTML = `No results found.
`;\n } else {\n resultsContainer.innerHTML = results.map(result => `\n \n
${result.title}
\n
\n
\n `).join('');\n \n // Reinitialize audio elements to ensure they work properly\n const audios = resultsContainer.querySelectorAll('audio');\n audios.forEach(audio => {\n const src = audio.querySelector('source').getAttribute('src');\n audio.load(); // Ensure the audio element is fully loaded\n audio.src = src; // Re-set the src to trigger playback readiness\n });\n }\n }\n },\n onConfirm() {\n data.selectedLayerIds.forEach(id => {\n const { layer } = findLayerById(id, project.html);\n if (layer) {\n source = document.getElementById(uniqueId).outerHTML;\n let obj = html2json(source)[0];\n \n saveState();\n // Update properties directly instead of reassigning the whole object\n Object.keys(obj).forEach(key => {\n if (key === \"id\") return;\n layer[key] = obj[key];\n });\n findLayerById(id, project.html).layer.state.selected = null;\n findLayerById(id, project.html).layer.state.selected = true;\n saveState();\n }\n });\n }\n });\n}\nwindow.searchOpenverseAudio = async query => {\n const url = `https://api.openverse.org/v1/audio?q=${encodeURIComponent(query)}`;\n const response = await fetch(url);\n if (response.ok) {\n const data = await response.json();\n return data.results;\n } else {\n console.error(\"API request failed:\", response.status);\n return [];\n }\n}\nwindow.updateMediaSource = async (event, type, element) => {\n const file = event.target.files[0];\n if (!file) return; // If no file selected, return\n\n try {\n // Check if the file is an SVG\n if (type === \"svg\") {\n // Read the file content as text (SVG markup)\n const svgCode = await file.text();\n \n // Update target with SVG code\n document.getElementById('vl61t8366').querySelector('svg').outerHTML = svgCode;\n document.getElementById('vl61t8366').querySelector('svg').setAttribute('id', 'p8gnvn4o7');\n } else {\n // Handle non-SVG and non-image files (e.g., convert to base64)\n const base64String = await fileToBase64(file);\n element.setAttribute('src', base64String);\n }\n } catch (error) {\n console.error('Error reading file:', error);\n }\n}\nwindow.checkApiConnection = async () => {\n try {\n const response = await fetch('https://api.iconify.design/collections');\n if (response.ok) {\n return true;\n }\n } catch (error) {\n console.error(\"API connection failed:\", error);\n }\n return false;\n}\nwindow.fetchIconifySvg = async icon => {\n const hosts = [\n `https://api.iconify.design/${icon}.svg`,\n `https://api.simplesvg.com/${icon}.svg`,\n `https://api.unisvg.com/${icon}.svg`\n ];\n\n for (const url of hosts) {\n try {\n const response = await fetch(url, { timeout: 750 });\n if (response.ok) {\n return await response.text();\n } else if (response.status === 404) {\n console.warn(`Icon not found at ${url}`);\n continue;\n }\n } catch (error) {\n console.warn(`Failed to fetch from ${url}:`, error);\n }\n }\n\n throw new Error(\"Icon not found or all hosts are unreachable.\");\n}\nwindow.searchIcons = async query => {\n const searchUrl = `https://api.iconify.design/search?query=${encodeURIComponent(query)}`;\n try {\n const response = await fetch(searchUrl);\n if (response.ok) {\n const data = await response.json();\n return data.icons || [];\n } else {\n console.error(\"Failed to fetch icon search results.\");\n }\n } catch (error) {\n console.error(\"Error during icon search:\", error);\n }\n return [];\n}\nwindow.updateSvgMedia = async (id, type) => {\n let title = \"Replace the SVG\";\n const target = findLayerById(id, project.html).layer;\n let display = \"\";\n const elm = document.createElement(\"template\");\n elm.innerHTML = json2html(target);\n const element = elm.content.firstElementChild;\n if (element) {\n display = ``;\n }\n elm.remove();\n\n let modalContent = `\n \n
\n
\n
\n
\n
`;\n\n Modal.render({\n title: title,\n content: modalContent,\n onLoad: async function() {\n const apiConnection = await checkApiConnection();\n const descriptionElement = document.getElementById('modal-description');\n const searchElement = document.getElementById('iconSearch');\n const iconResults = document.getElementById('iconResults');\n\n if (navigator.onLine && apiConnection) {\n descriptionElement.innerHTML = `Api courtesy of \n Iconify.\n `;\n searchElement.classList.remove('hidden');\n searchElement.focus();\n iconResults.classList.remove('hidden');\n } else {\n descriptionElement.textContent = 'Upload your SVG:';\n }\n },\n onConfirm: function() {\n data.selectedLayerIds.forEach(id => {\n const { layer } = findLayerById(id, project.html);\n if (layer && layer.tag === \"svg\") {\n const selectedSvg = document.getElementById('vl61t8366').querySelector('svg');\n if (selectedSvg) {\n let obj = html2json(selectedSvg.outerHTML)[0];\n saveState();\n Object.keys(obj).forEach(key => {\n if (key === \"id\") return;\n layer[key] = obj[key];\n });\n findLayerById(id, project.html).layer.state.selected = null;\n findLayerById(id, project.html).layer.state.selected = true;\n saveState();\n }\n }\n });\n }\n });\n}\nwindow.handleIconSearch = async event => {\n const query = event.target.value;\n const iconResultsElement = document.getElementById('iconResults');\n if (query.length > 2) {\n const icons = await searchIcons(query);\n \n iconResultsElement.innerHTML = ''; // Clear previous results\n\n for (const icon of icons) {\n try {\n const iconUrl = `https://api.iconify.design/${icon}.svg`;\n getFile(iconUrl, (error, svgContent) => {\n if (error) {\n console.error(\"Failed to fetch SVG:\", error);\n } else {\n const iconDiv = document.createElement('div');\n iconDiv.innerHTML = svgContent;\n iconDiv.onclick = () => {\n const selectedSvgElement = document.querySelector(\"#vl61t8366 label svg\");\n if (selectedSvgElement) {\n selectedSvgElement.outerHTML = svgContent; // Replace the outerHTML with the selected SVG\n iconDiv.closest('article').scrollTop = 0;\n }\n };\n iconResultsElement.appendChild(iconDiv);\n }\n });\n } catch (error) {\n console.warn(`Failed to fetch SVG for icon: ${icon}`, error);\n }\n }\n } else {\n const iconResultsElement = document.getElementById('iconResults');\n iconResultsElement.innerHTML = '';\n }\n}\nwindow.selectIcon = svgContent => {\n iconContainer.innerHTML = svgContent;\n}\nwindow.copyToClipboard = text => {\n navigator.clipboard.writeText(text).then(function() {\n }).catch(function(error) {\n console.error('Failed to copy text: ', error);\n });\n}\nwindow.collectComponents = layers => {\n const existingNames = new Set(project.components.map(comp => comp.name));\n\n layers.forEach(layer => {\n if (layer.isComponent) {\n // Check if the layer name already exists\n if (!existingNames.has(layer.name)) {\n const clone = { ...layer };\n let uniqueId = generateId();\n clone.id = uniqueId;\n\n project.components.push({\n id: uniqueId,\n name: layer.name,\n code: clone\n });\n\n // Add the new name to the set\n existingNames.add(layer.name);\n }\n }\n });\n}\nwindow.addComponent = () => {\n if (data.selectedLayerIds.length === 0) return;\n \n saveState(); // Save state before making changes\n\n data.selectedLayerIds.forEach(id => {\n const result = findLayerById(id, project.html);\n\n if (result) {\n const { layer } = result;\n const clone = { ...layer };\n clone.id = generateId();\n\n const newHtml = json2html(clone);\n\n // Check for duplicate name or HTML\n const isDuplicate = project.components.some(comp =>\n comp.name === clone.name || comp.code === newHtml\n );\n\n if (!isDuplicate) {\n project.components.push({\n name: clone.name,\n code: newHtml\n });\n } else {\n console.warn(`Component with name \"${clone.name}\" or identical HTML already exists.`);\n }\n } else {\n console.error('Layer not found for ID:', id);\n }\n });\n\n saveState(); // Save state after making changes\n}\nwindow.deleteComponent = index => {\n if (index >= 0 && index < project.components.length) {\n project.components.splice(index, 1);\n saveState(); // Save state after making changes\n } else {\n console.error('Invalid index:', index);\n }\n}\nwindow.commandPalette = () => {\n let buttonClass = `text-xs w-auto px-3 py-2 m-0 capitalize rounded-md bg-transparent border ${project.dark ? 'border-gray-600' : 'border-gray-400'}`;\n let commands = {\n \"fold all\": \"f\",\n \"unfold all\": \"u\",\n \"hide all\": \"h\",\n \"show all\": \"s\",\n \"empty children\": \"e\",\n \"clear all styles\": \"cas\"\n };\n\n // Generate buttons HTML from the commands object\n let buttonsHtml = Object.keys(commands).map(command => {\n return ` button').onclick();\n \"\n>\n ${command}\n`;\n }).join(''); // Join the array into a single string\n\n const guide = `\n -
Enter a Query:
\n Input a query in the format
t=tagname
,
t=.classname
,
t=[attribute=value]
, or
t=[id]
. You can also use pseudo-classes with the
t=
prefix like
t=.classname:first-child
.
\n\n -
Multiple Queries:
\n Separate multiple queries with a comma (e.g.,
t=li, t=.name
).
\n\n -
Pseudo-Classes:
\n You can use pseudo-classes to refine your selection. Supported pseudo-classes include:
\n
\n :first-child
- Selects the first child element. \n :last-child
- Selects the last child element. \n :nth-child(n)
- Selects the nth child element. \n :nth-last-child(n)
- Selects the nth last child element. \n :only-child
- Selects elements that are the only child. \n :empty
- Selects elements without children. \n :first-of-type
- Selects the first element of its type. \n :last-of-type
- Selects the last element of its type. \n :nth-of-type(n)
- Selects the nth element of its type. \n :nth-last-of-type(n)
- Selects the nth last element of its type. \n :only-of-type
- Selects elements of its type that are the only one. \n
\n\n -
Targeting Selections:
\n Use the
t=
prefix to specify the type of selection:
\n
\n t=tagname
- Targets elements with the specified tag. \n t=.classname
- Targets elements with the specified class. \n t=[attribute]
- Targets elements with the specified attribute (without defining its value). \n t=[attribute=value]
- Targets elements with the specified attribute and value. \n t=.classname:pseudo-class
- Targets elements with the specified class and pseudo-class. \n
\n\n -
Operation Prefixes:
\n Use the following prefixes to perform operations on the targeted elements:
\n
\n r=
- Remove targeted elements. For example, r=tagname
will remove all elements matching the specified tag. \n e=
- Empty all children from the targeted elements. For example, e=.name
will empty the children of all elements with the class name
. \n c=
- Apply operations to the children of the targeted elements. For example, c=tagname
will select the children of currently selected elements that match the specified tag. \n mv=
- Move targeted elements. For example, mv=tagname
will cut the currently selected elements and paste them as elements matching the specified tag. \n rs=
- Remove styles from the project as well as targeted elements. For example, rs=styleName
will clear the specified style from all elements that currently have it applied. \n
\n\n -
Special Commands:
\n Use special commands to quickly fold, unfold, hide, show all layers, or empty all children from selections. The supported commands are:
\n
\n f
- Collapse all layers. \n u
- Uncollapse all layers. \n h
- Hide all layers. \n s
- Show all layers. \n cas
- Clear all styles. \n e
- Empty all children from selections. \n
\n If no query is provided and you click \"Confirm\", all current selections will be cleared automatically. (You can also do this using the shortcut
Shift+Ctrl+A
on Windows or
Shift+Cmd+A
on Mac)
\n\n -
Replace Current Selection:
\n Toggle the switch to decide whether to replace the current selection or add to it.
\n You can also hold the
Ctrl
key on Windows (
Cmd
key on Mac) to do this as well to target replacing current selection.
\n\n -
Execute:
\n Press Enter to run the command or query.
\n\n -
Close:
\n The palette will close automatically after executing a command.
\n You can also open it using the shortcut
Ctrl+Shift+P
on Windows or
Cmd+Shift+P
on Mac.
\n You can also use the
Esc
key to close every opened dialog.\n
`;\n\n // Check if data.commandPalette is true\n if (!data.commandPalette) {\n data.commandPalette = true;\n\n // Modal rendering code\n Modal.render({\n title: \"Command Palette...\",\n content: `\n \n
button').onclick();\n }\n \"\n />\n
\n \n \n
\n
\n
\n \n Commands\n
\n \n ${buttonsHtml}\n
\n \n
\n
\n \n How to use the Command Palette:\n
\n ${guide}\n \n
`,\n onLoad() {\n document.getElementById('olphbh94a').focus();\n },\n onClose() {\n data.commandPalette = null;\n },\n onConfirm() {\n const query = document.getElementById('olphbh94a').value.trim();\n executeQuery(query, data.replaceCurrentSelection);\n data.commandPalette = null;\n }\n });\n }\n}\nwindow.updateVersionPart = (part, value) => {\n const versionParts = project.version.split('.');\n if (part === 'major') {\n versionParts[0] = value;\n } else if (part === 'minor') {\n versionParts[1] = value;\n } else if (part === 'patch') {\n versionParts[2] = value;\n }\n project.version = versionParts.join('.');\n}\n\n// iframe functions\nwindow.resizeCanvas = size => {\n data.selectedSize = size;\n getIFrameClientSize();\n}\nwindow.rotateCanvas = () => {\n const iframe = document.getElementById('previewElm').firstElementChild;\n if (iframe.style.width === '100%') return false;\n\n // Extract current width and height\n let width = parseInt(iframe.style.width);\n let height = parseInt(iframe.style.height);\n\n // Swap width and height\n [width, height] = [height, width];\n data.selectedSize = width+'x'+height;\n getIFrameClientSize();\n}\nlet fadeTimeout;\nwindow.getIFrameClientSize = () => {\n // resize canvas\n const iframe = document.getElementById('iframe');\n if (iframe.style.width !== '100%') {\n // Extract current width and height\n let width = parseInt(iframe.style.width);\n let height = parseInt(iframe.style.height);\n \n // Calculate the new transform scale\n const viewportWidth = previewElm.clientWidth;\n const viewportHeight = previewElm.clientHeight;\n const scale = Math.min(viewportWidth / width, viewportHeight / height);\n \n // Apply the new styles\n iframe.style.width = `${width}px`;\n iframe.style.height = `${height}px`;\n iframe.style.transform = `scale(${scale})`;\n iframe.style.marginTop = `-${height / 2}px`;\n iframe.style.marginLeft = `-${width / 2}px`;\n }\n\n data.iframeSize = `${iframe.clientWidth}px x ${iframe.clientHeight}px`;\n const element = document.getElementById('iframeClientSize');\n\n if (element.classList.contains('hidden')) {\n // Clear existing timeout to prevent multiple calls\n if (fadeTimeout) clearTimeout(fadeTimeout);\n\n // Remove hidden and add opacity-100 to show the element\n element.classList.remove('hidden', 'opacity-0');\n element.classList.add('opacity-100');\n\n // Set a timeout to handle fade-out\n fadeTimeout = setTimeout(() => {\n element.classList.remove('opacity-100');\n element.classList.add('opacity-0');\n\n // Add hidden class after fade-out\n setTimeout(() => {\n element.classList.add('hidden');\n }, 300); // Match the duration of the opacity transition\n }, 2000); // Show duration\n }\n}\n\n// save functions\nwindow.handleLogoChange = async event => {\n const file = event.target.files[0];\n if (!file) return; // If no file selected, return\n\n try {\n // Convert file to base64 string\n const base64String = await fileToBase64(file);\n // Update project.logo with base64String\n project.logo = base64String;\n } catch (error) {\n console.error('Error converting image to base64:', error);\n }\n}\nwindow.fileToBase64 = file => {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.readAsDataURL(file);\n reader.onload = () => resolve(reader.result);\n reader.onerror = error => reject(error);\n });\n}\nwindow.importJSON = (obj, callback = null) => {\n if (obj === null) return;\n App.initialRender = true;\n data.selectedLayerIds = [];\n project.name = obj.name;\n project.version = obj.version;\n project.title = obj.title;\n project.description = obj.description;\n project.author = obj.author;\n project.url = obj.url;\n project.logo = obj.logo;\n project.dark = obj.dark;\n project.pwa = obj.pwa;\n project.activePanel = obj.activePanel;\n \n project.meta = obj.meta;\n project.libraries = obj.libraries;\n project.css = obj.css;\n project.html = obj.html;\n if (obj.components) {\n project['components'] = obj.components;\n collectComponents(project.html);\n }\n App.initialRender = null;\n collectSelectedIDs(project.html);\n App.render('#app');\n renderPreview(true);\n\n // Call the callback function if provided\n if (typeof callback === 'function') {\n callback();\n }\n}\nwindow.newProject = () => {\n const obj = {\n name: \"App name\",\n version: \"0.0.1\",\n title: \"An attractive title\",\n description: \"The most attractive description ever!\",\n author: \"Polyrise\",\n url: \"https://michaelsboost.com/\",\n meta: \"\",\n libraries: [],\n css: {\n \"rootVariables\": {},\n \"styles\": {},\n \"animations\": {},\n \"breakpoints\": {}\n },\n components: [],\n html: [],\n logo: \"data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgoKPHN2ZwogICB3aWR0aD0iNTEyIgogICBoZWlnaHQ9IjUxMiIKICAgdmlld0JveD0iMCAwIDEzNS40NjY2NiAxMzUuNDY2NjciCiAgIHZlcnNpb249IjEuMSIKICAgaWQ9InN2ZzEiCiAgIHhtbDpzcGFjZT0icHJlc2VydmUiCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM6c3ZnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnMKICAgICBpZD0iZGVmczEiIC8+PGcKICAgICBpZD0iZzI0Ij48cGF0aAogICAgICAgaWQ9InBhdGgyMiIKICAgICAgIHN0eWxlPSJkaXNwbGF5OmlubGluZTtmaWxsOiMxMzNhZDQ7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlLXdpZHRoOjk2LjE3NDtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46cm91bmQiCiAgICAgICBkPSJNIDkuNTgyODc3NSw2Ny43MzMzMzIgViAxMzUuMjAwNTMgTCAyNS4zODc1OTcsMTI2LjAzMTA3IFYgMTA2Ljk2MDQgNjcuNzMzMzMyIFogbSA4NS45Njg5MTE1LDAgLTU3Ljc2OTA4MywzMi4yOTcyNTggdiAxOC44MTA3MyBMIDEyNS44ODIyNCw2Ny43MzMzMzIgWiIgLz48cGF0aAogICAgICAgc3R5bGU9ImRpc3BsYXk6aW5saW5lO2ZpbGw6IzA0YTJmZjtmaWxsLW9wYWNpdHk6MTtzdHJva2Utd2lkdGg6MTQ7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7c3Ryb2tlLWxpbmVqb2luOnJvdW5kIgogICAgICAgaWQ9InBhdGgyMyIKICAgICAgIGQ9Im0gNDkuNTY4NTI3LDM1LjgxOTU1MyAtMTYuOTcwNDc4LDkuNzk3OTEgMCwtMTkuNTk1ODIgeiIKICAgICAgIHRyYW5zZm9ybT0ibWF0cml4KDIuMjE3MjY1MiwwLDAsMi4xNDcwMjkzLC0zNC40OTUyNjksLTkuMjYyMTYyKSIgLz48cGF0aAogICAgICAgaWQ9InBhdGgyNCIKICAgICAgIHN0eWxlPSJkaXNwbGF5OmlubGluZTtmaWxsOiM4NjAwZWY7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlLXdpZHRoOjk2LjE3NDtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46cm91bmQiCiAgICAgICBkPSJNIDkuNTgyODc3NSwwLjI2NjEzMzYyIFYgNjcuNzMzMzMyIEggMjUuMzg3NTk3IFYgNDIuODU2ODE1IDI4LjMyNjk1MyBsIDcwLjMyNTkzOSwzOS4zMTU5NDYgLTAuMTYxNzQ3LDAuMDkwNDMgaCAzMC4zMzA0NTEgbCAwLjAwMiwtMC4wMDEgeiIgLz48L2c+PC9zdmc+Cg==\",\n lang: \"en\",\n dark: true,\n previewDark: true,\n pwa: false,\n activePanel: 'layers'\n }\n\n let modalContent = `\n \n
All current data will be lost.
\n
\u2728 Click the image to start with a template! \uD83D\uDE80
\n
\n
\n
\n
`;\n\n Modal.render({\n title: \"Are you sure you want to start a new project?\",\n content: modalContent,\n onLoad() {\n // Set up the event listener once the modal is loaded\n document.getElementById('starter-project').onclick = () => {\n fetch('json/bootstrap-landing-page-demo.json')\n .then(response => response.json())\n .then(data => {\n importJSON(data, () => {\n if (document.querySelector('dialog[open]')) {\n document.querySelector('dialog[open]').querySelector('header > button:last-child').onclick();\n }\n if (document.querySelector('dialog[open]')) {\n document.querySelector('dialog[open]').querySelector('header > button:last-child').onclick();\n }\n });\n })\n .catch(error => {\n console.error('Error loading the starter project:', error);\n });\n };\n },\n onConfirm() {\n importJSON(obj);\n data.menuDialog = null;\n }\n });\n};\n\nwindow.emptyStorage = () => {\n Modal.render({\n title: \"Are you sure you want to empty storage?\",\n content: 'All current data will be lost.
',\n onConfirm() {\n // Clear local storage\n localStorage.removeItem('Polyrise');\n \n // Clear session storage specific to Polyrise (if you use a specific key)\n sessionStorage.removeItem('Polyrise');\n \n // Clear cookies specific to Polyrise\n document.cookie.split(\";\").forEach(function(c) {\n if (c.trim().startsWith('Polyrise')) {\n document.cookie = c.trim().split(\"=\")[0] + \n '=;expires=Thu, 01 Jan 1970 00:00:00 UTC;path=/';\n }\n });\n \n // Clear service worker caches specific to Polyrise\n if ('caches' in window) {\n caches.keys().then(function(names) {\n names.forEach(function(name) {\n if (name === 'Polyrise-cache') {\n caches.delete(name);\n }\n });\n });\n }\n \n // Unregister service workers specific to Polyrise\n if ('serviceWorker' in navigator) {\n navigator.serviceWorker.getRegistrations().then(function(registrations) {\n registrations.forEach(function(registration) {\n if (registration.scope.includes('Polyrise')) {\n registration.unregister();\n }\n });\n });\n }\n \n location.reload();\n }\n });\n}\nwindow.clearAllStorage = () => {\n // Clear local storage\n localStorage.clear();\n\n // Clear session storage\n sessionStorage.clear();\n\n // Clear all cookies\n document.cookie.split(\";\").forEach(function(cookie) {\n const cookieName = cookie.split(\"=\")[0].trim();\n document.cookie = cookieName + '=;expires=Thu, 01 Jan 1970 00:00:00 UTC;path=/';\n });\n\n // Clear all service workers\n if ('serviceWorker' in navigator) {\n navigator.serviceWorker.getRegistrations().then(function(registrations) {\n registrations.forEach(function(registration) {\n registration.unregister();\n });\n });\n }\n\n // Clear all caches\n if ('caches' in window) {\n caches.keys().then(function(names) {\n names.forEach(function(name) {\n caches.delete(name);\n });\n });\n }\n\n // Reload the page\n location.reload();\n}\nwindow.importProject = () => {\n Modal.render({\n title: \"Are you sure you want to load a new project?\",\n content: `All current data will be lost.
`,\n onClose: function () {\n data.menuDialog = true;\n },\n onConfirm: function() {\n const input = document.createElement('input');\n input.type = 'file';\n input.accept = '.json';\n\n input.addEventListener('change', (event) => {\n const file = event.target.files[0];\n \n if (!file) {\n console.error('No file selected.');\n return;\n }\n \n const reader = new FileReader();\n \n reader.onload = event => {\n try {\n importJSON(JSON.parse(event.target.result));\n } catch (error) {\n console.error('Error parsing JSON file:', error);\n }\n };\n \n reader.readAsText(file);\n input.remove();\n });\n \n input.click();\n }\n });\n}\nwindow.getFileNameAndType = url => {\n // Extract the file name with extension from the URL\n const fileName = url.substring(url.lastIndexOf('/') + 1);\n \n // Extract the file extension\n const fileExtension = fileName.split('.').pop().toLowerCase();\n \n // Map file extensions to MIME types\n const mimeTypes = {\n 'jpeg': 'image/jpeg',\n 'jpg': 'image/jpeg',\n 'png': 'image/png',\n 'gif': 'image/gif',\n 'bmp': 'image/bmp',\n 'webp': 'image/webp',\n 'svg': 'image/svg+xml',\n 'mp3': 'audio/mpeg',\n 'wav': 'audio/wav',\n 'ogg': 'audio/ogg',\n 'mp4': 'video/mp4',\n 'webm': 'video/webm',\n 'ogv': 'video/ogg'\n };\n \n // Get the MIME type based on the file extension\n const fileType = mimeTypes[fileExtension] || 'application/octet-stream';\n \n return {\n fileName,\n fileType\n };\n}\nwindow.fetchResources = obj => {\n try {\n const doc = new DOMParser().parseFromString(json2html(obj.html), 'text/html');\n const body = doc.body;\n\n const imageResources = [];\n const audioResources = [];\n const vectorResources = [];\n const videoResources = [];\n\n let fileCounter = 1;\n\n // Helper function to check if a string is Base64\n function isBase64(str) {\n return str.startsWith('data:') && str.includes('base64,');\n }\n\n // Helper function to extract file type from Base64 string\n function getBase64FileType(str) {\n const mimeMatch = str.match(/^data:(.*);base64,/);\n if (mimeMatch) {\n const mimeType = mimeMatch[1];\n return mimeTypeToExtension(mimeType);\n }\n return 'unknown';\n }\n\n // Helper function to map MIME types to file extensions\n function mimeTypeToExtension(mimeType) {\n const typeMap = {\n // Images\n 'image/jpeg': 'jpg',\n 'image/png': 'png',\n 'image/gif': 'gif',\n 'image/svg+xml': 'svg',\n 'image/webp': 'webp',\n 'image/tiff': 'tiff',\n 'image/bmp': 'bmp',\n 'image/x-icon': 'ico',\n\n // Audio\n 'audio/mpeg': 'mp3',\n 'audio/wav': 'wav',\n 'audio/ogg': 'ogg',\n 'audio/aac': 'aac',\n 'audio/webm': 'webm',\n 'audio/flac': 'flac',\n\n // Video\n 'video/mp4': 'mp4',\n 'video/webm': 'webm',\n 'video/ogg': 'ogv',\n 'video/avi': 'avi',\n 'video/mpeg': 'mpg',\n 'video/quicktime': 'mov',\n 'video/x-msvideo': 'avi',\n 'video/x-matroska': 'mkv',\n\n // Fallback for unknown types\n 'unknown': 'bin'\n };\n return typeMap[mimeType] || 'bin';\n }\n\n // Helper function to extract file name from URL\n function getFileName(url) {\n return url.substring(url.lastIndexOf('/') + 1);\n }\n\n // Generate a file name for Base64 resources\n function getBase64FileName() {\n return `file-${fileCounter++}`;\n }\n\n // Function to extract and process background images from CSS\n function extractBackgroundImageUrls(css) {\n const urls = [];\n const regex = /background-image\\s*:\\s*url\\(([^)]+)\\)/g;\n let match;\n while ((match = regex.exec(css)) !== null) {\n let url = match[1].replace(/['\"]/g, \"\"); // Remove quotes around URLs\n if (isBase64(url)) {\n const fileType = getBase64FileType(url);\n const fileName = `${getBase64FileName()}.${fileType}`;\n imageResources.push({ url: url, fileName: fileName });\n css = css.replace(url, `../imgs/${fileName}`);\n } else {\n const fileName = getFileName(url);\n imageResources.push({ url: url, fileName: fileName });\n css = css.replace(url, `../imgs/${fileName}`);\n }\n urls.push(url);\n }\n return css;\n }\n\n // Extract image URLs and filenames\n body.querySelectorAll('img').forEach(img => {\n if (img.hasAttribute('src')) {\n const src = img.getAttribute('src');\n\n if (isBase64(src)) {\n const fileType = getBase64FileType(src);\n const fileName = `${getBase64FileName()}.${fileType}`;\n imageResources.push({ url: src, fileName: fileName });\n img.src = `imgs/${fileName}`;\n } else {\n const fileName = getFileName(src);\n imageResources.push({ url: src, fileName: fileName });\n img.src = `imgs/${getFileNameAndType(src).fileName}`;\n }\n }\n\n if (img.hasAttribute('srcset')) {\n img.srcset.split(',').forEach(srcset => {\n const url = srcset.trim().split(' ')[0];\n if (isBase64(url)) {\n const fileType = getBase64FileType(src);\n const fileName = `${getBase64FileName()}.${fileType}`;\n imageResources.push({ url: url, fileName: fileName });\n img.src = `imgs/${fileName}`;\n } else {\n const fileName = getFileName(url);\n imageResources.push({ url: url, fileName: fileName });\n img.src = `imgs/${getFileNameAndType(img.getAttribute('src')).fileName}`;\n }\n });\n }\n });\n\n // Extract audio URLs and filenames\n body.querySelectorAll('audio').forEach(audio => {\n audio.querySelectorAll('source').forEach(source => {\n if (source.hasAttribute('src')) {\n const src = source.getAttribute('src');\n\n if (isBase64(src)) {\n const fileType = getBase64FileType(src);\n const fileName = `${getBase64FileName()}.${fileType}`;\n audioResources.push({ url: src, fileName: fileName });\n source.src = `audios/${fileName}`;\n } else {\n const fileName = getFileName(src);\n audioResources.push({ url: src, fileName: fileName });\n source.src = `audios/${getFileNameAndType(src).fileName}`;\n }\n }\n });\n });\n\n // Extract vectors\n body.querySelectorAll('svg').forEach(svg => {\n vectorResources.push({ content: svg.outerHTML, fileName: `vector-${vectorResources.length + 1}.svg` });\n });\n\n // Extract video URLs and filenames\n body.querySelectorAll('video').forEach(video => {\n video.querySelectorAll('source').forEach(source => {\n if (source.hasAttribute('src')) {\n const src = source.getAttribute('src');\n\n if (isBase64(src)) {\n const fileType = getBase64FileType(src);\n const fileName = `${getBase64FileName()}.${fileType}`;\n videoResources.push({ url: src, fileName: fileName });\n source.src = `vids/${fileName}`;\n } else {\n const fileName = getFileName(src);\n videoResources.push({ url: src, fileName: fileName });\n source.src = `vids/${getFileNameAndType(src).fileName}`;\n }\n }\n });\n });\n\n // Process CSS background images\n const css = json2css(obj.css) || '';\n const updatedCss = extractBackgroundImageUrls(css);\n const stylesObj = updatedCss;\n body.querySelectorAll('style').forEach(style => style.remove());\n\n return {\n html: doc.body.innerHTML,\n stylesObj,\n imageResources,\n audioResources,\n vectorResources,\n videoResources\n };\n } catch (error) {\n console.error('Error fetching resources:', error);\n return null; // Or handle the error in an appropriate way\n }\n}\nwindow.getBase64Media = async mediaUrl => {\n const response = await fetch(mediaUrl);\n const blob = await response.blob();\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onloadend = () => resolve(reader.result.split(',')[1]);\n reader.onerror = reject;\n reader.readAsDataURL(blob);\n });\n}\nwindow.renderStyles = styles => {\n let css = '';\n\n // Define :root variables\n css += `:root {\\n`;\n for (const [variable, value] of Object.entries(styles.rootVariables || {})) {\n css += ` --${variable}: ${value};\\n`;\n }\n css += '}\\n';\n\n // Define styles for each class\n for (const [classId, style] of Object.entries(styles.styles)) {\n if (!style || (!Object.keys(style.variables || {}).length && \n !Object.keys(style.base || {}).length && \n !Object.keys(style.pseudos || {}).length && \n !Object.keys(style.animations || {}).length && \n !Object.keys(style.breakpoints || {}).length)) {\n continue; // Skip empty styles\n }\n const variables = style.variables || {};\n const baseStyles = style.base || {};\n const pseudos = style.pseudos || [];\n const animations = style.animations || {};\n const breakpoints = style.breakpoints || {};\n\n if (classId === \"html\" || classId === \"body\") {\n css += `${classId} {\\n`;\n } else {\n css += `.${classId} {\\n`;\n }\n \n for (const [variable, value] of Object.entries(variables)) {\n css += ` --${variable}: ${value};\\n`;\n }\n for (const [property, value] of Object.entries(baseStyles)) {\n css += ` ${property}: ${value};\\n`;\n }\n css += '}\\n';\n\n for (const { selector, styles: pseudoStyles } of pseudos) {\n css += `.${classId}${selector} {\\n`;\n for (const [property, value] of Object.entries(pseudoStyles)) {\n css += ` ${property}: ${value};\\n`;\n }\n css += '}\\n';\n }\n\n for (const [animationName, animation] of Object.entries(animations)) {\n css += `@keyframes ${animationName} {\\n`;\n for (const [key, frameStyles] of Object.entries(animation.keyframes)) {\n css += ` ${key} {\\n`;\n for (const [property, value] of Object.entries(frameStyles)) {\n css += ` ${property}: ${value};\\n`;\n }\n css += ' }\\n';\n }\n css += '}\\n';\n\n css += `.${classId} {\\n`;\n for (const [property, value] of Object.entries(animation.properties)) {\n css += ` ${property}: ${value};\\n`;\n }\n css += '}\\n';\n }\n\n for (const [breakpoint, breakpointStyles] of Object.entries(breakpoints)) {\n css += `@media (max-width: ${breakpoint}) {\\n`;\n css += ` .${classId} {\\n`;\n for (const [variable, value] of Object.entries(breakpointStyles.variables || {})) {\n css += ` --${variable}: ${value};\\n`;\n }\n for (const [property, value] of Object.entries(breakpointStyles.base || {})) {\n css += ` ${property}: ${value};\\n`;\n }\n css += ' }\\n';\n\n for (const { selector, styles: pseudoStyles } of breakpointStyles.pseudos || []) {\n css += ` .${classId}${selector} {\\n`;\n for (const [property, value] of Object.entries(pseudoStyles)) {\n css += ` ${property}: ${value};\\n`;\n }\n css += ' }\\n';\n }\n\n css += '}\\n';\n }\n }\n\n return css;\n}\nwindow.downloadJSON = async () => {\n try {\n await loadScript(\"libraries/jszip/FileSaver.min.js\");\n let blob = new Blob([JSON.stringify(project, null, 2)], {type: \"application/json\"});\n saveAs(blob, `${project.name.split(' ').join('').toLowerCase()}-Polyrise.json`);\n\n } catch (error) {\n console.error('Error:', error);\n } finally {\n // Clean up scripts after use\n removeScript(\"libraries/jszip/FileSaver.min.js\");\n }\n}\nwindow.downloadQuickCommands = () => {\n const colorMappings = {\n 'black': '#000000',\n 'white': '#ffffff',\n 'gray-50': '#f9fafb',\n 'gray-100': '#f3f4f6',\n 'gray-200': '#e5e7eb',\n 'gray-300': '#d1d5db',\n 'gray-400': '#9ca3af',\n 'gray-500': '#6b7280',\n 'gray-600': '#4b5563',\n 'gray-700': '#374151',\n 'gray-800': '#1f2937',\n 'gray-900': '#111827',\n 'red-50': '#fef2f2',\n 'red-100': '#fee2e2',\n 'red-200': '#fecaca',\n 'red-300': '#fca5a5',\n 'red-400': '#f87171',\n 'red-500': '#ef4444',\n 'red-600': '#dc2626',\n 'red-700': '#b91c1c',\n 'red-800': '#991b1b',\n 'red-900': '#7f1d1d',\n 'yellow-50': '#fefce8',\n 'yellow-100': '#fef9c3',\n 'yellow-200': '#fef08a',\n 'yellow-300': '#fde047',\n 'yellow-400': '#facc15',\n 'yellow-500': '#eab308',\n 'yellow-600': '#ca8a04',\n 'yellow-700': '#a16207',\n 'yellow-800': '#854d0e',\n 'yellow-900': '#713f12',\n 'green-50': '#f0fdf4',\n 'green-100': '#dcfce7',\n 'green-200': '#bbf7d0',\n 'green-300': '#86efac',\n 'green-400': '#4ade80',\n 'green-500': '#22c55e',\n 'green-600': '#16a34a',\n 'green-700': '#15803d',\n 'green-800': '#166534',\n 'green-900': '#14532d',\n 'blue-50': '#eff6ff',\n 'blue-100': '#dbeafe',\n 'blue-200': '#bfdbfe',\n 'blue-300': '#93c5fd',\n 'blue-400': '#60a5fa',\n 'blue-500': '#3b82f6',\n 'blue-600': '#2563eb',\n 'blue-700': '#1d4ed8',\n 'blue-800': '#1e40af',\n 'blue-900': '#1e3a8a',\n 'indigo-50': '#eef2ff',\n 'indigo-100': '#e0e7ff',\n 'indigo-200': '#c7d2fe',\n 'indigo-300': '#a5b4fc',\n 'indigo-400': '#818cf8',\n 'indigo-500': '#6366f1',\n 'indigo-600': '#4f46e5',\n 'indigo-700': '#4338ca',\n 'indigo-800': '#3730a3',\n 'indigo-900': '#312e81',\n 'purple-50': '#f5f3ff',\n 'purple-100': '#ede9fe',\n 'purple-200': '#ddd6fe',\n 'purple-300': '#c4b5fd',\n 'purple-400': '#a78bfa',\n 'purple-500': '#8b5cf6',\n 'purple-600': '#7c3aed',\n 'purple-700': '#6d28d9',\n 'purple-800': '#5b21b6',\n 'purple-900': '#4c1d95',\n 'pink-50': '#fdf2f8',\n 'pink-100': '#fce7f3',\n 'pink-200': '#fbcfe8',\n 'pink-300': '#f9a8d4',\n 'pink-400': '#f472b6',\n 'pink-500': '#ec4899',\n 'pink-600': '#db2777',\n 'pink-700': '#be185d',\n 'pink-800': '#9d174d',\n 'pink-900': '#831843'\n }; \n\n function updateBorderColors(data) {\n const updatedData = {};\n for (const [key, value] of Object.entries(data)) {\n if (key.startsWith('border-') && !key.includes('-opacity')) {\n const colorKey = key.split('-').slice(1).join('-');\n const color = colorMappings[colorKey] || 'transparent';\n updatedData[key] = `--tw-border-opacity: 1; border-top-color: ${color}; border-right-color: ${color}; border-bottom-color: ${color}; border-left-color: ${color};`;\n } else {\n updatedData[key] = value;\n }\n }\n return updatedData;\n }\n \n\n window.saveAsJson = async (data, filename) => {\n try {\n await loadScript(\"libraries/jszip/FileSaver.min.js\");\n const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });\n saveAs(blob, filename);\n \n } catch (error) {\n console.error('Error:', error);\n } finally {\n // Clean up scripts after use\n removeScript(\"libraries/jszip/FileSaver.min.js\");\n }\n }\n\n // Generate the CSS quick commands and save to a JSON file\n generateCssQuickCommands('libraries/tailwind/tailwind-mod.min.css').then(cssQuickCommands => {\n const updatedData = updateBorderColors(cssQuickCommands);\n saveAsJson(updatedData, 'cssQuickCommands.json');\n });\n}\nwindow.getFile = async (url, callback = null) => {\n try {\n const response = await fetch(url);\n if (!response.ok) throw new Error(\"Network response was not ok\");\n const fileContent = await response.text();\n if (callback && typeof callback === 'function') {\n callback(null, fileContent); // Call the callback with the file content\n } else {\n return fileContent; // Return the file content\n }\n } catch (error) {\n console.warn(\"Request error:\", error);\n if (callback && typeof callback === 'function') {\n callback(error, null); // Call the callback with the error\n } else {\n throw error; // Re-throw to handle in caller\n }\n }\n}\nwindow.minifyCSS = source => {\n // Convert the source to a string if it isn't one\n source = String(source);\n // Remove comments\n let minified = source.replace(/\\/\\*[\\s\\S]*?\\*\\//g, '');\n // Remove whitespace and newlines\n minified = minified.replace(/\\s{2,}/g, ' ').replace(/\\n/g, '');\n // Remove spaces around selectors, properties, and values\n minified = minified.replace(/\\s*([{}:;])\\s*/g, '$1');\n // Remove the last semicolon before the closing brace\n minified = minified.replace(/;}/g, '}');\n return minified;\n}\nwindow.downloadProject = async () => {\n try {\n await loadScripts([\n \"libraries/jszip/jszip.min.js\",\n \"libraries/jszip/FileSaver.min.js\"\n ]);\n\n // Extract srcset URLs\n const iframe = document.getElementById('iframe');\n if (!iframe) return;\n const idoc = iframe.contentDocument || iframe.contentWindow.document;\n const { html, stylesObj, imageResources, audioResources, vectorResources, videoResources } = fetchResources(project);\n\n const zip = new JSZip();\n\n // Project file\n zip.file(`${project.name.split(' ').join('').toLowerCase()}-Polyrise.json`, JSON.stringify(project, null, 2));\n\n // kodeWeave project file\n const kodeWeaveProject = {\n name: project.name,\n version: project.version,\n title: project.title,\n description: project.description,\n author: project.author,\n url: project.url,\n meta: project.meta,\n libraries: project.libraries,\n html_pre_processor: \"html\",\n css_pre_processor: \"css\",\n javascript_pre_processor: \"javascript\",\n html: json2html(project.html),\n css: json2css(project.css),\n javascript: '',\n logo: project.logo,\n console: false,\n dark: project.dark,\n module: true,\n autorun: true,\n pwa: project.pwa,\n preview: true,\n activePanel: 'html',\n columns: false,\n columnsRight: true\n };\n zip.file(`${project.name.split(' ').join('').toLowerCase()}-kodeWeave.json`, JSON.stringify(kodeWeaveProject, null, 2));\n\n let licenseStr = `The MIT License (MIT)\nCopyright (c) ${new Date().getFullYear()} ${project.author}\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.`;\n zip.file(\"LICENSE.md\", licenseStr);\n\n let READMEStr = `# ${project.name}\n\n**Description:**\n${project.description}\n\n**Built With ${app.name}!**\nThis site was made with [${app.name}](https://michaelsboost.com/${app.name.toString().split(' ').join('-')}/).\n\n**${app.name} Description:**\n${app.summary} ${app.description}\n\n**Website:**\n[${app.name}](https://michaelsboost.com/${app.name.toString().split(' ').join('-')}/)`;\n zip.file(\"README.md\", READMEStr);\n\n let cssContent = '';\n let cssBuildItems = [];\n let cssBuildItemsString = '';\n let TailwindNoReset = null;\n const promises = project.libraries.map(async library => {\n if (!library.endsWith(['.css', '.js'])) return false;\n const data = await getFile(library);\n const parts = library.split(\"/\");\n const name = parts[parts.length - 1];\n\n // Check if the library is one of the Tailwind files to ignore\n if (name === \"tailwind-mod-noreset.min.js\") {\n TailwindNoReset = true;\n }\n \n // Assuming libraries have .css extensions for simplicity\n if (name.endsWith('.css')) {\n cssContent += data + '\\n';\n cssBuildItems.push(name);\n cssBuildItemsString += `libraries/${name} `;\n zip.folder('libraries').file(name, data);\n }\n \n // Assuming libraries have .js extensions for simplicity\n if (name.endsWith('.js')) {\n zip.folder('libraries').file(name, data);\n }\n });\n await Promise.all(promises);\n\n // Checks css for html\n let cssBuild = '';\n let css4html = '';\n let twFound = '';\n let tailwindDirectives = '';\n let tailwindStyles = '';\n let cssImport = '';\n \n // Find out if user is using tailwind\n if (idoc.getElementById('vyhibnq91')) {\n twFound = true;\n tailwindDirectives = `\n ${!TailwindNoReset ? `@tailwind base;` : ''}\n@tailwind components;\n@tailwind utilities;\n`\n\n if (twFound) {\n cssBuildItems.map(async library => {\n cssImport += `@import '../libraries/${library}';\n`;\n });\n cssImport += tailwindDirectives;\n } else {\n cssImport = cssContent;\n }\n tailwindStyles = idoc.getElementById('vyhibnq91').textContent;\n }\n\n // Extract and join the content of all \n ${scriptTags ? scriptTags : ''}\n \n \n\n${json2html(project.html)}\n \n \n