Tengo la siguiente base estructural hecha sobre la que trabajar. Para empezar, Tenemos el archivo principal de la aplicación, el cual es `/Public/ecma/Application/HollowBond.ecma.js`. ```js "use strict"; const HollowBond = (function(){ const HollowBond = function(){ const self = this, screen = {x : 0, y : 0}; let cells = 40, thread_interval = null, fps = 12; this.item_self = document; this.settings = new SettingsManager(self); this.i18n = new I18NManager(self); this.views = {}; const constructor = () => { const position = self.settings.get("position", null, "body"); let target, preload = setInterval(() => { if(target = document.querySelector(position)){ clearInterval(preload); self.item_self = target.appendChild(document.createElement("div")); self.item_self.className = self.item_self.id = self.settings.get("class_name", null, "hollow-bond"); self.item_self.setAttribute("data-cells", cells = self.settings.get("cells", null, cells)); thread_interval = setInterval(thread, 1000 / fps); build(); }; }, fps = self.settings.get(["frames_per_second", "fps"], null, fps)); Common.SCSS(SCSS); }; const thread = () => { if(screen.x != self.item_self.offsetWidth || screen.y != self.item_self.offsetHeight){ screen.x = self.item_self.offsetWidth; screen.y = self.item_self.offsetHeight; self.item_self.style.fontSize = ((screen.x < screen.y ? screen.x : screen.y) / cells) + "px"; }; }; const build = () => {}; /** * @param {...(HTMLElement|string|Array.)} inputs * @returns {Array.} * @access public * @static */ this.html = (...inputs) => { const fragment = new DocumentFragment(), master = ( inputs[0] && (inputs[0].tagName || inputs[0].nodeName) ? inputs[0] : typeof inputs[0] == "string" ? document.querySelector(inputs[0]) : null), items = []; (master ? inputs.slice(1) : inputs).forEach(subinputs => { if(subinputs && (subinputs.tagName || subinputs.nodeName)) fragment.appendChild(subinputs); else if(typeof subinputs == "string") fragment.appendChild(document.createTextNode(subinputs)); else if(subinputs instanceof Array){ const [tag, attributes, children] = subinputs.concat([null, null]).slice(0, 3), item = document.createElement(tag); attributes && attributes.constructor == Object && Object.entries(attributes).forEach(([key, value]) => { if(/^on[_\-]?/i.test(key) && typeof value == "function") item.addEventListener(key.replace(/^on[_\-]?/i, "").replace(/[^a-z0-9]+/gi, "").toLowerCase(), event => { value(item, event); }); else if(value !== null) item.setAttribute(key.replace(/[^a-z0-9]+/gi, "-"), value); }); if(children instanceof Array) self.html(...children).forEach(child => item.appendChild(child)); else if(typeof children == "string") item.innerHTML += children; fragment.appendChild(item); }; }); items.push(...fragment.childNodes); master && master.appendChild(fragment); return items; }; constructor(); }; return HollowBond; })(); ``` Dentro de este hay dos gestores, siendo el primero de ellos el de configuración `/Public/ecma/Managers/SettingsManager.ecma.js`: ```js "use strict"; const SettingsManager = (function(){ const SettingsManager = function(hollow_bond){ const self = this, settings = {}; const constructor = () => { self.add(settings, true); }; this.add = (block, overwrite = true) => { for(const key in block) if(overwrite || settings[key] === undefined) settings[key] = block[key]; }; this.get = (keys, inputs = null, _default = null) => Common.get_value(keys, [inputs, settings], _default); constructor(); }; return SettingsManager; })(); ``` Y el segundo el de gestión de textos e idiomas `/Public/ecma/Managers/I18NManager.ecma.js`: ```js "use strict"; const I18NManager = (function(){ const I18NManager = function(hollow_bond){ const self = this, sentences = {}; let language = "espanol"; const constructor = () => { [i18n, typeof custom_i18n != "undefined" ? custom_i18n : {}].forEach(block => { self.add(block, true); }); self.set_language(hollow_bond.settings.get("language"), null, Object.keys(sentences)[0] || language); }; this.add = (new_sentences, overwrite = true) => { for(const language in new_sentences){ if(!sentences[language]) sentences[language] = {}; for(const key in new_sentences[language]) if(overwrite || sentences[language][key] === undefined) sentences[language][key] = new_sentences[language][key]; }; }; const get_sentence = (strings, _default) => { const done = []; if(typeof strings == "string") strings = [strings]; for(const current of [language].concat(Object.keys(sentences))) if(!done.includes(current)){ done.push(current); for(const key of strings) if(sentences[current][key] !== undefined) return sentences[current][key]; }; if(_default !== null) return _default; for(const text of strings) return text; return ""; }; this.get = (strings, inputs = null, _default = null) => Common.string_variables(get_sentence(strings, _default), inputs); this.update = () => { document.querySelectorAll("[data-i18n]").forEach(element => { const text = self.get(element.getAttribute("data-i18n")); if(element.getAttribute("data-i18n-without") != "true") element.textContent = text; ["title", "alt", "placeholder"].forEach(key => { element.hasAttribute(key) && element.setAttribute(key, text); }); }) }; this.set_language = new_language => { if(language != new_language && sentences[new_language]){ language = new_language; self.update(); }; }; this.get_languages = () => Object.keys(sentences); this.get_current_language = () => language; constructor(); }; return I18NManager; })(); ``` Por otro lado, tenemos una librería con métodos comunes la cual es `/Public/ecma/Utils/Common.ecma.js`: ```js "use strict"; const Common = (function(){ const Common = function(){}; Common.get_keys = (...items) => items.reduce((keys, item) => { if(item){ if(typeof item == "string" && /^[a-z_][a-z0-9_]*$/i.test(item) && !keys.includes(item)) keys.push(item); else if(item instanceof Array) for(const key of Common.get_keys(...item)) keys.includes(item) || keys.push(key); }; return keys; }, []); Common.get_dictionaries = (...items) => items.reduce((dictionaries, item) => { if(item){ if(item.constructor == Object) dictionaries.push(item); else if(item instanceof Array) dictionaries.push(...Common.get_dictionaries(...item)); }; return dictionaries; }, []); Common.get_value = (keys, inputs, _default = null) => { if((keys = Common.get_keys(keys)).length) for(const subinputs of Common.get_dictionaries(inputs)) for(const key of keys) if(subinputs[key] !== undefined) return subinputs[key]; return _default; }; Common.get_dictionary = (items, overwrite = false) => { if(items){ if(items.constructor == Object) return items; else if(items instanceof Array) return items.reduce((dictionary, item) => { Object.entries(Common.get_dictionary(item)).forEach(([key, value]) => { if(overwrite || dictionary[key] === undefined) dictionary[key] = value; }); return dictionary; }, {}); }; return {}; }; Common.string_variables = (string, variables, _default = null) => { variables = Common.get_dictionary(variables); return ("" + string).replace(/\{([^\{\}]+)\}/g, (all, key) => ( variables[key] !== undefined ? variables[key] : _default !== null ? _default : all)); }; Common.execute_array = (array, each_callback, end_callback, i = 0) => { if(i == array.length) end_callback(); else each_callback(array[i], () => { Common.execute_array(array, each_callback, end_callback, i + 1); }); }; Common.SCSS = (data, selector = "") => { /** @type {Arrya.} */ const childs = []; /** @type {string} */ let css = ``; Object.entries(data).forEach(([key, value]) => { if(value && value.constructor == Object) key.split(",").forEach(key => { /** @type {string} */ const subselector = `${(selector ? selector + ( key[0] == "&" ? "" : " " ) : "") + ( key[0] == "&" ? key.substring(1) : key )}`, /** @type {Array} */ subcss = Common.SCSS(value, subselector); childs.push(`\n\n${subselector}{${subcss[0]}\n}`, ...subcss.slice(1)); }); else css += `\n ${key.replace(/[^a-z0-9]+/gi, "-")} : ${value === null ? "none" : value};`; }); if(!selector){ /** @type {HTMLStyleElement} */ let style = document.querySelector("head>style"); if(!style){ style = document.querySelector("head").appendChild(document.createElement("style")); [ ["data-type", "text/css;charset=utf-8"], ["data-language", "CSS3"], ["charset", "utf-8"] ].forEach(([key, value]) => style.setAttribute(key, value === null ? "none" : value)); }; style.appendChild(document.createTextNode(css + childs.join(""))); }; return [css, ...childs]; }; return Common; })(); ``` Otra librería la cual es un diccionario para almacenar la configuración la cual es `/Public/ecma/Utils/Settings.ecma.js`: ```js "use strict"; /** @type {Dictionary.} */ const settings = { cells : 40, position : "body", class_name : "hollow-bond", frames_per_second : 12, language : "espanol", ...(typeof custom_settings == "undefined" ? {} : custom_settings) }; ``` Otra librería la cual es un diccionario de diccionarios con los textos donde el primer nivel, la clave indica el idioma, y en el diccionario anidado, la clave identifica al texto internacionalizado `/Public/ecma/Utils/I18N.ecma.js`: ```js "use strict"; /** @type {Dictionary.>} */ const i18n = { espanol : {} }; ``` Por otro lado, tenemos la librería de estilos, que es un JS que simula una especie de SASS la cual es `/Public/ecma/Utils/Styles.ecma.js`: ```js "use strict"; /** @type {Dictionary.} */ const SCSS = { ...{ "body,html" : { height : "100%", margin : "0em" } }, ...{ ".hollow-bond" : { position : "relative", top : "0em", left : "0em", width : "100%", height : "100%" } }, ...(typeof custom_styles == "undefined" ? {} : custom_styles) }; ``` Por otro lado, tenemos un directorio que está excluído del proyecto mediante `/.gitignore` que es `/Public/data` donde metemos cosas personalizadas o que no queramos incluir en el mismo, como pueden ser bases de datos JS, Assets agenos, etc. Finalmente tenemos el Index del proyecto el cual es `/Public/index.html`: ```html Hollow Bond ```