{"id":23390,"date":"2025-10-23T19:14:47","date_gmt":"2025-10-23T19:14:47","guid":{"rendered":"https:\/\/wedesignmarbella.com\/herramientas\/iconos-animados-que-cambian-de-color\/"},"modified":"2026-07-02T10:44:30","modified_gmt":"2026-07-02T10:44:30","slug":"iconos-animados-que-cambian-de-color","status":"publish","type":"page","link":"https:\/\/wedesignmarbella.com\/es\/herramientas\/iconos-animados-que-cambian-de-color\/","title":{"rendered":"Iconos animados que cambian de color"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"23390\" class=\"elementor elementor-23390 elementor-14969\" data-elementor-post-type=\"page\">\n\t\t\t\t<div class=\"elementor-element elementor-element-96a4d29 e-flex e-con-boxed e-con e-parent\" data-id=\"96a4d29\" data-element_type=\"container\" data-e-type=\"container\" data-settings=\"{&quot;jet_parallax_layout_list&quot;:[]}\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-359cd66 elementor-align-center elementor-widget elementor-widget-lottie\" data-id=\"359cd66\" data-element_type=\"widget\" data-e-type=\"widget\" data-settings=\"{&quot;source_json&quot;:{&quot;url&quot;:&quot;https:\\\/\\\/wedesignmarbella.com\\\/wp-content\\\/uploads\\\/2025\\\/10\\\/upload.json&quot;,&quot;id&quot;:14991,&quot;size&quot;:&quot;&quot;,&quot;alt&quot;:&quot;&quot;,&quot;source&quot;:&quot;library&quot;},&quot;loop&quot;:&quot;yes&quot;,&quot;number_of_times&quot;:50,&quot;lazyload&quot;:&quot;yes&quot;,&quot;source&quot;:&quot;media_file&quot;,&quot;caption_source&quot;:&quot;none&quot;,&quot;link_to&quot;:&quot;none&quot;,&quot;trigger&quot;:&quot;arriving_to_viewport&quot;,&quot;viewport&quot;:{&quot;unit&quot;:&quot;%&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:{&quot;start&quot;:0,&quot;end&quot;:100}},&quot;play_speed&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:1,&quot;sizes&quot;:[]},&quot;start_point&quot;:{&quot;unit&quot;:&quot;%&quot;,&quot;size&quot;:0,&quot;sizes&quot;:[]},&quot;end_point&quot;:{&quot;unit&quot;:&quot;%&quot;,&quot;size&quot;:100,&quot;sizes&quot;:[]},&quot;renderer&quot;:&quot;svg&quot;}\" data-widget_type=\"lottie.default\">\n\t\t\t\t\t<div class=\"e-lottie__container\"><div class=\"e-lottie__animation\"><\/div><\/div>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-7b44ee2 elementor-align-center elementor-widget elementor-widget-lottie\" data-id=\"7b44ee2\" data-element_type=\"widget\" data-e-type=\"widget\" data-settings=\"{&quot;source_json&quot;:{&quot;url&quot;:&quot;https:\\\/\\\/wedesignmarbella.com\\\/wp-content\\\/uploads\\\/2025\\\/10\\\/download.json&quot;,&quot;id&quot;:14987,&quot;size&quot;:&quot;&quot;,&quot;alt&quot;:&quot;&quot;,&quot;source&quot;:&quot;library&quot;},&quot;loop&quot;:&quot;yes&quot;,&quot;number_of_times&quot;:50,&quot;lazyload&quot;:&quot;yes&quot;,&quot;source&quot;:&quot;media_file&quot;,&quot;caption_source&quot;:&quot;none&quot;,&quot;link_to&quot;:&quot;none&quot;,&quot;trigger&quot;:&quot;arriving_to_viewport&quot;,&quot;viewport&quot;:{&quot;unit&quot;:&quot;%&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:{&quot;start&quot;:0,&quot;end&quot;:100}},&quot;play_speed&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:1,&quot;sizes&quot;:[]},&quot;start_point&quot;:{&quot;unit&quot;:&quot;%&quot;,&quot;size&quot;:0,&quot;sizes&quot;:[]},&quot;end_point&quot;:{&quot;unit&quot;:&quot;%&quot;,&quot;size&quot;:100,&quot;sizes&quot;:[]},&quot;renderer&quot;:&quot;svg&quot;}\" data-widget_type=\"lottie.default\">\n\t\t\t\t\t<div class=\"e-lottie__container\"><div class=\"e-lottie__animation\"><\/div><\/div>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-2453df2 elementor-align-center elementor-widget elementor-widget-lottie\" data-id=\"2453df2\" data-element_type=\"widget\" data-e-type=\"widget\" data-settings=\"{&quot;source_json&quot;:{&quot;url&quot;:&quot;https:\\\/\\\/wedesignmarbella.com\\\/wp-content\\\/uploads\\\/2025\\\/10\\\/palette.json&quot;,&quot;id&quot;:14988,&quot;size&quot;:&quot;&quot;,&quot;alt&quot;:&quot;&quot;,&quot;source&quot;:&quot;library&quot;},&quot;loop&quot;:&quot;yes&quot;,&quot;number_of_times&quot;:50,&quot;lazyload&quot;:&quot;yes&quot;,&quot;source&quot;:&quot;media_file&quot;,&quot;caption_source&quot;:&quot;none&quot;,&quot;link_to&quot;:&quot;none&quot;,&quot;trigger&quot;:&quot;arriving_to_viewport&quot;,&quot;viewport&quot;:{&quot;unit&quot;:&quot;%&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:{&quot;start&quot;:0,&quot;end&quot;:100}},&quot;play_speed&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:1,&quot;sizes&quot;:[]},&quot;start_point&quot;:{&quot;unit&quot;:&quot;%&quot;,&quot;size&quot;:0,&quot;sizes&quot;:[]},&quot;end_point&quot;:{&quot;unit&quot;:&quot;%&quot;,&quot;size&quot;:100,&quot;sizes&quot;:[]},&quot;renderer&quot;:&quot;svg&quot;}\" data-widget_type=\"lottie.default\">\n\t\t\t\t\t<div class=\"e-lottie__container\"><div class=\"e-lottie__animation\"><\/div><\/div>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-d33fb33 elementor-widget__width-inherit elementor-widget elementor-widget-heading\" data-id=\"d33fb33\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<h1 class=\"elementor-heading-title elementor-size-default\">Cambio de color de iconos animados<\/h1>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-82da7d8 elementor-widget__width-inherit elementor-widget elementor-widget-text-editor\" data-id=\"82da7d8\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>Sube un archivo JSON de Lottie, edita en tiempo real los colores de los trazos, previsualiza la animaci\u00f3n y descarga la versi\u00f3n actualizada.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-dba3742 e-flex e-con-boxed e-con e-parent\" data-id=\"dba3742\" data-element_type=\"container\" data-e-type=\"container\" data-settings=\"{&quot;jet_parallax_layout_list&quot;:[]}\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-0817142 elementor-widget elementor-widget-heading\" data-id=\"0817142\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Herramienta gratuita para cambiar el color de iconos animados Lottie \u2013 Sin necesidad de registro<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-8600507 elementor-widget__width-inherit elementor-widget elementor-widget-html\" data-id=\"8600507\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<style>\r\n  :root {\r\n    --bg-color: #f5f7fa; \/* Lighter background *\/\r\n    --panel-bg: #ffffff;\r\n    --panel-text: #495057;\r\n    --json-bg: #e9ecef; \/* Light gray for code block *\/\r\n    --border-color: #ced4da;\r\n    --primary-color: #007bff; \/* Define primary color for buttons *\/\r\n    --primary-hover: #0056b3; \/* Hover color for buttons *\/\r\n  }\r\n\r\n  \/* 3. Panel Styling *\/\r\n  .panel {\r\n    background: var(--panel-bg);\r\n    color: var(--panel-text);\r\n    padding: 25px;\r\n    border-radius: 10px; \/* Softer radius *\/\r\n    box-shadow: 0 6px 15px rgba(0, 0, 0, 0.1); \/* More pronounced shadow *\/\r\n    text-align: left;\r\n    border: 1px solid var(--border-color); \/* Subtle border *\/\r\n  }\r\n\r\n  .panel h2 {\r\n    font-size: 1.2em;\r\n    margin-top: 0;\r\n    margin-bottom: 20px;\r\n    display: inline-block;\r\n  }\r\n\r\n  .grid {\r\n    display: grid;\r\n    grid-template-columns: repeat(3, 1fr);\r\n    gap: 20px;\r\n    max-width: 1200px;\r\n    margin: 0 auto;\r\n  }\r\n\r\n  \/* 4. Inputs & Buttons *\/\r\n  button {\r\n    margin-top: 15px;\r\n    padding: 10px 24px;\r\n    font-size: 0.9em;\r\n    font-weight: 600;\r\n    border: none;\r\n    border-radius: 5px;\r\n    background: var(--primary-color);\r\n    color: white;\r\n    cursor: pointer;\r\n    transition: background 0.3s ease, transform 0.1s ease;\r\n    box-shadow: 0 4px 6px rgba(0, 123, 255, 0.2);\r\n  }\r\n\r\n  button:hover {\r\n    background: var(--primary-hover);\r\n    transform: translateY(-1px);\r\n  }\r\n\r\n  input[type=\"file\"] {\r\n    display: block;\r\n    margin-bottom: 15px;\r\n    padding: 8px 0;\r\n    font-size: 0.9em;\r\n  }\r\n\r\n  \/* 5. Color Field Adjustments *\/\r\n  .color-row {\r\n    margin-bottom: 8px;\r\n    padding: 5px 0;\r\n    font-size: 0.85em;\r\n    line-height: 1.2;\r\n  }\r\n\r\n  label {\r\n    margin-right: 10px;\r\n    font-weight: 300;\r\n    color: var(--text-color);\r\n    font-size: 14px;\r\n  }\r\n\r\n  input[type=\"color\"] {\r\n    vertical-align: middle;\r\n    cursor: pointer;\r\n    border: 1px solid var(--border-color);\r\n    border-radius: 3px;\r\n    height: 24px;\r\n  }\r\n\r\n  input[type=\"text\"] {\r\n    vertical-align: middle;\r\n    padding: 4px 6px;\r\n    border: 1px solid var(--border-color);\r\n    border-radius: 4px;\r\n    background: var(--bg-color);\r\n    color: var(--text-color);\r\n    font-size: 0.8em;\r\n  }\r\n\r\n  \/* 6. Preview and Output Areas *\/\r\n  #preview {\r\n    width: 100%;\r\n    height: 300px;\r\n    border: 1px solid var(--border-color);\r\n    border-radius: 8px;\r\n    background: #f8f9fa;\r\n  }\r\n\r\n  #jsonOutput {\r\n    width: 100%;\r\n    height: 300px;\r\n    background: var(--json-bg);\r\n    border: 1px solid var(--border-color);\r\n    border-radius: 8px;\r\n    padding: 10px;\r\n    font-family: 'Consolas', 'Courier New', monospace;\r\n    font-size: 0.85em;\r\n    overflow: auto;\r\n    white-space: pre-wrap;\r\n    color: var(--text-color);\r\n    line-height: 1.4;\r\n  }\r\n\r\n  #jsonOutput pre {\r\n    margin: 0;\r\n    white-space: pre-wrap;\r\n  }\r\n\r\n  .hardcoded-color {\r\n    color: #dc3545;\r\n    font-weight: bold;\r\n  }\r\n\r\n  #themeToggle {\r\n    position: absolute;\r\n    top: 20px;\r\n    right: 20px;\r\n    padding: 5px 10px;\r\n    font-size: 1em;\r\n    background: var(--panel-bg);\r\n    color: var(--text-color);\r\n    border: 1px solid var(--border-color);\r\n    box-shadow: none;\r\n  }\r\n\r\n  #themeToggle:hover {\r\n    background: var(--json-bg);\r\n    transform: none;\r\n  }\r\n\r\n  \/* Responsive Design *\/\r\n  @media (max-width: 1024px) {\r\n    .grid {\r\n      grid-template-columns: repeat(2, 1fr); \/* 2 columns for tablets *\/\r\n    }\r\n  }\r\n\r\n  @media (max-width: 768px) {\r\n    .grid {\r\n      grid-template-columns: 1fr; \/* 1 column for mobile *\/\r\n    }\r\n\r\n    .panel {\r\n      padding: 15px; \/* Reduce padding for smaller screens *\/\r\n    }\r\n  }\r\n\r\n  \/* Adjust button size for smaller screens *\/\r\n  @media (max-width: 768px) {\r\n    button {\r\n      padding: 8px 16px;\r\n      font-size: 0.8em;\r\n    }\r\n\r\n    input[type=\"file\"] {\r\n      font-size: 0.8em;\r\n    }\r\n\r\n    input[type=\"color\"], input[type=\"text\"] {\r\n      font-size: 0.8em;\r\n    }\r\n  }\r\n<\/style>\r\n\r\n\r\n  <div class=\"grid\">\r\n    <div class=\"panel\">\r\n      <h2>1. Importar y editar colores<\/h2>\r\n      <input type=\"file\" id=\"fileInput\" accept=\".json\"><br><br>      <div id=\"colorFields\"><\/div>\r\n      <div style=\"margin-top: 15px;\">\r\n        <button id=\"undoBtn\" style=\"display:none; margin-right: 10px;\">Undo<\/button>\r\n<button id=\"resetBtn\" style=\"display:none;\">Reset<\/button>\r\n<button id=\"downloadBtn\" style=\"display:none; margin-left: 20px;\">Download<\/button>\r\n      <\/div>\r\n    <\/div>\r\n\r\n    <div class=\"panel\">\r\n      <h2>2. Vista previa de la animaci\u00f3n en vivo<\/h2>\r\n      <div id=\"preview\"><\/div>\r\n    <\/div>\r\n\r\n    <div class=\"panel\">\r\n      <h2>3. Vista previa de la salida JSON<\/h2>\r\n      <div id=\"jsonOutput\">Sube un archivo Lottie para previsualizar su JSON aqu\u00ed.<\/div>\r\n      <button id=\"saveJsonBtn\" style=\"display:none;\">\ud83d\udcbe Guardar vista previa JSON<\/button>\r\n    <\/div>\r\n  <\/div>\r\n\r\n  <script>\r\n    let jsonData = null;\r\n    let originalJson = null;\r\n    let historyStack = [];\r\n    let colorRefs = []; \r\n    let animation = null;\r\n    \r\n    \/\/ --- LOTTIE COLOR UTILITIES ---\r\n    function rgbaToHex(arr) {\r\n        if (!Array.isArray(arr) || arr.length < 3) return '#000000';\r\n        const r = Math.round(arr[0] * 255);\r\n        const g = Math.round(arr[1] * 255);\r\n        const b = Math.round(arr[2] * 255);\r\n        return '#' + [r, g, b].map(x => x.toString(16).padStart(2, '0')).join('');\r\n    }\r\n\r\n    function hexToRgba(hex, alpha = 1) { \r\n        const bigint = parseInt(hex.slice(1), 16);\r\n        const r = ((bigint >> 16) & 255) \/ 255;\r\n        const g = ((bigint >> 8) & 255) \/ 255;\r\n        const b = (bigint & 255) \/ 255;\r\n        \/\/ CRITICAL: Now uses the existing alpha if provided, otherwise defaults to 1\r\n        return [r, g, b, alpha]; \r\n    }\r\n    \r\n    function colorStringToRgba(colorString) {\r\n        if (colorString.startsWith('rgb(')) {\r\n            const parts = colorString.match(\/\\d+\/g).map(Number);\r\n            if (parts.length >= 3) {\r\n                return [parts[0] \/ 255, parts[1] \/ 255, parts[2] \/ 255];\r\n            }\r\n        } else if (colorString.startsWith('#')) {\r\n            \/\/ Convert hex to a normalized RGBA array for matching\r\n            const bigint = parseInt(colorString.slice(1), 16);\r\n            const r = ((bigint >> 16) & 255) \/ 255;\r\n            const g = ((bigint >> 8) & 255) \/ 255;\r\n            const b = (bigint & 255) \/ 255;\r\n            return [r, g, b];\r\n        }\r\n        return null;\r\n    }\r\n    \/\/ *** CRITICAL FIX: Generalized setValueAtPath for all structures, especially Color Controls ***\r\n    function setValueAtPath(obj, path, value) {\r\n        let pointer = obj;\r\n        for (let i = 0; i < path.length - 1; i++) {\r\n            if (!pointer[path[i]]) return; \r\n            pointer = pointer[path[i]];\r\n        }\r\n        const lastKey = path[path.length - 1];\r\n        \r\n        \/\/ 1. Handle Gradient Stops (path ends in a number index inside the main gradient array 'g.k')\r\n        if (typeof lastKey === 'number' && Array.isArray(pointer)) {\r\n            const startIndex = lastKey;\r\n            if (pointer.length >= startIndex + 3) {\r\n                pointer[startIndex] = value[0];      \/\/ R\r\n                pointer[startIndex + 1] = value[1];  \/\/ G\r\n                pointer[startIndex + 2] = value[2];  \/\/ B\r\n                \r\n                \/\/ *** THE FIX: Explicitly target the Alpha component at startIndex + 3 ***\r\n                if (pointer.length >= startIndex + 4) {\r\n                    pointer[startIndex + 3] = value[3]; \/\/ A\r\n                }\r\n            }\r\n        } \r\n        \/\/ 2. Handle Solid\/Keyframed Colors and Color Controls (path ends in 'k' or 's', pointing directly to the [R,G,B,A] array)\r\n        else if (pointer.hasOwnProperty(lastKey) && Array.isArray(pointer[lastKey])) {\r\n            \/\/ Use splice to replace the array contents in place (This was the fix from the previous step)\r\n            pointer[lastKey].splice(0, value.length, ...value);\r\n        }\r\n        else {\r\n            \/\/ console.error(`Attempted to set color on an unknown path structure: ${path.join('.')}`);\r\n        }\r\n    }\r\n\r\n    \/\/ --- CORE LOGIC ---\r\n\r\n    function extractAndDisplayColors(obj) {\r\n        colorRefs = [];\r\n        const container = document.getElementById('colorFields');\r\n        container.innerHTML = '';\r\n        document.getElementById('resetBtn').style.display = 'inline-block';\r\n\r\n        let colorIndex = 1;\r\n\r\n        function walk(node, path = []) {\r\n            if (node && typeof node === 'object') {\r\n                \r\n                let parentName = '';\r\n                let colorType = '';\r\n                \r\n                \/\/ Determine color type and parent name (for shapes and groups)\r\n                let pointer = obj;\r\n                for (let i = 0; i < path.length; i++) {\r\n                    \/\/ Check for shape\/layer name in the current branch of the path\r\n                    if (pointer[path[i]] && typeof pointer[path[i]] === 'object') {\r\n                        const element = pointer[path[i]];\r\n                        if (element.nm) {\r\n                            parentName = element.nm;\r\n                        }\r\n                        if (element.ty === 'st' || element.ty === 'gs') {\r\n                            colorType = 'STROKE';\r\n                        } else if (element.ty === 'fl' || element.ty === 'gf') {\r\n                            colorType = 'FILL';\r\n                        }\r\n                    }\r\n                    pointer = pointer[path[i]];\r\n                }\r\n                \r\n                \/\/ --- A. Check for SOLID \/ KEYFRAMED COLORS (.c property) ---\r\n                if (node.hasOwnProperty('c') && node.c && typeof node.c === 'object') {\r\n                    const colorProp = node.c;\r\n                    let colorArray = null;\r\n                    let colorPath = null;\r\n                    \r\n                    if (Array.isArray(colorProp.k) && typeof colorProp.k[0] === 'number') {\r\n                        \/\/ Solid color: c.k = [r, g, b, a]\r\n                        colorArray = colorProp.k;\r\n                        colorPath = [...path, 'c', 'k'];\r\n                    } else if (Array.isArray(colorProp.k) && colorProp.k.length > 0 && colorProp.k[0].s && Array.isArray(colorProp.k[0].s)) {\r\n                        \/\/ Keyframed color: c.k[0].s = [r, g, b, a]\r\n                        colorArray = colorProp.k[0].s;\r\n                        colorPath = [...path, 'c', 'k', 0, 's']; \r\n                    }\r\n                    \r\n                    if (colorArray && colorPath && colorArray.length >= 3) {\r\n                        \/\/ Skip color references that are driven by an expression (i.e., control-driven)\r\n                        if (colorProp.x) return;\r\n                        \r\n                        const isDuplicate = colorRefs.some(ref => JSON.stringify(ref.path) === JSON.stringify(colorPath));\r\n                        if (!isDuplicate) {\r\n                            colorRefs.push({ index: colorIndex++, path: colorPath, value: colorArray, type: colorType || 'SHAPE COLOR', name: parentName || 'N\/A' });\r\n                        }\r\n                    }\r\n                }\r\n                \r\n                \/\/ --- B. Check for GRADIENT COLORS (.g property) ---\r\n                if (node.hasOwnProperty('g') && node.g && typeof node.g === 'object' && Array.isArray(node.g.k)) {\r\n                    const gradientArray = node.g.k;\r\n                    \/\/ Gradient stops start after the first 4 elements (start_point, end_point or opacity values)\r\n                    const gradientDataLength = gradientArray.length - 4; \r\n                    \r\n                    for (let i = 0; i < gradientDataLength; i += 4) {\r\n                        const colorArray = [\r\n                            gradientArray[4 + i],      \/\/ R\r\n                            gradientArray[4 + i + 1],  \/\/ G\r\n                            gradientArray[4 + i + 2],  \/\/ B\r\n                            gradientArray[4 + i + 3]   \/\/ A\r\n                        ];\r\n                        \r\n                        \/\/ Path to the specific color stop's R component index in the array\r\n                        const colorPath = [...path, 'g', 'k', 4 + i]; \r\n                        \r\n                        if (typeof colorArray[0] === 'number' && colorArray.length >= 3) {\r\n                            const isDuplicate = colorRefs.some(ref => JSON.stringify(ref.path) === JSON.stringify(colorPath));\r\n                            if (!isDuplicate) {\r\n                                colorRefs.push({ \r\n                                    index: colorIndex++, \r\n                                    path: colorPath, \r\n                                    value: colorArray, \r\n                                    type: `${colorType || 'GRADIENT'} STOP ${i\/4 + 1}`,\r\n                                    name: parentName || 'N\/A'\r\n                                });\r\n                            }\r\n                        }\r\n                    }\r\n                }\r\n                \r\n                \/\/ --- C. Check for COMPOSITION-level Color Controls (ty: 5) ---\r\n                if (\r\n                  node.ty === 5 && \/\/ Layer type 5 (Adjustment\/Null)\r\n                  node.mn === \"ADBE Color Control\" &&\r\n                  node.ef &&\r\n                  Array.isArray(node.ef)\r\n                ) {\r\n                  node.ef.forEach((effect, efIndex) => {\r\n                    \/\/ effect.ty === 2 is the color property inside the effect\r\n                    if (effect.ty === 2 && effect.v && Array.isArray(effect.v.k) && typeof effect.v.k[0] === 'number') {\r\n                        const colorArray = effect.v.k;\r\n                        const colorPath = [...path, \"ef\", efIndex, \"v\", \"k\"];\r\n                        \r\n                        \/\/ Get the parent layer name from the current node's name (nm)\r\n                        const controlLayerName = node.nm || 'Unnamed Layer';\r\n                        const effectName = effect.nm || 'Unnamed Color';\r\n\r\n                        const isDuplicate = colorRefs.some(ref => JSON.stringify(ref.path) === JSON.stringify(colorPath));\r\n                        if (!isDuplicate) {\r\n                            colorRefs.push({\r\n                                index: colorIndex++,\r\n                                path: colorPath,\r\n                                value: colorArray,\r\n                                type: \"CONTROL COLOR\",\r\n                                name: `${controlLayerName} > ${effectName}`\r\n                            });\r\n                        }\r\n                    }\r\n                  });\r\n                }\r\n                \r\n                \/\/ Continue recursion\r\n                for (let key in node) {\r\n                    \/\/ Optimization: Skip a few large, irrelevant arrays\/objects for speed\r\n                    if (key === 'layers' || key === 'shapes' || key === 'ef') {\r\n                        \/\/ If it's one of these large containers, iterate over its elements (if it's an array)\r\n                        if (Array.isArray(node[key])) {\r\n                            node[key].forEach((item, index) => {\r\n                                walk(item, [...path, key, index]);\r\n                            });\r\n                        } else if (key === 'ef' && Array.isArray(node[key])) {\r\n                            \/\/ Special handling for ef to ensure nested effect properties are checked if not handled above\r\n                            node[key].forEach((item, index) => {\r\n                                walk(item, [...path, key, index]);\r\n                            });\r\n                        }\r\n                    } else if (typeof node[key] === 'object' && key !== 'h' && key !== 'ln') {\r\n                        \/\/ Normal object recursion\r\n                        walk(node[key], [...path, key]);\r\n                    }\r\n                }\r\n            }\r\n        }\r\n\r\n        walk(obj);\r\n\r\n        if (colorRefs.length === 0) {\r\n            container.innerText = 'No compatible Lottie color fields found.';\r\n            document.getElementById('downloadBtn').style.display = 'none';\r\n            document.getElementById('resetBtn').style.display = 'none'; \/\/ Hide reset if nothing was found\r\n            return;\r\n        }\r\n\r\n        \/\/ Create color input fields\r\n        container.innerHTML = '';\r\n        colorRefs.forEach((ref, index) => {\r\n            const hex = rgbaToHex(ref.value);\r\n            \/\/ *** NEW: Get the current alpha value, default to 1 (100%) if not present ***\r\n            const alpha = ref.value.length > 3 ? ref.value[3] : 1; \r\n            const opacityPercent = Math.round(alpha * 100); \/\/ Convert 0-1 to 0-100%\r\n\r\n            const div = document.createElement('div');\r\n            div.className = 'color-row';\r\n            \r\n            const isControl = ref.type.includes('CONTROL');\r\n            \r\n            const labelText = `Color ${ref.index}: ${ref.type} (${ref.name})`;\r\n            const color = isControl ? '#9C27B0' : (ref.type.includes('STROKE') ? '#E91E63' : '#007ACC');\r\n            const labelStyle = `font-weight: bold; color: ${color};`;\r\n            const divStyle = isControl ? `border: 1px solid #9C27B0; padding: 5px; border-radius: 5px;` : '';\r\n            \/\/ Define unique IDs using the ref index\r\n            const colorId = `color-${index}`;\r\n            const hexId = `hex-${index}`;\r\n            const alphaId = `alpha-${index}`;\r\n\r\n            div.style.cssText = divStyle;\r\n\r\n            \/\/ *** CRITICAL HTML CHANGE: Added Opacity Controls and data-type attributes ***\r\n            div.innerHTML = `\r\n                <label for=\"${colorId}\" style=\"${labelStyle}\">${labelText}<\/label><br>\r\n                \r\n                <input type=\"color\" value=\"${hex}\" data-index=\"${index}\" data-type=\"color\" id=\"${colorId}\" name=\"${colorId}\">\r\n                <input type=\"text\" value=\"${hex}\" data-index=\"${index}\" maxlength=\"7\" style=\"width: 80px; margin-left: 10px;\" data-type=\"hex\" id=\"${hexId}\" name=\"${hexId}\">\r\n                \r\n                <span style=\"display: inline-block; margin-left: 15px; width: 60px;\">Opacity:<\/span>\r\n                <input type=\"range\" min=\"0\" max=\"100\" value=\"${opacityPercent}\" class=\"opacity-slider\" data-index=\"${index}\" data-type=\"alpha\" style=\"width: 100px; vertical-align: middle;\" id=\"${alphaId}\" name=\"${alphaId}\">\r\n                <span class=\"opacity-value\" data-index=\"${index}\" style=\"margin-left: 5px; width: 30px; display: inline-block; text-align: right;\">${opacityPercent}%<\/span>\r\n            `;\r\n            container.appendChild(div);\r\n            \r\n            \/\/ *** NEW: Store the current alpha value on the ref object for easy retrieval ***\r\n            ref.alpha = alpha;\r\n        });\r\n\r\n        const colorFieldsContainer = document.getElementById('colorFields');\r\n        colorFieldsContainer.removeEventListener('input', handleColorInput);\r\n        colorFieldsContainer.addEventListener('input', handleColorInput);\r\n\r\n        document.getElementById('downloadBtn').style.display = 'inline-block';\r\n    }\r\n    \r\n    \/\/ Unified event handler for color changes (Step 2)\r\n    \/\/ Unified event handler for color and opacity changes\r\n    function handleColorInput(event) {\r\n        const target = event.target;\r\n        if (target.tagName !== 'INPUT') return; \r\n\r\n        const idx = target.getAttribute('data-index');\r\n        \/\/ *** NEW: Identify the input type using the new data-type attribute ***\r\n        const inputType = target.getAttribute('data-type'); \r\n        if (idx === null) return; \r\n\r\n        \/\/ 1. Save state for undo\r\n        historyStack.push(JSON.stringify(jsonData));\r\n\r\n        const ref = colorRefs[idx];\r\n        const parentRow = target.closest('.color-row');\r\n        \r\n        \/\/ Start with the current RGB (converted to Hex) and current Alpha\r\n        let currentHex = rgbaToHex(ref.value); \r\n        \/\/ If the ref doesn't have 4 values, Lottie implicitly uses alpha 1 (or it's a gradient R, G, B, A segment)\r\n        let currentAlpha = ref.value.length > 3 ? ref.value[3] : 1; \r\n        \r\n        let newHex = currentHex;\r\n        let newAlpha = currentAlpha;\r\n        \r\n        \/\/ --- Update Hex or Alpha based on input type ---\r\n        if (inputType === 'color' || inputType === 'hex') {\r\n            newHex = target.value.toUpperCase();\r\n\r\n            if (inputType === 'hex') {\r\n                \/\/ Basic validation\r\n                if (!\/^#[0-9A-F]{6}$\/i.test(newHex)) {\r\n                    historyStack.pop(); \/\/ discard saved state if input is invalid\r\n                    return;\r\n                }\r\n                if (newHex.charAt(0) !== '#') newHex = '#' + newHex;\r\n            }\r\n\r\n            \/\/ Sync the other color\/hex field\r\n            if (inputType === 'color') {\r\n                parentRow.querySelector('input[data-type=\"hex\"]').value = newHex;\r\n            } else {\r\n                parentRow.querySelector('input[data-type=\"color\"]').value = newHex;\r\n            }\r\n        } \r\n        else if (inputType === 'alpha') {\r\n            const opacityPercent = parseInt(target.value, 10);\r\n            newAlpha = opacityPercent \/ 100;\r\n            \r\n            \/\/ Update the percentage display span\r\n            parentRow.querySelector('.opacity-value').innerText = `${opacityPercent}%`;\r\n        }\r\n\r\n        \/\/ 2. Update JSON data\r\n        \/\/ hexToRgba now takes the new hex and the new alpha value\r\n        const newRgba = hexToRgba(newHex, newAlpha); \r\n        \r\n        \/\/ Pass the complete RGBA array to Lottie\r\n        setValueAtPath(jsonData, ref.path, newRgba);\r\n\r\n        \/\/ CRITICAL: Update the colorRefs array with the new full RGBA array for future undo\/redo states\r\n        ref.value = newRgba; \r\n\r\n        \/\/ 3. Re-render previews\r\n        renderPreview(jsonData);\r\n        setTimeout(() => renderJsonPreview(jsonData), 50); \r\n        \r\n        \/\/ 4. Show undo button\r\n        document.getElementById('undoBtn').style.display = 'inline-block';\r\n    }\r\n\r\n\r\n    \/\/ Handle file upload\r\n    document.getElementById('fileInput').addEventListener('change', function (e) {\r\n      const file = e.target.files[0];\r\n      if (!file) return;\r\n\r\n      const reader = new FileReader();\r\n      reader.onload = function (event) {\r\n        try {\r\n          jsonData = JSON.parse(event.target.result);\r\n          originalJson = JSON.parse(event.target.result); \r\n          historyStack = []; \r\n\r\n          extractAndDisplayColors(jsonData);\r\n          renderPreview(jsonData);\r\n          \/\/ Delay JSON preview render slightly to allow Lottie to initialize and generate the SVG\r\n          setTimeout(() => renderJsonPreview(jsonData), 50); \r\n          document.getElementById('saveJsonBtn').style.display = 'inline-block';\r\n        } catch (err) {\r\n          alert('Invalid JSON file.');\r\n          console.error(err);\r\n        }\r\n      };\r\n      reader.readAsText(file);\r\n    });\r\n    \r\n    \/\/ Download updated JSON\r\n    document.getElementById('downloadBtn').addEventListener('click', () => {\r\n      if (!jsonData) return;\r\n      const dataStr = JSON.stringify(jsonData, null, 2);\r\n      const blob = new Blob([dataStr], { type: \"application\/json\" });\r\n      const url = URL.createObjectURL(blob);\r\n      const a = document.createElement('a');\r\n      a.href = url;\r\n      a.download = \"updated-lottie.json\";\r\n      document.body.appendChild(a);\r\n      a.click();\r\n      document.body.removeChild(a);\r\n      URL.revokeObjectURL(url);\r\n    });\r\n    \r\n    \/\/ Save JSON Preview Button Handler \r\n    document.getElementById('saveJsonBtn').addEventListener('click', () => {\r\n      \/\/ Get the *current* content from the preview area, which includes highlights\r\n      const content = document.getElementById('jsonOutput').innerText; \r\n      const blob = new Blob([content], { type: \"application\/json\" });\r\n      const url = URL.createObjectURL(blob);\r\n      const a = document.createElement('a');\r\n      a.href = url;\r\n      a.download = \"json-preview.json\";\r\n      document.body.appendChild(a);\r\n      a.click();\r\n      document.body.removeChild(a);\r\n      URL.revokeObjectURL(url);\r\n    });\r\n\r\n\r\n    \/\/ Render Lottie preview - FIX: Hard-reset the container to prevent caching issues.\r\n    function renderPreview(data) {\r\n      const container = document.getElementById('preview');\r\n\r\n      \/\/ 1. Destroy existing animation instance\r\n      if (animation) {\r\n        animation.destroy();\r\n      }\r\n\r\n      \/\/ 2. Clear the container's contents\r\n      container.innerHTML = '';\r\n      \r\n      \/\/ 3. Load the new animation\r\n      animation = lottie.loadAnimation({\r\n        container: container,\r\n        renderer: 'svg',\r\n        loop: true,\r\n        autoplay: true,\r\n        animationData: data\r\n      });\r\n      \r\n      \/\/ We need to wait for Lottie to fully render the SVG before we can inspect it for hardcoded colors\r\n      animation.addEventListener('DOMLoaded', () => {\r\n        highlightHardcodedColors(data);\r\n      });\r\n    }\r\n\r\n    \/\/ JSON Highlight Function \r\n    function highlightHardcodedColors(data) {\r\n        const jsonContainer = document.getElementById('jsonOutput');\r\n        const svgContainer = document.getElementById('preview');\r\n        \r\n        \/\/ Start with the raw JSON text\r\n        let jsonText = JSON.stringify(data, null, 2);\r\n        \r\n        \/\/ 1. Find all colors hardcoded in the Lottie-generated SVG\r\n        const svgContent = svgContainer.innerHTML;\r\n        \/\/ Regex to find hex, rgb, or rgba colors in fill\/stroke attributes\r\n        const colorRegex = \/(fill|stroke)=\"((#[0-9A-Fa-f]{3,6})|(rgb\\(\\d+,\\d+,\\d+\\))|(rgba\\(\\d+,\\d+,\\d+,\\d+.\\d+\\)))\"\/g;\r\n        const hardcodedColors = new Set();\r\n        let match;\r\n        \r\n        while ((match = colorRegex.exec(svgContent)) !== null) {\r\n            \/\/ Capture the color string (group 2)\r\n            let colorString = match[2]; \r\n            \r\n            \/\/ Convert the hardcoded color string to its normalized RGBA equivalent\r\n            const normalized = colorStringToRgba(colorString);\r\n            if (normalized) {\r\n                \/\/ Round to 4 decimal places for comparison and stringify\r\n                const normalizedKey = `[${normalized.map(c => c.toFixed(4)).join(', ')}]`;\r\n                \r\n                \/\/ Only add to the set if it's NOT a color that the editor found (i.e., not in colorRefs)\r\n                const isEditable = colorRefs.some(ref => {\r\n                    \/\/ Slice to R, G, B for comparison and round to 4 for reliable matching\r\n                    const refNormalized = ref.value.slice(0, 3).map(c => c.toFixed(4)).join(', ');\r\n                    return normalizedKey.includes(refNormalized);\r\n                });\r\n                \r\n                if (!isEditable) {\r\n                    hardcodedColors.add(normalizedKey);\r\n                }\r\n            }\r\n        }\r\n        \r\n        \/\/ 2. Replace hardcoded color arrays in the JSON text with the highlight span\r\n        hardcodedColors.forEach(colorKey => {\r\n            \/\/ The regex needs to find the JSON array string, rounding the normalized values\r\n            const parts = colorKey.replace(\/[\\[\\]]\/g, '').split(', ').map(n => parseFloat(n).toFixed(4));\r\n            \r\n            \/\/ Create a loose regex pattern to match the R, G, B parts of the array in the JSON string\r\n            \/\/ This targets the original, unrounded numbers in the JSON string\r\n            \/\/ It must match the first 3 or 4 elements\r\n            const colorArrayPattern = new RegExp(\r\n                `\\\\[\\\\s*(${parts[0].replace('.', '\\\\.')}[^,]*),\\\\s*(${parts[1].replace('.', '\\\\.')}[^,]*),\\\\s*(${parts[2].replace('.', '\\\\.')}[^\\\\]]*)(,.*?)?\\\\]`, \r\n                'g'\r\n            );\r\n\r\n            \/\/ Replace the found color array in the JSON string with the highlighted span\r\n            jsonText = jsonText.replace(colorArrayPattern, (match) => {\r\n                return `<span class=\"hardcoded-color\">${match}<\/span>`;\r\n            });\r\n        });\r\n        \r\n        \/\/ 3. Render the JSON content with HTML in the container\r\n        jsonContainer.innerHTML = `<pre>${jsonText}<\/pre>`;\r\n    }\r\n\r\n\r\n    \/\/ Render JSON preview (now just calls the highlight function)\r\n    function renderJsonPreview(data) {\r\n      highlightHardcodedColors(data);\r\n    }\r\n\r\n    \/\/ Theme toggle\r\n    function toggleTheme() {\r\n      document.body.classList.toggle('dark-mode');\r\n      localStorage.setItem('theme', document.body.classList.contains('dark-mode') ? 'dark' : 'light');\r\n    }\r\n\r\n    \/\/ Load theme preference on page load\r\n    window.onload = function () {\r\n      if (localStorage.getItem('theme') === 'dark') {\r\n        document.body.classList.add('dark-mode');\r\n      }\r\n    };\r\n\r\n    \/\/ Undo button\r\n    document.getElementById('undoBtn').addEventListener('click', () => {\r\n      if (historyStack.length === 0) return;\r\n      const lastState = historyStack.pop();\r\n      jsonData = JSON.parse(lastState);\r\n      extractAndDisplayColors(jsonData);\r\n      renderPreview(jsonData);\r\n      setTimeout(() => renderJsonPreview(jsonData), 50); \r\n      document.getElementById('undoBtn').style.display = historyStack.length > 0 ? 'inline-block' : 'none';\r\n    });\r\n    \r\n    \/\/ Reset button\r\n    document.getElementById('resetBtn').addEventListener('click', () => {\r\n      if (!originalJson) return;\r\n      jsonData = JSON.parse(JSON.stringify(originalJson));\r\n      historyStack = [];\r\n      extractAndDisplayColors(jsonData);\r\n      renderPreview(jsonData);\r\n      setTimeout(() => renderJsonPreview(jsonData), 50); \r\n      document.getElementById('undoBtn').style.display = 'none';\r\n    });\r\n<\/script>\r\n<script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/lottie-web\/5.12.2\/lottie.min.js\"><\/script>\r\n\r\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-bbd7832 elementor-widget-divider--view-line elementor-widget elementor-widget-divider\" data-id=\"bbd7832\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"divider.default\">\n\t\t\t\t\t\t\t<div class=\"elementor-divider\">\n\t\t\t<span class=\"elementor-divider-separator\">\n\t\t\t\t\t\t<\/span>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-69447bf elementor-widget__width-inherit elementor-widget elementor-widget-html\" data-id=\"69447bf\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<div>\r\n    <label for=\"iframe-code\">Copy the code below to embed the tool on your website:<\/label>\r\n\r\n<textarea id=\"iframe-code\" rows=\"4\" cols=\"50\">\r\n<iframe src=\"https:\/\/wedesignmarbella.com\/tools\/aminated-icons-colour-change\/\" width=\"100%\" height=\"600\" frameborder=\"0\" allowfullscreen=\"\"><\/iframe>\r\n<\/textarea>\r\n<button onclick=\"copyCode()\">Copy Code<\/button>\r\n<\/div>\r\n\r\n<script>\r\nfunction copyCode() {\r\n    var copyText = document.getElementById(\"iframe-code\");\r\n    copyText.select();\r\n    copyText.setSelectionRange(0, 99999); \/\/ For mobile devices\r\n    document.execCommand(\"copy\");\r\n    alert(\"Code copied to clipboard!\");\r\n}\r\n<\/script>\r\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-2b29287 elementor-widget elementor-widget-text-editor\" data-id=\"2b29287\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>\u00a1Cambia f\u00e1cilmente los colores de los iconos animados con nuestra herramienta online gratuita! No necesitas software costoso como Adobe After Effects: simplemente sube tu archivo JSON de Lottie, edita los colores del trazo en tiempo real y previsualiza la animaci\u00f3n al instante. Cuando est\u00e9s satisfecho con los colores, \u00a1descarga la versi\u00f3n actualizada con un solo clic!  <\/p><h3>Caracter\u00edsticas:<\/h3><ul><li>Importar y editar colores: suba cualquier archivo JSON de Lottie y cambie los colores de los trazos sin esfuerzo.<\/li><li>Vista previa de la animaci\u00f3n en vivo: vea sus cambios en tiempo real con una vista previa interactiva de la animaci\u00f3n.<\/li><li>Vista previa de la salida JSON: Vea y confirme la salida JSON actualizada para su descarga.<\/li><\/ul><p>Tanto si trabajas en dise\u00f1o web, aplicaciones o proyectos creativos, esta herramienta gratuita te permite personalizar iconos animados f\u00e1cilmente y prepararlos para su uso: \u00a1sin necesidad de registrarte, sin complicaciones, solo ediciones de color r\u00e1pidas!<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-a4908a4 e-flex e-con-boxed e-con e-parent\" data-id=\"a4908a4\" data-element_type=\"container\" data-e-type=\"container\" data-settings=\"{&quot;jet_parallax_layout_list&quot;:[]}\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-61d19ad elementor-align-center elementor-widget__width-inherit elementor-widget elementor-widget-button\" data-id=\"61d19ad\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"button.default\">\n\t\t\t\t\t\t\t\t\t\t<a class=\"elementor-button elementor-button-link elementor-size-sm\" href=\"https:\/\/wedesignmarbella.com\/es\/herramientas\/\" title=\"Back to Tools\">\n\t\t\t\t\t\t<span class=\"elementor-button-content-wrapper\">\n\t\t\t\t\t\t\t\t\t<span class=\"elementor-button-text\">Volver a Herramientas<\/span>\n\t\t\t\t\t<\/span>\n\t\t\t\t\t<\/a>\n\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>Cambio de color de iconos animados Sube un archivo JSON de Lottie, edita en tiempo real los colores de los trazos, previsualiza la animaci\u00f3n y descarga la versi\u00f3n actualizada. Herramienta gratuita para cambiar el color de iconos animados Lottie \u2013 Sin necesidad de registro 1. Importar y editar colores Undo Reset Download 2. Vista previa [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"parent":23340,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"ai_generated_summary":"","footnotes":""},"class_list":["post-23390","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/wedesignmarbella.com\/es\/wp-json\/wp\/v2\/pages\/23390","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wedesignmarbella.com\/es\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/wedesignmarbella.com\/es\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/wedesignmarbella.com\/es\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/wedesignmarbella.com\/es\/wp-json\/wp\/v2\/comments?post=23390"}],"version-history":[{"count":0,"href":"https:\/\/wedesignmarbella.com\/es\/wp-json\/wp\/v2\/pages\/23390\/revisions"}],"up":[{"embeddable":true,"href":"https:\/\/wedesignmarbella.com\/es\/wp-json\/wp\/v2\/pages\/23340"}],"wp:attachment":[{"href":"https:\/\/wedesignmarbella.com\/es\/wp-json\/wp\/v2\/media?parent=23390"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}