github.com/choria-io/go-choria@v0.28.1-0.20240416190746-b3bf9c7d5a45/docs/themes/hugo-theme-relearn/static/js/variant.js (about)

     1  window.relearn = window.relearn || {};
     2  
     3  // we need to load this script in the html head to avoid flickering
     4  // on page load if the user has selected a non default variant
     5  
     6  // polyfill this rotten piece of sh...oftware
     7  if( typeof NodeList !== "undefined" && NodeList.prototype && !NodeList.prototype.forEach ){
     8  	NodeList.prototype.forEach = Array.prototype.forEach;
     9  }
    10  
    11  if (!String.prototype.startsWith) {
    12  	Object.defineProperty(String.prototype, 'startsWith', {
    13  		value: function(search, rawPos) {
    14  			var pos = rawPos > 0 ? rawPos|0 : 0;
    15  			return this.substring(pos, pos + search.length) === search;
    16  		}
    17  	});
    18  }
    19  
    20  "function"!=typeof Object.assign&&(Object.assign=function(n,t){"use strict";if(null==n)throw new TypeError("Cannot convert undefined or null to object");for(var r=Object(n),e=1;e<arguments.length;e++){var o=arguments[e];if(null!=o)for(var c in o)Object.prototype.hasOwnProperty.call(o,c)&&(r[c]=o[c])}return r});
    21  
    22  if(!Array.prototype.find){Array.prototype.find=function(predicate){if(this===null){throw new TypeError('Array.prototype.find called on null or undefined')}if(typeof predicate!=='function'){throw new TypeError('predicate must be a function')}var list=Object(this);var length=list.length>>>0;var thisArg=arguments[1];var value;for(var i=0;i<length;i+=1){value=list[i];if(predicate.call(thisArg,value,i,list)){return value}}return undefined}}
    23  
    24  Array.from||(Array.from=function(){var r;try{r=Symbol.iterator?Symbol.iterator:"Symbol(Symbol.iterator)"}catch(t){r="Symbol(Symbol.iterator)"}var t=Object.prototype.toString,n=function(r){return"function"==typeof r||"[object Function]"===t.call(r)},o=Math.pow(2,53)-1,e=function(r){var t=function(r){var t=Number(r);return isNaN(t)?0:0!==t&&isFinite(t)?(t>0?1:-1)*Math.floor(Math.abs(t)):t}(r);return Math.min(Math.max(t,0),o)},a=function(t,n){var o=t&&n[r]();return function(r){return t?o.next():n[r]}},i=function(r,t,n,o,e,a){for(var i=0;i<n||e;){var u=o(i),f=e?u.value:u;if(e&&u.done)return t;t[i]=a?void 0===r?a(f,i):a.call(r,f,i):f,i+=1}if(e)throw new TypeError("Array.from: provided arrayLike or iterator has length more then 2 ** 52 - 1");return t.length=n,t};return function(t){var o=this,u=Object(t),f=n(u[r]);if(null==t&&!f)throw new TypeError("Array.from requires an array-like object or iterator - not null or undefined");var l,c=arguments.length>1?arguments[1]:void 0;if(void 0!==c){if(!n(c))throw new TypeError("Array.from: when provided, the second argument must be a function");arguments.length>2&&(l=arguments[2])}var y=e(u.length),h=n(o)?Object(new o(y)):new Array(y);return i(l,h,y,a(f,u),f,c)}}());
    25  
    26  const ElementPrototype=window.Element.prototype;
    27  if(typeof ElementPrototype.matches!=='function'){ElementPrototype.matches=ElementPrototype.msMatchesSelector||ElementPrototype.mozMatchesSelector||ElementPrototype.webkitMatchesSelector||function matches(selector){let element=this;const elements=(element.document||element.ownerDocument).querySelectorAll(selector);let index=0;while(elements[index]&&elements[index]!==element){index+=1}return Boolean(elements[index])}}if(typeof ElementPrototype.closest!=='function'){ElementPrototype.closest=function closest(selector){let element=this;while(element&&element.nodeType===1){if(element.matches(selector)){return element}element=element.parentNode}return null}}
    28  
    29  function _createForOfIteratorHelperLoose(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (it) return (it = it.call(o)).next.bind(it); if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; return function () { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
    30  
    31  function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
    32  
    33  function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
    34  
    35  function ready(fn) { if (document.readyState == 'complete') { fn(); } else { document.addEventListener('DOMContentLoaded',fn); } }
    36  
    37  var variants = {
    38  	variant: '',
    39  	variants: [],
    40  	customvariantname: 'my-custom-variant',
    41  	isstylesheetloaded: true,
    42  
    43  	init: function( variants ){
    44  		this.variants = variants;
    45  		var variant = window.localStorage.getItem( window.relearn.absBaseUri+'/variant' ) || ( this.variants.length ? this.variants[0] : '' );
    46  		this.changeVariant( variant );
    47  		document.addEventListener( 'readystatechange', function(){
    48  			if( document.readyState == 'interactive' ){
    49  				this.markSelectedVariant();
    50  			}
    51  		}.bind( this ) );
    52  	},
    53  
    54  	getVariant: function(){
    55  		return this.variant;
    56  	},
    57  
    58  	setVariant: function( variant ){
    59  		this.variant = variant;
    60  		window.localStorage.setItem( window.relearn.absBaseUri+'/variant', variant );
    61  	},
    62  
    63  	isVariantLoaded: function(){
    64  		return window.theme && this.isstylesheetloaded;
    65  	},
    66  
    67  	markSelectedVariant: function(){
    68  		var variant = this.getVariant();
    69  		var select = document.querySelector( '#R-select-variant' );
    70  		if( !select ){
    71  			return;
    72  		}
    73  		this.addCustomVariantOption();
    74  		if( variant && select.value != variant ){
    75  			select.value = variant;
    76  		}
    77  		var interval_id = setInterval( function(){
    78  			if( this.isVariantLoaded() ){
    79  				clearInterval( interval_id );
    80  				updateTheme({ variant: variant });
    81  			}
    82  		}.bind( this ), 25 );
    83  		// remove selection, because if some uses an arrow navigation"
    84  		// by pressing the left or right cursor key, we will automatically
    85  		// select a different style
    86  		if( document.activeElement ){
    87  			document.activeElement.blur();
    88  		}
    89  	},
    90  
    91  	generateVariantPath: function( variant, old_path ){
    92  		var mod = window.relearn.themeVariantModifier.replace( '.', '\\.' );
    93  		var new_path = old_path.replace( new RegExp(`^(.*\/theme-).*?(${mod}\.css.*)$`), '$1' + variant + '$2' );
    94  		return new_path;
    95  	},
    96  
    97  	addCustomVariantOption: function(){
    98  		var variantbase = window.localStorage.getItem( window.relearn.absBaseUri+'/customvariantbase' );
    99  		if( this.variants.indexOf( variantbase ) < 0 ){
   100  			variantbase = '';
   101  		}
   102  		if( !window.localStorage.getItem( window.relearn.absBaseUri+'/customvariant' ) ){
   103  			variantbase = '';
   104  		}
   105  		if( !variantbase ){
   106  			return;
   107  		}
   108  		var select = document.querySelector( '#R-select-variant' );
   109  		if( !select ){
   110  			return;
   111  		}
   112  		var option = document.querySelector( '#' + this.customvariantname );
   113  		if( !option ){
   114  			option = document.createElement( 'option' );
   115  			option.id = this.customvariantname;
   116  			option.value = this.customvariantname;
   117  			option.text = this.customvariantname.replace( /-/g, ' ' ).replace(/\w\S*/g, function(w){ return w.replace(/^\w/g, function(c){ return c.toUpperCase(); }); });
   118  			select.appendChild( option );
   119  			document.querySelectorAll( '.footerVariantSwitch' ).forEach( function( e ){
   120  				e.classList.add( 'showVariantSwitch' );
   121  			});
   122  		}
   123  	},
   124  
   125  	removeCustomVariantOption: function(){
   126  		var option = document.querySelector( '#' + this.customvariantname );
   127  		if( option ){
   128  			option.remove();
   129  		}
   130  		if( this.variants.length <= 1 ){
   131  			document.querySelectorAll( '.footerVariantSwitch' ).forEach( function( e ){
   132  				e.classList.remove( 'showVariantSwitch' );
   133  			});
   134  		}
   135  	},
   136  
   137  	saveCustomVariant: function(){
   138  		if( this.getVariant() != this.customvariantname ){
   139  			window.localStorage.setItem( window.relearn.absBaseUri+'/customvariantbase', this.getVariant() );
   140  		}
   141  		window.localStorage.setItem( window.relearn.absBaseUri+'/customvariant', this.generateStylesheet() );
   142  		this.setVariant( this.customvariantname );
   143  		this.markSelectedVariant();
   144  	},
   145  
   146  	loadCustomVariant: function(){
   147  		var stylesheet = window.localStorage.getItem( window.relearn.absBaseUri+'/customvariant' );
   148  
   149  		// temp styles to document
   150  		var head = document.querySelector( 'head' );
   151  		var style = document.createElement( 'style' );
   152  		style.id = 'R-custom-variant-style';
   153  		style.appendChild( document.createTextNode( stylesheet ) );
   154  		head.appendChild( style );
   155  
   156  		var interval_id = setInterval( function(){
   157  			if( this.findLoadedStylesheet( 'R-variant-style' ) ){
   158  				clearInterval( interval_id );
   159  				// save the styles to the current variant stylesheet
   160  				this.variantvariables.forEach( function( e ){
   161  					this.changeColor( e.name, true );
   162  				}.bind( this ) );
   163  
   164  				// remove temp styles
   165  				style.remove();
   166  
   167  				this.saveCustomVariant();
   168  			}
   169  		}.bind( this ), 25 );
   170  	},
   171  
   172  	resetVariant: function(){
   173  		var variantbase = window.localStorage.getItem( window.relearn.absBaseUri+'/customvariantbase' );
   174  		if( variantbase && confirm( 'You have made changes to your custom variant. Are you sure you want to reset all changes?' ) ){
   175  			window.localStorage.removeItem( window.relearn.absBaseUri+'/customvariantbase' );
   176  			window.localStorage.removeItem( window.relearn.absBaseUri+'/customvariant' );
   177  			this.removeCustomVariantOption();
   178  			if( this.getVariant() == this.customvariantname ){
   179  				this.changeVariant( variantbase );
   180  			}
   181  		}
   182  	},
   183  
   184  	onLoadStylesheet: function(){
   185  		variants.isstylesheetloaded = true;
   186  	},
   187  
   188  	switchStylesheet: function( variant, without_check ){
   189  		var link = document.querySelector( '#R-variant-style' );
   190  		if( !link ){
   191  			return;
   192  		}
   193  		var old_path = link.getAttribute( 'href' );
   194  		var new_path = this.generateVariantPath( variant, old_path );
   195  		this.isstylesheetloaded = false;
   196  
   197  		// Chrome needs a new element to trigger the load callback again
   198  		var new_link = document.createElement( 'link' );
   199  		new_link.id = 'R-variant-style';
   200  		new_link.rel = 'stylesheet';
   201  		new_link.onload = this.onLoadStylesheet;
   202  		new_link.setAttribute( 'href', new_path );
   203  		link.parentNode.replaceChild( new_link, link );
   204  	},
   205  
   206  	changeVariant: function( variant ){
   207  		if( variant == this.customvariantname ){
   208  			var variantbase = window.localStorage.getItem( window.relearn.absBaseUri+'/customvariantbase' );
   209  			if( this.variants.indexOf( variantbase ) < 0 ){
   210  				variant = '';
   211  			}
   212  			if( !window.localStorage.getItem( window.relearn.absBaseUri+'/customvariant' ) ){
   213  				variant = '';
   214  			}
   215  			this.setVariant( variant );
   216  			if( !variant ){
   217  				return;
   218  			}
   219  			this.switchStylesheet( variantbase );
   220  			this.loadCustomVariant();
   221  		}
   222  		else{
   223  			if( this.variants.indexOf( variant ) < 0 ){
   224  				variant = this.variants.length ? this.variants[ 0 ] : '';
   225  			}
   226  			this.setVariant( variant );
   227  			if( !variant ){
   228  				return;
   229  			}
   230  			this.switchStylesheet( variant );
   231  			this.markSelectedVariant();
   232  		}
   233  	},
   234  
   235  	generator: function( vargenerator ){
   236  		var graphDefinition = this.generateGraph();
   237  		var graphs = document.querySelectorAll( vargenerator );
   238  		graphs.forEach( function( e ){ e.innerHTML = graphDefinition; });
   239  
   240  		var interval_id = setInterval( function(){
   241  			if( document.querySelectorAll( vargenerator + ' .mermaid > svg' ).length ){
   242  				clearInterval( interval_id );
   243  				this.styleGraph();
   244  			}
   245  		}.bind( this ), 25 );
   246  	},
   247  
   248  	download: function(data, mimetype, filename){
   249  		var blob = new Blob([data], { type: mimetype });
   250  		var url = window.URL.createObjectURL(blob);
   251  		var a = document.createElement('a');
   252  		a.setAttribute('href', url);
   253  		a.setAttribute('download', filename);
   254  		a.click();
   255  	},
   256  
   257  	getStylesheet: function(){
   258  		var style = this.generateStylesheet();
   259  		if( !style ){
   260  			alert( 'There is nothing to be generated as auto mode variants will be generated by Hugo' );
   261  			return;
   262  		}
   263  		this.download( style, 'text/css', 'theme-' + this.customvariantname + '.css' );
   264  	},
   265  
   266  	adjustCSSRules: function(selector, props, sheets) {
   267  	// get stylesheet(s)
   268  	if (!sheets) sheets = [].concat(Array.from(document.styleSheets));else if (sheets.sup) {
   269  	  // sheets is a string
   270  	  var absoluteURL = new URL(sheets, document.baseURI).href;
   271  	  sheets = [].concat(document.styleSheets).filter(function (i) {
   272  		return i.href == absoluteURL;
   273  	  });
   274  	} else sheets = [sheets]; // sheets is a stylesheet
   275  	// CSS (& HTML) reduce spaces in selector to one.
   276  
   277  	selector = selector.replace(/\s+/g, ' ');
   278  
   279  	var findRule = function findRule(s) {
   280  	  return [].concat(s.cssRules).reverse().find(function (i) {
   281  		return i.selectorText == selector;
   282  	  });
   283  	};
   284  
   285  	var rule = sheets.map(findRule).filter(function (i) {
   286  	  return i;
   287  	}).pop();
   288  	var propsArr = props.sup ? props.split(/\s*;\s*/).map(function (i) {
   289  	  return i.split(/\s*:\s*/);
   290  	}) // from string
   291  	: Object.entries(props); // from Object
   292  
   293  	if (rule) {
   294  	  for (var _iterator = _createForOfIteratorHelperLoose(propsArr), _step; !(_step = _iterator()).done;) {
   295  		var _rule$style;
   296  		var _step$value = _step.value,
   297  			prop = _step$value[0],
   298  			val = _step$value[1];
   299  		// rule.style[prop] = val; is against the spec, and does not support !important.
   300  		(_rule$style = rule.style).setProperty.apply(_rule$style, [prop].concat(val.split(/ *!(?=important)/)));
   301  	  }
   302  	} else {
   303  	  sheet = sheets.pop();
   304  	  if (!props.sup) props = propsArr.reduce(function (str, _ref) {
   305  		var k = _ref[0],
   306  			v = _ref[1];
   307  		return str + "; " + k + ": " + v;
   308  	  }, '');
   309  	  sheet.insertRule(selector + " { " + props + " }", sheet.cssRules.length);
   310  	}
   311    },
   312  
   313  	normalizeColor: function( c ){
   314  		if( !c || !c.trim ){
   315  			return c;
   316  		}
   317  		c = c.trim();
   318  		c = c.replace( /\s*\(\s*/g, "( " );
   319  		c = c.replace( /\s*\)\s*/g, " )" );
   320  		c = c.replace( /\s*,\s*/g, ", " );
   321  		c = c.replace( /0*\./g, "." );
   322  		c = c.replace( / +/g, " " );
   323  		return c;
   324  	},
   325  
   326  	getColorValue: function( c ){
   327  		return this.normalizeColor( getComputedStyle( document.documentElement ).getPropertyValue( '--INTERNAL-'+c ) );
   328  	},
   329  
   330  	getColorProperty: function( c, read_style ){
   331  		var e = this.findColor( c );
   332  		var p = this.normalizeColor( read_style.getPropertyValue( '--'+c ) ).replace( '--INTERNAL-', '--' );
   333  		return p;
   334  	},
   335  
   336  	findLoadedStylesheet: function( id ){
   337  		for( var n = 0; n < document.styleSheets.length; ++n ){
   338  			if( document.styleSheets[n].ownerNode.id == id ){
   339  				var s = document.styleSheets[n];
   340  				if( s.rules && s.rules.length ){
   341  					for( var m = 0; m < s.rules.length; ++m ){
   342  						if( s.rules[m].selectorText == ':root' ){
   343  							return s.rules[m].style;
   344  						}
   345  						if( s.rules[m].cssRules && s.rules[m].cssRules.length ){
   346  							for( var o = 0; o < s.rules[m].cssRules.length; ++o ){
   347  								if( s.rules[m].cssRules[o].selectorText == ':root' ){
   348  									return s.rules[m].cssRules[o].style;
   349  								}
   350  							}
   351  						}
   352  					}
   353  				}
   354  				break;
   355  			}
   356  		}
   357  		return null;
   358  	},
   359  
   360  	changeColor: function( c, without_prompt ){
   361  		var with_prompt = !(without_prompt || false);
   362  
   363  		var read_style = this.findLoadedStylesheet( 'R-custom-variant-style' );
   364  		var write_style = this.findLoadedStylesheet( 'R-variant-style' );
   365  		if( !read_style ){
   366  			read_style = write_style;
   367  		}
   368  		if( !read_style ){
   369  			if( with_prompt ){
   370  				alert( 'An auto mode variant can not be changed. Please select its light/dark variant directly to make changes' );
   371  			}
   372  			return;
   373  		}
   374  
   375  		var e = this.findColor( c );
   376  		var v = this.getColorProperty( c, read_style );
   377  		var n = '';
   378  		if( !with_prompt ){
   379  			n = v;
   380  		}
   381  		else{
   382  			var t = c + '\n\n' + e.tooltip + '\n';
   383  			if( e.fallback ){
   384  				t += '\nInherits value "' + this.getColorValue(e.fallback) + '" from ' + e.fallback + ' if not set\n';
   385  			}
   386  			else if( e.default ){
   387  				t += '\nDefaults to value "' + this.normalizeColor(e.default) + '" if not set\n';
   388  			}
   389  			n = prompt( t, v );
   390  			if( n === null ){
   391  				// user canceld operation
   392  				return;
   393  			}
   394  		}
   395  
   396  		if( n ){
   397  			// value set to specific value
   398  			n = this.normalizeColor( n ).replace( '--INTERNAL-', '--' ).replace( '--', '--INTERNAL-' );
   399  			if( !with_prompt || n != v ){
   400  				write_style.setProperty( '--'+c, n );
   401  			}
   402  		}
   403  		else{
   404  			// value emptied, so delete it
   405  			write_style.removeProperty( '--'+c );
   406  		}
   407  
   408  		if( with_prompt ){
   409  			this.saveCustomVariant();
   410  		}
   411  	},
   412  
   413  	findColor: function( name ){
   414  		var f = this.variantvariables.find( function( x ){
   415  			return x.name == name;
   416  		});
   417  		return f;
   418  	},
   419  
   420  	generateColorVariable: function( e, read_style ){
   421  		var v = '';
   422  		var gen = this.getColorProperty( e.name, read_style );
   423  		if( gen ){
   424  			v += '  --' + e.name + ': ' + gen + '; /* ' + e.tooltip + ' */\n';
   425  		}
   426  		return v;
   427  	},
   428  
   429  	generateStylesheet: function(){
   430  		var read_style = this.findLoadedStylesheet( 'R-custom-variant-style' );
   431  		var write_style = this.findLoadedStylesheet( 'R-variant-style' );
   432  		if( !read_style ){
   433  			read_style = write_style;
   434  		}
   435  		if( !read_style ){
   436  			return;
   437  		}
   438  
   439  		var style =
   440  			'/* ' + this.customvariantname + ' */\n' +
   441  			':root {\n' +
   442  			this.variantvariables.reduce( function( a, e ){ return a + this.generateColorVariable( e, read_style ); }.bind( this ), '' ) +
   443  			'}\n';
   444  		console.log( style );
   445  		return style;
   446  	},
   447  
   448  	styleGraphGroup: function( selector, colorvar ){
   449  		this.adjustCSSRules( '#R-body svg '+selector+' > rect', 'color: var(--INTERNAL-'+colorvar+'); fill: var(--INTERNAL-'+colorvar+'); stroke: #80808080;' );
   450  		this.adjustCSSRules( '#R-body svg '+selector+' > .label .nodeLabel', 'color: var(--INTERNAL-'+colorvar+'); fill: var(--INTERNAL-'+colorvar+'); stroke: #80808080;' );
   451  		this.adjustCSSRules( '#R-body svg '+selector+' > .cluster-label .nodeLabel', 'color: var(--INTERNAL-'+colorvar+'); fill: var(--INTERNAL-'+colorvar+'); stroke: #80808080;' );
   452  		this.adjustCSSRules( '#R-body svg '+selector+' .nodeLabel', 'filter: grayscale(1) invert(1) contrast(10000);' );
   453  	},
   454  
   455  	styleGraph: function(){
   456  		this.variantvariables.forEach( function( e ){
   457  			this.styleGraphGroup( '.'+e.name, e.name );
   458  		}.bind( this ) );
   459  		this.styleGraphGroup( '#maincontent', 'MAIN-BG-color' );
   460  		this.styleGraphGroup( '#mainheadings', 'MAIN-BG-color' );
   461  		this.styleGraphGroup( '#code', 'CODE-BLOCK-BG-color' );
   462  		this.styleGraphGroup( '#inlinecode', 'CODE-INLINE-BG-color' );
   463  		this.styleGraphGroup( '#blockcode', 'CODE-BLOCK-BG-color' );
   464  		this.styleGraphGroup( '#thirdparty', 'MAIN-BG-color' );
   465  		this.styleGraphGroup( '#coloredboxes', 'BOX-BG-color' );
   466  		this.styleGraphGroup( '#menu', 'MENU-SECTIONS-BG-color' );
   467  		this.styleGraphGroup( '#menuheader', 'MENU-HEADER-BG-color' );
   468  		this.styleGraphGroup( '#menusections', 'MENU-SECTIONS-ACTIVE-BG-color' );
   469  	},
   470  
   471  	generateGraphGroupedEdge: function( e ){
   472  		var edge = '';
   473  		if( e.fallback && e.group == this.findColor( e.fallback ).group ){
   474  			edge += e.fallback+':::'+e.fallback+' --> '+e.name+':::'+e.name;
   475  		}
   476  		else{
   477  			edge += e.name+':::'+e.name;
   478  		}
   479  		return edge;
   480  	},
   481  
   482  	generateGraphVarGroupedEdge: function( e ){
   483  		var edge = '';
   484  		if( e.fallback && e.group != this.findColor( e.fallback ).group ){
   485  			edge += '  ' + e.fallback+':::'+e.fallback+' --> '+e.name+':::'+e.name + '\n';
   486  		}
   487  		return edge;
   488  	},
   489  
   490  	generateGraph: function(){
   491  		var g_groups = {};
   492  		var g_handler = '';
   493  
   494  		this.variantvariables.forEach( function( e ){
   495  			var group = e.group || ' ';
   496  			g_groups[ group ] = ( g_groups[ group ] || [] ).concat( e );
   497  			g_handler += '  click '+e.name+' variants.changeColor\n';
   498  		});
   499  
   500  		var graph =
   501  			'flowchart LR\n' +
   502  			'  subgraph menu["menu"]\n' +
   503  			'    direction TB\n' +
   504  			'    subgraph menuheader["header"]\n' +
   505  			'      direction LR\n' +
   506  					g_groups[ 'header' ].reduce( function( a, e ){ return a + '      ' + this.generateGraphGroupedEdge( e ) + '\n'; }.bind( this ), '' ) +
   507  			'    end\n' +
   508  			'    subgraph menusections["sections"]\n' +
   509  			'      direction LR\n' +
   510  					g_groups[ 'sections' ].reduce( function( a, e ){ return a + '      ' + this.generateGraphGroupedEdge( e ) + '\n'; }.bind( this ), '' ) +
   511  			'    end\n' +
   512  			'  end\n' +
   513  			'  subgraph maincontent["content"]\n' +
   514  			'    direction TB\n' +
   515  					g_groups[ 'content' ].reduce( function( a, e ){ return a + '    ' + this.generateGraphGroupedEdge( e ) + '\n'; }.bind( this ), '' ) +
   516  			'    subgraph mainheadings["headings"]\n' +
   517  			'      direction LR\n' +
   518  					g_groups[ 'headings' ].reduce( function( a, e ){ return a + '      ' + this.generateGraphGroupedEdge( e ) + '\n'; }.bind( this ), '' ) +
   519  			'    end\n' +
   520  			'    subgraph code["code"]\n' +
   521  			'      direction TB\n' +
   522  					g_groups[ 'code' ].reduce( function( a, e ){ return a + '    ' + this.generateGraphGroupedEdge( e ) + '\n'; }.bind( this ), '' ) +
   523  			'      subgraph inlinecode["inline code"]\n' +
   524  			'        direction LR\n' +
   525  						g_groups[ 'inline code' ].reduce( function( a, e ){ return a + '      ' + this.generateGraphGroupedEdge( e ) + '\n'; }.bind( this ), '' ) +
   526  			'      end\n' +
   527  			'      subgraph blockcode["code blocks"]\n' +
   528  			'        direction LR\n' +
   529  						g_groups[ 'code blocks' ].reduce( function( a, e ){ return a + '      ' + this.generateGraphGroupedEdge( e ) + '\n'; }.bind( this ), '' ) +
   530  			'      end\n' +
   531  			'    end\n' +
   532  			'    subgraph thirdparty["3rd party"]\n' +
   533  			'      direction LR\n' +
   534  					g_groups[ '3rd party' ].reduce( function( a, e ){ return a + '      ' + this.generateGraphGroupedEdge( e ) + '\n'; }.bind( this ), '' ) +
   535  			'    end\n' +
   536  			'    subgraph coloredboxes["colored boxes"]\n' +
   537  			'      direction LR\n' +
   538  					g_groups[ 'colored boxes' ].reduce( function( a, e ){ return a + '      ' + this.generateGraphGroupedEdge( e ) + '\n'; }.bind( this ), '' ) +
   539  			'    end\n' +
   540  			'  end\n' +
   541  				this.variantvariables.reduce( function( a, e ){ return a + this.generateGraphVarGroupedEdge( e ); }.bind( this ), '' ) +
   542  			g_handler;
   543  
   544  		console.log( graph );
   545  		return graph;
   546  	},
   547  
   548  	variantvariables: [
   549  		{ name: 'PRIMARY-color',                         group: 'content',       fallback: 'MENU-HEADER-BG-color',        tooltip: 'brand primary color', },
   550  		{ name: 'SECONDARY-color',                       group: 'content',       fallback: 'MAIN-LINK-color',             tooltip: 'brand secondary color', },
   551  		{ name: 'ACCENT-color',                          group: 'content',        default: '#ffff00',                     tooltip: 'brand accent color, used for search highlights', },
   552  
   553  		{ name: 'MAIN-TOPBAR-BORDER-color',              group: 'content',        default: 'transparent',                 tooltip: 'border color between topbar and content', },
   554  		{ name: 'MAIN-LINK-color',                       group: 'content',       fallback: 'SECONDARY-color',             tooltip: 'link color of content', },
   555  		{ name: 'MAIN-LINK-HOVER-color',                 group: 'content',       fallback: 'MAIN-LINK-color',             tooltip: 'hoverd link color of content', },
   556  		{ name: 'MAIN-BG-color',                         group: 'content',        default: '#ffffff',                     tooltip: 'background color of content', },
   557  		{ name: 'TAG-BG-color',                          group: 'content',       fallback: 'PRIMARY-color',               tooltip: 'tag color', },
   558  
   559  		{ name: 'MAIN-TEXT-color',                       group: 'content',        default: '#101010',                     tooltip: 'text color of content and h1 titles', },
   560  
   561  		{ name: 'MAIN-TITLES-TEXT-color',                group: 'headings',      fallback: 'MAIN-TEXT-color',             tooltip: 'text color of h2-h6 titles and transparent box titles', },
   562  		{ name: 'MAIN-TITLES-H1-color',                  group: 'headings',      fallback: 'MAIN-TEXT-color',             tooltip: 'text color of h1 titles', },
   563  		{ name: 'MAIN-TITLES-H2-color',                  group: 'headings',      fallback: 'MAIN-TITLES-TEXT-color',      tooltip: 'text color of h2-h6 titles', },
   564  		{ name: 'MAIN-TITLES-H3-color',                  group: 'headings',      fallback: 'MAIN-TITLES-H2-color',        tooltip: 'text color of h3-h6 titles', },
   565  		{ name: 'MAIN-TITLES-H4-color',                  group: 'headings',      fallback: 'MAIN-TITLES-H3-color',        tooltip: 'text color of h4-h6 titles', },
   566  		{ name: 'MAIN-TITLES-H5-color',                  group: 'headings',      fallback: 'MAIN-TITLES-H4-color',        tooltip: 'text color of h5-h6 titles', },
   567  		{ name: 'MAIN-TITLES-H6-color',                  group: 'headings',      fallback: 'MAIN-TITLES-H5-color',        tooltip: 'text color of h6 titles', },
   568  
   569  		{ name: 'MAIN-font',                             group: 'content',        default: '"Work Sans", "Helvetica", "Tahoma", "Geneva", "Arial", sans-serif', tooltip: 'text font of content and h1 titles', },
   570  
   571  		{ name: 'MAIN-TITLES-TEXT-font',                 group: 'headings',      fallback: 'MAIN-font',                   tooltip: 'text font of h2-h6 titles and transparent box titles', },
   572  		{ name: 'MAIN-TITLES-H1-font',                   group: 'headings',      fallback: 'MAIN-font',                   tooltip: 'text font of h1 titles', },
   573  		{ name: 'MAIN-TITLES-H2-font',                   group: 'headings',      fallback: 'MAIN-TITLES-TEXT-font',       tooltip: 'text font of h2-h6 titles', },
   574  		{ name: 'MAIN-TITLES-H3-font',                   group: 'headings',      fallback: 'MAIN-TITLES-H2-font',         tooltip: 'text font of h3-h6 titles', },
   575  		{ name: 'MAIN-TITLES-H4-font',                   group: 'headings',      fallback: 'MAIN-TITLES-H3-font',         tooltip: 'text font of h4-h6 titles', },
   576  		{ name: 'MAIN-TITLES-H5-font',                   group: 'headings',      fallback: 'MAIN-TITLES-H4-font',         tooltip: 'text font of h5-h6 titles', },
   577  		{ name: 'MAIN-TITLES-H6-font',                   group: 'headings',      fallback: 'MAIN-TITLES-H5-font',         tooltip: 'text font of h6 titles', },
   578  
   579  		{ name: 'CODE-theme',                            group: 'code',           default: 'relearn-light',               tooltip: 'name of the chroma stylesheet file', },
   580  		{ name: 'CODE-font',                             group: 'code',           default: '"Consolas", menlo, monospace', tooltip: 'text font of code', },
   581  		{ name: 'CODE-BLOCK-color',                      group: 'code blocks',    default: '#000000',                     tooltip: 'fallback text color of block code; should be adjusted to your selected chroma style', },
   582  		{ name: 'CODE-BLOCK-BG-color',                   group: 'code blocks',    default: '#f8f8f8',                     tooltip: 'fallback background color of block code; should be adjusted to your selected chroma style', },
   583  		{ name: 'CODE-BLOCK-BORDER-color',               group: 'code blocks',   fallback: 'CODE-BLOCK-BG-color',         tooltip: 'border color of block code', },
   584  		{ name: 'CODE-INLINE-color',                     group: 'inline code',    default: '#5e5e5e',                     tooltip: 'text color of inline code', },
   585  		{ name: 'CODE-INLINE-BG-color',                  group: 'inline code',    default: '#fffae9',                     tooltip: 'background color of inline code', },
   586  		{ name: 'CODE-INLINE-BORDER-color',              group: 'inline code',    default: '#fbf0cb',                     tooltip: 'border color of inline code', },
   587  
   588  		{ name: 'BROWSER-theme',                         group: '3rd party',      default: 'light',                       tooltip: 'name of the theme for browser scrollbars of the main section', },
   589  		{ name: 'MERMAID-theme',                         group: '3rd party',      default: 'default',                     tooltip: 'name of the default Mermaid theme for this variant, can be overridden in hugo.toml', },
   590  		{ name: 'OPENAPI-theme',                         group: '3rd party',      default: 'light',                       tooltip: 'name of the default OpenAPI theme for this variant, can be overridden in hugo.toml', },
   591  		{ name: 'OPENAPI-CODE-theme',                    group: '3rd party',      default: 'obsidian',                    tooltip: 'name of the default OpenAPI code theme for this variant, can be overridden in hugo.toml', },
   592  
   593  		{ name: 'MENU-BORDER-color',                     group: 'header',         default: 'transparent',                 tooltip: 'border color between menu and content', },
   594  		{ name: 'MENU-TOPBAR-BORDER-color',              group: 'header',        fallback: 'MENU-HEADER-BG-color',        tooltip: 'border color of vertical line between menu and topbar', },
   595  		{ name: 'MENU-TOPBAR-SEPARATOR-color',           group: 'header',         default: 'transparent',                 tooltip: 'separator color of vertical line between menu and topbar', },
   596  		{ name: 'MENU-HEADER-BG-color',                  group: 'header',        fallback: 'PRIMARY-color',               tooltip: 'background color of menu header', },
   597  		{ name: 'MENU-HEADER-BORDER-color',              group: 'header',        fallback: 'MENU-HEADER-BG-color',        tooltip: 'border color between menu header and menu', },
   598  		{ name: 'MENU-HEADER-SEPARATOR-color',           group: 'header',        fallback: 'MENU-HEADER-BORDER-color',    tooltip: 'separator color between menu header and menu', },
   599  		{ name: 'MENU-HOME-LINK-color',                  group: 'header',         default: '#323232',                     tooltip: 'home button color if configured', },
   600  		{ name: 'MENU-HOME-LINK-HOVER-color',            group: 'header',         default: '#808080',                     tooltip: 'hoverd home button color if configured', },
   601  		{ name: 'MENU-SEARCH-color',                     group: 'header',         default: '#e0e0e0',                     tooltip: 'text and icon color of search box', },
   602  		{ name: 'MENU-SEARCH-BG-color',                  group: 'header',         default: '#323232',                     tooltip: 'background color of search box', },
   603  		{ name: 'MENU-SEARCH-BORDER-color',              group: 'header',        fallback: 'MENU-SEARCH-BG-color',        tooltip: 'border color of search box', },
   604  
   605  		{ name: 'MENU-SECTIONS-BG-color',                group: 'sections',       default: '#282828',                     tooltip: 'background of the menu; this is NOT just a color value but can be a complete CSS background definition including gradients, etc.', },
   606  		{ name: 'MENU-SECTIONS-ACTIVE-BG-color',         group: 'sections',       default: 'rgba( 0, 0, 0, .166 )',       tooltip: 'background color of the active menu section', },
   607  		{ name: 'MENU-SECTIONS-LINK-color',              group: 'sections',       default: '#bababa',                     tooltip: 'link color of menu topics', },
   608  		{ name: 'MENU-SECTIONS-LINK-HOVER-color',        group: 'sections',      fallback: 'MENU-SECTIONS-LINK-color',    tooltip: 'hoverd link color of menu topics', },
   609  		{ name: 'MENU-SECTION-ACTIVE-CATEGORY-color',    group: 'sections',       default: '#444444',                     tooltip: 'text color of the displayed menu topic', },
   610  		{ name: 'MENU-SECTION-ACTIVE-CATEGORY-BG-color', group: 'sections',      fallback: 'MAIN-BG-color',               tooltip: 'background color of the displayed menu topic', },
   611  		{ name: 'MENU-SECTION-ACTIVE-CATEGORY-BORDER-color', group: 'sections',   default: 'transparent',                 tooltip: 'border color between the displayed menu topic and the content', },
   612  		{ name: 'MENU-SECTION-SEPARATOR-color',          group: 'sections',       default: '#606060',                     tooltip: 'separator color between menu sections and menu footer', },
   613  		{ name: 'MENU-VISITED-color',                    group: 'sections',      fallback: 'SECONDARY-color',             tooltip: 'icon color of visited menu topics if configured', },
   614  
   615  		{ name: 'BOX-CAPTION-color',                     group: 'colored boxes',  default: 'rgba( 255, 255, 255, 1 )',    tooltip: 'text color of colored box titles', },
   616  		{ name: 'BOX-BG-color',                          group: 'colored boxes',  default: 'rgba( 255, 255, 255, .833 )', tooltip: 'background color of colored boxes', },
   617  		{ name: 'BOX-TEXT-color',                        group: 'colored boxes', fallback: 'MAIN-TEXT-color',             tooltip: 'text color of colored box content', },
   618  
   619  		{ name: 'BOX-BLUE-color',                        group: 'colored boxes',  default: 'rgba( 48, 117, 229, 1 )',     tooltip: 'background color of blue boxes', },
   620  		{ name: 'BOX-INFO-color',                        group: 'colored boxes', fallback: 'BOX-BLUE-color',              tooltip: 'background color of info boxes', },
   621  		{ name: 'BOX-BLUE-TEXT-color',                   group: 'colored boxes', fallback: 'BOX-TEXT-color',              tooltip: 'text color of blue boxes', },
   622  		{ name: 'BOX-INFO-TEXT-color',                   group: 'colored boxes', fallback: 'BOX-BLUE-TEXT-color',         tooltip: 'text color of info boxes', },
   623  
   624  		{ name: 'BOX-GREEN-color',                       group: 'colored boxes',  default: 'rgba( 42, 178, 24, 1 )',      tooltip: 'background color of green boxes', },
   625  		{ name: 'BOX-TIP-color',                         group: 'colored boxes', fallback: 'BOX-GREEN-color',             tooltip: 'background color of tip boxes', },
   626  		{ name: 'BOX-GREEN-TEXT-color',                  group: 'colored boxes', fallback: 'BOX-TEXT-color',              tooltip: 'text color of green boxes', },
   627  		{ name: 'BOX-TIP-TEXT-color',                    group: 'colored boxes', fallback: 'BOX-GREEN-TEXT-color',        tooltip: 'text color of tip boxes', },
   628  
   629  		{ name: 'BOX-GREY-color',                        group: 'colored boxes',  default: 'rgba( 128, 128, 128, 1 )',    tooltip: 'background color of grey boxes', },
   630  		{ name: 'BOX-NEUTRAL-color',                     group: 'colored boxes', fallback: 'BOX-GREY-color',              tooltip: 'background color of neutral boxes', },
   631  		{ name: 'BOX-GREY-TEXT-color',                   group: 'colored boxes', fallback: 'BOX-TEXT-color',              tooltip: 'text color of grey boxes', },
   632  		{ name: 'BOX-NEUTRAL-TEXT-color',                group: 'colored boxes', fallback: 'BOX-GREY-TEXT-color',         tooltip: 'text color of neutral boxes', },
   633  
   634  		{ name: 'BOX-ORANGE-color',                      group: 'colored boxes',  default: 'rgba( 237, 153, 9, 1 )',      tooltip: 'background color of orange boxes', },
   635  		{ name: 'BOX-NOTE-color',                        group: 'colored boxes', fallback: 'BOX-ORANGE-color',            tooltip: 'background color of note boxes', },
   636  		{ name: 'BOX-ORANGE-TEXT-color',                 group: 'colored boxes', fallback: 'BOX-TEXT-color',              tooltip: 'text color of orange boxes', },
   637  		{ name: 'BOX-NOTE-TEXT-color',                   group: 'colored boxes', fallback: 'BOX-ORANGE-TEXT-color',       tooltip: 'text color of note boxes', },
   638  
   639  		{ name: 'BOX-RED-color',                         group: 'colored boxes',  default: 'rgba( 224, 62, 62, 1 )',      tooltip: 'background color of red boxes', },
   640  		{ name: 'BOX-WARNING-color',                     group: 'colored boxes', fallback: 'BOX-RED-color',               tooltip: 'background color of warning boxes', },
   641  		{ name: 'BOX-RED-TEXT-color',                    group: 'colored boxes', fallback: 'BOX-TEXT-color',              tooltip: 'text color of red boxes', },
   642  		{ name: 'BOX-WARNING-TEXT-color',                group: 'colored boxes', fallback: 'BOX-RED-TEXT-color',          tooltip: 'text color of warning boxes', },
   643  	],
   644  };