Index: /main/trunk/greenstone3/web/interfaces/default/cc/cookieconsent-gsinit.js
===================================================================
--- /main/trunk/greenstone3/web/interfaces/default/cc/cookieconsent-gsinit.js	(revision 37732)
+++ /main/trunk/greenstone3/web/interfaces/default/cc/cookieconsent-gsinit.js	(revision 37732)
@@ -0,0 +1,122 @@
+// @ts-nocheck
+
+/**
+ * Initialisation for the cookieconsent.js script, originally based on version 2.7.2.
+ * See https://github.com/orestbida/cookieconsent for customisation options.
+ * The translation strings that are referenced in this document via 'gs.text.ccm.XXX'
+ * can be found in the 'interface_default<_LANG>.properties' files, under 'WEB-INF/classes'.
+ */
+
+//alert("same_site = Strict");
+
+// obtain plugin
+var cc = initCookieConsent();
+
+// run plugin with your configuration
+cc.run({
+    current_lang: 'en',
+    autoclear_cookies: false,                  // default: false
+    page_scripts: true,                        // default: false
+    cookie_path: "/" + gs.xsltParams.cookie_path,  // default: "/"
+    
+    gui_options: {
+        consent_modal: {
+            layout: 'cloud',                   // box/cloud/bar
+            position: 'bottom left',           // bottom/middle/top + left/right/center
+            transition: 'slide',               // zoom/slide
+            swap_buttons: false                // enable to invert buttons
+        },
+        settings_modal: {
+            layout: 'box',                     // box/bar
+            // position: 'left',               // left/right - only valid with 'bar' layout
+            transition: 'slide'                // zoom/slide
+        }
+    },
+
+    // mode: 'opt-in'                          // default: 'opt-in'; value: 'opt-in' or 'opt-out'
+    // delay: 0,                               // default: 0
+    // auto_language: null                     // default: null; could also be 'browser' or 'document'
+    // autorun: true,                          // default: true
+    // force_consent: false,                   // default: false
+    // hide_from_bots: false,                  // default: false
+    // remove_cookie_tables: false             // default: false
+    // cookie_name: 'cc_cookie',               // default: 'cc_cookie'
+    // cookie_expiration: 182,                 // default: 182 (days)
+    // cookie_necessary_only_expiration: 182   // default: disabled
+    // cookie_domain: location.hostname,       // default: current domain
+    // // cookie_same_site: 'Lax',                // default: 'Lax'
+    // cookie_same_site: 'Strict',                // consider making 'Strict' if an https site ?!?! (GS3)
+    // use_rfc_cookie: false,                  // default: false
+    // revision: 0,                            // default: 0
+
+    onFirstAction: function(user_preferences, cookie){
+        // callback triggered only once
+    },
+
+    onAccept: function (cookie) {
+        // ...
+    },
+
+    onChange: function (cookie, changed_preferences) {
+        // ...
+    },
+
+    languages: {
+        'en': {
+            consent_modal: {
+                title: gs.text.ccm.Popup_Title,
+                description: gs.text.ccm.Popup_Description + ' <button type="button" data-cc="c-settings" class="cc-link">'
+                    + gs.text.ccm.Popup_OpenAdvancedManager + '</button>',
+                primary_btn: {
+                    text: gs.text.ccm.Popup_AcceptAllCookies,
+                    role: 'accept_necessary'              // 'accept_selected' or 'accept_all'
+                },
+                // secondary_btn: {
+                //     text: gs.text.ccm.Popup_AcceptNecessaryCookies,
+                //     role: 'accept_necessary'        // 'settings' or 'accept_necessary'
+                // }
+            },
+            settings_modal: {
+                title: gs.text.ccm.Manager_Title,
+                save_settings_btn: gs.text.ccm.Manager_SaveSettingsButton,
+                accept_all_btn: gs.text.ccm.Manager_AcceptAllButton,
+                // reject_all_btn: ccm.Manager_AcceptNecessaryButton,
+                close_btn_label: gs.text.ccm.Manager_CloseButton,
+                cookie_table_headers: [
+                    { name: gs.text.ccm.Manager.CookieTableHeaders_Name },
+                    { provider: gs.text.ccm.Manager.CookieTableHeaders_Provider },
+                    { expiration: gs.text.ccm.Manager.CookieTableHeaders_Expiration },
+                    { description: gs.text.ccm.Manager.CookieTableHeaders_Description }
+                ],
+                blocks: [
+                    {
+                        description: gs.text.ccm.Manager_Description
+                    },
+                    {
+                        title: gs.text.ccm.Manager.RequiredCookies_Title,
+                        description: gs.text.ccm.Manager.RequiredCookies_Description,
+                        toggle: {
+                            value: 'required', // The cookie collection to toggle
+                            enabled: true,
+                            readonly: true
+                        },
+                        cookie_table: [
+                            {
+                                name: 'JSESSIONID',
+                                provider: window.location.hostname,
+                                expiration: gs.text.ccm.Manager.CookieTable_EndOfSessionExpiration,
+                                description: gs.text.ccm.Manager.RequiredCookies.JSESSIONID_Description,
+                            },
+                            {
+                                name: 'supportsXSLT',
+                                provider: window.location.hostname,
+                                expiration: gs.text.ccm.Manager.CookieTable_EndOfSessionExpiration,
+                                description: gs.text.ccm.Manager.RequiredCookies.supportsXSLT_Description
+                            }
+                        ]
+                    }
+                ]
+            }
+        }
+    }
+});
Index: /main/trunk/greenstone3/web/interfaces/default/cc/cookieconsent.css
===================================================================
--- /main/trunk/greenstone3/web/interfaces/default/cc/cookieconsent.css	(revision 37732)
+++ /main/trunk/greenstone3/web/interfaces/default/cc/cookieconsent.css	(revision 37732)
@@ -0,0 +1,1351 @@
+/** Light color-scheme **/
+:root{
+    --cc-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
+    --cc-font-size: 16px;
+    --cc-bg: #fff;
+    --cc-z-index: 2147483647;
+    --cc-text: #2d4156;
+    --cc-border-radius: .45rem;
+    --cc-btn-primary-bg: #2d4156;
+    --cc-btn-primary-text: var(--cc-bg);
+    --cc-btn-primary-hover-bg: #1d2e38;
+    --cc-btn-primary-hover-text: var(--cc-btn-primary-text);
+    --cc-btn-secondary-bg: #eaeff2;
+    --cc-btn-secondary-text: var(--cc-text);
+    --cc-btn-secondary-hover-bg: #d8e0e6;
+    --cc-btn-secondary-hover-text: var(--cc-btn-secondary-text);
+    --cc-btn-border-radius: 0.375rem;
+    --cc-toggle-bg-off: #919ea6;
+    --cc-toggle-bg-on: var(--cc-btn-primary-bg);
+    --cc-toggle-bg-readonly: #d5dee2;
+    --cc-toggle-knob-bg: #fff;
+    --cc-toggle-knob-icon-color: #ecf2fa;
+    --cc-block-text: var(--cc-text);
+    --cc-cookie-category-block-bg: #f0f4f7;
+    --cc-cookie-category-block-bg-hover: #e9eff4;
+    --cc-section-border: #f1f3f5;
+    --cc-cookie-table-border: #e9edf2;
+    --cc-overlay-bg: rgb(4, 6, 8);
+    --cc-overlay-opacity: .85;
+    --cc-consent-modal-box-shadow: 0 0.625rem 1.875rem rgba(2, 2, 3, 0.28);
+    --cc-webkit-scrollbar-bg: #cfd5db;
+    --cc-webkit-scrollbar-bg-hover: #9199a0;
+}
+
+/** Dark color-scheme **/
+.c_darkmode{
+    --cc-bg: #181b1d;
+    --cc-text: #d8e5ea;
+    --cc-btn-primary-bg: #a6c4dd;
+    --cc-btn-primary-text: #000;
+    --cc-btn-primary-hover-bg: #c2dff7;
+    --cc-btn-primary-hover-text: var(--cc-btn-primary-text);
+    --cc-btn-secondary-bg: #33383c;
+    --cc-btn-secondary-text: var(--cc-text);
+    --cc-btn-secondary-hover-bg: #3e454a;
+    --cc-btn-secondary-hover-text: var(--cc-btn-secondary-text);
+    --cc-toggle-bg-off: #667481;
+    --cc-toggle-bg-on: var(--cc-btn-primary-bg);
+    --cc-toggle-bg-readonly: #454c54;
+    --cc-toggle-knob-bg: var(--cc-cookie-category-block-bg);
+    --cc-toggle-knob-icon-color: var(--cc-bg);
+    --cc-block-text: #b3bfc5;
+    --cc-cookie-category-block-bg: #23272a;
+    --cc-cookie-category-block-bg-hover: #2b3035;
+    --cc-section-border: #292d31;
+    --cc-cookie-table-border: #2b3035;
+    --cc-webkit-scrollbar-bg: #667481;
+    --cc-webkit-scrollbar-bg-hover: #9199a0;
+}
+
+#cc--main {
+    z-index: var(--cc-z-index);
+}
+
+.cc_div *,
+.cc_div *:hover,
+.cc_div :before,
+.cc_div :after{
+    -webkit-box-sizing: border-box;
+    box-sizing: border-box;
+    float: none;
+    font-style: inherit;
+    font-variant: normal;
+    font-weight: inherit;
+    font-family: inherit;
+	line-height: 1.2;
+    font-size: 1em;
+    transition: none;
+    animation: none;
+    margin: 0;
+    padding: 0;
+    text-transform: none;
+    letter-spacing: unset;
+    color: inherit;
+    background: none;
+    border: none;
+    border-radius: unset;
+    box-shadow: none;
+    text-decoration: none;
+    text-align: left;
+    visibility: unset;
+    height: auto;
+    vertical-align: baseline;
+}
+
+.cc_div {
+    font-size: var(--cc-font-size);
+    font-weight: 400;
+    font-family: var(--cc-font-family);
+    -webkit-font-smoothing: antialiased;
+    -moz-osx-font-smoothing: grayscale;
+    text-rendering: optimizeLegibility;
+    color: var(--cc-text);
+}
+
+.cc_div .c-bn,
+.cc_div .b-tl,
+#s-ttl,
+#c-ttl,
+#s-bl td:before{
+    font-weight: 600;
+}
+
+#cm,
+#s-inr,
+.cc_div .c-bl,
+.cc_div .b-tl,
+#s-bl .act .b-acc{
+    border-radius: var(--cc-border-radius);
+}
+
+#s-bl .act .b-acc{
+    border-top-left-radius: 0;
+    border-top-right-radius: 0;
+}
+
+.cc_div input,
+.cc_div button,
+.cc_div a{
+    -webkit-appearance: none;
+    -moz-appearance: none;
+    appearance: none;
+    overflow: hidden;
+}
+
+.cc_div a{
+    border-bottom: 1px solid;
+}
+
+.cc_div a:hover{
+    text-decoration: none;
+    border-color: transparent;
+}
+
+/* Make elements "animatable" */
+.c--anim #cm,
+.c--anim #s-cnt,
+.c--anim #s-inr,
+#cs-ov,
+#cm-ov{
+    transition: visibility .25s linear, opacity .25s ease, transform .25s ease!important;
+}
+
+.c--anim .c-bn{
+    transition: background-color .25s ease!important;
+}
+
+/* start transitions */
+.c--anim #cm.bar.slide,
+.c--anim .bar.slide #s-inr{
+    transition: visibility .4s ease, opacity .4s ease, transform .4s ease!important;
+}
+
+.c--anim #cm.bar.slide + #cm-ov,
+.c--anim .bar.slide + #cs-ov{
+    transition: visibility .4s ease, opacity .4s ease, transform .4s ease!important;
+}
+
+#cm.bar.slide,
+.cc_div .bar.slide #s-inr{
+    transform: translateX(100%);
+    opacity: 1;
+}
+
+#cm.bar.top.slide,
+.cc_div .bar.left.slide #s-inr{
+    transform: translateX(-100%);
+    opacity: 1;
+}
+
+#cm.slide,
+.cc_div .slide #s-inr{
+    transform: translateY(1.6em);
+}
+
+#cm.top.slide{
+    transform: translateY(-1.6em);
+}
+
+#cm.bar.slide{
+    transform: translateY(100%);
+}
+
+#cm.bar.top.slide{
+    transform: translateY(-100%);
+}
+/* end transitions */
+
+/* Show modals */
+
+.show--consent .c--anim #cm,
+.show--consent .c--anim #cm.bar,
+.show--settings .c--anim #s-inr,
+.show--settings .c--anim .bar.slide #s-inr{
+    opacity: 1;
+    transform: scale(1);
+    visibility: visible!important;
+}
+
+.show--consent .c--anim #cm.box.middle,
+.show--consent .c--anim #cm.cloud.middle{
+    transform: scale(1) translateY(-50%);
+}
+
+.show--settings .c--anim #s-cnt{
+    visibility: visible!important;
+}
+
+/* Show overlays */
+.force--consent.show--consent .c--anim #cm-ov,
+.show--settings .c--anim #cs-ov{
+    visibility: visible!important;
+    opacity: var(--cc-overlay-opacity)!important;
+}
+
+#cm{
+    font-family: inherit;
+    padding: 1em 1.4em 1.3em 1.4em;
+    position: fixed;
+    z-index: 1;
+    background: var(--cc-bg);
+    max-width: 24.2em;
+    width: 100%;
+    bottom: 1.250em;
+    right: 1.250em;
+    box-shadow: 0 0.625em 1.875em #000000;
+    box-shadow: var(--cc-consent-modal-box-shadow);
+    opacity: 0;
+    visibility: hidden;
+    transform: scale(.95);
+    line-height: initial;
+}
+
+/** fix https://github.com/orestbida/cookieconsent/issues/94 **/
+#cc_div #cm {
+    display: block!important;
+}
+
+#c-ttl{
+    margin-bottom: .7em;
+    font-size: 1.05em;
+}
+
+.cloud #c-ttl{
+    margin-top: -.15em;
+}
+
+#c-txt{
+    font-size: 0.9em;
+    line-height: 1.5em;
+}
+
+.cc_div #c-bns{
+    display: flex;
+    justify-content: space-between;
+    margin-top: 1.4em;
+}
+
+.cc_div .c-bn{
+    color: var(--cc-btn-secondary-text);
+    background: var(--cc-btn-secondary-bg);
+    padding: 1em 1.7em;
+    display: inline-block;
+    cursor: pointer;
+    font-size: 0.82em;
+    -moz-user-select: none;
+    -khtml-user-select: none;
+    -webkit-user-select: none;
+    -o-user-select: none;
+    user-select: none;
+    text-align: center;
+    border-radius: var(--cc-btn-border-radius);
+    flex: 1;
+}
+
+#c-bns button + button,
+#s-cnt button + button,
+#s-c-bn{
+    float: right;
+    margin-left: .5rem;
+}
+
+#s-cnt #s-rall-bn{
+    float: none;
+}
+
+#cm .c_link:hover,
+#cm .c_link:active,
+#s-cnt button + button:hover,
+#s-cnt button + button:active,
+#s-c-bn:active,
+#s-c-bn:hover{
+    background: var(--cc-btn-secondary-hover-bg);
+    color: var(--cc-btn-secondary-hover-text);
+}
+
+/**
+CookieConsent settings modal
+**/
+#s-cnt{
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+    z-index: 101;
+    display: table;
+    height: 100%;
+    visibility: hidden;
+}
+
+#s-bl{
+    outline: none;
+    scrollbar-width: thin;
+}
+
+#s-bl .title{
+    margin-top: 1.4em;
+}
+
+#s-bl .title:first-child{
+    margin-top: 0;
+}
+
+#s-bl .b-bn{
+    margin-top: 0;
+}
+
+#s-bl .b-acc .p{
+    margin-top: 0;
+    padding: 1em;
+}
+
+#s-cnt .b-bn .b-tl{
+    display: block;
+    font-family: inherit;
+    font-size: .95em;
+    width: 100%;
+    position: relative;
+    padding: 1.3em 6.4em 1.3em 2.7em;
+    background: none;
+    transition: background-color .25s ease;
+}
+
+#s-cnt .b-bn .b-tl.exp{
+    cursor: pointer;
+}
+
+#s-cnt .act .b-bn .b-tl{
+    border-bottom-right-radius: 0;
+    border-bottom-left-radius: 0;
+}
+
+#s-cnt .b-bn .b-tl:active,
+#s-cnt .b-bn .b-tl:hover{
+    background: var(--cc-cookie-category-block-bg-hover);
+}
+
+#s-bl .b-bn{
+    position: relative;
+}
+
+#s-bl .c-bl{
+    padding: 1em;
+    margin-bottom: .4rem;
+    border: 1px solid var(--cc-section-border);
+    transition: background-color .25s ease;
+}
+
+#s-bl .c-bl:hover{
+    background: var(--cc-cookie-category-block-bg);
+}
+
+#s-bl .c-bl:last-child{
+    margin-bottom: .5em;
+}
+
+#s-bl .c-bl:first-child{
+    transition: none;
+    padding: 0;
+    margin-top: 0;
+    border:none;
+    margin-bottom: 2em;
+}
+
+#s-bl .c-bl:not(.b-ex):first-child:hover{
+    background: transparent;
+    background: unset;
+}
+
+#s-bl .c-bl.b-ex{
+    padding: 0;
+    border: none;
+    background: var(--cc-cookie-category-block-bg);
+    transition: none;
+}
+
+#s-bl .c-bl.b-ex + .c-bl{
+    margin-top: 2em;
+}
+
+#s-bl .c-bl.b-ex + .c-bl.b-ex{
+    margin-top: 0;
+}
+
+#s-bl .c-bl.b-ex:first-child{
+    margin-bottom: 1em;
+}
+
+#s-bl .c-bl.b-ex:first-child{
+    margin-bottom: .5em;
+}
+
+#s-bl .b-acc{
+    max-height: 0;
+    overflow: hidden;
+    padding-top: 0;
+    margin-bottom: 0;
+    display: none;
+}
+
+#s-bl .act .b-acc{
+    max-height: 100%;
+    display: block;
+    overflow: hidden;
+}
+
+#s-cnt .p{
+    font-size: 0.9em;
+    line-height: 1.5em;
+    margin-top: .85em;
+    color: var(--cc-block-text);
+}
+
+.cc_div .b-tg .c-tgl:disabled{
+    cursor: not-allowed;
+}
+
+#c-vln{
+    display: table-cell;
+    vertical-align: middle;
+    position: relative;
+}
+
+#cs{
+    padding: 0 1.7em;
+    width: 100%;
+    position: fixed;
+    left: 0;
+    right: 0;
+    top: 0;
+    bottom: 0;
+    height: 100%;
+}
+
+#s-inr{
+    height: 100%;
+    position: relative;
+    max-width: 45em;
+    margin: 0 auto;
+    transform: scale(.96);
+    opacity: 0;
+    padding-top: 4.75em;
+    padding-bottom: 4.75em;
+    position: relative;
+    height: 100%;
+    overflow: hidden;
+    visibility: hidden;
+    box-shadow: rgba(3, 6, 9, .26) 0px 13px 27px -5px;
+}
+
+#s-inr,
+#s-hdr,
+#s-bns{
+    background: var(--cc-bg);
+}
+
+#s-bl{
+	overflow-y: auto;
+    overflow-y: overlay;
+    overflow-x: hidden;
+    height: 100%;
+    padding: 1.3em 1.6em;
+    display: block;
+    width: 100%;
+}
+
+#s-bns{
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    padding: 1em 1.8em;
+    border-top: 1px solid var(--cc-section-border);
+    height: 4.75em;
+}
+
+.cc_div .cc-link{
+    color: var(--cc-btn-primary-bg);
+    border-bottom: 1px solid var(--cc-btn-primary-bg);
+    display: inline;
+    padding-bottom: 0;
+    text-decoration: none;
+    cursor: pointer;
+    font-weight: 600;
+}
+
+.cc_div .cc-link:hover,
+.cc_div .cc-link:active{
+    border-color: transparent;
+}
+
+#c-bns button:first-child,
+#s-bns button:first-child{
+    color: var(--cc-btn-primary-text);
+    background: var(--cc-btn-primary-bg);
+}
+
+#c-bns.swap button:first-child{
+    color: var(--cc-btn-secondary-text);
+    background: var(--cc-btn-secondary-bg);
+}
+
+#c-bns.swap button:last-child{
+    color: var(--cc-btn-primary-text);
+    background: var(--cc-btn-primary-bg);
+}
+
+.cc_div .b-tg .c-tgl:checked ~ .c-tg{
+    background: var(--cc-toggle-bg-on);
+}
+
+#c-bns button:first-child:active,
+#c-bns button:first-child:hover,
+#s-bns button:first-child:active,
+#s-bns button:first-child:hover,
+#c-bns.swap button:last-child:active,
+#c-bns.swap button:last-child:hover{
+    background: var(--cc-btn-primary-hover-bg);
+    color: var(--cc-btn-primary-hover-text);
+}
+
+#c-bns.swap button:first-child:active,
+#c-bns.swap button:first-child:hover{
+    background: var(--cc-btn-secondary-hover-bg);
+    color: var(--cc-btn-secondary-hover-text);
+}
+
+#s-hdr{
+    position: absolute;
+    top: 0;
+    width: 100%;
+    display: table;
+    padding: 0 1.8em;
+    height: 4.75em;
+    vertical-align: middle;
+    z-index: 2;
+    border-bottom: 1px solid var(--cc-section-border);
+}
+
+#s-ttl{
+	display: table-cell;
+    vertical-align: middle;
+    font-size: 1em;
+}
+
+#s-c-bn{
+    padding: 0;
+    width: 1.7em;
+    height: 1.7em;
+    font-size: 1.45em;
+    margin: 0;
+    font-weight: initial;
+    position: relative;
+    overflow: hidden;
+}
+
+#s-c-bnc{
+    display: table-cell;
+    vertical-align: middle;
+}
+
+.cc_div span.t-lb {
+    position: absolute;
+    top: 0;
+    z-index: -1;
+    opacity: 0;
+    pointer-events: none;
+    overflow: hidden;
+}
+
+#c_policy__text{
+    height: 31.250em;
+    overflow-y: auto;
+    margin-top: 1.250em;
+}
+
+#c-s-in{
+    position: relative;
+    transform: translateY(-50%);
+    top: 50%;
+    height: 100%;
+    height: calc(100% - 2.5em);
+    max-height: 37.5em;
+}
+
+@media screen and (min-width: 688px) {
+    /** works only on webkit-based browsers **/
+    #s-bl::-webkit-scrollbar{
+        width: .8em;
+        height: 100%;
+        background: transparent;
+        border-radius: 0 0.250em 0.250em 0;
+    }
+
+    #s-bl::-webkit-scrollbar-thumb{
+        border: 0.25em solid var(--cc-bg);
+        background: var(--cc-webkit-scrollbar-bg);
+        border-radius: 100em;
+    }
+
+    #s-bl::-webkit-scrollbar-thumb:hover{
+        background: var(--cc-webkit-scrollbar-bg-hover);
+    }
+
+    #s-bl::-webkit-scrollbar-button {
+        width: 10px;
+        height: 5px;
+    }
+}
+
+/** custom checkbox **/
+/* The container */
+.cc_div .b-tg {
+    position: absolute;
+    right: 0;
+    top: 0;
+    bottom: 0;
+    display: inline-block;
+    margin: auto;
+    right: 1.2em;
+    cursor: pointer;
+    -webkit-user-select: none;
+    -moz-user-select: none;
+    -ms-user-select: none;
+    user-select: none;
+    vertical-align: middle;
+}
+
+/* Hide the browser's default checkbox */
+.cc_div .b-tg .c-tgl {
+    position: absolute;
+    cursor: pointer;
+    display: block;
+    top: 0;
+    left: 0;
+    margin: 0;
+    border: 0;
+}
+
+/* Create a custom checkbox */
+.cc_div .b-tg .c-tg {
+    position: absolute;
+    background: var(--cc-toggle-bg-off);
+    transition: background-color .25s ease, box-shadow .25s ease;
+    pointer-events: none;
+}
+
+.cc_div span.t-lb,
+.cc_div .b-tg,
+.cc_div .b-tg .c-tg,
+.cc_div .b-tg .c-tgl{
+    width: 3.4em;
+    height: 1.5em;
+    border-radius: 4em;
+}
+
+.cc_div .b-tg .c-tg.c-ro{
+    cursor: not-allowed;
+}
+
+.cc_div .b-tg .c-tgl ~ .c-tg.c-ro{
+    background: var(--cc-toggle-bg-readonly);
+}
+
+.cc_div .b-tg .c-tgl ~ .c-tg.c-ro:after{
+    box-shadow: none;
+}
+
+/* Style the checkmark/indicator */
+.cc_div .b-tg .c-tg:after {
+    content: "";
+    position: relative;
+    display: block;
+    left: 0.125em;
+    top: 0.125em;
+    width: 1.25em;
+    height: 1.25em;
+    border: none;
+    box-sizing: content-box;
+    background: var(--cc-toggle-knob-bg);
+    box-shadow: 0 1px 2px rgba(24, 32, 35, .36);
+    transition: transform .25s ease;
+    border-radius: 100%;
+}
+
+/* Show the checkmark when checked */
+.cc_div .b-tg .c-tgl:checked ~ .c-tg:after{
+    transform: translateX(1.9em);
+}
+
+#s-bl table,
+#s-bl th,
+#s-bl td{
+    border: none;
+}
+
+#s-bl tbody tr{
+    transition: background-color .25s ease;
+}
+
+#s-bl tbody tr:hover{
+    background: var(--cc-cookie-category-block-bg-hover);
+}
+
+#s-bl table {
+    text-align: left;
+    border-collapse: collapse;
+    width: 100%;
+    padding: 0;
+    margin: 0;
+    overflow: hidden;
+}
+
+#s-bl caption {
+    padding: 0.5rem 1rem;
+    font-size: .9em;
+    border-bottom: 1px solid var(--cc-cookie-table-border);
+    font-weight: 600;
+}
+
+#s-bl td,
+#s-bl th {
+    padding: 0.8em 0.625em;
+    text-align: left;
+    vertical-align: top;
+    font-size: .8em;
+    padding-left: 1.2em;
+}
+
+#s-bl th {
+	font-family: inherit;
+    padding: 1em 1rem;
+    font-weight: 600;
+}
+
+#s-bl thead tr:first-child{
+    border-bottom: 1px solid var(--cc-cookie-table-border);
+}
+
+.force--consent #s-cnt,
+.force--consent #cs{
+    width: 100vw;
+}
+
+#cm-ov,
+#cs-ov{
+    position: fixed;
+    left: 0;
+    right: 0;
+    top: 0;
+    bottom: 0;
+    visibility: hidden;
+    opacity: 0;
+    background: #070707;
+    background: var(--cc-overlay-bg);
+    display: none;
+    transition: none;
+}
+
+.show--settings #cs-ov,
+.c--anim #cs-ov,
+.force--consent .c--anim #cm-ov,
+.force--consent.show--consent #cm-ov{
+    display: block;
+}
+
+#cs-ov{
+    z-index: 2;
+}
+
+.force--consent .cc_div{
+    position: fixed;
+    top: 0;
+    left: 0;
+    bottom: 0;
+    width: 100%;
+    width: 100vw;
+    visibility: hidden;
+    transition: visibility .25s linear;
+}
+
+.force--consent.show--consent .c--anim .cc_div,
+.force--consent.show--settings .c--anim .cc_div{
+    visibility: visible;
+}
+
+.force--consent #cm{
+    position: absolute;
+}
+
+.force--consent #cm.bar{
+    width: 100vw;
+    max-width: 100vw;
+}
+
+html.force--consent.show--consent{
+    overflow-y: hidden!important;
+}
+
+html.force--consent.show--consent,
+html.force--consent.show--consent body{
+    height: auto!important;
+    overflow-x: hidden!important;
+}
+/** END BLOCK PAGE SCROLL */
+
+/** BEGIN ICONS **/
+.cc_div .b-bn .exp::before,
+.cc_div .act .b-bn .exp::before{
+    border: solid var(--cc-btn-secondary-text);
+    border-width: 0 2px 2px 0;
+    padding: .2em;
+    display: inline-block;
+    position: absolute;
+    content: '';
+    margin-right: 15px;
+    position: absolute;
+    transform: translateY(-50%) rotate(45deg);
+    left: 1.2em;
+    top: 50%;
+}
+
+.cc_div .act .b-bn .b-tl::before{
+    transform: translateY(-20%) rotate(225deg);
+}
+
+.cc_div .on-i::before{
+    border: solid var(--cc-toggle-knob-icon-color);
+    border-width: 0 2px 2px 0;
+    padding: .1em;
+    display: inline-block;
+    padding-bottom: .45em;
+    content: '';
+    margin: 0 auto;
+    transform: rotate(45deg);
+    top: .37em;
+    left: .75em;
+    position: absolute;
+}
+
+#s-c-bn::before,
+#s-c-bn::after{
+    content: '';
+    position: absolute;
+    left: .82em;
+    top: .58em;
+    height: .6em;
+    width: 1.5px;
+    background: var(--cc-btn-secondary-text);
+    transform: rotate(45deg);
+    border-radius: 1em;
+    margin: 0 auto;
+}
+
+#s-c-bn::after{
+    transform: rotate(-45deg);
+}
+
+.cc_div .off-i,
+.cc_div .on-i{
+    height: 100%;
+    width: 50%;
+    position: absolute;
+    right: 0;
+    display: block;
+    text-align: center;
+    transition: opacity .15s ease;
+}
+
+.cc_div .on-i{
+    left: 0;
+    opacity: 0;
+}
+
+.cc_div .off-i::before,
+.cc_div .off-i::after{
+    right: .8em;
+    top: .42em;
+    content: ' ';
+    height: .7em;
+    width: .09375em;
+    display: block;
+    background: var(--cc-toggle-knob-icon-color);
+    margin: 0 auto;
+    position: absolute;
+    transform-origin: center;
+}
+
+.cc_div .off-i::before {
+    transform: rotate(45deg);
+}
+.cc_div .off-i::after {
+    transform: rotate(-45deg);
+}
+
+.cc_div .b-tg .c-tgl:checked ~ .c-tg .on-i{
+    opacity: 1;
+}
+.cc_div .b-tg .c-tgl:checked ~ .c-tg .off-i{
+    opacity: 0;
+}
+/** END ICONS **/
+
+#cm.box.middle,
+#cm.cloud.middle{
+    top: 50%;
+    transform: translateY(-37%);
+    bottom: auto;
+}
+
+#cm.box.middle.zoom,
+#cm.cloud.middle.zoom{
+    transform: scale(.95) translateY(-50%);
+}
+
+#cm.box.center{
+    left: 1em;
+    right: 1em;
+    margin: 0 auto;
+}
+
+/* Start cloud layout */
+#cm.cloud {
+    max-width: 50em;
+    margin: 0 auto;
+    text-align: center;
+    left: 1em;
+    right: 1em;
+    overflow: hidden;
+    padding: 1.2em 1.7em;
+    width: unset;
+}
+
+.cc_div .cloud #c-inr{
+    display: table;
+    width: 100%;
+}
+
+.cc_div .cloud #c-inr-i{
+    width: 70%;
+    display: table-cell;
+    vertical-align: top;
+    padding-right: 2.4em;
+}
+
+.cc_div .cloud #c-txt{
+    font-size: 0.85em;
+}
+
+.cc_div .cloud #c-bns{
+    min-width: 170px;
+    display: table-cell;
+    vertical-align: middle;
+}
+
+#cm.cloud .c-bn{
+    margin: .5rem 0 0 0;
+    width: 100%;
+}
+
+#cm.cloud .c-bn:first-child{
+    margin: 0;
+}
+
+#cm.cloud.left{
+    margin-right: 1.25em;
+}
+
+#cm.cloud.right{
+    margin-left: 1.25em;
+}
+/* End cloud layout */
+
+/* Start bar layout */
+#cm.bar {
+    width: 100%;
+    max-width: 100%;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    border-radius: 0;
+    position: fixed;
+    padding: 2em;
+}
+
+#cm.bar #c-inr{
+    max-width: 32em;
+    margin: 0 auto;
+}
+
+#cm.bar #c-bns{
+    max-width: 33.75em;
+}
+
+#cm.bar #cs{
+    padding: 0;
+}
+
+.cc_div .bar #c-s-in{
+    top: 0;
+    transform: none;
+    height: 100%;
+    max-height: 100%;
+}
+
+.cc_div .bar #s-hdr,
+.cc_div .bar #s-bl,
+.cc_div .bar #s-bns {
+    padding-left: 1.4em;
+    padding-right: 1.4em;
+}
+
+.cc_div .bar #cs{
+    padding: 0;
+}
+
+/* align bar to right by default */
+.cc_div .bar #s-inr{
+    margin: 0;
+    margin-left: auto;
+    margin-right: 0;
+    border-radius: 0;
+    max-width: 32em;
+}
+
+.cc_div .bar.left #s-inr{
+    margin-left: 0;
+    margin-right: auto;
+}
+
+/* Force table to not be like tables anymore */
+.cc_div .bar #s-bl table,
+.cc_div .bar #s-bl thead,
+.cc_div .bar #s-bl tbody,
+.cc_div .bar #s-bl th,
+.cc_div .bar #s-bl td,
+.cc_div .bar #s-bl tr,
+.cc_div .bar #s-cnt{
+    display: block;
+}
+
+.cc_div .bar #s-bl caption {
+    display: block;
+    border-top: 0;
+    border-bottom: 1px solid var(--cc-cookie-table-border);
+}
+
+/* Hide table headers (but not display: none;, for accessibility) */
+.cc_div .bar #s-bl thead tr{
+    position: absolute;
+    top: -9999px;
+    left: -9999px;
+}
+
+.cc_div .bar #s-bl tr{
+    border-top: 1px solid var(--cc-cookie-table-border);
+}
+
+.cc_div .bar #s-bl td {
+    /* Behave  like a "row" */
+    border: none;
+    position: relative;
+    padding-left: 35%;
+}
+
+.cc_div .bar #s-bl td:before {
+    position: absolute;
+    left: 1rem;
+    padding-right: 0.625em;
+    white-space: nowrap;
+    content: attr(data-column);
+    color: var(--cc-text);
+    overflow: hidden;
+    text-overflow: ellipsis;
+}
+/* End bar layout */
+
+/* Positions */
+#cm.top {
+    bottom: auto;
+    top: 1.250em;
+}
+
+#cm.left{
+    right: auto;
+    left: 1.250em;
+}
+
+#cm.right{
+    left: auto;
+    right: 1.250em;
+}
+
+#cm.bar.left,
+#cm.bar.right{
+    left: 0;
+    right: 0;
+}
+
+#cm.bar.top{
+    top: 0;
+}
+/* end positions */
+
+/* Disable transitions if prefers-reduced-motion detected */
+@media (prefers-reduced-motion) {
+    #cc--main #cm,
+    #cc--main #s-cnt,
+    #cc--main #s-inr{
+        transition: none!important;
+    }
+}
+
+@media screen and (max-width: 688px) {
+
+    #cm,
+    #cm.cloud,
+    #cm.left,
+    #cm.right{
+        width: auto;
+        max-width: 100%;
+        margin: 0;
+        padding: 1.2em!important;
+        right: 1em;
+        left: 1em;
+        bottom: 1em;
+        display: block;
+    }
+
+    .force--consent #cm,
+    .force--consent #cm.cloud{
+        width: auto;
+        max-width: 100vw;
+    }
+
+    #cm.top{
+        top: 1em;
+        bottom: auto;
+    }
+
+    #cm.bottom{
+        bottom: 1em;
+        top: auto;
+    }
+
+    #cm.bar.bottom{
+        bottom: 0;
+    }
+
+    #cm.cloud .c-bn{
+        font-size: .85em;
+    }
+
+    #s-bns,
+    .cc_div .bar #s-bns{
+        padding: 1em 1.3em;
+    }
+
+    .cc_div .bar #s-inr{
+        max-width: 100%;
+        width: 100%;
+    }
+
+    .cc_div .cloud #c-inr-i{
+        padding-right: 0;
+    }
+
+    #cs{
+        border-radius: 0;
+        padding: 0;
+    }
+
+    #c-s-in{
+        max-height: 100%;
+        height: 100%;
+        top: 0;
+        transform: none;
+    }
+
+    .cc_div .b-tg{
+        transform: scale(1.1);
+        right: 1.1em;
+    }
+
+    #s-inr{
+        margin: 0;
+        padding-bottom: 7.9em;
+        border-radius: 0;
+    }
+
+    #s-bns{
+        height: 7.9em;
+    }
+
+	#s-bl,
+    .cc_div .bar #s-bl{
+		padding: 1.2em;
+    }
+
+	#s-hdr,
+    .cc_div .bar #s-hdr{
+        padding: 0 1.2em;
+    }
+
+    /** dynamic table layout **/
+    #s-bl table {
+        width: 100%;
+    }
+
+    #s-inr.bns-t{
+        padding-bottom: 10.5em;
+    }
+
+    .bns-t #s-bns{
+        height: 10.5em;
+    }
+
+    .cc_div .bns-t .c-bn{
+        font-size: 0.83em;
+        padding: .9em 1.6em;
+    }
+
+    #s-cnt .b-bn .b-tl{
+        padding-top: 1.2em;
+        padding-bottom: 1.2em;
+    }
+
+    /* Force table to not be like tables anymore */
+    #s-bl table,
+    #s-bl thead,
+    #s-bl tbody,
+    #s-bl th,
+    #s-bl td,
+    #s-bl tr,
+    #s-cnt{
+        display: block;
+    }
+
+    #s-bl caption {
+        border-bottom: 0;
+        display: block;
+    }
+
+    /* Hide table headers (but not display: none;, for accessibility) */
+    #s-bl thead tr{
+        position: absolute;
+        top: -9999px;
+        left: -9999px;
+    }
+
+    #s-bl tr{
+        border-top: 1px solid var(--cc-cookie-table-border);
+    }
+
+    #s-bl td {
+        /* Behave  like a "row" */
+        border: none;
+        position: relative;
+        padding-left: 35%;
+    }
+
+    #s-bl td:before {
+        position: absolute;
+        left: 1rem;
+        padding-right: 0.625em;
+        white-space: nowrap;
+        content: attr(data-column);
+        color: var(--cc-text);
+        overflow: hidden;
+        text-overflow: ellipsis;
+    }
+
+    #cm .c-bn,
+    .cc_div .c-bn{
+        width: 100%;
+        margin-right: 0;
+    }
+
+    #s-cnt #s-rall-bn{
+        margin-left: 0;
+    }
+
+    .cc_div #c-bns{
+        flex-direction: column;
+    }
+
+    #c-bns button + button,
+    #s-cnt button + button{
+        margin-top: 0.625em;
+        margin-left: 0;
+        float: unset;
+    }
+
+    #cm.cloud,
+    #cm.box{
+        left: 1em;
+        right: 1em;
+        width: auto;
+    }
+
+    #cm.cloud.right,
+    #cm.cloud.left{
+        margin: 0;
+    }
+
+    .cc_div .cloud #c-bns,
+    .cc_div .cloud #c-inr,
+    .cc_div .cloud #c-inr-i{
+        display: block;
+        width: auto;
+        min-width: unset;
+    }
+
+    .cc_div .cloud #c-txt{
+        font-size: .9em;
+    }
+
+    .cc_div .cloud #c-bns{
+        margin-top: 1.625em;
+    }
+}
Index: /main/trunk/greenstone3/web/interfaces/default/cc/cookieconsent.js
===================================================================
--- /main/trunk/greenstone3/web/interfaces/default/cc/cookieconsent.js	(revision 37732)
+++ /main/trunk/greenstone3/web/interfaces/default/cc/cookieconsent.js	(revision 37732)
@@ -0,0 +1,2250 @@
+/*!
+ * CookieConsent v2.9.0
+ * https://www.github.com/orestbida/cookieconsent
+ * Author Orest Bida
+ * Released under the MIT License
+ */
+(function(){
+    'use strict';
+    /**
+     * @param {HTMLElement} [root] - [optional] element where the cookieconsent will be appended
+     * @returns {Object} cookieconsent object with API
+     */
+    var CookieConsent = function(root){
+
+        /**
+         * CHANGE THIS FLAG FALSE TO DISABLE console.log()
+         */
+        var ENABLE_LOGS = true;
+
+        var _config = {
+            'mode': 'opt-in',                         // 'opt-in', 'opt-out'
+            'current_lang': 'en',
+            'auto_language': null,
+            'autorun': true,                          // run as soon as loaded
+            'page_scripts': true,
+            'hide_from_bots': true,
+            'cookie_name': 'cc_cookie',
+            'cookie_expiration': 182,                 // default: 6 months (in days)
+            'cookie_domain': location.hostname,       // default: current domain
+            'cookie_path': '/',
+            'cookie_same_site': 'Lax',
+            'use_rfc_cookie': false,
+            'autoclear_cookies': true,
+            'revision': 0,
+            'script_selector': 'data-cookiecategory'
+        };
+
+        var
+            /**
+             * Object which holds the main methods/API (.show, .run, ...)
+            */
+            _cookieconsent = {},
+
+            /**
+             * Global user configuration object
+             */
+            user_config,
+
+            /**
+             * Internal state variables
+             */
+            saved_cookie_content = {},
+            cookie_data = null,
+
+            /**
+             * @type {Date}
+             */
+            consent_date,
+
+            /**
+             * @type {Date}
+             */
+            last_consent_update,
+
+            /**
+             * @type {string}
+             */
+            consent_uuid,
+
+            /**
+             * @type {boolean}
+             */
+            invalid_consent = true,
+
+            consent_modal_exists = false,
+            consent_modal_visible = false,
+
+            settings_modal_visible = false,
+
+            /**
+             * @type {HTMLElement[]}
+             */
+            current_modal_focusable,
+
+            /**
+             * @type {HTMLDivElement}
+             */
+            current_focused_modal,
+
+            /**
+             * @type {HTMLSpanElement}
+             */
+            cmFocusSpan,
+
+            /**
+             * @type {HTMLSpanElement}
+             */
+            smFocusSpan,
+
+            all_table_headers,
+            all_blocks,
+
+            // Helper callback functions
+            // (avoid calling "user_config['onAccept']" all the time)
+            onAccept,
+            onChange,
+            onFirstAction,
+
+            revision_enabled = false,
+            valid_revision = true,
+            revision_message = '',
+
+            // State variables for the autoclearCookies function
+            changed_settings = [],
+            reload_page = false;
+
+        /**
+         * Accept type:
+         *  - "all"
+         *  - "necessary"
+         *  - "custom"
+         * @type {string}
+         */
+        var accept_type;
+
+        /**
+         * Contains all accepted categories
+         * @type {string[]}
+         */
+        var accepted_categories = [];
+
+        /**
+         * Contains all non-accepted (rejected) categories
+         * @type {string[]}
+         */
+        var rejected_categories = [];
+
+        /**
+         * Contains all categories enabled by default
+         * @type {string[]}
+         */
+        var default_enabled_categories = [];
+
+        // Don't run plugin (to avoid indexing its text content) if bot detected
+        var is_bot = false;
+
+        /**
+         * Save reference to the last focused element on the page
+         * (used later to restore focus when both modals are closed)
+         */
+        var last_elem_before_modal;
+        var last_consent_modal_btn_focus;
+
+        /**
+         * Both of the arrays below have the same structure:
+         * [0] => holds reference to the FIRST focusable element inside modal
+         * [1] => holds reference to the LAST focusable element inside modal
+         */
+        var consent_modal_focusable = [];
+        var settings_modal_focusable = [];
+
+        /**
+         * Keep track of enabled/disabled categories
+         * @type {boolean[]}
+         */
+        var toggle_states = [];
+
+        /**
+         * Stores all available categories
+         * @type {string[]}
+         */
+        var all_categories = [];
+
+        /**
+         * Keep track of readonly toggles
+         * @type {boolean[]}
+         */
+        var readonly_categories = [];
+
+        /**
+         * Pointers to main dom elements (to avoid retrieving them later using document.getElementById)
+         */
+        var
+            /** @type {HTMLElement} */ html_dom = document.documentElement,
+            /** @type {HTMLElement} */ main_container,
+            /** @type {HTMLElement} */ all_modals_container,
+
+            /** @type {HTMLElement} */ consent_modal,
+            /** @type {HTMLElement} */ consent_modal_title,
+            /** @type {HTMLElement} */ consent_modal_description,
+            /** @type {HTMLElement} */ consent_primary_btn,
+            /** @type {HTMLElement} */ consent_secondary_btn,
+            /** @type {HTMLElement} */ consent_buttons,
+            /** @type {HTMLElement} */ consent_modal_inner,
+
+            /** @type {HTMLElement} */ settings_container,
+            /** @type {HTMLElement} */ settings_inner,
+            /** @type {HTMLElement} */ settings_title,
+            /** @type {HTMLElement} */ settings_close_btn,
+            /** @type {HTMLElement} */ settings_blocks,
+            /** @type {HTMLElement} */ new_settings_blocks,
+            /** @type {HTMLElement} */ settings_buttons,
+            /** @type {HTMLElement} */ settings_save_btn,
+            /** @type {HTMLElement} */ settings_accept_all_btn,
+            /** @type {HTMLElement} */ settings_reject_all_btn;
+
+        /**
+         * Update config settings
+         * @param {Object} user_config
+         */
+        var _setConfig = function(_user_config){
+
+            /**
+             * Make user configuration globally available
+             */
+            user_config = _user_config;
+
+            _log("CookieConsent [CONFIG]: received_config_settings ", user_config);
+
+            if(typeof user_config['cookie_expiration'] === "number")
+                _config.cookie_expiration = user_config['cookie_expiration'];
+
+            if(typeof user_config['cookie_necessary_only_expiration'] === "number")
+                _config.cookie_necessary_only_expiration  = user_config['cookie_necessary_only_expiration'];
+
+            if(typeof user_config['autorun'] === "boolean")
+                _config.autorun = user_config['autorun'];
+
+            if(typeof user_config['cookie_domain'] === "string")
+                _config.cookie_domain = user_config['cookie_domain'];
+
+            if(typeof user_config['cookie_same_site'] === "string")
+                _config.cookie_same_site = user_config['cookie_same_site'];
+
+            if(typeof user_config['cookie_path'] === "string")
+                _config.cookie_path = user_config['cookie_path'];
+
+            if(typeof user_config['cookie_name'] === "string")
+                _config.cookie_name = user_config['cookie_name'];
+
+            if(typeof user_config['onAccept'] === "function")
+                onAccept = user_config['onAccept'];
+
+            if(typeof user_config['onFirstAction'] === "function")
+                onFirstAction = user_config['onFirstAction'];
+
+            if(typeof user_config['onChange'] === "function")
+                onChange = user_config['onChange'];
+
+            if(user_config['mode'] === 'opt-out')
+                _config.mode = 'opt-out';
+
+            if(typeof user_config['revision'] === "number"){
+                user_config['revision'] > -1 && (_config.revision = user_config['revision']);
+                revision_enabled = true;
+            }
+
+            if(typeof user_config['autoclear_cookies'] === "boolean")
+                _config.autoclear_cookies = user_config['autoclear_cookies'];
+
+            if(user_config['use_rfc_cookie'] === true)
+                _config.use_rfc_cookie = true;
+
+            if(typeof user_config['hide_from_bots'] === "boolean"){
+                _config.hide_from_bots = user_config['hide_from_bots'];
+            }
+
+            if(_config.hide_from_bots){
+                is_bot = navigator &&
+                    ((navigator.userAgent && /bot|crawl|spider|slurp|teoma/i.test(navigator.userAgent)) || navigator.webdriver);
+            }
+
+            _config.page_scripts = user_config['page_scripts'] === true;
+
+            if (user_config['auto_language'] === 'browser' || user_config['auto_language'] === true) {
+                _config.auto_language = 'browser';
+            } else if (user_config['auto_language'] === 'document') {
+                _config.auto_language = 'document';
+            }
+
+            _log("CookieConsent [LANG]: auto_language strategy is '" + _config.auto_language + "'");
+
+            _config.current_lang = _resolveCurrentLang(user_config.languages, user_config['current_lang']);
+        }
+
+        /**
+         * Add an onClick listeners to all html elements with data-cc attribute
+         */
+        var _addDataButtonListeners = function(elem){
+
+            var _a = 'accept-';
+
+            var show_settings = _getElements('c-settings');
+            var accept_all = _getElements(_a + 'all');
+            var accept_necessary = _getElements(_a + 'necessary');
+            var accept_custom_selection = _getElements(_a + 'custom');
+
+            for(var i=0; i<show_settings.length; i++){
+                show_settings[i].setAttribute('aria-haspopup', 'dialog');
+                _addEvent(show_settings[i], 'click', function(event){
+                    event.preventDefault();
+                    _cookieconsent.showSettings(0);
+                });
+            }
+
+            for(i=0; i<accept_all.length; i++){
+                _addEvent(accept_all[i], 'click', function(event){
+                    _acceptAction(event, 'all');
+                });
+            }
+
+            for(i=0; i<accept_custom_selection.length; i++){
+                _addEvent(accept_custom_selection[i], 'click', function(event){
+                    _acceptAction(event);
+                });
+            }
+
+            for(i=0; i<accept_necessary.length; i++){
+                _addEvent(accept_necessary[i], 'click', function(event){
+                    _acceptAction(event, []);
+                });
+            }
+
+            /**
+             * Return all elements with given data-cc role
+             * @param {string} data_role
+             * @returns {NodeListOf<Element>}
+             */
+            function _getElements(data_role){
+                return (elem || document).querySelectorAll('[data-cc="' + data_role + '"]');
+            }
+
+            /**
+             * Helper function: accept and then hide modals
+             * @param {PointerEvent} e source event
+             * @param {string} [accept_type]
+             */
+            function _acceptAction(e, accept_type){
+                e.preventDefault();
+                _cookieconsent.accept(accept_type);
+                _cookieconsent.hideSettings();
+                _cookieconsent.hide();
+            }
+        }
+
+        /**
+         * Get a valid language (at least 1 must be defined)
+         * @param {string} lang - desired language
+         * @param {Object} all_languages - all defined languages
+         * @returns {string} validated language
+         */
+        var _getValidatedLanguage = function(lang, all_languages){
+            if(Object.prototype.hasOwnProperty.call(all_languages, lang)){
+                return lang;
+            }else if(_getKeys(all_languages).length > 0){
+                if(Object.prototype.hasOwnProperty.call(all_languages, _config.current_lang)){
+                    return _config.current_lang ;
+                }else{
+                    return _getKeys(all_languages)[0];
+                }
+            }
+        }
+
+        /**
+         * Save reference to first and last focusable elements inside each modal
+         * to prevent losing focus while navigating with TAB
+         */
+        var _getModalFocusableData = function(){
+
+            /**
+             * Note: any of the below focusable elements, which has the attribute tabindex="-1" AND is either
+             * the first or last element of the modal, won't receive focus during "open/close" modal
+             */
+            var allowed_focusable_types = ['[href]', 'button', 'input', 'details', '[tabindex="0"]'];
+
+            function _getAllFocusableElements(modal, _array){
+                var focus_later=false, focus_first=false;
+
+                // ie might throw exception due to complex unsupported selector => a:not([tabindex="-1"])
+                try{
+                    var focusable_elems = modal.querySelectorAll(allowed_focusable_types.join(':not([tabindex="-1"]), '));
+                    var attr, len=focusable_elems.length, i=0;
+
+                    while(i < len){
+
+                        attr = focusable_elems[i].getAttribute('data-focus');
+
+                        if(!focus_first && attr === "1"){
+                            focus_first = focusable_elems[i];
+
+                        }else if(attr === "0"){
+                            focus_later = focusable_elems[i];
+                            if(!focus_first && focusable_elems[i+1].getAttribute('data-focus') !== "0"){
+                                focus_first = focusable_elems[i+1];
+                            }
+                        }
+
+                        i++;
+                    }
+
+                }catch(e){
+                    return modal.querySelectorAll(allowed_focusable_types.join(', '));
+                }
+
+                /**
+                 * Save first and last elements (used to lock/trap focus inside modal)
+                 */
+                _array[0] = focusable_elems[0];
+                _array[1] = focusable_elems[focusable_elems.length - 1];
+                _array[2] = focus_later;
+                _array[3] = focus_first;
+            }
+
+            /**
+             * Get settings modal'S all focusable elements
+             * Save first and last elements (used to lock/trap focus inside modal)
+             */
+            _getAllFocusableElements(settings_inner, settings_modal_focusable);
+
+            /**
+             * If consent modal exists, do the same
+             */
+            if(consent_modal_exists){
+                _getAllFocusableElements(consent_modal, consent_modal_focusable);
+            }
+        }
+
+        var _createConsentModal = function(lang){
+
+            if(user_config['force_consent'] === true)
+                _addClass(html_dom, 'force--consent');
+
+            // Create modal if it doesn't exist
+            if(!consent_modal){
+
+                consent_modal = _createNode('div');
+                var consent_modal_inner_inner = _createNode('div');
+                var overlay = _createNode('div');
+
+                consent_modal.id = 'cm';
+                consent_modal_inner_inner.id = 'c-inr-i';
+                overlay.id = 'cm-ov';
+
+                consent_modal.tabIndex = -1;
+                consent_modal.setAttribute('role', 'dialog');
+                consent_modal.setAttribute('aria-modal', 'true');
+                consent_modal.setAttribute('aria-hidden', 'false');
+                consent_modal.setAttribute('aria-labelledby', 'c-ttl');
+                consent_modal.setAttribute('aria-describedby', 'c-txt');
+
+                // Append consent modal to main container
+                all_modals_container.appendChild(consent_modal);
+                all_modals_container.appendChild(overlay);
+
+                /**
+                 * Make modal by default hidden to prevent weird page jumps/flashes (shown only once css is loaded)
+                 */
+                consent_modal.style.visibility = overlay.style.visibility = "hidden";
+                overlay.style.opacity = 0;
+            }
+
+            // Use insertAdjacentHTML instead of innerHTML
+            var consent_modal_title_value = user_config.languages[lang]['consent_modal']['title'];
+
+            // Add title (if valid)
+            if(consent_modal_title_value){
+
+                if(!consent_modal_title){
+                    consent_modal_title = _createNode('div');
+                    consent_modal_title.id = 'c-ttl';
+                    consent_modal_title.setAttribute('role', 'heading');
+                    consent_modal_title.setAttribute('aria-level', '2');
+                    consent_modal_inner_inner.appendChild(consent_modal_title);
+                }
+
+                consent_modal_title.innerHTML = consent_modal_title_value;
+            }
+
+            var description = user_config.languages[lang]['consent_modal']['description'];
+
+            if(revision_enabled){
+                if(!valid_revision){
+                    description = description.replace("{{revision_message}}", revision_message || user_config.languages[lang]['consent_modal']['revision_message'] || "");
+                }else{
+                    description = description.replace("{{revision_message}}", "");
+                }
+            }
+
+            if(!consent_modal_description){
+                consent_modal_description = _createNode('div');
+                consent_modal_description.id = 'c-txt';
+                consent_modal_inner_inner.appendChild(consent_modal_description);
+            }
+
+            // Set description content
+            consent_modal_description.innerHTML = description;
+
+            var primary_btn_data = user_config.languages[lang]['consent_modal']['primary_btn'],   // accept current selection
+                secondary_btn_data = user_config.languages[lang]['consent_modal']['secondary_btn'];
+
+            // Add primary button if not falsy
+            if(primary_btn_data){
+
+                if(!consent_primary_btn){
+                    consent_primary_btn = _createNode('button');
+                    consent_primary_btn.id = 'c-p-bn';
+                    consent_primary_btn.className =  "c-bn";
+                    consent_primary_btn.appendChild(generateFocusSpan(1))
+
+                    var _accept_type;
+
+                    if(primary_btn_data['role'] === 'accept_all')
+                        _accept_type = 'all'
+
+                    _addEvent(consent_primary_btn, "click", function(){
+                        _cookieconsent.hide();
+                        _log("CookieConsent [ACCEPT]: cookie_consent was accepted!");
+                        _cookieconsent.accept(_accept_type);
+                    });
+                }
+
+                consent_primary_btn.firstElementChild.innerHTML = user_config.languages[lang]['consent_modal']['primary_btn']['text'];
+            }
+
+            // Add secondary button if not falsy
+            if(secondary_btn_data){
+
+                if(!consent_secondary_btn){
+                    consent_secondary_btn = _createNode('button');
+                    consent_secondary_btn.id = 'c-s-bn';
+                    consent_secondary_btn.className = "c-bn c_link";
+                    consent_secondary_btn.appendChild(generateFocusSpan(1))
+
+                    if(secondary_btn_data['role'] === 'accept_necessary'){
+                        _addEvent(consent_secondary_btn, 'click', function(){
+                            _cookieconsent.hide();
+                            _cookieconsent.accept([]); // accept necessary only
+                        });
+                    }else{
+                        _addEvent(consent_secondary_btn, 'click', function(){
+                            _cookieconsent.showSettings(0);
+                        });
+                    }
+                }
+
+                consent_secondary_btn.firstElementChild.innerHTML = user_config.languages[lang]['consent_modal']['secondary_btn']['text'];
+            }
+
+            // Swap buttons
+            var gui_options_data = user_config['gui_options'];
+
+            if(!consent_modal_inner){
+                consent_modal_inner = _createNode('div');
+                consent_modal_inner.id = 'c-inr';
+
+                consent_modal_inner.appendChild(consent_modal_inner_inner);
+            }
+
+            if(!consent_buttons){
+                consent_buttons = _createNode('div');
+                consent_buttons.id = "c-bns";
+
+                if(gui_options_data && gui_options_data['consent_modal'] && gui_options_data['consent_modal']['swap_buttons'] === true){
+                    secondary_btn_data && consent_buttons.appendChild(consent_secondary_btn);
+                    primary_btn_data && consent_buttons.appendChild(consent_primary_btn);
+                    consent_buttons.className = 'swap';
+                }else{
+                    primary_btn_data && consent_buttons.appendChild(consent_primary_btn);
+                    secondary_btn_data && consent_buttons.appendChild(consent_secondary_btn);
+                }
+
+                (primary_btn_data || secondary_btn_data ) && consent_modal_inner.appendChild(consent_buttons);
+                consent_modal.appendChild(consent_modal_inner);
+            }
+
+            consent_modal_exists = true;
+
+            _addDataButtonListeners(consent_modal_inner);
+        }
+
+        var _createSettingsModal = function(lang){
+
+            /**
+             * Create all consent_modal elements
+             */
+            if(!settings_container){
+                settings_container = _createNode('div');
+                settings_container.tabIndex = -1;
+                var settings_container_valign = _createNode('div');
+                var settings = _createNode('div');
+                var settings_container_inner = _createNode('div');
+                settings_inner = _createNode('div');
+                settings_title = _createNode('div');
+                var settings_header = _createNode('div');
+                settings_close_btn = _createNode('button');
+                settings_close_btn.appendChild(generateFocusSpan(2));
+                var settings_close_btn_container = _createNode('div');
+                settings_blocks = _createNode('div');
+                var overlay = _createNode('div');
+
+                /**
+                 * Set ids
+                 */
+                settings_container.id = 's-cnt';
+                settings_container_valign.id = "c-vln";
+                settings_container_inner.id = "c-s-in";
+                settings.id = "cs";
+                settings_title.id = 's-ttl';
+                settings_inner.id = 's-inr';
+                settings_header.id = "s-hdr";
+                settings_blocks.id = 's-bl';
+                settings_close_btn.id = 's-c-bn';
+                overlay.id = 'cs-ov';
+                settings_close_btn_container.id = 's-c-bnc';
+                settings_close_btn.className = 'c-bn';
+
+                settings_container.setAttribute('role', 'dialog');
+                settings_container.setAttribute('aria-modal', 'true');
+                settings_container.setAttribute('aria-hidden', 'true');
+                settings_container.setAttribute('aria-labelledby', 's-ttl');
+                settings_title.setAttribute('role', 'heading');
+                settings_container.style.visibility = overlay.style.visibility = "hidden";
+                overlay.style.opacity = 0;
+
+                settings_close_btn_container.appendChild(settings_close_btn);
+
+                // If 'esc' key is pressed inside settings_container div => hide settings
+                _addEvent(document, 'keydown', function(evt){
+                    if (evt.keyCode === 27 && settings_modal_visible) {
+                        _cookieconsent.hideSettings();
+                    }
+                }, true);
+
+                _addEvent(settings_close_btn, 'click', function(){
+                    _cookieconsent.hideSettings();
+                });
+            }else{
+                new_settings_blocks = _createNode('div');
+                new_settings_blocks.id = 's-bl';
+            }
+
+            var settings_modal_config = user_config.languages[lang]['settings_modal'];
+
+            // Add label to close button
+            settings_close_btn.setAttribute('aria-label', settings_modal_config['close_btn_label'] || 'Close');
+
+            all_blocks = settings_modal_config['blocks'];
+            all_table_headers = settings_modal_config['cookie_table_headers'];
+            var table_caption = settings_modal_config['cookie_table_caption'];
+
+            var n_blocks = all_blocks.length;
+
+            // Set settings modal title
+            settings_title.innerHTML = settings_modal_config['title'];
+
+            // Create settings modal content (blocks)
+            for(var i=0; i<n_blocks; ++i){
+
+                var title_data = all_blocks[i]['title'],
+                    description_data = all_blocks[i]['description'],
+                    toggle_data = all_blocks[i]['toggle'],
+                    cookie_table_data = all_blocks[i]['cookie_table'],
+                    remove_cookie_tables = user_config['remove_cookie_tables'] === true,
+                    isExpandable = (description_data && 'truthy') || (!remove_cookie_tables && (cookie_table_data && 'truthy'));
+
+                // Create title
+                var block_section = _createNode('div');
+                var block_table_container = _createNode('div');
+
+                // Create description
+                if(description_data){
+                    var block_desc = _createNode('div');
+                    block_desc.className = 'p';
+                    block_desc.insertAdjacentHTML('beforeend', description_data);
+                }
+
+                var block_title_container = _createNode('div');
+                block_title_container.className = 'title';
+
+                block_section.className = 'c-bl';
+                block_table_container.className = 'desc';
+
+                // Create toggle if specified (opt in/out)
+                if(typeof toggle_data !== 'undefined'){
+
+                    var accordion_id = "c-ac-"+i;
+
+                    // Create button (to collapse/expand block description)
+                    var block_title_btn = isExpandable ? _createNode('button') : _createNode('div');
+                    var block_switch_label = _createNode('label');
+                    var block_switch = _createNode('input');
+                    var block_switch_span = _createNode('span');
+                    var label_text_span = _createNode('span');
+
+                    // These 2 spans will contain each 2 pseudo-elements to generate 'tick' and 'x' icons
+                    var block_switch_span_on_icon = _createNode('span');
+                    var block_switch_span_off_icon = _createNode('span');
+
+                    block_title_btn.className = isExpandable ? 'b-tl exp' : 'b-tl';
+                    block_switch_label.className = 'b-tg';
+                    block_switch.className = 'c-tgl';
+                    block_switch_span_on_icon.className = 'on-i';
+                    block_switch_span_off_icon.className = 'off-i';
+                    block_switch_span.className = 'c-tg';
+                    label_text_span.className = "t-lb";
+
+                    if(isExpandable){
+                        block_title_btn.setAttribute('aria-expanded', 'false');
+                        block_title_btn.setAttribute('aria-controls', accordion_id);
+                    }
+
+                    block_switch.type = 'checkbox';
+                    block_switch_span.setAttribute('aria-hidden', 'true');
+
+                    var cookie_category = toggle_data.value;
+                    block_switch.value = cookie_category;
+
+                    label_text_span.textContent = title_data;
+                    block_title_btn.insertAdjacentHTML('beforeend', title_data);
+
+                    block_title_container.appendChild(block_title_btn);
+                    block_switch_span.appendChild(block_switch_span_on_icon);
+                    block_switch_span.appendChild(block_switch_span_off_icon);
+
+                    /**
+                     * If consent is valid => retrieve category states from cookie
+                     * Otherwise use states defined in the user_config. object
+                     */
+                    if(!invalid_consent){
+                        if(_inArray(saved_cookie_content['categories'], cookie_category) > -1){
+                            block_switch.checked = true;
+                            !new_settings_blocks && toggle_states.push(true);
+                        }else{
+                            !new_settings_blocks && toggle_states.push(false);
+                        }
+                    }else if(toggle_data['enabled']){
+                        block_switch.checked = true;
+                        !new_settings_blocks && toggle_states.push(true);
+
+                        /**
+                         * Keep track of categories enabled by default (useful when mode=='opt-out')
+                         */
+                        if(toggle_data['enabled'])
+                            !new_settings_blocks && default_enabled_categories.push(cookie_category);
+
+                    }else{
+                        !new_settings_blocks && toggle_states.push(false);
+                    }
+
+                    !new_settings_blocks && all_categories.push(cookie_category);
+
+                    /**
+                     * Set toggle as readonly if true (disable checkbox)
+                     */
+                    if(toggle_data['readonly']){
+                        block_switch.disabled = true;
+                        _addClass(block_switch_span, 'c-ro');
+                        !new_settings_blocks && readonly_categories.push(true);
+                    }else{
+                        !new_settings_blocks && readonly_categories.push(false);
+                    }
+
+                    _addClass(block_table_container, 'b-acc');
+                    _addClass(block_title_container, 'b-bn');
+                    _addClass(block_section, 'b-ex');
+
+                    block_table_container.id = accordion_id;
+                    block_table_container.setAttribute('aria-hidden', 'true');
+
+                    block_switch_label.appendChild(block_switch);
+                    block_switch_label.appendChild(block_switch_span);
+                    block_switch_label.appendChild(label_text_span);
+                    block_title_container.appendChild(block_switch_label);
+
+                    /**
+                     * On button click handle the following :=> aria-expanded, aria-hidden and act class for current block
+                     */
+                    isExpandable && (function(accordion, block_section, btn){
+                        _addEvent(block_title_btn, 'click', function(){
+                            if(!_hasClass(block_section, 'act')){
+                                _addClass(block_section, 'act');
+                                btn.setAttribute('aria-expanded', 'true');
+                                accordion.setAttribute('aria-hidden', 'false');
+                            }else{
+                                _removeClass(block_section, 'act');
+                                btn.setAttribute('aria-expanded', 'false');
+                                accordion.setAttribute('aria-hidden', 'true');
+                            }
+                        }, false);
+                    })(block_table_container, block_section, block_title_btn);
+
+                }else{
+                    /**
+                     * If block is not a button (no toggle defined),
+                     * create a simple div instead
+                     */
+                    if(title_data){
+                        var block_title = _createNode('div');
+                        block_title.className = 'b-tl';
+                        block_title.setAttribute('role', 'heading');
+                        block_title.setAttribute('aria-level', '3');
+                        block_title.insertAdjacentHTML('beforeend', title_data);
+                        block_title_container.appendChild(block_title);
+                    }
+                }
+
+                title_data && block_section.appendChild(block_title_container);
+                description_data && block_table_container.appendChild(block_desc);
+
+                // if cookie table found, generate table for this block
+                if(!remove_cookie_tables && typeof cookie_table_data !== 'undefined'){
+                    var tr_tmp_fragment = document.createDocumentFragment();
+
+                    /**
+                     * Use custom table headers
+                     */
+                    for(var p=0; p<all_table_headers.length; ++p){
+                        // create new header
+                        var th1 = _createNode('th');
+                        var obj = all_table_headers[p];
+                        th1.setAttribute('scope', 'col');
+
+                        // get custom header content
+                        if(obj){
+                            var new_column_key = obj && _getKeys(obj)[0];
+                            th1.textContent = all_table_headers[p][new_column_key];
+                            tr_tmp_fragment.appendChild(th1);
+                        }
+                    }
+
+                    var tr_tmp = _createNode('tr');
+                    tr_tmp.appendChild(tr_tmp_fragment);
+
+                    // create table header & append fragment
+                    var thead = _createNode('thead');
+                    thead.appendChild(tr_tmp);
+
+                    var block_table = _createNode('table');
+
+                    if(table_caption) {
+                        var caption = _createNode('caption');
+                        caption.innerHTML = table_caption;
+                        block_table.appendChild(caption);
+                    }
+
+                    // append header to table
+                    block_table.appendChild(thead);
+
+                    var tbody_fragment = document.createDocumentFragment();
+
+                    // create table content
+                    for(var n=0; n<cookie_table_data.length; n++){
+                        var tr = _createNode('tr');
+
+                        for(var g=0; g<all_table_headers.length; ++g){
+                            // get custom header content
+                            obj = all_table_headers[g];
+                            if(obj){
+                                new_column_key = _getKeys(obj)[0];
+
+                                var td_tmp = _createNode('td');
+
+                                // Allow html inside table cells
+                                td_tmp.insertAdjacentHTML('beforeend', cookie_table_data[n][new_column_key]);
+                                td_tmp.setAttribute('data-column', obj[new_column_key]);
+
+                                tr.appendChild(td_tmp);
+                            }
+                        }
+
+                        tbody_fragment.appendChild(tr);
+                    }
+
+                    // append tbody_fragment to tbody & append the latter into the table
+                    var tbody = _createNode('tbody');
+                    tbody.appendChild(tbody_fragment);
+                    block_table.appendChild(tbody);
+
+                    block_table_container.appendChild(block_table);
+                }
+
+                /**
+                 * Append only if is either:
+                 * - togglable div with title
+                 * - a simple div with at least a title or description
+                 */
+                if(toggle_data && title_data || (!toggle_data && (title_data || description_data))){
+                    block_section.appendChild(block_table_container);
+
+                    if(new_settings_blocks)
+                        new_settings_blocks.appendChild(block_section);
+                    else
+                        settings_blocks.appendChild(block_section);
+                }
+            }
+
+            // Create settings buttons
+            if(!settings_buttons){
+                settings_buttons = _createNode('div');
+                settings_buttons.id = 's-bns';
+            }
+
+            if(!settings_accept_all_btn){
+                settings_accept_all_btn = _createNode('button');
+                settings_accept_all_btn.id = 's-all-bn';
+                settings_accept_all_btn.className ='c-bn';
+                settings_buttons.appendChild(settings_accept_all_btn);
+
+                _addEvent(settings_accept_all_btn, 'click', function(){
+                    _cookieconsent.accept('all');
+                    _cookieconsent.hideSettings();
+                    _cookieconsent.hide();
+                });
+            }
+
+            settings_accept_all_btn.innerHTML = settings_modal_config['accept_all_btn'];
+
+            var reject_all_btn_text = settings_modal_config['reject_all_btn'];
+
+            // Add third [optional] reject all button if provided
+            if(reject_all_btn_text){
+
+                if(!settings_reject_all_btn){
+                    settings_reject_all_btn = _createNode('button');
+                    settings_reject_all_btn.id = 's-rall-bn';
+                    settings_reject_all_btn.className = 'c-bn';
+
+                    _addEvent(settings_reject_all_btn, 'click', function(){
+                        _cookieconsent.accept([]);
+                        _cookieconsent.hideSettings();
+                        _cookieconsent.hide();
+                    });
+
+                    settings_inner.className = "bns-t";
+                    settings_buttons.appendChild(settings_reject_all_btn);
+                }
+
+                settings_reject_all_btn.innerHTML = reject_all_btn_text;
+            }
+
+
+            if(!settings_save_btn){
+                settings_save_btn = _createNode('button');
+                settings_save_btn.id = 's-sv-bn';
+                settings_save_btn.className ='c-bn';
+                settings_buttons.appendChild(settings_save_btn);
+
+                // Add save preferences button onClick event
+                // Hide both settings modal and consent modal
+                _addEvent(settings_save_btn, 'click', function(){
+                    _cookieconsent.accept();
+                    _cookieconsent.hideSettings();
+                    _cookieconsent.hide();
+                });
+            }
+
+            settings_save_btn.innerHTML = settings_modal_config['save_settings_btn'];
+
+
+            if(new_settings_blocks) {
+                // replace entire existing cookie category blocks with the new cookie categories new blocks (in a different language)
+                settings_inner.replaceChild(new_settings_blocks, settings_blocks);
+                settings_blocks = new_settings_blocks;
+                return;
+            };
+
+            settings_header.appendChild(settings_title);
+            settings_header.appendChild(settings_close_btn_container);
+            settings_inner.appendChild(settings_header);
+            settings_inner.appendChild(settings_blocks);
+            settings_inner.appendChild(settings_buttons);
+            settings_container_inner.appendChild(settings_inner);
+
+            settings.appendChild(settings_container_inner);
+            settings_container_valign.appendChild(settings);
+            settings_container.appendChild(settings_container_valign);
+
+            all_modals_container.appendChild(settings_container);
+            all_modals_container.appendChild(overlay);
+        }
+
+        /**
+         * Generate cookie consent html markup
+         */
+        var _createCookieConsentHTML = function(){
+
+            // Create main container which holds both consent modal & settings modal
+            main_container = _createNode('div');
+            main_container.id = 'cc--main';
+
+            // Fix layout flash
+            main_container.style.position = "fixed";
+            main_container.innerHTML = '<div id="cc_div" class="cc_div"></div>'
+            all_modals_container = main_container.children[0];
+
+            // Get current language
+            var lang = _config.current_lang;
+
+            // Create consent modal
+            if(consent_modal_exists)
+                _createConsentModal(lang);
+
+            // Always create settings modal
+            _createSettingsModal(lang);
+
+            // Finally append everything (main_container holds both modals)
+            (root || document.body).appendChild(main_container);
+        }
+
+        /**
+         * Update/change modals language
+         * @param {String} lang new language
+         * @param {Boolean} [force] update language fields forcefully
+         * @returns {Boolean}
+         */
+        _cookieconsent.updateLanguage = function(lang, force){
+
+            if(typeof lang !== 'string') return;
+
+            /**
+             * Validate language to avoid errors
+             */
+            var new_validated_lang = _getValidatedLanguage(lang, user_config.languages);
+
+            /**
+             * Set language only if it differs from current
+             */
+            if(new_validated_lang !== _config.current_lang || force === true){
+                _config.current_lang = new_validated_lang;
+
+                if(consent_modal_exists){
+                    _createConsentModal(new_validated_lang);
+                }
+
+                _createSettingsModal(new_validated_lang);
+
+                _log("CookieConsent [LANGUAGE]: curr_lang: '" + new_validated_lang + "'");
+
+                return true;
+            }
+
+            return false;
+        }
+
+        /**
+         * Delete all cookies which are unused (based on selected preferences)
+         *
+         * @param {boolean} [clearOnFirstAction]
+         */
+        var _autoclearCookies = function(clearOnFirstAction){
+
+            // Get number of blocks
+            var len = all_blocks.length;
+            var count = -1;
+
+            // reset reload state
+            reload_page = false;
+
+            // Retrieve all cookies
+            var all_cookies_array = _getCookie('', 'all');
+
+            // delete cookies on 'www.domain.com' and '.www.domain.com' (can also be without www)
+            var domains = [_config.cookie_domain, '.'+_config.cookie_domain];
+
+            // if domain has www, delete cookies also for 'domain.com' and '.domain.com'
+            if(_config.cookie_domain.slice(0, 4) === 'www.'){
+                var non_www_domain = _config.cookie_domain.substr(4);  // remove first 4 chars (www.)
+                domains.push(non_www_domain);
+                domains.push('.' + non_www_domain);
+            }
+
+            // For each block
+            for(var i=0; i<len; i++){
+
+                // Save current block (local scope & less accesses -> ~faster value retrieval)
+                var curr_block = all_blocks[i];
+
+                // If current block has a toggle for opt in/out
+                if(Object.prototype.hasOwnProperty.call(curr_block, "toggle")){
+
+                    // if current block has a cookie table, an off toggle,
+                    // and its preferences were just changed => delete cookies
+                    var category_just_disabled = _inArray(changed_settings, curr_block['toggle']['value']) > -1;
+                    if(
+                        !toggle_states[++count] &&
+                        Object.prototype.hasOwnProperty.call(curr_block, "cookie_table") &&
+                        (clearOnFirstAction || category_just_disabled)
+                    ){
+                        var curr_cookie_table = curr_block['cookie_table'];
+
+                        // Get first property name
+                        var ckey = _getKeys(all_table_headers[0])[0];
+
+                        // Get number of cookies defined in cookie_table
+                        var clen = curr_cookie_table.length;
+
+                        // set "reload_page" to true if reload=on_disable
+                        if(curr_block['toggle']['reload'] === 'on_disable')
+                            category_just_disabled && (reload_page = true);
+
+                        // for each row defined in the cookie table
+                        for(var j=0; j<clen; j++){
+                            var curr_domains = domains;
+
+                            // Get current row of table (corresponds to all cookie params)
+                            var curr_row = curr_cookie_table[j], found_cookies = [];
+                            var curr_cookie_name = curr_row[ckey];
+                            var is_regex = curr_row['is_regex'] || false;
+                            var curr_cookie_domain = curr_row['domain'] || null;
+                            var curr_cookie_path = curr_row['path'] || false;
+
+                            // set domain to the specified domain
+                            curr_cookie_domain && ( curr_domains = [curr_cookie_domain, '.'+curr_cookie_domain]);
+
+                            // If regex provided => filter cookie array
+                            if(is_regex){
+                                for(var n=0; n<all_cookies_array.length; n++){
+                                    if(all_cookies_array[n].match(curr_cookie_name)){
+                                        found_cookies.push(all_cookies_array[n]);
+                                    }
+                                }
+                            }else{
+                                var found_index = _inArray(all_cookies_array, curr_cookie_name);
+                                if(found_index > -1) found_cookies.push(all_cookies_array[found_index]);
+                            }
+
+                            _log("CookieConsent [AUTOCLEAR]: search cookie: '" + curr_cookie_name + "', found:", found_cookies);
+
+                            // If cookie exists -> delete it
+                            if(found_cookies.length > 0){
+                                _eraseCookies(found_cookies, curr_cookie_path, curr_domains);
+                                curr_block['toggle']['reload'] === 'on_clear' && (reload_page = true);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        /**
+         * Set toggles/checkboxes based on accepted categories and save cookie
+         * @param {string[]} accepted_categories - Array of categories to accept
+         */
+        var _saveCookiePreferences = function(accepted_categories){
+
+            changed_settings = [];
+
+            // Retrieve all toggle/checkbox values
+            var category_toggles = settings_container.querySelectorAll('.c-tgl') || [];
+
+            // If there are opt in/out toggles ...
+            if(category_toggles.length > 0){
+
+                for(var i=0; i<category_toggles.length; i++){
+                    if(_inArray(accepted_categories, all_categories[i]) !== -1){
+                        category_toggles[i].checked = true;
+                        if(!toggle_states[i]){
+                            changed_settings.push(all_categories[i]);
+                            toggle_states[i] = true;
+                        }
+                    }else{
+                        category_toggles[i].checked = false;
+                        if(toggle_states[i]){
+                            changed_settings.push(all_categories[i]);
+                            toggle_states[i] = false;
+                        }
+                    }
+                }
+            }
+
+            /**
+             * Clear cookies when settings/preferences change
+             */
+            if(!invalid_consent && _config.autoclear_cookies && changed_settings.length > 0)
+                _autoclearCookies();
+
+            if(!consent_date) consent_date = new Date();
+            if(!consent_uuid) consent_uuid = _uuidv4();
+
+            saved_cookie_content = {
+                "categories": accepted_categories,
+                "level": accepted_categories, // Copy of the `categories` property for compatibility purposes with version v2.8.0 and below.
+                "revision": _config.revision,
+                "data": cookie_data,
+                "rfc_cookie": _config.use_rfc_cookie,
+                "consent_date": consent_date.toISOString(),
+                "consent_uuid": consent_uuid
+            }
+
+            // save cookie with preferences 'categories' (only if never accepted or settings were updated)
+            if(invalid_consent || changed_settings.length > 0){
+                valid_revision = true;
+
+                /**
+                 * Update "last_consent_update" only if it is invalid (after t)
+                 */
+                if(!last_consent_update)
+                    last_consent_update = consent_date;
+                else
+                    last_consent_update = new Date();
+
+                saved_cookie_content['last_consent_update'] = last_consent_update.toISOString();
+
+                /**
+                 * Update accept type
+                 */
+                accept_type = _getAcceptType(_getCurrentCategoriesState());
+
+                _setCookie(_config.cookie_name, JSON.stringify(saved_cookie_content));
+                _manageExistingScripts();
+            }
+
+            if(invalid_consent){
+
+                /**
+                 * Delete unused/"zombie" cookies if consent is not valid (not yet expressed or cookie has expired)
+                 */
+                if(_config.autoclear_cookies)
+                    _autoclearCookies(true);
+
+                if(typeof onFirstAction === 'function')
+                    onFirstAction(_cookieconsent.getUserPreferences(), saved_cookie_content);
+
+                if(typeof onAccept === 'function')
+                    onAccept(saved_cookie_content);
+
+                /**
+                 * Set consent as valid
+                 */
+                invalid_consent = false;
+
+                if(_config.mode === 'opt-in') return;
+            }
+
+            // fire onChange only if settings were changed
+            if(typeof onChange === "function" && changed_settings.length > 0)
+                onChange(saved_cookie_content, changed_settings);
+
+            /**
+             * reload page if needed
+             */
+            if(reload_page)
+                location.reload();
+        }
+
+        /**
+         * Returns index of found element inside array, otherwise -1
+         * @param {Array} arr
+         * @param {Object} value
+         * @returns {number}
+         */
+        var _inArray = function(arr, value){
+            return arr.indexOf(value);
+        }
+
+        /**
+         * Helper function which prints info (console.log())
+         * @param {Object} print_msg
+         * @param {Object} [optional_param]
+         */
+        var _log = function(print_msg, optional_param, error){
+            ENABLE_LOGS && (!error ? console.log(print_msg, optional_param !== undefined ? optional_param : ' ') : console.error(print_msg, optional_param || ""));
+        }
+
+        /**
+         * Helper function which creates an HTMLElement object based on 'type' and returns it.
+         * @param {string} type
+         * @returns {HTMLElement}
+         */
+        var _createNode = function(type){
+            var el = document.createElement(type);
+            if(type === 'button'){
+                el.setAttribute('type', type);
+            }
+            return el;
+        }
+
+        /**
+         * Generate RFC4122-compliant UUIDs.
+         * https://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid?page=1&tab=votes#tab-top
+         * @returns {string}
+         */
+        var _uuidv4 = function(){
+            return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, function(c){
+                try{
+                    return (c ^ (window.crypto || window.msCrypto).getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
+                }catch(e){
+                    return '';
+                }
+            });
+        }
+
+        /**
+         * Resolve which language should be used.
+         *
+         * @param {Object} languages Object with language translations
+         * @param {string} [requested_language] Language specified by given configuration parameters
+         * @returns {string}
+         */
+        var _resolveCurrentLang = function (languages, requested_language) {
+
+            if (_config.auto_language === 'browser') {
+                return _getValidatedLanguage(_getBrowserLang(), languages);
+            } else if (_config.auto_language === 'document') {
+                return _getValidatedLanguage(document.documentElement.lang, languages);
+            } else {
+                if (typeof requested_language === 'string') {
+                    return _config.current_lang = _getValidatedLanguage(requested_language, languages);
+                }
+            }
+
+            _log("CookieConsent [LANG]: setting current_lang = '" + _config.current_lang + "'");
+            return _config.current_lang; // otherwise return default
+        }
+
+        /**
+         * Get current client's browser language
+         * @returns {string}
+         */
+        var _getBrowserLang = function(){
+            var browser_lang = navigator.language || navigator.browserLanguage;
+            browser_lang.length > 2 && (browser_lang = browser_lang[0]+browser_lang[1]);
+            _log("CookieConsent [LANG]: detected_browser_lang = '"+ browser_lang + "'");
+            return browser_lang.toLowerCase()
+        }
+
+        /**
+         * Trap focus inside modal and focus the first
+         * focusable element of current active modal
+         */
+        var _handleFocusTrap = function(){
+
+            _addEvent(document, 'keydown', function(e){
+
+                // If is tab key => ok
+                if(e.key !== 'Tab')
+                    return;
+
+                if(!consent_modal_visible && !settings_modal_visible)
+                    return;
+
+                // If there is any modal to focus
+                if(current_modal_focusable){
+
+                    var activeElement = document.activeElement;
+
+                    // If reached natural end of the tab sequence => restart
+                    // If modal is not focused => focus modal
+                    if(e.shiftKey){
+                        if (activeElement === current_modal_focusable[0] || !current_focused_modal.contains(activeElement)) {
+                            e.preventDefault();
+                            setFocus(current_modal_focusable[1])
+                        }
+                    }else{
+                        if (document.activeElement === current_modal_focusable[1] || !current_focused_modal.contains(activeElement)) {
+                            e.preventDefault();
+                            setFocus(current_modal_focusable[0]);
+                        }
+                    }
+                }
+            });
+
+            if(document.contains){
+                _addEvent(settings_container, 'click', function(e){
+                    /**
+                     * If click is on the foreground overlay (and not inside settings_modal),
+                     * hide settings modal
+                     *
+                     * Notice: click on div is not supported in IE
+                     */
+                    if(settings_modal_visible){
+                        if(!settings_inner.contains(e.target)){
+                            _cookieconsent.hideSettings();
+                        }
+                    }
+
+                }, true);
+            }
+        }
+
+        /**
+         * Manage each modal's layout
+         * @param {Object} gui_options
+         */
+        var _guiManager = function(gui_options, only_consent_modal){
+
+            // If gui_options is not object => exit
+            if(typeof gui_options !== 'object') return;
+
+            var consent_modal_options = gui_options['consent_modal'];
+            var settings_modal_options = gui_options['settings_modal'];
+
+            /**
+             * Helper function which adds layout and
+             * position classes to given modal
+             *
+             * @param {HTMLElement} modal
+             * @param {string[]} allowed_layouts
+             * @param {string[]} allowed_positions
+             * @param {string} layout
+             * @param {string[]} position
+             */
+            function _setLayout(modal, allowed_layouts, allowed_positions, allowed_transitions, layout, position, transition){
+                position = (position && position.split(" ")) || [];
+
+                // Check if specified layout is valid
+                if(_inArray(allowed_layouts, layout) > -1){
+
+                    // Add layout classes
+                    _addClass(modal, layout);
+
+                    // Add position class (if specified)
+                    if(!(layout === 'bar' && position[0] === 'middle') && _inArray(allowed_positions, position[0]) > -1){
+                        for(var i=0; i<position.length; i++){
+                            _addClass(modal, position[i]);
+                        }
+                    }
+                }
+
+                // Add transition class
+                (_inArray(allowed_transitions, transition) > -1) && _addClass(modal, transition);
+            }
+
+            if(consent_modal_exists && consent_modal_options){
+                _setLayout(
+                    consent_modal,
+                    ['box', 'bar', 'cloud'],
+                    ['top', 'middle', 'bottom'],
+                    ['zoom', 'slide'],
+                    consent_modal_options['layout'],
+                    consent_modal_options['position'],
+                    consent_modal_options['transition']
+                );
+            }
+
+            if(!only_consent_modal && settings_modal_options){
+                _setLayout(
+                    settings_container,
+                    ['bar'],
+                    ['left', 'right'],
+                    ['zoom', 'slide'],
+                    settings_modal_options['layout'],
+                    settings_modal_options['position'],
+                    settings_modal_options['transition']
+                );
+            }
+        }
+
+        /**
+         * Returns true if cookie category is accepted by the user
+         * @param {string} cookie_category
+         * @returns {boolean}
+         */
+        _cookieconsent.allowedCategory = function(cookie_category){
+
+            if(!invalid_consent || _config.mode === 'opt-in')
+                var allowed_categories = JSON.parse(_getCookie(_config.cookie_name, 'one', true) || '{}')['categories'] || []
+            else  // mode is 'opt-out'
+                var allowed_categories = default_enabled_categories;
+
+            return _inArray(allowed_categories, cookie_category) > -1;
+        }
+
+        /**
+         * "Init" method. Will run once and only if modals do not exist
+         */
+        _cookieconsent.run = function(user_config){
+            if(!document.getElementById('cc_div')){
+
+                // configure all parameters
+                _setConfig(user_config);
+
+                // if is bot, don't run plugin
+                if(is_bot) return;
+
+                // Retrieve cookie value (if set)
+                saved_cookie_content = JSON.parse(_getCookie(_config.cookie_name, 'one', true) || "{}");
+
+                // Retrieve "consent_uuid"
+                consent_uuid = saved_cookie_content['consent_uuid'];
+
+                // If "consent_uuid" is present => assume that consent was previously given
+                var cookie_consent_accepted = consent_uuid !== undefined;
+
+                // Retrieve "consent_date"
+                consent_date = saved_cookie_content['consent_date'];
+                consent_date && (consent_date = new Date(consent_date));
+
+                // Retrieve "last_consent_update"
+                last_consent_update = saved_cookie_content['last_consent_update'];
+                last_consent_update && (last_consent_update = new Date(last_consent_update));
+
+                // Retrieve "data"
+                cookie_data = saved_cookie_content['data'] !== undefined ? saved_cookie_content['data'] : null;
+
+                // If revision is enabled and current value !== saved value inside the cookie => revision is not valid
+                if(revision_enabled && saved_cookie_content['revision'] !== _config.revision){
+                    valid_revision = false;
+                }
+
+                // If consent is not valid => create consent modal
+                consent_modal_exists = invalid_consent = (!cookie_consent_accepted || !valid_revision || !consent_date || !last_consent_update || !consent_uuid);
+
+                // Generate cookie-settings dom (& consent modal)
+                _createCookieConsentHTML();
+
+                _getModalFocusableData();
+                _guiManager(user_config['gui_options']);
+                _addDataButtonListeners();
+
+                if(_config.autorun && consent_modal_exists){
+                    _cookieconsent.show(user_config['delay'] || 0);
+                }
+
+                // Add class to enable animations/transitions
+                setTimeout(function(){_addClass(main_container, 'c--anim');}, 30);
+
+                // Accessibility :=> if tab pressed => trap focus inside modal
+                setTimeout(function(){_handleFocusTrap();}, 100);
+
+                // If consent is valid
+                if(!invalid_consent){
+                    var rfc_prop_exists = typeof saved_cookie_content['rfc_cookie'] === "boolean";
+
+                    /*
+                     * Convert cookie to rfc format (if `use_rfc_cookie` is enabled)
+                     */
+                    if(!rfc_prop_exists || (rfc_prop_exists && saved_cookie_content['rfc_cookie'] !== _config.use_rfc_cookie)){
+                        saved_cookie_content['rfc_cookie'] = _config.use_rfc_cookie;
+                        _setCookie(_config.cookie_name, JSON.stringify(saved_cookie_content));
+                    }
+
+                    /**
+                     * Update accept type
+                     */
+                    accept_type = _getAcceptType(_getCurrentCategoriesState());
+
+                    _manageExistingScripts();
+
+                    if(typeof onAccept === 'function')
+                        onAccept(saved_cookie_content);
+
+                    _log("CookieConsent [NOTICE]: consent already given!", saved_cookie_content);
+
+                }else{
+                    if(_config.mode === 'opt-out'){
+                        _log("CookieConsent [CONFIG] mode='" + _config.mode + "', default enabled categories:", default_enabled_categories);
+                        _manageExistingScripts(default_enabled_categories);
+                    }
+                    _log("CookieConsent [NOTICE]: ask for consent!");
+                }
+            }else{
+                _log("CookieConsent [NOTICE]: cookie consent already attached to body!");
+            }
+        }
+
+        /**
+         * This function handles the loading/activation logic of the already
+         * existing scripts based on the current accepted cookie categories
+         *
+         * @param {string[]} [must_enable_categories]
+         */
+        var _manageExistingScripts = function(must_enable_categories){
+
+            if(!_config.page_scripts) return;
+
+            // get all the scripts with "cookie-category" attribute
+            var scripts = document.querySelectorAll('script[' + _config.script_selector + ']');
+            var accepted_categories = must_enable_categories || saved_cookie_content['categories'] || [];
+
+            /**
+             * Load scripts (sequentially), using a recursive function
+             * which loops through the scripts array
+             * @param {Element[]} scripts scripts to load
+             * @param {number} index current script to load
+             */
+            var _loadScripts = function(scripts, index){
+                if(index < scripts.length){
+
+                    var curr_script = scripts[index];
+                    var curr_script_category = curr_script.getAttribute(_config.script_selector);
+
+                    /**
+                     * If current script's category is on the array of categories
+                     * accepted by the user => load script
+                     */
+                    if(_inArray(accepted_categories, curr_script_category) > -1){
+
+                        curr_script.type = curr_script.getAttribute('data-type') || 'text/javascript';
+                        curr_script.removeAttribute(_config.script_selector);
+
+                        // get current script data-src
+                        var src = curr_script.getAttribute('data-src');
+
+                        // some scripts (like ga) might throw warning if data-src is present
+                        src && curr_script.removeAttribute('data-src');
+
+                        // create fresh script (with the same code)
+                        var fresh_script = _createNode('script');
+                        fresh_script.textContent = curr_script.innerHTML;
+
+                        // Copy attributes over to the new "revived" script
+                        (function(destination, source){
+                            var attributes = source.attributes;
+                            var len = attributes.length;
+                            for(var i=0; i<len; i++){
+                                var attr_name = attributes[i].nodeName;
+                                destination.setAttribute(attr_name , source[attr_name] || source.getAttribute(attr_name));
+                            }
+                        })(fresh_script, curr_script);
+
+                        // set src (if data-src found)
+                        src ? (fresh_script.src = src) : (src = curr_script.src);
+
+                        // if script has "src" attribute
+                        // try loading it sequentially
+                        if(src){
+                            // load script sequentially => the next script will not be loaded
+                            // until the current's script onload event triggers
+                            if(fresh_script.readyState) {  // only required for IE <9
+                                fresh_script.onreadystatechange = function() {
+                                    if (fresh_script.readyState === "loaded" || fresh_script.readyState === "complete" ) {
+                                        fresh_script.onreadystatechange = null;
+                                        _loadScripts(scripts, ++index);
+                                    }
+                                };
+                            }else{  // others
+                                fresh_script.onload = function(){
+                                    fresh_script.onload = null;
+                                    _loadScripts(scripts, ++index);
+                                };
+                            }
+                        }
+
+                        // Replace current "sleeping" script with the new "revived" one
+                        curr_script.parentNode.replaceChild(fresh_script, curr_script);
+
+                        /**
+                         * If we managed to get here and scr is still set, it means that
+                         * the script is loading/loaded sequentially so don't go any further
+                         */
+                        if(src) return;
+                    }
+
+                    // Go to next script right away
+                    _loadScripts(scripts, ++index);
+                }
+            }
+
+            _loadScripts(scripts, 0);
+        }
+
+        /**
+         * Save custom data inside cookie
+         * @param {object|string} new_data
+         * @param {string} [mode]
+         * @returns {boolean}
+         */
+        var _setCookieData = function(new_data, mode){
+
+            var set = false;
+            /**
+             * If mode is 'update':
+             * add/update only the specified props.
+             */
+            if(mode === 'update'){
+                cookie_data = _cookieconsent.get('data');
+                var same_type = typeof cookie_data === typeof new_data;
+
+                if(same_type && typeof cookie_data === "object"){
+                    !cookie_data && (cookie_data = {});
+
+                    for(var prop in new_data){
+                        if(cookie_data[prop] !== new_data[prop]){
+                            cookie_data[prop] = new_data[prop]
+                            set = true;
+                        }
+                    }
+                }else if((same_type || !cookie_data) && cookie_data !== new_data){
+                    cookie_data = new_data;
+                    set = true;
+                }
+            }else{
+                cookie_data = new_data;
+                set = true;
+            }
+
+            if(set){
+                saved_cookie_content['data'] = cookie_data;
+                _setCookie(_config.cookie_name, JSON.stringify(saved_cookie_content));
+            }
+
+            return set;
+        }
+
+        /**
+         * Helper method to set a variety of fields
+         * @param {string} field
+         * @param {object} data
+         * @returns {boolean}
+         */
+        _cookieconsent.set = function(field, data){
+            switch(field){
+                case 'data': return _setCookieData(data['value'], data['mode']);
+                default: return false;
+            }
+        }
+
+        /**
+         * Retrieve data from existing cookie
+         * @param {string} field
+         * @param {string} [cookie_name]
+         * @returns {any}
+         */
+        _cookieconsent.get = function(field, cookie_name){
+            var cookie = JSON.parse(_getCookie(cookie_name || _config.cookie_name, 'one', true) || "{}");
+
+            return cookie[field];
+        }
+
+        /**
+         * Read current configuration value
+         * @returns {any}
+         */
+        _cookieconsent.getConfig = function(field){
+            return _config[field] || user_config[field];
+        }
+
+        /**
+         * Obtain accepted and rejected categories
+         * @returns {{accepted: string[], rejected: string[]}}
+         */
+        var _getCurrentCategoriesState = function(){
+
+            // get accepted categories
+            accepted_categories = saved_cookie_content['categories'] || [];
+
+            // calculate rejected categories (all_categories - accepted_categories)
+            rejected_categories = all_categories.filter(function(category){
+                return (_inArray(accepted_categories, category) === -1);
+            });
+
+            return {
+                accepted: accepted_categories,
+                rejected: rejected_categories
+            }
+        }
+
+        /**
+         * Calculate "accept type" given current categories state
+         * @param {{accepted: string[], rejected: string[]}} currentCategoriesState
+         * @returns {string}
+         */
+        var _getAcceptType = function(currentCategoriesState){
+
+            var type = 'custom';
+
+            // number of categories marked as necessary/readonly
+            var necessary_categories_length = readonly_categories.filter(function(readonly){
+                return readonly === true;
+            }).length;
+
+            // calculate accept type based on accepted/rejected categories
+            if(currentCategoriesState.accepted.length === all_categories.length)
+                type = 'all';
+            else if(currentCategoriesState.accepted.length === necessary_categories_length)
+                type = 'necessary'
+
+            return type;
+        }
+
+        /**
+         * @typedef {object} userPreferences
+         * @property {string} accept_type
+         * @property {string[]} accepted_categories
+         * @property {string[]} rejected_categories
+         */
+
+        /**
+         * Retrieve current user preferences (summary)
+         * @returns {userPreferences}
+         */
+        _cookieconsent.getUserPreferences = function(){
+            var currentCategoriesState = _getCurrentCategoriesState();
+            var accept_type = _getAcceptType(currentCategoriesState);
+
+            return {
+                'accept_type': accept_type,
+                'accepted_categories': currentCategoriesState.accepted,
+                'rejected_categories': currentCategoriesState.rejected
+            }
+        }
+
+        /**
+         * Function which will run after script load
+         * @callback scriptLoaded
+         */
+
+        /**
+         * Dynamically load script (append to head)
+         * @param {string} src
+         * @param {scriptLoaded} callback
+         * @param {object[]} [attrs] Custom attributes
+         */
+        _cookieconsent.loadScript = function(src, callback, attrs){
+
+            var function_defined = typeof callback === 'function';
+
+            // Load script only if not already loaded
+            if(!document.querySelector('script[src="' + src + '"]')){
+
+                var script = _createNode('script');
+
+                // if an array is provided => add custom attributes
+                if(attrs && attrs.length > 0){
+                    for(var i=0; i<attrs.length; ++i){
+                        attrs[i] && script.setAttribute(attrs[i]['name'], attrs[i]['value']);
+                    }
+                }
+
+                // if callback function defined => run callback onload
+                if(function_defined){
+                    script.onload = callback;
+                }
+
+                script.src = src;
+
+                /**
+                 * Append script to head
+                 */
+                document.head.appendChild(script);
+            }else{
+                function_defined && callback();
+            }
+        }
+
+        /**
+         * Manage dynamically loaded scripts: https://github.com/orestbida/cookieconsent/issues/101
+         * If plugin has already run, call this method to enable
+         * the newly added scripts based on currently selected preferences
+         */
+        _cookieconsent.updateScripts = function(){
+            _manageExistingScripts();
+        }
+
+        /**
+         * Show cookie consent modal (with delay parameter)
+         * @param {number} [delay]
+         * @param {boolean} [create_modal] create modal if it doesn't exist
+         */
+        _cookieconsent.show = function(delay, create_modal){
+
+            if(create_modal === true)
+                _createConsentModal(_config.current_lang);
+
+            if(!consent_modal_exists)
+                return;
+
+            last_elem_before_modal = document.activeElement;
+            current_modal_focusable = consent_modal_focusable;
+            current_focused_modal = consent_modal;
+
+            consent_modal_visible = true;
+            consent_modal.removeAttribute('aria-hidden');
+
+            setTimeout(function() {
+                _addClass(html_dom, "show--consent");
+                _log("CookieConsent [MODAL]: show consent_modal");
+            }, delay > 0 ? delay : (create_modal ? 30 : 0));
+
+        }
+
+        /**
+         * Hide consent modal
+         */
+        _cookieconsent.hide = function(){
+
+            if(!consent_modal_exists)
+                return;
+
+            consent_modal_visible = false;
+
+            setFocus(cmFocusSpan);
+
+            consent_modal.setAttribute('aria-hidden', 'true');
+            _removeClass(html_dom, "show--consent");
+
+            if(last_elem_before_modal) {
+                setFocus(last_elem_before_modal);
+                last_elem_before_modal = null;
+            }
+
+            _log("CookieConsent [MODAL]: hide");
+        }
+
+        /**
+         * Show settings modal (with optional delay)
+         * @param {number} delay
+         */
+        _cookieconsent.showSettings = function(delay){
+
+            settings_modal_visible = true;
+            settings_container.removeAttribute('aria-hidden');
+
+            if(consent_modal_visible){
+                last_consent_modal_btn_focus = document.activeElement;
+            }else{
+                last_elem_before_modal = document.activeElement;
+            }
+
+            current_focused_modal = settings_container;
+            current_modal_focusable = settings_modal_focusable;
+
+            setTimeout(function() {
+                _addClass(html_dom, "show--settings");
+                _log("CookieConsent [SETTINGS]: show settings_modal");
+            }, delay > 0 ? delay : 0);
+        }
+
+        /**
+         * Hide settings modal
+         */
+        _cookieconsent.hideSettings = function(){
+
+            settings_modal_visible = false;
+
+            discardUnsavedToggles();
+
+            setFocus(smFocusSpan);
+
+            settings_container.setAttribute('aria-hidden', 'true');
+            _removeClass(html_dom, "show--settings");
+
+            if(consent_modal_visible){
+                if(last_consent_modal_btn_focus) {
+                    setFocus(last_consent_modal_btn_focus);
+                    last_consent_modal_btn_focus = null;
+                }
+                current_focused_modal = consent_modal;
+                current_modal_focusable = consent_modal_focusable;
+            }else{
+                if(last_elem_before_modal) {
+                    setFocus(last_elem_before_modal);
+                    last_elem_before_modal = null;
+                }
+            }
+
+            _log("CookieConsent [SETTINGS]: hide settings_modal");
+        }
+
+        /**
+         * Accept cookieconsent function API
+         * @param {string[]|string} _categories - Categories to accept
+         * @param {string[]} [_exclusions] - Excluded categories [optional]
+         */
+        _cookieconsent.accept = function(_categories, _exclusions){
+            var categories = _categories || undefined;
+            var exclusions = _exclusions || [];
+            var to_accept = [];
+
+            /**
+             * Get all accepted categories
+             * @returns {string[]}
+             */
+            var _getCurrentPreferences = function(){
+                var toggles = document.querySelectorAll('.c-tgl') || [];
+                var states = [];
+
+                for(var i=0; i<toggles.length; i++){
+                    if(toggles[i].checked){
+                        states.push(toggles[i].value);
+                    }
+                }
+                return states;
+            }
+
+            if(!categories){
+                to_accept = _getCurrentPreferences();
+            }else{
+                if(
+                    typeof categories === "object" &&
+                    typeof categories.length === "number"
+                ){
+                    for(var i=0; i<categories.length; i++){
+                        if(_inArray(all_categories, categories[i]) !== -1)
+                            to_accept.push(categories[i]);
+                    }
+                }else if(typeof categories === "string"){
+                    if(categories === 'all')
+                        to_accept = all_categories.slice();
+                    else{
+                        if(_inArray(all_categories, categories) !== -1)
+                            to_accept.push(categories);
+                    }
+                }
+            }
+
+            // Remove excluded categories
+            if(exclusions.length >= 1){
+                for(i=0; i<exclusions.length; i++){
+                    to_accept = to_accept.filter(function(item) {
+                        return item !== exclusions[i]
+                    })
+                }
+            }
+
+            // Add back all the categories set as "readonly/required"
+            for(i=0; i<all_categories.length; i++){
+                if(
+                    readonly_categories[i] === true &&
+                    _inArray(to_accept, all_categories[i]) === -1
+                ){
+                    to_accept.push(all_categories[i]);
+                }
+            }
+
+            _saveCookiePreferences(to_accept);
+        }
+
+        /**
+         * API function to easily erase cookies
+         * @param {(string|string[])} _cookies
+         * @param {string} [_path] - optional
+         * @param {string} [_domain] - optional
+         */
+        _cookieconsent.eraseCookies = function(_cookies, _path, _domain){
+            var cookies = [];
+            var domains = _domain
+                ? [_domain, "."+_domain]
+                : [_config.cookie_domain, "."+_config.cookie_domain];
+
+            if(typeof _cookies === "object" && _cookies.length > 0){
+                for(var i=0; i<_cookies.length; i++){
+                    this.validCookie(_cookies[i]) && cookies.push(_cookies[i]);
+                }
+            }else{
+                this.validCookie(_cookies) && cookies.push(_cookies);
+            }
+
+            _eraseCookies(cookies, _path, domains);
+        }
+
+        /**
+         * Set cookie, by specifying name and value
+         * @param {string} name
+         * @param {string} value
+         */
+        var _setCookie = function(name, value) {
+
+            var cookie_expiration = _config.cookie_expiration;
+
+            if(typeof _config.cookie_necessary_only_expiration === 'number' && accept_type === 'necessary')
+                cookie_expiration = _config.cookie_necessary_only_expiration;
+
+            value = _config.use_rfc_cookie ? encodeURIComponent(value) : value;
+
+            var date = new Date();
+            date.setTime(date.getTime() + (1000 * (cookie_expiration * 24 * 60 * 60)));
+            var expires = "; expires=" + date.toUTCString();
+
+            var cookieStr = name + "=" + (value || "") + expires + "; Path=" + _config.cookie_path + ";";
+            cookieStr += " SameSite=" + _config.cookie_same_site + ";";
+
+            // assures cookie works with localhost (=> don't specify domain if on localhost)
+            if(location.hostname.indexOf(".") > -1 && _config.cookie_domain){
+                cookieStr += " Domain=" + _config.cookie_domain + ";";
+            }
+
+            if(location.protocol === "https:") {
+                cookieStr += " Secure;";
+            }
+
+            document.cookie = cookieStr;
+
+            _log("CookieConsent [SET_COOKIE]: '" + name + "' expires after " + cookie_expiration + " day(s)");
+        }
+
+        /**
+         * Get cookie value by name,
+         * returns the cookie value if found (or an array
+         * of cookies if filter provided), otherwise empty string: ""
+         * @param {string} name
+         * @param {string} filter 'one' or 'all'
+         * @param {boolean} [get_value] set to true to obtain its value
+         * @returns {string|string[]}
+         */
+        var _getCookie = function(name, filter, get_value) {
+            var found;
+
+            if(filter === 'one'){
+                found = document.cookie.match("(^|;)\\s*" + name + "\\s*=\\s*([^;]+)");
+                found = found ? (get_value ? found.pop() : name) : "";
+
+                if(found && name === _config.cookie_name){
+                    try{
+                        found = JSON.parse(found)
+                    }catch(e){
+                        try {
+                            found = JSON.parse(decodeURIComponent(found))
+                        } catch (e) {
+                            // if I got here => cookie value is not a valid json string
+                            found = {};
+                        }
+                    }
+                    found = JSON.stringify(found);
+                }
+            }else if(filter === 'all'){
+                // array of names of all existing cookies
+                var cookies = document.cookie.split(/;\s*/); found = [];
+                for(var i=0; i<cookies.length; i++){
+                    found.push(cookies[i].split("=")[0]);
+                }
+            }
+
+            return found;
+        }
+
+        /**
+         * Delete cookie by name & path
+         * @param {string[]} cookies
+         * @param {string} [custom_path] - optional
+         * @param {string[]} domains - example: ['domain.com', '.domain.com']
+         */
+        var _eraseCookies = function(cookies, custom_path, domains) {
+            var path = custom_path ? custom_path : '/';
+            var expires = 'Expires=Thu, 01 Jan 1970 00:00:01 GMT;';
+
+            for(var i=0; i<cookies.length; i++){
+                for(var j=0; j<domains.length; j++){
+                    document.cookie = cookies[i] + '=; path=' + path +
+                    (domains[j].indexOf('.') == 0 ? '; domain=' + domains[j] : "") + '; ' + expires;
+                }
+                _log("CookieConsent [AUTOCLEAR]: deleting cookie: '" + cookies[i] + "' path: '" + path + "' domain:", domains);
+            }
+        }
+
+        /**
+         * Returns true if cookie was found and has valid value (not empty string)
+         * @param {string} cookie_name
+         * @returns {boolean}
+         */
+        _cookieconsent.validCookie = function(cookie_name){
+            return _getCookie(cookie_name, 'one', true) !== "";
+        }
+
+        /**
+         * Function to run when event is fired
+         * @callback eventFired
+         */
+
+        /**
+         * Add event listener to dom object (cross browser function)
+         * @param {Element} elem
+         * @param {string} event
+         * @param {eventFired} fn
+         * @param {boolean} [isPassive]
+         */
+        var _addEvent = function(elem, event, fn, isPassive) {
+            elem.addEventListener(event, fn , isPassive === true ? { passive: true } : false);
+        }
+
+        /**
+         * Get all prop. keys defined inside object
+         * @param {Object} obj
+         */
+        var _getKeys = function(obj){
+            if(typeof obj === "object"){
+                return Object.keys(obj);
+            }
+        }
+
+        /**
+         * Append class to the specified dom element
+         * @param {HTMLElement} elem
+         * @param {string} classname
+         */
+        var _addClass = function (elem, classname){
+            elem.classList.add(classname);
+        }
+
+        /**
+         * Remove specified class from dom element
+         * @param {HTMLElement} elem
+         * @param {string} classname
+         */
+        var _removeClass = function (el, className) {
+            el.classList.remove(className);
+        }
+
+        /**
+         * Check if html element has class
+         * @param {HTMLElement} el
+         * @param {string} className
+         */
+        var _hasClass = function(el, className) {
+            return el.classList.contains(className);
+        }
+
+        /**
+         * @param {1 | 2} modal_id
+         */
+        var generateFocusSpan = function(modal_id) {
+            var span = _createNode('span');
+            span.tabIndex = -1;
+
+            if(modal_id === 1)
+                cmFocusSpan = span;
+            else
+                smFocusSpan = span;
+
+            return span;
+        }
+
+        /**
+         * @param {HTMLElement} el
+         */
+        var setFocus = function(el) {
+            el && el.focus();
+        }
+
+        /**
+         * https://github.com/orestbida/cookieconsent/issues/481
+         */
+        var discardUnsavedToggles = function() {
+
+            /**
+             * @type {NodeListOf<HTMLInputElement>}
+             */
+            var toggles = settings_inner.querySelectorAll('.c-tgl');
+
+            for(var i=0; i<toggles.length; i++) {
+                var category = toggles[i].value;
+                var is_readonly = readonly_categories.indexOf(category) > -1;
+
+                toggles[i].checked = is_readonly || _cookieconsent.allowedCategory(category);
+            }
+
+        }
+
+        return _cookieconsent;
+    };
+
+    var init = 'initCookieConsent';
+    /**
+     * Make CookieConsent object accessible globally
+     */
+    if(typeof window !== 'undefined' && typeof window[init] !== 'function'){
+        window[init] = CookieConsent
+    }
+})();
Index: /main/trunk/greenstone3/web/interfaces/default/transform/layouts/header.xsl
===================================================================
--- /main/trunk/greenstone3/web/interfaces/default/transform/layouts/header.xsl	(revision 37731)
+++ /main/trunk/greenstone3/web/interfaces/default/transform/layouts/header.xsl	(revision 37732)
@@ -148,8 +148,8 @@
 	<xsl:choose>
 		<xsl:when test="$cookie_consent">
-			<link rel="stylesheet" href="interfaces/{$interface_name}/style/cookieconsent.css" type="text/css"/>
+			<link rel="stylesheet" href="interfaces/{$interface_name}/cc/cookieconsent.css" type="text/css"/>
 			<gslib:langfrag name="ccm" />
-			<script defer="true" type="text/javascript" src="interfaces/{$interface_name}/js/cookieconsent.js"><xsl:text> </xsl:text></script>
-			<script defer="true" type="text/javascript" src="interfaces/{$interface_name}/js/cookieconsent-init.js"><xsl:text> </xsl:text></script>
+			<script defer="true" type="text/javascript" src="interfaces/{$interface_name}/cc/cookieconsent.js"><xsl:text> </xsl:text></script>
+			<script defer="true" type="text/javascript" src="interfaces/{$interface_name}/cc/cookieconsent-gsinit.js"><xsl:text> </xsl:text></script>
 		</xsl:when>
 	</xsl:choose>
