<svg xmlns="http://www.w3.org/2000/svg" style="display:none;"><symbol id="gear" viewBox="0 0 100 100"><path d="M87.687 45.077c-.313-2.433-.816-4.807-1.542-7.08l8.145-11.73-5.827-7.514-.094-.123-5.825-7.514-12.955 5.577c-2.04-1.265-4.192-2.362-6.46-3.22L59.42 0H40.587l-3.71 13.474c-2.27.857-4.42 1.955-6.463 3.222L17.457 11.12l-5.825 7.513-.097.124-5.822 7.517 8.146 11.73c-.727 2.272-1.234 4.646-1.545 7.08L0 52.032l2.08 9.375.033.15 2.08 9.375 14 .76c1.296 2.03 2.772 3.927 4.4 5.67L20.2 91.587l8.416 4.173.138.068L37.17 100l9.308-10.796c1.16.11 2.335.173 3.524.173s2.358-.074 3.52-.184l9.317 10.804 8.414-4.173.14-.066 8.414-4.172-2.396-14.228c1.628-1.74 3.104-3.64 4.4-5.672l14-.76 2.078-9.374.035-.154L100 52.025l-12.313-6.948zM50.003 34.51c8.435 0 15.272 7.035 15.272 15.72 0 8.678-6.84 15.716-15.272 15.716-8.436 0-15.272-7.038-15.272-15.717 0-8.685 6.84-15.72 15.273-15.72z"/></symbol><symbol id="italic" width="50" height="100" viewBox="0 0 50 100"><path d="M12.9 8.7V0H50v8.7l-11.6 1.9-12.9 78.8 11.6 1.9v8.7H0v-8.7l11.6-1.9 12.9-78.8-11.6-1.9z"/></symbol><symbol id="underline" width="86.1" height="100" viewBox="0 0 86.1 100"><path d="M9.5 92.8h67.2v7.2H9.5zM35.1 0v10.3l-9 1.7v44.3c0 5.6 1.5 9.8 4.5 12.6S37.7 73 43 73c5.3 0 9.5-1.4 12.5-4.1 3-2.8 4.5-7 4.5-12.6V12l-9-1.7V0h35.1v10.3l-9 1.7v44.3c0 9.5-3.1 16.9-9.4 22-6.3 5.2-14.5 7.8-24.7 7.8-10.1 0-18.3-2.6-24.6-7.8-6.2-5.1-9.4-12.5-9.4-22V12l-9-1.7V0h35.1z"/></symbol></svg>
<div id="page-wrap" class="page-wrap">
  <div class="top-controls">
    <div class="base-ui-controls">
      <div class="base-ui__label">
        Dark
      </div>
      <div class="base-ui__toggle">
        <input type="checkbox" id="base-ui" class="giant-toggle">
        <label for="base-ui"></label>
      </div>
      <div class="base-ui__label">
        Light
      </div>
    </div>
    <div class="version-controls">
    </div>
    <div class="export-controls">
      <button id="export-json" class="button button--default button--save">
        Export For CodePen Enhancement Suite
      </button>
      <button id="export-css" class="button button--default button--save">
        Export CSS
      </button>
    </div>
  </div>
  <div class="main">
    <div id="controls" class="color-controls">
      <div class="presets-wrapper">
        Preset:
        <div class="presets-select-wrapper">
          <select name="presets" id="presets" class="presets"></select>
        </div>
        <button id="load-preset" class="button button--default button--tiny load-preset">Load</button>
      </div>
    </div>
    <div class="preview" id="preview">    <!-- this is where the markup for the CodeMirror stuff is stored -->
      [[[https://codepen.io/alexzaworski/pen/42eac2eb41b9fcb4a2931b4398aaf6c2]]]
    </div>
  </div>

::-webkit-scrollbar {
  width: .5em;
  height: .5em;
}

::-webkit-scrollbar-thumb {
  background: rgba(102, 102, 102, 0.4);
}

::-webkit-scrollbar-track {
  background: none;
}

html {
  box-sizing: border-box;
  overflow-y: hidden;
}

*,
*:before,
*:after {
  box-sizing: inherit;
}

body,
html {
  margin: 0;
}

body {
  font-family: lato;
  font-size: 16px;
  min-width: 1150px;
}

.page-wrap {
  display: flex;
  flex-direction: column;
  height: 100vh;
}

.main {
  display: flex;
  height: 100vh;
}

.preview {
  padding: 20px;
  background: #333;
  display: flex;
  overflow-x: auto;
}
.light .preview {
  background: #ccc;
}

.top-controls {
  display: flex;
  flex-shrink: 0;
  white-space: nowrap;
  overflow: hidden;
  background: #111111;
}

.version-controls {
  padding: 20px;
  flex-grow: 1;
}
.light .version-controls {
  background: white;
}

.button {
  background: transparent;
  border: 0;
  outline: none;
  font: inherit;
  cursor: pointer;
}

.button--default {
  letter-spacing: .05em;
  padding: 8px 10px;
  color: white;
  background: black;
  margin: 0;
  margin-right: 10px;
  border-radius: 4px;
  border: 3px solid #333;
}

.button--default:last-child {
  margin-right: 0;
}

.button--tiny {
  padding: 4px;
  border: 0;
  font-size: .8em;
  font-weight: normal;
}

.button--default:hover:not(:active) {
  background-color: #333;
}

.light .button--default:hover:not(:active) {
  background: #ccc;
}

.light .button--default:not(:active) {
  background: white;
  color: #555;
  border-color: #ccc;
}

.button--save:hover,
.light .button--save:hover {
  border-color: #46D06B;
}
.button--save:active {
  background: #46D06B;
}

.button--revert:hover,
.light .button--revert:hover {
  border-color: #ff3c41;
}
.button--revert:active {
  background: #ff3c41;
}

.save-info--hidden {
  display: none;
}

.last-saved {
  font-size: .8em;
  color: #666;
  font-weight: bold;
  letter-spacing: .02em;
  text-transform: uppercase;
  animation: fade 1s ease-out;
}

.light .last-saved {
  color: #aaa;
  animation-name: fade--dark;
}

@keyframes fade {
  0% {
    color: white;
  }
  100% {
    color: #666;
  }
}
@keyframes fade--dark {
  0% {
    color: black;
  }
  100% {
    color: #aaa;
  }
}

.color-controls {
  overflow: auto;
  padding: 20px;
  width: 275px;
  background: #eee;
  flex-shrink: 0;
}

.cmEl:not(:last-of-type) {
  border-bottom: 1px solid #ccc;
  padding-bottom: 10px;
  margin-bottom: 10px;
}

.cmEl__advanced {
  margin-top: 10px;
  display: none;
}
.cmEl__settings.active ~ .cmEl__advanced {
  display: block;
}

.cmEl__heading {
  display: inline-block;
  cursor: pointer;
  margin: 0;
  font-size: .8em;
  letter-spacing: .02em;
  color: #333;
  text-transform: uppercase;
  line-height: 20px;
  position: relative;
  bottom: 2px;
}

.cmEl__heading:after {
  content: '';
  clear: both;
  display: block;
}

.cmEl__settings {
  float: right;
  background: transparent;
  border: none;
  display: block;
  outline: none;
  cursor: pointer;
  padding: 0;
  height: 18px;
  width: 18px;
  line-height: 20px;
  fill: #aaa;
}

.cmEl__settings svg {
  display: block;
  height: 100%;
  width: 100%;
  transition: fill .1s;
}

.cmEl__settings:hover, .cmEl__settings.active {
  fill: #333;
}

.cmEl__color {
  display: block;
}

.cmEl__syncTo {
  display: inline-block;
  width: 150px;
}

.cmEl__syncTo-label {
  display: inline-block;
  margin-right: 5px;
  font-size: .7em;
  color: #666;
  font-weight: bold;
  letter-spacing: .02em;
  text-transform: uppercase;
}

.cmEl__color {
  opacity: 0;
  width: 100%;
  height: 100%;
  cursor: pointer;
}

.cmEl__fauxColor {
  height: 20px;
  width: 20px;
  margin-right: 10px;
  border-radius: 4px;
  border: 2px solid #ccc;
  float: left;
}

.cmEl__desc {
  font-style: italic;
  color: #111;
  font-size: .9em;
  line-height: 1.5em;
  color: #333;
  text-transform: none;
  margin: 10px 0;
}

.box {
  min-width: 200px;
  flex-basis: 33%;
  margin-left: 20px;
  overflow: auto;
  overflow-x: hidden;
  border-radius: 4px;
  padding: 5px;
}

.box:first-of-type {
  margin-left: 0;
}

.base-ui-controls {
  background: #333;
  width: 275px;
  display: flex;
  justify-content: center;
  flex-shrink: 0;
}
.light .base-ui-controls {
  background: #ccc;
}

.base-ui__toggle {
  position: relative;
  height: 30px;
  margin: auto 20px;
}

.giant-toggle {
  display: none;
}

.giant-toggle ~ label {
  cursor: pointer;

  /* Should be the same as the height of label:after */
  line-height: 30px;
}

.giant-toggle ~ label:after {
  content: '';
  display: block;
  float: left;

  /* Height/border radius should be equal */
  height: 30px;
  border-radius: 30px;
  
  width: 50px;
  background: #ccc;
}

.light .giant-toggle ~ label:after {
  background: #777;
}

.giant-toggle ~ label:before {
  top: 50%;
  transform: translateY(-50%);
  position: absolute;
  content: '';
  display: block;
  float: left;
  height: 35px;
  width: 35px;
  left: -5px;
  border-radius: 50%;
  background: white;
  transition: color .1s ease-out, left .1s ease-out;
}

.giant-toggle:checked ~ label:before {

  /* Difference between label:after width and label:before width */
  left: 20px;
}

.base-ui__label {
  cursor: pointer;
  margin: auto 0;
  font-size: .75em;
  color: #999;
  font-weight: bold;
  letter-spacing: .02em;
  text-transform: uppercase;
}

.light .base-ui__label {
  color: #777;
}

.presets-wrapper {
  font-size: .8em;
  font-weight: bold;
  text-transform: uppercase;
  margin: -20px;
  padding: 10px 20px;
  margin-bottom: 20px;
  background: #aaa;
}

.presets {
  margin-left: 4px;
  margin-right: 2px;
  background: black;
  color: white;
  border: 0;
  padding: 4px 20px 4px 10px;
  font-size: 0.8em;
  border-radius: 3px;
  cursor: pointer;
  outline: 0;
  -webkit-appearance: none;
}

.light .presets {
  background: white;
  color: #555;
}
.presets-select-wrapper {
  position: relative;
  display: inline-block;
  color: #555;
  vertical-align: top;
}

.presets-select-wrapper:after {
  position: absolute;
  display: inline-block;
  content: "";
  width: 0;
  height: 0;
  pointer-events: none;
  border: .35rem solid transparent;
  border-top-color: inherit;
  right: 8px;
  top: 40%;
}

.cmEl__font-controls {
  margin-bottom: 5px;
}

.cmEl__font-controls:after {
  display: block;
  content: "";
  clear: both;
}
.cmEl__font-control {
  padding: 5px;
  display: block;
  height: 20px;
  width: 20px;
  float: left;
  background: #999;
  fill: #333;
  margin-right: 5px;
  border-radius: 4px;
  cursor: pointer;
}

.cmEl__font-control.active {
  background: #333;
  fill: #999;
}

.export-controls {
  background: #111;
  padding: 20px;
  padding-left: 0;
}

.light .export-controls {
  background: white;
}
View Compiled
"use strict";
console.clear();

/*
* VENDORY STUFF
*/

// http://stackoverflow.com/a/9251169
var escapeHTML = (function() {
  "use strict";
  var escape = document.createElement("textarea");
  return function(html) {
    escape.textContent = html;
    return escape.innerHTML;
  };
})();

var pubsub = (function() {
  "use strict";
  var topics = {};
  var subUid = -1;
  var subscribe = function(topic, func) {
    if (!topics[topic]) {
      topics[topic] = [];
    }
    var token = (++subUid).toString();
    topics[topic].push({
      token: token,
      func: func
    });
    return token;
  };

  var publish = function(topic, args) {
    if (!topics[topic]) {
      return false;
    }
    setTimeout(function() {
      var subscribers = topics[topic];
      var len = subscribers ? subscribers.length : 0;
      while (len--) {
        subscribers[len].func(args);
      }
    }, 0);
  };

  var unsubscribe = function(token) {
    for (var m in topics) {
      if (topics[m]) {
        for (var i = 0, j = topics[m].length; i < j; i++) {
          if (topics[m][i].token === token) {
            topics[m].splice(i, 1);
            return token;
          }
        }
      }
    }
    return false;
  };

  var reset = function() {
    topics = {};
    subUid = -1;
  };

  return {
    publish: publish,
    subscribe: subscribe,
    unsubscribe: unsubscribe,
    reset: reset
  };
})();

/*
* ACTUAL STUFF
*/

// Object for overall theme. Keeps elements in sync,
// builds stylesheets, etc
var cmTheme = (function() {
  var rawElements = [];
  var elements = [];
  var presets = [];
  var styleEl = document.createElement("style");
  var isLightTheme = false;
  var container = document.getElementById("controls");
  var baseUIToggle = document.getElementById("base-ui");
  var fauxToggleLabels = document.getElementsByClassName("base-ui__label");
  var exportCES = document.getElementById("export-json");
  var exportCSS = document.getElementById("export-css");
  var pageWrap = document.getElementById("page-wrap");
  var presetSelect = document.getElementById("presets");
  var presetLoad = document.getElementById("load-preset");

  var appendStyles = function() {
    document.head.appendChild(styleEl);
  };

  var updateStyles = function(shouldSetSave) {
    styleEl.innerHTML = buildStyles();
  };

  var buildStyles = function() {
    var styles = "";
    elements.forEach(function(element) {
      styles += element.getStyleRule();
    });
    return styles;
  };

  var addElement = function(element) {
    elements.push(element);
  };

  var getElement = function(id) {
    var el;
    elements.some(function(element) {
      if (element.id === id) {
        el = element;
        return true;
      }
    });
    return el || false;
  };

  var getElements = function() {
    return elements;
  };

  var getContainerEl = function() {
    return container;
  };

  var setPageBackground = function() {
    pageWrap.classList.toggle("light", baseUIToggle.checked);
  };

  var handlefauxToggleLabelClick = function() {
    baseUIToggle.click();
  };

  var addGUIEventListeners = function() {
    exportCES.addEventListener("click", function() {
      exportTheme();
    });

    exportCSS.addEventListener("click", function() {
      exportThemeCSS();
    });

    baseUIToggle.addEventListener("click", function() {
      isLightTheme = baseUIToggle.checked;
      setPageBackground();
    });

    for (var i = 0; i < fauxToggleLabels.length; i++) {
      fauxToggleLabels[i].addEventListener("click", handlefauxToggleLabelClick);
    }

    presetLoad.addEventListener("click", function() {
      reset();
      buildElementsFromPreset(presets[presetSelect.value]);
    });
  };

  var drawGUI = function() {
    elements.forEach(function(element) {
      element.setupSelectEl();
      element.draw();
    });
  };

  var reset = function() {
    pubsub.reset();
    elements.forEach(function(element) {
      element.remove();
    });
    elements = [];
    styleEl.innerHTML = "";
  };

  var buildElementStash = function() {
    var elementStash = [];
    elements.forEach(function(element) {
      var toStash = {
        prettyName: element.prettyName,
        selector: element.selector,
        color: element.color,
        description: element.description,
        prop: element.prop,
        italic: element.italic,
        underline: element.underline,
        master: element.master.id
      };
      elementStash.push(toStash);
    });
    return elementStash;
  };

  var exportTheme = function() {
    var presetExport = {};
    presetExport.elements = buildElementStash();
    presetExport.light = isLightTheme;
    var dataStr = "data:text/json;charset=utf-8,";
    dataStr += encodeURIComponent(JSON.stringify(presetExport));
    var a = document.createElement("a");
    a.href = dataStr;
    a.setAttribute("download", "ces_theme.json");
    document.body.appendChild(a);
    a.click();
    a.parentNode.removeChild(a);
  };

  var exportThemeCSS = function() {
    var styles = buildStyles();
    var dataStr = "data:text/json;charset=utf-8," + styles;
    var a = document.createElement("a");
    a.href = dataStr;
    a.setAttribute("download", "custom_theme.css");
    document.body.appendChild(a);
    a.click();
    a.parentNode.removeChild(a);
  };

  var setupPresets = function(newPresets) {
    presets = newPresets;
    Object.keys(presets).forEach(function(preset) {
      var option = document.createElement("option");
      option.innerHTML = preset;
      presetSelect.appendChild(option);
    });
  };

  var buildElementsFromPreset = function(preset) {
    initElements(preset.elements);
    isLightTheme = preset.light;
    initGUI();
  };

  var init = function(presets) {
    setupPresets(presets);
    addGUIEventListeners();
    buildElementsFromPreset(presets[presetSelect.value]);
  };

  var setBaseUIToggle = function() {
    baseUIToggle.checked = isLightTheme;
    setPageBackground();
  };

  var initElements = function(rawElements) {
    rawElements.forEach(function(element) {
      new CMElement(element);
    });
  };

  var initGUI = function() {
    setPageBackground();
    drawGUI();
    appendStyles();
    setBaseUIToggle();
  };

  return {
    init: init,
    addElement: addElement,
    getElement: getElement,
    getElements: getElements,
    updateStyles: updateStyles,
    getContainerEl: getContainerEl
  };
})();

var CMElement = function(options) {
  cmTheme.addElement(this);
  this.prop = options.prop || "color";
  this.hideTypeControls = this.prop !== "color";
  this.prettyName = options.prettyName;
  this.selector = options.selector;
  this.color = options.color;
  this.description = options.description || false;
  this.master = cmTheme.getElement(options.master) || false;
  this.underline = options.underline || false;
  this.italic = options.italic || false;
  this.id = this.prettyName.toLowerCase().replace(/\s/g, "_");
  this.setupElements();
  if (this.master) {
    this.syncTo(this.master);
  }
};

CMElement.prototype.getStyleRule = function() {
  var styles = this.selector + "{";
  styles += this.prop + ":" + this.color + ";";
  if (this.italic) {
    styles += "font-style:italic;";
  }
  if (this.underline) {
    styles += "text-decoration:underline;";
  }
  styles += "}";
  return styles;
};

CMElement.prototype.updateColor = function(color, themeNeedsSave) {
  themeNeedsSave = !!themeNeedsSave;
  this.color = color;
  this.inputEl.value = color;
  this.fauxEl.style.backgroundColor = color;
  pubsub.publish(this.id, color);
  cmTheme.updateStyles(themeNeedsSave);
};

CMElement.prototype.syncTo = function(master) {
  if (this.master) {
    pubsub.unsubscribe(this.token);
  }
  this.master = master;
  this.updateColor(master.color);
  this.token = pubsub.subscribe(
    master.id,
    function(color) {
      this.updateColor(color);
    }.bind(this)
  );
};

CMElement.prototype.unSync = function() {
  if (!this.master) {
    return;
  }
  this.master = false;
  this.selectEl.value = "none";
  pubsub.unsubscribe(this.token);
};

CMElement.prototype.setupInputEl = function() {
  var fauxEl = document.createElement("div");
  fauxEl.classList.add("cmEl__fauxColor");
  this.fauxEl = fauxEl;
  var inputEl = document.createElement("input");
  inputEl.classList.add("cmEl__color");
  inputEl.type = "color";
  inputEl.value = this.color;
  inputEl.addEventListener(
    "input",
    function() {
      this.unSync();
      this.updateColor(this.inputEl.value, true);
    }.bind(this)
  );
  this.inputEl = inputEl;
  this.fauxEl.appendChild(this.inputEl);
};

CMElement.prototype.setupHeadingEl = function() {
  var headingEl = document.createElement("h2");
  headingEl.classList.add("cmEl__heading");
  headingEl.innerHTML = this.prettyName;
  headingEl.addEventListener(
    "click",
    function() {
      this.inputEl.click();
    }.bind(this)
  );
  this.headingEl = headingEl;
};

CMElement.prototype.setupDescriptionEl = function() {
  if (!this.description) {
    return;
  }
  var descriptionEl = document.createElement("p");
  descriptionEl.classList.add("cmEl__desc");
  descriptionEl.innerHTML = escapeHTML(this.description);
  this.descriptionEl = descriptionEl;
};

CMElement.prototype.setupLabelEl = function() {
  var labelEl = document.createElement("label");
  labelEl.htmlFor = this.id;
  labelEl.classList.add("cmEl__syncTo-label");
  labelEl.innerHTML = "Sync to:";
  this.labelEl = labelEl;
};

CMElement.prototype.setupSelectEl = function() {
  var selectEl = document.createElement("select");
  selectEl.id = this.id;
  selectEl.classList.add("cmEl__syncTo");
  selectEl.innerHTML = "<option value='none'>None</option>";
  cmTheme.getElements().forEach(
    function(element) {
      if (element != this) {
        var option = document.createElement("option");
        option.value = element.id;
        if (this.master && element == this.master) {
          option.selected = true;
        }
        option.innerHTML = element.prettyName;
        selectEl.appendChild(option);
      }
    }.bind(this)
  );
  selectEl.addEventListener(
    "change",
    function(e) {
      var selection = cmTheme.getElement(this.selectEl.value);
      if (selection) {
        this.syncTo(selection);
      } else {
        this.unSync();
      }
    }.bind(this)
  );
  this.selectEl = selectEl;
};

CMElement.prototype.setupSettingsEl = function() {
  var settingsEl = document.createElement("button");
  settingsEl.classList.add("cmEl__settings");
  settingsEl.addEventListener("click", function() {
    this.classList.toggle("active");
  });
  var svg = this.buildSVGIcon("gear");
  settingsEl.appendChild(svg);
  this.settingsEl = settingsEl;
};

CMElement.prototype.buildSVGIcon = function(id) {
  var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
  var use = document.createElementNS("http://www.w3.org/2000/svg", "use");
  use.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", "#" + id);
  svg.appendChild(use);
  return svg;
};

CMElement.prototype.setupItalicEl = function() {
  var italicEl = this.buildSVGIcon("italic");
  italicEl.classList.add("cmEl__font-control");
  italicEl.addEventListener(
    "click",
    function() {
      this.italic = !this.italic;
      italicEl.classList.toggle("active", this.italic);
      cmTheme.updateStyles();
    }.bind(this)
  );
  this.italicEl = italicEl;
};

CMElement.prototype.setupUnderlineEl = function() {
  var underlineEl = this.buildSVGIcon("underline");
  underlineEl.classList.add("cmEl__font-control");
  underlineEl.addEventListener(
    "click",
    function() {
      this.underline = !this.underline;
      underlineEl.classList.toggle("active", this.underline);
      cmTheme.updateStyles();
    }.bind(this)
  );
  this.underlineEl = underlineEl;
};

CMElement.prototype.setupFontEl = function() {
  var fontEl = document.createElement("div");
  this.setupItalicEl();
  this.setupUnderlineEl();
  fontEl.classList.add("cmEl__font-controls");
  fontEl.appendChild(this.italicEl);
  fontEl.appendChild(this.underlineEl);
  this.fontEl = fontEl;
};

CMElement.prototype.setupElements = function() {
  this.setupInputEl();
  this.setupHeadingEl();
  this.setupSettingsEl();
  this.setupLabelEl();
  this.setupDescriptionEl();
  if (!this.hideTypeControls) {
    this.setupFontEl();
  }
  this.updateColor(this.color);
};

CMElement.prototype.draw = function() {
  var advWrapper = document.createElement("div");
  advWrapper.classList.add("cmEl__advanced");
  if (this.descriptionEl) {
    advWrapper.appendChild(this.descriptionEl);
  }
  if (this.fontEl) {
    advWrapper.appendChild(this.fontEl);
  }
  advWrapper.appendChild(this.labelEl);
  advWrapper.appendChild(this.selectEl);
  var wrapper = document.createElement("div");
  wrapper.id = "cmEl_" + this.id;
  wrapper.classList.add("cmEl");
  wrapper.appendChild(this.fauxEl);
  wrapper.appendChild(this.settingsEl);
  wrapper.appendChild(this.headingEl);

  wrapper.appendChild(advWrapper);
  cmTheme.getContainerEl().appendChild(wrapper);
  this.wrapper = wrapper;
};

CMElement.prototype.remove = function() {
  this.wrapper.parentNode.removeChild(this.wrapper);
};

// Theme presets are loaded as an external resource. You can find 'em here:
// https://codepen.io/alexzaworski/pen/e2eac470cd7c51a1f14efaf2c92ac06c
cmTheme.init(presets);

External CSS

  1. https://codepen.io/alexzaworski/pen/42eac2eb41b9fcb4a2931b4398aaf6c2

External JavaScript

  1. https://codepen.io/alexzaworski/pen/e2eac470cd7c51a1f14efaf2c92ac06c