From 1a0f020f6920fc36493b1caa9aca0f59def3e0f9 Mon Sep 17 00:00:00 2001
From: Pierre Jarriges <pierre.jarriges@tutanota.com>
Date: Fri, 19 Nov 2021 16:44:58 +0100
Subject: [PATCH] ignore css git

---
 .gitignore                                    |    2 +-
 Makefile                                      |    4 +-
 .../src/components/create-article-form.js     |    2 +-
 .../src/components/update-article-form.js     |   46 +-
 public/assets/images/screen_mentalo_app.png   |  Bin 0 -> 57323 bytes
 public/assets/translations/en.json            |    4 +-
 public/assets/translations/fr.json            |    3 +-
 public/education/education.js                 |  881 +---
 public/games/games.js                         | 4505 +----------------
 public/main.js                                |  848 +---
 .../software-development.js                   |  986 +---
 public/style/style.css                        |  987 ----
 12 files changed, 25 insertions(+), 8243 deletions(-)
 create mode 100644 public/assets/images/screen_mentalo_app.png
 delete mode 100644 public/style/style.css

diff --git a/.gitignore b/.gitignore
index 37e1e05..7243b96 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,7 @@
 bundle.js
 *.map
 *.css.map
-src/**/*.css
+*.css
 node_modules
 target
 .env
diff --git a/Makefile b/Makefile
index 637aa2c..61dd6ea 100644
--- a/Makefile
+++ b/Makefile
@@ -23,10 +23,10 @@ bash-api:
 	docker exec -it kuadrado_server bash
 
 build-website:
-	npm run --prefix ./website build
+	npm run --prefix ./website build-prod
 
 build-website-debug:
-	npm run --prefix ./website build debug
+	npm run --prefix ./website build
 
 build-admin:
 	npm run --prefix ./admin-frontend build
diff --git a/admin-frontend/src/components/create-article-form.js b/admin-frontend/src/components/create-article-form.js
index e7651c0..494687d 100644
--- a/admin-frontend/src/components/create-article-form.js
+++ b/admin-frontend/src/components/create-article-form.js
@@ -193,7 +193,7 @@ class CreateArticleForm {
                 maxWidth: "800px",
             },
             contents: [
-                { tag: "button", contents: "RESET", onclick: this.reset.bind(this) },
+                !this.params.data && { tag: "button", contents: "RESET", onclick: this.reset.bind(this) },
                 { tag: "h2", contents: article.title },
                 { tag: "h4", contents: article.subtitle },
                 { tag: "p", contents: article.body.replace(/\n/g, "<br>") },
diff --git a/admin-frontend/src/components/update-article-form.js b/admin-frontend/src/components/update-article-form.js
index 67994d3..967c4d0 100644
--- a/admin-frontend/src/components/update-article-form.js
+++ b/admin-frontend/src/components/update-article-form.js
@@ -9,7 +9,12 @@ class UpdateArticleForm {
         this.state = {
             search_article_title: "",
             article_to_update: {},
-        }
+        };
+
+        this.articles_list = new ArticleList({
+            on_select_article: this.handle_select_article.bind(this),
+            on_delete_result: this.handle_delete_article.bind(this)
+        });
     }
 
     reset() {
@@ -39,38 +44,6 @@ class UpdateArticleForm {
             .catch(err => alert(err))
     }
 
-    refresh_search_result() {
-        obj2htm.subRender(
-            this.render_search_result(),
-            document.getElementById("update-article-form-search-result"),
-            { mode: "replace" },
-        );
-    }
-
-    render_search_result() {
-        const { search_result } = this.state;
-        return {
-            tag: "div",
-            id: "update-article-form-search-result",
-            style_rules: {
-                display: "flex",
-                gap: "10px",
-                alignItems: "center"
-            },
-            contents: search_result.title ? [
-                { tag: "strong", contents: search_result.title },
-                {
-                    tag: "button", contents: "SELECT",
-                    onclick: this.handle_select_result.bind(this)
-                },
-                {
-                    tag: "button", contents: "DELETE",
-                    onclick: this.handle_delete_article.bind(this)
-                }
-            ] : []
-        }
-    }
-
     refresh_update_form() {
         obj2htm.subRender(
             this.render_update_form(),
@@ -88,7 +61,7 @@ class UpdateArticleForm {
                     data: this.state.article_to_update,
                     on_article_sent: () => {
                         this.reset();
-                        this.refresh_search_result();
+                        this.articles_list.refresh_list();
                     }
                 }).render()]
                 : []
@@ -110,10 +83,7 @@ class UpdateArticleForm {
                 maxWidth: "800px",
             },
             contents: [
-                new ArticleList({
-                    on_select_article: this.handle_select_article.bind(this),
-                    on_delete_result: this.handle_delete_article.bind(this)
-                }).render(),
+                this.articles_list.render(),
                 { tag: "hr", style_rules: { width: "100%" } },
                 this.render_update_form(),
             ]
diff --git a/public/assets/images/screen_mentalo_app.png b/public/assets/images/screen_mentalo_app.png
new file mode 100644
index 0000000000000000000000000000000000000000..5220d25f95dba8e4d2c5fdb3f45a4cd9fcff4931
GIT binary patch
literal 57323
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
C7|!wl

literal 0
HcmV?d00001

diff --git a/public/assets/translations/en.json b/public/assets/translations/en.json
index 7dbe040..ad1ff38 100644
--- a/public/assets/translations/en.json
+++ b/public/assets/translations/en.json
@@ -29,5 +29,7 @@
     "edu-learn-mentalo": "Create a game in a few sessions with the Mentalo application. Handle logical, narrative and artistic concepts with maximum simplicity.",
     "Me contacter": "Contact me",
     "Pour s'inscrire ou en savoir plus": "To register or find out more",
-    "software-page-intro": "R&D, experimental projects, software tools for game development or for the web."
+    "software-page-intro": "R&D, experimental projects, software tools for game development or for the web.",
+    "Programme XXXX": "{%date%} program",
+    "Rien de prévu pour le moment": "Nothing planned for the moment"
 }
\ No newline at end of file
diff --git a/public/assets/translations/fr.json b/public/assets/translations/fr.json
index fdcaa31..b2aea9c 100644
--- a/public/assets/translations/fr.json
+++ b/public/assets/translations/fr.json
@@ -12,5 +12,6 @@
     "edu-learn-computer": "Perdu avec votre ordinateur ou votre smartphone, les logiciels, internet ? Prenez en main les fondamentaux apprenez pas à pas à utiliser sereinement la technologie.",
     "edu-learn-gnu": "<b>Passez le cap du libre ! </b><br/>Apprenez à installer Linux, faites vos premiers pas avec les logiciels libres et acquérez une autonomie suffisante pour une utilisation basique.",
     "edu-learn-mentalo": "Créez un jeu en quelques séances avec l'application Mentalo. Manipulez des concepts logiques, narratifs et artistiques avec le maximum de simplicité.",
-    "software-page-intro": "R&D, projets expérimentaux, outillage logiciel pour le développement de jeu ou pour le web."
+    "software-page-intro": "R&D, projets expérimentaux, outillage logiciel pour le développement de jeu ou pour le web.",
+    "Programme XXXX": "Programme {%date%}"
 }
\ No newline at end of file
diff --git a/public/education/education.js b/public/education/education.js
index 8207c7e..5669100 100644
--- a/public/education/education.js
+++ b/public/education/education.js
@@ -1,879 +1,2 @@
-(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
-module.exports = {
-    build: {
-        protected_dirs: ["assets", "style", "views", "standard"],
-        default_meta_keys: ["title", "description", "image", "open_graph", "json_ld"],
-    },
-};
-
-},{}],2:[function(require,module,exports){
-module.exports = {
-    images_url: `/assets/images`,
-    data_url: `/assets/data`,
-    translations_url: "/assets/translations"
-};
-
-},{}],3:[function(require,module,exports){
-"use strict";
-/**
- * A tiny library to handle text translation.
- */
-
-/**
- * init parameter object type
- * @typedef TranslatorParam
- * @property {String[]} supported_languages - DEFAULT: ["en"] - An array of ISO 639 lowercase language codes
- * @property {String} locale ISO 639 lowercase language code - DEFAULT: "en"
- *
- * @property {Boolean} use_url_locale_fragment - DEFAULT: true -
- * If true, the 1st fragment of the window.location url will be parsed as a language code
- * Example:
- * window.location
- * > https://example.com/en/some-page...
- * then the /en/ part of the url will be taken in priority and locale will be set to "en".
- * window.location
- * > https://example.com/some-page...
- * No locale fragment will be found so locale will not be set from url fragment.
- * window.location
- * > https://example.com/some-page/en
- * Doesn't work, locale fragment must be the first framgment
- *
- * @property {String} local_storage_key  - DEFAULT: "translator-prefered-language"
- *  The key used to saved the current locale into local storage
- *
- * @property {String} translations_url - REQUIRED - the url of the directory containing the static json files for translations
- * Translations files are expected to be named with their corresponding locale code.
- * Example:
- * if supported_languages is set to ["en", "fr", "it"]
- * and translations_url is set to "https://example.com/translations/""
- * Then the expected translations files are
- * https://example.com/translations/en.json
- * https://example.com/translations/fr.json
- * https://example.com/translations/it.json
- * The json resources must simple key value maps, value being the translated text.
- */
-
-module.exports = {
-    locale: "en", // ISO 639 lowercase language code
-    supported_languages: ["en"],
-    translations: {},
-    translations_url: "",
-    use_url_locale_fragment: true,
-    local_storage_key: "translator-prefered-language",
-
-    /**
-     * Initialize the lib with params
-     * @param {TranslatorParam} params 
-     * @returns {Promise}
-     */
-    init(params) {
-        Object.entries(params).forEach(k_v => {
-            const [key, value] = k_v;
-            if ([
-                "supported_languages",
-                "use_url_locale_fragment",
-                "local_storage_key",
-                "translations_url"
-            ].includes(key)) {
-                this[key] = value;
-            }
-        });
-
-        this.translations_url = this.format_translations_url(this.translations_url);
-        this.supported_languages = this.format_supported_languages(this.supported_languages);
-
-        return new Promise((resolve, reject) => {
-            const loc =
-                (() => {// Locale from url priority 1
-                    if (this.use_url_locale_fragment) {
-                        const first_url_fragment = window.location.pathname.substring(1).split("/")[0];
-                        const fragment_is_locale = this.supported_languages.includes(first_url_fragment);
-                        return fragment_is_locale ? first_url_fragment : "";
-                    } else {
-                        return "";
-                    }
-                })()
-                || localStorage.getItem(this.local_storage_key) // Locale from storage priority 2
-                || (() => { // locale from navigator priority 3
-                    const navigator_locale = navigator.language.split("-")[0].toLocaleLowerCase();
-                    return this.supported_languages.includes(navigator_locale)
-                        ? navigator_locale
-                        : this.supported_languages[0] // Default if navigator locale is not supported
-                })();
-
-            fetch(`${this.translations_url}${loc}.json`)
-                .then(response => response.json())
-                .then(response => {
-                    this.locale = loc;
-                    this.translations = response;
-                    resolve();
-                })
-                .catch(err => {
-                    this.locale = "en";
-                    reject(err);
-                });
-        });
-    },
-
-    /**
-     * Return a lowercase string without dash.
-     * If given locale is en-EN, then "en" will be returned.
-     * @param {String} locale 
-     * @returns A lowercase string
-     */
-    format_locale(locale) {
-        return locale.split("-")[0].toLowerCase();
-    },
-
-    /**
-     * Appends a slash at the end of the given string if missing, and returns the url.
-     * @param {String} url 
-     * @returns {String}
-     */
-    format_translations_url(url) {
-        if (url.charAt(url.length - 1) !== "/") {
-            url += "/"
-        }
-        return url;
-    },
-
-    /**
-     * Return the array of language codes formatted as lowsercase language code.
-     * if ["en-EN", "it-IT"]is given, ["en", "it"] will b returned.
-     * @param {String[]} languages_codes 
-     * @returns {String[]}
-     */
-    format_supported_languages(languages_codes) {
-        return languages_codes.map(lc => this.format_locale(lc));
-    },
-
-    /**
-     * Fetches a new set of translations in case the wanted language changes
-     * @param {String} locale A lowercase language code
-     * @returns {Promise}
-     */
-    update_translations(locale) {
-        locale = this.format_locale(locale);
-        return new Promise((resolve, reject) => {
-            fetch(`${this.translations_url}${locale}.json`)
-                .then(response => response.json())
-                .then(response => {
-                    this.translations = response;
-                    this.locale = locale;
-
-                    localStorage.setItem(this.local_storage_key, locale);
-
-                    const split_path = window.location.pathname.substring(1).split("/");
-                    const first_url_fragment = split_path[0];
-                    const fragment_is_locale = this.supported_languages.includes(first_url_fragment);
-
-                    if (fragment_is_locale) {
-                        split_path.splice(0, 1, locale);
-                        const updated_path = split_path.join("/");
-                        window.history.replaceState(null, "", "/" + updated_path);
-                    }
-                    resolve();
-                })
-                .catch(err => {
-                    reject(err);
-                });
-        });
-    },
-
-    /**
-     * Tries to get the translation of the source string, or return the string as it.
-     * @param {String} text The source text to translate
-     * @param {Object} params Some dynamic element to insert in the text
-     * Params can be used if the translated text provide placeholder like {%some_word%}
-     * Example:
-     * translator.trad("Some trad key", {some_word: "a dynamic parameter"})
-     * -> translation for "Some trad key": "A translated text with {%some_word%}"
-     * -> will be return as : "A translated text with a dynamic parameter"
-     * @returns {String}
-     */
-    trad: function (text, params = {}) {
-        text = this.translations[text] || text;
-
-        Object.keys(params).forEach(k => {
-            text = text.replace(`{%${k}%}`, params[k]);
-        });
-
-        return text;
-    }
-};
-},{}],4:[function(require,module,exports){
-"use strict";
-
-module.exports = {
-    register_key: "objectToHtmlRender",
-
-    /**
-     * Register "this" as a window scope accessible variable named by the given key, or default.
-     * @param {String} key 
-     */
-    register(key) {
-        const register_key = key || this.register_key;
-        window[register_key] = this;
-    },
-
-    /**
-     * This must be called before any other method in order to initialize the lib.
-     * It provides the root of the rendering cycle as a Javascript object.
-     * @param {Object} renderCycleRoot A JS component with a render method.
-     */
-    setRenderCycleRoot(renderCycleRoot) {
-        this.renderCycleRoot = renderCycleRoot;
-    },
-
-    event_name: "objtohtml-render-cycle",
-
-    /**
-     * Set a custom event name for the event that is trigger on render cycle.
-     * Default is "objtohtml-render-cycle".
-     * @param {String} evt_name 
-     */
-    setEventName(evt_name) {
-        this.event_name = evt_name;
-    },
-
-    /**
-     * This is the core agorithm that read an javascript Object and convert it into an HTML element.
-     * @param {Object} obj The object representing the html element must be formatted like:
-     * {
-     *      tag: String // The name of the html tag, Any valid html tag should work. div, section, br, ul, li...
-     *      xmlns: String // This can replace the tag key if the element is an element with a namespace URI, for example an <svg> tag.
-     *                      See https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS for more information
-     *      style_rules: Object // a object providing css attributes. The attributes names must be in JS syntax,
-     *                              like maxHeight: "500px", backgrouncColor: "#ff2d56",  margin: 0,  etc.
-     *      contents: Array or String // This reprensents the contents that will be nested in the created html element.
-     *                                   <div>{contents}</div>
-     *                                   The contents can be an array of other objects reprenting elements (with tag, contents, etc)
-     *                                   or it can be a simple string.
-     *      // All other attributes will be parsed as html attributes. They can be anything like onclick, href, onchange, title...
-     *      // or they can also define custom html5 attributes, like data, my_custom_attr or anything.
-     * }
-     * @returns {HTMLElement} The output html node.
-     */
-    objectToHtml(obj) {
-        if (!obj) return document.createElement("span"); // in case of invalid input, don't block the whole process.
-        const objectToHtml = this.objectToHtml.bind(this);
-        const { tag, xmlns } = obj;
-        const node = xmlns !== undefined ? document.createElementNS(xmlns, tag) : document.createElement(tag);
-        const excludeKeys = ["tag", "contents", "style_rules", "state", "xmlns"];
-
-        Object.keys(obj)
-            .filter(attr => !excludeKeys.includes(attr))
-            .forEach(attr => {
-                switch (attr) {
-                    case "class":
-                        node.classList.add(...obj[attr].split(" ").filter(s => s !== ""));
-                        break;
-                    case "on_render":
-                        if (!obj.id) {
-                            node.id = `${btoa(JSON.stringify(obj).slice(0, 127)).replace(/\=/g, '')}${window.performance.now()}`;
-                        }
-                        if (typeof obj.on_render !== "function") {
-                            console.error("The on_render attribute must be a function")
-                        } else {
-                            this.attach_on_render_callback(node, obj.on_render);
-                        }
-                        break;
-                    default:
-                        if (xmlns !== undefined) {
-                            node.setAttributeNS(null, attr, obj[attr])
-                        } else {
-                            node[attr] = obj[attr];
-                        }
-                }
-            });
-        if (obj.contents && typeof obj.contents === "string") {
-            node.innerHTML = obj.contents;
-        } else {
-            obj.contents &&
-                obj.contents.length > 0 &&
-                obj.contents.forEach(el => {
-                    switch (typeof el) {
-                        case "string":
-                            node.innerHTML = el;
-                            break;
-                        case "object":
-                            if (xmlns !== undefined) {
-                                el = Object.assign(el, { xmlns })
-                            }
-                            node.appendChild(objectToHtml(el));
-                            break;
-                    }
-                });
-        }
-
-        if (obj.style_rules) {
-            Object.keys(obj.style_rules).forEach(rule => {
-                node.style[rule] = obj.style_rules[rule];
-            });
-        }
-
-        return node;
-    },
-
-    on_render_callbacks: [],
-
-    /**
-     * This is called if the on_render attribute of a component is set.
-     * @param {HTMLElement} node The created html element
-     * @param {Function} callback The callback defined in the js component to render
-     */
-    attach_on_render_callback(node, callback) {
-        const callback_handler = {
-            callback: e => {
-                if (e.detail.outputNode === node || e.detail.outputNode.querySelector(`#${node.id}`)) {
-                    callback(node);
-                    const handler_index = this.on_render_callbacks.indexOf((this.on_render_callbacks.find(cb => cb.node === node)));
-                    if (handler_index === -1) {
-                        console.warn("A callback was registered for node with id " + node.id + " but callbacck handler is undefined.")
-                    } else {
-                        window.removeEventListener(this.event_name, this.on_render_callbacks[handler_index].callback)
-                        this.on_render_callbacks.splice(handler_index, 1);
-                    }
-                }
-            },
-            node,
-        };
-
-        const len = this.on_render_callbacks.push(callback_handler);
-        window.addEventListener(this.event_name, this.on_render_callbacks[len - 1].callback);
-    },
-
-    /**
-     * If a main element exists in the html document, it will be used as rendering root.
-     * If not, it will be created and inserted.
-     */
-    renderCycle: function () {
-        const main_elmt = document.getElementsByTagName("main")[0] || (function () {
-            const created_main = document.createElement("main");
-            document.body.appendChild(created_main);
-            return created_main;
-        })();
-
-        this.subRender(this.renderCycleRoot.render(), main_elmt, { mode: "replace" });
-    },
-
-    /**
-     * This method behaves like the renderCycle() method, but rather that starting the rendering cycle from the root component,
-    * it can start from any component of the tree. The root component must be given as the first argument, the second argument be
-    * be a valid html element in the dom and will be used as the insertion target.
-     * @param {Object} object An object providing a render method returning an object representation of the html to insert
-     * @param {HTMLElement} htmlNode The htlm element to update
-     * @param {Object} options can be used the define the insertion mode, default is set to "append" and can be set to "override",
-         * "insert-before" (must be defined along with an insertIndex key (integer)),
-         * "adjacent" (must be defined along with an insertLocation key (String)), "replace" or "remove".
-         * In case of "remove", the first argument "object" is not used and can be set to null, undefined or {}...
-     */
-    subRender(object, htmlNode, options = { mode: "append" }) {
-        let outputNode = null;
-
-        const get_insert = () => {
-            outputNode = this.objectToHtml(object);
-            return outputNode;
-        };
-
-        switch (options.mode) {
-            case "append":
-                htmlNode.appendChild(get_insert());
-                break;
-            case "override":
-                htmlNode.innerHTML = "";
-                htmlNode.appendChild(get_insert());
-                break;
-            case "insert-before":
-                htmlNode.insertBefore(get_insert(), htmlNode.childNodes[options.insertIndex]);
-                break;
-            case "adjacent":
-                /**
-                 * options.insertLocation must be one of:
-                 *
-                 * afterbegin
-                 * afterend
-                 * beforebegin
-                 * beforeend
-                 */
-                htmlNode.insertAdjacentHTML(options.insertLocation, get_insert());
-                break;
-            case "replace":
-                htmlNode.parentNode.replaceChild(get_insert(), htmlNode);
-                break;
-            case "remove":
-                htmlNode.remove();
-                break;
-        }
-        const evt_name = this.event_name;
-        const event = new CustomEvent(evt_name, {
-            detail: {
-                inputObject: object,
-                outputNode,
-                insertOptions: options,
-                targetNode: htmlNode,
-            }
-        });
-
-        window.dispatchEvent(event);
-    },
-};
-},{}],5:[function(require,module,exports){
-"use strict";
-const translator = require("ks-cheap-translator");
-const { translations_url } = require("../../constants");
-
-class WebPage {
-    constructor(args) {
-        Object.assign(this, args);
-
-        if (!this.id) {
-            this.id = "webpage-" + performance.now();
-        }
-
-        translator.init({
-            translations_url,
-            supported_languages: ["fr", "en"],
-        }).then(this.refresh_all.bind(this));
-    }
-
-    refresh() {
-        obj2htm.subRender(this.render(), document.getElementById(this.id), { mode: "replace" })
-    }
-
-    refresh_all() {
-        obj2htm.renderCycle()
-    }
-}
-
-module.exports = WebPage;
-},{"../../constants":2,"ks-cheap-translator":3}],6:[function(require,module,exports){
-"use strict";
-
-const { images_url } = require("../../../constants");
-const WebPage = require("../../lib/web-page");
-const translator = require("ks-cheap-translator");
-const t = translator.trad.bind(translator);
-
-const EDU_THEMES = [
-    {
-        title: "Programmation",
-        description: "edu-learn-coding",
-        image: "learning_theme_coding.png",
-    },
-    {
-        title: "Dessin numérique et animation 2D",
-        description: "edu-learn-2d",
-        image: "learning_theme_2d.png",
-    },
-    {
-        title: "Maths et physique",
-        description: "edu-learn-math",
-        image: "learning_theme_math.png",
-    },
-    {
-        title: "Aide informatique générale",
-        description: "edu-learn-computer",
-        image: "learning_theme_pc.png",
-    },
-    {
-        title: "Stage GNU/Linux",
-        description: "edu-learn-gnu",
-        image: "learning_theme_linux.png"
-    },
-    {
-        title: "Créer un jeu avec Mentalo",
-        description: "edu-learn-mentalo",
-        image: "learning_theme_mentalo.png",
-    }
-];
-
-class EducationPage extends WebPage {
-    render() {
-        return {
-            tag: "div",
-            id: "education-page",
-            typeof: "EducationalOrganization",
-            contents: [
-                {
-                    tag: "div",
-                    class: "page-header logo-left",
-                    contents: [
-                        {
-                            tag: "div",
-                            class: "page-contents-center grid-wrapper",
-                            contents: [
-                                {
-                                    tag: "div",
-                                    class: "logo",
-                                    contents: [
-                                        {
-                                            tag: "img",
-                                            alt: "image brain",
-                                            src: `${images_url}/brain.svg`,
-                                        },
-                                    ],
-                                },
-                                { tag: "h1", contents: t("Pédagogie") },
-                                {
-                                    tag: "p",
-                                    contents: t("edu-page-intro"),
-                                },
-                            ],
-                        },
-                    ],
-                },
-                {
-                    tag: "div",
-                    class: "title-banner",
-                },
-                {
-                    tag: "section",
-                    class: "bg-dark",
-                    contents: [
-                        {
-                            tag: "div",
-                            class: "page-contents-center",
-                            contents: [
-                                {
-                                    tag: "ul",
-                                    class: "edu-themes",
-                                    contents: EDU_THEMES.map(theme => {
-                                        return {
-                                            tag: "li",
-                                            class: "edu-theme",
-                                            contents: [
-                                                { tag: "img", width: 250, height: 140, class: "pixelated", src: `${images_url}/${theme.image}` },
-                                                { tag: "h3", contents: t(theme.title) },
-                                                { tag: "p", contents: t(theme.description) },
-                                            ]
-                                        }
-                                    })
-                                },
-                            ]
-                        },
-                    ]
-                },
-                {
-                    tag: "section",
-                    class: "practical-info",
-                    contents: [
-                        {
-                            tag: "div",
-                            class: "page-contents-center",
-                            contents: [
-                                {
-                                    tag: "div",
-                                    class: "info-block",
-                                    contents: [
-                                        { tag: "h3", class: "info-title", contents: `${t("Pour s'inscrire ou en savoir plus")} <em>(programme 2022 à définir, plus d'infos bientôt)</em>` },
-                                        {
-                                            tag: "ul",
-                                            class: "info-body",
-                                            contents: [
-                                                {
-                                                    tag: "li",
-                                                    contents: [
-                                                        { tag: "span", contents: t("Me contacter") },
-                                                        {
-                                                            tag: "a",
-                                                            href: "mailto:contact@kuadrado-software.fr",
-                                                            contents: "contact@kuadrado-software.fr",
-                                                        }
-                                                    ]
-                                                },
-                                                {
-                                                    tag: "li",
-                                                    contents: [
-                                                        { tag: "span", contents: "" },
-                                                        {
-                                                            tag: "a",
-                                                            href: "tel:+33475780872",
-                                                            contents: "04 75 78 08 72",
-                                                            property: "telephone",
-                                                        },
-                                                    ]
-                                                },
-                                            ]
-                                        }
-                                    ]
-                                }
-                            ]
-                        }
-                    ]
-
-                },
-            ],
-        };
-    }
-}
-
-module.exports = EducationPage;
-
-},{"../../../constants":2,"../../lib/web-page":5,"ks-cheap-translator":3}],7:[function(require,module,exports){
-"use strict";
-const runPage = require("../../run-page");
-const EducationPage = require("./education");
-runPage(EducationPage);
-
-},{"../../run-page":8,"./education":6}],8:[function(require,module,exports){
-"use strict";
-
-const renderer = require("object-to-html-renderer")
-const Template = require("./template/template");
-
-module.exports = function runPage(PageComponent) {
-    const template = new Template({ page: new PageComponent() });
-    renderer.register("obj2htm")
-    obj2htm.setRenderCycleRoot(template);
-    obj2htm.renderCycle();
-};
-
-},{"./template/template":10,"object-to-html-renderer":4}],9:[function(require,module,exports){
-"use strict";
-
-const { images_url } = require("../../../constants");
-const translator = require("ks-cheap-translator");
-const t = translator.trad.bind(translator);
-
-const NAV_MENU_ITEMS = [
-    { url: "/games/", text: "Jeux" },
-    {
-        url: "/education/",
-        text: "Pédagogie",
-    },
-    { url: "/software-development/", text: "Software" }
-];
-
-class NavBar {
-    constructor() {
-        this.initEventHandlers();
-    }
-
-    handleBurgerClick() {
-        document.getElementById("nav-menu-list").classList.toggle("responsive-show");
-    }
-
-    initEventHandlers() {
-        window.addEventListener("click", event => {
-            if (
-                event.target.id !== "nav-menu-list" &&
-                !event.target.classList.contains("burger") &&
-                !event.target.parentNode.classList.contains("burger")
-            ) {
-                document.getElementById("nav-menu-list").classList.remove("responsive-show");
-            }
-        });
-    }
-
-    handle_chang_lang(lang) {
-        translator.update_translations(lang).then(() => {
-            obj2htm.renderCycle();
-        }).catch(err => console.log(err));
-    }
-
-    renderHome() {
-        return {
-            tag: "div",
-            class: "home",
-            contents: [
-                {
-                    tag: "a",
-                    href: "/",
-                    contents: [
-                        {
-                            tag: "img",
-                            alt: "Logo Kuadrado",
-                            src: `${images_url}/logo_kuadrado.svg`,
-                        },
-                        {
-                            tag: "img",
-                            alt: "Kuadrado Software",
-                            class: "logo-text",
-                            src: `${images_url}/logo_kuadrado_txt.svg`,
-                        },
-                    ],
-                },
-            ],
-        };
-    }
-
-    renderMenu(menuItemsArray, isSubmenu = false, parentUrl = "") {
-        return {
-            tag: "ul",
-            id: "nav-menu-list",
-            class: isSubmenu ? "submenu" : "",
-            contents: menuItemsArray.map(item => {
-                const { url, text, submenu } = item;
-                const href = `${parentUrl}${url}`;
-                return {
-                    tag: "li",
-                    class: !isSubmenu && window.location.pathname === href ? "active" : "",
-                    contents: [
-                        {
-                            tag: "a",
-                            href,
-                            contents: t(text),
-                        },
-                    ].concat(submenu ? [this.renderMenu(submenu, true, url)] : []),
-                };
-            }).concat({
-                tag: "li",
-                class: "lang-flags",
-                contents: ["fr", "en"].map(lang => {
-                    return {
-                        tag: "img", src: `${images_url}/flag-${lang}.svg`,
-                        class: translator.locale === lang ? "selected" : "",
-                        onclick: this.handle_chang_lang.bind(this, lang)
-                    }
-                })
-            }),
-        };
-    }
-
-    renderResponsiveBurger() {
-        return {
-            tag: "div",
-            class: "burger",
-            onclick: this.handleBurgerClick.bind(this),
-            contents: [{ tag: "span", contents: "···" }],
-        };
-    }
-
-    render() {
-        return {
-            tag: "nav",
-            contents: [
-                this.renderHome(),
-                this.renderResponsiveBurger(),
-                this.renderMenu(NAV_MENU_ITEMS),
-            ],
-        };
-    }
-}
-
-module.exports = NavBar;
-
-},{"../../../constants":2,"ks-cheap-translator":3}],10:[function(require,module,exports){
-"use strict";
-
-const { in_construction } = require("../../config");
-const { images_url } = require("../../constants");
-const NavBar = require("./components/navbar");
-const translator = require("ks-cheap-translator");
-const t = translator.trad.bind(translator)
-
-class Template {
-    constructor(props) {
-        this.props = props;
-    }
-    render() {
-        return {
-            tag: "main",
-            contents: [
-                {
-                    tag: "header",
-                    contents: [new NavBar().render()],
-                },
-                in_construction && {
-                    tag: "section",
-                    class: "warning-banner",
-                    contents: [
-                        {
-                            tag: "strong",
-                            class: "page-contents-center",
-                            contents: t("Site en construction ..."),
-                        },
-                    ],
-                },
-                {
-                    tag: "section",
-                    id: "page-container",
-                    contents: [this.props.page.render()],
-                },
-                {
-                    tag: "footer",
-                    contents: [
-                        {
-                            tag: "div",
-                            class: "logo",
-                            contents: [
-                                {
-                                    tag: "img",
-                                    alt: `logo Kuadrado`,
-                                    src: `${images_url}/logo_kuadrado.svg`,
-                                },
-                                {
-                                    tag: "img",
-                                    class: "text-logo",
-                                    alt: "Kuadrado Software",
-                                    src: `${images_url}/logo_kuadrado_txt.svg`,
-                                },
-                            ],
-                        },
-                        {
-                            tag: "span",
-                            contents: "32 rue Simon Vialet, 07240 Vernoux en Vivarais. Ardèche, France",
-                        },
-                        {
-                            tag: "div",
-                            contents: [
-                                { tag: "strong", contents: "<blue>Contact : </blue>" },
-                                {
-                                    tag: "a",
-                                    href: "mailto:contact@kuadrado-software.fr",
-                                    contents: "contact@kuadrado-software.fr",
-                                },
-                            ],
-                        },
-                        {
-                            tag: "div",
-                            class: "social",
-                            contents: [
-                                {
-                                    tag: "strong",
-                                    contents: `<blue>${t("Sur les réseaux")} : </blue>`,
-                                },
-                                {
-                                    tag: "a",
-                                    href: "https://www.linkedin.com/company/kuadrado-software",
-                                    target: "_blank",
-                                    contents: "in",
-                                    title: "Linkedin",
-                                },
-                                {
-                                    tag: "a",
-                                    href: "https://mastodon.gamedev.place/@kuadrado_software",
-                                    target: "_blank",
-                                    contents: "m",
-                                    title: "Mastodon",
-                                }
-                            ],
-                        },
-                        {
-                            tag: "span",
-                            contents: `Copyleft 🄯 ${new Date()
-                                .getFullYear()} Kuadrado Software | 
-                                ${t("kuadrado-footer-copyleft")}`,
-                        },
-                        {
-                            tag: "div", contents: [
-                                { tag: "span", contents: t("Ce site web est") + " " },
-                                {
-                                    tag: "a", target: "_blank",
-                                    style_rules: { fontWeight: "bold" },
-                                    href: "https://gitlab.com/kuadrado-software/kuadrado-website/-/blob/master/README.md",
-                                    contents: "OPEN SOURCE"
-                                }
-                            ]
-                        }
-                    ],
-                },
-            ],
-        };
-    }
-}
-
-module.exports = Template;
-
-},{"../../config":1,"../../constants":2,"./components/navbar":9,"ks-cheap-translator":3}]},{},[7]);
+!function s(a,r,o){function i(t,e){if(!r[t]){if(!a[t]){var n="function"==typeof require&&require;if(!e&&n)return n(t,!0);if(c)return c(t,!0);throw(n=new Error("Cannot find module '"+t+"'")).code="MODULE_NOT_FOUND",n}n=r[t]={exports:{}},a[t][0].call(n.exports,function(e){return i(a[t][1][e]||e)},n,n.exports,s,a,r,o)}return r[t].exports}for(var c="function"==typeof require&&require,e=0;e<o.length;e++)i(o[e]);return i}({1:[function(e,t,n){t.exports={build:{protected_dirs:["assets","style","views","standard"],default_meta_keys:["title","description","image","open_graph","json_ld"]}}},{}],2:[function(e,t,n){t.exports={images_url:"/assets/images",data_url:"/assets/data",translations_url:"/assets/translations"}},{}],3:[function(e,t,n){"use strict";t.exports={locale:"en",supported_languages:["en"],translations:{},translations_url:"",use_url_locale_fragment:!0,local_storage_key:"translator-prefered-language",init(e){return Object.entries(e).forEach(e=>{var[t,e]=e;["supported_languages","use_url_locale_fragment","local_storage_key","translations_url"].includes(t)&&(this[t]=e)}),this.translations_url=this.format_translations_url(this.translations_url),this.supported_languages=this.format_supported_languages(this.supported_languages),new Promise((t,n)=>{const s=(()=>{if(this.use_url_locale_fragment){var e=window.location.pathname.substring(1).split("/")[0];return this.supported_languages.includes(e)?e:""}return""})()||localStorage.getItem(this.local_storage_key)||(e=navigator.language.split("-")[0].toLocaleLowerCase(),this.supported_languages.includes(e)?e:this.supported_languages[0]);var e;fetch(`${this.translations_url}${s}.json`).then(e=>e.json()).then(e=>{this.locale=s,this.translations=e,t()}).catch(e=>{this.locale="en",n(e)})})},format_locale(e){return e.split("-")[0].toLowerCase()},format_translations_url(e){return"/"!==e.charAt(e.length-1)&&(e+="/"),e},format_supported_languages(e){return e.map(e=>this.format_locale(e))},update_translations(s){return s=this.format_locale(s),new Promise((n,t)=>{fetch(`${this.translations_url}${s}.json`).then(e=>e.json()).then(e=>{this.translations=e,this.locale=s,localStorage.setItem(this.local_storage_key,s);const t=window.location.pathname.substring(1).split("/");e=t[0];this.supported_languages.includes(e)&&(t.splice(0,1,s),e=t.join("/"),window.history.replaceState(null,"","/"+e)),n()}).catch(e=>{t(e)})})},trad:function(t,n={}){return t=this.translations[t]||t,Object.keys(n).forEach(e=>{t=t.replace(`{%${e}%}`,n[e])}),t}}},{}],4:[function(e,t,n){"use strict";t.exports={register_key:"objectToHtmlRender",register(e){e=e||this.register_key;window[e]=this},setRenderCycleRoot(e){this.renderCycleRoot=e},event_name:"objtohtml-render-cycle",setEventName(e){this.event_name=e},objectToHtml(t){if(!t)return document.createElement("span");const n=this.objectToHtml.bind(this),{tag:e,xmlns:s}=t,a=void 0!==s?document.createElementNS(s,e):document.createElement(e),r=["tag","contents","style_rules","state","xmlns"];return Object.keys(t).filter(e=>!r.includes(e)).forEach(e=>{switch(e){case"class":a.classList.add(...t[e].split(" ").filter(e=>""!==e));break;case"on_render":t.id||(a.id=`${btoa(JSON.stringify(t).slice(0,127)).replace(/\=/g,"")}${window.performance.now()}`),"function"!=typeof t.on_render?console.error("The on_render attribute must be a function"):this.attach_on_render_callback(a,t.on_render);break;default:void 0!==s?a.setAttributeNS(null,e,t[e]):a[e]=t[e]}}),t.contents&&"string"==typeof t.contents?a.innerHTML=t.contents:t.contents&&0<t.contents.length&&t.contents.forEach(e=>{switch(typeof e){case"string":a.innerHTML=e;break;case"object":void 0!==s&&(e=Object.assign(e,{xmlns:s})),a.appendChild(n(e))}}),t.style_rules&&Object.keys(t.style_rules).forEach(e=>{a.style[e]=t.style_rules[e]}),a},on_render_callbacks:[],attach_on_render_callback(t,n){var e={callback:e=>{e.detail.outputNode!==t&&!e.detail.outputNode.querySelector(`#${t.id}`)||(n(t),-1===(e=this.on_render_callbacks.indexOf(this.on_render_callbacks.find(e=>e.node===t)))?console.warn("A callback was registered for node with id "+t.id+" but callbacck handler is undefined."):(window.removeEventListener(this.event_name,this.on_render_callbacks[e].callback),this.on_render_callbacks.splice(e,1)))},node:t},e=this.on_render_callbacks.push(e);window.addEventListener(this.event_name,this.on_render_callbacks[e-1].callback)},renderCycle:function(){var e,e=document.getElementsByTagName("main")[0]||(e=document.createElement("main"),document.body.appendChild(e),e);this.subRender(this.renderCycleRoot.render(),e,{mode:"replace"})},subRender(e,t,n={mode:"append"}){let s=null;var a=()=>(s=this.objectToHtml(e),s);switch(n.mode){case"append":t.appendChild(a());break;case"override":t.innerHTML="",t.appendChild(a());break;case"insert-before":t.insertBefore(a(),t.childNodes[n.insertIndex]);break;case"adjacent":t.insertAdjacentHTML(n.insertLocation,a());break;case"replace":t.parentNode.replaceChild(a(),t);break;case"remove":t.remove()}var r=this.event_name,r=new CustomEvent(r,{detail:{inputObject:e,outputNode:s,insertOptions:n,targetNode:t}});window.dispatchEvent(r)}}},{}],5:[function(e,t,n){"use strict";t.exports=class{constructor(e){this.props=e,this.id=this.props.images.join("").replace(/\s\./g),this.state={showImageIndex:0},this.RUN_INTERVAL=5e3,1<this.props.images.length&&this.run()}run(){this.runningInterval=setInterval(()=>{var{showImageIndex:e}=this.state,{images:t}=this.props;this.state.showImageIndex=e<t.length-1?++e:0,this.refreshImage()},this.RUN_INTERVAL)}setImageIndex(e){clearInterval(this.runningInterval),this.state.showImageIndex=e,this.refreshImage()}refreshImage(){obj2htm.subRender(this.render(),document.getElementById(this.id),{mode:"replace"})}render(){const{showImageIndex:n}=this.state,{images:e}=this.props;return{tag:"div",id:this.id,class:"image-carousel",contents:[{tag:"img",property:"image",alt:`image carousel ${e[n].replace(/\.[A-Za-z]+/,"")}`,src:e[n]},1<e.length&&{tag:"div",class:"carousel-bullets",contents:e.map((e,t)=>{return{tag:"span",class:`bullet ${n===t?"active":""}`,onclick:this.setImageIndex.bind(this,t)}})}]}}}},{}],6:[function(e,t,n){"use strict";const{fetch_json_or_error_text:s}=e("./fetch");t.exports={loadArticles:function(e,t){return s(`/articles/${e}/${t}`)},getArticleBody:function(e){return e.replaceAll("\n","<br/>")},getArticleDate:function(e){return`${e.getDate()}-${e.getMonth()+1}-${e.getFullYear()}`}}},{"./fetch":7}],7:[function(e,t,n){"use strict";t.exports={fetchjson:function(e){return new Promise((t,n)=>{fetch(e).then(e=>e.json()).then(e=>t(e)).catch(e=>n(e))})},fetchtext:function(e){return new Promise((t,n)=>{fetch(e).then(e=>e.text()).then(e=>t(e)).catch(e=>n(e))})},fetch_json_or_error_text:async function(e,s={}){return new Promise((t,n)=>{fetch(e,s).then(async e=>{400<=e.status&&e.status<600?n(await e.text()):t(await e.json())})})}}},{}],8:[function(e,t,n){"use strict";const s=e("ks-cheap-translator"),{translations_url:a}=e("../../constants");t.exports=class{constructor(e){Object.assign(this,e),this.id||(this.id="webpage-"+performance.now()),s.init({translations_url:a,supported_languages:["fr","en"]}).then(this.refresh_all.bind(this))}refresh(){obj2htm.subRender(this.render(),document.getElementById(this.id),{mode:"replace"})}refresh_all(){obj2htm.renderCycle()}}},{"../../constants":2,"ks-cheap-translator":3}],9:[function(e,t,n){"use strict";const{images_url:r}=e("../../../../constants"),o=e("../../../generic-components/image-carousel"),{getArticleBody:i}=e("../../../lib/article-utils");t.exports=class{constructor(e){this.props=e}render(){const{title:e,body:t,subtitle:n,images:s,details:a=[]}=this.props;return{tag:"article",class:"edu-article",typeof:"Article",contents:[{tag:"h2",class:"edu-art-title",contents:e,property:"name"},{tag:"div",class:"edu-art-image",contents:[{tag:"img",src:`${r}/${s[0]}`}]},{tag:"h3",class:"edu-art-subtitle",contents:n,property:"alternativeHeadline"},{tag:"div",class:"edu-art-description",contents:i(t),property:"description"},1<s.length&&{tag:"div",class:"edu-art-carousel",contents:[new o({images:s.map(e=>`${r}/${e}`)}).render()]},0<a.length&&{tag:"div",class:"article-details edu-art-details",contents:[{tag:"h2",contents:"Details"},{tag:"ul",class:"details-list",contents:a.map(e=>({tag:"li",class:"detail",contents:[{tag:"label",contents:e.label},{tag:"div",class:"detail-value",contents:e.value}]}))}]}]}}}},{"../../../../constants":2,"../../../generic-components/image-carousel":5,"../../../lib/article-utils":6}],10:[function(e,t,n){"use strict";const{loadArticles:s}=e("../../../lib/article-utils"),a=e("ks-cheap-translator"),r=a.trad.bind(a),o=e("./edu-article");t.exports=class{constructor(e){this.props=e,this.state={articles:[],loaded:!1},this.id="edu-articles-section",this.loadArticles()}loadArticles(){s("education",a.locale).then(e=>{this.state.articles=e}).catch(e=>console.log(e)).finally(()=>{this.state.loaded=!0,this.refresh()})}renderPlaceholder(){return{tag:"article",class:"placeholder",contents:[{tag:"div"},{tag:"div"},{tag:"div"},{tag:"div"}]}}refresh(){obj2htm.subRender(this.render(),document.getElementById(this.id),{mode:"replace"})}render(){const{articles:e,loaded:t}=this.state;return{tag:"section",class:"edu-articles page-contents-center",id:this.id,contents:t&&0<e.length?e.map(e=>new o({...e}).render()):t&&0===e.length?[{tag:"p",contents:r("Rien de prévu pour le moment")}]:[this.renderPlaceholder()]}}}},{"../../../lib/article-utils":6,"./edu-article":9,"ks-cheap-translator":3}],11:[function(e,t,n){"use strict";const{images_url:s}=e("../../../constants");var a=e("../../lib/web-page");const r=e("ks-cheap-translator"),o=e("./components/edu-articles"),i=r.trad.bind(r),c=[{title:"Programmation",description:"edu-learn-coding",image:"learning_theme_coding.png"},{title:"Dessin numérique et animation 2D",description:"edu-learn-2d",image:"learning_theme_2d.png"},{title:"Maths et physique",description:"edu-learn-math",image:"learning_theme_math.png"},{title:"Aide informatique générale",description:"edu-learn-computer",image:"learning_theme_pc.png"},{title:"Stage GNU/Linux",description:"edu-learn-gnu",image:"learning_theme_linux.png"},{title:"Créer un jeu avec Mentalo",description:"edu-learn-mentalo",image:"learning_theme_mentalo.png"}];class l extends a{render(){return{tag:"div",id:"education-page",typeof:"EducationalOrganization",contents:[{tag:"div",class:"page-header logo-left",contents:[{tag:"div",class:"page-contents-center grid-wrapper",contents:[{tag:"div",class:"logo",contents:[{tag:"img",alt:"image brain",src:`${s}/brain.svg`}]},{tag:"h1",contents:i("Pédagogie")},{tag:"p",contents:i("edu-page-intro")}]}]},{tag:"div",class:"title-banner"},{tag:"section",contents:[{tag:"div",class:"page-contents-center",contents:[{tag:"ul",class:"edu-themes",contents:c.map(e=>({tag:"li",class:"edu-theme",contents:[{tag:"img",width:250,height:140,class:"pixelated",src:`${s}/${e.image}`},{tag:"h3",contents:i(e.title)},{tag:"p",contents:i(e.description)}]}))}]}]},{tag:"h2",class:"edu-section-title page-contents-center",contents:i("Programme XXXX",{date:"2022"})},(new o).render()]}}}t.exports=l},{"../../../constants":2,"../../lib/web-page":8,"./components/edu-articles":10,"ks-cheap-translator":3}],12:[function(e,t,n){"use strict";const s=e("../../run-page");e=e("./education");s(e)},{"../../run-page":13,"./education":11}],13:[function(e,t,n){"use strict";const s=e("object-to-html-renderer"),a=e("./template/template");t.exports=function(e){e=new a({page:new e});s.register("obj2htm"),obj2htm.setRenderCycleRoot(e),obj2htm.renderCycle()}},{"./template/template":15,"object-to-html-renderer":4}],14:[function(e,t,n){"use strict";const{images_url:s}=e("../../../constants"),o=e("ks-cheap-translator"),i=o.trad.bind(o),a=[{url:"/games/",text:"Jeux"},{url:"/education/",text:"Pédagogie"},{url:"/software-development/",text:"Software"}];t.exports=class{constructor(){this.initEventHandlers()}handleBurgerClick(){document.getElementById("nav-menu-list").classList.toggle("responsive-show")}initEventHandlers(){window.addEventListener("click",e=>{"nav-menu-list"===e.target.id||e.target.classList.contains("burger")||e.target.parentNode.classList.contains("burger")||document.getElementById("nav-menu-list").classList.remove("responsive-show")})}handle_chang_lang(e){o.update_translations(e).then(()=>{obj2htm.renderCycle()}).catch(e=>console.log(e))}renderHome(){return{tag:"div",class:"home",contents:[{tag:"a",href:"/",contents:[{tag:"img",alt:"Logo Kuadrado",src:`${s}/logo_kuadrado.svg`},{tag:"img",alt:"Kuadrado Software",class:"logo-text",src:`${s}/logo_kuadrado_txt.svg`}]}]}}renderMenu(e,a=!1,r=""){return{tag:"ul",id:"nav-menu-list",class:a?"submenu":"",contents:e.map(e=>{var{url:t,text:n,submenu:e}=e;const s=`${r}${t}`;return{tag:"li",class:a||window.location.pathname!==s?"":"active",contents:[{tag:"a",href:s,contents:i(n)}].concat(e?[this.renderMenu(e,!0,t)]:[])}}).concat({tag:"li",class:"lang-flags",contents:["fr","en"].map(e=>({tag:"img",src:`${s}/flag-${e}.svg`,class:o.locale===e?"selected":"",onclick:this.handle_chang_lang.bind(this,e)}))})}}renderResponsiveBurger(){return{tag:"div",class:"burger",onclick:this.handleBurgerClick.bind(this),contents:[{tag:"span",contents:"···"}]}}render(){return{tag:"nav",contents:[this.renderHome(),this.renderResponsiveBurger(),this.renderMenu(a)]}}}},{"../../../constants":2,"ks-cheap-translator":3}],15:[function(e,t,n){"use strict";const{in_construction:s}=e("../../config"),{images_url:a}=e("../../constants"),r=e("./components/navbar"),o=e("ks-cheap-translator"),i=o.trad.bind(o);t.exports=class{constructor(e){this.props=e}render(){return{tag:"main",contents:[{tag:"header",contents:[(new r).render()]},s&&{tag:"section",class:"warning-banner",contents:[{tag:"strong",class:"page-contents-center",contents:i("Site en construction ...")}]},{tag:"section",id:"page-container",contents:[this.props.page.render()]},{tag:"footer",contents:[{tag:"div",class:"logo",contents:[{tag:"img",alt:"logo Kuadrado",src:`${a}/logo_kuadrado.svg`},{tag:"img",class:"text-logo",alt:"Kuadrado Software",src:`${a}/logo_kuadrado_txt.svg`}]},{tag:"span",contents:"32 rue Simon Vialet, 07240 Vernoux en Vivarais. Ardèche, France"},{tag:"div",contents:[{tag:"strong",contents:"<blue>Contact : </blue>"},{tag:"a",href:"mailto:contact@kuadrado-software.fr",contents:"contact@kuadrado-software.fr"}]},{tag:"div",class:"social",contents:[{tag:"strong",contents:`<blue>${i("Sur les réseaux")} : </blue>`},{tag:"a",href:"https://www.linkedin.com/company/kuadrado-software",target:"_blank",contents:"in",title:"Linkedin"},{tag:"a",href:"https://mastodon.gamedev.place/@kuadrado_software",target:"_blank",contents:"m",title:"Mastodon"}]},{tag:"span",contents:`Copyleft 🄯 ${(new Date).getFullYear()} Kuadrado Software | 
+                                ${i("kuadrado-footer-copyleft")}`},{tag:"div",contents:[{tag:"span",contents:i("Ce site web est")+" "},{tag:"a",target:"_blank",style_rules:{fontWeight:"bold"},href:"https://gitlab.com/kuadrado-software/kuadrado-website/-/blob/master/README.md",contents:"OPEN SOURCE"}]}]}]}}}},{"../../config":1,"../../constants":2,"./components/navbar":14,"ks-cheap-translator":3}]},{},[12]);
\ No newline at end of file
diff --git a/public/games/games.js b/public/games/games.js
index 45e5397..d497e27 100644
--- a/public/games/games.js
+++ b/public/games/games.js
@@ -1,4503 +1,2 @@
-(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
-module.exports = {
-    images_url: "/assets/images"
-}
-},{}],2:[function(require,module,exports){
-module.exports = {
-    build: {
-        protected_dirs: ["assets", "style", "views", "standard"],
-        default_meta_keys: ["title", "description", "image", "open_graph", "json_ld"],
-    },
-};
-
-},{}],3:[function(require,module,exports){
-module.exports = {
-    images_url: `/assets/images`,
-    data_url: `/assets/data`,
-    translations_url: "/assets/translations"
-};
-
-},{}],4:[function(require,module,exports){
-"use strict";
-/**
- * A tiny library to handle text translation.
- */
-
-/**
- * init parameter object type
- * @typedef TranslatorParam
- * @property {String[]} supported_languages - DEFAULT: ["en"] - An array of ISO 639 lowercase language codes
- * @property {String} locale ISO 639 lowercase language code - DEFAULT: "en"
- *
- * @property {Boolean} use_url_locale_fragment - DEFAULT: true -
- * If true, the 1st fragment of the window.location url will be parsed as a language code
- * Example:
- * window.location
- * > https://example.com/en/some-page...
- * then the /en/ part of the url will be taken in priority and locale will be set to "en".
- * window.location
- * > https://example.com/some-page...
- * No locale fragment will be found so locale will not be set from url fragment.
- * window.location
- * > https://example.com/some-page/en
- * Doesn't work, locale fragment must be the first framgment
- *
- * @property {String} local_storage_key  - DEFAULT: "translator-prefered-language"
- *  The key used to saved the current locale into local storage
- *
- * @property {String} translations_url - REQUIRED - the url of the directory containing the static json files for translations
- * Translations files are expected to be named with their corresponding locale code.
- * Example:
- * if supported_languages is set to ["en", "fr", "it"]
- * and translations_url is set to "https://example.com/translations/""
- * Then the expected translations files are
- * https://example.com/translations/en.json
- * https://example.com/translations/fr.json
- * https://example.com/translations/it.json
- * The json resources must simple key value maps, value being the translated text.
- */
-
-module.exports = {
-    locale: "en", // ISO 639 lowercase language code
-    supported_languages: ["en"],
-    translations: {},
-    translations_url: "",
-    use_url_locale_fragment: true,
-    local_storage_key: "translator-prefered-language",
-
-    /**
-     * Initialize the lib with params
-     * @param {TranslatorParam} params 
-     * @returns {Promise}
-     */
-    init(params) {
-        Object.entries(params).forEach(k_v => {
-            const [key, value] = k_v;
-            if ([
-                "supported_languages",
-                "use_url_locale_fragment",
-                "local_storage_key",
-                "translations_url"
-            ].includes(key)) {
-                this[key] = value;
-            }
-        });
-
-        this.translations_url = this.format_translations_url(this.translations_url);
-        this.supported_languages = this.format_supported_languages(this.supported_languages);
-
-        return new Promise((resolve, reject) => {
-            const loc =
-                (() => {// Locale from url priority 1
-                    if (this.use_url_locale_fragment) {
-                        const first_url_fragment = window.location.pathname.substring(1).split("/")[0];
-                        const fragment_is_locale = this.supported_languages.includes(first_url_fragment);
-                        return fragment_is_locale ? first_url_fragment : "";
-                    } else {
-                        return "";
-                    }
-                })()
-                || localStorage.getItem(this.local_storage_key) // Locale from storage priority 2
-                || (() => { // locale from navigator priority 3
-                    const navigator_locale = navigator.language.split("-")[0].toLocaleLowerCase();
-                    return this.supported_languages.includes(navigator_locale)
-                        ? navigator_locale
-                        : this.supported_languages[0] // Default if navigator locale is not supported
-                })();
-
-            fetch(`${this.translations_url}${loc}.json`)
-                .then(response => response.json())
-                .then(response => {
-                    this.locale = loc;
-                    this.translations = response;
-                    resolve();
-                })
-                .catch(err => {
-                    this.locale = "en";
-                    reject(err);
-                });
-        });
-    },
-
-    /**
-     * Return a lowercase string without dash.
-     * If given locale is en-EN, then "en" will be returned.
-     * @param {String} locale 
-     * @returns A lowercase string
-     */
-    format_locale(locale) {
-        return locale.split("-")[0].toLowerCase();
-    },
-
-    /**
-     * Appends a slash at the end of the given string if missing, and returns the url.
-     * @param {String} url 
-     * @returns {String}
-     */
-    format_translations_url(url) {
-        if (url.charAt(url.length - 1) !== "/") {
-            url += "/"
-        }
-        return url;
-    },
-
-    /**
-     * Return the array of language codes formatted as lowsercase language code.
-     * if ["en-EN", "it-IT"]is given, ["en", "it"] will b returned.
-     * @param {String[]} languages_codes 
-     * @returns {String[]}
-     */
-    format_supported_languages(languages_codes) {
-        return languages_codes.map(lc => this.format_locale(lc));
-    },
-
-    /**
-     * Fetches a new set of translations in case the wanted language changes
-     * @param {String} locale A lowercase language code
-     * @returns {Promise}
-     */
-    update_translations(locale) {
-        locale = this.format_locale(locale);
-        return new Promise((resolve, reject) => {
-            fetch(`${this.translations_url}${locale}.json`)
-                .then(response => response.json())
-                .then(response => {
-                    this.translations = response;
-                    this.locale = locale;
-
-                    localStorage.setItem(this.local_storage_key, locale);
-
-                    const split_path = window.location.pathname.substring(1).split("/");
-                    const first_url_fragment = split_path[0];
-                    const fragment_is_locale = this.supported_languages.includes(first_url_fragment);
-
-                    if (fragment_is_locale) {
-                        split_path.splice(0, 1, locale);
-                        const updated_path = split_path.join("/");
-                        window.history.replaceState(null, "", "/" + updated_path);
-                    }
-                    resolve();
-                })
-                .catch(err => {
-                    reject(err);
-                });
-        });
-    },
-
-    /**
-     * Tries to get the translation of the source string, or return the string as it.
-     * @param {String} text The source text to translate
-     * @param {Object} params Some dynamic element to insert in the text
-     * Params can be used if the translated text provide placeholder like {%some_word%}
-     * Example:
-     * translator.trad("Some trad key", {some_word: "a dynamic parameter"})
-     * -> translation for "Some trad key": "A translated text with {%some_word%}"
-     * -> will be return as : "A translated text with a dynamic parameter"
-     * @returns {String}
-     */
-    trad: function (text, params = {}) {
-        text = this.translations[text] || text;
-
-        Object.keys(params).forEach(k => {
-            text = text.replace(`{%${k}%}`, params[k]);
-        });
-
-        return text;
-    }
-};
-},{}],5:[function(require,module,exports){
-"use strict";
-
-const MtlAnimation = require("./model/animation");
-const MentaloEngine = require("./mentalo-engine");
-const MtlScene = require("./model/scene");
-const MtlGame = require("./model/game");
-const MtlSoundTrack = require("./model/sound-track");
-const SCENE_TYPES = require("./model/scene-types");
-const MtlChoice = require("./model/choice");
-const MtlGameObject = require("./model/game-object");
-const FrameRateController = require("./lib/frame-rate-controller");
-const font_tools = require("./lib/font-tools");
-const color_tools = require("./lib/color-tools");
-const shape_tools = require("./lib/shape-tools");
-module.exports = {
-    MentaloEngine,
-    MtlAnimation,
-    MtlScene,
-    MtlGame,
-    MtlSoundTrack,
-    SCENE_TYPES,
-    MtlChoice,
-    MtlGameObject,
-    FrameRateController,
-    font_tools,
-    color_tools,
-    shape_tools,
-};
-},{"./lib/color-tools":6,"./lib/font-tools":7,"./lib/frame-rate-controller":8,"./lib/shape-tools":9,"./mentalo-engine":11,"./model/animation":12,"./model/choice":13,"./model/game":15,"./model/game-object":14,"./model/scene":19,"./model/scene-types":18,"./model/sound-track":20}],6:[function(require,module,exports){
-"use strict";
-/**
- * Helpers to work with color values
- */
-
-/**
- * Get a RGB color value in String from either "rgb(x,x,x)" or #xxxxxx
- * @param {String} color A RGB color value as String in the form: #ffffff or rgb(x,x,x)
- * @returns {Uint8[]} An array of 3 values from 0 to 256 for [RED, GREEN, BLUE]
- */
-function color_str_to_rgb_array(color) {
-    color = color.toLowerCase(0);
-    if (color.includes("rgb")) {
-        return color.replaceAll(/[a-z\(\)w]/g, "")
-            .split(",")
-            .slice(0, 3)
-            .map(n => parseInt(n));
-    } else {
-        return color.replace("#", "")
-            .match(/.{0,2}/g)
-            .slice(0, 3)
-            .map(hex => parseInt(hex, 16));
-    }
-}
-
-/**
- * @param {String} color A color value (#xxxxxx or rbg(x,x,x))
- * @returns The average between each tone of the given color.
- */
-function get_average_rgb_color_tone(color) {
-    return color_str_to_rgb_array(color).reduce((tot, n) => tot + n / 3, 0);
-}
-
-/**
- * Gets an array of 3 8bits integers for red green and blue and and return a color value as an hexadecimal string #xxxxxx
- * @param {Uint8[]} rgb 
- * @returns {String} The hexacdecimal rgb value of the color including the hash character: #xxxxxx
- */
-function rgb_array_to_hex(rgb) {
-    return `#${rgb.slice(0, 3).map(n => {
-        const hex = n.toString(16);
-        return hex.length < 2 ? '0' + hex : hex;
-    }).join('')}`;
-}
-
-/**
- * Gets an array of 4 8bits integers for red green blue and transparency channel and return a color value as an hexadecimal string #xxxxxx
- * @param {Uint8[]} rgb
- * @returns {String} The hexacdecimal rgba value of the color including the hash character: #xxxxxxxx
- */
-function rgba_array_to_hex(rgba) {
-    rgba = rgba.length === 4 ? rgba : rgba.concat([255])
-    return `#${rgba.slice(0, 4).map(n => {
-        const hex = n.toString(16);
-        return hex.length < 2 ? '0' + hex : hex;
-    }).join('')}`;
-}
-
-/**
- * Gets a background color as argument and return a calculated optimal foreground color.
- * For example if the background is dark the function will return a lighten version of the background.
- * @param {String} base_color A RGB color value in hexadecimal form: #ffffff
- * @returns 
- */
-function get_optimal_visible_foreground_color(base_color) {
-    // create a 2D 1px canvas context
-    const tmp_canvas = document.createElement("canvas");
-    tmp_canvas.width = 1;
-    tmp_canvas.height = 1;
-    const ctx = tmp_canvas.getContext("2d");
-
-    // fill it with the given color
-    ctx.fillStyle = base_color;
-    ctx.fillRect(0, 0, 1, 1);
-
-    // Get either a semi-transparent black or semi-transparent white regarding the base color is above or below an avg of 127
-    const superpo_color = get_average_rgb_color_tone(base_color) > 127 ? "#0000003f" : "#ffffff3f";
-
-    // Fill the pixel with the semi-transparent black or white on top of the base color
-    ctx.fillStyle = superpo_color;
-    ctx.fillRect(0, 0, 1, 1);
-
-    // Return the flatten result of the superposition
-    return rgb_array_to_hex(Array.from(ctx.getImageData(0, 0, 1, 1).data));
-}
-
-/**
- * @param {Uint8[]} col1 A RGBA color value as an array of 4 0 to 256 integers
- * @param {Uint8[]} col2 A RGBA color value as an array of 4 0 to 256 integers
- * @returns {Boolean} true if the 2 colors are equals
- */
-function same_rgba(col1, col2) {
-    return col1[0] === col2[0]
-        && col1[1] === col2[1]
-        && col1[2] === col2[2]
-        && col1[3] === col2[3];
-}
-
-module.exports = {
-    get_average_rgb_color_tone,
-    get_optimal_visible_foreground_color,
-    color_str_to_rgb_array,
-    rgb_array_to_hex,
-    rgba_array_to_hex,
-    same_rgba,
-};
-},{}],7:[function(require,module,exports){
-"use strict";
-/**
- * Helpers to works with default web fonts
- */
-
-
-const FONT_FAMILIES = [
-    {
-        category: "Sans serif", values: [
-            { value: "sans-serif", text: "sans-serif" },
-            { text: "Arial", value: "Arial, sans-serif" },
-            { text: "Helvetica", value: "Helvetica, sans-serif" },
-            { text: "Verdana", value: "Verdana, sans-serif" },
-            { text: "Trebuchet MS", value: "Trebuchet MS, sans-serif" },
-            { text: "Noto Sans", value: "Noto Sans, sans-serif" },
-            { text: "Gill Sans", value: "Gill Sans, sans-serif" },
-            { text: "Avantgarde", value: "Avantgarde, TeX Gyre Adventor, URW Gothic L, sans-serif" },
-            { text: "Optima", value: "Optima, sans-serif" },
-            { text: "Arial Narrow", value: "Arial Narrow, sans-serif" }
-        ],
-    },
-    {
-        category: "Serif", values: [
-            { text: "serif", value: "serif" },
-            { text: "Times", value: "Times, Times New Roman, serif" },
-            { text: "Didot", value: "Didot, serif" },
-            { text: "Georgia", value: "Georgia, serif" },
-            { text: "Palatino", value: "Palatino, URW Palladio L, serif" },
-            { text: "Bookman", value: "Bookman, URW Bookman L, serif" },
-            { text: "New Century Schoolbook", value: "New Century Schoolbook, TeX Gyre Schola, serif" },
-            { text: "American Typewriter", value: "American Typewriter, serif" }
-        ],
-    },
-    {
-        category: "Monospace", values: [
-            { text: "monospace", value: "monospace" },
-            { text: "Andale Mono", value: "Andale Mono, monospace" },
-            { text: "Courrier New", value: "Courier New, monospace" },
-            { text: "Courrier", value: "Courier, monospace" },
-            { text: "FreeMono", value: "FreeMono, monospace" },
-            { text: "OCR A Std", value: "OCR A Std, monospace" },
-            { text: "DejaVu Sans Mono", value: "DejaVu Sans Mono, monospace" },
-        ],
-    },
-    {
-        category: "Cursive", values: [
-            { text: "cursive", value: "cursive" },
-            { text: "Comic Sans MS", value: "Comic Sans MS, Comic Sans, cursive" },
-            { text: "Apple Chancery", value: "Apple Chancery, cursive" },
-            { text: "Bradley Hand", value: "Bradley Hand, cursive" },
-            { text: "Brush Script MT", value: "Brush Script MT, Brush Script Std, cursive" },
-            { text: "Snell Roundhand", value: "Snell Roundhand, cursive" },
-            { text: "URW Chancery L", value: "URW Chancery L, cursive" },
-        ],
-    }
-];
-
-const TEXT_ALIGN_OPTIONS = [
-    { value: "left", text: "Left" }, // TODO trad or icon
-    { value: "right", text: "Right" },
-    { value: "center", text: "Center" },
-];
-
-const FONT_STYLE_OPTIONS = [
-    { value: "normal", text: "Normal" },
-    { value: "italic", text: "Italic" }, // TODO trad or icon
-];
-
-const FONT_WEIGHT_OPTIONS = [
-    { value: "1", text: "Thin" },
-    { value: "normal", text: "Normal" },
-    { value: "900", text: "Bold" },
-];
-
-/**
- * Gets an object description of a font and returns it formatted as a string that can be given to a 2D drawing context.
- * Example : ctx.font = get_canvas_font({font_family:"monospace", font_size: 32})
- * @param {Object} font_data An object description of the font settings, with font_size, font_family, font_style, font_weight.s
- * @returns {String}
- */
-function get_canvas_font(font_data = {}) {
-    const { font_size = 20, font_family = "sans-serif", font_style = "normal", font_weight = "normal" } = font_data;
-    const font_variant = "normal";
-    return `${font_style} ${font_variant} ${font_weight} ${font_size.toFixed()}px ${font_family}`;
-}
-
-/**
- * @returns {Object} A map of all defined constants for font families, text align options, font styles and font weights.
- */
-function get_font_options() {
-    return {
-        font_families: FONT_FAMILIES,
-        text_align_options: TEXT_ALIGN_OPTIONS,
-        font_style_options: FONT_STYLE_OPTIONS,
-        font_weight_options: FONT_WEIGHT_OPTIONS,
-    };
-}
-
-/**
- * An approximation of the average width and height of a character for a 2D drawing context with a given font configuration.
- * @param {CanvasRenderingContext2D} ctx
- * @returns {Object} An object with {width, height, text_line_height} entries
- */
-function get_canvas_char_size(ctx, font_settings) {
-    ctx.save();
-    ctx.font = get_canvas_font(font_settings);
-
-    const str = `Lorem ipsum dolor Sit amet, Consectetur adipiscing elit`;
-    const width = ctx.measureText(str).width / str.length;
-
-    // Scale width by an approximative 1.1 factor to calculate the height, 
-    // for example for letters like j q p etc that have a part bellow the text base line
-    const height = ctx.measureText("M").width * 1.1;
-    const text_line_height = height + height / 4;
-
-    ctx.restore();
-
-    return {
-        width,
-        height,
-        text_line_height,
-        interline_height: height / 4
-    }
-}
-
-module.exports = {
-    get_canvas_font,
-    get_font_options,
-    get_canvas_char_size,
-    FONT_FAMILIES,
-    TEXT_ALIGN_OPTIONS,
-    FONT_STYLE_OPTIONS,
-    FONT_WEIGHT_OPTIONS
-};
-},{}],8:[function(require,module,exports){
-"use strict";
-
-/**
- * A little helper class to control the rendering frame rate
- */
-class FrameRateController {
-    /**
-     * @param {Integer} fps The wanted frame rate in frame per second
-     */
-    constructor(fps) {
-        this.tframe = performance.now();
-        this.interval = 1000 / fps; // convert in milliseconds
-        this.initial = true;
-    }
-
-    /**
-     * @returns {Boolean} true if the defined interval is elapsed.
-     */
-    nextFrameReady() {
-        if (this.initial) {
-            this.initial = false;
-            return true;
-        }
-
-        const now = performance.now();
-        const elapsed = now - this.tframe;
-        const ready = elapsed > this.interval;
-
-        if (ready) {
-            this.tframe = now - (elapsed % this.interval);
-        }
-
-        return ready;
-    }
-}
-
-module.exports = FrameRateController;
-},{}],9:[function(require,module,exports){
-"use strict";
-
-/**
- * Draws a rectangle on a canvas 2D context with support of corner rounding and border options.
- * @param {CanvasRenderingContext2D} ctx
- * @param {Integer} x 
- * @param {Integer} y 
- * @param {Integer} width 
- * @param {Integer} height 
- * @param {Object} options 
- */
-function draw_rect(ctx, x, y, width, height, options = {
-    rounded_corners_radius: 0,
-    border: { width: 0, color: "rgba(0,0,0,0)" },
-    fill_color: "black",
-}) {
-    const { rounded_corners_radius = 0, border = { width: 0, color: "rgba(0,0,0,0)" }, fill_color = "black", fill_image } = options;
-    const smallest_axis = Math.min(width, height);
-    const radius = rounded_corners_radius > smallest_axis / 2 ? smallest_axis / 2 : rounded_corners_radius;
-
-    ctx.save();
-
-    ctx.beginPath();
-    ctx.arc(x + radius, y + radius, radius, Math.PI, 3 * Math.PI / 2);
-    ctx.lineTo(x + width - radius, y);
-    ctx.arc(x + width - radius, y + radius, radius, 3 * Math.PI / 2, 0);
-    ctx.lineTo(x + width, y + height - radius);
-    ctx.arc(x + width - radius, y + height - radius, radius, 0, Math.PI / 2);
-    ctx.lineTo(x + radius, y + height);
-    ctx.arc(x + radius, y + height - radius, radius, Math.PI / 2, Math.PI);
-    ctx.closePath();
-
-    ctx.clip();
-
-    if (fill_image) {
-        ctx.drawImage(
-            fill_image.src,
-            0, 0,
-            fill_image.src.width, fill_image.src.height,
-            fill_image.dx, fill_image.dy,
-            fill_image.dw, fill_image.dh,
-        )
-    } else {
-        ctx.fillStyle = fill_color;
-        ctx.fillRect(x, y, width, height);
-    }
-
-    if (border.width > 0) {
-        ctx.strokeStyle = border.color;
-        ctx.lineWidth = border.width;
-        ctx.stroke();
-    }
-
-    ctx.restore();
-}
-
-module.exports = {
-    draw_rect,
-}
-},{}],10:[function(require,module,exports){
-"use strict";
-
-const { get_canvas_font, get_canvas_char_size } = require("./font-tools");
-
-/**
- * A utility function to display text inside of a box coordinates
- * @param {CanvasRenderingContext2D} ctx
- * @param {String[]} lines 
- * @param {Object} bounds A bounding box object like {left, right, top, bottom, width, height}
- * @param {Object} settings An object with styling settings like {padding, font_color, background_color, ...}
- * @param {Object} a_state_ref Any object having a persistent life in the calling instance
- * @param {Boolean} streamed Wether the text should be displayed as a progressive text streaming or all at once
- * @returns {Boolean} true if the text is fully displayed
- */
-function draw_text_in_bounds(ctx, lines, bounds, settings, a_state_ref, streamed) {
-    ctx.save();
-    const char_size = get_canvas_char_size(ctx, settings);
-    const line_height = char_size.text_line_height;
-    ctx.font = get_canvas_font(settings);
-    ctx.fillStyle = settings.font_color;
-    ctx.textAlign = settings.text_align;
-    ctx.textBaseline = "top";
-
-    const get_text_position = () => {
-        const { padding = 0 } = settings;
-        const { left, top, width } = bounds;
-        switch (ctx.textAlign) {
-            case "left":
-                return {
-                    x: left + padding,
-                    y: top + padding,
-                }
-            case "right":
-                return {
-                    x: left + width - padding,
-                    y: top + padding,
-                }
-            case "center":
-                return {
-                    x: left + width / 2,
-                    y: top + padding,
-                }
-            default:
-                return {
-                    x: left + padding,
-                    y: top + padding,
-                }
-        }
-    };
-
-    const text_pos = get_text_position();
-
-    a_state_ref.stream = a_state_ref.stream || {
-        line_chars: 0,
-        line_index: 0,
-        complete: false,
-    };
-
-    const output_lines = streamed ? lines.map((line, i) => {
-        const stream_state = a_state_ref.stream;
-        if (i < stream_state.line_index || stream_state.complete) {
-            return line;
-        } else if (i > stream_state.line_index) {
-            return "";
-        } else {
-            const slice = line.slice(0, ++stream_state.line_chars);
-            if (slice.length === line.length) {
-                stream_state.complete = lines.length - 1 === stream_state.line_index;
-                stream_state.line_index = stream_state.complete
-                    ? stream_state.line_index
-                    : stream_state.line_index + 1;
-                stream_state.line_chars = 0;
-            }
-            return slice;
-        }
-    }) : (() => {
-        a_state_ref.stream.complete = true;
-        return lines;
-    })();
-
-    output_lines.forEach(line => {
-        ctx.fillText(line, text_pos.x, text_pos.y);
-        text_pos.y += line_height;
-    });
-
-    ctx.restore();
-
-    return a_state_ref.stream.complete;
-}
-
-module.exports = {
-    draw_text_in_bounds,
-}
-},{"./font-tools":7}],11:[function(require,module,exports){
-"use strict";
-
-const MtlGame = require("./model/game");
-const SCENE_TYPES = require("./model/scene-types");
-const MtlRender = require("./render/render");
-const { supported_locales, get_translated } = require("./translation");
-
-/**
- * The encapsulating class of all the components of the Mentalo game engine.
- * This is the class that should be used from outside to run a game.
- */
-class MentaloEngine {
-    /**
-     * Initialize A MtlGame instance, populates it with the gama_data given as param.
-     * Initializes the DOM element to contain the rendering of the engine.
-     * Defines the listeners for Escape key and initialize a MtlRender instance.
-     * @param {Object} params 
-     * Parameter params.game_data is required and should be the result of the exportation format of the Mentalo editor app.
-     */
-    constructor(params) {
-        this.game = new MtlGame();
-        this.game.load_data(params.game_data);
-
-        this.game.on_load_resources(() => this.loading = false);
-
-        this.use_locale = params.use_locale && supported_locales.includes(params.use_locale)
-            ? params.use_locale
-            : "en";
-
-        /**
-         * Allow to exit game by typing Escape or q
-         * @param {Event} e 
-         */
-        this.escape_listener = e => {
-            const k = e.key.toLowerCase();
-            (k === "escape" || k === "q") && this.quit();
-        };
-
-        window.addEventListener("keydown", this.escape_listener);
-
-        this.on_quit_game = function () {
-            if (this.used_default_container) {
-                this.container.remove();
-            }
-            window.removeEventListener("keydown", this.escape_listener);
-            params.on_quit_game && params.on_quit_game();
-        };
-
-        this.used_default_container = false;
-
-        this.container = params.container || (() => {
-            const el = document.createElement("div");
-            el.style.display = "flex";
-            el.style.justifyContent = "center";
-            el.style.alignItems = "center";
-            el.style.position = "absolute";
-            el.style.top = 0;
-            el.style.bottom = 0;
-            el.style.left = 0;
-            el.style.right = 0;
-            el.style.zIndex = 1000;
-            el.style.overflow = "hidden";
-            document.body.appendChild(el);
-            this.used_default_container = true
-            return el;
-        })();
-
-        this.render = new MtlRender({
-            container: this.container,
-            fullscreen: params.fullscreen,
-            frame_rate: params.frame_rate,
-            get_game_settings: this.get_game_settings.bind(this),
-            get_game_scenes: this.get_game_scenes.bind(this),
-            get_scene: this.get_scene.bind(this),
-            get_scene_index: this.get_scene_index.bind(this),
-            get_inventory: this.get_inventory.bind(this),
-            on_game_object_click: this.on_game_object_click.bind(this),
-            on_drop_inventory_object: this.on_drop_inventory_object.bind(this),
-            on_choice_click: this.on_choice_click.bind(this),
-        });
-
-        this.loading = true;
-        this.will_quit = false;
-    }
-
-    /**
-     * Returns a fragment of the game instance ui_options, which are the styling settings of the game interface.
-     * Can be the settings for text_boxes, choices_panel, inventory, etc.
-     * @param {String} key The key of the wanted interface settings.
-     * @returns {Object} The fragment of this.game.game_ui_options for the given key.
-     */
-    get_game_settings(key) {
-        return this.game.game_ui_options[key];
-    }
-
-    /**
-     * @returns {MtlScene[]} Returns an array of the scenes of the game.
-     */
-    get_game_scenes() {
-        return this.game.scenes;
-    }
-
-    /**
-     * @returns {MtlScene} The scene that is currently displayed
-     */
-    get_scene() {
-        return this.game.get_scene();
-    }
-
-    /**
-     * @returns {Integer} The index of the scene that is currently displayed
-     */
-    get_scene_index() {
-        return this.game.scenes.indexOf(this.get_scene());
-    }
-
-    /**
-     * Returns the game objects currently saved in the inventory.
-     * @returns {MtlGameObject[]}
-     */
-    get_inventory() {
-        return this.game.state.inventory;
-    }
-
-    /**
-     * Sets the flag this.will_quit to true.
-     * This flag will be parsed in the rendering loop and the engine will then effectively exit the game calling __exit().
-     */
-    quit() {
-        this.will_quit = true;
-    }
-
-    /**
-     * Clean the render instance, stops the rendering loop and calls the on_quit_game callback.
-     * This is called when the rendering loop reads the this.will_quit flag as true.
-     */
-    __quit() {
-        window.cancelAnimationFrame(window.mentalo_engine_animation_id);
-        this.render.exit_fullscreen();
-        this.render.clear_event_listeners();
-        this.game.get_soundtrack().stop();
-        this.on_quit_game();
-    }
-
-
-    /**
-     * Initialize the rendering loop and the MtlRender instance.
-     * Also checks that the first loaded scene has an image to load. 
-     * If it doesn't the game will exit immediately in order to avoid having a black screen.
-     * ( If the first scene doesn't have an image to load, the game.on_load_resource callback will 
-     * never be called and the engine will remain in loading state.  )
-     */
-    init() {
-        window.requestAnimationFrame = window.requestAnimationFrame
-            || window.mozRequestAnimationFrame
-            || window.webkitRequestAnimationFrame
-            || window.msRequestAnimationFrame;
-
-        window.cancelAnimationFrame = window.cancelAnimationFrame
-            || window.mozCancelAnimationFrame;
-
-        if (window.mentalo_engine_animation_id) {
-            window.cancelAnimationFrame(window.mentalo_engine_animation_id);
-        }
-
-        this.render.init();
-
-        if (this.game.scenes.find(scene => scene.animation.empty)) {
-            alert(get_translated("Some scenes have an empty image, game cannot be loaded", this.use_locale));
-            this.quit();
-        }
-    }
-
-    /**
-     * Clear the event listeners and rendering element related to the current scene from the MtlRender instance,
-     * And sets the game current scene state to a new index.
-     * @param {Integer} index 
-     */
-    go_to_scene(index) {
-        this.render.reset_user_error_popup();
-        this.render.clear_event_listeners();
-        this.game.get_soundtrack().stop();
-        this.game.go_to_scene(index);
-        this.render.clear_children();
-        this.render.reset_text_box_visibility();
-    }
-
-    /**
-     * Event listener for doubleclick on a game object.
-     * Adds the object to inventory.
-     * @param {MtlGameObject} obj 
-     */
-    on_game_object_click(obj) {
-        this.game.inventory_has_empty_slot() && this.game.add_object_to_inventory(obj);
-    }
-
-    /**
-     * All callback called from the inventory render component when an object image is clicked in the inventory.
-     * Removes the object from inventory.
-     * @param {MtlGameObject} obj 
-     */
-    on_drop_inventory_object(obj) {
-        this.game.remove_object_from_inventory(obj);
-    }
-
-    /**
-     * Callback called from the choices_panel render component.
-     * Handles the click on a choice.
-     * @param {MtlChoice} choice 
-     * @returns 
-     */
-    on_choice_click(choice) {
-        const { destination_scene_index, use_objects } = choice;
-        if (destination_scene_index === -1) { // -1 is default index when the choice destination scene is not set.
-            this.render.set_user_error_popup({
-                text: get_translated("Destination scene has not been set.", this.use_locale),
-            });
-            return;
-        }
-
-        if (destination_scene_index === -2) { // -2 is the index used to indicate that destination scene is just the end of program.
-            this.quit()
-            return;
-        }
-
-        // Handle the case where the clicked choice requires some objects to be presents in the inventory.
-        if (use_objects.value) {
-            const inventory = this.get_inventory();
-            const objs = [];
-            for (const it of use_objects.items) {
-                const ob = Array.from(inventory).find(o => o.name === it.name);
-                if (ob) {
-                    objs.push(ob);
-                } else {
-                    this.render.set_user_error_popup({
-                        text: use_objects.missing_object_message, stream_text: true,
-                    });
-                    return;
-                }
-            }
-
-            objs.forEach(o => use_objects.items
-                .find(it => o.name === it.name)
-                .consume_object && this.game.consume_game_object(o));
-        }
-
-        this.go_to_scene(destination_scene_index);
-    }
-
-    /**
-     * Callback called when a scene of type "Cinematic" reaches end.
-     */
-    on_cinematic_end() {
-        const scene = this.game.get_scene();
-        if (scene.end_cinematic_options.quit) {
-            this.quit();
-        } else if (scene.end_cinematic_options.destination_scene_index === -1) {
-            this.render.set_user_error_popup({
-                text: get_translated("Next scene has not been set.", this.use_locale),
-                on_close: this.quit.bind(this),
-            });
-        } else {
-            this.go_to_scene(scene.end_cinematic_options.destination_scene_index);
-        }
-    }
-
-    /**
-     * The rendering loop.
-     * Handles the different states of the engine (quit, loading or runnning normally)
-     * @returns The id of the registered window.requestAnimationFrame
-     */
-    run_game() {
-        if (this.will_quit) {
-            this.__quit();
-            return;
-        } else if (this.loading) {
-            this.render.draw_loading();
-        } else {
-            if (this.get_scene()._type === SCENE_TYPES.CINEMATIC) {
-                this.game.update_cinematic_timeout();
-                if (this.game.is_cinematic_ended()) {
-                    this.on_cinematic_end();
-                }
-            }
-
-            const sound_track = this.game.get_soundtrack();
-            if (sound_track.loaded && !sound_track.is_playing()) {
-                sound_track.play();
-            }
-
-            this.render.draw_game();
-        }
-
-        window.mentalo_engine_animation_id = requestAnimationFrame(this.run_game.bind(this));
-    }
-}
-
-module.exports = MentaloEngine;
-},{"./model/game":15,"./model/scene-types":18,"./render/render":23,"./translation":35}],12:[function(require,module,exports){
-"use strict";
-const Loadable = require("./loadable");
-
-/**
- * The data type used for a MtlScene animation
- */
-class MtlAnimation extends Loadable {
-    /**
-     * Initialize an empty instance of Image HtmlElement,
-     * and registers the onload callback to update the instance with the loaded image actual dimensions.
-     */
-    constructor() {
-        super(new Image(), "image", "load");
-        this.image.onload = () => this.update_dimensions();
-
-        this.name = "";
-        this.dimensions = {
-            width: 0,
-            height: 0,
-        };
-        this.frame_nb = 1;
-        this.frame = 0;
-        this.speed = 1;
-        this.play_once = false;
-        this.initialized = false;
-        this.finished = false;
-    }
-
-    /**
-     * Copies the dimensions of the loaded image at the root level of the instance.
-     * Deletes the canvas dimensions precalculations created by the SceneAnimation component
-     */
-    update_dimensions() {
-        this.dimensions = {
-            width: this.image.width / this.frame_nb,
-            height: this.image.height,
-        };
-
-        if (this.canvas_precalc) {
-            delete this.canvas_precalc;
-        }
-    }
-
-    /**
-     * Populates the instance with loaded litteral data.
-     * @param {Object} data 
-     */
-    init(data) {
-        this.empty = data.src === "";
-        this.name = data.name;
-        this.image.src = data.src;
-        this.frame_nb = data.frame_nb;
-        this.frame = 0;
-        this.speed = data.speed;
-        this.play_once = data.play_once;
-        this.initialized = true;
-    }
-
-    /**
-     * Increments the framcount argument if the animation next frame is ready to be rendered.
-     * @param {Integer} framecount 
-     */
-    update_frame(framecount) {
-        this.finished = this.play_once && this.frame === this.frame_nb - 1;
-        if (this.frame_nb > 1 && framecount % this.speed === 0 && !this.finished) {
-            this.frame = this.frame + 1 <= this.frame_nb - 1 ? this.frame + 1 : 0;
-        }
-    }
-
-    /**
-     * Reset the frame state.
-     */
-    reset_frame() {
-        this.finished = false;
-        this.frame = 0;
-    }
-}
-
-module.exports = MtlAnimation;
-},{"./loadable":17}],13:[function(require,module,exports){
-"use strict";
-/**
- * The data type used for the choices of a MtlScene
- */
-class MtlChoice {
-    /**
-     * Initializes the instance with given data or default empty data.
-     * @param {Object} data 
-     */
-    constructor(data = {
-        text: "",
-        destination_scene_index: -1,
-        use_objects: {
-            value: false,
-            items: [],
-            missing_object_message: "",
-        }
-    }) {
-        this.load_data(data);
-    }
-
-    /**
-     * Populates the instance with litteral description data
-     * @param {Object} data 
-     */
-    load_data(data) {
-        this.text = data.text;
-        this.destination_scene_index = data.destination_scene_index;
-        this.use_objects = data.use_objects;
-    }
-}
-
-module.exports = MtlChoice;
-},{}],14:[function(require,module,exports){
-"use strict";
-const Loadable = require("./loadable");
-
-/**
- * The data type used for the game_objects field of a MtlScene
- */
-class MtlGameObject extends Loadable {
-    /**
-     * Initializes the instance with an empty image and zero position.
-     */
-    constructor() {
-        super(new Image(), "image", "load");
-        this.name = "";
-        this.position = { x: 0, y: 0 };
-        this.state = {};
-    }
-
-    /**
-     * Populates the instance with a litteral description data object
-     * @param {Object} data 
-     */
-    load_data(data) {
-        this.image.src = data.image;
-        this.name = data.name;
-        this.position = data.position;
-    }
-
-    /**
-     * @returns {Object} A {width, height} object for the dimensions of the image.
-     */
-    get_dimensions() {
-        return {
-            width: this.image.width,
-            height: this.image.height,
-        }
-    }
-
-    /**
-     * Returns the bounding box coordinates of the object image as a {top, right, bottom, left} object.
-     * @returns {Object} 
-     */
-    get_bounds() {
-        const dims = this.get_dimensions();
-        return {
-            top: this.position.y,
-            right: this.position.x + dims.width,
-            bottom: this.position.y + dims.height,
-            left: this.position.x,
-        }
-    }
-}
-
-module.exports = MtlGameObject;
-},{"./loadable":17}],15:[function(require,module,exports){
-"use strict";
-
-const MtlScene = require("./scene");
-const SCENE_TYPES = require("./scene-types");
-
-/**
- * The data type to encapsulate a loaded game.
- */
-class MtlGame {
-    /**
-     * Initialize the instance with default data.
-     */
-    constructor() {
-        this.name = "";
-        this.scenes = [new MtlScene()];
-
-        // Game interface styling preferences
-        this.game_ui_options = {
-            // The overall look of the innterface
-            general: {
-                background_color: "#000000",
-                animation_canvas_dimensions: {
-                    width: 600,
-                    ratio: "4:3",
-                    height: function () {
-                        const split_ratio = this.ratio.split(":").map(n => parseInt(n));
-                        const factor = split_ratio[1] / split_ratio[0];
-                        return Number((this.width * factor).toFixed());
-                    },
-                },
-            },
-            // The text box displayed on top of a scene image if a message is defined
-            text_boxes: {
-                background_color: "#222222",
-                font_size: 20,
-                font_style: "normal",
-                font_weight: "normal",
-                font_family: "Arial, sans-serif",
-                font_color: "#ffffff",
-                text_align: "left",
-                padding: 20,
-                margin: 20,
-                border_width: 0,
-                rounded_corners_radius: 0,
-            },
-            // The panel containing the choices buttons below the scene image.
-            choices_panel: {
-                background_color: "#222222",
-                font_size: 20,
-                font_family: "sans",
-                font_color: "#ffffff",
-                font_style: "normal",
-                text_align: "left",
-                font_weight: "normal",
-                container_padding: 20,
-                choice_padding: 10,
-                active_choice_background_color: "rgba(255,255,255,.4)",
-                active_choice_border_width: 0,
-                active_choice_rounded_corners_radius: 0,
-            },
-            // the inventory grid drawn at the right of the scene image
-            inventory: {
-                background_color: "#222222",
-                columns: 1,
-                rows: 4,
-                gap: 10,
-                padding: 20,
-                slot_rounded_corner_radius: 0,
-                slot_border_width: 2,
-            },
-        };
-
-        this.starting_scene_index = 0;
-
-        // The state of the running game
-        this.state = {
-            scene: this.starting_scene_index,
-            inventory: new Set(),
-            cinematic_timeout: {
-                inc: 0,
-                last_update_time: -1,
-                timeout: false,
-            },
-        };
-
-        // This state will be incremented for each when it finishes to load its image and sound resources.
-        // The loaded_scenes value must be equal to the scenes number for the game to consider that all resources are loaded.
-        this.load_state = {
-            loaded_scenes: 0,
-        };
-    }
-
-    /**
-     * Returns the scene that is currently displayed
-     * @returns {MtlScene}
-     */
-    get_scene() {
-        return this.scenes[this.state.scene];
-    }
-
-    /**
-     * @returns {MtlSoundTrack} the soundtrack that is set for the current scene
-     */
-    get_soundtrack() {
-        return this.get_scene().sound_track;
-    }
-
-    /**
-     * Updates the state with a new scene index.
-     * @param {Integer} index The index of the wanted scene
-     */
-    go_to_scene(index) {
-        const current_scene = this.get_scene();
-        current_scene.animation.reset_frame();
-        if (current_scene._type == SCENE_TYPES.CINEMATIC) {
-            this.state.cinematic_timeout = {
-                inc: 0,
-                last_update_time: -1,
-                timeout: false,
-            };
-        }
-        this.state.scene = index;
-    }
-
-    /**
-     * This is called if the current scene is of type Cinematic.
-     * UIncrements the cinematic_timeout counter and update the 
-     * cinematic_timeout.timeout state to true if the cinematic 
-     * has been running as much or more time than what is defined in scene.cinematic_duration.
-     */
-    update_cinematic_timeout() {
-        if (this.state.cinematic_timeout.timeout) return;
-
-        const t = new Date().getTime();
-
-        if (this.state.cinematic_timeout.last_update_time === -1) {
-            this.state.cinematic_timeout.last_update_time = t;
-        }
-
-        this.state.cinematic_timeout.inc = t - this.state.cinematic_timeout.last_update_time;
-        this.state.cinematic_timeout.timeout = this.get_scene().cinematic_duration * 1000 <= this.state.cinematic_timeout.inc;
-    }
-
-    /**
-     * Returns a state wether the running cinematic is ended or not
-     * @returns {Boolean}
-     */
-    is_cinematic_ended() {
-        const scene = this.get_scene();
-        if (scene.cinematic_duration === 0) return scene.animation.finished;
-        return this.state.cinematic_timeout.timeout;
-    }
-
-    /**
-     * Takes a game object as parameter and references it in the inventory state.
-     * @param {MtlGameObject} obj 
-     */
-    add_object_to_inventory(obj) {
-        this.state.inventory.add(obj);
-    }
-
-    /**
-     * @returns {Boolean} true if inventory has an empty slot.
-     */
-    inventory_has_empty_slot() {
-        const { columns, rows } = this.game_ui_options.inventory;
-        return this.state.inventory.size < columns * rows;
-    }
-
-    /**
-     * Take a game object as argument and removes its reference from the inventory state.
-     * @param {MtlGameObject} obj 
-     */
-    remove_object_from_inventory(obj) {
-        this.state.inventory.delete(obj);
-    }
-
-    /**
-     * Takes a game object as argument, removes the reference from inventory 7
-     * and delete the object from the scene it's defined into.
-     * @param {MtlGameObject} obj 
-     */
-    consume_game_object(obj) {
-        this.remove_object_from_inventory(obj);
-        for (const s of this.scenes) {
-            const found_obj = s.game_objects.find(o => o === obj);
-            if (found_obj) {
-                s.game_objects.splice(s.game_objects.indexOf(found_obj), 1);
-                break;
-            }
-        }
-    }
-
-    /**
-     * Returns true if the number of scenes having finished to load their loadable resources is equal to the total number of scenes.
-     * @returns {Boolean}
-     */
-    all_resources_loaded() {
-        return this.load_state.loadable_elements === this.load_state.loaded_elements;
-    }
-
-    /**
-     * Returns a concatenation of all game objects of all scenes.
-     * @returns {MtlGameObject[]}
-     */
-    get_game_objects() {
-        return this.scenes.reduce((acc, scene) => acc.concat(scene.game_objects), []);
-    }
-
-    /**
-     * Populates the instance with a litteral game desriptor.
-     * The expected data format is identical to what's exported from the Mentalo app editor,
-     * or what's returned from the Mentalo API database.
-     * @param {Object} data 
-     */
-    load_data(data) {
-        this.name = data.name;
-
-        this.starting_scene_index = data.starting_scene_index || 0;
-        this.state.scene = this.starting_scene_index;
-
-        this.scenes = data.scenes.map(scene => {
-            const scene_instance = new MtlScene();
-            scene_instance.load_data(scene);
-            scene_instance.on_load_group_complete(() => {
-                this.load_state.loaded_scenes++;
-                if (this.load_state.loaded_scenes === this.scenes.length) {
-                    this.on_load_resources_callback && this.on_load_resources_callback();
-                }
-            })
-            return scene_instance;
-        });
-
-        this.game_ui_options = Object.assign(data.game_ui_options, {
-            general: {
-                background_color: data.game_ui_options.general.background_color,
-                animation_canvas_dimensions: Object.assign(data.game_ui_options.general.animation_canvas_dimensions, {
-                    height: function () {
-                        const split_ratio = this.ratio.split(":").map(n => parseInt(n));
-                        const factor = split_ratio[1] / split_ratio[0];
-                        return Number((this.width * factor).toFixed());
-                    },
-                }),
-            }
-        });
-    }
-
-    /**
-     * Sets the callback to call when all resources of all scenes are fully loaded.
-     * @param {Function} callback 
-     */
-    on_load_resources(callback) {
-        this.on_load_resources_callback = callback;
-    }
-}
-
-module.exports = MtlGame;
-},{"./scene":19,"./scene-types":18}],16:[function(require,module,exports){
-"use strict";
-
-/**
- * A structure to manage a group of object that extends from the ./Loadable class.
- * For each handled loadable, increments a loadable_elements state, and once the loadable has finished 
- * to load whatever it needs to load, the loaded_elements state is incremented.
- */
-class LoadableGroup {
-    constructor() {
-        this.loadable_elements = 0;
-        this.loaded_elements = 0;
-    }
-
-    /**
-     * Attaches a on_load callback to the Loadable object given as argument.
-     * Increments the loadable_elements state.
-     * @param {Loadable} object Any object that extends the Loadable class
-     */
-    add_loadable(object) {
-        this.loadable_elements++;
-        /**
-         * The callback that will be call by the Loadable object when it will have finished to load what it needs to load.
-         */
-        object.on_load(() => {
-            this.loaded_elements++;
-            if (this.loadable_elements === this.loaded_elements && this.on_load_group_complete_custom_callback) {
-                this.on_load_group_complete_custom_callback();
-            }
-        });
-    }
-
-    /**
-     * Defines a custom callback to call when all handled loadable objects have finished loading.
-     * @param {Function} callback 
-     */
-    on_load_group_complete(callback) {
-        this.on_load_group_complete_custom_callback = callback;
-    }
-}
-
-module.exports = LoadableGroup;
-},{}],17:[function(require,module,exports){
-"use strict";
-
-/**
- * An abstract type that must be inherited by any object that have a resource to load.
- * Can be an image or a sound.
- */
-class Loadable {
-    /**
-     * Initialization of the actual loadable element.
-     * @param {HTMLElement} loadable_element Image() or Audio() for example
-     * @param {String} field_name the name of the field that references the loadable element in the instance.
-     * @param {String} event_name the name of the event that is dispatched by the loadable 
-     *                            html element when it has loaded a resource ("load" or "loadeddata")
-     */
-    constructor(loadable_element, field_name, event_name) {
-        this.loaded = false;
-        this.init_loadable_element(loadable_element, field_name, event_name);
-    }
-
-    /**
-     * Initializes event listeners for the registered loadable_element.
-     * @param {HTMLElement} loadable_element 
-     * @param {String} field_name the name of the field that references the element
-     * @param {String} event_name the event to use for resource loading (images uses onload, audio uses onloadeddata ...)
-     */
-    init_loadable_element(loadable_element, field_name, event_name) {
-        this[field_name] = loadable_element;
-
-        this.load_listener = () => {
-            this.loaded = true;
-            this.on_load_callback();
-            this.clear();
-        };
-
-        this[field_name].addEventListener(event_name, this.load_listener);
-
-        this.clear = () => {
-            this[field_name].removeEventListener(event_name, this.load_listener);
-        }
-    }
-
-    /**
-     * Sets a callback to call when the loadable_element has finished to load its resource.
-     * @param {Function} callback 
-     */
-    on_load(callback) {
-        this.on_load_custom_callback = callback;
-    }
-
-    /**
-     * The callback that's called when the loadable element as finished loading its resource
-     */
-    on_load_callback() {
-        this.loaded = true;
-        this.on_load_custom_callback && this.on_load_custom_callback();
-    }
-}
-
-module.exports = Loadable;
-},{}],18:[function(require,module,exports){
-/**
- * An enum like object to describe the 2 possible type that can have a MtlScene
- */
-
-module.exports = {
-    PLAYABLE: "Playable",
-    CINEMATIC: "Cinematic",
-};
-},{}],19:[function(require,module,exports){
-"use strict";
-
-const SCENE_TYPES = require("./scene-types");
-const MtlAnimation = require("./animation");
-const MtlSoundTrack = require("./sound-track");
-const MtlChoice = require("./choice");
-const MtlGameObject = require("./game-object");
-const LoadableGroup = require("./loadable-group");
-
-/**
- * The type used for the scenes of a MtlGame.
- * Extends the LoadableGroup class because it manages multiple objects of the class Loadable.
- */
-class MtlScene extends LoadableGroup {
-    /**
-     * Initializes the instance with default empty data.
-     */
-    constructor() {
-        super();
-        const default_data = {
-            name: "",
-            _type: SCENE_TYPES.PLAYABLE,
-            animation: new MtlAnimation(),
-            sound_track: new MtlSoundTrack(),
-            choices: [],
-            text_box: "",
-            game_objects: [],
-            cinematic_duration: 0,
-            end_cinematic_options: {
-                destination_scene_index: -1,
-                quit: false,
-            }
-        };
-
-        this.name = default_data.name;
-        this._type = default_data._type;
-        this.animation = default_data.animation;
-        this.sound_track = default_data.sound_track;
-
-        this.choices = default_data.choices;
-        this.text_box = default_data.text_box;
-        this.game_objects = default_data.game_objects;
-
-        this.cinematic_duration = default_data.cinematic_duration || 0;
-        this.end_cinematic_options = default_data.end_cinematic_options;
-    }
-
-    /**
-     * Populates the instance from a litteral descriptor.
-     * Creates and populates the instances of Animation, Soundtrack and GameObjects
-     * @param {Object} data 
-     */
-    load_data(data) {
-        this.name = data.name;
-        this._type = data._type;
-
-        const animation = new MtlAnimation();
-        animation.init(data.animation);
-        this.animation.clear();
-        this.animation = animation;
-        this.animation.image.src && this.add_loadable(this.animation);
-
-        const sound_track = new MtlSoundTrack();
-        sound_track.init(data.sound_track);
-        this.sound_track.clear();
-        this.sound_track = sound_track;
-        this.sound_track.src && this.add_loadable(this.sound_track);
-
-        this.choices = data.choices.map(c => new MtlChoice(c));
-        this.text_box = data.text_box;
-        this.cinematic_duration = data.cinematic_duration || 0;
-
-        this.game_objects = data.game_objects.map(gob => {
-            const inst = new MtlGameObject();
-            inst.load_data(gob);
-            this.add_loadable(inst);
-            return inst;
-        });
-
-        this.end_cinematic_options = data.end_cinematic_options;
-    }
-}
-
-module.exports = MtlScene;
-},{"./animation":12,"./choice":13,"./game-object":14,"./loadable-group":16,"./scene-types":18,"./sound-track":20}],20:[function(require,module,exports){
-"use strict";
-
-const Loadable = require("./loadable");
-
-/**
- * The data type used to represent and manage the soundtrack of a scene
- */
-class MtlSoundTrack extends Loadable {
-    constructor() {
-        super(new Audio(), "audio", "loadeddata");
-        this.name = "";
-        this.initialized = false;
-    }
-
-    /**
-     * Reset the instance to empty values.
-     */
-    reset() {
-        this.init_loadable_element(new Audio(), "audio", "loadeddata");
-        this.name = "";
-        this.initialized = false;
-    }
-
-    /**
-     * Populates the instance from a litteral descriptor
-     * @param {Object} data 
-     */
-    init(data) {
-        this.empty = data.src === "";
-        this.audio.src = data.src;
-        this.name = data.name;
-        this.audio.loop = data._loop;
-        this.initialized = true;
-    }
-
-    /**
-     * Plays the audio resource.
-     */
-    play() {
-        this.audio.play();
-    }
-
-    /**
-     * Stops the audio player.
-     */
-    stop() {
-        this.audio.pause();
-        this.audio.currentTime = 0;
-    }
-
-    /**
-     * Returns the state of the inner audio element
-     * @returns {Boolean}
-     */
-    is_playing() {
-        return !this.audio.paused
-    }
-}
-
-module.exports = MtlSoundTrack;
-},{"./loadable":17}],21:[function(require,module,exports){
-"use strict";
-
-const ChoicesPanelMetrics = require("./choices-panel-metrics");
-
-class CanvasDimensions {
-    constructor(params) {
-        this.params = params;
-
-        // The original dimensions of the image part of the canvas
-        this.image = { width: 0, height: 0 };
-
-        // The scaled dimensions of the rendering canvas
-        this.width = 0;
-        this.height = 0;
-
-        // Scaled dimensions of the inventory zone
-        this.inventory = { width: 0, height: 0 };
-
-        // Scaled dimensions of the choices zone
-        this.choices_panel = { width: 0, height: 0 };
-
-        // The scale factor that will have been calculated
-        this.scale_factor = 1;
-
-        this.init_dimensions();
-    }
-
-    /**
-     * Calculate the dimensions from game settings
-     */
-    init_dimensions() {
-        const { get_game_settings } = this.params;
-
-        const general_settings = get_game_settings("general");
-
-        const image_h = general_settings.animation_canvas_dimensions.height();
-        const image_w = general_settings.animation_canvas_dimensions.width;
-        const inventory_w = this.get_inventory_base_width();
-        const base_w = image_w + inventory_w;
-
-        this.choices_panel_metrics = this.get_choices_panel_metrics();
-
-        const max_playground_width = window.screen.width - 200;
-        const max_playground_height = window.screen.height - 200;
-
-        const base_h = image_h + this.choices_panel_metrics.get_total_height();
-        const playground_ratio = base_w / base_h;
-
-        const screen_ratio = window.screen.width / window.screen.height;
-
-        // Calculate the width of the canvas to the maximum possible scale
-        if (screen_ratio > playground_ratio) {
-            // Screen is more panoramic than game canvas so we scale canvas to the maximum height
-            const scaled_w = max_playground_height * playground_ratio
-            this.width = scaled_w <= max_playground_width ? scaled_w : max_playground_width;
-        } else {
-            // Image is more panoramic so its scaled to the maximum width
-            const scaled_h = max_playground_width * (base_h / base_w)
-            this.height = scaled_h <= max_playground_height ? scaled_h : max_playground_height;
-            this.width = this.height * playground_ratio;
-        }
-
-        this.scale_factor = this.width / base_w;
-
-        this.choices_panel_metrics = this.choices_panel_metrics.to_scaled(this.scale_factor);
-
-        this.image = {
-            width: image_w * this.scale_factor,
-            height: image_h * this.scale_factor,
-        };
-
-        this.inventory = {
-            width: inventory_w * this.scale_factor,
-            height: this.image.height,
-        };
-
-        this.choices_panel = {
-            width: this.width,
-            height: this.choices_panel_metrics.get_total_height(),
-        };
-
-        this.height = this.image.height + this.choices_panel.height;
-    }
-
-    /**
-     * The width of the inventory panel
-     * @returns {Integer}
-     */
-    get_inventory_base_width() {
-        const { get_game_settings } = this.params;
-        const image_h = get_game_settings("general").animation_canvas_dimensions.height();
-        const inventory_style = get_game_settings("inventory");
-        const gap = inventory_style.gap;
-        const h = image_h - (2 * inventory_style.padding);
-        const gap_h = (inventory_style.rows - 1) * gap;
-        const gap_w = (inventory_style.columns - 1) * gap
-        const slot_side = (h - gap_h) / inventory_style.rows;
-        return (inventory_style.columns * slot_side)
-            + (2 * inventory_style.padding)
-            + gap_w;
-    }
-
-    /**
-     * The metrics in pixels for each row of choices in the choices panel
-     * @returns {ChoicesPanelMetrics}
-     */
-    get_choices_panel_metrics() {
-        const { get_game_settings, get_game_scenes } = this.params;
-        const choices_panel_settings = get_game_settings("choices_panel");
-        const general_settings = get_game_settings("general");
-
-        const container_padding = choices_panel_settings.container_padding;
-        const container_width = general_settings.animation_canvas_dimensions.width
-            + this.get_inventory_base_width()
-            - (2 * container_padding);
-
-        const scenes_choices = get_game_scenes().map(s => { return { choices: s.choices } });
-
-        return new ChoicesPanelMetrics({
-            settings: choices_panel_settings,
-            container_width,
-            scenes_choices,
-        });
-    }
-}
-
-module.exports = CanvasDimensions;
-},{"./choices-panel-metrics":22}],22:[function(require,module,exports){
-"use strict";
-
-const { get_canvas_char_size } = require("../lib/font-tools");
-
-/**
- * A class to facilitate the handling of the chocies panel outer and inner dimensions
- */
-class ChoicesPanelMetrics {
-    constructor(params) {
-        this.params = params;
-        const { scale_factor = 1 } = this.params;
-        this.container_width = this.params.container_width * scale_factor;
-        this.settings = this.get_scaled_settings(scale_factor);
-        this.scenes_formatted_choices = this.get_scenes_formatted_choices();
-        this.container_padding_height = 2 * this.settings.container_padding;
-        this.rows = this.get_rows_metrics();
-    }
-
-    /**
-     * @returns {object} the choices panel settings object given in the instance parameters 
-     * scaled by the scale_factor parameter
-     */
-    get_scaled_settings(scale_factor) {
-        const { settings } = this.params;
-        return Object.assign({ ...settings }, {
-            font_size: settings.font_size * scale_factor,
-            active_choice_border_width: settings.active_choice_border_width * scale_factor,
-            active_choice_rounded_corners_radius: settings.active_choice_rounded_corners_radius * scale_factor,
-            choice_padding: settings.choice_padding * scale_factor,
-            container_padding: settings.container_padding * scale_factor,
-        });
-    }
-
-    /**
-     * @param {Number} row_nb Optional - 1 or 2 if we don't want to get the number of choice rows at its max
-     * @param {Object[]} scene_choices Optional - The choices that we want to parse, default is all choices.
-     * @returns {Object[]} An array of 2 objects like {text_height, padding_height} for each choices row
-     */
-    get_rows_metrics(row_nb, scene_choices) { // params will be default if undefined
-        const { settings } = this;
-        const max_lines_per_row = this.get_choices_max_lines_per_row(row_nb, scene_choices);
-
-        const char_size = get_canvas_char_size(window.mentalo_drawing_context, settings);
-        const text_line_h = char_size.text_line_height;
-        const interline_h = char_size.interline_height;
-
-        const choice_padding_height = 2 * settings.choice_padding;
-
-        const height_text_row_1 = max_lines_per_row[0] * text_line_h - interline_h;
-        const height_text_row_2 = max_lines_per_row[1] > 0 // If some scene have more than 2 choices, there will be displayed on 2 rows.
-            ? max_lines_per_row[1] * text_line_h - interline_h
-            : 0;
-
-        return [
-            {
-                text_height: height_text_row_1,
-                padding_height: choice_padding_height
-            },
-            {
-                text_height: height_text_row_2,
-                padding_height: height_text_row_2 > 0 ? choice_padding_height : 0
-            }
-        ];
-    }
-
-    /** 
-     * The maximum number of text lines that each row of choice can have
-     * @returns {Integer[]} An array with 2 items. 1st is for the upper choices row, second is for the lower one.
-     */
-    get_choices_max_lines_per_row(row_nb, scenes_choices) {
-        const choices_max_row_nb = row_nb || this.get_max_choices_row_per_scene();
-        const formatted_choices = scenes_choices || this.scenes_formatted_choices;
-
-        // get the largest number of text lines per choice row
-        const max_lines_per_row = [0, 0];
-        Array.from({ length: choices_max_row_nb }).forEach((_row, i) => {
-            const slice_index = i === 0 ? [0, 2] : [2, 4];
-            const largest = formatted_choices
-                .map(s_choices => Math.max(...s_choices
-                    .slice(...slice_index)
-                    .map(c => c.text_lines.length)
-                ))
-                .reduce((res, nb) => Math.max(res, nb), 0);
-            max_lines_per_row[i] = largest;
-        });
-
-        return max_lines_per_row;
-    }
-
-    /**
-     * @returns {Integer} The maximum number of rows that the choices_panel can have in the loaded game.
-     */
-    get_max_choices_row_per_scene(scenes_slice_index) {
-        const scenes_choices = scenes_slice_index
-            ? this.params.scenes_choices.slice(scenes_slice_index, scenes_slice_index + 1)
-            : this.params.scenes_choices;
-
-        let choices_max_row_nb = 1;
-        if (Math.max(...scenes_choices.map(s => s.choices.length)) > 2) {
-            choices_max_row_nb = 2
-        }
-
-        return choices_max_row_nb;
-    }
-
-    /**
-     * Parses the raw text of each scene choices and returns them with an additional 
-     * text_lines field with the text split into lines ready to be rendered in canvas.
-     * @typedef FormattedChoice
-     * @property {String[]} text_lines
-     * @property {...MtlChoice} - the other MtlChoice fields
-     * 
-     * @returns {FormattedChoice[]}
-     */
-    get_scenes_formatted_choices() {
-        const { scenes_choices } = this.params;
-        const { settings, container_width } = this;
-
-        const char_size = get_canvas_char_size(window.mentalo_drawing_context, settings);
-
-        const { choice_padding } = settings;
-        const error_offset = .9;
-        const choice_max_width = ((container_width / 2) - (2 * choice_padding)) * error_offset;
-        const max_chars_per_row = choice_max_width / char_size.width;
-
-        return scenes_choices.map(s => s.choices.map(c => {
-            const words = c.text.split(" ");
-            const lines = [""];
-            let line_i = 0;
-
-            words.forEach(w => {
-                if ((lines[line_i] + w).length >= max_chars_per_row) {
-                    line_i++;
-                    lines.push("");
-                }
-                lines[line_i] = `${lines[line_i]}${lines[line_i] === "" ? "" : " "}${w}`;
-            });
-            return Object.assign({ ...c }, { text_lines: lines });
-        }));
-    }
-
-    /**
-     * The total calculated height of the choices panel
-     * @returns {Number}
-     */
-    get_total_height(rows) {
-        rows = rows || this.rows;
-        return rows.reduce(
-            (total, row) => total + row.text_height + row.padding_height, 0
-        ) + this.container_padding_height;
-    }
-
-    /**
-     * A copy of this instance with values scaled by the given factor
-     * @param {Number} scale_factor the scaling factor
-     * @returns {ChoicesPanelMetrics}
-     */
-    to_scaled(scale_factor) {
-        return new ChoicesPanelMetrics({ ...this.params, scale_factor });
-    }
-
-    /**
-     * The choices panel bounding zone has first been calculated to the maximum, 
-     * but certain scenes may need less space to display their choices.
-     * This return the only necessary height for a given scene
-     * @param {MtlChoice[]} scene_choices 
-     * @returns {Number} A bounding box {top, left, right, bottom, width, height}
-     */
-    get_minimum_panel_height_for_scene(scene_index) {
-        const scene_choices = this.scenes_formatted_choices.slice(scene_index, scene_index + 1);
-        const choices_row_nb = scene_choices[0].length > 2 ? 2 : 1;
-        const rows = this.get_rows_metrics(choices_row_nb, scene_choices);
-        return this.get_total_height(rows);
-    }
-
-    /**
-     * Get the metrics for a given row of choices
-     * @param {Integer} scene_index 
-     * @param {Number} scale_fator 
-     */
-    get_one_choices_row_metrics(scene_index, row_index) {
-        const scene_choices = this.scenes_formatted_choices.slice(scene_index, scene_index + 1);
-        const rows = this.get_rows_metrics(row_index + 1, scene_choices);
-        const row = rows[row_index];
-        return Object.assign(row, {
-            text_height: row.text_height,
-            padding_height: row.padding_height,
-        });
-    }
-}
-
-module.exports = ChoicesPanelMetrics;
-},{"../lib/font-tools":7}],23:[function(require,module,exports){
-"use strict";
-
-const FrameRateController = require("../lib/frame-rate-controller");
-const { get_optimal_visible_foreground_color } = require("../lib/color-tools");
-const { get_canvas_char_size } = require("../lib/font-tools");
-const SCENE_TYPES = require("../model/scene-types");
-const ChoiceCpt = require("./ui-components/choice-cpt");
-const ChoicesPanelCpt = require("./ui-components/choices-panel-cpt");
-const ClosingIconCpt = require("./ui-components/closing-icon-cpt");
-const GameObjectCpt = require("./ui-components/game-object-cpt");
-const InventoryCpt = require("./ui-components/inventory-cpt");
-const InventoryObjectCpt = require("./ui-components/inventory-object-cpt");
-const InventorySlotCpt = require("./ui-components/inventory-slot-cpt");
-const SceneAnimationCpt = require("./ui-components/scene-animation-cpt");
-const TextBoxCpt = require("./ui-components/text-box-cpt");
-const UserErrorPopup = require("./ui-components/user-error-popup");
-const CanvasDimensions = require("./canvas-dimensions");
-
-/**
- * The class that handles all interactions with the canvas 2D drawing context to draw the game.
- */
-class MtlRender {
-    constructor(params) {
-        this.params = params;
-        const frame_rate = this.params.frame_rate || 30;
-        this.fps_controller = new FrameRateController(frame_rate);
-
-        this.params.container.style.backgroundColor = this.params.get_game_settings("general").background_color;
-        this.canvas = document.createElement("canvas");
-        this.canvas.style.backgroundColor = "black";
-
-        window.mentalo_drawing_context = this.canvas.getContext("2d");
-
-        this.canvas_dimensions = {};
-        this.canvas_zones = {};
-        this.event_listeners = {
-            game_objects: [],
-            inventory: [],
-            text_box: [],
-        };
-
-        this.state = {
-            user_error_popup: { is_set: false, text: "", on_close: function () { } }
-        };
-
-        this.loading_frame = 0;
-    }
-
-    /**
-     * Initializes the rendering of the game, creates the components, the canvas zones and requests full screen.
-     */
-    init() {
-        if (this.params.fullscreen) {
-            this.set_full_screen();
-        }
-
-        this.set_canvas_dimensions();
-        this.create_canvas_zones();
-
-        this.params.container.appendChild(this.canvas);
-
-        const ctx = window.mentalo_drawing_context;
-        // Obsolete properties for browsers compatibility
-        ctx.mozImageSmoothingEnabled = false;
-        ctx.webkitImageSmoothingEnabled = false;
-        ctx.msImageSmoothingEnabled = false;
-        ctx.imageSmoothingEnabled = false;
-
-        this.create_components();
-    }
-
-    /**
-     * Removes the event listeners attached to the components.
-     */
-    clear_event_listeners() {
-        Object.values(this.components).forEach(cpt => {
-            cpt.clear_event_listeners();
-        });
-    }
-
-    /**
-     * Removes the elements rendered as children of the rendering components 
-     * (Example a TextBoxCpt is a child the scene_animation component)
-     * @param {Object} options can defined a set of constructor names to exclude from clean up
-     */
-    clear_children(options = { exclude: [] }) {
-        Object.values(this.components).forEach(cpt => {
-            cpt.clear_children(options);
-        });
-    }
-
-    /**
-     * Reset the scene text box visibility state to true.
-     * The text box visibility state is controlled by the scene_animation component and not directly by the text box component.
-     */
-    reset_text_box_visibility() {
-        this.components.scene_animation.set_text_box_visibility(true)
-    }
-
-    /**
-     * Tries to enable the window fullscreen mode.
-     */
-    set_full_screen() {
-        const body = document.body;
-        body.requestFullScreen = body.requestFullScreen
-            || body.webkitRequestFullScreen
-            || body.msRequestFullscreen
-            || body.mozRequestFullScreen;
-        try {
-            body.requestFullScreen();
-        } catch (err) {
-            console.error(err.message)
-        }
-    }
-
-    /**
-     * Exits the window fullscreen mode
-     */
-    exit_fullscreen() {
-        if (document.fullscreenElement) {
-            document.exitFullscreen();
-        }
-    }
-
-    /**
-     * Updates the error popup state with new values given as argument.
-     * @param {Object} params Must have a text<String> entry and a on_close<Function> entry.
-     */
-    set_user_error_popup(params) {
-        this.state.user_error_popup = {
-            is_set: true,
-            text: params.text,
-            stream_text: !!params.stream_text,
-            on_close: function () {
-                params.on_close && params.on_close();
-            },
-        };
-        this.clear_event_listeners();
-        // Clears children excluding everything but errorpopup
-        this.clear_children({ exclude: ["TextBoxCpt", "InventoryCpt", "ChoicesPanelCpt", "GameObjectCpt"] });
-    }
-
-    /**
-     * Unsets error popup
-     */
-    clear_user_error_popup() {
-        this.reset_user_error_popup();
-        this.clear_event_listeners();
-        this.clear_children({ exclude: ["TextBoxCpt", "InventoryCpt", "ChoicesPanelCpt", "GameObjectCpt"] });
-    }
-
-    /**
-     * Sets error popup to empty values
-     */
-    reset_user_error_popup() {
-        this.state.user_error_popup = {
-            is_set: false,
-            text: "",
-            on_close: function () { }
-        };
-    }
-
-    /**
-     * Callback called when error popup is closed
-     */
-    on_close_user_error_popup() {
-        this.state.user_error_popup.on_close();
-        this.clear_user_error_popup();
-    }
-
-    /**
-     * Parses game data and calculates optimal dimensions for the canvas.
-     */
-    set_canvas_dimensions() {
-        const { get_game_settings, get_game_scenes } = this.params;
-
-        this.canvas_dimensions = new CanvasDimensions({
-            get_game_settings,
-            get_game_scenes,
-        });
-
-        this.canvas.width = this.canvas_dimensions.width;
-        this.canvas.height = this.canvas_dimensions.height;
-    }
-
-    /**
-     * @returns {Object} The choices_panel settings scaled to display canvas size
-     */
-    get_scaled_choices_panel_settings() {
-        return this.canvas_dimensions.choices_panel_metrics.settings;
-    }
-
-    /**
-     * @returns {Object} The inventory settings scaled to display canvas size
-     */
-    get_scaled_inventory_settings() {
-        const { scale_factor } = this.canvas_dimensions;
-        const { get_game_settings } = this.params;
-
-        const base_settings = get_game_settings("inventory");
-
-        return Object.assign({ ...base_settings }, {
-            slot_rounded_corner_radius: base_settings.slot_rounded_corner_radius * scale_factor,
-            slot_border_width: base_settings.slot_border_width * scale_factor,
-            gap: base_settings.gap * scale_factor,
-            padding: base_settings.padding * scale_factor,
-        });
-    }
-
-    /**
-     * @returns {Object} The text boxes settings scaled to display canvas size
-     */
-    get_scaled_text_boxes_settings() {
-        const { scale_factor } = this.canvas_dimensions;
-        const { get_game_settings } = this.params;
-        const text_box_settings = get_game_settings("text_boxes");
-
-        return Object.assign({ ...text_box_settings }, {
-            font_size: text_box_settings.font_size * scale_factor,
-            padding: text_box_settings.padding * scale_factor,
-            margin: text_box_settings.margin * scale_factor,
-            rounded_corners_radius: text_box_settings.rounded_corners_radius * scale_factor,
-            border_width: text_box_settings.border_width * scale_factor,
-        })
-    }
-
-    /**
-     * Precalculates the bounding boxes for each canvas zone.
-     * set_canvas_dimensions() must have been called before this.
-     */
-    create_canvas_zones() {
-        const metrics = this.canvas_dimensions;
-        const inventory_settings = this.get_scaled_inventory_settings();
-        const choices_panel_settings = this.get_scaled_choices_panel_settings();
-
-        this.canvas_zones = {
-            root: {
-                left: 0,
-                top: 0,
-                right: metrics.width,
-                bottom: metrics.height,
-                width: metrics.width,
-                height: metrics.height,
-                padding: 0,
-            },
-            scene_animation: {
-                left: 0,
-                top: 0,
-                right: metrics.image.width,
-                bottom: metrics.image.height,
-                width: metrics.image.width,
-                height: metrics.image.height,
-                clear_color: "black",
-                padding: 0,
-            },
-            inventory: {
-                left: metrics.image.width,
-                top: 0,
-                right: metrics.width,
-                bottom: metrics.inventory.height,
-                width: metrics.width - metrics.image.width,
-                height: metrics.inventory.height,
-                clear_color: inventory_settings.background_color,
-                padding: inventory_settings.padding,
-            },
-            choices_panel: {
-                left: 0,
-                top: metrics.image.height,
-                right: metrics.width,
-                bottom: metrics.height,
-                width: metrics.width,
-                height: metrics.height - metrics.image.height,
-                clear_color: choices_panel_settings.background_color,
-                padding: choices_panel_settings.container_padding
-            },
-        };
-    }
-
-    /**
-     * @returns {Boolean} true if scene is of type Playable
-     */
-    scene_is_not_cinematic() {
-        return this.params.get_scene()._type === SCENE_TYPES.PLAYABLE;
-    }
-
-    /**
-     * Creates the tree of all the components to render. All component extends the UiComponent class.
-     * The root component instances (Scene animation, Inventory, Choices panel) will only be created once,
-     * but children components are functions of their parents so they are recreated when get_children is called on a parent component.
-     * For example A TextBoxCpt is a children a SceneAnimationCpt, so the text box component will be recreated each time 
-     * scene_animation.get_children() is called. This allow to display those components as functions of dynamical states.
-     */
-    create_components() {
-        const { get_game_settings, get_inventory, get_scene, get_game_scenes, get_scene_index } = this.params;
-        const { scale_factor } = this.canvas_dimensions;
-        const scene_is_not_cinematic = this.scene_is_not_cinematic.bind(this);
-        this.components = {
-            scene_animation: new SceneAnimationCpt({
-                bounding_zone: this.canvas_zones.scene_animation,
-                get_animation: () => get_scene().animation,
-                next_frame_ready: () => this.fps_controller.nextFrameReady(),
-                get_children: () => {
-                    const scene = get_scene();
-                    return scene.game_objects.map(o => {
-                        const parent_zone = this.canvas_zones.scene_animation;
-                        const obj_pos = {
-                            x: (o.position.x * scale_factor) + parent_zone.left,
-                            y: (o.position.y * scale_factor) + parent_zone.top,
-                        };
-
-                        const obj_dim = {
-                            w: o.image.width * scale_factor,
-                            h: o.image.height * scale_factor,
-                        };
-
-                        const obj_bounds = {
-                            left: obj_pos.x,
-                            right: obj_pos.x + obj_dim.w,
-                            top: obj_pos.y,
-                            bottom: obj_pos.y + obj_dim.h,
-                            width: obj_dim.w,
-                            height: obj_dim.h,
-                        };
-
-                        const game_objects_cpt_params = {
-                            bounding_zone: obj_bounds,
-                            position: obj_pos,
-                            dimensions: obj_dim,
-                            image: o.image,
-                            is_in_inventory: () => get_inventory().has(o),
-                        };
-
-                        const obj_cpt = new GameObjectCpt(game_objects_cpt_params);
-
-                        obj_cpt.add_event_listener({
-                            event_type: "mousemove",
-                            listener: e => {
-                                if (!get_inventory().has(o)) {
-                                    const cursor_is_over_obj = e.offsetX >= obj_bounds.left
-                                        && e.offsetX <= obj_bounds.right
-                                        && e.offsetY >= obj_bounds.top
-                                        && e.offsetY <= obj_bounds.bottom;
-                                    obj_cpt.state.draw_border = cursor_is_over_obj;
-                                }
-                            }
-                        });
-
-                        obj_cpt.add_event_listener({
-                            event_type: "click",
-                            listener: e => {
-                                if (!get_inventory().has(o)
-                                    && e.offsetX >= obj_bounds.left
-                                    && e.offsetX <= obj_bounds.right
-                                    && e.offsetY >= obj_bounds.top
-                                    && e.offsetY <= obj_bounds.bottom) {
-                                    this.params.on_game_object_click(o);
-                                    this.clear_event_listeners();
-                                    this.clear_children({ exclude: "TextBoxCpt" });
-                                }
-                            }
-                        });
-
-                        return obj_cpt;
-                    })
-                        .concat(scene.text_box ? [
-                            (() => {
-                                const text_box_settings = this.get_scaled_text_boxes_settings();
-
-                                // calculation of the minimum bound for textbox, without content
-                                const text_box_bounds = (() => {
-                                    const parent_zone = this.canvas_zones.scene_animation;
-                                    const padding = text_box_settings.padding;
-                                    const margin = text_box_settings.margin;
-                                    const left = parent_zone.left + margin;
-                                    const right = parent_zone.right - margin;
-                                    const bottom = parent_zone.bottom - margin;
-                                    const top = bottom - (2 * padding);
-                                    return {
-                                        left,
-                                        right,
-                                        top,
-                                        bottom,
-                                        width: right - left,
-                                        height: bottom - top,
-                                    }
-                                })();
-
-                                const closing_icon_radius = 10 * scale_factor;
-                                const closing_icon_center = {
-                                    x: text_box_bounds.right - (closing_icon_radius / 2),
-                                    y: text_box_bounds.top + (closing_icon_radius / 2),
-                                };
-
-                                const closing_icon_params = {
-                                    color: text_box_settings.font_color,
-                                    radius: closing_icon_radius,
-                                    center: closing_icon_center,
-                                    background_color: text_box_settings.background_color,
-                                    line_width: Math.floor(2 * scale_factor),
-                                    bounding_zone: {
-                                        left: closing_icon_center.x - closing_icon_radius,
-                                        right: closing_icon_center.x + closing_icon_radius,
-                                        top: closing_icon_center.y - closing_icon_radius,
-                                        bottom: closing_icon_center.y + closing_icon_radius,
-                                        width: 2 * closing_icon_radius,
-                                        height: 2 * closing_icon_radius,
-                                    },
-                                };
-
-                                const text_box = new TextBoxCpt({
-                                    text: get_scene().text_box,
-                                    settings: text_box_settings,
-                                    bounding_zone: text_box_bounds,
-                                    get_visibility_state: () => this.components.scene_animation.state.text_box_visible,
-                                    get_children: () => [new ClosingIconCpt(closing_icon_params)],
-                                });
-
-                                const close_text_box_icon = text_box.children[0];
-                                text_box.children[0].add_event_listener({
-                                    event_type: "click",
-                                    listener: e => {
-                                        const bounds = close_text_box_icon.params.bounding_zone;
-                                        const click_over_icon = e.offsetX >= bounds.left
-                                            && e.offsetX <= bounds.right
-                                            && e.offsetY >= bounds.top
-                                            && e.offsetY <= bounds.bottom;
-
-                                        const { scene_animation } = this.components;
-
-                                        if (click_over_icon && scene_animation.state.text_box_visible) {
-                                            scene_animation.set_text_box_visibility(false);
-                                        }
-                                    }
-                                });
-                                return text_box;
-                            })(),
-                        ] : [])
-                        .concat(this.state.user_error_popup.is_set ? [
-                            (() => {
-                                const { text, stream_text } = this.state.user_error_popup;
-                                const use_settings = this.get_scaled_text_boxes_settings();
-                                const parent_bounds = this.canvas_zones.scene_animation
-                                const char_size = get_canvas_char_size(window.mentalo_drawing_context, use_settings);
-
-                                const popup_width = parent_bounds.width / 2;
-                                const padding = char_size.width * 2;
-                                const max_chars_per_row = (popup_width - (2 * padding)) / char_size.width;
-                                const text_lines = [""];
-
-                                let line_i = 0;
-                                text.split(" ").forEach(word => {
-                                    if (`${text_lines[line_i]}${word} `.length > max_chars_per_row) {
-                                        line_i++;
-                                        text_lines.push("");
-                                    }
-                                    text_lines[line_i] += `${word} `;
-                                });
-
-                                const line_h = char_size.text_line_height;
-                                const popup_height = (text_lines.length * line_h) + (2 * padding) - char_size.interline_height;
-
-                                const background_color = use_settings.background_color;
-                                const text_color = use_settings.font_color;
-
-                                const popup_bounds = {
-                                    left: parent_bounds.left + (parent_bounds.width / 2) - (popup_width / 2),
-                                    top: parent_bounds.top + (parent_bounds.height / 2) - (popup_height / 2),
-                                    right: parent_bounds.left + (parent_bounds.width / 2) + (popup_width / 2),
-                                    bottom: parent_bounds.top + (parent_bounds.height / 2) - (popup_height / 2) + popup_height,
-                                    width: popup_width,
-                                    height: popup_height,
-                                    clear_color: background_color,
-                                };
-
-                                const popup_params = {
-                                    settings: use_settings,
-                                    bounding_zone: popup_bounds,
-                                    modal_bounds: this.canvas_zones.scene_animation,
-                                    clear_modal_color: "#0004",
-                                    text_lines,
-                                    padding,
-                                    stream_text,
-                                    get_children: () => {
-                                        const center = {
-                                            x: popup_bounds.right - 5,
-                                            y: popup_bounds.top + 5,
-                                        };
-
-                                        const radius = 15;
-
-                                        const closing_icon_bounds = {
-                                            left: center.x - radius,
-                                            right: center.x + radius,
-                                            top: center.y - radius,
-                                            bottom: center.y + radius,
-                                            width: radius * 2,
-                                            height: radius * 2,
-                                        };
-
-                                        const closing_icon_params = {
-                                            color: text_color,
-                                            radius,
-                                            center,
-                                            background_color,
-                                            line_width: 2,
-                                            bounding_zone: closing_icon_bounds,
-                                        };
-
-                                        const closing_icon = new ClosingIconCpt(closing_icon_params);
-
-                                        closing_icon.add_event_listener({
-                                            event_type: "click",
-                                            listener: e => {
-                                                if (e.offsetX >= closing_icon_bounds.left
-                                                    && e.offsetX <= closing_icon_bounds.right
-                                                    && e.offsetY >= closing_icon_bounds.top
-                                                    && e.offsetY <= closing_icon_bounds.bottom) {
-                                                    this.on_close_user_error_popup();
-                                                }
-                                            }
-                                        });
-
-                                        return [closing_icon];
-                                    }
-                                };
-                                const popup = new UserErrorPopup(popup_params);
-                                return popup;
-                            })()
-                        ] : []);
-                },
-            }),
-            inventory: new InventoryCpt({
-                bounding_zone: this.canvas_zones.inventory,
-                get_inventory,
-                is_visible: scene_is_not_cinematic,
-                invisible_clear_color: get_game_settings("general").background_color,
-                get_children: () => {
-                    const settings = this.get_scaled_inventory_settings();
-
-                    const slots_zone = {
-                        left: this.canvas_zones.inventory.left + settings.padding,
-                        top: this.canvas_zones.inventory.top + settings.padding,
-                        right: this.canvas_zones.inventory.right - settings.padding,
-                        bottom: this.canvas_zones.inventory.bottom - settings.padding,
-                        width: this.canvas_zones.inventory.width - (2 * settings.padding),
-                        height: this.canvas_zones.inventory.height - (2 * settings.padding)
-                    };
-
-                    const gap = settings.gap;
-                    const h = slots_zone.height;
-                    const gap_h = (settings.rows - 1) * gap;
-                    const slot_side = (h - gap_h) / settings.rows;
-                    const slots_total_w = (slot_side * settings.columns) + (gap * (settings.columns - 1));
-                    const center_slot_x_offset = (slots_zone.width - (slots_total_w)) / 2;
-
-                    const slots = [];
-                    let slot_i = 0;
-                    Array.from({ length: settings.rows }).forEach((_row, i) => {
-                        const top = slots_zone.top + (i * (slot_side + gap));
-                        Array.from({ length: settings.columns }).forEach((_col, j) => {
-                            const left = slots_zone.left + (j * (slot_side + gap)) + center_slot_x_offset;
-
-                            const slot_bounds = {
-                                left,
-                                top,
-                                right: left + slot_side,
-                                bottom: top + slot_side,
-                                width: slot_side,
-                                height: slot_side
-                            };
-
-                            const stroke_color = get_optimal_visible_foreground_color(settings.background_color);
-
-                            const slot_params = {
-                                bounding_zone: slot_bounds,
-                                stroke_color,
-                                is_visible: scene_is_not_cinematic,
-                                settings,
-                                get_children: () => {
-                                    const inventory_object = new InventoryObjectCpt({
-                                        slot_index: slot_i,
-                                        is_visible: scene_is_not_cinematic,
-                                        settings,
-                                        stroke_color,
-                                        get_game_object: index => {
-                                            const obj = Array.from(get_inventory())[index];
-                                            if (obj) {
-                                                const obj_dim = obj.get_dimensions();
-                                                const obj_ratio = obj_dim.width / obj_dim.height;
-                                                const scaled_dim = { width: 0, height: 0 };
-                                                const pos = { x: 0, y: 0 };
-                                                if (obj_ratio > 1) {
-                                                    // object image landscape oriented
-                                                    scaled_dim.width = slot_side;
-                                                    scaled_dim.height = slot_side * (obj_dim.height / obj_dim.width);
-                                                    pos.x = slot_bounds.left;
-                                                    pos.y = slot_bounds.top + ((slot_side / 2) - (scaled_dim.height / 2));
-                                                } else if (obj_ratio < 1) {
-                                                    // portrait oriented
-                                                    scaled_dim.height = slot_side;
-                                                    scaled_dim.width = slot_side * obj_ratio;
-                                                    pos.y = slot_bounds.top;
-                                                    pos.x = slot_bounds.left + ((slot_side / 2) - (scaled_dim.width / 2));
-                                                } else {
-                                                    // square
-                                                    scaled_dim.width = slot_side;
-                                                    scaled_dim.height = slot_side;
-                                                    pos.x = slot_bounds.left;
-                                                    pos.y = slot_bounds.top;
-                                                }
-                                                return {
-                                                    ref: obj,
-                                                    position: pos,
-                                                    dimensions: scaled_dim,
-                                                };
-                                            } else {
-                                                return undefined;
-                                            }
-                                        },
-                                        bounding_zone: slot_bounds,
-                                        get_children: () => [
-                                            new ClosingIconCpt({
-                                                is_visible: scene_is_not_cinematic,
-                                                color: "#bf5e43", // Some kind of red
-                                                center: {
-                                                    x: slot_bounds.left + (slot_side / 2),
-                                                    y: slot_bounds.top + (slot_side / 2)
-                                                },
-                                                radius: slot_side / 4,
-                                                line_width: slot_side / 20 > 2 ? slot_side / 20 : 2,
-                                                bounding_zone: { ...slot_bounds, clear_color: "rgba(0,0,0,0.2)" },
-                                            }),
-                                        ]
-                                    });
-
-                                    inventory_object.add_event_listener({
-                                        event_type: "mousemove",
-                                        listener: e => {
-                                            const obj = inventory_object.params.get_game_object(inventory_object.params.slot_index);
-                                            const bounds = inventory_object.params.bounding_zone;
-
-                                            inventory_object.state.draw_inventory_close_icon = inventory_object.params.is_visible()
-                                                && !!obj
-                                                && get_scene().game_objects.includes(obj.ref)
-                                                && (
-                                                    e.offsetX >= bounds.left
-                                                    && e.offsetX <= bounds.right
-                                                    && e.offsetY >= bounds.top
-                                                    && e.offsetY <= bounds.bottom
-                                                );
-                                        }
-                                    });
-
-                                    inventory_object.children[0].add_event_listener({
-                                        event_type: "click",
-                                        listener: e => {
-                                            if (!inventory_object.params.is_visible()) return false;
-                                            const obj = inventory_object.params.get_game_object(inventory_object.params.slot_index);
-                                            const bounds = inventory_object.params.bounding_zone;
-                                            const mouse_over_slot = e.offsetX >= bounds.left
-                                                && e.offsetX <= bounds.right
-                                                && e.offsetY >= bounds.top
-                                                && e.offsetY <= bounds.bottom;
-                                            if (!!obj && mouse_over_slot && get_scene().game_objects.includes(obj.ref)) {
-                                                this.params.on_drop_inventory_object(obj.ref);
-                                                this.clear_event_listeners();
-                                                this.clear_children({ exclude: "TextBoxCpt" });
-                                            }
-                                        }
-                                    });
-
-                                    return [inventory_object];
-                                },
-                            };
-
-                            const slot = new InventorySlotCpt(slot_params);
-                            slots.push(slot);
-                            slot_i++;
-                        });
-                    });
-                    return slots;
-                },
-            }),
-            choices_panel: new ChoicesPanelCpt({
-                bounding_zone: this.canvas_zones.choices_panel,
-                minimum_bounding_zone: () => {
-                    const initial_zone = this.canvas_zones.choices_panel;
-                    const minium_height = this.canvas_dimensions.choices_panel_metrics
-                        .get_minimum_panel_height_for_scene(
-                            get_scene_index()
-                        );
-                    const maximum_height = this.canvas_dimensions.choices_panel_metrics
-                        .get_total_height();
-                    const dif = maximum_height - minium_height;
-
-                    return Object.assign({ ...initial_zone }, {
-                        bottom: initial_zone.bottom - dif,
-                        height: initial_zone.height - dif,
-                    });
-                },
-                is_visible: scene_is_not_cinematic,
-                invisible_clear_color: get_game_settings("general").background_color,
-                get_children: () => Array.from({
-                    length: this.canvas_dimensions.choices_panel_metrics
-                        .get_max_choices_row_per_scene(get_scene_index()) * 2
-                }
-                ).map((_, i) => {
-                    const choices_settings = this.get_scaled_choices_panel_settings();
-                    const writable_parent_zone = { // The writable zone of the choices panel. = The choices_panel bounding box excluding the padding.
-                        left: this.canvas_zones.choices_panel.left + choices_settings.container_padding,
-                        right: this.canvas_zones.choices_panel.right - choices_settings.container_padding,
-                        top: this.canvas_zones.choices_panel.top + choices_settings.container_padding,
-                        bottom: this.canvas_zones.choices_panel.bottom - choices_settings.container_padding,
-                        width: 0,
-                        height: 0,
-                    };
-
-                    writable_parent_zone.width = writable_parent_zone.right - writable_parent_zone.left;
-                    writable_parent_zone.height = writable_parent_zone.bottom - writable_parent_zone.top;
-
-                    const choice_width = writable_parent_zone.width / 2;
-
-                    const { choices_panel_metrics } = this.canvas_dimensions;
-                    const choices_height_per_row = choices_panel_metrics.rows.map(row => row.text_height + row.padding_height);
-
-                    const choice_bounds = {
-                        left: writable_parent_zone.left + ((i % 2) * choice_width), // if i % 2 choice is on the right side of the panel
-                        top: writable_parent_zone.top + ((i > 1 ? 1 : 0) * choices_height_per_row[0]), // If index is > 1, choice is on the lower row
-                        right: writable_parent_zone.left + ((i % 2) * choice_width) + choice_width,
-                        bottom: writable_parent_zone.top + ((i > 1 ? 1 : 0) * choices_height_per_row[1]) + choices_height_per_row[i > 1 ? 1 : 0],
-                        width: choice_width,
-                        height: choices_height_per_row[i > 1 ? 1 : 0],
-                    };
-
-                    const choice_cpt = new ChoiceCpt({
-                        is_visible: scene_is_not_cinematic,
-                        minimum_bounding_zone: () => {
-                            if (!choice_cpt.minimum_bounding_zone) {
-                                const row_index = i > 1 ? 1 : 0;
-                                const metric = this.canvas_dimensions.choices_panel_metrics.get_one_choices_row_metrics(
-                                    get_scene_index(), row_index,
-                                );
-
-                                const min_height = metric.text_height + metric.padding_height;
-
-                                const dif_upper_row = (() => {
-                                    const choices_cpt = this.components.choices_panel.children;
-                                    const cpt_index = choices_cpt.indexOf(choice_cpt);
-                                    return cpt_index >= 2
-                                        ? choice_bounds.top - choices_cpt[0].params.minimum_bounding_zone().bottom
-                                        : 0;
-                                })();
-
-                                choice_cpt.minimum_bounding_zone = Object.assign({ ...choice_bounds }, {
-                                    height: min_height,
-                                    top: choice_bounds.top - dif_upper_row,
-                                    bottom: choice_bounds.top - dif_upper_row + min_height,
-                                });
-                            }
-                            return choice_cpt.minimum_bounding_zone;
-                        },
-                        get_formatted_choice: () => {
-                            const scene_choices = this.canvas_dimensions.choices_panel_metrics.scenes_formatted_choices[
-                                get_game_scenes().indexOf(get_scene())
-                            ];
-                            return scene_choices.length - 1 >= i ? scene_choices[i] : undefined;
-                        },
-                        settings: choices_settings,
-                    });
-
-                    choice_cpt.add_event_listener({
-                        event_type: "mousemove",
-                        listener: e => {
-                            choice_cpt.state.active =
-                                choice_cpt.params.is_visible()
-                                && !!choice_cpt.params.get_formatted_choice()
-                                && choice_cpt.is_hover(e);
-                        }
-                    });
-
-                    choice_cpt.add_event_listener({
-                        event_type: "click",
-                        listener: () => {
-                            if (choice_cpt.state.active) {
-                                const choice = choice_cpt.params.get_formatted_choice();
-                                const success = this.params.on_choice_click(choice);
-                                if (success) {
-                                    this.clear_event_listeners();
-                                    this.clear_children();
-                                }
-                            }
-                        }
-                    });
-
-                    return choice_cpt;
-                })
-            }),
-        };
-    }
-
-    /**
-     * Draw a loading state on the black screen while some game resources are loading.
-     * This shouldn't show up a lot except if some resources are really big...
-     */
-    draw_loading() {
-        const ctx = window.mentalo_drawing_context;
-        ctx.save();
-        ctx.font = '25px monospace';
-        ctx.fillStyle = "black";
-        ctx.fillRect(0, 0, window.innerWidth, window.innerHeight);
-        ctx.fillStyle = "white";
-        ctx.fillText("Loading", 50, window.innerHeight / 2);
-        const dots = Array.from({ length: ++this.loading_frame }).map(() => '.').join('');
-        ctx.font = '8px monospace';
-        ctx.fillText(dots, 50, window.innerHeight / 2 + 20);
-        ctx.restore();
-    }
-
-    /**
-     * Executes the draw method of each registered component.
-     */
-    draw_game() {
-        Object.values(this.components).forEach(cpt => cpt.draw());
-    }
-}
-
-module.exports = MtlRender;
-},{"../lib/color-tools":6,"../lib/font-tools":7,"../lib/frame-rate-controller":8,"../model/scene-types":18,"./canvas-dimensions":21,"./ui-components/choice-cpt":24,"./ui-components/choices-panel-cpt":25,"./ui-components/closing-icon-cpt":26,"./ui-components/game-object-cpt":27,"./ui-components/inventory-cpt":28,"./ui-components/inventory-object-cpt":29,"./ui-components/inventory-slot-cpt":30,"./ui-components/scene-animation-cpt":31,"./ui-components/text-box-cpt":32,"./ui-components/user-error-popup":34}],24:[function(require,module,exports){
-"use strict";
-
-const { draw_text_in_bounds } = require("../../lib/text-tools");
-const { draw_rect } = require("../../lib/shape-tools");
-const MtlUiComponent = require("./ui-component");
-
-/**
- * The rendering component for a scene choice.
- */
-class ChoiceCpt extends MtlUiComponent {
-    constructor(params) {
-        super(params);
-        this.set_str_id("ChoiceCpt")
-    }
-
-    /**
-     * Draw the choice on the registered 2D drawing context
-     */
-    draw() {
-        super.draw();
-        const {
-            get_formatted_choice,
-            settings,
-        } = this.params;
-
-        const bounding_zone = this.params.minimum_bounding_zone();
-
-        const writable_zone = {
-            left: bounding_zone.left + settings.choice_padding,
-            right: bounding_zone.right - settings.choice_padding,
-            top: bounding_zone.top + settings.choice_padding,
-            bottom: bounding_zone.bottom - settings.choice_padding,
-            width: bounding_zone.width - (2 * settings.choice_padding),
-            height: bounding_zone.height - (2 * settings.choice_padding),
-        };
-
-        const choice = get_formatted_choice();
-
-        const ctx = window.mentalo_drawing_context;
-
-        if (choice) {
-            if (this.state.active) {
-                const { left, top, width, height } = bounding_zone;
-
-                draw_rect(ctx, left, top, width, height, {
-                    fill_color: settings.active_choice_background_color,
-                    rounded_corners_radius: settings.active_choice_rounded_corners_radius,
-                    border: {
-                        width: settings.active_choice_border_width,
-                        color: settings.font_color,
-                    }
-                });
-            }
-
-            draw_text_in_bounds(ctx, choice.text_lines, writable_zone, settings, {}, false);
-        }
-    }
-
-    /**
-     * @param {Event} e 
-     * @returns {Boolean} true if the mouse event is inside the choice bounding box
-     */
-    is_hover(e) {
-        const bounds = this.params.minimum_bounding_zone();
-        return e.offsetX >= bounds.left
-            && e.offsetX <= bounds.right
-            && e.offsetY >= bounds.top
-            && e.offsetY <= bounds.bottom;
-    }
-}
-
-module.exports = ChoiceCpt;
-},{"../../lib/shape-tools":9,"../../lib/text-tools":10,"./ui-component":33}],25:[function(require,module,exports){
-"use strict";
-
-const MtlUiComponent = require("./ui-component");
-
-/**
- * The rendering component for the game choices panel
- */
-class ChoicesPanelCpt extends MtlUiComponent {
-    constructor(params) {
-        super(params);
-        this.set_str_id("ChoicesPanelCpt")
-    }
-
-    /**
-     * Draw the choices panel and the choices as children components on the registered 2D drawing context
-     */
-    draw() {
-        super.draw();
-        if (this.params.is_visible()) {
-            this.clear_bounding_zone({ clear_color: this.params.invisible_clear_color });
-            this.clear_bounding_zone({ use_bounding_zone: this.params.minimum_bounding_zone() })
-            this.draw_children();
-        } else {
-            this.clear_bounding_zone({ clear_color: this.params.invisible_clear_color })
-        }
-    }
-}
-
-module.exports = ChoicesPanelCpt;
-},{"./ui-component":33}],26:[function(require,module,exports){
-"use strict";
-
-const MtlUiComponent = require("./ui-component");
-
-/**
- * A rendering component that displays a closing icon (a cross inside a circle)
- */
-class ClosingIconCpt extends MtlUiComponent {
-    constructor(params) {
-        super(params);
-        this.set_str_id("ClosingIconCpt")
-    }
-
-    /**
-     * Uses standard vectorial drawing methods to draw a circle and a cross at 
-     * given coordinates on the registered 2D drawing context.
-     */
-    draw() {
-        super.draw();
-        this.clear_bounding_zone();
-        const { color, center, radius = 5, line_width = 2, background_color = "rgba(0,0,0,0)" } = this.params;
-        const ctx = window.mentalo_drawing_context;
-        const padding = radius / 1.5;
-
-        const cross_coords = {
-            left: center.x - radius + padding,
-            right: center.x + radius - padding,
-            top: center.y - radius + padding,
-            bottom: center.y + radius - padding,
-        };
-
-        ctx.fillStyle = background_color;
-        ctx.beginPath();
-        ctx.arc(center.x, center.y, radius, 0, 2 * Math.PI);
-        ctx.fill();
-
-        ctx.strokeStyle = color;
-        ctx.lineWidth = line_width;
-        ctx.beginPath();
-        ctx.arc(center.x, center.y, radius, 0, 2 * Math.PI);
-        ctx.stroke();
-
-        ctx.beginPath();
-        ctx.moveTo(cross_coords.left, cross_coords.top);
-        ctx.lineTo(cross_coords.right, cross_coords.bottom);
-        ctx.moveTo(cross_coords.left, cross_coords.bottom)
-        ctx.lineTo(cross_coords.right, cross_coords.top);
-        ctx.stroke();
-    }
-}
-
-module.exports = ClosingIconCpt;
-},{"./ui-component":33}],27:[function(require,module,exports){
-"use strict";
-
-const MtlUiComponent = require("./ui-component");
-
-/**
- * A component that draws a GameObject
- */
-class GameObjectCpt extends MtlUiComponent {
-    constructor(params) {
-        super(params);
-        this.set_str_id("GameObjectCpt")
-    }
-
-    /**
-     * Draws the image of the object on the canvas.
-     */
-    draw() {
-        super.draw();
-        const { position, dimensions, image, is_in_inventory } = this.params;
-        if (is_in_inventory()) {
-            return;
-        }
-
-        const ctx = window.mentalo_drawing_context;
-
-        ctx.drawImage(
-            image,
-            0, 0,
-            image.width, image.height,
-            position.x, position.y,
-            dimensions.w, dimensions.h,
-        );
-
-        if (this.state.draw_border) {
-            ctx.lineWidth = 1;
-            ctx.strokeStyle = "rgba(180, 180, 180, 0.5)";
-            ctx.strokeRect(position.x - 5, position.y - 5, dimensions.w + 10, dimensions.h + 10);
-        }
-    }
-}
-
-module.exports = GameObjectCpt;
-},{"./ui-component":33}],28:[function(require,module,exports){
-"use strict";
-
-const MtlUiComponent = require("./ui-component");
-
-/**
- * The component that draws the inventory panel
- */
-class InventoryCpt extends MtlUiComponent {
-    constructor(params) {
-        super(params);
-        this.set_str_id("InventoryCpt")
-    }
-
-    /**
-     * Draws the panel on the canvas, T
-     * the grid slots and the game object images are draws as children components (InventorySlotCpt and InventoryObjectCpt)
-     */
-    draw() {
-        super.draw();
-        if (this.params.is_visible()) {
-            this.clear_bounding_zone();
-            this.draw_children();
-        } else {
-            this.clear_bounding_zone({ clear_color: this.params.invisible_clear_color })
-        }
-    }
-}
-
-module.exports = InventoryCpt;
-},{"./ui-component":33}],29:[function(require,module,exports){
-"use strict";
-
-const { draw_rect } = require("../../lib/shape-tools");
-const MtlUiComponent = require("./ui-component");
-
-/**
- * The component to display a game object image inside a grid slot of the inventory panel.
- */
-class InventoryObjectCpt extends MtlUiComponent {
-    constructor(params) {
-        super(params);
-        this.set_str_id("InventoryObjectCpt")
-    }
-
-    /**
-     * If a game object exists for the slot bound to this instance, its image will be drawn cropped inside a bounding rectangle.
-     * When a game object is hovered in the inventory, the draw_inventory_closing_icon is updated and a red cross will be drawn 
-     * on top of the object image. If the cross get's clicked the object is removed from inventory.
-     */
-    draw() {
-        super.draw();
-        const { get_game_object, slot_index, settings, bounding_zone, stroke_color } = this.params;
-        const game_object = get_game_object(slot_index);
-        if (game_object) {
-            const ctx = window.mentalo_drawing_context;
-
-            const image = game_object.ref.image;
-            draw_rect(ctx, bounding_zone.left, bounding_zone.top, bounding_zone.width, bounding_zone.height, {
-                fill_image: {
-                    src: image,
-                    dw: game_object.dimensions.width,
-                    dh: game_object.dimensions.height,
-                    dx: game_object.position.x,
-                    dy: game_object.position.y,
-                },
-                rounded_corners_radius: settings.slot_rounded_corner_radius,
-                border: {
-                    width: settings.slot_border_width,
-                    color: stroke_color,
-                },
-            });
-
-            if (this.state.draw_inventory_close_icon) {
-                this.draw_children(); // drop object icon
-            }
-        }
-    }
-}
-
-module.exports = InventoryObjectCpt;
-},{"../../lib/shape-tools":9,"./ui-component":33}],30:[function(require,module,exports){
-"use strict";
-
-const { draw_rect } = require("../../lib/shape-tools");
-const MtlUiComponent = require("./ui-component");
-
-/**
- * The component to draw a grid slot in the inventory panel.
- */
-class InventorySlotCpt extends MtlUiComponent {
-    constructor(params) {
-        super(params);
-        this.set_str_id("InventorySlotCpt");
-    }
-
-    /**
-     * Draws a rectangle as the grid slot.
-     * Calls draw_children to draw the image of the game object stored in that slot if there is one.
-     */
-    draw() {
-        super.draw();
-        const { bounding_zone, stroke_color, settings } = this.params;
-        const { left, top, width, height } = bounding_zone;
-        const ctx = window.mentalo_drawing_context;
-
-        draw_rect(ctx, left, top, width, height, {
-            fill_color: "rgba(0,0,0,0)",
-            rounded_corners_radius: settings.slot_rounded_corner_radius,
-            border: {
-                width: settings.slot_border_width,
-                color: stroke_color,
-            }
-        });
-
-        this.draw_children();
-    }
-}
-
-module.exports = InventorySlotCpt;
-},{"../../lib/shape-tools":9,"./ui-component":33}],31:[function(require,module,exports){
-"use strict";
-
-const MtlUiComponent = require("./ui-component");
-
-/**
- * The component that draws the scene animation.
- */
-class SceneAnimationCpt extends MtlUiComponent {
-    constructor(params) {
-        super(params);
-        this.set_str_id("SceneAnimationCpt");
-        this.framecount = 0;
-        // This state controls the visibility of the text box child component.
-        this.state.text_box_visible = true;
-    }
-
-    /**
-     * Sets the text_box_visibility state.
-     * @param {Boolean} value 
-     */
-    set_text_box_visibility(value) {
-        this.state.text_box_visible = value;
-    }
-
-    /**
-     * Increments the animation frame count
-     */
-    update_framecount() {
-        this.framecount = this.framecount + 1 <= Number.MAX_SAFE_INTEGER ? this.framecount + 1 : 0;
-    }
-
-    /**
-     * Draw the scene animation on the canvas  at the frame given by the animation state
-     * and draw each child component like text box, game objects and error popup.
-     */
-    draw() {
-        super.draw();
-        const { bounding_zone, get_animation, next_frame_ready } = this.params;
-        if (next_frame_ready()) {
-            this.clear_bounding_zone();
-            const ctx = window.mentalo_drawing_context;
-
-            const animation = get_animation();
-            animation.update_frame(this.framecount);
-            const dim = animation.dimensions;
-            const w = dim.width;
-            const h = dim.height;
-            const offsetX = animation.frame * w;
-
-            const cw = bounding_zone.width;
-            const ch = bounding_zone.height;
-
-            // center the image
-            if (!animation.canvas_precalc) {
-                animation.canvas_precalc = {
-                    dx: bounding_zone.left,
-                    dy: bounding_zone.top,
-                    dw: cw,
-                    dh: ch,
-                };
-
-                if (w / h > cw / ch) {
-                    // image is more panoramic than canvas
-                    animation.canvas_precalc.dw = cw;
-                    animation.canvas_precalc.dh = cw * (h / w);
-                    // center vertically
-                    animation.canvas_precalc.dy = bounding_zone.top + ((ch - animation.canvas_precalc.dh) / 2);
-                } else if (cw / ch > w / h) {
-                    // canvas is more panoramic
-                    animation.canvas_precalc.dh = ch;
-                    animation.canvas_precalc.dw = ch * (w / h);
-                    // center horizontally
-                    animation.canvas_precalc.dx = bounding_zone.left + ((cw - animation.canvas_precalc.dw) / 2);
-                } else {
-                    // same ratio
-                    animation.canvas_precalc.dh = ch;
-                    animation.canvas_precalc.dw = cw;
-                }
-            }
-
-            const { dx, dy, dw, dh } = animation.canvas_precalc;
-            ctx.drawImage(
-                animation.image,
-                offsetX, 0,
-                w, h,
-                dx, dy,
-                dw, dh
-            );
-
-            this.draw_children();
-            this.update_framecount();
-        }
-    }
-}
-
-module.exports = SceneAnimationCpt;
-},{"./ui-component":33}],32:[function(require,module,exports){
-"use strict";
-
-const { draw_text_in_bounds } = require("../../lib/text-tools");
-const { get_canvas_char_size } = require("../../lib/font-tools");
-const { draw_rect } = require("../../lib/shape-tools");
-const MtlUiComponent = require("./ui-component");
-
-/**
- * The component that handles the displaying of a scene text boxe.
- */
-class TextBoxCpt extends MtlUiComponent {
-    constructor(params) {
-        super(params);
-        this.set_str_id("TextBoxCpt");
-        this.state.text = {};
-    }
-
-    /**
-     * Initialize precalculations for the text box, text dimensions, split lines, etc.
-     */
-    init() {
-        const { text, settings, bounding_zone } = this.params;
-
-        const char_size = get_canvas_char_size(window.mentalo_drawing_context, settings);
-
-        const line_height = char_size.text_line_height;
-        const error_offset = 3 * char_size.width;
-        const chars_per_line = (bounding_zone.width - error_offset) / char_size.width;
-        const output_lines = [];
-        const lines = text.split("\n");
-
-        lines.forEach(line => {
-            const res = [""];
-            let target_i = 0;
-            for (const word of line.split(" ")) {
-                if ((res[target_i] + word + " ").length > chars_per_line) {
-                    res.push("");
-                    target_i++;
-                }
-                res[target_i] += word + " ";
-            }
-
-            res.forEach(l => output_lines.push(l));
-        });
-
-        const text_height = output_lines.length * line_height - char_size.interline_height;
-
-        const updated_bounds = Object.assign({ ...bounding_zone }, {
-            height: bounding_zone.height + text_height,
-            top: bounding_zone.top - text_height,
-        });
-
-        this.state.text = {
-            lines: output_lines,
-            line_height,
-            bounding_zone: updated_bounds,
-        };
-
-        // Update closing icon position
-        const closing_icon = this.children[0];
-        let closing_icon_bounds = closing_icon.params.bounding_zone;
-        closing_icon_bounds = Object.assign(closing_icon_bounds, {
-            top: closing_icon_bounds.top - text_height,
-            bottom: closing_icon_bounds.bottom - text_height,
-        });
-
-        closing_icon.params.center.y -= text_height;
-
-        this.state.initialized = true;
-    }
-
-    /**
-     * Draw the text box and the text inside of it.
-     * The text is not drawn at once, the characters are drawn one by one at each frame 
-     * and a local text.stream state is updated until text is complete.
-     */
-    draw() {
-        if (!this.params.get_visibility_state()) {
-            return;
-        }
-
-        super.draw();
-
-        const { settings } = this.params;
-
-        if (!this.state.initialized) {
-            this.init();
-        }
-
-        const ctx = window.mentalo_drawing_context;
-        const { lines, bounding_zone } = this.state.text;
-
-        const box_pos = { x: bounding_zone.left, y: bounding_zone.top };
-        const box_width = bounding_zone.width;
-        const box_height = bounding_zone.height;
-
-        draw_rect(ctx, box_pos.x, box_pos.y, box_width, box_height, {
-            fill_color: settings.background_color,
-            rounded_corners_radius: settings.rounded_corners_radius,
-            border: {
-                width: settings.border_width,
-                color: settings.font_color,
-            },
-        });
-
-        const complete = draw_text_in_bounds(ctx, lines, bounding_zone, settings, this.state.text, true);
-
-        if (complete) { // draw closing_icon if text is entirely displayed
-            this.draw_children();
-        }
-    }
-}
-
-module.exports = TextBoxCpt;
-},{"../../lib/font-tools":7,"../../lib/shape-tools":9,"../../lib/text-tools":10,"./ui-component":33}],33:[function(require,module,exports){
-"use strict";
-
-const { draw_rect } = require("../../lib/shape-tools");
-
-// const { draw_rect } = require("../lib/shape-tools");
-
-/**
- * A generic class that must be extended by all components registered in MtlRender.components.
- * It provides helping methods to handle event listeners and children components.
- */
-class MtlUiComponent {
-    constructor(params) {
-        this.set_str_id();
-        this.params = params;
-        this.params.event_listeners = this.params.event_listeners || [];
-        this.params.get_children = this.params.get_children || (() => []);
-        this.params.is_visible = this.params.is_visible || (() => true);
-        this.children = [];
-        this.children_set = false;
-        this.set_children();
-        this.state = {
-            event_listeners_initialized: false,
-        };
-
-        this.init_event_listeners();
-    }
-
-    /**
-     * Provide a identifier for the object. Default is contructor name.
-     * Should be called from child class to set a custom id.
-     * @param {String} str 
-     */
-    set_str_id(str = this.constructor.name) {
-        this.str_id = str;
-    }
-
-    /**
-     * Draws a black rectangle on all the surface of the bounding zone defined for this component
-     * @param {Object} options can provide a clear_color<String> rgba value
-     */
-    clear_bounding_zone(options = {}) {
-        let { bounding_zone } = this.params;
-        const { use_bounding_zone } = options;
-        bounding_zone = use_bounding_zone || bounding_zone;
-        const { left, top, width, height } = bounding_zone;
-        draw_rect(
-            window.mentalo_drawing_context,
-            left, top, width, height,
-            {
-                fill_color: options.clear_color || bounding_zone.clear_color || "rgba(0,0,0,0)"
-            }
-        );
-    }
-
-    /**
-     * Removes the event listeners attached to this component and to children components.
-     * @param {Object} options can pass a recursive flag wether children must be cleared or not. Default is true.
-     */
-    clear_event_listeners(options = { recursive: true }) {
-        this.params.event_listeners.forEach(ref => {
-            window.removeEventListener(ref.event_type, ref.listener);
-        });
-
-        if (options.recursive) {
-            this.children.forEach(child => {
-                child.clear_event_listeners();
-            });
-        }
-
-        this.state.event_listeners_initialized = false;
-    }
-
-    /**
-     * Removes children components from this component.
-     * Options can define some component to exlude identifying them by their str_id.
-     * The excluded component will be kept as persistent components.
-     * @param {Object} options 
-     */
-    clear_children(options) {
-        const { exclude } = options;
-        const persistent_children = [];
-
-        this.children.forEach(child => {
-            if (exclude.includes(child.str_id)) {
-                persistent_children.push(child);
-            }
-        });
-
-        this.children = persistent_children;
-        this.children_set = false;
-    }
-
-    /**
-     * Register an array of children components in this component.
-     * This is called when chlidren_set is false. Which happend at initialization and when clear_children is called.
-     * Doing this allows saving the get_children() calculation at each frame.
-     * This children components are concatenated recursively with their own children components.
-     */
-    set_children() {
-        this.children = this.children.concat(
-            this.params.get_children()
-                .filter(child => {
-                    // If this child has been kept as a persistent component, keep it as it.
-                    const keep_previous_child = this.children.find(ch => ch.str_id === child.str_id);
-                    return !keep_previous_child;
-                })
-        );
-
-        this.children_set = true;
-    }
-
-    /**
-     * Attaches a event listener to this component
-     * @param {Object} obj A descriptor of the event listener like :
-     * {
-     *      event_type: "click",
-     *      listener: e => {
-     *          ... something to do on click that component
-     *      }
-     * }
-     */
-    add_event_listener(obj) {
-        const len = this.params.event_listeners.push(obj);
-        const ref = this.params.event_listeners[len - 1];
-        window.addEventListener(ref.event_type, ref.listener);
-    }
-
-    /**
-     * Attaches the event listeners given as parameter to the component
-     */
-    init_event_listeners() {
-        const { event_listeners } = this.params;
-        event_listeners.forEach(obj => this.add_event_listener(obj));
-        this.state.event_listeners_initialized = true
-    }
-
-    /**
-     * Call the draw method of the children component
-     */
-    draw_children() {
-        if (!this.children_set) {
-            this.set_children();
-        }
-        this.children.forEach(c => c.draw());
-    }
-
-    /**
-     * Initializes the event listeners if it's not already done.
-     * This must be called by the inherited call draw method.
-     */
-    draw() {
-        if (!this.state.event_listeners_initialized) {
-            this.init_event_listeners();
-        }
-    }
-}
-
-module.exports = MtlUiComponent;
-},{"../../lib/shape-tools":9}],34:[function(require,module,exports){
-"use strict";
-
-const { draw_text_in_bounds } = require("../../lib/text-tools");
-const { draw_rect } = require("../../lib/shape-tools");
-const MtlUiComponent = require("./ui-component");
-
-/**
- * A component to display an error message in a popup
- */
-class UserErrorPopup extends MtlUiComponent {
-    constructor(params) {
-        super(params);
-        this.state = {
-            text: {},
-        };
-
-        this.set_str_id("UserErrorPopup");
-    }
-
-    /**
-     * Draw the popup to the canvas.
-     * Draws the box, then draw the text lines.
-     * draw_children is for the closing_icon.
-     */
-    draw() {
-        super.draw();
-        const { text_lines, bounding_zone, padding, settings, modal_bounds, clear_modal_color } = this.params;
-        this.clear_bounding_zone({ use_bounding_zone: modal_bounds, clear_color: clear_modal_color });
-
-        const ctx = window.mentalo_drawing_context;
-        const { left, top, width, height } = bounding_zone;
-
-        draw_rect(ctx, left, top, width, height, {
-            fill_color: settings.background_color,
-            rounded_corners_radius: settings.rounded_corners_radius,
-            border: {
-                width: settings.border_width,
-                color: settings.font_color,
-            },
-        });
-
-        const complete = draw_text_in_bounds(
-            ctx,
-            text_lines,
-            bounding_zone,
-            Object.assign({ ...settings }, padding),
-            this.state.text,
-            !!this.params.stream_text
-        );
-
-        if (complete) {
-            this.draw_children();
-        }
-    }
-}
-
-module.exports = UserErrorPopup;
-},{"../../lib/shape-tools":9,"../../lib/text-tools":10,"./ui-component":33}],35:[function(require,module,exports){
-const supported_locales = ["en", "fr", "es"];
-
-/**
- * Translations for the default error messages.
- */
-
-const translations = {
-    "Some scenes have an empty image, game cannot be loaded": {
-        fr: "Certaines scènes ont une image vide, le jeu ne peut pas être chargé",
-        es: "Algunas escenas tienen una imagen vacía, el juego no se puede cargar"
-    },
-    "Destination scene has not been set.": {
-        fr: "La scène de destination n'a pas été définie.",
-        es: "La escena de destino no ha sido definida.",
-    },
-    "Next scene has not been set.": {
-        fr: "La scène suivante n'a pas été définie",
-        es: "La siguiente escena no ha sido definida.",
-    },
-};
-
-function get_translated(str, locale) {
-    return translations[str] && locale !== "en" && supported_locales.includes(locale)
-        ? translations[str][locale]
-        : str;
-}
-
-module.exports = {
-    get_translated,
-    supported_locales,
-};
-},{}],36:[function(require,module,exports){
-"use strict";
-
-module.exports = {
-    register_key: "objectToHtmlRender",
-
-    /**
-     * Register "this" as a window scope accessible variable named by the given key, or default.
-     * @param {String} key 
-     */
-    register(key) {
-        const register_key = key || this.register_key;
-        window[register_key] = this;
-    },
-
-    /**
-     * This must be called before any other method in order to initialize the lib.
-     * It provides the root of the rendering cycle as a Javascript object.
-     * @param {Object} renderCycleRoot A JS component with a render method.
-     */
-    setRenderCycleRoot(renderCycleRoot) {
-        this.renderCycleRoot = renderCycleRoot;
-    },
-
-    event_name: "objtohtml-render-cycle",
-
-    /**
-     * Set a custom event name for the event that is trigger on render cycle.
-     * Default is "objtohtml-render-cycle".
-     * @param {String} evt_name 
-     */
-    setEventName(evt_name) {
-        this.event_name = evt_name;
-    },
-
-    /**
-     * This is the core agorithm that read an javascript Object and convert it into an HTML element.
-     * @param {Object} obj The object representing the html element must be formatted like:
-     * {
-     *      tag: String // The name of the html tag, Any valid html tag should work. div, section, br, ul, li...
-     *      xmlns: String // This can replace the tag key if the element is an element with a namespace URI, for example an <svg> tag.
-     *                      See https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS for more information
-     *      style_rules: Object // a object providing css attributes. The attributes names must be in JS syntax,
-     *                              like maxHeight: "500px", backgrouncColor: "#ff2d56",  margin: 0,  etc.
-     *      contents: Array or String // This reprensents the contents that will be nested in the created html element.
-     *                                   <div>{contents}</div>
-     *                                   The contents can be an array of other objects reprenting elements (with tag, contents, etc)
-     *                                   or it can be a simple string.
-     *      // All other attributes will be parsed as html attributes. They can be anything like onclick, href, onchange, title...
-     *      // or they can also define custom html5 attributes, like data, my_custom_attr or anything.
-     * }
-     * @returns {HTMLElement} The output html node.
-     */
-    objectToHtml(obj) {
-        if (!obj) return document.createElement("span"); // in case of invalid input, don't block the whole process.
-        const objectToHtml = this.objectToHtml.bind(this);
-        const { tag, xmlns } = obj;
-        const node = xmlns !== undefined ? document.createElementNS(xmlns, tag) : document.createElement(tag);
-        const excludeKeys = ["tag", "contents", "style_rules", "state", "xmlns"];
-
-        Object.keys(obj)
-            .filter(attr => !excludeKeys.includes(attr))
-            .forEach(attr => {
-                switch (attr) {
-                    case "class":
-                        node.classList.add(...obj[attr].split(" ").filter(s => s !== ""));
-                        break;
-                    case "on_render":
-                        if (!obj.id) {
-                            node.id = `${btoa(JSON.stringify(obj).slice(0, 127)).replace(/\=/g, '')}${window.performance.now()}`;
-                        }
-                        if (typeof obj.on_render !== "function") {
-                            console.error("The on_render attribute must be a function")
-                        } else {
-                            this.attach_on_render_callback(node, obj.on_render);
-                        }
-                        break;
-                    default:
-                        if (xmlns !== undefined) {
-                            node.setAttributeNS(null, attr, obj[attr])
-                        } else {
-                            node[attr] = obj[attr];
-                        }
-                }
-            });
-        if (obj.contents && typeof obj.contents === "string") {
-            node.innerHTML = obj.contents;
-        } else {
-            obj.contents &&
-                obj.contents.length > 0 &&
-                obj.contents.forEach(el => {
-                    switch (typeof el) {
-                        case "string":
-                            node.innerHTML = el;
-                            break;
-                        case "object":
-                            if (xmlns !== undefined) {
-                                el = Object.assign(el, { xmlns })
-                            }
-                            node.appendChild(objectToHtml(el));
-                            break;
-                    }
-                });
-        }
-
-        if (obj.style_rules) {
-            Object.keys(obj.style_rules).forEach(rule => {
-                node.style[rule] = obj.style_rules[rule];
-            });
-        }
-
-        return node;
-    },
-
-    on_render_callbacks: [],
-
-    /**
-     * This is called if the on_render attribute of a component is set.
-     * @param {HTMLElement} node The created html element
-     * @param {Function} callback The callback defined in the js component to render
-     */
-    attach_on_render_callback(node, callback) {
-        const callback_handler = {
-            callback: e => {
-                if (e.detail.outputNode === node || e.detail.outputNode.querySelector(`#${node.id}`)) {
-                    callback(node);
-                    const handler_index = this.on_render_callbacks.indexOf((this.on_render_callbacks.find(cb => cb.node === node)));
-                    if (handler_index === -1) {
-                        console.warn("A callback was registered for node with id " + node.id + " but callbacck handler is undefined.")
-                    } else {
-                        window.removeEventListener(this.event_name, this.on_render_callbacks[handler_index].callback)
-                        this.on_render_callbacks.splice(handler_index, 1);
-                    }
-                }
-            },
-            node,
-        };
-
-        const len = this.on_render_callbacks.push(callback_handler);
-        window.addEventListener(this.event_name, this.on_render_callbacks[len - 1].callback);
-    },
-
-    /**
-     * If a main element exists in the html document, it will be used as rendering root.
-     * If not, it will be created and inserted.
-     */
-    renderCycle: function () {
-        const main_elmt = document.getElementsByTagName("main")[0] || (function () {
-            const created_main = document.createElement("main");
-            document.body.appendChild(created_main);
-            return created_main;
-        })();
-
-        this.subRender(this.renderCycleRoot.render(), main_elmt, { mode: "replace" });
-    },
-
-    /**
-     * This method behaves like the renderCycle() method, but rather that starting the rendering cycle from the root component,
-    * it can start from any component of the tree. The root component must be given as the first argument, the second argument be
-    * be a valid html element in the dom and will be used as the insertion target.
-     * @param {Object} object An object providing a render method returning an object representation of the html to insert
-     * @param {HTMLElement} htmlNode The htlm element to update
-     * @param {Object} options can be used the define the insertion mode, default is set to "append" and can be set to "override",
-         * "insert-before" (must be defined along with an insertIndex key (integer)),
-         * "adjacent" (must be defined along with an insertLocation key (String)), "replace" or "remove".
-         * In case of "remove", the first argument "object" is not used and can be set to null, undefined or {}...
-     */
-    subRender(object, htmlNode, options = { mode: "append" }) {
-        let outputNode = null;
-
-        const get_insert = () => {
-            outputNode = this.objectToHtml(object);
-            return outputNode;
-        };
-
-        switch (options.mode) {
-            case "append":
-                htmlNode.appendChild(get_insert());
-                break;
-            case "override":
-                htmlNode.innerHTML = "";
-                htmlNode.appendChild(get_insert());
-                break;
-            case "insert-before":
-                htmlNode.insertBefore(get_insert(), htmlNode.childNodes[options.insertIndex]);
-                break;
-            case "adjacent":
-                /**
-                 * options.insertLocation must be one of:
-                 *
-                 * afterbegin
-                 * afterend
-                 * beforebegin
-                 * beforeend
-                 */
-                htmlNode.insertAdjacentHTML(options.insertLocation, get_insert());
-                break;
-            case "replace":
-                htmlNode.parentNode.replaceChild(get_insert(), htmlNode);
-                break;
-            case "remove":
-                htmlNode.remove();
-                break;
-        }
-        const evt_name = this.event_name;
-        const event = new CustomEvent(evt_name, {
-            detail: {
-                inputObject: object,
-                outputNode,
-                insertOptions: options,
-                targetNode: htmlNode,
-            }
-        });
-
-        window.dispatchEvent(event);
-    },
-};
-},{}],37:[function(require,module,exports){
-"use strict";
-
-class ImageCarousel {
-    constructor(props) {
-        this.props = props;
-        this.id = this.props.images.join("").replace(/\s\./g);
-        this.state = {
-            showImageIndex: 0,
-        };
-        this.RUN_INTERVAL = 5000;
-        this.props.images.length > 1 && this.run();
-    }
-
-    run() {
-        this.runningInterval = setInterval(() => {
-            let { showImageIndex } = this.state;
-            const { images } = this.props;
-            this.state.showImageIndex = showImageIndex < images.length - 1 ? ++showImageIndex : 0;
-            this.refreshImage();
-        }, this.RUN_INTERVAL);
-    }
-
-    setImageIndex(i) {
-        clearInterval(this.runningInterval);
-        this.state.showImageIndex = i;
-        this.refreshImage();
-    }
-
-    refreshImage() {
-        obj2htm.subRender(this.render(), document.getElementById(this.id), {
-            mode: "replace",
-        });
-    }
-
-    render() {
-        const { showImageIndex } = this.state;
-        const { images } = this.props;
-        return {
-            tag: "div",
-            id: this.id,
-            class: "image-carousel",
-            contents: [
-                {
-                    tag: "img",
-                    property: "image",
-                    alt: `image carousel ${images[showImageIndex].replace(/\.[A-Za-z]+/, "")}`,
-                    src: images[showImageIndex],
-                },
-                images.length > 1 && {
-                    tag: "div",
-                    class: "carousel-bullets",
-                    contents: images.map((_, i) => {
-                        const active = showImageIndex === i;
-                        return {
-                            tag: "span",
-                            class: `bullet ${active ? "active" : ""}`,
-                            onclick: this.setImageIndex.bind(this, i),
-                        };
-                    }),
-                },
-            ],
-        };
-    }
-}
-
-module.exports = ImageCarousel;
-
-},{}],38:[function(require,module,exports){
-"use strict";
-
-const { fetch_json_or_error_text } = require("./fetch");
-
-function getArticleBody(text) {
-    return text.replaceAll("\n", "<br/>");
-}
-
-function getArticleDate(date) {
-    return `${date.getDate()}-${date.getMonth() + 1}-${date.getFullYear()}`;
-}
-
-function loadArticles(category, locale) {
-    return fetch_json_or_error_text(`/articles/${category}/${locale}`);
-}
-
-module.exports = {
-    loadArticles,
-    getArticleBody,
-    getArticleDate,
-};
-
-},{"./fetch":39}],39:[function(require,module,exports){
-"use strict";
-
-function fetchjson(url) {
-    return new Promise((resolve, reject) => {
-        fetch(url)
-            .then(r => r.json())
-            .then(r => resolve(r))
-            .catch(e => reject(e));
-    });
-}
-
-function fetchtext(url) {
-    return new Promise((resolve, reject) => {
-        fetch(url)
-            .then(r => r.text())
-            .then(r => resolve(r))
-            .catch(e => reject(e));
-    });
-}
-
-async function fetch_json_or_error_text(url, options = {}) {
-    return new Promise((resolve, reject) => {
-        fetch(url, options).then(async res => {
-            if (res.status >= 400 && res.status < 600) {
-                reject(await res.text());
-            } else {
-                resolve(await res.json());
-            }
-        })
-    })
-}
-
-module.exports = {
-    fetchjson,
-    fetchtext,
-    fetch_json_or_error_text,
-};
-
-},{}],40:[function(require,module,exports){
-"use strict";
-const translator = require("ks-cheap-translator");
-const { translations_url } = require("../../constants");
-
-class WebPage {
-    constructor(args) {
-        Object.assign(this, args);
-
-        if (!this.id) {
-            this.id = "webpage-" + performance.now();
-        }
-
-        translator.init({
-            translations_url,
-            supported_languages: ["fr", "en"],
-        }).then(this.refresh_all.bind(this));
-    }
-
-    refresh() {
-        obj2htm.subRender(this.render(), document.getElementById(this.id), { mode: "replace" })
-    }
-
-    refresh_all() {
-        obj2htm.renderCycle()
-    }
-}
-
-module.exports = WebPage;
-},{"../../constants":3,"ks-cheap-translator":4}],41:[function(require,module,exports){
-"use strict";
-
-const { images_url } = require("../../../../../admin-frontend/src/constants");
-const { data_url } = require("../../../../constants");
-const ImageCarousel = require("../../../generic-components/image-carousel");
-const { getArticleBody } = require("../../../lib/article-utils");
-const { fetch_json_or_error_text } = require("../../../lib/fetch");
-const { MentaloEngine } = require("mentalo-engine");
-const translator = require("ks-cheap-translator");
-const t = translator.trad.bind(translator);
-
-class GameArticle {
-    constructor(props) {
-        this.props = props;
-        this.parse_body();
-    }
-
-    parse_body() {
-        let body = getArticleBody(this.props.body);
-        const play_btn_regex = /\[PLAY_BUTTON\s\{.+\}\]/g;
-        const found_play_buttons = body.match(play_btn_regex);
-        if (found_play_buttons) {
-            this.build_play_button(JSON.parse(found_play_buttons[0].replace(/[\[\]PLAY_BUTTON\s]/g, "")));
-            body = body.replace(play_btn_regex, "");
-        }
-        this.body = body;
-    }
-
-    build_play_button(button_data) {
-        this.render_play_button = {
-            tag: "button",
-            class: "play-button",
-            contents: t("Jouer"),
-            onclick: this.handle_click_play.bind(this, button_data.filename, button_data.engine)
-        };
-    }
-
-    load_and_run_mentalo_game(filename, button_element) {
-        const button_text = button_element.innerHTML;
-        button_element.innerHTML = "Loading ...";
-        button_element.style.pointerEvents = "none";
-
-        fetch_json_or_error_text(`${data_url}/${filename}`)
-            .then(game_data => {
-                const container = document.createElement("div");
-                container.style.position = "fixed";
-                container.style.top = 0;
-                container.style.left = 0;
-                container.style.right = 0;
-                container.style.bottom = 0;
-                container.style.zIndex = 10;
-                container.style.display = "flex";
-                container.style.justifyContent = "center";
-                container.style.alignItems = "center";
-
-                container.id = "kuadrado-tmp-game-player-container";
-                document.body.appendChild(container);
-                document.body.style.overflow = "hidden";
-
-                const engine = new MentaloEngine({
-                    game_data,
-                    fullscreen: true,
-                    frame_rate: 30,
-                    container,
-                    on_quit_game: () => {
-                        container.remove();
-                        document.body.style.overflow = "visible";
-                    }
-                });
-
-                engine.init();
-                engine.run_game();
-            })
-            .catch(err => console.log(err))
-            .finally(() => {
-                button_element.innerHTML = button_text;
-                button_element.style.pointerEvents = "unset";
-            });
-    }
-
-    handle_click_play(filename, engine, e) {
-        switch (engine) {
-            case "mentalo":
-                this.load_and_run_mentalo_game(filename, e.target);
-                break;
-            default:
-                console.log("Error, unkown engine")
-                return;
-        }
-    }
-
-    render() {
-        const {
-            title,
-            subtitle,
-            images,
-            details,
-        } = this.props;
-
-        return {
-            tag: "article",
-            typeof: "VideoGame",
-            additionalType: "Article",
-            class: "game-article",
-            contents: [
-                {
-                    tag: "h2",
-                    property: "name",
-                    class: "game-title",
-                    contents: title,
-                },
-                {
-                    tag: "div",
-                    class: "game-banner",
-                    contents: [
-                        { tag: "img", class: "pixelated", src: `${images_url}/${images[0]}` },
-                    ],
-                },
-                {
-                    tag: "h3",
-                    class: "game-subtitle",
-                    contents: subtitle,
-                    property: "alternativeHeadline",
-                },
-                {
-                    tag: "div",
-                    class: "game-description",
-                    property: "description",
-                    contents: [{ tag: "p", style_rules: { margin: 0 }, contents: this.body }]
-                        .concat(this.render_play_button
-                            ? [this.render_play_button]
-                            : []),
-                },
-                new ImageCarousel({ images: images.map(img => `${images_url}/${img}`) }).render(),
-                details.length > 0 && {
-                    tag: "div",
-                    class: "article-details",
-                    contents: [
-                        {
-                            tag: "h2",
-                            contents: "Details",
-                        },
-                        {
-                            tag: "ul",
-                            class: "details-list",
-                            contents: details.map(detail => {
-                                return {
-                                    tag: "li",
-                                    class: "detail",
-                                    contents: [
-                                        { tag: "label", contents: detail.label },
-                                        {
-                                            tag: "div",
-                                            class: "detail-value",
-                                            contents: detail.value
-                                        },
-                                    ],
-                                };
-                            }),
-                        },
-                    ],
-                },
-            ],
-        };
-    }
-}
-
-module.exports = GameArticle;
-
-},{"../../../../../admin-frontend/src/constants":1,"../../../../constants":3,"../../../generic-components/image-carousel":37,"../../../lib/article-utils":38,"../../../lib/fetch":39,"ks-cheap-translator":4,"mentalo-engine":5}],42:[function(require,module,exports){
-"use strict";
-
-const { loadArticles } = require("../../../lib/article-utils");
-const GameArticle = require("./game-article");
-const translator = require("ks-cheap-translator");
-
-class GameArticles {
-    constructor(props) {
-        this.props = props;
-        this.state = {
-            articles: [],
-        };
-        this.id = "game-articles-section";
-        this.loadArticles();
-    }
-
-    loadArticles() {
-        loadArticles("games", translator.locale)
-            .then(articles => {
-                this.state.articles = articles;
-                this.refresh();
-            })
-            .catch(e => console.log(e));
-    }
-
-    renderPlaceholder() {
-        return {
-            tag: "article",
-            class: "placeholder",
-            contents: [{ tag: "div" }, { tag: "div" }],
-        };
-    }
-
-    refresh() {
-        obj2htm.subRender(this.render(), document.getElementById(this.id), {
-            mode: "replace",
-        });
-    }
-
-    render() {
-        const { articles } = this.state;
-        return {
-            tag: "section",
-            class: "game-articles page-contents-center",
-            id: this.id,
-            contents:
-                articles.length > 0
-                    ? articles.map(article => new GameArticle({ ...article }).render())
-                    : [this.renderPlaceholder()],
-        };
-    }
-}
-
-module.exports = GameArticles;
-
-},{"../../../lib/article-utils":38,"./game-article":41,"ks-cheap-translator":4}],43:[function(require,module,exports){
-"use strict";
-
-const { images_url } = require("../../../constants");
-const WebPage = require("../../lib/web-page");
-const GameArticles = require("./components/game-articles");
-const translator = require("ks-cheap-translator");
-const t = translator.trad.bind(translator);
-
-class GamesPage extends WebPage {
-    render() {
-        return {
-            tag: "div",
-            id: "games-page",
-            contents: [
-                {
-                    tag: "div",
-                    class: "page-header logo-left",
-                    contents: [
-                        {
-                            tag: "div",
-                            class: "page-contents-center grid-wrapper",
-                            contents: [
-                                {
-                                    tag: "div",
-                                    class: "logo",
-                                    contents: [
-                                        {
-                                            tag: "img",
-                                            alt: "image game controller",
-                                            src: `${images_url}/game_controller.svg`,
-                                        },
-                                    ],
-                                },
-                                { tag: "h1", contents: t("Jeux") },
-                                {
-                                    tag: "p",
-                                    contents: t("games-page-intro"),
-                                },
-                            ],
-                        },
-                    ],
-                },
-                new GameArticles().render(),
-            ],
-        };
-    }
-}
-
-module.exports = GamesPage;
-
-},{"../../../constants":3,"../../lib/web-page":40,"./components/game-articles":42,"ks-cheap-translator":4}],44:[function(require,module,exports){
-"use strict";
-
-"use strict";
-const runPage = require("../../run-page");
-const GamesPage = require("./games");
-runPage(GamesPage);
-
-},{"../../run-page":45,"./games":43}],45:[function(require,module,exports){
-"use strict";
-
-const renderer = require("object-to-html-renderer")
-const Template = require("./template/template");
-
-module.exports = function runPage(PageComponent) {
-    const template = new Template({ page: new PageComponent() });
-    renderer.register("obj2htm")
-    obj2htm.setRenderCycleRoot(template);
-    obj2htm.renderCycle();
-};
-
-},{"./template/template":47,"object-to-html-renderer":36}],46:[function(require,module,exports){
-"use strict";
-
-const { images_url } = require("../../../constants");
-const translator = require("ks-cheap-translator");
-const t = translator.trad.bind(translator);
-
-const NAV_MENU_ITEMS = [
-    { url: "/games/", text: "Jeux" },
-    {
-        url: "/education/",
-        text: "Pédagogie",
-    },
-    { url: "/software-development/", text: "Software" }
-];
-
-class NavBar {
-    constructor() {
-        this.initEventHandlers();
-    }
-
-    handleBurgerClick() {
-        document.getElementById("nav-menu-list").classList.toggle("responsive-show");
-    }
-
-    initEventHandlers() {
-        window.addEventListener("click", event => {
-            if (
-                event.target.id !== "nav-menu-list" &&
-                !event.target.classList.contains("burger") &&
-                !event.target.parentNode.classList.contains("burger")
-            ) {
-                document.getElementById("nav-menu-list").classList.remove("responsive-show");
-            }
-        });
-    }
-
-    handle_chang_lang(lang) {
-        translator.update_translations(lang).then(() => {
-            obj2htm.renderCycle();
-        }).catch(err => console.log(err));
-    }
-
-    renderHome() {
-        return {
-            tag: "div",
-            class: "home",
-            contents: [
-                {
-                    tag: "a",
-                    href: "/",
-                    contents: [
-                        {
-                            tag: "img",
-                            alt: "Logo Kuadrado",
-                            src: `${images_url}/logo_kuadrado.svg`,
-                        },
-                        {
-                            tag: "img",
-                            alt: "Kuadrado Software",
-                            class: "logo-text",
-                            src: `${images_url}/logo_kuadrado_txt.svg`,
-                        },
-                    ],
-                },
-            ],
-        };
-    }
-
-    renderMenu(menuItemsArray, isSubmenu = false, parentUrl = "") {
-        return {
-            tag: "ul",
-            id: "nav-menu-list",
-            class: isSubmenu ? "submenu" : "",
-            contents: menuItemsArray.map(item => {
-                const { url, text, submenu } = item;
-                const href = `${parentUrl}${url}`;
-                return {
-                    tag: "li",
-                    class: !isSubmenu && window.location.pathname === href ? "active" : "",
-                    contents: [
-                        {
-                            tag: "a",
-                            href,
-                            contents: t(text),
-                        },
-                    ].concat(submenu ? [this.renderMenu(submenu, true, url)] : []),
-                };
-            }).concat({
-                tag: "li",
-                class: "lang-flags",
-                contents: ["fr", "en"].map(lang => {
-                    return {
-                        tag: "img", src: `${images_url}/flag-${lang}.svg`,
-                        class: translator.locale === lang ? "selected" : "",
-                        onclick: this.handle_chang_lang.bind(this, lang)
-                    }
-                })
-            }),
-        };
-    }
-
-    renderResponsiveBurger() {
-        return {
-            tag: "div",
-            class: "burger",
-            onclick: this.handleBurgerClick.bind(this),
-            contents: [{ tag: "span", contents: "···" }],
-        };
-    }
-
-    render() {
-        return {
-            tag: "nav",
-            contents: [
-                this.renderHome(),
-                this.renderResponsiveBurger(),
-                this.renderMenu(NAV_MENU_ITEMS),
-            ],
-        };
-    }
-}
-
-module.exports = NavBar;
-
-},{"../../../constants":3,"ks-cheap-translator":4}],47:[function(require,module,exports){
-"use strict";
-
-const { in_construction } = require("../../config");
-const { images_url } = require("../../constants");
-const NavBar = require("./components/navbar");
-const translator = require("ks-cheap-translator");
-const t = translator.trad.bind(translator)
-
-class Template {
-    constructor(props) {
-        this.props = props;
-    }
-    render() {
-        return {
-            tag: "main",
-            contents: [
-                {
-                    tag: "header",
-                    contents: [new NavBar().render()],
-                },
-                in_construction && {
-                    tag: "section",
-                    class: "warning-banner",
-                    contents: [
-                        {
-                            tag: "strong",
-                            class: "page-contents-center",
-                            contents: t("Site en construction ..."),
-                        },
-                    ],
-                },
-                {
-                    tag: "section",
-                    id: "page-container",
-                    contents: [this.props.page.render()],
-                },
-                {
-                    tag: "footer",
-                    contents: [
-                        {
-                            tag: "div",
-                            class: "logo",
-                            contents: [
-                                {
-                                    tag: "img",
-                                    alt: `logo Kuadrado`,
-                                    src: `${images_url}/logo_kuadrado.svg`,
-                                },
-                                {
-                                    tag: "img",
-                                    class: "text-logo",
-                                    alt: "Kuadrado Software",
-                                    src: `${images_url}/logo_kuadrado_txt.svg`,
-                                },
-                            ],
-                        },
-                        {
-                            tag: "span",
-                            contents: "32 rue Simon Vialet, 07240 Vernoux en Vivarais. Ardèche, France",
-                        },
-                        {
-                            tag: "div",
-                            contents: [
-                                { tag: "strong", contents: "<blue>Contact : </blue>" },
-                                {
-                                    tag: "a",
-                                    href: "mailto:contact@kuadrado-software.fr",
-                                    contents: "contact@kuadrado-software.fr",
-                                },
-                            ],
-                        },
-                        {
-                            tag: "div",
-                            class: "social",
-                            contents: [
-                                {
-                                    tag: "strong",
-                                    contents: `<blue>${t("Sur les réseaux")} : </blue>`,
-                                },
-                                {
-                                    tag: "a",
-                                    href: "https://www.linkedin.com/company/kuadrado-software",
-                                    target: "_blank",
-                                    contents: "in",
-                                    title: "Linkedin",
-                                },
-                                {
-                                    tag: "a",
-                                    href: "https://mastodon.gamedev.place/@kuadrado_software",
-                                    target: "_blank",
-                                    contents: "m",
-                                    title: "Mastodon",
-                                }
-                            ],
-                        },
-                        {
-                            tag: "span",
-                            contents: `Copyleft 🄯 ${new Date()
-                                .getFullYear()} Kuadrado Software | 
-                                ${t("kuadrado-footer-copyleft")}`,
-                        },
-                        {
-                            tag: "div", contents: [
-                                { tag: "span", contents: t("Ce site web est") + " " },
-                                {
-                                    tag: "a", target: "_blank",
-                                    style_rules: { fontWeight: "bold" },
-                                    href: "https://gitlab.com/kuadrado-software/kuadrado-website/-/blob/master/README.md",
-                                    contents: "OPEN SOURCE"
-                                }
-                            ]
-                        }
-                    ],
-                },
-            ],
-        };
-    }
-}
-
-module.exports = Template;
-
-},{"../../config":2,"../../constants":3,"./components/navbar":46,"ks-cheap-translator":4}]},{},[44]);
+!function s(i,o,a){function r(t,e){if(!o[t]){if(!i[t]){var n="function"==typeof require&&require;if(!e&&n)return n(t,!0);if(c)return c(t,!0);throw(n=new Error("Cannot find module '"+t+"'")).code="MODULE_NOT_FOUND",n}n=o[t]={exports:{}},i[t][0].call(n.exports,function(e){return r(i[t][1][e]||e)},n,n.exports,s,i,o,a)}return o[t].exports}for(var c="function"==typeof require&&require,e=0;e<a.length;e++)r(a[e]);return r}({1:[function(e,t,n){t.exports={images_url:"/assets/images"}},{}],2:[function(e,t,n){t.exports={build:{protected_dirs:["assets","style","views","standard"],default_meta_keys:["title","description","image","open_graph","json_ld"]}}},{}],3:[function(e,t,n){t.exports={images_url:"/assets/images",data_url:"/assets/data",translations_url:"/assets/translations"}},{}],4:[function(e,t,n){"use strict";t.exports={locale:"en",supported_languages:["en"],translations:{},translations_url:"",use_url_locale_fragment:!0,local_storage_key:"translator-prefered-language",init(e){return Object.entries(e).forEach(e=>{var[t,e]=e;["supported_languages","use_url_locale_fragment","local_storage_key","translations_url"].includes(t)&&(this[t]=e)}),this.translations_url=this.format_translations_url(this.translations_url),this.supported_languages=this.format_supported_languages(this.supported_languages),new Promise((t,n)=>{const s=(()=>{if(this.use_url_locale_fragment){var e=window.location.pathname.substring(1).split("/")[0];return this.supported_languages.includes(e)?e:""}return""})()||localStorage.getItem(this.local_storage_key)||(e=navigator.language.split("-")[0].toLocaleLowerCase(),this.supported_languages.includes(e)?e:this.supported_languages[0]);var e;fetch(`${this.translations_url}${s}.json`).then(e=>e.json()).then(e=>{this.locale=s,this.translations=e,t()}).catch(e=>{this.locale="en",n(e)})})},format_locale(e){return e.split("-")[0].toLowerCase()},format_translations_url(e){return"/"!==e.charAt(e.length-1)&&(e+="/"),e},format_supported_languages(e){return e.map(e=>this.format_locale(e))},update_translations(s){return s=this.format_locale(s),new Promise((n,t)=>{fetch(`${this.translations_url}${s}.json`).then(e=>e.json()).then(e=>{this.translations=e,this.locale=s,localStorage.setItem(this.local_storage_key,s);const t=window.location.pathname.substring(1).split("/");e=t[0];this.supported_languages.includes(e)&&(t.splice(0,1,s),e=t.join("/"),window.history.replaceState(null,"","/"+e)),n()}).catch(e=>{t(e)})})},trad:function(t,n={}){return t=this.translations[t]||t,Object.keys(n).forEach(e=>{t=t.replace(`{%${e}%}`,n[e])}),t}}},{}],5:[function(e,t,n){"use strict";var s=e("./model/animation"),i=e("./mentalo-engine"),o=e("./model/scene"),a=e("./model/game"),r=e("./model/sound-track"),c=e("./model/scene-types"),l=e("./model/choice"),_=e("./model/game-object"),h=e("./lib/frame-rate-controller"),d=e("./lib/font-tools"),m=e("./lib/color-tools"),e=e("./lib/shape-tools");t.exports={MentaloEngine:i,MtlAnimation:s,MtlScene:o,MtlGame:a,MtlSoundTrack:r,SCENE_TYPES:c,MtlChoice:l,MtlGameObject:_,FrameRateController:h,font_tools:d,color_tools:m,shape_tools:e}},{"./lib/color-tools":6,"./lib/font-tools":7,"./lib/frame-rate-controller":8,"./lib/shape-tools":9,"./mentalo-engine":11,"./model/animation":12,"./model/choice":13,"./model/game":15,"./model/game-object":14,"./model/scene":19,"./model/scene-types":18,"./model/sound-track":20}],6:[function(e,t,n){"use strict";function s(e){return(e=e.toLowerCase(0)).includes("rgb")?e.replaceAll(/[a-z\(\)w]/g,"").split(",").slice(0,3).map(e=>parseInt(e)):e.replace("#","").match(/.{0,2}/g).slice(0,3).map(e=>parseInt(e,16))}function i(e){return s(e).reduce((e,t)=>e+t/3,0)}function o(e){return`#${e.slice(0,3).map(e=>{e=e.toString(16);return e.length<2?"0"+e:e}).join("")}`}t.exports={get_average_rgb_color_tone:i,get_optimal_visible_foreground_color:function(e){const t=document.createElement("canvas");t.width=1,t.height=1;const n=t.getContext("2d");return n.fillStyle=e,n.fillRect(0,0,1,1),e=127<i(e)?"#0000003f":"#ffffff3f",n.fillStyle=e,n.fillRect(0,0,1,1),o(Array.from(n.getImageData(0,0,1,1).data))},color_str_to_rgb_array:s,rgb_array_to_hex:o,rgba_array_to_hex:function(e){return`#${(e=4===e.length?e:e.concat([255])).slice(0,4).map(e=>{e=e.toString(16);return e.length<2?"0"+e:e}).join("")}`},same_rgba:function(e,t){return e[0]===t[0]&&e[1]===t[1]&&e[2]===t[2]&&e[3]===t[3]}}},{}],7:[function(e,t,n){"use strict";const s=[{category:"Sans serif",values:[{value:"sans-serif",text:"sans-serif"},{text:"Arial",value:"Arial, sans-serif"},{text:"Helvetica",value:"Helvetica, sans-serif"},{text:"Verdana",value:"Verdana, sans-serif"},{text:"Trebuchet MS",value:"Trebuchet MS, sans-serif"},{text:"Noto Sans",value:"Noto Sans, sans-serif"},{text:"Gill Sans",value:"Gill Sans, sans-serif"},{text:"Avantgarde",value:"Avantgarde, TeX Gyre Adventor, URW Gothic L, sans-serif"},{text:"Optima",value:"Optima, sans-serif"},{text:"Arial Narrow",value:"Arial Narrow, sans-serif"}]},{category:"Serif",values:[{text:"serif",value:"serif"},{text:"Times",value:"Times, Times New Roman, serif"},{text:"Didot",value:"Didot, serif"},{text:"Georgia",value:"Georgia, serif"},{text:"Palatino",value:"Palatino, URW Palladio L, serif"},{text:"Bookman",value:"Bookman, URW Bookman L, serif"},{text:"New Century Schoolbook",value:"New Century Schoolbook, TeX Gyre Schola, serif"},{text:"American Typewriter",value:"American Typewriter, serif"}]},{category:"Monospace",values:[{text:"monospace",value:"monospace"},{text:"Andale Mono",value:"Andale Mono, monospace"},{text:"Courrier New",value:"Courier New, monospace"},{text:"Courrier",value:"Courier, monospace"},{text:"FreeMono",value:"FreeMono, monospace"},{text:"OCR A Std",value:"OCR A Std, monospace"},{text:"DejaVu Sans Mono",value:"DejaVu Sans Mono, monospace"}]},{category:"Cursive",values:[{text:"cursive",value:"cursive"},{text:"Comic Sans MS",value:"Comic Sans MS, Comic Sans, cursive"},{text:"Apple Chancery",value:"Apple Chancery, cursive"},{text:"Bradley Hand",value:"Bradley Hand, cursive"},{text:"Brush Script MT",value:"Brush Script MT, Brush Script Std, cursive"},{text:"Snell Roundhand",value:"Snell Roundhand, cursive"},{text:"URW Chancery L",value:"URW Chancery L, cursive"}]}],i=[{value:"left",text:"Left"},{value:"right",text:"Right"},{value:"center",text:"Center"}],o=[{value:"normal",text:"Normal"},{value:"italic",text:"Italic"}],a=[{value:"1",text:"Thin"},{value:"normal",text:"Normal"},{value:"900",text:"Bold"}];function r(e={}){const{font_size:t=20,font_family:n="sans-serif",font_style:s="normal",font_weight:i="normal"}=e;return`${s} normal ${i} ${t.toFixed()}px ${n}`}t.exports={get_canvas_font:r,get_font_options:function(){return{font_families:s,text_align_options:i,font_style_options:o,font_weight_options:a}},get_canvas_char_size:function(e,t){e.save(),e.font=r(t);var n="Lorem ipsum dolor Sit amet, Consectetur adipiscing elit",s=e.measureText(n).width/n.length,n=(t=1.1*e.measureText("M").width)+t/4;return e.restore(),{width:s,height:t,text_line_height:n,interline_height:t/4}},FONT_FAMILIES:s,TEXT_ALIGN_OPTIONS:i,FONT_STYLE_OPTIONS:o,FONT_WEIGHT_OPTIONS:a}},{}],8:[function(e,t,n){"use strict";t.exports=class{constructor(e){this.tframe=performance.now(),this.interval=1e3/e,this.initial=!0}nextFrameReady(){if(this.initial)return!(this.initial=!1);var e=performance.now(),t=e-this.tframe,n=t>this.interval;return n&&(this.tframe=e-t%this.interval),n}}},{}],9:[function(e,t,n){"use strict";t.exports={draw_rect:function(e,t,n,s,i,o={rounded_corners_radius:0,border:{width:0,color:"rgba(0,0,0,0)"},fill_color:"black"}){var{rounded_corners_radius:a=0,border:r={width:0,color:"rgba(0,0,0,0)"},fill_color:c="black",fill_image:l}=o,a=(o=Math.min(s,i))/2<a?o/2:a;e.save(),e.beginPath(),e.arc(t+a,n+a,a,Math.PI,3*Math.PI/2),e.lineTo(t+s-a,n),e.arc(t+s-a,n+a,a,3*Math.PI/2,0),e.lineTo(t+s,n+i-a),e.arc(t+s-a,n+i-a,a,0,Math.PI/2),e.lineTo(t+a,n+i),e.arc(t+a,n+i-a,a,Math.PI/2,Math.PI),e.closePath(),e.clip(),l?e.drawImage(l.src,0,0,l.src.width,l.src.height,l.dx,l.dy,l.dw,l.dh):(e.fillStyle=c,e.fillRect(t,n,s,i)),0<r.width&&(e.strokeStyle=r.color,e.lineWidth=r.width,e.stroke()),e.restore()}}},{}],10:[function(e,t,n){"use strict";const{get_canvas_font:l,get_canvas_char_size:_}=e("./font-tools");t.exports={draw_text_in_bounds:function(i,s,o,a,r,e){i.save();const t=_(i,a).text_line_height;i.font=l(a),i.fillStyle=a.font_color,i.textAlign=a.text_align,i.textBaseline="top";const n=(()=>{var{padding:e=0}=a,{left:t,top:n,width:s}=o;switch(i.textAlign){case"left":return{x:t+e,y:n+e};case"right":return{x:t+s-e,y:n+e};case"center":return{x:t+s/2,y:n+e};default:return{x:t+e,y:n+e}}})();r.stream=r.stream||{line_chars:0,line_index:0,complete:!1};const c=e?s.map((e,t)=>{const n=r.stream;if(t<n.line_index||n.complete)return e;if(t>n.line_index)return"";t=e.slice(0,++n.line_chars);return t.length===e.length&&(n.complete=s.length-1===n.line_index,n.line_index=n.complete?n.line_index:n.line_index+1,n.line_chars=0),t}):(r.stream.complete=!0,s);return c.forEach(e=>{i.fillText(e,n.x,n.y),n.y+=t}),i.restore(),r.stream.complete}}},{"./font-tools":7}],11:[function(e,t,n){"use strict";const s=e("./model/game"),i=e("./model/scene-types"),o=e("./render/render"),{supported_locales:a,get_translated:r}=e("./translation");t.exports=class{constructor(e){this.game=new s,this.game.load_data(e.game_data),this.game.on_load_resources(()=>this.loading=!1),this.use_locale=e.use_locale&&a.includes(e.use_locale)?e.use_locale:"en",this.escape_listener=e=>{e=e.key.toLowerCase();"escape"!==e&&"q"!==e||this.quit()},window.addEventListener("keydown",this.escape_listener),this.on_quit_game=function(){this.used_default_container&&this.container.remove(),window.removeEventListener("keydown",this.escape_listener),e.on_quit_game&&e.on_quit_game()},this.used_default_container=!1,this.container=e.container||(()=>{const e=document.createElement("div");return e.style.display="flex",e.style.justifyContent="center",e.style.alignItems="center",e.style.position="absolute",e.style.top=0,e.style.bottom=0,e.style.left=0,e.style.right=0,e.style.zIndex=1e3,e.style.overflow="hidden",document.body.appendChild(e),this.used_default_container=!0,e})(),this.render=new o({container:this.container,fullscreen:e.fullscreen,frame_rate:e.frame_rate,get_game_settings:this.get_game_settings.bind(this),get_game_scenes:this.get_game_scenes.bind(this),get_scene:this.get_scene.bind(this),get_scene_index:this.get_scene_index.bind(this),get_inventory:this.get_inventory.bind(this),on_game_object_click:this.on_game_object_click.bind(this),on_drop_inventory_object:this.on_drop_inventory_object.bind(this),on_choice_click:this.on_choice_click.bind(this)}),this.loading=!0,this.will_quit=!1}get_game_settings(e){return this.game.game_ui_options[e]}get_game_scenes(){return this.game.scenes}get_scene(){return this.game.get_scene()}get_scene_index(){return this.game.scenes.indexOf(this.get_scene())}get_inventory(){return this.game.state.inventory}quit(){this.will_quit=!0}__quit(){window.cancelAnimationFrame(window.mentalo_engine_animation_id),this.render.exit_fullscreen(),this.render.clear_event_listeners(),this.game.get_soundtrack().stop(),this.on_quit_game()}init(){window.requestAnimationFrame=window.requestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame||window.msRequestAnimationFrame,window.cancelAnimationFrame=window.cancelAnimationFrame||window.mozCancelAnimationFrame,window.mentalo_engine_animation_id&&window.cancelAnimationFrame(window.mentalo_engine_animation_id),this.render.init(),this.game.scenes.find(e=>e.animation.empty)&&(alert(r("Some scenes have an empty image, game cannot be loaded",this.use_locale)),this.quit())}go_to_scene(e){this.render.reset_user_error_popup(),this.render.clear_event_listeners(),this.game.get_soundtrack().stop(),this.game.go_to_scene(e),this.render.clear_children(),this.render.reset_text_box_visibility()}on_game_object_click(e){this.game.inventory_has_empty_slot()&&this.game.add_object_to_inventory(e)}on_drop_inventory_object(e){this.game.remove_object_from_inventory(e)}on_choice_click(e){const{destination_scene_index:t,use_objects:n}=e;if(-1!==t)if(-2!==t){if(n.value){var s=this.get_inventory();const o=[];for(const a of n.items){var i=Array.from(s).find(e=>e.name===a.name);if(!i)return void this.render.set_user_error_popup({text:n.missing_object_message,stream_text:!0});o.push(i)}o.forEach(t=>n.items.find(e=>t.name===e.name).consume_object&&this.game.consume_game_object(t))}this.go_to_scene(t)}else this.quit();else this.render.set_user_error_popup({text:r("Destination scene has not been set.",this.use_locale)})}on_cinematic_end(){var e=this.game.get_scene();e.end_cinematic_options.quit?this.quit():-1===e.end_cinematic_options.destination_scene_index?this.render.set_user_error_popup({text:r("Next scene has not been set.",this.use_locale),on_close:this.quit.bind(this)}):this.go_to_scene(e.end_cinematic_options.destination_scene_index)}run_game(){if(this.will_quit)this.__quit();else{if(this.loading)this.render.draw_loading();else{this.get_scene()._type===i.CINEMATIC&&(this.game.update_cinematic_timeout(),this.game.is_cinematic_ended()&&this.on_cinematic_end());const e=this.game.get_soundtrack();e.loaded&&!e.is_playing()&&e.play(),this.render.draw_game()}window.mentalo_engine_animation_id=requestAnimationFrame(this.run_game.bind(this))}}}},{"./model/game":15,"./model/scene-types":18,"./render/render":23,"./translation":35}],12:[function(e,t,n){"use strict";class s extends e("./loadable"){constructor(){super(new Image,"image","load"),this.image.onload=()=>this.update_dimensions(),this.name="",this.dimensions={width:0,height:0},this.frame_nb=1,this.frame=0,this.speed=1,this.play_once=!1,this.initialized=!1,this.finished=!1}update_dimensions(){this.dimensions={width:this.image.width/this.frame_nb,height:this.image.height},this.canvas_precalc&&delete this.canvas_precalc}init(e){this.empty=""===e.src,this.name=e.name,this.image.src=e.src,this.frame_nb=e.frame_nb,this.frame=0,this.speed=e.speed,this.play_once=e.play_once,this.initialized=!0}update_frame(e){this.finished=this.play_once&&this.frame===this.frame_nb-1,1<this.frame_nb&&e%this.speed==0&&!this.finished&&(this.frame=this.frame+1<=this.frame_nb-1?this.frame+1:0)}reset_frame(){this.finished=!1,this.frame=0}}t.exports=s},{"./loadable":17}],13:[function(e,t,n){"use strict";t.exports=class{constructor(e={text:"",destination_scene_index:-1,use_objects:{value:!1,items:[],missing_object_message:""}}){this.load_data(e)}load_data(e){this.text=e.text,this.destination_scene_index=e.destination_scene_index,this.use_objects=e.use_objects}}},{}],14:[function(e,t,n){"use strict";class s extends e("./loadable"){constructor(){super(new Image,"image","load"),this.name="",this.position={x:0,y:0},this.state={}}load_data(e){this.image.src=e.image,this.name=e.name,this.position=e.position}get_dimensions(){return{width:this.image.width,height:this.image.height}}get_bounds(){var e=this.get_dimensions();return{top:this.position.y,right:this.position.x+e.width,bottom:this.position.y+e.height,left:this.position.x}}}t.exports=s},{"./loadable":17}],15:[function(e,t,n){"use strict";const s=e("./scene"),i=e("./scene-types");t.exports=class{constructor(){this.name="",this.scenes=[new s],this.game_ui_options={general:{background_color:"#000000",animation_canvas_dimensions:{width:600,ratio:"4:3",height:function(){var e=this.ratio.split(":").map(e=>parseInt(e)),e=e[1]/e[0];return Number((this.width*e).toFixed())}}},text_boxes:{background_color:"#222222",font_size:20,font_style:"normal",font_weight:"normal",font_family:"Arial, sans-serif",font_color:"#ffffff",text_align:"left",padding:20,margin:20,border_width:0,rounded_corners_radius:0},choices_panel:{background_color:"#222222",font_size:20,font_family:"sans",font_color:"#ffffff",font_style:"normal",text_align:"left",font_weight:"normal",container_padding:20,choice_padding:10,active_choice_background_color:"rgba(255,255,255,.4)",active_choice_border_width:0,active_choice_rounded_corners_radius:0},inventory:{background_color:"#222222",columns:1,rows:4,gap:10,padding:20,slot_rounded_corner_radius:0,slot_border_width:2}},this.starting_scene_index=0,this.state={scene:this.starting_scene_index,inventory:new Set,cinematic_timeout:{inc:0,last_update_time:-1,timeout:!1}},this.load_state={loaded_scenes:0}}get_scene(){return this.scenes[this.state.scene]}get_soundtrack(){return this.get_scene().sound_track}go_to_scene(e){const t=this.get_scene();t.animation.reset_frame(),t._type==i.CINEMATIC&&(this.state.cinematic_timeout={inc:0,last_update_time:-1,timeout:!1}),this.state.scene=e}update_cinematic_timeout(){var e;this.state.cinematic_timeout.timeout||(e=(new Date).getTime(),-1===this.state.cinematic_timeout.last_update_time&&(this.state.cinematic_timeout.last_update_time=e),this.state.cinematic_timeout.inc=e-this.state.cinematic_timeout.last_update_time,this.state.cinematic_timeout.timeout=1e3*this.get_scene().cinematic_duration<=this.state.cinematic_timeout.inc)}is_cinematic_ended(){var e=this.get_scene();return 0===e.cinematic_duration?e.animation.finished:this.state.cinematic_timeout.timeout}add_object_to_inventory(e){this.state.inventory.add(e)}inventory_has_empty_slot(){var{columns:e,rows:t}=this.game_ui_options.inventory;return this.state.inventory.size<e*t}remove_object_from_inventory(e){this.state.inventory.delete(e)}consume_game_object(t){this.remove_object_from_inventory(t);for(const n of this.scenes){var e=n.game_objects.find(e=>e===t);if(e){n.game_objects.splice(n.game_objects.indexOf(e),1);break}}}all_resources_loaded(){return this.load_state.loadable_elements===this.load_state.loaded_elements}get_game_objects(){return this.scenes.reduce((e,t)=>e.concat(t.game_objects),[])}load_data(e){this.name=e.name,this.starting_scene_index=e.starting_scene_index||0,this.state.scene=this.starting_scene_index,this.scenes=e.scenes.map(e=>{const t=new s;return t.load_data(e),t.on_load_group_complete(()=>{this.load_state.loaded_scenes++,this.load_state.loaded_scenes===this.scenes.length&&this.on_load_resources_callback&&this.on_load_resources_callback()}),t}),this.game_ui_options=Object.assign(e.game_ui_options,{general:{background_color:e.game_ui_options.general.background_color,animation_canvas_dimensions:Object.assign(e.game_ui_options.general.animation_canvas_dimensions,{height:function(){var e=this.ratio.split(":").map(e=>parseInt(e)),e=e[1]/e[0];return Number((this.width*e).toFixed())}})}})}on_load_resources(e){this.on_load_resources_callback=e}}},{"./scene":19,"./scene-types":18}],16:[function(e,t,n){"use strict";t.exports=class{constructor(){this.loadable_elements=0,this.loaded_elements=0}add_loadable(e){this.loadable_elements++,e.on_load(()=>{this.loaded_elements++,this.loadable_elements===this.loaded_elements&&this.on_load_group_complete_custom_callback&&this.on_load_group_complete_custom_callback()})}on_load_group_complete(e){this.on_load_group_complete_custom_callback=e}}},{}],17:[function(e,t,n){"use strict";t.exports=class{constructor(e,t,n){this.loaded=!1,this.init_loadable_element(e,t,n)}init_loadable_element(e,t,n){this[t]=e,this.load_listener=()=>{this.loaded=!0,this.on_load_callback(),this.clear()},this[t].addEventListener(n,this.load_listener),this.clear=()=>{this[t].removeEventListener(n,this.load_listener)}}on_load(e){this.on_load_custom_callback=e}on_load_callback(){this.loaded=!0,this.on_load_custom_callback&&this.on_load_custom_callback()}}},{}],18:[function(e,t,n){t.exports={PLAYABLE:"Playable",CINEMATIC:"Cinematic"}},{}],19:[function(e,t,n){"use strict";const s=e("./scene-types"),i=e("./animation"),o=e("./sound-track"),a=e("./choice"),r=e("./game-object");class c extends e("./loadable-group"){constructor(){super();var e={name:"",_type:s.PLAYABLE,animation:new i,sound_track:new o,choices:[],text_box:"",game_objects:[],cinematic_duration:0,end_cinematic_options:{destination_scene_index:-1,quit:!1}};this.name=e.name,this._type=e._type,this.animation=e.animation,this.sound_track=e.sound_track,this.choices=e.choices,this.text_box=e.text_box,this.game_objects=e.game_objects,this.cinematic_duration=e.cinematic_duration||0,this.end_cinematic_options=e.end_cinematic_options}load_data(e){this.name=e.name,this._type=e._type;const t=new i;t.init(e.animation),this.animation.clear(),this.animation=t,this.animation.image.src&&this.add_loadable(this.animation);const n=new o;n.init(e.sound_track),this.sound_track.clear(),this.sound_track=n,this.sound_track.src&&this.add_loadable(this.sound_track),this.choices=e.choices.map(e=>new a(e)),this.text_box=e.text_box,this.cinematic_duration=e.cinematic_duration||0,this.game_objects=e.game_objects.map(e=>{const t=new r;return t.load_data(e),this.add_loadable(t),t}),this.end_cinematic_options=e.end_cinematic_options}}t.exports=c},{"./animation":12,"./choice":13,"./game-object":14,"./loadable-group":16,"./scene-types":18,"./sound-track":20}],20:[function(e,t,n){"use strict";class s extends e("./loadable"){constructor(){super(new Audio,"audio","loadeddata"),this.name="",this.initialized=!1}reset(){this.init_loadable_element(new Audio,"audio","loadeddata"),this.name="",this.initialized=!1}init(e){this.empty=""===e.src,this.audio.src=e.src,this.name=e.name,this.audio.loop=e._loop,this.initialized=!0}play(){this.audio.play()}stop(){this.audio.pause(),this.audio.currentTime=0}is_playing(){return!this.audio.paused}}t.exports=s},{"./loadable":17}],21:[function(e,t,n){"use strict";const o=e("./choices-panel-metrics");t.exports=class{constructor(e){this.params=e,this.image={width:0,height:0},this.width=0,this.height=0,this.inventory={width:0,height:0},this.choices_panel={width:0,height:0},this.scale_factor=1,this.init_dimensions()}init_dimensions(){const{get_game_settings:e}=this.params,t=e("general");var n=t.animation_canvas_dimensions.height(),s=t.animation_canvas_dimensions.width,i=this.get_inventory_base_width(),o=s+i;this.choices_panel_metrics=this.get_choices_panel_metrics();var a,r=window.screen.width-200,c=window.screen.height-200,l=n+this.choices_panel_metrics.get_total_height(),_=o/l;_<window.screen.width/window.screen.height?this.width=(a=c*_)<=r?a:r:(this.height=(r=l/o*r)<=c?r:c,this.width=this.height*_),this.scale_factor=this.width/o,this.choices_panel_metrics=this.choices_panel_metrics.to_scaled(this.scale_factor),this.image={width:s*this.scale_factor,height:n*this.scale_factor},this.inventory={width:i*this.scale_factor,height:this.image.height},this.choices_panel={width:this.width,height:this.choices_panel_metrics.get_total_height()},this.height=this.image.height+this.choices_panel.height}get_inventory_base_width(){const{get_game_settings:e}=this.params;var t=e("general").animation_canvas_dimensions.height(),n=e("inventory"),s=n.gap,i=t-2*n.padding,t=(n.rows-1)*s,s=(n.columns-1)*s,t=(i-t)/n.rows;return n.columns*t+2*n.padding+s}get_choices_panel_metrics(){const{get_game_settings:e,get_game_scenes:t}=this.params;var n=e("choices_panel"),s=e("general"),i=n.container_padding,s=s.animation_canvas_dimensions.width+this.get_inventory_base_width()-2*i,i=t().map(e=>({choices:e.choices}));return new o({settings:n,container_width:s,scenes_choices:i})}}},{"./choices-panel-metrics":22}],22:[function(e,t,n){"use strict";const{get_canvas_char_size:o}=e("../lib/font-tools");t.exports=class t{constructor(e){this.params=e;var{scale_factor:e=1}=this.params;this.container_width=this.params.container_width*e,this.settings=this.get_scaled_settings(e),this.scenes_formatted_choices=this.get_scenes_formatted_choices(),this.container_padding_height=2*this.settings.container_padding,this.rows=this.get_rows_metrics()}get_scaled_settings(e){var{settings:t}=this.params;return Object.assign({...t},{font_size:t.font_size*e,active_choice_border_width:t.active_choice_border_width*e,active_choice_rounded_corners_radius:t.active_choice_rounded_corners_radius*e,choice_padding:t.choice_padding*e,container_padding:t.container_padding*e})}get_rows_metrics(e,t){var{settings:n}=this,s=this.get_choices_max_lines_per_row(e,t),e=(i=o(window.mentalo_drawing_context,n)).text_line_height,t=i.interline_height,i=2*n.choice_padding,n=s[0]*e-t,t=0<s[1]?s[1]*e-t:0;return[{text_height:n,padding_height:i},{text_height:t,padding_height:0<t?i:0}]}get_choices_max_lines_per_row(e,t){e=e||this.get_max_choices_row_per_scene();const i=t||this.scenes_formatted_choices,o=[0,0];return Array.from({length:e}).forEach((e,t)=>{const n=0===t?[0,2]:[2,4];var s=i.map(e=>Math.max(...e.slice(...n).map(e=>e.text_lines.length))).reduce((e,t)=>Math.max(e,t),0);o[t]=s}),o}get_max_choices_row_per_scene(e){const t=e?this.params.scenes_choices.slice(e,e+1):this.params.scenes_choices;let n=1;return 2<Math.max(...t.map(e=>e.choices.length))&&(n=2),n}get_scenes_formatted_choices(){const{scenes_choices:e}=this.params;var{settings:t,container_width:n}=this,{choice_padding:s}=t;const i=.9*(n/2-2*s)/o(window.mentalo_drawing_context,t).width;return e.map(e=>e.choices.map(e=>{const t=e.text.split(" "),n=[""];let s=0;return t.forEach(e=>{(n[s]+e).length>=i&&(s++,n.push("")),n[s]=`${n[s]}${""===n[s]?"":" "}${e}`}),Object.assign({...e},{text_lines:n})}))}get_total_height(e){return(e=e||this.rows).reduce((e,t)=>e+t.text_height+t.padding_height,0)+this.container_padding_height}to_scaled(e){return new t({...this.params,scale_factor:e})}get_minimum_panel_height_for_scene(e){var e=2<(t=this.scenes_formatted_choices.slice(e,e+1))[0].length?2:1,t=this.get_rows_metrics(e,t);return this.get_total_height(t)}get_one_choices_row_metrics(e,t){return e=this.scenes_formatted_choices.slice(e,e+1),t=this.get_rows_metrics(t+1,e)[t],Object.assign(t,{text_height:t.text_height,padding_height:t.padding_height})}}},{"../lib/font-tools":7}],23:[function(e,t,n){"use strict";const s=e("../lib/frame-rate-controller"),{get_optimal_visible_foreground_color:u}=e("../lib/color-tools"),{get_canvas_char_size:p}=e("../lib/font-tools"),i=e("../model/scene-types"),f=e("./ui-components/choice-cpt"),o=e("./ui-components/choices-panel-cpt"),b=e("./ui-components/closing-icon-cpt"),a=e("./ui-components/game-object-cpt"),r=e("./ui-components/inventory-cpt"),v=e("./ui-components/inventory-object-cpt"),w=e("./ui-components/inventory-slot-cpt"),c=e("./ui-components/scene-animation-cpt"),x=e("./ui-components/text-box-cpt"),y=e("./ui-components/user-error-popup"),l=e("./canvas-dimensions");t.exports=class{constructor(e){this.params=e;e=this.params.frame_rate||30;this.fps_controller=new s(e),this.params.container.style.backgroundColor=this.params.get_game_settings("general").background_color,this.canvas=document.createElement("canvas"),this.canvas.style.backgroundColor="black",window.mentalo_drawing_context=this.canvas.getContext("2d"),this.canvas_dimensions={},this.canvas_zones={},this.event_listeners={game_objects:[],inventory:[],text_box:[]},this.state={user_error_popup:{is_set:!1,text:"",on_close:function(){}}},this.loading_frame=0}init(){this.params.fullscreen&&this.set_full_screen(),this.set_canvas_dimensions(),this.create_canvas_zones(),this.params.container.appendChild(this.canvas);const e=window.mentalo_drawing_context;e.mozImageSmoothingEnabled=!1,e.webkitImageSmoothingEnabled=!1,e.msImageSmoothingEnabled=!1,e.imageSmoothingEnabled=!1,this.create_components()}clear_event_listeners(){Object.values(this.components).forEach(e=>{e.clear_event_listeners()})}clear_children(t={exclude:[]}){Object.values(this.components).forEach(e=>{e.clear_children(t)})}reset_text_box_visibility(){this.components.scene_animation.set_text_box_visibility(!0)}set_full_screen(){const e=document.body;e.requestFullScreen=e.requestFullScreen||e.webkitRequestFullScreen||e.msRequestFullscreen||e.mozRequestFullScreen;try{e.requestFullScreen()}catch(e){console.error(e.message)}}exit_fullscreen(){document.fullscreenElement&&document.exitFullscreen()}set_user_error_popup(e){this.state.user_error_popup={is_set:!0,text:e.text,stream_text:!!e.stream_text,on_close:function(){e.on_close&&e.on_close()}},this.clear_event_listeners(),this.clear_children({exclude:["TextBoxCpt","InventoryCpt","ChoicesPanelCpt","GameObjectCpt"]})}clear_user_error_popup(){this.reset_user_error_popup(),this.clear_event_listeners(),this.clear_children({exclude:["TextBoxCpt","InventoryCpt","ChoicesPanelCpt","GameObjectCpt"]})}reset_user_error_popup(){this.state.user_error_popup={is_set:!1,text:"",on_close:function(){}}}on_close_user_error_popup(){this.state.user_error_popup.on_close(),this.clear_user_error_popup()}set_canvas_dimensions(){var{get_game_settings:e,get_game_scenes:t}=this.params;this.canvas_dimensions=new l({get_game_settings:e,get_game_scenes:t}),this.canvas.width=this.canvas_dimensions.width,this.canvas.height=this.canvas_dimensions.height}get_scaled_choices_panel_settings(){return this.canvas_dimensions.choices_panel_metrics.settings}get_scaled_inventory_settings(){var{scale_factor:e}=this.canvas_dimensions;const{get_game_settings:t}=this.params;var n=t("inventory");return Object.assign({...n},{slot_rounded_corner_radius:n.slot_rounded_corner_radius*e,slot_border_width:n.slot_border_width*e,gap:n.gap*e,padding:n.padding*e})}get_scaled_text_boxes_settings(){var{scale_factor:e}=this.canvas_dimensions;const{get_game_settings:t}=this.params;var n=t("text_boxes");return Object.assign({...n},{font_size:n.font_size*e,padding:n.padding*e,margin:n.margin*e,rounded_corners_radius:n.rounded_corners_radius*e,border_width:n.border_width*e})}create_canvas_zones(){var e=this.canvas_dimensions,t=this.get_scaled_inventory_settings(),n=this.get_scaled_choices_panel_settings();this.canvas_zones={root:{left:0,top:0,right:e.width,bottom:e.height,width:e.width,height:e.height,padding:0},scene_animation:{left:0,top:0,right:e.image.width,bottom:e.image.height,width:e.image.width,height:e.image.height,clear_color:"black",padding:0},inventory:{left:e.image.width,top:0,right:e.width,bottom:e.inventory.height,width:e.width-e.image.width,height:e.inventory.height,clear_color:t.background_color,padding:t.padding},choices_panel:{left:0,top:e.image.height,right:e.width,bottom:e.height,width:e.width,height:e.height-e.image.height,clear_color:n.background_color,padding:n.container_padding}}}scene_is_not_cinematic(){return this.params.get_scene()._type===i.PLAYABLE}create_components(){const{get_game_settings:e,get_inventory:d,get_scene:m,get_game_scenes:l,get_scene_index:_}=this.params,{scale_factor:h}=this.canvas_dimensions,g=this.scene_is_not_cinematic.bind(this);this.components={scene_animation:new c({bounding_zone:this.canvas_zones.scene_animation,get_animation:()=>m().animation,next_frame_ready:()=>this.fps_controller.nextFrameReady(),get_children:()=>{const e=m();return e.game_objects.map(t=>{var e=this.canvas_zones.scene_animation,n={x:t.position.x*h+e.left,y:t.position.y*h+e.top},e={w:t.image.width*h,h:t.image.height*h};const s={left:n.x,right:n.x+e.w,top:n.y,bottom:n.y+e.h,width:e.w,height:e.h};e={bounding_zone:s,position:n,dimensions:e,image:t.image,is_in_inventory:()=>d().has(t)};const i=new a(e);return i.add_event_listener({event_type:"mousemove",listener:e=>{d().has(t)||(e=e.offsetX>=s.left&&e.offsetX<=s.right&&e.offsetY>=s.top&&e.offsetY<=s.bottom,i.state.draw_border=e)}}),i.add_event_listener({event_type:"click",listener:e=>{!d().has(t)&&e.offsetX>=s.left&&e.offsetX<=s.right&&e.offsetY>=s.top&&e.offsetY<=s.bottom&&(this.params.on_game_object_click(t),this.clear_event_listeners(),this.clear_children({exclude:"TextBoxCpt"}))}}),i}).concat(e.text_box?[(()=>{const e=this.get_scaled_text_boxes_settings();var t,n,s,n=(t=this.canvas_zones.scene_animation,o=e.padding,i=e.margin,n=t.left+i,s=t.right-i,i=t.bottom-i,{left:n,right:s,top:o=i-2*o,bottom:i,width:s-n,height:i-o}),i=10*h,o={x:n.right-i/2,y:n.top+i/2};const a={color:e.font_color,radius:i,center:o,background_color:e.background_color,line_width:Math.floor(2*h),bounding_zone:{left:o.x-i,right:o.x+i,top:o.y-i,bottom:o.y+i,width:2*i,height:2*i}},r=new x({text:m().text_box,settings:e,bounding_zone:n,get_visibility_state:()=>this.components.scene_animation.state.text_box_visible,get_children:()=>[new b(a)]}),c=r.children[0];return r.children[0].add_event_listener({event_type:"click",listener:e=>{var t=c.params.bounding_zone,t=e.offsetX>=t.left&&e.offsetX<=t.right&&e.offsetY>=t.top&&e.offsetY<=t.bottom;const{scene_animation:n}=this.components;t&&n.state.text_box_visible&&n.set_text_box_visibility(!1)}}),r})()]:[]).concat(this.state.user_error_popup.is_set?[(()=>{const{text:e,stream_text:t}=this.state.user_error_popup;var n=this.get_scaled_text_boxes_settings(),s=this.canvas_zones.scene_animation,i=p(window.mentalo_drawing_context,n),o=s.width/2,a=2*i.width;const r=(o-2*a)/i.width,c=[""];let l=0;e.split(" ").forEach(e=>{`${c[l]}${e} `.length>r&&(l++,c.push("")),c[l]+=`${e} `});var _=i.text_line_height,i=c.length*_+2*a-i.interline_height;const h=n.background_color,d=n.font_color,m={left:s.left+s.width/2-o/2,top:s.top+s.height/2-i/2,right:s.left+s.width/2+o/2,bottom:s.top+s.height/2-i/2+i,width:o,height:i,clear_color:h};a={settings:n,bounding_zone:m,modal_bounds:this.canvas_zones.scene_animation,clear_modal_color:"#0004",text_lines:c,padding:a,stream_text:t,get_children:()=>{var e={x:m.right-5,y:m.top+5};const t={left:e.x-15,right:e.x+15,top:e.y-15,bottom:e.y+15,width:30,height:30};e={color:d,radius:15,center:e,background_color:h,line_width:2,bounding_zone:t};const n=new b(e);return n.add_event_listener({event_type:"click",listener:e=>{e.offsetX>=t.left&&e.offsetX<=t.right&&e.offsetY>=t.top&&e.offsetY<=t.bottom&&this.on_close_user_error_popup()}}),[n]}};return new y(a)})()]:[])}}),inventory:new r({bounding_zone:this.canvas_zones.inventory,get_inventory:d,is_visible:g,invisible_clear_color:e("general").background_color,get_children:()=>{const i=this.get_scaled_inventory_settings(),a={left:this.canvas_zones.inventory.left+i.padding,top:this.canvas_zones.inventory.top+i.padding,right:this.canvas_zones.inventory.right-i.padding,bottom:this.canvas_zones.inventory.bottom-i.padding,width:this.canvas_zones.inventory.width-2*i.padding,height:this.canvas_zones.inventory.height-2*i.padding},r=i.gap;const c=(a.height-(i.rows-1)*r)/i.rows;var e=c*i.columns+r*(i.columns-1);const l=(a.width-e)/2,_=[];let h=0;return Array.from({length:i.rows}).forEach((e,t)=>{const s=a.top+t*(c+r);Array.from({length:i.columns}).forEach((e,t)=>{t=a.left+t*(c+r)+l;const o={left:t,top:s,right:t+c,bottom:s+c,width:c,height:c},n=u(i.background_color);t={bounding_zone:o,stroke_color:n,is_visible:g,settings:i,get_children:()=>{const s=new v({slot_index:h,is_visible:g,settings:i,stroke_color:n,get_game_object:e=>{const t=Array.from(d())[e];if(t){var n=t.get_dimensions(),e=n.width/n.height;const s={width:0,height:0},i={x:0,y:0};return 1<e?(s.width=c,s.height=c*(n.height/n.width),i.x=o.left,i.y=o.top+(c/2-s.height/2)):e<1?(s.height=c,s.width=c*e,i.y=o.top,i.x=o.left+(c/2-s.width/2)):(s.width=c,s.height=c,i.x=o.left,i.y=o.top),{ref:t,position:i,dimensions:s}}},bounding_zone:o,get_children:()=>[new b({is_visible:g,color:"#bf5e43",center:{x:o.left+c/2,y:o.top+c/2},radius:c/4,line_width:2<c/20?c/20:2,bounding_zone:{...o,clear_color:"rgba(0,0,0,0.2)"}})]});return s.add_event_listener({event_type:"mousemove",listener:e=>{var t=s.params.get_game_object(s.params.slot_index),n=s.params.bounding_zone;s.state.draw_inventory_close_icon=s.params.is_visible()&&!!t&&m().game_objects.includes(t.ref)&&e.offsetX>=n.left&&e.offsetX<=n.right&&e.offsetY>=n.top&&e.offsetY<=n.bottom}}),s.children[0].add_event_listener({event_type:"click",listener:e=>{if(!s.params.is_visible())return!1;var t=s.params.get_game_object(s.params.slot_index),n=s.params.bounding_zone,n=e.offsetX>=n.left&&e.offsetX<=n.right&&e.offsetY>=n.top&&e.offsetY<=n.bottom;t&&n&&m().game_objects.includes(t.ref)&&(this.params.on_drop_inventory_object(t.ref),this.clear_event_listeners(),this.clear_children({exclude:"TextBoxCpt"}))}}),[s]}},t=new w(t);_.push(t),h++})}),_}}),choices_panel:new o({bounding_zone:this.canvas_zones.choices_panel,minimum_bounding_zone:()=>{var e=this.canvas_zones.choices_panel,t=this.canvas_dimensions.choices_panel_metrics.get_minimum_panel_height_for_scene(_()),t=this.canvas_dimensions.choices_panel_metrics.get_total_height()-t;return Object.assign({...e},{bottom:e.bottom-t,height:e.height-t})},is_visible:g,invisible_clear_color:e("general").background_color,get_children:()=>Array.from({length:2*this.canvas_dimensions.choices_panel_metrics.get_max_choices_row_per_scene(_())}).map((e,n)=>{var t=this.get_scaled_choices_panel_settings();const s={left:this.canvas_zones.choices_panel.left+t.container_padding,right:this.canvas_zones.choices_panel.right-t.container_padding,top:this.canvas_zones.choices_panel.top+t.container_padding,bottom:this.canvas_zones.choices_panel.bottom-t.container_padding,width:0,height:0};s.width=s.right-s.left,s.height=s.bottom-s.top;var i=s.width/2;const{choices_panel_metrics:o}=this.canvas_dimensions;var a=o.rows.map(e=>e.text_height+e.padding_height);const r={left:s.left+n%2*i,top:s.top+(1<n?1:0)*a[0],right:s.left+n%2*i+i,bottom:s.top+(1<n?1:0)*a[1]+a[1<n?1:0],width:i,height:a[1<n?1:0]},c=new f({is_visible:g,minimum_bounding_zone:()=>{var e,t;return c.minimum_bounding_zone||(e=1<n?1:0,e=(t=this.canvas_dimensions.choices_panel_metrics.get_one_choices_row_metrics(_(),e)).text_height+t.padding_height,t=(()=>{const e=this.components.choices_panel.children;return 2<=e.indexOf(c)?r.top-e[0].params.minimum_bounding_zone().bottom:0})(),c.minimum_bounding_zone=Object.assign({...r},{height:e,top:r.top-t,bottom:r.top-t+e})),c.minimum_bounding_zone},get_formatted_choice:()=>{var e=this.canvas_dimensions.choices_panel_metrics.scenes_formatted_choices[l().indexOf(m())];return e.length-1>=n?e[n]:void 0},settings:t});return c.add_event_listener({event_type:"mousemove",listener:e=>{c.state.active=c.params.is_visible()&&!!c.params.get_formatted_choice()&&c.is_hover(e)}}),c.add_event_listener({event_type:"click",listener:()=>{var e;c.state.active&&(e=c.params.get_formatted_choice(),this.params.on_choice_click(e)&&(this.clear_event_listeners(),this.clear_children()))}}),c})})}}draw_loading(){const e=window.mentalo_drawing_context;e.save(),e.font="25px monospace",e.fillStyle="black",e.fillRect(0,0,window.innerWidth,window.innerHeight),e.fillStyle="white",e.fillText("Loading",50,window.innerHeight/2);var t=Array.from({length:++this.loading_frame}).map(()=>".").join("");e.font="8px monospace",e.fillText(t,50,window.innerHeight/2+20),e.restore()}draw_game(){Object.values(this.components).forEach(e=>e.draw())}}},{"../lib/color-tools":6,"../lib/font-tools":7,"../lib/frame-rate-controller":8,"../model/scene-types":18,"./canvas-dimensions":21,"./ui-components/choice-cpt":24,"./ui-components/choices-panel-cpt":25,"./ui-components/closing-icon-cpt":26,"./ui-components/game-object-cpt":27,"./ui-components/inventory-cpt":28,"./ui-components/inventory-object-cpt":29,"./ui-components/inventory-slot-cpt":30,"./ui-components/scene-animation-cpt":31,"./ui-components/text-box-cpt":32,"./ui-components/user-error-popup":34}],24:[function(e,t,n){"use strict";const{draw_text_in_bounds:l}=e("../../lib/text-tools"),{draw_rect:_}=e("../../lib/shape-tools");class s extends e("./ui-component"){constructor(e){super(e),this.set_str_id("ChoiceCpt")}draw(){super.draw();const{get_formatted_choice:e,settings:t}=this.params;var n,s,i,o=this.params.minimum_bounding_zone(),a={left:o.left+t.choice_padding,right:o.right-t.choice_padding,top:o.top+t.choice_padding,bottom:o.bottom-t.choice_padding,width:o.width-2*t.choice_padding,height:o.height-2*t.choice_padding},r=e(),c=window.mentalo_drawing_context;r&&(this.state.active&&({left:n,top:s,width:i,height:o}=o,_(c,n,s,i,o,{fill_color:t.active_choice_background_color,rounded_corners_radius:t.active_choice_rounded_corners_radius,border:{width:t.active_choice_border_width,color:t.font_color}})),l(c,r.text_lines,a,t,{},!1))}is_hover(e){var t=this.params.minimum_bounding_zone();return e.offsetX>=t.left&&e.offsetX<=t.right&&e.offsetY>=t.top&&e.offsetY<=t.bottom}}t.exports=s},{"../../lib/shape-tools":9,"../../lib/text-tools":10,"./ui-component":33}],25:[function(e,t,n){"use strict";class s extends e("./ui-component"){constructor(e){super(e),this.set_str_id("ChoicesPanelCpt")}draw(){super.draw(),this.params.is_visible()?(this.clear_bounding_zone({clear_color:this.params.invisible_clear_color}),this.clear_bounding_zone({use_bounding_zone:this.params.minimum_bounding_zone()}),this.draw_children()):this.clear_bounding_zone({clear_color:this.params.invisible_clear_color})}}t.exports=s},{"./ui-component":33}],26:[function(e,t,n){"use strict";class s extends e("./ui-component"){constructor(e){super(e),this.set_str_id("ClosingIconCpt")}draw(){super.draw(),this.clear_bounding_zone();var{color:e,center:t,radius:n=5,line_width:s=2,background_color:i="rgba(0,0,0,0)"}=this.params;const o=window.mentalo_drawing_context;var a=n/1.5,a={left:t.x-n+a,right:t.x+n-a,top:t.y-n+a,bottom:t.y+n-a};o.fillStyle=i,o.beginPath(),o.arc(t.x,t.y,n,0,2*Math.PI),o.fill(),o.strokeStyle=e,o.lineWidth=s,o.beginPath(),o.arc(t.x,t.y,n,0,2*Math.PI),o.stroke(),o.beginPath(),o.moveTo(a.left,a.top),o.lineTo(a.right,a.bottom),o.moveTo(a.left,a.bottom),o.lineTo(a.right,a.top),o.stroke()}}t.exports=s},{"./ui-component":33}],27:[function(e,t,n){"use strict";class s extends e("./ui-component"){constructor(e){super(e),this.set_str_id("GameObjectCpt")}draw(){super.draw();const{position:e,dimensions:t,image:n,is_in_inventory:s}=this.params;if(!s()){const i=window.mentalo_drawing_context;i.drawImage(n,0,0,n.width,n.height,e.x,e.y,t.w,t.h),this.state.draw_border&&(i.lineWidth=1,i.strokeStyle="rgba(180, 180, 180, 0.5)",i.strokeRect(e.x-5,e.y-5,t.w+10,t.h+10))}}}t.exports=s},{"./ui-component":33}],28:[function(e,t,n){"use strict";class s extends e("./ui-component"){constructor(e){super(e),this.set_str_id("InventoryCpt")}draw(){super.draw(),this.params.is_visible()?(this.clear_bounding_zone(),this.draw_children()):this.clear_bounding_zone({clear_color:this.params.invisible_clear_color})}}t.exports=s},{"./ui-component":33}],29:[function(e,t,n){"use strict";const{draw_rect:c}=e("../../lib/shape-tools");class s extends e("./ui-component"){constructor(e){super(e),this.set_str_id("InventoryObjectCpt")}draw(){super.draw();const{get_game_object:e,slot_index:t,settings:n,bounding_zone:s,stroke_color:i}=this.params;var o,a,r=e(t);r&&(o=window.mentalo_drawing_context,a=r.ref.image,c(o,s.left,s.top,s.width,s.height,{fill_image:{src:a,dw:r.dimensions.width,dh:r.dimensions.height,dx:r.position.x,dy:r.position.y},rounded_corners_radius:n.slot_rounded_corner_radius,border:{width:n.slot_border_width,color:i}}),this.state.draw_inventory_close_icon&&this.draw_children())}}t.exports=s},{"../../lib/shape-tools":9,"./ui-component":33}],30:[function(e,t,n){"use strict";const{draw_rect:r}=e("../../lib/shape-tools");class s extends e("./ui-component"){constructor(e){super(e),this.set_str_id("InventorySlotCpt")}draw(){super.draw();var{bounding_zone:e,stroke_color:t,settings:n}=this.params,{left:s,top:i,width:o,height:a}=e,e=window.mentalo_drawing_context;r(e,s,i,o,a,{fill_color:"rgba(0,0,0,0)",rounded_corners_radius:n.slot_rounded_corner_radius,border:{width:n.slot_border_width,color:t}}),this.draw_children()}}t.exports=s},{"../../lib/shape-tools":9,"./ui-component":33}],31:[function(e,t,n){"use strict";class s extends e("./ui-component"){constructor(e){super(e),this.set_str_id("SceneAnimationCpt"),this.framecount=0,this.state.text_box_visible=!0}set_text_box_visibility(e){this.state.text_box_visible=e}update_framecount(){this.framecount=this.framecount+1<=Number.MAX_SAFE_INTEGER?this.framecount+1:0}draw(){super.draw();const{bounding_zone:e,get_animation:t,next_frame_ready:n}=this.params;if(n()){this.clear_bounding_zone();const _=window.mentalo_drawing_context,h=t();h.update_frame(this.framecount);var s=h.dimensions,i=s.width,o=s.height,a=h.frame*i,r=e.width,c=e.height;h.canvas_precalc||(h.canvas_precalc={dx:e.left,dy:e.top,dw:r,dh:c},r/c<i/o?(h.canvas_precalc.dw=r,h.canvas_precalc.dh=r*(o/i),h.canvas_precalc.dy=e.top+(c-h.canvas_precalc.dh)/2):i/o<r/c?(h.canvas_precalc.dh=c,h.canvas_precalc.dw=c*(i/o),h.canvas_precalc.dx=e.left+(r-h.canvas_precalc.dw)/2):(h.canvas_precalc.dh=c,h.canvas_precalc.dw=r));var{dx:l,dy:s,dw:c,dh:r}=h.canvas_precalc;_.drawImage(h.image,a,0,i,o,l,s,c,r),this.draw_children(),this.update_framecount()}}}t.exports=s},{"./ui-component":33}],32:[function(e,t,n){"use strict";const{draw_text_in_bounds:r}=e("../../lib/text-tools"),{get_canvas_char_size:_}=e("../../lib/font-tools"),{draw_rect:c}=e("../../lib/shape-tools");class s extends e("./ui-component"){constructor(e){super(e),this.set_str_id("TextBoxCpt"),this.state.text={}}init(){const{text:e,settings:t,bounding_zone:n}=this.params;var s=_(window.mentalo_drawing_context,t),i=s.text_line_height,o=3*s.width;const a=(n.width-o)/s.width,r=[],c=e.split("\n");c.forEach(e=>{const t=[""];let n=0;for(const s of e.split(" "))(t[n]+s+" ").length>a&&(t.push(""),n++),t[n]+=s+" ";t.forEach(e=>r.push(e))});o=r.length*i-s.interline_height,s=Object.assign({...n},{height:n.height+o,top:n.top-o});this.state.text={lines:r,line_height:i,bounding_zone:s};const l=this.children[0];s=l.params.bounding_zone,Object.assign(s,{top:s.top-o,bottom:s.bottom-o});l.params.center.y-=o,this.state.initialized=!0}draw(){var e,t,n,s,i,o,a;this.params.get_visibility_state()&&(super.draw(),{settings:e}=this.params,this.state.initialized||this.init(),t=window.mentalo_drawing_context,{lines:n,bounding_zone:s}=this.state.text,i={x:s.left,y:s.top},o=s.width,a=s.height,c(t,i.x,i.y,o,a,{fill_color:e.background_color,rounded_corners_radius:e.rounded_corners_radius,border:{width:e.border_width,color:e.font_color}}),r(t,n,s,e,this.state.text,!0)&&this.draw_children())}}t.exports=s},{"../../lib/font-tools":7,"../../lib/shape-tools":9,"../../lib/text-tools":10,"./ui-component":33}],33:[function(e,t,n){"use strict";const{draw_rect:a}=e("../../lib/shape-tools");t.exports=class{constructor(e){this.set_str_id(),this.params=e,this.params.event_listeners=this.params.event_listeners||[],this.params.get_children=this.params.get_children||(()=>[]),this.params.is_visible=this.params.is_visible||(()=>!0),this.children=[],this.children_set=!1,this.set_children(),this.state={event_listeners_initialized:!1},this.init_event_listeners()}set_str_id(e=this.constructor.name){this.str_id=e}clear_bounding_zone(e={}){let{bounding_zone:t}=this.params;var{use_bounding_zone:n}=e;t=n||t;var{left:s,top:i,width:o,height:n}=t;a(window.mentalo_drawing_context,s,i,o,n,{fill_color:e.clear_color||t.clear_color||"rgba(0,0,0,0)"})}clear_event_listeners(e={recursive:!0}){this.params.event_listeners.forEach(e=>{window.removeEventListener(e.event_type,e.listener)}),e.recursive&&this.children.forEach(e=>{e.clear_event_listeners()}),this.state.event_listeners_initialized=!1}clear_children(e){const{exclude:t}=e,n=[];this.children.forEach(e=>{t.includes(e.str_id)&&n.push(e)}),this.children=n,this.children_set=!1}set_children(){this.children=this.children.concat(this.params.get_children().filter(t=>{return!this.children.find(e=>e.str_id===t.str_id)})),this.children_set=!0}add_event_listener(e){e=this.params.event_listeners.push(e),e=this.params.event_listeners[e-1];window.addEventListener(e.event_type,e.listener)}init_event_listeners(){const{event_listeners:e}=this.params;e.forEach(e=>this.add_event_listener(e)),this.state.event_listeners_initialized=!0}draw_children(){this.children_set||this.set_children(),this.children.forEach(e=>e.draw())}draw(){this.state.event_listeners_initialized||this.init_event_listeners()}}},{"../../lib/shape-tools":9}],34:[function(e,t,n){"use strict";const{draw_text_in_bounds:l}=e("../../lib/text-tools"),{draw_rect:_}=e("../../lib/shape-tools");class s extends e("./ui-component"){constructor(e){super(e),this.state={text:{}},this.set_str_id("UserErrorPopup")}draw(){super.draw();var{text_lines:e,bounding_zone:t,padding:n,settings:s,modal_bounds:i,clear_modal_color:o}=this.params;this.clear_bounding_zone({use_bounding_zone:i,clear_color:o});var a=window.mentalo_drawing_context,{left:r,top:c,width:i,height:o}=t;_(a,r,c,i,o,{fill_color:s.background_color,rounded_corners_radius:s.rounded_corners_radius,border:{width:s.border_width,color:s.font_color}}),l(a,e,t,Object.assign({...s},n),this.state.text,!!this.params.stream_text)&&this.draw_children()}}t.exports=s},{"../../lib/shape-tools":9,"../../lib/text-tools":10,"./ui-component":33}],35:[function(e,t,n){const s=["en","fr","es"],i={"Some scenes have an empty image, game cannot be loaded":{fr:"Certaines scènes ont une image vide, le jeu ne peut pas être chargé",es:"Algunas escenas tienen una imagen vacía, el juego no se puede cargar"},"Destination scene has not been set.":{fr:"La scène de destination n'a pas été définie.",es:"La escena de destino no ha sido definida."},"Next scene has not been set.":{fr:"La scène suivante n'a pas été définie",es:"La siguiente escena no ha sido definida."}};t.exports={get_translated:function(e,t){return i[e]&&"en"!==t&&s.includes(t)?i[e][t]:e},supported_locales:s}},{}],36:[function(e,t,n){"use strict";t.exports={register_key:"objectToHtmlRender",register(e){e=e||this.register_key;window[e]=this},setRenderCycleRoot(e){this.renderCycleRoot=e},event_name:"objtohtml-render-cycle",setEventName(e){this.event_name=e},objectToHtml(t){if(!t)return document.createElement("span");const n=this.objectToHtml.bind(this),{tag:e,xmlns:s}=t,i=void 0!==s?document.createElementNS(s,e):document.createElement(e),o=["tag","contents","style_rules","state","xmlns"];return Object.keys(t).filter(e=>!o.includes(e)).forEach(e=>{switch(e){case"class":i.classList.add(...t[e].split(" ").filter(e=>""!==e));break;case"on_render":t.id||(i.id=`${btoa(JSON.stringify(t).slice(0,127)).replace(/\=/g,"")}${window.performance.now()}`),"function"!=typeof t.on_render?console.error("The on_render attribute must be a function"):this.attach_on_render_callback(i,t.on_render);break;default:void 0!==s?i.setAttributeNS(null,e,t[e]):i[e]=t[e]}}),t.contents&&"string"==typeof t.contents?i.innerHTML=t.contents:t.contents&&0<t.contents.length&&t.contents.forEach(e=>{switch(typeof e){case"string":i.innerHTML=e;break;case"object":void 0!==s&&(e=Object.assign(e,{xmlns:s})),i.appendChild(n(e))}}),t.style_rules&&Object.keys(t.style_rules).forEach(e=>{i.style[e]=t.style_rules[e]}),i},on_render_callbacks:[],attach_on_render_callback(t,n){var e={callback:e=>{e.detail.outputNode!==t&&!e.detail.outputNode.querySelector(`#${t.id}`)||(n(t),-1===(e=this.on_render_callbacks.indexOf(this.on_render_callbacks.find(e=>e.node===t)))?console.warn("A callback was registered for node with id "+t.id+" but callbacck handler is undefined."):(window.removeEventListener(this.event_name,this.on_render_callbacks[e].callback),this.on_render_callbacks.splice(e,1)))},node:t},e=this.on_render_callbacks.push(e);window.addEventListener(this.event_name,this.on_render_callbacks[e-1].callback)},renderCycle:function(){var e,e=document.getElementsByTagName("main")[0]||(e=document.createElement("main"),document.body.appendChild(e),e);this.subRender(this.renderCycleRoot.render(),e,{mode:"replace"})},subRender(e,t,n={mode:"append"}){let s=null;var i=()=>(s=this.objectToHtml(e),s);switch(n.mode){case"append":t.appendChild(i());break;case"override":t.innerHTML="",t.appendChild(i());break;case"insert-before":t.insertBefore(i(),t.childNodes[n.insertIndex]);break;case"adjacent":t.insertAdjacentHTML(n.insertLocation,i());break;case"replace":t.parentNode.replaceChild(i(),t);break;case"remove":t.remove()}var o=this.event_name,o=new CustomEvent(o,{detail:{inputObject:e,outputNode:s,insertOptions:n,targetNode:t}});window.dispatchEvent(o)}}},{}],37:[function(e,t,n){"use strict";t.exports=class{constructor(e){this.props=e,this.id=this.props.images.join("").replace(/\s\./g),this.state={showImageIndex:0},this.RUN_INTERVAL=5e3,1<this.props.images.length&&this.run()}run(){this.runningInterval=setInterval(()=>{var{showImageIndex:e}=this.state,{images:t}=this.props;this.state.showImageIndex=e<t.length-1?++e:0,this.refreshImage()},this.RUN_INTERVAL)}setImageIndex(e){clearInterval(this.runningInterval),this.state.showImageIndex=e,this.refreshImage()}refreshImage(){obj2htm.subRender(this.render(),document.getElementById(this.id),{mode:"replace"})}render(){const{showImageIndex:n}=this.state,{images:e}=this.props;return{tag:"div",id:this.id,class:"image-carousel",contents:[{tag:"img",property:"image",alt:`image carousel ${e[n].replace(/\.[A-Za-z]+/,"")}`,src:e[n]},1<e.length&&{tag:"div",class:"carousel-bullets",contents:e.map((e,t)=>{return{tag:"span",class:`bullet ${n===t?"active":""}`,onclick:this.setImageIndex.bind(this,t)}})}]}}}},{}],38:[function(e,t,n){"use strict";const{fetch_json_or_error_text:s}=e("./fetch");t.exports={loadArticles:function(e,t){return s(`/articles/${e}/${t}`)},getArticleBody:function(e){return e.replaceAll("\n","<br/>")},getArticleDate:function(e){return`${e.getDate()}-${e.getMonth()+1}-${e.getFullYear()}`}}},{"./fetch":39}],39:[function(e,t,n){"use strict";t.exports={fetchjson:function(e){return new Promise((t,n)=>{fetch(e).then(e=>e.json()).then(e=>t(e)).catch(e=>n(e))})},fetchtext:function(e){return new Promise((t,n)=>{fetch(e).then(e=>e.text()).then(e=>t(e)).catch(e=>n(e))})},fetch_json_or_error_text:async function(e,s={}){return new Promise((t,n)=>{fetch(e,s).then(async e=>{400<=e.status&&e.status<600?n(await e.text()):t(await e.json())})})}}},{}],40:[function(e,t,n){"use strict";const s=e("ks-cheap-translator"),{translations_url:i}=e("../../constants");t.exports=class{constructor(e){Object.assign(this,e),this.id||(this.id="webpage-"+performance.now()),s.init({translations_url:i,supported_languages:["fr","en"]}).then(this.refresh_all.bind(this))}refresh(){obj2htm.subRender(this.render(),document.getElementById(this.id),{mode:"replace"})}refresh_all(){obj2htm.renderCycle()}}},{"../../constants":3,"ks-cheap-translator":4}],41:[function(e,t,n){"use strict";const{images_url:i}=e("../../../../../admin-frontend/src/constants"),{data_url:s}=e("../../../../constants"),o=e("../../../generic-components/image-carousel"),{getArticleBody:a}=e("../../../lib/article-utils"),{fetch_json_or_error_text:r}=e("../../../lib/fetch"),{MentaloEngine:c}=e("mentalo-engine"),l=e("ks-cheap-translator"),_=l.trad.bind(l);t.exports=class{constructor(e){this.props=e,this.parse_body()}parse_body(){let e=a(this.props.body);var t=/\[PLAY_BUTTON\s\{.+\}\]/g;const n=e.match(t);n&&(this.build_play_button(JSON.parse(n[0].replace(/[\[\]PLAY_BUTTON\s]/g,""))),e=e.replace(t,"")),this.body=e}build_play_button(e){this.render_play_button={tag:"button",class:"play-button",contents:_("Jouer"),onclick:this.handle_click_play.bind(this,e.filename,e.engine)}}load_and_run_mentalo_game(e,t){const n=t.innerHTML;t.innerHTML="Loading ...",t.style.pointerEvents="none",r(`${s}/${e}`).then(e=>{const t=document.createElement("div");t.style.position="fixed",t.style.top=0,t.style.left=0,t.style.right=0,t.style.bottom=0,t.style.zIndex=10,t.style.display="flex",t.style.justifyContent="center",t.style.alignItems="center",t.id="kuadrado-tmp-game-player-container",document.body.appendChild(t),document.body.style.overflow="hidden";const n=new c({game_data:e,fullscreen:!0,frame_rate:30,container:t,on_quit_game:()=>{t.remove(),document.body.style.overflow="visible"}});n.init(),n.run_game()}).catch(e=>console.log(e)).finally(()=>{t.innerHTML=n,t.style.pointerEvents="unset"})}handle_click_play(e,t,n){"mentalo"===t?this.load_and_run_mentalo_game(e,n.target):console.log("Error, unkown engine")}render(){const{title:e,subtitle:t,images:n,details:s}=this.props;return{tag:"article",typeof:"VideoGame",additionalType:"Article",class:"game-article",contents:[{tag:"h2",property:"name",class:"game-title",contents:e},{tag:"div",class:"game-banner",contents:[{tag:"img",class:"pixelated",src:`${i}/${n[0]}`}]},{tag:"h3",class:"game-subtitle",contents:t,property:"alternativeHeadline"},{tag:"div",class:"game-description",property:"description",contents:[{tag:"p",style_rules:{margin:0},contents:this.body}].concat(this.render_play_button?[this.render_play_button]:[])},new o({images:n.map(e=>`${i}/${e}`)}).render(),0<s.length&&{tag:"div",class:"article-details",contents:[{tag:"h2",contents:"Details"},{tag:"ul",class:"details-list",contents:s.map(e=>({tag:"li",class:"detail",contents:[{tag:"label",contents:e.label},{tag:"div",class:"detail-value",contents:e.value}]}))}]}]}}}},{"../../../../../admin-frontend/src/constants":1,"../../../../constants":3,"../../../generic-components/image-carousel":37,"../../../lib/article-utils":38,"../../../lib/fetch":39,"ks-cheap-translator":4,"mentalo-engine":5}],42:[function(e,t,n){"use strict";const{loadArticles:s}=e("../../../lib/article-utils"),i=e("./game-article"),o=e("ks-cheap-translator");t.exports=class{constructor(e){this.props=e,this.state={articles:[]},this.id="game-articles-section",this.loadArticles()}loadArticles(){s("games",o.locale).then(e=>{this.state.articles=e,this.refresh()}).catch(e=>console.log(e))}renderPlaceholder(){return{tag:"article",class:"placeholder",contents:[{tag:"div"},{tag:"div"}]}}refresh(){obj2htm.subRender(this.render(),document.getElementById(this.id),{mode:"replace"})}render(){const{articles:e}=this.state;return{tag:"section",class:"game-articles page-contents-center",id:this.id,contents:0<e.length?e.map(e=>new i({...e}).render()):[this.renderPlaceholder()]}}}},{"../../../lib/article-utils":38,"./game-article":41,"ks-cheap-translator":4}],43:[function(e,t,n){"use strict";const{images_url:s}=e("../../../constants");var i=e("../../lib/web-page");const o=e("./components/game-articles"),a=e("ks-cheap-translator"),r=a.trad.bind(a);class c extends i{render(){return{tag:"div",id:"games-page",contents:[{tag:"div",class:"page-header logo-left",contents:[{tag:"div",class:"page-contents-center grid-wrapper",contents:[{tag:"div",class:"logo",contents:[{tag:"img",alt:"image game controller",src:`${s}/game_controller.svg`}]},{tag:"h1",contents:r("Jeux")},{tag:"p",contents:r("games-page-intro")}]}]},(new o).render()]}}}t.exports=c},{"../../../constants":3,"../../lib/web-page":40,"./components/game-articles":42,"ks-cheap-translator":4}],44:[function(e,t,n){"use strict";const s=e("../../run-page");e=e("./games");s(e)},{"../../run-page":45,"./games":43}],45:[function(e,t,n){"use strict";const s=e("object-to-html-renderer"),i=e("./template/template");t.exports=function(e){e=new i({page:new e});s.register("obj2htm"),obj2htm.setRenderCycleRoot(e),obj2htm.renderCycle()}},{"./template/template":47,"object-to-html-renderer":36}],46:[function(e,t,n){"use strict";const{images_url:s}=e("../../../constants"),a=e("ks-cheap-translator"),r=a.trad.bind(a),i=[{url:"/games/",text:"Jeux"},{url:"/education/",text:"Pédagogie"},{url:"/software-development/",text:"Software"}];t.exports=class{constructor(){this.initEventHandlers()}handleBurgerClick(){document.getElementById("nav-menu-list").classList.toggle("responsive-show")}initEventHandlers(){window.addEventListener("click",e=>{"nav-menu-list"===e.target.id||e.target.classList.contains("burger")||e.target.parentNode.classList.contains("burger")||document.getElementById("nav-menu-list").classList.remove("responsive-show")})}handle_chang_lang(e){a.update_translations(e).then(()=>{obj2htm.renderCycle()}).catch(e=>console.log(e))}renderHome(){return{tag:"div",class:"home",contents:[{tag:"a",href:"/",contents:[{tag:"img",alt:"Logo Kuadrado",src:`${s}/logo_kuadrado.svg`},{tag:"img",alt:"Kuadrado Software",class:"logo-text",src:`${s}/logo_kuadrado_txt.svg`}]}]}}renderMenu(e,i=!1,o=""){return{tag:"ul",id:"nav-menu-list",class:i?"submenu":"",contents:e.map(e=>{var{url:t,text:n,submenu:e}=e;const s=`${o}${t}`;return{tag:"li",class:i||window.location.pathname!==s?"":"active",contents:[{tag:"a",href:s,contents:r(n)}].concat(e?[this.renderMenu(e,!0,t)]:[])}}).concat({tag:"li",class:"lang-flags",contents:["fr","en"].map(e=>({tag:"img",src:`${s}/flag-${e}.svg`,class:a.locale===e?"selected":"",onclick:this.handle_chang_lang.bind(this,e)}))})}}renderResponsiveBurger(){return{tag:"div",class:"burger",onclick:this.handleBurgerClick.bind(this),contents:[{tag:"span",contents:"···"}]}}render(){return{tag:"nav",contents:[this.renderHome(),this.renderResponsiveBurger(),this.renderMenu(i)]}}}},{"../../../constants":3,"ks-cheap-translator":4}],47:[function(e,t,n){"use strict";const{in_construction:s}=e("../../config"),{images_url:i}=e("../../constants"),o=e("./components/navbar"),a=e("ks-cheap-translator"),r=a.trad.bind(a);t.exports=class{constructor(e){this.props=e}render(){return{tag:"main",contents:[{tag:"header",contents:[(new o).render()]},s&&{tag:"section",class:"warning-banner",contents:[{tag:"strong",class:"page-contents-center",contents:r("Site en construction ...")}]},{tag:"section",id:"page-container",contents:[this.props.page.render()]},{tag:"footer",contents:[{tag:"div",class:"logo",contents:[{tag:"img",alt:"logo Kuadrado",src:`${i}/logo_kuadrado.svg`},{tag:"img",class:"text-logo",alt:"Kuadrado Software",src:`${i}/logo_kuadrado_txt.svg`}]},{tag:"span",contents:"32 rue Simon Vialet, 07240 Vernoux en Vivarais. Ardèche, France"},{tag:"div",contents:[{tag:"strong",contents:"<blue>Contact : </blue>"},{tag:"a",href:"mailto:contact@kuadrado-software.fr",contents:"contact@kuadrado-software.fr"}]},{tag:"div",class:"social",contents:[{tag:"strong",contents:`<blue>${r("Sur les réseaux")} : </blue>`},{tag:"a",href:"https://www.linkedin.com/company/kuadrado-software",target:"_blank",contents:"in",title:"Linkedin"},{tag:"a",href:"https://mastodon.gamedev.place/@kuadrado_software",target:"_blank",contents:"m",title:"Mastodon"}]},{tag:"span",contents:`Copyleft 🄯 ${(new Date).getFullYear()} Kuadrado Software | 
+                                ${r("kuadrado-footer-copyleft")}`},{tag:"div",contents:[{tag:"span",contents:r("Ce site web est")+" "},{tag:"a",target:"_blank",style_rules:{fontWeight:"bold"},href:"https://gitlab.com/kuadrado-software/kuadrado-website/-/blob/master/README.md",contents:"OPEN SOURCE"}]}]}]}}}},{"../../config":2,"../../constants":3,"./components/navbar":46,"ks-cheap-translator":4}]},{},[44]);
\ No newline at end of file
diff --git a/public/main.js b/public/main.js
index 1e3f2ac..f94c5f5 100644
--- a/public/main.js
+++ b/public/main.js
@@ -1,846 +1,2 @@
-(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
-module.exports = {
-    build: {
-        protected_dirs: ["assets", "style", "views", "standard"],
-        default_meta_keys: ["title", "description", "image", "open_graph", "json_ld"],
-    },
-};
-
-},{}],2:[function(require,module,exports){
-module.exports = {
-    images_url: `/assets/images`,
-    data_url: `/assets/data`,
-    translations_url: "/assets/translations"
-};
-
-},{}],3:[function(require,module,exports){
-"use strict";
-/**
- * A tiny library to handle text translation.
- */
-
-/**
- * init parameter object type
- * @typedef TranslatorParam
- * @property {String[]} supported_languages - DEFAULT: ["en"] - An array of ISO 639 lowercase language codes
- * @property {String} locale ISO 639 lowercase language code - DEFAULT: "en"
- *
- * @property {Boolean} use_url_locale_fragment - DEFAULT: true -
- * If true, the 1st fragment of the window.location url will be parsed as a language code
- * Example:
- * window.location
- * > https://example.com/en/some-page...
- * then the /en/ part of the url will be taken in priority and locale will be set to "en".
- * window.location
- * > https://example.com/some-page...
- * No locale fragment will be found so locale will not be set from url fragment.
- * window.location
- * > https://example.com/some-page/en
- * Doesn't work, locale fragment must be the first framgment
- *
- * @property {String} local_storage_key  - DEFAULT: "translator-prefered-language"
- *  The key used to saved the current locale into local storage
- *
- * @property {String} translations_url - REQUIRED - the url of the directory containing the static json files for translations
- * Translations files are expected to be named with their corresponding locale code.
- * Example:
- * if supported_languages is set to ["en", "fr", "it"]
- * and translations_url is set to "https://example.com/translations/""
- * Then the expected translations files are
- * https://example.com/translations/en.json
- * https://example.com/translations/fr.json
- * https://example.com/translations/it.json
- * The json resources must simple key value maps, value being the translated text.
- */
-
-module.exports = {
-    locale: "en", // ISO 639 lowercase language code
-    supported_languages: ["en"],
-    translations: {},
-    translations_url: "",
-    use_url_locale_fragment: true,
-    local_storage_key: "translator-prefered-language",
-
-    /**
-     * Initialize the lib with params
-     * @param {TranslatorParam} params 
-     * @returns {Promise}
-     */
-    init(params) {
-        Object.entries(params).forEach(k_v => {
-            const [key, value] = k_v;
-            if ([
-                "supported_languages",
-                "use_url_locale_fragment",
-                "local_storage_key",
-                "translations_url"
-            ].includes(key)) {
-                this[key] = value;
-            }
-        });
-
-        this.translations_url = this.format_translations_url(this.translations_url);
-        this.supported_languages = this.format_supported_languages(this.supported_languages);
-
-        return new Promise((resolve, reject) => {
-            const loc =
-                (() => {// Locale from url priority 1
-                    if (this.use_url_locale_fragment) {
-                        const first_url_fragment = window.location.pathname.substring(1).split("/")[0];
-                        const fragment_is_locale = this.supported_languages.includes(first_url_fragment);
-                        return fragment_is_locale ? first_url_fragment : "";
-                    } else {
-                        return "";
-                    }
-                })()
-                || localStorage.getItem(this.local_storage_key) // Locale from storage priority 2
-                || (() => { // locale from navigator priority 3
-                    const navigator_locale = navigator.language.split("-")[0].toLocaleLowerCase();
-                    return this.supported_languages.includes(navigator_locale)
-                        ? navigator_locale
-                        : this.supported_languages[0] // Default if navigator locale is not supported
-                })();
-
-            fetch(`${this.translations_url}${loc}.json`)
-                .then(response => response.json())
-                .then(response => {
-                    this.locale = loc;
-                    this.translations = response;
-                    resolve();
-                })
-                .catch(err => {
-                    this.locale = "en";
-                    reject(err);
-                });
-        });
-    },
-
-    /**
-     * Return a lowercase string without dash.
-     * If given locale is en-EN, then "en" will be returned.
-     * @param {String} locale 
-     * @returns A lowercase string
-     */
-    format_locale(locale) {
-        return locale.split("-")[0].toLowerCase();
-    },
-
-    /**
-     * Appends a slash at the end of the given string if missing, and returns the url.
-     * @param {String} url 
-     * @returns {String}
-     */
-    format_translations_url(url) {
-        if (url.charAt(url.length - 1) !== "/") {
-            url += "/"
-        }
-        return url;
-    },
-
-    /**
-     * Return the array of language codes formatted as lowsercase language code.
-     * if ["en-EN", "it-IT"]is given, ["en", "it"] will b returned.
-     * @param {String[]} languages_codes 
-     * @returns {String[]}
-     */
-    format_supported_languages(languages_codes) {
-        return languages_codes.map(lc => this.format_locale(lc));
-    },
-
-    /**
-     * Fetches a new set of translations in case the wanted language changes
-     * @param {String} locale A lowercase language code
-     * @returns {Promise}
-     */
-    update_translations(locale) {
-        locale = this.format_locale(locale);
-        return new Promise((resolve, reject) => {
-            fetch(`${this.translations_url}${locale}.json`)
-                .then(response => response.json())
-                .then(response => {
-                    this.translations = response;
-                    this.locale = locale;
-
-                    localStorage.setItem(this.local_storage_key, locale);
-
-                    const split_path = window.location.pathname.substring(1).split("/");
-                    const first_url_fragment = split_path[0];
-                    const fragment_is_locale = this.supported_languages.includes(first_url_fragment);
-
-                    if (fragment_is_locale) {
-                        split_path.splice(0, 1, locale);
-                        const updated_path = split_path.join("/");
-                        window.history.replaceState(null, "", "/" + updated_path);
-                    }
-                    resolve();
-                })
-                .catch(err => {
-                    reject(err);
-                });
-        });
-    },
-
-    /**
-     * Tries to get the translation of the source string, or return the string as it.
-     * @param {String} text The source text to translate
-     * @param {Object} params Some dynamic element to insert in the text
-     * Params can be used if the translated text provide placeholder like {%some_word%}
-     * Example:
-     * translator.trad("Some trad key", {some_word: "a dynamic parameter"})
-     * -> translation for "Some trad key": "A translated text with {%some_word%}"
-     * -> will be return as : "A translated text with a dynamic parameter"
-     * @returns {String}
-     */
-    trad: function (text, params = {}) {
-        text = this.translations[text] || text;
-
-        Object.keys(params).forEach(k => {
-            text = text.replace(`{%${k}%}`, params[k]);
-        });
-
-        return text;
-    }
-};
-},{}],4:[function(require,module,exports){
-"use strict";
-
-module.exports = {
-    register_key: "objectToHtmlRender",
-
-    /**
-     * Register "this" as a window scope accessible variable named by the given key, or default.
-     * @param {String} key 
-     */
-    register(key) {
-        const register_key = key || this.register_key;
-        window[register_key] = this;
-    },
-
-    /**
-     * This must be called before any other method in order to initialize the lib.
-     * It provides the root of the rendering cycle as a Javascript object.
-     * @param {Object} renderCycleRoot A JS component with a render method.
-     */
-    setRenderCycleRoot(renderCycleRoot) {
-        this.renderCycleRoot = renderCycleRoot;
-    },
-
-    event_name: "objtohtml-render-cycle",
-
-    /**
-     * Set a custom event name for the event that is trigger on render cycle.
-     * Default is "objtohtml-render-cycle".
-     * @param {String} evt_name 
-     */
-    setEventName(evt_name) {
-        this.event_name = evt_name;
-    },
-
-    /**
-     * This is the core agorithm that read an javascript Object and convert it into an HTML element.
-     * @param {Object} obj The object representing the html element must be formatted like:
-     * {
-     *      tag: String // The name of the html tag, Any valid html tag should work. div, section, br, ul, li...
-     *      xmlns: String // This can replace the tag key if the element is an element with a namespace URI, for example an <svg> tag.
-     *                      See https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS for more information
-     *      style_rules: Object // a object providing css attributes. The attributes names must be in JS syntax,
-     *                              like maxHeight: "500px", backgrouncColor: "#ff2d56",  margin: 0,  etc.
-     *      contents: Array or String // This reprensents the contents that will be nested in the created html element.
-     *                                   <div>{contents}</div>
-     *                                   The contents can be an array of other objects reprenting elements (with tag, contents, etc)
-     *                                   or it can be a simple string.
-     *      // All other attributes will be parsed as html attributes. They can be anything like onclick, href, onchange, title...
-     *      // or they can also define custom html5 attributes, like data, my_custom_attr or anything.
-     * }
-     * @returns {HTMLElement} The output html node.
-     */
-    objectToHtml(obj) {
-        if (!obj) return document.createElement("span"); // in case of invalid input, don't block the whole process.
-        const objectToHtml = this.objectToHtml.bind(this);
-        const { tag, xmlns } = obj;
-        const node = xmlns !== undefined ? document.createElementNS(xmlns, tag) : document.createElement(tag);
-        const excludeKeys = ["tag", "contents", "style_rules", "state", "xmlns"];
-
-        Object.keys(obj)
-            .filter(attr => !excludeKeys.includes(attr))
-            .forEach(attr => {
-                switch (attr) {
-                    case "class":
-                        node.classList.add(...obj[attr].split(" ").filter(s => s !== ""));
-                        break;
-                    case "on_render":
-                        if (!obj.id) {
-                            node.id = `${btoa(JSON.stringify(obj).slice(0, 127)).replace(/\=/g, '')}${window.performance.now()}`;
-                        }
-                        if (typeof obj.on_render !== "function") {
-                            console.error("The on_render attribute must be a function")
-                        } else {
-                            this.attach_on_render_callback(node, obj.on_render);
-                        }
-                        break;
-                    default:
-                        if (xmlns !== undefined) {
-                            node.setAttributeNS(null, attr, obj[attr])
-                        } else {
-                            node[attr] = obj[attr];
-                        }
-                }
-            });
-        if (obj.contents && typeof obj.contents === "string") {
-            node.innerHTML = obj.contents;
-        } else {
-            obj.contents &&
-                obj.contents.length > 0 &&
-                obj.contents.forEach(el => {
-                    switch (typeof el) {
-                        case "string":
-                            node.innerHTML = el;
-                            break;
-                        case "object":
-                            if (xmlns !== undefined) {
-                                el = Object.assign(el, { xmlns })
-                            }
-                            node.appendChild(objectToHtml(el));
-                            break;
-                    }
-                });
-        }
-
-        if (obj.style_rules) {
-            Object.keys(obj.style_rules).forEach(rule => {
-                node.style[rule] = obj.style_rules[rule];
-            });
-        }
-
-        return node;
-    },
-
-    on_render_callbacks: [],
-
-    /**
-     * This is called if the on_render attribute of a component is set.
-     * @param {HTMLElement} node The created html element
-     * @param {Function} callback The callback defined in the js component to render
-     */
-    attach_on_render_callback(node, callback) {
-        const callback_handler = {
-            callback: e => {
-                if (e.detail.outputNode === node || e.detail.outputNode.querySelector(`#${node.id}`)) {
-                    callback(node);
-                    const handler_index = this.on_render_callbacks.indexOf((this.on_render_callbacks.find(cb => cb.node === node)));
-                    if (handler_index === -1) {
-                        console.warn("A callback was registered for node with id " + node.id + " but callbacck handler is undefined.")
-                    } else {
-                        window.removeEventListener(this.event_name, this.on_render_callbacks[handler_index].callback)
-                        this.on_render_callbacks.splice(handler_index, 1);
-                    }
-                }
-            },
-            node,
-        };
-
-        const len = this.on_render_callbacks.push(callback_handler);
-        window.addEventListener(this.event_name, this.on_render_callbacks[len - 1].callback);
-    },
-
-    /**
-     * If a main element exists in the html document, it will be used as rendering root.
-     * If not, it will be created and inserted.
-     */
-    renderCycle: function () {
-        const main_elmt = document.getElementsByTagName("main")[0] || (function () {
-            const created_main = document.createElement("main");
-            document.body.appendChild(created_main);
-            return created_main;
-        })();
-
-        this.subRender(this.renderCycleRoot.render(), main_elmt, { mode: "replace" });
-    },
-
-    /**
-     * This method behaves like the renderCycle() method, but rather that starting the rendering cycle from the root component,
-    * it can start from any component of the tree. The root component must be given as the first argument, the second argument be
-    * be a valid html element in the dom and will be used as the insertion target.
-     * @param {Object} object An object providing a render method returning an object representation of the html to insert
-     * @param {HTMLElement} htmlNode The htlm element to update
-     * @param {Object} options can be used the define the insertion mode, default is set to "append" and can be set to "override",
-         * "insert-before" (must be defined along with an insertIndex key (integer)),
-         * "adjacent" (must be defined along with an insertLocation key (String)), "replace" or "remove".
-         * In case of "remove", the first argument "object" is not used and can be set to null, undefined or {}...
-     */
-    subRender(object, htmlNode, options = { mode: "append" }) {
-        let outputNode = null;
-
-        const get_insert = () => {
-            outputNode = this.objectToHtml(object);
-            return outputNode;
-        };
-
-        switch (options.mode) {
-            case "append":
-                htmlNode.appendChild(get_insert());
-                break;
-            case "override":
-                htmlNode.innerHTML = "";
-                htmlNode.appendChild(get_insert());
-                break;
-            case "insert-before":
-                htmlNode.insertBefore(get_insert(), htmlNode.childNodes[options.insertIndex]);
-                break;
-            case "adjacent":
-                /**
-                 * options.insertLocation must be one of:
-                 *
-                 * afterbegin
-                 * afterend
-                 * beforebegin
-                 * beforeend
-                 */
-                htmlNode.insertAdjacentHTML(options.insertLocation, get_insert());
-                break;
-            case "replace":
-                htmlNode.parentNode.replaceChild(get_insert(), htmlNode);
-                break;
-            case "remove":
-                htmlNode.remove();
-                break;
-        }
-        const evt_name = this.event_name;
-        const event = new CustomEvent(evt_name, {
-            detail: {
-                inputObject: object,
-                outputNode,
-                insertOptions: options,
-                targetNode: htmlNode,
-            }
-        });
-
-        window.dispatchEvent(event);
-    },
-};
-},{}],5:[function(require,module,exports){
-"use strict";
-
-const { images_url } = require("../../constants");
-
-class ThemeCard {
-    constructor(props) {
-        this.props = props;
-    }
-
-    render() {
-        return {
-            tag: "a",
-            class: "theme-card",
-            href: this.props.href,
-            contents: [
-                {
-                    tag: "div",
-                    class: "card-img",
-                    contents: [{ tag: "img", alt: `thematic image ${this.props.img.replace(/\.[A-Za-z]+/, "")}`, src: `${images_url}/${this.props.img}` }],
-                },
-                {
-                    tag: "div",
-                    class: "card-title",
-                    contents: [{ tag: "h2", class: "section-title", contents: this.props.title }],
-                },
-                {
-                    tag: "div",
-                    class: "card-description",
-                    contents: [{ tag: "p", contents: this.props.description }],
-                },
-            ],
-        };
-    }
-}
-
-module.exports = ThemeCard;
-
-},{"../../constants":2}],6:[function(require,module,exports){
-"use strict";
-
-const { images_url } = require("../constants");
-const ThemeCard = require("./home-page-components/theme-card");
-const WebPage = require("./lib/web-page");
-const translator = require("ks-cheap-translator");
-const t = translator.trad.bind(translator);
-
-class HomePage extends WebPage {
-    constructor() {
-        super({ id: "home-page" });
-    }
-
-    render() {
-        return {
-            tag: "div",
-            id: this.id,
-            contents: [
-                {
-                    tag: "div",
-                    class: "page-header",
-                    contents: [
-                        {
-                            tag: "div",
-                            class: "big-logo page-contents-center",
-                            contents: [
-                                {
-                                    tag: "img",
-                                    alt: "logo Kuadrado",
-                                    src: `${images_url}/logo_kuadrado.svg`,
-                                },
-                                {
-                                    tag: "img",
-                                    class: "logo-text",
-                                    alt: "Kuadrado",
-                                    src: `${images_url}/logo_kuadrado_txt.svg`,
-                                },
-                            ],
-                        },
-                        { tag: "h1", contents: "Kuadrado Software", class: "page-contents-center" },
-                        {
-                            tag: "p",
-                            class: "page-contents-center",
-                            contents: t("kuadrado-home-description"),
-                        },
-                        {
-                            tag: "ul",
-                            class: "philo-bubbles",
-                            contents: [t("Simplicité"), t("Légèreté"), t("Écologie")].map(word => {
-                                return {
-                                    tag: "li",
-                                    contents: [{ tag: "span", contents: word }],
-                                };
-                            }),
-                        },
-                    ],
-                },
-                {
-                    tag: "section",
-                    class: "page-contents-center poles",
-                    contents: [
-                        {
-                            title: t("Jeux"),
-                            img: "game_controller.svg",
-                            href: "/games/",
-                            description:
-                                t("games-description"),
-                        },
-                        {
-                            title: t("Pédagogie"),
-                            img: "brain.svg",
-                            href: "/education/",
-                            description: t("education-description"),
-                        },
-                        {
-                            title: "Software",
-                            img: "meca_proc.svg",
-                            href: "/software-development/",
-                            description: t("software-description"),
-                        },
-                    ].map(cardProps => new ThemeCard(cardProps).render()),
-                },
-            ],
-        };
-    }
-}
-
-module.exports = HomePage;
-
-},{"../constants":2,"./home-page-components/theme-card":5,"./lib/web-page":7,"ks-cheap-translator":3}],7:[function(require,module,exports){
-"use strict";
-const translator = require("ks-cheap-translator");
-const { translations_url } = require("../../constants");
-
-class WebPage {
-    constructor(args) {
-        Object.assign(this, args);
-
-        if (!this.id) {
-            this.id = "webpage-" + performance.now();
-        }
-
-        translator.init({
-            translations_url,
-            supported_languages: ["fr", "en"],
-        }).then(this.refresh_all.bind(this));
-    }
-
-    refresh() {
-        obj2htm.subRender(this.render(), document.getElementById(this.id), { mode: "replace" })
-    }
-
-    refresh_all() {
-        obj2htm.renderCycle()
-    }
-}
-
-module.exports = WebPage;
-},{"../../constants":2,"ks-cheap-translator":3}],8:[function(require,module,exports){
-"use strict";
-
-const HomePage = require("./homepage");
-const runPage = require("./run-page");
-
-runPage(HomePage);
-
-},{"./homepage":6,"./run-page":9}],9:[function(require,module,exports){
-"use strict";
-
-const renderer = require("object-to-html-renderer")
-const Template = require("./template/template");
-
-module.exports = function runPage(PageComponent) {
-    const template = new Template({ page: new PageComponent() });
-    renderer.register("obj2htm")
-    obj2htm.setRenderCycleRoot(template);
-    obj2htm.renderCycle();
-};
-
-},{"./template/template":11,"object-to-html-renderer":4}],10:[function(require,module,exports){
-"use strict";
-
-const { images_url } = require("../../../constants");
-const translator = require("ks-cheap-translator");
-const t = translator.trad.bind(translator);
-
-const NAV_MENU_ITEMS = [
-    { url: "/games/", text: "Jeux" },
-    {
-        url: "/education/",
-        text: "Pédagogie",
-    },
-    { url: "/software-development/", text: "Software" }
-];
-
-class NavBar {
-    constructor() {
-        this.initEventHandlers();
-    }
-
-    handleBurgerClick() {
-        document.getElementById("nav-menu-list").classList.toggle("responsive-show");
-    }
-
-    initEventHandlers() {
-        window.addEventListener("click", event => {
-            if (
-                event.target.id !== "nav-menu-list" &&
-                !event.target.classList.contains("burger") &&
-                !event.target.parentNode.classList.contains("burger")
-            ) {
-                document.getElementById("nav-menu-list").classList.remove("responsive-show");
-            }
-        });
-    }
-
-    handle_chang_lang(lang) {
-        translator.update_translations(lang).then(() => {
-            obj2htm.renderCycle();
-        }).catch(err => console.log(err));
-    }
-
-    renderHome() {
-        return {
-            tag: "div",
-            class: "home",
-            contents: [
-                {
-                    tag: "a",
-                    href: "/",
-                    contents: [
-                        {
-                            tag: "img",
-                            alt: "Logo Kuadrado",
-                            src: `${images_url}/logo_kuadrado.svg`,
-                        },
-                        {
-                            tag: "img",
-                            alt: "Kuadrado Software",
-                            class: "logo-text",
-                            src: `${images_url}/logo_kuadrado_txt.svg`,
-                        },
-                    ],
-                },
-            ],
-        };
-    }
-
-    renderMenu(menuItemsArray, isSubmenu = false, parentUrl = "") {
-        return {
-            tag: "ul",
-            id: "nav-menu-list",
-            class: isSubmenu ? "submenu" : "",
-            contents: menuItemsArray.map(item => {
-                const { url, text, submenu } = item;
-                const href = `${parentUrl}${url}`;
-                return {
-                    tag: "li",
-                    class: !isSubmenu && window.location.pathname === href ? "active" : "",
-                    contents: [
-                        {
-                            tag: "a",
-                            href,
-                            contents: t(text),
-                        },
-                    ].concat(submenu ? [this.renderMenu(submenu, true, url)] : []),
-                };
-            }).concat({
-                tag: "li",
-                class: "lang-flags",
-                contents: ["fr", "en"].map(lang => {
-                    return {
-                        tag: "img", src: `${images_url}/flag-${lang}.svg`,
-                        class: translator.locale === lang ? "selected" : "",
-                        onclick: this.handle_chang_lang.bind(this, lang)
-                    }
-                })
-            }),
-        };
-    }
-
-    renderResponsiveBurger() {
-        return {
-            tag: "div",
-            class: "burger",
-            onclick: this.handleBurgerClick.bind(this),
-            contents: [{ tag: "span", contents: "···" }],
-        };
-    }
-
-    render() {
-        return {
-            tag: "nav",
-            contents: [
-                this.renderHome(),
-                this.renderResponsiveBurger(),
-                this.renderMenu(NAV_MENU_ITEMS),
-            ],
-        };
-    }
-}
-
-module.exports = NavBar;
-
-},{"../../../constants":2,"ks-cheap-translator":3}],11:[function(require,module,exports){
-"use strict";
-
-const { in_construction } = require("../../config");
-const { images_url } = require("../../constants");
-const NavBar = require("./components/navbar");
-const translator = require("ks-cheap-translator");
-const t = translator.trad.bind(translator)
-
-class Template {
-    constructor(props) {
-        this.props = props;
-    }
-    render() {
-        return {
-            tag: "main",
-            contents: [
-                {
-                    tag: "header",
-                    contents: [new NavBar().render()],
-                },
-                in_construction && {
-                    tag: "section",
-                    class: "warning-banner",
-                    contents: [
-                        {
-                            tag: "strong",
-                            class: "page-contents-center",
-                            contents: t("Site en construction ..."),
-                        },
-                    ],
-                },
-                {
-                    tag: "section",
-                    id: "page-container",
-                    contents: [this.props.page.render()],
-                },
-                {
-                    tag: "footer",
-                    contents: [
-                        {
-                            tag: "div",
-                            class: "logo",
-                            contents: [
-                                {
-                                    tag: "img",
-                                    alt: `logo Kuadrado`,
-                                    src: `${images_url}/logo_kuadrado.svg`,
-                                },
-                                {
-                                    tag: "img",
-                                    class: "text-logo",
-                                    alt: "Kuadrado Software",
-                                    src: `${images_url}/logo_kuadrado_txt.svg`,
-                                },
-                            ],
-                        },
-                        {
-                            tag: "span",
-                            contents: "32 rue Simon Vialet, 07240 Vernoux en Vivarais. Ardèche, France",
-                        },
-                        {
-                            tag: "div",
-                            contents: [
-                                { tag: "strong", contents: "<blue>Contact : </blue>" },
-                                {
-                                    tag: "a",
-                                    href: "mailto:contact@kuadrado-software.fr",
-                                    contents: "contact@kuadrado-software.fr",
-                                },
-                            ],
-                        },
-                        {
-                            tag: "div",
-                            class: "social",
-                            contents: [
-                                {
-                                    tag: "strong",
-                                    contents: `<blue>${t("Sur les réseaux")} : </blue>`,
-                                },
-                                {
-                                    tag: "a",
-                                    href: "https://www.linkedin.com/company/kuadrado-software",
-                                    target: "_blank",
-                                    contents: "in",
-                                    title: "Linkedin",
-                                },
-                                {
-                                    tag: "a",
-                                    href: "https://mastodon.gamedev.place/@kuadrado_software",
-                                    target: "_blank",
-                                    contents: "m",
-                                    title: "Mastodon",
-                                }
-                            ],
-                        },
-                        {
-                            tag: "span",
-                            contents: `Copyleft 🄯 ${new Date()
-                                .getFullYear()} Kuadrado Software | 
-                                ${t("kuadrado-footer-copyleft")}`,
-                        },
-                        {
-                            tag: "div", contents: [
-                                { tag: "span", contents: t("Ce site web est") + " " },
-                                {
-                                    tag: "a", target: "_blank",
-                                    style_rules: { fontWeight: "bold" },
-                                    href: "https://gitlab.com/kuadrado-software/kuadrado-website/-/blob/master/README.md",
-                                    contents: "OPEN SOURCE"
-                                }
-                            ]
-                        }
-                    ],
-                },
-            ],
-        };
-    }
-}
-
-module.exports = Template;
-
-},{"../../config":1,"../../constants":2,"./components/navbar":10,"ks-cheap-translator":3}]},{},[8]);
+!function s(a,r,o){function c(e,t){if(!r[e]){if(!a[e]){var n="function"==typeof require&&require;if(!t&&n)return n(e,!0);if(i)return i(e,!0);throw(n=new Error("Cannot find module '"+e+"'")).code="MODULE_NOT_FOUND",n}n=r[e]={exports:{}},a[e][0].call(n.exports,function(t){return c(a[e][1][t]||t)},n,n.exports,s,a,r,o)}return r[e].exports}for(var i="function"==typeof require&&require,t=0;t<o.length;t++)c(o[t]);return c}({1:[function(t,e,n){e.exports={build:{protected_dirs:["assets","style","views","standard"],default_meta_keys:["title","description","image","open_graph","json_ld"]}}},{}],2:[function(t,e,n){e.exports={images_url:"/assets/images",data_url:"/assets/data",translations_url:"/assets/translations"}},{}],3:[function(t,e,n){"use strict";e.exports={locale:"en",supported_languages:["en"],translations:{},translations_url:"",use_url_locale_fragment:!0,local_storage_key:"translator-prefered-language",init(t){return Object.entries(t).forEach(t=>{var[e,t]=t;["supported_languages","use_url_locale_fragment","local_storage_key","translations_url"].includes(e)&&(this[e]=t)}),this.translations_url=this.format_translations_url(this.translations_url),this.supported_languages=this.format_supported_languages(this.supported_languages),new Promise((e,n)=>{const s=(()=>{if(this.use_url_locale_fragment){var t=window.location.pathname.substring(1).split("/")[0];return this.supported_languages.includes(t)?t:""}return""})()||localStorage.getItem(this.local_storage_key)||(t=navigator.language.split("-")[0].toLocaleLowerCase(),this.supported_languages.includes(t)?t:this.supported_languages[0]);var t;fetch(`${this.translations_url}${s}.json`).then(t=>t.json()).then(t=>{this.locale=s,this.translations=t,e()}).catch(t=>{this.locale="en",n(t)})})},format_locale(t){return t.split("-")[0].toLowerCase()},format_translations_url(t){return"/"!==t.charAt(t.length-1)&&(t+="/"),t},format_supported_languages(t){return t.map(t=>this.format_locale(t))},update_translations(s){return s=this.format_locale(s),new Promise((n,e)=>{fetch(`${this.translations_url}${s}.json`).then(t=>t.json()).then(t=>{this.translations=t,this.locale=s,localStorage.setItem(this.local_storage_key,s);const e=window.location.pathname.substring(1).split("/");t=e[0];this.supported_languages.includes(t)&&(e.splice(0,1,s),t=e.join("/"),window.history.replaceState(null,"","/"+t)),n()}).catch(t=>{e(t)})})},trad:function(e,n={}){return e=this.translations[e]||e,Object.keys(n).forEach(t=>{e=e.replace(`{%${t}%}`,n[t])}),e}}},{}],4:[function(t,e,n){"use strict";e.exports={register_key:"objectToHtmlRender",register(t){t=t||this.register_key;window[t]=this},setRenderCycleRoot(t){this.renderCycleRoot=t},event_name:"objtohtml-render-cycle",setEventName(t){this.event_name=t},objectToHtml(e){if(!e)return document.createElement("span");const n=this.objectToHtml.bind(this),{tag:t,xmlns:s}=e,a=void 0!==s?document.createElementNS(s,t):document.createElement(t),r=["tag","contents","style_rules","state","xmlns"];return Object.keys(e).filter(t=>!r.includes(t)).forEach(t=>{switch(t){case"class":a.classList.add(...e[t].split(" ").filter(t=>""!==t));break;case"on_render":e.id||(a.id=`${btoa(JSON.stringify(e).slice(0,127)).replace(/\=/g,"")}${window.performance.now()}`),"function"!=typeof e.on_render?console.error("The on_render attribute must be a function"):this.attach_on_render_callback(a,e.on_render);break;default:void 0!==s?a.setAttributeNS(null,t,e[t]):a[t]=e[t]}}),e.contents&&"string"==typeof e.contents?a.innerHTML=e.contents:e.contents&&0<e.contents.length&&e.contents.forEach(t=>{switch(typeof t){case"string":a.innerHTML=t;break;case"object":void 0!==s&&(t=Object.assign(t,{xmlns:s})),a.appendChild(n(t))}}),e.style_rules&&Object.keys(e.style_rules).forEach(t=>{a.style[t]=e.style_rules[t]}),a},on_render_callbacks:[],attach_on_render_callback(e,n){var t={callback:t=>{t.detail.outputNode!==e&&!t.detail.outputNode.querySelector(`#${e.id}`)||(n(e),-1===(t=this.on_render_callbacks.indexOf(this.on_render_callbacks.find(t=>t.node===e)))?console.warn("A callback was registered for node with id "+e.id+" but callbacck handler is undefined."):(window.removeEventListener(this.event_name,this.on_render_callbacks[t].callback),this.on_render_callbacks.splice(t,1)))},node:e},t=this.on_render_callbacks.push(t);window.addEventListener(this.event_name,this.on_render_callbacks[t-1].callback)},renderCycle:function(){var t,t=document.getElementsByTagName("main")[0]||(t=document.createElement("main"),document.body.appendChild(t),t);this.subRender(this.renderCycleRoot.render(),t,{mode:"replace"})},subRender(t,e,n={mode:"append"}){let s=null;var a=()=>(s=this.objectToHtml(t),s);switch(n.mode){case"append":e.appendChild(a());break;case"override":e.innerHTML="",e.appendChild(a());break;case"insert-before":e.insertBefore(a(),e.childNodes[n.insertIndex]);break;case"adjacent":e.insertAdjacentHTML(n.insertLocation,a());break;case"replace":e.parentNode.replaceChild(a(),e);break;case"remove":e.remove()}var r=this.event_name,r=new CustomEvent(r,{detail:{inputObject:t,outputNode:s,insertOptions:n,targetNode:e}});window.dispatchEvent(r)}}},{}],5:[function(t,e,n){"use strict";const{images_url:s}=t("../../constants");e.exports=class{constructor(t){this.props=t}render(){return{tag:"a",class:"theme-card",href:this.props.href,contents:[{tag:"div",class:"card-img",contents:[{tag:"img",alt:`thematic image ${this.props.img.replace(/\.[A-Za-z]+/,"")}`,src:`${s}/${this.props.img}`}]},{tag:"div",class:"card-title",contents:[{tag:"h2",class:"section-title",contents:this.props.title}]},{tag:"div",class:"card-description",contents:[{tag:"p",contents:this.props.description}]}]}}}},{"../../constants":2}],6:[function(t,e,n){"use strict";const{images_url:s}=t("../constants"),a=t("./home-page-components/theme-card");var r=t("./lib/web-page");const o=t("ks-cheap-translator"),c=o.trad.bind(o);class i extends r{constructor(){super({id:"home-page"})}render(){return{tag:"div",id:this.id,contents:[{tag:"div",class:"page-header",contents:[{tag:"div",class:"big-logo page-contents-center",contents:[{tag:"img",alt:"logo Kuadrado",src:`${s}/logo_kuadrado.svg`},{tag:"img",class:"logo-text",alt:"Kuadrado",src:`${s}/logo_kuadrado_txt.svg`}]},{tag:"h1",contents:"Kuadrado Software",class:"page-contents-center"},{tag:"p",class:"page-contents-center",contents:c("kuadrado-home-description")},{tag:"ul",class:"philo-bubbles",contents:[c("Simplicité"),c("Légèreté"),c("Écologie")].map(t=>({tag:"li",contents:[{tag:"span",contents:t}]}))}]},{tag:"section",class:"page-contents-center poles",contents:[{title:c("Jeux"),img:"game_controller.svg",href:"/games/",description:c("games-description")},{title:c("Pédagogie"),img:"brain.svg",href:"/education/",description:c("education-description")},{title:"Software",img:"meca_proc.svg",href:"/software-development/",description:c("software-description")}].map(t=>new a(t).render())}]}}}e.exports=i},{"../constants":2,"./home-page-components/theme-card":5,"./lib/web-page":7,"ks-cheap-translator":3}],7:[function(t,e,n){"use strict";const s=t("ks-cheap-translator"),{translations_url:a}=t("../../constants");e.exports=class{constructor(t){Object.assign(this,t),this.id||(this.id="webpage-"+performance.now()),s.init({translations_url:a,supported_languages:["fr","en"]}).then(this.refresh_all.bind(this))}refresh(){obj2htm.subRender(this.render(),document.getElementById(this.id),{mode:"replace"})}refresh_all(){obj2htm.renderCycle()}}},{"../../constants":2,"ks-cheap-translator":3}],8:[function(t,e,n){"use strict";var s=t("./homepage");const a=t("./run-page");a(s)},{"./homepage":6,"./run-page":9}],9:[function(t,e,n){"use strict";const s=t("object-to-html-renderer"),a=t("./template/template");e.exports=function(t){t=new a({page:new t});s.register("obj2htm"),obj2htm.setRenderCycleRoot(t),obj2htm.renderCycle()}},{"./template/template":11,"object-to-html-renderer":4}],10:[function(t,e,n){"use strict";const{images_url:s}=t("../../../constants"),o=t("ks-cheap-translator"),c=o.trad.bind(o),a=[{url:"/games/",text:"Jeux"},{url:"/education/",text:"Pédagogie"},{url:"/software-development/",text:"Software"}];e.exports=class{constructor(){this.initEventHandlers()}handleBurgerClick(){document.getElementById("nav-menu-list").classList.toggle("responsive-show")}initEventHandlers(){window.addEventListener("click",t=>{"nav-menu-list"===t.target.id||t.target.classList.contains("burger")||t.target.parentNode.classList.contains("burger")||document.getElementById("nav-menu-list").classList.remove("responsive-show")})}handle_chang_lang(t){o.update_translations(t).then(()=>{obj2htm.renderCycle()}).catch(t=>console.log(t))}renderHome(){return{tag:"div",class:"home",contents:[{tag:"a",href:"/",contents:[{tag:"img",alt:"Logo Kuadrado",src:`${s}/logo_kuadrado.svg`},{tag:"img",alt:"Kuadrado Software",class:"logo-text",src:`${s}/logo_kuadrado_txt.svg`}]}]}}renderMenu(t,a=!1,r=""){return{tag:"ul",id:"nav-menu-list",class:a?"submenu":"",contents:t.map(t=>{var{url:e,text:n,submenu:t}=t;const s=`${r}${e}`;return{tag:"li",class:a||window.location.pathname!==s?"":"active",contents:[{tag:"a",href:s,contents:c(n)}].concat(t?[this.renderMenu(t,!0,e)]:[])}}).concat({tag:"li",class:"lang-flags",contents:["fr","en"].map(t=>({tag:"img",src:`${s}/flag-${t}.svg`,class:o.locale===t?"selected":"",onclick:this.handle_chang_lang.bind(this,t)}))})}}renderResponsiveBurger(){return{tag:"div",class:"burger",onclick:this.handleBurgerClick.bind(this),contents:[{tag:"span",contents:"···"}]}}render(){return{tag:"nav",contents:[this.renderHome(),this.renderResponsiveBurger(),this.renderMenu(a)]}}}},{"../../../constants":2,"ks-cheap-translator":3}],11:[function(t,e,n){"use strict";const{in_construction:s}=t("../../config"),{images_url:a}=t("../../constants"),r=t("./components/navbar"),o=t("ks-cheap-translator"),c=o.trad.bind(o);e.exports=class{constructor(t){this.props=t}render(){return{tag:"main",contents:[{tag:"header",contents:[(new r).render()]},s&&{tag:"section",class:"warning-banner",contents:[{tag:"strong",class:"page-contents-center",contents:c("Site en construction ...")}]},{tag:"section",id:"page-container",contents:[this.props.page.render()]},{tag:"footer",contents:[{tag:"div",class:"logo",contents:[{tag:"img",alt:"logo Kuadrado",src:`${a}/logo_kuadrado.svg`},{tag:"img",class:"text-logo",alt:"Kuadrado Software",src:`${a}/logo_kuadrado_txt.svg`}]},{tag:"span",contents:"32 rue Simon Vialet, 07240 Vernoux en Vivarais. Ardèche, France"},{tag:"div",contents:[{tag:"strong",contents:"<blue>Contact : </blue>"},{tag:"a",href:"mailto:contact@kuadrado-software.fr",contents:"contact@kuadrado-software.fr"}]},{tag:"div",class:"social",contents:[{tag:"strong",contents:`<blue>${c("Sur les réseaux")} : </blue>`},{tag:"a",href:"https://www.linkedin.com/company/kuadrado-software",target:"_blank",contents:"in",title:"Linkedin"},{tag:"a",href:"https://mastodon.gamedev.place/@kuadrado_software",target:"_blank",contents:"m",title:"Mastodon"}]},{tag:"span",contents:`Copyleft 🄯 ${(new Date).getFullYear()} Kuadrado Software | 
+                                ${c("kuadrado-footer-copyleft")}`},{tag:"div",contents:[{tag:"span",contents:c("Ce site web est")+" "},{tag:"a",target:"_blank",style_rules:{fontWeight:"bold"},href:"https://gitlab.com/kuadrado-software/kuadrado-website/-/blob/master/README.md",contents:"OPEN SOURCE"}]}]}]}}}},{"../../config":1,"../../constants":2,"./components/navbar":10,"ks-cheap-translator":3}]},{},[8]);
\ No newline at end of file
diff --git a/public/software-development/software-development.js b/public/software-development/software-development.js
index c2ed856..5fa85b3 100644
--- a/public/software-development/software-development.js
+++ b/public/software-development/software-development.js
@@ -1,984 +1,2 @@
-(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
-module.exports = {
-    images_url: "/assets/images"
-}
-},{}],2:[function(require,module,exports){
-module.exports = {
-    build: {
-        protected_dirs: ["assets", "style", "views", "standard"],
-        default_meta_keys: ["title", "description", "image", "open_graph", "json_ld"],
-    },
-};
-
-},{}],3:[function(require,module,exports){
-module.exports = {
-    images_url: `/assets/images`,
-    data_url: `/assets/data`,
-    translations_url: "/assets/translations"
-};
-
-},{}],4:[function(require,module,exports){
-"use strict";
-/**
- * A tiny library to handle text translation.
- */
-
-/**
- * init parameter object type
- * @typedef TranslatorParam
- * @property {String[]} supported_languages - DEFAULT: ["en"] - An array of ISO 639 lowercase language codes
- * @property {String} locale ISO 639 lowercase language code - DEFAULT: "en"
- *
- * @property {Boolean} use_url_locale_fragment - DEFAULT: true -
- * If true, the 1st fragment of the window.location url will be parsed as a language code
- * Example:
- * window.location
- * > https://example.com/en/some-page...
- * then the /en/ part of the url will be taken in priority and locale will be set to "en".
- * window.location
- * > https://example.com/some-page...
- * No locale fragment will be found so locale will not be set from url fragment.
- * window.location
- * > https://example.com/some-page/en
- * Doesn't work, locale fragment must be the first framgment
- *
- * @property {String} local_storage_key  - DEFAULT: "translator-prefered-language"
- *  The key used to saved the current locale into local storage
- *
- * @property {String} translations_url - REQUIRED - the url of the directory containing the static json files for translations
- * Translations files are expected to be named with their corresponding locale code.
- * Example:
- * if supported_languages is set to ["en", "fr", "it"]
- * and translations_url is set to "https://example.com/translations/""
- * Then the expected translations files are
- * https://example.com/translations/en.json
- * https://example.com/translations/fr.json
- * https://example.com/translations/it.json
- * The json resources must simple key value maps, value being the translated text.
- */
-
-module.exports = {
-    locale: "en", // ISO 639 lowercase language code
-    supported_languages: ["en"],
-    translations: {},
-    translations_url: "",
-    use_url_locale_fragment: true,
-    local_storage_key: "translator-prefered-language",
-
-    /**
-     * Initialize the lib with params
-     * @param {TranslatorParam} params 
-     * @returns {Promise}
-     */
-    init(params) {
-        Object.entries(params).forEach(k_v => {
-            const [key, value] = k_v;
-            if ([
-                "supported_languages",
-                "use_url_locale_fragment",
-                "local_storage_key",
-                "translations_url"
-            ].includes(key)) {
-                this[key] = value;
-            }
-        });
-
-        this.translations_url = this.format_translations_url(this.translations_url);
-        this.supported_languages = this.format_supported_languages(this.supported_languages);
-
-        return new Promise((resolve, reject) => {
-            const loc =
-                (() => {// Locale from url priority 1
-                    if (this.use_url_locale_fragment) {
-                        const first_url_fragment = window.location.pathname.substring(1).split("/")[0];
-                        const fragment_is_locale = this.supported_languages.includes(first_url_fragment);
-                        return fragment_is_locale ? first_url_fragment : "";
-                    } else {
-                        return "";
-                    }
-                })()
-                || localStorage.getItem(this.local_storage_key) // Locale from storage priority 2
-                || (() => { // locale from navigator priority 3
-                    const navigator_locale = navigator.language.split("-")[0].toLocaleLowerCase();
-                    return this.supported_languages.includes(navigator_locale)
-                        ? navigator_locale
-                        : this.supported_languages[0] // Default if navigator locale is not supported
-                })();
-
-            fetch(`${this.translations_url}${loc}.json`)
-                .then(response => response.json())
-                .then(response => {
-                    this.locale = loc;
-                    this.translations = response;
-                    resolve();
-                })
-                .catch(err => {
-                    this.locale = "en";
-                    reject(err);
-                });
-        });
-    },
-
-    /**
-     * Return a lowercase string without dash.
-     * If given locale is en-EN, then "en" will be returned.
-     * @param {String} locale 
-     * @returns A lowercase string
-     */
-    format_locale(locale) {
-        return locale.split("-")[0].toLowerCase();
-    },
-
-    /**
-     * Appends a slash at the end of the given string if missing, and returns the url.
-     * @param {String} url 
-     * @returns {String}
-     */
-    format_translations_url(url) {
-        if (url.charAt(url.length - 1) !== "/") {
-            url += "/"
-        }
-        return url;
-    },
-
-    /**
-     * Return the array of language codes formatted as lowsercase language code.
-     * if ["en-EN", "it-IT"]is given, ["en", "it"] will b returned.
-     * @param {String[]} languages_codes 
-     * @returns {String[]}
-     */
-    format_supported_languages(languages_codes) {
-        return languages_codes.map(lc => this.format_locale(lc));
-    },
-
-    /**
-     * Fetches a new set of translations in case the wanted language changes
-     * @param {String} locale A lowercase language code
-     * @returns {Promise}
-     */
-    update_translations(locale) {
-        locale = this.format_locale(locale);
-        return new Promise((resolve, reject) => {
-            fetch(`${this.translations_url}${locale}.json`)
-                .then(response => response.json())
-                .then(response => {
-                    this.translations = response;
-                    this.locale = locale;
-
-                    localStorage.setItem(this.local_storage_key, locale);
-
-                    const split_path = window.location.pathname.substring(1).split("/");
-                    const first_url_fragment = split_path[0];
-                    const fragment_is_locale = this.supported_languages.includes(first_url_fragment);
-
-                    if (fragment_is_locale) {
-                        split_path.splice(0, 1, locale);
-                        const updated_path = split_path.join("/");
-                        window.history.replaceState(null, "", "/" + updated_path);
-                    }
-                    resolve();
-                })
-                .catch(err => {
-                    reject(err);
-                });
-        });
-    },
-
-    /**
-     * Tries to get the translation of the source string, or return the string as it.
-     * @param {String} text The source text to translate
-     * @param {Object} params Some dynamic element to insert in the text
-     * Params can be used if the translated text provide placeholder like {%some_word%}
-     * Example:
-     * translator.trad("Some trad key", {some_word: "a dynamic parameter"})
-     * -> translation for "Some trad key": "A translated text with {%some_word%}"
-     * -> will be return as : "A translated text with a dynamic parameter"
-     * @returns {String}
-     */
-    trad: function (text, params = {}) {
-        text = this.translations[text] || text;
-
-        Object.keys(params).forEach(k => {
-            text = text.replace(`{%${k}%}`, params[k]);
-        });
-
-        return text;
-    }
-};
-},{}],5:[function(require,module,exports){
-"use strict";
-
-module.exports = {
-    register_key: "objectToHtmlRender",
-
-    /**
-     * Register "this" as a window scope accessible variable named by the given key, or default.
-     * @param {String} key 
-     */
-    register(key) {
-        const register_key = key || this.register_key;
-        window[register_key] = this;
-    },
-
-    /**
-     * This must be called before any other method in order to initialize the lib.
-     * It provides the root of the rendering cycle as a Javascript object.
-     * @param {Object} renderCycleRoot A JS component with a render method.
-     */
-    setRenderCycleRoot(renderCycleRoot) {
-        this.renderCycleRoot = renderCycleRoot;
-    },
-
-    event_name: "objtohtml-render-cycle",
-
-    /**
-     * Set a custom event name for the event that is trigger on render cycle.
-     * Default is "objtohtml-render-cycle".
-     * @param {String} evt_name 
-     */
-    setEventName(evt_name) {
-        this.event_name = evt_name;
-    },
-
-    /**
-     * This is the core agorithm that read an javascript Object and convert it into an HTML element.
-     * @param {Object} obj The object representing the html element must be formatted like:
-     * {
-     *      tag: String // The name of the html tag, Any valid html tag should work. div, section, br, ul, li...
-     *      xmlns: String // This can replace the tag key if the element is an element with a namespace URI, for example an <svg> tag.
-     *                      See https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS for more information
-     *      style_rules: Object // a object providing css attributes. The attributes names must be in JS syntax,
-     *                              like maxHeight: "500px", backgrouncColor: "#ff2d56",  margin: 0,  etc.
-     *      contents: Array or String // This reprensents the contents that will be nested in the created html element.
-     *                                   <div>{contents}</div>
-     *                                   The contents can be an array of other objects reprenting elements (with tag, contents, etc)
-     *                                   or it can be a simple string.
-     *      // All other attributes will be parsed as html attributes. They can be anything like onclick, href, onchange, title...
-     *      // or they can also define custom html5 attributes, like data, my_custom_attr or anything.
-     * }
-     * @returns {HTMLElement} The output html node.
-     */
-    objectToHtml(obj) {
-        if (!obj) return document.createElement("span"); // in case of invalid input, don't block the whole process.
-        const objectToHtml = this.objectToHtml.bind(this);
-        const { tag, xmlns } = obj;
-        const node = xmlns !== undefined ? document.createElementNS(xmlns, tag) : document.createElement(tag);
-        const excludeKeys = ["tag", "contents", "style_rules", "state", "xmlns"];
-
-        Object.keys(obj)
-            .filter(attr => !excludeKeys.includes(attr))
-            .forEach(attr => {
-                switch (attr) {
-                    case "class":
-                        node.classList.add(...obj[attr].split(" ").filter(s => s !== ""));
-                        break;
-                    case "on_render":
-                        if (!obj.id) {
-                            node.id = `${btoa(JSON.stringify(obj).slice(0, 127)).replace(/\=/g, '')}${window.performance.now()}`;
-                        }
-                        if (typeof obj.on_render !== "function") {
-                            console.error("The on_render attribute must be a function")
-                        } else {
-                            this.attach_on_render_callback(node, obj.on_render);
-                        }
-                        break;
-                    default:
-                        if (xmlns !== undefined) {
-                            node.setAttributeNS(null, attr, obj[attr])
-                        } else {
-                            node[attr] = obj[attr];
-                        }
-                }
-            });
-        if (obj.contents && typeof obj.contents === "string") {
-            node.innerHTML = obj.contents;
-        } else {
-            obj.contents &&
-                obj.contents.length > 0 &&
-                obj.contents.forEach(el => {
-                    switch (typeof el) {
-                        case "string":
-                            node.innerHTML = el;
-                            break;
-                        case "object":
-                            if (xmlns !== undefined) {
-                                el = Object.assign(el, { xmlns })
-                            }
-                            node.appendChild(objectToHtml(el));
-                            break;
-                    }
-                });
-        }
-
-        if (obj.style_rules) {
-            Object.keys(obj.style_rules).forEach(rule => {
-                node.style[rule] = obj.style_rules[rule];
-            });
-        }
-
-        return node;
-    },
-
-    on_render_callbacks: [],
-
-    /**
-     * This is called if the on_render attribute of a component is set.
-     * @param {HTMLElement} node The created html element
-     * @param {Function} callback The callback defined in the js component to render
-     */
-    attach_on_render_callback(node, callback) {
-        const callback_handler = {
-            callback: e => {
-                if (e.detail.outputNode === node || e.detail.outputNode.querySelector(`#${node.id}`)) {
-                    callback(node);
-                    const handler_index = this.on_render_callbacks.indexOf((this.on_render_callbacks.find(cb => cb.node === node)));
-                    if (handler_index === -1) {
-                        console.warn("A callback was registered for node with id " + node.id + " but callbacck handler is undefined.")
-                    } else {
-                        window.removeEventListener(this.event_name, this.on_render_callbacks[handler_index].callback)
-                        this.on_render_callbacks.splice(handler_index, 1);
-                    }
-                }
-            },
-            node,
-        };
-
-        const len = this.on_render_callbacks.push(callback_handler);
-        window.addEventListener(this.event_name, this.on_render_callbacks[len - 1].callback);
-    },
-
-    /**
-     * If a main element exists in the html document, it will be used as rendering root.
-     * If not, it will be created and inserted.
-     */
-    renderCycle: function () {
-        const main_elmt = document.getElementsByTagName("main")[0] || (function () {
-            const created_main = document.createElement("main");
-            document.body.appendChild(created_main);
-            return created_main;
-        })();
-
-        this.subRender(this.renderCycleRoot.render(), main_elmt, { mode: "replace" });
-    },
-
-    /**
-     * This method behaves like the renderCycle() method, but rather that starting the rendering cycle from the root component,
-    * it can start from any component of the tree. The root component must be given as the first argument, the second argument be
-    * be a valid html element in the dom and will be used as the insertion target.
-     * @param {Object} object An object providing a render method returning an object representation of the html to insert
-     * @param {HTMLElement} htmlNode The htlm element to update
-     * @param {Object} options can be used the define the insertion mode, default is set to "append" and can be set to "override",
-         * "insert-before" (must be defined along with an insertIndex key (integer)),
-         * "adjacent" (must be defined along with an insertLocation key (String)), "replace" or "remove".
-         * In case of "remove", the first argument "object" is not used and can be set to null, undefined or {}...
-     */
-    subRender(object, htmlNode, options = { mode: "append" }) {
-        let outputNode = null;
-
-        const get_insert = () => {
-            outputNode = this.objectToHtml(object);
-            return outputNode;
-        };
-
-        switch (options.mode) {
-            case "append":
-                htmlNode.appendChild(get_insert());
-                break;
-            case "override":
-                htmlNode.innerHTML = "";
-                htmlNode.appendChild(get_insert());
-                break;
-            case "insert-before":
-                htmlNode.insertBefore(get_insert(), htmlNode.childNodes[options.insertIndex]);
-                break;
-            case "adjacent":
-                /**
-                 * options.insertLocation must be one of:
-                 *
-                 * afterbegin
-                 * afterend
-                 * beforebegin
-                 * beforeend
-                 */
-                htmlNode.insertAdjacentHTML(options.insertLocation, get_insert());
-                break;
-            case "replace":
-                htmlNode.parentNode.replaceChild(get_insert(), htmlNode);
-                break;
-            case "remove":
-                htmlNode.remove();
-                break;
-        }
-        const evt_name = this.event_name;
-        const event = new CustomEvent(evt_name, {
-            detail: {
-                inputObject: object,
-                outputNode,
-                insertOptions: options,
-                targetNode: htmlNode,
-            }
-        });
-
-        window.dispatchEvent(event);
-    },
-};
-},{}],6:[function(require,module,exports){
-"use strict";
-
-const { fetch_json_or_error_text } = require("./fetch");
-
-function getArticleBody(text) {
-    return text.replaceAll("\n", "<br/>");
-}
-
-function getArticleDate(date) {
-    return `${date.getDate()}-${date.getMonth() + 1}-${date.getFullYear()}`;
-}
-
-function loadArticles(category, locale) {
-    return fetch_json_or_error_text(`/articles/${category}/${locale}`);
-}
-
-module.exports = {
-    loadArticles,
-    getArticleBody,
-    getArticleDate,
-};
-
-},{"./fetch":7}],7:[function(require,module,exports){
-"use strict";
-
-function fetchjson(url) {
-    return new Promise((resolve, reject) => {
-        fetch(url)
-            .then(r => r.json())
-            .then(r => resolve(r))
-            .catch(e => reject(e));
-    });
-}
-
-function fetchtext(url) {
-    return new Promise((resolve, reject) => {
-        fetch(url)
-            .then(r => r.text())
-            .then(r => resolve(r))
-            .catch(e => reject(e));
-    });
-}
-
-async function fetch_json_or_error_text(url, options = {}) {
-    return new Promise((resolve, reject) => {
-        fetch(url, options).then(async res => {
-            if (res.status >= 400 && res.status < 600) {
-                reject(await res.text());
-            } else {
-                resolve(await res.json());
-            }
-        })
-    })
-}
-
-module.exports = {
-    fetchjson,
-    fetchtext,
-    fetch_json_or_error_text,
-};
-
-},{}],8:[function(require,module,exports){
-"use strict";
-const translator = require("ks-cheap-translator");
-const { translations_url } = require("../../constants");
-
-class WebPage {
-    constructor(args) {
-        Object.assign(this, args);
-
-        if (!this.id) {
-            this.id = "webpage-" + performance.now();
-        }
-
-        translator.init({
-            translations_url,
-            supported_languages: ["fr", "en"],
-        }).then(this.refresh_all.bind(this));
-    }
-
-    refresh() {
-        obj2htm.subRender(this.render(), document.getElementById(this.id), { mode: "replace" })
-    }
-
-    refresh_all() {
-        obj2htm.renderCycle()
-    }
-}
-
-module.exports = WebPage;
-},{"../../constants":3,"ks-cheap-translator":4}],9:[function(require,module,exports){
-"use strict";
-
-const { images_url } = require("../../../../../admin-frontend/src/constants");
-const { getArticleBody } = require("../../../lib/article-utils");
-
-class SoftwareArticle {
-    constructor(props) {
-        this.props = props;
-    }
-
-    render() {
-        const { title, body, subtitle, images, details = [] } = this.props;
-
-        return {
-            tag: "article",
-            class: "software-article",
-            typeof: "SoftwareApplication",
-            additionalType: "Article",
-            contents: [
-                {
-                    tag: "h2",
-                    class: "software-title",
-                    contents: title,
-                    property: "name",
-                },
-                {
-                    tag: "div", class: "software-image",
-                    contents: [
-                        {
-                            tag: "img", src: `${images_url}/${images[0]}`
-                        }
-                    ]
-                },
-                {
-                    tag: "h3",
-                    class: "software-subtitle",
-                    contents: subtitle,
-                    property: "alternativeHeadline",
-                },
-                {
-                    tag: "div",
-                    class: "software-description",
-                    contents: getArticleBody(body),
-                    property: "description",
-                },
-                details.length > 0 && {
-                    tag: "div",
-                    class: "article-details",
-                    contents: [
-                        {
-                            tag: "h2",
-                            contents: "Details",
-                        },
-                        {
-                            tag: "ul",
-                            class: "details-list",
-                            contents: details.map(detail => {
-                                return {
-                                    tag: "li",
-                                    class: "detail",
-                                    contents: [
-                                        { tag: "label", contents: detail.label },
-                                        {
-                                            tag: "div",
-                                            class: "detail-value",
-                                            contents: detail.value
-                                        },
-                                    ],
-                                };
-                            }),
-                        },
-                    ],
-                },
-            ],
-        };
-    }
-}
-
-module.exports = SoftwareArticle;
-},{"../../../../../admin-frontend/src/constants":1,"../../../lib/article-utils":6}],10:[function(require,module,exports){
-"use strict";
-
-const { loadArticles } = require("../../../lib/article-utils");
-const SoftwareArticle = require("./software-article");
-const translator = require("ks-cheap-translator");
-
-class SoftwareArticles {
-    constructor(props) {
-        this.props = props;
-        this.state = {
-            articles: [],
-        };
-        this.id = "software-articles-section";
-        this.loadArticles();
-    }
-
-    loadArticles() {
-        loadArticles("software", translator.locale).then(articles => {
-            this.state.articles = articles;
-            this.refresh();
-            this.fixScroll();
-        }).catch(e => console.log(e))
-    }
-
-    renderPlaceholder() {
-        return {
-            tag: "article",
-            class: "placeholder",
-            contents: [
-                { tag: "div", class: "title" },
-                { tag: "div", class: "body" },
-                { tag: "div", class: "details" },
-            ],
-        };
-    }
-
-    refresh() {
-        obj2htm.subRender(this.render(), document.getElementById(this.id), {
-            mode: "replace",
-        });
-    }
-
-    fixScroll() {
-        if (window.location.href.includes("#")) {
-            window.scrollTo(
-                0,
-                document.getElementById(window.location.href.match(/#.+/)[0].replace("#", ""))
-                    .offsetTop
-            );
-        }
-    }
-
-    render() {
-        const { articles } = this.state;
-        return {
-            tag: "section",
-            class: "software-articles page-contents-center",
-            id: this.id,
-            contents:
-                articles.length > 0
-                    ? articles.map(article => new SoftwareArticle({ ...article }).render())
-                    : [this.renderPlaceholder()],
-        };
-    }
-}
-
-module.exports = SoftwareArticles;
-
-},{"../../../lib/article-utils":6,"./software-article":9,"ks-cheap-translator":4}],11:[function(require,module,exports){
-"use strict";
-
-const { images_url } = require("../../../constants");
-const WebPage = require("../../lib/web-page");
-const SoftwareArticles = require("./components/software-articles");
-const translator = require("ks-cheap-translator");
-const t = translator.trad.bind(translator);
-
-class SoftwareDevelopment extends WebPage {
-    render() {
-        return {
-            tag: "div",
-            id: "software-page",
-            contents: [
-                {
-                    tag: "div",
-                    class: "page-header logo-left",
-                    contents: [
-                        {
-                            tag: "div",
-                            class: "page-contents-center grid-wrapper",
-                            contents: [
-                                {
-                                    tag: "div",
-                                    class: "logo",
-                                    contents: [
-                                        {
-                                            tag: "img",
-                                            alt: `image mechanic electronic`,
-                                            src: `${images_url}/meca_proc.svg`,
-                                        },
-                                    ],
-                                },
-                                { tag: "h1", contents: "Software" },
-                                {
-                                    tag: "p",
-                                    contents: t("software-page-intro"),
-                                },
-                            ],
-                        },
-                    ],
-                },
-                new SoftwareArticles().render(),
-            ],
-        };
-    }
-}
-
-module.exports = SoftwareDevelopment;
-
-},{"../../../constants":3,"../../lib/web-page":8,"./components/software-articles":10,"ks-cheap-translator":4}],12:[function(require,module,exports){
-"use strict";
-
-"use strict";
-const runPage = require("../../run-page");
-const SoftwareDevelopment = require("./software-development");
-runPage(SoftwareDevelopment);
-
-},{"../../run-page":13,"./software-development":11}],13:[function(require,module,exports){
-"use strict";
-
-const renderer = require("object-to-html-renderer")
-const Template = require("./template/template");
-
-module.exports = function runPage(PageComponent) {
-    const template = new Template({ page: new PageComponent() });
-    renderer.register("obj2htm")
-    obj2htm.setRenderCycleRoot(template);
-    obj2htm.renderCycle();
-};
-
-},{"./template/template":15,"object-to-html-renderer":5}],14:[function(require,module,exports){
-"use strict";
-
-const { images_url } = require("../../../constants");
-const translator = require("ks-cheap-translator");
-const t = translator.trad.bind(translator);
-
-const NAV_MENU_ITEMS = [
-    { url: "/games/", text: "Jeux" },
-    {
-        url: "/education/",
-        text: "Pédagogie",
-    },
-    { url: "/software-development/", text: "Software" }
-];
-
-class NavBar {
-    constructor() {
-        this.initEventHandlers();
-    }
-
-    handleBurgerClick() {
-        document.getElementById("nav-menu-list").classList.toggle("responsive-show");
-    }
-
-    initEventHandlers() {
-        window.addEventListener("click", event => {
-            if (
-                event.target.id !== "nav-menu-list" &&
-                !event.target.classList.contains("burger") &&
-                !event.target.parentNode.classList.contains("burger")
-            ) {
-                document.getElementById("nav-menu-list").classList.remove("responsive-show");
-            }
-        });
-    }
-
-    handle_chang_lang(lang) {
-        translator.update_translations(lang).then(() => {
-            obj2htm.renderCycle();
-        }).catch(err => console.log(err));
-    }
-
-    renderHome() {
-        return {
-            tag: "div",
-            class: "home",
-            contents: [
-                {
-                    tag: "a",
-                    href: "/",
-                    contents: [
-                        {
-                            tag: "img",
-                            alt: "Logo Kuadrado",
-                            src: `${images_url}/logo_kuadrado.svg`,
-                        },
-                        {
-                            tag: "img",
-                            alt: "Kuadrado Software",
-                            class: "logo-text",
-                            src: `${images_url}/logo_kuadrado_txt.svg`,
-                        },
-                    ],
-                },
-            ],
-        };
-    }
-
-    renderMenu(menuItemsArray, isSubmenu = false, parentUrl = "") {
-        return {
-            tag: "ul",
-            id: "nav-menu-list",
-            class: isSubmenu ? "submenu" : "",
-            contents: menuItemsArray.map(item => {
-                const { url, text, submenu } = item;
-                const href = `${parentUrl}${url}`;
-                return {
-                    tag: "li",
-                    class: !isSubmenu && window.location.pathname === href ? "active" : "",
-                    contents: [
-                        {
-                            tag: "a",
-                            href,
-                            contents: t(text),
-                        },
-                    ].concat(submenu ? [this.renderMenu(submenu, true, url)] : []),
-                };
-            }).concat({
-                tag: "li",
-                class: "lang-flags",
-                contents: ["fr", "en"].map(lang => {
-                    return {
-                        tag: "img", src: `${images_url}/flag-${lang}.svg`,
-                        class: translator.locale === lang ? "selected" : "",
-                        onclick: this.handle_chang_lang.bind(this, lang)
-                    }
-                })
-            }),
-        };
-    }
-
-    renderResponsiveBurger() {
-        return {
-            tag: "div",
-            class: "burger",
-            onclick: this.handleBurgerClick.bind(this),
-            contents: [{ tag: "span", contents: "···" }],
-        };
-    }
-
-    render() {
-        return {
-            tag: "nav",
-            contents: [
-                this.renderHome(),
-                this.renderResponsiveBurger(),
-                this.renderMenu(NAV_MENU_ITEMS),
-            ],
-        };
-    }
-}
-
-module.exports = NavBar;
-
-},{"../../../constants":3,"ks-cheap-translator":4}],15:[function(require,module,exports){
-"use strict";
-
-const { in_construction } = require("../../config");
-const { images_url } = require("../../constants");
-const NavBar = require("./components/navbar");
-const translator = require("ks-cheap-translator");
-const t = translator.trad.bind(translator)
-
-class Template {
-    constructor(props) {
-        this.props = props;
-    }
-    render() {
-        return {
-            tag: "main",
-            contents: [
-                {
-                    tag: "header",
-                    contents: [new NavBar().render()],
-                },
-                in_construction && {
-                    tag: "section",
-                    class: "warning-banner",
-                    contents: [
-                        {
-                            tag: "strong",
-                            class: "page-contents-center",
-                            contents: t("Site en construction ..."),
-                        },
-                    ],
-                },
-                {
-                    tag: "section",
-                    id: "page-container",
-                    contents: [this.props.page.render()],
-                },
-                {
-                    tag: "footer",
-                    contents: [
-                        {
-                            tag: "div",
-                            class: "logo",
-                            contents: [
-                                {
-                                    tag: "img",
-                                    alt: `logo Kuadrado`,
-                                    src: `${images_url}/logo_kuadrado.svg`,
-                                },
-                                {
-                                    tag: "img",
-                                    class: "text-logo",
-                                    alt: "Kuadrado Software",
-                                    src: `${images_url}/logo_kuadrado_txt.svg`,
-                                },
-                            ],
-                        },
-                        {
-                            tag: "span",
-                            contents: "32 rue Simon Vialet, 07240 Vernoux en Vivarais. Ardèche, France",
-                        },
-                        {
-                            tag: "div",
-                            contents: [
-                                { tag: "strong", contents: "<blue>Contact : </blue>" },
-                                {
-                                    tag: "a",
-                                    href: "mailto:contact@kuadrado-software.fr",
-                                    contents: "contact@kuadrado-software.fr",
-                                },
-                            ],
-                        },
-                        {
-                            tag: "div",
-                            class: "social",
-                            contents: [
-                                {
-                                    tag: "strong",
-                                    contents: `<blue>${t("Sur les réseaux")} : </blue>`,
-                                },
-                                {
-                                    tag: "a",
-                                    href: "https://www.linkedin.com/company/kuadrado-software",
-                                    target: "_blank",
-                                    contents: "in",
-                                    title: "Linkedin",
-                                },
-                                {
-                                    tag: "a",
-                                    href: "https://mastodon.gamedev.place/@kuadrado_software",
-                                    target: "_blank",
-                                    contents: "m",
-                                    title: "Mastodon",
-                                }
-                            ],
-                        },
-                        {
-                            tag: "span",
-                            contents: `Copyleft 🄯 ${new Date()
-                                .getFullYear()} Kuadrado Software | 
-                                ${t("kuadrado-footer-copyleft")}`,
-                        },
-                        {
-                            tag: "div", contents: [
-                                { tag: "span", contents: t("Ce site web est") + " " },
-                                {
-                                    tag: "a", target: "_blank",
-                                    style_rules: { fontWeight: "bold" },
-                                    href: "https://gitlab.com/kuadrado-software/kuadrado-website/-/blob/master/README.md",
-                                    contents: "OPEN SOURCE"
-                                }
-                            ]
-                        }
-                    ],
-                },
-            ],
-        };
-    }
-}
-
-module.exports = Template;
-
-},{"../../config":2,"../../constants":3,"./components/navbar":14,"ks-cheap-translator":4}]},{},[12]);
+!function s(a,r,o){function c(e,t){if(!r[e]){if(!a[e]){var n="function"==typeof require&&require;if(!t&&n)return n(e,!0);if(i)return i(e,!0);throw(n=new Error("Cannot find module '"+e+"'")).code="MODULE_NOT_FOUND",n}n=r[e]={exports:{}},a[e][0].call(n.exports,function(t){return c(a[e][1][t]||t)},n,n.exports,s,a,r,o)}return r[e].exports}for(var i="function"==typeof require&&require,t=0;t<o.length;t++)c(o[t]);return c}({1:[function(t,e,n){e.exports={images_url:"/assets/images"}},{}],2:[function(t,e,n){e.exports={build:{protected_dirs:["assets","style","views","standard"],default_meta_keys:["title","description","image","open_graph","json_ld"]}}},{}],3:[function(t,e,n){e.exports={images_url:"/assets/images",data_url:"/assets/data",translations_url:"/assets/translations"}},{}],4:[function(t,e,n){"use strict";e.exports={locale:"en",supported_languages:["en"],translations:{},translations_url:"",use_url_locale_fragment:!0,local_storage_key:"translator-prefered-language",init(t){return Object.entries(t).forEach(t=>{var[e,t]=t;["supported_languages","use_url_locale_fragment","local_storage_key","translations_url"].includes(e)&&(this[e]=t)}),this.translations_url=this.format_translations_url(this.translations_url),this.supported_languages=this.format_supported_languages(this.supported_languages),new Promise((e,n)=>{const s=(()=>{if(this.use_url_locale_fragment){var t=window.location.pathname.substring(1).split("/")[0];return this.supported_languages.includes(t)?t:""}return""})()||localStorage.getItem(this.local_storage_key)||(t=navigator.language.split("-")[0].toLocaleLowerCase(),this.supported_languages.includes(t)?t:this.supported_languages[0]);var t;fetch(`${this.translations_url}${s}.json`).then(t=>t.json()).then(t=>{this.locale=s,this.translations=t,e()}).catch(t=>{this.locale="en",n(t)})})},format_locale(t){return t.split("-")[0].toLowerCase()},format_translations_url(t){return"/"!==t.charAt(t.length-1)&&(t+="/"),t},format_supported_languages(t){return t.map(t=>this.format_locale(t))},update_translations(s){return s=this.format_locale(s),new Promise((n,e)=>{fetch(`${this.translations_url}${s}.json`).then(t=>t.json()).then(t=>{this.translations=t,this.locale=s,localStorage.setItem(this.local_storage_key,s);const e=window.location.pathname.substring(1).split("/");t=e[0];this.supported_languages.includes(t)&&(e.splice(0,1,s),t=e.join("/"),window.history.replaceState(null,"","/"+t)),n()}).catch(t=>{e(t)})})},trad:function(e,n={}){return e=this.translations[e]||e,Object.keys(n).forEach(t=>{e=e.replace(`{%${t}%}`,n[t])}),e}}},{}],5:[function(t,e,n){"use strict";e.exports={register_key:"objectToHtmlRender",register(t){t=t||this.register_key;window[t]=this},setRenderCycleRoot(t){this.renderCycleRoot=t},event_name:"objtohtml-render-cycle",setEventName(t){this.event_name=t},objectToHtml(e){if(!e)return document.createElement("span");const n=this.objectToHtml.bind(this),{tag:t,xmlns:s}=e,a=void 0!==s?document.createElementNS(s,t):document.createElement(t),r=["tag","contents","style_rules","state","xmlns"];return Object.keys(e).filter(t=>!r.includes(t)).forEach(t=>{switch(t){case"class":a.classList.add(...e[t].split(" ").filter(t=>""!==t));break;case"on_render":e.id||(a.id=`${btoa(JSON.stringify(e).slice(0,127)).replace(/\=/g,"")}${window.performance.now()}`),"function"!=typeof e.on_render?console.error("The on_render attribute must be a function"):this.attach_on_render_callback(a,e.on_render);break;default:void 0!==s?a.setAttributeNS(null,t,e[t]):a[t]=e[t]}}),e.contents&&"string"==typeof e.contents?a.innerHTML=e.contents:e.contents&&0<e.contents.length&&e.contents.forEach(t=>{switch(typeof t){case"string":a.innerHTML=t;break;case"object":void 0!==s&&(t=Object.assign(t,{xmlns:s})),a.appendChild(n(t))}}),e.style_rules&&Object.keys(e.style_rules).forEach(t=>{a.style[t]=e.style_rules[t]}),a},on_render_callbacks:[],attach_on_render_callback(e,n){var t={callback:t=>{t.detail.outputNode!==e&&!t.detail.outputNode.querySelector(`#${e.id}`)||(n(e),-1===(t=this.on_render_callbacks.indexOf(this.on_render_callbacks.find(t=>t.node===e)))?console.warn("A callback was registered for node with id "+e.id+" but callbacck handler is undefined."):(window.removeEventListener(this.event_name,this.on_render_callbacks[t].callback),this.on_render_callbacks.splice(t,1)))},node:e},t=this.on_render_callbacks.push(t);window.addEventListener(this.event_name,this.on_render_callbacks[t-1].callback)},renderCycle:function(){var t,t=document.getElementsByTagName("main")[0]||(t=document.createElement("main"),document.body.appendChild(t),t);this.subRender(this.renderCycleRoot.render(),t,{mode:"replace"})},subRender(t,e,n={mode:"append"}){let s=null;var a=()=>(s=this.objectToHtml(t),s);switch(n.mode){case"append":e.appendChild(a());break;case"override":e.innerHTML="",e.appendChild(a());break;case"insert-before":e.insertBefore(a(),e.childNodes[n.insertIndex]);break;case"adjacent":e.insertAdjacentHTML(n.insertLocation,a());break;case"replace":e.parentNode.replaceChild(a(),e);break;case"remove":e.remove()}var r=this.event_name,r=new CustomEvent(r,{detail:{inputObject:t,outputNode:s,insertOptions:n,targetNode:e}});window.dispatchEvent(r)}}},{}],6:[function(t,e,n){"use strict";const{fetch_json_or_error_text:s}=t("./fetch");e.exports={loadArticles:function(t,e){return s(`/articles/${t}/${e}`)},getArticleBody:function(t){return t.replaceAll("\n","<br/>")},getArticleDate:function(t){return`${t.getDate()}-${t.getMonth()+1}-${t.getFullYear()}`}}},{"./fetch":7}],7:[function(t,e,n){"use strict";e.exports={fetchjson:function(t){return new Promise((e,n)=>{fetch(t).then(t=>t.json()).then(t=>e(t)).catch(t=>n(t))})},fetchtext:function(t){return new Promise((e,n)=>{fetch(t).then(t=>t.text()).then(t=>e(t)).catch(t=>n(t))})},fetch_json_or_error_text:async function(t,s={}){return new Promise((e,n)=>{fetch(t,s).then(async t=>{400<=t.status&&t.status<600?n(await t.text()):e(await t.json())})})}}},{}],8:[function(t,e,n){"use strict";const s=t("ks-cheap-translator"),{translations_url:a}=t("../../constants");e.exports=class{constructor(t){Object.assign(this,t),this.id||(this.id="webpage-"+performance.now()),s.init({translations_url:a,supported_languages:["fr","en"]}).then(this.refresh_all.bind(this))}refresh(){obj2htm.subRender(this.render(),document.getElementById(this.id),{mode:"replace"})}refresh_all(){obj2htm.renderCycle()}}},{"../../constants":3,"ks-cheap-translator":4}],9:[function(t,e,n){"use strict";const{images_url:r}=t("../../../../../admin-frontend/src/constants"),{getArticleBody:o}=t("../../../lib/article-utils");e.exports=class{constructor(t){this.props=t}render(){const{title:t,body:e,subtitle:n,images:s,details:a=[]}=this.props;return{tag:"article",class:"software-article",typeof:"SoftwareApplication",additionalType:"Article",contents:[{tag:"h2",class:"software-title",contents:t,property:"name"},{tag:"div",class:"software-image",contents:[{tag:"img",src:`${r}/${s[0]}`}]},{tag:"h3",class:"software-subtitle",contents:n,property:"alternativeHeadline"},{tag:"div",class:"software-description",contents:o(e),property:"description"},0<a.length&&{tag:"div",class:"article-details",contents:[{tag:"h2",contents:"Details"},{tag:"ul",class:"details-list",contents:a.map(t=>({tag:"li",class:"detail",contents:[{tag:"label",contents:t.label},{tag:"div",class:"detail-value",contents:t.value}]}))}]}]}}}},{"../../../../../admin-frontend/src/constants":1,"../../../lib/article-utils":6}],10:[function(t,e,n){"use strict";const{loadArticles:s}=t("../../../lib/article-utils"),a=t("./software-article"),r=t("ks-cheap-translator");e.exports=class{constructor(t){this.props=t,this.state={articles:[]},this.id="software-articles-section",this.loadArticles()}loadArticles(){s("software",r.locale).then(t=>{this.state.articles=t,this.refresh(),this.fixScroll()}).catch(t=>console.log(t))}renderPlaceholder(){return{tag:"article",class:"placeholder",contents:[{tag:"div",class:"title"},{tag:"div",class:"body"},{tag:"div",class:"details"}]}}refresh(){obj2htm.subRender(this.render(),document.getElementById(this.id),{mode:"replace"})}fixScroll(){window.location.href.includes("#")&&window.scrollTo(0,document.getElementById(window.location.href.match(/#.+/)[0].replace("#","")).offsetTop)}render(){const{articles:t}=this.state;return{tag:"section",class:"software-articles page-contents-center",id:this.id,contents:0<t.length?t.map(t=>new a({...t}).render()):[this.renderPlaceholder()]}}}},{"../../../lib/article-utils":6,"./software-article":9,"ks-cheap-translator":4}],11:[function(t,e,n){"use strict";const{images_url:s}=t("../../../constants");var a=t("../../lib/web-page");const r=t("./components/software-articles"),o=t("ks-cheap-translator"),c=o.trad.bind(o);class i extends a{render(){return{tag:"div",id:"software-page",contents:[{tag:"div",class:"page-header logo-left",contents:[{tag:"div",class:"page-contents-center grid-wrapper",contents:[{tag:"div",class:"logo",contents:[{tag:"img",alt:"image mechanic electronic",src:`${s}/meca_proc.svg`}]},{tag:"h1",contents:"Software"},{tag:"p",contents:c("software-page-intro")}]}]},(new r).render()]}}}e.exports=i},{"../../../constants":3,"../../lib/web-page":8,"./components/software-articles":10,"ks-cheap-translator":4}],12:[function(t,e,n){"use strict";const s=t("../../run-page");t=t("./software-development");s(t)},{"../../run-page":13,"./software-development":11}],13:[function(t,e,n){"use strict";const s=t("object-to-html-renderer"),a=t("./template/template");e.exports=function(t){t=new a({page:new t});s.register("obj2htm"),obj2htm.setRenderCycleRoot(t),obj2htm.renderCycle()}},{"./template/template":15,"object-to-html-renderer":5}],14:[function(t,e,n){"use strict";const{images_url:s}=t("../../../constants"),o=t("ks-cheap-translator"),c=o.trad.bind(o),a=[{url:"/games/",text:"Jeux"},{url:"/education/",text:"Pédagogie"},{url:"/software-development/",text:"Software"}];e.exports=class{constructor(){this.initEventHandlers()}handleBurgerClick(){document.getElementById("nav-menu-list").classList.toggle("responsive-show")}initEventHandlers(){window.addEventListener("click",t=>{"nav-menu-list"===t.target.id||t.target.classList.contains("burger")||t.target.parentNode.classList.contains("burger")||document.getElementById("nav-menu-list").classList.remove("responsive-show")})}handle_chang_lang(t){o.update_translations(t).then(()=>{obj2htm.renderCycle()}).catch(t=>console.log(t))}renderHome(){return{tag:"div",class:"home",contents:[{tag:"a",href:"/",contents:[{tag:"img",alt:"Logo Kuadrado",src:`${s}/logo_kuadrado.svg`},{tag:"img",alt:"Kuadrado Software",class:"logo-text",src:`${s}/logo_kuadrado_txt.svg`}]}]}}renderMenu(t,a=!1,r=""){return{tag:"ul",id:"nav-menu-list",class:a?"submenu":"",contents:t.map(t=>{var{url:e,text:n,submenu:t}=t;const s=`${r}${e}`;return{tag:"li",class:a||window.location.pathname!==s?"":"active",contents:[{tag:"a",href:s,contents:c(n)}].concat(t?[this.renderMenu(t,!0,e)]:[])}}).concat({tag:"li",class:"lang-flags",contents:["fr","en"].map(t=>({tag:"img",src:`${s}/flag-${t}.svg`,class:o.locale===t?"selected":"",onclick:this.handle_chang_lang.bind(this,t)}))})}}renderResponsiveBurger(){return{tag:"div",class:"burger",onclick:this.handleBurgerClick.bind(this),contents:[{tag:"span",contents:"···"}]}}render(){return{tag:"nav",contents:[this.renderHome(),this.renderResponsiveBurger(),this.renderMenu(a)]}}}},{"../../../constants":3,"ks-cheap-translator":4}],15:[function(t,e,n){"use strict";const{in_construction:s}=t("../../config"),{images_url:a}=t("../../constants"),r=t("./components/navbar"),o=t("ks-cheap-translator"),c=o.trad.bind(o);e.exports=class{constructor(t){this.props=t}render(){return{tag:"main",contents:[{tag:"header",contents:[(new r).render()]},s&&{tag:"section",class:"warning-banner",contents:[{tag:"strong",class:"page-contents-center",contents:c("Site en construction ...")}]},{tag:"section",id:"page-container",contents:[this.props.page.render()]},{tag:"footer",contents:[{tag:"div",class:"logo",contents:[{tag:"img",alt:"logo Kuadrado",src:`${a}/logo_kuadrado.svg`},{tag:"img",class:"text-logo",alt:"Kuadrado Software",src:`${a}/logo_kuadrado_txt.svg`}]},{tag:"span",contents:"32 rue Simon Vialet, 07240 Vernoux en Vivarais. Ardèche, France"},{tag:"div",contents:[{tag:"strong",contents:"<blue>Contact : </blue>"},{tag:"a",href:"mailto:contact@kuadrado-software.fr",contents:"contact@kuadrado-software.fr"}]},{tag:"div",class:"social",contents:[{tag:"strong",contents:`<blue>${c("Sur les réseaux")} : </blue>`},{tag:"a",href:"https://www.linkedin.com/company/kuadrado-software",target:"_blank",contents:"in",title:"Linkedin"},{tag:"a",href:"https://mastodon.gamedev.place/@kuadrado_software",target:"_blank",contents:"m",title:"Mastodon"}]},{tag:"span",contents:`Copyleft 🄯 ${(new Date).getFullYear()} Kuadrado Software | 
+                                ${c("kuadrado-footer-copyleft")}`},{tag:"div",contents:[{tag:"span",contents:c("Ce site web est")+" "},{tag:"a",target:"_blank",style_rules:{fontWeight:"bold"},href:"https://gitlab.com/kuadrado-software/kuadrado-website/-/blob/master/README.md",contents:"OPEN SOURCE"}]}]}]}}}},{"../../config":2,"../../constants":3,"./components/navbar":14,"ks-cheap-translator":4}]},{},[12]);
\ No newline at end of file
diff --git a/public/style/style.css b/public/style/style.css
deleted file mode 100644
index db7b101..0000000
--- a/public/style/style.css
+++ /dev/null
@@ -1,987 +0,0 @@
-body {
-  font-family: Arial, Helvetica, sans-serif;
-  margin: 0;
-}
-body * {
-  box-sizing: border-box;
-  color: #35393c;
-  line-height: 1.3em;
-}
-body ul {
-  margin: 0;
-  padding: 0;
-  list-style-type: none;
-}
-body a {
-  color: #4baabb;
-  text-decoration: none;
-}
-body a:hover {
-  color: #72e3f0;
-}
-body blue {
-  color: #4baabb;
-}
-body red {
-  color: #9c3030;
-}
-body green {
-  color: #368736;
-}
-body emoji {
-  font-style: initial;
-  font-size: 25px;
-}
-body .bg-blue {
-  background-color: #4baabb;
-  color: white;
-}
-body .bg-dark {
-  background-color: #3c4144;
-  color: #aabbc8;
-}
-body #seo-title {
-  visibility: hidden;
-}
-body img.pixelated {
-  image-rendering: pixelated;
-  image-rendering: -moz-crisp-edges;
-  image-rendering: crisp-edges;
-}
-
-main {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  min-height: 100vh;
-}
-main .warning-banner {
-  background: url("/assets/images/wallpaper_warning.svg");
-  width: 100%;
-  height: 40px;
-  padding: 20px 10%;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  flex-direction: row;
-}
-main .warning-banner strong {
-  font-size: 18px;
-  color: #1c3db2;
-}
-main .image-carousel {
-  overflow: hidden;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  flex-direction: row;
-  background-color: black;
-  position: relative;
-}
-main .image-carousel img {
-  position: absolute;
-  object-fit: contain;
-  height: 80%;
-  max-width: 100%;
-}
-main .image-carousel .carousel-bullets {
-  position: absolute;
-  bottom: 0;
-  padding: 20px;
-  display: flex;
-  gap: 10px;
-}
-main .image-carousel .carousel-bullets .bullet {
-  cursor: pointer;
-  width: 8px;
-  height: 8px;
-  background-color: #6b7880;
-  border-radius: 100%;
-  box-shadow: 0 0 3px black;
-}
-main .image-carousel .carousel-bullets .bullet.active {
-  background-color: #d4d9dd;
-}
-@media screen and (max-width: 900px) {
-  main .image-carousel .carousel-bullets {
-    gap: 30px;
-  }
-  main .image-carousel .carousel-bullets .bullet {
-    width: 12px;
-    height: 12px;
-  }
-}
-main header {
-  width: 100%;
-  background-color: white;
-  position: sticky;
-  position: -webkit-sticky;
-  top: 0;
-  z-index: 10;
-}
-main header nav {
-  display: flex;
-  align-items: center;
-  height: 60px;
-}
-main header nav .home {
-  margin: 0 10px;
-}
-main header nav .home a {
-  display: flex;
-  align-items: center;
-  gap: 10px;
-}
-main header nav .home a img {
-  height: 40px;
-  width: auto;
-}
-main header nav .home a img.logo-text {
-  width: 120px;
-  height: auto;
-}
-main header nav ul {
-  display: flex;
-  padding: 0;
-  margin: 0;
-  list-style-type: none;
-  height: 100%;
-  flex: 1;
-}
-main header nav ul li {
-  position: relative;
-}
-main header nav ul li a {
-  display: flex;
-  align-items: center;
-  height: 100%;
-  padding: 10px 20px;
-  color: #96a5ae;
-  font-weight: 800;
-  text-decoration: none;
-}
-main header nav ul li .submenu {
-  visibility: hidden;
-  overflow: hidden;
-  position: absolute;
-  height: auto;
-  max-height: 0;
-  transition: max-height 0.6s;
-  top: 100%;
-  left: 50%;
-  flex-direction: column;
-  background-color: white;
-  white-space: nowrap;
-}
-main header nav ul li.active a {
-  color: #3c4144;
-  border-bottom: 3px solid;
-}
-main header nav ul li:hover a {
-  color: #3c4144;
-}
-main header nav ul li:hover .submenu {
-  visibility: unset;
-  max-height: 1000px;
-}
-main header nav ul li:hover .submenu a {
-  color: #96a5ae;
-  border: none;
-}
-main header nav ul li:hover .submenu li:hover a {
-  color: #3c4144;
-}
-main header nav ul li.lang-flags {
-  display: flex;
-  align-items: center;
-  gap: 10px;
-  margin-left: auto;
-  padding: 0 20px;
-}
-main header nav ul li.lang-flags img {
-  width: 35px;
-  height: 30px;
-  cursor: pointer;
-  opacity: 0.5;
-}
-main header nav ul li.lang-flags img.selected, main header nav ul li.lang-flags img:hover {
-  opacity: 1;
-}
-main header nav .burger {
-  display: none;
-}
-@media screen and (max-width: 560px) {
-  main header nav {
-    justify-content: space-between;
-  }
-  main header nav .burger {
-    display: flex;
-    justify-content: center;
-    align-items: center;
-    flex-direction: row;
-    flex-direction: column;
-    font-weight: bold;
-    border: 1px solid;
-    margin: 0 20px;
-    cursor: pointer;
-    border-radius: 100%;
-    width: 35px;
-    height: 35px;
-    color: #555d61;
-    font-size: 25px;
-  }
-  main header nav ul {
-    display: none;
-  }
-  main header nav ul.responsive-show {
-    display: flex;
-    flex-direction: column;
-    position: absolute;
-    right: 0;
-    max-width: 100vw;
-    min-width: 50vw;
-    top: 60px;
-    background-color: white;
-    box-shadow: 0 4px 6px 2px #0000000a;
-    height: unset;
-  }
-  main header nav ul.responsive-show li.active a {
-    border: none;
-  }
-  main header nav ul.responsive-show li .submenu {
-    display: flex;
-    visibility: visible;
-    position: relative;
-    height: unset;
-    max-height: unset;
-    transition: max-height 0.6s;
-    top: unset;
-    left: unset;
-    margin-left: 20px;
-  }
-  main header nav ul.responsive-show li .submenu li a {
-    font-weight: 400;
-    font-size: 14px;
-    color: #96a5ae;
-  }
-  main header nav ul.responsive-show li.lang-flags {
-    margin-left: unset;
-    justify-content: space-around;
-    padding: 20px;
-  }
-}
-main #page-container {
-  width: 100%;
-  flex: 1;
-}
-main #page-container .page-header {
-  background-image: url("/assets/images/wallpaper_binary.png");
-  padding: 50px 0;
-}
-main #page-container .page-header h1 {
-  padding: 15px 40px 0;
-  font-size: 25px;
-  color: #4baabb;
-  margin: 0 auto;
-}
-main #page-container .page-header p {
-  color: #72e3f0;
-  font-style: italic;
-  padding: 15px 40px 15px 100px;
-  margin: 0 auto;
-  font-size: 18px;
-}
-main #page-container .page-header p * {
-  color: #72e3f0;
-}
-main #page-container .page-header .big-logo {
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  flex-direction: row;
-  gap: 20px;
-  padding: 20px;
-}
-main #page-container .page-header .big-logo img {
-  width: 200px;
-  max-width: 100%;
-}
-main #page-container .page-header .big-logo img.logo-text {
-  width: 300px;
-  max-width: 100%;
-}
-main #page-container .page-header .logo {
-  padding-left: 30px;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  flex-direction: row;
-}
-main #page-container .page-header .logo img {
-  width: 100%;
-}
-@media screen and (max-width: 560px) {
-  main #page-container .page-header h1 {
-    padding: 15px 20px 0;
-  }
-  main #page-container .page-header p {
-    padding: 20px 20px 30px 40px;
-    text-align: justify;
-  }
-  main #page-container .page-header .big-logo {
-    flex-direction: column;
-  }
-}
-main #page-container .page-header.logo-left .grid-wrapper {
-  display: grid;
-  grid-template-columns: 120px 1fr;
-  grid-template-rows: auto 1fr;
-}
-main #page-container .page-header.logo-left .grid-wrapper h1 {
-  width: 100%;
-}
-main #page-container .page-header.logo-left .grid-wrapper .logo {
-  grid-column: 1;
-  grid-row: 1;
-  width: 100%;
-}
-main #page-container .page-header.logo-left .grid-wrapper p {
-  margin: 0;
-  grid-column: 1/span 2;
-}
-@media screen and (max-width: 780px) {
-  main #page-container .page-header.logo-left .grid-wrapper h1 {
-    padding: 0 20px;
-  }
-  main #page-container .page-header.logo-left .grid-wrapper .logo {
-    padding: 0 20px;
-  }
-}
-main #page-container .page-philo {
-  background-image: url("/assets/images/wallpaper_binary.png");
-  padding: 120px 30px;
-}
-main #page-container .page-philo p {
-  width: 100%;
-  max-width: 600px;
-  font-size: 18px;
-  color: #aabbc8;
-  text-align: center;
-  font-style: italic;
-  font-weight: bold;
-}
-main #page-container .page-philo p * {
-  color: #aabbc8;
-}
-main #page-container .page-contents-center {
-  width: 1300px;
-  max-width: 100%;
-  margin: 0 auto;
-}
-@media screen and (max-width: 1300px) {
-  main #page-container .page-contents-center {
-    padding: 20px 20px 0;
-  }
-}
-main #page-container h2.page-section-title {
-  color: #4baabb;
-  padding: 20px 0 10px;
-  width: 1300px;
-  max-width: 100%;
-  margin: 0 auto;
-}
-@media screen and (max-width: 1300px) {
-  main #page-container h2.page-section-title {
-    padding: 20px 20px 0;
-  }
-}
-main #page-container .article-details {
-  grid-column: 1/span 2;
-}
-main #page-container .article-details h2 {
-  color: #6b7880;
-  margin: 0 10px;
-  padding: 10px 0 0;
-  font-size: 16px;
-}
-main #page-container .article-details ul.details-list {
-  margin: 10px;
-}
-main #page-container .article-details ul.details-list .detail {
-  display: grid;
-  grid-template-columns: 1fr auto;
-  gap: 20px;
-  font-size: 12px;
-  border-bottom: 1px solid #d4d9dd;
-  padding: 5px 0;
-}
-main #page-container .article-details ul.details-list .detail label {
-  font-weight: bold;
-  color: #6b7880;
-}
-main #page-container .article-details ul.details-list .detail .detail-value {
-  text-align: right;
-}
-main #page-container #home-page {
-  display: flex;
-  flex-direction: column;
-}
-main #page-container #home-page .section-title {
-  padding: 10px;
-  margin: 0;
-  color: #aabbc8;
-}
-main #page-container #home-page .page-header .philo-bubbles {
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  flex-direction: row;
-  flex-wrap: wrap;
-  gap: 40px;
-  margin: 30px 20px;
-}
-@media screen and (max-width: 780px) {
-  main #page-container #home-page .page-header .philo-bubbles {
-    gap: 20px;
-  }
-}
-main #page-container #home-page .page-header .philo-bubbles li {
-  border-radius: 100%;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  flex-direction: row;
-  background-color: #d4d9dd;
-  width: 100px;
-  height: 100px;
-}
-main #page-container #home-page .page-header .philo-bubbles li * {
-  color: #6b7880;
-}
-@media screen and (max-width: 560px) {
-  main #page-container #home-page .page-header .philo-bubbles li {
-    width: 75px;
-    height: 75px;
-  }
-  main #page-container #home-page .page-header .philo-bubbles li * {
-    font-size: 12px;
-  }
-}
-main #page-container #home-page .page-header .philo-bubbles li:first-child {
-  background-color: #6b7880;
-}
-main #page-container #home-page .page-header .philo-bubbles li:first-child * {
-  color: white;
-}
-main #page-container #home-page .page-header .philo-bubbles li:last-child {
-  background-color: #35393c;
-}
-main #page-container #home-page .page-header .philo-bubbles li:last-child * {
-  color: #96a5ae;
-}
-main #page-container #home-page .poles {
-  display: grid;
-  grid-template-columns: 1fr 1fr 1fr;
-  gap: 30px;
-  padding: 100px 0;
-}
-main #page-container #home-page .poles .theme-card {
-  display: flex;
-  flex-direction: column;
-  width: 100%;
-  cursor: pointer;
-  transition: transform 0.3s;
-}
-main #page-container #home-page .poles .theme-card .card-img {
-  width: 100%;
-  height: 240px;
-  overflow: hidden;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  flex-direction: row;
-  flex-direction: column;
-  position: relative;
-}
-main #page-container #home-page .poles .theme-card .card-img img {
-  position: absolute;
-  max-width: 100%;
-  height: 100%;
-  padding: 10px;
-}
-main #page-container #home-page .poles .theme-card .card-title h2 {
-  margin: 0;
-  text-align: center;
-  padding: 10px 20px;
-  color: #4baabb;
-  display: block;
-  background-color: white;
-}
-main #page-container #home-page .poles .theme-card .card-description {
-  flex: 1;
-  padding: 30px 20px;
-}
-main #page-container #home-page .poles .theme-card .card-description p {
-  margin: 0;
-  color: #4baabb;
-  text-align: center;
-}
-main #page-container #home-page .poles .theme-card:hover {
-  transform: scale(1.03);
-}
-main #page-container #home-page .kuadrado-values {
-  background-image: url("/assets/images/wallpaper_binary_light.png");
-  padding: 100px 0 120px;
-}
-main #page-container #home-page .kuadrado-values *:not(a, blue) {
-  color: #d4d9dd;
-}
-main #page-container #home-page .kuadrado-values h2 {
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  flex-direction: row;
-  margin: 0 auto 60px;
-  width: 120px;
-  height: 120px;
-  background-image: url("/assets/images/wallpaper_binary.png");
-  border-radius: 100%;
-  color: #72e3f0;
-}
-main #page-container #home-page .kuadrado-values ul.values-list {
-  display: grid;
-  grid-template-columns: repeat(3, 1fr);
-  gap: 30px;
-}
-main #page-container #home-page .kuadrado-values ul.values-list li {
-  background-image: url("/assets/images/wallpaper_binary.png");
-  padding: 30px 20px 40px;
-}
-main #page-container #home-page .kuadrado-values ul.values-list li h3 {
-  text-align: center;
-}
-main #page-container #home-page .kuadrado-values ul.values-list li p {
-  text-align: justify;
-}
-@media screen and (max-width: 900px) {
-  main #page-container #home-page .poles {
-    grid-template-columns: 1fr;
-    gap: 40px;
-  }
-  main #page-container #home-page .poles .theme-card {
-    transition: transform 0.3s;
-  }
-  main #page-container #home-page .poles .theme-card .card-img {
-    height: 300px;
-  }
-  main #page-container #home-page .poles .theme-card .card-img img {
-    min-width: unset;
-    height: 100%;
-  }
-  main #page-container #home-page .poles .theme-card .card-title h2 {
-    padding: 5px 20px;
-  }
-  main #page-container #home-page .poles .theme-card .card-description {
-    padding: 20px 30px;
-  }
-  main #page-container #home-page .poles .theme-card:hover {
-    transform: none;
-  }
-  main #page-container #home-page .kuadrado-values ul.values-list {
-    grid-template-columns: 1fr;
-  }
-}
-@media screen and (max-width: 1300px) {
-  main #page-container #home-page .poles {
-    padding: 20px;
-  }
-  main #page-container #home-page .articles-displayer {
-    padding: 0;
-  }
-}
-main #page-container #education-page h3.big {
-  font-size: 30px;
-}
-main #page-container #education-page .title-banner {
-  display: flex;
-  justify-content: flex-end;
-  flex-direction: column;
-  height: 20vw;
-  min-height: 250px;
-  background-image: url("/assets/images/popularization_banner.png");
-  background-size: cover;
-  background-repeat: no-repeat;
-  background-position: center;
-}
-main #page-container #education-page .title-banner h2 {
-  color: white;
-  font-size: 2.5em;
-  margin: 40px;
-  text-shadow: 0 0 6px #0003;
-}
-main #page-container #education-page .special-announcement {
-  background-color: #ffd000;
-}
-main #page-container #education-page .special-announcement .page-contents-center {
-  padding: 0 20px;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  flex-direction: row;
-}
-main #page-container #education-page .special-announcement .page-contents-center p {
-  color: #555d61;
-  font-size: 20px;
-  font-weight: 600;
-  margin: 0;
-  padding: 40px 0;
-}
-main #page-container #education-page .edu-themes {
-  display: grid;
-  grid-template-columns: 1fr 1fr;
-  gap: 50px;
-  font-family: monospace;
-  padding: 70px 0;
-}
-main #page-container #education-page .edu-themes .edu-theme {
-  display: grid;
-  grid-template-columns: auto 1fr;
-}
-main #page-container #education-page .edu-themes .edu-theme * {
-  border-style: dashed;
-  border-color: #00ff00;
-  border-width: 0 0 0 0;
-}
-main #page-container #education-page .edu-themes .edu-theme h3 {
-  color: #00ff00;
-  grid-row: 1;
-  margin: 0;
-  padding: 10px;
-  display: flex;
-  align-items: center;
-  border-width: 0 0 0 1px;
-}
-main #page-container #education-page .edu-themes .edu-theme img {
-  width: 100%;
-  grid-row: 1/span 2;
-  border-width: 1px 0 1px 1px;
-}
-main #page-container #education-page .edu-themes .edu-theme p {
-  text-align: justify;
-  color: #72e3f0;
-  grid-row: 2;
-  margin: 0;
-  padding: 10px 30px 0 10px;
-  border-width: 1px 1px 1px 0;
-}
-main #page-container #education-page .edu-themes .edu-theme p * {
-  color: #72e3f0;
-}
-main #page-container #education-page .practical-info {
-  padding: 50px 0;
-}
-main #page-container #education-page .practical-info .page-contents-center {
-  display: grid;
-  grid-template-columns: 1fr 1fr;
-  gap: 50px;
-}
-main #page-container #education-page .practical-info .page-contents-center .info-block {
-  display: grid;
-  grid-template-rows: auto 1fr;
-}
-main #page-container #education-page .practical-info .page-contents-center .info-block .info-title {
-  color: #4baabb;
-  margin: 0;
-  border-bottom: 1px dashed #aabbc8;
-  border-left: 1px dashed #aabbc8;
-  padding: 10px;
-}
-main #page-container #education-page .practical-info .page-contents-center .info-block .info-body {
-  margin: 0;
-  padding: 20px 10px;
-  border-right: 1px dashed #aabbc8;
-  border-bottom: 1px dashed #aabbc8;
-}
-main #page-container #education-page .practical-info .page-contents-center .info-block ul {
-  display: flex;
-  flex-direction: column;
-  gap: 5px;
-}
-main #page-container #education-page .practical-info .page-contents-center .info-block ul li:not(.fullwidth) {
-  display: grid;
-  grid-template-columns: 1fr 1fr;
-  gap: 10px;
-}
-main #page-container #education-page .practical-info .page-contents-center .info-block ul.tabled li span {
-  padding: 3px 0;
-}
-main #page-container #education-page .practical-info .page-contents-center .info-block ul.tabled li span:first-child {
-  font-weight: bold;
-  color: #6b7880;
-}
-main #page-container #education-page .practical-info .page-contents-center .info-block ul.tabled li span:last-child {
-  color: #4baabb;
-}
-@media screen and (max-width: 1200px) {
-  main #page-container #education-page .edu-themes {
-    grid-template-columns: 1fr;
-    gap: 30px;
-    padding: 70px 0;
-  }
-}
-@media screen and (max-width: 780px) {
-  main #page-container #education-page .practical-info .page-contents-center {
-    grid-template-columns: 1fr;
-    gap: 30px;
-  }
-  main #page-container #education-page .practical-info .page-contents-center .info-block .info-title {
-    border-top: 1px dashed #aabbc8;
-  }
-  main #page-container #education-page .practical-info .page-contents-center .info-block .info-body {
-    border-bottom: none;
-  }
-}
-@media screen and (max-width: 560px) {
-  main #page-container #education-page .edu-themes .edu-theme h3 {
-    border-width: 0 0 1px 1px;
-  }
-  main #page-container #education-page .edu-themes .edu-theme img {
-    max-width: 150px;
-    height: auto;
-    grid-row: 1;
-    border-width: 1px 0 0 1px;
-  }
-  main #page-container #education-page .edu-themes .edu-theme p {
-    grid-row: 2;
-    grid-column: 1/span 2;
-    padding: 20px 10px 30px 10px;
-    border-width: 0 1px 1px 1px;
-  }
-}
-main #page-container #games-page .game-articles article {
-  display: grid;
-  grid-template-columns: 0.7fr 1fr;
-  gap: 30px 50px;
-  margin: 20px 0;
-}
-main #page-container #games-page .game-articles article.game-article {
-  grid-template-rows: repeat(7, auto);
-  width: 100%;
-}
-main #page-container #games-page .game-articles article.game-article .game-title {
-  grid-column: 1/span 2;
-  margin: 0;
-  padding: 30px 20px;
-  color: #aabbc8;
-  font-size: 35px;
-  font-style: italic;
-}
-main #page-container #games-page .game-articles article.game-article .game-banner {
-  grid-column: 1/span 2;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  flex-direction: row;
-  flex-direction: column;
-  background-color: black;
-  overflow: hidden;
-  height: 300px;
-}
-main #page-container #games-page .game-articles article.game-article .game-banner img {
-  width: 100%;
-}
-main #page-container #games-page .game-articles article.game-article .game-tags {
-  display: flex;
-  gap: 10px;
-  flex-wrap: wrap;
-  margin: 10px 20px;
-}
-main #page-container #games-page .game-articles article.game-article .game-tags span {
-  font-size: 12px;
-  padding: 4px;
-  background-color: #d4d9dd;
-  color: #6b7880;
-  border-radius: 5px;
-  font-weight: 600;
-}
-main #page-container #games-page .game-articles article.game-article .game-subtitle {
-  grid-column: 1;
-  margin: 10px 20px;
-  color: #6b7880;
-}
-main #page-container #games-page .game-articles article.game-article .game-description {
-  grid-column: 1;
-  text-align: justify;
-  margin: 10px 20px 30px;
-}
-main #page-container #games-page .game-articles article.game-article .image-carousel {
-  grid-column: 2;
-  grid-row: 3/span 4;
-  height: 400px;
-}
-main #page-container #games-page .game-articles article.placeholder {
-  height: 400px;
-}
-main #page-container #games-page .game-articles article.placeholder * {
-  background-color: #d4d9dd;
-}
-@media screen and (max-width: 900px) {
-  main #page-container #games-page .game-articles article {
-    grid-template-columns: 1fr;
-  }
-  main #page-container #games-page .game-articles article.game-article {
-    grid-template-rows: repeat(6, auto);
-  }
-  main #page-container #games-page .game-articles article.game-article .game-title {
-    grid-column: 1;
-    padding: 0;
-    font-size: 25px;
-  }
-  main #page-container #games-page .game-articles article.game-article .game-banner {
-    grid-column: 1;
-    margin: 0 -20px;
-    height: 200px;
-  }
-  main #page-container #games-page .game-articles article.game-article .image-carousel {
-    grid-column: 1;
-    grid-row: 3;
-    margin: 0 -20px;
-  }
-}
-main #page-container #games-page .game-articles article .play-button {
-  border: none;
-  background-color: unset;
-  font-weight: bold;
-  font-size: 20px;
-  cursor: pointer;
-  color: #4baabb;
-}
-main #page-container #games-page .game-articles article .play-button:hover {
-  color: #72e3f0;
-}
-main #page-container #software-page .software-articles {
-  margin: 20px auto 50px;
-}
-main #page-container #software-page .software-articles article.software-article {
-  display: grid;
-  grid-template-columns: auto 1fr;
-  margin: 0 0 50px;
-  gap: 10px 30px;
-}
-main #page-container #software-page .software-articles article.software-article .software-title {
-  grid-column: 2;
-  color: #aabbc8;
-  margin: 0;
-  padding: 10px;
-}
-main #page-container #software-page .software-articles article.software-article .software-subtitle {
-  grid-column: 2;
-  margin: 10px;
-  color: #6b7880;
-}
-main #page-container #software-page .software-articles article.software-article .software-description {
-  grid-column: 2;
-  text-align: justify;
-  margin: 10px;
-}
-main #page-container #software-page .software-articles article.software-article .software-image {
-  padding: 20px;
-  background-color: black;
-  grid-column: 1;
-  grid-row: 1/span 3;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  flex-direction: row;
-  width: 200px;
-  height: 200px;
-  overflow: hidden;
-  border-radius: 100%;
-}
-main #page-container #software-page .software-articles article.software-article .software-image img {
-  max-width: 100%;
-  max-height: 400px;
-}
-@media screen and (max-width: 900px) {
-  main #page-container #software-page .software-articles article.software-article .software-title {
-    display: flex;
-    align-items: center;
-  }
-  main #page-container #software-page .software-articles article.software-article .software-subtitle,
-main #page-container #software-page .software-articles article.software-article .software-description {
-    grid-column: 1/span 2;
-  }
-  main #page-container #software-page .software-articles article.software-article .software-image {
-    width: 100px;
-    height: 100px;
-    grid-row: 1;
-  }
-}
-main #page-container #software-page .software-articles article.placeholder {
-  display: flex;
-  flex-direction: column;
-  gap: 10px;
-  margin: 30px;
-}
-main #page-container #software-page .software-articles article.placeholder * {
-  background-color: #d4d9dd;
-}
-main #page-container #software-page .software-articles article.placeholder .title {
-  height: 60px;
-}
-main #page-container #software-page .software-articles article.placeholder .body {
-  height: 400px;
-}
-main #page-container #software-page .software-articles article.placeholder .details {
-  height: 200px;
-}
-main footer {
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  flex-direction: row;
-  flex-direction: column;
-  width: 100%;
-  background-image: url("/assets/images/wallpaper_binary.png");
-  padding: 40px 20px;
-  gap: 20px;
-  font-size: 12px;
-}
-main footer span {
-  color: #96a5ae;
-  text-align: center;
-}
-main footer .logo {
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  flex-direction: row;
-  gap: 10px;
-}
-main footer .logo img {
-  width: 35px;
-}
-main footer .logo img.text-logo {
-  width: 100px;
-}
-main footer .social {
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  flex-direction: row;
-  gap: 20px;
-}
-main footer .social a {
-  background-color: #555d61;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  flex-direction: row;
-  width: 25px;
-  height: 25px;
-  font-weight: bold;
-  font-size: 16px;
-  border-radius: 100%;
-}
-
-/*# sourceMappingURL=style.css.map */
-- 
GitLab