diff --git a/site/.gitignore b/site/.gitignore new file mode 100644 index 0000000..f87f0e6 --- /dev/null +++ b/site/.gitignore @@ -0,0 +1,28 @@ + +# ── Secrets ────────────────────────────────────────────────────────────────── +# outreach is mirrored to a PUBLIC github remote (ADR-062 forge rule), so a leak +# here is a publication, not a mistake. Both files are real keys, not templates. +.env +.k + +# ── Generated: content indexes ─────────────────────────────────────────────── +# site/r is written by content_processor (canonical serve tree); site/public/r is +# the deploy mirror. Both are reproducible from site/content via `just content`. +site/r/ +site/public/r/ + +# ── Generated: deploy packs ────────────────────────────────────────────────── +# 228 MB of dated tarballs. They are the only forensic record of what shipped — +# valuable, but they are artifacts, not source. Keep them local. +provisioning/.content-packs/ + +# ── Generated: runtime + build outputs ─────────────────────────────────────── +# htmx-templates/ and htmx-assets/ are ASSEMBLED (`just templates` rm -rf's them). +# They stay tracked until templates-overlay/ is the declared source for every +# hand-authored template and `just templates-check` proves regeneration is a +# no-op — ignoring a generated tree that still holds unique work loses it. +cache/ +data/ +logs_*.out +.wrks/ +works-pv/ diff --git a/site/.just/content.just b/site/.just/content.just new file mode 120000 index 0000000..b2c7770 --- /dev/null +++ b/site/.just/content.just @@ -0,0 +1 @@ +../../../../website-htmx-rustelo/code/justfiles/content.just \ No newline at end of file diff --git a/site/htmx-assets/ext/head-support.js b/site/htmx-assets/ext/head-support.js new file mode 100644 index 0000000..6ed3265 --- /dev/null +++ b/site/htmx-assets/ext/head-support.js @@ -0,0 +1,147 @@ +//========================================================== +// head-support.js +// +// An extension to htmx 1.0 to add head tag merging. +//========================================================== +(function(){ + + var api = null; + + function log() { + //console.log(arguments); + } + + function mergeHead(newContent, defaultMergeStrategy) { + + if (newContent && newContent.indexOf(' -1) { + const htmlDoc = document.createElement("html"); + // remove svgs to avoid conflicts + var contentWithSvgsRemoved = newContent.replace(/]*>|>)([\s\S]*?)<\/svg>/gim, ''); + // extract head tag + var headTag = contentWithSvgsRemoved.match(/(]*>|>)([\s\S]*?)<\/head>)/im); + + // if the head tag exists... + if (headTag) { + + var added = [] + var removed = [] + var preserved = [] + var nodesToAppend = [] + + htmlDoc.innerHTML = headTag; + var newHeadTag = htmlDoc.querySelector("head"); + var currentHead = document.head; + + if (newHeadTag == null) { + return; + } else { + // put all new head elements into a Map, by their outerHTML + var srcToNewHeadNodes = new Map(); + for (const newHeadChild of newHeadTag.children) { + srcToNewHeadNodes.set(newHeadChild.outerHTML, newHeadChild); + } + } + + + + // determine merge strategy + var mergeStrategy = api.getAttributeValue(newHeadTag, "hx-head") || defaultMergeStrategy; + + // get the current head + for (const currentHeadElt of currentHead.children) { + + // If the current head element is in the map + var inNewContent = srcToNewHeadNodes.has(currentHeadElt.outerHTML); + var isReAppended = currentHeadElt.getAttribute("hx-head") === "re-eval"; + var isPreserved = api.getAttributeValue(currentHeadElt, "hx-preserve") === "true"; + if (inNewContent || isPreserved) { + if (isReAppended) { + // remove the current version and let the new version replace it and re-execute + removed.push(currentHeadElt); + } else { + // this element already exists and should not be re-appended, so remove it from + // the new content map, preserving it in the DOM + srcToNewHeadNodes.delete(currentHeadElt.outerHTML); + preserved.push(currentHeadElt); + } + } else { + if (mergeStrategy === "append") { + // we are appending and this existing element is not new content + // so if and only if it is marked for re-append do we do anything + if (isReAppended) { + removed.push(currentHeadElt); + nodesToAppend.push(currentHeadElt); + } + } else { + // if this is a merge, we remove this content since it is not in the new head + if (api.triggerEvent(document.body, "htmx:removingHeadElement", {headElement: currentHeadElt}) !== false) { + removed.push(currentHeadElt); + } + } + } + } + + // Push the tremaining new head elements in the Map into the + // nodes to append to the head tag + nodesToAppend.push(...srcToNewHeadNodes.values()); + log("to append: ", nodesToAppend); + + for (const newNode of nodesToAppend) { + log("adding: ", newNode); + var newElt = document.createRange().createContextualFragment(newNode.outerHTML); + log(newElt); + if (api.triggerEvent(document.body, "htmx:addingHeadElement", {headElement: newElt}) !== false) { + currentHead.appendChild(newElt); + added.push(newElt); + } + } + + // remove all removed elements, after we have appended the new elements to avoid + // additional network requests for things like style sheets + for (const removedElement of removed) { + if (api.triggerEvent(document.body, "htmx:removingHeadElement", {headElement: removedElement}) !== false) { + currentHead.removeChild(removedElement); + } + } + + api.triggerEvent(document.body, "htmx:afterHeadMerge", {added: added, kept: preserved, removed: removed}); + } + } + } + + htmx.defineExtension("head-support", { + init: function(apiRef) { + // store a reference to the internal API. + api = apiRef; + + htmx.on('htmx:afterSwap', function(evt){ + // PATCH: htmx:afterSwap also fires on history restore from + // popstate, where evt.detail.xhr is undefined. The original + // line would TypeError and block subsequent navigations. + // History restores have their own handler below + // (htmx:historyRestore), so we just skip here. + if (!evt.detail || !evt.detail.xhr) return; + var serverResponse = evt.detail.xhr.response; + if (api.triggerEvent(document.body, "htmx:beforeHeadMerge", evt.detail)) { + mergeHead(serverResponse, evt.detail.boosted ? "merge" : "append"); + } + }) + + htmx.on('htmx:historyRestore', function(evt){ + if (api.triggerEvent(document.body, "htmx:beforeHeadMerge", evt.detail)) { + if (evt.detail.cacheMiss) { + mergeHead(evt.detail.serverResponse, "merge"); + } else { + mergeHead(evt.detail.item.head, "merge"); + } + } + }) + + htmx.on('htmx:historyItemCreated', function(evt){ + var historyItem = evt.detail.item; + historyItem.head = document.head.outerHTML; + }) + } + }); + +})() \ No newline at end of file diff --git a/site/htmx-assets/ext/idiomorph.js b/site/htmx-assets/ext/idiomorph.js new file mode 100644 index 0000000..c21426d --- /dev/null +++ b/site/htmx-assets/ext/idiomorph.js @@ -0,0 +1 @@ +var Idiomorph=function(){"use strict";let o=new Set;let n={morphStyle:"outerHTML",callbacks:{beforeNodeAdded:t,afterNodeAdded:t,beforeNodeMorphed:t,afterNodeMorphed:t,beforeNodeRemoved:t,afterNodeRemoved:t,beforeAttributeUpdated:t},head:{style:"merge",shouldPreserve:function(e){return e.getAttribute("im-preserve")==="true"},shouldReAppend:function(e){return e.getAttribute("im-re-append")==="true"},shouldRemove:t,afterHeadMorphed:t}};function e(e,t,n={}){if(e instanceof Document){e=e.documentElement}if(typeof t==="string"){t=y(t)}let l=M(t);let r=m(e,l,n);return a(e,l,r)}function a(r,i,o){if(o.head.block){let t=r.querySelector("head");let n=i.querySelector("head");if(t&&n){let e=c(n,t,o);Promise.all(e).then(function(){a(r,i,Object.assign(o,{head:{block:false,ignore:true}}))});return}}if(o.morphStyle==="innerHTML"){l(i,r,o);return r.children}else if(o.morphStyle==="outerHTML"||o.morphStyle==null){let e=N(i,r,o);let t=e?.previousSibling;let n=e?.nextSibling;let l=d(r,e,o);if(e){return k(t,l,n)}else{return[]}}else{throw"Do not understand how to morph style "+o.morphStyle}}function u(e,t){return t.ignoreActiveValue&&e===document.activeElement}function d(e,t,n){if(n.ignoreActive&&e===document.activeElement){}else if(t==null){if(n.callbacks.beforeNodeRemoved(e)===false)return e;e.remove();n.callbacks.afterNodeRemoved(e);return null}else if(!g(e,t)){if(n.callbacks.beforeNodeRemoved(e)===false)return e;if(n.callbacks.beforeNodeAdded(t)===false)return e;e.parentElement.replaceChild(t,e);n.callbacks.afterNodeAdded(t);n.callbacks.afterNodeRemoved(e);return t}else{if(n.callbacks.beforeNodeMorphed(e,t)===false)return e;if(e instanceof HTMLHeadElement&&n.head.ignore){}else if(e instanceof HTMLHeadElement&&n.head.style!=="morph"){c(t,e,n)}else{r(t,e,n);if(!u(e,n)){l(t,e,n)}}n.callbacks.afterNodeMorphed(e,t);return e}}function l(n,l,r){let i=n.firstChild;let o=l.firstChild;let a;while(i){a=i;i=a.nextSibling;if(o==null){if(r.callbacks.beforeNodeAdded(a)===false)return;l.appendChild(a);r.callbacks.afterNodeAdded(a);x(r,a);continue}if(b(a,o,r)){d(o,a,r);o=o.nextSibling;x(r,a);continue}let e=S(n,l,a,o,r);if(e){o=v(o,e,r);d(e,a,r);x(r,a);continue}let t=A(n,l,a,o,r);if(t){o=v(o,t,r);d(t,a,r);x(r,a);continue}if(r.callbacks.beforeNodeAdded(a)===false)return;l.insertBefore(a,o);r.callbacks.afterNodeAdded(a);x(r,a)}while(o!==null){let e=o;o=o.nextSibling;w(e,r)}}function f(e,t,n,l){if(e==="value"&&l.ignoreActiveValue&&t===document.activeElement){return true}return l.callbacks.beforeAttributeUpdated(e,t,n)===false}function r(t,n,l){let e=t.nodeType;if(e===1){const r=t.attributes;const i=n.attributes;for(const o of r){if(f(o.name,n,"update",l)){continue}if(n.getAttribute(o.name)!==o.value){n.setAttribute(o.name,o.value)}}for(let e=i.length-1;0<=e;e--){const a=i[e];if(f(a.name,n,"remove",l)){continue}if(!t.hasAttribute(a.name)){n.removeAttribute(a.name)}}}if(e===8||e===3){if(n.nodeValue!==t.nodeValue){n.nodeValue=t.nodeValue}}if(!u(n,l)){s(t,n,l)}}function i(t,n,l,r){if(t[l]!==n[l]){let e=f(l,n,"update",r);if(!e){n[l]=t[l]}if(t[l]){if(!e){n.setAttribute(l,t[l])}}else{if(!f(l,n,"remove",r)){n.removeAttribute(l)}}}}function s(n,l,r){if(n instanceof HTMLInputElement&&l instanceof HTMLInputElement&&n.type!=="file"){let e=n.value;let t=l.value;i(n,l,"checked",r);i(n,l,"disabled",r);if(!n.hasAttribute("value")){if(!f("value",l,"remove",r)){l.value="";l.removeAttribute("value")}}else if(e!==t){if(!f("value",l,"update",r)){l.setAttribute("value",e);l.value=e}}}else if(n instanceof HTMLOptionElement){i(n,l,"selected",r)}else if(n instanceof HTMLTextAreaElement&&l instanceof HTMLTextAreaElement){let e=n.value;let t=l.value;if(f("value",l,"update",r)){return}if(e!==t){l.value=e}if(l.firstChild&&l.firstChild.nodeValue!==e){l.firstChild.nodeValue=e}}}function c(e,t,l){let r=[];let i=[];let o=[];let a=[];let u=l.head.style;let d=new Map;for(const n of e.children){d.set(n.outerHTML,n)}for(const s of t.children){let e=d.has(s.outerHTML);let t=l.head.shouldReAppend(s);let n=l.head.shouldPreserve(s);if(e||n){if(t){i.push(s)}else{d.delete(s.outerHTML);o.push(s)}}else{if(u==="append"){if(t){i.push(s);a.push(s)}}else{if(l.head.shouldRemove(s)!==false){i.push(s)}}}}a.push(...d.values());p("to append: ",a);let f=[];for(const c of a){p("adding: ",c);let n=document.createRange().createContextualFragment(c.outerHTML).firstChild;p(n);if(l.callbacks.beforeNodeAdded(n)!==false){if(n.href||n.src){let t=null;let e=new Promise(function(e){t=e});n.addEventListener("load",function(){t()});f.push(e)}t.appendChild(n);l.callbacks.afterNodeAdded(n);r.push(n)}}for(const h of i){if(l.callbacks.beforeNodeRemoved(h)!==false){t.removeChild(h);l.callbacks.afterNodeRemoved(h)}}l.head.afterHeadMorphed(t,{added:r,kept:o,removed:i});return f}function p(){}function t(){}function h(e){let t={};Object.assign(t,n);Object.assign(t,e);t.callbacks={};Object.assign(t.callbacks,n.callbacks);Object.assign(t.callbacks,e.callbacks);t.head={};Object.assign(t.head,n.head);Object.assign(t.head,e.head);return t}function m(e,t,n){n=h(n);return{target:e,newContent:t,config:n,morphStyle:n.morphStyle,ignoreActive:n.ignoreActive,ignoreActiveValue:n.ignoreActiveValue,idMap:C(e,t),deadIds:new Set,callbacks:n.callbacks,head:n.head}}function b(e,t,n){if(e==null||t==null){return false}if(e.nodeType===t.nodeType&&e.tagName===t.tagName){if(e.id!==""&&e.id===t.id){return true}else{return L(n,e,t)>0}}return false}function g(e,t){if(e==null||t==null){return false}return e.nodeType===t.nodeType&&e.tagName===t.tagName}function v(t,e,n){while(t!==e){let e=t;t=t.nextSibling;w(e,n)}x(n,e);return e.nextSibling}function S(n,e,l,r,i){let o=L(i,l,e);let t=null;if(o>0){let e=r;let t=0;while(e!=null){if(b(l,e,i)){return e}t+=L(i,e,n);if(t>o){return null}e=e.nextSibling}}return t}function A(e,t,n,l,r){let i=l;let o=n.nextSibling;let a=0;while(i!=null){if(L(r,i,e)>0){return null}if(g(n,i)){return i}if(g(o,i)){a++;o=o.nextSibling;if(a>=2){return null}}i=i.nextSibling}return i}function y(n){let l=new DOMParser;let e=n.replace(/]*>|>)([\s\S]*?)<\/svg>/gim,"");if(e.match(/<\/html>/)||e.match(/<\/head>/)||e.match(/<\/body>/)){let t=l.parseFromString(n,"text/html");if(e.match(/<\/html>/)){t.generatedByIdiomorph=true;return t}else{let e=t.firstChild;if(e){e.generatedByIdiomorph=true;return e}else{return null}}}else{let e=l.parseFromString("","text/html");let t=e.body.querySelector("template").content;t.generatedByIdiomorph=true;return t}}function M(e){if(e==null){const t=document.createElement("div");return t}else if(e.generatedByIdiomorph){return e}else if(e instanceof Node){const t=document.createElement("div");t.append(e);return t}else{const t=document.createElement("div");for(const n of[...e]){t.append(n)}return t}}function k(e,t,n){let l=[];let r=[];while(e!=null){l.push(e);e=e.previousSibling}while(l.length>0){let e=l.pop();r.push(e);t.parentElement.insertBefore(e,t)}r.push(t);while(n!=null){l.push(n);r.push(n);n=n.nextSibling}while(l.length>0){t.parentElement.insertBefore(l.pop(),t.nextSibling)}return r}function N(e,t,n){let l;l=e.firstChild;let r=l;let i=0;while(l){let e=T(l,t,n);if(e>i){r=l;i=e}l=l.nextSibling}return r}function T(e,t,n){if(g(e,t)){return.5+L(n,e,t)}return 0}function w(e,t){x(t,e);if(t.callbacks.beforeNodeRemoved(e)===false)return;e.remove();t.callbacks.afterNodeRemoved(e)}function H(e,t){return!e.deadIds.has(t)}function E(e,t,n){let l=e.idMap.get(n)||o;return l.has(t)}function x(e,t){let n=e.idMap.get(t)||o;for(const l of n){e.deadIds.add(l)}}function L(e,t,n){let l=e.idMap.get(t)||o;let r=0;for(const i of l){if(H(e,i)&&E(e,i,n)){++r}}return r}function R(e,n){let l=e.parentElement;let t=e.querySelectorAll("[id]");for(const r of t){let t=r;while(t!==l&&t!=null){let e=n.get(t);if(e==null){e=new Set;n.set(t,e)}e.add(r.id);t=t.parentElement}}}function C(e,t){let n=new Map;R(e,n);R(t,n);return n}return{morph:e,defaults:n}}();(function(){function r(e){if(e==="morph"||e==="morph:outerHTML"){return{morphStyle:"outerHTML"}}else if(e==="morph:innerHTML"){return{morphStyle:"innerHTML"}}else if(e.startsWith("morph:")){return Function("return ("+e.slice(6)+")")()}}htmx.defineExtension("morph",{isInlineSwap:function(e){let t=r(e);return t.swapStyle==="outerHTML"||t.swapStyle==null},handleSwap:function(e,t,n){let l=r(e);if(l){return Idiomorph.morph(t,n.children,l)}}})})(); \ No newline at end of file diff --git a/site/htmx-assets/ext/loading-states.js b/site/htmx-assets/ext/loading-states.js new file mode 100644 index 0000000..e42bb52 --- /dev/null +++ b/site/htmx-assets/ext/loading-states.js @@ -0,0 +1,184 @@ +;(function() { + const loadingStatesUndoQueue = [] + + function loadingStateContainer(target) { + return htmx.closest(target, '[data-loading-states]') || document.body + } + + function mayProcessUndoCallback(target, callback) { + if (document.body.contains(target)) { + callback() + } + } + + function mayProcessLoadingStateByPath(elt, requestPath) { + const pathElt = htmx.closest(elt, '[data-loading-path]') + if (!pathElt) { + return true + } + + return pathElt.getAttribute('data-loading-path') === requestPath + } + + function queueLoadingState(sourceElt, targetElt, doCallback, undoCallback) { + const delayElt = htmx.closest(sourceElt, '[data-loading-delay]') + if (delayElt) { + const delayInMilliseconds = + delayElt.getAttribute('data-loading-delay') || 200 + const timeout = setTimeout(function() { + doCallback() + + loadingStatesUndoQueue.push(function() { + mayProcessUndoCallback(targetElt, undoCallback) + }) + }, delayInMilliseconds) + + loadingStatesUndoQueue.push(function() { + mayProcessUndoCallback(targetElt, function() { clearTimeout(timeout) }) + }) + } else { + doCallback() + loadingStatesUndoQueue.push(function() { + mayProcessUndoCallback(targetElt, undoCallback) + }) + } + } + + function getLoadingStateElts(loadingScope, type, path) { + return Array.from(htmx.findAll(loadingScope, '[' + type + ']')).filter( + function(elt) { return mayProcessLoadingStateByPath(elt, path) } + ) + } + + function getLoadingTarget(elt) { + if (elt.getAttribute('data-loading-target')) { + return Array.from( + htmx.findAll(elt.getAttribute('data-loading-target')) + ) + } + return [elt] + } + + htmx.defineExtension('loading-states', { + onEvent: function(name, evt) { + if (name === 'htmx:beforeRequest') { + const container = loadingStateContainer(evt.target) + + const loadingStateTypes = [ + 'data-loading', + 'data-loading-class', + 'data-loading-class-remove', + 'data-loading-disable', + 'data-loading-aria-busy' + ] + + const loadingStateEltsByType = {} + + loadingStateTypes.forEach(function(type) { + loadingStateEltsByType[type] = getLoadingStateElts( + container, + type, + evt.detail.pathInfo.requestPath + ) + }) + + loadingStateEltsByType['data-loading'].forEach(function(sourceElt) { + getLoadingTarget(sourceElt).forEach(function(targetElt) { + queueLoadingState( + sourceElt, + targetElt, + function() { + targetElt.style.display = + sourceElt.getAttribute('data-loading') || + 'inline-block' + }, + function() { targetElt.style.display = 'none' } + ) + }) + }) + + loadingStateEltsByType['data-loading-class'].forEach( + function(sourceElt) { + const classNames = sourceElt + .getAttribute('data-loading-class') + .split(' ') + + getLoadingTarget(sourceElt).forEach(function(targetElt) { + queueLoadingState( + sourceElt, + targetElt, + function() { + classNames.forEach(function(className) { + targetElt.classList.add(className) + }) + }, + function() { + classNames.forEach(function(className) { + targetElt.classList.remove(className) + }) + } + ) + }) + } + ) + + loadingStateEltsByType['data-loading-class-remove'].forEach( + function(sourceElt) { + const classNames = sourceElt + .getAttribute('data-loading-class-remove') + .split(' ') + + getLoadingTarget(sourceElt).forEach(function(targetElt) { + queueLoadingState( + sourceElt, + targetElt, + function() { + classNames.forEach(function(className) { + targetElt.classList.remove(className) + }) + }, + function() { + classNames.forEach(function(className) { + targetElt.classList.add(className) + }) + } + ) + }) + } + ) + + loadingStateEltsByType['data-loading-disable'].forEach( + function(sourceElt) { + getLoadingTarget(sourceElt).forEach(function(targetElt) { + queueLoadingState( + sourceElt, + targetElt, + function() { targetElt.disabled = true }, + function() { targetElt.disabled = false } + ) + }) + } + ) + + loadingStateEltsByType['data-loading-aria-busy'].forEach( + function(sourceElt) { + getLoadingTarget(sourceElt).forEach(function(targetElt) { + queueLoadingState( + sourceElt, + targetElt, + function() { targetElt.setAttribute('aria-busy', 'true') }, + function() { targetElt.removeAttribute('aria-busy') } + ) + }) + } + ) + } + + if (name === 'htmx:beforeOnLoad') { + while (loadingStatesUndoQueue.length > 0) { + loadingStatesUndoQueue.shift()() + } + } + } + }) +})() diff --git a/site/htmx-assets/ext/multi-swap.js b/site/htmx-assets/ext/multi-swap.js new file mode 100644 index 0000000..4d3f9f5 --- /dev/null +++ b/site/htmx-assets/ext/multi-swap.js @@ -0,0 +1,44 @@ +(function() { + /** @type {import("../htmx").HtmxInternalApi} */ + var api + + htmx.defineExtension('multi-swap', { + init: function(apiRef) { + api = apiRef + }, + isInlineSwap: function(swapStyle) { + return swapStyle.indexOf('multi:') === 0 + }, + handleSwap: function(swapStyle, target, fragment, settleInfo) { + if (swapStyle.indexOf('multi:') === 0) { + var selectorToSwapStyle = {} + var elements = swapStyle.replace(/^multi\s*:\s*/, '').split(/\s*,\s*/) + + elements.forEach(function(element) { + var split = element.split(/\s*:\s*/) + var elementSelector = split[0] + var elementSwapStyle = typeof (split[1]) !== 'undefined' ? split[1] : 'innerHTML' + + if (elementSelector.charAt(0) !== '#') { + console.error("HTMX multi-swap: unsupported selector '" + elementSelector + "'. Only ID selectors starting with '#' are supported.") + return + } + + selectorToSwapStyle[elementSelector] = elementSwapStyle + }) + + for (var selector in selectorToSwapStyle) { + var swapStyle = selectorToSwapStyle[selector] + var elementToSwap = fragment.querySelector(selector) + if (elementToSwap) { + api.oobSwap(swapStyle, elementToSwap, settleInfo) + } else { + console.warn("HTMX multi-swap: selector '" + selector + "' not found in source content.") + } + } + + return true + } + } + }) +})() diff --git a/site/htmx-assets/ext/path-deps.js b/site/htmx-assets/ext/path-deps.js new file mode 100644 index 0000000..4c73179 --- /dev/null +++ b/site/htmx-assets/ext/path-deps.js @@ -0,0 +1,59 @@ +(function() { + 'use strict' + + // Save a reference to the global object (window in the browser) + var _root = this + + function dependsOn(pathSpec, url) { + if (pathSpec === 'ignore') { + return false + } + var dependencyPath = pathSpec.split('/') + var urlPath = url.split('/') + for (var i = 0; i < urlPath.length; i++) { + var dependencyElement = dependencyPath.shift() + var pathElement = urlPath[i] + if (dependencyElement !== pathElement && dependencyElement !== '*') { + return false + } + if (dependencyPath.length === 0 || (dependencyPath.length === 1 && dependencyPath[0] === '')) { + return true + } + } + return false + } + + function refreshPath(path) { + var eltsWithDeps = htmx.findAll('[path-deps]') + for (var i = 0; i < eltsWithDeps.length; i++) { + var elt = eltsWithDeps[i] + if (dependsOn(elt.getAttribute('path-deps'), path)) { + htmx.trigger(elt, 'path-deps') + } + } + } + + htmx.defineExtension('path-deps', { + onEvent: function(name, evt) { + if (name === 'htmx:beforeOnLoad') { + var config = evt.detail.requestConfig + // mutating call + if (config.verb !== 'get' && evt.target.getAttribute('path-deps') !== 'ignore') { + refreshPath(config.path) + } + } + } + }) + + /** + * ******************** + * Expose functionality + * ******************** + */ + + _root.PathDeps = { + refresh: function(path) { + refreshPath(path) + } + } +}).call(this) diff --git a/site/htmx-assets/ext/preload.js b/site/htmx-assets/ext/preload.js new file mode 100644 index 0000000..8d92a3e --- /dev/null +++ b/site/htmx-assets/ext/preload.js @@ -0,0 +1,140 @@ +// This adds the "preload" extension to htmx. By default, this will +// preload the targets of any tags with `href` or `hx-get` attributes +// if they also have a `preload` attribute as well. See documentation +// for more details +htmx.defineExtension('preload', { + + onEvent: function(name, event) { + // Only take actions on "htmx:afterProcessNode" + if (name !== 'htmx:afterProcessNode') { + return + } + + // SOME HELPER FUNCTIONS WE'LL NEED ALONG THE WAY + + // attr gets the closest non-empty value from the attribute. + var attr = function(node, property) { + if (node == undefined) { return undefined } + return node.getAttribute(property) || node.getAttribute('data-' + property) || attr(node.parentElement, property) + } + + // load handles the actual HTTP fetch, and uses htmx.ajax in cases where we're + // preloading an htmx resource (this sends the same HTTP headers as a regular htmx request) + var load = function(node) { + // Called after a successful AJAX request, to mark the + // content as loaded (and prevent additional AJAX calls.) + var done = function(html) { + if (!node.preloadAlways) { + node.preloadState = 'DONE' + } + + if (attr(node, 'preload-images') == 'true') { + document.createElement('div').innerHTML = html // create and populate a node to load linked resources, too. + } + } + + return function() { + // If this value has already been loaded, then do not try again. + if (node.preloadState !== 'READY') { + return + } + + // Special handling for HX-GET - use built-in htmx.ajax function + // so that headers match other htmx requests, then set + // node.preloadState = TRUE so that requests are not duplicated + // in the future + var hxGet = node.getAttribute('hx-get') || node.getAttribute('data-hx-get') + if (hxGet) { + htmx.ajax('GET', hxGet, { + source: node, + handler: function(elt, info) { + done(info.xhr.responseText) + } + }) + return + } + + // Otherwise, perform a standard xhr request, then set + // node.preloadState = TRUE so that requests are not duplicated + // in the future. + if (node.getAttribute('href')) { + var r = new XMLHttpRequest() + r.open('GET', node.getAttribute('href')) + r.onload = function() { done(r.responseText) } + r.send() + } + } + } + + // This function processes a specific node and sets up event handlers. + // We'll search for nodes and use it below. + var init = function(node) { + // If this node DOES NOT include a "GET" transaction, then there's nothing to do here. + if (node.getAttribute('href') + node.getAttribute('hx-get') + node.getAttribute('data-hx-get') == '') { + return + } + + // Guarantee that we only initialize each node once. + if (node.preloadState !== undefined) { + return + } + + // Get event name from config. + var on = attr(node, 'preload') || 'mousedown' + const always = on.indexOf('always') !== -1 + if (always) { + on = on.replace('always', '').trim() + } + + // FALL THROUGH to here means we need to add an EventListener + + // Apply the listener to the node + node.addEventListener(on, function(evt) { + if (node.preloadState === 'PAUSE') { // Only add one event listener + node.preloadState = 'READY' // Required for the `load` function to trigger + + // Special handling for "mouseover" events. Wait 100ms before triggering load. + if (on === 'mouseover') { + window.setTimeout(load(node), 100) + } else { + load(node)() // all other events trigger immediately. + } + } + }) + + // Special handling for certain built-in event handlers + switch (on) { + case 'mouseover': + // Mirror `touchstart` events (fires immediately) + node.addEventListener('touchstart', load(node)) + + // WHhen the mouse leaves, immediately disable the preload + node.addEventListener('mouseout', function(evt) { + if ((evt.target === node) && (node.preloadState === 'READY')) { + node.preloadState = 'PAUSE' + } + }) + break + + case 'mousedown': + // Mirror `touchstart` events (fires immediately) + node.addEventListener('touchstart', load(node)) + break + } + + // Mark the node as ready to run. + node.preloadState = 'PAUSE' + node.preloadAlways = always + htmx.trigger(node, 'preload:init') // This event can be used to load content immediately. + } + + // Search for all child nodes that have a "preload" attribute + event.target.querySelectorAll('[preload]').forEach(function(node) { + // Initialize the node with the "preload" attribute + init(node) + + // Initialize all child elements that are anchors or have `hx-get` (use with care) + node.querySelectorAll('a,[hx-get],[data-hx-get]').forEach(init) + }) + } +}) diff --git a/site/htmx-assets/ext/response-targets.js b/site/htmx-assets/ext/response-targets.js new file mode 100644 index 0000000..01e1285 --- /dev/null +++ b/site/htmx-assets/ext/response-targets.js @@ -0,0 +1,129 @@ +(function() { + /** @type {import("../htmx").HtmxInternalApi} */ + var api + + var attrPrefix = 'hx-target-' + + // IE11 doesn't support string.startsWith + function startsWith(str, prefix) { + return str.substring(0, prefix.length) === prefix + } + + /** + * @param {HTMLElement} elt + * @param {number} respCode + * @returns {HTMLElement | null} + */ + function getRespCodeTarget(elt, respCodeNumber) { + if (!elt || !respCodeNumber) return null + + var respCode = respCodeNumber.toString() + + // '*' is the original syntax, as the obvious character for a wildcard. + // The 'x' alternative was added for maximum compatibility with HTML + // templating engines, due to ambiguity around which characters are + // supported in HTML attributes. + // + // Start with the most specific possible attribute and generalize from + // there. + var attrPossibilities = [ + respCode, + + respCode.substr(0, 2) + '*', + respCode.substr(0, 2) + 'x', + + respCode.substr(0, 1) + '*', + respCode.substr(0, 1) + 'x', + respCode.substr(0, 1) + '**', + respCode.substr(0, 1) + 'xx', + + '*', + 'x', + '***', + 'xxx' + ] + if (startsWith(respCode, '4') || startsWith(respCode, '5')) { + attrPossibilities.push('error') + } + + for (var i = 0; i < attrPossibilities.length; i++) { + var attr = attrPrefix + attrPossibilities[i] + var attrValue = api.getClosestAttributeValue(elt, attr) + if (attrValue) { + if (attrValue === 'this') { + return api.findThisElement(elt, attr) + } else { + return api.querySelectorExt(elt, attrValue) + } + } + } + + return null + } + + /** @param {Event} evt */ + function handleErrorFlag(evt) { + if (evt.detail.isError) { + if (htmx.config.responseTargetUnsetsError) { + evt.detail.isError = false + } + } else if (htmx.config.responseTargetSetsError) { + evt.detail.isError = true + } + } + + htmx.defineExtension('response-targets', { + + /** @param {import("../htmx").HtmxInternalApi} apiRef */ + init: function(apiRef) { + api = apiRef + + if (htmx.config.responseTargetUnsetsError === undefined) { + htmx.config.responseTargetUnsetsError = true + } + if (htmx.config.responseTargetSetsError === undefined) { + htmx.config.responseTargetSetsError = false + } + if (htmx.config.responseTargetPrefersExisting === undefined) { + htmx.config.responseTargetPrefersExisting = false + } + if (htmx.config.responseTargetPrefersRetargetHeader === undefined) { + htmx.config.responseTargetPrefersRetargetHeader = true + } + }, + + /** + * @param {string} name + * @param {Event} evt + */ + onEvent: function(name, evt) { + if (name === 'htmx:beforeSwap' && + evt.detail.xhr && + evt.detail.xhr.status !== 200) { + if (evt.detail.target) { + if (htmx.config.responseTargetPrefersExisting) { + evt.detail.shouldSwap = true + handleErrorFlag(evt) + return true + } + if (htmx.config.responseTargetPrefersRetargetHeader && + evt.detail.xhr.getAllResponseHeaders().match(/HX-Retarget:/i)) { + evt.detail.shouldSwap = true + handleErrorFlag(evt) + return true + } + } + if (!evt.detail.requestConfig) { + return true + } + var target = getRespCodeTarget(evt.detail.requestConfig.elt, evt.detail.xhr.status) + if (target) { + handleErrorFlag(evt) + evt.detail.shouldSwap = true + evt.detail.target = target + } + return true + } + } + }) +})() diff --git a/site/htmx-assets/ext/sse.js b/site/htmx-assets/ext/sse.js new file mode 100644 index 0000000..1550172 --- /dev/null +++ b/site/htmx-assets/ext/sse.js @@ -0,0 +1,301 @@ +/* +Server Sent Events Extension +============================ +This extension adds support for Server Sent Events to htmx. See /www/extensions/sse.md for usage instructions. + +*/ + +(function() { + /** @type {import("../htmx").HtmxInternalApi} */ + var api + + htmx.defineExtension('sse', { + + /** + * Init saves the provided reference to the internal HTMX API. + * + * @param {import("../htmx").HtmxInternalApi} api + * @returns void + */ + init: function(apiRef) { + // store a reference to the internal API. + api = apiRef + + // set a function in the public API for creating new EventSource objects + if (htmx.createEventSource == undefined) { + htmx.createEventSource = createEventSource + } + }, + + /** + * onEvent handles all events passed to this extension. + * + * @param {string} name + * @param {Event} evt + * @returns void + */ + onEvent: function(name, evt) { + var parent = evt.target || evt.detail.elt + switch (name) { + case 'htmx:beforeCleanupElement': + var internalData = api.getInternalData(parent) + // Try to remove remove an EventSource when elements are removed + if (internalData.sseEventSource) { + internalData.sseEventSource.close() + } + + return + + // Try to create EventSources when elements are processed + case 'htmx:afterProcessNode': + ensureEventSourceOnElement(parent) + } + } + }) + + /// //////////////////////////////////////////// + // HELPER FUNCTIONS + /// //////////////////////////////////////////// + + /** + * createEventSource is the default method for creating new EventSource objects. + * it is hoisted into htmx.config.createEventSource to be overridden by the user, if needed. + * + * @param {string} url + * @returns EventSource + */ + function createEventSource(url) { + return new EventSource(url, { withCredentials: true }) + } + + /** + * registerSSE looks for attributes that can contain sse events, right + * now hx-trigger and sse-swap and adds listeners based on these attributes too + * the closest event source + * + * @param {HTMLElement} elt + */ + function registerSSE(elt) { + // Add message handlers for every `sse-swap` attribute + queryAttributeOnThisOrChildren(elt, 'sse-swap').forEach(function(child) { + // Find closest existing event source + var sourceElement = api.getClosestMatch(child, hasEventSource) + if (sourceElement == null) { + // api.triggerErrorEvent(elt, "htmx:noSSESourceError") + return null // no eventsource in parentage, orphaned element + } + + // Set internalData and source + var internalData = api.getInternalData(sourceElement) + var source = internalData.sseEventSource + + var sseSwapAttr = api.getAttributeValue(child, 'sse-swap') + var sseEventNames = sseSwapAttr.split(',') + + for (var i = 0; i < sseEventNames.length; i++) { + var sseEventName = sseEventNames[i].trim() + var listener = function(event) { + // If the source is missing then close SSE + if (maybeCloseSSESource(sourceElement)) { + return + } + + // If the body no longer contains the element, remove the listener + if (!api.bodyContains(child)) { + source.removeEventListener(sseEventName, listener) + return + } + + // swap the response into the DOM and trigger a notification + if (!api.triggerEvent(elt, 'htmx:sseBeforeMessage', event)) { + return + } + swap(child, event.data) + api.triggerEvent(elt, 'htmx:sseMessage', event) + } + + // Register the new listener + api.getInternalData(child).sseEventListener = listener + source.addEventListener(sseEventName, listener) + } + }) + + // Add message handlers for every `hx-trigger="sse:*"` attribute + queryAttributeOnThisOrChildren(elt, 'hx-trigger').forEach(function(child) { + // Find closest existing event source + var sourceElement = api.getClosestMatch(child, hasEventSource) + if (sourceElement == null) { + // api.triggerErrorEvent(elt, "htmx:noSSESourceError") + return null // no eventsource in parentage, orphaned element + } + + // Set internalData and source + var internalData = api.getInternalData(sourceElement) + var source = internalData.sseEventSource + + var sseEventName = api.getAttributeValue(child, 'hx-trigger') + if (sseEventName == null) { + return + } + + // Only process hx-triggers for events with the "sse:" prefix + if (sseEventName.slice(0, 4) != 'sse:') { + return + } + + var listener = function(event) { + if (maybeCloseSSESource(sourceElement)) { + return + } + + if (!api.bodyContains(child)) { + source.removeEventListener(sseEventName, listener) + } + + // Trigger events to be handled by the rest of htmx + htmx.trigger(child, sseEventName, event) + htmx.trigger(child, 'htmx:sseMessage', event) + } + + // Register the new listener + api.getInternalData(elt).sseEventListener = listener + source.addEventListener(sseEventName.slice(4), listener) + }) + } + + /** + * ensureEventSourceOnElement creates a new EventSource connection on the provided element. + * If a usable EventSource already exists, then it is returned. If not, then a new EventSource + * is created and stored in the element's internalData. + * @param {HTMLElement} elt + * @param {number} retryCount + * @returns {EventSource | null} + */ + function ensureEventSourceOnElement(elt, retryCount) { + if (elt == null) { + return null + } + + // handle extension source creation attribute + queryAttributeOnThisOrChildren(elt, 'sse-connect').forEach(function(child) { + var sseURL = api.getAttributeValue(child, 'sse-connect') + if (sseURL == null) { + return + } + + ensureEventSource(child, sseURL, retryCount) + }) + + registerSSE(elt) + } + + function ensureEventSource(elt, url, retryCount) { + var source = htmx.createEventSource(url) + + source.onerror = function(err) { + // Log an error event + api.triggerErrorEvent(elt, 'htmx:sseError', { error: err, source }) + + // If parent no longer exists in the document, then clean up this EventSource + if (maybeCloseSSESource(elt)) { + return + } + + // Otherwise, try to reconnect the EventSource + if (source.readyState === EventSource.CLOSED) { + retryCount = retryCount || 0 + var timeout = Math.random() * (2 ^ retryCount) * 500 + window.setTimeout(function() { + ensureEventSourceOnElement(elt, Math.min(7, retryCount + 1)) + }, timeout) + } + } + + source.onopen = function(evt) { + api.triggerEvent(elt, 'htmx:sseOpen', { source }) + } + + api.getInternalData(elt).sseEventSource = source + } + + /** + * maybeCloseSSESource confirms that the parent element still exists. + * If not, then any associated SSE source is closed and the function returns true. + * + * @param {HTMLElement} elt + * @returns boolean + */ + function maybeCloseSSESource(elt) { + if (!api.bodyContains(elt)) { + var source = api.getInternalData(elt).sseEventSource + if (source != undefined) { + source.close() + // source = null + return true + } + } + return false + } + + /** + * queryAttributeOnThisOrChildren returns all nodes that contain the requested attributeName, INCLUDING THE PROVIDED ROOT ELEMENT. + * + * @param {HTMLElement} elt + * @param {string} attributeName + */ + function queryAttributeOnThisOrChildren(elt, attributeName) { + var result = [] + + // If the parent element also contains the requested attribute, then add it to the results too. + if (api.hasAttribute(elt, attributeName)) { + result.push(elt) + } + + // Search all child nodes that match the requested attribute + elt.querySelectorAll('[' + attributeName + '], [data-' + attributeName + ']').forEach(function(node) { + result.push(node) + }) + + return result + } + + /** + * @param {HTMLElement} elt + * @param {string} content + */ + function swap(elt, content) { + api.withExtensions(elt, function(extension) { + content = extension.transformResponse(content, null, elt) + }) + + var swapSpec = api.getSwapSpecification(elt) + var target = api.getTarget(elt) + api.swap(target, content, swapSpec) + } + + /** + * doSettle mirrors much of the functionality in htmx that + * settles elements after their content has been swapped. + * TODO: this should be published by htmx, and not duplicated here + * @param {import("../htmx").HtmxSettleInfo} settleInfo + * @returns () => void + */ + function doSettle(settleInfo) { + return function() { + settleInfo.tasks.forEach(function(task) { + task.call() + }) + + settleInfo.elts.forEach(function(elt) { + if (elt.classList) { + elt.classList.remove(htmx.config.settlingClass) + } + api.triggerEvent(elt, 'htmx:afterSettle') + }) + } + } + + function hasEventSource(node) { + return api.getInternalData(node).sseEventSource != null + } +})() diff --git a/site/htmx-assets/ext/ws.js b/site/htmx-assets/ext/ws.js new file mode 100644 index 0000000..bf3a63a --- /dev/null +++ b/site/htmx-assets/ext/ws.js @@ -0,0 +1,467 @@ +/* +WebSockets Extension +============================ +This extension adds support for WebSockets to htmx. See /www/extensions/ws.md for usage instructions. +*/ + +(function() { + /** @type {import("../htmx").HtmxInternalApi} */ + var api + + htmx.defineExtension('ws', { + + /** + * init is called once, when this extension is first registered. + * @param {import("../htmx").HtmxInternalApi} apiRef + */ + init: function(apiRef) { + // Store reference to internal API + api = apiRef + + // Default function for creating new EventSource objects + if (!htmx.createWebSocket) { + htmx.createWebSocket = createWebSocket + } + + // Default setting for reconnect delay + if (!htmx.config.wsReconnectDelay) { + htmx.config.wsReconnectDelay = 'full-jitter' + } + }, + + /** + * onEvent handles all events passed to this extension. + * + * @param {string} name + * @param {Event} evt + */ + onEvent: function(name, evt) { + var parent = evt.target || evt.detail.elt + switch (name) { + // Try to close the socket when elements are removed + case 'htmx:beforeCleanupElement': + + var internalData = api.getInternalData(parent) + + if (internalData.webSocket) { + internalData.webSocket.close() + } + return + + // Try to create websockets when elements are processed + case 'htmx:beforeProcessNode': + + forEach(queryAttributeOnThisOrChildren(parent, 'ws-connect'), function(child) { + ensureWebSocket(child) + }) + forEach(queryAttributeOnThisOrChildren(parent, 'ws-send'), function(child) { + ensureWebSocketSend(child) + }) + } + } + }) + + function splitOnWhitespace(trigger) { + return trigger.trim().split(/\s+/) + } + + function getLegacyWebsocketURL(elt) { + var legacySSEValue = api.getAttributeValue(elt, 'hx-ws') + if (legacySSEValue) { + var values = splitOnWhitespace(legacySSEValue) + for (var i = 0; i < values.length; i++) { + var value = values[i].split(/:(.+)/) + if (value[0] === 'connect') { + return value[1] + } + } + } + } + + /** + * ensureWebSocket creates a new WebSocket on the designated element, using + * the element's "ws-connect" attribute. + * @param {HTMLElement} socketElt + * @returns + */ + function ensureWebSocket(socketElt) { + // If the element containing the WebSocket connection no longer exists, then + // do not connect/reconnect the WebSocket. + if (!api.bodyContains(socketElt)) { + return + } + + // Get the source straight from the element's value + var wssSource = api.getAttributeValue(socketElt, 'ws-connect') + + if (wssSource == null || wssSource === '') { + var legacySource = getLegacyWebsocketURL(socketElt) + if (legacySource == null) { + return + } else { + wssSource = legacySource + } + } + + // Guarantee that the wssSource value is a fully qualified URL + if (wssSource.indexOf('/') === 0) { + var base_part = location.hostname + (location.port ? ':' + location.port : '') + if (location.protocol === 'https:') { + wssSource = 'wss://' + base_part + wssSource + } else if (location.protocol === 'http:') { + wssSource = 'ws://' + base_part + wssSource + } + } + + var socketWrapper = createWebsocketWrapper(socketElt, function() { + return htmx.createWebSocket(wssSource) + }) + + socketWrapper.addEventListener('message', function(event) { + if (maybeCloseWebSocketSource(socketElt)) { + return + } + + var response = event.data + if (!api.triggerEvent(socketElt, 'htmx:wsBeforeMessage', { + message: response, + socketWrapper: socketWrapper.publicInterface + })) { + return + } + + api.withExtensions(socketElt, function(extension) { + response = extension.transformResponse(response, null, socketElt) + }) + + var settleInfo = api.makeSettleInfo(socketElt) + var fragment = api.makeFragment(response) + + if (fragment.children.length) { + var children = Array.from(fragment.children) + for (var i = 0; i < children.length; i++) { + api.oobSwap(api.getAttributeValue(children[i], 'hx-swap-oob') || 'true', children[i], settleInfo) + } + } + + api.settleImmediately(settleInfo.tasks) + api.triggerEvent(socketElt, 'htmx:wsAfterMessage', { message: response, socketWrapper: socketWrapper.publicInterface }) + }) + + // Put the WebSocket into the HTML Element's custom data. + api.getInternalData(socketElt).webSocket = socketWrapper + } + + /** + * @typedef {Object} WebSocketWrapper + * @property {WebSocket} socket + * @property {Array<{message: string, sendElt: Element}>} messageQueue + * @property {number} retryCount + * @property {(message: string, sendElt: Element) => void} sendImmediately sendImmediately sends message regardless of websocket connection state + * @property {(message: string, sendElt: Element) => void} send + * @property {(event: string, handler: Function) => void} addEventListener + * @property {() => void} handleQueuedMessages + * @property {() => void} init + * @property {() => void} close + */ + /** + * + * @param socketElt + * @param socketFunc + * @returns {WebSocketWrapper} + */ + function createWebsocketWrapper(socketElt, socketFunc) { + var wrapper = { + socket: null, + messageQueue: [], + retryCount: 0, + + /** @type {Object} */ + events: {}, + + addEventListener: function(event, handler) { + if (this.socket) { + this.socket.addEventListener(event, handler) + } + + if (!this.events[event]) { + this.events[event] = [] + } + + this.events[event].push(handler) + }, + + sendImmediately: function(message, sendElt) { + if (!this.socket) { + api.triggerErrorEvent() + } + if (!sendElt || api.triggerEvent(sendElt, 'htmx:wsBeforeSend', { + message, + socketWrapper: this.publicInterface + })) { + this.socket.send(message) + sendElt && api.triggerEvent(sendElt, 'htmx:wsAfterSend', { + message, + socketWrapper: this.publicInterface + }) + } + }, + + send: function(message, sendElt) { + if (this.socket.readyState !== this.socket.OPEN) { + this.messageQueue.push({ message, sendElt }) + } else { + this.sendImmediately(message, sendElt) + } + }, + + handleQueuedMessages: function() { + while (this.messageQueue.length > 0) { + var queuedItem = this.messageQueue[0] + if (this.socket.readyState === this.socket.OPEN) { + this.sendImmediately(queuedItem.message, queuedItem.sendElt) + this.messageQueue.shift() + } else { + break + } + } + }, + + init: function() { + if (this.socket && this.socket.readyState === this.socket.OPEN) { + // Close discarded socket + this.socket.close() + } + + // Create a new WebSocket and event handlers + /** @type {WebSocket} */ + var socket = socketFunc() + + // The event.type detail is added for interface conformance with the + // other two lifecycle events (open and close) so a single handler method + // can handle them polymorphically, if required. + api.triggerEvent(socketElt, 'htmx:wsConnecting', { event: { type: 'connecting' } }) + + this.socket = socket + + socket.onopen = function(e) { + wrapper.retryCount = 0 + api.triggerEvent(socketElt, 'htmx:wsOpen', { event: e, socketWrapper: wrapper.publicInterface }) + wrapper.handleQueuedMessages() + } + + socket.onclose = function(e) { + // If socket should not be connected, stop further attempts to establish connection + // If Abnormal Closure/Service Restart/Try Again Later, then set a timer to reconnect after a pause. + if (!maybeCloseWebSocketSource(socketElt) && [1006, 1012, 1013].indexOf(e.code) >= 0) { + var delay = getWebSocketReconnectDelay(wrapper.retryCount) + setTimeout(function() { + wrapper.retryCount += 1 + wrapper.init() + }, delay) + } + + // Notify client code that connection has been closed. Client code can inspect `event` field + // to determine whether closure has been valid or abnormal + api.triggerEvent(socketElt, 'htmx:wsClose', { event: e, socketWrapper: wrapper.publicInterface }) + } + + socket.onerror = function(e) { + api.triggerErrorEvent(socketElt, 'htmx:wsError', { error: e, socketWrapper: wrapper }) + maybeCloseWebSocketSource(socketElt) + } + + var events = this.events + Object.keys(events).forEach(function(k) { + events[k].forEach(function(e) { + socket.addEventListener(k, e) + }) + }) + }, + + close: function() { + this.socket.close() + } + } + + wrapper.init() + + wrapper.publicInterface = { + send: wrapper.send.bind(wrapper), + sendImmediately: wrapper.sendImmediately.bind(wrapper), + queue: wrapper.messageQueue + } + + return wrapper + } + + /** + * ensureWebSocketSend attaches trigger handles to elements with + * "ws-send" attribute + * @param {HTMLElement} elt + */ + function ensureWebSocketSend(elt) { + var legacyAttribute = api.getAttributeValue(elt, 'hx-ws') + if (legacyAttribute && legacyAttribute !== 'send') { + return + } + + var webSocketParent = api.getClosestMatch(elt, hasWebSocket) + processWebSocketSend(webSocketParent, elt) + } + + /** + * hasWebSocket function checks if a node has webSocket instance attached + * @param {HTMLElement} node + * @returns {boolean} + */ + function hasWebSocket(node) { + return api.getInternalData(node).webSocket != null + } + + /** + * processWebSocketSend adds event listeners to the
element so that + * messages can be sent to the WebSocket server when the form is submitted. + * @param {HTMLElement} socketElt + * @param {HTMLElement} sendElt + */ + function processWebSocketSend(socketElt, sendElt) { + var nodeData = api.getInternalData(sendElt) + var triggerSpecs = api.getTriggerSpecs(sendElt) + triggerSpecs.forEach(function(ts) { + api.addTriggerHandler(sendElt, ts, nodeData, function(elt, evt) { + if (maybeCloseWebSocketSource(socketElt)) { + return + } + + /** @type {WebSocketWrapper} */ + var socketWrapper = api.getInternalData(socketElt).webSocket + var headers = api.getHeaders(sendElt, api.getTarget(sendElt)) + var results = api.getInputValues(sendElt, 'post') + var errors = results.errors + var rawParameters = results.values + var expressionVars = api.getExpressionVars(sendElt) + var allParameters = api.mergeObjects(rawParameters, expressionVars) + var filteredParameters = api.filterValues(allParameters, sendElt) + + var sendConfig = { + parameters: filteredParameters, + unfilteredParameters: allParameters, + headers, + errors, + + triggeringEvent: evt, + messageBody: undefined, + socketWrapper: socketWrapper.publicInterface + } + + if (!api.triggerEvent(elt, 'htmx:wsConfigSend', sendConfig)) { + return + } + + if (errors && errors.length > 0) { + api.triggerEvent(elt, 'htmx:validation:halted', errors) + return + } + + var body = sendConfig.messageBody + if (body === undefined) { + var toSend = Object.assign({}, sendConfig.parameters) + if (sendConfig.headers) { toSend.HEADERS = headers } + body = JSON.stringify(toSend) + } + + socketWrapper.send(body, elt) + + if (evt && api.shouldCancel(evt, elt)) { + evt.preventDefault() + } + }) + }) + } + + /** + * getWebSocketReconnectDelay is the default easing function for WebSocket reconnects. + * @param {number} retryCount // The number of retries that have already taken place + * @returns {number} + */ + function getWebSocketReconnectDelay(retryCount) { + /** @type {"full-jitter" | ((retryCount:number) => number)} */ + var delay = htmx.config.wsReconnectDelay + if (typeof delay === 'function') { + return delay(retryCount) + } + if (delay === 'full-jitter') { + var exp = Math.min(retryCount, 6) + var maxDelay = 1000 * Math.pow(2, exp) + return maxDelay * Math.random() + } + + logError('htmx.config.wsReconnectDelay must either be a function or the string "full-jitter"') + } + + /** + * maybeCloseWebSocketSource checks to the if the element that created the WebSocket + * still exists in the DOM. If NOT, then the WebSocket is closed and this function + * returns TRUE. If the element DOES EXIST, then no action is taken, and this function + * returns FALSE. + * + * @param {*} elt + * @returns + */ + function maybeCloseWebSocketSource(elt) { + if (!api.bodyContains(elt)) { + api.getInternalData(elt).webSocket.close() + return true + } + return false + } + + /** + * createWebSocket is the default method for creating new WebSocket objects. + * it is hoisted into htmx.createWebSocket to be overridden by the user, if needed. + * + * @param {string} url + * @returns WebSocket + */ + function createWebSocket(url) { + var sock = new WebSocket(url, []) + sock.binaryType = htmx.config.wsBinaryType + return sock + } + + /** + * queryAttributeOnThisOrChildren returns all nodes that contain the requested attributeName, INCLUDING THE PROVIDED ROOT ELEMENT. + * + * @param {HTMLElement} elt + * @param {string} attributeName + */ + function queryAttributeOnThisOrChildren(elt, attributeName) { + var result = [] + + // If the parent element also contains the requested attribute, then add it to the results too. + if (api.hasAttribute(elt, attributeName) || api.hasAttribute(elt, 'hx-ws')) { + result.push(elt) + } + + // Search all child nodes that match the requested attribute + elt.querySelectorAll('[' + attributeName + '], [data-' + attributeName + '], [data-hx-ws], [hx-ws]').forEach(function(node) { + result.push(node) + }) + + return result + } + + /** + * @template T + * @param {T[]} arr + * @param {(T) => void} func + */ + function forEach(arr, func) { + if (arr) { + for (var i = 0; i < arr.length; i++) { + func(arr[i]) + } + } + } +})() diff --git a/site/htmx-assets/htmx.min.js b/site/htmx-assets/htmx.min.js new file mode 100644 index 0000000..bb0c5b4 --- /dev/null +++ b/site/htmx-assets/htmx.min.js @@ -0,0 +1 @@ +var htmx=function(){"use strict";const Q={onLoad:null,process:null,on:null,off:null,trigger:null,ajax:null,find:null,findAll:null,closest:null,values:function(e,t){const n=dn(e,t||"post");return n.values},remove:null,addClass:null,removeClass:null,toggleClass:null,takeClass:null,swap:null,defineExtension:null,removeExtension:null,logAll:null,logNone:null,logger:null,config:{historyEnabled:true,historyCacheSize:10,refreshOnHistoryMiss:false,defaultSwapStyle:"innerHTML",defaultSwapDelay:0,defaultSettleDelay:20,includeIndicatorStyles:true,indicatorClass:"htmx-indicator",requestClass:"htmx-request",addedClass:"htmx-added",settlingClass:"htmx-settling",swappingClass:"htmx-swapping",allowEval:true,allowScriptTags:true,inlineScriptNonce:"",inlineStyleNonce:"",attributesToSettle:["class","style","width","height"],withCredentials:false,timeout:0,wsReconnectDelay:"full-jitter",wsBinaryType:"blob",disableSelector:"[hx-disable], [data-hx-disable]",scrollBehavior:"instant",defaultFocusScroll:false,getCacheBusterParam:false,globalViewTransitions:false,methodsThatUseUrlParams:["get","delete"],selfRequestsOnly:true,ignoreTitle:false,scrollIntoViewOnBoost:true,triggerSpecsCache:null,disableInheritance:false,responseHandling:[{code:"204",swap:false},{code:"[23]..",swap:true},{code:"[45]..",swap:false,error:true}],allowNestedOobSwaps:true,historyRestoreAsHxRequest:true},parseInterval:null,location:location,_:null,version:"2.0.6"};Q.onLoad=j;Q.process=Ft;Q.on=xe;Q.off=be;Q.trigger=ae;Q.ajax=Ln;Q.find=f;Q.findAll=x;Q.closest=g;Q.remove=z;Q.addClass=K;Q.removeClass=G;Q.toggleClass=W;Q.takeClass=Z;Q.swap=$e;Q.defineExtension=zn;Q.removeExtension=$n;Q.logAll=V;Q.logNone=_;Q.parseInterval=d;Q._=e;const n={addTriggerHandler:St,bodyContains:se,canAccessLocalStorage:B,findThisElement:Se,filterValues:yn,swap:$e,hasAttribute:s,getAttributeValue:a,getClosestAttributeValue:ne,getClosestMatch:q,getExpressionVars:Tn,getHeaders:mn,getInputValues:dn,getInternalData:oe,getSwapSpecification:bn,getTriggerSpecs:st,getTarget:Ee,makeFragment:P,mergeObjects:le,makeSettleInfo:Sn,oobSwap:He,querySelectorExt:ue,settleImmediately:Yt,shouldCancel:ht,triggerEvent:ae,triggerErrorEvent:fe,withExtensions:jt};const de=["get","post","put","delete","patch"];const T=de.map(function(e){return"[hx-"+e+"], [data-hx-"+e+"]"}).join(", ");function d(e){if(e==undefined){return undefined}let t=NaN;if(e.slice(-2)=="ms"){t=parseFloat(e.slice(0,-2))}else if(e.slice(-1)=="s"){t=parseFloat(e.slice(0,-1))*1e3}else if(e.slice(-1)=="m"){t=parseFloat(e.slice(0,-1))*1e3*60}else{t=parseFloat(e)}return isNaN(t)?undefined:t}function ee(e,t){return e instanceof Element&&e.getAttribute(t)}function s(e,t){return!!e.hasAttribute&&(e.hasAttribute(t)||e.hasAttribute("data-"+t))}function a(e,t){return ee(e,t)||ee(e,"data-"+t)}function u(e){const t=e.parentElement;if(!t&&e.parentNode instanceof ShadowRoot)return e.parentNode;return t}function te(){return document}function y(e,t){return e.getRootNode?e.getRootNode({composed:t}):te()}function q(e,t){while(e&&!t(e)){e=u(e)}return e||null}function o(e,t,n){const r=a(t,n);const o=a(t,"hx-disinherit");var i=a(t,"hx-inherit");if(e!==t){if(Q.config.disableInheritance){if(i&&(i==="*"||i.split(" ").indexOf(n)>=0)){return r}else{return null}}if(o&&(o==="*"||o.split(" ").indexOf(n)>=0)){return"unset"}}return r}function ne(t,n){let r=null;q(t,function(e){return!!(r=o(t,ce(e),n))});if(r!=="unset"){return r}}function h(e,t){return e instanceof Element&&e.matches(t)}function A(e){const t=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i;const n=t.exec(e);if(n){return n[1].toLowerCase()}else{return""}}function L(e){const t=new DOMParser;return t.parseFromString(e,"text/html")}function N(e,t){while(t.childNodes.length>0){e.append(t.childNodes[0])}}function r(e){const t=te().createElement("script");ie(e.attributes,function(e){t.setAttribute(e.name,e.value)});t.textContent=e.textContent;t.async=false;if(Q.config.inlineScriptNonce){t.nonce=Q.config.inlineScriptNonce}return t}function i(e){return e.matches("script")&&(e.type==="text/javascript"||e.type==="module"||e.type==="")}function I(e){Array.from(e.querySelectorAll("script")).forEach(e=>{if(i(e)){const t=r(e);const n=e.parentNode;try{n.insertBefore(t,e)}catch(e){R(e)}finally{e.remove()}}})}function P(e){const t=e.replace(/]*)?>[\s\S]*?<\/head>/i,"");const n=A(t);let r;if(n==="html"){r=new DocumentFragment;const i=L(e);N(r,i.body);r.title=i.title}else if(n==="body"){r=new DocumentFragment;const i=L(t);N(r,i.body);r.title=i.title}else{const i=L('");r=i.querySelector("template").content;r.title=i.title;var o=r.querySelector("title");if(o&&o.parentNode===r){o.remove();r.title=o.innerText}}if(r){if(Q.config.allowScriptTags){I(r)}else{r.querySelectorAll("script").forEach(e=>e.remove())}}return r}function re(e){if(e){e()}}function t(e,t){return Object.prototype.toString.call(e)==="[object "+t+"]"}function D(e){return typeof e==="function"}function k(e){return t(e,"Object")}function oe(e){const t="htmx-internal-data";let n=e[t];if(!n){n=e[t]={}}return n}function M(t){const n=[];if(t){for(let e=0;e=0}function se(e){return e.getRootNode({composed:true})===document}function X(e){return e.trim().split(/\s+/)}function le(e,t){for(const n in t){if(t.hasOwnProperty(n)){e[n]=t[n]}}return e}function v(e){try{return JSON.parse(e)}catch(e){R(e);return null}}function B(){const e="htmx:sessionStorageTest";try{sessionStorage.setItem(e,e);sessionStorage.removeItem(e);return true}catch(e){return false}}function U(e){const t=new URL(e,"http://x");if(t){e=t.pathname+t.search}if(e!="/"){e=e.replace(/\/+$/,"")}return e}function e(e){return On(te().body,function(){return eval(e)})}function j(t){const e=Q.on("htmx:load",function(e){t(e.detail.elt)});return e}function V(){Q.logger=function(e,t,n){if(console){console.log(t,e,n)}}}function _(){Q.logger=null}function f(e,t){if(typeof e!=="string"){return e.querySelector(t)}else{return f(te(),e)}}function x(e,t){if(typeof e!=="string"){return e.querySelectorAll(t)}else{return x(te(),e)}}function b(){return window}function z(e,t){e=w(e);if(t){b().setTimeout(function(){z(e);e=null},t)}else{u(e).removeChild(e)}}function ce(e){return e instanceof Element?e:null}function $(e){return e instanceof HTMLElement?e:null}function J(e){return typeof e==="string"?e:null}function p(e){return e instanceof Element||e instanceof Document||e instanceof DocumentFragment?e:null}function K(e,t,n){e=ce(w(e));if(!e){return}if(n){b().setTimeout(function(){K(e,t);e=null},n)}else{e.classList&&e.classList.add(t)}}function G(e,t,n){let r=ce(w(e));if(!r){return}if(n){b().setTimeout(function(){G(r,t);r=null},n)}else{if(r.classList){r.classList.remove(t);if(r.classList.length===0){r.removeAttribute("class")}}}}function W(e,t){e=w(e);e.classList.toggle(t)}function Z(e,t){e=w(e);ie(e.parentElement.children,function(e){G(e,t)});K(ce(e),t)}function g(e,t){e=ce(w(e));if(e){return e.closest(t)}return null}function l(e,t){return e.substring(0,t.length)===t}function Y(e,t){return e.substring(e.length-t.length)===t}function pe(e){const t=e.trim();if(l(t,"<")&&Y(t,"/>")){return t.substring(1,t.length-2)}else{return t}}function m(t,r,n){if(r.indexOf("global ")===0){return m(t,r.slice(7),true)}t=w(t);const o=[];{let t=0;let n=0;for(let e=0;e"){t--}}if(n0){const r=pe(o.shift());let e;if(r.indexOf("closest ")===0){e=g(ce(t),pe(r.slice(8)))}else if(r.indexOf("find ")===0){e=f(p(t),pe(r.slice(5)))}else if(r==="next"||r==="nextElementSibling"){e=ce(t).nextElementSibling}else if(r.indexOf("next ")===0){e=ge(t,pe(r.slice(5)),!!n)}else if(r==="previous"||r==="previousElementSibling"){e=ce(t).previousElementSibling}else if(r.indexOf("previous ")===0){e=me(t,pe(r.slice(9)),!!n)}else if(r==="document"){e=document}else if(r==="window"){e=window}else if(r==="body"){e=document.body}else if(r==="root"){e=y(t,!!n)}else if(r==="host"){e=t.getRootNode().host}else{s.push(r)}if(e){i.push(e)}}if(s.length>0){const e=s.join(",");const c=p(y(t,!!n));i.push(...M(c.querySelectorAll(e)))}return i}var ge=function(t,e,n){const r=p(y(t,n)).querySelectorAll(e);for(let e=0;e=0;e--){const o=r[e];if(o.compareDocumentPosition(t)===Node.DOCUMENT_POSITION_FOLLOWING){return o}}};function ue(e,t){if(typeof e!=="string"){return m(e,t)[0]}else{return m(te().body,e)[0]}}function w(e,t){if(typeof e==="string"){return f(p(t)||document,e)}else{return e}}function ye(e,t,n,r){if(D(t)){return{target:te().body,event:J(e),listener:t,options:n}}else{return{target:w(e),event:J(t),listener:n,options:r}}}function xe(t,n,r,o){Gn(function(){const e=ye(t,n,r,o);e.target.addEventListener(e.event,e.listener,e.options)});const e=D(n);return e?n:r}function be(t,n,r){Gn(function(){const e=ye(t,n,r);e.target.removeEventListener(e.event,e.listener)});return D(n)?n:r}const ve=te().createElement("output");function we(t,n){const e=ne(t,n);if(e){if(e==="this"){return[Se(t,n)]}else{const r=m(t,e);const o=/(^|,)(\s*)inherit(\s*)($|,)/.test(e);if(o){const i=ce(q(t,function(e){return e!==t&&s(ce(e),n)}));if(i){r.push(...we(i,n))}}if(r.length===0){R('The selector "'+e+'" on '+n+" returned no matches!");return[ve]}else{return r}}}}function Se(e,t){return ce(q(e,function(e){return a(ce(e),t)!=null}))}function Ee(e){const t=ne(e,"hx-target");if(t){if(t==="this"){return Se(e,"hx-target")}else{return ue(e,t)}}else{const n=oe(e);if(n.boosted){return te().body}else{return e}}}function Ce(e){return Q.config.attributesToSettle.includes(e)}function Oe(t,n){ie(t.attributes,function(e){if(!n.hasAttribute(e.name)&&Ce(e.name)){t.removeAttribute(e.name)}});ie(n.attributes,function(e){if(Ce(e.name)){t.setAttribute(e.name,e.value)}})}function Re(t,e){const n=Jn(e);for(let e=0;e0){s=e.substring(0,e.indexOf(":"));n=e.substring(e.indexOf(":")+1)}else{s=e}o.removeAttribute("hx-swap-oob");o.removeAttribute("data-hx-swap-oob");const r=m(t,n,false);if(r.length){ie(r,function(e){let t;const n=o.cloneNode(true);t=te().createDocumentFragment();t.appendChild(n);if(!Re(s,e)){t=p(n)}const r={shouldSwap:true,target:e,fragment:t};if(!ae(e,"htmx:oobBeforeSwap",r))return;e=r.target;if(r.shouldSwap){qe(t);_e(s,e,e,t,i);Te()}ie(i.elts,function(e){ae(e,"htmx:oobAfterSwap",r)})});o.parentNode.removeChild(o)}else{o.parentNode.removeChild(o);fe(te().body,"htmx:oobErrorNoTarget",{content:o})}return e}function Te(){const e=f("#--htmx-preserve-pantry--");if(e){for(const t of[...e.children]){const n=f("#"+t.id);n.parentNode.moveBefore(t,n);n.remove()}e.remove()}}function qe(e){ie(x(e,"[hx-preserve], [data-hx-preserve]"),function(e){const t=a(e,"id");const n=te().getElementById(t);if(n!=null){if(e.moveBefore){let e=f("#--htmx-preserve-pantry--");if(e==null){te().body.insertAdjacentHTML("afterend","
");e=f("#--htmx-preserve-pantry--")}e.moveBefore(n,null)}else{e.parentNode.replaceChild(n,e)}}})}function Ae(l,e,c){ie(e.querySelectorAll("[id]"),function(t){const n=ee(t,"id");if(n&&n.length>0){const r=n.replace("'","\\'");const o=t.tagName.replace(":","\\:");const e=p(l);const i=e&&e.querySelector(o+"[id='"+r+"']");if(i&&i!==e){const s=t.cloneNode();Oe(t,i);c.tasks.push(function(){Oe(t,s)})}}})}function Le(e){return function(){G(e,Q.config.addedClass);Ft(ce(e));Ne(p(e));ae(e,"htmx:load")}}function Ne(e){const t="[autofocus]";const n=$(h(e,t)?e:e.querySelector(t));if(n!=null){n.focus()}}function c(e,t,n,r){Ae(e,n,r);while(n.childNodes.length>0){const o=n.firstChild;K(ce(o),Q.config.addedClass);e.insertBefore(o,t);if(o.nodeType!==Node.TEXT_NODE&&o.nodeType!==Node.COMMENT_NODE){r.tasks.push(Le(o))}}}function Ie(e,t){let n=0;while(n0}function $e(h,d,p,g){if(!g){g={}}let m=null;let n=null;let e=function(){re(g.beforeSwapCallback);h=w(h);const r=g.contextElement?y(g.contextElement,false):te();const e=document.activeElement;let t={};t={elt:e,start:e?e.selectionStart:null,end:e?e.selectionEnd:null};const o=Sn(h);if(p.swapStyle==="textContent"){h.textContent=d}else{let n=P(d);o.title=g.title||n.title;if(g.historyRequest){n=n.querySelector("[hx-history-elt],[data-hx-history-elt]")||n}if(g.selectOOB){const i=g.selectOOB.split(",");for(let t=0;t0){b().setTimeout(n,p.settleDelay)}else{n()}};let t=Q.config.globalViewTransitions;if(p.hasOwnProperty("transition")){t=p.transition}const r=g.contextElement||te();if(t&&ae(r,"htmx:beforeTransition",g.eventInfo)&&typeof Promise!=="undefined"&&document.startViewTransition){const o=new Promise(function(e,t){m=e;n=t});const i=e;e=function(){document.startViewTransition(function(){i();return o})}}try{if(p?.swapDelay&&p.swapDelay>0){b().setTimeout(e,p.swapDelay)}else{e()}}catch(e){fe(r,"htmx:swapError",g.eventInfo);re(n);throw e}}function Je(e,t,n){const r=e.getResponseHeader(t);if(r.indexOf("{")===0){const o=v(r);for(const i in o){if(o.hasOwnProperty(i)){let e=o[i];if(k(e)){n=e.target!==undefined?e.target:n}else{e={value:e}}ae(n,i,e)}}}else{const s=r.split(",");for(let e=0;e0){const s=o[0];if(s==="]"){e--;if(e===0){if(n===null){t=t+"true"}o.shift();t+=")})";try{const l=On(r,function(){return Function(t)()},function(){return true});l.source=t;return l}catch(e){fe(te().body,"htmx:syntax:error",{error:e,source:t});return null}}}else if(s==="["){e++}if(tt(s,n,i)){t+="(("+i+"."+s+") ? ("+i+"."+s+") : (window."+s+"))"}else{t=t+s}n=o.shift()}}}function O(e,t){let n="";while(e.length>0&&!t.test(e[0])){n+=e.shift()}return n}function rt(e){let t;if(e.length>0&&Ye.test(e[0])){e.shift();t=O(e,Qe).trim();e.shift()}else{t=O(e,E)}return t}const ot="input, textarea, select";function it(e,t,n){const r=[];const o=et(t);do{O(o,C);const l=o.length;const c=O(o,/[,\[\s]/);if(c!==""){if(c==="every"){const u={trigger:"every"};O(o,C);u.pollInterval=d(O(o,/[,\[\s]/));O(o,C);var i=nt(e,o,"event");if(i){u.eventFilter=i}r.push(u)}else{const f={trigger:c};var i=nt(e,o,"event");if(i){f.eventFilter=i}O(o,C);while(o.length>0&&o[0]!==","){const a=o.shift();if(a==="changed"){f.changed=true}else if(a==="once"){f.once=true}else if(a==="consume"){f.consume=true}else if(a==="delay"&&o[0]===":"){o.shift();f.delay=d(O(o,E))}else if(a==="from"&&o[0]===":"){o.shift();if(Ye.test(o[0])){var s=rt(o)}else{var s=O(o,E);if(s==="closest"||s==="find"||s==="next"||s==="previous"){o.shift();const h=rt(o);if(h.length>0){s+=" "+h}}}f.from=s}else if(a==="target"&&o[0]===":"){o.shift();f.target=rt(o)}else if(a==="throttle"&&o[0]===":"){o.shift();f.throttle=d(O(o,E))}else if(a==="queue"&&o[0]===":"){o.shift();f.queue=O(o,E)}else if(a==="root"&&o[0]===":"){o.shift();f[a]=rt(o)}else if(a==="threshold"&&o[0]===":"){o.shift();f[a]=O(o,E)}else{fe(e,"htmx:syntax:error",{token:o.shift()})}O(o,C)}r.push(f)}}if(o.length===l){fe(e,"htmx:syntax:error",{token:o.shift()})}O(o,C)}while(o[0]===","&&o.shift());if(n){n[t]=r}return r}function st(e){const t=a(e,"hx-trigger");let n=[];if(t){const r=Q.config.triggerSpecsCache;n=r&&r[t]||it(e,t,r)}if(n.length>0){return n}else if(h(e,"form")){return[{trigger:"submit"}]}else if(h(e,'input[type="button"], input[type="submit"]')){return[{trigger:"click"}]}else if(h(e,ot)){return[{trigger:"change"}]}else{return[{trigger:"click"}]}}function lt(e){oe(e).cancelled=true}function ct(e,t,n){const r=oe(e);r.timeout=b().setTimeout(function(){if(se(e)&&r.cancelled!==true){if(!pt(n,e,Bt("hx:poll:trigger",{triggerSpec:n,target:e}))){t(e)}ct(e,t,n)}},n.pollInterval)}function ut(e){return location.hostname===e.hostname&&ee(e,"href")&&ee(e,"href").indexOf("#")!==0}function ft(e){return g(e,Q.config.disableSelector)}function at(t,n,e){if(t instanceof HTMLAnchorElement&&ut(t)&&(t.target===""||t.target==="_self")||t.tagName==="FORM"&&String(ee(t,"method")).toLowerCase()!=="dialog"){n.boosted=true;let r,o;if(t.tagName==="A"){r="get";o=ee(t,"href")}else{const i=ee(t,"method");r=i?i.toLowerCase():"get";o=ee(t,"action");if(o==null||o===""){o=location.href}if(r==="get"&&o.includes("?")){o=o.replace(/\?[^#]+/,"")}}e.forEach(function(e){gt(t,function(e,t){const n=ce(e);if(ft(n)){S(n);return}he(r,o,n,t)},n,e,true)})}}function ht(e,t){if(e.type==="submit"||e.type==="click"){t=ce(e.target)||t;if(t.tagName==="FORM"){return true}if(t.form&&t.type==="submit"){return true}t=t.closest("a");if(t&&t.href&&(t.getAttribute("href")==="#"||t.getAttribute("href").indexOf("#")!==0)){return true}}return false}function dt(e,t){return oe(e).boosted&&e instanceof HTMLAnchorElement&&t.type==="click"&&(t.ctrlKey||t.metaKey)}function pt(e,t,n){const r=e.eventFilter;if(r){try{return r.call(t,n)!==true}catch(e){const o=r.source;fe(te().body,"htmx:eventFilter:error",{error:e,source:o});return true}}return false}function gt(l,c,e,u,f){const a=oe(l);let t;if(u.from){t=m(l,u.from)}else{t=[l]}if(u.changed){if(!("lastValue"in a)){a.lastValue=new WeakMap}t.forEach(function(e){if(!a.lastValue.has(u)){a.lastValue.set(u,new WeakMap)}a.lastValue.get(u).set(e,e.value)})}ie(t,function(i){const s=function(e){if(!se(l)){i.removeEventListener(u.trigger,s);return}if(dt(l,e)){return}if(f||ht(e,l)){e.preventDefault()}if(pt(u,l,e)){return}const t=oe(e);t.triggerSpec=u;if(t.handledFor==null){t.handledFor=[]}if(t.handledFor.indexOf(l)<0){t.handledFor.push(l);if(u.consume){e.stopPropagation()}if(u.target&&e.target){if(!h(ce(e.target),u.target)){return}}if(u.once){if(a.triggeredOnce){return}else{a.triggeredOnce=true}}if(u.changed){const n=e.target;const r=n.value;const o=a.lastValue.get(u);if(o.has(n)&&o.get(n)===r){return}o.set(n,r)}if(a.delayed){clearTimeout(a.delayed)}if(a.throttle){return}if(u.throttle>0){if(!a.throttle){ae(l,"htmx:trigger");c(l,e);a.throttle=b().setTimeout(function(){a.throttle=null},u.throttle)}}else if(u.delay>0){a.delayed=b().setTimeout(function(){ae(l,"htmx:trigger");c(l,e)},u.delay)}else{ae(l,"htmx:trigger");c(l,e)}}};if(e.listenerInfos==null){e.listenerInfos=[]}e.listenerInfos.push({trigger:u.trigger,listener:s,on:i});i.addEventListener(u.trigger,s)})}let mt=false;let yt=null;function xt(){if(!yt){yt=function(){mt=true};window.addEventListener("scroll",yt);window.addEventListener("resize",yt);setInterval(function(){if(mt){mt=false;ie(te().querySelectorAll("[hx-trigger*='revealed'],[data-hx-trigger*='revealed']"),function(e){bt(e)})}},200)}}function bt(e){if(!s(e,"data-hx-revealed")&&F(e)){e.setAttribute("data-hx-revealed","true");const t=oe(e);if(t.initHash){ae(e,"revealed")}else{e.addEventListener("htmx:afterProcessNode",function(){ae(e,"revealed")},{once:true})}}}function vt(e,t,n,r){const o=function(){if(!n.loaded){n.loaded=true;ae(e,"htmx:trigger");t(e)}};if(r>0){b().setTimeout(o,r)}else{o()}}function wt(t,n,e){let i=false;ie(de,function(r){if(s(t,"hx-"+r)){const o=a(t,"hx-"+r);i=true;n.path=o;n.verb=r;e.forEach(function(e){St(t,e,n,function(e,t){const n=ce(e);if(ft(n)){S(n);return}he(r,o,n,t)})})}});return i}function St(r,e,t,n){if(e.trigger==="revealed"){xt();gt(r,n,t,e);bt(ce(r))}else if(e.trigger==="intersect"){const o={};if(e.root){o.root=ue(r,e.root)}if(e.threshold){o.threshold=parseFloat(e.threshold)}const i=new IntersectionObserver(function(t){for(let e=0;e0){t.polling=true;ct(ce(r),n,e)}else{gt(r,n,t,e)}}function Et(e){const t=ce(e);if(!t){return false}const n=t.attributes;for(let e=0;e", "+e).join(""));return o}else{return[]}}function Tt(e){const t=At(e.target);const n=Nt(e);if(n){n.lastButtonClicked=t}}function qt(e){const t=Nt(e);if(t){t.lastButtonClicked=null}}function At(e){return g(ce(e),"button, input[type='submit']")}function Lt(e){return e.form||g(e,"form")}function Nt(e){const t=At(e.target);if(!t){return}const n=Lt(t);return oe(n)}function It(e){e.addEventListener("click",Tt);e.addEventListener("focusin",Tt);e.addEventListener("focusout",qt)}function Pt(t,e,n){const r=oe(t);if(!Array.isArray(r.onHandlers)){r.onHandlers=[]}let o;const i=function(e){On(t,function(){if(ft(t)){return}if(!o){o=new Function("event",n)}o.call(t,e)})};t.addEventListener(e,i);r.onHandlers.push({event:e,listener:i})}function Dt(t){De(t);for(let e=0;eQ.config.historyCacheSize){i.shift()}while(i.length>0){try{sessionStorage.setItem("htmx-history-cache",JSON.stringify(i));break}catch(e){fe(te().body,"htmx:historyCacheError",{cause:e,cache:i});i.shift()}}}function Jt(t){if(!B()){return null}t=U(t);const n=v(sessionStorage.getItem("htmx-history-cache"))||[];for(let e=0;e=200&&this.status<400){r.response=this.response;ae(te().body,"htmx:historyCacheMissLoad",r);$e(r.historyElt,r.response,n,{contextElement:r.historyElt,historyRequest:true});_t(r.path);ae(te().body,"htmx:historyRestore",{path:e,cacheMiss:true,serverResponse:r.response})}else{fe(te().body,"htmx:historyCacheMissLoadError",r)}};if(ae(te().body,"htmx:historyCacheMiss",r)){t.send()}}function en(e){Gt();e=e||location.pathname+location.search;const t=Jt(e);if(t){const n={swapStyle:"innerHTML",swapDelay:0,settleDelay:0,scroll:t.scroll};const r={path:e,item:t,historyElt:zt(),swapSpec:n};if(ae(te().body,"htmx:historyCacheHit",r)){$e(r.historyElt,t.content,n,{contextElement:r.historyElt,title:t.title});_t(r.path);ae(te().body,"htmx:historyRestore",r)}}else{if(Q.config.refreshOnHistoryMiss){Q.location.reload(true)}else{Qt(e)}}}function tn(e){let t=we(e,"hx-indicator");if(t==null){t=[e]}ie(t,function(e){const t=oe(e);t.requestCount=(t.requestCount||0)+1;e.classList.add.call(e.classList,Q.config.requestClass)});return t}function nn(e){let t=we(e,"hx-disabled-elt");if(t==null){t=[]}ie(t,function(e){const t=oe(e);t.requestCount=(t.requestCount||0)+1;e.setAttribute("disabled","");e.setAttribute("data-disabled-by-htmx","")});return t}function rn(e,t){ie(e.concat(t),function(e){const t=oe(e);t.requestCount=(t.requestCount||1)-1});ie(e,function(e){const t=oe(e);if(t.requestCount===0){e.classList.remove.call(e.classList,Q.config.requestClass)}});ie(t,function(e){const t=oe(e);if(t.requestCount===0){e.removeAttribute("disabled");e.removeAttribute("data-disabled-by-htmx")}})}function on(t,n){for(let e=0;en.indexOf(e)<0)}else{e=e.filter(e=>e!==n)}r.delete(t);ie(e,e=>r.append(t,e))}}function un(e){if(e instanceof HTMLSelectElement&&e.multiple){return M(e.querySelectorAll("option:checked")).map(function(e){return e.value})}if(e instanceof HTMLInputElement&&e.files){return M(e.files)}return e.value}function fn(t,n,r,e,o){if(e==null||on(t,e)){return}else{t.push(e)}if(sn(e)){const i=ee(e,"name");ln(i,un(e),n);if(o){an(e,r)}}if(e instanceof HTMLFormElement){ie(e.elements,function(e){if(t.indexOf(e)>=0){cn(e.name,un(e),n)}else{t.push(e)}if(o){an(e,r)}});new FormData(e).forEach(function(e,t){if(e instanceof File&&e.name===""){return}ln(t,e,n)})}}function an(e,t){const n=e;if(n.willValidate){ae(n,"htmx:validation:validate");if(!n.checkValidity()){t.push({elt:n,message:n.validationMessage,validity:n.validity});ae(n,"htmx:validation:failed",{message:n.validationMessage,validity:n.validity})}}}function hn(n,e){for(const t of e.keys()){n.delete(t)}e.forEach(function(e,t){n.append(t,e)});return n}function dn(e,t){const n=[];const r=new FormData;const o=new FormData;const i=[];const s=oe(e);if(s.lastButtonClicked&&!se(s.lastButtonClicked)){s.lastButtonClicked=null}let l=e instanceof HTMLFormElement&&e.noValidate!==true||a(e,"hx-validate")==="true";if(s.lastButtonClicked){l=l&&s.lastButtonClicked.formNoValidate!==true}if(t!=="get"){fn(n,o,i,Lt(e),l)}fn(n,r,i,e,l);if(s.lastButtonClicked||e.tagName==="BUTTON"||e.tagName==="INPUT"&&ee(e,"type")==="submit"){const u=s.lastButtonClicked||e;const f=ee(u,"name");ln(f,u.value,o)}const c=we(e,"hx-include");ie(c,function(e){fn(n,r,i,ce(e),l);if(!h(e,"form")){ie(p(e).querySelectorAll(ot),function(e){fn(n,r,i,e,l)})}});hn(r,o);return{errors:i,formData:r,values:kn(r)}}function pn(e,t,n){if(e!==""){e+="&"}if(String(n)==="[object Object]"){n=JSON.stringify(n)}const r=encodeURIComponent(n);e+=encodeURIComponent(t)+"="+r;return e}function gn(e){e=Pn(e);let n="";e.forEach(function(e,t){n=pn(n,t,e)});return n}function mn(e,t,n){const r={"HX-Request":"true","HX-Trigger":ee(e,"id"),"HX-Trigger-Name":ee(e,"name"),"HX-Target":a(t,"id"),"HX-Current-URL":location.href};Cn(e,"hx-headers",false,r);if(n!==undefined){r["HX-Prompt"]=n}if(oe(e).boosted){r["HX-Boosted"]="true"}return r}function yn(n,e){const t=ne(e,"hx-params");if(t){if(t==="none"){return new FormData}else if(t==="*"){return n}else if(t.indexOf("not ")===0){ie(t.slice(4).split(","),function(e){e=e.trim();n.delete(e)});return n}else{const r=new FormData;ie(t.split(","),function(t){t=t.trim();if(n.has(t)){n.getAll(t).forEach(function(e){r.append(t,e)})}});return r}}else{return n}}function xn(e){return!!ee(e,"href")&&ee(e,"href").indexOf("#")>=0}function bn(e,t){const n=t||ne(e,"hx-swap");const r={swapStyle:oe(e).boosted?"innerHTML":Q.config.defaultSwapStyle,swapDelay:Q.config.defaultSwapDelay,settleDelay:Q.config.defaultSettleDelay};if(Q.config.scrollIntoViewOnBoost&&oe(e).boosted&&!xn(e)){r.show="top"}if(n){const s=X(n);if(s.length>0){for(let e=0;e0?o.join(":"):null;r.scroll=u;r.scrollTarget=i}else if(l.indexOf("show:")===0){const f=l.slice(5);var o=f.split(":");const a=o.pop();var i=o.length>0?o.join(":"):null;r.show=a;r.showTarget=i}else if(l.indexOf("focus-scroll:")===0){const h=l.slice("focus-scroll:".length);r.focusScroll=h=="true"}else if(e==0){r.swapStyle=l}else{R("Unknown modifier in hx-swap: "+l)}}}}return r}function vn(e){return ne(e,"hx-encoding")==="multipart/form-data"||h(e,"form")&&ee(e,"enctype")==="multipart/form-data"}function wn(t,n,r){let o=null;jt(n,function(e){if(o==null){o=e.encodeParameters(t,r,n)}});if(o!=null){return o}else{if(vn(n)){return hn(new FormData,Pn(r))}else{return gn(r)}}}function Sn(e){return{tasks:[],elts:[e]}}function En(e,t){const n=e[0];const r=e[e.length-1];if(t.scroll){var o=null;if(t.scrollTarget){o=ce(ue(n,t.scrollTarget))}if(t.scroll==="top"&&(n||o)){o=o||n;o.scrollTop=0}if(t.scroll==="bottom"&&(r||o)){o=o||r;o.scrollTop=o.scrollHeight}if(typeof t.scroll==="number"){b().setTimeout(function(){window.scrollTo(0,t.scroll)},0)}}if(t.show){var o=null;if(t.showTarget){let e=t.showTarget;if(t.showTarget==="window"){e="body"}o=ce(ue(n,e))}if(t.show==="top"&&(n||o)){o=o||n;o.scrollIntoView({block:"start",behavior:Q.config.scrollBehavior})}if(t.show==="bottom"&&(r||o)){o=o||r;o.scrollIntoView({block:"end",behavior:Q.config.scrollBehavior})}}}function Cn(r,e,o,i,s){if(i==null){i={}}if(r==null){return i}const l=a(r,e);if(l){let e=l.trim();let t=o;if(e==="unset"){return null}if(e.indexOf("javascript:")===0){e=e.slice(11);t=true}else if(e.indexOf("js:")===0){e=e.slice(3);t=true}if(e.indexOf("{")!==0){e="{"+e+"}"}let n;if(t){n=On(r,function(){if(s){return Function("event","return ("+e+")").call(r,s)}else{return Function("return ("+e+")").call(r)}},{})}else{n=v(e)}for(const c in n){if(n.hasOwnProperty(c)){if(i[c]==null){i[c]=n[c]}}}}return Cn(ce(u(r)),e,o,i,s)}function On(e,t,n){if(Q.config.allowEval){return t()}else{fe(e,"htmx:evalDisallowedError");return n}}function Rn(e,t,n){return Cn(e,"hx-vars",true,n,t)}function Hn(e,t,n){return Cn(e,"hx-vals",false,n,t)}function Tn(e,t){return le(Rn(e,t),Hn(e,t))}function qn(t,n,r){if(r!==null){try{t.setRequestHeader(n,r)}catch(e){t.setRequestHeader(n,encodeURIComponent(r));t.setRequestHeader(n+"-URI-AutoEncoded","true")}}}function An(t){if(t.responseURL){try{const e=new URL(t.responseURL);return e.pathname+e.search}catch(e){fe(te().body,"htmx:badResponseUrl",{url:t.responseURL})}}}function H(e,t){return t.test(e.getAllResponseHeaders())}function Ln(t,n,r){t=t.toLowerCase();if(r){if(r instanceof Element||typeof r==="string"){return he(t,n,null,null,{targetOverride:w(r)||ve,returnPromise:true})}else{let e=w(r.target);if(r.target&&!e||r.source&&!e&&!w(r.source)){e=ve}return he(t,n,w(r.source),r.event,{handler:r.handler,headers:r.headers,values:r.values,targetOverride:e,swapOverride:r.swap,select:r.select,returnPromise:true})}}else{return he(t,n,null,null,{returnPromise:true})}}function Nn(e){const t=[];while(e){t.push(e);e=e.parentElement}return t}function In(e,t,n){const r=new URL(t,location.protocol!=="about:"?location.href:window.origin);const o=location.protocol!=="about:"?location.origin:window.origin;const i=o===r.origin;if(Q.config.selfRequestsOnly){if(!i){return false}}return ae(e,"htmx:validateUrl",le({url:r,sameHost:i},n))}function Pn(e){if(e instanceof FormData)return e;const t=new FormData;for(const n in e){if(e.hasOwnProperty(n)){if(e[n]&&typeof e[n].forEach==="function"){e[n].forEach(function(e){t.append(n,e)})}else if(typeof e[n]==="object"&&!(e[n]instanceof Blob)){t.append(n,JSON.stringify(e[n]))}else{t.append(n,e[n])}}}return t}function Dn(r,o,e){return new Proxy(e,{get:function(t,e){if(typeof e==="number")return t[e];if(e==="length")return t.length;if(e==="push"){return function(e){t.push(e);r.append(o,e)}}if(typeof t[e]==="function"){return function(){t[e].apply(t,arguments);r.delete(o);t.forEach(function(e){r.append(o,e)})}}if(t[e]&&t[e].length===1){return t[e][0]}else{return t[e]}},set:function(e,t,n){e[t]=n;r.delete(o);e.forEach(function(e){r.append(o,e)});return true}})}function kn(o){return new Proxy(o,{get:function(e,t){if(typeof t==="symbol"){const r=Reflect.get(e,t);if(typeof r==="function"){return function(){return r.apply(o,arguments)}}else{return r}}if(t==="toJSON"){return()=>Object.fromEntries(o)}if(t in e){if(typeof e[t]==="function"){return function(){return o[t].apply(o,arguments)}}}const n=o.getAll(t);if(n.length===0){return undefined}else if(n.length===1){return n[0]}else{return Dn(e,t,n)}},set:function(t,n,e){if(typeof n!=="string"){return false}t.delete(n);if(e&&typeof e.forEach==="function"){e.forEach(function(e){t.append(n,e)})}else if(typeof e==="object"&&!(e instanceof Blob)){t.append(n,JSON.stringify(e))}else{t.append(n,e)}return true},deleteProperty:function(e,t){if(typeof t==="string"){e.delete(t)}return true},ownKeys:function(e){return Reflect.ownKeys(Object.fromEntries(e))},getOwnPropertyDescriptor:function(e,t){return Reflect.getOwnPropertyDescriptor(Object.fromEntries(e),t)}})}function he(t,n,r,o,i,k){let s=null;let l=null;i=i!=null?i:{};if(i.returnPromise&&typeof Promise!=="undefined"){var e=new Promise(function(e,t){s=e;l=t})}if(r==null){r=te().body}const M=i.handler||jn;const F=i.select||null;if(!se(r)){re(s);return e}const c=i.targetOverride||ce(Ee(r));if(c==null||c==ve){fe(r,"htmx:targetError",{target:ne(r,"hx-target")});re(l);return e}let u=oe(r);const f=u.lastButtonClicked;if(f){const A=ee(f,"formaction");if(A!=null){n=A}const L=ee(f,"formmethod");if(L!=null){if(de.includes(L.toLowerCase())){t=L}else{re(s);return e}}}const a=ne(r,"hx-confirm");if(k===undefined){const K=function(e){return he(t,n,r,o,i,!!e)};const G={target:c,elt:r,path:n,verb:t,triggeringEvent:o,etc:i,issueRequest:K,question:a};if(ae(r,"htmx:confirm",G)===false){re(s);return e}}let h=r;let d=ne(r,"hx-sync");let p=null;let X=false;if(d){const N=d.split(":");const I=N[0].trim();if(I==="this"){h=Se(r,"hx-sync")}else{h=ce(ue(r,I))}d=(N[1]||"drop").trim();u=oe(h);if(d==="drop"&&u.xhr&&u.abortable!==true){re(s);return e}else if(d==="abort"){if(u.xhr){re(s);return e}else{X=true}}else if(d==="replace"){ae(h,"htmx:abort")}else if(d.indexOf("queue")===0){const W=d.split(" ");p=(W[1]||"last").trim()}}if(u.xhr){if(u.abortable){ae(h,"htmx:abort")}else{if(p==null){if(o){const P=oe(o);if(P&&P.triggerSpec&&P.triggerSpec.queue){p=P.triggerSpec.queue}}if(p==null){p="last"}}if(u.queuedRequests==null){u.queuedRequests=[]}if(p==="first"&&u.queuedRequests.length===0){u.queuedRequests.push(function(){he(t,n,r,o,i)})}else if(p==="all"){u.queuedRequests.push(function(){he(t,n,r,o,i)})}else if(p==="last"){u.queuedRequests=[];u.queuedRequests.push(function(){he(t,n,r,o,i)})}re(s);return e}}const g=new XMLHttpRequest;u.xhr=g;u.abortable=X;const m=function(){u.xhr=null;u.abortable=false;if(u.queuedRequests!=null&&u.queuedRequests.length>0){const e=u.queuedRequests.shift();e()}};const B=ne(r,"hx-prompt");if(B){var y=prompt(B);if(y===null||!ae(r,"htmx:prompt",{prompt:y,target:c})){re(s);m();return e}}if(a&&!k){if(!confirm(a)){re(s);m();return e}}let x=mn(r,c,y);if(t!=="get"&&!vn(r)){x["Content-Type"]="application/x-www-form-urlencoded"}if(i.headers){x=le(x,i.headers)}const U=dn(r,t);let b=U.errors;const j=U.formData;if(i.values){hn(j,Pn(i.values))}const V=Pn(Tn(r,o));const v=hn(j,V);let w=yn(v,r);if(Q.config.getCacheBusterParam&&t==="get"){w.set("org.htmx.cache-buster",ee(c,"id")||"true")}if(n==null||n===""){n=location.href}const S=Cn(r,"hx-request");const _=oe(r).boosted;let E=Q.config.methodsThatUseUrlParams.indexOf(t)>=0;const C={boosted:_,useUrlParams:E,formData:w,parameters:kn(w),unfilteredFormData:v,unfilteredParameters:kn(v),headers:x,elt:r,target:c,verb:t,errors:b,withCredentials:i.credentials||S.credentials||Q.config.withCredentials,timeout:i.timeout||S.timeout||Q.config.timeout,path:n,triggeringEvent:o};if(!ae(r,"htmx:configRequest",C)){re(s);m();return e}n=C.path;t=C.verb;x=C.headers;w=Pn(C.parameters);b=C.errors;E=C.useUrlParams;if(b&&b.length>0){ae(r,"htmx:validation:halted",C);re(s);m();return e}const z=n.split("#");const $=z[0];const O=z[1];let R=n;if(E){R=$;const Z=!w.keys().next().done;if(Z){if(R.indexOf("?")<0){R+="?"}else{R+="&"}R+=gn(w);if(O){R+="#"+O}}}if(!In(r,R,C)){fe(r,"htmx:invalidPath",C);re(l);m();return e}g.open(t.toUpperCase(),R,true);g.overrideMimeType("text/html");g.withCredentials=C.withCredentials;g.timeout=C.timeout;if(S.noHeaders){}else{for(const D in x){if(x.hasOwnProperty(D)){const Y=x[D];qn(g,D,Y)}}}const H={xhr:g,target:c,requestConfig:C,etc:i,boosted:_,select:F,pathInfo:{requestPath:n,finalRequestPath:R,responsePath:null,anchor:O}};g.onload=function(){try{const t=Nn(r);H.pathInfo.responsePath=An(g);M(r,H);if(H.keepIndicators!==true){rn(T,q)}ae(r,"htmx:afterRequest",H);ae(r,"htmx:afterOnLoad",H);if(!se(r)){let e=null;while(t.length>0&&e==null){const n=t.shift();if(se(n)){e=n}}if(e){ae(e,"htmx:afterRequest",H);ae(e,"htmx:afterOnLoad",H)}}re(s)}catch(e){fe(r,"htmx:onLoadError",le({error:e},H));throw e}finally{m()}};g.onerror=function(){rn(T,q);fe(r,"htmx:afterRequest",H);fe(r,"htmx:sendError",H);re(l);m()};g.onabort=function(){rn(T,q);fe(r,"htmx:afterRequest",H);fe(r,"htmx:sendAbort",H);re(l);m()};g.ontimeout=function(){rn(T,q);fe(r,"htmx:afterRequest",H);fe(r,"htmx:timeout",H);re(l);m()};if(!ae(r,"htmx:beforeRequest",H)){re(s);m();return e}var T=tn(r);var q=nn(r);ie(["loadstart","loadend","progress","abort"],function(t){ie([g,g.upload],function(e){e.addEventListener(t,function(e){ae(r,"htmx:xhr:"+t,{lengthComputable:e.lengthComputable,loaded:e.loaded,total:e.total})})})});ae(r,"htmx:beforeSend",H);const J=E?null:wn(g,r,w);g.send(J);return e}function Mn(e,t){const n=t.xhr;let r=null;let o=null;if(H(n,/HX-Push:/i)){r=n.getResponseHeader("HX-Push");o="push"}else if(H(n,/HX-Push-Url:/i)){r=n.getResponseHeader("HX-Push-Url");o="push"}else if(H(n,/HX-Replace-Url:/i)){r=n.getResponseHeader("HX-Replace-Url");o="replace"}if(r){if(r==="false"){return{}}else{return{type:o,path:r}}}const i=t.pathInfo.finalRequestPath;const s=t.pathInfo.responsePath;const l=ne(e,"hx-push-url");const c=ne(e,"hx-replace-url");const u=oe(e).boosted;let f=null;let a=null;if(l){f="push";a=l}else if(c){f="replace";a=c}else if(u){f="push";a=s||i}if(a){if(a==="false"){return{}}if(a==="true"){a=s||i}if(t.pathInfo.anchor&&a.indexOf("#")===-1){a=a+"#"+t.pathInfo.anchor}return{type:f,path:a}}else{return{}}}function Fn(e,t){var n=new RegExp(e.code);return n.test(t.toString(10))}function Xn(e){for(var t=0;t ."+Q.config.indicatorClass+"{opacity:0} ."+Q.config.requestClass+" ."+Q.config.indicatorClass+"{opacity:1; transition: opacity 200ms ease-in;} ."+Q.config.requestClass+"."+Q.config.indicatorClass+"{opacity:1; transition: opacity 200ms ease-in;} ")}}function Zn(){const e=te().querySelector('meta[name="htmx-config"]');if(e){return v(e.content)}else{return null}}function Yn(){const e=Zn();if(e){Q.config=le(Q.config,e)}}Gn(function(){Yn();Wn();let e=te().body;Ft(e);const t=te().querySelectorAll("[hx-trigger='restored'],[data-hx-trigger='restored']");e.addEventListener("htmx:abort",function(e){const t=e.target;const n=oe(t);if(n&&n.xhr){n.xhr.abort()}});const n=window.onpopstate?window.onpopstate.bind(window):null;window.onpopstate=function(e){if(e.state&&e.state.htmx){en();ie(t,function(e){ae(e,"htmx:restored",{document:te(),triggerEvent:ae})})}else{if(n){n(e)}}};b().setTimeout(function(){ae(e,"htmx:load",{});e=null},0)});return Q}(); \ No newline at end of file diff --git a/site/htmx-templates/pages/about.j2 b/site/htmx-templates/pages/about.j2 new file mode 100644 index 0000000..d1be8c6 --- /dev/null +++ b/site/htmx-templates/pages/about.j2 @@ -0,0 +1,173 @@ +
+ +
+
+
+
+ Site logo + Jesús Pérez Profile Picture +
+

+ {{ texts["about-page-title"] }} +

+

+ {{ texts["about-page-subtitle"] }} +

+
+
+
+ +
+
+
+
+

+ {{ texts["about-my-journey"] }} +

+
+

{{ texts["about-journey-paragraph-1"] }}

+

{{ texts["about-journey-paragraph-2"] }}

+

{{ texts["about-journey-paragraph-3"] }}

+
+
+ +
+

+ {{ texts["about-philosophy-approach"] }} +

+
    +
  • + + {{ texts["about-open-source-first"] }} +
  • +
  • + + {{ texts["about-self-hosted-solutions"] }} +
  • +
  • + + {{ texts["about-performance-security-design"] }} +
  • +
  • + + {{ texts["about-pragmatic-solutions"] }} +
  • +
  • + + {{ texts["about-knowledge-transfer-empowerment"] }} +
  • +
+
+
+
+
+ +
+
+
+

{{ texts["about-technical-expertise"] }}

+

{{ texts["about-technologies-work-with"] }}

+
+ +
+
+
+ 🦀 +
+

{{ texts["about-rust-development-title"] }}

+
    +
  • {{ texts["about-systems-programming-item"] }}
  • +
  • {{ texts["about-web-applications-item"] }}
  • +
  • {{ texts["about-cli-tools-automation"] }}
  • +
  • {{ texts["about-performance-optimization-item"] }}
  • +
  • {{ texts["about-memory-safety-concurrency"] }}
  • +
+
+ +
+
+ 🏗️ +
+

{{ texts["about-infrastructure-title"] }}

+
    +
  • {{ texts["about-kubernetes-container"] }}
  • +
  • {{ texts["about-cicd-pipeline-design"] }}
  • +
  • {{ texts["about-monitoring-observability"] }}
  • +
  • {{ texts["about-self-hosted-alternatives"] }}
  • +
  • {{ texts["about-security-compliance"] }}
  • +
+
+ +
+
+ 🌐 +
+

{{ texts["about-web3-blockchain-title"] }}

+
    +
  • {{ texts["about-polkadot-ecosystem-dev"] }}
  • +
  • {{ texts["about-substrate-blockchain"] }}
  • +
  • {{ texts["about-smart-contract-dev"] }}
  • +
  • {{ texts["about-dapp-architecture"] }}
  • +
  • {{ texts["about-cross-chain-communication"] }}
  • +
+
+
+
+
+ +
+
+
+

{{ texts["about-why-self-hosted-open-source"] }}

+
+ +
+
+
+

{{ texts["about-control-privacy"] }}

+

{{ texts["about-control-privacy-desc"] }}

+
+
+

{{ texts["about-cost-effectiveness"] }}

+

{{ texts["about-cost-effectiveness-desc"] }}

+
+
+

{{ texts["about-resilience"] }}

+

{{ texts["about-resilience-desc"] }}

+
+
+ +
+
+

{{ texts["about-performance"] }}

+

{{ texts["about-performance-desc"] }}

+
+
+

{{ texts["about-customization"] }}

+

{{ texts["about-customization-desc"] }}

+
+
+

{{ texts["about-community"] }}

+

{{ texts["about-community-desc"] }}

+
+
+
+
+
+ +
+
+

{{ texts["about-lets-work-together"] }}

+

{{ texts["about-work-together-desc"] }}

+ +
+
+ +
diff --git a/site/htmx-templates/pages/about_project.j2 b/site/htmx-templates/pages/about_project.j2 new file mode 100644 index 0000000..529a141 --- /dev/null +++ b/site/htmx-templates/pages/about_project.j2 @@ -0,0 +1,116 @@ +{# Level-aware project About — rendered when about.json declares kind="project". + Context: `about` (view-model), `lang`, `texts`. + + Uses the ontoref `.doc-*` document chrome (styles/custom.css) so the SSR site + matches outreach/web. GENERIC over a block vocabulary: a section is + { key, title, blocks }, each block a typed primitive (prose | timeline | + cards | links | graph | kv). New SECTIONS of an existing block type are + projector-only changes (gen-about-pages.nu); only a brand-new block TYPE + touches this template. Path/href values are `| safe` (trusted contract + values, not user input). Bilingual fields indexed by `lang`. #} +
+
+ +
+

{{ about.level_id }}

+

{% if lang == "es" %}Acerca de{% else %}About{% endif %} {{ about.level_id }}

+ {% if about.hero and about.hero[lang] %} +

{{ about.hero[lang] }}

+ {% endif %} +
+ + {% set about_img = "ontoref-about-es" if lang == "es" else "ontoref-about-en" %} + + + + {% if lang == 'es' %}Acerca de{% else %}About{% endif %} {{ about.level_id }} + + + {% if about.sections | length > 1 %} + + {% endif %} + + {% for section in about.sections %} +
+

{{ section.title[lang] }}

+ + {% for block in section.blocks %} + + {% if block.type == "prose" %} +
{{ (block.html_es if lang == "es" and block.html_es else block.html) | safe }}
+ + {% elif block.type == "timeline" %} + {% if block.started %} +

{% if lang == "es" %}Desde{% else %}Started{% endif %} {{ block.started }}

+ {% endif %} +
    + {% for m in block.items %} +
  • + {{ m.date }} + {{ m.text[lang] }} +
  • + {% endfor %} +
+ + {% elif block.type == "cards" %} + {% for grp in block.groups %} + {% if grp.label %}

{{ grp.label }} ({{ grp.items | length }})

{% endif %} +
    + {% for n in grp.items %} + {% set card_body = n.body_es if lang == "es" and n.body_es else n.body %} +
  • +

    {{ n.title_es if lang == "es" and n.title_es else n.title }}{% if n.meta %} · {{ n.meta }}{% endif %}

    + {% if card_body %}

    {{ card_body }}

    {% endif %} +
  • + {% endfor %} +
+ {% endfor %} + + {% elif block.type == "links" %} + {% for grp in block.groups %} + {% if grp.label %}

{{ grp.label }} ({{ grp.items | length }})

{% endif %} + + {% endfor %} + {% if block.browse and block.browse.href %} + {{ block.browse.label[lang] }} → + {% endif %} + + {% elif block.type == "graph" %} + {% if block.svg %}
{{ block.svg | safe }}
{% endif %} + {% if block.live and block.live.href %} + ↗ {{ block.live.label[lang] }} + {% endif %} + + {% elif block.type == "kv" %} + + {% for r in block.rows %} + + {% endfor %} +
{{ r.k_es if lang == "es" and r.k_es else r.k }}{{ r.v_es if lang == "es" and r.v_es else r.v }}
+ + {% endif %} + + {% endfor %} +
+ {% endfor %} + +
+
+ +{% include "partials/image-zoom.j2" %} diff --git a/site/htmx-templates/pages/contact.j2 b/site/htmx-templates/pages/contact.j2 new file mode 100644 index 0000000..70057c1 --- /dev/null +++ b/site/htmx-templates/pages/contact.j2 @@ -0,0 +1,241 @@ +
+
+ + {# ── Header ── #} +
+

{{ texts["contact-page-title"] }}

+

{{ texts["contact-page-subtitle"] | safe }}

+ {% set contact_img = "ontoref-contactar" if lang == "es" else "ontoref-contact-en" %} + + + + {{ texts['contact-page-title'] }} + +
+ + {# ── Main grid: sidebar (1) + form (2) ── #} +
+ + {# ── Contact info sidebar ── #} +
+
+

{{ texts["contact-lets-connect"] }}

+ +
+ {# Email #} +
+
+
+ 📧 +
+
+
+

{{ texts["contact-email-contact"] }}

+

hello@jesusperez.pro

+

{{ texts["contact-best-for-detailed"] }}

+
+
+ + {# Telegram #} +
+
+
+ 💬 +
+
+
+

{{ texts["contact-telegram-contact"] }}

+

@jesusperez_dev

+

{{ texts["contact-quick-questions"] }}

+
+
+ + {# Schedule a call #} +
+
+
+ 📞 +
+
+
+

{{ texts["contact-schedule-call"] }}

+

{{ texts["contact-consultation-30min"] }}

+ + {{ texts["contact-book-time-slot"] }} + +
+
+
+ + {# Professional networks #} + +
+
+ + {# ── Contact form (2 cols) ── #} +
+
+
+

{{ texts["contact-send-message"] }}

+

{{ texts["contact-contact-form-description"] }}

+
+ + + + {# Name + Email row #} +
+
+ + +
+
+ + +
+
+ + {# Subject #} +
+ + +
+ + {# Message #} +
+ + +
+ +
+ +
+ +
+ +
+ + {# Response time box #} +
+

{{ texts["contact-response-time"] }}

+

{{ texts["contact-response-time-text"] }}

+
+
+
+ + {# ── FAQ section ── #} +
+
+

{{ texts["contact-frequently-asked-questions"] }}

+
+
+

{{ texts["contact-faq-what-projects"] }}

+

{{ texts["contact-faq-what-projects-answer"] }}

+
+
+

{{ texts["contact-faq-remote-teams"] }}

+

{{ texts["contact-faq-remote-teams-answer"] }}

+
+
+

{{ texts["contact-faq-pricing-approach"] }}

+

{{ texts["contact-faq-pricing-answer"] }}

+
+
+

{{ texts["contact-faq-existing-projects"] }}

+

{{ texts["contact-faq-existing-projects-answer"] }}

+
+
+
+
+ + {# ── Other ways to connect ── #} +
+
+

{{ texts["contact-other-ways-connect"] }}

+
+ +
+
+ 🚀 +
+

{{ texts["contact-project-consultation"] }}

+

{{ texts["contact-consultation-help"] }}

+ + {{ texts["contact-request-quote"] }} + +
+ +
+
+ 🧑‍🏫 +
+

{{ texts["contact-mentoring-training"] }}

+

{{ texts["contact-mentoring-help"] }}

+ + {{ texts["contact-learn-more"] }} + +
+ +
+
+ 🎤 +
+

{{ texts["contact-speaking-events"] }}

+

{{ texts["contact-speaking-help"] }}

+ + {{ texts["contact-get-in-touch"] }} + +
+ +
+
+
+ +
+
+ +{% include "partials/image-zoom.j2" %} diff --git a/site/htmx-templates/pages/home.j2 b/site/htmx-templates/pages/home.j2 new file mode 100644 index 0000000..ecf16b8 --- /dev/null +++ b/site/htmx-templates/pages/home.j2 @@ -0,0 +1,220 @@ +
+ {# TEMP: hide the Posts/Recipes door CTAs for now (remove this + + {# Hero — three-door vestibule, projected from .ontoref/positioning/spine.ncl (ADR-057). + Never an exposition of the core: the doors route, the prize is revealed once inside. + Tech features live below the fold (Three Pillars / How it works). #} +
+
+ + {{ texts["home-hero-badge"] }} + + {# A · hero — the language-specific banner carries logo + headline + sub-hero + the + four value nodes (baked in) and scales fluidly at every width, so it shows at all + breakpoints. The h1/sub stay sr-only for SEO + a11y; the is click-to-zoom. #} +

{{ texts["spine-hero"] }}

+

{{ texts["spine-sub"] }}

+ {% set hero_img = "ontoref-home-seguro" if lang == "es" else "ontoref-home-sure-footed" %} + + + + {{ texts['spine-hero'] }} + + {# rotating definition-phrases by audience + focus #} +

+ {{ texts["spine-hero"] }} +

+ + {# C · four doors — primary nav: each switches taglines AND reveals its panel #} +
+ + + + + +
+ {# door panels — hidden until a door is clicked; 'all' hides all. A teaser that + ROUTES to the door landing (full value-prop) + the live graph, never argues. #} + + {# D · prize — the reward once inside #} +

+ {{ texts["spine-prize"] }} +

+ + {# E · close — bookend with the hero #} +

{{ texts["spine-close"] }}

+
+
+ + {# Three Pillars #} +
+
+
+

{{ texts["home-pillars-title"] }}

+

{{ texts["home-pillars-subtitle"] }}

+
+
+ +
+
+ + {{ texts["home-pillar-impact-label"] }} + +
+

{{ texts["home-pillar-impact-title"] }}

+

{{ texts["home-pillar-impact-desc"] }}

+
+ +
+
+ + {{ texts["home-pillar-witness-label"] }} + +
+

{{ texts["home-pillar-witness-title"] }}

+

{{ texts["home-pillar-witness-desc"] }}

+
+ +
+
+ + {{ texts["home-pillar-shared-label"] }} + +
+

{{ texts["home-pillar-shared-title"] }}

+

{{ texts["home-pillar-shared-desc"] }}

+
+ +
+
+
+ + {# Architecture image — config-driven & optional: shown only when the consumer's + home FTL declares `home-arch-img-light` (per-site content, never hardcoded). #} + {% if texts["home-arch-img-light"] %} +
+
+

{{ texts["home-arch-title"] }}

+ {# The SVG carries its own dark surface + rounded corners — no wrapper card, or + its background double-frames and clashes. Shown in both themes; wrapped in + so the global lightbox makes it click-to-zoom. #} +
+ + {{ texts['home-arch-title'] }} + +

{{ texts["home-arch-caption"] }}

+
+
+
+ {% endif %} + + {# How it works #} +
+
+
+

{{ texts["home-how-title"] }}

+

{{ texts["home-how-subtitle"] }}

+
+
+ +
+
1
+

{{ texts["home-how-step1-title"] }}

+

{{ texts["home-how-step1-desc"] }}

+
+ +
+
2
+

{{ texts["home-how-step2-title"] }}

+

{{ texts["home-how-step2-desc"] }}

+
+ +
+
3
+

{{ texts["home-how-step3-title"] }}

+

{{ texts["home-how-step3-desc"] }}

+
+ +
+
+
+ + {# CTA #} +
+
+

{{ texts["home-cta-title"] }}

+

{{ texts["home-cta-desc"] }}

+ +
+
+ + {# Hero tagline rotator — typed.js (typewriter) + native fade fallback #} + + +
diff --git a/site/htmx-templates/pages/not_found.j2 b/site/htmx-templates/pages/not_found.j2 new file mode 100644 index 0000000..b21e8bd --- /dev/null +++ b/site/htmx-templates/pages/not_found.j2 @@ -0,0 +1,22 @@ +{# 404 Not Found page body. Context: path, lang #} +
+ +

+ {% if lang == "es" %}Página no encontrada{% else %}Page not found{% endif %} +

+

+ {% if lang == "es" %} + La ruta {{ path }} no existe. + {% else %} + The path {{ path }} does not exist. + {% endif %} +

+ + {% if lang == "es" %}← Volver al inicio{% else %}← Back to home{% endif %} + +
diff --git a/site/htmx-templates/pages/post.j2 b/site/htmx-templates/pages/post.j2 new file mode 100644 index 0000000..9f808ce --- /dev/null +++ b/site/htmx-templates/pages/post.j2 @@ -0,0 +1,96 @@ +{# Content post viewer — website override of the framework post.j2. + Differences from the framework default: + - No max-w-3xl / mx-auto on article (column width already constrained by grid). + - post-content-body on the prose div so the lead-image CSS rule applies. + - overflow-hidden on article to prevent images from bleeding into the sidebar. #} +
+ {% include "partials/content_header.j2" %} + + {% if item.excerpt %} +
+ {{ item.excerpt }} +
+ {% endif %} + +
+ {{ body_html | safe }} +
+ + {# ── Per-post engagement: rating · comment · views ───────────────────────── + Endpoints live in the rustelo_server `post-engagement` feature. Layout is + styled in custom.css (pe-*), not Tailwind utilities, so spacing is reliable. #} +
+ + {# Rating CTA — invites a vote; loads current average + your own vote on render. #} +
+ + {% if lang == "es" %}¿Te ha resultado útil? Valóralo{% else %}Was this useful? Rate it{% endif %} + +
+
+ + + + + +
+
+
+ + {# Comment CTA — invites participation; opens the modal form in #modal-slot. #} +
+
+ + {% if lang == "es" %}¿Tienes algo que aportar?{% else %}Got something to add?{% endif %} + + + {% if lang == "es" %}Cuéntame qué opinas, qué sugieres, o si seguimos explorando este tema.{% else %}Tell me what you think, what you'd suggest, or whether we should keep exploring this topic.{% endif %} + +
+ +
+ + {# Views — subtle line; beacon fires once on load and fills #post-views. #} +
+ · + {% if lang == "es" %}lecturas{% else %}reads{% endif %} +
+
+ + {% include "partials/cta/subscribe.j2" %} + + {% if item.translations and item.translations | length > 1 %} +
+

+ {% if lang == "es" %}También disponible en:{% else %}Also available in:{% endif %} + {% for tlang in item.translations %} + {% if tlang != lang %} + {% set tslug = item.translation_slugs[tlang] %} + {{ tlang | upper }} + {% endif %} + {% endfor %} +

+
+ {% endif %} +
+ +{% include "partials/image-zoom.j2" %} diff --git a/site/htmx-templates/pages/services.j2 b/site/htmx-templates/pages/services.j2 new file mode 100644 index 0000000..01172f1 --- /dev/null +++ b/site/htmx-templates/pages/services.j2 @@ -0,0 +1,211 @@ +{# ontoref Services — tier-structured. + The protocol is a gift (voluntary-adoption, protocol-not-runtime); what is + offered is help OPERATING its highest tier (tier-2: operations, witness, + governance — via-001) and TRAINING/COACHING to climb the lower tiers (tier-0 + declarative, tier-1 substrate). Bilingual via `texts[...]`; ES deep-link + anchors via data-anchor-es. Mirrors the jpl services design (ds-* classes). #} +
+ +
+
+

+ {{ texts["services-page-title"] }} +

+

+ {{ texts["services-page-subtitle"] }} +

+

+ {{ texts["services-page-note"] }} +

+
+
+ + {# ── Tier 2: the proposal — operate the operations/governance surface ─────── #} +
+
+
+ + {{ texts["services-tier2-eyebrow"] }} + +

{{ texts["services-tier2-title"] }}

+

+ {{ texts["services-tier2-desc"] }} +

+
+ +
+
+

{{ texts["services-tier2-ops-title"] }}

+

{{ texts["services-tier2-ops-desc"] }}

+
+
+

{{ texts["services-tier2-audit-title"] }}

+

{{ texts["services-tier2-audit-desc"] }}

+
+
+

{{ texts["services-tier2-validators-title"] }}

+

{{ texts["services-tier2-validators-desc"] }}

+
+
+

{{ texts["services-tier2-migration-title"] }}

+

{{ texts["services-tier2-migration-desc"] }}

+
+
+ +
+

{{ texts["services-what-you-get"] }}

+
+
{{ texts["services-get-witnessed-history"] }}
+
{{ texts["services-get-validators"] }}
+
{{ texts["services-get-knowledge-transfer"] }}
+
{{ texts["services-get-no-lockin"] }}
+
{{ texts["services-get-open-protocol"] }}
+
+
+
+
+ + {# ── Training & coaching for the lower tiers — use or learn ───────────────── #} +
+
+
+ {% set training_img = "ontoref-formacion" if lang == "es" else "ontoref-training" %} + + + + {{ texts['services-learn-title'] }} + +

{{ texts["services-learn-title"] }}

+

{{ texts["services-learn-desc"] }}

+ {% set coaching_img = "ontoref-coaching-es" if lang == "es" else "ontoref-coaching-en" %} + + + + {{ texts['services-learn-title'] }} + +
+ +
+
+ {{ texts["services-coaching-eyebrow"] }} +

{{ texts["services-coaching-title"] }}

+

{{ texts["services-coaching-desc"] }}

+
+
+
+

{{ texts["services-coaching-1to1-title"] }}

+ {{ texts["services-coaching-1to1-note"] }} +
+

{{ texts["services-coaching-1to1-desc"] }}

+
+
+
+

{{ texts["services-coaching-ondaod-title"] }}

+ {{ texts["services-coaching-ondaod-note"] }} +
+

{{ texts["services-coaching-ondaod-desc"] }}

+
+
+
+

{{ texts["services-coaching-adr-title"] }}

+ {{ texts["services-coaching-adr-note"] }} +
+

{{ texts["services-coaching-adr-desc"] }}

+
+
+
+

{{ texts["services-coaching-ongoing-title"] }}

+ {{ texts["services-coaching-ongoing-note"] }} +
+

{{ texts["services-coaching-ongoing-desc"] }}

+
+
+
+ +
+ {{ texts["services-training-eyebrow"] }} +

{{ texts["services-training-title"] }}

+

{{ texts["services-training-desc"] }}

+
+
+

{{ texts["services-training-courses-title"] }}

+

{{ texts["services-training-courses-desc"] }}

+
+
+

{{ texts["services-training-workshops-title"] }}

+

{{ texts["services-training-workshops-desc"] }}

+
+
+

{{ texts["services-training-talks-title"] }}

+

{{ texts["services-training-talks-desc"] }}

+
+
+

{{ texts["services-training-content-title"] }}

+

{{ texts["services-training-content-desc"] }}

+
+
+
+
+
+
+ + {# ── The three tiers — which one fits (taglines from tier-taglines.ncl) ───── #} +
+
+
+

{{ texts["services-tiers-title"] }}

+

{{ texts["services-tiers-desc"] }}

+
+
+
+ {{ texts["services-tier0-label"] }} +

{{ texts["services-tier0-title"] }}

+

{{ texts["services-tier0-desc"] }}

+
+
+ {{ texts["services-tier1-label"] }} +

{{ texts["services-tier1-title"] }}

+

{{ texts["services-tier1-desc"] }}

+
+
+ {{ texts["services-tier2-label"] }} +

{{ texts["services-tier2-card-title"] }}

+

{{ texts["services-tier2-card-desc"] }}

+
+
+
+
+ +
+
+ {% set action_img = "ontoref-accion" if lang == "es" else "ontoref-action" %} + + + + {{ texts['services-cta-title'] }} + +

{{ texts["services-cta-title"] }}

+

{{ texts["services-cta-desc"] }}

+ +
+
+ +
+ +{% include "partials/image-zoom.j2" %} diff --git a/site/htmx-templates/pages/static.j2 b/site/htmx-templates/pages/static.j2 new file mode 100644 index 0000000..58d7b94 --- /dev/null +++ b/site/htmx-templates/pages/static.j2 @@ -0,0 +1,31 @@ +
+
+
+

{{ texts[page_id ~ "-page-title"] | default(page_id) }}

+ {% if texts[page_id ~ "-page-subtitle"] %} +

{{ texts[page_id ~ "-page-subtitle"] | safe }}

+ {% endif %} +
+
+ +
+
+ {# Each page's content is driven by its FTL translations. #} + {# Common structure: description paragraph + sections keyed by page_id prefix. #} + {% if texts[page_id ~ "-page-description"] %} +

{{ texts[page_id ~ "-page-description"] }}

+ {% endif %} + + {# Product pages: show a minimal overview with a "learn more" link pattern. #} + {% set products = ["syntaxis", "vapora", "rustelo", "provisioning", "stratumiops", "kogral", "typedialog", "ontoref", "secretumvault"] %} + {% if page_id in products %} + {% include "partials/product_page.j2" %} + {% endif %} + + {# Auth pages: show the login form fragment inline. #} + {% if page_id == "login" %} + {% include "partials/login_form.j2" %} + {% endif %} +
+
+
diff --git a/site/htmx-templates/partials/auth/otp_code.j2 b/site/htmx-templates/partials/auth/otp_code.j2 new file mode 100644 index 0000000..c4766e8 --- /dev/null +++ b/site/htmx-templates/partials/auth/otp_code.j2 @@ -0,0 +1,43 @@ +
+ +

{{ check_inbox }}

+

{{ sent_to | safe }}

+ + {% if error %} + + {% endif %} + + + + {% if return_url %}{% endif %} + + + + + + + + +
diff --git a/site/htmx-templates/partials/auth/otp_email.j2 b/site/htmx-templates/partials/auth/otp_email.j2 new file mode 100644 index 0000000..045f237 --- /dev/null +++ b/site/htmx-templates/partials/auth/otp_email.j2 @@ -0,0 +1,26 @@ +
+ +

{{ subtitle }}

+ + {% if error %} + + {% endif %} + + + {% if return_url %}{% endif %} + + + +
diff --git a/site/htmx-templates/partials/auth/otp_gdpr.j2 b/site/htmx-templates/partials/auth/otp_gdpr.j2 new file mode 100644 index 0000000..a8568a1 --- /dev/null +++ b/site/htmx-templates/partials/auth/otp_gdpr.j2 @@ -0,0 +1,39 @@ +
+ +

{{ gdpr_title }}

+

{{ gdpr_description }}

+ + {% if error %} + + {% endif %} + + + {% if return_url %}{% endif %} + + + + + + +
diff --git a/site/htmx-templates/partials/auth/otp_modal.j2 b/site/htmx-templates/partials/auth/otp_modal.j2 new file mode 100644 index 0000000..fb6741d --- /dev/null +++ b/site/htmx-templates/partials/auth/otp_modal.j2 @@ -0,0 +1,25 @@ + diff --git a/site/htmx-templates/partials/content/card.j2 b/site/htmx-templates/partials/content/card.j2 new file mode 100644 index 0000000..3475fb9 --- /dev/null +++ b/site/htmx-templates/partials/content/card.j2 @@ -0,0 +1,3 @@ +
+

{{ title }}

+
diff --git a/site/htmx-templates/partials/content/grid.j2 b/site/htmx-templates/partials/content/grid.j2 new file mode 100644 index 0000000..6867a9c --- /dev/null +++ b/site/htmx-templates/partials/content/grid.j2 @@ -0,0 +1,18 @@ +
+
+ + + +
+ {{ initial_grid_html | safe }} +
diff --git a/site/htmx-templates/partials/content_header.j2 b/site/htmx-templates/partials/content_header.j2 new file mode 100644 index 0000000..9df0fac --- /dev/null +++ b/site/htmx-templates/partials/content_header.j2 @@ -0,0 +1,33 @@ +{# Shared header for content items: title, subtitle, meta row, tags. #} +{# Expected context vars: item (UnifiedContentItem fields), lang #} +
+

{{ item.title }}

+ {% if item.subtitle %} +

{{ item.subtitle }}

+ {% endif %} +
+ {% if item.author %} + {{ item.author }} + + {% endif %} + {% if item.created_at %} + + {% endif %} + {% if item.read_time %} + + {{ item.read_time }} + {% endif %} +
+ {% if item.tags %} +
+ {% for tag in item.tags %} + {{ tag }} + {% endfor %} +
+ {% endif %} +
diff --git a/site/htmx-templates/partials/cta/subscribe.j2 b/site/htmx-templates/partials/cta/subscribe.j2 new file mode 100644 index 0000000..3a0770c --- /dev/null +++ b/site/htmx-templates/partials/cta/subscribe.j2 @@ -0,0 +1,106 @@ +{# Subscribe CTA — config-driven newsletter call-to-action. + Banner (heading/subtext + buttons) rendered at the foot of content grids and + below post ratings. The primary button opens a lightbox holding the + listmonk subscription form, which POSTs straight to `newsletter.action` for + the single list `newsletter.list_uuid`. Endpoint/list/copy come from the + `newsletter` global (htmx_env.rs ← site.ncl's sibling newsletter.ncl); nothing + listmonk-specific is hardcoded here except listmonk's own fixed field names + (email, name, nonce honeypot, l, consent). The listmonk host must be allowed + in the CSP form-action or the browser blocks the POST. #} +{% if newsletter and newsletter.enabled and newsletter.action %} +{% set nl_heading = newsletter.heading[lang] or newsletter.heading['en'] %} +{% set nl_subtext = newsletter.subtext[lang] or newsletter.subtext['en'] %} +{% set nl_cta = newsletter.cta_label[lang] or newsletter.cta_label['en'] %} +{% set f = newsletter.form %} + + +{% endif %} diff --git a/site/htmx-templates/partials/image-zoom.j2 b/site/htmx-templates/partials/image-zoom.j2 new file mode 100644 index 0000000..75436f0 --- /dev/null +++ b/site/htmx-templates/partials/image-zoom.j2 @@ -0,0 +1,113 @@ +{# Global in-page lightbox. Click to zoom, full-screen modal overlay. + Handles two media kinds via event delegation: + • images → shown as (browser-picked AVIF/WebP, currentSrc) + • .content-graph-mini-preview → its inline ego-network , cloned & enlarged + + Boost-proof: htmx navigation can drop the overlay node, so styles + overlay are + recreated ON DEMAND (looked up by id, rebuilt if missing) rather than cached in + closure refs. Listeners bind to `document` exactly once (document survives htmx + swaps), guarded by a window flag so re-running after a swap never double-binds. #} + diff --git a/site/htmx-templates/partials/login_form.j2 b/site/htmx-templates/partials/login_form.j2 new file mode 100644 index 0000000..1a1d4d1 --- /dev/null +++ b/site/htmx-templates/partials/login_form.j2 @@ -0,0 +1,38 @@ +
+
+
+ + +
+
+ + +
+
+ + {{ texts["auth-forgot-password"] }} +
+ +
+
+
diff --git a/site/htmx-templates/partials/nav/lang_selector.j2 b/site/htmx-templates/partials/nav/lang_selector.j2 new file mode 100644 index 0000000..b40878b --- /dev/null +++ b/site/htmx-templates/partials/nav/lang_selector.j2 @@ -0,0 +1,23 @@ +
+ + + + + + +
diff --git a/site/htmx-templates/partials/nav/login_button.j2 b/site/htmx-templates/partials/nav/login_button.j2 new file mode 100644 index 0000000..de7fd62 --- /dev/null +++ b/site/htmx-templates/partials/nav/login_button.j2 @@ -0,0 +1,5 @@ + diff --git a/site/htmx-templates/partials/nav/logout_button.j2 b/site/htmx-templates/partials/nav/logout_button.j2 new file mode 100644 index 0000000..6b6ecbd --- /dev/null +++ b/site/htmx-templates/partials/nav/logout_button.j2 @@ -0,0 +1,5 @@ + diff --git a/site/htmx-templates/partials/nav/theme_toggle.j2 b/site/htmx-templates/partials/nav/theme_toggle.j2 new file mode 100644 index 0000000..b98653e --- /dev/null +++ b/site/htmx-templates/partials/nav/theme_toggle.j2 @@ -0,0 +1,20 @@ + diff --git a/site/htmx-templates/partials/overlays/cookie_banner.j2 b/site/htmx-templates/partials/overlays/cookie_banner.j2 new file mode 100644 index 0000000..27ba941 --- /dev/null +++ b/site/htmx-templates/partials/overlays/cookie_banner.j2 @@ -0,0 +1,28 @@ + diff --git a/site/htmx-templates/partials/overlays/modal_slot.j2 b/site/htmx-templates/partials/overlays/modal_slot.j2 new file mode 100644 index 0000000..03d7a99 --- /dev/null +++ b/site/htmx-templates/partials/overlays/modal_slot.j2 @@ -0,0 +1 @@ + diff --git a/site/htmx-templates/partials/overlays/toast_container.j2 b/site/htmx-templates/partials/overlays/toast_container.j2 new file mode 100644 index 0000000..86efefc --- /dev/null +++ b/site/htmx-templates/partials/overlays/toast_container.j2 @@ -0,0 +1,7 @@ +
diff --git a/site/htmx-templates/partials/product_page.j2 b/site/htmx-templates/partials/product_page.j2 new file mode 100644 index 0000000..22f97eb --- /dev/null +++ b/site/htmx-templates/partials/product_page.j2 @@ -0,0 +1,30 @@ +
+ {% if texts[page_id ~ "-badge"] %} + {{ texts[page_id ~ "-badge"] }} + {% endif %} + + {% if texts[page_id ~ "-tagline"] %} +

{{ texts[page_id ~ "-tagline"] }}

+ {% endif %} + + {% if texts[page_id ~ "-hero-title"] %} +

{{ texts[page_id ~ "-hero-title"] }}

+ {% endif %} + + {% if texts[page_id ~ "-hero-subtitle"] %} +

{{ texts[page_id ~ "-hero-subtitle"] | safe }}

+ {% endif %} + +
+ {% if texts[page_id ~ "-cta-github"] %} + + {{ texts[page_id ~ "-cta-github"] }} + + {% endif %} + {% if texts[page_id ~ "-cta-get-started"] %} + + {{ texts[page_id ~ "-cta-get-started"] }} + + {% endif %} +
+
diff --git a/site/htmx-templates/partials/shell/footer.j2 b/site/htmx-templates/partials/shell/footer.j2 new file mode 100644 index 0000000..12d037a --- /dev/null +++ b/site/htmx-templates/partials/shell/footer.j2 @@ -0,0 +1,72 @@ +
+
+
+ +
+ {% if logo.show_in_footer %} + {% if logo.has_dark_variant %} + + {{ logo.alt }} + + + {% else %} + + {{ logo.alt }} + + {% endif %} + {% endif %} +

{{ company_desc }}

+

{{ copyright_text }}

+
+ + {% for section in sections %} +
+

{{ section.title }}

+ +
+ {% endfor %} + + {% if social_links %} +
+

{{ social_title }}

+
+ {% for social in social_links %} + {{ social.icon_html | safe }} + {% endfor %} +
+
+ {% endif %} + +
+ +
+ {{ built_label }} + + Rustelo + Rustelo + +
+
+
+ + diff --git a/site/htmx-templates/partials/shell/nav.j2 b/site/htmx-templates/partials/shell/nav.j2 new file mode 100644 index 0000000..6be8286 --- /dev/null +++ b/site/htmx-templates/partials/shell/nav.j2 @@ -0,0 +1,119 @@ + diff --git a/site/justfile b/site/justfile new file mode 100644 index 0000000..1a2b6c4 --- /dev/null +++ b/site/justfile @@ -0,0 +1,278 @@ +set shell := ["bash", "-c"] +set dotenv-load := true + +project_url := "https://ontoref.dev" +project_root := justfile_directory() +source_project := justfile_directory() + "/../../../website-htmx-rustelo/code" +rustelo_root := justfile_directory() + "/../../../rustelo/code" + +# `authoring` (not `content`) to avoid colliding with the flat content recipe below. +# Content-authoring toolkit via rustelo-content CLI: images | sync | validate | new +mod authoring ".just/content.just" + +default: + @just --list + @echo -e "\nproject_url = {{ project_url }}" + +# ── Server ────────────────────────────────────────────────────────────────── + +# Start the HTMX-SSR dev server (auto-detects site/ in PWD) +[no-cd] +dev: + lsof -ti:3030 | xargs kill -9 2>/dev/null || true + rustelo-htmx-server + +# ── Content ───────────────────────────────────────────────────────────────── + +# Positional filters: `just content projects` or `just content projects es`. +# A `type=`/`lang=` prefix is tolerated, so `just content type=projects lang=es` works too. +# The targeted (--content-type/--language) path is non-incremental — it always rewrites. +# +# The two trees own DIFFERENT files, so the mirror runs in both directions: +# site/r ← content_processor writes the content indexes here (canonical); +# the runtime server reads it (SITE_SERVER_ROOT_CONTENT=r). +# site/public/r ← the nu generators write about/adr-map/taglines/content_graph here, +# and this is the tree the deploy pack ships (site/public/**). +# So: content indexes go r → public/r (or nothing new ever reaches production), and +# the four nu artifacts come back public/r → r (or dev serves June's about/adr-map). +# A whole-tree rsync in ONE direction cannot be right for both, and the old one +# (public/r → r) clobbered each fresh index with the stale deploy-tree copy. +# Regenerate content indexes from markdown in site/content/, then reconcile both trees. +[no-cd] +content type="" lang="": + #!/usr/bin/env bash + set -e + # Brand-linkify blog "ontoref" mentions → /about (idempotent, source-level). + nu "{{ project_root }}/scripts/build/linkify-site.nu" "{{ project_root }}/site/content/blog/**/*.md" + # just args are POSITIONAL: `just content type=projects` binds type="type=projects". + # Strip an optional `key=` prefix so both positional and key=value forms work. + TYPE="{{ type }}"; TYPE="${TYPE#*=}" + LANG_ARG="{{ lang }}"; LANG_ARG="${LANG_ARG#*=}" + ARGS=() + [ -n "$TYPE" ] && ARGS+=(--content-type "$TYPE") + [ -n "$LANG_ARG" ] && ARGS+=(--language "$LANG_ARG") + content_processor "${ARGS[@]}" + # Content indexes → deploy tree. --delete is what keeps a renamed slug from living + # on as a live URL: content_processor prunes site/r, and without --delete the dead + # page would survive in public/r and ship to production. The four nu artifacts are + # excluded (site/r holds only stale copies that would overwrite the fresh originals + # gen-adr-map / gen-about-pages / gen-taglines just wrote) — and --exclude also + # protects them from --delete, since they legitimately exist only on this side. + rsync -a --delete \ + --exclude about.json --exclude adr-map.json \ + --exclude taglines.json --exclude content_graph.json \ + "{{ project_root }}/site/r/" "{{ project_root }}/site/public/r/" + # nu artifacts → serve tree, so `./run.sh` renders the current About/ADR map. + for f in about.json adr-map.json taglines.json content_graph.json; do + src="{{ project_root }}/site/public/r/$f" + if [ -f "$src" ]; then cp -f "$src" "{{ project_root }}/site/r/$f"; fi + done + +# Watch mode: auto-regenerate content on file changes +[no-cd] +content-watch type="" lang="": + #!/usr/bin/env bash + set -e + TYPE="{{ type }}"; TYPE="${TYPE#*=}" + LANG_ARG="{{ lang }}"; LANG_ARG="${LANG_ARG#*=}" + ARGS=(--watch) + [ -n "$TYPE" ] && ARGS+=(--content-type "$TYPE") + [ -n "$LANG_ARG" ] && ARGS+=(--language "$LANG_ARG") + content_processor "${ARGS[@]}" + +# Regenerate content_graph.json (mini-graphs + ONTOLOGY CONTEXT sidebar) +[no-cd] +graph daemon=project_url: + RUSTELO_ROOT="{{ rustelo_root }}" \ + ONTOREF_NICKEL_IMPORT_PATH="{{ project_root }}/../../code/ontology" \ + CONTENT_GRAPH_IMPL_ONTOLOGY="{{ project_root }}/../../.ontoref/ontology/core.ncl" \ + CONTENT_GRAPH_IMPL_ADRS_GLOB="{{ project_root }}/../../.ontoref/adrs/adr-[0-9]*.ncl" \ + CONTENT_GRAPH_FRAMEWORK_ONTOLOGY="{{ rustelo_root }}/../.ontoref/ontology/core.ncl" \ + CONTENT_GRAPH_FRAMEWORK_ADRS_GLOB="{{ rustelo_root }}/../.ontoref/adrs/adr-[0-9]*.ncl" \ + CONTENT_GRAPH_DAEMON_BASE="{{ daemon }}" \ + gen-content-graph + +# Regenerate ADR reference map from .ontoref/adrs → site/public/r/adr-map.json +[no-cd] +adr-map daemon=project_url: + nu "{{ project_root }}/scripts/build/gen-adr-map.nu" --root "{{ project_root }}/../.." --daemon "{{ daemon }}" + +# The web-usable form of `onre diagram` (served at /images/, the static asset root). +# Referenced by the About graph block, so must run before about-json. +# Project the ontology (core.ncl) → site/public/images/ontoref-diagram.svg. +[no-cd] +diagram: + nu "{{ project_root }}/scripts/build/gen-diagram.nu" --root "{{ project_root }}/../.." --out "{{ project_root }}/site/public/images/ontoref-diagram.svg" + nu "{{ project_root }}/scripts/build/gen-graph-page.nu" --root "{{ project_root }}/../.." --out "{{ project_root }}/site/public/images/ontoref-graph.html" + +# Makes /catalog (browse) + /catalog/{slug} serve as SSR content (runtime kind, no +# rebuild — registered in site/content/content-kinds.toml). +# Project .ontoref/catalog → catalog markdown, then build the content index. +[no-cd] +catalog-content: + nu "{{ project_root }}/scripts/build/gen-catalog-pages.nu" --root "{{ project_root }}/../.." --mode content --content-out "{{ project_root }}/site/content/catalog/en" + content_processor --content-type catalog + +# Hot-reloaded by the SSR /about handler (via pages/about_project.j2 when kind=project). +# daemon="" → the "see it live" graph link is emitted host-relative (/graph?…), +# because on the SSR site /graph is served by this same origin. The static +# outreach/web pages pass an absolute --daemon since they may be served elsewhere. +# Emit the level-aware About view-model → site/public/r/about.json (needs adr-map fresh). +[no-cd] +about-json daemon="": diagram + nu "{{ project_root }}/scripts/build/gen-about-pages.nu" \ + --root "{{ project_root }}/../.." \ + --emit-json "{{ project_root }}/site/public/r/about.json" \ + --daemon "{{ daemon }}" + +# ── Spine (bl-035) ──────────────────────────────────────────────────────────── + +# Generate spine landing pages from .ontoref/positioning/spine.ncl +[no-cd] +spine daemon=project_url: + nu "{{ project_root }}/scripts/build/gen-spine-pages.nu" \ + --root "{{ project_root }}/../.." \ + --content "{{ project_root }}/site/content" \ + --daemon "{{ daemon }}" + +# ── Taglines (rotating hero phrases) ────────────────────────────────────────── + +# Drift-checked at export (taglines-schema.ncl); the hero rotator fetches /r/taglines.json. +# Project .ontoref/positioning/taglines.ncl → site/public/r/taglines.json. +[no-cd] +taglines: + nu "{{ project_root }}/scripts/build/gen-taglines.nu" \ + --root "{{ project_root }}/../.." \ + --out "{{ project_root }}/site/public/r/taglines.json" + +# Projects code/Cargo.toml [workspace.package].version into the home hero badge — the +# one hand-written version literal on the site. Run after a version bump so the badge +# never drifts. Scoped to the home-hero-badge line, so other version strings are untouched. +# Stamp the canonical workspace version into the home hero badge (en + es). +[no-cd] +stamp-version: + #!/usr/bin/env nu + let ver = (open "{{ project_root }}/../../code/Cargo.toml" | get workspace.package.version) + let ftls = [ + "{{ project_root }}/site/i18n/locales/en/pages/home.ftl" + "{{ project_root }}/site/i18n/locales/es/pages/home.ftl" + ] + for f in $ftls { + let out = (open --raw $f | lines | each {|l| + if ($l | str starts-with "home-hero-badge") { + $l | str replace --all --regex 'v\d+\.\d+\.\d+' $"v($ver)" + } else { $l } + } | str join "\n") + $"($out)\n" | save --force $f + } + print $"stamped v($ver) into home-hero-badge \(en + es\)" + +# From the constellation canonical source (assets/vendor) into the site asset trees +# (_public source + public served mirror). +# Materialize the shared hero rotator + typed.js vendor into the site asset trees. +[no-cd] +rotator-assets: + cp -f "{{ project_root }}/../../assets/vendor/tagline-rotator.js" "{{ project_root }}/site/_public/js/tagline-rotator.js" + cp -f "{{ project_root }}/../../assets/vendor/typed.umd.js" "{{ project_root }}/site/_public/js/typed.umd.js" + cp -f "{{ project_root }}/../../assets/vendor/tagline-rotator.js" "{{ project_root }}/site/public/js/tagline-rotator.js" + cp -f "{{ project_root }}/../../assets/vendor/typed.umd.js" "{{ project_root }}/site/public/js/typed.umd.js" + +# assets/site/ is the source of truth; only the web formats are mirrored — PNG/JPG +# originals stay out of the deploy. Safe to re-run. +# Mirror web-optimised images (AVIF + WebP) from assets/site/ → site/public/images/. +[no-cd] +sync-site-images: + #!/usr/bin/env nu + let src = "{{ project_root }}/../../assets/site" + let dest = "{{ project_root }}/site/public/images" + mkdir $dest + let files = (glob $"($src)/*.{avif,webp}") + if ($files | is-empty) { + error make { msg: $"no AVIF/WebP found in ($src)" } + } + $files | each {|f| cp $f $dest } + print $"synced ($files | length) image\(s\) from ($src) → ($dest)" + +# ── Templates ─────────────────────────────────────────────────────────────── + +# Safe to re-run; overwrites htmx-templates/ from scratch — which is exactly why the +# site's own templates cannot live in the output. The assembler has two layers +# (framework defaults, then source-project templates) but the level chain has three: +# rustelo → website-htmx-rustelo → outreach/site. site/templates-overlay/ is the +# missing third slot: the only declared home for template work owned by THIS level. +# Anything edited straight into htmx-templates/ is destroyed on the next run — that is +# how the nav submenus were lost once already. +# Assemble htmx-templates: framework defaults → source-project templates → site overlay. +[no-cd] +templates: + nu "{{ source_project }}/scripts/build/assemble-htmx-templates.nu" \ + --project-templates "{{ source_project }}/crates/pages_htmx/templates" \ + --out "{{ project_root }}/htmx-templates" + cp -R "{{ project_root }}/site/templates-overlay/." "{{ project_root }}/htmx-templates/" + +# Proves `just templates` is lossless: reassemble into a temp tree and diff it against +# the live one. Any difference means htmx-templates/ holds work that exists nowhere +# else and the next `just templates` (or `just sync`, which calls it) will delete it — +# the fix is to move that work into site/templates-overlay/, never to skip the run. +# Gate: assert htmx-templates/ is exactly reproducible from its declared sources. +[no-cd] +templates-check: + #!/usr/bin/env bash + set -e + tmp=$(mktemp -d) + trap 'rm -rf "$tmp"' EXIT + nu "{{ source_project }}/scripts/build/assemble-htmx-templates.nu" \ + --project-templates "{{ source_project }}/crates/pages_htmx/templates" \ + --out "$tmp" >/dev/null + cp -R "{{ project_root }}/site/templates-overlay/." "$tmp/" + if diff -r "{{ project_root }}/htmx-templates" "$tmp" >/dev/null 2>&1; then + echo "templates-check: htmx-templates/ is reproducible — no unwitnessed work" + else + echo "templates-check: DRIFT — htmx-templates/ holds work absent from its sources:" + diff -rq "{{ project_root }}/htmx-templates" "$tmp" | sed "s|$tmp||g" + exit 1 + fi + +# Safe to re-run; overwrites htmx-assets/ from scratch. +# Materialize the framework htmx runtime (htmx.min.js + ext/*) into a top-level +# htmx-assets/ tree (sibling of htmx-templates/). Shipped to the PV by content.nu +# (distro.ncl deliverable) and served at /assets/htmx/* via HTMX_ASSETS_PATH — the +# runtime lives OUTSIDE site/public (router mounts /assets/htmx as its own ServeDir, +# not from the public tree). Dev masks this via the local-install wrapper's +# HTMX_ASSETS_PATH; prod has no such mount. Source of truth: templates/shared/htmx. +[no-cd] +htmx-assets: + rm -rf "{{ project_root }}/htmx-assets" + mkdir -p "{{ project_root }}/htmx-assets/ext" + cp -f "{{ rustelo_root }}/templates/shared/htmx/htmx.min.js" "{{ project_root }}/htmx-assets/htmx.min.js" + cp -R "{{ rustelo_root }}/templates/shared/htmx/ext/." "{{ project_root }}/htmx-assets/ext/" + +# ── Posts ───────────────────────────────────────────────────────────────────── + +# Brand-linkify mentions → /about, regenerate the content indexes, and rebuild the +# content graph (mini-graph + sidecar relations). Run after editing a post + .ncl sidecar. +# Build a post for publication: linkify + content indexes + content graph. +[no-cd] +post: content graph + +# 1. every brand mention is already linkified (no `just content` pending) +# 2. every post graph sidecar resolves into content_graph.json (no stale graph) +# Verify ALL posts are publication-ready without mutating anything (CI gate). +[no-cd] +posts-check: + #!/usr/bin/env bash + set -e + nu "{{ project_root }}/scripts/build/linkify-site.nu" --check "{{ project_root }}/site/content/blog/**/*.md" + nu "{{ project_root }}/scripts/build/check-graph-coverage.nu" "{{ project_root }}" + +# ── Sync ──────────────────────────────────────────────────────────────────── + +# Full resync: templates + htmx runtime + vendor assets + content indexes +# (no graph — that needs ONTOREF env) +[no-cd] +sync: templates htmx-assets rotator-assets content + +# Requires ONTOREF_NICKEL_IMPORT_PATH in .env. +# Full resync including content_graph + ADR map + About view-model. +[no-cd] +sync-full: templates htmx-assets rotator-assets content catalog-content graph adr-map about-json diff --git a/site/run-secure.sh b/site/run-secure.sh new file mode 100755 index 0000000..3941319 --- /dev/null +++ b/site/run-secure.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# Standalone secure launch: decrypt the SOPS secrets in-memory and inject them +# as ENV for the server (nothing plaintext hits disk). email.ncl reads +# ${SMTP_*}/${EMAIL_FROM} from that injected env. +# +# ./run-secure.sh +# +# Secrets file + age identity are overridable: +# SECRETS=/path/to/x.sops.env SOPS_AGE_KEY_FILE=/path/to/id.kage ./run-secure.sh +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +SECRETS="${SECRETS:-$SCRIPT_DIR/../../vault/ontoref-site.sops.env}" +# Default to the ontoref vault identity; in infra, SOPS_AGE_KEY_FILE points at +# the libre-wuji key (also a recipient of the blob). +export SOPS_AGE_KEY_FILE="${SOPS_AGE_KEY_FILE:-$SCRIPT_DIR/../../vault/ontoref.kage}" + +exec sops exec-env "$SECRETS" ./run.sh diff --git a/site/run.sh b/site/run.sh new file mode 100755 index 0000000..e123d06 --- /dev/null +++ b/site/run.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +lsof -ti:3030 | xargs kill -9 2>/dev/null || true + +# Secrets (SMTP creds, CRYPTO_KEY, …) are provided as ENV by the deploy/provisioning +# layer (SOPS-decrypted at run time — e.g. `sops exec-env`), not from a plaintext +# file here. email.ncl reads ${SMTP_*}/${EMAIL_FROM} from that injected env. + +# Only export what the server genuinely needs from here. NICKEL_IMPORT_PATH and +# SITE_CONFIG_PATH are intentionally LEFT to the binary's built-in defaults +# (which resolve site/contracts.ncl from ~/.local/share/rustelo/nickel) — +# exporting a relative path here broke NCL contract resolution. +export DATABASE_URL="${DATABASE_URL:-sqlite:data/dev.db}" +# Templates from the local runtime tree so .j2 edits need no cargo rebuild. +export HTMX_TEMPLATE_PATH="${HTMX_TEMPLATE_PATH:-htmx-templates}" +# Canonical public origin — feeds SEO canonical/OG URLs (seo::site_base_url). +# Without it the head falls back to http://127.0.0.1:3030. +export SITE_BASE_URL="${SITE_BASE_URL:-https://ontoref.dev}" +export SITE_NAME="${SITE_NAME:-ontoref}" +# Logo is config-driven: site/config/site.ncl [logo]. No env needed. + +# ── Email delivery (post comments + contact form) ────────────────────────── +# Owner inbox for new post comments and contact-form submissions. Change to your +# real address. Delivery also needs email.enabled=true in site/config/email.ncl +# (provider=console logs to stdout in dev; set provider=smtp + creds for real send). +export POST_MESSAGE_RECIPIENT="${POST_MESSAGE_RECIPIENT:-jpl@jesusperez.pro}" +export CONTACT_RECIPIENT="${CONTACT_RECIPIENT:-$POST_MESSAGE_RECIPIENT}" +export EMAIL_FROM="${EMAIL_FROM:-noreply@ontoref.dev}" +export EMAIL_FROM_NAME="${EMAIL_FROM_NAME:-$SITE_NAME}" + +# Quiet noisy framework tracing (these are informational logs the framework emits at +# WARN, not real problems): per-request routing conversions (🔄/🔍/✅), the category +# filter-index fallback ("using empty meta config"), NATS-not-configured, and the +# missing rustelo/migrations dir. Genuine warnings/errors (incl. RBAC audit) still show. +# Override by exporting RUST_LOG yourself before running. +export RUST_LOG="${RUST_LOG:-warn,rustelo_core_lib::routing=error,rustelo_core_lib::categories::cache=error,rustelo_server::nats=error,rustelo_server::migrations=error}" + +exec rustelo-htmx-server diff --git a/site/rustelo.manifest.toml b/site/rustelo.manifest.toml new file mode 100644 index 0000000..302cde6 --- /dev/null +++ b/site/rustelo.manifest.toml @@ -0,0 +1,44 @@ +# Rustelo Manifest — Single Source of Truth for Path Resolution +# PAP-Compliant: configuration-driven, no hardcoding, language-agnostic + +[manifest] +version = "1.0" +schema = "https://rustelo.dev/schemas/manifest/v1" + +[project] +name = "ontoref-website" +type = "rustelo-app" +description = "Bilingual EN/ES website built on the Rustelo framework" + +[paths] +# All paths relative to manifest location +content = "site/public/r" +config = "site/config" +routes = "site/config/routes" +i18n = "site/i18n" +assets = "site/public" +ui = "site/config" +build_output = "site/public" +wasm_output = "site/public/pkg" + +[discovery] +default_lang = "en" +languages = "auto" +content_types = "auto" + +[deployment] +namespace = "ontoref" +cp_node = "libre-wuji-cp-0" +base_url = "${BASE_URL:-http://localhost:3030}" +content_url = "/r" +content_root = "r" +cache_path = "rustelo-cache" +api_endpoint = "${API_ENDPOINT:-/api}" +database_url = "${DATABASE_URL:-sqlite:data/dev.db}" + +[build] +leptos_output_name = "website" +site_addr = "127.0.0.1:3030" +reload_port = 3031 +debug = 0 +cache_build_path = "rustelo-cache" diff --git a/site/rustelo/migrations/001_post_engagement_postgres.sql b/site/rustelo/migrations/001_post_engagement_postgres.sql new file mode 100644 index 0000000..ea14a7e --- /dev/null +++ b/site/rustelo/migrations/001_post_engagement_postgres.sql @@ -0,0 +1,35 @@ +-- Post engagement surface: unique views, 1-5 ratings, and post messages. +-- Applied by rustelo_server::migrations::MigrationRunner on PostgreSQL connect. +-- Identity is an opaque visitor_id (first-party `vid` cookie); no raw IP/UA is stored. + +CREATE TABLE IF NOT EXISTS post_view ( + post_id TEXT NOT NULL, + lang TEXT NOT NULL, + visitor_id TEXT NOT NULL, + first_seen TIMESTAMPTZ NOT NULL, + PRIMARY KEY (post_id, lang, visitor_id) +); + +CREATE TABLE IF NOT EXISTS post_rating ( + post_id TEXT NOT NULL, + visitor_id TEXT NOT NULL, + value INTEGER NOT NULL CHECK (value BETWEEN 1 AND 5), + updated_at TIMESTAMPTZ NOT NULL, + PRIMARY KEY (post_id, visitor_id) +); + +CREATE TABLE IF NOT EXISTS post_message ( + id TEXT PRIMARY KEY, + post_id TEXT NOT NULL, + post_title TEXT NOT NULL, + lang TEXT NOT NULL, + name TEXT NOT NULL, + email TEXT, + body TEXT NOT NULL, + status TEXT NOT NULL DEFAULT 'new', -- new | approved | spam (approved => public later) + visitor_id TEXT, + created_at TIMESTAMPTZ NOT NULL +); + +CREATE INDEX IF NOT EXISTS idx_post_view_post ON post_view(post_id, lang); +CREATE INDEX IF NOT EXISTS idx_post_message_post ON post_message(post_id, status); diff --git a/site/rustelo/migrations/001_post_engagement_sqlite.sql b/site/rustelo/migrations/001_post_engagement_sqlite.sql new file mode 100644 index 0000000..424c4e5 --- /dev/null +++ b/site/rustelo/migrations/001_post_engagement_sqlite.sql @@ -0,0 +1,35 @@ +-- Post engagement surface: unique views, 1-5 ratings, and post messages. +-- Applied by rustelo_server::migrations::MigrationRunner on SQLite connect. +-- Identity is an opaque visitor_id (first-party `vid` cookie); no raw IP/UA is stored. + +CREATE TABLE IF NOT EXISTS post_view ( + post_id TEXT NOT NULL, + lang TEXT NOT NULL, + visitor_id TEXT NOT NULL, + first_seen TEXT NOT NULL, + PRIMARY KEY (post_id, lang, visitor_id) +); + +CREATE TABLE IF NOT EXISTS post_rating ( + post_id TEXT NOT NULL, + visitor_id TEXT NOT NULL, + value INTEGER NOT NULL CHECK (value BETWEEN 1 AND 5), + updated_at TEXT NOT NULL, + PRIMARY KEY (post_id, visitor_id) +); + +CREATE TABLE IF NOT EXISTS post_message ( + id TEXT PRIMARY KEY, + post_id TEXT NOT NULL, + post_title TEXT NOT NULL, + lang TEXT NOT NULL, + name TEXT NOT NULL, + email TEXT, + body TEXT NOT NULL, + status TEXT NOT NULL DEFAULT 'new', -- new | approved | spam (approved => public later) + visitor_id TEXT, + created_at TEXT NOT NULL +); + +CREATE INDEX IF NOT EXISTS idx_post_view_post ON post_view(post_id, lang); +CREATE INDEX IF NOT EXISTS idx_post_message_post ON post_message(post_id, status); diff --git a/site/rustelo/migrations/002_post_message_consent_postgres.sql b/site/rustelo/migrations/002_post_message_consent_postgres.sql new file mode 100644 index 0000000..241f4e4 --- /dev/null +++ b/site/rustelo/migrations/002_post_message_consent_postgres.sql @@ -0,0 +1,5 @@ +-- Publish-consent for post messages: whether the visitor authorised showing +-- their comment publicly, plus timestamp + IP recorded as proof of consent. +ALTER TABLE post_message ADD COLUMN IF NOT EXISTS publish_consent BOOLEAN NOT NULL DEFAULT FALSE; +ALTER TABLE post_message ADD COLUMN IF NOT EXISTS consent_at TIMESTAMPTZ; +ALTER TABLE post_message ADD COLUMN IF NOT EXISTS consent_ip TEXT; diff --git a/site/rustelo/migrations/002_post_message_consent_sqlite.sql b/site/rustelo/migrations/002_post_message_consent_sqlite.sql new file mode 100644 index 0000000..5c3f830 --- /dev/null +++ b/site/rustelo/migrations/002_post_message_consent_sqlite.sql @@ -0,0 +1,5 @@ +-- Publish-consent for post messages: whether the visitor authorised showing +-- their comment publicly, plus timestamp + IP recorded as proof of consent. +ALTER TABLE post_message ADD COLUMN publish_consent INTEGER NOT NULL DEFAULT 0; +ALTER TABLE post_message ADD COLUMN consent_at TEXT; +ALTER TABLE post_message ADD COLUMN consent_ip TEXT; diff --git a/site/scripts/build/check-graph-coverage.nu b/site/scripts/build/check-graph-coverage.nu new file mode 100644 index 0000000..d3478cb --- /dev/null +++ b/site/scripts/build/check-graph-coverage.nu @@ -0,0 +1,38 @@ +#!/usr/bin/env nu + +# Verify every blog post that ships a graph sidecar (.ncl) is present in the +# generated content graph (site/public/r/content_graph.json). A sidecar with no +# entry means `just graph` was never re-run after the post was added — the post +# would render without its mini-graph and ONTOLOGY CONTEXT relations. +# +# Read-only. Exits non-zero on any gap, so it works as a CI gate. +# +# Usage (from outreach/site): +# nu scripts/build/check-graph-coverage.nu . + +def main [root: string = "."] { + let graph_path = ($root | path join "site/public/r/content_graph.json") + if not ($graph_path | path exists) { + error make { msg: $"content_graph.json not found at ($graph_path) — run `just graph`" } + } + + let keys = (open $graph_path | get mini | columns) + let sidecars = (glob $"($root)/site/content/blog/**/*.ncl" | where { |p| ($p | path basename) != "_index.ncl" }) + mut missing = 0 + + for s in $sidecars { + let id = (open --raw $s | parse --regex 'id\s*=\s*"(?[^"]+)"' | get id.0?) + if ($id == null) { + print $" ($s | path basename): no id field" + $missing = ($missing + 1) + } else if ($id not-in $keys) { + print $" MISSING in content graph: ($id) \(($s | path basename)\)" + $missing = ($missing + 1) + } + } + + if $missing > 0 { + error make { msg: $"graph-coverage: ($missing) post sidecar\(s\) absent from content_graph.json — run `just graph`" } + } + print $"graph-coverage: ($sidecars | length) sidecar\(s\) present in content graph" +} diff --git a/site/scripts/build/gen-about-pages.nu b/site/scripts/build/gen-about-pages.nu new file mode 100644 index 0000000..633f3b7 --- /dev/null +++ b/site/scripts/build/gen-about-pages.nu @@ -0,0 +1,382 @@ +#!/usr/bin/env nu + +# Project a level's About surface from its about.ncl contract + its .ontoref/. +# +# A single view-model is assembled daemon-free and shaped as a GENERIC BLOCK +# vocabulary so new sections are projector-only changes (no template/handler edit +# in the site framework). A section = { key, title, blocks }; each block is a +# typed render primitive the consumers know how to render: +# +# prose { type, html } raw HTML body +# timeline { type, started?, items:[{date, text{en,es}}] } +# cards { type, groups:[{label, items:[{title, meta?, body?}]}] } +# links { type, groups:[{label?, items:[{href, code?, text}]}], browse?{href,label{en,es}} } +# graph { type, svg?, live?{href, label{en,es}} } raw mini-SVG + daemon CTA +# kv { type, rows:[{k, v}] } +# +# Two consumers of the view-model, both rendering the SAME block vocabulary: +# static (Option A) : self-contained brand-wrapped HTML → outreach/web/about// +# --emit-json (B) : about.json for the SSR /about handler (hot-reloaded) +# +# Adding a section of an existing block type → edit this projector only. A brand +# new block type → also extend about_project.j2 (the render-primitive vocabulary, +# in the site framework) and vm-block-html below. That is the only coupling left. +# +# kind = 'Personal levels are skipped: their About stays about.j2. +# +# Usage (from outreach/site): +# nu scripts/build/gen-about-pages.nu --root ../.. --out ../../outreach/web/about --daemon https://ontoref.dev +# nu scripts/build/gen-about-pages.nu --root ../.. --emit-json site/public/r/about.json --daemon https://ontoref.dev + +use lib/doc-page.nu [esc paras sec page-shell] + +const TITLES = { + philosophy: { en: "Philosophy", es: "Filosofía" }, + features: { en: "Features", es: "Características" }, + why: { en: "Why it matters", es: "Por qué importa" }, + history: { en: "History", es: "Historia" }, + defs: { en: "What it is", es: "Qué es" }, + graph: { en: "Graph", es: "Grafo" }, + adrs: { en: "Architecture Decisions", es: "Decisiones de Arquitectura" }, + catalog: { en: "Catalog of verifiables", es: "Catálogo de verificables" }, + glossary: { en: "Glossary", es: "Glosario" }, +} + +def hook [text: string, cap: int] { + let first = ($text | str trim | split row "\n\n" | first | default "" | str trim) + if (($first | str length) <= $cap) { $first } else { (($first | str substring 0..$cap) | str trim) + "…" } +} + +def select-defs [nodes: list, level: string, mode: string, ids: list] { + let pool = ($nodes | where {|n| ($n.level? | default "") == $level }) + if ($mode == "All") { $pool + } else if ($mode == "Selected") { ($pool | where {|n| ($n.id? | default "") in $ids }) + } else { [] } +} + +# A block carries renderable content for its type. Empty blocks are dropped so a +# section with no content (sparse .ontoref/) disappears rather than rendering hollow. +def block-nonempty [b: record] { + let t = $b.type + if $t == "prose" { ($b.html? | default "" | str trim | is-not-empty) + } else if $t == "timeline" { (($b.items? | default [] | is-not-empty) or (($b.started? | default "") | is-not-empty)) + } else if $t == "cards" { (($b.groups? | default []) | any {|g| ($g.items? | default []) | is-not-empty }) + } else if $t == "links" { (($b.groups? | default []) | any {|g| ($g.items? | default []) | is-not-empty }) + } else if $t == "graph" { (($b.svg? | default "" | is-not-empty) or (($b.live?.href? | default "") | is-not-empty)) + } else if $t == "kv" { ($b.rows? | default [] | is-not-empty) + } else { false } +} + +# ── history from CHANGELOG (sourced, dated from the ADRs each release accepts) ─ + +# Latest `date = "YYYY-MM-DD"` declared in adr-NNN-*.ncl, or "" if none. +def adr-date [root: string, num: string] { + let padded = ($num | into int | fill --alignment right --character '0' --width 3) + let files = (glob $"($root)/.ontoref/adrs/adr-($padded)-*.ncl") + if ($files | is-empty) { "" } else { + let m = (open --raw ($files | first) | parse --regex 'date\s*=\s*"(?\d{4}-\d{2}-\d{2})"') + if ($m | is-empty) { "" } else { ($m | get d | sort | last) } + } +} + +# One timeline milestone per `## [version]` CHANGELOG release, in file order +# (newest first). The row date is the latest ADR date the release references +# (falls back to the version tag); the text is the bilingual `releases` label, +# falling back to the release's first `### ` headline. +def changelog-milestones [root: string, releases: list] { + let path = $"($root)/code/CHANGELOG.md" + if not ($path | path exists) { return [] } + let lines = (open --raw $path | lines) + let hdr_idx = ($lines | enumerate | where {|r| ($r.item | str starts-with "## [") } | get index) + if ($hdr_idx | is-empty) { return [] } + let n = ($hdr_idx | length) + $hdr_idx | enumerate | each {|e| + let start = $e.item + let end = (if ($e.index + 1 < $n) { ($hdr_idx | get ($e.index + 1)) } else { ($lines | length) }) + let block = ($lines | slice $start..($end - 1)) + let version = ($block | first | parse --regex '## \[(?[^\]]+)\]' | get v.0? | default "") + let heads = ($block | skip 1 | where {|l| ($l | str starts-with "### ") }) + let headline = ($heads | get 0? | default "" | str replace --regex '^###\s+' '') + let body = ($block | str join "\n") + # Release cut date = the latest of: the `Session(s) YYYY-MM-DD` token the entry + # carries, and the dates of the ADRs this release INTRODUCES (its ### headings + # + **ADR-NNN** bold refs) — never every ADR cross-referenced in prose. + let head_nums = ($heads | str join "\n" | parse --regex '(?i)adr-(?\d+)' | get x) + let bold_nums = ($body | parse --regex '\*\*ADR-(?\d+)\*\*' | get x) + let nums = ($head_nums | append $bold_nums | uniq) + let adr_dates = ($nums | each {|x| adr-date $root $x } | where {|d| ($d | is-not-empty) }) + let sess_dates = ($body | parse --regex '(?i)sessions?[^\n]*?(?\d{4}-\d{2}-\d{2})' | get d) + let dates = ($adr_dates | append $sess_dates | where {|d| ($d | is-not-empty) }) + let date = (if ($dates | is-empty) { $version } else { ($dates | sort | last | str substring 0..9) }) + let lbl = ($releases | where {|r| ($r.version? | default "") == $version } | get 0.line? | default null) + let text = (if ($lbl != null) { $lbl } else { { en: $headline, es: $headline } }) + { date: $date, text: $text } + } +} + +# ── view-model assembly → sections of typed blocks ──────────────────────────── + +def build-vm [about: record, root: string, ip: string, daemon: string, nodes: list, adr_map: record, mini_graphs: record] { + let p = $about.project + + # philosophy → prose (lead, bilingual) + kv (built with). Projected from card.ncl. + let phil = ($p.philosophy? | default null) + let philosophy_blocks = if ($phil == null) { [] } else { + let lead = ($phil.lead? | default { en: "", es: "" }) + let bw = ($phil.built_with? | default []) + ([ + { type: "prose", html: $"

(esc ($lead.en? | default ''))

", html_es: $"

(esc ($lead.es? | default ''))

" } + (if ($bw | is-empty) { null } else { + { type: "kv", rows: [ { k: "Built with", k_es: "Construido con", v: ($bw | str join " · ") } ] } + }) + ] | where {|b| $b != null }) + } + + # features → cards (title-only, bilingual). card.ncl.features, translated. + let feat = ($p.features? | default null) + let features_blocks = if ($feat == null) { [] } else { + let items = ($feat.items? | default []) + [ { type: "cards", groups: [ { label: "Key ideas", items: ($items | each {|it| { title: ($it.en? | default ""), title_es: ($it.es? | default "") } }) } ] } ] + } + + # why → prose (lead) + cards (titled differentiator points). Projected from positioning. + let why = ($p.why? | default null) + let why_blocks = if ($why == null) { [] } else { + let lead = ($why.lead? | default null) + let pts = ($why.points? | default []) + ([ + (if ($lead == null) { null } else { { type: "prose", html: $"

(esc ($lead.en? | default ''))

", html_es: $"

(esc ($lead.es? | default ''))

" } }) + (if ($pts | is-empty) { null } else { + { type: "cards", groups: [ { label: "", items: ($pts | each {|pt| { + title: ($pt.title.en? | default ""), title_es: ($pt.title.es? | default ""), + body: ($pt.body.en? | default ""), body_es: ($pt.body.es? | default ""), + } }) } ] } + }) + ] | where {|b| $b != null }) + } + + # history → prose (narrative, if authored) + timeline. source='Changelog derives + # dated rows from code/CHANGELOG.md (sourced); 'Manual keeps hand-listed milestones. + let h = ($p.history? | default {}) + let narr_path = $"($root)/.ontoref/($h.narrative_path? | default '')" + let narrative = if (($h.narrative_path? | default "" | is-not-empty) and ($narr_path | path exists)) { (open --raw $narr_path) } else { "" } + # changelog-derived releases (recent) merged with any hand-declared `milestones` + # (the genesis/foundational rows the CHANGELOG version tags don't reach), newest + # first. So source='Changelog still shows where the project STARTS. + let cl_items = if (($h.source? | default "Manual") == "Changelog") { + (changelog-milestones $root ($h.releases? | default [])) + } else { [] } + let manual_items = (($h.milestones? | default []) | each {|m| { date: ($m.date? | default ""), text: ($m.line? | default { en: "", es: "" }) } }) + let tl_items = ($cl_items | append $manual_items | sort-by date --reverse) + let history_blocks = [ + { type: "prose", html: $narrative } + { type: "timeline", started: ($h.started_at? | default ""), items: $tl_items } + ] + + # defs → cards grouped by level + let dsel = ($p.defs? | default {}) + let def_specs = [ + [label level mode ids]; + ["Axioms" "Axiom" ($dsel.axioms_mode? | default "All") ($dsel.axioms_ids? | default [])] + ["Tensions" "Tension" ($dsel.tensions_mode? | default "All") ($dsel.tensions_ids? | default [])] + ["Practices" "Practice" ($dsel.practices_mode? | default "Selected") ($dsel.practices_ids? | default [])] + ] + let def_groups = ($def_specs | each {|g| + let sel = (select-defs $nodes $g.level $g.mode $g.ids) + { label: $g.label, items: ($sel | each {|n| { title: ($n.name? | default ($n.id? | default "")), meta: ($n.pole? | default ""), body: (hook ($n.description? | default "") 240) } }) } + } | where {|grp| ($grp.items | is-not-empty) }) + + # glossary → cards grouped by category, bilingual (term.name/.definition .en/.es). + # The es fields carry the anglicism-free Spanish terms; the SSR /about handler + # renders them on the ES surface. Projected from .ontoref/ontology/glossary.ncl. + let gloss_export = (do { ^nickel export --format json --import-path $ip --import-path $"($root)/.ontoref" $"($root)/.ontoref/ontology/glossary.ncl" } | complete) + if $gloss_export.exit_code != 0 { error make { msg: $"glossary export failed: ($gloss_export.stderr)" } } + let gloss_terms = ($gloss_export.stdout | from json | get terms) + let gloss_cats = [ + [key label]; + ["Discipline" "Disciplines"] + ["Concept" "Concepts"] + ["Practice" "Practices"] + ["Artifact" "Artifacts"] + ["Procedure" "Procedures"] + ["Antipattern" "Antipatterns"] + ] + let glossary_groups = ($gloss_cats | each {|c| + let items = ($gloss_terms | where category == $c.key | each {|t| { + title: ($t.name.en? | default ($t.id? | default "")), + title_es: ($t.name.es? | default ($t.name.en? | default "")), + meta: (($t.aliases? | default []) | append [""] | first), + body: (hook ($t.definition.en? | default "") 240), + body_es: (hook ($t.definition.es? | default "") 240), + } }) + { label: $c.label, items: $items } + } | where {|g| ($g.items | is-not-empty) }) + + # graph → the generated diagram image (preferred), else a content_graph mini-svg, + # plus the daemon live CTA. The image is a standalone file served at /r/. + let g = ($p.graph? | default {}) + let focus = ($g.focus? | default "") + let diagram_file = $"($root)/outreach/site/site/public/images/ontoref-diagram.svg" + let mini = (if ($diagram_file | path exists) { + "\"ontoref" + } else if ($focus | is-not-empty) and ($focus in $mini_graphs) { ($mini_graphs | get $focus) } else { "" }) + # live link → the self-contained navigable Cytoscape page (daemon-free, same for + # both languages — node names are the labels). The static diagram is the hook. + let live = ($g.live_view? | default "/images/ontoref-graph.html") + let live_href = (if ($live | is-empty) { "" } else { $"($daemon)($live)" }) + let graph_block = { type: "graph", svg: $mini, live: { href: $live_href, label: { en: "Open the interactive graph", es: "Abrir el grafo interactivo" } } } + + # adrs → links (one group) + browse + let ainc = ($p.adrs? | default {}) + let abase = ($ainc.route_base? | default "/adr") + let astatuses = ($ainc.statuses? | default []) + let aentries = ($adr_map | transpose id v | each {|e| { num: ($e.id | str replace 'adr-' ''), name: ($e.v.name? | default ""), status: ($e.v.status? | default "") } } | sort-by num) + let afiltered = (if ($astatuses | is-empty) { $aentries } else { $aentries | where {|e| $e.status in $astatuses } }) + let alimit = ($ainc.digest_limit? | default 0) + let aitems = (if ($alimit > 0) { $afiltered | first $alimit } else { $afiltered }) + let adrs_browse = (if ($ainc.show_browse? | default true) { { browse: { href: $abase, label: { en: "Browse all ADRs", es: "Ver todas las ADR" } } } } else { {} }) + let adrs_block = ({ + type: "links", + groups: [ { label: "", items: ($aitems | each {|e| { href: $"($abase)/($e.num)", code: $"ADR-($e.num)", text: $e.name } }) } ], + } | merge $adrs_browse) + + # catalog → links grouped by kind + browse + let cinc = ($p.catalog? | default {}) + let cbase = ($cinc.route_base? | default "/catalog") + let ckinds = ($cinc.kinds? | default ["operations" "validators"]) + let cgroups = ($ckinds | each {|kind| + let dir = $"($root)/.ontoref/catalog/($kind)" + if not ($dir | path exists) { null } else { + let files = (glob $"($dir)/*.ncl" | where {|f| ($f | path basename) not-in ["schema.ncl" "_template.ncl"] } | sort) + let items = ($files | each {|f| + let rec = (try { nickel export --format json --import-path $ip --import-path $"($root)/.ontoref/catalog" $f | from json } catch { null }) + if ($rec == null) { null } else { { href: $"($cbase)/($rec.id)", code: (if ($kind == "operations") { "op" } else { "val" }), text: $rec.id } } + } | where {|x| $x != null }) + { label: ($kind | str title-case), items: $items } + } + } | where {|x| ($x != null) and ($x.items | is-not-empty) }) + let catalog_browse = (if ($cinc.show_browse? | default true) { { browse: { href: $cbase, label: { en: "Browse the full catalog", es: "Ver el catálogo completo" } } } } else { {} }) + let catalog_block = ({ type: "links", groups: $cgroups } | merge $catalog_browse) + + let blocks_for = { + philosophy: $philosophy_blocks, + features: $features_blocks, + why: $why_blocks, + history: $history_blocks, + defs: [ { type: "cards", groups: $def_groups } ], + glossary: [ { type: "cards", groups: $glossary_groups } ], + graph: [ $graph_block ], + adrs: [ $adrs_block ], + catalog: [ $catalog_block ], + } + + let order = ($p.sections_order? | default ["philosophy" "features" "why" "history" "defs" "glossary" "graph" "adrs" "catalog"]) + let sections = ($order | each {|k| + let blocks = (($blocks_for | get $k) | where {|b| block-nonempty $b }) + if ($blocks | is-empty) { null } else { { key: $k, title: ($TITLES | get $k), blocks: $blocks } } + } | where {|s| $s != null }) + + { + kind: ($about.kind | str downcase), + level_id: $about.level_id, + hero: ($about.hero? | default { en: "", es: "" }), + sections: $sections, + } +} + +# ── Option A: static HTML from the block vocabulary (English primary) ───────── + +def vm-block-html [b: record] { + let t = $b.type + if $t == "prose" { + (paras $b.html) + } else if $t == "timeline" { + let started = (if ($b.started | is-empty) { "" } else { $"

Started (esc $b.started)

" }) + let rows = ($b.items | each {|m| $"
  • (esc $m.date)

    (esc ($m.text.en? | default ''))

  • " } | str join) + $"($started)
      ($rows)
    " + } else if $t == "cards" { + ($b.groups | each {|grp| + let cards = ($grp.items | each {|n| + let meta = (if (($n.meta? | default "") | is-not-empty) { $"(esc $n.meta)" } else { "" }) + $"
  • (esc $n.title)($meta)

    (esc ($n.body? | default ''))

  • " + } | str join) + $"

    (esc $grp.label) \(($grp.items | length)\)

      ($cards)
    " + } | str join) + } else if $t == "links" { + let groups = ($b.groups | each {|grp| + let head = (if (($grp.label? | default "") | is-not-empty) { $"

    (esc $grp.label) \(($grp.items | length)\)

    " } else { "" }) + let rows = ($grp.items | each {|i| + let code = (if (($i.code? | default "") | is-not-empty) { $"(esc $i.code) " } else { "" }) + $"
  • ($code)(esc $i.text)
  • " + } | str join) + $"($head)
      ($rows)
    " + } | str join) + let browse = (if (($b.browse?.href? | default "") | is-not-empty) { $"

    (esc ($b.browse.label.en? | default 'Browse'))

    " } else { "" }) + $"($groups)($browse)" + } else if $t == "graph" { + let mini_html = (if (($b.svg? | default "") | is-empty) { "" } else { $"
    ($b.svg)
    " }) + let live_html = (if (($b.live?.href? | default "") | is-empty) { "" } else { $"

    ↗ (esc ($b.live.label.en? | default 'See it live'))

    " }) + $"($mini_html)($live_html)" + } else if $t == "kv" { + let rows = ($b.rows | each {|r| $"(esc $r.k)(esc $r.v)" } | str join) + $"($rows)
    " + } else { "" } +} + +def vm-to-html [vm: record] { + let hero = ($vm.hero.en? | default "") + let hero_html = (if ($hero | is-empty) { "" } else { $"

    (esc $hero)

    " }) + let sections = ($vm.sections | each {|s| + let body = ($s.blocks | each {|b| vm-block-html $b } | str join "\n") + (sec $s.key ($s.title.en) $body) + } | str join "\n") + $"
    + Project + (esc $vm.level_id) +

    About (esc $vm.level_id)

    + ($hero_html) +
    +($sections)" +} + +# ── main ────────────────────────────────────────────────────────────────────── + +def main [ + --root: string = "../.." + --out: string = "../../outreach/web/about" # static output dir (Option A) + --emit-json: string = "" # if set, write the view-model JSON here (Option B) + --daemon: string = "https://ontoref.dev" +] { + let pos = $"($root)/.ontoref/positioning" + let about_path = $"($pos)/about.ncl" + let ip = $"($root)/code/ontology" + let daemon = ($daemon | str trim --right --char '/') + + if not ($about_path | path exists) { error make { msg: $"about.ncl not found: ($about_path)" } } + let about = (nickel export --format json --import-path $ip $about_path | from json) + + if ($about.kind != "Project") { + print $"gen-about-pages: level ($about.level_id) kind=($about.kind) — personal About uses about.j2, skipping" + return + } + + let nodes = (try { (nickel export --format json --import-path $ip $"($root)/.ontoref/ontology/core.ncl" | from json).nodes } catch { [] }) + let cg_path = $"($root)/outreach/site/site/public/r/content_graph.json" + let mini_graphs = if ($cg_path | path exists) { (open $cg_path).mini } else { {} } + let map_path = $"($root)/outreach/site/site/public/r/adr-map.json" + let adr_map = if ($map_path | path exists) { (open $map_path) } else { {} } + + let vm = (build-vm $about $root $ip $daemon $nodes $adr_map $mini_graphs) + + if ($emit_json | is-not-empty) { + let parent = ($emit_json | path dirname) + if ($parent | is-not-empty) { mkdir $parent } + $vm | to json --indent 2 | save -f $emit_json + print $"gen-about-pages: ($vm.level_id) view-model → ($emit_json)" + } else { + let dir = $"($out)/($vm.level_id)" + mkdir $dir + page-shell $"About ($vm.level_id)" (vm-to-html $vm) | save -f $"($dir)/index.html" + print $"gen-about-pages: ($vm.level_id) → ($dir)/index.html" + } +} diff --git a/site/scripts/build/gen-adr-map.nu b/site/scripts/build/gen-adr-map.nu new file mode 100644 index 0000000..db39025 --- /dev/null +++ b/site/scripts/build/gen-adr-map.nu @@ -0,0 +1,43 @@ +#!/usr/bin/env nu + +# Project the ADR set into a flat reference map — the single source consumed by +# both outreach surfaces: the site sidebar (merged into content_graph.json) and +# the outreach/web linkifier. Source of truth: .ontoref/adrs/adr-NNN.ncl. +# +# Two-tier links (ADR-057 hook-static-prize-live): the map carries the ADR's +# human title (the daemon-free static label) plus the live deep-links — +# detail_url → daemon ADR page, graph_url → daemon graph focused on the node. +# +# Usage: +# nu scripts/build/gen-adr-map.nu --root [--daemon ] [--out ] + +def main [ + --root: string = "../.." # constellation root, relative to outreach/site + --daemon: string = "https://ontoref.dev" # daemon base the deep-links resolve against + --out: string = "site/public/r/adr-map.json" # output path, relative to cwd (outreach/site) +] { + let adrs_dir = $"($root)/.ontoref/adrs" + let ip_data = $"($root)/code/ontology" + + let files = ( + glob $"($adrs_dir)/adr-*.ncl" + | where {|f| ($f | path basename) =~ '^adr-[0-9]' } + | sort + ) + + mut map = {} + for f in $files { + let adr = (nickel export --format json --import-path $ip_data --import-path $adrs_dir $f | from json) + let id = $adr.id + let slug = ($id | str replace 'adr-' '') + $map = ($map | insert $id { + name: $adr.title, + status: ($adr.status? | default "Unknown"), + detail_url: $"($daemon)/adr/($slug)", + graph_url: $"($daemon)/graph?focus=($id)", + }) + } + + $map | to json --indent 2 | save -f $out + print $"adr-map: ($map | columns | length) ADRs → ($out)" +} diff --git a/site/scripts/build/gen-adr-pages.nu b/site/scripts/build/gen-adr-pages.nu new file mode 100644 index 0000000..95be632 --- /dev/null +++ b/site/scripts/build/gen-adr-pages.nu @@ -0,0 +1,262 @@ +#!/usr/bin/env nu + +# Project the ADR set (.ontoref/adrs) into renderable content — same role as +# gen-spine-pages / gen-content-graph, decoupled from the live ADRs. Two modes: +# content : markdown per ADR into the `adr` content-kind tree +# static : self-contained brand-wrapped HTML viewer — outreach/web + docs +# +# Page chrome (brand palette, dark/light theme, nav, .doc-* CSS) comes from the +# shared lib/doc-page.nu module so ADR + catalog surfaces look identical. +# +# Usage: +# nu scripts/build/gen-adr-pages.nu --root ../.. --mode both --slug ontoref + +use lib/doc-page.nu [esc paras sec page-shell] + +def short-adr [rid: string] { + let m = ($rid | parse --regex '(?Padr-\d+)') + if ($m | is-empty) { $rid } else { $m.k.0 } +} + +# ── section renderers (HTML, static mode) ───────────────────────────────────── + +def html-constraints [items: list] { + if ($items | is-empty) { return "" } + let rows = ($items | each {|c| + let sev = ($c.severity? | default "") + $"
  • (if ($sev | is-empty) { '—' } else { esc $sev })

    (esc ($c.claim? | default ''))

  • " + } | str join) + $"
      ($rows)
    " +} + +def html-alts [items: list] { + if ($items | is-empty) { return "" } + let rows = ($items | each {|a| + $"
  • (esc ($a.option? | default ''))

    Rejected: (esc ($a.why_rejected? | default ''))

  • " + } | str join) + $"
      ($rows)
    " +} + +def html-anti [items: list] { + if ($items | is-empty) { return "" } + let rows = ($items | each {|p| + if ($p | describe | str starts-with "record") { + $"
  • (esc ($p.name? | default ($p.id? | default '')))

    (esc ($p.description? | default ''))

  • " + } else { $"
  • (esc $p)
  • " } + } | str join) + $"
      ($rows)
    " +} + +# Related ADR links: /adr/NNN path, ADR-NNN display text +def html-related [ids: list] { + if ($ids | is-empty) { return "" } + let links = ($ids | each {|r| + let k = (short-adr $r) + let num = ($k | str replace 'adr-' '') + $"ADR-(esc $num)" + } | str join " · ") + $"

    ($links)

    " +} + +def html-toc [adr: record] { + let secs = [ + [anchor title present]; + [context "Context" ($adr.context? | default "" | is-not-empty)] + [decision "Decision" ($adr.decision? | default "" | is-not-empty)] + [constraints "Constraints" (($adr.constraints? | default []) | is-not-empty)] + [alternatives "Alternatives" (($adr.alternatives_considered? | default []) | is-not-empty)] + [anti-patterns "Anti-patterns" (($adr.anti_patterns? | default []) | is-not-empty)] + [related "Related ADRs" (($adr.related_adrs? | default []) | is-not-empty)] + ] + let items = ($secs | where present | each {|s| $"
  • (esc $s.title)
  • " } | str join) + if ($items | is-empty) { "" } else { $"" } +} + +# ── markdown render (content-kind mode) ─────────────────────────────────────── + +def render-md [adr: record, daemon: string, slug: string] { + let aid = $adr.id + let num = ($aid | str replace 'adr-' '') + let status = ($adr.status? | default "Unknown") + let st_l = ($status | str downcase) + let title = ($adr.title? | default "" | str replace --all '"' "'") + let excerpt = ($adr.context? | default "" | str trim | split row "\n" | first | default "" | str substring 0..200 | str replace --all '"' "'") + let front = $"--- +id: \"($aid)\" +title: \"($title)\" +slug: \"($num)\" +subtitle: \"($status)\" +excerpt: \"($excerpt)\" +author: \"ontoref\" +date: \"($adr.date? | default '')\" +published: true +featured: false +category: \"($st_l)\" +tags: [\"($aid)\", \"adr\", \"($st_l)\"] +css_class: \"category-adr\" +--- +" + let cons = (($adr.constraints? | default []) | each {|c| $"
  • (esc ($c.severity? | default '')) (esc ($c.claim? | default ''))
  • " } | str join) + let alts = (($adr.alternatives_considered? | default []) | each {|a| $"
  • (esc ($a.option? | default ''))rejected: (esc ($a.why_rejected? | default ''))
  • " } | str join) + let anti = (($adr.anti_patterns? | default []) | each {|p| + if ($p | describe | str starts-with "record") { $"
  • (esc ($p.name? | default ($p.id? | default ''))) — (esc ($p.description? | default ''))
  • " } else { $"
  • (esc $p)
  • " } + } | str join) + let rel = (($adr.related_adrs? | default []) | each {|r| let k = (short-adr $r); let rn = ($k | str replace 'adr-' ''); $"ADR-(esc $rn)" } | str join " · ") + mut body = $"

    Context

    \n\n(paras ($adr.context? | default ''))\n\n

    Decision

    \n\n(paras ($adr.decision? | default ''))\n" + if ($cons | is-not-empty) { $body = $body + $"\n

    Constraints

    \n\n
      ($cons)
    \n" } + if ($alts | is-not-empty) { $body = $body + $"\n

    Alternatives considered

    \n\n
      ($alts)
    \n" } + if ($anti | is-not-empty) { $body = $body + $"\n

    Anti-patterns

    \n\n
      ($anti)
    \n" } + if ($rel | is-not-empty) { $body = $body + $"\n

    Related ADRs

    \n\n

    ($rel)

    \n" } + $"($front)($body)" +} + +# Sidecar .ncl for the content-graph generator (foot mini-graph) +def render-adr-ncl [adr: record] { + let aid = $adr.id + let num = ($aid | str replace 'adr-' '') + let st_l = ($adr.status? | default "" | str downcase) + let title = ($adr.title? | default "" | str replace --all '"' "'") + let rel = (($adr.related_adrs? | default []) | each {|r| short-adr $r } | uniq | where {|k| $k != $aid } | each {|k| $"\"($k)\"" } | str join ", ") + $"{ + id = \"($aid)\", + title = \"($title)\", + slug = \"($num)\", + excerpt = \"\", + tags = [\"adr\", \"($st_l)\"], + page_route = \"/adr/($num)\", + graph = { + implements = [], + related_to = [($rel)], + }, +} +" +} + +# ── page render (static mode) ───────────────────────────────────────────────── + +def render-html [adr: record, daemon: string, slug: string, mini_svg: string] { + let aid = $adr.id + let status = ($adr.status? | default "Unknown") + let st_l = ($status | str downcase) + + let graph_section = if ($mini_svg | is-not-empty) { + $"
    ($mini_svg)
    " + } else { "" } + + let body = ([ + $"
    + (esc $status) + (esc $aid) · (esc ($adr.date? | default '')) +

    (esc ($adr.title? | default ''))

    +
    " + $graph_section + (html-toc $adr) + (sec "context" "Context" (paras ($adr.context? | default ""))) + (sec "decision" "Decision" (paras ($adr.decision? | default ""))) + (sec "constraints" "Constraints" (html-constraints ($adr.constraints? | default []))) + (sec "alternatives" "Alternatives considered" (html-alts ($adr.alternatives_considered? | default []))) + (sec "anti-patterns" "Anti-patterns" (html-anti ($adr.anti_patterns? | default []))) + (sec "related" "Related ADRs" (html-related ($adr.related_adrs? | default []))) + ] | str join "\n") + + page-shell ($adr.title? | default "") $body +} + +def render-index [adrs: list] { + let order = ["Accepted" "Proposed" "Superseded" "Deprecated"] + let groups = ($adrs | group-by {|a| $a.status? | default "Other" }) + let keys = ($order | append ($groups | columns) | uniq | where {|k| $k in ($groups | columns) }) + + let search_entries = ($adrs | each {|a| + let id = ($a.id? | default "") + let num = ($id | str replace 'adr-' '') + let title = ($a.title? | default "" | str replace --all "'" "\\'") + let status = ($a.status? | default "" | str replace --all "'" "\\'") + let ctx = ($a.context? | default "" | str trim | str substring 0..500 | str replace --all "'" "\\'" | str replace --all "\n" " ") + let dec = ($a.decision? | default "" | str trim | str substring 0..500 | str replace --all "'" "\\'" | str replace --all "\n" " ") + $" \{id:'($id)',num:'($num)',title:'($title)',status:'($status)',text:'($ctx) ($dec)'\}" + } | str join ",\n") + + let blocks = ($keys | each {|st| + let items = ($groups | get $st | sort-by id) + let rows = ($items | each {|a| + let num = ($a.id | str replace 'adr-' '') + $"
  • ADR-(esc $num)(esc ($a.title? | default ''))
  • " + } | str join) + $"

    (esc $st) \(($items | length)\)

      ($rows)
    " + } | str join) + + let search_js = $"" + + let content = $"

    Architecture Decision Records

    + +

    No ADRs match your search.

    +($blocks) +($search_js)" + + page-shell "Architecture Decision Records" $content +} + +# ── main ────────────────────────────────────────────────────────────────────── + +def main [ + --root: string = "../.." + --mode: string = "static" # static | content | both + --out: string = "site/public/adr" # static output dir + --content-out: string = "site/content/adr/en" # content-kind output + --daemon: string = "https://ontoref.dev" + --slug: string = "ontoref" +] { + let adrs_dir = $"($root)/.ontoref/adrs" + let ip_data = $"($root)/code/ontology" + let daemon = ($daemon | str trim --right --char '/') + let do_static = ($mode in ["static" "both"]) + let do_content = ($mode in ["content" "both"]) + + let cg_path = $"($root)/outreach/site/site/public/r/content_graph.json" + let mini_graphs = if ($cg_path | path exists) { (open $cg_path).mini } else { {} } + + let files = (glob $"($adrs_dir)/adr-*.ncl" | where {|f| ($f | path basename) =~ '^adr-[0-9]' } | sort) + mut adrs = [] + for f in $files { + let adr = (try { nickel export --format json --import-path $ip_data --import-path $adrs_dir $f | from json } catch { null }) + if ($adr == null) { print $" skip ($f | path basename)"; continue } + $adrs = ($adrs | append $adr) + if $do_static { + let num = ($adr.id | str replace 'adr-' '') + let dir = $"($out)/($num)" + mkdir $dir + let mini_svg = (if $num in $mini_graphs { $mini_graphs | get $num } else { "" }) + render-html $adr $daemon $slug $mini_svg | save -f $"($dir)/index.html" + } + if $do_content { + let cat = ($adr.status? | default "adr" | str downcase) + mkdir $"($content_out)/($cat)" + render-md $adr $daemon $slug | save -f $"($content_out)/($cat)/($adr.id).md" + render-adr-ncl $adr | save -f $"($content_out)/($cat)/($adr.id).ncl" + } + } + if $do_static { + mkdir $out + render-index $adrs | save -f $"($out)/index.html" + } + print $"gen-adr-pages [($mode)]: ($adrs | length) ADRs" +} diff --git a/site/scripts/build/gen-catalog-pages.nu b/site/scripts/build/gen-catalog-pages.nu new file mode 100644 index 0000000..3a516a3 --- /dev/null +++ b/site/scripts/build/gen-catalog-pages.nu @@ -0,0 +1,313 @@ +#!/usr/bin/env nu + +# Project the catalog of verifiables (.ontoref/catalog) into renderable content — +# sibling of gen-adr-pages.nu. The catalog holds two kinds: +# operations : typed agent actions (inputs, effects, constraints, witness shape) +# validators : the criteria that gate them (category, severity, slice reads) +# +# Two modes, identical to the ADR surface: +# static : self-contained brand-wrapped HTML viewer — outreach/web + docs +# content : markdown per entry into the `catalog` content-kind tree (needs the +# kind registered in content.ncl/routes.ncl — a rebuild boundary) +# +# Page chrome comes from lib/doc-page.nu so ADR + catalog look identical. +# +# Usage: +# nu scripts/build/gen-catalog-pages.nu --root ../.. --mode static --out ../../outreach/web/catalog + +use lib/doc-page.nu [esc paras sec page-shell] + +# ── shared small renderers ──────────────────────────────────────────────────── + +def kv-table [rows: list] { + let body = ($rows | where {|r| ($r.1 | str trim | is-not-empty) } + | each {|r| $"(esc $r.0)(esc $r.1)" } | str join) + if ($body | is-empty) { "" } else { $"($body)
    " } +} + +def bool-str [b] { if ($b == true) { "yes" } else if ($b == false) { "no" } else { "" } } + +# Link a referenced validator id to its catalog page if it resolves to a known +# entry; otherwise render it as inline code (inline expression refs, unknowns). +def vref-link [ref, known_ids: list] { + if (($ref | describe) != "string") { return "inline" } + if ($ref in $known_ids) { + $"($ref)" + } else { + $"(esc $ref)" + } +} + +# ── validator page ──────────────────────────────────────────────────────────── + +def render-validator-html [v: record] { + let cat = ($v.category? | default "") + let sev = ($v.severity? | default "") + let cat_badge = if ($cat | is-not-empty) { $"(esc $cat)" } else { "" } + let sev_badge = if ($sev | is-not-empty) { $"(esc $sev)" } else { "" } + + let meta = (kv-table [ + ["criterion" ($v.criterion_ref? | default "")] + ["predicate" ($v.predicate_ref? | default "")] + ["decidable" (bool-str ($v.decidable?))] + ["touches spiral" (bool-str ($v.touches_spiral?))] + ["needs state root" (bool-str ($v.slice?.needs_state_root?))] + ]) + + let reads = ($v.slice?.reads? | default []) + let reads_html = if ($reads | is-empty) { "" } else { + let rows = ($reads | each {|r| + let fields = (($r.fields? | default []) | each {|f| esc $f } | str join ", ") + $"
  • (esc ($r.entity? | default ''))

    ($fields)

  • " + } | str join) + $"
      ($rows)
    " + } + + let body = ([ + $"
    + Validator($cat_badge)($sev_badge) + (esc ($v.id? | default '')) +

    (esc ($v.id? | default ''))

    +
    " + (sec "description" "Description" (paras ($v.description? | default ""))) + (sec "spec" "Specification" $meta) + (sec "reads" "Reads (slice)" $reads_html) + ] | str join "\n") + + page-shell ($v.id? | default "Validator") $body +} + +# ── operation page ──────────────────────────────────────────────────────────── + +def render-operation-html [op: record, known_ids: list] { + let sla = ($op.validation_sla? | default "") + let sla_str = if (($sla | describe) == "string") { $sla } else if (($sla | describe | str starts-with "record")) { ($sla.tag? | default "") } else { "" } + + let pre = (kv-table [ + ["slice query" ($op.precondition?.slice_query? | default "")] + ["slice kind" ($op.precondition?.slice_kind? | default "")] + ["validation SLA" $sla_str] + ["roles" (($op.actor_policy?.roles? | default []) | str join ", ")] + ]) + + let inputs = ($op.inputs? | default {}) + let inputs_html = if (($inputs | columns | length) == 0) { "" } else { + let rows = ($inputs | transpose k v | each {|i| $"(esc $i.k)(esc $i.v)" } | str join) + $"($rows)
    " + } + + let effects = ($op.effects? | default []) + let effects_html = if ($effects | is-empty) { "" } else { + let rows = ($effects | each {|e| + let attrs = (($e.attrs? | default []) | each {|a| esc $a } | str join ", ") + let dims = (($e.dimensions? | default []) | where {|d| ($d | str trim | is-not-empty) } | each {|d| esc $d } | str join ", ") + let dim_s = if ($dims | is-empty) { "" } else { $" · dims: ($dims)" } + $"
  • (esc ($e.kind? | default ''))

    (esc ($e.entity? | default ''))($attrs)($dim_s)

  • " + } | str join) + $"
      ($rows)
    " + } + + let pre_v = ($op.constraints?.pre? | default []) + let post_v = ($op.constraints?.post? | default []) + let constraints_html = if (($pre_v | is-empty) and ($post_v | is-empty)) { "" } else { + let pre_links = if ($pre_v | is-empty) { "—" } else { ($pre_v | each {|r| vref-link $r $known_ids } | str join " · ") } + let post_links = if ($post_v | is-empty) { "—" } else { ($post_v | each {|r| vref-link $r $known_ids } | str join " · ") } + $"
    pre($pre_links)
    post($post_links)
    " + } + + let ws = ($op.witness_shape? | default {}) + let witness_html = if (($ws | columns | length) == 0) { "" } else { + let proofs = (($ws.proof_paths? | default []) | each {|p| esc $p } | str join "
    ") + (kv-table [ + ["payload kind" ($ws.payload_kind? | default "")] + ["proof paths" ""] + ] | str replace 'proof paths' $"proof paths($proofs)") + } + + let renders = ($op.render_paths? | default []) + let render_html = if ($renders | is-empty) { "" } else { + let items = ($renders | each {|p| $"
  • (esc $p)
  • " } | str join) + $"
      ($items)
    " + } + + let sla_badge = if ($sla_str | is-not-empty) { $"(esc $sla_str)" } else { "" } + let body = ([ + $"
    + Operation($sla_badge) + (esc ($op.id? | default '')) +

    (esc ($op.id? | default ''))

    +
    " + (sec "description" "Description" (paras ($op.description? | default ""))) + (sec "preconditions" "Preconditions & policy" $pre) + (sec "inputs" "Inputs" $inputs_html) + (sec "effects" "Effects" $effects_html) + (sec "constraints" "Validators" $constraints_html) + (sec "witness" "Witness shape" $witness_html) + (sec "renders" "Render paths" $render_html) + ] | str join "\n") + + page-shell ($op.id? | default "Operation") $body +} + +# ── index ────────────────────────────────────────────────────────────────────── + +def render-index [ops: list, validators: list] { + let op_search = ($ops | each {|o| + let id = ($o.id? | default "") + let desc = ($o.description? | default "" | str trim | str substring 0..400 | str replace --all "'" "\\'" | str replace --all "\n" " ") + $" \{id:'($id)',kind:'operation',text:'($desc)'\}" + }) + let v_search = ($validators | each {|v| + let id = ($v.id? | default "") + let desc = ($v.description? | default "" | str trim | str substring 0..400 | str replace --all "'" "\\'" | str replace --all "\n" " ") + $" \{id:'($id)',kind:'validator',text:'($desc)'\}" + }) + + let op_rows = ($ops | sort-by id | each {|o| + $"
  • op(esc ($o.id? | default ''))
  • " + } | str join) + let v_rows = ($validators | sort-by id | each {|v| + $"
  • val(esc ($v.id? | default ''))
  • " + } | str join) + + let blocks = $"

    Operations \(($ops | length)\)

      ($op_rows)

    Validators \(($validators | length)\)

      ($v_rows)
    " + + let search_js = $"" + + let content = $"

    Catalog of Verifiables

    +

    Typed agent operations and the validators that gate them \(ADR-024 / ADR-026 / ADR-050\).

    + +

    Nothing in the catalog matches your search.

    +($blocks) +($search_js)" + + page-shell "Catalog of Verifiables" $content +} + +# ── content-kind sidecar (for the future `catalog` kind + content_graph) ────── + +def render-catalog-ncl [id: string, kind: string, title: string, related: list] { + let rel = ($related | uniq | where {|k| $k != $id } | each {|k| $"\"($k)\"" } | str join ", ") + $"{ + id = \"($id)\", + title = \"($title)\", + slug = \"($id)\", + excerpt = \"\", + tags = [\"catalog\", \"($kind)\"], + page_route = \"/catalog/($id)\", + graph = { + implements = [], + related_to = [($rel)], + }, +} +" +} + +def render-md [id: string, kind: string, description: string] { + let excerpt = ($description | str trim | str substring 0..200 | str replace --all '"' "'") + $"--- +id: \"($id)\" +title: \"($id)\" +slug: \"($id)\" +subtitle: \"($kind)\" +excerpt: \"($excerpt)\" +author: \"ontoref\" +published: true +featured: false +category: \"($kind)\" +tags: [\"($id)\", \"catalog\", \"($kind)\"] +css_class: \"category-catalog\" +--- + +

    Description

    + +(paras $description) +" +} + +# ── main ────────────────────────────────────────────────────────────────────── + +def main [ + --root: string = "../.." + --mode: string = "static" # static | content | both + --out: string = "site/public/catalog" # static output dir + --content-out: string = "site/content/catalog/en" # content-kind output +] { + let cat_dir = $"($root)/.ontoref/catalog" + let ip_data = $"($root)/code/ontology" + let do_static = ($mode in ["static" "both"]) + let do_content = ($mode in ["content" "both"]) + + let op_files = (glob $"($cat_dir)/operations/*.ncl" | where {|f| ($f | path basename) not-in ["schema.ncl" "_template.ncl"] } | sort) + let v_files = (glob $"($cat_dir)/validators/*.ncl" | where {|f| ($f | path basename) != "schema.ncl" } | sort) + + mut ops = [] + for f in $op_files { + let op = (try { nickel export --format json --import-path $ip_data --import-path $cat_dir $f | from json } catch {|e| print $" skip ($f | path basename): ($e.msg)"; null }) + if ($op != null) { $ops = ($ops | append $op) } + } + mut validators = [] + for f in $v_files { + let v = (try { nickel export --format json --import-path $ip_data --import-path $cat_dir $f | from json } catch {|e| print $" skip ($f | path basename): ($e.msg)"; null }) + if ($v != null) { $validators = ($validators | append $v) } + } + + let known_ids = ($validators | each {|v| $v.id? | default "" } | where {|x| $x | is-not-empty }) + + if $do_static { + for op in $ops { + let id = ($op.id? | default "") + if ($id | is-empty) { continue } + let dir = $"($out)/($id)" + mkdir $dir + render-operation-html $op $known_ids | save -f $"($dir)/index.html" + } + for v in $validators { + let id = ($v.id? | default "") + if ($id | is-empty) { continue } + let dir = $"($out)/($id)" + mkdir $dir + render-validator-html $v | save -f $"($dir)/index.html" + } + mkdir $out + render-index $ops $validators | save -f $"($out)/index.html" + } + + if $do_content { + for op in $ops { + let id = ($op.id? | default "") + if ($id | is-empty) { continue } + mkdir $"($content_out)/operations" + let related = (($op.constraints?.pre? | default []) | append ($op.constraints?.post? | default []) | where {|r| ($r | describe) == "string" }) + render-md $id "operations" ($op.description? | default "") | save -f $"($content_out)/operations/($id).md" + render-catalog-ncl $id "operation" $id $related | save -f $"($content_out)/operations/($id).ncl" + } + for v in $validators { + let id = ($v.id? | default "") + if ($id | is-empty) { continue } + mkdir $"($content_out)/validators" + render-md $id "validators" ($v.description? | default "") | save -f $"($content_out)/validators/($id).md" + render-catalog-ncl $id "validator" $id [] | save -f $"($content_out)/validators/($id).ncl" + } + } + + print $"gen-catalog-pages [($mode)]: ($ops | length) operations, ($validators | length) validators" +} diff --git a/site/scripts/build/gen-diagram.nu b/site/scripts/build/gen-diagram.nu new file mode 100644 index 0000000..4cc5890 --- /dev/null +++ b/site/scripts/build/gen-diagram.nu @@ -0,0 +1,124 @@ +#!/usr/bin/env nu + +# Project the ontology (core.ncl) into a standalone node-link GRAPH diagram SVG +# and save it as an image file in the site. This is the web-usable form of +# `onre diagram`: the 55 nodes laid out on a circle (grouped by level), with the +# 144 edges drawn as chords across it — a real graph, not a list. Served at +# /images/ontoref-diagram.svg, referenced by the About graph block. +# +# Rendered as an (isolated from page CSS), so colours are baked literal +# (the ontoref dark palette) rather than CSS vars. +# +# Usage (from outreach/site): +# nu scripts/build/gen-diagram.nu --root ../.. --out site/public/images/ontoref-diagram.svg + +const TAU = 6.283185307179586 +const DEG = 57.29577951308232 + +# Level order around the ring + per-level colour and dot radius. +const LEVELS = [ + [level color dot]; + ["Axiom" "#E8A838" 6.0] + ["Tension" "#C0CCD8" 6.0] + ["Practice" "#7fc8e0" 4.5] +] + +def svg-esc [s: string] { + $s | str replace --all '&' '&' | str replace --all '<' '<' | str replace --all '>' '>' +} + +def clip [s: string, n: int] { + if (($s | str length) <= $n) { $s } else { (($s | str substring 0..($n - 1)) | str trim) + "…" } +} + +def r1 [x] { $x | into float | math round --precision 1 } + +def main [ + --root: string = "../.." + --out: string = "site/public/images/ontoref-diagram.svg" +] { + let ip = $"($root)/code/ontology" + let core = $"($root)/.ontoref/ontology/core.ncl" + if not ($core | path exists) { error make { msg: $"core.ncl not found: ($core)" } } + let graph = (nickel export --format json --import-path $ip $core | from json) + let nodes = ($graph | get nodes) + let edges = ($graph.edges? | default []) + + # Order nodes by level (contiguous arcs: axioms, then tensions, then practices). + let ordered = ($LEVELS | get level | each {|lvl| + $nodes | where {|n| ($n.level? | default "") == $lvl } + } | flatten) + let count = ($ordered | length) + let n_edges = ($edges | length) + + let W = 920.0 + let cx = ($W / 2) + let cy = (($W / 2) + 14) # nudge down, leave the title band clear + let R = 322.0 # node ring radius (labels radiate outside it) + + # Position every node on the ring; keep id → {x,y,ang} for edge chords. + let placed = ($ordered | enumerate | each {|e| + let ang = ($TAU * $e.index / $count) + let lvl = ($e.item.level? | default "") + let spec = ($LEVELS | where level == $lvl | first) + { + id: ($e.item.id? | default ""), + name: ($e.item.name? | default ($e.item.id? | default "")), + level: $lvl, + color: $spec.color, + dot: $spec.dot, + ang: $ang, + x: ($cx + $R * ($ang | math cos)), + y: ($cy + $R * ($ang | math sin)), + } + }) + let pmap = ($placed | reduce --fold {} {|it, acc| $acc | insert $it.id $it }) + + # Edge chords — only between two placed nodes (skip edges to ADR/project ids). + let edge_svg = ($edges | each {|ed| + let a = (try { $pmap | get ($ed.from? | default "") } catch { null }) + let b = (try { $pmap | get ($ed.to? | default "") } catch { null }) + if ($a == null) or ($b == null) { null } else { + $"" + } + } | compact | str join "\n ") + + # Nodes — dot + radial label (rotated to read outward, flipped on the left half). + let node_svg = ($placed | each {|p| + let dot = $"" + let deg = ($p.ang * $DEG) + let left = (($p.ang | math cos) < 0) + let lx = ($cx + ($R + 10) * ($p.ang | math cos)) + let ly = ($cy + ($R + 10) * ($p.ang | math sin)) + let rot = (if $left { $deg + 180 } else { $deg }) + let anchor = (if $left { "end" } else { "start" }) + let txt = (clip $p.name 26) + let label = $"(svg-esc $txt)" + $"($dot)\n ($label)" + } | str join "\n ") + + # Legend (level → colour + count). + let legend = ($LEVELS | get level | enumerate | each {|e| + let lvl = $e.item + let spec = ($LEVELS | where level == $lvl | first) + let c = ($placed | where level == $lvl | length) + let ly = (64 + $e.index * 20) + $"($lvl)s · ($c)" + } | str join "\n ") + + let H = ($W + 4) + let title = $"ontorefontology graph · ($count) nodes · ($n_edges) edges" + + let svg = $" + + ($title) + ($legend) + ($edge_svg) + ($node_svg) +" + + let parent = ($out | path dirname) + if ($parent | is-not-empty) { mkdir $parent } + $svg | save -f $out + print $"gen-diagram: ($count) nodes · ($n_edges) edges → ($out)" +} diff --git a/site/scripts/build/gen-graph-page.nu b/site/scripts/build/gen-graph-page.nu new file mode 100644 index 0000000..3124551 --- /dev/null +++ b/site/scripts/build/gen-graph-page.nu @@ -0,0 +1,150 @@ +#!/usr/bin/env nu + +# Project the ontology (core.ncl) into a SELF-CONTAINED, navigable Cytoscape graph +# page. This is the interactive "prize" the About graph block links to (ADR-057 +# hook-static / prize-live) — but daemon-free: the data is embedded and the page +# uses the cytoscape libs already shipped under /js/. The site's own /graph +# (Leptos GraphView) renders an empty canvas under htmx-ssr (no WASM hydration), +# so we ship our own page instead. +# +# Served as a static file at /images/ontoref-graph.html (only /images, /js, /styles +# are static-served; bare paths are intercepted by the content router). +# +# Usage (from outreach/site): +# nu scripts/build/gen-graph-page.nu --root ../.. --out site/public/images/ontoref-graph.html + +const TEMPLATE = r##' + + + + +ontoref · ontology graph + + + +
    + ontoref + ontology graph · __NCOUNT__ nodes · __ECOUNT__ edges + ← About +
    +
    +
    +
    Axioms · __NAX__
    +
    Tensions · __NTN__
    +
    Practices · __NPR__
    +
    +
    drag to pan · scroll to zoom · click a node to focus · click empty space to reset
    + + + + + + + + +'## + +def main [ + --root: string = "../.." + --out: string = "site/public/images/ontoref-graph.html" +] { + let ip = $"($root)/code/ontology" + let core = $"($root)/.ontoref/ontology/core.ncl" + if not ($core | path exists) { error make { msg: $"core.ncl not found: ($core)" } } + let graph = (nickel export --format json --import-path $ip $core | from json) + let nodes = ($graph | get nodes) + let edges = ($graph.edges? | default []) + + let ids = ($nodes | each {|n| $n.id? | default "" } | where {|x| $x | is-not-empty }) + let node_els = ($nodes | each {|n| { data: { + id: ($n.id? | default ""), + title: ($n.name? | default ($n.id? | default "")), + level: ($n.level? | default "Practice"), + } } }) + let edge_els = ($edges | where {|e| (($e.from? | default "") in $ids) and (($e.to? | default "") in $ids) } | each {|e| { data: { + id: $"($e.from)__($e.to)__($e.kind? | default 'rel')", + source: ($e.from? | default ""), + target: ($e.to? | default ""), + kind: ($e.kind? | default ""), + } } }) + let elements = ($node_els | append $edge_els | to json --raw) + + let nax = ($nodes | where {|n| ($n.level? | default "") == "Axiom" } | length) + let ntn = ($nodes | where {|n| ($n.level? | default "") == "Tension" } | length) + let npr = ($nodes | where {|n| ($n.level? | default "") == "Practice" } | length) + + let html = ($TEMPLATE + | str replace --all '__NCOUNT__' ($nodes | length | into string) + | str replace --all '__ECOUNT__' ($edge_els | length | into string) + | str replace --all '__NAX__' ($nax | into string) + | str replace --all '__NTN__' ($ntn | into string) + | str replace --all '__NPR__' ($npr | into string) + | str replace '/*__ELEMENTS__*/' $elements) + + let parent = ($out | path dirname) + if ($parent | is-not-empty) { mkdir $parent } + $html | save -f $out + print $"gen-graph-page: ($nodes | length) nodes · ($edge_els | length) edges → ($out)" +} diff --git a/site/scripts/build/gen-spine-pages.nu b/site/scripts/build/gen-spine-pages.nu new file mode 100644 index 0000000..ec62262 --- /dev/null +++ b/site/scripts/build/gen-spine-pages.nu @@ -0,0 +1,217 @@ +#!/usr/bin/env nu + +# Generate the four-door domain spine from the positioning graph (bl-035, ADR-064/065). +# +# Consumes the structured projection contract (.ontoref/positioning/spine.ncl) +# and each referenced value-prop, then emits the four doors as posts of the +# `dominios` content-kind — these are ontoref's DOMAINS, not projects (ADR-065). +# `dominios` content is regenerated here + reindexed by content_processor, BUT the +# `/dominios` + `/dominios/{slug}` ROUTES are baked: crates/server/build.rs codegen +# reads content-kinds.{toml,ncl} at BUILD time. Empirically verified — a fresh +# restart with `dominios` in content-kinds.toml still 404s. So activating the kind +# needs `dominios` in content.ncl + content-kinds.ncl AND a website-htmx-rustelo +# rebuild + reinstall (the catalog kind required the same; the route is not runtime). +# +# Layout (category-layout — matches the loader's body-path resolution +# `{lang}/{category}/{id}.md`; URL slug stays the frontmatter `slug`): +# content/domains//use-cases/door-.md → /domains/ (EN) · /dominios/ (ES) +# +# The hook (door landings) is static NCL→build; reveal rungs 1 & 3 (live +# describe-impact, browsable graph) link to the running daemon — the site stays +# usable without it. Hero/sub-hero/close are emitted as _spine fragments for +# manual wiring into the Rust home page component (they need a handler, not a kind). +# +# Usage: +# nu scripts/build/gen-spine-pages.nu --root --content [--daemon ] [--date ] + +def main [ + --root: string = "../.." # constellation root, relative to outreach/site + --content: string = "site/content" # site content dir, relative to outreach/site + --daemon: string = "https://ontoref.dev" # base URL the live-graph links resolve against + --date: string = "2026-06-10" # publication date stamped on generated posts + --langs: list = ["es" "en"] +] { + let spine_path = $"($root)/.ontoref/positioning/spine.ncl" + let vp_dir = $"($root)/.ontoref/positioning/value-props" + + if not ($spine_path | path exists) { + error make { msg: $"spine contract not found: ($spine_path)" } + } + let spine = (nickel export --format json $spine_path | from json) + + for lang in $langs { + let is_es = ($lang == "es") + let kind_path = (if $is_es { "dominios" } else { "domains" }) + + # ── Doors as `domains` posts (routable: /domains/{slug} EN · /dominios/{slug} ES) ── + for item in ($spine.doors | enumerate) { + let d = $item.item + let vp = (nickel export --format json $"($vp_dir)/($d.value_prop_id).ncl" | from json) + let label = ($d.label | get $lang) + let line = ($d.line | get $lang) + let blog_url = $"/blog?tag=($d.tag)" + let recipes_url = (if $is_es { $"/recetas?tag=($d.tag)" } else { $"/recipes?tag=($d.tag)" }) + let prize_line = (if $is_es { $spine.prize.line } else { $spine.prize.en }) + # CTA routes to real content tagged for this door (blog + recipes), not the daemon + # graph — reach-vs-qualification: route to a live, populated surface. + let cta = (if $is_es { + $"[Posts]\(($blog_url)\) · [Recetas]\(($recipes_url)\)" + } else { + $"[Posts]\(($blog_url)\) · [Recipes]\(($recipes_url)\)" + }) + let prize_label = (if $is_es { "El premio, al entrar" } else { "The prize, once inside" }) + let excerpt = ($vp.problem_solved | lines | where ($it | str trim | is-not-empty) | first | str trim | str replace --all '"' "'") + let cat_desc = (if $is_es { "Casos de uso e implementaciones de ontoref" } else { "Use cases and implementations of ontoref" }) + + let proj_dir = $"($content)/domains/($lang)/use-cases" + mkdir $proj_dir + + let front = $"--- +id: \"door-($d.key)\" +title: \"($label)\" +slug: \"($d.key)\" +subtitle: \"($line)\" +excerpt: \"($excerpt)\" +author: \"ontoref\" +date: \"($date)\" +published: true +featured: true +category: \"use-cases\" +tags: [\"($d.key)\", \"ontoref\", \"use-case\"] +read_time: \"3 min read\" +sort_order: ($item.index + 1) +css_class: \"category-use-cases\" +category_description: \"($cat_desc)\" +category_published: true +--- +" + # Cold-open evidence hook (spine.evidence_hooks) — the market's own number + # as the honest gancho above the claim, routing to the door's live graph + # (route-to-live-graph principle). Inert when no hook targets this door. + let hook = ($spine.evidence_hooks? | default [] | where {|h| $h.door == $d.key }) + let hook_block = (if ($hook | is-empty) { "" } else { + let h = ($hook | first) + let hline = ($h.hook | get $lang) + let see = (if $is_es { "Comprueba qué se rompe" } else { "See what breaks" }) + let see_url = $"/blog?tag=($d.tag)" + $"> **($hline)** +> +> — [($h.source.name)]\(($h.source.url)\) · [($see)]\(($see_url)\) + +" + }) + let body = $"($hook_block)**($vp.claim)** + +($vp.problem_solved) + +--- + +($cta) + +### ($prize_label) + +> ($prize_line) +" + $"($front)\n($body)" | save -f $"($proj_dir)/door-($d.key).md" + + # Sidecar .ncl carrying the `graph` block read by gen-content-graph (the foot + # mini-graph). gen-content-graph globs **/*.ncl (never the .md), so without this + # the door has no non-tag edges and its sidebar renders empty. Emitted once on + # the en pass — the mini graph is keyed by content id, language-agnostic. + if not $is_es { + let adr_short = ($vp.evidence_adrs | each {|a| ($a | split row "-" | take 2 | str join "-") }) + let implements = ($adr_short | append ["positioning-layer", "reach-vs-qualification"]) + let related = ($spine.doors | each {|x| $x.key } | where {|k| $k != $d.key } | each {|k| $"door-($k)" }) + let impl_arr = ($implements | each {|x| $"\"($x)\"" } | str join ", ") + let rel_arr = ($related | each {|x| $"\"($x)\"" } | str join ", ") + $"{ + id = \"door-($d.key)\", + title = \"($label)\", + slug = \"($d.key)\", + excerpt = \"($excerpt)\", + tags = [\"($d.key)\", \"ontoref\", \"use-case\"], + page_route = \"/domains/($d.key)\", + graph = { + implements = [($impl_arr)], + related_to = [($rel_arr)], + }, +} +" | save -f $"($proj_dir)/door-($d.key).ncl" + } + } + + # ── Home hero + close as _spine fragments (manual wiring into Rust page) ─── + let frag_dir = $"($content)/domains/_spine" + mkdir $frag_dir + let hero_line = (if $is_es { $spine.hero.line } else { $spine.hero.en }) + let sub_line = (if $is_es { $spine.sub_hero.line } else { $spine.sub_hero.en }) + let close_line = (if $is_es { $spine.close.line } else { $spine.close.en }) + let door_cards = ( + $spine.doors + | each {|d| + let label = ($d.label | get $lang) + let dline = ($d.line | get $lang) + let slug_path = $"/($kind_path)/($d.key)" + $"- **[($label)]\(($slug_path)\)** — ($dline)" + } + | str join "\n" + ) + let heading = (if $is_es { "Cuatro puertas, el mismo edificio" } else { "Four doors, one building" }) + $" +# ($hero_line) + +> ($sub_line) + +## ($heading) + +($door_cards) + +--- + +_($close_line)_ +" | save -f $"($frag_dir)/home-($lang).md" + + # ── Spine lines projected into i18n (`spine-*` keys, owned by this generator; + # hand-maintained protocol-mechanics keys stay in pages/home.ftl) ── + let prize_line = (if $is_es { $spine.prize.line } else { $spine.prize.en }) + let i18n_pages = ($content | path dirname | path join "i18n" "locales" $lang "pages") + mkdir $i18n_pages + # Evidence-hook banner lines, one fluent key per hooked door (spine-hook-). + let hook_keys = ( + $spine.evidence_hooks? | default [] + | each {|h| let hl = (if $is_es { $h.hook.es } else { $h.hook.en }); $"spine-hook-($h.door) = ($hl)" } + | str join "\n" + ) + # Per-door panel keys for the home vestibule (ADR-057): label + imperative line + # + the door's own landing href (lang-aware) + live-graph url. The panel is a + # teaser that ROUTES (reach-vs-qualification); the full value-prop lives on the + # door landing the href points to. + let door_keys = ( + $spine.doors + | each {|d| + let dlabel = ($d.label | get $lang) + let dline = ($d.line | get $lang) + let dhref = (if $is_es { $"/dominios/($d.key)" } else { $"/domains/($d.key)" }) + # door CTA routes to real content tagged for this door (blog + recipes), + # not to the daemon graph — reach-vs-qualification: route to a live surface. + let dblog = $"/blog?tag=($d.tag)" + let drecipes = (if $is_es { $"/recetas?tag=($d.tag)" } else { $"/recipes?tag=($d.tag)" }) + $"spine-door-($d.key)-label = ($dlabel)\nspine-door-($d.key)-line = ($dline)\nspine-door-($d.key)-href = ($dhref)\nspine-door-($d.key)-blog = ($dblog)\nspine-door-($d.key)-recipes = ($drecipes)" + } + | str join "\n" + ) + let door_cta = (if $is_es { "spine-door-cta = Ver más\nspine-door-blog-cta = Posts\nspine-door-recipes-cta = Recetas" } else { "spine-door-cta = See more\nspine-door-blog-cta = Posts\nspine-door-recipes-cta = Recipes" }) + $"# GENERATED from .ontoref/positioning/spine.ncl by gen-spine-pages.nu — do not edit by hand. +spine-hero = ($hero_line) +spine-sub = ($sub_line) +spine-close = ($close_line) +spine-prize = ($prize_line) +spine-doors-heading = ($heading) +($door_cta) +($door_keys) +($hook_keys) +" | save -f ($i18n_pages | path join "home_spine.ftl") + } + + print $"spine generated: doors as `dominios` posts + _spine fragments for langs ($langs | str join ', ')" +} diff --git a/site/scripts/build/gen-taglines.nu b/site/scripts/build/gen-taglines.nu new file mode 100644 index 0000000..25bfe92 --- /dev/null +++ b/site/scripts/build/gen-taglines.nu @@ -0,0 +1,101 @@ +#!/usr/bin/env nu + +# Project the curated definition-phrases (.ontoref/positioning/taglines.ncl) into a +# grouped JSON the hero rotator consumes (typed.js / CSS-fade). The phrases are typed +# and drift-checked by taglines-schema.ncl at export — an unknown audience, or a focus +# that is not a real differentiator id, fails here and never reaches the page +# (diff-reflexive-no-drift). +# +# Output shape (by_audience is the contract the rotator binds to): +# { generated_from, audiences: [], +# by_audience: { "all": [{id,es,en,focus,register}], "arriving-developer": [...], ... } } +# +# Driver semantics the JSON supports: home starts on by_audience.all (neutral +# auto-cycle); a door click fixes the rotator to that audience's queue; door/audience +# pages mount with a fixed key. "all" is the curated neutral queue, NOT the union — +# reach-vs-qualification: a routing gancho, never a wall of every phrase. +# +# Usage: +# nu gen-taglines.nu --root --out + +def main [ + --root: string = "../.." # constellation root, relative to caller PWD + --out: string = "site/public/r/taglines.json" # output JSON, relative to caller PWD +] { + let src = $"($root)/.ontoref/positioning/taglines.ncl" + if not ($src | path exists) { + error make { msg: $"taglines source not found: ($src)" } + } + # `nickel export` applies TaglinesShape|TaglinesCap|TaglinesUniqueIds — drift fails here. + let data = (nickel export --format json $src | from json) + + # Canonical audience order (mirror taglines-schema.ncl AudienceIds). "all" leads so + # the home neutral queue comes first; the rest serve door/audience pages. + let order = [ + "all" + "arriving-developer" + "infrastructure-inheritor" + "personal-ontology" + "compliance-officer" + "engineering-leader" + "platform-engineer" + ] + + # Consumer-facing shape — drop provenance (source); keep what the rotator renders. + let project = {|t| + { + id: $t.id, + es: $t.es, + en: $t.en, + focus: ($t.focus? | default null), + register: $t.register, + } + } + + let by_audience = ( + $order + | reduce --fold {} {|aid, acc| + let items = ($data.taglines | where audience_id == $aid | each $project) + $acc | insert $aid $items + } + ) + + let present = ($order | where {|aid| ($by_audience | get $aid | length) > 0 }) + + # Localized focus labels (es/en) for the 6 differentiator ids — the rotator renders + # these next to each phrase and re-renders on language change. Slug fallback in the JS. + let focus_labels = { + "diff-accredited-knowledge": { es: "conocimiento acreditado", en: "accredited knowledge" }, + "diff-agent-native": { es: "nativo para agentes", en: "agent-native" }, + "diff-domain-transversality": { es: "transversalidad de dominio", en: "domain transversality" }, + "diff-graduated-adoption": { es: "adopción graduada", en: "graduated adoption" }, + "diff-reflexive-no-drift": { es: "coherencia reflexiva", en: "reflexive coherence" }, + "diff-witness-seam": { es: "costura testigo", en: "witness seam" }, + } + + # Tier taglines — a PARALLEL axis (adoption tier, not audience): projected from + # tier-taglines.ncl into `by_tier` so the hero's tier rotator (data-tier-rotator) + # cycles the per-tier lines. Empty if the file is absent (opt-in, ADR-029). + let tier_src = $"($root)/.ontoref/positioning/tier-taglines.ncl" + let by_tier = ( + if ($tier_src | path exists) { + nickel export --format json $tier_src | from json | get tier_taglines + | reduce --fold {} {|t, acc| + $acc | insert ($t.tier | into string) [{ id: $"tier-($t.tier)", es: $t.es, en: $t.en, focus: null, register: "Tier" }] + } + } else { {} } + ) + + let payload = { + generated_from: ".ontoref/positioning/taglines.ncl", + audiences: $present, + by_audience: $by_audience, + by_tier: $by_tier, + focus_labels: $focus_labels, + } + + let out_dir = ($out | path dirname) + if ($out_dir | str length) > 0 { mkdir $out_dir } + $payload | to json --indent 2 | save -f $out + print $"taglines projected → ($out) [($data.taglines | length) phrases · audiences: ($present | str join ', ')]" +} diff --git a/site/scripts/build/lib/doc-page.nu b/site/scripts/build/lib/doc-page.nu new file mode 100644 index 0000000..ed0df2b --- /dev/null +++ b/site/scripts/build/lib/doc-page.nu @@ -0,0 +1,139 @@ +#!/usr/bin/env nu + +# Shared page chrome for projected static doc surfaces (ADRs, catalog, …). +# One source of truth for the brand palette, dark/light theme, nav, and the +# common structural CSS (.doc-* classes). Consumed by gen-adr-pages.nu and +# gen-catalog-pages.nu so both outreach/web (static) and the SSR site share an +# identical look and feel. + +export def esc [s: string] { + $s | str replace --all '&' '&' | str replace --all '<' '<' | str replace --all '>' '>' | str replace --all '"' '"' +} + +export def paras [text: string] { + $text | split row "\n\n" | where {|b| ($b | str trim | is-not-empty) } + | each {|b| $"

    (esc ($b | str trim))

    " } | str join "\n" +} + +# A single
    with an anchor + h2; empty body collapses to nothing. +export def sec [anchor: string, title: string, body: string] { + if ($body | is-empty) { "" } else { $"

    (esc $title)

    ($body)
    " } +} + +# Brand-aligned CSS: ontoref palette tokens, dark default + html.light overrides, +# content-graph SVG var bindings, and the .doc-* structural classes. +export def page-css [] { r#''# } + +# Persisted theme toggle, shared across all doc pages. +export def theme-js [] { r#''# } + +# Full HTML document: brand chrome + sibling-surface nav (ADRs · Catalog) + body. +export def page-shell [title: string, content: string] { + $" + + + + +(esc $title) · Ontoref + + +(page-css) + + + +
    +($content) +
    +(theme-js) + +" +} diff --git a/site/scripts/build/linkify-adrs.nu b/site/scripts/build/linkify-adrs.nu new file mode 100644 index 0000000..f2029a3 --- /dev/null +++ b/site/scripts/build/linkify-adrs.nu @@ -0,0 +1,84 @@ +#!/usr/bin/env nu + +# Linkify ADR mentions in outreach/web/src/*.html. +# +# Two passes per file: +# 1. Fix href format: absolute https://ontoref.dev/adr/adr-NNN → site-relative /adr/NNN +# 2. Wrap plain ADR-NNN text (not already inside ...) with a link +# +# All hrefs use site-relative paths (/adr/NNN) so links work in both localhost +# and production without domain changes. +# Single-quotes are used for injected href/title/class attributes so the link +# is safe inside both text nodes and double-quoted data-en/data-es attributes. +# +# Usage (from outreach/site): +# nu scripts/build/linkify-adrs.nu +# nu scripts/build/linkify-adrs.nu --map site/public/r/adr-map.json --web ../../outreach/web --daemon https://ontoref.dev + +def escape-attr [s: string] { + $s + | str replace --all '&' '&' + | str replace --all '<' '<' + | str replace --all '>' '>' + | str replace --all "'" ''' + | str replace --all '"' '"' +} + +def main [ + --map: string = "site/public/r/adr-map.json" + --web: string = "../../outreach/web" + --daemon: string = "https://ontoref.dev" # used only to recognise existing absolute hrefs to fix +] { + let adr_map = (open $map) + let web_dir = ($web | path expand) + let src_dir = ($web_dir | path join "src") + + if not ($src_dir | path exists) { + error make { msg: $"src dir not found: ($src_dir)" } + } + + let files = (glob $"($src_dir)/*.html" | sort) + mut total_files = 0 + mut total_changes = 0 + + for f in $files { + mut content = (open --raw $f) + let original = $content + + # Sort ADRs longest-id-first so adr-010 doesn't shadow adr-0100 (hypothetical), + # and to process higher numbers first (safer for text replacement ordering). + let adrs = ($adr_map | transpose key val | sort-by key --reverse) + + for entry in $adrs { + let id = $entry.key # "adr-009" + let val = $entry.val + let num = ($id | str replace 'adr-' '') # "009" + let adr_upper = $"ADR-($num)" # "ADR-009" + let path = $"/adr/($num)" # "/adr/009" — site-relative, works everywhere + let safe_name = (escape-attr ($val.name? | default "")) + + # Pass 1: fix existing absolute hrefs to the canonical site-relative path. + # Handles double-quoted (original manual links) and single-quoted (injected links). + $content = ($content | str replace --all $"href=\"($daemon)/adr/($id)\"" $"href=\"($path)\"") + $content = ($content | str replace --all $"href=\"($daemon)/adr/($num)\"" $"href=\"($path)\"") + $content = ($content | str replace --all $"href='($daemon)/adr/($id)'" $"href='($path)'") + $content = ($content | str replace --all $"href='($daemon)/adr/($num)'" $"href='($path)'") + + # Pass 2: linkify plain ADR-NNN not already followed by + # Uses single-quote attrs: safe inside double-quoted HTML attribute values. + let link = $"($adr_upper)" + let pattern = $"ADR-($num)\(?!\)" + $content = ($content | str replace --all --regex $pattern $link) + } + + if $content != $original { + $content | save -f $f + let linked_after = ($content | find --regex "ADR-[0-9]{3}" | length) + print $" ($f | path basename): updated" + $total_files = ($total_files + 1) + $total_changes = ($total_changes + 1) + } + } + + print $"linkify-adrs: ($total_files) files updated" +} diff --git a/site/scripts/build/linkify-site.nu b/site/scripts/build/linkify-site.nu new file mode 100644 index 0000000..77c790b --- /dev/null +++ b/site/scripts/build/linkify-site.nu @@ -0,0 +1,116 @@ +#!/usr/bin/env nu + +# Linkify site conventions in blog markdown source: +# +# ontoref / Ontoref → (brand mention, case-preserving) +# ADR-NNN → ADR-NNN (decision deep-link) +# +# Operates on the MARKDOWN SOURCE (not the generated HTML) so the change survives +# every `just content` regeneration. Both rewrites are idempotent via lookarounds: +# an already-wrapped mention (preceded by `>` / followed by ``) is left as-is. +# +# Skipped, so code and metadata never get a stray link: +# - YAML frontmatter (between the leading --- delimiters) +# - fenced code blocks (``` … ```) +# - inline code spans (`…`) +# - headings (lines beginning with #) +# - compound identifiers: ontoref-daemon, ontoref/dist, .ontoref, /adr/038 (lookarounds) +# +# Usage (from outreach/site): +# nu scripts/build/linkify-site.nu # default glob +# nu scripts/build/linkify-site.nu site/content/blog/**/*.md +# nu scripts/build/linkify-site.nu --check # CI gate, no writes + +const LINK = '${n}' +const WORD = '(?\w./-])(?[Oo]ntoref)(?![<\w./-])' +const ADR_LINK = 'ADR-${num}' +const ADR_WORD = '(?/\w-])ADR-(?\d+)(?!)' + +# Apply both site rewrites outside inline-code spans on a single line. +def linkify-line [line: string]: nothing -> string { + $line + | split row '`' + | enumerate + | each {|it| + if ($it.index mod 2) == 0 { + $it.item + | str replace --all --regex $WORD $LINK + | str replace --all --regex $ADR_WORD $ADR_LINK + } else { + $it.item + } + } + | str join '`' +} + +def main [ + pattern: string = "site/content/blog/**/*.md" + --check # report posts that WOULD change and exit non-zero; never writes +] { + let files = (glob $pattern | sort) + mut changed = 0 + + for f in $files { + let lines = (open --raw $f | lines) + mut out = [] + mut in_front = false + mut in_fence = false + mut idx = 0 + + for line in $lines { + let trimmed = ($line | str trim) + + # Frontmatter: a leading --- opens it, the next --- closes it. + if $trimmed == "---" and not $in_fence { + if $idx == 0 { + $in_front = true + } else if $in_front { + $in_front = false + } + $out = ($out | append $line) + $idx = ($idx + 1) + continue + } + + if ($trimmed | str starts-with '```') { + $in_fence = (not $in_fence) + $out = ($out | append $line) + $idx = ($idx + 1) + continue + } + + if $in_front or $in_fence or ($trimmed | str starts-with '#') { + $out = ($out | append $line) + $idx = ($idx + 1) + continue + } + + $out = ($out | append (linkify-line $line)) + $idx = ($idx + 1) + } + + let result = ($out | str join "\n") + let original = (open --raw $f) + # Preserve a trailing newline if the source had one. + let final = if ($original | str ends-with "\n") { $result + "\n" } else { $result } + + if $final != $original { + if $check { + print $" ($f | path basename): unlinked site mentions pending" + } else { + $final | save -f $f + print $" ($f | path basename): linked" + } + $changed = ($changed + 1) + } + } + + if $check { + if $changed > 0 { + error make { msg: $"linkify-site --check: ($changed) post\(s\) have unlinked site mentions — run `just content`" } + } + print "linkify-site --check: all posts clean" + } else { + print $"linkify-site: ($changed) file\(s\) updated" + } +} diff --git a/site/scripts/build/test-about-contract.nu b/site/scripts/build/test-about-contract.nu new file mode 100644 index 0000000..d7872c0 --- /dev/null +++ b/site/scripts/build/test-about-contract.nu @@ -0,0 +1,53 @@ +#!/usr/bin/env nu + +# Typecheck gate for the About projection contract (about-schema.ncl). +# This is the ⛓ design gate: it must pass before any gen-about-pages projector +# or /about handler work begins. Verifies: +# 1. the real ontoref about.ncl exports (positive) +# 2. a valid 'Personal instance exports (both kinds expressible) +# 3. 'Project with a stray `personal` block is rejected (coherence) +# 4. 'Personal missing its `personal` block is rejected (coherence) +# +# Usage (from outreach/site): +# nu scripts/build/test-about-contract.nu +# nu scripts/build/test-about-contract.nu --root ../.. + +def exports [dir: string, body: string] { + let tmp = $"($dir)/.about-contract-test.ncl" + $body | save -f $tmp + let ok = (do { nickel export --format json $tmp } | complete | get exit_code) == 0 + rm -f $tmp + $ok +} + +def main [--root: string = "../.."] { + let pos_dir = $"($root)/.ontoref/positioning" + if not ($"($pos_dir)/about-schema.ncl" | path exists) { + error make { msg: $"about-schema.ncl not found under ($pos_dir)" } + } + + mut failures = [] + + # 1 — real instance exports + let real_ok = (do { nickel export --format json $"($pos_dir)/about.ncl" } | complete | get exit_code) == 0 + if not $real_ok { $failures = ($failures | append "about.ncl (ontoref, 'Project) failed to export") } + + # 2 — valid personal instance + let personal_ok = (exports $pos_dir "let s = import \"about-schema.ncl\" in { kind = 'Personal, level_id = \"t\", personal = { template = \"about.j2\" } } | s.AboutShape | s.AboutCoherence") + if not $personal_ok { $failures = ($failures | append "valid 'Personal instance was rejected") } + + # 3 — 'Project with stray personal block must be rejected + let proj_stray_rejected = not (exports $pos_dir "let s = import \"about-schema.ncl\" in { kind = 'Project, level_id = \"t\", personal = { template = \"about.j2\" } } | s.AboutShape | s.AboutCoherence") + if not $proj_stray_rejected { $failures = ($failures | append "'Project with stray personal block was accepted (coherence breach)") } + + # 4 — 'Personal missing its block must be rejected + let personal_missing_rejected = not (exports $pos_dir "let s = import \"about-schema.ncl\" in { kind = 'Personal, level_id = \"t\" } | s.AboutShape | s.AboutCoherence") + if not $personal_missing_rejected { $failures = ($failures | append "'Personal missing its block was accepted (coherence breach)") } + + if ($failures | is-empty) { + print "about-contract: 4/4 checks pass — ⛓ gate OPEN" + } else { + for f in $failures { print $" ✗ ($f)" } + error make { msg: $"about-contract: ($failures | length) check\(s\) failed — ⛓ gate CLOSED" } + } +} diff --git a/site/site/_public/README.md b/site/site/_public/README.md new file mode 100644 index 0000000..9b9fb32 --- /dev/null +++ b/site/site/_public/README.md @@ -0,0 +1,27 @@ +# website-impl Public Assets + +This directory contains static assets served directly by the web server. + +## Directory Structure + +``` +public/ +├── images/ # Static images (logos, icons, etc.) +├── scripts/ # Client-side JavaScript +├── styles/ # Public CSS files +├── documents/ # Downloadable documents +└── README.md # This file +``` + +## Usage + +Files in this directory are served at the root URL: +- `public/logo.png` → `https://yoursite.com/logo.png` +- `public/styles/custom.css` → `https://yoursite.com/styles/custom.css` + +## Guidelines + +- Keep file sizes optimized for web delivery +- Use descriptive filenames +- Organize by type (images, scripts, styles, documents) +- Consider CDN deployment for production diff --git a/site/site/_public/console-logger.js b/site/site/_public/console-logger.js new file mode 100644 index 0000000..3244b32 --- /dev/null +++ b/site/site/_public/console-logger.js @@ -0,0 +1,95 @@ +/** + * Console Logger - Captures all browser console output to localStorage + * This allows viewing logs from /public/logs.html across all pages + */ + +(function() { + // Capture all console methods + const originalLog = console.log; + const originalError = console.error; + const originalWarn = console.warn; + const originalInfo = console.info; + const originalDebug = console.debug; + + function formatTimestamp() { + return new Date().toISOString(); + } + + function captureLog(level, args) { + const message = args.map(arg => { + if (typeof arg === 'object') { + try { + return JSON.stringify(arg, null, 2); + } catch (e) { + return String(arg); + } + } + return String(arg); + }).join(' '); + + const logEntry = { + timestamp: formatTimestamp(), + level, + message + }; + + // Save to localStorage for logs viewer + try { + const stored = localStorage.getItem('console_logs'); + let logs = stored ? JSON.parse(stored) : []; + logs.push(logEntry); + // Keep last 100 logs in localStorage + if (logs.length > 100) { + logs = logs.slice(-100); + } + localStorage.setItem('console_logs', JSON.stringify(logs)); + } catch (e) { + // localStorage might be unavailable or full - silently fail + } + } + + // Override console methods + console.log = function(...args) { + originalLog(...args); + captureLog('LOG', args); + }; + + console.error = function(...args) { + originalError(...args); + captureLog('ERROR', args); + }; + + console.warn = function(...args) { + originalWarn(...args); + captureLog('WARN', args); + }; + + console.info = function(...args) { + originalInfo(...args); + captureLog('INFO', args); + }; + + console.debug = function(...args) { + originalDebug(...args); + captureLog('DEBUG', args); + }; + + // Capture uncaught errors and unhandled promise rejections (module load failures go here) + window.addEventListener('error', function(e) { + const msg = e.message || 'Unknown error'; + const src = e.filename ? ` @ ${e.filename}:${e.lineno}` : ''; + captureLog('ERROR', [`[WINDOW-ERROR] ${msg}${src}`]); + originalError('[WINDOW-ERROR]', msg, src, e.error); + }); + + window.addEventListener('unhandledrejection', function(e) { + const reason = e.reason instanceof Error + ? `${e.reason.message}\n${e.reason.stack}` + : String(e.reason); + captureLog('ERROR', [`[UNHANDLED-REJECTION] ${reason}`]); + originalError('[UNHANDLED-REJECTION]', e.reason); + }); + + // Initial message + originalLog('🔍 Console Logger initialized - capturing to localStorage'); +})(); diff --git a/site/site/_public/images/JesusPerez_f.jpg b/site/site/_public/images/JesusPerez_f.jpg new file mode 100644 index 0000000..f0bbc3c Binary files /dev/null and b/site/site/_public/images/JesusPerez_f.jpg differ diff --git a/site/site/_public/images/favicon.ico b/site/site/_public/images/favicon.ico new file mode 100644 index 0000000..2ba8527 Binary files /dev/null and b/site/site/_public/images/favicon.ico differ diff --git a/site/site/_public/images/jpl-imago-160.png b/site/site/_public/images/jpl-imago-160.png new file mode 100644 index 0000000..a0a350d Binary files /dev/null and b/site/site/_public/images/jpl-imago-160.png differ diff --git a/site/site/_public/images/jpl-imago-320.png b/site/site/_public/images/jpl-imago-320.png new file mode 100644 index 0000000..3a89175 Binary files /dev/null and b/site/site/_public/images/jpl-imago-320.png differ diff --git a/site/site/_public/images/jpl-imago-static-sig.png.svg b/site/site/_public/images/jpl-imago-static-sig.png.svg new file mode 100644 index 0000000..86163d3 --- /dev/null +++ b/site/site/_public/images/jpl-imago-static-sig.png.svg @@ -0,0 +1,173 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Jesús rez + \ No newline at end of file diff --git a/site/site/_public/images/jpl-imago-static-sig.svg b/site/site/_public/images/jpl-imago-static-sig.svg new file mode 100644 index 0000000..b3a6fde --- /dev/null +++ b/site/site/_public/images/jpl-imago-static-sig.svg @@ -0,0 +1,180 @@ + + + + JPL Imago Static Signature + Jesús Pérez + https://rustelo.dev + Yin-Yang symbolic figure with signature — coexistence principle + © 2026 Jesús Pérez. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Jesús rez + \ No newline at end of file diff --git a/site/site/_public/images/jpl-imago-static.svg b/site/site/_public/images/jpl-imago-static.svg new file mode 100644 index 0000000..a28f32b --- /dev/null +++ b/site/site/_public/images/jpl-imago-static.svg @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/site/site/_public/images/logo-dark.png b/site/site/_public/images/logo-dark.png new file mode 100644 index 0000000..ecf47c9 Binary files /dev/null and b/site/site/_public/images/logo-dark.png differ diff --git a/site/site/_public/images/logo-light.png b/site/site/_public/images/logo-light.png new file mode 100644 index 0000000..1217712 Binary files /dev/null and b/site/site/_public/images/logo-light.png differ diff --git a/site/site/_public/images/ontoref-logo.svg b/site/site/_public/images/ontoref-logo.svg new file mode 100644 index 0000000..180b071 --- /dev/null +++ b/site/site/_public/images/ontoref-logo.svg @@ -0,0 +1,193 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Ontoref + + + Structure that remembers why + + + diff --git a/site/site/_public/images/ontoref.svg b/site/site/_public/images/ontoref.svg new file mode 100644 index 0000000..180b071 --- /dev/null +++ b/site/site/_public/images/ontoref.svg @@ -0,0 +1,193 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Ontoref + + + Structure that remembers why + + + diff --git a/site/site/_public/images/ontoref_text.svg b/site/site/_public/images/ontoref_text.svg new file mode 100644 index 0000000..349c9ee --- /dev/null +++ b/site/site/_public/images/ontoref_text.svg @@ -0,0 +1,19 @@ + + + + + + + + + Ontoref + + + Structure that remembers why. + + + diff --git a/site/site/_public/images/ontoref_v.svg b/site/site/_public/images/ontoref_v.svg new file mode 100644 index 0000000..95d7a8a --- /dev/null +++ b/site/site/_public/images/ontoref_v.svg @@ -0,0 +1,193 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Ontoref + + + Structure that remembers why + + + diff --git a/site/site/_public/images/projects/ontoref_architecture-dark.svg b/site/site/_public/images/projects/ontoref_architecture-dark.svg new file mode 100644 index 0000000..3f2b5f9 --- /dev/null +++ b/site/site/_public/images/projects/ontoref_architecture-dark.svg @@ -0,0 +1,273 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + PROTOCOL LAYERS + + + + + + + + DECLARATIVE · NICKEL + type-safe contracts · fails at definition time + + + .ontology/ + + adrs/*.ncl + + modes/*.ncl + + schemas/*.ncl + + qa.ncl + + config.ncl + + + nickel export → JSON + + + + + KNOWLEDGE GRAPH · .ontology/ + self-describing · actor-agnostic · machine-queryable + + + axioms + + tensions + + practices + + nodes + edges + + invariants + + gates + + + reads KG via nickel export + + + + + OPERATIONAL · NUSHELL + 16 modules · DAG-validated modes · typed pipelines + + + adr.nu + + backlog.nu + + describe.nu + + sync.nu + + coder.nu + + +11 modules + + + dispatches command + actor + + + + + ENTRY POINT · BASH → NU + actor detect · advisory lock · NICKEL_IMPORT_PATH + + + ./ontoref + + actor detect + + advisory lock + + → ontoref.nu + + + optional · caches · serves · never required + + + + + RUNTIME · RUST + axum (optional daemon) + 10 UI pages · 19 MCP tools · actor registry · search · SurrealDB? + + + ontoref-daemon + + axum HTTP + + MCP ×19 + + DashMap + + search + + SurrealDB? + + + configures · ONTOREF_PROJECT_ROOT + + + + + ADOPTION · PER-PROJECT + templates/ · one ontoref checkout · zero lock-in + + + templates/ + + adopt_ontoref + + ONTOREF_PROJECT_ROOT + + zero lock-in + + plain NCL + + + + + + REQUEST FLOWS + + + CLI path — developer · agent · CI + + + + + Actor + dev · agent · CI + + + + + ./ontoref + bash wrapper + + + lock + + + ontoref.nu + Nu dispatcher + + + + + module.nu + adr · backlog · etc + + + export + + + .ontology/ + nickel export + + + + + reflection/ + qa.ncl · backlog + + + + daemon? (optional) + + + MCP path — AI agent · Claude Code · any MCP client + + + AI Agent + Claude · any MCP + + + + + MCP server + stdio · HTTP /mcp + + + + + 19 tools + read · write · query + + + + + NCL files + git-versioned + + + Pre-commit barrier — notification-gated commit path + + + git commit + pre-commit fires + + + + + GET /notify + /pending?token=X + + + + + pending? + + + YES + + + BLOCK + until acked in UI + + + NO — daemon unreachable → fail-open + + + acked + + + PASS ✓ + commit proceeds + + + ontoref v0.1.0 + diff --git a/site/site/_public/images/projects/ontoref_architecture-light.svg b/site/site/_public/images/projects/ontoref_architecture-light.svg new file mode 100644 index 0000000..bff30ef --- /dev/null +++ b/site/site/_public/images/projects/ontoref_architecture-light.svg @@ -0,0 +1,273 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + PROTOCOL LAYERS + + + + + + + + DECLARATIVE · NICKEL + type-safe contracts · fails at definition time + + + .ontology/ + + adrs/*.ncl + + modes/*.ncl + + schemas/*.ncl + + qa.ncl + + config.ncl + + + nickel export → JSON + + + + + KNOWLEDGE GRAPH · .ontology/ + self-describing · actor-agnostic · machine-queryable + + + axioms + + tensions + + practices + + nodes + edges + + invariants + + gates + + + reads KG via nickel export + + + + + OPERATIONAL · NUSHELL + 16 modules · DAG-validated modes · typed pipelines + + + adr.nu + + backlog.nu + + describe.nu + + sync.nu + + coder.nu + + +11 modules + + + dispatches command + actor + + + + + ENTRY POINT · BASH → NU + actor detect · advisory lock · NICKEL_IMPORT_PATH + + + ./ontoref + + actor detect + + advisory lock + + → ontoref.nu + + + optional · caches · serves · never required + + + + + RUNTIME · RUST + axum (optional daemon) + 10 UI pages · 19 MCP tools · actor registry · search · SurrealDB? + + + ontoref-daemon + + axum HTTP + + MCP ×19 + + DashMap + + search + + SurrealDB? + + + configures · ONTOREF_PROJECT_ROOT + + + + + ADOPTION · PER-PROJECT + templates/ · one ontoref checkout · zero lock-in + + + templates/ + + adopt_ontoref + + ONTOREF_PROJECT_ROOT + + zero lock-in + + plain NCL + + + + + + REQUEST FLOWS + + + CLI path — developer · agent · CI + + + + + Actor + dev · agent · CI + + + + + ./ontoref + bash wrapper + + + lock + + + ontoref.nu + Nu dispatcher + + + + + module.nu + adr · backlog · etc + + + export + + + .ontology/ + nickel export + + + + + reflection/ + qa.ncl · backlog + + + + daemon? (optional) + + + MCP path — AI agent · Claude Code · any MCP client + + + AI Agent + Claude · any MCP + + + + + MCP server + stdio · HTTP /mcp + + + + + 19 tools + read · write · query + + + + + NCL files + git-versioned + + + Pre-commit barrier — notification-gated commit path + + + git commit + pre-commit fires + + + + + GET /notify + /pending?token=X + + + + + pending? + + + YES + + + BLOCK + until acked in UI + + + NO — daemon unreachable → fail-open + + + acked + + + PASS ✓ + commit proceeds + + + ontoref v0.1.0 + diff --git a/site/site/_public/images/projects/ontoref_graph_view-dark.png b/site/site/_public/images/projects/ontoref_graph_view-dark.png new file mode 100644 index 0000000..81dbee3 Binary files /dev/null and b/site/site/_public/images/projects/ontoref_graph_view-dark.png differ diff --git a/site/site/_public/images/projects/ontoref_graph_view-light.png b/site/site/_public/images/projects/ontoref_graph_view-light.png new file mode 100644 index 0000000..6eb5d3b Binary files /dev/null and b/site/site/_public/images/projects/ontoref_graph_view-light.png differ diff --git a/site/site/_public/images/projects/provisioning_architecture-dark.svg b/site/site/_public/images/projects/provisioning_architecture-dark.svg new file mode 100644 index 0000000..4eb7b37 --- /dev/null +++ b/site/site/_public/images/projects/provisioning_architecture-dark.svg @@ -0,0 +1,700 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CONTROL PLANE ARCHITECTURE + + + + + + + + + $ + CLI + provisioning + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Events Stream + NATS JetStream + :4222 + + + + + + + + + + + + + + + Orchestrator + :9011 + + + + Task State Machine • Provider API • SSH + + Webhooks • Rollback • Audit Collector + + + + + + + + + + Configuration + Nickel • ∞ TypeDialog + + + + + + + + + + Control Center + :9012 + + + + Cedar Policies • JWT • Sessions + + WebSocket • RBAC • Solo: auto-session + + + + + + + + + + + Extensions Registry + :8084 + + + + Git • OCI • LRU + + Providers • Taskservs • vault:// creds + + + + + + + + + + 🧠 + AI • MCP + :9082 + + + + RAG Engine • MCP Server • Tools + + Embeddings • Model Routing • KGraph + + + + + + + + + + 🛡 + Vault Service + :9094 + + + + Lease Lifecycle • Key Management + + SOPS • Age • Secrets never in NATS + + + + + + + + + + + + + + + + + + + + + + + + + + + + HTTPS + + + + + HTTPS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DATA PERSISTENCE + Solo: RocksDB • Multi: WebSocket + + + + orchestrator + + + vault + + + control_center + + + audit + + + workspace + + + Nickel config + + + + + + + + SOLID ENFORCEMENT LAYERS + + + Compile-time + + Dev-time + + Pre-commit + + CI/CD + + Runtime + + Audit + + + Providers APIs or CLI: Orchestrator only | SSH: Orchestrator + Machines | Auth: Control Center only | Secrets: Vault Service API only + + + + + + + + SERVICES + Orchestrator + Control Center + Vault Service + Extension Registry + AI • MCP + + INFRASTRUCTURE + + NATS Orbital Ring + + + + OCI registry + + Token and Auth + + SecretumVault + + Nickel + + + + SurrealDB + + CONNECTIONS + + + Encrypted + + I/O and Defs + + DBs data + + Secure access + + Events Stream + + MODES + + SOLO + RocksDB + local NATS + auto-session + + MULTI + WebSocket DB + NATS cluster + JWT+Cedar + + ENT + Enterprise + + + + + + + + Production + Hetzner • AWS + + + Staging + K8s cluster + + + Dev + Solo mode + + + Edge + On-prem • IoT + + + Custom + GitOps • Webhook + + + + v3.0.11 • ∞ Architecture + diff --git a/site/site/_public/images/projects/provisioning_architecture-light.svg b/site/site/_public/images/projects/provisioning_architecture-light.svg new file mode 100644 index 0000000..4291305 --- /dev/null +++ b/site/site/_public/images/projects/provisioning_architecture-light.svg @@ -0,0 +1,703 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CONTROL PLANE ARCHITECTURE + + + + + + + + + $ + CLI + provisioning + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Events Stream + NATS JetStream + :4222 + + + + + + + + + + + + + + + Orchestrator + :9011 + + + + Task State Machine • Provider API • SSH + + Webhooks • Rollback • Audit Collector + + + + + + + + + + Configuration + Nickel • ∞ TypeDialog + + + + + + + + + + Control Center + :9012 + + + + Cedar Policies • JWT • Sessions + + WebSocket • RBAC • Solo: auto-session + + + + + + + + + + + Extensions Registry + :8084 + + + + Git • OCI • LRU + + Providers • Taskservs • vault:// creds + + + + + + + + + + 🧠 + AI • MCP + :9082 + + + + RAG Engine • MCP Server • Tools + + Embeddings • Model Routing • KGraph + + + + + + + + + + 🛡 + Vault Service + :9094 + + + + Lease Lifecycle • Key Management + + SOPS • Age • Secrets never in NATS + + + + + + + + + + + + + + + + + + + + + + + + + + + + HTTPS + + + + + HTTPS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DATA PERSISTENCE + Solo: RocksDB • Multi: WebSocket + + + + orchestrator + + + vault + + + control_center + + + audit + + + workspace + + + Nickel config + + + + + + + + SOLID ENFORCEMENT LAYERS + + + Compile-time + + Dev-time + + Pre-commit + + CI/CD + + Runtime + + Audit + + + Providers APIs or CLI: Orchestrator only | SSH: Orchestrator + Machines | Auth: Control Center only | Secrets: Vault Service API only + + + + + + + + SERVICES + Orchestrator + Control Center + Vault Service + Extension Registry + AI • MCP + + INFRASTRUCTURE + + NATS Orbital Ring + + + + OCI registry + + Token and Auth + + SecretumVault + + Nickel + + + + SurrealDB + + CONNECTIONS + + + Encrypted + + I/O and Defs + + DBs data + + Secure access + + Events Stream + + MODES + + SOLO + RocksDB + local NATS + auto-session + + MULTI + WebSocket DB + NATS cluster + JWT+Cedar + + ENT + Enterprise + + + + + + + + Production + Hetzner • AWS + + + Staging + K8s cluster + + + Dev + Solo mode + + + Edge + On-prem • IoT + + + Custom + GitOps • Webhook + + + + v3.0.11 • ∞ Architecture + diff --git a/site/site/_public/images/projects/stratumiops_operation-flow-dark.svg b/site/site/_public/images/projects/stratumiops_operation-flow-dark.svg new file mode 100644 index 0000000..c4f18b2 --- /dev/null +++ b/site/site/_public/images/projects/stratumiops_operation-flow-dark.svg @@ -0,0 +1,253 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + stratumiops — Operation Flow + two main flows: knowledge registration · config sealed state — actor-aware, advisory locking + + + + + FLOW A — KNOWLEDGE REGISTRATION (stratum register) + + + + FLOW B — CONFIG SEALED STATE (stratum config apply) + + + + + + ACTOR + developer (TTY) + agent (piped) + ci / admin + + + + + + LOCK ACQUIRE + mkdir .stratum/locks/changelog + write PID to lock dir + stale? kill -0 PID → remove + + + + + + FORM (actor-aware) + developer → typedialog UI + agent/ci → nickel-export + tera + forms/*.ncl drives field layout + + + filled + + + + REGISTER OUTPUTS + CHANGELOG append (locked) + ADR hint → adrs/adr-NNN.ncl + ontology patch → .ontology/ + mode stub → reflection/modes/ + optional: trigger config apply + + + + LOCK RELEASE + rm -rf .stratum/locks/changelog + triggered by EXIT trap + + + + + + + + + + ADR CONSTRAINT VALIDATION (stratum adr validate) + nickel export adrs/*.ncl | where status == Accepted | get constraints | where severity == Hard + for each: nu -c check_hint → exit 0 + no stdout = pass + ^nickel export (not plugin cache) — always fresh · Superseded ADRs excluded + + + + + + + + + ACTOR + developer (TTY) + agent (piped) + ci / admin + + + + + + LOCK ACQUIRE + mkdir .stratum/locks/manifest + write PID to lock dir + stale? kill -0 PID → remove + + + + + + EXPORT + HASH + nickel export <profile>.ncl + sha256(export) → seal.hash + sha256(snapshot) → snapshot_hash + + + + + + HISTORY ENTRY + cfg-<YYYYMMDDTHHmmSS>-<actor>.ncl + ConfigState record (immutable) + seal.hash + snapshot_hash + supersedes: prev entry | null + + + + + + PATCH MANIFEST + manifest.ncl → active[<profile>] + = <new cfg entry ref> + under manifest lock + + + + LOCK RELEASE + rm -rf .stratum/locks/manifest + triggered by EXIT trap + + + + + + + + + + DRIFT AUDIT (stratum config audit) + for each profile in manifest: sha256(live profile.ncl) == manifest.active[p].seal.hash + match → clean · mismatch → drift (warn actor, optionally block) + read-only; no lock acquired · rollback: verify snapshot_hash → restore → new apply (forward operation) + + + + actor flows through both paths automatically — locking is per-resource, not global + + + stratum.sh register + STRATUM_ACTOR=developer (TTY) + typedialog form → CHANGELOG locked + + + stratum.sh config apply prod + STRATUM_ACTOR=ci + manifest lock → cfg-ts-ci.ncl written + + + stratum.sh config audit + any actor, no lock needed + sha256 compare live vs seal.hash + + + stratum.sh config rollback <ts> + verify snapshot_hash → restore → new apply + rollback is always a forward operation + + + + Legend: + + main flow + + pass / ok + + error / lock release + + form complete + + config flow (hash/write) + + output / audit + + Lock resources: + changelog (register) · manifest (config apply/rollback) · backlog (backlog done/cancel) + History IDs: + cfg-<YYYYMMDDTHHmmSS>-<actor>.ncl — timestamp collision-free, no lock needed for history writes + + + + FLOW C — BACKLOG (stratum backlog …) + + + backlog list|show + read backlog.ncl (no lock) + nickel export → filter/sort + + + + + backlog add + no lock (append, unique ID) + typed item: Todo|Bug|Wish|Debt + + + + + backlog done|cancel + mkdir lock on backlog resource + mutates status field in backlog.ncl + + + + + backlog promote + graduates_to: Adr|Mode|StateTransition + triggers adr or mode stub creation + + + + + backlog roadmap + state.ncl dimensions not-yet-reached + open items by priority (read-only) + + 2026-03 + diff --git a/site/site/_public/images/projects/stratumiops_operation-flow-light.svg b/site/site/_public/images/projects/stratumiops_operation-flow-light.svg new file mode 100644 index 0000000..1db8b3d --- /dev/null +++ b/site/site/_public/images/projects/stratumiops_operation-flow-light.svg @@ -0,0 +1,252 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + stratumiops — Operation Flow + two main flows: knowledge registration · config sealed state — actor-aware, advisory locking + + + + + FLOW A — KNOWLEDGE REGISTRATION (stratum register) + + + + FLOW B — CONFIG SEALED STATE (stratum config apply) + + + + + + ACTOR + developer (TTY) + agent (piped) + ci / admin + + + + + + LOCK ACQUIRE + mkdir .stratum/locks/changelog + write PID to lock dir + stale? kill -0 PID → remove + + + + + + FORM (actor-aware) + developer → typedialog UI + agent/ci → nickel-export + tera + forms/*.ncl drives field layout + + + filled + + + + REGISTER OUTPUTS + CHANGELOG append (locked) + ADR hint → adrs/adr-NNN.ncl + ontology patch → .ontology/ + mode stub → reflection/modes/ + optional: trigger config apply + + + + LOCK RELEASE + rm -rf .stratum/locks/changelog + triggered by EXIT trap + + + + + + + + + + ADR CONSTRAINT VALIDATION (stratum adr validate) + nickel export adrs/*.ncl | where status == Accepted | get constraints | where severity == Hard + for each: nu -c check_hint → exit 0 + no stdout = pass + ^nickel export (not plugin cache) — always fresh · Superseded ADRs excluded + + + + + + + + + ACTOR + developer (TTY) + agent (piped) + ci / admin + + + + + + LOCK ACQUIRE + mkdir .stratum/locks/manifest + write PID to lock dir + stale? kill -0 PID → remove + + + + + + EXPORT + HASH + nickel export <profile>.ncl + sha256(export) → seal.hash + sha256(snapshot) → snapshot_hash + + + + + + HISTORY ENTRY + cfg-<YYYYMMDDTHHmmSS>-<actor>.ncl + ConfigState record (immutable) + seal.hash + snapshot_hash + supersedes: prev entry | null + + + + + + PATCH MANIFEST + manifest.ncl → active[<profile>] + = <new cfg entry ref> + under manifest lock + + + + LOCK RELEASE + rm -rf .stratum/locks/manifest + triggered by EXIT trap + + + + + + + + + + DRIFT AUDIT (stratum config audit) + for each profile in manifest: sha256(live profile.ncl) == manifest.active[p].seal.hash + match → clean · mismatch → drift (warn actor, optionally block) + read-only; no lock acquired · rollback: verify snapshot_hash → restore → new apply (forward operation) + + + + actor flows through both paths automatically — locking is per-resource, not global + + + stratum.sh register + STRATUM_ACTOR=developer (TTY) + typedialog form → CHANGELOG locked + + + stratum.sh config apply prod + STRATUM_ACTOR=ci + manifest lock → cfg-ts-ci.ncl written + + + stratum.sh config audit + any actor, no lock needed + sha256 compare live vs seal.hash + + + stratum.sh config rollback <ts> + verify snapshot_hash → restore → new apply + rollback is always a forward operation + + + + Legend: + + main flow + + pass / ok + + error / lock release + + form complete + + config flow (hash/write) + + output / audit + + Lock resources: + changelog (register) · manifest (config apply/rollback) · backlog (backlog done/cancel) + History IDs: + cfg-<YYYYMMDDTHHmmSS>-<actor>.ncl — timestamp collision-free, no lock needed for history writes + + + + FLOW C — BACKLOG (stratum backlog …) + + + backlog list|show + read backlog.ncl (no lock) + nickel export → filter/sort + + + + + backlog add + no lock (append, unique ID) + typed item: Todo|Bug|Wish|Debt + + + + + backlog done|cancel + mkdir lock on backlog resource + mutates status field in backlog.ncl + + + + + backlog promote + graduates_to: Adr|Mode|StateTransition + triggers adr or mode stub creation + + + + + backlog roadmap + state.ncl dimensions not-yet-reached + open items by priority (read-only) + + 2026-03 + diff --git a/site/site/_public/images/projects/stratumiops_orchestrator-dark.svg b/site/site/_public/images/projects/stratumiops_orchestrator-dark.svg new file mode 100644 index 0000000..0685f35 --- /dev/null +++ b/site/site/_public/images/projects/stratumiops_orchestrator-dark.svg @@ -0,0 +1,477 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +STRATUM ORCHESTRATOR +Event-Driven · Graph-Guided · Atomic Execution · Stateless · OCI-Native + + +EVENT SOURCES + + + + +provisioning +emits dev.crate.> +crate-modified · deploy + + + + +kogral +emits dev.knowledge.> +node-updated · indexed + + + + +syntaxis +emits dev.project.> +phase · task-completed + + + + +stratumiops +emits dev.model.> +llm-call · embed-request + + + + +typedialog +emits dev.form.> +submitted · validated + + ++ more ... + + + + + + + + + + + + + + + + + + + + +NATS +JetStream +dev.> + + + + + + + + + + + + + + + + + +NKey verify +ed25519 JWT + + + + + + +STRATUM ORCHESTRATOR +Agnostic · Stateless · Graph-Guided + + + + +◈ ActionGraph +in-memory · Nickel nodes +topo-sort · cycle-detect + + + +⬡ PipelineCtx +DB-first · typed caps +schema-validated + + + +▶ StageRunner +JoinSet · parallel stages +CancellationToken +retry + backoff on failure +saga compensate.nu + + + +⚙ RuleEngine + +Cedar + +◈ Cedar +permit · forbid · conditions +per-node authz policies + +◈ NKey +ed25519 asymmetric keys +JWT per-process · verify + +↺ Saga rollback on failure · compensate.nu in reverse + + + + + + + + + + + +SurrealDB +Pipeline state · Step results +orchestrator_state ns +crash recovery + + + + + +SecretumVault +Credentials · TTL leases +vault:/secret/... +never in NATS payload + + + + + +Zot OCI Registry +Node defs · Nickel libs +oci://registry/nodes/ +content-addressed · signed + + +OPTIONAL + + + + + + + + +Kogral +Knowledge graph +node-updated triggers + +optional + + + +Syntaxis +Project orchestration +phase-transition events + +optional + + + +TypeDialog +Service config UI +startup config NCL only + + + + + + + + + + +Git repo +Git events → NATS +webhook → dev.crate.> +push · tag · pr + + + + + + + + + + +Nu Executor +Atomic steps · Pure functions +scripts/nu/*.nu +stdout=output · exit-code=status + + + + +AI Agent +stratum-llm · NATS protocol +dev.agent.*.requested/responded +oneshot correlation · timeout + + + + + +Nickel Base Library +OCI-published · content-addressed · build-verified · typecheck-gated +orchestrator-types.ncl · capability-schemas.ncl · defaults.ncl + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + StratumIOps + + + + + + + +LEGEND + +event flow (NATS) + +auth / credentials + +execution + +state (DB) + +OCI / registry + +optional + + +stratumiops · v0.1 + diff --git a/site/site/_public/images/projects/stratumiops_orchestrator-light.svg b/site/site/_public/images/projects/stratumiops_orchestrator-light.svg new file mode 100644 index 0000000..59e2814 --- /dev/null +++ b/site/site/_public/images/projects/stratumiops_orchestrator-light.svg @@ -0,0 +1,469 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +STRATUM ORCHESTRATOR +Event-Driven · Graph-Guided · Atomic Execution · Stateless · OCI-Native + + +EVENT SOURCES + + + + +provisioning +emits dev.crate.> +crate-modified · deploy + + + + +kogral +emits dev.knowledge.> +node-updated · indexed + + + + +syntaxis +emits dev.project.> +phase · task-completed + + + + +stratumiops +emits dev.model.> +llm-call · embed-request + + + + +typedialog +emits dev.form.> +submitted · validated + + ++ more ... + + + + + + + + + + + + + + + + + + +NATS +JetStream +dev.> + + + + + + + + + + + + + + + + + +NKey verify +ed25519 JWT + + + + + + +STRATUM ORCHESTRATOR +Agnostic · Stateless · Graph-Guided + + + + +◈ ActionGraph +in-memory · Nickel nodes +topo-sort · cycle-detect + + + +⬡ PipelineCtx +DB-first · typed caps +schema-validated + + + +▶ StageRunner +JoinSet · parallel stages +CancellationToken +retry + backoff on failure +saga compensate.nu + + + +⚙ RuleEngine + +Cedar + +◈ Cedar +permit · forbid · conditions +per-node authz policies + +◈ NKey +ed25519 asymmetric keys +JWT per-process · verify + +↺ Saga rollback on failure · compensate.nu in reverse + + + + + + + + + + +SurrealDB +Pipeline state · Step results +orchestrator_state ns +crash recovery + + + + + +SecretumVault +Credentials · TTL leases +vault:/secret/... +never in NATS payload + + + + + +Zot OCI Registry +Node defs · Nickel libs +oci://registry/nodes/ +content-addressed · signed + + +OPTIONAL + + + + + + + +Kogral +Knowledge graph +node-updated triggers + +optional + + + +Syntaxis +Project orchestration +phase-transition events + +optional + + + +TypeDialog +Service config UI +startup config NCL only + + + + + + + + +Git repo +Git events → NATS +webhook → dev.crate.> +push · tag · pr + + + + + + + + +Nu Executor +Atomic steps · Pure functions +scripts/nu/*.nu +stdout=output · exit-code=status + + + + +AI Agent +stratum-llm · NATS protocol +dev.agent.*.requested/responded +oneshot correlation · timeout + + + + + +Nickel Base Library +OCI-published · content-addressed · build-verified · typecheck-gated +orchestrator-types.ncl · capability-schemas.ncl · defaults.ncl + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + StratumIOps + + + + + + + +LEGEND + +event flow (NATS) + +auth / credentials + +execution + +state (DB) + +OCI / registry + +optional + + +stratumiops · v0.1 + diff --git a/site/site/_public/images/projects/syntaxis_architecture-dark.svg b/site/site/_public/images/projects/syntaxis_architecture-dark.svg new file mode 100644 index 0000000..5537d7f --- /dev/null +++ b/site/site/_public/images/projects/syntaxis_architecture-dark.svg @@ -0,0 +1,372 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SYNTAXIS Architecture + Systematic Orchestration, Perfectly Arranged + + + + + + + + User Interfaces + + 4 + + + + + CLI + syntaxis-cli + clap structured commands + Automation & CI/CD + + ACTIVE + + + + + + TUI + syntaxis-tui + ratatui + vim keys (hjkl) + Interactive & SSH-friendly + + ACTIVE + + + + + + Dashboard + Leptos WASM (CSR) + Trunk + UnoCSS build + Real-time & Responsive + + ACTIVE + + + + + + REST API + syntaxis-api (axum) + Tower middleware + WebSocket + Auth, Rate Limit, Metrics + + ACTIVE + + + + + + + + + + + + + + + + + Core Engine + syntaxis-core + + + + + Domain Models + + Project + id, name, metadata + Phase + create/devel/pub/arch + Task + state, priority, audit + Template + reusable structures + + + + + + Phase Lifecycle + + + + CREATE + + + + DEVEL + + + + PUBLISH + + + + ARCHIVE + State transitions with audit trail + + + + + + Business Logic + + Audit + Full change history & rollback + Checklist + Phase-based verification + Security + Auth & access control + Config + TOML + Nickel driven + + + + + + Quality + + 632+ TESTS + + ZERO UNSAFE + + NO UNWRAP() + + 100% DOCS + + CLIPPY 0W + thiserror | Result<T> | #![forbid(unsafe_code)] | cargo fmt | cargo audit + + + + + Test distribution: + core:173 + tui-lib:262 + api-lib:93 + vapora:57 + dashboard:52 + shared:33 + tui:10 + dash-shared:4 + + + + + + + + + VAPORA SST + syntaxis-vapora + + + + Orchestration Adapter + + Agent triggering on task state changes + Phase-based workflow orchestration + Enterprise audit & rollback support + + + + + Event Streaming + + NATS + KOGRAL & SYNTAXIS_SOURCE streams + Real-time federation between services + + + + 57 TESTS | ACTIVE + + + + + + + + Persistence Layer + Database trait abstraction + + + + + SQLite (Default) + + sqlx async + Connection pooling + WAL mode + Prepared statements | Indexed queries | Batch ops + Best for: Solo dev, small teams, local + + + + + + SurrealDB 2.3 + + 40+ operations + Type-safe async | JSON binding + In-Memory | File (RocksDB) | Server | Docker | K8s + Best for: Teams, distributed, enterprise + + + + + + Configuration + + TOML + Nickel + Runtime backend selection + database-default.toml | database-surrealdb.toml + Switch via: cp configs/database-*.toml configs/database.toml + + + + + + + + + + + + + + + + Shared Libraries + + + + shared-api-lib + REST utilities, request/response helpers (93 tests) + + + + + rust-tui + TUI utilities, ratatui helpers (262 tests) + + + + + tools-shared + Configuration, utilities, helpers (33 tests) + + + + + + + + + Infra + + + tokio + + + + axum + + + + sqlx + + + + serde + + + + clap + + + + ratatui + + + + leptos + + + + tower + + + + nats + + + + nickel + + + + + SYNTAXIS v0.x | Rust 2021 (MSRV 1.75+) | 10K+ LOC | 632+ tests | Production Beta + diff --git a/site/site/_public/images/projects/syntaxis_architecture-light.svg b/site/site/_public/images/projects/syntaxis_architecture-light.svg new file mode 100644 index 0000000..b93709b --- /dev/null +++ b/site/site/_public/images/projects/syntaxis_architecture-light.svg @@ -0,0 +1,316 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SYNTAXIS Architecture + Systematic Orchestration, Perfectly Arranged + + + + + + User Interfaces + + 4 + + + + CLI + syntaxis-cli + clap structured commands + Automation & CI/CD + + ACTIVE + + + + + TUI + syntaxis-tui + ratatui + vim keys (hjkl) + Interactive & SSH-friendly + + ACTIVE + + + + + Dashboard + Leptos WASM (CSR) + Trunk + UnoCSS build + Real-time & Responsive + + ACTIVE + + + + + REST API + syntaxis-api (axum) + Tower middleware + WebSocket + Auth, Rate Limit, Metrics + + ACTIVE + + + + + + + + + + + + + + + Core Engine + syntaxis-core + + + + Domain Models + + Projectid, name, metadata + Phasecreate/devel/pub/arch + Taskstate, priority, audit + Templatereusable structures + + + + + Phase Lifecycle + + + CREATE + + + DEVEL + + + PUBLISH + + ARCHIVE + State transitions with audit trail + + + + + Business Logic + + AuditFull change history & rollback + ChecklistPhase-based verification + SecurityAuth & access control + ConfigTOML + Nickel driven + + + + + Quality + + 632+ TESTS + + ZERO UNSAFE + + NO UNWRAP() + + 100% DOCS + + CLIPPY 0W + thiserror | Result<T> | #![forbid(unsafe_code)] | cargo fmt | cargo audit + + + + Test distribution: + core:173 + tui-lib:262 + api-lib:93 + vapora:57 + dashboard:52 + shared:33 + tui:10 + dash-shared:4 + + + + + + + VAPORA SST + syntaxis-vapora + + + + Orchestration Adapter + + Agent triggering on task state changes + Phase-based workflow orchestration + Enterprise audit & rollback support + + + + + Event Streaming + + NATS + KOGRAL & SYNTAXIS_SOURCE streams + Real-time federation between services + + + + 57 TESTS | ACTIVE + + + + + + Persistence Layer + Database trait abstraction + + + + SQLite (Default) + + sqlx asyncConnection pooling + WAL mode + Prepared statements | Indexed queries | Batch ops + Best for: Solo dev, small teams, local + + + + + SurrealDB 2.3 + + 40+ operationsType-safe async | JSON binding + In-Memory | File (RocksDB) | Server | Docker | K8s + Best for: Teams, distributed, enterprise + + + + + Configuration + + TOML + NickelRuntime backend selection + database-default.toml | database-surrealdb.toml + Switch via: cp configs/database-*.toml configs/database.toml + + + + + + + + + + + + + + Shared Libraries + + + + shared-api-lib + REST utilities, request/response helpers (93 tests) + + + + + rust-tui + TUI utilities, ratatui helpers (262 tests) + + + + + tools-shared + Configuration, utilities, helpers (33 tests) + + + + + + + Infra + + tokio + + + axum + + + sqlx + + + serde + + + clap + + + ratatui + + + leptos + + + tower + + + nats + + + nickel + + + + + SYNTAXIS v0.x | Rust 2021 (MSRV 1.75+) | 10K+ LOC | 632+ tests | Production Beta + diff --git a/site/site/_public/images/projects/typedialog_architecture-dark.svg b/site/site/_public/images/projects/typedialog_architecture-dark.svg new file mode 100644 index 0000000..e4c8908 --- /dev/null +++ b/site/site/_public/images/projects/typedialog_architecture-dark.svg @@ -0,0 +1,253 @@ + + + + + + + + + + + + + + + + + + + + + + + + + TypeDialog Architecture + Multi-Backend Form Orchestration Layer + + + FORM DEFINITIONS + + + + + Nickel (.ncl) + nickel export --format json + + + + TOML (.toml) + serde direct deserialization + + + + load_form() — Unified Entry Point + Extension dispatch: .ncl → subprocess | .toml → serde | Fail-fast on errors + + + + + FormDefinition + + + TYPEDIALOG-CORE + + + + THREE-PHASE EXECUTION + + + + Phase 1: Selectors + Identify & execute + + + + Phase 2: Element List + Pure, no I/O + + + + Phase 3: Dispatch + when/when_false eval + + + + + + + + + CORE MODULES + + + + form_parser + TOML/Nickel parsing + Field definitions + + + + validation + Nickel contracts + Pre/post conditions + + + + i18n (Fluent) + Locale detection + .ftl translations + + + + encryption + Field-level encrypt + External services + + + + templates (Tera) + Jinja2-compatible + Variable rendering + + + + RenderContext + results: HashMap + locale + + + + BackendFactory + #[cfg(feature)] compile-time + + runtime BackendType match + + + + + Box<dyn FormBackend> + + trait FormBackend: Send + Sync { execute_field, execute_form_complete } + + + BACKENDS (6 CRATES) + + + + + CLI + inquire 0.9 + Interactive prompts + Scripts, CI/CD + + + + TUI + ratatui + Terminal UI + Keyboard + Mouse + + + + Web + axum + HTTP server + Browser forms + + + + AI + RAG + embeddings + Semantic search + Knowledge graph + + + + Agent + Multi-LLM execution + .agent.mdx files + Streaming, templates + + + + Prov-Gen + IaC generation + AWS, GCP, Azure + Hetzner, UpCloud, LXD + + + + + HashMap<String, Value> + + + OUTPUT FORMATS + + + + + JSON + + + YAML + + + TOML + + + Nickel Roundtrip + + + LLM PROVIDERS + + + + Claude + + + OpenAI + + + Gemini + + + Ollama (local) + + + INTEGRATIONS + + + + Nushell Plugin + + + Nickel Contracts + + + Template Engine (Tera) + + + Multi-Cloud APIs + + + CI/CD (GitHub + Woodpecker) + + + 8 crates | 6 backends | 3,818 tests | 4 output formats | 4 LLM providers | 6 cloud targets + + + diff --git a/site/site/_public/images/projects/typedialog_architecture-light.svg b/site/site/_public/images/projects/typedialog_architecture-light.svg new file mode 100644 index 0000000..63f7daf --- /dev/null +++ b/site/site/_public/images/projects/typedialog_architecture-light.svg @@ -0,0 +1,229 @@ + + + + + + + + + + + + + + + + + TypeDialog Architecture + Multi-Backend Form Orchestration Layer + + + FORM DEFINITIONS + + + + + Nickel (.ncl) + nickel export --format json + + + + TOML (.toml) + serde direct deserialization + + + + load_form() — Unified Entry Point + Extension dispatch: .ncl → subprocess | .toml → serde | Fail-fast on errors + + + + + FormDefinition + + + TYPEDIALOG-CORE + + + + THREE-PHASE EXECUTION + + + Phase 1: Selectors + Identify & execute + + + Phase 2: Element List + Pure, no I/O + + + Phase 3: Dispatch + when/when_false eval + + + + + + + + CORE MODULES + + + form_parser + TOML/Nickel parsing + Field definitions + + + validation + Nickel contracts + Pre/post conditions + + + i18n (Fluent) + Locale detection + .ftl translations + + + encryption + Field-level encrypt + External services + + + templates (Tera) + Jinja2-compatible + Variable rendering + + + + RenderContext + results: HashMap + locale + + + + BackendFactory + #[cfg(feature)] compile-time + + runtime BackendType match + + + + + Box<dyn FormBackend> + + trait FormBackend: Send + Sync { execute_field, execute_form_complete } + + + BACKENDS (6 CRATES) + + + + CLI + inquire 0.9 + Interactive prompts + Scripts, CI/CD + + + TUI + ratatui + Terminal UI + Keyboard + Mouse + + + Web + axum + HTTP server + Browser forms + + + AI + RAG + embeddings + Semantic search + Knowledge graph + + + Agent + Multi-LLM execution + .agent.mdx files + Streaming, templates + + + Prov-Gen + IaC generation + AWS, GCP, Azure + Hetzner, UpCloud, LXD + + + + + HashMap<String, Value> + + + OUTPUT FORMATS + + + + JSON + + + YAML + + + TOML + + + Nickel Roundtrip + + + LLM PROVIDERS + + + + Claude + + + OpenAI + + + Gemini + + + Ollama (local) + + + INTEGRATIONS + + + + Nushell Plugin + + + Nickel Contracts + + + Template Engine (Tera) + + + Multi-Cloud APIs + + + CI/CD (GitHub + Woodpecker) + + + 8 crates | 6 backends | 3,818 tests | 4 output formats | 4 LLM providers | 6 cloud targets + + + diff --git a/site/site/_public/images/projects/vapora_architecture-dark.svg b/site/site/_public/images/projects/vapora_architecture-dark.svg new file mode 100644 index 0000000..024d66f --- /dev/null +++ b/site/site/_public/images/projects/vapora_architecture-dark.svg @@ -0,0 +1,576 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + VAPORA ARCHITECTURE + 18 CRATES · 354 TESTS · 100% RUST + + + PRESENTATION + SERVICES + INTELLIGENCE + DATA + PROVIDERS + + + + + + + + + + + Leptos WASM Frontend + Kanban Board · Glassmorphism UI · UnoCSS · Reactive Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Axum Backend API + 40+ REST Endpoints · 161 Tests + + + Projects + Tasks + Agents + Workflows + Proposals + Swarm + Metrics + RLM API + WebSocket + + + + + + + + Agent Runtime + Orchestration · Learning Profiles · 71 Tests + + Registry + Coordinator + Scoring + Profiles + 12 Roles + Health Checks + Recency Bias + + + + + + + + + + + + + MCP Gateway + Model Context Protocol · Plugin System + + Stdio Transport + SSE Transport + JSON-RPC + 6 Tools + Tool Registry + Schema Validation + + + + + + + A2A Protocol + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RLM Engine + Recursive Language Models · 38 Tests · 17k+ LOC + + + + + + Chunking + + + Hybrid Search + + + Dispatcher + + + + + BM25 + + + Semantic + + + RRF + + + Sandbox + + + + + + + + + + + + + + + + + + + Multi-IA LLM Router + Budget Enforcement · Cost Tracking · 53 Tests + + + + + Rule Router + + Budget Manager + + Cost Tracker + + + + Fallback Chain + + Cost Ranker + + Providers + + + + + + + + + + + + + + + + + + Swarm Coordinator + Load Balancing · Prometheus · 6 Tests + + + + Assignment + + Filtering + + Metrics + + + + success_rate / (1+load) + + Capabilities + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Knowledge Graph + Temporal History · Learning Curves · 20 Tests + + + Executions + + Similarity + + Causal + + + + + + + + SurrealDB + Multi-Model Database · Multi-Tenant Scopes + + + + Projects + + Tasks + + Agents + + Chunks + + A2A + + + + + + + + + + + + + + + + + + + + + + + + + NATS JetStream + Message Queue · Async Coordination · Pub/Sub + + + + Agent Jobs + + Workflows + + Proposals + + A2A + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Anthropic Claude + Opus · Sonnet · Haiku + + + + + + OpenAI + GPT-4 · GPT-4o · GPT-3.5 + + + + + + Google Gemini + 2.0 Pro · Flash · 1.5 Pro + + + + + + Ollama (Local) + Llama · Mistral · CodeLlama + + + + + + + + + SUPPORTING CRATES + + vapora-shared + vapora-tracking + vapora-telemetry + vapora-analytics + vapora-worktree + vapora-doc-lifecycle + vapora-workflow-engine + vapora-cli + vapora-leptos-ui + + + + + + + + + + + + PROMETHEUS · GRAFANA · OPENTELEMETRY + + + + + + + VAPORA v1.2.0 + Evaporate complexity · Full-Stack Rust · Production Ready + + + + + diff --git a/site/site/_public/images/projects/vapora_architecture-light.svg b/site/site/_public/images/projects/vapora_architecture-light.svg new file mode 100644 index 0000000..2dd47d8 --- /dev/null +++ b/site/site/_public/images/projects/vapora_architecture-light.svg @@ -0,0 +1,576 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + VAPORA ARCHITECTURE + 18 CRATES · 354 TESTS · 100% RUST + + + PRESENTATION + SERVICES + INTELLIGENCE + DATA + PROVIDERS + + + + + + + + + + + Leptos WASM Frontend + Kanban Board · Glassmorphism UI · UnoCSS · Reactive Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Axum Backend API + 40+ REST Endpoints · 161 Tests + + + Projects + Tasks + Agents + Workflows + Proposals + Swarm + Metrics + RLM API + WebSocket + + + + + + + + Agent Runtime + Orchestration · Learning Profiles · 71 Tests + + Registry + Coordinator + Scoring + Profiles + 12 Roles + Health Checks + Recency Bias + + + + + + + + + + + + + MCP Gateway + Model Context Protocol · Plugin System + + Stdio Transport + SSE Transport + JSON-RPC + 6 Tools + Tool Registry + Schema Validation + + + + + + + A2A Protocol + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RLM Engine + Recursive Language Models · 38 Tests · 17k+ LOC + + + + + + Chunking + + + Hybrid Search + + + Dispatcher + + + + + BM25 + + + Semantic + + + RRF + + + Sandbox + + + + + + + + + + + + + + + + + + + Multi-IA LLM Router + Budget Enforcement · Cost Tracking · 53 Tests + + + + + Rule Router + + Budget Manager + + Cost Tracker + + + + Fallback Chain + + Cost Ranker + + Providers + + + + + + + + + + + + + + + + + + Swarm Coordinator + Load Balancing · Prometheus · 6 Tests + + + + Assignment + + Filtering + + Metrics + + + + success_rate / (1+load) + + Capabilities + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Knowledge Graph + Temporal History · Learning Curves · 20 Tests + + + Executions + + Similarity + + Causal + + + + + + + + SurrealDB + Multi-Model Database · Multi-Tenant Scopes + + + + Projects + + Tasks + + Agents + + Chunks + + A2A + + + + + + + + + + + + + + + + + + + + + + + + + NATS JetStream + Message Queue · Async Coordination · Pub/Sub + + + + Agent Jobs + + Workflows + + Proposals + + A2A + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Anthropic Claude + Opus · Sonnet · Haiku + + + + + + OpenAI + GPT-4 · GPT-4o · GPT-3.5 + + + + + + Google Gemini + 2.0 Pro · Flash · 1.5 Pro + + + + + + Ollama (Local) + Llama · Mistral · CodeLlama + + + + + + + + + SUPPORTING CRATES + + vapora-shared + vapora-tracking + vapora-telemetry + vapora-analytics + vapora-worktree + vapora-doc-lifecycle + vapora-workflow-engine + vapora-cli + vapora-leptos-ui + + + + + + + + + + + + PROMETHEUS · GRAFANA · OPENTELEMETRY + + + + + + + VAPORA v1.2.0 + Evaporate complexity · Full-Stack Rust · Production Ready + + + + + diff --git a/site/site/_public/js/content-graph-shim.js b/site/site/_public/js/content-graph-shim.js new file mode 100644 index 0000000..6573c2f --- /dev/null +++ b/site/site/_public/js/content-graph-shim.js @@ -0,0 +1,262 @@ +/** + * content-graph-shim.js + * + * Thin bridge between Leptos/WASM and Cytoscape.js. + * Exposes window.ContentGraph with a single render() entry point. + * + * Called from GraphView component after mount: + * window.ContentGraph.render(containerId, focusNodeId, theme) + * + * Reads graph data from: + * + * + * Requires (loaded before this script via assets pipeline): + * - cytoscape.min.js + * - cytoscape-cose-bilkent.min.js + */ + +(function () { + 'use strict'; + + // ── Theme tokens ──────────────────────────────────────────────────────────── + + function buildPalette(isDark) { + return { + // Content node variants + contentFill: isDark ? '#3b82f6' : '#2563eb', + // Ontology node variants by level + axiomFill: isDark ? '#8b5cf6' : '#7c3aed', + tensionFill: isDark ? '#f59e0b' : '#d97706', + practiceFill: isDark ? '#10b981' : '#059669', + projectFill: isDark ? '#06b6d4' : '#0891b2', + // ADR + adrFill: isDark ? '#f97316' : '#ea580c', + // Ego node ring + egoStroke: isDark ? '#facc15' : '#ca8a04', + // Edge colours by kind group + declaredEdge: isDark ? '#6b7280' : '#9ca3af', + autoEdge: isDark ? '#4b5563' : '#d1d5db', + implicitEdge: isDark ? '#374151' : '#e5e7eb', + // Labels + labelColor: isDark ? '#f9fafb' : '#111827', + labelBg: isDark ? 'rgba(17,24,39,0.75)' : 'rgba(255,255,255,0.75)', + // Background + bg: isDark ? '#111827' : '#ffffff', + }; + } + + // ── Cytoscape stylesheet ───────────────────────────────────────────────────── + + function buildStylesheet(palette, egoId) { + const label = { + label: 'data(title)', + color: palette.labelColor, + 'background-color':palette.labelBg, + 'background-opacity': 0.75, + 'background-padding': '3px', + 'font-size': '11px', + 'text-valign': 'bottom', + 'text-halign': 'center', + 'text-margin-y': '4px', + }; + + return [ + // Default node + { + selector: 'node', + style: { + ...label, + shape: 'ellipse', + width: '36px', + height: '36px', + 'background-color': palette.contentFill, + 'border-width': '0px', + 'cursor': 'pointer', + }, + }, + // Ego node (focused) + { + selector: `node[id="${egoId}"]`, + style: { + width: '48px', + height: '48px', + 'border-width': '3px', + 'border-color': palette.egoStroke, + 'font-size': '13px', + 'font-weight': 'bold', + }, + }, + // Ontology variants + { selector: 'node[level="Axiom"]', style: { 'background-color': palette.axiomFill, shape: 'diamond' } }, + { selector: 'node[level="Tension"]', style: { 'background-color': palette.tensionFill, shape: 'triangle' } }, + { selector: 'node[level="Practice"]', style: { 'background-color': palette.practiceFill, shape: 'rectangle' } }, + { selector: 'node[level="Project"]', style: { 'background-color': palette.projectFill, shape: 'rectangle' } }, + // ADR + { selector: 'node[type="adr"]', style: { 'background-color': palette.adrFill, shape: 'pentagon' } }, + // Hover + { + selector: 'node:selected', + style: { 'border-width': '3px', 'border-color': palette.egoStroke }, + }, + // Default edge + { + selector: 'edge', + style: { + width: '1.5px', + 'line-color': palette.declaredEdge, + 'target-arrow-color':palette.declaredEdge, + 'target-arrow-shape':'triangle', + 'curve-style': 'bezier', + opacity: 0.7, + }, + }, + // Auto-backlink edges (lighter) + { + selector: 'edge[source="auto_backlink"]', + style: { 'line-color': palette.autoEdge, 'target-arrow-color': palette.autoEdge, opacity: 0.5 }, + }, + // Implicit / SharedTag edges (dashed, very light) + { + selector: 'edge[kind="shared_tag"]', + style: { + 'line-style': 'dashed', + 'line-color': palette.implicitEdge, + 'target-arrow-color':palette.implicitEdge, + opacity: 0.35, + }, + }, + ]; + } + + // ── Element conversion ─────────────────────────────────────────────────────── + + function toCytoscapeElements(graphData) { + const nodes = (graphData.nodes || []).map(n => ({ + data: { + id: n.id, + title: n.title || n.name || n.id, + type: n.type, + level: n.level || null, + url: n.url || null, + instance: n.instance || null, + }, + })); + + const edges = (graphData.edges || []).map((e, i) => ({ + data: { + id: `e-${i}`, + source: e.from, + target: e.to, + kind: e.kind, + source_type: e.source, // "declared" | "auto_backlink" | "implicit" + }, + })); + + return [...nodes, ...edges]; + } + + // ── Layout config ──────────────────────────────────────────────────────────── + + function buildLayout(nodeCount) { + // cose-bilkent for larger graphs; grid fallback for tiny graphs + if (nodeCount <= 4) { + return { name: 'circle', animate: false, padding: 40 }; + } + return { + name: 'cose-bilkent', + animate: false, + nodeDimensionsIncludeLabels: true, + idealEdgeLength: 100, + nodeRepulsion: 8000, + padding: 30, + }; + } + + // ── Instances registry ─────────────────────────────────────────────────────── + // Keeps track of active cy instances so they can be destroyed on re-render. + + const _instances = new Map(); + + // ── Public API ─────────────────────────────────────────────────────────────── + + window.ContentGraph = { + /** + * Render the knowledge graph into `containerId`. + * + * @param {string} containerId - DOM id of the container element + * @param {string} focusNodeId - node id to highlight/center; may be empty + * @param {string} theme - "dark" | "light" + */ + render(containerId, focusNodeId, theme) { + const container = document.getElementById(containerId); + if (!container) { + console.warn('[ContentGraph] container not found:', containerId); + return; + } + + // Destroy previous instance if re-rendering + if (_instances.has(containerId)) { + _instances.get(containerId).destroy(); + _instances.delete(containerId); + } + + // Read graph data embedded by SSR + const dataEl = document.getElementById('content-graph-data'); + if (!dataEl) { + console.warn('[ContentGraph] #content-graph-data not found'); + return; + } + + let graphData; + try { + graphData = JSON.parse(dataEl.textContent); + } catch (e) { + console.error('[ContentGraph] failed to parse graph data:', e); + return; + } + + const isDark = theme === 'dark'; + const palette = buildPalette(isDark); + const egoId = focusNodeId || ''; + + const cy = window.cytoscape({ + container, + elements: toCytoscapeElements(graphData), + style: buildStylesheet(palette, egoId), + layout: buildLayout((graphData.nodes || []).length), + minZoom: 0.3, + maxZoom: 4, + }); + + _instances.set(containerId, cy); + + // Navigate to content URL on node tap + cy.on('tap', 'node', evt => { + const url = evt.target.data('url'); + if (url) window.location.href = url; + }); + + // Pointer cursor on hover + cy.on('mouseover', 'node[?url]', () => { container.style.cursor = 'pointer'; }); + cy.on('mouseout', 'node', () => { container.style.cursor = 'default'; }); + + // Focus and center on ego node + if (egoId) { + const egoNode = cy.$(`#${CSS.escape(egoId)}`); + if (egoNode.length) { + cy.animate({ fit: { eles: egoNode.neighborhood().add(egoNode), padding: 60 } }, { duration: 400 }); + } + } + }, + + /** + * Destroy the Cytoscape instance for a given container (cleanup on unmount). + */ + destroy(containerId) { + if (_instances.has(containerId)) { + _instances.get(containerId).destroy(); + _instances.delete(containerId); + } + }, + }; +})(); diff --git a/site/site/_public/js/cytoscape-cose-bilkent.js b/site/site/_public/js/cytoscape-cose-bilkent.js new file mode 100644 index 0000000..f8847be --- /dev/null +++ b/site/site/_public/js/cytoscape-cose-bilkent.js @@ -0,0 +1,5296 @@ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.cytoscapeCoseBilkent = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0) + + { + this.positionNodesRadially(forest); + } + // The graph associated with this layout is not flat or a forest + else + { + this.positionNodesRandomly(); + } + } + + this.initSpringEmbedder(); + this.runSpringEmbedder(); + + return true; +}; + +CoSELayout.prototype.tick = function() { + this.totalIterations++; + + if (this.totalIterations === this.maxIterations) { + return true; // Layout is not ended return true + } + + if (this.totalIterations % FDLayoutConstants.CONVERGENCE_CHECK_PERIOD == 0) + { + if (this.isConverged()) + { + return true; // Layout is not ended return true + } + + this.coolingFactor = this.initialCoolingFactor * + ((this.maxIterations - this.totalIterations) / this.maxIterations); + this.animationPeriod = Math.ceil(this.initialAnimationPeriod * Math.sqrt(this.coolingFactor)); + + } + this.totalDisplacement = 0; + this.graphManager.updateBounds(); + this.calcSpringForces(); + this.calcRepulsionForces(); + this.calcGravitationalForces(); + this.moveNodes(); + this.animate(); + + return false; // Layout is not ended yet return false +}; + +CoSELayout.prototype.getPositionsData = function() { + var allNodes = this.graphManager.getAllNodes(); + var pData = {}; + for (var i = 0; i < allNodes.length; i++) { + var rect = allNodes[i].rect; + var id = allNodes[i].id; + pData[id] = { + id: id, + x: rect.getCenterX(), + y: rect.getCenterY(), + w: rect.width, + h: rect.height + }; + } + + return pData; +}; + +CoSELayout.prototype.runSpringEmbedder = function () { + this.initialAnimationPeriod = 25; + this.animationPeriod = this.initialAnimationPeriod; + var layoutEnded = false; + + // If aminate option is 'during' signal that layout is supposed to start iterating + if ( FDLayoutConstants.ANIMATE === 'during' ) { + this.emit('layoutstarted'); + } + else { + // If aminate option is 'during' tick() function will be called on index.js + while (!layoutEnded) { + layoutEnded = this.tick(); + } + + this.graphManager.updateBounds(); + } +}; + +CoSELayout.prototype.calculateNodesToApplyGravitationTo = function () { + var nodeList = []; + var graph; + + var graphs = this.graphManager.getGraphs(); + var size = graphs.length; + var i; + for (i = 0; i < size; i++) + { + graph = graphs[i]; + + graph.updateConnected(); + + if (!graph.isConnected) + { + nodeList = nodeList.concat(graph.getNodes()); + } + } + + this.graphManager.setAllNodesToApplyGravitation(nodeList); +}; + +CoSELayout.prototype.createBendpoints = function () { + var edges = []; + edges = edges.concat(this.graphManager.getAllEdges()); + var visited = new HashSet(); + var i; + for (i = 0; i < edges.length; i++) + { + var edge = edges[i]; + + if (!visited.contains(edge)) + { + var source = edge.getSource(); + var target = edge.getTarget(); + + if (source == target) + { + edge.getBendpoints().push(new PointD()); + edge.getBendpoints().push(new PointD()); + this.createDummyNodesForBendpoints(edge); + visited.add(edge); + } + else + { + var edgeList = []; + + edgeList = edgeList.concat(source.getEdgeListToNode(target)); + edgeList = edgeList.concat(target.getEdgeListToNode(source)); + + if (!visited.contains(edgeList[0])) + { + if (edgeList.length > 1) + { + var k; + for (k = 0; k < edgeList.length; k++) + { + var multiEdge = edgeList[k]; + multiEdge.getBendpoints().push(new PointD()); + this.createDummyNodesForBendpoints(multiEdge); + } + } + visited.addAll(list); + } + } + } + + if (visited.size() == edges.length) + { + break; + } + } +}; + +CoSELayout.prototype.positionNodesRadially = function (forest) { + // We tile the trees to a grid row by row; first tree starts at (0,0) + var currentStartingPoint = new Point(0, 0); + var numberOfColumns = Math.ceil(Math.sqrt(forest.length)); + var height = 0; + var currentY = 0; + var currentX = 0; + var point = new PointD(0, 0); + + for (var i = 0; i < forest.length; i++) + { + if (i % numberOfColumns == 0) + { + // Start of a new row, make the x coordinate 0, increment the + // y coordinate with the max height of the previous row + currentX = 0; + currentY = height; + + if (i != 0) + { + currentY += CoSEConstants.DEFAULT_COMPONENT_SEPERATION; + } + + height = 0; + } + + var tree = forest[i]; + + // Find the center of the tree + var centerNode = Layout.findCenterOfTree(tree); + + // Set the staring point of the next tree + currentStartingPoint.x = currentX; + currentStartingPoint.y = currentY; + + // Do a radial layout starting with the center + point = + CoSELayout.radialLayout(tree, centerNode, currentStartingPoint); + + if (point.y > height) + { + height = Math.floor(point.y); + } + + currentX = Math.floor(point.x + CoSEConstants.DEFAULT_COMPONENT_SEPERATION); + } + + this.transform( + new PointD(LayoutConstants.WORLD_CENTER_X - point.x / 2, + LayoutConstants.WORLD_CENTER_Y - point.y / 2)); +}; + +CoSELayout.radialLayout = function (tree, centerNode, startingPoint) { + var radialSep = Math.max(this.maxDiagonalInTree(tree), + CoSEConstants.DEFAULT_RADIAL_SEPARATION); + CoSELayout.branchRadialLayout(centerNode, null, 0, 359, 0, radialSep); + var bounds = LGraph.calculateBounds(tree); + + var transform = new Transform(); + transform.setDeviceOrgX(bounds.getMinX()); + transform.setDeviceOrgY(bounds.getMinY()); + transform.setWorldOrgX(startingPoint.x); + transform.setWorldOrgY(startingPoint.y); + + for (var i = 0; i < tree.length; i++) + { + var node = tree[i]; + node.transform(transform); + } + + var bottomRight = + new PointD(bounds.getMaxX(), bounds.getMaxY()); + + return transform.inverseTransformPoint(bottomRight); +}; + +CoSELayout.branchRadialLayout = function (node, parentOfNode, startAngle, endAngle, distance, radialSeparation) { + // First, position this node by finding its angle. + var halfInterval = ((endAngle - startAngle) + 1) / 2; + + if (halfInterval < 0) + { + halfInterval += 180; + } + + var nodeAngle = (halfInterval + startAngle) % 360; + var teta = (nodeAngle * IGeometry.TWO_PI) / 360; + + // Make polar to java cordinate conversion. + var cos_teta = Math.cos(teta); + var x_ = distance * Math.cos(teta); + var y_ = distance * Math.sin(teta); + + node.setCenter(x_, y_); + + // Traverse all neighbors of this node and recursively call this + // function. + var neighborEdges = []; + neighborEdges = neighborEdges.concat(node.getEdges()); + var childCount = neighborEdges.length; + + if (parentOfNode != null) + { + childCount--; + } + + var branchCount = 0; + + var incEdgesCount = neighborEdges.length; + var startIndex; + + var edges = node.getEdgesBetween(parentOfNode); + + // If there are multiple edges, prune them until there remains only one + // edge. + while (edges.length > 1) + { + //neighborEdges.remove(edges.remove(0)); + var temp = edges[0]; + edges.splice(0, 1); + var index = neighborEdges.indexOf(temp); + if (index >= 0) { + neighborEdges.splice(index, 1); + } + incEdgesCount--; + childCount--; + } + + if (parentOfNode != null) + { + //assert edges.length == 1; + startIndex = (neighborEdges.indexOf(edges[0]) + 1) % incEdgesCount; + } + else + { + startIndex = 0; + } + + var stepAngle = Math.abs(endAngle - startAngle) / childCount; + + for (var i = startIndex; + branchCount != childCount; + i = (++i) % incEdgesCount) + { + var currentNeighbor = + neighborEdges[i].getOtherEnd(node); + + // Don't back traverse to root node in current tree. + if (currentNeighbor == parentOfNode) + { + continue; + } + + var childStartAngle = + (startAngle + branchCount * stepAngle) % 360; + var childEndAngle = (childStartAngle + stepAngle) % 360; + + CoSELayout.branchRadialLayout(currentNeighbor, + node, + childStartAngle, childEndAngle, + distance + radialSeparation, radialSeparation); + + branchCount++; + } +}; + +CoSELayout.maxDiagonalInTree = function (tree) { + var maxDiagonal = Integer.MIN_VALUE; + + for (var i = 0; i < tree.length; i++) + { + var node = tree[i]; + var diagonal = node.getDiagonal(); + + if (diagonal > maxDiagonal) + { + maxDiagonal = diagonal; + } + } + + return maxDiagonal; +}; + +CoSELayout.prototype.calcRepulsionRange = function () { + // formula is 2 x (level + 1) x idealEdgeLength + return (2 * (this.level + 1) * this.idealEdgeLength); +}; + +module.exports = CoSELayout; + +},{"./CoSEConstants":1,"./CoSEEdge":2,"./CoSEGraph":3,"./CoSEGraphManager":4,"./CoSENode":6,"./FDLayout":9,"./FDLayoutConstants":10,"./IGeometry":15,"./Integer":17,"./LGraph":19,"./Layout":23,"./LayoutConstants":24,"./Point":25,"./PointD":26,"./Transform":30}],6:[function(require,module,exports){ +var FDLayoutNode = require('./FDLayoutNode'); +var IMath = require('./IMath'); + +function CoSENode(gm, loc, size, vNode) { + FDLayoutNode.call(this, gm, loc, size, vNode); +} + + +CoSENode.prototype = Object.create(FDLayoutNode.prototype); +for (var prop in FDLayoutNode) { + CoSENode[prop] = FDLayoutNode[prop]; +} + +CoSENode.prototype.move = function () +{ + var layout = this.graphManager.getLayout(); + this.displacementX = layout.coolingFactor * + (this.springForceX + this.repulsionForceX + this.gravitationForceX); + this.displacementY = layout.coolingFactor * + (this.springForceY + this.repulsionForceY + this.gravitationForceY); + + + if (Math.abs(this.displacementX) > layout.coolingFactor * layout.maxNodeDisplacement) + { + this.displacementX = layout.coolingFactor * layout.maxNodeDisplacement * + IMath.sign(this.displacementX); + } + + if (Math.abs(this.displacementY) > layout.coolingFactor * layout.maxNodeDisplacement) + { + this.displacementY = layout.coolingFactor * layout.maxNodeDisplacement * + IMath.sign(this.displacementY); + } + + // a simple node, just move it + if (this.child == null) + { + this.moveBy(this.displacementX, this.displacementY); + } + // an empty compound node, again just move it + else if (this.child.getNodes().length == 0) + { + this.moveBy(this.displacementX, this.displacementY); + } + // non-empty compound node, propogate movement to children as well + else + { + this.propogateDisplacementToChildren(this.displacementX, + this.displacementY); + } + + layout.totalDisplacement += + Math.abs(this.displacementX) + Math.abs(this.displacementY); + + this.springForceX = 0; + this.springForceY = 0; + this.repulsionForceX = 0; + this.repulsionForceY = 0; + this.gravitationForceX = 0; + this.gravitationForceY = 0; + this.displacementX = 0; + this.displacementY = 0; +}; + +CoSENode.prototype.propogateDisplacementToChildren = function (dX, dY) +{ + var nodes = this.getChild().getNodes(); + var node; + for (var i = 0; i < nodes.length; i++) + { + node = nodes[i]; + if (node.getChild() == null) + { + node.moveBy(dX, dY); + node.displacementX += dX; + node.displacementY += dY; + } + else + { + node.propogateDisplacementToChildren(dX, dY); + } + } +}; + +CoSENode.prototype.setPred1 = function (pred1) +{ + this.pred1 = pred1; +}; + +CoSENode.prototype.getPred1 = function () +{ + return pred1; +}; + +CoSENode.prototype.getPred2 = function () +{ + return pred2; +}; + +CoSENode.prototype.setNext = function (next) +{ + this.next = next; +}; + +CoSENode.prototype.getNext = function () +{ + return next; +}; + +CoSENode.prototype.setProcessed = function (processed) +{ + this.processed = processed; +}; + +CoSENode.prototype.isProcessed = function () +{ + return processed; +}; + +module.exports = CoSENode; + +},{"./FDLayoutNode":12,"./IMath":16}],7:[function(require,module,exports){ +function DimensionD(width, height) { + this.width = 0; + this.height = 0; + if (width !== null && height !== null) { + this.height = height; + this.width = width; + } +} + +DimensionD.prototype.getWidth = function () +{ + return this.width; +}; + +DimensionD.prototype.setWidth = function (width) +{ + this.width = width; +}; + +DimensionD.prototype.getHeight = function () +{ + return this.height; +}; + +DimensionD.prototype.setHeight = function (height) +{ + this.height = height; +}; + +module.exports = DimensionD; + +},{}],8:[function(require,module,exports){ +function Emitter(){ + this.listeners = []; +} + +var p = Emitter.prototype; + +p.addListener = function( event, callback ){ + this.listeners.push({ + event: event, + callback: callback + }); +}; + +p.removeListener = function( event, callback ){ + for( var i = this.listeners.length; i >= 0; i-- ){ + var l = this.listeners[i]; + + if( l.event === event && l.callback === callback ){ + this.listeners.splice( i, 1 ); + } + } +}; + +p.emit = function( event, data ){ + for( var i = 0; i < this.listeners.length; i++ ){ + var l = this.listeners[i]; + + if( event === l.event ){ + l.callback( data ); + } + } +}; + +module.exports = Emitter; + +},{}],9:[function(require,module,exports){ +var Layout = require('./Layout'); +var FDLayoutConstants = require('./FDLayoutConstants'); +var LayoutConstants = require('./LayoutConstants'); +var IGeometry = require('./IGeometry'); +var IMath = require('./IMath'); + +function FDLayout() { + Layout.call(this); + + this.useSmartIdealEdgeLengthCalculation = FDLayoutConstants.DEFAULT_USE_SMART_IDEAL_EDGE_LENGTH_CALCULATION; + this.idealEdgeLength = FDLayoutConstants.DEFAULT_EDGE_LENGTH; + this.springConstant = FDLayoutConstants.DEFAULT_SPRING_STRENGTH; + this.repulsionConstant = FDLayoutConstants.DEFAULT_REPULSION_STRENGTH; + this.gravityConstant = FDLayoutConstants.DEFAULT_GRAVITY_STRENGTH; + this.compoundGravityConstant = FDLayoutConstants.DEFAULT_COMPOUND_GRAVITY_STRENGTH; + this.gravityRangeFactor = FDLayoutConstants.DEFAULT_GRAVITY_RANGE_FACTOR; + this.compoundGravityRangeFactor = FDLayoutConstants.DEFAULT_COMPOUND_GRAVITY_RANGE_FACTOR; + this.displacementThresholdPerNode = (3.0 * FDLayoutConstants.DEFAULT_EDGE_LENGTH) / 100; + this.coolingFactor = 1.0; + this.initialCoolingFactor = 1.0; + this.totalDisplacement = 0.0; + this.oldTotalDisplacement = 0.0; + this.maxIterations = FDLayoutConstants.MAX_ITERATIONS; +} + +FDLayout.prototype = Object.create(Layout.prototype); + +for (var prop in Layout) { + FDLayout[prop] = Layout[prop]; +} + +FDLayout.prototype.initParameters = function () { + Layout.prototype.initParameters.call(this, arguments); + + if (this.layoutQuality == LayoutConstants.DRAFT_QUALITY) + { + this.displacementThresholdPerNode += 0.30; + this.maxIterations *= 0.8; + } + else if (this.layoutQuality == LayoutConstants.PROOF_QUALITY) + { + this.displacementThresholdPerNode -= 0.30; + this.maxIterations *= 1.2; + } + + this.totalIterations = 0; + this.notAnimatedIterations = 0; + +// this.useFRGridVariant = layoutOptionsPack.smartRepulsionRangeCalc; +}; + +FDLayout.prototype.calcIdealEdgeLengths = function () { + var edge; + var lcaDepth; + var source; + var target; + var sizeOfSourceInLca; + var sizeOfTargetInLca; + + var allEdges = this.getGraphManager().getAllEdges(); + for (var i = 0; i < allEdges.length; i++) + { + edge = allEdges[i]; + + edge.idealLength = this.idealEdgeLength; + + if (edge.isInterGraph) + { + source = edge.getSource(); + target = edge.getTarget(); + + sizeOfSourceInLca = edge.getSourceInLca().getEstimatedSize(); + sizeOfTargetInLca = edge.getTargetInLca().getEstimatedSize(); + + if (this.useSmartIdealEdgeLengthCalculation) + { + edge.idealLength += sizeOfSourceInLca + sizeOfTargetInLca - + 2 * LayoutConstants.SIMPLE_NODE_SIZE; + } + + lcaDepth = edge.getLca().getInclusionTreeDepth(); + + edge.idealLength += FDLayoutConstants.DEFAULT_EDGE_LENGTH * + FDLayoutConstants.PER_LEVEL_IDEAL_EDGE_LENGTH_FACTOR * + (source.getInclusionTreeDepth() + + target.getInclusionTreeDepth() - 2 * lcaDepth); + } + } +}; + +FDLayout.prototype.initSpringEmbedder = function () { + + if (this.incremental) + { + this.coolingFactor = 0.8; + this.initialCoolingFactor = 0.8; + this.maxNodeDisplacement = + FDLayoutConstants.MAX_NODE_DISPLACEMENT_INCREMENTAL; + } + else + { + this.coolingFactor = 1.0; + this.initialCoolingFactor = 1.0; + this.maxNodeDisplacement = + FDLayoutConstants.MAX_NODE_DISPLACEMENT; + } + + this.maxIterations = + Math.max(this.getAllNodes().length * 5, this.maxIterations); + + this.totalDisplacementThreshold = + this.displacementThresholdPerNode * this.getAllNodes().length; + + this.repulsionRange = this.calcRepulsionRange(); +}; + +FDLayout.prototype.calcSpringForces = function () { + var lEdges = this.getAllEdges(); + var edge; + + for (var i = 0; i < lEdges.length; i++) + { + edge = lEdges[i]; + + this.calcSpringForce(edge, edge.idealLength); + } +}; + +FDLayout.prototype.calcRepulsionForces = function () { + var i, j; + var nodeA, nodeB; + var lNodes = this.getAllNodes(); + + for (i = 0; i < lNodes.length; i++) + { + nodeA = lNodes[i]; + + for (j = i + 1; j < lNodes.length; j++) + { + nodeB = lNodes[j]; + + // If both nodes are not members of the same graph, skip. + if (nodeA.getOwner() != nodeB.getOwner()) + { + continue; + } + + this.calcRepulsionForce(nodeA, nodeB); + } + } +}; + +FDLayout.prototype.calcGravitationalForces = function () { + var node; + var lNodes = this.getAllNodesToApplyGravitation(); + + for (var i = 0; i < lNodes.length; i++) + { + node = lNodes[i]; + this.calcGravitationalForce(node); + } +}; + +FDLayout.prototype.moveNodes = function () { + var lNodes = this.getAllNodes(); + var node; + + for (var i = 0; i < lNodes.length; i++) + { + node = lNodes[i]; + node.move(); + } +} + +FDLayout.prototype.calcSpringForce = function (edge, idealLength) { + var sourceNode = edge.getSource(); + var targetNode = edge.getTarget(); + + var length; + var springForce; + var springForceX; + var springForceY; + + // Update edge length + if (this.uniformLeafNodeSizes && + sourceNode.getChild() == null && targetNode.getChild() == null) + { + edge.updateLengthSimple(); + } + else + { + edge.updateLength(); + + if (edge.isOverlapingSourceAndTarget) + { + return; + } + } + + length = edge.getLength(); + + // Calculate spring forces + springForce = this.springConstant * (length - idealLength); + + // Project force onto x and y axes + springForceX = springForce * (edge.lengthX / length); + springForceY = springForce * (edge.lengthY / length); + + // Apply forces on the end nodes + sourceNode.springForceX += springForceX; + sourceNode.springForceY += springForceY; + targetNode.springForceX -= springForceX; + targetNode.springForceY -= springForceY; +}; + +FDLayout.prototype.calcRepulsionForce = function (nodeA, nodeB) { + var rectA = nodeA.getRect(); + var rectB = nodeB.getRect(); + var overlapAmount = new Array(2); + var clipPoints = new Array(4); + var distanceX; + var distanceY; + var distanceSquared; + var distance; + var repulsionForce; + var repulsionForceX; + var repulsionForceY; + + if (rectA.intersects(rectB))// two nodes overlap + { + // calculate separation amount in x and y directions + IGeometry.calcSeparationAmount(rectA, + rectB, + overlapAmount, + FDLayoutConstants.DEFAULT_EDGE_LENGTH / 2.0); + + repulsionForceX = overlapAmount[0]; + repulsionForceY = overlapAmount[1]; + } + else// no overlap + { + // calculate distance + + if (this.uniformLeafNodeSizes && + nodeA.getChild() == null && nodeB.getChild() == null)// simply base repulsion on distance of node centers + { + distanceX = rectB.getCenterX() - rectA.getCenterX(); + distanceY = rectB.getCenterY() - rectA.getCenterY(); + } + else// use clipping points + { + IGeometry.getIntersection(rectA, rectB, clipPoints); + + distanceX = clipPoints[2] - clipPoints[0]; + distanceY = clipPoints[3] - clipPoints[1]; + } + + // No repulsion range. FR grid variant should take care of this. + if (Math.abs(distanceX) < FDLayoutConstants.MIN_REPULSION_DIST) + { + distanceX = IMath.sign(distanceX) * + FDLayoutConstants.MIN_REPULSION_DIST; + } + + if (Math.abs(distanceY) < FDLayoutConstants.MIN_REPULSION_DIST) + { + distanceY = IMath.sign(distanceY) * + FDLayoutConstants.MIN_REPULSION_DIST; + } + + distanceSquared = distanceX * distanceX + distanceY * distanceY; + distance = Math.sqrt(distanceSquared); + + repulsionForce = this.repulsionConstant / distanceSquared; + + // Project force onto x and y axes + repulsionForceX = repulsionForce * distanceX / distance; + repulsionForceY = repulsionForce * distanceY / distance; + } + + // Apply forces on the two nodes + nodeA.repulsionForceX -= repulsionForceX; + nodeA.repulsionForceY -= repulsionForceY; + nodeB.repulsionForceX += repulsionForceX; + nodeB.repulsionForceY += repulsionForceY; +}; + +FDLayout.prototype.calcGravitationalForce = function (node) { + var ownerGraph; + var ownerCenterX; + var ownerCenterY; + var distanceX; + var distanceY; + var absDistanceX; + var absDistanceY; + var estimatedSize; + ownerGraph = node.getOwner(); + + ownerCenterX = (ownerGraph.getRight() + ownerGraph.getLeft()) / 2; + ownerCenterY = (ownerGraph.getTop() + ownerGraph.getBottom()) / 2; + distanceX = node.getCenterX() - ownerCenterX; + distanceY = node.getCenterY() - ownerCenterY; + absDistanceX = Math.abs(distanceX); + absDistanceY = Math.abs(distanceY); + + if (node.getOwner() == this.graphManager.getRoot())// in the root graph + { + Math.floor(80); + estimatedSize = Math.floor(ownerGraph.getEstimatedSize() * + this.gravityRangeFactor); + + if (absDistanceX > estimatedSize || absDistanceY > estimatedSize) + { + node.gravitationForceX = -this.gravityConstant * distanceX; + node.gravitationForceY = -this.gravityConstant * distanceY; + } + } + else// inside a compound + { + estimatedSize = Math.floor((ownerGraph.getEstimatedSize() * + this.compoundGravityRangeFactor)); + + if (absDistanceX > estimatedSize || absDistanceY > estimatedSize) + { + node.gravitationForceX = -this.gravityConstant * distanceX * + this.compoundGravityConstant; + node.gravitationForceY = -this.gravityConstant * distanceY * + this.compoundGravityConstant; + } + } +}; + +FDLayout.prototype.isConverged = function () { + var converged; + var oscilating = false; + + if (this.totalIterations > this.maxIterations / 3) + { + oscilating = + Math.abs(this.totalDisplacement - this.oldTotalDisplacement) < 2; + } + + converged = this.totalDisplacement < this.totalDisplacementThreshold; + + this.oldTotalDisplacement = this.totalDisplacement; + + return converged || oscilating; +}; + +FDLayout.prototype.animate = function () { + if (this.animationDuringLayout && !this.isSubLayout) + { + if (this.notAnimatedIterations == this.animationPeriod) + { + this.update(); + this.notAnimatedIterations = 0; + } + else + { + this.notAnimatedIterations++; + } + } +}; + +FDLayout.prototype.calcRepulsionRange = function () { + return 0.0; +}; + +module.exports = FDLayout; + +},{"./FDLayoutConstants":10,"./IGeometry":15,"./IMath":16,"./Layout":23,"./LayoutConstants":24}],10:[function(require,module,exports){ +var LayoutConstants = require('./LayoutConstants'); + +function FDLayoutConstants() { +} + +//FDLayoutConstants inherits static props in LayoutConstants +for (var prop in LayoutConstants) { + FDLayoutConstants[prop] = LayoutConstants[prop]; +} + +FDLayoutConstants.MAX_ITERATIONS = 2500; + +FDLayoutConstants.DEFAULT_EDGE_LENGTH = 50; +FDLayoutConstants.DEFAULT_SPRING_STRENGTH = 0.45; +FDLayoutConstants.DEFAULT_REPULSION_STRENGTH = 4500.0; +FDLayoutConstants.DEFAULT_GRAVITY_STRENGTH = 0.4; +FDLayoutConstants.DEFAULT_COMPOUND_GRAVITY_STRENGTH = 1.0; +FDLayoutConstants.DEFAULT_GRAVITY_RANGE_FACTOR = 3.8; +FDLayoutConstants.DEFAULT_COMPOUND_GRAVITY_RANGE_FACTOR = 1.5; +FDLayoutConstants.DEFAULT_USE_SMART_IDEAL_EDGE_LENGTH_CALCULATION = true; +FDLayoutConstants.DEFAULT_USE_SMART_REPULSION_RANGE_CALCULATION = true; +FDLayoutConstants.MAX_NODE_DISPLACEMENT_INCREMENTAL = 100.0; +FDLayoutConstants.MAX_NODE_DISPLACEMENT = FDLayoutConstants.MAX_NODE_DISPLACEMENT_INCREMENTAL * 3; +FDLayoutConstants.MIN_REPULSION_DIST = FDLayoutConstants.DEFAULT_EDGE_LENGTH / 10.0; +FDLayoutConstants.CONVERGENCE_CHECK_PERIOD = 100; +FDLayoutConstants.PER_LEVEL_IDEAL_EDGE_LENGTH_FACTOR = 0.1; +FDLayoutConstants.MIN_EDGE_LENGTH = 1; +FDLayoutConstants.GRID_CALCULATION_CHECK_PERIOD = 10; + +module.exports = FDLayoutConstants; + +},{"./LayoutConstants":24}],11:[function(require,module,exports){ +var LEdge = require('./LEdge'); +var FDLayoutConstants = require('./FDLayoutConstants'); + +function FDLayoutEdge(source, target, vEdge) { + LEdge.call(this, source, target, vEdge); + this.idealLength = FDLayoutConstants.DEFAULT_EDGE_LENGTH; +} + +FDLayoutEdge.prototype = Object.create(LEdge.prototype); + +for (var prop in LEdge) { + FDLayoutEdge[prop] = LEdge[prop]; +} + +module.exports = FDLayoutEdge; + +},{"./FDLayoutConstants":10,"./LEdge":18}],12:[function(require,module,exports){ +var LNode = require('./LNode'); + +function FDLayoutNode(gm, loc, size, vNode) { + // alternative constructor is handled inside LNode + LNode.call(this, gm, loc, size, vNode); + //Spring, repulsion and gravitational forces acting on this node + this.springForceX = 0; + this.springForceY = 0; + this.repulsionForceX = 0; + this.repulsionForceY = 0; + this.gravitationForceX = 0; + this.gravitationForceY = 0; + //Amount by which this node is to be moved in this iteration + this.displacementX = 0; + this.displacementY = 0; + + //Start and finish grid coordinates that this node is fallen into + this.startX = 0; + this.finishX = 0; + this.startY = 0; + this.finishY = 0; + + //Geometric neighbors of this node + this.surrounding = []; +} + +FDLayoutNode.prototype = Object.create(LNode.prototype); + +for (var prop in LNode) { + FDLayoutNode[prop] = LNode[prop]; +} + +FDLayoutNode.prototype.setGridCoordinates = function (_startX, _finishX, _startY, _finishY) +{ + this.startX = _startX; + this.finishX = _finishX; + this.startY = _startY; + this.finishY = _finishY; + +}; + +module.exports = FDLayoutNode; + +},{"./LNode":22}],13:[function(require,module,exports){ +var UniqueIDGeneretor = require('./UniqueIDGeneretor'); + +function HashMap() { + this.map = {}; + this.keys = []; +} + +HashMap.prototype.put = function (key, value) { + var theId = UniqueIDGeneretor.createID(key); + if (!this.contains(theId)) { + this.map[theId] = value; + this.keys.push(key); + } +}; + +HashMap.prototype.contains = function (key) { + var theId = UniqueIDGeneretor.createID(key); + return this.map[key] != null; +}; + +HashMap.prototype.get = function (key) { + var theId = UniqueIDGeneretor.createID(key); + return this.map[theId]; +}; + +HashMap.prototype.keySet = function () { + return this.keys; +}; + +module.exports = HashMap; + +},{"./UniqueIDGeneretor":31}],14:[function(require,module,exports){ +var UniqueIDGeneretor = require('./UniqueIDGeneretor'); + +function HashSet() { + this.set = {}; +} +; + +HashSet.prototype.add = function (obj) { + var theId = UniqueIDGeneretor.createID(obj); + if (!this.contains(theId)) + this.set[theId] = obj; +}; + +HashSet.prototype.remove = function (obj) { + delete this.set[UniqueIDGeneretor.createID(obj)]; +}; + +HashSet.prototype.clear = function () { + this.set = {}; +}; + +HashSet.prototype.contains = function (obj) { + return this.set[UniqueIDGeneretor.createID(obj)] == obj; +}; + +HashSet.prototype.isEmpty = function () { + return this.size() === 0; +}; + +HashSet.prototype.size = function () { + return Object.keys(this.set).length; +}; + +//concats this.set to the given list +HashSet.prototype.addAllTo = function (list) { + var keys = Object.keys(this.set); + var length = keys.length; + for (var i = 0; i < length; i++) { + list.push(this.set[keys[i]]); + } +}; + +HashSet.prototype.size = function () { + return Object.keys(this.set).length; +}; + +HashSet.prototype.addAll = function (list) { + var s = list.length; + for (var i = 0; i < s; i++) { + var v = list[i]; + this.add(v); + } +}; + +module.exports = HashSet; + +},{"./UniqueIDGeneretor":31}],15:[function(require,module,exports){ +function IGeometry() { +} + +IGeometry.calcSeparationAmount = function (rectA, rectB, overlapAmount, separationBuffer) +{ + if (!rectA.intersects(rectB)) { + throw "assert failed"; + } + var directions = new Array(2); + IGeometry.decideDirectionsForOverlappingNodes(rectA, rectB, directions); + overlapAmount[0] = Math.min(rectA.getRight(), rectB.getRight()) - + Math.max(rectA.x, rectB.x); + overlapAmount[1] = Math.min(rectA.getBottom(), rectB.getBottom()) - + Math.max(rectA.y, rectB.y); + // update the overlapping amounts for the following cases: + if ((rectA.getX() <= rectB.getX()) && (rectA.getRight() >= rectB.getRight())) + { + overlapAmount[0] += Math.min((rectB.getX() - rectA.getX()), + (rectA.getRight() - rectB.getRight())); + } + else if ((rectB.getX() <= rectA.getX()) && (rectB.getRight() >= rectA.getRight())) + { + overlapAmount[0] += Math.min((rectA.getX() - rectB.getX()), + (rectB.getRight() - rectA.getRight())); + } + if ((rectA.getY() <= rectB.getY()) && (rectA.getBottom() >= rectB.getBottom())) + { + overlapAmount[1] += Math.min((rectB.getY() - rectA.getY()), + (rectA.getBottom() - rectB.getBottom())); + } + else if ((rectB.getY() <= rectA.getY()) && (rectB.getBottom() >= rectA.getBottom())) + { + overlapAmount[1] += Math.min((rectA.getY() - rectB.getY()), + (rectB.getBottom() - rectA.getBottom())); + } + + // find slope of the line passes two centers + var slope = Math.abs((rectB.getCenterY() - rectA.getCenterY()) / + (rectB.getCenterX() - rectA.getCenterX())); + // if centers are overlapped + if ((rectB.getCenterY() == rectA.getCenterY()) && + (rectB.getCenterX() == rectA.getCenterX())) + { + // assume the slope is 1 (45 degree) + slope = 1.0; + } + + var moveByY = slope * overlapAmount[0]; + var moveByX = overlapAmount[1] / slope; + if (overlapAmount[0] < moveByX) + { + moveByX = overlapAmount[0]; + } + else + { + moveByY = overlapAmount[1]; + } + // return half the amount so that if each rectangle is moved by these + // amounts in opposite directions, overlap will be resolved + overlapAmount[0] = -1 * directions[0] * ((moveByX / 2) + separationBuffer); + overlapAmount[1] = -1 * directions[1] * ((moveByY / 2) + separationBuffer); +} + +IGeometry.decideDirectionsForOverlappingNodes = function (rectA, rectB, directions) +{ + if (rectA.getCenterX() < rectB.getCenterX()) + { + directions[0] = -1; + } + else + { + directions[0] = 1; + } + + if (rectA.getCenterY() < rectB.getCenterY()) + { + directions[1] = -1; + } + else + { + directions[1] = 1; + } +} + +IGeometry.getIntersection2 = function (rectA, rectB, result) +{ + //result[0-1] will contain clipPoint of rectA, result[2-3] will contain clipPoint of rectB + var p1x = rectA.getCenterX(); + var p1y = rectA.getCenterY(); + var p2x = rectB.getCenterX(); + var p2y = rectB.getCenterY(); + + //if two rectangles intersect, then clipping points are centers + if (rectA.intersects(rectB)) + { + result[0] = p1x; + result[1] = p1y; + result[2] = p2x; + result[3] = p2y; + return true; + } + //variables for rectA + var topLeftAx = rectA.getX(); + var topLeftAy = rectA.getY(); + var topRightAx = rectA.getRight(); + var bottomLeftAx = rectA.getX(); + var bottomLeftAy = rectA.getBottom(); + var bottomRightAx = rectA.getRight(); + var halfWidthA = rectA.getWidthHalf(); + var halfHeightA = rectA.getHeightHalf(); + //variables for rectB + var topLeftBx = rectB.getX(); + var topLeftBy = rectB.getY(); + var topRightBx = rectB.getRight(); + var bottomLeftBx = rectB.getX(); + var bottomLeftBy = rectB.getBottom(); + var bottomRightBx = rectB.getRight(); + var halfWidthB = rectB.getWidthHalf(); + var halfHeightB = rectB.getHeightHalf(); + //flag whether clipping points are found + var clipPointAFound = false; + var clipPointBFound = false; + + // line is vertical + if (p1x == p2x) + { + if (p1y > p2y) + { + result[0] = p1x; + result[1] = topLeftAy; + result[2] = p2x; + result[3] = bottomLeftBy; + return false; + } + else if (p1y < p2y) + { + result[0] = p1x; + result[1] = bottomLeftAy; + result[2] = p2x; + result[3] = topLeftBy; + return false; + } + else + { + //not line, return null; + } + } + // line is horizontal + else if (p1y == p2y) + { + if (p1x > p2x) + { + result[0] = topLeftAx; + result[1] = p1y; + result[2] = topRightBx; + result[3] = p2y; + return false; + } + else if (p1x < p2x) + { + result[0] = topRightAx; + result[1] = p1y; + result[2] = topLeftBx; + result[3] = p2y; + return false; + } + else + { + //not valid line, return null; + } + } + else + { + //slopes of rectA's and rectB's diagonals + var slopeA = rectA.height / rectA.width; + var slopeB = rectB.height / rectB.width; + + //slope of line between center of rectA and center of rectB + var slopePrime = (p2y - p1y) / (p2x - p1x); + var cardinalDirectionA; + var cardinalDirectionB; + var tempPointAx; + var tempPointAy; + var tempPointBx; + var tempPointBy; + + //determine whether clipping point is the corner of nodeA + if ((-slopeA) == slopePrime) + { + if (p1x > p2x) + { + result[0] = bottomLeftAx; + result[1] = bottomLeftAy; + clipPointAFound = true; + } + else + { + result[0] = topRightAx; + result[1] = topLeftAy; + clipPointAFound = true; + } + } + else if (slopeA == slopePrime) + { + if (p1x > p2x) + { + result[0] = topLeftAx; + result[1] = topLeftAy; + clipPointAFound = true; + } + else + { + result[0] = bottomRightAx; + result[1] = bottomLeftAy; + clipPointAFound = true; + } + } + + //determine whether clipping point is the corner of nodeB + if ((-slopeB) == slopePrime) + { + if (p2x > p1x) + { + result[2] = bottomLeftBx; + result[3] = bottomLeftBy; + clipPointBFound = true; + } + else + { + result[2] = topRightBx; + result[3] = topLeftBy; + clipPointBFound = true; + } + } + else if (slopeB == slopePrime) + { + if (p2x > p1x) + { + result[2] = topLeftBx; + result[3] = topLeftBy; + clipPointBFound = true; + } + else + { + result[2] = bottomRightBx; + result[3] = bottomLeftBy; + clipPointBFound = true; + } + } + + //if both clipping points are corners + if (clipPointAFound && clipPointBFound) + { + return false; + } + + //determine Cardinal Direction of rectangles + if (p1x > p2x) + { + if (p1y > p2y) + { + cardinalDirectionA = IGeometry.getCardinalDirection(slopeA, slopePrime, 4); + cardinalDirectionB = IGeometry.getCardinalDirection(slopeB, slopePrime, 2); + } + else + { + cardinalDirectionA = IGeometry.getCardinalDirection(-slopeA, slopePrime, 3); + cardinalDirectionB = IGeometry.getCardinalDirection(-slopeB, slopePrime, 1); + } + } + else + { + if (p1y > p2y) + { + cardinalDirectionA = IGeometry.getCardinalDirection(-slopeA, slopePrime, 1); + cardinalDirectionB = IGeometry.getCardinalDirection(-slopeB, slopePrime, 3); + } + else + { + cardinalDirectionA = IGeometry.getCardinalDirection(slopeA, slopePrime, 2); + cardinalDirectionB = IGeometry.getCardinalDirection(slopeB, slopePrime, 4); + } + } + //calculate clipping Point if it is not found before + if (!clipPointAFound) + { + switch (cardinalDirectionA) + { + case 1: + tempPointAy = topLeftAy; + tempPointAx = p1x + (-halfHeightA) / slopePrime; + result[0] = tempPointAx; + result[1] = tempPointAy; + break; + case 2: + tempPointAx = bottomRightAx; + tempPointAy = p1y + halfWidthA * slopePrime; + result[0] = tempPointAx; + result[1] = tempPointAy; + break; + case 3: + tempPointAy = bottomLeftAy; + tempPointAx = p1x + halfHeightA / slopePrime; + result[0] = tempPointAx; + result[1] = tempPointAy; + break; + case 4: + tempPointAx = bottomLeftAx; + tempPointAy = p1y + (-halfWidthA) * slopePrime; + result[0] = tempPointAx; + result[1] = tempPointAy; + break; + } + } + if (!clipPointBFound) + { + switch (cardinalDirectionB) + { + case 1: + tempPointBy = topLeftBy; + tempPointBx = p2x + (-halfHeightB) / slopePrime; + result[2] = tempPointBx; + result[3] = tempPointBy; + break; + case 2: + tempPointBx = bottomRightBx; + tempPointBy = p2y + halfWidthB * slopePrime; + result[2] = tempPointBx; + result[3] = tempPointBy; + break; + case 3: + tempPointBy = bottomLeftBy; + tempPointBx = p2x + halfHeightB / slopePrime; + result[2] = tempPointBx; + result[3] = tempPointBy; + break; + case 4: + tempPointBx = bottomLeftBx; + tempPointBy = p2y + (-halfWidthB) * slopePrime; + result[2] = tempPointBx; + result[3] = tempPointBy; + break; + } + } + } + return false; +} + +IGeometry.getCardinalDirection = function (slope, slopePrime, line) +{ + if (slope > slopePrime) + { + return line; + } + else + { + return 1 + line % 4; + } +} + +IGeometry.getIntersection = function (s1, s2, f1, f2) +{ + if (f2 == null) { + return IGeometry.getIntersection2(s1, s2, f1); + } + var x1 = s1.x; + var y1 = s1.y; + var x2 = s2.x; + var y2 = s2.y; + var x3 = f1.x; + var y3 = f1.y; + var x4 = f2.x; + var y4 = f2.y; + var x, y; // intersection point + var a1, a2, b1, b2, c1, c2; // coefficients of line eqns. + var denom; + + a1 = y2 - y1; + b1 = x1 - x2; + c1 = x2 * y1 - x1 * y2; // { a1*x + b1*y + c1 = 0 is line 1 } + + a2 = y4 - y3; + b2 = x3 - x4; + c2 = x4 * y3 - x3 * y4; // { a2*x + b2*y + c2 = 0 is line 2 } + + denom = a1 * b2 - a2 * b1; + + if (denom == 0) + { + return null; + } + + x = (b1 * c2 - b2 * c1) / denom; + y = (a2 * c1 - a1 * c2) / denom; + + return new Point(x, y); +} + +// ----------------------------------------------------------------------------- +// Section: Class Constants +// ----------------------------------------------------------------------------- +/** + * Some useful pre-calculated constants + */ +IGeometry.HALF_PI = 0.5 * Math.PI; +IGeometry.ONE_AND_HALF_PI = 1.5 * Math.PI; +IGeometry.TWO_PI = 2.0 * Math.PI; +IGeometry.THREE_PI = 3.0 * Math.PI; + +module.exports = IGeometry; + +},{}],16:[function(require,module,exports){ +function IMath() { +} + +/** + * This method returns the sign of the input value. + */ +IMath.sign = function (value) { + if (value > 0) + { + return 1; + } + else if (value < 0) + { + return -1; + } + else + { + return 0; + } +} + +IMath.floor = function (value) { + return value < 0 ? Math.ceil(value) : Math.floor(value); +} + +IMath.ceil = function (value) { + return value < 0 ? Math.floor(value) : Math.ceil(value); +} + +module.exports = IMath; + +},{}],17:[function(require,module,exports){ +function Integer() { +} + +Integer.MAX_VALUE = 2147483647; +Integer.MIN_VALUE = -2147483648; + +module.exports = Integer; + +},{}],18:[function(require,module,exports){ +var LGraphObject = require('./LGraphObject'); +var IGeometry = require('./IGeometry'); +var IMath = require('./IMath'); + +function LEdge(source, target, vEdge) { + LGraphObject.call(this, vEdge); + + this.isOverlapingSourceAndTarget = false; + this.vGraphObject = vEdge; + this.bendpoints = []; + this.source = source; + this.target = target; +} + +LEdge.prototype = Object.create(LGraphObject.prototype); + +for (var prop in LGraphObject) { + LEdge[prop] = LGraphObject[prop]; +} + +LEdge.prototype.getSource = function () +{ + return this.source; +}; + +LEdge.prototype.getTarget = function () +{ + return this.target; +}; + +LEdge.prototype.isInterGraph = function () +{ + return this.isInterGraph; +}; + +LEdge.prototype.getLength = function () +{ + return this.length; +}; + +LEdge.prototype.isOverlapingSourceAndTarget = function () +{ + return this.isOverlapingSourceAndTarget; +}; + +LEdge.prototype.getBendpoints = function () +{ + return this.bendpoints; +}; + +LEdge.prototype.getLca = function () +{ + return this.lca; +}; + +LEdge.prototype.getSourceInLca = function () +{ + return this.sourceInLca; +}; + +LEdge.prototype.getTargetInLca = function () +{ + return this.targetInLca; +}; + +LEdge.prototype.getOtherEnd = function (node) +{ + if (this.source === node) + { + return this.target; + } + else if (this.target === node) + { + return this.source; + } + else + { + throw "Node is not incident with this edge"; + } +} + +LEdge.prototype.getOtherEndInGraph = function (node, graph) +{ + var otherEnd = this.getOtherEnd(node); + var root = graph.getGraphManager().getRoot(); + + while (true) + { + if (otherEnd.getOwner() == graph) + { + return otherEnd; + } + + if (otherEnd.getOwner() == root) + { + break; + } + + otherEnd = otherEnd.getOwner().getParent(); + } + + return null; +}; + +LEdge.prototype.updateLength = function () +{ + var clipPointCoordinates = new Array(4); + + this.isOverlapingSourceAndTarget = + IGeometry.getIntersection(this.target.getRect(), + this.source.getRect(), + clipPointCoordinates); + + if (!this.isOverlapingSourceAndTarget) + { + this.lengthX = clipPointCoordinates[0] - clipPointCoordinates[2]; + this.lengthY = clipPointCoordinates[1] - clipPointCoordinates[3]; + + if (Math.abs(this.lengthX) < 1.0) + { + this.lengthX = IMath.sign(this.lengthX); + } + + if (Math.abs(this.lengthY) < 1.0) + { + this.lengthY = IMath.sign(this.lengthY); + } + + this.length = Math.sqrt( + this.lengthX * this.lengthX + this.lengthY * this.lengthY); + } +}; + +LEdge.prototype.updateLengthSimple = function () +{ + this.lengthX = this.target.getCenterX() - this.source.getCenterX(); + this.lengthY = this.target.getCenterY() - this.source.getCenterY(); + + if (Math.abs(this.lengthX) < 1.0) + { + this.lengthX = IMath.sign(this.lengthX); + } + + if (Math.abs(this.lengthY) < 1.0) + { + this.lengthY = IMath.sign(this.lengthY); + } + + this.length = Math.sqrt( + this.lengthX * this.lengthX + this.lengthY * this.lengthY); +} + +module.exports = LEdge; + +},{"./IGeometry":15,"./IMath":16,"./LGraphObject":21}],19:[function(require,module,exports){ +var LGraphObject = require('./LGraphObject'); +var Integer = require('./Integer'); +var LayoutConstants = require('./LayoutConstants'); +var LGraphManager = require('./LGraphManager'); +var LNode = require('./LNode'); +var HashSet = require('./HashSet'); +var RectangleD = require('./RectangleD'); +var Point = require('./Point'); + +function LGraph(parent, obj2, vGraph) { + LGraphObject.call(this, vGraph); + this.estimatedSize = Integer.MIN_VALUE; + this.margin = LayoutConstants.DEFAULT_GRAPH_MARGIN; + this.edges = []; + this.nodes = []; + this.isConnected = false; + this.parent = parent; + + if (obj2 != null && obj2 instanceof LGraphManager) { + this.graphManager = obj2; + } + else if (obj2 != null && obj2 instanceof Layout) { + this.graphManager = obj2.graphManager; + } +} + +LGraph.prototype = Object.create(LGraphObject.prototype); +for (var prop in LGraphObject) { + LGraph[prop] = LGraphObject[prop]; +} + +LGraph.prototype.getNodes = function () { + return this.nodes; +}; + +LGraph.prototype.getEdges = function () { + return this.edges; +}; + +LGraph.prototype.getGraphManager = function () +{ + return this.graphManager; +}; + +LGraph.prototype.getParent = function () +{ + return this.parent; +}; + +LGraph.prototype.getLeft = function () +{ + return this.left; +}; + +LGraph.prototype.getRight = function () +{ + return this.right; +}; + +LGraph.prototype.getTop = function () +{ + return this.top; +}; + +LGraph.prototype.getBottom = function () +{ + return this.bottom; +}; + +LGraph.prototype.isConnected = function () +{ + return this.isConnected; +}; + +LGraph.prototype.add = function (obj1, sourceNode, targetNode) { + if (sourceNode == null && targetNode == null) { + var newNode = obj1; + if (this.graphManager == null) { + throw "Graph has no graph mgr!"; + } + if (this.getNodes().indexOf(newNode) > -1) { + throw "Node already in graph!"; + } + newNode.owner = this; + this.getNodes().push(newNode); + + return newNode; + } + else { + var newEdge = obj1; + if (!(this.getNodes().indexOf(sourceNode) > -1 && (this.getNodes().indexOf(targetNode)) > -1)) { + throw "Source or target not in graph!"; + } + + if (!(sourceNode.owner == targetNode.owner && sourceNode.owner == this)) { + throw "Both owners must be this graph!"; + } + + if (sourceNode.owner != targetNode.owner) + { + return null; + } + + // set source and target + newEdge.source = sourceNode; + newEdge.target = targetNode; + + // set as intra-graph edge + newEdge.isInterGraph = false; + + // add to graph edge list + this.getEdges().push(newEdge); + + // add to incidency lists + sourceNode.edges.push(newEdge); + + if (targetNode != sourceNode) + { + targetNode.edges.push(newEdge); + } + + return newEdge; + } +}; + +LGraph.prototype.remove = function (obj) { + var node = obj; + if (obj instanceof LNode) { + if (node == null) { + throw "Node is null!"; + } + if (!(node.owner != null && node.owner == this)) { + throw "Owner graph is invalid!"; + } + if (this.graphManager == null) { + throw "Owner graph manager is invalid!"; + } + // remove incident edges first (make a copy to do it safely) + var edgesToBeRemoved = node.edges.slice(); + var edge; + var s = edgesToBeRemoved.length; + for (var i = 0; i < s; i++) + { + edge = edgesToBeRemoved[i]; + + if (edge.isInterGraph) + { + this.graphManager.remove(edge); + } + else + { + edge.source.owner.remove(edge); + } + } + + // now the node itself + var index = this.nodes.indexOf(node); + if (index == -1) { + throw "Node not in owner node list!"; + } + + this.nodes.splice(index, 1); + } + else if (obj instanceof LEdge) { + var edge = obj; + if (edge == null) { + throw "Edge is null!"; + } + if (!(edge.source != null && edge.target != null)) { + throw "Source and/or target is null!"; + } + if (!(edge.source.owner != null && edge.target.owner != null && + edge.source.owner == this && edge.target.owner == this)) { + throw "Source and/or target owner is invalid!"; + } + + var sourceIndex = edge.source.edges.indexOf(edge); + var targetIndex = edge.target.edges.indexOf(edge); + if (!(sourceIndex > -1 && targetIndex > -1)) { + throw "Source and/or target doesn't know this edge!"; + } + + edge.source.edges.splice(sourceIndex, 1); + + if (edge.target != edge.source) + { + edge.target.edges.splice(targetIndex, 1); + } + + var index = edge.source.owner.getEdges().indexOf(edge); + if (index == -1) { + throw "Not in owner's edge list!"; + } + + edge.source.owner.getEdges().splice(index, 1); + } +}; + +LGraph.prototype.updateLeftTop = function () +{ + var top = Integer.MAX_VALUE; + var left = Integer.MAX_VALUE; + var nodeTop; + var nodeLeft; + + var nodes = this.getNodes(); + var s = nodes.length; + + for (var i = 0; i < s; i++) + { + var lNode = nodes[i]; + nodeTop = Math.floor(lNode.getTop()); + nodeLeft = Math.floor(lNode.getLeft()); + + if (top > nodeTop) + { + top = nodeTop; + } + + if (left > nodeLeft) + { + left = nodeLeft; + } + } + + // Do we have any nodes in this graph? + if (top == Integer.MAX_VALUE) + { + return null; + } + + this.left = left - this.margin; + this.top = top - this.margin; + + // Apply the margins and return the result + return new Point(this.left, this.top); +}; + +LGraph.prototype.updateBounds = function (recursive) +{ + // calculate bounds + var left = Integer.MAX_VALUE; + var right = -Integer.MAX_VALUE; + var top = Integer.MAX_VALUE; + var bottom = -Integer.MAX_VALUE; + var nodeLeft; + var nodeRight; + var nodeTop; + var nodeBottom; + + var nodes = this.nodes; + var s = nodes.length; + for (var i = 0; i < s; i++) + { + var lNode = nodes[i]; + + if (recursive && lNode.child != null) + { + lNode.updateBounds(); + } + nodeLeft = Math.floor(lNode.getLeft()); + nodeRight = Math.floor(lNode.getRight()); + nodeTop = Math.floor(lNode.getTop()); + nodeBottom = Math.floor(lNode.getBottom()); + + if (left > nodeLeft) + { + left = nodeLeft; + } + + if (right < nodeRight) + { + right = nodeRight; + } + + if (top > nodeTop) + { + top = nodeTop; + } + + if (bottom < nodeBottom) + { + bottom = nodeBottom; + } + } + + var boundingRect = new RectangleD(left, top, right - left, bottom - top); + if (left == Integer.MAX_VALUE) + { + this.left = Math.floor(this.parent.getLeft()); + this.right = Math.floor(this.parent.getRight()); + this.top = Math.floor(this.parent.getTop()); + this.bottom = Math.floor(this.parent.getBottom()); + } + + this.left = boundingRect.x - this.margin; + this.right = boundingRect.x + boundingRect.width + this.margin; + this.top = boundingRect.y - this.margin; + this.bottom = boundingRect.y + boundingRect.height + this.margin; +}; + +LGraph.calculateBounds = function (nodes) +{ + var left = Integer.MAX_VALUE; + var right = -Integer.MAX_VALUE; + var top = Integer.MAX_VALUE; + var bottom = -Integer.MAX_VALUE; + var nodeLeft; + var nodeRight; + var nodeTop; + var nodeBottom; + + var s = nodes.length; + + for (var i = 0; i < s; i++) + { + var lNode = nodes[i]; + nodeLeft = Math.floor(lNode.getLeft()); + nodeRight = Math.floor(lNode.getRight()); + nodeTop = Math.floor(lNode.getTop()); + nodeBottom = Math.floor(lNode.getBottom()); + + if (left > nodeLeft) + { + left = nodeLeft; + } + + if (right < nodeRight) + { + right = nodeRight; + } + + if (top > nodeTop) + { + top = nodeTop; + } + + if (bottom < nodeBottom) + { + bottom = nodeBottom; + } + } + + var boundingRect = new RectangleD(left, top, right - left, bottom - top); + + return boundingRect; +}; + +LGraph.prototype.getInclusionTreeDepth = function () +{ + if (this == this.graphManager.getRoot()) + { + return 1; + } + else + { + return this.parent.getInclusionTreeDepth(); + } +}; + +LGraph.prototype.getEstimatedSize = function () +{ + if (this.estimatedSize == Integer.MIN_VALUE) { + throw "assert failed"; + } + return this.estimatedSize; +}; + +LGraph.prototype.calcEstimatedSize = function () +{ + var size = 0; + var nodes = this.nodes; + var s = nodes.length; + + for (var i = 0; i < s; i++) + { + var lNode = nodes[i]; + size += lNode.calcEstimatedSize(); + } + + if (size == 0) + { + this.estimatedSize = LayoutConstants.EMPTY_COMPOUND_NODE_SIZE; + } + else + { + this.estimatedSize = Math.floor(size / Math.sqrt(this.nodes.length)); + } + + return Math.floor(this.estimatedSize); +}; + +LGraph.prototype.updateConnected = function () +{ + if (this.nodes.length == 0) + { + this.isConnected = true; + return; + } + + var toBeVisited = []; + var visited = new HashSet(); + var currentNode = this.nodes[0]; + var neighborEdges; + var currentNeighbor; + toBeVisited = toBeVisited.concat(currentNode.withChildren()); + + while (toBeVisited.length > 0) + { + currentNode = toBeVisited.shift(); + visited.add(currentNode); + + // Traverse all neighbors of this node + neighborEdges = currentNode.getEdges(); + var s = neighborEdges.length; + for (var i = 0; i < s; i++) + { + var neighborEdge = neighborEdges[i]; + currentNeighbor = + neighborEdge.getOtherEndInGraph(currentNode, this); + + // Add unvisited neighbors to the list to visit + if (currentNeighbor != null && + !visited.contains(currentNeighbor)) + { + toBeVisited = toBeVisited.concat(currentNeighbor.withChildren()); + } + } + } + + this.isConnected = false; + + if (visited.size() >= this.nodes.length) + { + var noOfVisitedInThisGraph = 0; + + var s = visited.size(); + for (var visitedId in visited.set) + { + var visitedNode = visited.set[visitedId]; + if (visitedNode.owner == this) + { + noOfVisitedInThisGraph++; + } + } + + if (noOfVisitedInThisGraph == this.nodes.length) + { + this.isConnected = true; + } + } +}; + +module.exports = LGraph; + +},{"./HashSet":14,"./Integer":17,"./LGraphManager":20,"./LGraphObject":21,"./LNode":22,"./LayoutConstants":24,"./Point":25,"./RectangleD":28}],20:[function(require,module,exports){ +function LGraphManager(layout) { + this.layout = layout; + + this.graphs = []; + this.edges = []; +} + +LGraphManager.prototype.addRoot = function () +{ + var ngraph = this.layout.newGraph(); + var nnode = this.layout.newNode(null); + var root = this.add(ngraph, nnode); + this.setRootGraph(root); + return this.rootGraph; +}; + +LGraphManager.prototype.add = function (newGraph, parentNode, newEdge, sourceNode, targetNode) +{ + //there are just 2 parameters are passed then it adds an LGraph else it adds an LEdge + if (newEdge == null && sourceNode == null && targetNode == null) { + if (newGraph == null) { + throw "Graph is null!"; + } + if (parentNode == null) { + throw "Parent node is null!"; + } + if (this.graphs.indexOf(newGraph) > -1) { + throw "Graph already in this graph mgr!"; + } + + this.graphs.push(newGraph); + + if (newGraph.parent != null) { + throw "Already has a parent!"; + } + if (parentNode.child != null) { + throw "Already has a child!"; + } + + newGraph.parent = parentNode; + parentNode.child = newGraph; + + return newGraph; + } + else { + //change the order of the parameters + targetNode = newEdge; + sourceNode = parentNode; + newEdge = newGraph; + var sourceGraph = sourceNode.getOwner(); + var targetGraph = targetNode.getOwner(); + + if (!(sourceGraph != null && sourceGraph.getGraphManager() == this)) { + throw "Source not in this graph mgr!"; + } + if (!(targetGraph != null && targetGraph.getGraphManager() == this)) { + throw "Target not in this graph mgr!"; + } + + if (sourceGraph == targetGraph) + { + newEdge.isInterGraph = false; + return sourceGraph.add(newEdge, sourceNode, targetNode); + } + else + { + newEdge.isInterGraph = true; + + // set source and target + newEdge.source = sourceNode; + newEdge.target = targetNode; + + // add edge to inter-graph edge list + if (this.edges.indexOf(newEdge) > -1) { + throw "Edge already in inter-graph edge list!"; + } + + this.edges.push(newEdge); + + // add edge to source and target incidency lists + if (!(newEdge.source != null && newEdge.target != null)) { + throw "Edge source and/or target is null!"; + } + + if (!(newEdge.source.edges.indexOf(newEdge) == -1 && newEdge.target.edges.indexOf(newEdge) == -1)) { + throw "Edge already in source and/or target incidency list!"; + } + + newEdge.source.edges.push(newEdge); + newEdge.target.edges.push(newEdge); + + return newEdge; + } + } +}; + +LGraphManager.prototype.remove = function (lObj) { + if (lObj instanceof LGraph) { + var graph = lObj; + if (graph.getGraphManager() != this) { + throw "Graph not in this graph mgr"; + } + if (!(graph == this.rootGraph || (graph.parent != null && graph.parent.graphManager == this))) { + throw "Invalid parent node!"; + } + + // first the edges (make a copy to do it safely) + var edgesToBeRemoved = []; + + edgesToBeRemoved = edgesToBeRemoved.concat(graph.getEdges()); + + var edge; + var s = edgesToBeRemoved.length; + for (var i = 0; i < s; i++) + { + edge = edgesToBeRemoved[i]; + graph.remove(edge); + } + + // then the nodes (make a copy to do it safely) + var nodesToBeRemoved = []; + + nodesToBeRemoved = nodesToBeRemoved.concat(graph.getNodes()); + + var node; + s = nodesToBeRemoved.length; + for (var i = 0; i < s; i++) + { + node = nodesToBeRemoved[i]; + graph.remove(node); + } + + // check if graph is the root + if (graph == this.rootGraph) + { + this.setRootGraph(null); + } + + // now remove the graph itself + var index = this.graphs.indexOf(graph); + this.graphs.splice(index, 1); + + // also reset the parent of the graph + graph.parent = null; + } + else if (lObj instanceof LEdge) { + edge = lObj; + if (edge == null) { + throw "Edge is null!"; + } + if (!edge.isInterGraph) { + throw "Not an inter-graph edge!"; + } + if (!(edge.source != null && edge.target != null)) { + throw "Source and/or target is null!"; + } + + // remove edge from source and target nodes' incidency lists + + if (!(edge.source.edges.indexOf(edge) != -1 && edge.target.edges.indexOf(edge) != -1)) { + throw "Source and/or target doesn't know this edge!"; + } + + var index = edge.source.edges.indexOf(edge); + edge.source.edges.splice(index, 1); + index = edge.target.edges.indexOf(edge); + edge.target.edges.splice(index, 1); + + // remove edge from owner graph manager's inter-graph edge list + + if (!(edge.source.owner != null && edge.source.owner.getGraphManager() != null)) { + throw "Edge owner graph or owner graph manager is null!"; + } + if (edge.source.owner.getGraphManager().edges.indexOf(edge) == -1) { + throw "Not in owner graph manager's edge list!"; + } + + var index = edge.source.owner.getGraphManager().edges.indexOf(edge); + edge.source.owner.getGraphManager().edges.splice(index, 1); + } +}; + +LGraphManager.prototype.updateBounds = function () +{ + this.rootGraph.updateBounds(true); +}; + +LGraphManager.prototype.getGraphs = function () +{ + return this.graphs; +}; + +LGraphManager.prototype.getAllNodes = function () +{ + if (this.allNodes == null) + { + var nodeList = []; + var graphs = this.getGraphs(); + var s = graphs.length; + for (var i = 0; i < s; i++) + { + nodeList = nodeList.concat(graphs[i].getNodes()); + } + this.allNodes = nodeList; + } + return this.allNodes; +}; + +LGraphManager.prototype.resetAllNodes = function () +{ + this.allNodes = null; +}; + +LGraphManager.prototype.resetAllEdges = function () +{ + this.allEdges = null; +}; + +LGraphManager.prototype.resetAllNodesToApplyGravitation = function () +{ + this.allNodesToApplyGravitation = null; +}; + +LGraphManager.prototype.getAllEdges = function () +{ + if (this.allEdges == null) + { + var edgeList = []; + var graphs = this.getGraphs(); + var s = graphs.length; + for (var i = 0; i < graphs.length; i++) + { + edgeList = edgeList.concat(graphs[i].getEdges()); + } + + edgeList = edgeList.concat(this.edges); + + this.allEdges = edgeList; + } + return this.allEdges; +}; + +LGraphManager.prototype.getAllNodesToApplyGravitation = function () +{ + return this.allNodesToApplyGravitation; +}; + +LGraphManager.prototype.setAllNodesToApplyGravitation = function (nodeList) +{ + if (this.allNodesToApplyGravitation != null) { + throw "assert failed"; + } + + this.allNodesToApplyGravitation = nodeList; +}; + +LGraphManager.prototype.getRoot = function () +{ + return this.rootGraph; +}; + +LGraphManager.prototype.setRootGraph = function (graph) +{ + if (graph.getGraphManager() != this) { + throw "Root not in this graph mgr!"; + } + + this.rootGraph = graph; + // root graph must have a root node associated with it for convenience + if (graph.parent == null) + { + graph.parent = this.layout.newNode("Root node"); + } +}; + +LGraphManager.prototype.getLayout = function () +{ + return this.layout; +}; + +LGraphManager.prototype.isOneAncestorOfOther = function (firstNode, secondNode) +{ + if (!(firstNode != null && secondNode != null)) { + throw "assert failed"; + } + + if (firstNode == secondNode) + { + return true; + } + // Is second node an ancestor of the first one? + var ownerGraph = firstNode.getOwner(); + var parentNode; + + do + { + parentNode = ownerGraph.getParent(); + + if (parentNode == null) + { + break; + } + + if (parentNode == secondNode) + { + return true; + } + + ownerGraph = parentNode.getOwner(); + if (ownerGraph == null) + { + break; + } + } while (true); + // Is first node an ancestor of the second one? + ownerGraph = secondNode.getOwner(); + + do + { + parentNode = ownerGraph.getParent(); + + if (parentNode == null) + { + break; + } + + if (parentNode == firstNode) + { + return true; + } + + ownerGraph = parentNode.getOwner(); + if (ownerGraph == null) + { + break; + } + } while (true); + + return false; +}; + +LGraphManager.prototype.calcLowestCommonAncestors = function () +{ + var edge; + var sourceNode; + var targetNode; + var sourceAncestorGraph; + var targetAncestorGraph; + + var edges = this.getAllEdges(); + var s = edges.length; + for (var i = 0; i < s; i++) + { + edge = edges[i]; + + sourceNode = edge.source; + targetNode = edge.target; + edge.lca = null; + edge.sourceInLca = sourceNode; + edge.targetInLca = targetNode; + + if (sourceNode == targetNode) + { + edge.lca = sourceNode.getOwner(); + continue; + } + + sourceAncestorGraph = sourceNode.getOwner(); + + while (edge.lca == null) + { + targetAncestorGraph = targetNode.getOwner(); + + while (edge.lca == null) + { + if (targetAncestorGraph == sourceAncestorGraph) + { + edge.lca = targetAncestorGraph; + break; + } + + if (targetAncestorGraph == this.rootGraph) + { + break; + } + + if (edge.lca != null) { + throw "assert failed"; + } + edge.targetInLca = targetAncestorGraph.getParent(); + targetAncestorGraph = edge.targetInLca.getOwner(); + } + + if (sourceAncestorGraph == this.rootGraph) + { + break; + } + + if (edge.lca == null) + { + edge.sourceInLca = sourceAncestorGraph.getParent(); + sourceAncestorGraph = edge.sourceInLca.getOwner(); + } + } + + if (edge.lca == null) { + throw "assert failed"; + } + } +}; + +LGraphManager.prototype.calcLowestCommonAncestor = function (firstNode, secondNode) +{ + if (firstNode == secondNode) + { + return firstNode.getOwner(); + } + var firstOwnerGraph = firstNode.getOwner(); + + do + { + if (firstOwnerGraph == null) + { + break; + } + var secondOwnerGraph = secondNode.getOwner(); + + do + { + if (secondOwnerGraph == null) + { + break; + } + + if (secondOwnerGraph == firstOwnerGraph) + { + return secondOwnerGraph; + } + secondOwnerGraph = secondOwnerGraph.getParent().getOwner(); + } while (true); + + firstOwnerGraph = firstOwnerGraph.getParent().getOwner(); + } while (true); + + return firstOwnerGraph; +}; + +LGraphManager.prototype.calcInclusionTreeDepths = function (graph, depth) { + if (graph == null && depth == null) { + graph = this.rootGraph; + depth = 1; + } + var node; + + var nodes = graph.getNodes(); + var s = nodes.length; + for (var i = 0; i < s; i++) + { + node = nodes[i]; + node.inclusionTreeDepth = depth; + + if (node.child != null) + { + this.calcInclusionTreeDepths(node.child, depth + 1); + } + } +}; + +LGraphManager.prototype.includesInvalidEdge = function () +{ + var edge; + + var s = this.edges.length; + for (var i = 0; i < s; i++) + { + edge = this.edges[i]; + + if (this.isOneAncestorOfOther(edge.source, edge.target)) + { + return true; + } + } + return false; +}; + +module.exports = LGraphManager; + +},{}],21:[function(require,module,exports){ +function LGraphObject(vGraphObject) { + this.vGraphObject = vGraphObject; +} + +module.exports = LGraphObject; + +},{}],22:[function(require,module,exports){ +var LGraphObject = require('./LGraphObject'); +var Integer = require('./Integer'); +var RectangleD = require('./RectangleD'); +var LayoutConstants = require('./LayoutConstants'); +var RandomSeed = require('./RandomSeed'); +var PointD = require('./PointD'); +var HashSet = require('./HashSet'); + +function LNode(gm, loc, size, vNode) { + //Alternative constructor 1 : LNode(LGraphManager gm, Point loc, Dimension size, Object vNode) + if (size == null && vNode == null) { + vNode = loc; + } + + LGraphObject.call(this, vNode); + + //Alternative constructor 2 : LNode(Layout layout, Object vNode) + if (gm.graphManager != null) + gm = gm.graphManager; + + this.estimatedSize = Integer.MIN_VALUE; + this.inclusionTreeDepth = Integer.MAX_VALUE; + this.vGraphObject = vNode; + this.edges = []; + this.graphManager = gm; + + if (size != null && loc != null) + this.rect = new RectangleD(loc.x, loc.y, size.width, size.height); + else + this.rect = new RectangleD(); +} + +LNode.prototype = Object.create(LGraphObject.prototype); +for (var prop in LGraphObject) { + LNode[prop] = LGraphObject[prop]; +} + +LNode.prototype.getEdges = function () +{ + return this.edges; +}; + +LNode.prototype.getChild = function () +{ + return this.child; +}; + +LNode.prototype.getOwner = function () +{ + if (this.owner != null) { + if (!(this.owner == null || this.owner.getNodes().indexOf(this) > -1)) { + throw "assert failed"; + } + } + + return this.owner; +}; + +LNode.prototype.getWidth = function () +{ + return this.rect.width; +}; + +LNode.prototype.setWidth = function (width) +{ + this.rect.width = width; +}; + +LNode.prototype.getHeight = function () +{ + return this.rect.height; +}; + +LNode.prototype.setHeight = function (height) +{ + this.rect.height = height; +}; + +LNode.prototype.getCenterX = function () +{ + return this.rect.x + this.rect.width / 2; +}; + +LNode.prototype.getCenterY = function () +{ + return this.rect.y + this.rect.height / 2; +}; + +LNode.prototype.getCenter = function () +{ + return new PointD(this.rect.x + this.rect.width / 2, + this.rect.y + this.rect.height / 2); +}; + +LNode.prototype.getLocation = function () +{ + return new PointD(this.rect.x, this.rect.y); +}; + +LNode.prototype.getRect = function () +{ + return this.rect; +}; + +LNode.prototype.getDiagonal = function () +{ + return Math.sqrt(this.rect.width * this.rect.width + + this.rect.height * this.rect.height); +}; + +LNode.prototype.setRect = function (upperLeft, dimension) +{ + this.rect.x = upperLeft.x; + this.rect.y = upperLeft.y; + this.rect.width = dimension.width; + this.rect.height = dimension.height; +}; + +LNode.prototype.setCenter = function (cx, cy) +{ + this.rect.x = cx - this.rect.width / 2; + this.rect.y = cy - this.rect.height / 2; +}; + +LNode.prototype.setLocation = function (x, y) +{ + this.rect.x = x; + this.rect.y = y; +}; + +LNode.prototype.moveBy = function (dx, dy) +{ + this.rect.x += dx; + this.rect.y += dy; +}; + +LNode.prototype.getEdgeListToNode = function (to) +{ + var edgeList = []; + var edge; + + for (var obj in this.edges) + { + edge = obj; + + if (edge.target == to) + { + if (edge.source != this) + throw "Incorrect edge source!"; + + edgeList.push(edge); + } + } + + return edgeList; +}; + +LNode.prototype.getEdgesBetween = function (other) +{ + var edgeList = []; + var edge; + + for (var obj in this.edges) + { + edge = this.edges[obj]; + + if (!(edge.source == this || edge.target == this)) + throw "Incorrect edge source and/or target"; + + if ((edge.target == other) || (edge.source == other)) + { + edgeList.push(edge); + } + } + + return edgeList; +}; + +LNode.prototype.getNeighborsList = function () +{ + var neighbors = new HashSet(); + var edge; + + for (var obj in this.edges) + { + edge = this.edges[obj]; + + if (edge.source == this) + { + neighbors.add(edge.target); + } + else + { + if (!edge.target == this) + throw "Incorrect incidency!"; + neighbors.add(edge.source); + } + } + + return neighbors; +}; + +LNode.prototype.withChildren = function () +{ + var withNeighborsList = []; + var childNode; + + withNeighborsList.push(this); + + if (this.child != null) + { + var nodes = this.child.getNodes(); + for (var i = 0; i < nodes.length; i++) + { + childNode = nodes[i]; + + withNeighborsList = withNeighborsList.concat(childNode.withChildren()); + } + } + + return withNeighborsList; +}; + +LNode.prototype.getEstimatedSize = function () { + if (this.estimatedSize == Integer.MIN_VALUE) { + throw "assert failed"; + } + return this.estimatedSize; +}; + +LNode.prototype.calcEstimatedSize = function () { + if (this.child == null) + { + return this.estimatedSize = Math.floor((this.rect.width + this.rect.height) / 2); + } + else + { + this.estimatedSize = this.child.calcEstimatedSize(); + this.rect.width = this.estimatedSize; + this.rect.height = this.estimatedSize; + + return this.estimatedSize; + } +}; + +LNode.prototype.scatter = function () { + var randomCenterX; + var randomCenterY; + + var minX = -LayoutConstants.INITIAL_WORLD_BOUNDARY; + var maxX = LayoutConstants.INITIAL_WORLD_BOUNDARY; + randomCenterX = LayoutConstants.WORLD_CENTER_X + + (RandomSeed.nextDouble() * (maxX - minX)) + minX; + + var minY = -LayoutConstants.INITIAL_WORLD_BOUNDARY; + var maxY = LayoutConstants.INITIAL_WORLD_BOUNDARY; + randomCenterY = LayoutConstants.WORLD_CENTER_Y + + (RandomSeed.nextDouble() * (maxY - minY)) + minY; + + this.rect.x = randomCenterX; + this.rect.y = randomCenterY +}; + +LNode.prototype.updateBounds = function () { + if (this.getChild() == null) { + throw "assert failed"; + } + if (this.getChild().getNodes().length != 0) + { + // wrap the children nodes by re-arranging the boundaries + var childGraph = this.getChild(); + childGraph.updateBounds(true); + + this.rect.x = childGraph.getLeft(); + this.rect.y = childGraph.getTop(); + + this.setWidth(childGraph.getRight() - childGraph.getLeft()); + this.setHeight(childGraph.getBottom() - childGraph.getTop()); + } +}; + +LNode.prototype.getInclusionTreeDepth = function () +{ + if (this.inclusionTreeDepth == Integer.MAX_VALUE) { + throw "assert failed"; + } + return this.inclusionTreeDepth; +}; + +LNode.prototype.transform = function (trans) +{ + var left = this.rect.x; + + if (left > LayoutConstants.WORLD_BOUNDARY) + { + left = LayoutConstants.WORLD_BOUNDARY; + } + else if (left < -LayoutConstants.WORLD_BOUNDARY) + { + left = -LayoutConstants.WORLD_BOUNDARY; + } + + var top = this.rect.y; + + if (top > LayoutConstants.WORLD_BOUNDARY) + { + top = LayoutConstants.WORLD_BOUNDARY; + } + else if (top < -LayoutConstants.WORLD_BOUNDARY) + { + top = -LayoutConstants.WORLD_BOUNDARY; + } + + var leftTop = new PointD(left, top); + var vLeftTop = trans.inverseTransformPoint(leftTop); + + this.setLocation(vLeftTop.x, vLeftTop.y); +}; + +LNode.prototype.getLeft = function () +{ + return this.rect.x; +}; + +LNode.prototype.getRight = function () +{ + return this.rect.x + this.rect.width; +}; + +LNode.prototype.getTop = function () +{ + return this.rect.y; +}; + +LNode.prototype.getBottom = function () +{ + return this.rect.y + this.rect.height; +}; + +LNode.prototype.getParent = function () +{ + if (this.owner == null) + { + return null; + } + + return this.owner.getParent(); +}; + +module.exports = LNode; + +},{"./HashSet":14,"./Integer":17,"./LGraphObject":21,"./LayoutConstants":24,"./PointD":26,"./RandomSeed":27,"./RectangleD":28}],23:[function(require,module,exports){ +var LayoutConstants = require('./LayoutConstants'); +var HashMap = require('./HashMap'); +var LGraphManager = require('./LGraphManager'); +var LNode = require('./LNode'); +var LEdge = require('./LEdge'); +var LGraph = require('./LGraph'); +var PointD = require('./PointD'); +var Transform = require('./Transform'); +var Emitter = require('./Emitter'); +var HashSet = require('./HashSet'); + +function Layout(isRemoteUse) { + Emitter.call( this ); + + //Layout Quality: 0:proof, 1:default, 2:draft + this.layoutQuality = LayoutConstants.DEFAULT_QUALITY; + //Whether layout should create bendpoints as needed or not + this.createBendsAsNeeded = + LayoutConstants.DEFAULT_CREATE_BENDS_AS_NEEDED; + //Whether layout should be incremental or not + this.incremental = LayoutConstants.DEFAULT_INCREMENTAL; + //Whether we animate from before to after layout node positions + this.animationOnLayout = + LayoutConstants.DEFAULT_ANIMATION_ON_LAYOUT; + //Whether we animate the layout process or not + this.animationDuringLayout = LayoutConstants.DEFAULT_ANIMATION_DURING_LAYOUT; + //Number iterations that should be done between two successive animations + this.animationPeriod = LayoutConstants.DEFAULT_ANIMATION_PERIOD; + /** + * Whether or not leaf nodes (non-compound nodes) are of uniform sizes. When + * they are, both spring and repulsion forces between two leaf nodes can be + * calculated without the expensive clipping point calculations, resulting + * in major speed-up. + */ + this.uniformLeafNodeSizes = + LayoutConstants.DEFAULT_UNIFORM_LEAF_NODE_SIZES; + /** + * This is used for creation of bendpoints by using dummy nodes and edges. + * Maps an LEdge to its dummy bendpoint path. + */ + this.edgeToDummyNodes = new HashMap(); + this.graphManager = new LGraphManager(this); + this.isLayoutFinished = false; + this.isSubLayout = false; + this.isRemoteUse = false; + + if (isRemoteUse != null) { + this.isRemoteUse = isRemoteUse; + } +} + +Layout.RANDOM_SEED = 1; + +Layout.prototype = Object.create( Emitter.prototype ); + +Layout.prototype.getGraphManager = function () { + return this.graphManager; +}; + +Layout.prototype.getAllNodes = function () { + return this.graphManager.getAllNodes(); +}; + +Layout.prototype.getAllEdges = function () { + return this.graphManager.getAllEdges(); +}; + +Layout.prototype.getAllNodesToApplyGravitation = function () { + return this.graphManager.getAllNodesToApplyGravitation(); +}; + +Layout.prototype.newGraphManager = function () { + var gm = new LGraphManager(this); + this.graphManager = gm; + return gm; +}; + +Layout.prototype.newGraph = function (vGraph) +{ + return new LGraph(null, this.graphManager, vGraph); +}; + +Layout.prototype.newNode = function (vNode) +{ + return new LNode(this.graphManager, vNode); +}; + +Layout.prototype.newEdge = function (vEdge) +{ + return new LEdge(null, null, vEdge); +}; + +Layout.prototype.runLayout = function () +{ + this.isLayoutFinished = false; + + this.initParameters(); + var isLayoutSuccessfull; + + if ((this.graphManager.getRoot() == null) + || this.graphManager.getRoot().getNodes().length == 0 + || this.graphManager.includesInvalidEdge()) + { + isLayoutSuccessfull = false; + } + else + { + // calculate execution time + var startTime = 0; + + if (!this.isSubLayout) + { + startTime = new Date().getTime() + } + + isLayoutSuccessfull = this.layout(); + + if (!this.isSubLayout) + { + var endTime = new Date().getTime(); + var excTime = endTime - startTime; + } + } + + if (isLayoutSuccessfull) + { + if (!this.isSubLayout) + { + this.doPostLayout(); + } + } + + this.isLayoutFinished = true; + + return isLayoutSuccessfull; +}; + +/** + * This method performs the operations required after layout. + */ +Layout.prototype.doPostLayout = function () +{ + //assert !isSubLayout : "Should not be called on sub-layout!"; + // Propagate geometric changes to v-level objects + this.transform(); + this.update(); +}; + +/** + * This method updates the geometry of the target graph according to + * calculated layout. + */ +Layout.prototype.update2 = function () { + // update bend points + if (this.createBendsAsNeeded) + { + this.createBendpointsFromDummyNodes(); + + // reset all edges, since the topology has changed + this.graphManager.resetAllEdges(); + } + + // perform edge, node and root updates if layout is not called + // remotely + if (!this.isRemoteUse) + { + // update all edges + var edge; + var allEdges = this.graphManager.getAllEdges(); + for (var i = 0; i < allEdges.length; i++) + { + edge = allEdges[i]; +// this.update(edge); + } + + // recursively update nodes + var node; + var nodes = this.graphManager.getRoot().getNodes(); + for (var i = 0; i < nodes.length; i++) + { + node = nodes[i]; +// this.update(node); + } + + // update root graph + this.update(this.graphManager.getRoot()); + } +}; + +Layout.prototype.update = function (obj) { + if (obj == null) { + this.update2(); + } + else if (obj instanceof LNode) { + var node = obj; + if (node.getChild() != null) + { + // since node is compound, recursively update child nodes + var nodes = node.getChild().getNodes(); + for (var i = 0; i < nodes.length; i++) + { + update(nodes[i]); + } + } + + // if the l-level node is associated with a v-level graph object, + // then it is assumed that the v-level node implements the + // interface Updatable. + if (node.vGraphObject != null) + { + // cast to Updatable without any type check + var vNode = node.vGraphObject; + + // call the update method of the interface + vNode.update(node); + } + } + else if (obj instanceof LEdge) { + var edge = obj; + // if the l-level edge is associated with a v-level graph object, + // then it is assumed that the v-level edge implements the + // interface Updatable. + + if (edge.vGraphObject != null) + { + // cast to Updatable without any type check + var vEdge = edge.vGraphObject; + + // call the update method of the interface + vEdge.update(edge); + } + } + else if (obj instanceof LGraph) { + var graph = obj; + // if the l-level graph is associated with a v-level graph object, + // then it is assumed that the v-level object implements the + // interface Updatable. + + if (graph.vGraphObject != null) + { + // cast to Updatable without any type check + var vGraph = graph.vGraphObject; + + // call the update method of the interface + vGraph.update(graph); + } + } +}; + +/** + * This method is used to set all layout parameters to default values + * determined at compile time. + */ +Layout.prototype.initParameters = function () { + if (!this.isSubLayout) + { + this.layoutQuality = LayoutConstants.DEFAULT_QUALITY; + this.animationDuringLayout = LayoutConstants.DEFAULT_ANIMATION_ON_LAYOUT; + this.animationPeriod = LayoutConstants.DEFAULT_ANIMATION_PERIOD; + this.animationOnLayout = LayoutConstants.DEFAULT_ANIMATION_DURING_LAYOUT; + this.incremental = LayoutConstants.DEFAULT_INCREMENTAL; + this.createBendsAsNeeded = LayoutConstants.DEFAULT_CREATE_BENDS_AS_NEEDED; + this.uniformLeafNodeSizes = LayoutConstants.DEFAULT_UNIFORM_LEAF_NODE_SIZES; + } + + if (this.animationDuringLayout) + { + animationOnLayout = false; + } +}; + +Layout.prototype.transform = function (newLeftTop) { + if (newLeftTop == undefined) { + this.transform(new PointD(0, 0)); + } + else { + // create a transformation object (from Eclipse to layout). When an + // inverse transform is applied, we get upper-left coordinate of the + // drawing or the root graph at given input coordinate (some margins + // already included in calculation of left-top). + + var trans = new Transform(); + var leftTop = this.graphManager.getRoot().updateLeftTop(); + + if (leftTop != null) + { + trans.setWorldOrgX(newLeftTop.x); + trans.setWorldOrgY(newLeftTop.y); + + trans.setDeviceOrgX(leftTop.x); + trans.setDeviceOrgY(leftTop.y); + + var nodes = this.getAllNodes(); + var node; + + for (var i = 0; i < nodes.length; i++) + { + node = nodes[i]; + node.transform(trans); + } + } + } +}; + +Layout.prototype.positionNodesRandomly = function (graph) { + + if (graph == undefined) { + //assert !this.incremental; + this.positionNodesRandomly(this.getGraphManager().getRoot()); + this.getGraphManager().getRoot().updateBounds(true); + } + else { + var lNode; + var childGraph; + + var nodes = graph.getNodes(); + for (var i = 0; i < nodes.length; i++) + { + lNode = nodes[i]; + childGraph = lNode.getChild(); + + if (childGraph == null) + { + lNode.scatter(); + } + else if (childGraph.getNodes().length == 0) + { + lNode.scatter(); + } + else + { + this.positionNodesRandomly(childGraph); + lNode.updateBounds(); + } + } + } +}; + +/** + * This method returns a list of trees where each tree is represented as a + * list of l-nodes. The method returns a list of size 0 when: + * - The graph is not flat or + * - One of the component(s) of the graph is not a tree. + */ +Layout.prototype.getFlatForest = function () +{ + var flatForest = []; + var isForest = true; + + // Quick reference for all nodes in the graph manager associated with + // this layout. The list should not be changed. + var allNodes = this.graphManager.getRoot().getNodes(); + + // First be sure that the graph is flat + var isFlat = true; + + for (var i = 0; i < allNodes.length; i++) + { + if (allNodes[i].getChild() != null) + { + isFlat = false; + } + } + + // Return empty forest if the graph is not flat. + if (!isFlat) + { + return flatForest; + } + + // Run BFS for each component of the graph. + + var visited = new HashSet(); + var toBeVisited = []; + var parents = new HashMap(); + var unProcessedNodes = []; + + unProcessedNodes = unProcessedNodes.concat(allNodes); + + // Each iteration of this loop finds a component of the graph and + // decides whether it is a tree or not. If it is a tree, adds it to the + // forest and continued with the next component. + + while (unProcessedNodes.length > 0 && isForest) + { + toBeVisited.push(unProcessedNodes[0]); + + // Start the BFS. Each iteration of this loop visits a node in a + // BFS manner. + while (toBeVisited.length > 0 && isForest) + { + //pool operation + var currentNode = toBeVisited[0]; + toBeVisited.splice(0, 1); + visited.add(currentNode); + + // Traverse all neighbors of this node + var neighborEdges = currentNode.getEdges(); + + for (var i = 0; i < neighborEdges.length; i++) + { + var currentNeighbor = + neighborEdges[i].getOtherEnd(currentNode); + + // If BFS is not growing from this neighbor. + if (parents.get(currentNode) != currentNeighbor) + { + // We haven't previously visited this neighbor. + if (!visited.contains(currentNeighbor)) + { + toBeVisited.push(currentNeighbor); + parents.put(currentNeighbor, currentNode); + } + // Since we have previously visited this neighbor and + // this neighbor is not parent of currentNode, given + // graph contains a component that is not tree, hence + // it is not a forest. + else + { + isForest = false; + break; + } + } + } + } + + // The graph contains a component that is not a tree. Empty + // previously found trees. The method will end. + if (!isForest) + { + flatForest = []; + } + // Save currently visited nodes as a tree in our forest. Reset + // visited and parents lists. Continue with the next component of + // the graph, if any. + else + { + var temp = []; + visited.addAllTo(temp); + flatForest.push(temp); + //flatForest = flatForest.concat(temp); + //unProcessedNodes.removeAll(visited); + for (var i = 0; i < temp.length; i++) { + var value = temp[i]; + var index = unProcessedNodes.indexOf(value); + if (index > -1) { + unProcessedNodes.splice(index, 1); + } + } + visited = new HashSet(); + parents = new HashMap(); + } + } + + return flatForest; +}; + +/** + * This method creates dummy nodes (an l-level node with minimal dimensions) + * for the given edge (one per bendpoint). The existing l-level structure + * is updated accordingly. + */ +Layout.prototype.createDummyNodesForBendpoints = function (edge) +{ + var dummyNodes = []; + var prev = edge.source; + + var graph = this.graphManager.calcLowestCommonAncestor(edge.source, edge.target); + + for (var i = 0; i < edge.bendpoints.length; i++) + { + // create new dummy node + var dummyNode = this.newNode(null); + dummyNode.setRect(new Point(0, 0), new Dimension(1, 1)); + + graph.add(dummyNode); + + // create new dummy edge between prev and dummy node + var dummyEdge = this.newEdge(null); + this.graphManager.add(dummyEdge, prev, dummyNode); + + dummyNodes.add(dummyNode); + prev = dummyNode; + } + + var dummyEdge = this.newEdge(null); + this.graphManager.add(dummyEdge, prev, edge.target); + + this.edgeToDummyNodes.put(edge, dummyNodes); + + // remove real edge from graph manager if it is inter-graph + if (edge.isInterGraph()) + { + this.graphManager.remove(edge); + } + // else, remove the edge from the current graph + else + { + graph.remove(edge); + } + + return dummyNodes; +}; + +/** + * This method creates bendpoints for edges from the dummy nodes + * at l-level. + */ +Layout.prototype.createBendpointsFromDummyNodes = function () +{ + var edges = []; + edges = edges.concat(this.graphManager.getAllEdges()); + edges = this.edgeToDummyNodes.keySet().concat(edges); + + for (var k = 0; k < edges.length; k++) + { + var lEdge = edges[k]; + + if (lEdge.bendpoints.length > 0) + { + var path = this.edgeToDummyNodes.get(lEdge); + + for (var i = 0; i < path.length; i++) + { + var dummyNode = path[i]; + var p = new PointD(dummyNode.getCenterX(), + dummyNode.getCenterY()); + + // update bendpoint's location according to dummy node + var ebp = lEdge.bendpoints.get(i); + ebp.x = p.x; + ebp.y = p.y; + + // remove the dummy node, dummy edges incident with this + // dummy node is also removed (within the remove method) + dummyNode.getOwner().remove(dummyNode); + } + + // add the real edge to graph + this.graphManager.add(lEdge, lEdge.source, lEdge.target); + } + } +}; + +Layout.transform = function (sliderValue, defaultValue, minDiv, maxMul) { + if (minDiv != undefined && maxMul != undefined) { + var value = defaultValue; + + if (sliderValue <= 50) + { + var minValue = defaultValue / minDiv; + value -= ((defaultValue - minValue) / 50) * (50 - sliderValue); + } + else + { + var maxValue = defaultValue * maxMul; + value += ((maxValue - defaultValue) / 50) * (sliderValue - 50); + } + + return value; + } + else { + var a, b; + + if (sliderValue <= 50) + { + a = 9.0 * defaultValue / 500.0; + b = defaultValue / 10.0; + } + else + { + a = 9.0 * defaultValue / 50.0; + b = -8 * defaultValue; + } + + return (a * sliderValue + b); + } +}; + +/** + * This method finds and returns the center of the given nodes, assuming + * that the given nodes form a tree in themselves. + */ +Layout.findCenterOfTree = function (nodes) +{ + var list = []; + list = list.concat(nodes); + + var removedNodes = []; + var remainingDegrees = new HashMap(); + var foundCenter = false; + var centerNode = null; + + if (list.length == 1 || list.length == 2) + { + foundCenter = true; + centerNode = list[0]; + } + + for (var i = 0; i < list.length; i++) + { + var node = list[i]; + var degree = node.getNeighborsList().size(); + remainingDegrees.put(node, node.getNeighborsList().size()); + + if (degree == 1) + { + removedNodes.push(node); + } + } + + var tempList = []; + tempList = tempList.concat(removedNodes); + + while (!foundCenter) + { + var tempList2 = []; + tempList2 = tempList2.concat(tempList); + tempList = []; + + for (var i = 0; i < list.length; i++) + { + var node = list[i]; + + var index = list.indexOf(node); + if (index >= 0) { + list.splice(index, 1); + } + + var neighbours = node.getNeighborsList(); + + for (var j in neighbours.set) + { + var neighbour = neighbours.set[j]; + if (removedNodes.indexOf(neighbour) < 0) + { + var otherDegree = remainingDegrees.get(neighbour); + var newDegree = otherDegree - 1; + + if (newDegree == 1) + { + tempList.push(neighbour); + } + + remainingDegrees.put(neighbour, newDegree); + } + } + } + + removedNodes = removedNodes.concat(tempList); + + if (list.length == 1 || list.length == 2) + { + foundCenter = true; + centerNode = list[0]; + } + } + + return centerNode; +}; + +/** + * During the coarsening process, this layout may be referenced by two graph managers + * this setter function grants access to change the currently being used graph manager + */ +Layout.prototype.setGraphManager = function (gm) +{ + this.graphManager = gm; +}; + +module.exports = Layout; + +},{"./Emitter":8,"./HashMap":13,"./HashSet":14,"./LEdge":18,"./LGraph":19,"./LGraphManager":20,"./LNode":22,"./LayoutConstants":24,"./PointD":26,"./Transform":30}],24:[function(require,module,exports){ +function LayoutConstants() { +} + +/** + * Layout Quality + */ +LayoutConstants.PROOF_QUALITY = 0; +LayoutConstants.DEFAULT_QUALITY = 1; +LayoutConstants.DRAFT_QUALITY = 2; + +/** + * Default parameters + */ +LayoutConstants.DEFAULT_CREATE_BENDS_AS_NEEDED = false; +//LayoutConstants.DEFAULT_INCREMENTAL = true; +LayoutConstants.DEFAULT_INCREMENTAL = false; +LayoutConstants.DEFAULT_ANIMATION_ON_LAYOUT = true; +LayoutConstants.DEFAULT_ANIMATION_DURING_LAYOUT = false; +LayoutConstants.DEFAULT_ANIMATION_PERIOD = 50; +LayoutConstants.DEFAULT_UNIFORM_LEAF_NODE_SIZES = false; + +// ----------------------------------------------------------------------------- +// Section: General other constants +// ----------------------------------------------------------------------------- +/* + * Margins of a graph to be applied on bouding rectangle of its contents. We + * assume margins on all four sides to be uniform. + */ +LayoutConstants.DEFAULT_GRAPH_MARGIN = 15; + +/* + * Default dimension of a non-compound node. + */ +LayoutConstants.SIMPLE_NODE_SIZE = 40; + +/* + * Default dimension of a non-compound node. + */ +LayoutConstants.SIMPLE_NODE_HALF_SIZE = LayoutConstants.SIMPLE_NODE_SIZE / 2; + +/* + * Empty compound node size. When a compound node is empty, its both + * dimensions should be of this value. + */ +LayoutConstants.EMPTY_COMPOUND_NODE_SIZE = 40; + +/* + * Minimum length that an edge should take during layout + */ +LayoutConstants.MIN_EDGE_LENGTH = 1; + +/* + * World boundaries that layout operates on + */ +LayoutConstants.WORLD_BOUNDARY = 1000000; + +/* + * World boundaries that random positioning can be performed with + */ +LayoutConstants.INITIAL_WORLD_BOUNDARY = LayoutConstants.WORLD_BOUNDARY / 1000; + +/* + * Coordinates of the world center + */ +LayoutConstants.WORLD_CENTER_X = 1200; +LayoutConstants.WORLD_CENTER_Y = 900; + +module.exports = LayoutConstants; + +},{}],25:[function(require,module,exports){ +/* + *This class is the javascript implementation of the Point.java class in jdk + */ +function Point(x, y, p) { + this.x = null; + this.y = null; + if (x == null && y == null && p == null) { + this.x = 0; + this.y = 0; + } + else if (typeof x == 'number' && typeof y == 'number' && p == null) { + this.x = x; + this.y = y; + } + else if (x.constructor.name == 'Point' && y == null && p == null) { + p = x; + this.x = p.x; + this.y = p.y; + } +} + +Point.prototype.getX = function () { + return this.x; +} + +Point.prototype.getY = function () { + return this.y; +} + +Point.prototype.getLocation = function () { + return new Point(this.x, this.y); +} + +Point.prototype.setLocation = function (x, y, p) { + if (x.constructor.name == 'Point' && y == null && p == null) { + p = x; + this.setLocation(p.x, p.y); + } + else if (typeof x == 'number' && typeof y == 'number' && p == null) { + //if both parameters are integer just move (x,y) location + if (parseInt(x) == x && parseInt(y) == y) { + this.move(x, y); + } + else { + this.x = Math.floor(x + 0.5); + this.y = Math.floor(y + 0.5); + } + } +} + +Point.prototype.move = function (x, y) { + this.x = x; + this.y = y; +} + +Point.prototype.translate = function (dx, dy) { + this.x += dx; + this.y += dy; +} + +Point.prototype.equals = function (obj) { + if (obj.constructor.name == "Point") { + var pt = obj; + return (this.x == pt.x) && (this.y == pt.y); + } + return this == obj; +} + +Point.prototype.toString = function () { + return new Point().constructor.name + "[x=" + this.x + ",y=" + this.y + "]"; +} + +module.exports = Point; + +},{}],26:[function(require,module,exports){ +function PointD(x, y) { + if (x == null && y == null) { + this.x = 0; + this.y = 0; + } else { + this.x = x; + this.y = y; + } +} + +PointD.prototype.getX = function () +{ + return this.x; +}; + +PointD.prototype.getY = function () +{ + return this.y; +}; + +PointD.prototype.setX = function (x) +{ + this.x = x; +}; + +PointD.prototype.setY = function (y) +{ + this.y = y; +}; + +PointD.prototype.getDifference = function (pt) +{ + return new DimensionD(this.x - pt.x, this.y - pt.y); +}; + +PointD.prototype.getCopy = function () +{ + return new PointD(this.x, this.y); +}; + +PointD.prototype.translate = function (dim) +{ + this.x += dim.width; + this.y += dim.height; + return this; +}; + +module.exports = PointD; + +},{}],27:[function(require,module,exports){ +function RandomSeed() { +} +RandomSeed.seed = 1; +RandomSeed.x = 0; + +RandomSeed.nextDouble = function () { + RandomSeed.x = Math.sin(RandomSeed.seed++) * 10000; + return RandomSeed.x - Math.floor(RandomSeed.x); +}; + +module.exports = RandomSeed; + +},{}],28:[function(require,module,exports){ +function RectangleD(x, y, width, height) { + this.x = 0; + this.y = 0; + this.width = 0; + this.height = 0; + + if (x != null && y != null && width != null && height != null) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } +} + +RectangleD.prototype.getX = function () +{ + return this.x; +}; + +RectangleD.prototype.setX = function (x) +{ + this.x = x; +}; + +RectangleD.prototype.getY = function () +{ + return this.y; +}; + +RectangleD.prototype.setY = function (y) +{ + this.y = y; +}; + +RectangleD.prototype.getWidth = function () +{ + return this.width; +}; + +RectangleD.prototype.setWidth = function (width) +{ + this.width = width; +}; + +RectangleD.prototype.getHeight = function () +{ + return this.height; +}; + +RectangleD.prototype.setHeight = function (height) +{ + this.height = height; +}; + +RectangleD.prototype.getRight = function () +{ + return this.x + this.width; +}; + +RectangleD.prototype.getBottom = function () +{ + return this.y + this.height; +}; + +RectangleD.prototype.intersects = function (a) +{ + if (this.getRight() < a.x) + { + return false; + } + + if (this.getBottom() < a.y) + { + return false; + } + + if (a.getRight() < this.x) + { + return false; + } + + if (a.getBottom() < this.y) + { + return false; + } + + return true; +}; + +RectangleD.prototype.getCenterX = function () +{ + return this.x + this.width / 2; +}; + +RectangleD.prototype.getMinX = function () +{ + return this.getX(); +}; + +RectangleD.prototype.getMaxX = function () +{ + return this.getX() + this.width; +}; + +RectangleD.prototype.getCenterY = function () +{ + return this.y + this.height / 2; +}; + +RectangleD.prototype.getMinY = function () +{ + return this.getY(); +}; + +RectangleD.prototype.getMaxY = function () +{ + return this.getY() + this.height; +}; + +RectangleD.prototype.getWidthHalf = function () +{ + return this.width / 2; +}; + +RectangleD.prototype.getHeightHalf = function () +{ + return this.height / 2; +}; + +module.exports = RectangleD; + +},{}],29:[function(require,module,exports){ +module.exports = function (instance) { + instance.toBeTiled = {}; + + instance.getToBeTiled = function (node) { + var id = node.data("id"); + //firstly check the previous results + if (instance.toBeTiled[id] != null) { + return instance.toBeTiled[id]; + } + + //only compound nodes are to be tiled + var children = node.children(); + if (children == null || children.length == 0) { + instance.toBeTiled[id] = false; + return false; + } + + //a compound node is not to be tiled if all of its compound children are not to be tiled + for (var i = 0; i < children.length; i++) { + var theChild = children[i]; + + if (instance.getNodeDegree(theChild) > 0) { + instance.toBeTiled[id] = false; + return false; + } + + //pass the children not having the compound structure + if (theChild.children() == null || theChild.children().length == 0) { + instance.toBeTiled[theChild.data("id")] = false; + continue; + } + + if (!instance.getToBeTiled(theChild)) { + instance.toBeTiled[id] = false; + return false; + } + } + instance.toBeTiled[id] = true; + return true; + }; + + instance.getNodeDegree = function (node) { + var id = node.id(); + var edges = instance.options.eles.edges().filter(function (ele, i) { + if (typeof ele === "number") { + ele = i; + } + var source = ele.data('source'); + var target = ele.data('target'); + if (source != target && (source == id || target == id)) { + return true; + } + }); + return edges.length; + }; + + instance.getNodeDegreeWithChildren = function (node) { + var degree = instance.getNodeDegree(node); + var children = node.children(); + for (var i = 0; i < children.length; i++) { + var child = children[i]; + degree += instance.getNodeDegreeWithChildren(child); + } + return degree; + }; + + instance.groupZeroDegreeMembers = function () { + // array of [parent_id x oneDegreeNode_id] + var tempMemberGroups = []; + var memberGroups = []; + var self = this; + var parentMap = {}; + + for (var i = 0; i < instance.options.eles.nodes().length; i++) { + parentMap[instance.options.eles.nodes()[i].id()] = true; + } + + // Find all zero degree nodes which aren't covered by a compound + var zeroDegree = instance.options.eles.nodes().filter(function (ele, i) { + if (typeof ele === "number") { + ele = i; + } + var pid = ele.data('parent'); + if (pid != undefined && !parentMap[pid]) { + pid = undefined; + } + + if (self.getNodeDegreeWithChildren(ele) == 0 && (pid == undefined || (pid != undefined && !self.getToBeTiled(ele.parent()[0])))) + return true; + else + return false; + }); + + // Create a map of parent node and its zero degree members + for (var i = 0; i < zeroDegree.length; i++) + { + var node = zeroDegree[i]; + var p_id = node.parent().id(); + + if (p_id != undefined && !parentMap[p_id]) { + p_id = undefined; + } + + if (typeof tempMemberGroups[p_id] === "undefined") + tempMemberGroups[p_id] = []; + + tempMemberGroups[p_id] = tempMemberGroups[p_id].concat(node); + } + + // If there are at least two nodes at a level, create a dummy compound for them + for (var p_id in tempMemberGroups) { + if (tempMemberGroups[p_id].length > 1) { + var dummyCompoundId = "DummyCompound_" + p_id; + memberGroups[dummyCompoundId] = tempMemberGroups[p_id]; + + // Create a dummy compound + if (instance.options.cy.getElementById(dummyCompoundId).empty()) { + instance.options.cy.add({ + group: "nodes", + data: {id: dummyCompoundId, parent: p_id + } + }); + + var dummy = instance.options.cy.nodes()[instance.options.cy.nodes().length - 1]; + instance.options.eles = instance.options.eles.union(dummy); + dummy.hide(); + + for (var i = 0; i < tempMemberGroups[p_id].length; i++) { + if (i == 0) { + dummy.scratch('coseBilkent', {tempchildren: []}); + } + var node = tempMemberGroups[p_id][i]; + var scratchObj = node.scratch('coseBilkent'); + if (!scratchObj) { + scratchObj = {}; + node.scratch('coseBilkent', scratchObj); + } + scratchObj['dummy_parent_id'] = dummyCompoundId; + instance.options.cy.add({ + group: "nodes", + data: {parent: dummyCompoundId, width: node.width(), height: node.height() + } + }); + var tempchild = instance.options.cy.nodes()[instance.options.cy.nodes().length - 1]; + tempchild.hide(); + tempchild.css('width', tempchild.data('width')); + tempchild.css('height', tempchild.data('height')); + tempchild.width(); + dummy.scratch('coseBilkent').tempchildren.push(tempchild); + } + } + } + } + + return memberGroups; + }; + + instance.performDFSOnCompounds = function (options) { + var compoundOrder = []; + + var roots = instance.getTopMostNodes(instance.options.eles.nodes()); + instance.fillCompexOrderByDFS(compoundOrder, roots); + + return compoundOrder; + }; + + instance.fillCompexOrderByDFS = function (compoundOrder, children) { + for (var i = 0; i < children.length; i++) { + var child = children[i]; + instance.fillCompexOrderByDFS(compoundOrder, child.children()); + if (instance.getToBeTiled(child)) { + compoundOrder.push(child); + } + } + }; + + instance.clearCompounds = function () { + var childGraphMap = []; + + // Get compound ordering by finding the inner one first + var compoundOrder = instance.performDFSOnCompounds(instance.options); + instance.compoundOrder = compoundOrder; + instance.processChildrenList(instance.root, instance.getTopMostNodes(instance.options.eles.nodes()), instance.layout); + + for (var i = 0; i < compoundOrder.length; i++) { + // find the corresponding layout node + var lCompoundNode = instance.idToLNode[compoundOrder[i].id()]; + + childGraphMap[compoundOrder[i].id()] = compoundOrder[i].children(); + + // Remove children of compounds + lCompoundNode.child = null; + } + + // Tile the removed children + var tiledMemberPack = instance.tileCompoundMembers(childGraphMap); + + return tiledMemberPack; + }; + + instance.clearZeroDegreeMembers = function (memberGroups) { + var tiledZeroDegreePack = []; + + for (var id in memberGroups) { + var compoundNode = instance.idToLNode[id]; + + tiledZeroDegreePack[id] = instance.tileNodes(memberGroups[id]); + + // Set the width and height of the dummy compound as calculated + compoundNode.rect.width = tiledZeroDegreePack[id].width; + compoundNode.rect.height = tiledZeroDegreePack[id].height; + } + return tiledZeroDegreePack; + }; + + instance.repopulateCompounds = function (tiledMemberPack) { + for (var i = instance.compoundOrder.length - 1; i >= 0; i--) { + var id = instance.compoundOrder[i].id(); + var lCompoundNode = instance.idToLNode[id]; + var horizontalMargin = parseInt(instance.compoundOrder[i].css('padding-left')); + var verticalMargin = parseInt(instance.compoundOrder[i].css('padding-top')); + + instance.adjustLocations(tiledMemberPack[id], lCompoundNode.rect.x, lCompoundNode.rect.y, horizontalMargin, verticalMargin); + } + }; + + instance.repopulateZeroDegreeMembers = function (tiledPack) { + for (var i in tiledPack) { + var compound = instance.cy.getElementById(i); + var compoundNode = instance.idToLNode[i]; + var horizontalMargin = parseInt(compound.css('padding-left')); + var verticalMargin = parseInt(compound.css('padding-top')); + + // Adjust the positions of nodes wrt its compound + instance.adjustLocations(tiledPack[i], compoundNode.rect.x, compoundNode.rect.y, horizontalMargin, verticalMargin); + + var tempchildren = compound.scratch('coseBilkent').tempchildren; + for (var i = 0; i < tempchildren.length; i++) { + tempchildren[i].remove(); + } + + // Remove the dummy compound + compound.remove(); + } + }; + + /** + * This method places each zero degree member wrt given (x,y) coordinates (top left). + */ + instance.adjustLocations = function (organization, x, y, compoundHorizontalMargin, compoundVerticalMargin) { + x += compoundHorizontalMargin; + y += compoundVerticalMargin; + + var left = x; + + for (var i = 0; i < organization.rows.length; i++) { + var row = organization.rows[i]; + x = left; + var maxHeight = 0; + + for (var j = 0; j < row.length; j++) { + var lnode = row[j]; + var node = instance.cy.getElementById(lnode.id); + + lnode.rect.x = x;// + lnode.rect.width / 2; + lnode.rect.y = y;// + lnode.rect.height / 2; + + x += lnode.rect.width + organization.horizontalPadding; + + if (lnode.rect.height > maxHeight) + maxHeight = lnode.rect.height; + } + + y += maxHeight + organization.verticalPadding; + } + }; + + instance.tileCompoundMembers = function (childGraphMap) { + var tiledMemberPack = []; + + for (var id in childGraphMap) { + // Access layoutInfo nodes to set the width and height of compounds + var compoundNode = instance.idToLNode[id]; + + tiledMemberPack[id] = instance.tileNodes(childGraphMap[id]); + + compoundNode.rect.width = tiledMemberPack[id].width + 20; + compoundNode.rect.height = tiledMemberPack[id].height + 20; + } + + return tiledMemberPack; + }; + + instance.tileNodes = function (nodes) { + var self = this; + var verticalPadding = typeof self.options.tilingPaddingVertical === 'function' ? self.options.tilingPaddingVertical.call() : self.options.tilingPaddingVertical; + var horizontalPadding = typeof self.options.tilingPaddingHorizontal === 'function' ? self.options.tilingPaddingHorizontal.call() : self.options.tilingPaddingHorizontal; + var organization = { + rows: [], + rowWidth: [], + rowHeight: [], + width: 20, + height: 20, + verticalPadding: verticalPadding, + horizontalPadding: horizontalPadding + }; + + var layoutNodes = []; + + // Get layout nodes + for (var i = 0; i < nodes.length; i++) { + var node = nodes[i]; + var lNode = instance.idToLNode[node.id()]; + + if (!node.scratch('coseBilkent') || !node.scratch('coseBilkent').dummy_parent_id) { + var owner = lNode.owner; + owner.remove(lNode); + + instance.gm.resetAllNodes(); + instance.gm.getAllNodes(); + } + + layoutNodes.push(lNode); + } + + // Sort the nodes in ascending order of their areas + layoutNodes.sort(function (n1, n2) { + if (n1.rect.width * n1.rect.height > n2.rect.width * n2.rect.height) + return -1; + if (n1.rect.width * n1.rect.height < n2.rect.width * n2.rect.height) + return 1; + return 0; + }); + + // Create the organization -> tile members + for (var i = 0; i < layoutNodes.length; i++) { + var lNode = layoutNodes[i]; + + var cyNode = instance.cy.getElementById(lNode.id).parent()[0]; + var minWidth = 0; + if (cyNode) { + minWidth = parseInt(cyNode.css('padding-left')) + parseInt(cyNode.css('padding-right')); + } + + if (organization.rows.length == 0) { + instance.insertNodeToRow(organization, lNode, 0, minWidth); + } + else if (instance.canAddHorizontal(organization, lNode.rect.width, lNode.rect.height)) { + instance.insertNodeToRow(organization, lNode, instance.getShortestRowIndex(organization), minWidth); + } + else { + instance.insertNodeToRow(organization, lNode, organization.rows.length, minWidth); + } + + instance.shiftToLastRow(organization); + } + + return organization; + }; + + instance.insertNodeToRow = function (organization, node, rowIndex, minWidth) { + var minCompoundSize = minWidth; + + // Add new row if needed + if (rowIndex == organization.rows.length) { + var secondDimension = []; + + organization.rows.push(secondDimension); + organization.rowWidth.push(minCompoundSize); + organization.rowHeight.push(0); + } + + // Update row width + var w = organization.rowWidth[rowIndex] + node.rect.width; + + if (organization.rows[rowIndex].length > 0) { + w += organization.horizontalPadding; + } + + organization.rowWidth[rowIndex] = w; + // Update compound width + if (organization.width < w) { + organization.width = w; + } + + // Update height + var h = node.rect.height; + if (rowIndex > 0) + h += organization.verticalPadding; + + var extraHeight = 0; + if (h > organization.rowHeight[rowIndex]) { + extraHeight = organization.rowHeight[rowIndex]; + organization.rowHeight[rowIndex] = h; + extraHeight = organization.rowHeight[rowIndex] - extraHeight; + } + + organization.height += extraHeight; + + // Insert node + organization.rows[rowIndex].push(node); + }; + +//Scans the rows of an organization and returns the one with the min width + instance.getShortestRowIndex = function (organization) { + var r = -1; + var min = Number.MAX_VALUE; + + for (var i = 0; i < organization.rows.length; i++) { + if (organization.rowWidth[i] < min) { + r = i; + min = organization.rowWidth[i]; + } + } + return r; + }; + +//Scans the rows of an organization and returns the one with the max width + instance.getLongestRowIndex = function (organization) { + var r = -1; + var max = Number.MIN_VALUE; + + for (var i = 0; i < organization.rows.length; i++) { + + if (organization.rowWidth[i] > max) { + r = i; + max = organization.rowWidth[i]; + } + } + + return r; + }; + + /** + * This method checks whether adding extra width to the organization violates + * the aspect ratio(1) or not. + */ + instance.canAddHorizontal = function (organization, extraWidth, extraHeight) { + + var sri = instance.getShortestRowIndex(organization); + + if (sri < 0) { + return true; + } + + var min = organization.rowWidth[sri]; + + if (min + organization.horizontalPadding + extraWidth <= organization.width) + return true; + + var hDiff = 0; + + // Adding to an existing row + if (organization.rowHeight[sri] < extraHeight) { + if (sri > 0) + hDiff = extraHeight + organization.verticalPadding - organization.rowHeight[sri]; + } + + var add_to_row_ratio; + if (organization.width - min >= extraWidth + organization.horizontalPadding) { + add_to_row_ratio = (organization.height + hDiff) / (min + extraWidth + organization.horizontalPadding); + } else { + add_to_row_ratio = (organization.height + hDiff) / organization.width; + } + + // Adding a new row for this node + hDiff = extraHeight + organization.verticalPadding; + var add_new_row_ratio; + if (organization.width < extraWidth) { + add_new_row_ratio = (organization.height + hDiff) / extraWidth; + } else { + add_new_row_ratio = (organization.height + hDiff) / organization.width; + } + + if (add_new_row_ratio < 1) + add_new_row_ratio = 1 / add_new_row_ratio; + + if (add_to_row_ratio < 1) + add_to_row_ratio = 1 / add_to_row_ratio; + + return add_to_row_ratio < add_new_row_ratio; + }; + + +//If moving the last node from the longest row and adding it to the last +//row makes the bounding box smaller, do it. + instance.shiftToLastRow = function (organization) { + var longest = instance.getLongestRowIndex(organization); + var last = organization.rowWidth.length - 1; + var row = organization.rows[longest]; + var node = row[row.length - 1]; + + var diff = node.width + organization.horizontalPadding; + + // Check if there is enough space on the last row + if (organization.width - organization.rowWidth[last] > diff && longest != last) { + // Remove the last element of the longest row + row.splice(-1, 1); + + // Push it to the last row + organization.rows[last].push(node); + + organization.rowWidth[longest] = organization.rowWidth[longest] - diff; + organization.rowWidth[last] = organization.rowWidth[last] + diff; + organization.width = organization.rowWidth[instance.getLongestRowIndex(organization)]; + + // Update heights of the organization + var maxHeight = Number.MIN_VALUE; + for (var i = 0; i < row.length; i++) { + if (row[i].height > maxHeight) + maxHeight = row[i].height; + } + if (longest > 0) + maxHeight += organization.verticalPadding; + + var prevTotal = organization.rowHeight[longest] + organization.rowHeight[last]; + + organization.rowHeight[longest] = maxHeight; + if (organization.rowHeight[last] < node.height + organization.verticalPadding) + organization.rowHeight[last] = node.height + organization.verticalPadding; + + var finalTotal = organization.rowHeight[longest] + organization.rowHeight[last]; + organization.height += (finalTotal - prevTotal); + + instance.shiftToLastRow(organization); + } + }; + + instance.preLayout = function() { + // Find zero degree nodes and create a compound for each level + var memberGroups = instance.groupZeroDegreeMembers(); + // Tile and clear children of each compound + instance.tiledMemberPack = instance.clearCompounds(); + // Separately tile and clear zero degree nodes for each level + instance.tiledZeroDegreeNodes = instance.clearZeroDegreeMembers(memberGroups); + }; + + instance.postLayout = function() { + var nodes = instance.options.eles.nodes(); + //fill the toBeTiled map + for (var i = 0; i < nodes.length; i++) { + instance.getToBeTiled(nodes[i]); + } + + // Repopulate members + instance.repopulateZeroDegreeMembers(instance.tiledZeroDegreeNodes); + + instance.repopulateCompounds(instance.tiledMemberPack); + + instance.options.cy.nodes().updateCompoundBounds(); + }; +}; +},{}],30:[function(require,module,exports){ +var PointD = require('./PointD'); + +function Transform(x, y) { + this.lworldOrgX = 0.0; + this.lworldOrgY = 0.0; + this.ldeviceOrgX = 0.0; + this.ldeviceOrgY = 0.0; + this.lworldExtX = 1.0; + this.lworldExtY = 1.0; + this.ldeviceExtX = 1.0; + this.ldeviceExtY = 1.0; +} + +Transform.prototype.getWorldOrgX = function () +{ + return this.lworldOrgX; +} + +Transform.prototype.setWorldOrgX = function (wox) +{ + this.lworldOrgX = wox; +} + +Transform.prototype.getWorldOrgY = function () +{ + return this.lworldOrgY; +} + +Transform.prototype.setWorldOrgY = function (woy) +{ + this.lworldOrgY = woy; +} + +Transform.prototype.getWorldExtX = function () +{ + return this.lworldExtX; +} + +Transform.prototype.setWorldExtX = function (wex) +{ + this.lworldExtX = wex; +} + +Transform.prototype.getWorldExtY = function () +{ + return this.lworldExtY; +} + +Transform.prototype.setWorldExtY = function (wey) +{ + this.lworldExtY = wey; +} + +/* Device related */ + +Transform.prototype.getDeviceOrgX = function () +{ + return this.ldeviceOrgX; +} + +Transform.prototype.setDeviceOrgX = function (dox) +{ + this.ldeviceOrgX = dox; +} + +Transform.prototype.getDeviceOrgY = function () +{ + return this.ldeviceOrgY; +} + +Transform.prototype.setDeviceOrgY = function (doy) +{ + this.ldeviceOrgY = doy; +} + +Transform.prototype.getDeviceExtX = function () +{ + return this.ldeviceExtX; +} + +Transform.prototype.setDeviceExtX = function (dex) +{ + this.ldeviceExtX = dex; +} + +Transform.prototype.getDeviceExtY = function () +{ + return this.ldeviceExtY; +} + +Transform.prototype.setDeviceExtY = function (dey) +{ + this.ldeviceExtY = dey; +} + +Transform.prototype.transformX = function (x) +{ + var xDevice = 0.0; + var worldExtX = this.lworldExtX; + if (worldExtX != 0.0) + { + xDevice = this.ldeviceOrgX + + ((x - this.lworldOrgX) * this.ldeviceExtX / worldExtX); + } + + return xDevice; +} + +Transform.prototype.transformY = function (y) +{ + var yDevice = 0.0; + var worldExtY = this.lworldExtY; + if (worldExtY != 0.0) + { + yDevice = this.ldeviceOrgY + + ((y - this.lworldOrgY) * this.ldeviceExtY / worldExtY); + } + + + return yDevice; +} + +Transform.prototype.inverseTransformX = function (x) +{ + var xWorld = 0.0; + var deviceExtX = this.ldeviceExtX; + if (deviceExtX != 0.0) + { + xWorld = this.lworldOrgX + + ((x - this.ldeviceOrgX) * this.lworldExtX / deviceExtX); + } + + + return xWorld; +} + +Transform.prototype.inverseTransformY = function (y) +{ + var yWorld = 0.0; + var deviceExtY = this.ldeviceExtY; + if (deviceExtY != 0.0) + { + yWorld = this.lworldOrgY + + ((y - this.ldeviceOrgY) * this.lworldExtY / deviceExtY); + } + return yWorld; +} + +Transform.prototype.inverseTransformPoint = function (inPoint) +{ + var outPoint = + new PointD(this.inverseTransformX(inPoint.x), + this.inverseTransformY(inPoint.y)); + return outPoint; +} + +module.exports = Transform; + +},{"./PointD":26}],31:[function(require,module,exports){ +function UniqueIDGeneretor() { +} + +UniqueIDGeneretor.lastID = 0; + +UniqueIDGeneretor.createID = function (obj) { + if (UniqueIDGeneretor.isPrimitive(obj)) { + return obj; + } + if (obj.uniqueID != null) { + return obj.uniqueID; + } + obj.uniqueID = UniqueIDGeneretor.getString(); + UniqueIDGeneretor.lastID++; + return obj.uniqueID; +} + +UniqueIDGeneretor.getString = function (id) { + if (id == null) + id = UniqueIDGeneretor.lastID; + return "Object#" + id + ""; +} + +UniqueIDGeneretor.isPrimitive = function (arg) { + var type = typeof arg; + return arg == null || (type != "object" && type != "function"); +} + +module.exports = UniqueIDGeneretor; + +},{}],32:[function(require,module,exports){ +'use strict'; + +var DimensionD = require('./DimensionD'); +var HashMap = require('./HashMap'); +var HashSet = require('./HashSet'); +var IGeometry = require('./IGeometry'); +var IMath = require('./IMath'); +var Integer = require('./Integer'); +var Point = require('./Point'); +var PointD = require('./PointD'); +var RandomSeed = require('./RandomSeed'); +var RectangleD = require('./RectangleD'); +var Transform = require('./Transform'); +var UniqueIDGeneretor = require('./UniqueIDGeneretor'); +var LGraphObject = require('./LGraphObject'); +var LGraph = require('./LGraph'); +var LEdge = require('./LEdge'); +var LGraphManager = require('./LGraphManager'); +var LNode = require('./LNode'); +var Layout = require('./Layout'); +var LayoutConstants = require('./LayoutConstants'); +var FDLayout = require('./FDLayout'); +var FDLayoutConstants = require('./FDLayoutConstants'); +var FDLayoutEdge = require('./FDLayoutEdge'); +var FDLayoutNode = require('./FDLayoutNode'); +var CoSEConstants = require('./CoSEConstants'); +var CoSEEdge = require('./CoSEEdge'); +var CoSEGraph = require('./CoSEGraph'); +var CoSEGraphManager = require('./CoSEGraphManager'); +var CoSELayout = require('./CoSELayout'); +var CoSENode = require('./CoSENode'); +var TilingExtension = require('./TilingExtension'); + +var defaults = { + // Called on `layoutready` + ready: function () { + }, + // Called on `layoutstop` + stop: function () { + }, + // number of ticks per frame; higher is faster but more jerky + refresh: 30, + // Whether to fit the network view after when done + fit: true, + // Padding on fit + padding: 10, + // Padding for compounds + paddingCompound: 15, + // Whether to enable incremental mode + randomize: true, + // Node repulsion (non overlapping) multiplier + nodeRepulsion: 4500, + // Ideal edge (non nested) length + idealEdgeLength: 50, + // Divisor to compute edge forces + edgeElasticity: 0.45, + // Nesting factor (multiplier) to compute ideal edge length for nested edges + nestingFactor: 0.1, + // Gravity force (constant) + gravity: 0.25, + // Maximum number of iterations to perform + numIter: 2500, + // For enabling tiling + tile: true, + // Type of layout animation. The option set is {'during', 'end', false} + animate: 'end', + // Duration for animate:end + animationDuration: 500, + // Represents the amount of the vertical space to put between the zero degree members during the tiling operation(can also be a function) + tilingPaddingVertical: 10, + // Represents the amount of the horizontal space to put between the zero degree members during the tiling operation(can also be a function) + tilingPaddingHorizontal: 10, + // Gravity range (constant) for compounds + gravityRangeCompound: 1.5, + // Gravity force (constant) for compounds + gravityCompound: 1.0, + // Gravity range (constant) + gravityRange: 3.8 +}; + +function extend(defaults, options) { + var obj = {}; + + for (var i in defaults) { + obj[i] = defaults[i]; + } + + for (var i in options) { + obj[i] = options[i]; + } + + return obj; +}; + +function _CoSELayout(_options) { + TilingExtension(this); // Extend this instance with tiling functions + this.options = extend(defaults, _options); + getUserOptions(this.options); +} + +var getUserOptions = function (options) { + if (options.nodeRepulsion != null) + CoSEConstants.DEFAULT_REPULSION_STRENGTH = FDLayoutConstants.DEFAULT_REPULSION_STRENGTH = options.nodeRepulsion; + if (options.idealEdgeLength != null) + CoSEConstants.DEFAULT_EDGE_LENGTH = FDLayoutConstants.DEFAULT_EDGE_LENGTH = options.idealEdgeLength; + if (options.edgeElasticity != null) + CoSEConstants.DEFAULT_SPRING_STRENGTH = FDLayoutConstants.DEFAULT_SPRING_STRENGTH = options.edgeElasticity; + if (options.nestingFactor != null) + CoSEConstants.PER_LEVEL_IDEAL_EDGE_LENGTH_FACTOR = FDLayoutConstants.PER_LEVEL_IDEAL_EDGE_LENGTH_FACTOR = options.nestingFactor; + if (options.gravity != null) + CoSEConstants.DEFAULT_GRAVITY_STRENGTH = FDLayoutConstants.DEFAULT_GRAVITY_STRENGTH = options.gravity; + if (options.numIter != null) + CoSEConstants.MAX_ITERATIONS = FDLayoutConstants.MAX_ITERATIONS = options.numIter; + if (options.paddingCompound != null) + CoSEConstants.DEFAULT_GRAPH_MARGIN = FDLayoutConstants.DEFAULT_GRAPH_MARGIN = LayoutConstants.DEFAULT_GRAPH_MARGIN = options.paddingCompound; + if (options.gravityRange != null) + CoSEConstants.DEFAULT_GRAVITY_RANGE_FACTOR = FDLayoutConstants.DEFAULT_GRAVITY_RANGE_FACTOR = options.gravityRange; + if(options.gravityCompound != null) + CoSEConstants.DEFAULT_COMPOUND_GRAVITY_STRENGTH = FDLayoutConstants.DEFAULT_COMPOUND_GRAVITY_STRENGTH = options.gravityCompound; + if(options.gravityRangeCompound != null) + CoSEConstants.DEFAULT_COMPOUND_GRAVITY_RANGE_FACTOR = FDLayoutConstants.DEFAULT_COMPOUND_GRAVITY_RANGE_FACTOR = options.gravityRangeCompound; + + CoSEConstants.DEFAULT_INCREMENTAL = FDLayoutConstants.DEFAULT_INCREMENTAL = LayoutConstants.DEFAULT_INCREMENTAL = + !(options.randomize); + CoSEConstants.ANIMATE = FDLayoutConstants.ANIMATE = options.animate; +}; + +_CoSELayout.prototype.run = function () { + var ready; + var frameId; + var options = this.options; + var idToLNode = this.idToLNode = {}; + var layout = this.layout = new CoSELayout(); + var self = this; + + this.cy = this.options.cy; + + this.cy.trigger('layoutstart'); + + var gm = layout.newGraphManager(); + this.gm = gm; + + var nodes = this.options.eles.nodes(); + var edges = this.options.eles.edges(); + + this.root = gm.addRoot(); + + if (!this.options.tile) { + this.processChildrenList(this.root, this.getTopMostNodes(nodes), layout); + } + else { + this.preLayout(); + } + + + for (var i = 0; i < edges.length; i++) { + var edge = edges[i]; + var sourceNode = this.idToLNode[edge.data("source")]; + var targetNode = this.idToLNode[edge.data("target")]; + var e1 = gm.add(layout.newEdge(), sourceNode, targetNode); + e1.id = edge.id(); + } + + var getPositions = function(ele, i){ + if(typeof ele === "number") { + ele = i; + } + var theId = ele.data('id'); + var lNode = self.idToLNode[theId]; + + return { + x: lNode.getRect().getCenterX(), + y: lNode.getRect().getCenterY() + }; + }; + + /* + * Reposition nodes in iterations animatedly + */ + var iterateAnimated = function () { + // Thigs to perform after nodes are repositioned on screen + var afterReposition = function() { + if (options.fit) { + options.cy.fit(options.eles.nodes(), options.padding); + } + + if (!ready) { + ready = true; + self.cy.one('layoutready', options.ready); + self.cy.trigger({type: 'layoutready', layout: self}); + } + }; + + var ticksPerFrame = self.options.refresh; + var isDone; + + for( var i = 0; i < ticksPerFrame && !isDone; i++ ){ + isDone = self.layout.tick(); + } + + // If layout is done + if (isDone) { + if (self.options.tile) { + self.postLayout(); + } + self.options.eles.nodes().positions(getPositions); + + afterReposition(); + + // trigger layoutstop when the layout stops (e.g. finishes) + self.cy.one('layoutstop', self.options.stop); + self.cy.trigger('layoutstop'); + + if (frameId) { + cancelAnimationFrame(frameId); + } + + self.options.eles.nodes().removeScratch('coseBilkent'); + ready = false; + return; + } + + var animationData = self.layout.getPositionsData(); // Get positions of layout nodes note that all nodes may not be layout nodes because of tiling + // Position nodes, for the nodes who are not passed to layout because of tiling return the position of their dummy compound + options.eles.nodes().positions(function (ele, i) { + if (typeof ele === "number") { + ele = i; + } + if (ele.scratch('coseBilkent') && ele.scratch('coseBilkent').dummy_parent_id) { + var dummyParent = ele.scratch('coseBilkent').dummy_parent_id; + return { + x: dummyParent.x, + y: dummyParent.y + }; + } + var theId = ele.data('id'); + var pNode = animationData[theId]; + var temp = ele; + while (pNode == null) { + temp = temp.parent()[0]; + pNode = animationData[temp.id()]; + animationData[theId] = pNode; + } + return { + x: pNode.x, + y: pNode.y + }; + }); + + afterReposition(); + + frameId = requestAnimationFrame(iterateAnimated); + }; + + /* + * Listen 'layoutstarted' event and start animated iteration if animate option is 'during' + */ + layout.addListener('layoutstarted', function () { + if (self.options.animate === 'during') { + frameId = requestAnimationFrame(iterateAnimated); + } + }); + + layout.runLayout(); // Run cose layout + + /* + * If animate option is not 'during' ('end' or false) perform these here (If it is 'during' similar things are already performed) + */ + if(this.options.animate !== 'during'){ + setTimeout(function() { + if (self.options.tile) { + self.postLayout(); + } + self.options.eles.nodes().not(":parent").layoutPositions(self, self.options, getPositions); // Use layout positions to reposition the nodes it considers the options parameter + self.options.eles.nodes().removeScratch('coseBilkent'); + ready = false; + }, 0); + + } + + return this; // chaining +}; + +//Get the top most ones of a list of nodes +_CoSELayout.prototype.getTopMostNodes = function(nodes) { + var nodesMap = {}; + for (var i = 0; i < nodes.length; i++) { + nodesMap[nodes[i].id()] = true; + } + var roots = nodes.filter(function (ele, i) { + if(typeof ele === "number") { + ele = i; + } + var parent = ele.parent()[0]; + while(parent != null){ + if(nodesMap[parent.id()]){ + return false; + } + parent = parent.parent()[0]; + } + return true; + }); + + return roots; +}; + +_CoSELayout.prototype.processChildrenList = function (parent, children, layout) { + var size = children.length; + for (var i = 0; i < size; i++) { + var theChild = children[i]; + this.options.eles.nodes().length; + var children_of_children = theChild.children(); + var theNode; + + if (theChild.width() != null + && theChild.height() != null) { + theNode = parent.add(new CoSENode(layout.graphManager, + new PointD(theChild.position('x'), theChild.position('y')), + new DimensionD(parseFloat(theChild.width()), + parseFloat(theChild.height())))); + } + else { + theNode = parent.add(new CoSENode(this.graphManager)); + } + theNode.id = theChild.data("id"); + this.idToLNode[theChild.data("id")] = theNode; + + if (isNaN(theNode.rect.x)) { + theNode.rect.x = 0; + } + + if (isNaN(theNode.rect.y)) { + theNode.rect.y = 0; + } + + if (children_of_children != null && children_of_children.length > 0) { + var theNewGraph; + theNewGraph = layout.getGraphManager().add(layout.newGraph(), theNode); + this.processChildrenList(theNewGraph, children_of_children, layout); + } + } +}; + +/** + * @brief : called on continuous layouts to stop them before they finish + */ +_CoSELayout.prototype.stop = function () { + this.stopped = true; + + this.trigger('layoutstop'); + + return this; // chaining +}; + +module.exports = function get(cytoscape) { + return _CoSELayout; +}; + +},{"./CoSEConstants":1,"./CoSEEdge":2,"./CoSEGraph":3,"./CoSEGraphManager":4,"./CoSELayout":5,"./CoSENode":6,"./DimensionD":7,"./FDLayout":9,"./FDLayoutConstants":10,"./FDLayoutEdge":11,"./FDLayoutNode":12,"./HashMap":13,"./HashSet":14,"./IGeometry":15,"./IMath":16,"./Integer":17,"./LEdge":18,"./LGraph":19,"./LGraphManager":20,"./LGraphObject":21,"./LNode":22,"./Layout":23,"./LayoutConstants":24,"./Point":25,"./PointD":26,"./RandomSeed":27,"./RectangleD":28,"./TilingExtension":29,"./Transform":30,"./UniqueIDGeneretor":31}],33:[function(require,module,exports){ +'use strict'; + +// registers the extension on a cytoscape lib ref +var getLayout = require('./Layout'); + +var register = function( cytoscape ){ + var Layout = getLayout( cytoscape ); + + cytoscape('layout', 'cose-bilkent', Layout); +}; + +// auto reg for globals +if( typeof cytoscape !== 'undefined' ){ + register( cytoscape ); +} + +module.exports = register; + +},{"./Layout":32}]},{},[33])(33) +}); +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy9icm93c2VyLXBhY2svX3ByZWx1ZGUuanMiLCJzcmMvTGF5b3V0L0NvU0VDb25zdGFudHMuanMiLCJzcmMvTGF5b3V0L0NvU0VFZGdlLmpzIiwic3JjL0xheW91dC9Db1NFR3JhcGguanMiLCJzcmMvTGF5b3V0L0NvU0VHcmFwaE1hbmFnZXIuanMiLCJzcmMvTGF5b3V0L0NvU0VMYXlvdXQuanMiLCJzcmMvTGF5b3V0L0NvU0VOb2RlLmpzIiwic3JjL0xheW91dC9EaW1lbnNpb25ELmpzIiwic3JjL0xheW91dC9FbWl0dGVyLmpzIiwic3JjL0xheW91dC9GRExheW91dC5qcyIsInNyYy9MYXlvdXQvRkRMYXlvdXRDb25zdGFudHMuanMiLCJzcmMvTGF5b3V0L0ZETGF5b3V0RWRnZS5qcyIsInNyYy9MYXlvdXQvRkRMYXlvdXROb2RlLmpzIiwic3JjL0xheW91dC9IYXNoTWFwLmpzIiwic3JjL0xheW91dC9IYXNoU2V0LmpzIiwic3JjL0xheW91dC9JR2VvbWV0cnkuanMiLCJzcmMvTGF5b3V0L0lNYXRoLmpzIiwic3JjL0xheW91dC9JbnRlZ2VyLmpzIiwic3JjL0xheW91dC9MRWRnZS5qcyIsInNyYy9MYXlvdXQvTEdyYXBoLmpzIiwic3JjL0xheW91dC9MR3JhcGhNYW5hZ2VyLmpzIiwic3JjL0xheW91dC9MR3JhcGhPYmplY3QuanMiLCJzcmMvTGF5b3V0L0xOb2RlLmpzIiwic3JjL0xheW91dC9MYXlvdXQuanMiLCJzcmMvTGF5b3V0L0xheW91dENvbnN0YW50cy5qcyIsInNyYy9MYXlvdXQvUG9pbnQuanMiLCJzcmMvTGF5b3V0L1BvaW50RC5qcyIsInNyYy9MYXlvdXQvUmFuZG9tU2VlZC5qcyIsInNyYy9MYXlvdXQvUmVjdGFuZ2xlRC5qcyIsInNyYy9MYXlvdXQvVGlsaW5nRXh0ZW5zaW9uLmpzIiwic3JjL0xheW91dC9UcmFuc2Zvcm0uanMiLCJzcmMvTGF5b3V0L1VuaXF1ZUlER2VuZXJldG9yLmpzIiwic3JjL0xheW91dC9pbmRleC5qcyIsInNyYyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTtBQ0FBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ2ZBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ1pBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ1pBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ1pBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQzViQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUN4SEE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDOUJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDbENBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ2pYQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUM5QkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDZkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDMUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQzlCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3ZEQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDMVpBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQzlCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ1BBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3pKQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3RjQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUN0ZUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ0xBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUM5VkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQy9wQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3BFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3pFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNoREE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ1hBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDbElBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUN2aUJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDN0pBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUM3QkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDcldBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSIsImZpbGUiOiJnZW5lcmF0ZWQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlc0NvbnRlbnQiOlsiKGZ1bmN0aW9uIGUodCxuLHIpe2Z1bmN0aW9uIHMobyx1KXtpZighbltvXSl7aWYoIXRbb10pe3ZhciBhPXR5cGVvZiByZXF1aXJlPT1cImZ1bmN0aW9uXCImJnJlcXVpcmU7aWYoIXUmJmEpcmV0dXJuIGEobywhMCk7aWYoaSlyZXR1cm4gaShvLCEwKTt2YXIgZj1uZXcgRXJyb3IoXCJDYW5ub3QgZmluZCBtb2R1bGUgJ1wiK28rXCInXCIpO3Rocm93IGYuY29kZT1cIk1PRFVMRV9OT1RfRk9VTkRcIixmfXZhciBsPW5bb109e2V4cG9ydHM6e319O3Rbb11bMF0uY2FsbChsLmV4cG9ydHMsZnVuY3Rpb24oZSl7dmFyIG49dFtvXVsxXVtlXTtyZXR1cm4gcyhuP246ZSl9LGwsbC5leHBvcnRzLGUsdCxuLHIpfXJldHVybiBuW29dLmV4cG9ydHN9dmFyIGk9dHlwZW9mIHJlcXVpcmU9PVwiZnVuY3Rpb25cIiYmcmVxdWlyZTtmb3IodmFyIG89MDtvPHIubGVuZ3RoO28rKylzKHJbb10pO3JldHVybiBzfSkiLCJ2YXIgRkRMYXlvdXRDb25zdGFudHMgPSByZXF1aXJlKCcuL0ZETGF5b3V0Q29uc3RhbnRzJyk7XG5cbmZ1bmN0aW9uIENvU0VDb25zdGFudHMoKSB7XG59XG5cbi8vQ29TRUNvbnN0YW50cyBpbmhlcml0cyBzdGF0aWMgcHJvcHMgaW4gRkRMYXlvdXRDb25zdGFudHNcbmZvciAodmFyIHByb3AgaW4gRkRMYXlvdXRDb25zdGFudHMpIHtcbiAgQ29TRUNvbnN0YW50c1twcm9wXSA9IEZETGF5b3V0Q29uc3RhbnRzW3Byb3BdO1xufVxuXG5Db1NFQ29uc3RhbnRzLkRFRkFVTFRfVVNFX01VTFRJX0xFVkVMX1NDQUxJTkcgPSBmYWxzZTtcbkNvU0VDb25zdGFudHMuREVGQVVMVF9SQURJQUxfU0VQQVJBVElPTiA9IEZETGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfRURHRV9MRU5HVEg7XG5Db1NFQ29uc3RhbnRzLkRFRkFVTFRfQ09NUE9ORU5UX1NFUEVSQVRJT04gPSA2MDtcblxubW9kdWxlLmV4cG9ydHMgPSBDb1NFQ29uc3RhbnRzO1xuIiwidmFyIEZETGF5b3V0RWRnZSA9IHJlcXVpcmUoJy4vRkRMYXlvdXRFZGdlJyk7XG5cbmZ1bmN0aW9uIENvU0VFZGdlKHNvdXJjZSwgdGFyZ2V0LCB2RWRnZSkge1xuICBGRExheW91dEVkZ2UuY2FsbCh0aGlzLCBzb3VyY2UsIHRhcmdldCwgdkVkZ2UpO1xufVxuXG5Db1NFRWRnZS5wcm90b3R5cGUgPSBPYmplY3QuY3JlYXRlKEZETGF5b3V0RWRnZS5wcm90b3R5cGUpO1xuZm9yICh2YXIgcHJvcCBpbiBGRExheW91dEVkZ2UpIHtcbiAgQ29TRUVkZ2VbcHJvcF0gPSBGRExheW91dEVkZ2VbcHJvcF07XG59XG5cbm1vZHVsZS5leHBvcnRzID0gQ29TRUVkZ2VcbiIsInZhciBMR3JhcGggPSByZXF1aXJlKCcuL0xHcmFwaCcpO1xuXG5mdW5jdGlvbiBDb1NFR3JhcGgocGFyZW50LCBncmFwaE1nciwgdkdyYXBoKSB7XG4gIExHcmFwaC5jYWxsKHRoaXMsIHBhcmVudCwgZ3JhcGhNZ3IsIHZHcmFwaCk7XG59XG5cbkNvU0VHcmFwaC5wcm90b3R5cGUgPSBPYmplY3QuY3JlYXRlKExHcmFwaC5wcm90b3R5cGUpO1xuZm9yICh2YXIgcHJvcCBpbiBMR3JhcGgpIHtcbiAgQ29TRUdyYXBoW3Byb3BdID0gTEdyYXBoW3Byb3BdO1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IENvU0VHcmFwaDtcbiIsInZhciBMR3JhcGhNYW5hZ2VyID0gcmVxdWlyZSgnLi9MR3JhcGhNYW5hZ2VyJyk7XG5cbmZ1bmN0aW9uIENvU0VHcmFwaE1hbmFnZXIobGF5b3V0KSB7XG4gIExHcmFwaE1hbmFnZXIuY2FsbCh0aGlzLCBsYXlvdXQpO1xufVxuXG5Db1NFR3JhcGhNYW5hZ2VyLnByb3RvdHlwZSA9IE9iamVjdC5jcmVhdGUoTEdyYXBoTWFuYWdlci5wcm90b3R5cGUpO1xuZm9yICh2YXIgcHJvcCBpbiBMR3JhcGhNYW5hZ2VyKSB7XG4gIENvU0VHcmFwaE1hbmFnZXJbcHJvcF0gPSBMR3JhcGhNYW5hZ2VyW3Byb3BdO1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IENvU0VHcmFwaE1hbmFnZXI7XG4iLCJ2YXIgRkRMYXlvdXQgPSByZXF1aXJlKCcuL0ZETGF5b3V0Jyk7XG52YXIgQ29TRUdyYXBoTWFuYWdlciA9IHJlcXVpcmUoJy4vQ29TRUdyYXBoTWFuYWdlcicpO1xudmFyIENvU0VHcmFwaCA9IHJlcXVpcmUoJy4vQ29TRUdyYXBoJyk7XG52YXIgQ29TRU5vZGUgPSByZXF1aXJlKCcuL0NvU0VOb2RlJyk7XG52YXIgQ29TRUVkZ2UgPSByZXF1aXJlKCcuL0NvU0VFZGdlJyk7XG52YXIgQ29TRUNvbnN0YW50cyA9IHJlcXVpcmUoJy4vQ29TRUNvbnN0YW50cycpO1xudmFyIEZETGF5b3V0Q29uc3RhbnRzID0gcmVxdWlyZSgnLi9GRExheW91dENvbnN0YW50cycpO1xudmFyIExheW91dENvbnN0YW50cyA9IHJlcXVpcmUoJy4vTGF5b3V0Q29uc3RhbnRzJyk7XG52YXIgUG9pbnQgPSByZXF1aXJlKCcuL1BvaW50Jyk7XG52YXIgUG9pbnREID0gcmVxdWlyZSgnLi9Qb2ludEQnKTtcbnZhciBMYXlvdXQgPSByZXF1aXJlKCcuL0xheW91dCcpO1xudmFyIEludGVnZXIgPSByZXF1aXJlKCcuL0ludGVnZXInKTtcbnZhciBJR2VvbWV0cnkgPSByZXF1aXJlKCcuL0lHZW9tZXRyeScpO1xudmFyIExHcmFwaCA9IHJlcXVpcmUoJy4vTEdyYXBoJyk7XG52YXIgVHJhbnNmb3JtID0gcmVxdWlyZSgnLi9UcmFuc2Zvcm0nKTtcblxuZnVuY3Rpb24gQ29TRUxheW91dCgpIHtcbiAgRkRMYXlvdXQuY2FsbCh0aGlzKTtcbn1cblxuQ29TRUxheW91dC5wcm90b3R5cGUgPSBPYmplY3QuY3JlYXRlKEZETGF5b3V0LnByb3RvdHlwZSk7XG5cbmZvciAodmFyIHByb3AgaW4gRkRMYXlvdXQpIHtcbiAgQ29TRUxheW91dFtwcm9wXSA9IEZETGF5b3V0W3Byb3BdO1xufVxuXG5Db1NFTGF5b3V0LnByb3RvdHlwZS5uZXdHcmFwaE1hbmFnZXIgPSBmdW5jdGlvbiAoKSB7XG4gIHZhciBnbSA9IG5ldyBDb1NFR3JhcGhNYW5hZ2VyKHRoaXMpO1xuICB0aGlzLmdyYXBoTWFuYWdlciA9IGdtO1xuICByZXR1cm4gZ207XG59O1xuXG5Db1NFTGF5b3V0LnByb3RvdHlwZS5uZXdHcmFwaCA9IGZ1bmN0aW9uICh2R3JhcGgpIHtcbiAgcmV0dXJuIG5ldyBDb1NFR3JhcGgobnVsbCwgdGhpcy5ncmFwaE1hbmFnZXIsIHZHcmFwaCk7XG59O1xuXG5Db1NFTGF5b3V0LnByb3RvdHlwZS5uZXdOb2RlID0gZnVuY3Rpb24gKHZOb2RlKSB7XG4gIHJldHVybiBuZXcgQ29TRU5vZGUodGhpcy5ncmFwaE1hbmFnZXIsIHZOb2RlKTtcbn07XG5cbkNvU0VMYXlvdXQucHJvdG90eXBlLm5ld0VkZ2UgPSBmdW5jdGlvbiAodkVkZ2UpIHtcbiAgcmV0dXJuIG5ldyBDb1NFRWRnZShudWxsLCBudWxsLCB2RWRnZSk7XG59O1xuXG5Db1NFTGF5b3V0LnByb3RvdHlwZS5pbml0UGFyYW1ldGVycyA9IGZ1bmN0aW9uICgpIHtcbiAgRkRMYXlvdXQucHJvdG90eXBlLmluaXRQYXJhbWV0ZXJzLmNhbGwodGhpcywgYXJndW1lbnRzKTtcbiAgaWYgKCF0aGlzLmlzU3ViTGF5b3V0KSB7XG4gICAgaWYgKENvU0VDb25zdGFudHMuREVGQVVMVF9FREdFX0xFTkdUSCA8IDEwKVxuICAgIHtcbiAgICAgIHRoaXMuaWRlYWxFZGdlTGVuZ3RoID0gMTA7XG4gICAgfVxuICAgIGVsc2VcbiAgICB7XG4gICAgICB0aGlzLmlkZWFsRWRnZUxlbmd0aCA9IENvU0VDb25zdGFudHMuREVGQVVMVF9FREdFX0xFTkdUSDtcbiAgICB9XG5cbiAgICB0aGlzLnVzZVNtYXJ0SWRlYWxFZGdlTGVuZ3RoQ2FsY3VsYXRpb24gPVxuICAgICAgICAgICAgQ29TRUNvbnN0YW50cy5ERUZBVUxUX1VTRV9TTUFSVF9JREVBTF9FREdFX0xFTkdUSF9DQUxDVUxBVElPTjtcbiAgICB0aGlzLnNwcmluZ0NvbnN0YW50ID1cbiAgICAgICAgICAgIEZETGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfU1BSSU5HX1NUUkVOR1RIO1xuICAgIHRoaXMucmVwdWxzaW9uQ29uc3RhbnQgPVxuICAgICAgICAgICAgRkRMYXlvdXRDb25zdGFudHMuREVGQVVMVF9SRVBVTFNJT05fU1RSRU5HVEg7XG4gICAgdGhpcy5ncmF2aXR5Q29uc3RhbnQgPVxuICAgICAgICAgICAgRkRMYXlvdXRDb25zdGFudHMuREVGQVVMVF9HUkFWSVRZX1NUUkVOR1RIO1xuICAgIHRoaXMuY29tcG91bmRHcmF2aXR5Q29uc3RhbnQgPVxuICAgICAgICAgICAgRkRMYXlvdXRDb25zdGFudHMuREVGQVVMVF9DT01QT1VORF9HUkFWSVRZX1NUUkVOR1RIO1xuICAgIHRoaXMuZ3Jhdml0eVJhbmdlRmFjdG9yID1cbiAgICAgICAgICAgIEZETGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfR1JBVklUWV9SQU5HRV9GQUNUT1I7XG4gICAgdGhpcy5jb21wb3VuZEdyYXZpdHlSYW5nZUZhY3RvciA9XG4gICAgICAgICAgICBGRExheW91dENvbnN0YW50cy5ERUZBVUxUX0NPTVBPVU5EX0dSQVZJVFlfUkFOR0VfRkFDVE9SO1xuICB9XG59O1xuXG5Db1NFTGF5b3V0LnByb3RvdHlwZS5sYXlvdXQgPSBmdW5jdGlvbiAoKSB7XG4gIHZhciBjcmVhdGVCZW5kc0FzTmVlZGVkID0gTGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfQ1JFQVRFX0JFTkRTX0FTX05FRURFRDtcbiAgaWYgKGNyZWF0ZUJlbmRzQXNOZWVkZWQpXG4gIHtcbiAgICB0aGlzLmNyZWF0ZUJlbmRwb2ludHMoKTtcbiAgICB0aGlzLmdyYXBoTWFuYWdlci5yZXNldEFsbEVkZ2VzKCk7XG4gIH1cblxuICB0aGlzLmxldmVsID0gMDtcbiAgcmV0dXJuIHRoaXMuY2xhc3NpY0xheW91dCgpO1xufTtcblxuQ29TRUxheW91dC5wcm90b3R5cGUuY2xhc3NpY0xheW91dCA9IGZ1bmN0aW9uICgpIHtcbiAgdGhpcy5jYWxjdWxhdGVOb2Rlc1RvQXBwbHlHcmF2aXRhdGlvblRvKCk7XG4gIHRoaXMuZ3JhcGhNYW5hZ2VyLmNhbGNMb3dlc3RDb21tb25BbmNlc3RvcnMoKTtcbiAgdGhpcy5ncmFwaE1hbmFnZXIuY2FsY0luY2x1c2lvblRyZWVEZXB0aHMoKTtcbiAgdGhpcy5ncmFwaE1hbmFnZXIuZ2V0Um9vdCgpLmNhbGNFc3RpbWF0ZWRTaXplKCk7XG4gIHRoaXMuY2FsY0lkZWFsRWRnZUxlbmd0aHMoKTtcbiAgaWYgKCF0aGlzLmluY3JlbWVudGFsKVxuICB7XG4gICAgdmFyIGZvcmVzdCA9IHRoaXMuZ2V0RmxhdEZvcmVzdCgpO1xuXG4gICAgLy8gVGhlIGdyYXBoIGFzc29jaWF0ZWQgd2l0aCB0aGlzIGxheW91dCBpcyBmbGF0IGFuZCBhIGZvcmVzdFxuICAgIGlmIChmb3Jlc3QubGVuZ3RoID4gMClcblxuICAgIHtcbiAgICAgIHRoaXMucG9zaXRpb25Ob2Rlc1JhZGlhbGx5KGZvcmVzdCk7XG4gICAgfVxuICAgIC8vIFRoZSBncmFwaCBhc3NvY2lhdGVkIHdpdGggdGhpcyBsYXlvdXQgaXMgbm90IGZsYXQgb3IgYSBmb3Jlc3RcbiAgICBlbHNlXG4gICAge1xuICAgICAgdGhpcy5wb3NpdGlvbk5vZGVzUmFuZG9tbHkoKTtcbiAgICB9XG4gIH1cblxuICB0aGlzLmluaXRTcHJpbmdFbWJlZGRlcigpO1xuICB0aGlzLnJ1blNwcmluZ0VtYmVkZGVyKCk7XG5cbiAgcmV0dXJuIHRydWU7XG59O1xuXG5Db1NFTGF5b3V0LnByb3RvdHlwZS50aWNrID0gZnVuY3Rpb24oKSB7XG4gIHRoaXMudG90YWxJdGVyYXRpb25zKys7XG4gIFxuICBpZiAodGhpcy50b3RhbEl0ZXJhdGlvbnMgPT09IHRoaXMubWF4SXRlcmF0aW9ucykge1xuICAgIHJldHVybiB0cnVlOyAvLyBMYXlvdXQgaXMgbm90IGVuZGVkIHJldHVybiB0cnVlXG4gIH1cbiAgXG4gIGlmICh0aGlzLnRvdGFsSXRlcmF0aW9ucyAlIEZETGF5b3V0Q29uc3RhbnRzLkNPTlZFUkdFTkNFX0NIRUNLX1BFUklPRCA9PSAwKVxuICB7XG4gICAgaWYgKHRoaXMuaXNDb252ZXJnZWQoKSlcbiAgICB7XG4gICAgICByZXR1cm4gdHJ1ZTsgLy8gTGF5b3V0IGlzIG5vdCBlbmRlZCByZXR1cm4gdHJ1ZVxuICAgIH1cblxuICAgIHRoaXMuY29vbGluZ0ZhY3RvciA9IHRoaXMuaW5pdGlhbENvb2xpbmdGYWN0b3IgKlxuICAgICAgICAgICAgKCh0aGlzLm1heEl0ZXJhdGlvbnMgLSB0aGlzLnRvdGFsSXRlcmF0aW9ucykgLyB0aGlzLm1heEl0ZXJhdGlvbnMpO1xuICAgIHRoaXMuYW5pbWF0aW9uUGVyaW9kID0gTWF0aC5jZWlsKHRoaXMuaW5pdGlhbEFuaW1hdGlvblBlcmlvZCAqIE1hdGguc3FydCh0aGlzLmNvb2xpbmdGYWN0b3IpKTtcblxuICB9XG4gIHRoaXMudG90YWxEaXNwbGFjZW1lbnQgPSAwO1xuICB0aGlzLmdyYXBoTWFuYWdlci51cGRhdGVCb3VuZHMoKTtcbiAgdGhpcy5jYWxjU3ByaW5nRm9yY2VzKCk7XG4gIHRoaXMuY2FsY1JlcHVsc2lvbkZvcmNlcygpO1xuICB0aGlzLmNhbGNHcmF2aXRhdGlvbmFsRm9yY2VzKCk7XG4gIHRoaXMubW92ZU5vZGVzKCk7XG4gIHRoaXMuYW5pbWF0ZSgpO1xuICBcbiAgcmV0dXJuIGZhbHNlOyAvLyBMYXlvdXQgaXMgbm90IGVuZGVkIHlldCByZXR1cm4gZmFsc2Vcbn07XG5cbkNvU0VMYXlvdXQucHJvdG90eXBlLmdldFBvc2l0aW9uc0RhdGEgPSBmdW5jdGlvbigpIHtcbiAgdmFyIGFsbE5vZGVzID0gdGhpcy5ncmFwaE1hbmFnZXIuZ2V0QWxsTm9kZXMoKTtcbiAgdmFyIHBEYXRhID0ge307XG4gIGZvciAodmFyIGkgPSAwOyBpIDwgYWxsTm9kZXMubGVuZ3RoOyBpKyspIHtcbiAgICB2YXIgcmVjdCA9IGFsbE5vZGVzW2ldLnJlY3Q7XG4gICAgdmFyIGlkID0gYWxsTm9kZXNbaV0uaWQ7XG4gICAgcERhdGFbaWRdID0ge1xuICAgICAgaWQ6IGlkLFxuICAgICAgeDogcmVjdC5nZXRDZW50ZXJYKCksXG4gICAgICB5OiByZWN0LmdldENlbnRlclkoKSxcbiAgICAgIHc6IHJlY3Qud2lkdGgsXG4gICAgICBoOiByZWN0LmhlaWdodFxuICAgIH07XG4gIH1cbiAgXG4gIHJldHVybiBwRGF0YTtcbn07XG5cbkNvU0VMYXlvdXQucHJvdG90eXBlLnJ1blNwcmluZ0VtYmVkZGVyID0gZnVuY3Rpb24gKCkge1xuICB0aGlzLmluaXRpYWxBbmltYXRpb25QZXJpb2QgPSAyNTtcbiAgdGhpcy5hbmltYXRpb25QZXJpb2QgPSB0aGlzLmluaXRpYWxBbmltYXRpb25QZXJpb2Q7XG4gIHZhciBsYXlvdXRFbmRlZCA9IGZhbHNlO1xuICBcbiAgLy8gSWYgYW1pbmF0ZSBvcHRpb24gaXMgJ2R1cmluZycgc2lnbmFsIHRoYXQgbGF5b3V0IGlzIHN1cHBvc2VkIHRvIHN0YXJ0IGl0ZXJhdGluZ1xuICBpZiAoIEZETGF5b3V0Q29uc3RhbnRzLkFOSU1BVEUgPT09ICdkdXJpbmcnICkge1xuICAgIHRoaXMuZW1pdCgnbGF5b3V0c3RhcnRlZCcpO1xuICB9XG4gIGVsc2Uge1xuICAgIC8vIElmIGFtaW5hdGUgb3B0aW9uIGlzICdkdXJpbmcnIHRpY2soKSBmdW5jdGlvbiB3aWxsIGJlIGNhbGxlZCBvbiBpbmRleC5qc1xuICAgIHdoaWxlICghbGF5b3V0RW5kZWQpIHtcbiAgICAgIGxheW91dEVuZGVkID0gdGhpcy50aWNrKCk7XG4gICAgfVxuXG4gICAgdGhpcy5ncmFwaE1hbmFnZXIudXBkYXRlQm91bmRzKCk7XG4gIH1cbn07XG5cbkNvU0VMYXlvdXQucHJvdG90eXBlLmNhbGN1bGF0ZU5vZGVzVG9BcHBseUdyYXZpdGF0aW9uVG8gPSBmdW5jdGlvbiAoKSB7XG4gIHZhciBub2RlTGlzdCA9IFtdO1xuICB2YXIgZ3JhcGg7XG5cbiAgdmFyIGdyYXBocyA9IHRoaXMuZ3JhcGhNYW5hZ2VyLmdldEdyYXBocygpO1xuICB2YXIgc2l6ZSA9IGdyYXBocy5sZW5ndGg7XG4gIHZhciBpO1xuICBmb3IgKGkgPSAwOyBpIDwgc2l6ZTsgaSsrKVxuICB7XG4gICAgZ3JhcGggPSBncmFwaHNbaV07XG5cbiAgICBncmFwaC51cGRhdGVDb25uZWN0ZWQoKTtcblxuICAgIGlmICghZ3JhcGguaXNDb25uZWN0ZWQpXG4gICAge1xuICAgICAgbm9kZUxpc3QgPSBub2RlTGlzdC5jb25jYXQoZ3JhcGguZ2V0Tm9kZXMoKSk7XG4gICAgfVxuICB9XG5cbiAgdGhpcy5ncmFwaE1hbmFnZXIuc2V0QWxsTm9kZXNUb0FwcGx5R3Jhdml0YXRpb24obm9kZUxpc3QpO1xufTtcblxuQ29TRUxheW91dC5wcm90b3R5cGUuY3JlYXRlQmVuZHBvaW50cyA9IGZ1bmN0aW9uICgpIHtcbiAgdmFyIGVkZ2VzID0gW107XG4gIGVkZ2VzID0gZWRnZXMuY29uY2F0KHRoaXMuZ3JhcGhNYW5hZ2VyLmdldEFsbEVkZ2VzKCkpO1xuICB2YXIgdmlzaXRlZCA9IG5ldyBIYXNoU2V0KCk7XG4gIHZhciBpO1xuICBmb3IgKGkgPSAwOyBpIDwgZWRnZXMubGVuZ3RoOyBpKyspXG4gIHtcbiAgICB2YXIgZWRnZSA9IGVkZ2VzW2ldO1xuXG4gICAgaWYgKCF2aXNpdGVkLmNvbnRhaW5zKGVkZ2UpKVxuICAgIHtcbiAgICAgIHZhciBzb3VyY2UgPSBlZGdlLmdldFNvdXJjZSgpO1xuICAgICAgdmFyIHRhcmdldCA9IGVkZ2UuZ2V0VGFyZ2V0KCk7XG5cbiAgICAgIGlmIChzb3VyY2UgPT0gdGFyZ2V0KVxuICAgICAge1xuICAgICAgICBlZGdlLmdldEJlbmRwb2ludHMoKS5wdXNoKG5ldyBQb2ludEQoKSk7XG4gICAgICAgIGVkZ2UuZ2V0QmVuZHBvaW50cygpLnB1c2gobmV3IFBvaW50RCgpKTtcbiAgICAgICAgdGhpcy5jcmVhdGVEdW1teU5vZGVzRm9yQmVuZHBvaW50cyhlZGdlKTtcbiAgICAgICAgdmlzaXRlZC5hZGQoZWRnZSk7XG4gICAgICB9XG4gICAgICBlbHNlXG4gICAgICB7XG4gICAgICAgIHZhciBlZGdlTGlzdCA9IFtdO1xuXG4gICAgICAgIGVkZ2VMaXN0ID0gZWRnZUxpc3QuY29uY2F0KHNvdXJjZS5nZXRFZGdlTGlzdFRvTm9kZSh0YXJnZXQpKTtcbiAgICAgICAgZWRnZUxpc3QgPSBlZGdlTGlzdC5jb25jYXQodGFyZ2V0LmdldEVkZ2VMaXN0VG9Ob2RlKHNvdXJjZSkpO1xuXG4gICAgICAgIGlmICghdmlzaXRlZC5jb250YWlucyhlZGdlTGlzdFswXSkpXG4gICAgICAgIHtcbiAgICAgICAgICBpZiAoZWRnZUxpc3QubGVuZ3RoID4gMSlcbiAgICAgICAgICB7XG4gICAgICAgICAgICB2YXIgaztcbiAgICAgICAgICAgIGZvciAoayA9IDA7IGsgPCBlZGdlTGlzdC5sZW5ndGg7IGsrKylcbiAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgdmFyIG11bHRpRWRnZSA9IGVkZ2VMaXN0W2tdO1xuICAgICAgICAgICAgICBtdWx0aUVkZ2UuZ2V0QmVuZHBvaW50cygpLnB1c2gobmV3IFBvaW50RCgpKTtcbiAgICAgICAgICAgICAgdGhpcy5jcmVhdGVEdW1teU5vZGVzRm9yQmVuZHBvaW50cyhtdWx0aUVkZ2UpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgICB2aXNpdGVkLmFkZEFsbChsaXN0KTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cblxuICAgIGlmICh2aXNpdGVkLnNpemUoKSA9PSBlZGdlcy5sZW5ndGgpXG4gICAge1xuICAgICAgYnJlYWs7XG4gICAgfVxuICB9XG59O1xuXG5Db1NFTGF5b3V0LnByb3RvdHlwZS5wb3NpdGlvbk5vZGVzUmFkaWFsbHkgPSBmdW5jdGlvbiAoZm9yZXN0KSB7XG4gIC8vIFdlIHRpbGUgdGhlIHRyZWVzIHRvIGEgZ3JpZCByb3cgYnkgcm93OyBmaXJzdCB0cmVlIHN0YXJ0cyBhdCAoMCwwKVxuICB2YXIgY3VycmVudFN0YXJ0aW5nUG9pbnQgPSBuZXcgUG9pbnQoMCwgMCk7XG4gIHZhciBudW1iZXJPZkNvbHVtbnMgPSBNYXRoLmNlaWwoTWF0aC5zcXJ0KGZvcmVzdC5sZW5ndGgpKTtcbiAgdmFyIGhlaWdodCA9IDA7XG4gIHZhciBjdXJyZW50WSA9IDA7XG4gIHZhciBjdXJyZW50WCA9IDA7XG4gIHZhciBwb2ludCA9IG5ldyBQb2ludEQoMCwgMCk7XG5cbiAgZm9yICh2YXIgaSA9IDA7IGkgPCBmb3Jlc3QubGVuZ3RoOyBpKyspXG4gIHtcbiAgICBpZiAoaSAlIG51bWJlck9mQ29sdW1ucyA9PSAwKVxuICAgIHtcbiAgICAgIC8vIFN0YXJ0IG9mIGEgbmV3IHJvdywgbWFrZSB0aGUgeCBjb29yZGluYXRlIDAsIGluY3JlbWVudCB0aGVcbiAgICAgIC8vIHkgY29vcmRpbmF0ZSB3aXRoIHRoZSBtYXggaGVpZ2h0IG9mIHRoZSBwcmV2aW91cyByb3dcbiAgICAgIGN1cnJlbnRYID0gMDtcbiAgICAgIGN1cnJlbnRZID0gaGVpZ2h0O1xuXG4gICAgICBpZiAoaSAhPSAwKVxuICAgICAge1xuICAgICAgICBjdXJyZW50WSArPSBDb1NFQ29uc3RhbnRzLkRFRkFVTFRfQ09NUE9ORU5UX1NFUEVSQVRJT047XG4gICAgICB9XG5cbiAgICAgIGhlaWdodCA9IDA7XG4gICAgfVxuXG4gICAgdmFyIHRyZWUgPSBmb3Jlc3RbaV07XG5cbiAgICAvLyBGaW5kIHRoZSBjZW50ZXIgb2YgdGhlIHRyZWVcbiAgICB2YXIgY2VudGVyTm9kZSA9IExheW91dC5maW5kQ2VudGVyT2ZUcmVlKHRyZWUpO1xuXG4gICAgLy8gU2V0IHRoZSBzdGFyaW5nIHBvaW50IG9mIHRoZSBuZXh0IHRyZWVcbiAgICBjdXJyZW50U3RhcnRpbmdQb2ludC54ID0gY3VycmVudFg7XG4gICAgY3VycmVudFN0YXJ0aW5nUG9pbnQueSA9IGN1cnJlbnRZO1xuXG4gICAgLy8gRG8gYSByYWRpYWwgbGF5b3V0IHN0YXJ0aW5nIHdpdGggdGhlIGNlbnRlclxuICAgIHBvaW50ID1cbiAgICAgICAgICAgIENvU0VMYXlvdXQucmFkaWFsTGF5b3V0KHRyZWUsIGNlbnRlck5vZGUsIGN1cnJlbnRTdGFydGluZ1BvaW50KTtcblxuICAgIGlmIChwb2ludC55ID4gaGVpZ2h0KVxuICAgIHtcbiAgICAgIGhlaWdodCA9IE1hdGguZmxvb3IocG9pbnQueSk7XG4gICAgfVxuXG4gICAgY3VycmVudFggPSBNYXRoLmZsb29yKHBvaW50LnggKyBDb1NFQ29uc3RhbnRzLkRFRkFVTFRfQ09NUE9ORU5UX1NFUEVSQVRJT04pO1xuICB9XG5cbiAgdGhpcy50cmFuc2Zvcm0oXG4gICAgICAgICAgbmV3IFBvaW50RChMYXlvdXRDb25zdGFudHMuV09STERfQ0VOVEVSX1ggLSBwb2ludC54IC8gMixcbiAgICAgICAgICAgICAgICAgIExheW91dENvbnN0YW50cy5XT1JMRF9DRU5URVJfWSAtIHBvaW50LnkgLyAyKSk7XG59O1xuXG5Db1NFTGF5b3V0LnJhZGlhbExheW91dCA9IGZ1bmN0aW9uICh0cmVlLCBjZW50ZXJOb2RlLCBzdGFydGluZ1BvaW50KSB7XG4gIHZhciByYWRpYWxTZXAgPSBNYXRoLm1heCh0aGlzLm1heERpYWdvbmFsSW5UcmVlKHRyZWUpLFxuICAgICAgICAgIENvU0VDb25zdGFudHMuREVGQVVMVF9SQURJQUxfU0VQQVJBVElPTik7XG4gIENvU0VMYXlvdXQuYnJhbmNoUmFkaWFsTGF5b3V0KGNlbnRlck5vZGUsIG51bGwsIDAsIDM1OSwgMCwgcmFkaWFsU2VwKTtcbiAgdmFyIGJvdW5kcyA9IExHcmFwaC5jYWxjdWxhdGVCb3VuZHModHJlZSk7XG5cbiAgdmFyIHRyYW5zZm9ybSA9IG5ldyBUcmFuc2Zvcm0oKTtcbiAgdHJhbnNmb3JtLnNldERldmljZU9yZ1goYm91bmRzLmdldE1pblgoKSk7XG4gIHRyYW5zZm9ybS5zZXREZXZpY2VPcmdZKGJvdW5kcy5nZXRNaW5ZKCkpO1xuICB0cmFuc2Zvcm0uc2V0V29ybGRPcmdYKHN0YXJ0aW5nUG9pbnQueCk7XG4gIHRyYW5zZm9ybS5zZXRXb3JsZE9yZ1koc3RhcnRpbmdQb2ludC55KTtcblxuICBmb3IgKHZhciBpID0gMDsgaSA8IHRyZWUubGVuZ3RoOyBpKyspXG4gIHtcbiAgICB2YXIgbm9kZSA9IHRyZWVbaV07XG4gICAgbm9kZS50cmFuc2Zvcm0odHJhbnNmb3JtKTtcbiAgfVxuXG4gIHZhciBib3R0b21SaWdodCA9XG4gICAgICAgICAgbmV3IFBvaW50RChib3VuZHMuZ2V0TWF4WCgpLCBib3VuZHMuZ2V0TWF4WSgpKTtcblxuICByZXR1cm4gdHJhbnNmb3JtLmludmVyc2VUcmFuc2Zvcm1Qb2ludChib3R0b21SaWdodCk7XG59O1xuXG5Db1NFTGF5b3V0LmJyYW5jaFJhZGlhbExheW91dCA9IGZ1bmN0aW9uIChub2RlLCBwYXJlbnRPZk5vZGUsIHN0YXJ0QW5nbGUsIGVuZEFuZ2xlLCBkaXN0YW5jZSwgcmFkaWFsU2VwYXJhdGlvbikge1xuICAvLyBGaXJzdCwgcG9zaXRpb24gdGhpcyBub2RlIGJ5IGZpbmRpbmcgaXRzIGFuZ2xlLlxuICB2YXIgaGFsZkludGVydmFsID0gKChlbmRBbmdsZSAtIHN0YXJ0QW5nbGUpICsgMSkgLyAyO1xuXG4gIGlmIChoYWxmSW50ZXJ2YWwgPCAwKVxuICB7XG4gICAgaGFsZkludGVydmFsICs9IDE4MDtcbiAgfVxuXG4gIHZhciBub2RlQW5nbGUgPSAoaGFsZkludGVydmFsICsgc3RhcnRBbmdsZSkgJSAzNjA7XG4gIHZhciB0ZXRhID0gKG5vZGVBbmdsZSAqIElHZW9tZXRyeS5UV09fUEkpIC8gMzYwO1xuXG4gIC8vIE1ha2UgcG9sYXIgdG8gamF2YSBjb3JkaW5hdGUgY29udmVyc2lvbi5cbiAgdmFyIGNvc190ZXRhID0gTWF0aC5jb3ModGV0YSk7XG4gIHZhciB4XyA9IGRpc3RhbmNlICogTWF0aC5jb3ModGV0YSk7XG4gIHZhciB5XyA9IGRpc3RhbmNlICogTWF0aC5zaW4odGV0YSk7XG5cbiAgbm9kZS5zZXRDZW50ZXIoeF8sIHlfKTtcblxuICAvLyBUcmF2ZXJzZSBhbGwgbmVpZ2hib3JzIG9mIHRoaXMgbm9kZSBhbmQgcmVjdXJzaXZlbHkgY2FsbCB0aGlzXG4gIC8vIGZ1bmN0aW9uLlxuICB2YXIgbmVpZ2hib3JFZGdlcyA9IFtdO1xuICBuZWlnaGJvckVkZ2VzID0gbmVpZ2hib3JFZGdlcy5jb25jYXQobm9kZS5nZXRFZGdlcygpKTtcbiAgdmFyIGNoaWxkQ291bnQgPSBuZWlnaGJvckVkZ2VzLmxlbmd0aDtcblxuICBpZiAocGFyZW50T2ZOb2RlICE9IG51bGwpXG4gIHtcbiAgICBjaGlsZENvdW50LS07XG4gIH1cblxuICB2YXIgYnJhbmNoQ291bnQgPSAwO1xuXG4gIHZhciBpbmNFZGdlc0NvdW50ID0gbmVpZ2hib3JFZGdlcy5sZW5ndGg7XG4gIHZhciBzdGFydEluZGV4O1xuXG4gIHZhciBlZGdlcyA9IG5vZGUuZ2V0RWRnZXNCZXR3ZWVuKHBhcmVudE9mTm9kZSk7XG5cbiAgLy8gSWYgdGhlcmUgYXJlIG11bHRpcGxlIGVkZ2VzLCBwcnVuZSB0aGVtIHVudGlsIHRoZXJlIHJlbWFpbnMgb25seSBvbmVcbiAgLy8gZWRnZS5cbiAgd2hpbGUgKGVkZ2VzLmxlbmd0aCA+IDEpXG4gIHtcbiAgICAvL25laWdoYm9yRWRnZXMucmVtb3ZlKGVkZ2VzLnJlbW92ZSgwKSk7XG4gICAgdmFyIHRlbXAgPSBlZGdlc1swXTtcbiAgICBlZGdlcy5zcGxpY2UoMCwgMSk7XG4gICAgdmFyIGluZGV4ID0gbmVpZ2hib3JFZGdlcy5pbmRleE9mKHRlbXApO1xuICAgIGlmIChpbmRleCA+PSAwKSB7XG4gICAgICBuZWlnaGJvckVkZ2VzLnNwbGljZShpbmRleCwgMSk7XG4gICAgfVxuICAgIGluY0VkZ2VzQ291bnQtLTtcbiAgICBjaGlsZENvdW50LS07XG4gIH1cblxuICBpZiAocGFyZW50T2ZOb2RlICE9IG51bGwpXG4gIHtcbiAgICAvL2Fzc2VydCBlZGdlcy5sZW5ndGggPT0gMTtcbiAgICBzdGFydEluZGV4ID0gKG5laWdoYm9yRWRnZXMuaW5kZXhPZihlZGdlc1swXSkgKyAxKSAlIGluY0VkZ2VzQ291bnQ7XG4gIH1cbiAgZWxzZVxuICB7XG4gICAgc3RhcnRJbmRleCA9IDA7XG4gIH1cblxuICB2YXIgc3RlcEFuZ2xlID0gTWF0aC5hYnMoZW5kQW5nbGUgLSBzdGFydEFuZ2xlKSAvIGNoaWxkQ291bnQ7XG5cbiAgZm9yICh2YXIgaSA9IHN0YXJ0SW5kZXg7XG4gICAgICAgICAgYnJhbmNoQ291bnQgIT0gY2hpbGRDb3VudDtcbiAgICAgICAgICBpID0gKCsraSkgJSBpbmNFZGdlc0NvdW50KVxuICB7XG4gICAgdmFyIGN1cnJlbnROZWlnaGJvciA9XG4gICAgICAgICAgICBuZWlnaGJvckVkZ2VzW2ldLmdldE90aGVyRW5kKG5vZGUpO1xuXG4gICAgLy8gRG9uJ3QgYmFjayB0cmF2ZXJzZSB0byByb290IG5vZGUgaW4gY3VycmVudCB0cmVlLlxuICAgIGlmIChjdXJyZW50TmVpZ2hib3IgPT0gcGFyZW50T2ZOb2RlKVxuICAgIHtcbiAgICAgIGNvbnRpbnVlO1xuICAgIH1cblxuICAgIHZhciBjaGlsZFN0YXJ0QW5nbGUgPVxuICAgICAgICAgICAgKHN0YXJ0QW5nbGUgKyBicmFuY2hDb3VudCAqIHN0ZXBBbmdsZSkgJSAzNjA7XG4gICAgdmFyIGNoaWxkRW5kQW5nbGUgPSAoY2hpbGRTdGFydEFuZ2xlICsgc3RlcEFuZ2xlKSAlIDM2MDtcblxuICAgIENvU0VMYXlvdXQuYnJhbmNoUmFkaWFsTGF5b3V0KGN1cnJlbnROZWlnaGJvcixcbiAgICAgICAgICAgIG5vZGUsXG4gICAgICAgICAgICBjaGlsZFN0YXJ0QW5nbGUsIGNoaWxkRW5kQW5nbGUsXG4gICAgICAgICAgICBkaXN0YW5jZSArIHJhZGlhbFNlcGFyYXRpb24sIHJhZGlhbFNlcGFyYXRpb24pO1xuXG4gICAgYnJhbmNoQ291bnQrKztcbiAgfVxufTtcblxuQ29TRUxheW91dC5tYXhEaWFnb25hbEluVHJlZSA9IGZ1bmN0aW9uICh0cmVlKSB7XG4gIHZhciBtYXhEaWFnb25hbCA9IEludGVnZXIuTUlOX1ZBTFVFO1xuXG4gIGZvciAodmFyIGkgPSAwOyBpIDwgdHJlZS5sZW5ndGg7IGkrKylcbiAge1xuICAgIHZhciBub2RlID0gdHJlZVtpXTtcbiAgICB2YXIgZGlhZ29uYWwgPSBub2RlLmdldERpYWdvbmFsKCk7XG5cbiAgICBpZiAoZGlhZ29uYWwgPiBtYXhEaWFnb25hbClcbiAgICB7XG4gICAgICBtYXhEaWFnb25hbCA9IGRpYWdvbmFsO1xuICAgIH1cbiAgfVxuXG4gIHJldHVybiBtYXhEaWFnb25hbDtcbn07XG5cbkNvU0VMYXlvdXQucHJvdG90eXBlLmNhbGNSZXB1bHNpb25SYW5nZSA9IGZ1bmN0aW9uICgpIHtcbiAgLy8gZm9ybXVsYSBpcyAyIHggKGxldmVsICsgMSkgeCBpZGVhbEVkZ2VMZW5ndGhcbiAgcmV0dXJuICgyICogKHRoaXMubGV2ZWwgKyAxKSAqIHRoaXMuaWRlYWxFZGdlTGVuZ3RoKTtcbn07XG5cbm1vZHVsZS5leHBvcnRzID0gQ29TRUxheW91dDtcbiIsInZhciBGRExheW91dE5vZGUgPSByZXF1aXJlKCcuL0ZETGF5b3V0Tm9kZScpO1xudmFyIElNYXRoID0gcmVxdWlyZSgnLi9JTWF0aCcpO1xuXG5mdW5jdGlvbiBDb1NFTm9kZShnbSwgbG9jLCBzaXplLCB2Tm9kZSkge1xuICBGRExheW91dE5vZGUuY2FsbCh0aGlzLCBnbSwgbG9jLCBzaXplLCB2Tm9kZSk7XG59XG5cblxuQ29TRU5vZGUucHJvdG90eXBlID0gT2JqZWN0LmNyZWF0ZShGRExheW91dE5vZGUucHJvdG90eXBlKTtcbmZvciAodmFyIHByb3AgaW4gRkRMYXlvdXROb2RlKSB7XG4gIENvU0VOb2RlW3Byb3BdID0gRkRMYXlvdXROb2RlW3Byb3BdO1xufVxuXG5Db1NFTm9kZS5wcm90b3R5cGUubW92ZSA9IGZ1bmN0aW9uICgpXG57XG4gIHZhciBsYXlvdXQgPSB0aGlzLmdyYXBoTWFuYWdlci5nZXRMYXlvdXQoKTtcbiAgdGhpcy5kaXNwbGFjZW1lbnRYID0gbGF5b3V0LmNvb2xpbmdGYWN0b3IgKlxuICAgICAgICAgICh0aGlzLnNwcmluZ0ZvcmNlWCArIHRoaXMucmVwdWxzaW9uRm9yY2VYICsgdGhpcy5ncmF2aXRhdGlvbkZvcmNlWCk7XG4gIHRoaXMuZGlzcGxhY2VtZW50WSA9IGxheW91dC5jb29saW5nRmFjdG9yICpcbiAgICAgICAgICAodGhpcy5zcHJpbmdGb3JjZVkgKyB0aGlzLnJlcHVsc2lvbkZvcmNlWSArIHRoaXMuZ3Jhdml0YXRpb25Gb3JjZVkpO1xuXG5cbiAgaWYgKE1hdGguYWJzKHRoaXMuZGlzcGxhY2VtZW50WCkgPiBsYXlvdXQuY29vbGluZ0ZhY3RvciAqIGxheW91dC5tYXhOb2RlRGlzcGxhY2VtZW50KVxuICB7XG4gICAgdGhpcy5kaXNwbGFjZW1lbnRYID0gbGF5b3V0LmNvb2xpbmdGYWN0b3IgKiBsYXlvdXQubWF4Tm9kZURpc3BsYWNlbWVudCAqXG4gICAgICAgICAgICBJTWF0aC5zaWduKHRoaXMuZGlzcGxhY2VtZW50WCk7XG4gIH1cblxuICBpZiAoTWF0aC5hYnModGhpcy5kaXNwbGFjZW1lbnRZKSA+IGxheW91dC5jb29saW5nRmFjdG9yICogbGF5b3V0Lm1heE5vZGVEaXNwbGFjZW1lbnQpXG4gIHtcbiAgICB0aGlzLmRpc3BsYWNlbWVudFkgPSBsYXlvdXQuY29vbGluZ0ZhY3RvciAqIGxheW91dC5tYXhOb2RlRGlzcGxhY2VtZW50ICpcbiAgICAgICAgICAgIElNYXRoLnNpZ24odGhpcy5kaXNwbGFjZW1lbnRZKTtcbiAgfVxuXG4gIC8vIGEgc2ltcGxlIG5vZGUsIGp1c3QgbW92ZSBpdFxuICBpZiAodGhpcy5jaGlsZCA9PSBudWxsKVxuICB7XG4gICAgdGhpcy5tb3ZlQnkodGhpcy5kaXNwbGFjZW1lbnRYLCB0aGlzLmRpc3BsYWNlbWVudFkpO1xuICB9XG4gIC8vIGFuIGVtcHR5IGNvbXBvdW5kIG5vZGUsIGFnYWluIGp1c3QgbW92ZSBpdFxuICBlbHNlIGlmICh0aGlzLmNoaWxkLmdldE5vZGVzKCkubGVuZ3RoID09IDApXG4gIHtcbiAgICB0aGlzLm1vdmVCeSh0aGlzLmRpc3BsYWNlbWVudFgsIHRoaXMuZGlzcGxhY2VtZW50WSk7XG4gIH1cbiAgLy8gbm9uLWVtcHR5IGNvbXBvdW5kIG5vZGUsIHByb3BvZ2F0ZSBtb3ZlbWVudCB0byBjaGlsZHJlbiBhcyB3ZWxsXG4gIGVsc2VcbiAge1xuICAgIHRoaXMucHJvcG9nYXRlRGlzcGxhY2VtZW50VG9DaGlsZHJlbih0aGlzLmRpc3BsYWNlbWVudFgsXG4gICAgICAgICAgICB0aGlzLmRpc3BsYWNlbWVudFkpO1xuICB9XG5cbiAgbGF5b3V0LnRvdGFsRGlzcGxhY2VtZW50ICs9XG4gICAgICAgICAgTWF0aC5hYnModGhpcy5kaXNwbGFjZW1lbnRYKSArIE1hdGguYWJzKHRoaXMuZGlzcGxhY2VtZW50WSk7XG5cbiAgdGhpcy5zcHJpbmdGb3JjZVggPSAwO1xuICB0aGlzLnNwcmluZ0ZvcmNlWSA9IDA7XG4gIHRoaXMucmVwdWxzaW9uRm9yY2VYID0gMDtcbiAgdGhpcy5yZXB1bHNpb25Gb3JjZVkgPSAwO1xuICB0aGlzLmdyYXZpdGF0aW9uRm9yY2VYID0gMDtcbiAgdGhpcy5ncmF2aXRhdGlvbkZvcmNlWSA9IDA7XG4gIHRoaXMuZGlzcGxhY2VtZW50WCA9IDA7XG4gIHRoaXMuZGlzcGxhY2VtZW50WSA9IDA7XG59O1xuXG5Db1NFTm9kZS5wcm90b3R5cGUucHJvcG9nYXRlRGlzcGxhY2VtZW50VG9DaGlsZHJlbiA9IGZ1bmN0aW9uIChkWCwgZFkpXG57XG4gIHZhciBub2RlcyA9IHRoaXMuZ2V0Q2hpbGQoKS5nZXROb2RlcygpO1xuICB2YXIgbm9kZTtcbiAgZm9yICh2YXIgaSA9IDA7IGkgPCBub2Rlcy5sZW5ndGg7IGkrKylcbiAge1xuICAgIG5vZGUgPSBub2Rlc1tpXTtcbiAgICBpZiAobm9kZS5nZXRDaGlsZCgpID09IG51bGwpXG4gICAge1xuICAgICAgbm9kZS5tb3ZlQnkoZFgsIGRZKTtcbiAgICAgIG5vZGUuZGlzcGxhY2VtZW50WCArPSBkWDtcbiAgICAgIG5vZGUuZGlzcGxhY2VtZW50WSArPSBkWTtcbiAgICB9XG4gICAgZWxzZVxuICAgIHtcbiAgICAgIG5vZGUucHJvcG9nYXRlRGlzcGxhY2VtZW50VG9DaGlsZHJlbihkWCwgZFkpO1xuICAgIH1cbiAgfVxufTtcblxuQ29TRU5vZGUucHJvdG90eXBlLnNldFByZWQxID0gZnVuY3Rpb24gKHByZWQxKVxue1xuICB0aGlzLnByZWQxID0gcHJlZDE7XG59O1xuXG5Db1NFTm9kZS5wcm90b3R5cGUuZ2V0UHJlZDEgPSBmdW5jdGlvbiAoKVxue1xuICByZXR1cm4gcHJlZDE7XG59O1xuXG5Db1NFTm9kZS5wcm90b3R5cGUuZ2V0UHJlZDIgPSBmdW5jdGlvbiAoKVxue1xuICByZXR1cm4gcHJlZDI7XG59O1xuXG5Db1NFTm9kZS5wcm90b3R5cGUuc2V0TmV4dCA9IGZ1bmN0aW9uIChuZXh0KVxue1xuICB0aGlzLm5leHQgPSBuZXh0O1xufTtcblxuQ29TRU5vZGUucHJvdG90eXBlLmdldE5leHQgPSBmdW5jdGlvbiAoKVxue1xuICByZXR1cm4gbmV4dDtcbn07XG5cbkNvU0VOb2RlLnByb3RvdHlwZS5zZXRQcm9jZXNzZWQgPSBmdW5jdGlvbiAocHJvY2Vzc2VkKVxue1xuICB0aGlzLnByb2Nlc3NlZCA9IHByb2Nlc3NlZDtcbn07XG5cbkNvU0VOb2RlLnByb3RvdHlwZS5pc1Byb2Nlc3NlZCA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiBwcm9jZXNzZWQ7XG59O1xuXG5tb2R1bGUuZXhwb3J0cyA9IENvU0VOb2RlO1xuIiwiZnVuY3Rpb24gRGltZW5zaW9uRCh3aWR0aCwgaGVpZ2h0KSB7XG4gIHRoaXMud2lkdGggPSAwO1xuICB0aGlzLmhlaWdodCA9IDA7XG4gIGlmICh3aWR0aCAhPT0gbnVsbCAmJiBoZWlnaHQgIT09IG51bGwpIHtcbiAgICB0aGlzLmhlaWdodCA9IGhlaWdodDtcbiAgICB0aGlzLndpZHRoID0gd2lkdGg7XG4gIH1cbn1cblxuRGltZW5zaW9uRC5wcm90b3R5cGUuZ2V0V2lkdGggPSBmdW5jdGlvbiAoKVxue1xuICByZXR1cm4gdGhpcy53aWR0aDtcbn07XG5cbkRpbWVuc2lvbkQucHJvdG90eXBlLnNldFdpZHRoID0gZnVuY3Rpb24gKHdpZHRoKVxue1xuICB0aGlzLndpZHRoID0gd2lkdGg7XG59O1xuXG5EaW1lbnNpb25ELnByb3RvdHlwZS5nZXRIZWlnaHQgPSBmdW5jdGlvbiAoKVxue1xuICByZXR1cm4gdGhpcy5oZWlnaHQ7XG59O1xuXG5EaW1lbnNpb25ELnByb3RvdHlwZS5zZXRIZWlnaHQgPSBmdW5jdGlvbiAoaGVpZ2h0KVxue1xuICB0aGlzLmhlaWdodCA9IGhlaWdodDtcbn07XG5cbm1vZHVsZS5leHBvcnRzID0gRGltZW5zaW9uRDtcbiIsImZ1bmN0aW9uIEVtaXR0ZXIoKXtcbiAgdGhpcy5saXN0ZW5lcnMgPSBbXTtcbn1cblxudmFyIHAgPSBFbWl0dGVyLnByb3RvdHlwZTtcblxucC5hZGRMaXN0ZW5lciA9IGZ1bmN0aW9uKCBldmVudCwgY2FsbGJhY2sgKXtcbiAgdGhpcy5saXN0ZW5lcnMucHVzaCh7XG4gICAgZXZlbnQ6IGV2ZW50LFxuICAgIGNhbGxiYWNrOiBjYWxsYmFja1xuICB9KTtcbn07XG5cbnAucmVtb3ZlTGlzdGVuZXIgPSBmdW5jdGlvbiggZXZlbnQsIGNhbGxiYWNrICl7XG4gIGZvciggdmFyIGkgPSB0aGlzLmxpc3RlbmVycy5sZW5ndGg7IGkgPj0gMDsgaS0tICl7XG4gICAgdmFyIGwgPSB0aGlzLmxpc3RlbmVyc1tpXTtcblxuICAgIGlmKCBsLmV2ZW50ID09PSBldmVudCAmJiBsLmNhbGxiYWNrID09PSBjYWxsYmFjayApe1xuICAgICAgdGhpcy5saXN0ZW5lcnMuc3BsaWNlKCBpLCAxICk7XG4gICAgfVxuICB9XG59O1xuXG5wLmVtaXQgPSBmdW5jdGlvbiggZXZlbnQsIGRhdGEgKXtcbiAgZm9yKCB2YXIgaSA9IDA7IGkgPCB0aGlzLmxpc3RlbmVycy5sZW5ndGg7IGkrKyApe1xuICAgIHZhciBsID0gdGhpcy5saXN0ZW5lcnNbaV07XG5cbiAgICBpZiggZXZlbnQgPT09IGwuZXZlbnQgKXtcbiAgICAgIGwuY2FsbGJhY2soIGRhdGEgKTtcbiAgICB9XG4gIH1cbn07XG5cbm1vZHVsZS5leHBvcnRzID0gRW1pdHRlcjtcbiIsInZhciBMYXlvdXQgPSByZXF1aXJlKCcuL0xheW91dCcpO1xudmFyIEZETGF5b3V0Q29uc3RhbnRzID0gcmVxdWlyZSgnLi9GRExheW91dENvbnN0YW50cycpO1xudmFyIExheW91dENvbnN0YW50cyA9IHJlcXVpcmUoJy4vTGF5b3V0Q29uc3RhbnRzJyk7XG52YXIgSUdlb21ldHJ5ID0gcmVxdWlyZSgnLi9JR2VvbWV0cnknKTtcbnZhciBJTWF0aCA9IHJlcXVpcmUoJy4vSU1hdGgnKTtcblxuZnVuY3Rpb24gRkRMYXlvdXQoKSB7XG4gIExheW91dC5jYWxsKHRoaXMpO1xuXG4gIHRoaXMudXNlU21hcnRJZGVhbEVkZ2VMZW5ndGhDYWxjdWxhdGlvbiA9IEZETGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfVVNFX1NNQVJUX0lERUFMX0VER0VfTEVOR1RIX0NBTENVTEFUSU9OO1xuICB0aGlzLmlkZWFsRWRnZUxlbmd0aCA9IEZETGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfRURHRV9MRU5HVEg7XG4gIHRoaXMuc3ByaW5nQ29uc3RhbnQgPSBGRExheW91dENvbnN0YW50cy5ERUZBVUxUX1NQUklOR19TVFJFTkdUSDtcbiAgdGhpcy5yZXB1bHNpb25Db25zdGFudCA9IEZETGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfUkVQVUxTSU9OX1NUUkVOR1RIO1xuICB0aGlzLmdyYXZpdHlDb25zdGFudCA9IEZETGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfR1JBVklUWV9TVFJFTkdUSDtcbiAgdGhpcy5jb21wb3VuZEdyYXZpdHlDb25zdGFudCA9IEZETGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfQ09NUE9VTkRfR1JBVklUWV9TVFJFTkdUSDtcbiAgdGhpcy5ncmF2aXR5UmFuZ2VGYWN0b3IgPSBGRExheW91dENvbnN0YW50cy5ERUZBVUxUX0dSQVZJVFlfUkFOR0VfRkFDVE9SO1xuICB0aGlzLmNvbXBvdW5kR3Jhdml0eVJhbmdlRmFjdG9yID0gRkRMYXlvdXRDb25zdGFudHMuREVGQVVMVF9DT01QT1VORF9HUkFWSVRZX1JBTkdFX0ZBQ1RPUjtcbiAgdGhpcy5kaXNwbGFjZW1lbnRUaHJlc2hvbGRQZXJOb2RlID0gKDMuMCAqIEZETGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfRURHRV9MRU5HVEgpIC8gMTAwO1xuICB0aGlzLmNvb2xpbmdGYWN0b3IgPSAxLjA7XG4gIHRoaXMuaW5pdGlhbENvb2xpbmdGYWN0b3IgPSAxLjA7XG4gIHRoaXMudG90YWxEaXNwbGFjZW1lbnQgPSAwLjA7XG4gIHRoaXMub2xkVG90YWxEaXNwbGFjZW1lbnQgPSAwLjA7XG4gIHRoaXMubWF4SXRlcmF0aW9ucyA9IEZETGF5b3V0Q29uc3RhbnRzLk1BWF9JVEVSQVRJT05TO1xufVxuXG5GRExheW91dC5wcm90b3R5cGUgPSBPYmplY3QuY3JlYXRlKExheW91dC5wcm90b3R5cGUpO1xuXG5mb3IgKHZhciBwcm9wIGluIExheW91dCkge1xuICBGRExheW91dFtwcm9wXSA9IExheW91dFtwcm9wXTtcbn1cblxuRkRMYXlvdXQucHJvdG90eXBlLmluaXRQYXJhbWV0ZXJzID0gZnVuY3Rpb24gKCkge1xuICBMYXlvdXQucHJvdG90eXBlLmluaXRQYXJhbWV0ZXJzLmNhbGwodGhpcywgYXJndW1lbnRzKTtcblxuICBpZiAodGhpcy5sYXlvdXRRdWFsaXR5ID09IExheW91dENvbnN0YW50cy5EUkFGVF9RVUFMSVRZKVxuICB7XG4gICAgdGhpcy5kaXNwbGFjZW1lbnRUaHJlc2hvbGRQZXJOb2RlICs9IDAuMzA7XG4gICAgdGhpcy5tYXhJdGVyYXRpb25zICo9IDAuODtcbiAgfVxuICBlbHNlIGlmICh0aGlzLmxheW91dFF1YWxpdHkgPT0gTGF5b3V0Q29uc3RhbnRzLlBST09GX1FVQUxJVFkpXG4gIHtcbiAgICB0aGlzLmRpc3BsYWNlbWVudFRocmVzaG9sZFBlck5vZGUgLT0gMC4zMDtcbiAgICB0aGlzLm1heEl0ZXJhdGlvbnMgKj0gMS4yO1xuICB9XG5cbiAgdGhpcy50b3RhbEl0ZXJhdGlvbnMgPSAwO1xuICB0aGlzLm5vdEFuaW1hdGVkSXRlcmF0aW9ucyA9IDA7XG5cbi8vICAgIHRoaXMudXNlRlJHcmlkVmFyaWFudCA9IGxheW91dE9wdGlvbnNQYWNrLnNtYXJ0UmVwdWxzaW9uUmFuZ2VDYWxjO1xufTtcblxuRkRMYXlvdXQucHJvdG90eXBlLmNhbGNJZGVhbEVkZ2VMZW5ndGhzID0gZnVuY3Rpb24gKCkge1xuICB2YXIgZWRnZTtcbiAgdmFyIGxjYURlcHRoO1xuICB2YXIgc291cmNlO1xuICB2YXIgdGFyZ2V0O1xuICB2YXIgc2l6ZU9mU291cmNlSW5MY2E7XG4gIHZhciBzaXplT2ZUYXJnZXRJbkxjYTtcblxuICB2YXIgYWxsRWRnZXMgPSB0aGlzLmdldEdyYXBoTWFuYWdlcigpLmdldEFsbEVkZ2VzKCk7XG4gIGZvciAodmFyIGkgPSAwOyBpIDwgYWxsRWRnZXMubGVuZ3RoOyBpKyspXG4gIHtcbiAgICBlZGdlID0gYWxsRWRnZXNbaV07XG5cbiAgICBlZGdlLmlkZWFsTGVuZ3RoID0gdGhpcy5pZGVhbEVkZ2VMZW5ndGg7XG5cbiAgICBpZiAoZWRnZS5pc0ludGVyR3JhcGgpXG4gICAge1xuICAgICAgc291cmNlID0gZWRnZS5nZXRTb3VyY2UoKTtcbiAgICAgIHRhcmdldCA9IGVkZ2UuZ2V0VGFyZ2V0KCk7XG5cbiAgICAgIHNpemVPZlNvdXJjZUluTGNhID0gZWRnZS5nZXRTb3VyY2VJbkxjYSgpLmdldEVzdGltYXRlZFNpemUoKTtcbiAgICAgIHNpemVPZlRhcmdldEluTGNhID0gZWRnZS5nZXRUYXJnZXRJbkxjYSgpLmdldEVzdGltYXRlZFNpemUoKTtcblxuICAgICAgaWYgKHRoaXMudXNlU21hcnRJZGVhbEVkZ2VMZW5ndGhDYWxjdWxhdGlvbilcbiAgICAgIHtcbiAgICAgICAgZWRnZS5pZGVhbExlbmd0aCArPSBzaXplT2ZTb3VyY2VJbkxjYSArIHNpemVPZlRhcmdldEluTGNhIC1cbiAgICAgICAgICAgICAgICAyICogTGF5b3V0Q29uc3RhbnRzLlNJTVBMRV9OT0RFX1NJWkU7XG4gICAgICB9XG5cbiAgICAgIGxjYURlcHRoID0gZWRnZS5nZXRMY2EoKS5nZXRJbmNsdXNpb25UcmVlRGVwdGgoKTtcblxuICAgICAgZWRnZS5pZGVhbExlbmd0aCArPSBGRExheW91dENvbnN0YW50cy5ERUZBVUxUX0VER0VfTEVOR1RIICpcbiAgICAgICAgICAgICAgRkRMYXlvdXRDb25zdGFudHMuUEVSX0xFVkVMX0lERUFMX0VER0VfTEVOR1RIX0ZBQ1RPUiAqXG4gICAgICAgICAgICAgIChzb3VyY2UuZ2V0SW5jbHVzaW9uVHJlZURlcHRoKCkgK1xuICAgICAgICAgICAgICAgICAgICAgIHRhcmdldC5nZXRJbmNsdXNpb25UcmVlRGVwdGgoKSAtIDIgKiBsY2FEZXB0aCk7XG4gICAgfVxuICB9XG59O1xuXG5GRExheW91dC5wcm90b3R5cGUuaW5pdFNwcmluZ0VtYmVkZGVyID0gZnVuY3Rpb24gKCkge1xuXG4gIGlmICh0aGlzLmluY3JlbWVudGFsKVxuICB7XG4gICAgdGhpcy5jb29saW5nRmFjdG9yID0gMC44O1xuICAgIHRoaXMuaW5pdGlhbENvb2xpbmdGYWN0b3IgPSAwLjg7XG4gICAgdGhpcy5tYXhOb2RlRGlzcGxhY2VtZW50ID1cbiAgICAgICAgICAgIEZETGF5b3V0Q29uc3RhbnRzLk1BWF9OT0RFX0RJU1BMQUNFTUVOVF9JTkNSRU1FTlRBTDtcbiAgfVxuICBlbHNlXG4gIHtcbiAgICB0aGlzLmNvb2xpbmdGYWN0b3IgPSAxLjA7XG4gICAgdGhpcy5pbml0aWFsQ29vbGluZ0ZhY3RvciA9IDEuMDtcbiAgICB0aGlzLm1heE5vZGVEaXNwbGFjZW1lbnQgPVxuICAgICAgICAgICAgRkRMYXlvdXRDb25zdGFudHMuTUFYX05PREVfRElTUExBQ0VNRU5UO1xuICB9XG5cbiAgdGhpcy5tYXhJdGVyYXRpb25zID1cbiAgICAgICAgICBNYXRoLm1heCh0aGlzLmdldEFsbE5vZGVzKCkubGVuZ3RoICogNSwgdGhpcy5tYXhJdGVyYXRpb25zKTtcblxuICB0aGlzLnRvdGFsRGlzcGxhY2VtZW50VGhyZXNob2xkID1cbiAgICAgICAgICB0aGlzLmRpc3BsYWNlbWVudFRocmVzaG9sZFBlck5vZGUgKiB0aGlzLmdldEFsbE5vZGVzKCkubGVuZ3RoO1xuXG4gIHRoaXMucmVwdWxzaW9uUmFuZ2UgPSB0aGlzLmNhbGNSZXB1bHNpb25SYW5nZSgpO1xufTtcblxuRkRMYXlvdXQucHJvdG90eXBlLmNhbGNTcHJpbmdGb3JjZXMgPSBmdW5jdGlvbiAoKSB7XG4gIHZhciBsRWRnZXMgPSB0aGlzLmdldEFsbEVkZ2VzKCk7XG4gIHZhciBlZGdlO1xuXG4gIGZvciAodmFyIGkgPSAwOyBpIDwgbEVkZ2VzLmxlbmd0aDsgaSsrKVxuICB7XG4gICAgZWRnZSA9IGxFZGdlc1tpXTtcblxuICAgIHRoaXMuY2FsY1NwcmluZ0ZvcmNlKGVkZ2UsIGVkZ2UuaWRlYWxMZW5ndGgpO1xuICB9XG59O1xuXG5GRExheW91dC5wcm90b3R5cGUuY2FsY1JlcHVsc2lvbkZvcmNlcyA9IGZ1bmN0aW9uICgpIHtcbiAgdmFyIGksIGo7XG4gIHZhciBub2RlQSwgbm9kZUI7XG4gIHZhciBsTm9kZXMgPSB0aGlzLmdldEFsbE5vZGVzKCk7XG5cbiAgZm9yIChpID0gMDsgaSA8IGxOb2Rlcy5sZW5ndGg7IGkrKylcbiAge1xuICAgIG5vZGVBID0gbE5vZGVzW2ldO1xuXG4gICAgZm9yIChqID0gaSArIDE7IGogPCBsTm9kZXMubGVuZ3RoOyBqKyspXG4gICAge1xuICAgICAgbm9kZUIgPSBsTm9kZXNbal07XG5cbiAgICAgIC8vIElmIGJvdGggbm9kZXMgYXJlIG5vdCBtZW1iZXJzIG9mIHRoZSBzYW1lIGdyYXBoLCBza2lwLlxuICAgICAgaWYgKG5vZGVBLmdldE93bmVyKCkgIT0gbm9kZUIuZ2V0T3duZXIoKSlcbiAgICAgIHtcbiAgICAgICAgY29udGludWU7XG4gICAgICB9XG5cbiAgICAgIHRoaXMuY2FsY1JlcHVsc2lvbkZvcmNlKG5vZGVBLCBub2RlQik7XG4gICAgfVxuICB9XG59O1xuXG5GRExheW91dC5wcm90b3R5cGUuY2FsY0dyYXZpdGF0aW9uYWxGb3JjZXMgPSBmdW5jdGlvbiAoKSB7XG4gIHZhciBub2RlO1xuICB2YXIgbE5vZGVzID0gdGhpcy5nZXRBbGxOb2Rlc1RvQXBwbHlHcmF2aXRhdGlvbigpO1xuXG4gIGZvciAodmFyIGkgPSAwOyBpIDwgbE5vZGVzLmxlbmd0aDsgaSsrKVxuICB7XG4gICAgbm9kZSA9IGxOb2Rlc1tpXTtcbiAgICB0aGlzLmNhbGNHcmF2aXRhdGlvbmFsRm9yY2Uobm9kZSk7XG4gIH1cbn07XG5cbkZETGF5b3V0LnByb3RvdHlwZS5tb3ZlTm9kZXMgPSBmdW5jdGlvbiAoKSB7XG4gIHZhciBsTm9kZXMgPSB0aGlzLmdldEFsbE5vZGVzKCk7XG4gIHZhciBub2RlO1xuXG4gIGZvciAodmFyIGkgPSAwOyBpIDwgbE5vZGVzLmxlbmd0aDsgaSsrKVxuICB7XG4gICAgbm9kZSA9IGxOb2Rlc1tpXTtcbiAgICBub2RlLm1vdmUoKTtcbiAgfVxufVxuXG5GRExheW91dC5wcm90b3R5cGUuY2FsY1NwcmluZ0ZvcmNlID0gZnVuY3Rpb24gKGVkZ2UsIGlkZWFsTGVuZ3RoKSB7XG4gIHZhciBzb3VyY2VOb2RlID0gZWRnZS5nZXRTb3VyY2UoKTtcbiAgdmFyIHRhcmdldE5vZGUgPSBlZGdlLmdldFRhcmdldCgpO1xuXG4gIHZhciBsZW5ndGg7XG4gIHZhciBzcHJpbmdGb3JjZTtcbiAgdmFyIHNwcmluZ0ZvcmNlWDtcbiAgdmFyIHNwcmluZ0ZvcmNlWTtcblxuICAvLyBVcGRhdGUgZWRnZSBsZW5ndGhcbiAgaWYgKHRoaXMudW5pZm9ybUxlYWZOb2RlU2l6ZXMgJiZcbiAgICAgICAgICBzb3VyY2VOb2RlLmdldENoaWxkKCkgPT0gbnVsbCAmJiB0YXJnZXROb2RlLmdldENoaWxkKCkgPT0gbnVsbClcbiAge1xuICAgIGVkZ2UudXBkYXRlTGVuZ3RoU2ltcGxlKCk7XG4gIH1cbiAgZWxzZVxuICB7XG4gICAgZWRnZS51cGRhdGVMZW5ndGgoKTtcblxuICAgIGlmIChlZGdlLmlzT3ZlcmxhcGluZ1NvdXJjZUFuZFRhcmdldClcbiAgICB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICB9XG5cbiAgbGVuZ3RoID0gZWRnZS5nZXRMZW5ndGgoKTtcblxuICAvLyBDYWxjdWxhdGUgc3ByaW5nIGZvcmNlc1xuICBzcHJpbmdGb3JjZSA9IHRoaXMuc3ByaW5nQ29uc3RhbnQgKiAobGVuZ3RoIC0gaWRlYWxMZW5ndGgpO1xuXG4gIC8vIFByb2plY3QgZm9yY2Ugb250byB4IGFuZCB5IGF4ZXNcbiAgc3ByaW5nRm9yY2VYID0gc3ByaW5nRm9yY2UgKiAoZWRnZS5sZW5ndGhYIC8gbGVuZ3RoKTtcbiAgc3ByaW5nRm9yY2VZID0gc3ByaW5nRm9yY2UgKiAoZWRnZS5sZW5ndGhZIC8gbGVuZ3RoKTtcblxuICAvLyBBcHBseSBmb3JjZXMgb24gdGhlIGVuZCBub2Rlc1xuICBzb3VyY2VOb2RlLnNwcmluZ0ZvcmNlWCArPSBzcHJpbmdGb3JjZVg7XG4gIHNvdXJjZU5vZGUuc3ByaW5nRm9yY2VZICs9IHNwcmluZ0ZvcmNlWTtcbiAgdGFyZ2V0Tm9kZS5zcHJpbmdGb3JjZVggLT0gc3ByaW5nRm9yY2VYO1xuICB0YXJnZXROb2RlLnNwcmluZ0ZvcmNlWSAtPSBzcHJpbmdGb3JjZVk7XG59O1xuXG5GRExheW91dC5wcm90b3R5cGUuY2FsY1JlcHVsc2lvbkZvcmNlID0gZnVuY3Rpb24gKG5vZGVBLCBub2RlQikge1xuICB2YXIgcmVjdEEgPSBub2RlQS5nZXRSZWN0KCk7XG4gIHZhciByZWN0QiA9IG5vZGVCLmdldFJlY3QoKTtcbiAgdmFyIG92ZXJsYXBBbW91bnQgPSBuZXcgQXJyYXkoMik7XG4gIHZhciBjbGlwUG9pbnRzID0gbmV3IEFycmF5KDQpO1xuICB2YXIgZGlzdGFuY2VYO1xuICB2YXIgZGlzdGFuY2VZO1xuICB2YXIgZGlzdGFuY2VTcXVhcmVkO1xuICB2YXIgZGlzdGFuY2U7XG4gIHZhciByZXB1bHNpb25Gb3JjZTtcbiAgdmFyIHJlcHVsc2lvbkZvcmNlWDtcbiAgdmFyIHJlcHVsc2lvbkZvcmNlWTtcblxuICBpZiAocmVjdEEuaW50ZXJzZWN0cyhyZWN0QikpLy8gdHdvIG5vZGVzIG92ZXJsYXBcbiAge1xuICAgIC8vIGNhbGN1bGF0ZSBzZXBhcmF0aW9uIGFtb3VudCBpbiB4IGFuZCB5IGRpcmVjdGlvbnNcbiAgICBJR2VvbWV0cnkuY2FsY1NlcGFyYXRpb25BbW91bnQocmVjdEEsXG4gICAgICAgICAgICByZWN0QixcbiAgICAgICAgICAgIG92ZXJsYXBBbW91bnQsXG4gICAgICAgICAgICBGRExheW91dENvbnN0YW50cy5ERUZBVUxUX0VER0VfTEVOR1RIIC8gMi4wKTtcblxuICAgIHJlcHVsc2lvbkZvcmNlWCA9IG92ZXJsYXBBbW91bnRbMF07XG4gICAgcmVwdWxzaW9uRm9yY2VZID0gb3ZlcmxhcEFtb3VudFsxXTtcbiAgfVxuICBlbHNlLy8gbm8gb3ZlcmxhcFxuICB7XG4gICAgLy8gY2FsY3VsYXRlIGRpc3RhbmNlXG5cbiAgICBpZiAodGhpcy51bmlmb3JtTGVhZk5vZGVTaXplcyAmJlxuICAgICAgICAgICAgbm9kZUEuZ2V0Q2hpbGQoKSA9PSBudWxsICYmIG5vZGVCLmdldENoaWxkKCkgPT0gbnVsbCkvLyBzaW1wbHkgYmFzZSByZXB1bHNpb24gb24gZGlzdGFuY2Ugb2Ygbm9kZSBjZW50ZXJzXG4gICAge1xuICAgICAgZGlzdGFuY2VYID0gcmVjdEIuZ2V0Q2VudGVyWCgpIC0gcmVjdEEuZ2V0Q2VudGVyWCgpO1xuICAgICAgZGlzdGFuY2VZID0gcmVjdEIuZ2V0Q2VudGVyWSgpIC0gcmVjdEEuZ2V0Q2VudGVyWSgpO1xuICAgIH1cbiAgICBlbHNlLy8gdXNlIGNsaXBwaW5nIHBvaW50c1xuICAgIHtcbiAgICAgIElHZW9tZXRyeS5nZXRJbnRlcnNlY3Rpb24ocmVjdEEsIHJlY3RCLCBjbGlwUG9pbnRzKTtcblxuICAgICAgZGlzdGFuY2VYID0gY2xpcFBvaW50c1syXSAtIGNsaXBQb2ludHNbMF07XG4gICAgICBkaXN0YW5jZVkgPSBjbGlwUG9pbnRzWzNdIC0gY2xpcFBvaW50c1sxXTtcbiAgICB9XG5cbiAgICAvLyBObyByZXB1bHNpb24gcmFuZ2UuIEZSIGdyaWQgdmFyaWFudCBzaG91bGQgdGFrZSBjYXJlIG9mIHRoaXMuXG4gICAgaWYgKE1hdGguYWJzKGRpc3RhbmNlWCkgPCBGRExheW91dENvbnN0YW50cy5NSU5fUkVQVUxTSU9OX0RJU1QpXG4gICAge1xuICAgICAgZGlzdGFuY2VYID0gSU1hdGguc2lnbihkaXN0YW5jZVgpICpcbiAgICAgICAgICAgICAgRkRMYXlvdXRDb25zdGFudHMuTUlOX1JFUFVMU0lPTl9ESVNUO1xuICAgIH1cblxuICAgIGlmIChNYXRoLmFicyhkaXN0YW5jZVkpIDwgRkRMYXlvdXRDb25zdGFudHMuTUlOX1JFUFVMU0lPTl9ESVNUKVxuICAgIHtcbiAgICAgIGRpc3RhbmNlWSA9IElNYXRoLnNpZ24oZGlzdGFuY2VZKSAqXG4gICAgICAgICAgICAgIEZETGF5b3V0Q29uc3RhbnRzLk1JTl9SRVBVTFNJT05fRElTVDtcbiAgICB9XG5cbiAgICBkaXN0YW5jZVNxdWFyZWQgPSBkaXN0YW5jZVggKiBkaXN0YW5jZVggKyBkaXN0YW5jZVkgKiBkaXN0YW5jZVk7XG4gICAgZGlzdGFuY2UgPSBNYXRoLnNxcnQoZGlzdGFuY2VTcXVhcmVkKTtcblxuICAgIHJlcHVsc2lvbkZvcmNlID0gdGhpcy5yZXB1bHNpb25Db25zdGFudCAvIGRpc3RhbmNlU3F1YXJlZDtcblxuICAgIC8vIFByb2plY3QgZm9yY2Ugb250byB4IGFuZCB5IGF4ZXNcbiAgICByZXB1bHNpb25Gb3JjZVggPSByZXB1bHNpb25Gb3JjZSAqIGRpc3RhbmNlWCAvIGRpc3RhbmNlO1xuICAgIHJlcHVsc2lvbkZvcmNlWSA9IHJlcHVsc2lvbkZvcmNlICogZGlzdGFuY2VZIC8gZGlzdGFuY2U7XG4gIH1cblxuICAvLyBBcHBseSBmb3JjZXMgb24gdGhlIHR3byBub2Rlc1xuICBub2RlQS5yZXB1bHNpb25Gb3JjZVggLT0gcmVwdWxzaW9uRm9yY2VYO1xuICBub2RlQS5yZXB1bHNpb25Gb3JjZVkgLT0gcmVwdWxzaW9uRm9yY2VZO1xuICBub2RlQi5yZXB1bHNpb25Gb3JjZVggKz0gcmVwdWxzaW9uRm9yY2VYO1xuICBub2RlQi5yZXB1bHNpb25Gb3JjZVkgKz0gcmVwdWxzaW9uRm9yY2VZO1xufTtcblxuRkRMYXlvdXQucHJvdG90eXBlLmNhbGNHcmF2aXRhdGlvbmFsRm9yY2UgPSBmdW5jdGlvbiAobm9kZSkge1xuICB2YXIgb3duZXJHcmFwaDtcbiAgdmFyIG93bmVyQ2VudGVyWDtcbiAgdmFyIG93bmVyQ2VudGVyWTtcbiAgdmFyIGRpc3RhbmNlWDtcbiAgdmFyIGRpc3RhbmNlWTtcbiAgdmFyIGFic0Rpc3RhbmNlWDtcbiAgdmFyIGFic0Rpc3RhbmNlWTtcbiAgdmFyIGVzdGltYXRlZFNpemU7XG4gIG93bmVyR3JhcGggPSBub2RlLmdldE93bmVyKCk7XG5cbiAgb3duZXJDZW50ZXJYID0gKG93bmVyR3JhcGguZ2V0UmlnaHQoKSArIG93bmVyR3JhcGguZ2V0TGVmdCgpKSAvIDI7XG4gIG93bmVyQ2VudGVyWSA9IChvd25lckdyYXBoLmdldFRvcCgpICsgb3duZXJHcmFwaC5nZXRCb3R0b20oKSkgLyAyO1xuICBkaXN0YW5jZVggPSBub2RlLmdldENlbnRlclgoKSAtIG93bmVyQ2VudGVyWDtcbiAgZGlzdGFuY2VZID0gbm9kZS5nZXRDZW50ZXJZKCkgLSBvd25lckNlbnRlclk7XG4gIGFic0Rpc3RhbmNlWCA9IE1hdGguYWJzKGRpc3RhbmNlWCk7XG4gIGFic0Rpc3RhbmNlWSA9IE1hdGguYWJzKGRpc3RhbmNlWSk7XG5cbiAgaWYgKG5vZGUuZ2V0T3duZXIoKSA9PSB0aGlzLmdyYXBoTWFuYWdlci5nZXRSb290KCkpLy8gaW4gdGhlIHJvb3QgZ3JhcGhcbiAge1xuICAgIE1hdGguZmxvb3IoODApO1xuICAgIGVzdGltYXRlZFNpemUgPSBNYXRoLmZsb29yKG93bmVyR3JhcGguZ2V0RXN0aW1hdGVkU2l6ZSgpICpcbiAgICAgICAgICAgIHRoaXMuZ3Jhdml0eVJhbmdlRmFjdG9yKTtcblxuICAgIGlmIChhYnNEaXN0YW5jZVggPiBlc3RpbWF0ZWRTaXplIHx8IGFic0Rpc3RhbmNlWSA+IGVzdGltYXRlZFNpemUpXG4gICAge1xuICAgICAgbm9kZS5ncmF2aXRhdGlvbkZvcmNlWCA9IC10aGlzLmdyYXZpdHlDb25zdGFudCAqIGRpc3RhbmNlWDtcbiAgICAgIG5vZGUuZ3Jhdml0YXRpb25Gb3JjZVkgPSAtdGhpcy5ncmF2aXR5Q29uc3RhbnQgKiBkaXN0YW5jZVk7XG4gICAgfVxuICB9XG4gIGVsc2UvLyBpbnNpZGUgYSBjb21wb3VuZFxuICB7XG4gICAgZXN0aW1hdGVkU2l6ZSA9IE1hdGguZmxvb3IoKG93bmVyR3JhcGguZ2V0RXN0aW1hdGVkU2l6ZSgpICpcbiAgICAgICAgICAgIHRoaXMuY29tcG91bmRHcmF2aXR5UmFuZ2VGYWN0b3IpKTtcblxuICAgIGlmIChhYnNEaXN0YW5jZVggPiBlc3RpbWF0ZWRTaXplIHx8IGFic0Rpc3RhbmNlWSA+IGVzdGltYXRlZFNpemUpXG4gICAge1xuICAgICAgbm9kZS5ncmF2aXRhdGlvbkZvcmNlWCA9IC10aGlzLmdyYXZpdHlDb25zdGFudCAqIGRpc3RhbmNlWCAqXG4gICAgICAgICAgICAgIHRoaXMuY29tcG91bmRHcmF2aXR5Q29uc3RhbnQ7XG4gICAgICBub2RlLmdyYXZpdGF0aW9uRm9yY2VZID0gLXRoaXMuZ3Jhdml0eUNvbnN0YW50ICogZGlzdGFuY2VZICpcbiAgICAgICAgICAgICAgdGhpcy5jb21wb3VuZEdyYXZpdHlDb25zdGFudDtcbiAgICB9XG4gIH1cbn07XG5cbkZETGF5b3V0LnByb3RvdHlwZS5pc0NvbnZlcmdlZCA9IGZ1bmN0aW9uICgpIHtcbiAgdmFyIGNvbnZlcmdlZDtcbiAgdmFyIG9zY2lsYXRpbmcgPSBmYWxzZTtcblxuICBpZiAodGhpcy50b3RhbEl0ZXJhdGlvbnMgPiB0aGlzLm1heEl0ZXJhdGlvbnMgLyAzKVxuICB7XG4gICAgb3NjaWxhdGluZyA9XG4gICAgICAgICAgICBNYXRoLmFicyh0aGlzLnRvdGFsRGlzcGxhY2VtZW50IC0gdGhpcy5vbGRUb3RhbERpc3BsYWNlbWVudCkgPCAyO1xuICB9XG5cbiAgY29udmVyZ2VkID0gdGhpcy50b3RhbERpc3BsYWNlbWVudCA8IHRoaXMudG90YWxEaXNwbGFjZW1lbnRUaHJlc2hvbGQ7XG5cbiAgdGhpcy5vbGRUb3RhbERpc3BsYWNlbWVudCA9IHRoaXMudG90YWxEaXNwbGFjZW1lbnQ7XG5cbiAgcmV0dXJuIGNvbnZlcmdlZCB8fCBvc2NpbGF0aW5nO1xufTtcblxuRkRMYXlvdXQucHJvdG90eXBlLmFuaW1hdGUgPSBmdW5jdGlvbiAoKSB7XG4gIGlmICh0aGlzLmFuaW1hdGlvbkR1cmluZ0xheW91dCAmJiAhdGhpcy5pc1N1YkxheW91dClcbiAge1xuICAgIGlmICh0aGlzLm5vdEFuaW1hdGVkSXRlcmF0aW9ucyA9PSB0aGlzLmFuaW1hdGlvblBlcmlvZClcbiAgICB7XG4gICAgICB0aGlzLnVwZGF0ZSgpO1xuICAgICAgdGhpcy5ub3RBbmltYXRlZEl0ZXJhdGlvbnMgPSAwO1xuICAgIH1cbiAgICBlbHNlXG4gICAge1xuICAgICAgdGhpcy5ub3RBbmltYXRlZEl0ZXJhdGlvbnMrKztcbiAgICB9XG4gIH1cbn07XG5cbkZETGF5b3V0LnByb3RvdHlwZS5jYWxjUmVwdWxzaW9uUmFuZ2UgPSBmdW5jdGlvbiAoKSB7XG4gIHJldHVybiAwLjA7XG59O1xuXG5tb2R1bGUuZXhwb3J0cyA9IEZETGF5b3V0O1xuIiwidmFyIExheW91dENvbnN0YW50cyA9IHJlcXVpcmUoJy4vTGF5b3V0Q29uc3RhbnRzJyk7XG5cbmZ1bmN0aW9uIEZETGF5b3V0Q29uc3RhbnRzKCkge1xufVxuXG4vL0ZETGF5b3V0Q29uc3RhbnRzIGluaGVyaXRzIHN0YXRpYyBwcm9wcyBpbiBMYXlvdXRDb25zdGFudHNcbmZvciAodmFyIHByb3AgaW4gTGF5b3V0Q29uc3RhbnRzKSB7XG4gIEZETGF5b3V0Q29uc3RhbnRzW3Byb3BdID0gTGF5b3V0Q29uc3RhbnRzW3Byb3BdO1xufVxuXG5GRExheW91dENvbnN0YW50cy5NQVhfSVRFUkFUSU9OUyA9IDI1MDA7XG5cbkZETGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfRURHRV9MRU5HVEggPSA1MDtcbkZETGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfU1BSSU5HX1NUUkVOR1RIID0gMC40NTtcbkZETGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfUkVQVUxTSU9OX1NUUkVOR1RIID0gNDUwMC4wO1xuRkRMYXlvdXRDb25zdGFudHMuREVGQVVMVF9HUkFWSVRZX1NUUkVOR1RIID0gMC40O1xuRkRMYXlvdXRDb25zdGFudHMuREVGQVVMVF9DT01QT1VORF9HUkFWSVRZX1NUUkVOR1RIID0gMS4wO1xuRkRMYXlvdXRDb25zdGFudHMuREVGQVVMVF9HUkFWSVRZX1JBTkdFX0ZBQ1RPUiA9IDMuODtcbkZETGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfQ09NUE9VTkRfR1JBVklUWV9SQU5HRV9GQUNUT1IgPSAxLjU7XG5GRExheW91dENvbnN0YW50cy5ERUZBVUxUX1VTRV9TTUFSVF9JREVBTF9FREdFX0xFTkdUSF9DQUxDVUxBVElPTiA9IHRydWU7XG5GRExheW91dENvbnN0YW50cy5ERUZBVUxUX1VTRV9TTUFSVF9SRVBVTFNJT05fUkFOR0VfQ0FMQ1VMQVRJT04gPSB0cnVlO1xuRkRMYXlvdXRDb25zdGFudHMuTUFYX05PREVfRElTUExBQ0VNRU5UX0lOQ1JFTUVOVEFMID0gMTAwLjA7XG5GRExheW91dENvbnN0YW50cy5NQVhfTk9ERV9ESVNQTEFDRU1FTlQgPSBGRExheW91dENvbnN0YW50cy5NQVhfTk9ERV9ESVNQTEFDRU1FTlRfSU5DUkVNRU5UQUwgKiAzO1xuRkRMYXlvdXRDb25zdGFudHMuTUlOX1JFUFVMU0lPTl9ESVNUID0gRkRMYXlvdXRDb25zdGFudHMuREVGQVVMVF9FREdFX0xFTkdUSCAvIDEwLjA7XG5GRExheW91dENvbnN0YW50cy5DT05WRVJHRU5DRV9DSEVDS19QRVJJT0QgPSAxMDA7XG5GRExheW91dENvbnN0YW50cy5QRVJfTEVWRUxfSURFQUxfRURHRV9MRU5HVEhfRkFDVE9SID0gMC4xO1xuRkRMYXlvdXRDb25zdGFudHMuTUlOX0VER0VfTEVOR1RIID0gMTtcbkZETGF5b3V0Q29uc3RhbnRzLkdSSURfQ0FMQ1VMQVRJT05fQ0hFQ0tfUEVSSU9EID0gMTA7XG5cbm1vZHVsZS5leHBvcnRzID0gRkRMYXlvdXRDb25zdGFudHM7XG4iLCJ2YXIgTEVkZ2UgPSByZXF1aXJlKCcuL0xFZGdlJyk7XG52YXIgRkRMYXlvdXRDb25zdGFudHMgPSByZXF1aXJlKCcuL0ZETGF5b3V0Q29uc3RhbnRzJyk7XG5cbmZ1bmN0aW9uIEZETGF5b3V0RWRnZShzb3VyY2UsIHRhcmdldCwgdkVkZ2UpIHtcbiAgTEVkZ2UuY2FsbCh0aGlzLCBzb3VyY2UsIHRhcmdldCwgdkVkZ2UpO1xuICB0aGlzLmlkZWFsTGVuZ3RoID0gRkRMYXlvdXRDb25zdGFudHMuREVGQVVMVF9FREdFX0xFTkdUSDtcbn1cblxuRkRMYXlvdXRFZGdlLnByb3RvdHlwZSA9IE9iamVjdC5jcmVhdGUoTEVkZ2UucHJvdG90eXBlKTtcblxuZm9yICh2YXIgcHJvcCBpbiBMRWRnZSkge1xuICBGRExheW91dEVkZ2VbcHJvcF0gPSBMRWRnZVtwcm9wXTtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBGRExheW91dEVkZ2U7XG4iLCJ2YXIgTE5vZGUgPSByZXF1aXJlKCcuL0xOb2RlJyk7XG5cbmZ1bmN0aW9uIEZETGF5b3V0Tm9kZShnbSwgbG9jLCBzaXplLCB2Tm9kZSkge1xuICAvLyBhbHRlcm5hdGl2ZSBjb25zdHJ1Y3RvciBpcyBoYW5kbGVkIGluc2lkZSBMTm9kZVxuICBMTm9kZS5jYWxsKHRoaXMsIGdtLCBsb2MsIHNpemUsIHZOb2RlKTtcbiAgLy9TcHJpbmcsIHJlcHVsc2lvbiBhbmQgZ3Jhdml0YXRpb25hbCBmb3JjZXMgYWN0aW5nIG9uIHRoaXMgbm9kZVxuICB0aGlzLnNwcmluZ0ZvcmNlWCA9IDA7XG4gIHRoaXMuc3ByaW5nRm9yY2VZID0gMDtcbiAgdGhpcy5yZXB1bHNpb25Gb3JjZVggPSAwO1xuICB0aGlzLnJlcHVsc2lvbkZvcmNlWSA9IDA7XG4gIHRoaXMuZ3Jhdml0YXRpb25Gb3JjZVggPSAwO1xuICB0aGlzLmdyYXZpdGF0aW9uRm9yY2VZID0gMDtcbiAgLy9BbW91bnQgYnkgd2hpY2ggdGhpcyBub2RlIGlzIHRvIGJlIG1vdmVkIGluIHRoaXMgaXRlcmF0aW9uXG4gIHRoaXMuZGlzcGxhY2VtZW50WCA9IDA7XG4gIHRoaXMuZGlzcGxhY2VtZW50WSA9IDA7XG5cbiAgLy9TdGFydCBhbmQgZmluaXNoIGdyaWQgY29vcmRpbmF0ZXMgdGhhdCB0aGlzIG5vZGUgaXMgZmFsbGVuIGludG9cbiAgdGhpcy5zdGFydFggPSAwO1xuICB0aGlzLmZpbmlzaFggPSAwO1xuICB0aGlzLnN0YXJ0WSA9IDA7XG4gIHRoaXMuZmluaXNoWSA9IDA7XG5cbiAgLy9HZW9tZXRyaWMgbmVpZ2hib3JzIG9mIHRoaXMgbm9kZVxuICB0aGlzLnN1cnJvdW5kaW5nID0gW107XG59XG5cbkZETGF5b3V0Tm9kZS5wcm90b3R5cGUgPSBPYmplY3QuY3JlYXRlKExOb2RlLnByb3RvdHlwZSk7XG5cbmZvciAodmFyIHByb3AgaW4gTE5vZGUpIHtcbiAgRkRMYXlvdXROb2RlW3Byb3BdID0gTE5vZGVbcHJvcF07XG59XG5cbkZETGF5b3V0Tm9kZS5wcm90b3R5cGUuc2V0R3JpZENvb3JkaW5hdGVzID0gZnVuY3Rpb24gKF9zdGFydFgsIF9maW5pc2hYLCBfc3RhcnRZLCBfZmluaXNoWSlcbntcbiAgdGhpcy5zdGFydFggPSBfc3RhcnRYO1xuICB0aGlzLmZpbmlzaFggPSBfZmluaXNoWDtcbiAgdGhpcy5zdGFydFkgPSBfc3RhcnRZO1xuICB0aGlzLmZpbmlzaFkgPSBfZmluaXNoWTtcblxufTtcblxubW9kdWxlLmV4cG9ydHMgPSBGRExheW91dE5vZGU7XG4iLCJ2YXIgVW5pcXVlSURHZW5lcmV0b3IgPSByZXF1aXJlKCcuL1VuaXF1ZUlER2VuZXJldG9yJyk7XG5cbmZ1bmN0aW9uIEhhc2hNYXAoKSB7XG4gIHRoaXMubWFwID0ge307XG4gIHRoaXMua2V5cyA9IFtdO1xufVxuXG5IYXNoTWFwLnByb3RvdHlwZS5wdXQgPSBmdW5jdGlvbiAoa2V5LCB2YWx1ZSkge1xuICB2YXIgdGhlSWQgPSBVbmlxdWVJREdlbmVyZXRvci5jcmVhdGVJRChrZXkpO1xuICBpZiAoIXRoaXMuY29udGFpbnModGhlSWQpKSB7XG4gICAgdGhpcy5tYXBbdGhlSWRdID0gdmFsdWU7XG4gICAgdGhpcy5rZXlzLnB1c2goa2V5KTtcbiAgfVxufTtcblxuSGFzaE1hcC5wcm90b3R5cGUuY29udGFpbnMgPSBmdW5jdGlvbiAoa2V5KSB7XG4gIHZhciB0aGVJZCA9IFVuaXF1ZUlER2VuZXJldG9yLmNyZWF0ZUlEKGtleSk7XG4gIHJldHVybiB0aGlzLm1hcFtrZXldICE9IG51bGw7XG59O1xuXG5IYXNoTWFwLnByb3RvdHlwZS5nZXQgPSBmdW5jdGlvbiAoa2V5KSB7XG4gIHZhciB0aGVJZCA9IFVuaXF1ZUlER2VuZXJldG9yLmNyZWF0ZUlEKGtleSk7XG4gIHJldHVybiB0aGlzLm1hcFt0aGVJZF07XG59O1xuXG5IYXNoTWFwLnByb3RvdHlwZS5rZXlTZXQgPSBmdW5jdGlvbiAoKSB7XG4gIHJldHVybiB0aGlzLmtleXM7XG59O1xuXG5tb2R1bGUuZXhwb3J0cyA9IEhhc2hNYXA7XG4iLCJ2YXIgVW5pcXVlSURHZW5lcmV0b3IgPSByZXF1aXJlKCcuL1VuaXF1ZUlER2VuZXJldG9yJyk7XG5cbmZ1bmN0aW9uIEhhc2hTZXQoKSB7XG4gIHRoaXMuc2V0ID0ge307XG59XG47XG5cbkhhc2hTZXQucHJvdG90eXBlLmFkZCA9IGZ1bmN0aW9uIChvYmopIHtcbiAgdmFyIHRoZUlkID0gVW5pcXVlSURHZW5lcmV0b3IuY3JlYXRlSUQob2JqKTtcbiAgaWYgKCF0aGlzLmNvbnRhaW5zKHRoZUlkKSlcbiAgICB0aGlzLnNldFt0aGVJZF0gPSBvYmo7XG59O1xuXG5IYXNoU2V0LnByb3RvdHlwZS5yZW1vdmUgPSBmdW5jdGlvbiAob2JqKSB7XG4gIGRlbGV0ZSB0aGlzLnNldFtVbmlxdWVJREdlbmVyZXRvci5jcmVhdGVJRChvYmopXTtcbn07XG5cbkhhc2hTZXQucHJvdG90eXBlLmNsZWFyID0gZnVuY3Rpb24gKCkge1xuICB0aGlzLnNldCA9IHt9O1xufTtcblxuSGFzaFNldC5wcm90b3R5cGUuY29udGFpbnMgPSBmdW5jdGlvbiAob2JqKSB7XG4gIHJldHVybiB0aGlzLnNldFtVbmlxdWVJREdlbmVyZXRvci5jcmVhdGVJRChvYmopXSA9PSBvYmo7XG59O1xuXG5IYXNoU2V0LnByb3RvdHlwZS5pc0VtcHR5ID0gZnVuY3Rpb24gKCkge1xuICByZXR1cm4gdGhpcy5zaXplKCkgPT09IDA7XG59O1xuXG5IYXNoU2V0LnByb3RvdHlwZS5zaXplID0gZnVuY3Rpb24gKCkge1xuICByZXR1cm4gT2JqZWN0LmtleXModGhpcy5zZXQpLmxlbmd0aDtcbn07XG5cbi8vY29uY2F0cyB0aGlzLnNldCB0byB0aGUgZ2l2ZW4gbGlzdFxuSGFzaFNldC5wcm90b3R5cGUuYWRkQWxsVG8gPSBmdW5jdGlvbiAobGlzdCkge1xuICB2YXIga2V5cyA9IE9iamVjdC5rZXlzKHRoaXMuc2V0KTtcbiAgdmFyIGxlbmd0aCA9IGtleXMubGVuZ3RoO1xuICBmb3IgKHZhciBpID0gMDsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgbGlzdC5wdXNoKHRoaXMuc2V0W2tleXNbaV1dKTtcbiAgfVxufTtcblxuSGFzaFNldC5wcm90b3R5cGUuc2l6ZSA9IGZ1bmN0aW9uICgpIHtcbiAgcmV0dXJuIE9iamVjdC5rZXlzKHRoaXMuc2V0KS5sZW5ndGg7XG59O1xuXG5IYXNoU2V0LnByb3RvdHlwZS5hZGRBbGwgPSBmdW5jdGlvbiAobGlzdCkge1xuICB2YXIgcyA9IGxpc3QubGVuZ3RoO1xuICBmb3IgKHZhciBpID0gMDsgaSA8IHM7IGkrKykge1xuICAgIHZhciB2ID0gbGlzdFtpXTtcbiAgICB0aGlzLmFkZCh2KTtcbiAgfVxufTtcblxubW9kdWxlLmV4cG9ydHMgPSBIYXNoU2V0O1xuIiwiZnVuY3Rpb24gSUdlb21ldHJ5KCkge1xufVxuXG5JR2VvbWV0cnkuY2FsY1NlcGFyYXRpb25BbW91bnQgPSBmdW5jdGlvbiAocmVjdEEsIHJlY3RCLCBvdmVybGFwQW1vdW50LCBzZXBhcmF0aW9uQnVmZmVyKVxue1xuICBpZiAoIXJlY3RBLmludGVyc2VjdHMocmVjdEIpKSB7XG4gICAgdGhyb3cgXCJhc3NlcnQgZmFpbGVkXCI7XG4gIH1cbiAgdmFyIGRpcmVjdGlvbnMgPSBuZXcgQXJyYXkoMik7XG4gIElHZW9tZXRyeS5kZWNpZGVEaXJlY3Rpb25zRm9yT3ZlcmxhcHBpbmdOb2RlcyhyZWN0QSwgcmVjdEIsIGRpcmVjdGlvbnMpO1xuICBvdmVybGFwQW1vdW50WzBdID0gTWF0aC5taW4ocmVjdEEuZ2V0UmlnaHQoKSwgcmVjdEIuZ2V0UmlnaHQoKSkgLVxuICAgICAgICAgIE1hdGgubWF4KHJlY3RBLngsIHJlY3RCLngpO1xuICBvdmVybGFwQW1vdW50WzFdID0gTWF0aC5taW4ocmVjdEEuZ2V0Qm90dG9tKCksIHJlY3RCLmdldEJvdHRvbSgpKSAtXG4gICAgICAgICAgTWF0aC5tYXgocmVjdEEueSwgcmVjdEIueSk7XG4gIC8vIHVwZGF0ZSB0aGUgb3ZlcmxhcHBpbmcgYW1vdW50cyBmb3IgdGhlIGZvbGxvd2luZyBjYXNlczpcbiAgaWYgKChyZWN0QS5nZXRYKCkgPD0gcmVjdEIuZ2V0WCgpKSAmJiAocmVjdEEuZ2V0UmlnaHQoKSA+PSByZWN0Qi5nZXRSaWdodCgpKSlcbiAge1xuICAgIG92ZXJsYXBBbW91bnRbMF0gKz0gTWF0aC5taW4oKHJlY3RCLmdldFgoKSAtIHJlY3RBLmdldFgoKSksXG4gICAgICAgICAgICAocmVjdEEuZ2V0UmlnaHQoKSAtIHJlY3RCLmdldFJpZ2h0KCkpKTtcbiAgfVxuICBlbHNlIGlmICgocmVjdEIuZ2V0WCgpIDw9IHJlY3RBLmdldFgoKSkgJiYgKHJlY3RCLmdldFJpZ2h0KCkgPj0gcmVjdEEuZ2V0UmlnaHQoKSkpXG4gIHtcbiAgICBvdmVybGFwQW1vdW50WzBdICs9IE1hdGgubWluKChyZWN0QS5nZXRYKCkgLSByZWN0Qi5nZXRYKCkpLFxuICAgICAgICAgICAgKHJlY3RCLmdldFJpZ2h0KCkgLSByZWN0QS5nZXRSaWdodCgpKSk7XG4gIH1cbiAgaWYgKChyZWN0QS5nZXRZKCkgPD0gcmVjdEIuZ2V0WSgpKSAmJiAocmVjdEEuZ2V0Qm90dG9tKCkgPj0gcmVjdEIuZ2V0Qm90dG9tKCkpKVxuICB7XG4gICAgb3ZlcmxhcEFtb3VudFsxXSArPSBNYXRoLm1pbigocmVjdEIuZ2V0WSgpIC0gcmVjdEEuZ2V0WSgpKSxcbiAgICAgICAgICAgIChyZWN0QS5nZXRCb3R0b20oKSAtIHJlY3RCLmdldEJvdHRvbSgpKSk7XG4gIH1cbiAgZWxzZSBpZiAoKHJlY3RCLmdldFkoKSA8PSByZWN0QS5nZXRZKCkpICYmIChyZWN0Qi5nZXRCb3R0b20oKSA+PSByZWN0QS5nZXRCb3R0b20oKSkpXG4gIHtcbiAgICBvdmVybGFwQW1vdW50WzFdICs9IE1hdGgubWluKChyZWN0QS5nZXRZKCkgLSByZWN0Qi5nZXRZKCkpLFxuICAgICAgICAgICAgKHJlY3RCLmdldEJvdHRvbSgpIC0gcmVjdEEuZ2V0Qm90dG9tKCkpKTtcbiAgfVxuXG4gIC8vIGZpbmQgc2xvcGUgb2YgdGhlIGxpbmUgcGFzc2VzIHR3byBjZW50ZXJzXG4gIHZhciBzbG9wZSA9IE1hdGguYWJzKChyZWN0Qi5nZXRDZW50ZXJZKCkgLSByZWN0QS5nZXRDZW50ZXJZKCkpIC9cbiAgICAgICAgICAocmVjdEIuZ2V0Q2VudGVyWCgpIC0gcmVjdEEuZ2V0Q2VudGVyWCgpKSk7XG4gIC8vIGlmIGNlbnRlcnMgYXJlIG92ZXJsYXBwZWRcbiAgaWYgKChyZWN0Qi5nZXRDZW50ZXJZKCkgPT0gcmVjdEEuZ2V0Q2VudGVyWSgpKSAmJlxuICAgICAgICAgIChyZWN0Qi5nZXRDZW50ZXJYKCkgPT0gcmVjdEEuZ2V0Q2VudGVyWCgpKSlcbiAge1xuICAgIC8vIGFzc3VtZSB0aGUgc2xvcGUgaXMgMSAoNDUgZGVncmVlKVxuICAgIHNsb3BlID0gMS4wO1xuICB9XG5cbiAgdmFyIG1vdmVCeVkgPSBzbG9wZSAqIG92ZXJsYXBBbW91bnRbMF07XG4gIHZhciBtb3ZlQnlYID0gb3ZlcmxhcEFtb3VudFsxXSAvIHNsb3BlO1xuICBpZiAob3ZlcmxhcEFtb3VudFswXSA8IG1vdmVCeVgpXG4gIHtcbiAgICBtb3ZlQnlYID0gb3ZlcmxhcEFtb3VudFswXTtcbiAgfVxuICBlbHNlXG4gIHtcbiAgICBtb3ZlQnlZID0gb3ZlcmxhcEFtb3VudFsxXTtcbiAgfVxuICAvLyByZXR1cm4gaGFsZiB0aGUgYW1vdW50IHNvIHRoYXQgaWYgZWFjaCByZWN0YW5nbGUgaXMgbW92ZWQgYnkgdGhlc2VcbiAgLy8gYW1vdW50cyBpbiBvcHBvc2l0ZSBkaXJlY3Rpb25zLCBvdmVybGFwIHdpbGwgYmUgcmVzb2x2ZWRcbiAgb3ZlcmxhcEFtb3VudFswXSA9IC0xICogZGlyZWN0aW9uc1swXSAqICgobW92ZUJ5WCAvIDIpICsgc2VwYXJhdGlvbkJ1ZmZlcik7XG4gIG92ZXJsYXBBbW91bnRbMV0gPSAtMSAqIGRpcmVjdGlvbnNbMV0gKiAoKG1vdmVCeVkgLyAyKSArIHNlcGFyYXRpb25CdWZmZXIpO1xufVxuXG5JR2VvbWV0cnkuZGVjaWRlRGlyZWN0aW9uc0Zvck92ZXJsYXBwaW5nTm9kZXMgPSBmdW5jdGlvbiAocmVjdEEsIHJlY3RCLCBkaXJlY3Rpb25zKVxue1xuICBpZiAocmVjdEEuZ2V0Q2VudGVyWCgpIDwgcmVjdEIuZ2V0Q2VudGVyWCgpKVxuICB7XG4gICAgZGlyZWN0aW9uc1swXSA9IC0xO1xuICB9XG4gIGVsc2VcbiAge1xuICAgIGRpcmVjdGlvbnNbMF0gPSAxO1xuICB9XG5cbiAgaWYgKHJlY3RBLmdldENlbnRlclkoKSA8IHJlY3RCLmdldENlbnRlclkoKSlcbiAge1xuICAgIGRpcmVjdGlvbnNbMV0gPSAtMTtcbiAgfVxuICBlbHNlXG4gIHtcbiAgICBkaXJlY3Rpb25zWzFdID0gMTtcbiAgfVxufVxuXG5JR2VvbWV0cnkuZ2V0SW50ZXJzZWN0aW9uMiA9IGZ1bmN0aW9uIChyZWN0QSwgcmVjdEIsIHJlc3VsdClcbntcbiAgLy9yZXN1bHRbMC0xXSB3aWxsIGNvbnRhaW4gY2xpcFBvaW50IG9mIHJlY3RBLCByZXN1bHRbMi0zXSB3aWxsIGNvbnRhaW4gY2xpcFBvaW50IG9mIHJlY3RCXG4gIHZhciBwMXggPSByZWN0QS5nZXRDZW50ZXJYKCk7XG4gIHZhciBwMXkgPSByZWN0QS5nZXRDZW50ZXJZKCk7XG4gIHZhciBwMnggPSByZWN0Qi5nZXRDZW50ZXJYKCk7XG4gIHZhciBwMnkgPSByZWN0Qi5nZXRDZW50ZXJZKCk7XG5cbiAgLy9pZiB0d28gcmVjdGFuZ2xlcyBpbnRlcnNlY3QsIHRoZW4gY2xpcHBpbmcgcG9pbnRzIGFyZSBjZW50ZXJzXG4gIGlmIChyZWN0QS5pbnRlcnNlY3RzKHJlY3RCKSlcbiAge1xuICAgIHJlc3VsdFswXSA9IHAxeDtcbiAgICByZXN1bHRbMV0gPSBwMXk7XG4gICAgcmVzdWx0WzJdID0gcDJ4O1xuICAgIHJlc3VsdFszXSA9IHAyeTtcbiAgICByZXR1cm4gdHJ1ZTtcbiAgfVxuICAvL3ZhcmlhYmxlcyBmb3IgcmVjdEFcbiAgdmFyIHRvcExlZnRBeCA9IHJlY3RBLmdldFgoKTtcbiAgdmFyIHRvcExlZnRBeSA9IHJlY3RBLmdldFkoKTtcbiAgdmFyIHRvcFJpZ2h0QXggPSByZWN0QS5nZXRSaWdodCgpO1xuICB2YXIgYm90dG9tTGVmdEF4ID0gcmVjdEEuZ2V0WCgpO1xuICB2YXIgYm90dG9tTGVmdEF5ID0gcmVjdEEuZ2V0Qm90dG9tKCk7XG4gIHZhciBib3R0b21SaWdodEF4ID0gcmVjdEEuZ2V0UmlnaHQoKTtcbiAgdmFyIGhhbGZXaWR0aEEgPSByZWN0QS5nZXRXaWR0aEhhbGYoKTtcbiAgdmFyIGhhbGZIZWlnaHRBID0gcmVjdEEuZ2V0SGVpZ2h0SGFsZigpO1xuICAvL3ZhcmlhYmxlcyBmb3IgcmVjdEJcbiAgdmFyIHRvcExlZnRCeCA9IHJlY3RCLmdldFgoKTtcbiAgdmFyIHRvcExlZnRCeSA9IHJlY3RCLmdldFkoKTtcbiAgdmFyIHRvcFJpZ2h0QnggPSByZWN0Qi5nZXRSaWdodCgpO1xuICB2YXIgYm90dG9tTGVmdEJ4ID0gcmVjdEIuZ2V0WCgpO1xuICB2YXIgYm90dG9tTGVmdEJ5ID0gcmVjdEIuZ2V0Qm90dG9tKCk7XG4gIHZhciBib3R0b21SaWdodEJ4ID0gcmVjdEIuZ2V0UmlnaHQoKTtcbiAgdmFyIGhhbGZXaWR0aEIgPSByZWN0Qi5nZXRXaWR0aEhhbGYoKTtcbiAgdmFyIGhhbGZIZWlnaHRCID0gcmVjdEIuZ2V0SGVpZ2h0SGFsZigpO1xuICAvL2ZsYWcgd2hldGhlciBjbGlwcGluZyBwb2ludHMgYXJlIGZvdW5kXG4gIHZhciBjbGlwUG9pbnRBRm91bmQgPSBmYWxzZTtcbiAgdmFyIGNsaXBQb2ludEJGb3VuZCA9IGZhbHNlO1xuXG4gIC8vIGxpbmUgaXMgdmVydGljYWxcbiAgaWYgKHAxeCA9PSBwMngpXG4gIHtcbiAgICBpZiAocDF5ID4gcDJ5KVxuICAgIHtcbiAgICAgIHJlc3VsdFswXSA9IHAxeDtcbiAgICAgIHJlc3VsdFsxXSA9IHRvcExlZnRBeTtcbiAgICAgIHJlc3VsdFsyXSA9IHAyeDtcbiAgICAgIHJlc3VsdFszXSA9IGJvdHRvbUxlZnRCeTtcbiAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG4gICAgZWxzZSBpZiAocDF5IDwgcDJ5KVxuICAgIHtcbiAgICAgIHJlc3VsdFswXSA9IHAxeDtcbiAgICAgIHJlc3VsdFsxXSA9IGJvdHRvbUxlZnRBeTtcbiAgICAgIHJlc3VsdFsyXSA9IHAyeDtcbiAgICAgIHJlc3VsdFszXSA9IHRvcExlZnRCeTtcbiAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG4gICAgZWxzZVxuICAgIHtcbiAgICAgIC8vbm90IGxpbmUsIHJldHVybiBudWxsO1xuICAgIH1cbiAgfVxuICAvLyBsaW5lIGlzIGhvcml6b250YWxcbiAgZWxzZSBpZiAocDF5ID09IHAyeSlcbiAge1xuICAgIGlmIChwMXggPiBwMngpXG4gICAge1xuICAgICAgcmVzdWx0WzBdID0gdG9wTGVmdEF4O1xuICAgICAgcmVzdWx0WzFdID0gcDF5O1xuICAgICAgcmVzdWx0WzJdID0gdG9wUmlnaHRCeDtcbiAgICAgIHJlc3VsdFszXSA9IHAyeTtcbiAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG4gICAgZWxzZSBpZiAocDF4IDwgcDJ4KVxuICAgIHtcbiAgICAgIHJlc3VsdFswXSA9IHRvcFJpZ2h0QXg7XG4gICAgICByZXN1bHRbMV0gPSBwMXk7XG4gICAgICByZXN1bHRbMl0gPSB0b3BMZWZ0Qng7XG4gICAgICByZXN1bHRbM10gPSBwMnk7XG4gICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICAgIGVsc2VcbiAgICB7XG4gICAgICAvL25vdCB2YWxpZCBsaW5lLCByZXR1cm4gbnVsbDtcbiAgICB9XG4gIH1cbiAgZWxzZVxuICB7XG4gICAgLy9zbG9wZXMgb2YgcmVjdEEncyBhbmQgcmVjdEIncyBkaWFnb25hbHNcbiAgICB2YXIgc2xvcGVBID0gcmVjdEEuaGVpZ2h0IC8gcmVjdEEud2lkdGg7XG4gICAgdmFyIHNsb3BlQiA9IHJlY3RCLmhlaWdodCAvIHJlY3RCLndpZHRoO1xuXG4gICAgLy9zbG9wZSBvZiBsaW5lIGJldHdlZW4gY2VudGVyIG9mIHJlY3RBIGFuZCBjZW50ZXIgb2YgcmVjdEJcbiAgICB2YXIgc2xvcGVQcmltZSA9IChwMnkgLSBwMXkpIC8gKHAyeCAtIHAxeCk7XG4gICAgdmFyIGNhcmRpbmFsRGlyZWN0aW9uQTtcbiAgICB2YXIgY2FyZGluYWxEaXJlY3Rpb25CO1xuICAgIHZhciB0ZW1wUG9pbnRBeDtcbiAgICB2YXIgdGVtcFBvaW50QXk7XG4gICAgdmFyIHRlbXBQb2ludEJ4O1xuICAgIHZhciB0ZW1wUG9pbnRCeTtcblxuICAgIC8vZGV0ZXJtaW5lIHdoZXRoZXIgY2xpcHBpbmcgcG9pbnQgaXMgdGhlIGNvcm5lciBvZiBub2RlQVxuICAgIGlmICgoLXNsb3BlQSkgPT0gc2xvcGVQcmltZSlcbiAgICB7XG4gICAgICBpZiAocDF4ID4gcDJ4KVxuICAgICAge1xuICAgICAgICByZXN1bHRbMF0gPSBib3R0b21MZWZ0QXg7XG4gICAgICAgIHJlc3VsdFsxXSA9IGJvdHRvbUxlZnRBeTtcbiAgICAgICAgY2xpcFBvaW50QUZvdW5kID0gdHJ1ZTtcbiAgICAgIH1cbiAgICAgIGVsc2VcbiAgICAgIHtcbiAgICAgICAgcmVzdWx0WzBdID0gdG9wUmlnaHRBeDtcbiAgICAgICAgcmVzdWx0WzFdID0gdG9wTGVmdEF5O1xuICAgICAgICBjbGlwUG9pbnRBRm91bmQgPSB0cnVlO1xuICAgICAgfVxuICAgIH1cbiAgICBlbHNlIGlmIChzbG9wZUEgPT0gc2xvcGVQcmltZSlcbiAgICB7XG4gICAgICBpZiAocDF4ID4gcDJ4KVxuICAgICAge1xuICAgICAgICByZXN1bHRbMF0gPSB0b3BMZWZ0QXg7XG4gICAgICAgIHJlc3VsdFsxXSA9IHRvcExlZnRBeTtcbiAgICAgICAgY2xpcFBvaW50QUZvdW5kID0gdHJ1ZTtcbiAgICAgIH1cbiAgICAgIGVsc2VcbiAgICAgIHtcbiAgICAgICAgcmVzdWx0WzBdID0gYm90dG9tUmlnaHRBeDtcbiAgICAgICAgcmVzdWx0WzFdID0gYm90dG9tTGVmdEF5O1xuICAgICAgICBjbGlwUG9pbnRBRm91bmQgPSB0cnVlO1xuICAgICAgfVxuICAgIH1cblxuICAgIC8vZGV0ZXJtaW5lIHdoZXRoZXIgY2xpcHBpbmcgcG9pbnQgaXMgdGhlIGNvcm5lciBvZiBub2RlQlxuICAgIGlmICgoLXNsb3BlQikgPT0gc2xvcGVQcmltZSlcbiAgICB7XG4gICAgICBpZiAocDJ4ID4gcDF4KVxuICAgICAge1xuICAgICAgICByZXN1bHRbMl0gPSBib3R0b21MZWZ0Qng7XG4gICAgICAgIHJlc3VsdFszXSA9IGJvdHRvbUxlZnRCeTtcbiAgICAgICAgY2xpcFBvaW50QkZvdW5kID0gdHJ1ZTtcbiAgICAgIH1cbiAgICAgIGVsc2VcbiAgICAgIHtcbiAgICAgICAgcmVzdWx0WzJdID0gdG9wUmlnaHRCeDtcbiAgICAgICAgcmVzdWx0WzNdID0gdG9wTGVmdEJ5O1xuICAgICAgICBjbGlwUG9pbnRCRm91bmQgPSB0cnVlO1xuICAgICAgfVxuICAgIH1cbiAgICBlbHNlIGlmIChzbG9wZUIgPT0gc2xvcGVQcmltZSlcbiAgICB7XG4gICAgICBpZiAocDJ4ID4gcDF4KVxuICAgICAge1xuICAgICAgICByZXN1bHRbMl0gPSB0b3BMZWZ0Qng7XG4gICAgICAgIHJlc3VsdFszXSA9IHRvcExlZnRCeTtcbiAgICAgICAgY2xpcFBvaW50QkZvdW5kID0gdHJ1ZTtcbiAgICAgIH1cbiAgICAgIGVsc2VcbiAgICAgIHtcbiAgICAgICAgcmVzdWx0WzJdID0gYm90dG9tUmlnaHRCeDtcbiAgICAgICAgcmVzdWx0WzNdID0gYm90dG9tTGVmdEJ5O1xuICAgICAgICBjbGlwUG9pbnRCRm91bmQgPSB0cnVlO1xuICAgICAgfVxuICAgIH1cblxuICAgIC8vaWYgYm90aCBjbGlwcGluZyBwb2ludHMgYXJlIGNvcm5lcnNcbiAgICBpZiAoY2xpcFBvaW50QUZvdW5kICYmIGNsaXBQb2ludEJGb3VuZClcbiAgICB7XG4gICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuXG4gICAgLy9kZXRlcm1pbmUgQ2FyZGluYWwgRGlyZWN0aW9uIG9mIHJlY3RhbmdsZXNcbiAgICBpZiAocDF4ID4gcDJ4KVxuICAgIHtcbiAgICAgIGlmIChwMXkgPiBwMnkpXG4gICAgICB7XG4gICAgICAgIGNhcmRpbmFsRGlyZWN0aW9uQSA9IElHZW9tZXRyeS5nZXRDYXJkaW5hbERpcmVjdGlvbihzbG9wZUEsIHNsb3BlUHJpbWUsIDQpO1xuICAgICAgICBjYXJkaW5hbERpcmVjdGlvbkIgPSBJR2VvbWV0cnkuZ2V0Q2FyZGluYWxEaXJlY3Rpb24oc2xvcGVCLCBzbG9wZVByaW1lLCAyKTtcbiAgICAgIH1cbiAgICAgIGVsc2VcbiAgICAgIHtcbiAgICAgICAgY2FyZGluYWxEaXJlY3Rpb25BID0gSUdlb21ldHJ5LmdldENhcmRpbmFsRGlyZWN0aW9uKC1zbG9wZUEsIHNsb3BlUHJpbWUsIDMpO1xuICAgICAgICBjYXJkaW5hbERpcmVjdGlvbkIgPSBJR2VvbWV0cnkuZ2V0Q2FyZGluYWxEaXJlY3Rpb24oLXNsb3BlQiwgc2xvcGVQcmltZSwgMSk7XG4gICAgICB9XG4gICAgfVxuICAgIGVsc2VcbiAgICB7XG4gICAgICBpZiAocDF5ID4gcDJ5KVxuICAgICAge1xuICAgICAgICBjYXJkaW5hbERpcmVjdGlvbkEgPSBJR2VvbWV0cnkuZ2V0Q2FyZGluYWxEaXJlY3Rpb24oLXNsb3BlQSwgc2xvcGVQcmltZSwgMSk7XG4gICAgICAgIGNhcmRpbmFsRGlyZWN0aW9uQiA9IElHZW9tZXRyeS5nZXRDYXJkaW5hbERpcmVjdGlvbigtc2xvcGVCLCBzbG9wZVByaW1lLCAzKTtcbiAgICAgIH1cbiAgICAgIGVsc2VcbiAgICAgIHtcbiAgICAgICAgY2FyZGluYWxEaXJlY3Rpb25BID0gSUdlb21ldHJ5LmdldENhcmRpbmFsRGlyZWN0aW9uKHNsb3BlQSwgc2xvcGVQcmltZSwgMik7XG4gICAgICAgIGNhcmRpbmFsRGlyZWN0aW9uQiA9IElHZW9tZXRyeS5nZXRDYXJkaW5hbERpcmVjdGlvbihzbG9wZUIsIHNsb3BlUHJpbWUsIDQpO1xuICAgICAgfVxuICAgIH1cbiAgICAvL2NhbGN1bGF0ZSBjbGlwcGluZyBQb2ludCBpZiBpdCBpcyBub3QgZm91bmQgYmVmb3JlXG4gICAgaWYgKCFjbGlwUG9pbnRBRm91bmQpXG4gICAge1xuICAgICAgc3dpdGNoIChjYXJkaW5hbERpcmVjdGlvbkEpXG4gICAgICB7XG4gICAgICAgIGNhc2UgMTpcbiAgICAgICAgICB0ZW1wUG9pbnRBeSA9IHRvcExlZnRBeTtcbiAgICAgICAgICB0ZW1wUG9pbnRBeCA9IHAxeCArICgtaGFsZkhlaWdodEEpIC8gc2xvcGVQcmltZTtcbiAgICAgICAgICByZXN1bHRbMF0gPSB0ZW1wUG9pbnRBeDtcbiAgICAgICAgICByZXN1bHRbMV0gPSB0ZW1wUG9pbnRBeTtcbiAgICAgICAgICBicmVhaztcbiAgICAgICAgY2FzZSAyOlxuICAgICAgICAgIHRlbXBQb2ludEF4ID0gYm90dG9tUmlnaHRBeDtcbiAgICAgICAgICB0ZW1wUG9pbnRBeSA9IHAxeSArIGhhbGZXaWR0aEEgKiBzbG9wZVByaW1lO1xuICAgICAgICAgIHJlc3VsdFswXSA9IHRlbXBQb2ludEF4O1xuICAgICAgICAgIHJlc3VsdFsxXSA9IHRlbXBQb2ludEF5O1xuICAgICAgICAgIGJyZWFrO1xuICAgICAgICBjYXNlIDM6XG4gICAgICAgICAgdGVtcFBvaW50QXkgPSBib3R0b21MZWZ0QXk7XG4gICAgICAgICAgdGVtcFBvaW50QXggPSBwMXggKyBoYWxmSGVpZ2h0QSAvIHNsb3BlUHJpbWU7XG4gICAgICAgICAgcmVzdWx0WzBdID0gdGVtcFBvaW50QXg7XG4gICAgICAgICAgcmVzdWx0WzFdID0gdGVtcFBvaW50QXk7XG4gICAgICAgICAgYnJlYWs7XG4gICAgICAgIGNhc2UgNDpcbiAgICAgICAgICB0ZW1wUG9pbnRBeCA9IGJvdHRvbUxlZnRBeDtcbiAgICAgICAgICB0ZW1wUG9pbnRBeSA9IHAxeSArICgtaGFsZldpZHRoQSkgKiBzbG9wZVByaW1lO1xuICAgICAgICAgIHJlc3VsdFswXSA9IHRlbXBQb2ludEF4O1xuICAgICAgICAgIHJlc3VsdFsxXSA9IHRlbXBQb2ludEF5O1xuICAgICAgICAgIGJyZWFrO1xuICAgICAgfVxuICAgIH1cbiAgICBpZiAoIWNsaXBQb2ludEJGb3VuZClcbiAgICB7XG4gICAgICBzd2l0Y2ggKGNhcmRpbmFsRGlyZWN0aW9uQilcbiAgICAgIHtcbiAgICAgICAgY2FzZSAxOlxuICAgICAgICAgIHRlbXBQb2ludEJ5ID0gdG9wTGVmdEJ5O1xuICAgICAgICAgIHRlbXBQb2ludEJ4ID0gcDJ4ICsgKC1oYWxmSGVpZ2h0QikgLyBzbG9wZVByaW1lO1xuICAgICAgICAgIHJlc3VsdFsyXSA9IHRlbXBQb2ludEJ4O1xuICAgICAgICAgIHJlc3VsdFszXSA9IHRlbXBQb2ludEJ5O1xuICAgICAgICAgIGJyZWFrO1xuICAgICAgICBjYXNlIDI6XG4gICAgICAgICAgdGVtcFBvaW50QnggPSBib3R0b21SaWdodEJ4O1xuICAgICAgICAgIHRlbXBQb2ludEJ5ID0gcDJ5ICsgaGFsZldpZHRoQiAqIHNsb3BlUHJpbWU7XG4gICAgICAgICAgcmVzdWx0WzJdID0gdGVtcFBvaW50Qng7XG4gICAgICAgICAgcmVzdWx0WzNdID0gdGVtcFBvaW50Qnk7XG4gICAgICAgICAgYnJlYWs7XG4gICAgICAgIGNhc2UgMzpcbiAgICAgICAgICB0ZW1wUG9pbnRCeSA9IGJvdHRvbUxlZnRCeTtcbiAgICAgICAgICB0ZW1wUG9pbnRCeCA9IHAyeCArIGhhbGZIZWlnaHRCIC8gc2xvcGVQcmltZTtcbiAgICAgICAgICByZXN1bHRbMl0gPSB0ZW1wUG9pbnRCeDtcbiAgICAgICAgICByZXN1bHRbM10gPSB0ZW1wUG9pbnRCeTtcbiAgICAgICAgICBicmVhaztcbiAgICAgICAgY2FzZSA0OlxuICAgICAgICAgIHRlbXBQb2ludEJ4ID0gYm90dG9tTGVmdEJ4O1xuICAgICAgICAgIHRlbXBQb2ludEJ5ID0gcDJ5ICsgKC1oYWxmV2lkdGhCKSAqIHNsb3BlUHJpbWU7XG4gICAgICAgICAgcmVzdWx0WzJdID0gdGVtcFBvaW50Qng7XG4gICAgICAgICAgcmVzdWx0WzNdID0gdGVtcFBvaW50Qnk7XG4gICAgICAgICAgYnJlYWs7XG4gICAgICB9XG4gICAgfVxuICB9XG4gIHJldHVybiBmYWxzZTtcbn1cblxuSUdlb21ldHJ5LmdldENhcmRpbmFsRGlyZWN0aW9uID0gZnVuY3Rpb24gKHNsb3BlLCBzbG9wZVByaW1lLCBsaW5lKVxue1xuICBpZiAoc2xvcGUgPiBzbG9wZVByaW1lKVxuICB7XG4gICAgcmV0dXJuIGxpbmU7XG4gIH1cbiAgZWxzZVxuICB7XG4gICAgcmV0dXJuIDEgKyBsaW5lICUgNDtcbiAgfVxufVxuXG5JR2VvbWV0cnkuZ2V0SW50ZXJzZWN0aW9uID0gZnVuY3Rpb24gKHMxLCBzMiwgZjEsIGYyKVxue1xuICBpZiAoZjIgPT0gbnVsbCkge1xuICAgIHJldHVybiBJR2VvbWV0cnkuZ2V0SW50ZXJzZWN0aW9uMihzMSwgczIsIGYxKTtcbiAgfVxuICB2YXIgeDEgPSBzMS54O1xuICB2YXIgeTEgPSBzMS55O1xuICB2YXIgeDIgPSBzMi54O1xuICB2YXIgeTIgPSBzMi55O1xuICB2YXIgeDMgPSBmMS54O1xuICB2YXIgeTMgPSBmMS55O1xuICB2YXIgeDQgPSBmMi54O1xuICB2YXIgeTQgPSBmMi55O1xuICB2YXIgeCwgeTsgLy8gaW50ZXJzZWN0aW9uIHBvaW50XG4gIHZhciBhMSwgYTIsIGIxLCBiMiwgYzEsIGMyOyAvLyBjb2VmZmljaWVudHMgb2YgbGluZSBlcW5zLlxuICB2YXIgZGVub207XG5cbiAgYTEgPSB5MiAtIHkxO1xuICBiMSA9IHgxIC0geDI7XG4gIGMxID0geDIgKiB5MSAtIHgxICogeTI7ICAvLyB7IGExKnggKyBiMSp5ICsgYzEgPSAwIGlzIGxpbmUgMSB9XG5cbiAgYTIgPSB5NCAtIHkzO1xuICBiMiA9IHgzIC0geDQ7XG4gIGMyID0geDQgKiB5MyAtIHgzICogeTQ7ICAvLyB7IGEyKnggKyBiMip5ICsgYzIgPSAwIGlzIGxpbmUgMiB9XG5cbiAgZGVub20gPSBhMSAqIGIyIC0gYTIgKiBiMTtcblxuICBpZiAoZGVub20gPT0gMClcbiAge1xuICAgIHJldHVybiBudWxsO1xuICB9XG5cbiAgeCA9IChiMSAqIGMyIC0gYjIgKiBjMSkgLyBkZW5vbTtcbiAgeSA9IChhMiAqIGMxIC0gYTEgKiBjMikgLyBkZW5vbTtcblxuICByZXR1cm4gbmV3IFBvaW50KHgsIHkpO1xufVxuXG4vLyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuLy8gU2VjdGlvbjogQ2xhc3MgQ29uc3RhbnRzXG4vLyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuLyoqXG4gKiBTb21lIHVzZWZ1bCBwcmUtY2FsY3VsYXRlZCBjb25zdGFudHNcbiAqL1xuSUdlb21ldHJ5LkhBTEZfUEkgPSAwLjUgKiBNYXRoLlBJO1xuSUdlb21ldHJ5Lk9ORV9BTkRfSEFMRl9QSSA9IDEuNSAqIE1hdGguUEk7XG5JR2VvbWV0cnkuVFdPX1BJID0gMi4wICogTWF0aC5QSTtcbklHZW9tZXRyeS5USFJFRV9QSSA9IDMuMCAqIE1hdGguUEk7XG5cbm1vZHVsZS5leHBvcnRzID0gSUdlb21ldHJ5O1xuIiwiZnVuY3Rpb24gSU1hdGgoKSB7XG59XG5cbi8qKlxuICogVGhpcyBtZXRob2QgcmV0dXJucyB0aGUgc2lnbiBvZiB0aGUgaW5wdXQgdmFsdWUuXG4gKi9cbklNYXRoLnNpZ24gPSBmdW5jdGlvbiAodmFsdWUpIHtcbiAgaWYgKHZhbHVlID4gMClcbiAge1xuICAgIHJldHVybiAxO1xuICB9XG4gIGVsc2UgaWYgKHZhbHVlIDwgMClcbiAge1xuICAgIHJldHVybiAtMTtcbiAgfVxuICBlbHNlXG4gIHtcbiAgICByZXR1cm4gMDtcbiAgfVxufVxuXG5JTWF0aC5mbG9vciA9IGZ1bmN0aW9uICh2YWx1ZSkge1xuICByZXR1cm4gdmFsdWUgPCAwID8gTWF0aC5jZWlsKHZhbHVlKSA6IE1hdGguZmxvb3IodmFsdWUpO1xufVxuXG5JTWF0aC5jZWlsID0gZnVuY3Rpb24gKHZhbHVlKSB7XG4gIHJldHVybiB2YWx1ZSA8IDAgPyBNYXRoLmZsb29yKHZhbHVlKSA6IE1hdGguY2VpbCh2YWx1ZSk7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gSU1hdGg7XG4iLCJmdW5jdGlvbiBJbnRlZ2VyKCkge1xufVxuXG5JbnRlZ2VyLk1BWF9WQUxVRSA9IDIxNDc0ODM2NDc7XG5JbnRlZ2VyLk1JTl9WQUxVRSA9IC0yMTQ3NDgzNjQ4O1xuXG5tb2R1bGUuZXhwb3J0cyA9IEludGVnZXI7XG4iLCJ2YXIgTEdyYXBoT2JqZWN0ID0gcmVxdWlyZSgnLi9MR3JhcGhPYmplY3QnKTtcbnZhciBJR2VvbWV0cnkgPSByZXF1aXJlKCcuL0lHZW9tZXRyeScpO1xudmFyIElNYXRoID0gcmVxdWlyZSgnLi9JTWF0aCcpO1xuXG5mdW5jdGlvbiBMRWRnZShzb3VyY2UsIHRhcmdldCwgdkVkZ2UpIHtcbiAgTEdyYXBoT2JqZWN0LmNhbGwodGhpcywgdkVkZ2UpO1xuXG4gIHRoaXMuaXNPdmVybGFwaW5nU291cmNlQW5kVGFyZ2V0ID0gZmFsc2U7XG4gIHRoaXMudkdyYXBoT2JqZWN0ID0gdkVkZ2U7XG4gIHRoaXMuYmVuZHBvaW50cyA9IFtdO1xuICB0aGlzLnNvdXJjZSA9IHNvdXJjZTtcbiAgdGhpcy50YXJnZXQgPSB0YXJnZXQ7XG59XG5cbkxFZGdlLnByb3RvdHlwZSA9IE9iamVjdC5jcmVhdGUoTEdyYXBoT2JqZWN0LnByb3RvdHlwZSk7XG5cbmZvciAodmFyIHByb3AgaW4gTEdyYXBoT2JqZWN0KSB7XG4gIExFZGdlW3Byb3BdID0gTEdyYXBoT2JqZWN0W3Byb3BdO1xufVxuXG5MRWRnZS5wcm90b3R5cGUuZ2V0U291cmNlID0gZnVuY3Rpb24gKClcbntcbiAgcmV0dXJuIHRoaXMuc291cmNlO1xufTtcblxuTEVkZ2UucHJvdG90eXBlLmdldFRhcmdldCA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLnRhcmdldDtcbn07XG5cbkxFZGdlLnByb3RvdHlwZS5pc0ludGVyR3JhcGggPSBmdW5jdGlvbiAoKVxue1xuICByZXR1cm4gdGhpcy5pc0ludGVyR3JhcGg7XG59O1xuXG5MRWRnZS5wcm90b3R5cGUuZ2V0TGVuZ3RoID0gZnVuY3Rpb24gKClcbntcbiAgcmV0dXJuIHRoaXMubGVuZ3RoO1xufTtcblxuTEVkZ2UucHJvdG90eXBlLmlzT3ZlcmxhcGluZ1NvdXJjZUFuZFRhcmdldCA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLmlzT3ZlcmxhcGluZ1NvdXJjZUFuZFRhcmdldDtcbn07XG5cbkxFZGdlLnByb3RvdHlwZS5nZXRCZW5kcG9pbnRzID0gZnVuY3Rpb24gKClcbntcbiAgcmV0dXJuIHRoaXMuYmVuZHBvaW50cztcbn07XG5cbkxFZGdlLnByb3RvdHlwZS5nZXRMY2EgPSBmdW5jdGlvbiAoKVxue1xuICByZXR1cm4gdGhpcy5sY2E7XG59O1xuXG5MRWRnZS5wcm90b3R5cGUuZ2V0U291cmNlSW5MY2EgPSBmdW5jdGlvbiAoKVxue1xuICByZXR1cm4gdGhpcy5zb3VyY2VJbkxjYTtcbn07XG5cbkxFZGdlLnByb3RvdHlwZS5nZXRUYXJnZXRJbkxjYSA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLnRhcmdldEluTGNhO1xufTtcblxuTEVkZ2UucHJvdG90eXBlLmdldE90aGVyRW5kID0gZnVuY3Rpb24gKG5vZGUpXG57XG4gIGlmICh0aGlzLnNvdXJjZSA9PT0gbm9kZSlcbiAge1xuICAgIHJldHVybiB0aGlzLnRhcmdldDtcbiAgfVxuICBlbHNlIGlmICh0aGlzLnRhcmdldCA9PT0gbm9kZSlcbiAge1xuICAgIHJldHVybiB0aGlzLnNvdXJjZTtcbiAgfVxuICBlbHNlXG4gIHtcbiAgICB0aHJvdyBcIk5vZGUgaXMgbm90IGluY2lkZW50IHdpdGggdGhpcyBlZGdlXCI7XG4gIH1cbn1cblxuTEVkZ2UucHJvdG90eXBlLmdldE90aGVyRW5kSW5HcmFwaCA9IGZ1bmN0aW9uIChub2RlLCBncmFwaClcbntcbiAgdmFyIG90aGVyRW5kID0gdGhpcy5nZXRPdGhlckVuZChub2RlKTtcbiAgdmFyIHJvb3QgPSBncmFwaC5nZXRHcmFwaE1hbmFnZXIoKS5nZXRSb290KCk7XG5cbiAgd2hpbGUgKHRydWUpXG4gIHtcbiAgICBpZiAob3RoZXJFbmQuZ2V0T3duZXIoKSA9PSBncmFwaClcbiAgICB7XG4gICAgICByZXR1cm4gb3RoZXJFbmQ7XG4gICAgfVxuXG4gICAgaWYgKG90aGVyRW5kLmdldE93bmVyKCkgPT0gcm9vdClcbiAgICB7XG4gICAgICBicmVhaztcbiAgICB9XG5cbiAgICBvdGhlckVuZCA9IG90aGVyRW5kLmdldE93bmVyKCkuZ2V0UGFyZW50KCk7XG4gIH1cblxuICByZXR1cm4gbnVsbDtcbn07XG5cbkxFZGdlLnByb3RvdHlwZS51cGRhdGVMZW5ndGggPSBmdW5jdGlvbiAoKVxue1xuICB2YXIgY2xpcFBvaW50Q29vcmRpbmF0ZXMgPSBuZXcgQXJyYXkoNCk7XG5cbiAgdGhpcy5pc092ZXJsYXBpbmdTb3VyY2VBbmRUYXJnZXQgPVxuICAgICAgICAgIElHZW9tZXRyeS5nZXRJbnRlcnNlY3Rpb24odGhpcy50YXJnZXQuZ2V0UmVjdCgpLFxuICAgICAgICAgICAgICAgICAgdGhpcy5zb3VyY2UuZ2V0UmVjdCgpLFxuICAgICAgICAgICAgICAgICAgY2xpcFBvaW50Q29vcmRpbmF0ZXMpO1xuXG4gIGlmICghdGhpcy5pc092ZXJsYXBpbmdTb3VyY2VBbmRUYXJnZXQpXG4gIHtcbiAgICB0aGlzLmxlbmd0aFggPSBjbGlwUG9pbnRDb29yZGluYXRlc1swXSAtIGNsaXBQb2ludENvb3JkaW5hdGVzWzJdO1xuICAgIHRoaXMubGVuZ3RoWSA9IGNsaXBQb2ludENvb3JkaW5hdGVzWzFdIC0gY2xpcFBvaW50Q29vcmRpbmF0ZXNbM107XG5cbiAgICBpZiAoTWF0aC5hYnModGhpcy5sZW5ndGhYKSA8IDEuMClcbiAgICB7XG4gICAgICB0aGlzLmxlbmd0aFggPSBJTWF0aC5zaWduKHRoaXMubGVuZ3RoWCk7XG4gICAgfVxuXG4gICAgaWYgKE1hdGguYWJzKHRoaXMubGVuZ3RoWSkgPCAxLjApXG4gICAge1xuICAgICAgdGhpcy5sZW5ndGhZID0gSU1hdGguc2lnbih0aGlzLmxlbmd0aFkpO1xuICAgIH1cblxuICAgIHRoaXMubGVuZ3RoID0gTWF0aC5zcXJ0KFxuICAgICAgICAgICAgdGhpcy5sZW5ndGhYICogdGhpcy5sZW5ndGhYICsgdGhpcy5sZW5ndGhZICogdGhpcy5sZW5ndGhZKTtcbiAgfVxufTtcblxuTEVkZ2UucHJvdG90eXBlLnVwZGF0ZUxlbmd0aFNpbXBsZSA9IGZ1bmN0aW9uICgpXG57XG4gIHRoaXMubGVuZ3RoWCA9IHRoaXMudGFyZ2V0LmdldENlbnRlclgoKSAtIHRoaXMuc291cmNlLmdldENlbnRlclgoKTtcbiAgdGhpcy5sZW5ndGhZID0gdGhpcy50YXJnZXQuZ2V0Q2VudGVyWSgpIC0gdGhpcy5zb3VyY2UuZ2V0Q2VudGVyWSgpO1xuXG4gIGlmIChNYXRoLmFicyh0aGlzLmxlbmd0aFgpIDwgMS4wKVxuICB7XG4gICAgdGhpcy5sZW5ndGhYID0gSU1hdGguc2lnbih0aGlzLmxlbmd0aFgpO1xuICB9XG5cbiAgaWYgKE1hdGguYWJzKHRoaXMubGVuZ3RoWSkgPCAxLjApXG4gIHtcbiAgICB0aGlzLmxlbmd0aFkgPSBJTWF0aC5zaWduKHRoaXMubGVuZ3RoWSk7XG4gIH1cblxuICB0aGlzLmxlbmd0aCA9IE1hdGguc3FydChcbiAgICAgICAgICB0aGlzLmxlbmd0aFggKiB0aGlzLmxlbmd0aFggKyB0aGlzLmxlbmd0aFkgKiB0aGlzLmxlbmd0aFkpO1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IExFZGdlO1xuIiwidmFyIExHcmFwaE9iamVjdCA9IHJlcXVpcmUoJy4vTEdyYXBoT2JqZWN0Jyk7XG52YXIgSW50ZWdlciA9IHJlcXVpcmUoJy4vSW50ZWdlcicpO1xudmFyIExheW91dENvbnN0YW50cyA9IHJlcXVpcmUoJy4vTGF5b3V0Q29uc3RhbnRzJyk7XG52YXIgTEdyYXBoTWFuYWdlciA9IHJlcXVpcmUoJy4vTEdyYXBoTWFuYWdlcicpO1xudmFyIExOb2RlID0gcmVxdWlyZSgnLi9MTm9kZScpO1xudmFyIEhhc2hTZXQgPSByZXF1aXJlKCcuL0hhc2hTZXQnKTtcbnZhciBSZWN0YW5nbGVEID0gcmVxdWlyZSgnLi9SZWN0YW5nbGVEJyk7XG52YXIgUG9pbnQgPSByZXF1aXJlKCcuL1BvaW50Jyk7XG5cbmZ1bmN0aW9uIExHcmFwaChwYXJlbnQsIG9iajIsIHZHcmFwaCkge1xuICBMR3JhcGhPYmplY3QuY2FsbCh0aGlzLCB2R3JhcGgpO1xuICB0aGlzLmVzdGltYXRlZFNpemUgPSBJbnRlZ2VyLk1JTl9WQUxVRTtcbiAgdGhpcy5tYXJnaW4gPSBMYXlvdXRDb25zdGFudHMuREVGQVVMVF9HUkFQSF9NQVJHSU47XG4gIHRoaXMuZWRnZXMgPSBbXTtcbiAgdGhpcy5ub2RlcyA9IFtdO1xuICB0aGlzLmlzQ29ubmVjdGVkID0gZmFsc2U7XG4gIHRoaXMucGFyZW50ID0gcGFyZW50O1xuXG4gIGlmIChvYmoyICE9IG51bGwgJiYgb2JqMiBpbnN0YW5jZW9mIExHcmFwaE1hbmFnZXIpIHtcbiAgICB0aGlzLmdyYXBoTWFuYWdlciA9IG9iajI7XG4gIH1cbiAgZWxzZSBpZiAob2JqMiAhPSBudWxsICYmIG9iajIgaW5zdGFuY2VvZiBMYXlvdXQpIHtcbiAgICB0aGlzLmdyYXBoTWFuYWdlciA9IG9iajIuZ3JhcGhNYW5hZ2VyO1xuICB9XG59XG5cbkxHcmFwaC5wcm90b3R5cGUgPSBPYmplY3QuY3JlYXRlKExHcmFwaE9iamVjdC5wcm90b3R5cGUpO1xuZm9yICh2YXIgcHJvcCBpbiBMR3JhcGhPYmplY3QpIHtcbiAgTEdyYXBoW3Byb3BdID0gTEdyYXBoT2JqZWN0W3Byb3BdO1xufVxuXG5MR3JhcGgucHJvdG90eXBlLmdldE5vZGVzID0gZnVuY3Rpb24gKCkge1xuICByZXR1cm4gdGhpcy5ub2Rlcztcbn07XG5cbkxHcmFwaC5wcm90b3R5cGUuZ2V0RWRnZXMgPSBmdW5jdGlvbiAoKSB7XG4gIHJldHVybiB0aGlzLmVkZ2VzO1xufTtcblxuTEdyYXBoLnByb3RvdHlwZS5nZXRHcmFwaE1hbmFnZXIgPSBmdW5jdGlvbiAoKVxue1xuICByZXR1cm4gdGhpcy5ncmFwaE1hbmFnZXI7XG59O1xuXG5MR3JhcGgucHJvdG90eXBlLmdldFBhcmVudCA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLnBhcmVudDtcbn07XG5cbkxHcmFwaC5wcm90b3R5cGUuZ2V0TGVmdCA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLmxlZnQ7XG59O1xuXG5MR3JhcGgucHJvdG90eXBlLmdldFJpZ2h0ID0gZnVuY3Rpb24gKClcbntcbiAgcmV0dXJuIHRoaXMucmlnaHQ7XG59O1xuXG5MR3JhcGgucHJvdG90eXBlLmdldFRvcCA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLnRvcDtcbn07XG5cbkxHcmFwaC5wcm90b3R5cGUuZ2V0Qm90dG9tID0gZnVuY3Rpb24gKClcbntcbiAgcmV0dXJuIHRoaXMuYm90dG9tO1xufTtcblxuTEdyYXBoLnByb3RvdHlwZS5pc0Nvbm5lY3RlZCA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLmlzQ29ubmVjdGVkO1xufTtcblxuTEdyYXBoLnByb3RvdHlwZS5hZGQgPSBmdW5jdGlvbiAob2JqMSwgc291cmNlTm9kZSwgdGFyZ2V0Tm9kZSkge1xuICBpZiAoc291cmNlTm9kZSA9PSBudWxsICYmIHRhcmdldE5vZGUgPT0gbnVsbCkge1xuICAgIHZhciBuZXdOb2RlID0gb2JqMTtcbiAgICBpZiAodGhpcy5ncmFwaE1hbmFnZXIgPT0gbnVsbCkge1xuICAgICAgdGhyb3cgXCJHcmFwaCBoYXMgbm8gZ3JhcGggbWdyIVwiO1xuICAgIH1cbiAgICBpZiAodGhpcy5nZXROb2RlcygpLmluZGV4T2YobmV3Tm9kZSkgPiAtMSkge1xuICAgICAgdGhyb3cgXCJOb2RlIGFscmVhZHkgaW4gZ3JhcGghXCI7XG4gICAgfVxuICAgIG5ld05vZGUub3duZXIgPSB0aGlzO1xuICAgIHRoaXMuZ2V0Tm9kZXMoKS5wdXNoKG5ld05vZGUpO1xuXG4gICAgcmV0dXJuIG5ld05vZGU7XG4gIH1cbiAgZWxzZSB7XG4gICAgdmFyIG5ld0VkZ2UgPSBvYmoxO1xuICAgIGlmICghKHRoaXMuZ2V0Tm9kZXMoKS5pbmRleE9mKHNvdXJjZU5vZGUpID4gLTEgJiYgKHRoaXMuZ2V0Tm9kZXMoKS5pbmRleE9mKHRhcmdldE5vZGUpKSA+IC0xKSkge1xuICAgICAgdGhyb3cgXCJTb3VyY2Ugb3IgdGFyZ2V0IG5vdCBpbiBncmFwaCFcIjtcbiAgICB9XG5cbiAgICBpZiAoIShzb3VyY2VOb2RlLm93bmVyID09IHRhcmdldE5vZGUub3duZXIgJiYgc291cmNlTm9kZS5vd25lciA9PSB0aGlzKSkge1xuICAgICAgdGhyb3cgXCJCb3RoIG93bmVycyBtdXN0IGJlIHRoaXMgZ3JhcGghXCI7XG4gICAgfVxuXG4gICAgaWYgKHNvdXJjZU5vZGUub3duZXIgIT0gdGFyZ2V0Tm9kZS5vd25lcilcbiAgICB7XG4gICAgICByZXR1cm4gbnVsbDtcbiAgICB9XG5cbiAgICAvLyBzZXQgc291cmNlIGFuZCB0YXJnZXRcbiAgICBuZXdFZGdlLnNvdXJjZSA9IHNvdXJjZU5vZGU7XG4gICAgbmV3RWRnZS50YXJnZXQgPSB0YXJnZXROb2RlO1xuXG4gICAgLy8gc2V0IGFzIGludHJhLWdyYXBoIGVkZ2VcbiAgICBuZXdFZGdlLmlzSW50ZXJHcmFwaCA9IGZhbHNlO1xuXG4gICAgLy8gYWRkIHRvIGdyYXBoIGVkZ2UgbGlzdFxuICAgIHRoaXMuZ2V0RWRnZXMoKS5wdXNoKG5ld0VkZ2UpO1xuXG4gICAgLy8gYWRkIHRvIGluY2lkZW5jeSBsaXN0c1xuICAgIHNvdXJjZU5vZGUuZWRnZXMucHVzaChuZXdFZGdlKTtcblxuICAgIGlmICh0YXJnZXROb2RlICE9IHNvdXJjZU5vZGUpXG4gICAge1xuICAgICAgdGFyZ2V0Tm9kZS5lZGdlcy5wdXNoKG5ld0VkZ2UpO1xuICAgIH1cblxuICAgIHJldHVybiBuZXdFZGdlO1xuICB9XG59O1xuXG5MR3JhcGgucHJvdG90eXBlLnJlbW92ZSA9IGZ1bmN0aW9uIChvYmopIHtcbiAgdmFyIG5vZGUgPSBvYmo7XG4gIGlmIChvYmogaW5zdGFuY2VvZiBMTm9kZSkge1xuICAgIGlmIChub2RlID09IG51bGwpIHtcbiAgICAgIHRocm93IFwiTm9kZSBpcyBudWxsIVwiO1xuICAgIH1cbiAgICBpZiAoIShub2RlLm93bmVyICE9IG51bGwgJiYgbm9kZS5vd25lciA9PSB0aGlzKSkge1xuICAgICAgdGhyb3cgXCJPd25lciBncmFwaCBpcyBpbnZhbGlkIVwiO1xuICAgIH1cbiAgICBpZiAodGhpcy5ncmFwaE1hbmFnZXIgPT0gbnVsbCkge1xuICAgICAgdGhyb3cgXCJPd25lciBncmFwaCBtYW5hZ2VyIGlzIGludmFsaWQhXCI7XG4gICAgfVxuICAgIC8vIHJlbW92ZSBpbmNpZGVudCBlZGdlcyBmaXJzdCAobWFrZSBhIGNvcHkgdG8gZG8gaXQgc2FmZWx5KVxuICAgIHZhciBlZGdlc1RvQmVSZW1vdmVkID0gbm9kZS5lZGdlcy5zbGljZSgpO1xuICAgIHZhciBlZGdlO1xuICAgIHZhciBzID0gZWRnZXNUb0JlUmVtb3ZlZC5sZW5ndGg7XG4gICAgZm9yICh2YXIgaSA9IDA7IGkgPCBzOyBpKyspXG4gICAge1xuICAgICAgZWRnZSA9IGVkZ2VzVG9CZVJlbW92ZWRbaV07XG5cbiAgICAgIGlmIChlZGdlLmlzSW50ZXJHcmFwaClcbiAgICAgIHtcbiAgICAgICAgdGhpcy5ncmFwaE1hbmFnZXIucmVtb3ZlKGVkZ2UpO1xuICAgICAgfVxuICAgICAgZWxzZVxuICAgICAge1xuICAgICAgICBlZGdlLnNvdXJjZS5vd25lci5yZW1vdmUoZWRnZSk7XG4gICAgICB9XG4gICAgfVxuXG4gICAgLy8gbm93IHRoZSBub2RlIGl0c2VsZlxuICAgIHZhciBpbmRleCA9IHRoaXMubm9kZXMuaW5kZXhPZihub2RlKTtcbiAgICBpZiAoaW5kZXggPT0gLTEpIHtcbiAgICAgIHRocm93IFwiTm9kZSBub3QgaW4gb3duZXIgbm9kZSBsaXN0IVwiO1xuICAgIH1cblxuICAgIHRoaXMubm9kZXMuc3BsaWNlKGluZGV4LCAxKTtcbiAgfVxuICBlbHNlIGlmIChvYmogaW5zdGFuY2VvZiBMRWRnZSkge1xuICAgIHZhciBlZGdlID0gb2JqO1xuICAgIGlmIChlZGdlID09IG51bGwpIHtcbiAgICAgIHRocm93IFwiRWRnZSBpcyBudWxsIVwiO1xuICAgIH1cbiAgICBpZiAoIShlZGdlLnNvdXJjZSAhPSBudWxsICYmIGVkZ2UudGFyZ2V0ICE9IG51bGwpKSB7XG4gICAgICB0aHJvdyBcIlNvdXJjZSBhbmQvb3IgdGFyZ2V0IGlzIG51bGwhXCI7XG4gICAgfVxuICAgIGlmICghKGVkZ2Uuc291cmNlLm93bmVyICE9IG51bGwgJiYgZWRnZS50YXJnZXQub3duZXIgIT0gbnVsbCAmJlxuICAgICAgICAgICAgZWRnZS5zb3VyY2Uub3duZXIgPT0gdGhpcyAmJiBlZGdlLnRhcmdldC5vd25lciA9PSB0aGlzKSkge1xuICAgICAgdGhyb3cgXCJTb3VyY2UgYW5kL29yIHRhcmdldCBvd25lciBpcyBpbnZhbGlkIVwiO1xuICAgIH1cblxuICAgIHZhciBzb3VyY2VJbmRleCA9IGVkZ2Uuc291cmNlLmVkZ2VzLmluZGV4T2YoZWRnZSk7XG4gICAgdmFyIHRhcmdldEluZGV4ID0gZWRnZS50YXJnZXQuZWRnZXMuaW5kZXhPZihlZGdlKTtcbiAgICBpZiAoIShzb3VyY2VJbmRleCA+IC0xICYmIHRhcmdldEluZGV4ID4gLTEpKSB7XG4gICAgICB0aHJvdyBcIlNvdXJjZSBhbmQvb3IgdGFyZ2V0IGRvZXNuJ3Qga25vdyB0aGlzIGVkZ2UhXCI7XG4gICAgfVxuXG4gICAgZWRnZS5zb3VyY2UuZWRnZXMuc3BsaWNlKHNvdXJjZUluZGV4LCAxKTtcblxuICAgIGlmIChlZGdlLnRhcmdldCAhPSBlZGdlLnNvdXJjZSlcbiAgICB7XG4gICAgICBlZGdlLnRhcmdldC5lZGdlcy5zcGxpY2UodGFyZ2V0SW5kZXgsIDEpO1xuICAgIH1cblxuICAgIHZhciBpbmRleCA9IGVkZ2Uuc291cmNlLm93bmVyLmdldEVkZ2VzKCkuaW5kZXhPZihlZGdlKTtcbiAgICBpZiAoaW5kZXggPT0gLTEpIHtcbiAgICAgIHRocm93IFwiTm90IGluIG93bmVyJ3MgZWRnZSBsaXN0IVwiO1xuICAgIH1cblxuICAgIGVkZ2Uuc291cmNlLm93bmVyLmdldEVkZ2VzKCkuc3BsaWNlKGluZGV4LCAxKTtcbiAgfVxufTtcblxuTEdyYXBoLnByb3RvdHlwZS51cGRhdGVMZWZ0VG9wID0gZnVuY3Rpb24gKClcbntcbiAgdmFyIHRvcCA9IEludGVnZXIuTUFYX1ZBTFVFO1xuICB2YXIgbGVmdCA9IEludGVnZXIuTUFYX1ZBTFVFO1xuICB2YXIgbm9kZVRvcDtcbiAgdmFyIG5vZGVMZWZ0O1xuXG4gIHZhciBub2RlcyA9IHRoaXMuZ2V0Tm9kZXMoKTtcbiAgdmFyIHMgPSBub2Rlcy5sZW5ndGg7XG5cbiAgZm9yICh2YXIgaSA9IDA7IGkgPCBzOyBpKyspXG4gIHtcbiAgICB2YXIgbE5vZGUgPSBub2Rlc1tpXTtcbiAgICBub2RlVG9wID0gTWF0aC5mbG9vcihsTm9kZS5nZXRUb3AoKSk7XG4gICAgbm9kZUxlZnQgPSBNYXRoLmZsb29yKGxOb2RlLmdldExlZnQoKSk7XG5cbiAgICBpZiAodG9wID4gbm9kZVRvcClcbiAgICB7XG4gICAgICB0b3AgPSBub2RlVG9wO1xuICAgIH1cblxuICAgIGlmIChsZWZ0ID4gbm9kZUxlZnQpXG4gICAge1xuICAgICAgbGVmdCA9IG5vZGVMZWZ0O1xuICAgIH1cbiAgfVxuXG4gIC8vIERvIHdlIGhhdmUgYW55IG5vZGVzIGluIHRoaXMgZ3JhcGg/XG4gIGlmICh0b3AgPT0gSW50ZWdlci5NQVhfVkFMVUUpXG4gIHtcbiAgICByZXR1cm4gbnVsbDtcbiAgfVxuXG4gIHRoaXMubGVmdCA9IGxlZnQgLSB0aGlzLm1hcmdpbjtcbiAgdGhpcy50b3AgPSB0b3AgLSB0aGlzLm1hcmdpbjtcblxuICAvLyBBcHBseSB0aGUgbWFyZ2lucyBhbmQgcmV0dXJuIHRoZSByZXN1bHRcbiAgcmV0dXJuIG5ldyBQb2ludCh0aGlzLmxlZnQsIHRoaXMudG9wKTtcbn07XG5cbkxHcmFwaC5wcm90b3R5cGUudXBkYXRlQm91bmRzID0gZnVuY3Rpb24gKHJlY3Vyc2l2ZSlcbntcbiAgLy8gY2FsY3VsYXRlIGJvdW5kc1xuICB2YXIgbGVmdCA9IEludGVnZXIuTUFYX1ZBTFVFO1xuICB2YXIgcmlnaHQgPSAtSW50ZWdlci5NQVhfVkFMVUU7XG4gIHZhciB0b3AgPSBJbnRlZ2VyLk1BWF9WQUxVRTtcbiAgdmFyIGJvdHRvbSA9IC1JbnRlZ2VyLk1BWF9WQUxVRTtcbiAgdmFyIG5vZGVMZWZ0O1xuICB2YXIgbm9kZVJpZ2h0O1xuICB2YXIgbm9kZVRvcDtcbiAgdmFyIG5vZGVCb3R0b207XG5cbiAgdmFyIG5vZGVzID0gdGhpcy5ub2RlcztcbiAgdmFyIHMgPSBub2Rlcy5sZW5ndGg7XG4gIGZvciAodmFyIGkgPSAwOyBpIDwgczsgaSsrKVxuICB7XG4gICAgdmFyIGxOb2RlID0gbm9kZXNbaV07XG5cbiAgICBpZiAocmVjdXJzaXZlICYmIGxOb2RlLmNoaWxkICE9IG51bGwpXG4gICAge1xuICAgICAgbE5vZGUudXBkYXRlQm91bmRzKCk7XG4gICAgfVxuICAgIG5vZGVMZWZ0ID0gTWF0aC5mbG9vcihsTm9kZS5nZXRMZWZ0KCkpO1xuICAgIG5vZGVSaWdodCA9IE1hdGguZmxvb3IobE5vZGUuZ2V0UmlnaHQoKSk7XG4gICAgbm9kZVRvcCA9IE1hdGguZmxvb3IobE5vZGUuZ2V0VG9wKCkpO1xuICAgIG5vZGVCb3R0b20gPSBNYXRoLmZsb29yKGxOb2RlLmdldEJvdHRvbSgpKTtcblxuICAgIGlmIChsZWZ0ID4gbm9kZUxlZnQpXG4gICAge1xuICAgICAgbGVmdCA9IG5vZGVMZWZ0O1xuICAgIH1cblxuICAgIGlmIChyaWdodCA8IG5vZGVSaWdodClcbiAgICB7XG4gICAgICByaWdodCA9IG5vZGVSaWdodDtcbiAgICB9XG5cbiAgICBpZiAodG9wID4gbm9kZVRvcClcbiAgICB7XG4gICAgICB0b3AgPSBub2RlVG9wO1xuICAgIH1cblxuICAgIGlmIChib3R0b20gPCBub2RlQm90dG9tKVxuICAgIHtcbiAgICAgIGJvdHRvbSA9IG5vZGVCb3R0b207XG4gICAgfVxuICB9XG5cbiAgdmFyIGJvdW5kaW5nUmVjdCA9IG5ldyBSZWN0YW5nbGVEKGxlZnQsIHRvcCwgcmlnaHQgLSBsZWZ0LCBib3R0b20gLSB0b3ApO1xuICBpZiAobGVmdCA9PSBJbnRlZ2VyLk1BWF9WQUxVRSlcbiAge1xuICAgIHRoaXMubGVmdCA9IE1hdGguZmxvb3IodGhpcy5wYXJlbnQuZ2V0TGVmdCgpKTtcbiAgICB0aGlzLnJpZ2h0ID0gTWF0aC5mbG9vcih0aGlzLnBhcmVudC5nZXRSaWdodCgpKTtcbiAgICB0aGlzLnRvcCA9IE1hdGguZmxvb3IodGhpcy5wYXJlbnQuZ2V0VG9wKCkpO1xuICAgIHRoaXMuYm90dG9tID0gTWF0aC5mbG9vcih0aGlzLnBhcmVudC5nZXRCb3R0b20oKSk7XG4gIH1cblxuICB0aGlzLmxlZnQgPSBib3VuZGluZ1JlY3QueCAtIHRoaXMubWFyZ2luO1xuICB0aGlzLnJpZ2h0ID0gYm91bmRpbmdSZWN0LnggKyBib3VuZGluZ1JlY3Qud2lkdGggKyB0aGlzLm1hcmdpbjtcbiAgdGhpcy50b3AgPSBib3VuZGluZ1JlY3QueSAtIHRoaXMubWFyZ2luO1xuICB0aGlzLmJvdHRvbSA9IGJvdW5kaW5nUmVjdC55ICsgYm91bmRpbmdSZWN0LmhlaWdodCArIHRoaXMubWFyZ2luO1xufTtcblxuTEdyYXBoLmNhbGN1bGF0ZUJvdW5kcyA9IGZ1bmN0aW9uIChub2RlcylcbntcbiAgdmFyIGxlZnQgPSBJbnRlZ2VyLk1BWF9WQUxVRTtcbiAgdmFyIHJpZ2h0ID0gLUludGVnZXIuTUFYX1ZBTFVFO1xuICB2YXIgdG9wID0gSW50ZWdlci5NQVhfVkFMVUU7XG4gIHZhciBib3R0b20gPSAtSW50ZWdlci5NQVhfVkFMVUU7XG4gIHZhciBub2RlTGVmdDtcbiAgdmFyIG5vZGVSaWdodDtcbiAgdmFyIG5vZGVUb3A7XG4gIHZhciBub2RlQm90dG9tO1xuXG4gIHZhciBzID0gbm9kZXMubGVuZ3RoO1xuXG4gIGZvciAodmFyIGkgPSAwOyBpIDwgczsgaSsrKVxuICB7XG4gICAgdmFyIGxOb2RlID0gbm9kZXNbaV07XG4gICAgbm9kZUxlZnQgPSBNYXRoLmZsb29yKGxOb2RlLmdldExlZnQoKSk7XG4gICAgbm9kZVJpZ2h0ID0gTWF0aC5mbG9vcihsTm9kZS5nZXRSaWdodCgpKTtcbiAgICBub2RlVG9wID0gTWF0aC5mbG9vcihsTm9kZS5nZXRUb3AoKSk7XG4gICAgbm9kZUJvdHRvbSA9IE1hdGguZmxvb3IobE5vZGUuZ2V0Qm90dG9tKCkpO1xuXG4gICAgaWYgKGxlZnQgPiBub2RlTGVmdClcbiAgICB7XG4gICAgICBsZWZ0ID0gbm9kZUxlZnQ7XG4gICAgfVxuXG4gICAgaWYgKHJpZ2h0IDwgbm9kZVJpZ2h0KVxuICAgIHtcbiAgICAgIHJpZ2h0ID0gbm9kZVJpZ2h0O1xuICAgIH1cblxuICAgIGlmICh0b3AgPiBub2RlVG9wKVxuICAgIHtcbiAgICAgIHRvcCA9IG5vZGVUb3A7XG4gICAgfVxuXG4gICAgaWYgKGJvdHRvbSA8IG5vZGVCb3R0b20pXG4gICAge1xuICAgICAgYm90dG9tID0gbm9kZUJvdHRvbTtcbiAgICB9XG4gIH1cblxuICB2YXIgYm91bmRpbmdSZWN0ID0gbmV3IFJlY3RhbmdsZUQobGVmdCwgdG9wLCByaWdodCAtIGxlZnQsIGJvdHRvbSAtIHRvcCk7XG5cbiAgcmV0dXJuIGJvdW5kaW5nUmVjdDtcbn07XG5cbkxHcmFwaC5wcm90b3R5cGUuZ2V0SW5jbHVzaW9uVHJlZURlcHRoID0gZnVuY3Rpb24gKClcbntcbiAgaWYgKHRoaXMgPT0gdGhpcy5ncmFwaE1hbmFnZXIuZ2V0Um9vdCgpKVxuICB7XG4gICAgcmV0dXJuIDE7XG4gIH1cbiAgZWxzZVxuICB7XG4gICAgcmV0dXJuIHRoaXMucGFyZW50LmdldEluY2x1c2lvblRyZWVEZXB0aCgpO1xuICB9XG59O1xuXG5MR3JhcGgucHJvdG90eXBlLmdldEVzdGltYXRlZFNpemUgPSBmdW5jdGlvbiAoKVxue1xuICBpZiAodGhpcy5lc3RpbWF0ZWRTaXplID09IEludGVnZXIuTUlOX1ZBTFVFKSB7XG4gICAgdGhyb3cgXCJhc3NlcnQgZmFpbGVkXCI7XG4gIH1cbiAgcmV0dXJuIHRoaXMuZXN0aW1hdGVkU2l6ZTtcbn07XG5cbkxHcmFwaC5wcm90b3R5cGUuY2FsY0VzdGltYXRlZFNpemUgPSBmdW5jdGlvbiAoKVxue1xuICB2YXIgc2l6ZSA9IDA7XG4gIHZhciBub2RlcyA9IHRoaXMubm9kZXM7XG4gIHZhciBzID0gbm9kZXMubGVuZ3RoO1xuXG4gIGZvciAodmFyIGkgPSAwOyBpIDwgczsgaSsrKVxuICB7XG4gICAgdmFyIGxOb2RlID0gbm9kZXNbaV07XG4gICAgc2l6ZSArPSBsTm9kZS5jYWxjRXN0aW1hdGVkU2l6ZSgpO1xuICB9XG5cbiAgaWYgKHNpemUgPT0gMClcbiAge1xuICAgIHRoaXMuZXN0aW1hdGVkU2l6ZSA9IExheW91dENvbnN0YW50cy5FTVBUWV9DT01QT1VORF9OT0RFX1NJWkU7XG4gIH1cbiAgZWxzZVxuICB7XG4gICAgdGhpcy5lc3RpbWF0ZWRTaXplID0gTWF0aC5mbG9vcihzaXplIC8gTWF0aC5zcXJ0KHRoaXMubm9kZXMubGVuZ3RoKSk7XG4gIH1cblxuICByZXR1cm4gTWF0aC5mbG9vcih0aGlzLmVzdGltYXRlZFNpemUpO1xufTtcblxuTEdyYXBoLnByb3RvdHlwZS51cGRhdGVDb25uZWN0ZWQgPSBmdW5jdGlvbiAoKVxue1xuICBpZiAodGhpcy5ub2Rlcy5sZW5ndGggPT0gMClcbiAge1xuICAgIHRoaXMuaXNDb25uZWN0ZWQgPSB0cnVlO1xuICAgIHJldHVybjtcbiAgfVxuXG4gIHZhciB0b0JlVmlzaXRlZCA9IFtdO1xuICB2YXIgdmlzaXRlZCA9IG5ldyBIYXNoU2V0KCk7XG4gIHZhciBjdXJyZW50Tm9kZSA9IHRoaXMubm9kZXNbMF07XG4gIHZhciBuZWlnaGJvckVkZ2VzO1xuICB2YXIgY3VycmVudE5laWdoYm9yO1xuICB0b0JlVmlzaXRlZCA9IHRvQmVWaXNpdGVkLmNvbmNhdChjdXJyZW50Tm9kZS53aXRoQ2hpbGRyZW4oKSk7XG5cbiAgd2hpbGUgKHRvQmVWaXNpdGVkLmxlbmd0aCA+IDApXG4gIHtcbiAgICBjdXJyZW50Tm9kZSA9IHRvQmVWaXNpdGVkLnNoaWZ0KCk7XG4gICAgdmlzaXRlZC5hZGQoY3VycmVudE5vZGUpO1xuXG4gICAgLy8gVHJhdmVyc2UgYWxsIG5laWdoYm9ycyBvZiB0aGlzIG5vZGVcbiAgICBuZWlnaGJvckVkZ2VzID0gY3VycmVudE5vZGUuZ2V0RWRnZXMoKTtcbiAgICB2YXIgcyA9IG5laWdoYm9yRWRnZXMubGVuZ3RoO1xuICAgIGZvciAodmFyIGkgPSAwOyBpIDwgczsgaSsrKVxuICAgIHtcbiAgICAgIHZhciBuZWlnaGJvckVkZ2UgPSBuZWlnaGJvckVkZ2VzW2ldO1xuICAgICAgY3VycmVudE5laWdoYm9yID1cbiAgICAgICAgICAgICAgbmVpZ2hib3JFZGdlLmdldE90aGVyRW5kSW5HcmFwaChjdXJyZW50Tm9kZSwgdGhpcyk7XG5cbiAgICAgIC8vIEFkZCB1bnZpc2l0ZWQgbmVpZ2hib3JzIHRvIHRoZSBsaXN0IHRvIHZpc2l0XG4gICAgICBpZiAoY3VycmVudE5laWdoYm9yICE9IG51bGwgJiZcbiAgICAgICAgICAgICAgIXZpc2l0ZWQuY29udGFpbnMoY3VycmVudE5laWdoYm9yKSlcbiAgICAgIHtcbiAgICAgICAgdG9CZVZpc2l0ZWQgPSB0b0JlVmlzaXRlZC5jb25jYXQoY3VycmVudE5laWdoYm9yLndpdGhDaGlsZHJlbigpKTtcbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICB0aGlzLmlzQ29ubmVjdGVkID0gZmFsc2U7XG5cbiAgaWYgKHZpc2l0ZWQuc2l6ZSgpID49IHRoaXMubm9kZXMubGVuZ3RoKVxuICB7XG4gICAgdmFyIG5vT2ZWaXNpdGVkSW5UaGlzR3JhcGggPSAwO1xuXG4gICAgdmFyIHMgPSB2aXNpdGVkLnNpemUoKTtcbiAgICBmb3IgKHZhciB2aXNpdGVkSWQgaW4gdmlzaXRlZC5zZXQpXG4gICAge1xuICAgICAgdmFyIHZpc2l0ZWROb2RlID0gdmlzaXRlZC5zZXRbdmlzaXRlZElkXTtcbiAgICAgIGlmICh2aXNpdGVkTm9kZS5vd25lciA9PSB0aGlzKVxuICAgICAge1xuICAgICAgICBub09mVmlzaXRlZEluVGhpc0dyYXBoKys7XG4gICAgICB9XG4gICAgfVxuXG4gICAgaWYgKG5vT2ZWaXNpdGVkSW5UaGlzR3JhcGggPT0gdGhpcy5ub2Rlcy5sZW5ndGgpXG4gICAge1xuICAgICAgdGhpcy5pc0Nvbm5lY3RlZCA9IHRydWU7XG4gICAgfVxuICB9XG59O1xuXG5tb2R1bGUuZXhwb3J0cyA9IExHcmFwaDtcbiIsImZ1bmN0aW9uIExHcmFwaE1hbmFnZXIobGF5b3V0KSB7XG4gIHRoaXMubGF5b3V0ID0gbGF5b3V0O1xuXG4gIHRoaXMuZ3JhcGhzID0gW107XG4gIHRoaXMuZWRnZXMgPSBbXTtcbn1cblxuTEdyYXBoTWFuYWdlci5wcm90b3R5cGUuYWRkUm9vdCA9IGZ1bmN0aW9uICgpXG57XG4gIHZhciBuZ3JhcGggPSB0aGlzLmxheW91dC5uZXdHcmFwaCgpO1xuICB2YXIgbm5vZGUgPSB0aGlzLmxheW91dC5uZXdOb2RlKG51bGwpO1xuICB2YXIgcm9vdCA9IHRoaXMuYWRkKG5ncmFwaCwgbm5vZGUpO1xuICB0aGlzLnNldFJvb3RHcmFwaChyb290KTtcbiAgcmV0dXJuIHRoaXMucm9vdEdyYXBoO1xufTtcblxuTEdyYXBoTWFuYWdlci5wcm90b3R5cGUuYWRkID0gZnVuY3Rpb24gKG5ld0dyYXBoLCBwYXJlbnROb2RlLCBuZXdFZGdlLCBzb3VyY2VOb2RlLCB0YXJnZXROb2RlKVxue1xuICAvL3RoZXJlIGFyZSBqdXN0IDIgcGFyYW1ldGVycyBhcmUgcGFzc2VkIHRoZW4gaXQgYWRkcyBhbiBMR3JhcGggZWxzZSBpdCBhZGRzIGFuIExFZGdlXG4gIGlmIChuZXdFZGdlID09IG51bGwgJiYgc291cmNlTm9kZSA9PSBudWxsICYmIHRhcmdldE5vZGUgPT0gbnVsbCkge1xuICAgIGlmIChuZXdHcmFwaCA9PSBudWxsKSB7XG4gICAgICB0aHJvdyBcIkdyYXBoIGlzIG51bGwhXCI7XG4gICAgfVxuICAgIGlmIChwYXJlbnROb2RlID09IG51bGwpIHtcbiAgICAgIHRocm93IFwiUGFyZW50IG5vZGUgaXMgbnVsbCFcIjtcbiAgICB9XG4gICAgaWYgKHRoaXMuZ3JhcGhzLmluZGV4T2YobmV3R3JhcGgpID4gLTEpIHtcbiAgICAgIHRocm93IFwiR3JhcGggYWxyZWFkeSBpbiB0aGlzIGdyYXBoIG1nciFcIjtcbiAgICB9XG5cbiAgICB0aGlzLmdyYXBocy5wdXNoKG5ld0dyYXBoKTtcblxuICAgIGlmIChuZXdHcmFwaC5wYXJlbnQgIT0gbnVsbCkge1xuICAgICAgdGhyb3cgXCJBbHJlYWR5IGhhcyBhIHBhcmVudCFcIjtcbiAgICB9XG4gICAgaWYgKHBhcmVudE5vZGUuY2hpbGQgIT0gbnVsbCkge1xuICAgICAgdGhyb3cgIFwiQWxyZWFkeSBoYXMgYSBjaGlsZCFcIjtcbiAgICB9XG5cbiAgICBuZXdHcmFwaC5wYXJlbnQgPSBwYXJlbnROb2RlO1xuICAgIHBhcmVudE5vZGUuY2hpbGQgPSBuZXdHcmFwaDtcblxuICAgIHJldHVybiBuZXdHcmFwaDtcbiAgfVxuICBlbHNlIHtcbiAgICAvL2NoYW5nZSB0aGUgb3JkZXIgb2YgdGhlIHBhcmFtZXRlcnNcbiAgICB0YXJnZXROb2RlID0gbmV3RWRnZTtcbiAgICBzb3VyY2VOb2RlID0gcGFyZW50Tm9kZTtcbiAgICBuZXdFZGdlID0gbmV3R3JhcGg7XG4gICAgdmFyIHNvdXJjZUdyYXBoID0gc291cmNlTm9kZS5nZXRPd25lcigpO1xuICAgIHZhciB0YXJnZXRHcmFwaCA9IHRhcmdldE5vZGUuZ2V0T3duZXIoKTtcblxuICAgIGlmICghKHNvdXJjZUdyYXBoICE9IG51bGwgJiYgc291cmNlR3JhcGguZ2V0R3JhcGhNYW5hZ2VyKCkgPT0gdGhpcykpIHtcbiAgICAgIHRocm93IFwiU291cmNlIG5vdCBpbiB0aGlzIGdyYXBoIG1nciFcIjtcbiAgICB9XG4gICAgaWYgKCEodGFyZ2V0R3JhcGggIT0gbnVsbCAmJiB0YXJnZXRHcmFwaC5nZXRHcmFwaE1hbmFnZXIoKSA9PSB0aGlzKSkge1xuICAgICAgdGhyb3cgXCJUYXJnZXQgbm90IGluIHRoaXMgZ3JhcGggbWdyIVwiO1xuICAgIH1cblxuICAgIGlmIChzb3VyY2VHcmFwaCA9PSB0YXJnZXRHcmFwaClcbiAgICB7XG4gICAgICBuZXdFZGdlLmlzSW50ZXJHcmFwaCA9IGZhbHNlO1xuICAgICAgcmV0dXJuIHNvdXJjZUdyYXBoLmFkZChuZXdFZGdlLCBzb3VyY2VOb2RlLCB0YXJnZXROb2RlKTtcbiAgICB9XG4gICAgZWxzZVxuICAgIHtcbiAgICAgIG5ld0VkZ2UuaXNJbnRlckdyYXBoID0gdHJ1ZTtcblxuICAgICAgLy8gc2V0IHNvdXJjZSBhbmQgdGFyZ2V0XG4gICAgICBuZXdFZGdlLnNvdXJjZSA9IHNvdXJjZU5vZGU7XG4gICAgICBuZXdFZGdlLnRhcmdldCA9IHRhcmdldE5vZGU7XG5cbiAgICAgIC8vIGFkZCBlZGdlIHRvIGludGVyLWdyYXBoIGVkZ2UgbGlzdFxuICAgICAgaWYgKHRoaXMuZWRnZXMuaW5kZXhPZihuZXdFZGdlKSA+IC0xKSB7XG4gICAgICAgIHRocm93IFwiRWRnZSBhbHJlYWR5IGluIGludGVyLWdyYXBoIGVkZ2UgbGlzdCFcIjtcbiAgICAgIH1cblxuICAgICAgdGhpcy5lZGdlcy5wdXNoKG5ld0VkZ2UpO1xuXG4gICAgICAvLyBhZGQgZWRnZSB0byBzb3VyY2UgYW5kIHRhcmdldCBpbmNpZGVuY3kgbGlzdHNcbiAgICAgIGlmICghKG5ld0VkZ2Uuc291cmNlICE9IG51bGwgJiYgbmV3RWRnZS50YXJnZXQgIT0gbnVsbCkpIHtcbiAgICAgICAgdGhyb3cgXCJFZGdlIHNvdXJjZSBhbmQvb3IgdGFyZ2V0IGlzIG51bGwhXCI7XG4gICAgICB9XG5cbiAgICAgIGlmICghKG5ld0VkZ2Uuc291cmNlLmVkZ2VzLmluZGV4T2YobmV3RWRnZSkgPT0gLTEgJiYgbmV3RWRnZS50YXJnZXQuZWRnZXMuaW5kZXhPZihuZXdFZGdlKSA9PSAtMSkpIHtcbiAgICAgICAgdGhyb3cgXCJFZGdlIGFscmVhZHkgaW4gc291cmNlIGFuZC9vciB0YXJnZXQgaW5jaWRlbmN5IGxpc3QhXCI7XG4gICAgICB9XG5cbiAgICAgIG5ld0VkZ2Uuc291cmNlLmVkZ2VzLnB1c2gobmV3RWRnZSk7XG4gICAgICBuZXdFZGdlLnRhcmdldC5lZGdlcy5wdXNoKG5ld0VkZ2UpO1xuXG4gICAgICByZXR1cm4gbmV3RWRnZTtcbiAgICB9XG4gIH1cbn07XG5cbkxHcmFwaE1hbmFnZXIucHJvdG90eXBlLnJlbW92ZSA9IGZ1bmN0aW9uIChsT2JqKSB7XG4gIGlmIChsT2JqIGluc3RhbmNlb2YgTEdyYXBoKSB7XG4gICAgdmFyIGdyYXBoID0gbE9iajtcbiAgICBpZiAoZ3JhcGguZ2V0R3JhcGhNYW5hZ2VyKCkgIT0gdGhpcykge1xuICAgICAgdGhyb3cgXCJHcmFwaCBub3QgaW4gdGhpcyBncmFwaCBtZ3JcIjtcbiAgICB9XG4gICAgaWYgKCEoZ3JhcGggPT0gdGhpcy5yb290R3JhcGggfHwgKGdyYXBoLnBhcmVudCAhPSBudWxsICYmIGdyYXBoLnBhcmVudC5ncmFwaE1hbmFnZXIgPT0gdGhpcykpKSB7XG4gICAgICB0aHJvdyBcIkludmFsaWQgcGFyZW50IG5vZGUhXCI7XG4gICAgfVxuXG4gICAgLy8gZmlyc3QgdGhlIGVkZ2VzIChtYWtlIGEgY29weSB0byBkbyBpdCBzYWZlbHkpXG4gICAgdmFyIGVkZ2VzVG9CZVJlbW92ZWQgPSBbXTtcblxuICAgIGVkZ2VzVG9CZVJlbW92ZWQgPSBlZGdlc1RvQmVSZW1vdmVkLmNvbmNhdChncmFwaC5nZXRFZGdlcygpKTtcblxuICAgIHZhciBlZGdlO1xuICAgIHZhciBzID0gZWRnZXNUb0JlUmVtb3ZlZC5sZW5ndGg7XG4gICAgZm9yICh2YXIgaSA9IDA7IGkgPCBzOyBpKyspXG4gICAge1xuICAgICAgZWRnZSA9IGVkZ2VzVG9CZVJlbW92ZWRbaV07XG4gICAgICBncmFwaC5yZW1vdmUoZWRnZSk7XG4gICAgfVxuXG4gICAgLy8gdGhlbiB0aGUgbm9kZXMgKG1ha2UgYSBjb3B5IHRvIGRvIGl0IHNhZmVseSlcbiAgICB2YXIgbm9kZXNUb0JlUmVtb3ZlZCA9IFtdO1xuXG4gICAgbm9kZXNUb0JlUmVtb3ZlZCA9IG5vZGVzVG9CZVJlbW92ZWQuY29uY2F0KGdyYXBoLmdldE5vZGVzKCkpO1xuXG4gICAgdmFyIG5vZGU7XG4gICAgcyA9IG5vZGVzVG9CZVJlbW92ZWQubGVuZ3RoO1xuICAgIGZvciAodmFyIGkgPSAwOyBpIDwgczsgaSsrKVxuICAgIHtcbiAgICAgIG5vZGUgPSBub2Rlc1RvQmVSZW1vdmVkW2ldO1xuICAgICAgZ3JhcGgucmVtb3ZlKG5vZGUpO1xuICAgIH1cblxuICAgIC8vIGNoZWNrIGlmIGdyYXBoIGlzIHRoZSByb290XG4gICAgaWYgKGdyYXBoID09IHRoaXMucm9vdEdyYXBoKVxuICAgIHtcbiAgICAgIHRoaXMuc2V0Um9vdEdyYXBoKG51bGwpO1xuICAgIH1cblxuICAgIC8vIG5vdyByZW1vdmUgdGhlIGdyYXBoIGl0c2VsZlxuICAgIHZhciBpbmRleCA9IHRoaXMuZ3JhcGhzLmluZGV4T2YoZ3JhcGgpO1xuICAgIHRoaXMuZ3JhcGhzLnNwbGljZShpbmRleCwgMSk7XG5cbiAgICAvLyBhbHNvIHJlc2V0IHRoZSBwYXJlbnQgb2YgdGhlIGdyYXBoXG4gICAgZ3JhcGgucGFyZW50ID0gbnVsbDtcbiAgfVxuICBlbHNlIGlmIChsT2JqIGluc3RhbmNlb2YgTEVkZ2UpIHtcbiAgICBlZGdlID0gbE9iajtcbiAgICBpZiAoZWRnZSA9PSBudWxsKSB7XG4gICAgICB0aHJvdyBcIkVkZ2UgaXMgbnVsbCFcIjtcbiAgICB9XG4gICAgaWYgKCFlZGdlLmlzSW50ZXJHcmFwaCkge1xuICAgICAgdGhyb3cgXCJOb3QgYW4gaW50ZXItZ3JhcGggZWRnZSFcIjtcbiAgICB9XG4gICAgaWYgKCEoZWRnZS5zb3VyY2UgIT0gbnVsbCAmJiBlZGdlLnRhcmdldCAhPSBudWxsKSkge1xuICAgICAgdGhyb3cgXCJTb3VyY2UgYW5kL29yIHRhcmdldCBpcyBudWxsIVwiO1xuICAgIH1cblxuICAgIC8vIHJlbW92ZSBlZGdlIGZyb20gc291cmNlIGFuZCB0YXJnZXQgbm9kZXMnIGluY2lkZW5jeSBsaXN0c1xuXG4gICAgaWYgKCEoZWRnZS5zb3VyY2UuZWRnZXMuaW5kZXhPZihlZGdlKSAhPSAtMSAmJiBlZGdlLnRhcmdldC5lZGdlcy5pbmRleE9mKGVkZ2UpICE9IC0xKSkge1xuICAgICAgdGhyb3cgXCJTb3VyY2UgYW5kL29yIHRhcmdldCBkb2Vzbid0IGtub3cgdGhpcyBlZGdlIVwiO1xuICAgIH1cblxuICAgIHZhciBpbmRleCA9IGVkZ2Uuc291cmNlLmVkZ2VzLmluZGV4T2YoZWRnZSk7XG4gICAgZWRnZS5zb3VyY2UuZWRnZXMuc3BsaWNlKGluZGV4LCAxKTtcbiAgICBpbmRleCA9IGVkZ2UudGFyZ2V0LmVkZ2VzLmluZGV4T2YoZWRnZSk7XG4gICAgZWRnZS50YXJnZXQuZWRnZXMuc3BsaWNlKGluZGV4LCAxKTtcblxuICAgIC8vIHJlbW92ZSBlZGdlIGZyb20gb3duZXIgZ3JhcGggbWFuYWdlcidzIGludGVyLWdyYXBoIGVkZ2UgbGlzdFxuXG4gICAgaWYgKCEoZWRnZS5zb3VyY2Uub3duZXIgIT0gbnVsbCAmJiBlZGdlLnNvdXJjZS5vd25lci5nZXRHcmFwaE1hbmFnZXIoKSAhPSBudWxsKSkge1xuICAgICAgdGhyb3cgXCJFZGdlIG93bmVyIGdyYXBoIG9yIG93bmVyIGdyYXBoIG1hbmFnZXIgaXMgbnVsbCFcIjtcbiAgICB9XG4gICAgaWYgKGVkZ2Uuc291cmNlLm93bmVyLmdldEdyYXBoTWFuYWdlcigpLmVkZ2VzLmluZGV4T2YoZWRnZSkgPT0gLTEpIHtcbiAgICAgIHRocm93IFwiTm90IGluIG93bmVyIGdyYXBoIG1hbmFnZXIncyBlZGdlIGxpc3QhXCI7XG4gICAgfVxuXG4gICAgdmFyIGluZGV4ID0gZWRnZS5zb3VyY2Uub3duZXIuZ2V0R3JhcGhNYW5hZ2VyKCkuZWRnZXMuaW5kZXhPZihlZGdlKTtcbiAgICBlZGdlLnNvdXJjZS5vd25lci5nZXRHcmFwaE1hbmFnZXIoKS5lZGdlcy5zcGxpY2UoaW5kZXgsIDEpO1xuICB9XG59O1xuXG5MR3JhcGhNYW5hZ2VyLnByb3RvdHlwZS51cGRhdGVCb3VuZHMgPSBmdW5jdGlvbiAoKVxue1xuICB0aGlzLnJvb3RHcmFwaC51cGRhdGVCb3VuZHModHJ1ZSk7XG59O1xuXG5MR3JhcGhNYW5hZ2VyLnByb3RvdHlwZS5nZXRHcmFwaHMgPSBmdW5jdGlvbiAoKVxue1xuICByZXR1cm4gdGhpcy5ncmFwaHM7XG59O1xuXG5MR3JhcGhNYW5hZ2VyLnByb3RvdHlwZS5nZXRBbGxOb2RlcyA9IGZ1bmN0aW9uICgpXG57XG4gIGlmICh0aGlzLmFsbE5vZGVzID09IG51bGwpXG4gIHtcbiAgICB2YXIgbm9kZUxpc3QgPSBbXTtcbiAgICB2YXIgZ3JhcGhzID0gdGhpcy5nZXRHcmFwaHMoKTtcbiAgICB2YXIgcyA9IGdyYXBocy5sZW5ndGg7XG4gICAgZm9yICh2YXIgaSA9IDA7IGkgPCBzOyBpKyspXG4gICAge1xuICAgICAgbm9kZUxpc3QgPSBub2RlTGlzdC5jb25jYXQoZ3JhcGhzW2ldLmdldE5vZGVzKCkpO1xuICAgIH1cbiAgICB0aGlzLmFsbE5vZGVzID0gbm9kZUxpc3Q7XG4gIH1cbiAgcmV0dXJuIHRoaXMuYWxsTm9kZXM7XG59O1xuXG5MR3JhcGhNYW5hZ2VyLnByb3RvdHlwZS5yZXNldEFsbE5vZGVzID0gZnVuY3Rpb24gKClcbntcbiAgdGhpcy5hbGxOb2RlcyA9IG51bGw7XG59O1xuXG5MR3JhcGhNYW5hZ2VyLnByb3RvdHlwZS5yZXNldEFsbEVkZ2VzID0gZnVuY3Rpb24gKClcbntcbiAgdGhpcy5hbGxFZGdlcyA9IG51bGw7XG59O1xuXG5MR3JhcGhNYW5hZ2VyLnByb3RvdHlwZS5yZXNldEFsbE5vZGVzVG9BcHBseUdyYXZpdGF0aW9uID0gZnVuY3Rpb24gKClcbntcbiAgdGhpcy5hbGxOb2Rlc1RvQXBwbHlHcmF2aXRhdGlvbiA9IG51bGw7XG59O1xuXG5MR3JhcGhNYW5hZ2VyLnByb3RvdHlwZS5nZXRBbGxFZGdlcyA9IGZ1bmN0aW9uICgpXG57XG4gIGlmICh0aGlzLmFsbEVkZ2VzID09IG51bGwpXG4gIHtcbiAgICB2YXIgZWRnZUxpc3QgPSBbXTtcbiAgICB2YXIgZ3JhcGhzID0gdGhpcy5nZXRHcmFwaHMoKTtcbiAgICB2YXIgcyA9IGdyYXBocy5sZW5ndGg7XG4gICAgZm9yICh2YXIgaSA9IDA7IGkgPCBncmFwaHMubGVuZ3RoOyBpKyspXG4gICAge1xuICAgICAgZWRnZUxpc3QgPSBlZGdlTGlzdC5jb25jYXQoZ3JhcGhzW2ldLmdldEVkZ2VzKCkpO1xuICAgIH1cblxuICAgIGVkZ2VMaXN0ID0gZWRnZUxpc3QuY29uY2F0KHRoaXMuZWRnZXMpO1xuXG4gICAgdGhpcy5hbGxFZGdlcyA9IGVkZ2VMaXN0O1xuICB9XG4gIHJldHVybiB0aGlzLmFsbEVkZ2VzO1xufTtcblxuTEdyYXBoTWFuYWdlci5wcm90b3R5cGUuZ2V0QWxsTm9kZXNUb0FwcGx5R3Jhdml0YXRpb24gPSBmdW5jdGlvbiAoKVxue1xuICByZXR1cm4gdGhpcy5hbGxOb2Rlc1RvQXBwbHlHcmF2aXRhdGlvbjtcbn07XG5cbkxHcmFwaE1hbmFnZXIucHJvdG90eXBlLnNldEFsbE5vZGVzVG9BcHBseUdyYXZpdGF0aW9uID0gZnVuY3Rpb24gKG5vZGVMaXN0KVxue1xuICBpZiAodGhpcy5hbGxOb2Rlc1RvQXBwbHlHcmF2aXRhdGlvbiAhPSBudWxsKSB7XG4gICAgdGhyb3cgXCJhc3NlcnQgZmFpbGVkXCI7XG4gIH1cblxuICB0aGlzLmFsbE5vZGVzVG9BcHBseUdyYXZpdGF0aW9uID0gbm9kZUxpc3Q7XG59O1xuXG5MR3JhcGhNYW5hZ2VyLnByb3RvdHlwZS5nZXRSb290ID0gZnVuY3Rpb24gKClcbntcbiAgcmV0dXJuIHRoaXMucm9vdEdyYXBoO1xufTtcblxuTEdyYXBoTWFuYWdlci5wcm90b3R5cGUuc2V0Um9vdEdyYXBoID0gZnVuY3Rpb24gKGdyYXBoKVxue1xuICBpZiAoZ3JhcGguZ2V0R3JhcGhNYW5hZ2VyKCkgIT0gdGhpcykge1xuICAgIHRocm93IFwiUm9vdCBub3QgaW4gdGhpcyBncmFwaCBtZ3IhXCI7XG4gIH1cblxuICB0aGlzLnJvb3RHcmFwaCA9IGdyYXBoO1xuICAvLyByb290IGdyYXBoIG11c3QgaGF2ZSBhIHJvb3Qgbm9kZSBhc3NvY2lhdGVkIHdpdGggaXQgZm9yIGNvbnZlbmllbmNlXG4gIGlmIChncmFwaC5wYXJlbnQgPT0gbnVsbClcbiAge1xuICAgIGdyYXBoLnBhcmVudCA9IHRoaXMubGF5b3V0Lm5ld05vZGUoXCJSb290IG5vZGVcIik7XG4gIH1cbn07XG5cbkxHcmFwaE1hbmFnZXIucHJvdG90eXBlLmdldExheW91dCA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLmxheW91dDtcbn07XG5cbkxHcmFwaE1hbmFnZXIucHJvdG90eXBlLmlzT25lQW5jZXN0b3JPZk90aGVyID0gZnVuY3Rpb24gKGZpcnN0Tm9kZSwgc2Vjb25kTm9kZSlcbntcbiAgaWYgKCEoZmlyc3ROb2RlICE9IG51bGwgJiYgc2Vjb25kTm9kZSAhPSBudWxsKSkge1xuICAgIHRocm93IFwiYXNzZXJ0IGZhaWxlZFwiO1xuICB9XG5cbiAgaWYgKGZpcnN0Tm9kZSA9PSBzZWNvbmROb2RlKVxuICB7XG4gICAgcmV0dXJuIHRydWU7XG4gIH1cbiAgLy8gSXMgc2Vjb25kIG5vZGUgYW4gYW5jZXN0b3Igb2YgdGhlIGZpcnN0IG9uZT9cbiAgdmFyIG93bmVyR3JhcGggPSBmaXJzdE5vZGUuZ2V0T3duZXIoKTtcbiAgdmFyIHBhcmVudE5vZGU7XG5cbiAgZG9cbiAge1xuICAgIHBhcmVudE5vZGUgPSBvd25lckdyYXBoLmdldFBhcmVudCgpO1xuXG4gICAgaWYgKHBhcmVudE5vZGUgPT0gbnVsbClcbiAgICB7XG4gICAgICBicmVhaztcbiAgICB9XG5cbiAgICBpZiAocGFyZW50Tm9kZSA9PSBzZWNvbmROb2RlKVxuICAgIHtcbiAgICAgIHJldHVybiB0cnVlO1xuICAgIH1cblxuICAgIG93bmVyR3JhcGggPSBwYXJlbnROb2RlLmdldE93bmVyKCk7XG4gICAgaWYgKG93bmVyR3JhcGggPT0gbnVsbClcbiAgICB7XG4gICAgICBicmVhaztcbiAgICB9XG4gIH0gd2hpbGUgKHRydWUpO1xuICAvLyBJcyBmaXJzdCBub2RlIGFuIGFuY2VzdG9yIG9mIHRoZSBzZWNvbmQgb25lP1xuICBvd25lckdyYXBoID0gc2Vjb25kTm9kZS5nZXRPd25lcigpO1xuXG4gIGRvXG4gIHtcbiAgICBwYXJlbnROb2RlID0gb3duZXJHcmFwaC5nZXRQYXJlbnQoKTtcblxuICAgIGlmIChwYXJlbnROb2RlID09IG51bGwpXG4gICAge1xuICAgICAgYnJlYWs7XG4gICAgfVxuXG4gICAgaWYgKHBhcmVudE5vZGUgPT0gZmlyc3ROb2RlKVxuICAgIHtcbiAgICAgIHJldHVybiB0cnVlO1xuICAgIH1cblxuICAgIG93bmVyR3JhcGggPSBwYXJlbnROb2RlLmdldE93bmVyKCk7XG4gICAgaWYgKG93bmVyR3JhcGggPT0gbnVsbClcbiAgICB7XG4gICAgICBicmVhaztcbiAgICB9XG4gIH0gd2hpbGUgKHRydWUpO1xuXG4gIHJldHVybiBmYWxzZTtcbn07XG5cbkxHcmFwaE1hbmFnZXIucHJvdG90eXBlLmNhbGNMb3dlc3RDb21tb25BbmNlc3RvcnMgPSBmdW5jdGlvbiAoKVxue1xuICB2YXIgZWRnZTtcbiAgdmFyIHNvdXJjZU5vZGU7XG4gIHZhciB0YXJnZXROb2RlO1xuICB2YXIgc291cmNlQW5jZXN0b3JHcmFwaDtcbiAgdmFyIHRhcmdldEFuY2VzdG9yR3JhcGg7XG5cbiAgdmFyIGVkZ2VzID0gdGhpcy5nZXRBbGxFZGdlcygpO1xuICB2YXIgcyA9IGVkZ2VzLmxlbmd0aDtcbiAgZm9yICh2YXIgaSA9IDA7IGkgPCBzOyBpKyspXG4gIHtcbiAgICBlZGdlID0gZWRnZXNbaV07XG5cbiAgICBzb3VyY2VOb2RlID0gZWRnZS5zb3VyY2U7XG4gICAgdGFyZ2V0Tm9kZSA9IGVkZ2UudGFyZ2V0O1xuICAgIGVkZ2UubGNhID0gbnVsbDtcbiAgICBlZGdlLnNvdXJjZUluTGNhID0gc291cmNlTm9kZTtcbiAgICBlZGdlLnRhcmdldEluTGNhID0gdGFyZ2V0Tm9kZTtcblxuICAgIGlmIChzb3VyY2VOb2RlID09IHRhcmdldE5vZGUpXG4gICAge1xuICAgICAgZWRnZS5sY2EgPSBzb3VyY2VOb2RlLmdldE93bmVyKCk7XG4gICAgICBjb250aW51ZTtcbiAgICB9XG5cbiAgICBzb3VyY2VBbmNlc3RvckdyYXBoID0gc291cmNlTm9kZS5nZXRPd25lcigpO1xuXG4gICAgd2hpbGUgKGVkZ2UubGNhID09IG51bGwpXG4gICAge1xuICAgICAgdGFyZ2V0QW5jZXN0b3JHcmFwaCA9IHRhcmdldE5vZGUuZ2V0T3duZXIoKTtcblxuICAgICAgd2hpbGUgKGVkZ2UubGNhID09IG51bGwpXG4gICAgICB7XG4gICAgICAgIGlmICh0YXJnZXRBbmNlc3RvckdyYXBoID09IHNvdXJjZUFuY2VzdG9yR3JhcGgpXG4gICAgICAgIHtcbiAgICAgICAgICBlZGdlLmxjYSA9IHRhcmdldEFuY2VzdG9yR3JhcGg7XG4gICAgICAgICAgYnJlYWs7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAodGFyZ2V0QW5jZXN0b3JHcmFwaCA9PSB0aGlzLnJvb3RHcmFwaClcbiAgICAgICAge1xuICAgICAgICAgIGJyZWFrO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKGVkZ2UubGNhICE9IG51bGwpIHtcbiAgICAgICAgICB0aHJvdyBcImFzc2VydCBmYWlsZWRcIjtcbiAgICAgICAgfVxuICAgICAgICBlZGdlLnRhcmdldEluTGNhID0gdGFyZ2V0QW5jZXN0b3JHcmFwaC5nZXRQYXJlbnQoKTtcbiAgICAgICAgdGFyZ2V0QW5jZXN0b3JHcmFwaCA9IGVkZ2UudGFyZ2V0SW5MY2EuZ2V0T3duZXIoKTtcbiAgICAgIH1cblxuICAgICAgaWYgKHNvdXJjZUFuY2VzdG9yR3JhcGggPT0gdGhpcy5yb290R3JhcGgpXG4gICAgICB7XG4gICAgICAgIGJyZWFrO1xuICAgICAgfVxuXG4gICAgICBpZiAoZWRnZS5sY2EgPT0gbnVsbClcbiAgICAgIHtcbiAgICAgICAgZWRnZS5zb3VyY2VJbkxjYSA9IHNvdXJjZUFuY2VzdG9yR3JhcGguZ2V0UGFyZW50KCk7XG4gICAgICAgIHNvdXJjZUFuY2VzdG9yR3JhcGggPSBlZGdlLnNvdXJjZUluTGNhLmdldE93bmVyKCk7XG4gICAgICB9XG4gICAgfVxuXG4gICAgaWYgKGVkZ2UubGNhID09IG51bGwpIHtcbiAgICAgIHRocm93IFwiYXNzZXJ0IGZhaWxlZFwiO1xuICAgIH1cbiAgfVxufTtcblxuTEdyYXBoTWFuYWdlci5wcm90b3R5cGUuY2FsY0xvd2VzdENvbW1vbkFuY2VzdG9yID0gZnVuY3Rpb24gKGZpcnN0Tm9kZSwgc2Vjb25kTm9kZSlcbntcbiAgaWYgKGZpcnN0Tm9kZSA9PSBzZWNvbmROb2RlKVxuICB7XG4gICAgcmV0dXJuIGZpcnN0Tm9kZS5nZXRPd25lcigpO1xuICB9XG4gIHZhciBmaXJzdE93bmVyR3JhcGggPSBmaXJzdE5vZGUuZ2V0T3duZXIoKTtcblxuICBkb1xuICB7XG4gICAgaWYgKGZpcnN0T3duZXJHcmFwaCA9PSBudWxsKVxuICAgIHtcbiAgICAgIGJyZWFrO1xuICAgIH1cbiAgICB2YXIgc2Vjb25kT3duZXJHcmFwaCA9IHNlY29uZE5vZGUuZ2V0T3duZXIoKTtcblxuICAgIGRvXG4gICAge1xuICAgICAgaWYgKHNlY29uZE93bmVyR3JhcGggPT0gbnVsbClcbiAgICAgIHtcbiAgICAgICAgYnJlYWs7XG4gICAgICB9XG5cbiAgICAgIGlmIChzZWNvbmRPd25lckdyYXBoID09IGZpcnN0T3duZXJHcmFwaClcbiAgICAgIHtcbiAgICAgICAgcmV0dXJuIHNlY29uZE93bmVyR3JhcGg7XG4gICAgICB9XG4gICAgICBzZWNvbmRPd25lckdyYXBoID0gc2Vjb25kT3duZXJHcmFwaC5nZXRQYXJlbnQoKS5nZXRPd25lcigpO1xuICAgIH0gd2hpbGUgKHRydWUpO1xuXG4gICAgZmlyc3RPd25lckdyYXBoID0gZmlyc3RPd25lckdyYXBoLmdldFBhcmVudCgpLmdldE93bmVyKCk7XG4gIH0gd2hpbGUgKHRydWUpO1xuXG4gIHJldHVybiBmaXJzdE93bmVyR3JhcGg7XG59O1xuXG5MR3JhcGhNYW5hZ2VyLnByb3RvdHlwZS5jYWxjSW5jbHVzaW9uVHJlZURlcHRocyA9IGZ1bmN0aW9uIChncmFwaCwgZGVwdGgpIHtcbiAgaWYgKGdyYXBoID09IG51bGwgJiYgZGVwdGggPT0gbnVsbCkge1xuICAgIGdyYXBoID0gdGhpcy5yb290R3JhcGg7XG4gICAgZGVwdGggPSAxO1xuICB9XG4gIHZhciBub2RlO1xuXG4gIHZhciBub2RlcyA9IGdyYXBoLmdldE5vZGVzKCk7XG4gIHZhciBzID0gbm9kZXMubGVuZ3RoO1xuICBmb3IgKHZhciBpID0gMDsgaSA8IHM7IGkrKylcbiAge1xuICAgIG5vZGUgPSBub2Rlc1tpXTtcbiAgICBub2RlLmluY2x1c2lvblRyZWVEZXB0aCA9IGRlcHRoO1xuXG4gICAgaWYgKG5vZGUuY2hpbGQgIT0gbnVsbClcbiAgICB7XG4gICAgICB0aGlzLmNhbGNJbmNsdXNpb25UcmVlRGVwdGhzKG5vZGUuY2hpbGQsIGRlcHRoICsgMSk7XG4gICAgfVxuICB9XG59O1xuXG5MR3JhcGhNYW5hZ2VyLnByb3RvdHlwZS5pbmNsdWRlc0ludmFsaWRFZGdlID0gZnVuY3Rpb24gKClcbntcbiAgdmFyIGVkZ2U7XG5cbiAgdmFyIHMgPSB0aGlzLmVkZ2VzLmxlbmd0aDtcbiAgZm9yICh2YXIgaSA9IDA7IGkgPCBzOyBpKyspXG4gIHtcbiAgICBlZGdlID0gdGhpcy5lZGdlc1tpXTtcblxuICAgIGlmICh0aGlzLmlzT25lQW5jZXN0b3JPZk90aGVyKGVkZ2Uuc291cmNlLCBlZGdlLnRhcmdldCkpXG4gICAge1xuICAgICAgcmV0dXJuIHRydWU7XG4gICAgfVxuICB9XG4gIHJldHVybiBmYWxzZTtcbn07XG5cbm1vZHVsZS5leHBvcnRzID0gTEdyYXBoTWFuYWdlcjtcbiIsImZ1bmN0aW9uIExHcmFwaE9iamVjdCh2R3JhcGhPYmplY3QpIHtcbiAgdGhpcy52R3JhcGhPYmplY3QgPSB2R3JhcGhPYmplY3Q7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gTEdyYXBoT2JqZWN0O1xuIiwidmFyIExHcmFwaE9iamVjdCA9IHJlcXVpcmUoJy4vTEdyYXBoT2JqZWN0Jyk7XG52YXIgSW50ZWdlciA9IHJlcXVpcmUoJy4vSW50ZWdlcicpO1xudmFyIFJlY3RhbmdsZUQgPSByZXF1aXJlKCcuL1JlY3RhbmdsZUQnKTtcbnZhciBMYXlvdXRDb25zdGFudHMgPSByZXF1aXJlKCcuL0xheW91dENvbnN0YW50cycpO1xudmFyIFJhbmRvbVNlZWQgPSByZXF1aXJlKCcuL1JhbmRvbVNlZWQnKTtcbnZhciBQb2ludEQgPSByZXF1aXJlKCcuL1BvaW50RCcpO1xudmFyIEhhc2hTZXQgPSByZXF1aXJlKCcuL0hhc2hTZXQnKTtcblxuZnVuY3Rpb24gTE5vZGUoZ20sIGxvYywgc2l6ZSwgdk5vZGUpIHtcbiAgLy9BbHRlcm5hdGl2ZSBjb25zdHJ1Y3RvciAxIDogTE5vZGUoTEdyYXBoTWFuYWdlciBnbSwgUG9pbnQgbG9jLCBEaW1lbnNpb24gc2l6ZSwgT2JqZWN0IHZOb2RlKVxuICBpZiAoc2l6ZSA9PSBudWxsICYmIHZOb2RlID09IG51bGwpIHtcbiAgICB2Tm9kZSA9IGxvYztcbiAgfVxuXG4gIExHcmFwaE9iamVjdC5jYWxsKHRoaXMsIHZOb2RlKTtcblxuICAvL0FsdGVybmF0aXZlIGNvbnN0cnVjdG9yIDIgOiBMTm9kZShMYXlvdXQgbGF5b3V0LCBPYmplY3Qgdk5vZGUpXG4gIGlmIChnbS5ncmFwaE1hbmFnZXIgIT0gbnVsbClcbiAgICBnbSA9IGdtLmdyYXBoTWFuYWdlcjtcblxuICB0aGlzLmVzdGltYXRlZFNpemUgPSBJbnRlZ2VyLk1JTl9WQUxVRTtcbiAgdGhpcy5pbmNsdXNpb25UcmVlRGVwdGggPSBJbnRlZ2VyLk1BWF9WQUxVRTtcbiAgdGhpcy52R3JhcGhPYmplY3QgPSB2Tm9kZTtcbiAgdGhpcy5lZGdlcyA9IFtdO1xuICB0aGlzLmdyYXBoTWFuYWdlciA9IGdtO1xuXG4gIGlmIChzaXplICE9IG51bGwgJiYgbG9jICE9IG51bGwpXG4gICAgdGhpcy5yZWN0ID0gbmV3IFJlY3RhbmdsZUQobG9jLngsIGxvYy55LCBzaXplLndpZHRoLCBzaXplLmhlaWdodCk7XG4gIGVsc2VcbiAgICB0aGlzLnJlY3QgPSBuZXcgUmVjdGFuZ2xlRCgpO1xufVxuXG5MTm9kZS5wcm90b3R5cGUgPSBPYmplY3QuY3JlYXRlKExHcmFwaE9iamVjdC5wcm90b3R5cGUpO1xuZm9yICh2YXIgcHJvcCBpbiBMR3JhcGhPYmplY3QpIHtcbiAgTE5vZGVbcHJvcF0gPSBMR3JhcGhPYmplY3RbcHJvcF07XG59XG5cbkxOb2RlLnByb3RvdHlwZS5nZXRFZGdlcyA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLmVkZ2VzO1xufTtcblxuTE5vZGUucHJvdG90eXBlLmdldENoaWxkID0gZnVuY3Rpb24gKClcbntcbiAgcmV0dXJuIHRoaXMuY2hpbGQ7XG59O1xuXG5MTm9kZS5wcm90b3R5cGUuZ2V0T3duZXIgPSBmdW5jdGlvbiAoKVxue1xuICBpZiAodGhpcy5vd25lciAhPSBudWxsKSB7XG4gICAgaWYgKCEodGhpcy5vd25lciA9PSBudWxsIHx8IHRoaXMub3duZXIuZ2V0Tm9kZXMoKS5pbmRleE9mKHRoaXMpID4gLTEpKSB7XG4gICAgICB0aHJvdyBcImFzc2VydCBmYWlsZWRcIjtcbiAgICB9XG4gIH1cblxuICByZXR1cm4gdGhpcy5vd25lcjtcbn07XG5cbkxOb2RlLnByb3RvdHlwZS5nZXRXaWR0aCA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLnJlY3Qud2lkdGg7XG59O1xuXG5MTm9kZS5wcm90b3R5cGUuc2V0V2lkdGggPSBmdW5jdGlvbiAod2lkdGgpXG57XG4gIHRoaXMucmVjdC53aWR0aCA9IHdpZHRoO1xufTtcblxuTE5vZGUucHJvdG90eXBlLmdldEhlaWdodCA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLnJlY3QuaGVpZ2h0O1xufTtcblxuTE5vZGUucHJvdG90eXBlLnNldEhlaWdodCA9IGZ1bmN0aW9uIChoZWlnaHQpXG57XG4gIHRoaXMucmVjdC5oZWlnaHQgPSBoZWlnaHQ7XG59O1xuXG5MTm9kZS5wcm90b3R5cGUuZ2V0Q2VudGVyWCA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLnJlY3QueCArIHRoaXMucmVjdC53aWR0aCAvIDI7XG59O1xuXG5MTm9kZS5wcm90b3R5cGUuZ2V0Q2VudGVyWSA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLnJlY3QueSArIHRoaXMucmVjdC5oZWlnaHQgLyAyO1xufTtcblxuTE5vZGUucHJvdG90eXBlLmdldENlbnRlciA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiBuZXcgUG9pbnREKHRoaXMucmVjdC54ICsgdGhpcy5yZWN0LndpZHRoIC8gMixcbiAgICAgICAgICB0aGlzLnJlY3QueSArIHRoaXMucmVjdC5oZWlnaHQgLyAyKTtcbn07XG5cbkxOb2RlLnByb3RvdHlwZS5nZXRMb2NhdGlvbiA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiBuZXcgUG9pbnREKHRoaXMucmVjdC54LCB0aGlzLnJlY3QueSk7XG59O1xuXG5MTm9kZS5wcm90b3R5cGUuZ2V0UmVjdCA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLnJlY3Q7XG59O1xuXG5MTm9kZS5wcm90b3R5cGUuZ2V0RGlhZ29uYWwgPSBmdW5jdGlvbiAoKVxue1xuICByZXR1cm4gTWF0aC5zcXJ0KHRoaXMucmVjdC53aWR0aCAqIHRoaXMucmVjdC53aWR0aCArXG4gICAgICAgICAgdGhpcy5yZWN0LmhlaWdodCAqIHRoaXMucmVjdC5oZWlnaHQpO1xufTtcblxuTE5vZGUucHJvdG90eXBlLnNldFJlY3QgPSBmdW5jdGlvbiAodXBwZXJMZWZ0LCBkaW1lbnNpb24pXG57XG4gIHRoaXMucmVjdC54ID0gdXBwZXJMZWZ0Lng7XG4gIHRoaXMucmVjdC55ID0gdXBwZXJMZWZ0Lnk7XG4gIHRoaXMucmVjdC53aWR0aCA9IGRpbWVuc2lvbi53aWR0aDtcbiAgdGhpcy5yZWN0LmhlaWdodCA9IGRpbWVuc2lvbi5oZWlnaHQ7XG59O1xuXG5MTm9kZS5wcm90b3R5cGUuc2V0Q2VudGVyID0gZnVuY3Rpb24gKGN4LCBjeSlcbntcbiAgdGhpcy5yZWN0LnggPSBjeCAtIHRoaXMucmVjdC53aWR0aCAvIDI7XG4gIHRoaXMucmVjdC55ID0gY3kgLSB0aGlzLnJlY3QuaGVpZ2h0IC8gMjtcbn07XG5cbkxOb2RlLnByb3RvdHlwZS5zZXRMb2NhdGlvbiA9IGZ1bmN0aW9uICh4LCB5KVxue1xuICB0aGlzLnJlY3QueCA9IHg7XG4gIHRoaXMucmVjdC55ID0geTtcbn07XG5cbkxOb2RlLnByb3RvdHlwZS5tb3ZlQnkgPSBmdW5jdGlvbiAoZHgsIGR5KVxue1xuICB0aGlzLnJlY3QueCArPSBkeDtcbiAgdGhpcy5yZWN0LnkgKz0gZHk7XG59O1xuXG5MTm9kZS5wcm90b3R5cGUuZ2V0RWRnZUxpc3RUb05vZGUgPSBmdW5jdGlvbiAodG8pXG57XG4gIHZhciBlZGdlTGlzdCA9IFtdO1xuICB2YXIgZWRnZTtcblxuICBmb3IgKHZhciBvYmogaW4gdGhpcy5lZGdlcylcbiAge1xuICAgIGVkZ2UgPSBvYmo7XG5cbiAgICBpZiAoZWRnZS50YXJnZXQgPT0gdG8pXG4gICAge1xuICAgICAgaWYgKGVkZ2Uuc291cmNlICE9IHRoaXMpXG4gICAgICAgIHRocm93IFwiSW5jb3JyZWN0IGVkZ2Ugc291cmNlIVwiO1xuXG4gICAgICBlZGdlTGlzdC5wdXNoKGVkZ2UpO1xuICAgIH1cbiAgfVxuXG4gIHJldHVybiBlZGdlTGlzdDtcbn07XG5cbkxOb2RlLnByb3RvdHlwZS5nZXRFZGdlc0JldHdlZW4gPSBmdW5jdGlvbiAob3RoZXIpXG57XG4gIHZhciBlZGdlTGlzdCA9IFtdO1xuICB2YXIgZWRnZTtcblxuICBmb3IgKHZhciBvYmogaW4gdGhpcy5lZGdlcylcbiAge1xuICAgIGVkZ2UgPSB0aGlzLmVkZ2VzW29ial07XG5cbiAgICBpZiAoIShlZGdlLnNvdXJjZSA9PSB0aGlzIHx8IGVkZ2UudGFyZ2V0ID09IHRoaXMpKVxuICAgICAgdGhyb3cgXCJJbmNvcnJlY3QgZWRnZSBzb3VyY2UgYW5kL29yIHRhcmdldFwiO1xuXG4gICAgaWYgKChlZGdlLnRhcmdldCA9PSBvdGhlcikgfHwgKGVkZ2Uuc291cmNlID09IG90aGVyKSlcbiAgICB7XG4gICAgICBlZGdlTGlzdC5wdXNoKGVkZ2UpO1xuICAgIH1cbiAgfVxuXG4gIHJldHVybiBlZGdlTGlzdDtcbn07XG5cbkxOb2RlLnByb3RvdHlwZS5nZXROZWlnaGJvcnNMaXN0ID0gZnVuY3Rpb24gKClcbntcbiAgdmFyIG5laWdoYm9ycyA9IG5ldyBIYXNoU2V0KCk7XG4gIHZhciBlZGdlO1xuXG4gIGZvciAodmFyIG9iaiBpbiB0aGlzLmVkZ2VzKVxuICB7XG4gICAgZWRnZSA9IHRoaXMuZWRnZXNbb2JqXTtcblxuICAgIGlmIChlZGdlLnNvdXJjZSA9PSB0aGlzKVxuICAgIHtcbiAgICAgIG5laWdoYm9ycy5hZGQoZWRnZS50YXJnZXQpO1xuICAgIH1cbiAgICBlbHNlXG4gICAge1xuICAgICAgaWYgKCFlZGdlLnRhcmdldCA9PSB0aGlzKVxuICAgICAgICB0aHJvdyBcIkluY29ycmVjdCBpbmNpZGVuY3khXCI7XG4gICAgICBuZWlnaGJvcnMuYWRkKGVkZ2Uuc291cmNlKTtcbiAgICB9XG4gIH1cblxuICByZXR1cm4gbmVpZ2hib3JzO1xufTtcblxuTE5vZGUucHJvdG90eXBlLndpdGhDaGlsZHJlbiA9IGZ1bmN0aW9uICgpXG57XG4gIHZhciB3aXRoTmVpZ2hib3JzTGlzdCA9IFtdO1xuICB2YXIgY2hpbGROb2RlO1xuXG4gIHdpdGhOZWlnaGJvcnNMaXN0LnB1c2godGhpcyk7XG5cbiAgaWYgKHRoaXMuY2hpbGQgIT0gbnVsbClcbiAge1xuICAgIHZhciBub2RlcyA9IHRoaXMuY2hpbGQuZ2V0Tm9kZXMoKTtcbiAgICBmb3IgKHZhciBpID0gMDsgaSA8IG5vZGVzLmxlbmd0aDsgaSsrKVxuICAgIHtcbiAgICAgIGNoaWxkTm9kZSA9IG5vZGVzW2ldO1xuXG4gICAgICB3aXRoTmVpZ2hib3JzTGlzdCA9IHdpdGhOZWlnaGJvcnNMaXN0LmNvbmNhdChjaGlsZE5vZGUud2l0aENoaWxkcmVuKCkpO1xuICAgIH1cbiAgfVxuXG4gIHJldHVybiB3aXRoTmVpZ2hib3JzTGlzdDtcbn07XG5cbkxOb2RlLnByb3RvdHlwZS5nZXRFc3RpbWF0ZWRTaXplID0gZnVuY3Rpb24gKCkge1xuICBpZiAodGhpcy5lc3RpbWF0ZWRTaXplID09IEludGVnZXIuTUlOX1ZBTFVFKSB7XG4gICAgdGhyb3cgXCJhc3NlcnQgZmFpbGVkXCI7XG4gIH1cbiAgcmV0dXJuIHRoaXMuZXN0aW1hdGVkU2l6ZTtcbn07XG5cbkxOb2RlLnByb3RvdHlwZS5jYWxjRXN0aW1hdGVkU2l6ZSA9IGZ1bmN0aW9uICgpIHtcbiAgaWYgKHRoaXMuY2hpbGQgPT0gbnVsbClcbiAge1xuICAgIHJldHVybiB0aGlzLmVzdGltYXRlZFNpemUgPSBNYXRoLmZsb29yKCh0aGlzLnJlY3Qud2lkdGggKyB0aGlzLnJlY3QuaGVpZ2h0KSAvIDIpO1xuICB9XG4gIGVsc2VcbiAge1xuICAgIHRoaXMuZXN0aW1hdGVkU2l6ZSA9IHRoaXMuY2hpbGQuY2FsY0VzdGltYXRlZFNpemUoKTtcbiAgICB0aGlzLnJlY3Qud2lkdGggPSB0aGlzLmVzdGltYXRlZFNpemU7XG4gICAgdGhpcy5yZWN0LmhlaWdodCA9IHRoaXMuZXN0aW1hdGVkU2l6ZTtcblxuICAgIHJldHVybiB0aGlzLmVzdGltYXRlZFNpemU7XG4gIH1cbn07XG5cbkxOb2RlLnByb3RvdHlwZS5zY2F0dGVyID0gZnVuY3Rpb24gKCkge1xuICB2YXIgcmFuZG9tQ2VudGVyWDtcbiAgdmFyIHJhbmRvbUNlbnRlclk7XG5cbiAgdmFyIG1pblggPSAtTGF5b3V0Q29uc3RhbnRzLklOSVRJQUxfV09STERfQk9VTkRBUlk7XG4gIHZhciBtYXhYID0gTGF5b3V0Q29uc3RhbnRzLklOSVRJQUxfV09STERfQk9VTkRBUlk7XG4gIHJhbmRvbUNlbnRlclggPSBMYXlvdXRDb25zdGFudHMuV09STERfQ0VOVEVSX1ggK1xuICAgICAgICAgIChSYW5kb21TZWVkLm5leHREb3VibGUoKSAqIChtYXhYIC0gbWluWCkpICsgbWluWDtcblxuICB2YXIgbWluWSA9IC1MYXlvdXRDb25zdGFudHMuSU5JVElBTF9XT1JMRF9CT1VOREFSWTtcbiAgdmFyIG1heFkgPSBMYXlvdXRDb25zdGFudHMuSU5JVElBTF9XT1JMRF9CT1VOREFSWTtcbiAgcmFuZG9tQ2VudGVyWSA9IExheW91dENvbnN0YW50cy5XT1JMRF9DRU5URVJfWSArXG4gICAgICAgICAgKFJhbmRvbVNlZWQubmV4dERvdWJsZSgpICogKG1heFkgLSBtaW5ZKSkgKyBtaW5ZO1xuXG4gIHRoaXMucmVjdC54ID0gcmFuZG9tQ2VudGVyWDtcbiAgdGhpcy5yZWN0LnkgPSByYW5kb21DZW50ZXJZXG59O1xuXG5MTm9kZS5wcm90b3R5cGUudXBkYXRlQm91bmRzID0gZnVuY3Rpb24gKCkge1xuICBpZiAodGhpcy5nZXRDaGlsZCgpID09IG51bGwpIHtcbiAgICB0aHJvdyBcImFzc2VydCBmYWlsZWRcIjtcbiAgfVxuICBpZiAodGhpcy5nZXRDaGlsZCgpLmdldE5vZGVzKCkubGVuZ3RoICE9IDApXG4gIHtcbiAgICAvLyB3cmFwIHRoZSBjaGlsZHJlbiBub2RlcyBieSByZS1hcnJhbmdpbmcgdGhlIGJvdW5kYXJpZXNcbiAgICB2YXIgY2hpbGRHcmFwaCA9IHRoaXMuZ2V0Q2hpbGQoKTtcbiAgICBjaGlsZEdyYXBoLnVwZGF0ZUJvdW5kcyh0cnVlKTtcblxuICAgIHRoaXMucmVjdC54ID0gY2hpbGRHcmFwaC5nZXRMZWZ0KCk7XG4gICAgdGhpcy5yZWN0LnkgPSBjaGlsZEdyYXBoLmdldFRvcCgpO1xuXG4gICAgdGhpcy5zZXRXaWR0aChjaGlsZEdyYXBoLmdldFJpZ2h0KCkgLSBjaGlsZEdyYXBoLmdldExlZnQoKSk7XG4gICAgdGhpcy5zZXRIZWlnaHQoY2hpbGRHcmFwaC5nZXRCb3R0b20oKSAtIGNoaWxkR3JhcGguZ2V0VG9wKCkpO1xuICB9XG59O1xuXG5MTm9kZS5wcm90b3R5cGUuZ2V0SW5jbHVzaW9uVHJlZURlcHRoID0gZnVuY3Rpb24gKClcbntcbiAgaWYgKHRoaXMuaW5jbHVzaW9uVHJlZURlcHRoID09IEludGVnZXIuTUFYX1ZBTFVFKSB7XG4gICAgdGhyb3cgXCJhc3NlcnQgZmFpbGVkXCI7XG4gIH1cbiAgcmV0dXJuIHRoaXMuaW5jbHVzaW9uVHJlZURlcHRoO1xufTtcblxuTE5vZGUucHJvdG90eXBlLnRyYW5zZm9ybSA9IGZ1bmN0aW9uICh0cmFucylcbntcbiAgdmFyIGxlZnQgPSB0aGlzLnJlY3QueDtcblxuICBpZiAobGVmdCA+IExheW91dENvbnN0YW50cy5XT1JMRF9CT1VOREFSWSlcbiAge1xuICAgIGxlZnQgPSBMYXlvdXRDb25zdGFudHMuV09STERfQk9VTkRBUlk7XG4gIH1cbiAgZWxzZSBpZiAobGVmdCA8IC1MYXlvdXRDb25zdGFudHMuV09STERfQk9VTkRBUlkpXG4gIHtcbiAgICBsZWZ0ID0gLUxheW91dENvbnN0YW50cy5XT1JMRF9CT1VOREFSWTtcbiAgfVxuXG4gIHZhciB0b3AgPSB0aGlzLnJlY3QueTtcblxuICBpZiAodG9wID4gTGF5b3V0Q29uc3RhbnRzLldPUkxEX0JPVU5EQVJZKVxuICB7XG4gICAgdG9wID0gTGF5b3V0Q29uc3RhbnRzLldPUkxEX0JPVU5EQVJZO1xuICB9XG4gIGVsc2UgaWYgKHRvcCA8IC1MYXlvdXRDb25zdGFudHMuV09STERfQk9VTkRBUlkpXG4gIHtcbiAgICB0b3AgPSAtTGF5b3V0Q29uc3RhbnRzLldPUkxEX0JPVU5EQVJZO1xuICB9XG5cbiAgdmFyIGxlZnRUb3AgPSBuZXcgUG9pbnREKGxlZnQsIHRvcCk7XG4gIHZhciB2TGVmdFRvcCA9IHRyYW5zLmludmVyc2VUcmFuc2Zvcm1Qb2ludChsZWZ0VG9wKTtcblxuICB0aGlzLnNldExvY2F0aW9uKHZMZWZ0VG9wLngsIHZMZWZ0VG9wLnkpO1xufTtcblxuTE5vZGUucHJvdG90eXBlLmdldExlZnQgPSBmdW5jdGlvbiAoKVxue1xuICByZXR1cm4gdGhpcy5yZWN0Lng7XG59O1xuXG5MTm9kZS5wcm90b3R5cGUuZ2V0UmlnaHQgPSBmdW5jdGlvbiAoKVxue1xuICByZXR1cm4gdGhpcy5yZWN0LnggKyB0aGlzLnJlY3Qud2lkdGg7XG59O1xuXG5MTm9kZS5wcm90b3R5cGUuZ2V0VG9wID0gZnVuY3Rpb24gKClcbntcbiAgcmV0dXJuIHRoaXMucmVjdC55O1xufTtcblxuTE5vZGUucHJvdG90eXBlLmdldEJvdHRvbSA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLnJlY3QueSArIHRoaXMucmVjdC5oZWlnaHQ7XG59O1xuXG5MTm9kZS5wcm90b3R5cGUuZ2V0UGFyZW50ID0gZnVuY3Rpb24gKClcbntcbiAgaWYgKHRoaXMub3duZXIgPT0gbnVsbClcbiAge1xuICAgIHJldHVybiBudWxsO1xuICB9XG5cbiAgcmV0dXJuIHRoaXMub3duZXIuZ2V0UGFyZW50KCk7XG59O1xuXG5tb2R1bGUuZXhwb3J0cyA9IExOb2RlO1xuIiwidmFyIExheW91dENvbnN0YW50cyA9IHJlcXVpcmUoJy4vTGF5b3V0Q29uc3RhbnRzJyk7XG52YXIgSGFzaE1hcCA9IHJlcXVpcmUoJy4vSGFzaE1hcCcpO1xudmFyIExHcmFwaE1hbmFnZXIgPSByZXF1aXJlKCcuL0xHcmFwaE1hbmFnZXInKTtcbnZhciBMTm9kZSA9IHJlcXVpcmUoJy4vTE5vZGUnKTtcbnZhciBMRWRnZSA9IHJlcXVpcmUoJy4vTEVkZ2UnKTtcbnZhciBMR3JhcGggPSByZXF1aXJlKCcuL0xHcmFwaCcpO1xudmFyIFBvaW50RCA9IHJlcXVpcmUoJy4vUG9pbnREJyk7XG52YXIgVHJhbnNmb3JtID0gcmVxdWlyZSgnLi9UcmFuc2Zvcm0nKTtcbnZhciBFbWl0dGVyID0gcmVxdWlyZSgnLi9FbWl0dGVyJyk7XG52YXIgSGFzaFNldCA9IHJlcXVpcmUoJy4vSGFzaFNldCcpO1xuXG5mdW5jdGlvbiBMYXlvdXQoaXNSZW1vdGVVc2UpIHtcbiAgRW1pdHRlci5jYWxsKCB0aGlzICk7XG5cbiAgLy9MYXlvdXQgUXVhbGl0eTogMDpwcm9vZiwgMTpkZWZhdWx0LCAyOmRyYWZ0XG4gIHRoaXMubGF5b3V0UXVhbGl0eSA9IExheW91dENvbnN0YW50cy5ERUZBVUxUX1FVQUxJVFk7XG4gIC8vV2hldGhlciBsYXlvdXQgc2hvdWxkIGNyZWF0ZSBiZW5kcG9pbnRzIGFzIG5lZWRlZCBvciBub3RcbiAgdGhpcy5jcmVhdGVCZW5kc0FzTmVlZGVkID1cbiAgICAgICAgICBMYXlvdXRDb25zdGFudHMuREVGQVVMVF9DUkVBVEVfQkVORFNfQVNfTkVFREVEO1xuICAvL1doZXRoZXIgbGF5b3V0IHNob3VsZCBiZSBpbmNyZW1lbnRhbCBvciBub3RcbiAgdGhpcy5pbmNyZW1lbnRhbCA9IExheW91dENvbnN0YW50cy5ERUZBVUxUX0lOQ1JFTUVOVEFMO1xuICAvL1doZXRoZXIgd2UgYW5pbWF0ZSBmcm9tIGJlZm9yZSB0byBhZnRlciBsYXlvdXQgbm9kZSBwb3NpdGlvbnNcbiAgdGhpcy5hbmltYXRpb25PbkxheW91dCA9XG4gICAgICAgICAgTGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfQU5JTUFUSU9OX09OX0xBWU9VVDtcbiAgLy9XaGV0aGVyIHdlIGFuaW1hdGUgdGhlIGxheW91dCBwcm9jZXNzIG9yIG5vdFxuICB0aGlzLmFuaW1hdGlvbkR1cmluZ0xheW91dCA9IExheW91dENvbnN0YW50cy5ERUZBVUxUX0FOSU1BVElPTl9EVVJJTkdfTEFZT1VUO1xuICAvL051bWJlciBpdGVyYXRpb25zIHRoYXQgc2hvdWxkIGJlIGRvbmUgYmV0d2VlbiB0d28gc3VjY2Vzc2l2ZSBhbmltYXRpb25zXG4gIHRoaXMuYW5pbWF0aW9uUGVyaW9kID0gTGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfQU5JTUFUSU9OX1BFUklPRDtcbiAgLyoqXG4gICAqIFdoZXRoZXIgb3Igbm90IGxlYWYgbm9kZXMgKG5vbi1jb21wb3VuZCBub2RlcykgYXJlIG9mIHVuaWZvcm0gc2l6ZXMuIFdoZW5cbiAgICogdGhleSBhcmUsIGJvdGggc3ByaW5nIGFuZCByZXB1bHNpb24gZm9yY2VzIGJldHdlZW4gdHdvIGxlYWYgbm9kZXMgY2FuIGJlXG4gICAqIGNhbGN1bGF0ZWQgd2l0aG91dCB0aGUgZXhwZW5zaXZlIGNsaXBwaW5nIHBvaW50IGNhbGN1bGF0aW9ucywgcmVzdWx0aW5nXG4gICAqIGluIG1ham9yIHNwZWVkLXVwLlxuICAgKi9cbiAgdGhpcy51bmlmb3JtTGVhZk5vZGVTaXplcyA9XG4gICAgICAgICAgTGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfVU5JRk9STV9MRUFGX05PREVfU0laRVM7XG4gIC8qKlxuICAgKiBUaGlzIGlzIHVzZWQgZm9yIGNyZWF0aW9uIG9mIGJlbmRwb2ludHMgYnkgdXNpbmcgZHVtbXkgbm9kZXMgYW5kIGVkZ2VzLlxuICAgKiBNYXBzIGFuIExFZGdlIHRvIGl0cyBkdW1teSBiZW5kcG9pbnQgcGF0aC5cbiAgICovXG4gIHRoaXMuZWRnZVRvRHVtbXlOb2RlcyA9IG5ldyBIYXNoTWFwKCk7XG4gIHRoaXMuZ3JhcGhNYW5hZ2VyID0gbmV3IExHcmFwaE1hbmFnZXIodGhpcyk7XG4gIHRoaXMuaXNMYXlvdXRGaW5pc2hlZCA9IGZhbHNlO1xuICB0aGlzLmlzU3ViTGF5b3V0ID0gZmFsc2U7XG4gIHRoaXMuaXNSZW1vdGVVc2UgPSBmYWxzZTtcblxuICBpZiAoaXNSZW1vdGVVc2UgIT0gbnVsbCkge1xuICAgIHRoaXMuaXNSZW1vdGVVc2UgPSBpc1JlbW90ZVVzZTtcbiAgfVxufVxuXG5MYXlvdXQuUkFORE9NX1NFRUQgPSAxO1xuXG5MYXlvdXQucHJvdG90eXBlID0gT2JqZWN0LmNyZWF0ZSggRW1pdHRlci5wcm90b3R5cGUgKTtcblxuTGF5b3V0LnByb3RvdHlwZS5nZXRHcmFwaE1hbmFnZXIgPSBmdW5jdGlvbiAoKSB7XG4gIHJldHVybiB0aGlzLmdyYXBoTWFuYWdlcjtcbn07XG5cbkxheW91dC5wcm90b3R5cGUuZ2V0QWxsTm9kZXMgPSBmdW5jdGlvbiAoKSB7XG4gIHJldHVybiB0aGlzLmdyYXBoTWFuYWdlci5nZXRBbGxOb2RlcygpO1xufTtcblxuTGF5b3V0LnByb3RvdHlwZS5nZXRBbGxFZGdlcyA9IGZ1bmN0aW9uICgpIHtcbiAgcmV0dXJuIHRoaXMuZ3JhcGhNYW5hZ2VyLmdldEFsbEVkZ2VzKCk7XG59O1xuXG5MYXlvdXQucHJvdG90eXBlLmdldEFsbE5vZGVzVG9BcHBseUdyYXZpdGF0aW9uID0gZnVuY3Rpb24gKCkge1xuICByZXR1cm4gdGhpcy5ncmFwaE1hbmFnZXIuZ2V0QWxsTm9kZXNUb0FwcGx5R3Jhdml0YXRpb24oKTtcbn07XG5cbkxheW91dC5wcm90b3R5cGUubmV3R3JhcGhNYW5hZ2VyID0gZnVuY3Rpb24gKCkge1xuICB2YXIgZ20gPSBuZXcgTEdyYXBoTWFuYWdlcih0aGlzKTtcbiAgdGhpcy5ncmFwaE1hbmFnZXIgPSBnbTtcbiAgcmV0dXJuIGdtO1xufTtcblxuTGF5b3V0LnByb3RvdHlwZS5uZXdHcmFwaCA9IGZ1bmN0aW9uICh2R3JhcGgpXG57XG4gIHJldHVybiBuZXcgTEdyYXBoKG51bGwsIHRoaXMuZ3JhcGhNYW5hZ2VyLCB2R3JhcGgpO1xufTtcblxuTGF5b3V0LnByb3RvdHlwZS5uZXdOb2RlID0gZnVuY3Rpb24gKHZOb2RlKVxue1xuICByZXR1cm4gbmV3IExOb2RlKHRoaXMuZ3JhcGhNYW5hZ2VyLCB2Tm9kZSk7XG59O1xuXG5MYXlvdXQucHJvdG90eXBlLm5ld0VkZ2UgPSBmdW5jdGlvbiAodkVkZ2UpXG57XG4gIHJldHVybiBuZXcgTEVkZ2UobnVsbCwgbnVsbCwgdkVkZ2UpO1xufTtcblxuTGF5b3V0LnByb3RvdHlwZS5ydW5MYXlvdXQgPSBmdW5jdGlvbiAoKVxue1xuICB0aGlzLmlzTGF5b3V0RmluaXNoZWQgPSBmYWxzZTtcblxuICB0aGlzLmluaXRQYXJhbWV0ZXJzKCk7XG4gIHZhciBpc0xheW91dFN1Y2Nlc3NmdWxsO1xuXG4gIGlmICgodGhpcy5ncmFwaE1hbmFnZXIuZ2V0Um9vdCgpID09IG51bGwpXG4gICAgICAgICAgfHwgdGhpcy5ncmFwaE1hbmFnZXIuZ2V0Um9vdCgpLmdldE5vZGVzKCkubGVuZ3RoID09IDBcbiAgICAgICAgICB8fCB0aGlzLmdyYXBoTWFuYWdlci5pbmNsdWRlc0ludmFsaWRFZGdlKCkpXG4gIHtcbiAgICBpc0xheW91dFN1Y2Nlc3NmdWxsID0gZmFsc2U7XG4gIH1cbiAgZWxzZVxuICB7XG4gICAgLy8gY2FsY3VsYXRlIGV4ZWN1dGlvbiB0aW1lXG4gICAgdmFyIHN0YXJ0VGltZSA9IDA7XG5cbiAgICBpZiAoIXRoaXMuaXNTdWJMYXlvdXQpXG4gICAge1xuICAgICAgc3RhcnRUaW1lID0gbmV3IERhdGUoKS5nZXRUaW1lKClcbiAgICB9XG5cbiAgICBpc0xheW91dFN1Y2Nlc3NmdWxsID0gdGhpcy5sYXlvdXQoKTtcblxuICAgIGlmICghdGhpcy5pc1N1YkxheW91dClcbiAgICB7XG4gICAgICB2YXIgZW5kVGltZSA9IG5ldyBEYXRlKCkuZ2V0VGltZSgpO1xuICAgICAgdmFyIGV4Y1RpbWUgPSBlbmRUaW1lIC0gc3RhcnRUaW1lO1xuICAgIH1cbiAgfVxuXG4gIGlmIChpc0xheW91dFN1Y2Nlc3NmdWxsKVxuICB7XG4gICAgaWYgKCF0aGlzLmlzU3ViTGF5b3V0KVxuICAgIHtcbiAgICAgIHRoaXMuZG9Qb3N0TGF5b3V0KCk7XG4gICAgfVxuICB9XG5cbiAgdGhpcy5pc0xheW91dEZpbmlzaGVkID0gdHJ1ZTtcblxuICByZXR1cm4gaXNMYXlvdXRTdWNjZXNzZnVsbDtcbn07XG5cbi8qKlxuICogVGhpcyBtZXRob2QgcGVyZm9ybXMgdGhlIG9wZXJhdGlvbnMgcmVxdWlyZWQgYWZ0ZXIgbGF5b3V0LlxuICovXG5MYXlvdXQucHJvdG90eXBlLmRvUG9zdExheW91dCA9IGZ1bmN0aW9uICgpXG57XG4gIC8vYXNzZXJ0ICFpc1N1YkxheW91dCA6IFwiU2hvdWxkIG5vdCBiZSBjYWxsZWQgb24gc3ViLWxheW91dCFcIjtcbiAgLy8gUHJvcGFnYXRlIGdlb21ldHJpYyBjaGFuZ2VzIHRvIHYtbGV2ZWwgb2JqZWN0c1xuICB0aGlzLnRyYW5zZm9ybSgpO1xuICB0aGlzLnVwZGF0ZSgpO1xufTtcblxuLyoqXG4gKiBUaGlzIG1ldGhvZCB1cGRhdGVzIHRoZSBnZW9tZXRyeSBvZiB0aGUgdGFyZ2V0IGdyYXBoIGFjY29yZGluZyB0b1xuICogY2FsY3VsYXRlZCBsYXlvdXQuXG4gKi9cbkxheW91dC5wcm90b3R5cGUudXBkYXRlMiA9IGZ1bmN0aW9uICgpIHtcbiAgLy8gdXBkYXRlIGJlbmQgcG9pbnRzXG4gIGlmICh0aGlzLmNyZWF0ZUJlbmRzQXNOZWVkZWQpXG4gIHtcbiAgICB0aGlzLmNyZWF0ZUJlbmRwb2ludHNGcm9tRHVtbXlOb2RlcygpO1xuXG4gICAgLy8gcmVzZXQgYWxsIGVkZ2VzLCBzaW5jZSB0aGUgdG9wb2xvZ3kgaGFzIGNoYW5nZWRcbiAgICB0aGlzLmdyYXBoTWFuYWdlci5yZXNldEFsbEVkZ2VzKCk7XG4gIH1cblxuICAvLyBwZXJmb3JtIGVkZ2UsIG5vZGUgYW5kIHJvb3QgdXBkYXRlcyBpZiBsYXlvdXQgaXMgbm90IGNhbGxlZFxuICAvLyByZW1vdGVseVxuICBpZiAoIXRoaXMuaXNSZW1vdGVVc2UpXG4gIHtcbiAgICAvLyB1cGRhdGUgYWxsIGVkZ2VzXG4gICAgdmFyIGVkZ2U7XG4gICAgdmFyIGFsbEVkZ2VzID0gdGhpcy5ncmFwaE1hbmFnZXIuZ2V0QWxsRWRnZXMoKTtcbiAgICBmb3IgKHZhciBpID0gMDsgaSA8IGFsbEVkZ2VzLmxlbmd0aDsgaSsrKVxuICAgIHtcbiAgICAgIGVkZ2UgPSBhbGxFZGdlc1tpXTtcbi8vICAgICAgdGhpcy51cGRhdGUoZWRnZSk7XG4gICAgfVxuXG4gICAgLy8gcmVjdXJzaXZlbHkgdXBkYXRlIG5vZGVzXG4gICAgdmFyIG5vZGU7XG4gICAgdmFyIG5vZGVzID0gdGhpcy5ncmFwaE1hbmFnZXIuZ2V0Um9vdCgpLmdldE5vZGVzKCk7XG4gICAgZm9yICh2YXIgaSA9IDA7IGkgPCBub2Rlcy5sZW5ndGg7IGkrKylcbiAgICB7XG4gICAgICBub2RlID0gbm9kZXNbaV07XG4vLyAgICAgIHRoaXMudXBkYXRlKG5vZGUpO1xuICAgIH1cblxuICAgIC8vIHVwZGF0ZSByb290IGdyYXBoXG4gICAgdGhpcy51cGRhdGUodGhpcy5ncmFwaE1hbmFnZXIuZ2V0Um9vdCgpKTtcbiAgfVxufTtcblxuTGF5b3V0LnByb3RvdHlwZS51cGRhdGUgPSBmdW5jdGlvbiAob2JqKSB7XG4gIGlmIChvYmogPT0gbnVsbCkge1xuICAgIHRoaXMudXBkYXRlMigpO1xuICB9XG4gIGVsc2UgaWYgKG9iaiBpbnN0YW5jZW9mIExOb2RlKSB7XG4gICAgdmFyIG5vZGUgPSBvYmo7XG4gICAgaWYgKG5vZGUuZ2V0Q2hpbGQoKSAhPSBudWxsKVxuICAgIHtcbiAgICAgIC8vIHNpbmNlIG5vZGUgaXMgY29tcG91bmQsIHJlY3Vyc2l2ZWx5IHVwZGF0ZSBjaGlsZCBub2Rlc1xuICAgICAgdmFyIG5vZGVzID0gbm9kZS5nZXRDaGlsZCgpLmdldE5vZGVzKCk7XG4gICAgICBmb3IgKHZhciBpID0gMDsgaSA8IG5vZGVzLmxlbmd0aDsgaSsrKVxuICAgICAge1xuICAgICAgICB1cGRhdGUobm9kZXNbaV0pO1xuICAgICAgfVxuICAgIH1cblxuICAgIC8vIGlmIHRoZSBsLWxldmVsIG5vZGUgaXMgYXNzb2NpYXRlZCB3aXRoIGEgdi1sZXZlbCBncmFwaCBvYmplY3QsXG4gICAgLy8gdGhlbiBpdCBpcyBhc3N1bWVkIHRoYXQgdGhlIHYtbGV2ZWwgbm9kZSBpbXBsZW1lbnRzIHRoZVxuICAgIC8vIGludGVyZmFjZSBVcGRhdGFibGUuXG4gICAgaWYgKG5vZGUudkdyYXBoT2JqZWN0ICE9IG51bGwpXG4gICAge1xuICAgICAgLy8gY2FzdCB0byBVcGRhdGFibGUgd2l0aG91dCBhbnkgdHlwZSBjaGVja1xuICAgICAgdmFyIHZOb2RlID0gbm9kZS52R3JhcGhPYmplY3Q7XG5cbiAgICAgIC8vIGNhbGwgdGhlIHVwZGF0ZSBtZXRob2Qgb2YgdGhlIGludGVyZmFjZVxuICAgICAgdk5vZGUudXBkYXRlKG5vZGUpO1xuICAgIH1cbiAgfVxuICBlbHNlIGlmIChvYmogaW5zdGFuY2VvZiBMRWRnZSkge1xuICAgIHZhciBlZGdlID0gb2JqO1xuICAgIC8vIGlmIHRoZSBsLWxldmVsIGVkZ2UgaXMgYXNzb2NpYXRlZCB3aXRoIGEgdi1sZXZlbCBncmFwaCBvYmplY3QsXG4gICAgLy8gdGhlbiBpdCBpcyBhc3N1bWVkIHRoYXQgdGhlIHYtbGV2ZWwgZWRnZSBpbXBsZW1lbnRzIHRoZVxuICAgIC8vIGludGVyZmFjZSBVcGRhdGFibGUuXG5cbiAgICBpZiAoZWRnZS52R3JhcGhPYmplY3QgIT0gbnVsbClcbiAgICB7XG4gICAgICAvLyBjYXN0IHRvIFVwZGF0YWJsZSB3aXRob3V0IGFueSB0eXBlIGNoZWNrXG4gICAgICB2YXIgdkVkZ2UgPSBlZGdlLnZHcmFwaE9iamVjdDtcblxuICAgICAgLy8gY2FsbCB0aGUgdXBkYXRlIG1ldGhvZCBvZiB0aGUgaW50ZXJmYWNlXG4gICAgICB2RWRnZS51cGRhdGUoZWRnZSk7XG4gICAgfVxuICB9XG4gIGVsc2UgaWYgKG9iaiBpbnN0YW5jZW9mIExHcmFwaCkge1xuICAgIHZhciBncmFwaCA9IG9iajtcbiAgICAvLyBpZiB0aGUgbC1sZXZlbCBncmFwaCBpcyBhc3NvY2lhdGVkIHdpdGggYSB2LWxldmVsIGdyYXBoIG9iamVjdCxcbiAgICAvLyB0aGVuIGl0IGlzIGFzc3VtZWQgdGhhdCB0aGUgdi1sZXZlbCBvYmplY3QgaW1wbGVtZW50cyB0aGVcbiAgICAvLyBpbnRlcmZhY2UgVXBkYXRhYmxlLlxuXG4gICAgaWYgKGdyYXBoLnZHcmFwaE9iamVjdCAhPSBudWxsKVxuICAgIHtcbiAgICAgIC8vIGNhc3QgdG8gVXBkYXRhYmxlIHdpdGhvdXQgYW55IHR5cGUgY2hlY2tcbiAgICAgIHZhciB2R3JhcGggPSBncmFwaC52R3JhcGhPYmplY3Q7XG5cbiAgICAgIC8vIGNhbGwgdGhlIHVwZGF0ZSBtZXRob2Qgb2YgdGhlIGludGVyZmFjZVxuICAgICAgdkdyYXBoLnVwZGF0ZShncmFwaCk7XG4gICAgfVxuICB9XG59O1xuXG4vKipcbiAqIFRoaXMgbWV0aG9kIGlzIHVzZWQgdG8gc2V0IGFsbCBsYXlvdXQgcGFyYW1ldGVycyB0byBkZWZhdWx0IHZhbHVlc1xuICogZGV0ZXJtaW5lZCBhdCBjb21waWxlIHRpbWUuXG4gKi9cbkxheW91dC5wcm90b3R5cGUuaW5pdFBhcmFtZXRlcnMgPSBmdW5jdGlvbiAoKSB7XG4gIGlmICghdGhpcy5pc1N1YkxheW91dClcbiAge1xuICAgIHRoaXMubGF5b3V0UXVhbGl0eSA9IExheW91dENvbnN0YW50cy5ERUZBVUxUX1FVQUxJVFk7XG4gICAgdGhpcy5hbmltYXRpb25EdXJpbmdMYXlvdXQgPSBMYXlvdXRDb25zdGFudHMuREVGQVVMVF9BTklNQVRJT05fT05fTEFZT1VUO1xuICAgIHRoaXMuYW5pbWF0aW9uUGVyaW9kID0gTGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfQU5JTUFUSU9OX1BFUklPRDtcbiAgICB0aGlzLmFuaW1hdGlvbk9uTGF5b3V0ID0gTGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfQU5JTUFUSU9OX0RVUklOR19MQVlPVVQ7XG4gICAgdGhpcy5pbmNyZW1lbnRhbCA9IExheW91dENvbnN0YW50cy5ERUZBVUxUX0lOQ1JFTUVOVEFMO1xuICAgIHRoaXMuY3JlYXRlQmVuZHNBc05lZWRlZCA9IExheW91dENvbnN0YW50cy5ERUZBVUxUX0NSRUFURV9CRU5EU19BU19ORUVERUQ7XG4gICAgdGhpcy51bmlmb3JtTGVhZk5vZGVTaXplcyA9IExheW91dENvbnN0YW50cy5ERUZBVUxUX1VOSUZPUk1fTEVBRl9OT0RFX1NJWkVTO1xuICB9XG5cbiAgaWYgKHRoaXMuYW5pbWF0aW9uRHVyaW5nTGF5b3V0KVxuICB7XG4gICAgYW5pbWF0aW9uT25MYXlvdXQgPSBmYWxzZTtcbiAgfVxufTtcblxuTGF5b3V0LnByb3RvdHlwZS50cmFuc2Zvcm0gPSBmdW5jdGlvbiAobmV3TGVmdFRvcCkge1xuICBpZiAobmV3TGVmdFRvcCA9PSB1bmRlZmluZWQpIHtcbiAgICB0aGlzLnRyYW5zZm9ybShuZXcgUG9pbnREKDAsIDApKTtcbiAgfVxuICBlbHNlIHtcbiAgICAvLyBjcmVhdGUgYSB0cmFuc2Zvcm1hdGlvbiBvYmplY3QgKGZyb20gRWNsaXBzZSB0byBsYXlvdXQpLiBXaGVuIGFuXG4gICAgLy8gaW52ZXJzZSB0cmFuc2Zvcm0gaXMgYXBwbGllZCwgd2UgZ2V0IHVwcGVyLWxlZnQgY29vcmRpbmF0ZSBvZiB0aGVcbiAgICAvLyBkcmF3aW5nIG9yIHRoZSByb290IGdyYXBoIGF0IGdpdmVuIGlucHV0IGNvb3JkaW5hdGUgKHNvbWUgbWFyZ2luc1xuICAgIC8vIGFscmVhZHkgaW5jbHVkZWQgaW4gY2FsY3VsYXRpb24gb2YgbGVmdC10b3ApLlxuXG4gICAgdmFyIHRyYW5zID0gbmV3IFRyYW5zZm9ybSgpO1xuICAgIHZhciBsZWZ0VG9wID0gdGhpcy5ncmFwaE1hbmFnZXIuZ2V0Um9vdCgpLnVwZGF0ZUxlZnRUb3AoKTtcblxuICAgIGlmIChsZWZ0VG9wICE9IG51bGwpXG4gICAge1xuICAgICAgdHJhbnMuc2V0V29ybGRPcmdYKG5ld0xlZnRUb3AueCk7XG4gICAgICB0cmFucy5zZXRXb3JsZE9yZ1kobmV3TGVmdFRvcC55KTtcblxuICAgICAgdHJhbnMuc2V0RGV2aWNlT3JnWChsZWZ0VG9wLngpO1xuICAgICAgdHJhbnMuc2V0RGV2aWNlT3JnWShsZWZ0VG9wLnkpO1xuXG4gICAgICB2YXIgbm9kZXMgPSB0aGlzLmdldEFsbE5vZGVzKCk7XG4gICAgICB2YXIgbm9kZTtcblxuICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCBub2Rlcy5sZW5ndGg7IGkrKylcbiAgICAgIHtcbiAgICAgICAgbm9kZSA9IG5vZGVzW2ldO1xuICAgICAgICBub2RlLnRyYW5zZm9ybSh0cmFucyk7XG4gICAgICB9XG4gICAgfVxuICB9XG59O1xuXG5MYXlvdXQucHJvdG90eXBlLnBvc2l0aW9uTm9kZXNSYW5kb21seSA9IGZ1bmN0aW9uIChncmFwaCkge1xuXG4gIGlmIChncmFwaCA9PSB1bmRlZmluZWQpIHtcbiAgICAvL2Fzc2VydCAhdGhpcy5pbmNyZW1lbnRhbDtcbiAgICB0aGlzLnBvc2l0aW9uTm9kZXNSYW5kb21seSh0aGlzLmdldEdyYXBoTWFuYWdlcigpLmdldFJvb3QoKSk7XG4gICAgdGhpcy5nZXRHcmFwaE1hbmFnZXIoKS5nZXRSb290KCkudXBkYXRlQm91bmRzKHRydWUpO1xuICB9XG4gIGVsc2Uge1xuICAgIHZhciBsTm9kZTtcbiAgICB2YXIgY2hpbGRHcmFwaDtcblxuICAgIHZhciBub2RlcyA9IGdyYXBoLmdldE5vZGVzKCk7XG4gICAgZm9yICh2YXIgaSA9IDA7IGkgPCBub2Rlcy5sZW5ndGg7IGkrKylcbiAgICB7XG4gICAgICBsTm9kZSA9IG5vZGVzW2ldO1xuICAgICAgY2hpbGRHcmFwaCA9IGxOb2RlLmdldENoaWxkKCk7XG5cbiAgICAgIGlmIChjaGlsZEdyYXBoID09IG51bGwpXG4gICAgICB7XG4gICAgICAgIGxOb2RlLnNjYXR0ZXIoKTtcbiAgICAgIH1cbiAgICAgIGVsc2UgaWYgKGNoaWxkR3JhcGguZ2V0Tm9kZXMoKS5sZW5ndGggPT0gMClcbiAgICAgIHtcbiAgICAgICAgbE5vZGUuc2NhdHRlcigpO1xuICAgICAgfVxuICAgICAgZWxzZVxuICAgICAge1xuICAgICAgICB0aGlzLnBvc2l0aW9uTm9kZXNSYW5kb21seShjaGlsZEdyYXBoKTtcbiAgICAgICAgbE5vZGUudXBkYXRlQm91bmRzKCk7XG4gICAgICB9XG4gICAgfVxuICB9XG59O1xuXG4vKipcbiAqIFRoaXMgbWV0aG9kIHJldHVybnMgYSBsaXN0IG9mIHRyZWVzIHdoZXJlIGVhY2ggdHJlZSBpcyByZXByZXNlbnRlZCBhcyBhXG4gKiBsaXN0IG9mIGwtbm9kZXMuIFRoZSBtZXRob2QgcmV0dXJucyBhIGxpc3Qgb2Ygc2l6ZSAwIHdoZW46XG4gKiAtIFRoZSBncmFwaCBpcyBub3QgZmxhdCBvclxuICogLSBPbmUgb2YgdGhlIGNvbXBvbmVudChzKSBvZiB0aGUgZ3JhcGggaXMgbm90IGEgdHJlZS5cbiAqL1xuTGF5b3V0LnByb3RvdHlwZS5nZXRGbGF0Rm9yZXN0ID0gZnVuY3Rpb24gKClcbntcbiAgdmFyIGZsYXRGb3Jlc3QgPSBbXTtcbiAgdmFyIGlzRm9yZXN0ID0gdHJ1ZTtcblxuICAvLyBRdWljayByZWZlcmVuY2UgZm9yIGFsbCBub2RlcyBpbiB0aGUgZ3JhcGggbWFuYWdlciBhc3NvY2lhdGVkIHdpdGhcbiAgLy8gdGhpcyBsYXlvdXQuIFRoZSBsaXN0IHNob3VsZCBub3QgYmUgY2hhbmdlZC5cbiAgdmFyIGFsbE5vZGVzID0gdGhpcy5ncmFwaE1hbmFnZXIuZ2V0Um9vdCgpLmdldE5vZGVzKCk7XG5cbiAgLy8gRmlyc3QgYmUgc3VyZSB0aGF0IHRoZSBncmFwaCBpcyBmbGF0XG4gIHZhciBpc0ZsYXQgPSB0cnVlO1xuXG4gIGZvciAodmFyIGkgPSAwOyBpIDwgYWxsTm9kZXMubGVuZ3RoOyBpKyspXG4gIHtcbiAgICBpZiAoYWxsTm9kZXNbaV0uZ2V0Q2hpbGQoKSAhPSBudWxsKVxuICAgIHtcbiAgICAgIGlzRmxhdCA9IGZhbHNlO1xuICAgIH1cbiAgfVxuXG4gIC8vIFJldHVybiBlbXB0eSBmb3Jlc3QgaWYgdGhlIGdyYXBoIGlzIG5vdCBmbGF0LlxuICBpZiAoIWlzRmxhdClcbiAge1xuICAgIHJldHVybiBmbGF0Rm9yZXN0O1xuICB9XG5cbiAgLy8gUnVuIEJGUyBmb3IgZWFjaCBjb21wb25lbnQgb2YgdGhlIGdyYXBoLlxuXG4gIHZhciB2aXNpdGVkID0gbmV3IEhhc2hTZXQoKTtcbiAgdmFyIHRvQmVWaXNpdGVkID0gW107XG4gIHZhciBwYXJlbnRzID0gbmV3IEhhc2hNYXAoKTtcbiAgdmFyIHVuUHJvY2Vzc2VkTm9kZXMgPSBbXTtcblxuICB1blByb2Nlc3NlZE5vZGVzID0gdW5Qcm9jZXNzZWROb2Rlcy5jb25jYXQoYWxsTm9kZXMpO1xuXG4gIC8vIEVhY2ggaXRlcmF0aW9uIG9mIHRoaXMgbG9vcCBmaW5kcyBhIGNvbXBvbmVudCBvZiB0aGUgZ3JhcGggYW5kXG4gIC8vIGRlY2lkZXMgd2hldGhlciBpdCBpcyBhIHRyZWUgb3Igbm90LiBJZiBpdCBpcyBhIHRyZWUsIGFkZHMgaXQgdG8gdGhlXG4gIC8vIGZvcmVzdCBhbmQgY29udGludWVkIHdpdGggdGhlIG5leHQgY29tcG9uZW50LlxuXG4gIHdoaWxlICh1blByb2Nlc3NlZE5vZGVzLmxlbmd0aCA+IDAgJiYgaXNGb3Jlc3QpXG4gIHtcbiAgICB0b0JlVmlzaXRlZC5wdXNoKHVuUHJvY2Vzc2VkTm9kZXNbMF0pO1xuXG4gICAgLy8gU3RhcnQgdGhlIEJGUy4gRWFjaCBpdGVyYXRpb24gb2YgdGhpcyBsb29wIHZpc2l0cyBhIG5vZGUgaW4gYVxuICAgIC8vIEJGUyBtYW5uZXIuXG4gICAgd2hpbGUgKHRvQmVWaXNpdGVkLmxlbmd0aCA+IDAgJiYgaXNGb3Jlc3QpXG4gICAge1xuICAgICAgLy9wb29sIG9wZXJhdGlvblxuICAgICAgdmFyIGN1cnJlbnROb2RlID0gdG9CZVZpc2l0ZWRbMF07XG4gICAgICB0b0JlVmlzaXRlZC5zcGxpY2UoMCwgMSk7XG4gICAgICB2aXNpdGVkLmFkZChjdXJyZW50Tm9kZSk7XG5cbiAgICAgIC8vIFRyYXZlcnNlIGFsbCBuZWlnaGJvcnMgb2YgdGhpcyBub2RlXG4gICAgICB2YXIgbmVpZ2hib3JFZGdlcyA9IGN1cnJlbnROb2RlLmdldEVkZ2VzKCk7XG5cbiAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwgbmVpZ2hib3JFZGdlcy5sZW5ndGg7IGkrKylcbiAgICAgIHtcbiAgICAgICAgdmFyIGN1cnJlbnROZWlnaGJvciA9XG4gICAgICAgICAgICAgICAgbmVpZ2hib3JFZGdlc1tpXS5nZXRPdGhlckVuZChjdXJyZW50Tm9kZSk7XG5cbiAgICAgICAgLy8gSWYgQkZTIGlzIG5vdCBncm93aW5nIGZyb20gdGhpcyBuZWlnaGJvci5cbiAgICAgICAgaWYgKHBhcmVudHMuZ2V0KGN1cnJlbnROb2RlKSAhPSBjdXJyZW50TmVpZ2hib3IpXG4gICAgICAgIHtcbiAgICAgICAgICAvLyBXZSBoYXZlbid0IHByZXZpb3VzbHkgdmlzaXRlZCB0aGlzIG5laWdoYm9yLlxuICAgICAgICAgIGlmICghdmlzaXRlZC5jb250YWlucyhjdXJyZW50TmVpZ2hib3IpKVxuICAgICAgICAgIHtcbiAgICAgICAgICAgIHRvQmVWaXNpdGVkLnB1c2goY3VycmVudE5laWdoYm9yKTtcbiAgICAgICAgICAgIHBhcmVudHMucHV0KGN1cnJlbnROZWlnaGJvciwgY3VycmVudE5vZGUpO1xuICAgICAgICAgIH1cbiAgICAgICAgICAvLyBTaW5jZSB3ZSBoYXZlIHByZXZpb3VzbHkgdmlzaXRlZCB0aGlzIG5laWdoYm9yIGFuZFxuICAgICAgICAgIC8vIHRoaXMgbmVpZ2hib3IgaXMgbm90IHBhcmVudCBvZiBjdXJyZW50Tm9kZSwgZ2l2ZW5cbiAgICAgICAgICAvLyBncmFwaCBjb250YWlucyBhIGNvbXBvbmVudCB0aGF0IGlzIG5vdCB0cmVlLCBoZW5jZVxuICAgICAgICAgIC8vIGl0IGlzIG5vdCBhIGZvcmVzdC5cbiAgICAgICAgICBlbHNlXG4gICAgICAgICAge1xuICAgICAgICAgICAgaXNGb3Jlc3QgPSBmYWxzZTtcbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cblxuICAgIC8vIFRoZSBncmFwaCBjb250YWlucyBhIGNvbXBvbmVudCB0aGF0IGlzIG5vdCBhIHRyZWUuIEVtcHR5XG4gICAgLy8gcHJldmlvdXNseSBmb3VuZCB0cmVlcy4gVGhlIG1ldGhvZCB3aWxsIGVuZC5cbiAgICBpZiAoIWlzRm9yZXN0KVxuICAgIHtcbiAgICAgIGZsYXRGb3Jlc3QgPSBbXTtcbiAgICB9XG4gICAgLy8gU2F2ZSBjdXJyZW50bHkgdmlzaXRlZCBub2RlcyBhcyBhIHRyZWUgaW4gb3VyIGZvcmVzdC4gUmVzZXRcbiAgICAvLyB2aXNpdGVkIGFuZCBwYXJlbnRzIGxpc3RzLiBDb250aW51ZSB3aXRoIHRoZSBuZXh0IGNvbXBvbmVudCBvZlxuICAgIC8vIHRoZSBncmFwaCwgaWYgYW55LlxuICAgIGVsc2VcbiAgICB7XG4gICAgICB2YXIgdGVtcCA9IFtdO1xuICAgICAgdmlzaXRlZC5hZGRBbGxUbyh0ZW1wKTtcbiAgICAgIGZsYXRGb3Jlc3QucHVzaCh0ZW1wKTtcbiAgICAgIC8vZmxhdEZvcmVzdCA9IGZsYXRGb3Jlc3QuY29uY2F0KHRlbXApO1xuICAgICAgLy91blByb2Nlc3NlZE5vZGVzLnJlbW92ZUFsbCh2aXNpdGVkKTtcbiAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwgdGVtcC5sZW5ndGg7IGkrKykge1xuICAgICAgICB2YXIgdmFsdWUgPSB0ZW1wW2ldO1xuICAgICAgICB2YXIgaW5kZXggPSB1blByb2Nlc3NlZE5vZGVzLmluZGV4T2YodmFsdWUpO1xuICAgICAgICBpZiAoaW5kZXggPiAtMSkge1xuICAgICAgICAgIHVuUHJvY2Vzc2VkTm9kZXMuc3BsaWNlKGluZGV4LCAxKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgdmlzaXRlZCA9IG5ldyBIYXNoU2V0KCk7XG4gICAgICBwYXJlbnRzID0gbmV3IEhhc2hNYXAoKTtcbiAgICB9XG4gIH1cblxuICByZXR1cm4gZmxhdEZvcmVzdDtcbn07XG5cbi8qKlxuICogVGhpcyBtZXRob2QgY3JlYXRlcyBkdW1teSBub2RlcyAoYW4gbC1sZXZlbCBub2RlIHdpdGggbWluaW1hbCBkaW1lbnNpb25zKVxuICogZm9yIHRoZSBnaXZlbiBlZGdlIChvbmUgcGVyIGJlbmRwb2ludCkuIFRoZSBleGlzdGluZyBsLWxldmVsIHN0cnVjdHVyZVxuICogaXMgdXBkYXRlZCBhY2NvcmRpbmdseS5cbiAqL1xuTGF5b3V0LnByb3RvdHlwZS5jcmVhdGVEdW1teU5vZGVzRm9yQmVuZHBvaW50cyA9IGZ1bmN0aW9uIChlZGdlKVxue1xuICB2YXIgZHVtbXlOb2RlcyA9IFtdO1xuICB2YXIgcHJldiA9IGVkZ2Uuc291cmNlO1xuXG4gIHZhciBncmFwaCA9IHRoaXMuZ3JhcGhNYW5hZ2VyLmNhbGNMb3dlc3RDb21tb25BbmNlc3RvcihlZGdlLnNvdXJjZSwgZWRnZS50YXJnZXQpO1xuXG4gIGZvciAodmFyIGkgPSAwOyBpIDwgZWRnZS5iZW5kcG9pbnRzLmxlbmd0aDsgaSsrKVxuICB7XG4gICAgLy8gY3JlYXRlIG5ldyBkdW1teSBub2RlXG4gICAgdmFyIGR1bW15Tm9kZSA9IHRoaXMubmV3Tm9kZShudWxsKTtcbiAgICBkdW1teU5vZGUuc2V0UmVjdChuZXcgUG9pbnQoMCwgMCksIG5ldyBEaW1lbnNpb24oMSwgMSkpO1xuXG4gICAgZ3JhcGguYWRkKGR1bW15Tm9kZSk7XG5cbiAgICAvLyBjcmVhdGUgbmV3IGR1bW15IGVkZ2UgYmV0d2VlbiBwcmV2IGFuZCBkdW1teSBub2RlXG4gICAgdmFyIGR1bW15RWRnZSA9IHRoaXMubmV3RWRnZShudWxsKTtcbiAgICB0aGlzLmdyYXBoTWFuYWdlci5hZGQoZHVtbXlFZGdlLCBwcmV2LCBkdW1teU5vZGUpO1xuXG4gICAgZHVtbXlOb2Rlcy5hZGQoZHVtbXlOb2RlKTtcbiAgICBwcmV2ID0gZHVtbXlOb2RlO1xuICB9XG5cbiAgdmFyIGR1bW15RWRnZSA9IHRoaXMubmV3RWRnZShudWxsKTtcbiAgdGhpcy5ncmFwaE1hbmFnZXIuYWRkKGR1bW15RWRnZSwgcHJldiwgZWRnZS50YXJnZXQpO1xuXG4gIHRoaXMuZWRnZVRvRHVtbXlOb2Rlcy5wdXQoZWRnZSwgZHVtbXlOb2Rlcyk7XG5cbiAgLy8gcmVtb3ZlIHJlYWwgZWRnZSBmcm9tIGdyYXBoIG1hbmFnZXIgaWYgaXQgaXMgaW50ZXItZ3JhcGhcbiAgaWYgKGVkZ2UuaXNJbnRlckdyYXBoKCkpXG4gIHtcbiAgICB0aGlzLmdyYXBoTWFuYWdlci5yZW1vdmUoZWRnZSk7XG4gIH1cbiAgLy8gZWxzZSwgcmVtb3ZlIHRoZSBlZGdlIGZyb20gdGhlIGN1cnJlbnQgZ3JhcGhcbiAgZWxzZVxuICB7XG4gICAgZ3JhcGgucmVtb3ZlKGVkZ2UpO1xuICB9XG5cbiAgcmV0dXJuIGR1bW15Tm9kZXM7XG59O1xuXG4vKipcbiAqIFRoaXMgbWV0aG9kIGNyZWF0ZXMgYmVuZHBvaW50cyBmb3IgZWRnZXMgZnJvbSB0aGUgZHVtbXkgbm9kZXNcbiAqIGF0IGwtbGV2ZWwuXG4gKi9cbkxheW91dC5wcm90b3R5cGUuY3JlYXRlQmVuZHBvaW50c0Zyb21EdW1teU5vZGVzID0gZnVuY3Rpb24gKClcbntcbiAgdmFyIGVkZ2VzID0gW107XG4gIGVkZ2VzID0gZWRnZXMuY29uY2F0KHRoaXMuZ3JhcGhNYW5hZ2VyLmdldEFsbEVkZ2VzKCkpO1xuICBlZGdlcyA9IHRoaXMuZWRnZVRvRHVtbXlOb2Rlcy5rZXlTZXQoKS5jb25jYXQoZWRnZXMpO1xuXG4gIGZvciAodmFyIGsgPSAwOyBrIDwgZWRnZXMubGVuZ3RoOyBrKyspXG4gIHtcbiAgICB2YXIgbEVkZ2UgPSBlZGdlc1trXTtcblxuICAgIGlmIChsRWRnZS5iZW5kcG9pbnRzLmxlbmd0aCA+IDApXG4gICAge1xuICAgICAgdmFyIHBhdGggPSB0aGlzLmVkZ2VUb0R1bW15Tm9kZXMuZ2V0KGxFZGdlKTtcblxuICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCBwYXRoLmxlbmd0aDsgaSsrKVxuICAgICAge1xuICAgICAgICB2YXIgZHVtbXlOb2RlID0gcGF0aFtpXTtcbiAgICAgICAgdmFyIHAgPSBuZXcgUG9pbnREKGR1bW15Tm9kZS5nZXRDZW50ZXJYKCksXG4gICAgICAgICAgICAgICAgZHVtbXlOb2RlLmdldENlbnRlclkoKSk7XG5cbiAgICAgICAgLy8gdXBkYXRlIGJlbmRwb2ludCdzIGxvY2F0aW9uIGFjY29yZGluZyB0byBkdW1teSBub2RlXG4gICAgICAgIHZhciBlYnAgPSBsRWRnZS5iZW5kcG9pbnRzLmdldChpKTtcbiAgICAgICAgZWJwLnggPSBwLng7XG4gICAgICAgIGVicC55ID0gcC55O1xuXG4gICAgICAgIC8vIHJlbW92ZSB0aGUgZHVtbXkgbm9kZSwgZHVtbXkgZWRnZXMgaW5jaWRlbnQgd2l0aCB0aGlzXG4gICAgICAgIC8vIGR1bW15IG5vZGUgaXMgYWxzbyByZW1vdmVkICh3aXRoaW4gdGhlIHJlbW92ZSBtZXRob2QpXG4gICAgICAgIGR1bW15Tm9kZS5nZXRPd25lcigpLnJlbW92ZShkdW1teU5vZGUpO1xuICAgICAgfVxuXG4gICAgICAvLyBhZGQgdGhlIHJlYWwgZWRnZSB0byBncmFwaFxuICAgICAgdGhpcy5ncmFwaE1hbmFnZXIuYWRkKGxFZGdlLCBsRWRnZS5zb3VyY2UsIGxFZGdlLnRhcmdldCk7XG4gICAgfVxuICB9XG59O1xuXG5MYXlvdXQudHJhbnNmb3JtID0gZnVuY3Rpb24gKHNsaWRlclZhbHVlLCBkZWZhdWx0VmFsdWUsIG1pbkRpdiwgbWF4TXVsKSB7XG4gIGlmIChtaW5EaXYgIT0gdW5kZWZpbmVkICYmIG1heE11bCAhPSB1bmRlZmluZWQpIHtcbiAgICB2YXIgdmFsdWUgPSBkZWZhdWx0VmFsdWU7XG5cbiAgICBpZiAoc2xpZGVyVmFsdWUgPD0gNTApXG4gICAge1xuICAgICAgdmFyIG1pblZhbHVlID0gZGVmYXVsdFZhbHVlIC8gbWluRGl2O1xuICAgICAgdmFsdWUgLT0gKChkZWZhdWx0VmFsdWUgLSBtaW5WYWx1ZSkgLyA1MCkgKiAoNTAgLSBzbGlkZXJWYWx1ZSk7XG4gICAgfVxuICAgIGVsc2VcbiAgICB7XG4gICAgICB2YXIgbWF4VmFsdWUgPSBkZWZhdWx0VmFsdWUgKiBtYXhNdWw7XG4gICAgICB2YWx1ZSArPSAoKG1heFZhbHVlIC0gZGVmYXVsdFZhbHVlKSAvIDUwKSAqIChzbGlkZXJWYWx1ZSAtIDUwKTtcbiAgICB9XG5cbiAgICByZXR1cm4gdmFsdWU7XG4gIH1cbiAgZWxzZSB7XG4gICAgdmFyIGEsIGI7XG5cbiAgICBpZiAoc2xpZGVyVmFsdWUgPD0gNTApXG4gICAge1xuICAgICAgYSA9IDkuMCAqIGRlZmF1bHRWYWx1ZSAvIDUwMC4wO1xuICAgICAgYiA9IGRlZmF1bHRWYWx1ZSAvIDEwLjA7XG4gICAgfVxuICAgIGVsc2VcbiAgICB7XG4gICAgICBhID0gOS4wICogZGVmYXVsdFZhbHVlIC8gNTAuMDtcbiAgICAgIGIgPSAtOCAqIGRlZmF1bHRWYWx1ZTtcbiAgICB9XG5cbiAgICByZXR1cm4gKGEgKiBzbGlkZXJWYWx1ZSArIGIpO1xuICB9XG59O1xuXG4vKipcbiAqIFRoaXMgbWV0aG9kIGZpbmRzIGFuZCByZXR1cm5zIHRoZSBjZW50ZXIgb2YgdGhlIGdpdmVuIG5vZGVzLCBhc3N1bWluZ1xuICogdGhhdCB0aGUgZ2l2ZW4gbm9kZXMgZm9ybSBhIHRyZWUgaW4gdGhlbXNlbHZlcy5cbiAqL1xuTGF5b3V0LmZpbmRDZW50ZXJPZlRyZWUgPSBmdW5jdGlvbiAobm9kZXMpXG57XG4gIHZhciBsaXN0ID0gW107XG4gIGxpc3QgPSBsaXN0LmNvbmNhdChub2Rlcyk7XG5cbiAgdmFyIHJlbW92ZWROb2RlcyA9IFtdO1xuICB2YXIgcmVtYWluaW5nRGVncmVlcyA9IG5ldyBIYXNoTWFwKCk7XG4gIHZhciBmb3VuZENlbnRlciA9IGZhbHNlO1xuICB2YXIgY2VudGVyTm9kZSA9IG51bGw7XG5cbiAgaWYgKGxpc3QubGVuZ3RoID09IDEgfHwgbGlzdC5sZW5ndGggPT0gMilcbiAge1xuICAgIGZvdW5kQ2VudGVyID0gdHJ1ZTtcbiAgICBjZW50ZXJOb2RlID0gbGlzdFswXTtcbiAgfVxuXG4gIGZvciAodmFyIGkgPSAwOyBpIDwgbGlzdC5sZW5ndGg7IGkrKylcbiAge1xuICAgIHZhciBub2RlID0gbGlzdFtpXTtcbiAgICB2YXIgZGVncmVlID0gbm9kZS5nZXROZWlnaGJvcnNMaXN0KCkuc2l6ZSgpO1xuICAgIHJlbWFpbmluZ0RlZ3JlZXMucHV0KG5vZGUsIG5vZGUuZ2V0TmVpZ2hib3JzTGlzdCgpLnNpemUoKSk7XG5cbiAgICBpZiAoZGVncmVlID09IDEpXG4gICAge1xuICAgICAgcmVtb3ZlZE5vZGVzLnB1c2gobm9kZSk7XG4gICAgfVxuICB9XG5cbiAgdmFyIHRlbXBMaXN0ID0gW107XG4gIHRlbXBMaXN0ID0gdGVtcExpc3QuY29uY2F0KHJlbW92ZWROb2Rlcyk7XG5cbiAgd2hpbGUgKCFmb3VuZENlbnRlcilcbiAge1xuICAgIHZhciB0ZW1wTGlzdDIgPSBbXTtcbiAgICB0ZW1wTGlzdDIgPSB0ZW1wTGlzdDIuY29uY2F0KHRlbXBMaXN0KTtcbiAgICB0ZW1wTGlzdCA9IFtdO1xuXG4gICAgZm9yICh2YXIgaSA9IDA7IGkgPCBsaXN0Lmxlbmd0aDsgaSsrKVxuICAgIHtcbiAgICAgIHZhciBub2RlID0gbGlzdFtpXTtcblxuICAgICAgdmFyIGluZGV4ID0gbGlzdC5pbmRleE9mKG5vZGUpO1xuICAgICAgaWYgKGluZGV4ID49IDApIHtcbiAgICAgICAgbGlzdC5zcGxpY2UoaW5kZXgsIDEpO1xuICAgICAgfVxuXG4gICAgICB2YXIgbmVpZ2hib3VycyA9IG5vZGUuZ2V0TmVpZ2hib3JzTGlzdCgpO1xuXG4gICAgICBmb3IgKHZhciBqIGluIG5laWdoYm91cnMuc2V0KVxuICAgICAge1xuICAgICAgICB2YXIgbmVpZ2hib3VyID0gbmVpZ2hib3Vycy5zZXRbal07XG4gICAgICAgIGlmIChyZW1vdmVkTm9kZXMuaW5kZXhPZihuZWlnaGJvdXIpIDwgMClcbiAgICAgICAge1xuICAgICAgICAgIHZhciBvdGhlckRlZ3JlZSA9IHJlbWFpbmluZ0RlZ3JlZXMuZ2V0KG5laWdoYm91cik7XG4gICAgICAgICAgdmFyIG5ld0RlZ3JlZSA9IG90aGVyRGVncmVlIC0gMTtcblxuICAgICAgICAgIGlmIChuZXdEZWdyZWUgPT0gMSlcbiAgICAgICAgICB7XG4gICAgICAgICAgICB0ZW1wTGlzdC5wdXNoKG5laWdoYm91cik7XG4gICAgICAgICAgfVxuXG4gICAgICAgICAgcmVtYWluaW5nRGVncmVlcy5wdXQobmVpZ2hib3VyLCBuZXdEZWdyZWUpO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuXG4gICAgcmVtb3ZlZE5vZGVzID0gcmVtb3ZlZE5vZGVzLmNvbmNhdCh0ZW1wTGlzdCk7XG5cbiAgICBpZiAobGlzdC5sZW5ndGggPT0gMSB8fCBsaXN0Lmxlbmd0aCA9PSAyKVxuICAgIHtcbiAgICAgIGZvdW5kQ2VudGVyID0gdHJ1ZTtcbiAgICAgIGNlbnRlck5vZGUgPSBsaXN0WzBdO1xuICAgIH1cbiAgfVxuXG4gIHJldHVybiBjZW50ZXJOb2RlO1xufTtcblxuLyoqXG4gKiBEdXJpbmcgdGhlIGNvYXJzZW5pbmcgcHJvY2VzcywgdGhpcyBsYXlvdXQgbWF5IGJlIHJlZmVyZW5jZWQgYnkgdHdvIGdyYXBoIG1hbmFnZXJzXG4gKiB0aGlzIHNldHRlciBmdW5jdGlvbiBncmFudHMgYWNjZXNzIHRvIGNoYW5nZSB0aGUgY3VycmVudGx5IGJlaW5nIHVzZWQgZ3JhcGggbWFuYWdlclxuICovXG5MYXlvdXQucHJvdG90eXBlLnNldEdyYXBoTWFuYWdlciA9IGZ1bmN0aW9uIChnbSlcbntcbiAgdGhpcy5ncmFwaE1hbmFnZXIgPSBnbTtcbn07XG5cbm1vZHVsZS5leHBvcnRzID0gTGF5b3V0O1xuIiwiZnVuY3Rpb24gTGF5b3V0Q29uc3RhbnRzKCkge1xufVxuXG4vKipcbiAqIExheW91dCBRdWFsaXR5XG4gKi9cbkxheW91dENvbnN0YW50cy5QUk9PRl9RVUFMSVRZID0gMDtcbkxheW91dENvbnN0YW50cy5ERUZBVUxUX1FVQUxJVFkgPSAxO1xuTGF5b3V0Q29uc3RhbnRzLkRSQUZUX1FVQUxJVFkgPSAyO1xuXG4vKipcbiAqIERlZmF1bHQgcGFyYW1ldGVyc1xuICovXG5MYXlvdXRDb25zdGFudHMuREVGQVVMVF9DUkVBVEVfQkVORFNfQVNfTkVFREVEID0gZmFsc2U7XG4vL0xheW91dENvbnN0YW50cy5ERUZBVUxUX0lOQ1JFTUVOVEFMID0gdHJ1ZTtcbkxheW91dENvbnN0YW50cy5ERUZBVUxUX0lOQ1JFTUVOVEFMID0gZmFsc2U7XG5MYXlvdXRDb25zdGFudHMuREVGQVVMVF9BTklNQVRJT05fT05fTEFZT1VUID0gdHJ1ZTtcbkxheW91dENvbnN0YW50cy5ERUZBVUxUX0FOSU1BVElPTl9EVVJJTkdfTEFZT1VUID0gZmFsc2U7XG5MYXlvdXRDb25zdGFudHMuREVGQVVMVF9BTklNQVRJT05fUEVSSU9EID0gNTA7XG5MYXlvdXRDb25zdGFudHMuREVGQVVMVF9VTklGT1JNX0xFQUZfTk9ERV9TSVpFUyA9IGZhbHNlO1xuXG4vLyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuLy8gU2VjdGlvbjogR2VuZXJhbCBvdGhlciBjb25zdGFudHNcbi8vIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4vKlxuICogTWFyZ2lucyBvZiBhIGdyYXBoIHRvIGJlIGFwcGxpZWQgb24gYm91ZGluZyByZWN0YW5nbGUgb2YgaXRzIGNvbnRlbnRzLiBXZVxuICogYXNzdW1lIG1hcmdpbnMgb24gYWxsIGZvdXIgc2lkZXMgdG8gYmUgdW5pZm9ybS5cbiAqL1xuTGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfR1JBUEhfTUFSR0lOID0gMTU7XG5cbi8qXG4gKiBEZWZhdWx0IGRpbWVuc2lvbiBvZiBhIG5vbi1jb21wb3VuZCBub2RlLlxuICovXG5MYXlvdXRDb25zdGFudHMuU0lNUExFX05PREVfU0laRSA9IDQwO1xuXG4vKlxuICogRGVmYXVsdCBkaW1lbnNpb24gb2YgYSBub24tY29tcG91bmQgbm9kZS5cbiAqL1xuTGF5b3V0Q29uc3RhbnRzLlNJTVBMRV9OT0RFX0hBTEZfU0laRSA9IExheW91dENvbnN0YW50cy5TSU1QTEVfTk9ERV9TSVpFIC8gMjtcblxuLypcbiAqIEVtcHR5IGNvbXBvdW5kIG5vZGUgc2l6ZS4gV2hlbiBhIGNvbXBvdW5kIG5vZGUgaXMgZW1wdHksIGl0cyBib3RoXG4gKiBkaW1lbnNpb25zIHNob3VsZCBiZSBvZiB0aGlzIHZhbHVlLlxuICovXG5MYXlvdXRDb25zdGFudHMuRU1QVFlfQ09NUE9VTkRfTk9ERV9TSVpFID0gNDA7XG5cbi8qXG4gKiBNaW5pbXVtIGxlbmd0aCB0aGF0IGFuIGVkZ2Ugc2hvdWxkIHRha2UgZHVyaW5nIGxheW91dFxuICovXG5MYXlvdXRDb25zdGFudHMuTUlOX0VER0VfTEVOR1RIID0gMTtcblxuLypcbiAqIFdvcmxkIGJvdW5kYXJpZXMgdGhhdCBsYXlvdXQgb3BlcmF0ZXMgb25cbiAqL1xuTGF5b3V0Q29uc3RhbnRzLldPUkxEX0JPVU5EQVJZID0gMTAwMDAwMDtcblxuLypcbiAqIFdvcmxkIGJvdW5kYXJpZXMgdGhhdCByYW5kb20gcG9zaXRpb25pbmcgY2FuIGJlIHBlcmZvcm1lZCB3aXRoXG4gKi9cbkxheW91dENvbnN0YW50cy5JTklUSUFMX1dPUkxEX0JPVU5EQVJZID0gTGF5b3V0Q29uc3RhbnRzLldPUkxEX0JPVU5EQVJZIC8gMTAwMDtcblxuLypcbiAqIENvb3JkaW5hdGVzIG9mIHRoZSB3b3JsZCBjZW50ZXJcbiAqL1xuTGF5b3V0Q29uc3RhbnRzLldPUkxEX0NFTlRFUl9YID0gMTIwMDtcbkxheW91dENvbnN0YW50cy5XT1JMRF9DRU5URVJfWSA9IDkwMDtcblxubW9kdWxlLmV4cG9ydHMgPSBMYXlvdXRDb25zdGFudHM7XG4iLCIvKlxuICpUaGlzIGNsYXNzIGlzIHRoZSBqYXZhc2NyaXB0IGltcGxlbWVudGF0aW9uIG9mIHRoZSBQb2ludC5qYXZhIGNsYXNzIGluIGpka1xuICovXG5mdW5jdGlvbiBQb2ludCh4LCB5LCBwKSB7XG4gIHRoaXMueCA9IG51bGw7XG4gIHRoaXMueSA9IG51bGw7XG4gIGlmICh4ID09IG51bGwgJiYgeSA9PSBudWxsICYmIHAgPT0gbnVsbCkge1xuICAgIHRoaXMueCA9IDA7XG4gICAgdGhpcy55ID0gMDtcbiAgfVxuICBlbHNlIGlmICh0eXBlb2YgeCA9PSAnbnVtYmVyJyAmJiB0eXBlb2YgeSA9PSAnbnVtYmVyJyAmJiBwID09IG51bGwpIHtcbiAgICB0aGlzLnggPSB4O1xuICAgIHRoaXMueSA9IHk7XG4gIH1cbiAgZWxzZSBpZiAoeC5jb25zdHJ1Y3Rvci5uYW1lID09ICdQb2ludCcgJiYgeSA9PSBudWxsICYmIHAgPT0gbnVsbCkge1xuICAgIHAgPSB4O1xuICAgIHRoaXMueCA9IHAueDtcbiAgICB0aGlzLnkgPSBwLnk7XG4gIH1cbn1cblxuUG9pbnQucHJvdG90eXBlLmdldFggPSBmdW5jdGlvbiAoKSB7XG4gIHJldHVybiB0aGlzLng7XG59XG5cblBvaW50LnByb3RvdHlwZS5nZXRZID0gZnVuY3Rpb24gKCkge1xuICByZXR1cm4gdGhpcy55O1xufVxuXG5Qb2ludC5wcm90b3R5cGUuZ2V0TG9jYXRpb24gPSBmdW5jdGlvbiAoKSB7XG4gIHJldHVybiBuZXcgUG9pbnQodGhpcy54LCB0aGlzLnkpO1xufVxuXG5Qb2ludC5wcm90b3R5cGUuc2V0TG9jYXRpb24gPSBmdW5jdGlvbiAoeCwgeSwgcCkge1xuICBpZiAoeC5jb25zdHJ1Y3Rvci5uYW1lID09ICdQb2ludCcgJiYgeSA9PSBudWxsICYmIHAgPT0gbnVsbCkge1xuICAgIHAgPSB4O1xuICAgIHRoaXMuc2V0TG9jYXRpb24ocC54LCBwLnkpO1xuICB9XG4gIGVsc2UgaWYgKHR5cGVvZiB4ID09ICdudW1iZXInICYmIHR5cGVvZiB5ID09ICdudW1iZXInICYmIHAgPT0gbnVsbCkge1xuICAgIC8vaWYgYm90aCBwYXJhbWV0ZXJzIGFyZSBpbnRlZ2VyIGp1c3QgbW92ZSAoeCx5KSBsb2NhdGlvblxuICAgIGlmIChwYXJzZUludCh4KSA9PSB4ICYmIHBhcnNlSW50KHkpID09IHkpIHtcbiAgICAgIHRoaXMubW92ZSh4LCB5KTtcbiAgICB9XG4gICAgZWxzZSB7XG4gICAgICB0aGlzLnggPSBNYXRoLmZsb29yKHggKyAwLjUpO1xuICAgICAgdGhpcy55ID0gTWF0aC5mbG9vcih5ICsgMC41KTtcbiAgICB9XG4gIH1cbn1cblxuUG9pbnQucHJvdG90eXBlLm1vdmUgPSBmdW5jdGlvbiAoeCwgeSkge1xuICB0aGlzLnggPSB4O1xuICB0aGlzLnkgPSB5O1xufVxuXG5Qb2ludC5wcm90b3R5cGUudHJhbnNsYXRlID0gZnVuY3Rpb24gKGR4LCBkeSkge1xuICB0aGlzLnggKz0gZHg7XG4gIHRoaXMueSArPSBkeTtcbn1cblxuUG9pbnQucHJvdG90eXBlLmVxdWFscyA9IGZ1bmN0aW9uIChvYmopIHtcbiAgaWYgKG9iai5jb25zdHJ1Y3Rvci5uYW1lID09IFwiUG9pbnRcIikge1xuICAgIHZhciBwdCA9IG9iajtcbiAgICByZXR1cm4gKHRoaXMueCA9PSBwdC54KSAmJiAodGhpcy55ID09IHB0LnkpO1xuICB9XG4gIHJldHVybiB0aGlzID09IG9iajtcbn1cblxuUG9pbnQucHJvdG90eXBlLnRvU3RyaW5nID0gZnVuY3Rpb24gKCkge1xuICByZXR1cm4gbmV3IFBvaW50KCkuY29uc3RydWN0b3IubmFtZSArIFwiW3g9XCIgKyB0aGlzLnggKyBcIix5PVwiICsgdGhpcy55ICsgXCJdXCI7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gUG9pbnQ7XG4iLCJmdW5jdGlvbiBQb2ludEQoeCwgeSkge1xuICBpZiAoeCA9PSBudWxsICYmIHkgPT0gbnVsbCkge1xuICAgIHRoaXMueCA9IDA7XG4gICAgdGhpcy55ID0gMDtcbiAgfSBlbHNlIHtcbiAgICB0aGlzLnggPSB4O1xuICAgIHRoaXMueSA9IHk7XG4gIH1cbn1cblxuUG9pbnRELnByb3RvdHlwZS5nZXRYID0gZnVuY3Rpb24gKClcbntcbiAgcmV0dXJuIHRoaXMueDtcbn07XG5cblBvaW50RC5wcm90b3R5cGUuZ2V0WSA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLnk7XG59O1xuXG5Qb2ludEQucHJvdG90eXBlLnNldFggPSBmdW5jdGlvbiAoeClcbntcbiAgdGhpcy54ID0geDtcbn07XG5cblBvaW50RC5wcm90b3R5cGUuc2V0WSA9IGZ1bmN0aW9uICh5KVxue1xuICB0aGlzLnkgPSB5O1xufTtcblxuUG9pbnRELnByb3RvdHlwZS5nZXREaWZmZXJlbmNlID0gZnVuY3Rpb24gKHB0KVxue1xuICByZXR1cm4gbmV3IERpbWVuc2lvbkQodGhpcy54IC0gcHQueCwgdGhpcy55IC0gcHQueSk7XG59O1xuXG5Qb2ludEQucHJvdG90eXBlLmdldENvcHkgPSBmdW5jdGlvbiAoKVxue1xuICByZXR1cm4gbmV3IFBvaW50RCh0aGlzLngsIHRoaXMueSk7XG59O1xuXG5Qb2ludEQucHJvdG90eXBlLnRyYW5zbGF0ZSA9IGZ1bmN0aW9uIChkaW0pXG57XG4gIHRoaXMueCArPSBkaW0ud2lkdGg7XG4gIHRoaXMueSArPSBkaW0uaGVpZ2h0O1xuICByZXR1cm4gdGhpcztcbn07XG5cbm1vZHVsZS5leHBvcnRzID0gUG9pbnREO1xuIiwiZnVuY3Rpb24gUmFuZG9tU2VlZCgpIHtcbn1cblJhbmRvbVNlZWQuc2VlZCA9IDE7XG5SYW5kb21TZWVkLnggPSAwO1xuXG5SYW5kb21TZWVkLm5leHREb3VibGUgPSBmdW5jdGlvbiAoKSB7XG4gIFJhbmRvbVNlZWQueCA9IE1hdGguc2luKFJhbmRvbVNlZWQuc2VlZCsrKSAqIDEwMDAwO1xuICByZXR1cm4gUmFuZG9tU2VlZC54IC0gTWF0aC5mbG9vcihSYW5kb21TZWVkLngpO1xufTtcblxubW9kdWxlLmV4cG9ydHMgPSBSYW5kb21TZWVkO1xuIiwiZnVuY3Rpb24gUmVjdGFuZ2xlRCh4LCB5LCB3aWR0aCwgaGVpZ2h0KSB7XG4gIHRoaXMueCA9IDA7XG4gIHRoaXMueSA9IDA7XG4gIHRoaXMud2lkdGggPSAwO1xuICB0aGlzLmhlaWdodCA9IDA7XG5cbiAgaWYgKHggIT0gbnVsbCAmJiB5ICE9IG51bGwgJiYgd2lkdGggIT0gbnVsbCAmJiBoZWlnaHQgIT0gbnVsbCkge1xuICAgIHRoaXMueCA9IHg7XG4gICAgdGhpcy55ID0geTtcbiAgICB0aGlzLndpZHRoID0gd2lkdGg7XG4gICAgdGhpcy5oZWlnaHQgPSBoZWlnaHQ7XG4gIH1cbn1cblxuUmVjdGFuZ2xlRC5wcm90b3R5cGUuZ2V0WCA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLng7XG59O1xuXG5SZWN0YW5nbGVELnByb3RvdHlwZS5zZXRYID0gZnVuY3Rpb24gKHgpXG57XG4gIHRoaXMueCA9IHg7XG59O1xuXG5SZWN0YW5nbGVELnByb3RvdHlwZS5nZXRZID0gZnVuY3Rpb24gKClcbntcbiAgcmV0dXJuIHRoaXMueTtcbn07XG5cblJlY3RhbmdsZUQucHJvdG90eXBlLnNldFkgPSBmdW5jdGlvbiAoeSlcbntcbiAgdGhpcy55ID0geTtcbn07XG5cblJlY3RhbmdsZUQucHJvdG90eXBlLmdldFdpZHRoID0gZnVuY3Rpb24gKClcbntcbiAgcmV0dXJuIHRoaXMud2lkdGg7XG59O1xuXG5SZWN0YW5nbGVELnByb3RvdHlwZS5zZXRXaWR0aCA9IGZ1bmN0aW9uICh3aWR0aClcbntcbiAgdGhpcy53aWR0aCA9IHdpZHRoO1xufTtcblxuUmVjdGFuZ2xlRC5wcm90b3R5cGUuZ2V0SGVpZ2h0ID0gZnVuY3Rpb24gKClcbntcbiAgcmV0dXJuIHRoaXMuaGVpZ2h0O1xufTtcblxuUmVjdGFuZ2xlRC5wcm90b3R5cGUuc2V0SGVpZ2h0ID0gZnVuY3Rpb24gKGhlaWdodClcbntcbiAgdGhpcy5oZWlnaHQgPSBoZWlnaHQ7XG59O1xuXG5SZWN0YW5nbGVELnByb3RvdHlwZS5nZXRSaWdodCA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLnggKyB0aGlzLndpZHRoO1xufTtcblxuUmVjdGFuZ2xlRC5wcm90b3R5cGUuZ2V0Qm90dG9tID0gZnVuY3Rpb24gKClcbntcbiAgcmV0dXJuIHRoaXMueSArIHRoaXMuaGVpZ2h0O1xufTtcblxuUmVjdGFuZ2xlRC5wcm90b3R5cGUuaW50ZXJzZWN0cyA9IGZ1bmN0aW9uIChhKVxue1xuICBpZiAodGhpcy5nZXRSaWdodCgpIDwgYS54KVxuICB7XG4gICAgcmV0dXJuIGZhbHNlO1xuICB9XG5cbiAgaWYgKHRoaXMuZ2V0Qm90dG9tKCkgPCBhLnkpXG4gIHtcbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cblxuICBpZiAoYS5nZXRSaWdodCgpIDwgdGhpcy54KVxuICB7XG4gICAgcmV0dXJuIGZhbHNlO1xuICB9XG5cbiAgaWYgKGEuZ2V0Qm90dG9tKCkgPCB0aGlzLnkpXG4gIHtcbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cblxuICByZXR1cm4gdHJ1ZTtcbn07XG5cblJlY3RhbmdsZUQucHJvdG90eXBlLmdldENlbnRlclggPSBmdW5jdGlvbiAoKVxue1xuICByZXR1cm4gdGhpcy54ICsgdGhpcy53aWR0aCAvIDI7XG59O1xuXG5SZWN0YW5nbGVELnByb3RvdHlwZS5nZXRNaW5YID0gZnVuY3Rpb24gKClcbntcbiAgcmV0dXJuIHRoaXMuZ2V0WCgpO1xufTtcblxuUmVjdGFuZ2xlRC5wcm90b3R5cGUuZ2V0TWF4WCA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLmdldFgoKSArIHRoaXMud2lkdGg7XG59O1xuXG5SZWN0YW5nbGVELnByb3RvdHlwZS5nZXRDZW50ZXJZID0gZnVuY3Rpb24gKClcbntcbiAgcmV0dXJuIHRoaXMueSArIHRoaXMuaGVpZ2h0IC8gMjtcbn07XG5cblJlY3RhbmdsZUQucHJvdG90eXBlLmdldE1pblkgPSBmdW5jdGlvbiAoKVxue1xuICByZXR1cm4gdGhpcy5nZXRZKCk7XG59O1xuXG5SZWN0YW5nbGVELnByb3RvdHlwZS5nZXRNYXhZID0gZnVuY3Rpb24gKClcbntcbiAgcmV0dXJuIHRoaXMuZ2V0WSgpICsgdGhpcy5oZWlnaHQ7XG59O1xuXG5SZWN0YW5nbGVELnByb3RvdHlwZS5nZXRXaWR0aEhhbGYgPSBmdW5jdGlvbiAoKVxue1xuICByZXR1cm4gdGhpcy53aWR0aCAvIDI7XG59O1xuXG5SZWN0YW5nbGVELnByb3RvdHlwZS5nZXRIZWlnaHRIYWxmID0gZnVuY3Rpb24gKClcbntcbiAgcmV0dXJuIHRoaXMuaGVpZ2h0IC8gMjtcbn07XG5cbm1vZHVsZS5leHBvcnRzID0gUmVjdGFuZ2xlRDtcbiIsIm1vZHVsZS5leHBvcnRzID0gZnVuY3Rpb24gKGluc3RhbmNlKSB7XG4gIGluc3RhbmNlLnRvQmVUaWxlZCA9IHt9O1xuICBcbiAgaW5zdGFuY2UuZ2V0VG9CZVRpbGVkID0gZnVuY3Rpb24gKG5vZGUpIHtcbiAgICB2YXIgaWQgPSBub2RlLmRhdGEoXCJpZFwiKTtcbiAgICAvL2ZpcnN0bHkgY2hlY2sgdGhlIHByZXZpb3VzIHJlc3VsdHNcbiAgICBpZiAoaW5zdGFuY2UudG9CZVRpbGVkW2lkXSAhPSBudWxsKSB7XG4gICAgICByZXR1cm4gaW5zdGFuY2UudG9CZVRpbGVkW2lkXTtcbiAgICB9XG5cbiAgICAvL29ubHkgY29tcG91bmQgbm9kZXMgYXJlIHRvIGJlIHRpbGVkXG4gICAgdmFyIGNoaWxkcmVuID0gbm9kZS5jaGlsZHJlbigpO1xuICAgIGlmIChjaGlsZHJlbiA9PSBudWxsIHx8IGNoaWxkcmVuLmxlbmd0aCA9PSAwKSB7XG4gICAgICBpbnN0YW5jZS50b0JlVGlsZWRbaWRdID0gZmFsc2U7XG4gICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuXG4gICAgLy9hIGNvbXBvdW5kIG5vZGUgaXMgbm90IHRvIGJlIHRpbGVkIGlmIGFsbCBvZiBpdHMgY29tcG91bmQgY2hpbGRyZW4gYXJlIG5vdCB0byBiZSB0aWxlZFxuICAgIGZvciAodmFyIGkgPSAwOyBpIDwgY2hpbGRyZW4ubGVuZ3RoOyBpKyspIHtcbiAgICAgIHZhciB0aGVDaGlsZCA9IGNoaWxkcmVuW2ldO1xuXG4gICAgICBpZiAoaW5zdGFuY2UuZ2V0Tm9kZURlZ3JlZSh0aGVDaGlsZCkgPiAwKSB7XG4gICAgICAgIGluc3RhbmNlLnRvQmVUaWxlZFtpZF0gPSBmYWxzZTtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgfVxuXG4gICAgICAvL3Bhc3MgdGhlIGNoaWxkcmVuIG5vdCBoYXZpbmcgdGhlIGNvbXBvdW5kIHN0cnVjdHVyZVxuICAgICAgaWYgKHRoZUNoaWxkLmNoaWxkcmVuKCkgPT0gbnVsbCB8fCB0aGVDaGlsZC5jaGlsZHJlbigpLmxlbmd0aCA9PSAwKSB7XG4gICAgICAgIGluc3RhbmNlLnRvQmVUaWxlZFt0aGVDaGlsZC5kYXRhKFwiaWRcIildID0gZmFsc2U7XG4gICAgICAgIGNvbnRpbnVlO1xuICAgICAgfVxuXG4gICAgICBpZiAoIWluc3RhbmNlLmdldFRvQmVUaWxlZCh0aGVDaGlsZCkpIHtcbiAgICAgICAgaW5zdGFuY2UudG9CZVRpbGVkW2lkXSA9IGZhbHNlO1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICB9XG4gICAgfVxuICAgIGluc3RhbmNlLnRvQmVUaWxlZFtpZF0gPSB0cnVlO1xuICAgIHJldHVybiB0cnVlO1xuICB9O1xuXG4gIGluc3RhbmNlLmdldE5vZGVEZWdyZWUgPSBmdW5jdGlvbiAobm9kZSkge1xuICAgIHZhciBpZCA9IG5vZGUuaWQoKTtcbiAgICB2YXIgZWRnZXMgPSBpbnN0YW5jZS5vcHRpb25zLmVsZXMuZWRnZXMoKS5maWx0ZXIoZnVuY3Rpb24gKGVsZSwgaSkge1xuICAgICAgaWYgKHR5cGVvZiBlbGUgPT09IFwibnVtYmVyXCIpIHtcbiAgICAgICAgZWxlID0gaTtcbiAgICAgIH1cbiAgICAgIHZhciBzb3VyY2UgPSBlbGUuZGF0YSgnc291cmNlJyk7XG4gICAgICB2YXIgdGFyZ2V0ID0gZWxlLmRhdGEoJ3RhcmdldCcpO1xuICAgICAgaWYgKHNvdXJjZSAhPSB0YXJnZXQgJiYgKHNvdXJjZSA9PSBpZCB8fCB0YXJnZXQgPT0gaWQpKSB7XG4gICAgICAgIHJldHVybiB0cnVlO1xuICAgICAgfVxuICAgIH0pO1xuICAgIHJldHVybiBlZGdlcy5sZW5ndGg7XG4gIH07XG5cbiAgaW5zdGFuY2UuZ2V0Tm9kZURlZ3JlZVdpdGhDaGlsZHJlbiA9IGZ1bmN0aW9uIChub2RlKSB7XG4gICAgdmFyIGRlZ3JlZSA9IGluc3RhbmNlLmdldE5vZGVEZWdyZWUobm9kZSk7XG4gICAgdmFyIGNoaWxkcmVuID0gbm9kZS5jaGlsZHJlbigpO1xuICAgIGZvciAodmFyIGkgPSAwOyBpIDwgY2hpbGRyZW4ubGVuZ3RoOyBpKyspIHtcbiAgICAgIHZhciBjaGlsZCA9IGNoaWxkcmVuW2ldO1xuICAgICAgZGVncmVlICs9IGluc3RhbmNlLmdldE5vZGVEZWdyZWVXaXRoQ2hpbGRyZW4oY2hpbGQpO1xuICAgIH1cbiAgICByZXR1cm4gZGVncmVlO1xuICB9O1xuXG4gIGluc3RhbmNlLmdyb3VwWmVyb0RlZ3JlZU1lbWJlcnMgPSBmdW5jdGlvbiAoKSB7XG4gICAgLy8gYXJyYXkgb2YgW3BhcmVudF9pZCB4IG9uZURlZ3JlZU5vZGVfaWRdXG4gICAgdmFyIHRlbXBNZW1iZXJHcm91cHMgPSBbXTtcbiAgICB2YXIgbWVtYmVyR3JvdXBzID0gW107XG4gICAgdmFyIHNlbGYgPSB0aGlzO1xuICAgIHZhciBwYXJlbnRNYXAgPSB7fTtcblxuICAgIGZvciAodmFyIGkgPSAwOyBpIDwgaW5zdGFuY2Uub3B0aW9ucy5lbGVzLm5vZGVzKCkubGVuZ3RoOyBpKyspIHtcbiAgICAgIHBhcmVudE1hcFtpbnN0YW5jZS5vcHRpb25zLmVsZXMubm9kZXMoKVtpXS5pZCgpXSA9IHRydWU7XG4gICAgfVxuXG4gICAgLy8gRmluZCBhbGwgemVybyBkZWdyZWUgbm9kZXMgd2hpY2ggYXJlbid0IGNvdmVyZWQgYnkgYSBjb21wb3VuZFxuICAgIHZhciB6ZXJvRGVncmVlID0gaW5zdGFuY2Uub3B0aW9ucy5lbGVzLm5vZGVzKCkuZmlsdGVyKGZ1bmN0aW9uIChlbGUsIGkpIHtcbiAgICAgIGlmICh0eXBlb2YgZWxlID09PSBcIm51bWJlclwiKSB7XG4gICAgICAgIGVsZSA9IGk7XG4gICAgICB9XG4gICAgICB2YXIgcGlkID0gZWxlLmRhdGEoJ3BhcmVudCcpO1xuICAgICAgaWYgKHBpZCAhPSB1bmRlZmluZWQgJiYgIXBhcmVudE1hcFtwaWRdKSB7XG4gICAgICAgIHBpZCA9IHVuZGVmaW5lZDtcbiAgICAgIH1cblxuICAgICAgaWYgKHNlbGYuZ2V0Tm9kZURlZ3JlZVdpdGhDaGlsZHJlbihlbGUpID09IDAgJiYgKHBpZCA9PSB1bmRlZmluZWQgfHwgKHBpZCAhPSB1bmRlZmluZWQgJiYgIXNlbGYuZ2V0VG9CZVRpbGVkKGVsZS5wYXJlbnQoKVswXSkpKSlcbiAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgICBlbHNlXG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9KTtcblxuICAgIC8vIENyZWF0ZSBhIG1hcCBvZiBwYXJlbnQgbm9kZSBhbmQgaXRzIHplcm8gZGVncmVlIG1lbWJlcnNcbiAgICBmb3IgKHZhciBpID0gMDsgaSA8IHplcm9EZWdyZWUubGVuZ3RoOyBpKyspXG4gICAge1xuICAgICAgdmFyIG5vZGUgPSB6ZXJvRGVncmVlW2ldO1xuICAgICAgdmFyIHBfaWQgPSBub2RlLnBhcmVudCgpLmlkKCk7XG5cbiAgICAgIGlmIChwX2lkICE9IHVuZGVmaW5lZCAmJiAhcGFyZW50TWFwW3BfaWRdKSB7XG4gICAgICAgIHBfaWQgPSB1bmRlZmluZWQ7XG4gICAgICB9XG5cbiAgICAgIGlmICh0eXBlb2YgdGVtcE1lbWJlckdyb3Vwc1twX2lkXSA9PT0gXCJ1bmRlZmluZWRcIilcbiAgICAgICAgdGVtcE1lbWJlckdyb3Vwc1twX2lkXSA9IFtdO1xuXG4gICAgICB0ZW1wTWVtYmVyR3JvdXBzW3BfaWRdID0gdGVtcE1lbWJlckdyb3Vwc1twX2lkXS5jb25jYXQobm9kZSk7XG4gICAgfVxuXG4gICAgLy8gSWYgdGhlcmUgYXJlIGF0IGxlYXN0IHR3byBub2RlcyBhdCBhIGxldmVsLCBjcmVhdGUgYSBkdW1teSBjb21wb3VuZCBmb3IgdGhlbVxuICAgIGZvciAodmFyIHBfaWQgaW4gdGVtcE1lbWJlckdyb3Vwcykge1xuICAgICAgaWYgKHRlbXBNZW1iZXJHcm91cHNbcF9pZF0ubGVuZ3RoID4gMSkge1xuICAgICAgICB2YXIgZHVtbXlDb21wb3VuZElkID0gXCJEdW1teUNvbXBvdW5kX1wiICsgcF9pZDtcbiAgICAgICAgbWVtYmVyR3JvdXBzW2R1bW15Q29tcG91bmRJZF0gPSB0ZW1wTWVtYmVyR3JvdXBzW3BfaWRdO1xuXG4gICAgICAgIC8vIENyZWF0ZSBhIGR1bW15IGNvbXBvdW5kXG4gICAgICAgIGlmIChpbnN0YW5jZS5vcHRpb25zLmN5LmdldEVsZW1lbnRCeUlkKGR1bW15Q29tcG91bmRJZCkuZW1wdHkoKSkge1xuICAgICAgICAgIGluc3RhbmNlLm9wdGlvbnMuY3kuYWRkKHtcbiAgICAgICAgICAgIGdyb3VwOiBcIm5vZGVzXCIsXG4gICAgICAgICAgICBkYXRhOiB7aWQ6IGR1bW15Q29tcG91bmRJZCwgcGFyZW50OiBwX2lkXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSk7XG5cbiAgICAgICAgICB2YXIgZHVtbXkgPSBpbnN0YW5jZS5vcHRpb25zLmN5Lm5vZGVzKClbaW5zdGFuY2Uub3B0aW9ucy5jeS5ub2RlcygpLmxlbmd0aCAtIDFdO1xuICAgICAgICAgIGluc3RhbmNlLm9wdGlvbnMuZWxlcyA9IGluc3RhbmNlLm9wdGlvbnMuZWxlcy51bmlvbihkdW1teSk7XG4gICAgICAgICAgZHVtbXkuaGlkZSgpO1xuXG4gICAgICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCB0ZW1wTWVtYmVyR3JvdXBzW3BfaWRdLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICBpZiAoaSA9PSAwKSB7XG4gICAgICAgICAgICAgIGR1bW15LnNjcmF0Y2goJ2Nvc2VCaWxrZW50Jywge3RlbXBjaGlsZHJlbjogW119KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHZhciBub2RlID0gdGVtcE1lbWJlckdyb3Vwc1twX2lkXVtpXTtcbiAgICAgICAgICAgIHZhciBzY3JhdGNoT2JqID0gbm9kZS5zY3JhdGNoKCdjb3NlQmlsa2VudCcpO1xuICAgICAgICAgICAgaWYgKCFzY3JhdGNoT2JqKSB7XG4gICAgICAgICAgICAgIHNjcmF0Y2hPYmogPSB7fTtcbiAgICAgICAgICAgICAgbm9kZS5zY3JhdGNoKCdjb3NlQmlsa2VudCcsIHNjcmF0Y2hPYmopO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgc2NyYXRjaE9ialsnZHVtbXlfcGFyZW50X2lkJ10gPSBkdW1teUNvbXBvdW5kSWQ7XG4gICAgICAgICAgICBpbnN0YW5jZS5vcHRpb25zLmN5LmFkZCh7XG4gICAgICAgICAgICAgIGdyb3VwOiBcIm5vZGVzXCIsXG4gICAgICAgICAgICAgIGRhdGE6IHtwYXJlbnQ6IGR1bW15Q29tcG91bmRJZCwgd2lkdGg6IG5vZGUud2lkdGgoKSwgaGVpZ2h0OiBub2RlLmhlaWdodCgpXG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgdmFyIHRlbXBjaGlsZCA9IGluc3RhbmNlLm9wdGlvbnMuY3kubm9kZXMoKVtpbnN0YW5jZS5vcHRpb25zLmN5Lm5vZGVzKCkubGVuZ3RoIC0gMV07XG4gICAgICAgICAgICB0ZW1wY2hpbGQuaGlkZSgpO1xuICAgICAgICAgICAgdGVtcGNoaWxkLmNzcygnd2lkdGgnLCB0ZW1wY2hpbGQuZGF0YSgnd2lkdGgnKSk7XG4gICAgICAgICAgICB0ZW1wY2hpbGQuY3NzKCdoZWlnaHQnLCB0ZW1wY2hpbGQuZGF0YSgnaGVpZ2h0JykpO1xuICAgICAgICAgICAgdGVtcGNoaWxkLndpZHRoKCk7XG4gICAgICAgICAgICBkdW1teS5zY3JhdGNoKCdjb3NlQmlsa2VudCcpLnRlbXBjaGlsZHJlbi5wdXNoKHRlbXBjaGlsZCk7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuXG4gICAgcmV0dXJuIG1lbWJlckdyb3VwcztcbiAgfTtcblxuICBpbnN0YW5jZS5wZXJmb3JtREZTT25Db21wb3VuZHMgPSBmdW5jdGlvbiAob3B0aW9ucykge1xuICAgIHZhciBjb21wb3VuZE9yZGVyID0gW107XG5cbiAgICB2YXIgcm9vdHMgPSBpbnN0YW5jZS5nZXRUb3BNb3N0Tm9kZXMoaW5zdGFuY2Uub3B0aW9ucy5lbGVzLm5vZGVzKCkpO1xuICAgIGluc3RhbmNlLmZpbGxDb21wZXhPcmRlckJ5REZTKGNvbXBvdW5kT3JkZXIsIHJvb3RzKTtcblxuICAgIHJldHVybiBjb21wb3VuZE9yZGVyO1xuICB9O1xuXG4gIGluc3RhbmNlLmZpbGxDb21wZXhPcmRlckJ5REZTID0gZnVuY3Rpb24gKGNvbXBvdW5kT3JkZXIsIGNoaWxkcmVuKSB7XG4gICAgZm9yICh2YXIgaSA9IDA7IGkgPCBjaGlsZHJlbi5sZW5ndGg7IGkrKykge1xuICAgICAgdmFyIGNoaWxkID0gY2hpbGRyZW5baV07XG4gICAgICBpbnN0YW5jZS5maWxsQ29tcGV4T3JkZXJCeURGUyhjb21wb3VuZE9yZGVyLCBjaGlsZC5jaGlsZHJlbigpKTtcbiAgICAgIGlmIChpbnN0YW5jZS5nZXRUb0JlVGlsZWQoY2hpbGQpKSB7XG4gICAgICAgIGNvbXBvdW5kT3JkZXIucHVzaChjaGlsZCk7XG4gICAgICB9XG4gICAgfVxuICB9O1xuXG4gIGluc3RhbmNlLmNsZWFyQ29tcG91bmRzID0gZnVuY3Rpb24gKCkge1xuICAgIHZhciBjaGlsZEdyYXBoTWFwID0gW107XG5cbiAgICAvLyBHZXQgY29tcG91bmQgb3JkZXJpbmcgYnkgZmluZGluZyB0aGUgaW5uZXIgb25lIGZpcnN0XG4gICAgdmFyIGNvbXBvdW5kT3JkZXIgPSBpbnN0YW5jZS5wZXJmb3JtREZTT25Db21wb3VuZHMoaW5zdGFuY2Uub3B0aW9ucyk7XG4gICAgaW5zdGFuY2UuY29tcG91bmRPcmRlciA9IGNvbXBvdW5kT3JkZXI7XG4gICAgaW5zdGFuY2UucHJvY2Vzc0NoaWxkcmVuTGlzdChpbnN0YW5jZS5yb290LCBpbnN0YW5jZS5nZXRUb3BNb3N0Tm9kZXMoaW5zdGFuY2Uub3B0aW9ucy5lbGVzLm5vZGVzKCkpLCBpbnN0YW5jZS5sYXlvdXQpO1xuXG4gICAgZm9yICh2YXIgaSA9IDA7IGkgPCBjb21wb3VuZE9yZGVyLmxlbmd0aDsgaSsrKSB7XG4gICAgICAvLyBmaW5kIHRoZSBjb3JyZXNwb25kaW5nIGxheW91dCBub2RlXG4gICAgICB2YXIgbENvbXBvdW5kTm9kZSA9IGluc3RhbmNlLmlkVG9MTm9kZVtjb21wb3VuZE9yZGVyW2ldLmlkKCldO1xuXG4gICAgICBjaGlsZEdyYXBoTWFwW2NvbXBvdW5kT3JkZXJbaV0uaWQoKV0gPSBjb21wb3VuZE9yZGVyW2ldLmNoaWxkcmVuKCk7XG5cbiAgICAgIC8vIFJlbW92ZSBjaGlsZHJlbiBvZiBjb21wb3VuZHNcbiAgICAgIGxDb21wb3VuZE5vZGUuY2hpbGQgPSBudWxsO1xuICAgIH1cblxuICAgIC8vIFRpbGUgdGhlIHJlbW92ZWQgY2hpbGRyZW5cbiAgICB2YXIgdGlsZWRNZW1iZXJQYWNrID0gaW5zdGFuY2UudGlsZUNvbXBvdW5kTWVtYmVycyhjaGlsZEdyYXBoTWFwKTtcblxuICAgIHJldHVybiB0aWxlZE1lbWJlclBhY2s7XG4gIH07XG5cbiAgaW5zdGFuY2UuY2xlYXJaZXJvRGVncmVlTWVtYmVycyA9IGZ1bmN0aW9uIChtZW1iZXJHcm91cHMpIHtcbiAgICB2YXIgdGlsZWRaZXJvRGVncmVlUGFjayA9IFtdO1xuXG4gICAgZm9yICh2YXIgaWQgaW4gbWVtYmVyR3JvdXBzKSB7XG4gICAgICB2YXIgY29tcG91bmROb2RlID0gaW5zdGFuY2UuaWRUb0xOb2RlW2lkXTtcblxuICAgICAgdGlsZWRaZXJvRGVncmVlUGFja1tpZF0gPSBpbnN0YW5jZS50aWxlTm9kZXMobWVtYmVyR3JvdXBzW2lkXSk7XG5cbiAgICAgIC8vIFNldCB0aGUgd2lkdGggYW5kIGhlaWdodCBvZiB0aGUgZHVtbXkgY29tcG91bmQgYXMgY2FsY3VsYXRlZFxuICAgICAgY29tcG91bmROb2RlLnJlY3Qud2lkdGggPSB0aWxlZFplcm9EZWdyZWVQYWNrW2lkXS53aWR0aDtcbiAgICAgIGNvbXBvdW5kTm9kZS5yZWN0LmhlaWdodCA9IHRpbGVkWmVyb0RlZ3JlZVBhY2tbaWRdLmhlaWdodDtcbiAgICB9XG4gICAgcmV0dXJuIHRpbGVkWmVyb0RlZ3JlZVBhY2s7XG4gIH07XG5cbiAgaW5zdGFuY2UucmVwb3B1bGF0ZUNvbXBvdW5kcyA9IGZ1bmN0aW9uICh0aWxlZE1lbWJlclBhY2spIHtcbiAgICBmb3IgKHZhciBpID0gaW5zdGFuY2UuY29tcG91bmRPcmRlci5sZW5ndGggLSAxOyBpID49IDA7IGktLSkge1xuICAgICAgdmFyIGlkID0gaW5zdGFuY2UuY29tcG91bmRPcmRlcltpXS5pZCgpO1xuICAgICAgdmFyIGxDb21wb3VuZE5vZGUgPSBpbnN0YW5jZS5pZFRvTE5vZGVbaWRdO1xuICAgICAgdmFyIGhvcml6b250YWxNYXJnaW4gPSBwYXJzZUludChpbnN0YW5jZS5jb21wb3VuZE9yZGVyW2ldLmNzcygncGFkZGluZy1sZWZ0JykpO1xuICAgICAgdmFyIHZlcnRpY2FsTWFyZ2luID0gcGFyc2VJbnQoaW5zdGFuY2UuY29tcG91bmRPcmRlcltpXS5jc3MoJ3BhZGRpbmctdG9wJykpO1xuXG4gICAgICBpbnN0YW5jZS5hZGp1c3RMb2NhdGlvbnModGlsZWRNZW1iZXJQYWNrW2lkXSwgbENvbXBvdW5kTm9kZS5yZWN0LngsIGxDb21wb3VuZE5vZGUucmVjdC55LCBob3Jpem9udGFsTWFyZ2luLCB2ZXJ0aWNhbE1hcmdpbik7XG4gICAgfVxuICB9O1xuXG4gIGluc3RhbmNlLnJlcG9wdWxhdGVaZXJvRGVncmVlTWVtYmVycyA9IGZ1bmN0aW9uICh0aWxlZFBhY2spIHtcbiAgICBmb3IgKHZhciBpIGluIHRpbGVkUGFjaykge1xuICAgICAgdmFyIGNvbXBvdW5kID0gaW5zdGFuY2UuY3kuZ2V0RWxlbWVudEJ5SWQoaSk7XG4gICAgICB2YXIgY29tcG91bmROb2RlID0gaW5zdGFuY2UuaWRUb0xOb2RlW2ldO1xuICAgICAgdmFyIGhvcml6b250YWxNYXJnaW4gPSBwYXJzZUludChjb21wb3VuZC5jc3MoJ3BhZGRpbmctbGVmdCcpKTtcbiAgICAgIHZhciB2ZXJ0aWNhbE1hcmdpbiA9IHBhcnNlSW50KGNvbXBvdW5kLmNzcygncGFkZGluZy10b3AnKSk7XG5cbiAgICAgIC8vIEFkanVzdCB0aGUgcG9zaXRpb25zIG9mIG5vZGVzIHdydCBpdHMgY29tcG91bmRcbiAgICAgIGluc3RhbmNlLmFkanVzdExvY2F0aW9ucyh0aWxlZFBhY2tbaV0sIGNvbXBvdW5kTm9kZS5yZWN0LngsIGNvbXBvdW5kTm9kZS5yZWN0LnksIGhvcml6b250YWxNYXJnaW4sIHZlcnRpY2FsTWFyZ2luKTtcblxuICAgICAgdmFyIHRlbXBjaGlsZHJlbiA9IGNvbXBvdW5kLnNjcmF0Y2goJ2Nvc2VCaWxrZW50JykudGVtcGNoaWxkcmVuO1xuICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCB0ZW1wY2hpbGRyZW4ubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgdGVtcGNoaWxkcmVuW2ldLnJlbW92ZSgpO1xuICAgICAgfVxuXG4gICAgICAvLyBSZW1vdmUgdGhlIGR1bW15IGNvbXBvdW5kXG4gICAgICBjb21wb3VuZC5yZW1vdmUoKTtcbiAgICB9XG4gIH07XG5cbiAgLyoqXG4gICAqIFRoaXMgbWV0aG9kIHBsYWNlcyBlYWNoIHplcm8gZGVncmVlIG1lbWJlciB3cnQgZ2l2ZW4gKHgseSkgY29vcmRpbmF0ZXMgKHRvcCBsZWZ0KS5cbiAgICovXG4gIGluc3RhbmNlLmFkanVzdExvY2F0aW9ucyA9IGZ1bmN0aW9uIChvcmdhbml6YXRpb24sIHgsIHksIGNvbXBvdW5kSG9yaXpvbnRhbE1hcmdpbiwgY29tcG91bmRWZXJ0aWNhbE1hcmdpbikge1xuICAgIHggKz0gY29tcG91bmRIb3Jpem9udGFsTWFyZ2luO1xuICAgIHkgKz0gY29tcG91bmRWZXJ0aWNhbE1hcmdpbjtcblxuICAgIHZhciBsZWZ0ID0geDtcblxuICAgIGZvciAodmFyIGkgPSAwOyBpIDwgb3JnYW5pemF0aW9uLnJvd3MubGVuZ3RoOyBpKyspIHtcbiAgICAgIHZhciByb3cgPSBvcmdhbml6YXRpb24ucm93c1tpXTtcbiAgICAgIHggPSBsZWZ0O1xuICAgICAgdmFyIG1heEhlaWdodCA9IDA7XG5cbiAgICAgIGZvciAodmFyIGogPSAwOyBqIDwgcm93Lmxlbmd0aDsgaisrKSB7XG4gICAgICAgIHZhciBsbm9kZSA9IHJvd1tqXTtcbiAgICAgICAgdmFyIG5vZGUgPSBpbnN0YW5jZS5jeS5nZXRFbGVtZW50QnlJZChsbm9kZS5pZCk7XG5cbiAgICAgICAgbG5vZGUucmVjdC54ID0geDsvLyArIGxub2RlLnJlY3Qud2lkdGggLyAyO1xuICAgICAgICBsbm9kZS5yZWN0LnkgPSB5Oy8vICsgbG5vZGUucmVjdC5oZWlnaHQgLyAyO1xuXG4gICAgICAgIHggKz0gbG5vZGUucmVjdC53aWR0aCArIG9yZ2FuaXphdGlvbi5ob3Jpem9udGFsUGFkZGluZztcblxuICAgICAgICBpZiAobG5vZGUucmVjdC5oZWlnaHQgPiBtYXhIZWlnaHQpXG4gICAgICAgICAgbWF4SGVpZ2h0ID0gbG5vZGUucmVjdC5oZWlnaHQ7XG4gICAgICB9XG5cbiAgICAgIHkgKz0gbWF4SGVpZ2h0ICsgb3JnYW5pemF0aW9uLnZlcnRpY2FsUGFkZGluZztcbiAgICB9XG4gIH07XG5cbiAgaW5zdGFuY2UudGlsZUNvbXBvdW5kTWVtYmVycyA9IGZ1bmN0aW9uIChjaGlsZEdyYXBoTWFwKSB7XG4gICAgdmFyIHRpbGVkTWVtYmVyUGFjayA9IFtdO1xuXG4gICAgZm9yICh2YXIgaWQgaW4gY2hpbGRHcmFwaE1hcCkge1xuICAgICAgLy8gQWNjZXNzIGxheW91dEluZm8gbm9kZXMgdG8gc2V0IHRoZSB3aWR0aCBhbmQgaGVpZ2h0IG9mIGNvbXBvdW5kc1xuICAgICAgdmFyIGNvbXBvdW5kTm9kZSA9IGluc3RhbmNlLmlkVG9MTm9kZVtpZF07XG5cbiAgICAgIHRpbGVkTWVtYmVyUGFja1tpZF0gPSBpbnN0YW5jZS50aWxlTm9kZXMoY2hpbGRHcmFwaE1hcFtpZF0pO1xuXG4gICAgICBjb21wb3VuZE5vZGUucmVjdC53aWR0aCA9IHRpbGVkTWVtYmVyUGFja1tpZF0ud2lkdGggKyAyMDtcbiAgICAgIGNvbXBvdW5kTm9kZS5yZWN0LmhlaWdodCA9IHRpbGVkTWVtYmVyUGFja1tpZF0uaGVpZ2h0ICsgMjA7XG4gICAgfVxuXG4gICAgcmV0dXJuIHRpbGVkTWVtYmVyUGFjaztcbiAgfTtcblxuICBpbnN0YW5jZS50aWxlTm9kZXMgPSBmdW5jdGlvbiAobm9kZXMpIHtcbiAgICB2YXIgc2VsZiA9IHRoaXM7XG4gICAgdmFyIHZlcnRpY2FsUGFkZGluZyA9IHR5cGVvZiBzZWxmLm9wdGlvbnMudGlsaW5nUGFkZGluZ1ZlcnRpY2FsID09PSAnZnVuY3Rpb24nID8gc2VsZi5vcHRpb25zLnRpbGluZ1BhZGRpbmdWZXJ0aWNhbC5jYWxsKCkgOiBzZWxmLm9wdGlvbnMudGlsaW5nUGFkZGluZ1ZlcnRpY2FsO1xuICAgIHZhciBob3Jpem9udGFsUGFkZGluZyA9IHR5cGVvZiBzZWxmLm9wdGlvbnMudGlsaW5nUGFkZGluZ0hvcml6b250YWwgPT09ICdmdW5jdGlvbicgPyBzZWxmLm9wdGlvbnMudGlsaW5nUGFkZGluZ0hvcml6b250YWwuY2FsbCgpIDogc2VsZi5vcHRpb25zLnRpbGluZ1BhZGRpbmdIb3Jpem9udGFsO1xuICAgIHZhciBvcmdhbml6YXRpb24gPSB7XG4gICAgICByb3dzOiBbXSxcbiAgICAgIHJvd1dpZHRoOiBbXSxcbiAgICAgIHJvd0hlaWdodDogW10sXG4gICAgICB3aWR0aDogMjAsXG4gICAgICBoZWlnaHQ6IDIwLFxuICAgICAgdmVydGljYWxQYWRkaW5nOiB2ZXJ0aWNhbFBhZGRpbmcsXG4gICAgICBob3Jpem9udGFsUGFkZGluZzogaG9yaXpvbnRhbFBhZGRpbmdcbiAgICB9O1xuXG4gICAgdmFyIGxheW91dE5vZGVzID0gW107XG5cbiAgICAvLyBHZXQgbGF5b3V0IG5vZGVzXG4gICAgZm9yICh2YXIgaSA9IDA7IGkgPCBub2Rlcy5sZW5ndGg7IGkrKykge1xuICAgICAgdmFyIG5vZGUgPSBub2Rlc1tpXTtcbiAgICAgIHZhciBsTm9kZSA9IGluc3RhbmNlLmlkVG9MTm9kZVtub2RlLmlkKCldO1xuXG4gICAgICBpZiAoIW5vZGUuc2NyYXRjaCgnY29zZUJpbGtlbnQnKSB8fCAhbm9kZS5zY3JhdGNoKCdjb3NlQmlsa2VudCcpLmR1bW15X3BhcmVudF9pZCkge1xuICAgICAgICB2YXIgb3duZXIgPSBsTm9kZS5vd25lcjtcbiAgICAgICAgb3duZXIucmVtb3ZlKGxOb2RlKTtcblxuICAgICAgICBpbnN0YW5jZS5nbS5yZXNldEFsbE5vZGVzKCk7XG4gICAgICAgIGluc3RhbmNlLmdtLmdldEFsbE5vZGVzKCk7XG4gICAgICB9XG5cbiAgICAgIGxheW91dE5vZGVzLnB1c2gobE5vZGUpO1xuICAgIH1cblxuICAgIC8vIFNvcnQgdGhlIG5vZGVzIGluIGFzY2VuZGluZyBvcmRlciBvZiB0aGVpciBhcmVhc1xuICAgIGxheW91dE5vZGVzLnNvcnQoZnVuY3Rpb24gKG4xLCBuMikge1xuICAgICAgaWYgKG4xLnJlY3Qud2lkdGggKiBuMS5yZWN0LmhlaWdodCA+IG4yLnJlY3Qud2lkdGggKiBuMi5yZWN0LmhlaWdodClcbiAgICAgICAgcmV0dXJuIC0xO1xuICAgICAgaWYgKG4xLnJlY3Qud2lkdGggKiBuMS5yZWN0LmhlaWdodCA8IG4yLnJlY3Qud2lkdGggKiBuMi5yZWN0LmhlaWdodClcbiAgICAgICAgcmV0dXJuIDE7XG4gICAgICByZXR1cm4gMDtcbiAgICB9KTtcblxuICAgIC8vIENyZWF0ZSB0aGUgb3JnYW5pemF0aW9uIC0+IHRpbGUgbWVtYmVyc1xuICAgIGZvciAodmFyIGkgPSAwOyBpIDwgbGF5b3V0Tm9kZXMubGVuZ3RoOyBpKyspIHtcbiAgICAgIHZhciBsTm9kZSA9IGxheW91dE5vZGVzW2ldO1xuXG4gICAgICB2YXIgY3lOb2RlID0gaW5zdGFuY2UuY3kuZ2V0RWxlbWVudEJ5SWQobE5vZGUuaWQpLnBhcmVudCgpWzBdO1xuICAgICAgdmFyIG1pbldpZHRoID0gMDtcbiAgICAgIGlmIChjeU5vZGUpIHtcbiAgICAgICAgbWluV2lkdGggPSBwYXJzZUludChjeU5vZGUuY3NzKCdwYWRkaW5nLWxlZnQnKSkgKyBwYXJzZUludChjeU5vZGUuY3NzKCdwYWRkaW5nLXJpZ2h0JykpO1xuICAgICAgfVxuXG4gICAgICBpZiAob3JnYW5pemF0aW9uLnJvd3MubGVuZ3RoID09IDApIHtcbiAgICAgICAgaW5zdGFuY2UuaW5zZXJ0Tm9kZVRvUm93KG9yZ2FuaXphdGlvbiwgbE5vZGUsIDAsIG1pbldpZHRoKTtcbiAgICAgIH1cbiAgICAgIGVsc2UgaWYgKGluc3RhbmNlLmNhbkFkZEhvcml6b250YWwob3JnYW5pemF0aW9uLCBsTm9kZS5yZWN0LndpZHRoLCBsTm9kZS5yZWN0LmhlaWdodCkpIHtcbiAgICAgICAgaW5zdGFuY2UuaW5zZXJ0Tm9kZVRvUm93KG9yZ2FuaXphdGlvbiwgbE5vZGUsIGluc3RhbmNlLmdldFNob3J0ZXN0Um93SW5kZXgob3JnYW5pemF0aW9uKSwgbWluV2lkdGgpO1xuICAgICAgfVxuICAgICAgZWxzZSB7XG4gICAgICAgIGluc3RhbmNlLmluc2VydE5vZGVUb1Jvdyhvcmdhbml6YXRpb24sIGxOb2RlLCBvcmdhbml6YXRpb24ucm93cy5sZW5ndGgsIG1pbldpZHRoKTtcbiAgICAgIH1cblxuICAgICAgaW5zdGFuY2Uuc2hpZnRUb0xhc3RSb3cob3JnYW5pemF0aW9uKTtcbiAgICB9XG5cbiAgICByZXR1cm4gb3JnYW5pemF0aW9uO1xuICB9O1xuXG4gIGluc3RhbmNlLmluc2VydE5vZGVUb1JvdyA9IGZ1bmN0aW9uIChvcmdhbml6YXRpb24sIG5vZGUsIHJvd0luZGV4LCBtaW5XaWR0aCkge1xuICAgIHZhciBtaW5Db21wb3VuZFNpemUgPSBtaW5XaWR0aDtcblxuICAgIC8vIEFkZCBuZXcgcm93IGlmIG5lZWRlZFxuICAgIGlmIChyb3dJbmRleCA9PSBvcmdhbml6YXRpb24ucm93cy5sZW5ndGgpIHtcbiAgICAgIHZhciBzZWNvbmREaW1lbnNpb24gPSBbXTtcblxuICAgICAgb3JnYW5pemF0aW9uLnJvd3MucHVzaChzZWNvbmREaW1lbnNpb24pO1xuICAgICAgb3JnYW5pemF0aW9uLnJvd1dpZHRoLnB1c2gobWluQ29tcG91bmRTaXplKTtcbiAgICAgIG9yZ2FuaXphdGlvbi5yb3dIZWlnaHQucHVzaCgwKTtcbiAgICB9XG5cbiAgICAvLyBVcGRhdGUgcm93IHdpZHRoXG4gICAgdmFyIHcgPSBvcmdhbml6YXRpb24ucm93V2lkdGhbcm93SW5kZXhdICsgbm9kZS5yZWN0LndpZHRoO1xuXG4gICAgaWYgKG9yZ2FuaXphdGlvbi5yb3dzW3Jvd0luZGV4XS5sZW5ndGggPiAwKSB7XG4gICAgICB3ICs9IG9yZ2FuaXphdGlvbi5ob3Jpem9udGFsUGFkZGluZztcbiAgICB9XG5cbiAgICBvcmdhbml6YXRpb24ucm93V2lkdGhbcm93SW5kZXhdID0gdztcbiAgICAvLyBVcGRhdGUgY29tcG91bmQgd2lkdGhcbiAgICBpZiAob3JnYW5pemF0aW9uLndpZHRoIDwgdykge1xuICAgICAgb3JnYW5pemF0aW9uLndpZHRoID0gdztcbiAgICB9XG5cbiAgICAvLyBVcGRhdGUgaGVpZ2h0XG4gICAgdmFyIGggPSBub2RlLnJlY3QuaGVpZ2h0O1xuICAgIGlmIChyb3dJbmRleCA+IDApXG4gICAgICBoICs9IG9yZ2FuaXphdGlvbi52ZXJ0aWNhbFBhZGRpbmc7XG5cbiAgICB2YXIgZXh0cmFIZWlnaHQgPSAwO1xuICAgIGlmIChoID4gb3JnYW5pemF0aW9uLnJvd0hlaWdodFtyb3dJbmRleF0pIHtcbiAgICAgIGV4dHJhSGVpZ2h0ID0gb3JnYW5pemF0aW9uLnJvd0hlaWdodFtyb3dJbmRleF07XG4gICAgICBvcmdhbml6YXRpb24ucm93SGVpZ2h0W3Jvd0luZGV4XSA9IGg7XG4gICAgICBleHRyYUhlaWdodCA9IG9yZ2FuaXphdGlvbi5yb3dIZWlnaHRbcm93SW5kZXhdIC0gZXh0cmFIZWlnaHQ7XG4gICAgfVxuXG4gICAgb3JnYW5pemF0aW9uLmhlaWdodCArPSBleHRyYUhlaWdodDtcblxuICAgIC8vIEluc2VydCBub2RlXG4gICAgb3JnYW5pemF0aW9uLnJvd3Nbcm93SW5kZXhdLnB1c2gobm9kZSk7XG4gIH07XG5cbi8vU2NhbnMgdGhlIHJvd3Mgb2YgYW4gb3JnYW5pemF0aW9uIGFuZCByZXR1cm5zIHRoZSBvbmUgd2l0aCB0aGUgbWluIHdpZHRoXG4gIGluc3RhbmNlLmdldFNob3J0ZXN0Um93SW5kZXggPSBmdW5jdGlvbiAob3JnYW5pemF0aW9uKSB7XG4gICAgdmFyIHIgPSAtMTtcbiAgICB2YXIgbWluID0gTnVtYmVyLk1BWF9WQUxVRTtcblxuICAgIGZvciAodmFyIGkgPSAwOyBpIDwgb3JnYW5pemF0aW9uLnJvd3MubGVuZ3RoOyBpKyspIHtcbiAgICAgIGlmIChvcmdhbml6YXRpb24ucm93V2lkdGhbaV0gPCBtaW4pIHtcbiAgICAgICAgciA9IGk7XG4gICAgICAgIG1pbiA9IG9yZ2FuaXphdGlvbi5yb3dXaWR0aFtpXTtcbiAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIHI7XG4gIH07XG5cbi8vU2NhbnMgdGhlIHJvd3Mgb2YgYW4gb3JnYW5pemF0aW9uIGFuZCByZXR1cm5zIHRoZSBvbmUgd2l0aCB0aGUgbWF4IHdpZHRoXG4gIGluc3RhbmNlLmdldExvbmdlc3RSb3dJbmRleCA9IGZ1bmN0aW9uIChvcmdhbml6YXRpb24pIHtcbiAgICB2YXIgciA9IC0xO1xuICAgIHZhciBtYXggPSBOdW1iZXIuTUlOX1ZBTFVFO1xuXG4gICAgZm9yICh2YXIgaSA9IDA7IGkgPCBvcmdhbml6YXRpb24ucm93cy5sZW5ndGg7IGkrKykge1xuXG4gICAgICBpZiAob3JnYW5pemF0aW9uLnJvd1dpZHRoW2ldID4gbWF4KSB7XG4gICAgICAgIHIgPSBpO1xuICAgICAgICBtYXggPSBvcmdhbml6YXRpb24ucm93V2lkdGhbaV07XG4gICAgICB9XG4gICAgfVxuXG4gICAgcmV0dXJuIHI7XG4gIH07XG5cbiAgLyoqXG4gICAqIFRoaXMgbWV0aG9kIGNoZWNrcyB3aGV0aGVyIGFkZGluZyBleHRyYSB3aWR0aCB0byB0aGUgb3JnYW5pemF0aW9uIHZpb2xhdGVzXG4gICAqIHRoZSBhc3BlY3QgcmF0aW8oMSkgb3Igbm90LlxuICAgKi9cbiAgaW5zdGFuY2UuY2FuQWRkSG9yaXpvbnRhbCA9IGZ1bmN0aW9uIChvcmdhbml6YXRpb24sIGV4dHJhV2lkdGgsIGV4dHJhSGVpZ2h0KSB7XG5cbiAgICB2YXIgc3JpID0gaW5zdGFuY2UuZ2V0U2hvcnRlc3RSb3dJbmRleChvcmdhbml6YXRpb24pO1xuXG4gICAgaWYgKHNyaSA8IDApIHtcbiAgICAgIHJldHVybiB0cnVlO1xuICAgIH1cblxuICAgIHZhciBtaW4gPSBvcmdhbml6YXRpb24ucm93V2lkdGhbc3JpXTtcblxuICAgIGlmIChtaW4gKyBvcmdhbml6YXRpb24uaG9yaXpvbnRhbFBhZGRpbmcgKyBleHRyYVdpZHRoIDw9IG9yZ2FuaXphdGlvbi53aWR0aClcbiAgICAgIHJldHVybiB0cnVlO1xuXG4gICAgdmFyIGhEaWZmID0gMDtcblxuICAgIC8vIEFkZGluZyB0byBhbiBleGlzdGluZyByb3dcbiAgICBpZiAob3JnYW5pemF0aW9uLnJvd0hlaWdodFtzcmldIDwgZXh0cmFIZWlnaHQpIHtcbiAgICAgIGlmIChzcmkgPiAwKVxuICAgICAgICBoRGlmZiA9IGV4dHJhSGVpZ2h0ICsgb3JnYW5pemF0aW9uLnZlcnRpY2FsUGFkZGluZyAtIG9yZ2FuaXphdGlvbi5yb3dIZWlnaHRbc3JpXTtcbiAgICB9XG5cbiAgICB2YXIgYWRkX3RvX3Jvd19yYXRpbztcbiAgICBpZiAob3JnYW5pemF0aW9uLndpZHRoIC0gbWluID49IGV4dHJhV2lkdGggKyBvcmdhbml6YXRpb24uaG9yaXpvbnRhbFBhZGRpbmcpIHtcbiAgICAgIGFkZF90b19yb3dfcmF0aW8gPSAob3JnYW5pemF0aW9uLmhlaWdodCArIGhEaWZmKSAvIChtaW4gKyBleHRyYVdpZHRoICsgb3JnYW5pemF0aW9uLmhvcml6b250YWxQYWRkaW5nKTtcbiAgICB9IGVsc2Uge1xuICAgICAgYWRkX3RvX3Jvd19yYXRpbyA9IChvcmdhbml6YXRpb24uaGVpZ2h0ICsgaERpZmYpIC8gb3JnYW5pemF0aW9uLndpZHRoO1xuICAgIH1cblxuICAgIC8vIEFkZGluZyBhIG5ldyByb3cgZm9yIHRoaXMgbm9kZVxuICAgIGhEaWZmID0gZXh0cmFIZWlnaHQgKyBvcmdhbml6YXRpb24udmVydGljYWxQYWRkaW5nO1xuICAgIHZhciBhZGRfbmV3X3Jvd19yYXRpbztcbiAgICBpZiAob3JnYW5pemF0aW9uLndpZHRoIDwgZXh0cmFXaWR0aCkge1xuICAgICAgYWRkX25ld19yb3dfcmF0aW8gPSAob3JnYW5pemF0aW9uLmhlaWdodCArIGhEaWZmKSAvIGV4dHJhV2lkdGg7XG4gICAgfSBlbHNlIHtcbiAgICAgIGFkZF9uZXdfcm93X3JhdGlvID0gKG9yZ2FuaXphdGlvbi5oZWlnaHQgKyBoRGlmZikgLyBvcmdhbml6YXRpb24ud2lkdGg7XG4gICAgfVxuXG4gICAgaWYgKGFkZF9uZXdfcm93X3JhdGlvIDwgMSlcbiAgICAgIGFkZF9uZXdfcm93X3JhdGlvID0gMSAvIGFkZF9uZXdfcm93X3JhdGlvO1xuXG4gICAgaWYgKGFkZF90b19yb3dfcmF0aW8gPCAxKVxuICAgICAgYWRkX3RvX3Jvd19yYXRpbyA9IDEgLyBhZGRfdG9fcm93X3JhdGlvO1xuXG4gICAgcmV0dXJuIGFkZF90b19yb3dfcmF0aW8gPCBhZGRfbmV3X3Jvd19yYXRpbztcbiAgfTtcblxuXG4vL0lmIG1vdmluZyB0aGUgbGFzdCBub2RlIGZyb20gdGhlIGxvbmdlc3Qgcm93IGFuZCBhZGRpbmcgaXQgdG8gdGhlIGxhc3Rcbi8vcm93IG1ha2VzIHRoZSBib3VuZGluZyBib3ggc21hbGxlciwgZG8gaXQuXG4gIGluc3RhbmNlLnNoaWZ0VG9MYXN0Um93ID0gZnVuY3Rpb24gKG9yZ2FuaXphdGlvbikge1xuICAgIHZhciBsb25nZXN0ID0gaW5zdGFuY2UuZ2V0TG9uZ2VzdFJvd0luZGV4KG9yZ2FuaXphdGlvbik7XG4gICAgdmFyIGxhc3QgPSBvcmdhbml6YXRpb24ucm93V2lkdGgubGVuZ3RoIC0gMTtcbiAgICB2YXIgcm93ID0gb3JnYW5pemF0aW9uLnJvd3NbbG9uZ2VzdF07XG4gICAgdmFyIG5vZGUgPSByb3dbcm93Lmxlbmd0aCAtIDFdO1xuXG4gICAgdmFyIGRpZmYgPSBub2RlLndpZHRoICsgb3JnYW5pemF0aW9uLmhvcml6b250YWxQYWRkaW5nO1xuXG4gICAgLy8gQ2hlY2sgaWYgdGhlcmUgaXMgZW5vdWdoIHNwYWNlIG9uIHRoZSBsYXN0IHJvd1xuICAgIGlmIChvcmdhbml6YXRpb24ud2lkdGggLSBvcmdhbml6YXRpb24ucm93V2lkdGhbbGFzdF0gPiBkaWZmICYmIGxvbmdlc3QgIT0gbGFzdCkge1xuICAgICAgLy8gUmVtb3ZlIHRoZSBsYXN0IGVsZW1lbnQgb2YgdGhlIGxvbmdlc3Qgcm93XG4gICAgICByb3cuc3BsaWNlKC0xLCAxKTtcblxuICAgICAgLy8gUHVzaCBpdCB0byB0aGUgbGFzdCByb3dcbiAgICAgIG9yZ2FuaXphdGlvbi5yb3dzW2xhc3RdLnB1c2gobm9kZSk7XG5cbiAgICAgIG9yZ2FuaXphdGlvbi5yb3dXaWR0aFtsb25nZXN0XSA9IG9yZ2FuaXphdGlvbi5yb3dXaWR0aFtsb25nZXN0XSAtIGRpZmY7XG4gICAgICBvcmdhbml6YXRpb24ucm93V2lkdGhbbGFzdF0gPSBvcmdhbml6YXRpb24ucm93V2lkdGhbbGFzdF0gKyBkaWZmO1xuICAgICAgb3JnYW5pemF0aW9uLndpZHRoID0gb3JnYW5pemF0aW9uLnJvd1dpZHRoW2luc3RhbmNlLmdldExvbmdlc3RSb3dJbmRleChvcmdhbml6YXRpb24pXTtcblxuICAgICAgLy8gVXBkYXRlIGhlaWdodHMgb2YgdGhlIG9yZ2FuaXphdGlvblxuICAgICAgdmFyIG1heEhlaWdodCA9IE51bWJlci5NSU5fVkFMVUU7XG4gICAgICBmb3IgKHZhciBpID0gMDsgaSA8IHJvdy5sZW5ndGg7IGkrKykge1xuICAgICAgICBpZiAocm93W2ldLmhlaWdodCA+IG1heEhlaWdodClcbiAgICAgICAgICBtYXhIZWlnaHQgPSByb3dbaV0uaGVpZ2h0O1xuICAgICAgfVxuICAgICAgaWYgKGxvbmdlc3QgPiAwKVxuICAgICAgICBtYXhIZWlnaHQgKz0gb3JnYW5pemF0aW9uLnZlcnRpY2FsUGFkZGluZztcblxuICAgICAgdmFyIHByZXZUb3RhbCA9IG9yZ2FuaXphdGlvbi5yb3dIZWlnaHRbbG9uZ2VzdF0gKyBvcmdhbml6YXRpb24ucm93SGVpZ2h0W2xhc3RdO1xuXG4gICAgICBvcmdhbml6YXRpb24ucm93SGVpZ2h0W2xvbmdlc3RdID0gbWF4SGVpZ2h0O1xuICAgICAgaWYgKG9yZ2FuaXphdGlvbi5yb3dIZWlnaHRbbGFzdF0gPCBub2RlLmhlaWdodCArIG9yZ2FuaXphdGlvbi52ZXJ0aWNhbFBhZGRpbmcpXG4gICAgICAgIG9yZ2FuaXphdGlvbi5yb3dIZWlnaHRbbGFzdF0gPSBub2RlLmhlaWdodCArIG9yZ2FuaXphdGlvbi52ZXJ0aWNhbFBhZGRpbmc7XG5cbiAgICAgIHZhciBmaW5hbFRvdGFsID0gb3JnYW5pemF0aW9uLnJvd0hlaWdodFtsb25nZXN0XSArIG9yZ2FuaXphdGlvbi5yb3dIZWlnaHRbbGFzdF07XG4gICAgICBvcmdhbml6YXRpb24uaGVpZ2h0ICs9IChmaW5hbFRvdGFsIC0gcHJldlRvdGFsKTtcblxuICAgICAgaW5zdGFuY2Uuc2hpZnRUb0xhc3RSb3cob3JnYW5pemF0aW9uKTtcbiAgICB9XG4gIH07XG4gIFxuICBpbnN0YW5jZS5wcmVMYXlvdXQgPSBmdW5jdGlvbigpIHtcbiAgICAvLyBGaW5kIHplcm8gZGVncmVlIG5vZGVzIGFuZCBjcmVhdGUgYSBjb21wb3VuZCBmb3IgZWFjaCBsZXZlbFxuICAgIHZhciBtZW1iZXJHcm91cHMgPSBpbnN0YW5jZS5ncm91cFplcm9EZWdyZWVNZW1iZXJzKCk7XG4gICAgLy8gVGlsZSBhbmQgY2xlYXIgY2hpbGRyZW4gb2YgZWFjaCBjb21wb3VuZFxuICAgIGluc3RhbmNlLnRpbGVkTWVtYmVyUGFjayA9IGluc3RhbmNlLmNsZWFyQ29tcG91bmRzKCk7XG4gICAgLy8gU2VwYXJhdGVseSB0aWxlIGFuZCBjbGVhciB6ZXJvIGRlZ3JlZSBub2RlcyBmb3IgZWFjaCBsZXZlbFxuICAgIGluc3RhbmNlLnRpbGVkWmVyb0RlZ3JlZU5vZGVzID0gaW5zdGFuY2UuY2xlYXJaZXJvRGVncmVlTWVtYmVycyhtZW1iZXJHcm91cHMpO1xuICB9O1xuICBcbiAgaW5zdGFuY2UucG9zdExheW91dCA9IGZ1bmN0aW9uKCkge1xuICAgIHZhciBub2RlcyA9IGluc3RhbmNlLm9wdGlvbnMuZWxlcy5ub2RlcygpO1xuICAgIC8vZmlsbCB0aGUgdG9CZVRpbGVkIG1hcFxuICAgIGZvciAodmFyIGkgPSAwOyBpIDwgbm9kZXMubGVuZ3RoOyBpKyspIHtcbiAgICAgIGluc3RhbmNlLmdldFRvQmVUaWxlZChub2Rlc1tpXSk7XG4gICAgfVxuXG4gICAgLy8gUmVwb3B1bGF0ZSBtZW1iZXJzXG4gICAgaW5zdGFuY2UucmVwb3B1bGF0ZVplcm9EZWdyZWVNZW1iZXJzKGluc3RhbmNlLnRpbGVkWmVyb0RlZ3JlZU5vZGVzKTtcblxuICAgIGluc3RhbmNlLnJlcG9wdWxhdGVDb21wb3VuZHMoaW5zdGFuY2UudGlsZWRNZW1iZXJQYWNrKTtcblxuICAgIGluc3RhbmNlLm9wdGlvbnMuY3kubm9kZXMoKS51cGRhdGVDb21wb3VuZEJvdW5kcygpO1xuICB9O1xufTsiLCJ2YXIgUG9pbnREID0gcmVxdWlyZSgnLi9Qb2ludEQnKTtcblxuZnVuY3Rpb24gVHJhbnNmb3JtKHgsIHkpIHtcbiAgdGhpcy5sd29ybGRPcmdYID0gMC4wO1xuICB0aGlzLmx3b3JsZE9yZ1kgPSAwLjA7XG4gIHRoaXMubGRldmljZU9yZ1ggPSAwLjA7XG4gIHRoaXMubGRldmljZU9yZ1kgPSAwLjA7XG4gIHRoaXMubHdvcmxkRXh0WCA9IDEuMDtcbiAgdGhpcy5sd29ybGRFeHRZID0gMS4wO1xuICB0aGlzLmxkZXZpY2VFeHRYID0gMS4wO1xuICB0aGlzLmxkZXZpY2VFeHRZID0gMS4wO1xufVxuXG5UcmFuc2Zvcm0ucHJvdG90eXBlLmdldFdvcmxkT3JnWCA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLmx3b3JsZE9yZ1g7XG59XG5cblRyYW5zZm9ybS5wcm90b3R5cGUuc2V0V29ybGRPcmdYID0gZnVuY3Rpb24gKHdveClcbntcbiAgdGhpcy5sd29ybGRPcmdYID0gd294O1xufVxuXG5UcmFuc2Zvcm0ucHJvdG90eXBlLmdldFdvcmxkT3JnWSA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLmx3b3JsZE9yZ1k7XG59XG5cblRyYW5zZm9ybS5wcm90b3R5cGUuc2V0V29ybGRPcmdZID0gZnVuY3Rpb24gKHdveSlcbntcbiAgdGhpcy5sd29ybGRPcmdZID0gd295O1xufVxuXG5UcmFuc2Zvcm0ucHJvdG90eXBlLmdldFdvcmxkRXh0WCA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLmx3b3JsZEV4dFg7XG59XG5cblRyYW5zZm9ybS5wcm90b3R5cGUuc2V0V29ybGRFeHRYID0gZnVuY3Rpb24gKHdleClcbntcbiAgdGhpcy5sd29ybGRFeHRYID0gd2V4O1xufVxuXG5UcmFuc2Zvcm0ucHJvdG90eXBlLmdldFdvcmxkRXh0WSA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLmx3b3JsZEV4dFk7XG59XG5cblRyYW5zZm9ybS5wcm90b3R5cGUuc2V0V29ybGRFeHRZID0gZnVuY3Rpb24gKHdleSlcbntcbiAgdGhpcy5sd29ybGRFeHRZID0gd2V5O1xufVxuXG4vKiBEZXZpY2UgcmVsYXRlZCAqL1xuXG5UcmFuc2Zvcm0ucHJvdG90eXBlLmdldERldmljZU9yZ1ggPSBmdW5jdGlvbiAoKVxue1xuICByZXR1cm4gdGhpcy5sZGV2aWNlT3JnWDtcbn1cblxuVHJhbnNmb3JtLnByb3RvdHlwZS5zZXREZXZpY2VPcmdYID0gZnVuY3Rpb24gKGRveClcbntcbiAgdGhpcy5sZGV2aWNlT3JnWCA9IGRveDtcbn1cblxuVHJhbnNmb3JtLnByb3RvdHlwZS5nZXREZXZpY2VPcmdZID0gZnVuY3Rpb24gKClcbntcbiAgcmV0dXJuIHRoaXMubGRldmljZU9yZ1k7XG59XG5cblRyYW5zZm9ybS5wcm90b3R5cGUuc2V0RGV2aWNlT3JnWSA9IGZ1bmN0aW9uIChkb3kpXG57XG4gIHRoaXMubGRldmljZU9yZ1kgPSBkb3k7XG59XG5cblRyYW5zZm9ybS5wcm90b3R5cGUuZ2V0RGV2aWNlRXh0WCA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLmxkZXZpY2VFeHRYO1xufVxuXG5UcmFuc2Zvcm0ucHJvdG90eXBlLnNldERldmljZUV4dFggPSBmdW5jdGlvbiAoZGV4KVxue1xuICB0aGlzLmxkZXZpY2VFeHRYID0gZGV4O1xufVxuXG5UcmFuc2Zvcm0ucHJvdG90eXBlLmdldERldmljZUV4dFkgPSBmdW5jdGlvbiAoKVxue1xuICByZXR1cm4gdGhpcy5sZGV2aWNlRXh0WTtcbn1cblxuVHJhbnNmb3JtLnByb3RvdHlwZS5zZXREZXZpY2VFeHRZID0gZnVuY3Rpb24gKGRleSlcbntcbiAgdGhpcy5sZGV2aWNlRXh0WSA9IGRleTtcbn1cblxuVHJhbnNmb3JtLnByb3RvdHlwZS50cmFuc2Zvcm1YID0gZnVuY3Rpb24gKHgpXG57XG4gIHZhciB4RGV2aWNlID0gMC4wO1xuICB2YXIgd29ybGRFeHRYID0gdGhpcy5sd29ybGRFeHRYO1xuICBpZiAod29ybGRFeHRYICE9IDAuMClcbiAge1xuICAgIHhEZXZpY2UgPSB0aGlzLmxkZXZpY2VPcmdYICtcbiAgICAgICAgICAgICgoeCAtIHRoaXMubHdvcmxkT3JnWCkgKiB0aGlzLmxkZXZpY2VFeHRYIC8gd29ybGRFeHRYKTtcbiAgfVxuXG4gIHJldHVybiB4RGV2aWNlO1xufVxuXG5UcmFuc2Zvcm0ucHJvdG90eXBlLnRyYW5zZm9ybVkgPSBmdW5jdGlvbiAoeSlcbntcbiAgdmFyIHlEZXZpY2UgPSAwLjA7XG4gIHZhciB3b3JsZEV4dFkgPSB0aGlzLmx3b3JsZEV4dFk7XG4gIGlmICh3b3JsZEV4dFkgIT0gMC4wKVxuICB7XG4gICAgeURldmljZSA9IHRoaXMubGRldmljZU9yZ1kgK1xuICAgICAgICAgICAgKCh5IC0gdGhpcy5sd29ybGRPcmdZKSAqIHRoaXMubGRldmljZUV4dFkgLyB3b3JsZEV4dFkpO1xuICB9XG5cblxuICByZXR1cm4geURldmljZTtcbn1cblxuVHJhbnNmb3JtLnByb3RvdHlwZS5pbnZlcnNlVHJhbnNmb3JtWCA9IGZ1bmN0aW9uICh4KVxue1xuICB2YXIgeFdvcmxkID0gMC4wO1xuICB2YXIgZGV2aWNlRXh0WCA9IHRoaXMubGRldmljZUV4dFg7XG4gIGlmIChkZXZpY2VFeHRYICE9IDAuMClcbiAge1xuICAgIHhXb3JsZCA9IHRoaXMubHdvcmxkT3JnWCArXG4gICAgICAgICAgICAoKHggLSB0aGlzLmxkZXZpY2VPcmdYKSAqIHRoaXMubHdvcmxkRXh0WCAvIGRldmljZUV4dFgpO1xuICB9XG5cblxuICByZXR1cm4geFdvcmxkO1xufVxuXG5UcmFuc2Zvcm0ucHJvdG90eXBlLmludmVyc2VUcmFuc2Zvcm1ZID0gZnVuY3Rpb24gKHkpXG57XG4gIHZhciB5V29ybGQgPSAwLjA7XG4gIHZhciBkZXZpY2VFeHRZID0gdGhpcy5sZGV2aWNlRXh0WTtcbiAgaWYgKGRldmljZUV4dFkgIT0gMC4wKVxuICB7XG4gICAgeVdvcmxkID0gdGhpcy5sd29ybGRPcmdZICtcbiAgICAgICAgICAgICgoeSAtIHRoaXMubGRldmljZU9yZ1kpICogdGhpcy5sd29ybGRFeHRZIC8gZGV2aWNlRXh0WSk7XG4gIH1cbiAgcmV0dXJuIHlXb3JsZDtcbn1cblxuVHJhbnNmb3JtLnByb3RvdHlwZS5pbnZlcnNlVHJhbnNmb3JtUG9pbnQgPSBmdW5jdGlvbiAoaW5Qb2ludClcbntcbiAgdmFyIG91dFBvaW50ID1cbiAgICAgICAgICBuZXcgUG9pbnREKHRoaXMuaW52ZXJzZVRyYW5zZm9ybVgoaW5Qb2ludC54KSxcbiAgICAgICAgICAgICAgICAgIHRoaXMuaW52ZXJzZVRyYW5zZm9ybVkoaW5Qb2ludC55KSk7XG4gIHJldHVybiBvdXRQb2ludDtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBUcmFuc2Zvcm07XG4iLCJmdW5jdGlvbiBVbmlxdWVJREdlbmVyZXRvcigpIHtcbn1cblxuVW5pcXVlSURHZW5lcmV0b3IubGFzdElEID0gMDtcblxuVW5pcXVlSURHZW5lcmV0b3IuY3JlYXRlSUQgPSBmdW5jdGlvbiAob2JqKSB7XG4gIGlmIChVbmlxdWVJREdlbmVyZXRvci5pc1ByaW1pdGl2ZShvYmopKSB7XG4gICAgcmV0dXJuIG9iajtcbiAgfVxuICBpZiAob2JqLnVuaXF1ZUlEICE9IG51bGwpIHtcbiAgICByZXR1cm4gb2JqLnVuaXF1ZUlEO1xuICB9XG4gIG9iai51bmlxdWVJRCA9IFVuaXF1ZUlER2VuZXJldG9yLmdldFN0cmluZygpO1xuICBVbmlxdWVJREdlbmVyZXRvci5sYXN0SUQrKztcbiAgcmV0dXJuIG9iai51bmlxdWVJRDtcbn1cblxuVW5pcXVlSURHZW5lcmV0b3IuZ2V0U3RyaW5nID0gZnVuY3Rpb24gKGlkKSB7XG4gIGlmIChpZCA9PSBudWxsKVxuICAgIGlkID0gVW5pcXVlSURHZW5lcmV0b3IubGFzdElEO1xuICByZXR1cm4gXCJPYmplY3QjXCIgKyBpZCArIFwiXCI7XG59XG5cblVuaXF1ZUlER2VuZXJldG9yLmlzUHJpbWl0aXZlID0gZnVuY3Rpb24gKGFyZykge1xuICB2YXIgdHlwZSA9IHR5cGVvZiBhcmc7XG4gIHJldHVybiBhcmcgPT0gbnVsbCB8fCAodHlwZSAhPSBcIm9iamVjdFwiICYmIHR5cGUgIT0gXCJmdW5jdGlvblwiKTtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBVbmlxdWVJREdlbmVyZXRvcjtcbiIsIid1c2Ugc3RyaWN0JztcblxudmFyIERpbWVuc2lvbkQgPSByZXF1aXJlKCcuL0RpbWVuc2lvbkQnKTtcbnZhciBIYXNoTWFwID0gcmVxdWlyZSgnLi9IYXNoTWFwJyk7XG52YXIgSGFzaFNldCA9IHJlcXVpcmUoJy4vSGFzaFNldCcpO1xudmFyIElHZW9tZXRyeSA9IHJlcXVpcmUoJy4vSUdlb21ldHJ5Jyk7XG52YXIgSU1hdGggPSByZXF1aXJlKCcuL0lNYXRoJyk7XG52YXIgSW50ZWdlciA9IHJlcXVpcmUoJy4vSW50ZWdlcicpO1xudmFyIFBvaW50ID0gcmVxdWlyZSgnLi9Qb2ludCcpO1xudmFyIFBvaW50RCA9IHJlcXVpcmUoJy4vUG9pbnREJyk7XG52YXIgUmFuZG9tU2VlZCA9IHJlcXVpcmUoJy4vUmFuZG9tU2VlZCcpO1xudmFyIFJlY3RhbmdsZUQgPSByZXF1aXJlKCcuL1JlY3RhbmdsZUQnKTtcbnZhciBUcmFuc2Zvcm0gPSByZXF1aXJlKCcuL1RyYW5zZm9ybScpO1xudmFyIFVuaXF1ZUlER2VuZXJldG9yID0gcmVxdWlyZSgnLi9VbmlxdWVJREdlbmVyZXRvcicpO1xudmFyIExHcmFwaE9iamVjdCA9IHJlcXVpcmUoJy4vTEdyYXBoT2JqZWN0Jyk7XG52YXIgTEdyYXBoID0gcmVxdWlyZSgnLi9MR3JhcGgnKTtcbnZhciBMRWRnZSA9IHJlcXVpcmUoJy4vTEVkZ2UnKTtcbnZhciBMR3JhcGhNYW5hZ2VyID0gcmVxdWlyZSgnLi9MR3JhcGhNYW5hZ2VyJyk7XG52YXIgTE5vZGUgPSByZXF1aXJlKCcuL0xOb2RlJyk7XG52YXIgTGF5b3V0ID0gcmVxdWlyZSgnLi9MYXlvdXQnKTtcbnZhciBMYXlvdXRDb25zdGFudHMgPSByZXF1aXJlKCcuL0xheW91dENvbnN0YW50cycpO1xudmFyIEZETGF5b3V0ID0gcmVxdWlyZSgnLi9GRExheW91dCcpO1xudmFyIEZETGF5b3V0Q29uc3RhbnRzID0gcmVxdWlyZSgnLi9GRExheW91dENvbnN0YW50cycpO1xudmFyIEZETGF5b3V0RWRnZSA9IHJlcXVpcmUoJy4vRkRMYXlvdXRFZGdlJyk7XG52YXIgRkRMYXlvdXROb2RlID0gcmVxdWlyZSgnLi9GRExheW91dE5vZGUnKTtcbnZhciBDb1NFQ29uc3RhbnRzID0gcmVxdWlyZSgnLi9Db1NFQ29uc3RhbnRzJyk7XG52YXIgQ29TRUVkZ2UgPSByZXF1aXJlKCcuL0NvU0VFZGdlJyk7XG52YXIgQ29TRUdyYXBoID0gcmVxdWlyZSgnLi9Db1NFR3JhcGgnKTtcbnZhciBDb1NFR3JhcGhNYW5hZ2VyID0gcmVxdWlyZSgnLi9Db1NFR3JhcGhNYW5hZ2VyJyk7XG52YXIgQ29TRUxheW91dCA9IHJlcXVpcmUoJy4vQ29TRUxheW91dCcpO1xudmFyIENvU0VOb2RlID0gcmVxdWlyZSgnLi9Db1NFTm9kZScpO1xudmFyIFRpbGluZ0V4dGVuc2lvbiA9IHJlcXVpcmUoJy4vVGlsaW5nRXh0ZW5zaW9uJyk7XG5cbnZhciBkZWZhdWx0cyA9IHtcbiAgLy8gQ2FsbGVkIG9uIGBsYXlvdXRyZWFkeWBcbiAgcmVhZHk6IGZ1bmN0aW9uICgpIHtcbiAgfSxcbiAgLy8gQ2FsbGVkIG9uIGBsYXlvdXRzdG9wYFxuICBzdG9wOiBmdW5jdGlvbiAoKSB7XG4gIH0sXG4gIC8vIG51bWJlciBvZiB0aWNrcyBwZXIgZnJhbWU7IGhpZ2hlciBpcyBmYXN0ZXIgYnV0IG1vcmUgamVya3lcbiAgcmVmcmVzaDogMzAsXG4gIC8vIFdoZXRoZXIgdG8gZml0IHRoZSBuZXR3b3JrIHZpZXcgYWZ0ZXIgd2hlbiBkb25lXG4gIGZpdDogdHJ1ZSxcbiAgLy8gUGFkZGluZyBvbiBmaXRcbiAgcGFkZGluZzogMTAsXG4gIC8vIFBhZGRpbmcgZm9yIGNvbXBvdW5kc1xuICBwYWRkaW5nQ29tcG91bmQ6IDE1LFxuICAvLyBXaGV0aGVyIHRvIGVuYWJsZSBpbmNyZW1lbnRhbCBtb2RlXG4gIHJhbmRvbWl6ZTogdHJ1ZSxcbiAgLy8gTm9kZSByZXB1bHNpb24gKG5vbiBvdmVybGFwcGluZykgbXVsdGlwbGllclxuICBub2RlUmVwdWxzaW9uOiA0NTAwLFxuICAvLyBJZGVhbCBlZGdlIChub24gbmVzdGVkKSBsZW5ndGhcbiAgaWRlYWxFZGdlTGVuZ3RoOiA1MCxcbiAgLy8gRGl2aXNvciB0byBjb21wdXRlIGVkZ2UgZm9yY2VzXG4gIGVkZ2VFbGFzdGljaXR5OiAwLjQ1LFxuICAvLyBOZXN0aW5nIGZhY3RvciAobXVsdGlwbGllcikgdG8gY29tcHV0ZSBpZGVhbCBlZGdlIGxlbmd0aCBmb3IgbmVzdGVkIGVkZ2VzXG4gIG5lc3RpbmdGYWN0b3I6IDAuMSxcbiAgLy8gR3Jhdml0eSBmb3JjZSAoY29uc3RhbnQpXG4gIGdyYXZpdHk6IDAuMjUsXG4gIC8vIE1heGltdW0gbnVtYmVyIG9mIGl0ZXJhdGlvbnMgdG8gcGVyZm9ybVxuICBudW1JdGVyOiAyNTAwLFxuICAvLyBGb3IgZW5hYmxpbmcgdGlsaW5nXG4gIHRpbGU6IHRydWUsXG4gIC8vIFR5cGUgb2YgbGF5b3V0IGFuaW1hdGlvbi4gVGhlIG9wdGlvbiBzZXQgaXMgeydkdXJpbmcnLCAnZW5kJywgZmFsc2V9XG4gIGFuaW1hdGU6ICdlbmQnLFxuICAvLyBEdXJhdGlvbiBmb3IgYW5pbWF0ZTplbmRcbiAgYW5pbWF0aW9uRHVyYXRpb246IDUwMCxcbiAgLy8gUmVwcmVzZW50cyB0aGUgYW1vdW50IG9mIHRoZSB2ZXJ0aWNhbCBzcGFjZSB0byBwdXQgYmV0d2VlbiB0aGUgemVybyBkZWdyZWUgbWVtYmVycyBkdXJpbmcgdGhlIHRpbGluZyBvcGVyYXRpb24oY2FuIGFsc28gYmUgYSBmdW5jdGlvbilcbiAgdGlsaW5nUGFkZGluZ1ZlcnRpY2FsOiAxMCxcbiAgLy8gUmVwcmVzZW50cyB0aGUgYW1vdW50IG9mIHRoZSBob3Jpem9udGFsIHNwYWNlIHRvIHB1dCBiZXR3ZWVuIHRoZSB6ZXJvIGRlZ3JlZSBtZW1iZXJzIGR1cmluZyB0aGUgdGlsaW5nIG9wZXJhdGlvbihjYW4gYWxzbyBiZSBhIGZ1bmN0aW9uKVxuICB0aWxpbmdQYWRkaW5nSG9yaXpvbnRhbDogMTAsXG4gIC8vIEdyYXZpdHkgcmFuZ2UgKGNvbnN0YW50KSBmb3IgY29tcG91bmRzXG4gIGdyYXZpdHlSYW5nZUNvbXBvdW5kOiAxLjUsXG4gIC8vIEdyYXZpdHkgZm9yY2UgKGNvbnN0YW50KSBmb3IgY29tcG91bmRzXG4gIGdyYXZpdHlDb21wb3VuZDogMS4wLFxuICAvLyBHcmF2aXR5IHJhbmdlIChjb25zdGFudClcbiAgZ3Jhdml0eVJhbmdlOiAzLjhcbn07XG5cbmZ1bmN0aW9uIGV4dGVuZChkZWZhdWx0cywgb3B0aW9ucykge1xuICB2YXIgb2JqID0ge307XG5cbiAgZm9yICh2YXIgaSBpbiBkZWZhdWx0cykge1xuICAgIG9ialtpXSA9IGRlZmF1bHRzW2ldO1xuICB9XG5cbiAgZm9yICh2YXIgaSBpbiBvcHRpb25zKSB7XG4gICAgb2JqW2ldID0gb3B0aW9uc1tpXTtcbiAgfVxuXG4gIHJldHVybiBvYmo7XG59O1xuXG5mdW5jdGlvbiBfQ29TRUxheW91dChfb3B0aW9ucykge1xuICBUaWxpbmdFeHRlbnNpb24odGhpcyk7IC8vIEV4dGVuZCB0aGlzIGluc3RhbmNlIHdpdGggdGlsaW5nIGZ1bmN0aW9uc1xuICB0aGlzLm9wdGlvbnMgPSBleHRlbmQoZGVmYXVsdHMsIF9vcHRpb25zKTtcbiAgZ2V0VXNlck9wdGlvbnModGhpcy5vcHRpb25zKTtcbn1cblxudmFyIGdldFVzZXJPcHRpb25zID0gZnVuY3Rpb24gKG9wdGlvbnMpIHtcbiAgaWYgKG9wdGlvbnMubm9kZVJlcHVsc2lvbiAhPSBudWxsKVxuICAgIENvU0VDb25zdGFudHMuREVGQVVMVF9SRVBVTFNJT05fU1RSRU5HVEggPSBGRExheW91dENvbnN0YW50cy5ERUZBVUxUX1JFUFVMU0lPTl9TVFJFTkdUSCA9IG9wdGlvbnMubm9kZVJlcHVsc2lvbjtcbiAgaWYgKG9wdGlvbnMuaWRlYWxFZGdlTGVuZ3RoICE9IG51bGwpXG4gICAgQ29TRUNvbnN0YW50cy5ERUZBVUxUX0VER0VfTEVOR1RIID0gRkRMYXlvdXRDb25zdGFudHMuREVGQVVMVF9FREdFX0xFTkdUSCA9IG9wdGlvbnMuaWRlYWxFZGdlTGVuZ3RoO1xuICBpZiAob3B0aW9ucy5lZGdlRWxhc3RpY2l0eSAhPSBudWxsKVxuICAgIENvU0VDb25zdGFudHMuREVGQVVMVF9TUFJJTkdfU1RSRU5HVEggPSBGRExheW91dENvbnN0YW50cy5ERUZBVUxUX1NQUklOR19TVFJFTkdUSCA9IG9wdGlvbnMuZWRnZUVsYXN0aWNpdHk7XG4gIGlmIChvcHRpb25zLm5lc3RpbmdGYWN0b3IgIT0gbnVsbClcbiAgICBDb1NFQ29uc3RhbnRzLlBFUl9MRVZFTF9JREVBTF9FREdFX0xFTkdUSF9GQUNUT1IgPSBGRExheW91dENvbnN0YW50cy5QRVJfTEVWRUxfSURFQUxfRURHRV9MRU5HVEhfRkFDVE9SID0gb3B0aW9ucy5uZXN0aW5nRmFjdG9yO1xuICBpZiAob3B0aW9ucy5ncmF2aXR5ICE9IG51bGwpXG4gICAgQ29TRUNvbnN0YW50cy5ERUZBVUxUX0dSQVZJVFlfU1RSRU5HVEggPSBGRExheW91dENvbnN0YW50cy5ERUZBVUxUX0dSQVZJVFlfU1RSRU5HVEggPSBvcHRpb25zLmdyYXZpdHk7XG4gIGlmIChvcHRpb25zLm51bUl0ZXIgIT0gbnVsbClcbiAgICBDb1NFQ29uc3RhbnRzLk1BWF9JVEVSQVRJT05TID0gRkRMYXlvdXRDb25zdGFudHMuTUFYX0lURVJBVElPTlMgPSBvcHRpb25zLm51bUl0ZXI7XG4gIGlmIChvcHRpb25zLnBhZGRpbmdDb21wb3VuZCAhPSBudWxsKVxuICAgIENvU0VDb25zdGFudHMuREVGQVVMVF9HUkFQSF9NQVJHSU4gPSBGRExheW91dENvbnN0YW50cy5ERUZBVUxUX0dSQVBIX01BUkdJTiA9IExheW91dENvbnN0YW50cy5ERUZBVUxUX0dSQVBIX01BUkdJTiA9IG9wdGlvbnMucGFkZGluZ0NvbXBvdW5kO1xuICBpZiAob3B0aW9ucy5ncmF2aXR5UmFuZ2UgIT0gbnVsbClcbiAgICBDb1NFQ29uc3RhbnRzLkRFRkFVTFRfR1JBVklUWV9SQU5HRV9GQUNUT1IgPSBGRExheW91dENvbnN0YW50cy5ERUZBVUxUX0dSQVZJVFlfUkFOR0VfRkFDVE9SID0gb3B0aW9ucy5ncmF2aXR5UmFuZ2U7XG4gIGlmKG9wdGlvbnMuZ3Jhdml0eUNvbXBvdW5kICE9IG51bGwpXG4gICAgQ29TRUNvbnN0YW50cy5ERUZBVUxUX0NPTVBPVU5EX0dSQVZJVFlfU1RSRU5HVEggPSBGRExheW91dENvbnN0YW50cy5ERUZBVUxUX0NPTVBPVU5EX0dSQVZJVFlfU1RSRU5HVEggPSBvcHRpb25zLmdyYXZpdHlDb21wb3VuZDtcbiAgaWYob3B0aW9ucy5ncmF2aXR5UmFuZ2VDb21wb3VuZCAhPSBudWxsKVxuICAgIENvU0VDb25zdGFudHMuREVGQVVMVF9DT01QT1VORF9HUkFWSVRZX1JBTkdFX0ZBQ1RPUiA9IEZETGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfQ09NUE9VTkRfR1JBVklUWV9SQU5HRV9GQUNUT1IgPSBvcHRpb25zLmdyYXZpdHlSYW5nZUNvbXBvdW5kO1xuXG4gIENvU0VDb25zdGFudHMuREVGQVVMVF9JTkNSRU1FTlRBTCA9IEZETGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfSU5DUkVNRU5UQUwgPSBMYXlvdXRDb25zdGFudHMuREVGQVVMVF9JTkNSRU1FTlRBTCA9XG4gICAgICAgICAgIShvcHRpb25zLnJhbmRvbWl6ZSk7XG4gIENvU0VDb25zdGFudHMuQU5JTUFURSA9IEZETGF5b3V0Q29uc3RhbnRzLkFOSU1BVEUgPSBvcHRpb25zLmFuaW1hdGU7XG59O1xuXG5fQ29TRUxheW91dC5wcm90b3R5cGUucnVuID0gZnVuY3Rpb24gKCkge1xuICB2YXIgcmVhZHk7XG4gIHZhciBmcmFtZUlkO1xuICB2YXIgb3B0aW9ucyA9IHRoaXMub3B0aW9ucztcbiAgdmFyIGlkVG9MTm9kZSA9IHRoaXMuaWRUb0xOb2RlID0ge307XG4gIHZhciBsYXlvdXQgPSB0aGlzLmxheW91dCA9IG5ldyBDb1NFTGF5b3V0KCk7XG4gIHZhciBzZWxmID0gdGhpcztcbiAgXG4gIHRoaXMuY3kgPSB0aGlzLm9wdGlvbnMuY3k7XG5cbiAgdGhpcy5jeS50cmlnZ2VyKCdsYXlvdXRzdGFydCcpO1xuXG4gIHZhciBnbSA9IGxheW91dC5uZXdHcmFwaE1hbmFnZXIoKTtcbiAgdGhpcy5nbSA9IGdtO1xuXG4gIHZhciBub2RlcyA9IHRoaXMub3B0aW9ucy5lbGVzLm5vZGVzKCk7XG4gIHZhciBlZGdlcyA9IHRoaXMub3B0aW9ucy5lbGVzLmVkZ2VzKCk7XG5cbiAgdGhpcy5yb290ID0gZ20uYWRkUm9vdCgpO1xuXG4gIGlmICghdGhpcy5vcHRpb25zLnRpbGUpIHtcbiAgICB0aGlzLnByb2Nlc3NDaGlsZHJlbkxpc3QodGhpcy5yb290LCB0aGlzLmdldFRvcE1vc3ROb2Rlcyhub2RlcyksIGxheW91dCk7XG4gIH1cbiAgZWxzZSB7XG4gICAgdGhpcy5wcmVMYXlvdXQoKTtcbiAgfVxuXG5cbiAgZm9yICh2YXIgaSA9IDA7IGkgPCBlZGdlcy5sZW5ndGg7IGkrKykge1xuICAgIHZhciBlZGdlID0gZWRnZXNbaV07XG4gICAgdmFyIHNvdXJjZU5vZGUgPSB0aGlzLmlkVG9MTm9kZVtlZGdlLmRhdGEoXCJzb3VyY2VcIildO1xuICAgIHZhciB0YXJnZXROb2RlID0gdGhpcy5pZFRvTE5vZGVbZWRnZS5kYXRhKFwidGFyZ2V0XCIpXTtcbiAgICB2YXIgZTEgPSBnbS5hZGQobGF5b3V0Lm5ld0VkZ2UoKSwgc291cmNlTm9kZSwgdGFyZ2V0Tm9kZSk7XG4gICAgZTEuaWQgPSBlZGdlLmlkKCk7XG4gIH1cbiAgXG4gICB2YXIgZ2V0UG9zaXRpb25zID0gZnVuY3Rpb24oZWxlLCBpKXtcbiAgICBpZih0eXBlb2YgZWxlID09PSBcIm51bWJlclwiKSB7XG4gICAgICBlbGUgPSBpO1xuICAgIH1cbiAgICB2YXIgdGhlSWQgPSBlbGUuZGF0YSgnaWQnKTtcbiAgICB2YXIgbE5vZGUgPSBzZWxmLmlkVG9MTm9kZVt0aGVJZF07XG5cbiAgICByZXR1cm4ge1xuICAgICAgeDogbE5vZGUuZ2V0UmVjdCgpLmdldENlbnRlclgoKSxcbiAgICAgIHk6IGxOb2RlLmdldFJlY3QoKS5nZXRDZW50ZXJZKClcbiAgICB9O1xuICB9O1xuICBcbiAgLypcbiAgICogUmVwb3NpdGlvbiBub2RlcyBpbiBpdGVyYXRpb25zIGFuaW1hdGVkbHlcbiAgICovXG4gIHZhciBpdGVyYXRlQW5pbWF0ZWQgPSBmdW5jdGlvbiAoKSB7XG4gICAgLy8gVGhpZ3MgdG8gcGVyZm9ybSBhZnRlciBub2RlcyBhcmUgcmVwb3NpdGlvbmVkIG9uIHNjcmVlblxuICAgIHZhciBhZnRlclJlcG9zaXRpb24gPSBmdW5jdGlvbigpIHtcbiAgICAgIGlmIChvcHRpb25zLmZpdCkge1xuICAgICAgICBvcHRpb25zLmN5LmZpdChvcHRpb25zLmVsZXMubm9kZXMoKSwgb3B0aW9ucy5wYWRkaW5nKTtcbiAgICAgIH1cblxuICAgICAgaWYgKCFyZWFkeSkge1xuICAgICAgICByZWFkeSA9IHRydWU7XG4gICAgICAgIHNlbGYuY3kub25lKCdsYXlvdXRyZWFkeScsIG9wdGlvbnMucmVhZHkpO1xuICAgICAgICBzZWxmLmN5LnRyaWdnZXIoe3R5cGU6ICdsYXlvdXRyZWFkeScsIGxheW91dDogc2VsZn0pO1xuICAgICAgfVxuICAgIH07XG4gICAgXG4gICAgdmFyIHRpY2tzUGVyRnJhbWUgPSBzZWxmLm9wdGlvbnMucmVmcmVzaDtcbiAgICB2YXIgaXNEb25lO1xuXG4gICAgZm9yKCB2YXIgaSA9IDA7IGkgPCB0aWNrc1BlckZyYW1lICYmICFpc0RvbmU7IGkrKyApe1xuICAgICAgaXNEb25lID0gc2VsZi5sYXlvdXQudGljaygpO1xuICAgIH1cbiAgICBcbiAgICAvLyBJZiBsYXlvdXQgaXMgZG9uZVxuICAgIGlmIChpc0RvbmUpIHtcbiAgICAgIGlmIChzZWxmLm9wdGlvbnMudGlsZSkge1xuICAgICAgICBzZWxmLnBvc3RMYXlvdXQoKTtcbiAgICAgIH1cbiAgICAgIHNlbGYub3B0aW9ucy5lbGVzLm5vZGVzKCkucG9zaXRpb25zKGdldFBvc2l0aW9ucyk7XG4gICAgICBcbiAgICAgIGFmdGVyUmVwb3NpdGlvbigpO1xuICAgICAgXG4gICAgICAvLyB0cmlnZ2VyIGxheW91dHN0b3Agd2hlbiB0aGUgbGF5b3V0IHN0b3BzIChlLmcuIGZpbmlzaGVzKVxuICAgICAgc2VsZi5jeS5vbmUoJ2xheW91dHN0b3AnLCBzZWxmLm9wdGlvbnMuc3RvcCk7XG4gICAgICBzZWxmLmN5LnRyaWdnZXIoJ2xheW91dHN0b3AnKTtcblxuICAgICAgaWYgKGZyYW1lSWQpIHtcbiAgICAgICAgY2FuY2VsQW5pbWF0aW9uRnJhbWUoZnJhbWVJZCk7XG4gICAgICB9XG4gICAgICBcbiAgICAgIHNlbGYub3B0aW9ucy5lbGVzLm5vZGVzKCkucmVtb3ZlU2NyYXRjaCgnY29zZUJpbGtlbnQnKTtcbiAgICAgIHJlYWR5ID0gZmFsc2U7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIFxuICAgIHZhciBhbmltYXRpb25EYXRhID0gc2VsZi5sYXlvdXQuZ2V0UG9zaXRpb25zRGF0YSgpOyAvLyBHZXQgcG9zaXRpb25zIG9mIGxheW91dCBub2RlcyBub3RlIHRoYXQgYWxsIG5vZGVzIG1heSBub3QgYmUgbGF5b3V0IG5vZGVzIGJlY2F1c2Ugb2YgdGlsaW5nXG4gICAgLy8gUG9zaXRpb24gbm9kZXMsIGZvciB0aGUgbm9kZXMgd2hvIGFyZSBub3QgcGFzc2VkIHRvIGxheW91dCBiZWNhdXNlIG9mIHRpbGluZyByZXR1cm4gdGhlIHBvc2l0aW9uIG9mIHRoZWlyIGR1bW15IGNvbXBvdW5kXG4gICAgb3B0aW9ucy5lbGVzLm5vZGVzKCkucG9zaXRpb25zKGZ1bmN0aW9uIChlbGUsIGkpIHtcbiAgICAgIGlmICh0eXBlb2YgZWxlID09PSBcIm51bWJlclwiKSB7XG4gICAgICAgIGVsZSA9IGk7XG4gICAgICB9XG4gICAgICBpZiAoZWxlLnNjcmF0Y2goJ2Nvc2VCaWxrZW50JykgJiYgZWxlLnNjcmF0Y2goJ2Nvc2VCaWxrZW50JykuZHVtbXlfcGFyZW50X2lkKSB7XG4gICAgICAgIHZhciBkdW1teVBhcmVudCA9IGVsZS5zY3JhdGNoKCdjb3NlQmlsa2VudCcpLmR1bW15X3BhcmVudF9pZDtcbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICB4OiBkdW1teVBhcmVudC54LFxuICAgICAgICAgIHk6IGR1bW15UGFyZW50LnlcbiAgICAgICAgfTtcbiAgICAgIH1cbiAgICAgIHZhciB0aGVJZCA9IGVsZS5kYXRhKCdpZCcpO1xuICAgICAgdmFyIHBOb2RlID0gYW5pbWF0aW9uRGF0YVt0aGVJZF07XG4gICAgICB2YXIgdGVtcCA9IGVsZTtcbiAgICAgIHdoaWxlIChwTm9kZSA9PSBudWxsKSB7XG4gICAgICAgIHRlbXAgPSB0ZW1wLnBhcmVudCgpWzBdO1xuICAgICAgICBwTm9kZSA9IGFuaW1hdGlvbkRhdGFbdGVtcC5pZCgpXTtcbiAgICAgICAgYW5pbWF0aW9uRGF0YVt0aGVJZF0gPSBwTm9kZTtcbiAgICAgIH1cbiAgICAgIHJldHVybiB7XG4gICAgICAgIHg6IHBOb2RlLngsXG4gICAgICAgIHk6IHBOb2RlLnlcbiAgICAgIH07XG4gICAgfSk7XG5cbiAgICBhZnRlclJlcG9zaXRpb24oKTtcblxuICAgIGZyYW1lSWQgPSByZXF1ZXN0QW5pbWF0aW9uRnJhbWUoaXRlcmF0ZUFuaW1hdGVkKTtcbiAgfTtcbiAgXG4gIC8qXG4gICogTGlzdGVuICdsYXlvdXRzdGFydGVkJyBldmVudCBhbmQgc3RhcnQgYW5pbWF0ZWQgaXRlcmF0aW9uIGlmIGFuaW1hdGUgb3B0aW9uIGlzICdkdXJpbmcnXG4gICovXG4gIGxheW91dC5hZGRMaXN0ZW5lcignbGF5b3V0c3RhcnRlZCcsIGZ1bmN0aW9uICgpIHtcbiAgICBpZiAoc2VsZi5vcHRpb25zLmFuaW1hdGUgPT09ICdkdXJpbmcnKSB7XG4gICAgICBmcmFtZUlkID0gcmVxdWVzdEFuaW1hdGlvbkZyYW1lKGl0ZXJhdGVBbmltYXRlZCk7XG4gICAgfVxuICB9KTtcbiAgXG4gIGxheW91dC5ydW5MYXlvdXQoKTsgLy8gUnVuIGNvc2UgbGF5b3V0XG4gIFxuICAvKlxuICAgKiBJZiBhbmltYXRlIG9wdGlvbiBpcyBub3QgJ2R1cmluZycgKCdlbmQnIG9yIGZhbHNlKSBwZXJmb3JtIHRoZXNlIGhlcmUgKElmIGl0IGlzICdkdXJpbmcnIHNpbWlsYXIgdGhpbmdzIGFyZSBhbHJlYWR5IHBlcmZvcm1lZClcbiAgICovXG4gIGlmKHRoaXMub3B0aW9ucy5hbmltYXRlICE9PSAnZHVyaW5nJyl7XG4gICAgc2V0VGltZW91dChmdW5jdGlvbigpIHtcbiAgICAgIGlmIChzZWxmLm9wdGlvbnMudGlsZSkge1xuICAgICAgICBzZWxmLnBvc3RMYXlvdXQoKTtcbiAgICAgIH1cbiAgICAgIHNlbGYub3B0aW9ucy5lbGVzLm5vZGVzKCkubm90KFwiOnBhcmVudFwiKS5sYXlvdXRQb3NpdGlvbnMoc2VsZiwgc2VsZi5vcHRpb25zLCBnZXRQb3NpdGlvbnMpOyAvLyBVc2UgbGF5b3V0IHBvc2l0aW9ucyB0byByZXBvc2l0aW9uIHRoZSBub2RlcyBpdCBjb25zaWRlcnMgdGhlIG9wdGlvbnMgcGFyYW1ldGVyXG4gICAgICBzZWxmLm9wdGlvbnMuZWxlcy5ub2RlcygpLnJlbW92ZVNjcmF0Y2goJ2Nvc2VCaWxrZW50Jyk7XG4gICAgICByZWFkeSA9IGZhbHNlO1xuICAgIH0sIDApO1xuICAgIFxuICB9XG5cbiAgcmV0dXJuIHRoaXM7IC8vIGNoYWluaW5nXG59O1xuXG4vL0dldCB0aGUgdG9wIG1vc3Qgb25lcyBvZiBhIGxpc3Qgb2Ygbm9kZXNcbl9Db1NFTGF5b3V0LnByb3RvdHlwZS5nZXRUb3BNb3N0Tm9kZXMgPSBmdW5jdGlvbihub2Rlcykge1xuICB2YXIgbm9kZXNNYXAgPSB7fTtcbiAgZm9yICh2YXIgaSA9IDA7IGkgPCBub2Rlcy5sZW5ndGg7IGkrKykge1xuICAgICAgbm9kZXNNYXBbbm9kZXNbaV0uaWQoKV0gPSB0cnVlO1xuICB9XG4gIHZhciByb290cyA9IG5vZGVzLmZpbHRlcihmdW5jdGlvbiAoZWxlLCBpKSB7XG4gICAgICBpZih0eXBlb2YgZWxlID09PSBcIm51bWJlclwiKSB7XG4gICAgICAgIGVsZSA9IGk7XG4gICAgICB9XG4gICAgICB2YXIgcGFyZW50ID0gZWxlLnBhcmVudCgpWzBdO1xuICAgICAgd2hpbGUocGFyZW50ICE9IG51bGwpe1xuICAgICAgICBpZihub2Rlc01hcFtwYXJlbnQuaWQoKV0pe1xuICAgICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgICAgfVxuICAgICAgICBwYXJlbnQgPSBwYXJlbnQucGFyZW50KClbMF07XG4gICAgICB9XG4gICAgICByZXR1cm4gdHJ1ZTtcbiAgfSk7XG5cbiAgcmV0dXJuIHJvb3RzO1xufTtcblxuX0NvU0VMYXlvdXQucHJvdG90eXBlLnByb2Nlc3NDaGlsZHJlbkxpc3QgPSBmdW5jdGlvbiAocGFyZW50LCBjaGlsZHJlbiwgbGF5b3V0KSB7XG4gIHZhciBzaXplID0gY2hpbGRyZW4ubGVuZ3RoO1xuICBmb3IgKHZhciBpID0gMDsgaSA8IHNpemU7IGkrKykge1xuICAgIHZhciB0aGVDaGlsZCA9IGNoaWxkcmVuW2ldO1xuICAgIHRoaXMub3B0aW9ucy5lbGVzLm5vZGVzKCkubGVuZ3RoO1xuICAgIHZhciBjaGlsZHJlbl9vZl9jaGlsZHJlbiA9IHRoZUNoaWxkLmNoaWxkcmVuKCk7XG4gICAgdmFyIHRoZU5vZGU7XG5cbiAgICBpZiAodGhlQ2hpbGQud2lkdGgoKSAhPSBudWxsXG4gICAgICAgICAgICAmJiB0aGVDaGlsZC5oZWlnaHQoKSAhPSBudWxsKSB7XG4gICAgICB0aGVOb2RlID0gcGFyZW50LmFkZChuZXcgQ29TRU5vZGUobGF5b3V0LmdyYXBoTWFuYWdlcixcbiAgICAgICAgICAgICAgbmV3IFBvaW50RCh0aGVDaGlsZC5wb3NpdGlvbigneCcpLCB0aGVDaGlsZC5wb3NpdGlvbigneScpKSxcbiAgICAgICAgICAgICAgbmV3IERpbWVuc2lvbkQocGFyc2VGbG9hdCh0aGVDaGlsZC53aWR0aCgpKSxcbiAgICAgICAgICAgICAgICAgICAgICBwYXJzZUZsb2F0KHRoZUNoaWxkLmhlaWdodCgpKSkpKTtcbiAgICB9XG4gICAgZWxzZSB7XG4gICAgICB0aGVOb2RlID0gcGFyZW50LmFkZChuZXcgQ29TRU5vZGUodGhpcy5ncmFwaE1hbmFnZXIpKTtcbiAgICB9XG4gICAgdGhlTm9kZS5pZCA9IHRoZUNoaWxkLmRhdGEoXCJpZFwiKTtcbiAgICB0aGlzLmlkVG9MTm9kZVt0aGVDaGlsZC5kYXRhKFwiaWRcIildID0gdGhlTm9kZTtcblxuICAgIGlmIChpc05hTih0aGVOb2RlLnJlY3QueCkpIHtcbiAgICAgIHRoZU5vZGUucmVjdC54ID0gMDtcbiAgICB9XG5cbiAgICBpZiAoaXNOYU4odGhlTm9kZS5yZWN0LnkpKSB7XG4gICAgICB0aGVOb2RlLnJlY3QueSA9IDA7XG4gICAgfVxuXG4gICAgaWYgKGNoaWxkcmVuX29mX2NoaWxkcmVuICE9IG51bGwgJiYgY2hpbGRyZW5fb2ZfY2hpbGRyZW4ubGVuZ3RoID4gMCkge1xuICAgICAgdmFyIHRoZU5ld0dyYXBoO1xuICAgICAgdGhlTmV3R3JhcGggPSBsYXlvdXQuZ2V0R3JhcGhNYW5hZ2VyKCkuYWRkKGxheW91dC5uZXdHcmFwaCgpLCB0aGVOb2RlKTtcbiAgICAgIHRoaXMucHJvY2Vzc0NoaWxkcmVuTGlzdCh0aGVOZXdHcmFwaCwgY2hpbGRyZW5fb2ZfY2hpbGRyZW4sIGxheW91dCk7XG4gICAgfVxuICB9XG59O1xuXG4vKipcbiAqIEBicmllZiA6IGNhbGxlZCBvbiBjb250aW51b3VzIGxheW91dHMgdG8gc3RvcCB0aGVtIGJlZm9yZSB0aGV5IGZpbmlzaFxuICovXG5fQ29TRUxheW91dC5wcm90b3R5cGUuc3RvcCA9IGZ1bmN0aW9uICgpIHtcbiAgdGhpcy5zdG9wcGVkID0gdHJ1ZTtcbiAgXG4gIHRoaXMudHJpZ2dlcignbGF5b3V0c3RvcCcpO1xuXG4gIHJldHVybiB0aGlzOyAvLyBjaGFpbmluZ1xufTtcblxubW9kdWxlLmV4cG9ydHMgPSBmdW5jdGlvbiBnZXQoY3l0b3NjYXBlKSB7XG4gIHJldHVybiBfQ29TRUxheW91dDtcbn07XG4iLCIndXNlIHN0cmljdCc7XG5cbi8vIHJlZ2lzdGVycyB0aGUgZXh0ZW5zaW9uIG9uIGEgY3l0b3NjYXBlIGxpYiByZWZcbnZhciBnZXRMYXlvdXQgPSByZXF1aXJlKCcuL0xheW91dCcpO1xuXG52YXIgcmVnaXN0ZXIgPSBmdW5jdGlvbiggY3l0b3NjYXBlICl7XG4gIHZhciBMYXlvdXQgPSBnZXRMYXlvdXQoIGN5dG9zY2FwZSApO1xuXG4gIGN5dG9zY2FwZSgnbGF5b3V0JywgJ2Nvc2UtYmlsa2VudCcsIExheW91dCk7XG59O1xuXG4vLyBhdXRvIHJlZyBmb3IgZ2xvYmFsc1xuaWYoIHR5cGVvZiBjeXRvc2NhcGUgIT09ICd1bmRlZmluZWQnICl7XG4gIHJlZ2lzdGVyKCBjeXRvc2NhcGUgKTtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSByZWdpc3RlcjtcbiJdfQ== diff --git a/site/site/_public/js/cytoscape.min.js b/site/site/_public/js/cytoscape.min.js new file mode 100644 index 0000000..ff0d7c1 --- /dev/null +++ b/site/site/_public/js/cytoscape.min.js @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2016-2024, The Cytoscape Consortium. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the “Software”), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).cytoscape=t()}(this,(function(){"use strict";function e(t){return(e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(t)}function t(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function n(e,t){for(var n=0;ne.length)&&(t=e.length);for(var n=0,r=new Array(t);n=e.length?{done:!0}:{done:!1,value:e[r++]}},e:function(e){throw e},f: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.")}var a,s=!0,l=!1;return{s:function(){n=n.call(e)},n:function(){var e=n.next();return s=e.done,e},e:function(e){l=!0,a=e},f:function(){try{s||null==n.return||n.return()}finally{if(l)throw a}}}}var u="undefined"==typeof window?null:window,c=u?u.navigator:null;u&&u.document;var d=e(""),h=e({}),p=e((function(){})),f="undefined"==typeof HTMLElement?"undefined":e(HTMLElement),g=function(e){return e&&e.instanceString&&y(e.instanceString)?e.instanceString():null},v=function(t){return null!=t&&e(t)==d},y=function(t){return null!=t&&e(t)===p},m=function(e){return!E(e)&&(Array.isArray?Array.isArray(e):null!=e&&e instanceof Array)},b=function(t){return null!=t&&e(t)===h&&!m(t)&&t.constructor===Object},x=function(t){return null!=t&&e(t)===e(1)&&!isNaN(t)},w=function(e){return"undefined"===f?void 0:null!=e&&e instanceof HTMLElement},E=function(e){return k(e)||C(e)},k=function(e){return"collection"===g(e)&&e._private.single},C=function(e){return"collection"===g(e)&&!e._private.single},S=function(e){return"core"===g(e)},P=function(e){return"stylesheet"===g(e)},D=function(e){return null==e||!(""!==e&&!e.match(/^\s+$/))},T=function(t){return function(t){return null!=t&&e(t)===h}(t)&&y(t.then)},_=function(e,t){t||(t=function(){if(1===arguments.length)return arguments[0];if(0===arguments.length)return"undefined";for(var e=[],t=0;tt?1:0},L=null!=Object.assign?Object.assign.bind(Object):function(e){for(var t=arguments,n=1;n255)return;t.push(Math.floor(a))}var o=r[1]||r[2]||r[3],s=r[1]&&r[2]&&r[3];if(o&&!s)return;var l=n[4];if(void 0!==l){if((l=parseFloat(l))<0||l>1)return;t.push(l)}}return t}(e)||function(e){var t,n,r,i,a,o,s,l;function u(e,t,n){return n<0&&(n+=1),n>1&&(n-=1),n<1/6?e+6*(t-e)*n:n<.5?t:n<2/3?e+(t-e)*(2/3-n)*6:e}var c=new RegExp("^hsl[a]?\\(((?:[-+]?(?:(?:\\d+|\\d*\\.\\d+)(?:[Ee][+-]?\\d+)?)))\\s*,\\s*((?:[-+]?(?:(?:\\d+|\\d*\\.\\d+)(?:[Ee][+-]?\\d+)?))[%])\\s*,\\s*((?:[-+]?(?:(?:\\d+|\\d*\\.\\d+)(?:[Ee][+-]?\\d+)?))[%])(?:\\s*,\\s*((?:[-+]?(?:(?:\\d+|\\d*\\.\\d+)(?:[Ee][+-]?\\d+)?))))?\\)$").exec(e);if(c){if((n=parseInt(c[1]))<0?n=(360- -1*n%360)%360:n>360&&(n%=360),n/=360,(r=parseFloat(c[2]))<0||r>100)return;if(r/=100,(i=parseFloat(c[3]))<0||i>100)return;if(i/=100,void 0!==(a=c[4])&&((a=parseFloat(a))<0||a>1))return;if(0===r)o=s=l=Math.round(255*i);else{var d=i<.5?i*(1+r):i+r-i*r,h=2*i-d;o=Math.round(255*u(h,d,n+1/3)),s=Math.round(255*u(h,d,n)),l=Math.round(255*u(h,d,n-1/3))}t=[o,s,l,a]}return t}(e)},R={transparent:[0,0,0,0],aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],grey:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]},V=function(e){for(var t=e.map,n=e.keys,r=n.length,i=0;i=t||n<0||d&&e-u>=a}function v(){var e=H();if(g(e))return y(e);s=setTimeout(v,function(e){var n=t-(e-l);return d?ge(n,a-(e-u)):n}(e))}function y(e){return s=void 0,h&&r?p(e):(r=i=void 0,o)}function m(){var e=H(),n=g(e);if(r=arguments,i=this,l=e,n){if(void 0===s)return f(l);if(d)return clearTimeout(s),s=setTimeout(v,t),p(l)}return void 0===s&&(s=setTimeout(v,t)),o}return t=pe(t)||0,j(n)&&(c=!!n.leading,a=(d="maxWait"in n)?fe(pe(n.maxWait)||0,t):a,h="trailing"in n?!!n.trailing:h),m.cancel=function(){void 0!==s&&clearTimeout(s),u=0,r=l=i=s=void 0},m.flush=function(){return void 0===s?o:y(H())},m},ye=u?u.performance:null,me=ye&&ye.now?function(){return ye.now()}:function(){return Date.now()},be=function(){if(u){if(u.requestAnimationFrame)return function(e){u.requestAnimationFrame(e)};if(u.mozRequestAnimationFrame)return function(e){u.mozRequestAnimationFrame(e)};if(u.webkitRequestAnimationFrame)return function(e){u.webkitRequestAnimationFrame(e)};if(u.msRequestAnimationFrame)return function(e){u.msRequestAnimationFrame(e)}}return function(e){e&&setTimeout((function(){e(me())}),1e3/60)}}(),xe=function(e){return be(e)},we=me,Ee=65599,ke=function(e){for(var t,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:9261,r=n;!(t=e.next()).done;)r=r*Ee+t.value|0;return r},Ce=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:9261;return t*Ee+e|0},Se=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:5381;return(t<<5)+t+e|0},Pe=function(e){return 2097152*e[0]+e[1]},De=function(e,t){return[Ce(e[0],t[0]),Se(e[1],t[1])]},Te=function(e,t){var n={value:0,done:!1},r=0,i=e.length;return ke({next:function(){return r=0&&(e[r]!==t||(e.splice(r,1),!n));r--);},Ge=function(e){e.splice(0,e.length)},Ue=function(e,t,n){return n&&(t=N(n,t)),e[t]},Ze=function(e,t,n,r){n&&(t=N(n,t)),e[t]=r},$e="undefined"!=typeof Map?Map:function(){function e(){t(this,e),this._obj={}}return r(e,[{key:"set",value:function(e,t){return this._obj[e]=t,this}},{key:"delete",value:function(e){return this._obj[e]=void 0,this}},{key:"clear",value:function(){this._obj={}}},{key:"has",value:function(e){return void 0!==this._obj[e]}},{key:"get",value:function(e){return this._obj[e]}}]),e}(),Qe=function(){function e(n){if(t(this,e),this._obj=Object.create(null),this.size=0,null!=n){var r;r=null!=n.instanceString&&n.instanceString()===this.instanceString()?n.toArray():n;for(var i=0;i2&&void 0!==arguments[2])||arguments[2];if(void 0!==e&&void 0!==t&&S(e)){var r=t.group;if(null==r&&(r=t.data&&null!=t.data.source&&null!=t.data.target?"edges":"nodes"),"nodes"===r||"edges"===r){this.length=1,this[0]=this;var i=this._private={cy:e,single:!0,data:t.data||{},position:t.position||{x:0,y:0},autoWidth:void 0,autoHeight:void 0,autoPadding:void 0,compoundBoundsClean:!1,listeners:[],group:r,style:{},rstyle:{},styleCxts:[],styleKeys:{},removed:!0,selected:!!t.selected,selectable:void 0===t.selectable||!!t.selectable,locked:!!t.locked,grabbed:!1,grabbable:void 0===t.grabbable||!!t.grabbable,pannable:void 0===t.pannable?"edges"===r:!!t.pannable,active:!1,classes:new Je,animation:{current:[],queue:[]},rscratch:{},scratch:t.scratch||{},edges:[],children:[],parent:t.parent&&t.parent.isNode()?t.parent:null,traversalCache:{},backgrounding:!1,bbCache:null,bbCacheShift:{x:0,y:0},bodyBounds:null,overlayBounds:null,labelBounds:{all:null,source:null,target:null,main:null},arrowBounds:{source:null,target:null,"mid-source":null,"mid-target":null}};if(null==i.position.x&&(i.position.x=0),null==i.position.y&&(i.position.y=0),t.renderedPosition){var a=t.renderedPosition,o=e.pan(),s=e.zoom();i.position={x:(a.x-o.x)/s,y:(a.y-o.y)/s}}var l=[];m(t.classes)?l=t.classes:v(t.classes)&&(l=t.classes.split(/\s+/));for(var u=0,c=l.length;ut?1:0},u=function(e,t,i,a,o){var s;if(null==i&&(i=0),null==o&&(o=n),i<0)throw new Error("lo must be non-negative");for(null==a&&(a=e.length);in;0<=n?t++:t--)u.push(t);return u}.apply(this).reverse()).length;ag;0<=g?++h:--h)v.push(a(e,r));return v},f=function(e,t,r,i){var a,o,s;for(null==i&&(i=n),a=e[r];r>t&&i(a,o=e[s=r-1>>1])<0;)e[r]=o,r=s;return e[r]=a},g=function(e,t,r){var i,a,o,s,l;for(null==r&&(r=n),a=e.length,l=t,o=e[t],i=2*t+1;i0;){var k=m.pop(),C=g(k),S=k.id();if(d[S]=C,C!==1/0)for(var P=k.neighborhood().intersect(p),D=0;D0)for(n.unshift(t);c[i];){var a=c[i];n.unshift(a.edge),n.unshift(a.node),i=(r=a.node).id()}return o.spawn(n)}}}},ot={kruskal:function(e){e=e||function(e){return 1};for(var t=this.byGroup(),n=t.nodes,r=t.edges,i=n.length,a=new Array(i),o=n,s=function(e){for(var t=0;t0;){if(l=g.pop(),u=l.id(),v.delete(u),w++,u===d){for(var E=[],k=i,C=d,S=m[C];E.unshift(k),null!=S&&E.unshift(S),null!=(k=y[C]);)S=m[C=k.id()];return{found:!0,distance:h[u],path:this.spawn(E),steps:w}}f[u]=!0;for(var P=l._private.edges,D=0;DD&&(p[P]=D,m[P]=S,b[P]=w),!i){var T=S*u+C;!i&&p[T]>D&&(p[T]=D,m[T]=C,b[T]=w)}}}for(var _=0;_1&&void 0!==arguments[1]?arguments[1]:a,r=b(e),i=[],o=r;;){if(null==o)return t.spawn();var l=m(o),u=l.edge,c=l.pred;if(i.unshift(o[0]),o.same(n)&&i.length>0)break;null!=u&&i.unshift(u),o=c}return s.spawn(i)},hasNegativeWeightCycle:f,negativeWeightCycles:g}}},pt=Math.sqrt(2),ft=function(e,t,n){0===n.length&&Ve("Karger-Stein must be run on a connected (sub)graph");for(var r=n[e],i=r[1],a=r[2],o=t[i],s=t[a],l=n,u=l.length-1;u>=0;u--){var c=l[u],d=c[1],h=c[2];(t[d]===o&&t[h]===s||t[d]===s&&t[h]===o)&&l.splice(u,1)}for(var p=0;pr;){var i=Math.floor(Math.random()*t.length);t=ft(i,e,t),n--}return t},vt={kargerStein:function(){var e=this,t=this.byGroup(),n=t.nodes,r=t.edges;r.unmergeBy((function(e){return e.isLoop()}));var i=n.length,a=r.length,o=Math.ceil(Math.pow(Math.log(i)/Math.LN2,2)),s=Math.floor(i/pt);if(!(i<2)){for(var l=[],u=0;u0?1:e<0?-1:0},kt=function(e,t){return Math.sqrt(Ct(e,t))},Ct=function(e,t){var n=t.x-e.x,r=t.y-e.y;return n*n+r*r},St=function(e){for(var t=e.length,n=0,r=0;r=e.x1&&e.y2>=e.y1)return{x1:e.x1,y1:e.y1,x2:e.x2,y2:e.y2,w:e.x2-e.x1,h:e.y2-e.y1};if(null!=e.w&&null!=e.h&&e.w>=0&&e.h>=0)return{x1:e.x1,y1:e.y1,x2:e.x1+e.w,y2:e.y1+e.h,w:e.w,h:e.h}}},Mt=function(e,t){e.x1=Math.min(e.x1,t.x1),e.x2=Math.max(e.x2,t.x2),e.w=e.x2-e.x1,e.y1=Math.min(e.y1,t.y1),e.y2=Math.max(e.y2,t.y2),e.h=e.y2-e.y1},Bt=function(e,t,n){e.x1=Math.min(e.x1,t),e.x2=Math.max(e.x2,t),e.w=e.x2-e.x1,e.y1=Math.min(e.y1,n),e.y2=Math.max(e.y2,n),e.h=e.y2-e.y1},Nt=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0;return e.x1-=t,e.x2+=t,e.y1-=t,e.y2+=t,e.w=e.x2-e.x1,e.h=e.y2-e.y1,e},zt=function(e){var t,n,r,i,o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[0];if(1===o.length)t=n=r=i=o[0];else if(2===o.length)t=r=o[0],i=n=o[1];else if(4===o.length){var s=a(o,4);t=s[0],n=s[1],r=s[2],i=s[3]}return e.x1-=i,e.x2+=n,e.y1-=t,e.y2+=r,e.w=e.x2-e.x1,e.h=e.y2-e.y1,e},It=function(e,t){e.x1=t.x1,e.y1=t.y1,e.x2=t.x2,e.y2=t.y2,e.w=e.x2-e.x1,e.h=e.y2-e.y1},At=function(e,t){return!(e.x1>t.x2)&&(!(t.x1>e.x2)&&(!(e.x2t.y2)&&!(t.y1>e.y2)))))))},Lt=function(e,t,n){return e.x1<=t&&t<=e.x2&&e.y1<=n&&n<=e.y2},Ot=function(e,t){return Lt(e,t.x1,t.y1)&&Lt(e,t.x2,t.y2)},Rt=function(e,t,n,r,i,a,o){var s,l,u=arguments.length>7&&void 0!==arguments[7]?arguments[7]:"auto",c="auto"===u?nn(i,a):u,d=i/2,h=a/2,p=(c=Math.min(c,d,h))!==d,f=c!==h;if(p){var g=n-d+c-o,v=r-h-o,y=n+d-c+o,m=v;if((s=Zt(e,t,n,r,g,v,y,m,!1)).length>0)return s}if(f){var b=n+d+o,x=r-h+c-o,w=b,E=r+h-c+o;if((s=Zt(e,t,n,r,b,x,w,E,!1)).length>0)return s}if(p){var k=n-d+c-o,C=r+h+o,S=n+d-c+o,P=C;if((s=Zt(e,t,n,r,k,C,S,P,!1)).length>0)return s}if(f){var D=n-d-o,T=r-h+c-o,_=D,M=r+h-c+o;if((s=Zt(e,t,n,r,D,T,_,M,!1)).length>0)return s}var B=n-d+c,N=r-h+c;if((l=Gt(e,t,n,r,B,N,c+o)).length>0&&l[0]<=B&&l[1]<=N)return[l[0],l[1]];var z=n+d-c,I=r-h+c;if((l=Gt(e,t,n,r,z,I,c+o)).length>0&&l[0]>=z&&l[1]<=I)return[l[0],l[1]];var A=n+d-c,L=r+h-c;if((l=Gt(e,t,n,r,A,L,c+o)).length>0&&l[0]>=A&&l[1]>=L)return[l[0],l[1]];var O=n-d+c,R=r+h-c;return(l=Gt(e,t,n,r,O,R,c+o)).length>0&&l[0]<=O&&l[1]>=R?[l[0],l[1]]:[]},Vt=function(e,t,n,r,i,a,o){var s=o,l=Math.min(n,i),u=Math.max(n,i),c=Math.min(r,a),d=Math.max(r,a);return l-s<=e&&e<=u+s&&c-s<=t&&t<=d+s},Ft=function(e,t,n,r,i,a,o,s,l){var u=Math.min(n,o,i)-l,c=Math.max(n,o,i)+l,d=Math.min(r,s,a)-l,h=Math.max(r,s,a)+l;return!(ec||th)},jt=function(e,t,n,r,i,a,o,s){var l=[];!function(e,t,n,r,i){var a,o,s,l,u,c,d,h;0===e&&(e=1e-5),s=-27*(r/=e)+(t/=e)*(9*(n/=e)-t*t*2),a=(o=(3*n-t*t)/9)*o*o+(s/=54)*s,i[1]=0,d=t/3,a>0?(u=(u=s+Math.sqrt(a))<0?-Math.pow(-u,1/3):Math.pow(u,1/3),c=(c=s-Math.sqrt(a))<0?-Math.pow(-c,1/3):Math.pow(c,1/3),i[0]=-d+u+c,d+=(u+c)/2,i[4]=i[2]=-d,d=Math.sqrt(3)*(-c+u)/2,i[3]=d,i[5]=-d):(i[5]=i[3]=0,0===a?(h=s<0?-Math.pow(-s,1/3):Math.pow(s,1/3),i[0]=2*h-d,i[4]=i[2]=-(h+d)):(l=(o=-o)*o*o,l=Math.acos(s/Math.sqrt(l)),h=2*Math.sqrt(o),i[0]=-d+h*Math.cos(l/3),i[2]=-d+h*Math.cos((l+2*Math.PI)/3),i[4]=-d+h*Math.cos((l+4*Math.PI)/3)))}(1*n*n-4*n*i+2*n*o+4*i*i-4*i*o+o*o+r*r-4*r*a+2*r*s+4*a*a-4*a*s+s*s,9*n*i-3*n*n-3*n*o-6*i*i+3*i*o+9*r*a-3*r*r-3*r*s-6*a*a+3*a*s,3*n*n-6*n*i+n*o-n*e+2*i*i+2*i*e-o*e+3*r*r-6*r*a+r*s-r*t+2*a*a+2*a*t-s*t,1*n*i-n*n+n*e-i*e+r*a-r*r+r*t-a*t,l);for(var u=[],c=0;c<6;c+=2)Math.abs(l[c+1])<1e-7&&l[c]>=0&&l[c]<=1&&u.push(l[c]);u.push(1),u.push(0);for(var d,h,p,f=-1,g=0;g=0?pl?(e-i)*(e-i)+(t-a)*(t-a):u-d},Yt=function(e,t,n){for(var r,i,a,o,s=0,l=0;l=e&&e>=a||r<=e&&e<=a))continue;(e-r)/(a-r)*(o-i)+i>t&&s++}return s%2!=0},Xt=function(e,t,n,r,i,a,o,s,l){var u,c=new Array(n.length);null!=s[0]?(u=Math.atan(s[1]/s[0]),s[0]<0?u+=Math.PI/2:u=-u-Math.PI/2):u=s;for(var d,h=Math.cos(-u),p=Math.sin(-u),f=0;f0){var g=Ht(c,-l);d=Wt(g)}else d=c;return Yt(e,t,d)},Wt=function(e){for(var t,n,r,i,a,o,s,l,u=new Array(e.length/2),c=0;c=0&&f<=1&&v.push(f),g>=0&&g<=1&&v.push(g),0===v.length)return[];var y=v[0]*s[0]+e,m=v[0]*s[1]+t;return v.length>1?v[0]==v[1]?[y,m]:[y,m,v[1]*s[0]+e,v[1]*s[1]+t]:[y,m]},Ut=function(e,t,n){return t<=e&&e<=n||n<=e&&e<=t?e:e<=t&&t<=n||n<=t&&t<=e?t:n},Zt=function(e,t,n,r,i,a,o,s,l){var u=e-i,c=n-e,d=o-i,h=t-a,p=r-t,f=s-a,g=d*h-f*u,v=c*h-p*u,y=f*c-d*p;if(0!==y){var m=g/y,b=v/y;return-.001<=m&&m<=1.001&&-.001<=b&&b<=1.001||l?[e+m*c,t+m*p]:[]}return 0===g||0===v?Ut(e,n,o)===o?[o,s]:Ut(e,n,i)===i?[i,a]:Ut(i,o,n)===n?[n,r]:[]:[]},$t=function(e,t,n,r,i,a,o,s){var l,u,c,d,h,p,f=[],g=new Array(n.length),v=!0;if(null==a&&(v=!1),v){for(var y=0;y0){var m=Ht(g,-s);u=Wt(m)}else u=g}else u=n;for(var b=0;bu&&(u=t)},d=function(e){return l[e]},h=0;h0?b.edgesTo(m)[0]:m.edgesTo(b)[0];var w=r(x);m=m.id(),h[m]>h[v]+w&&(h[m]=h[v]+w,p.nodes.indexOf(m)<0?p.push(m):p.updateItem(m),u[m]=0,l[m]=[]),h[m]==h[v]+w&&(u[m]=u[m]+u[v],l[m].push(v))}else for(var E=0;E0;){for(var P=n.pop(),D=0;D0&&o.push(n[s]);0!==o.length&&i.push(r.collection(o))}return i}(c,l,t,r);return b=function(e){for(var t=0;t5&&void 0!==arguments[5]?arguments[5]:Cn,o=r,s=0;s=2?Mn(e,t,n,0,Dn,Tn):Mn(e,t,n,0,Pn)},squaredEuclidean:function(e,t,n){return Mn(e,t,n,0,Dn)},manhattan:function(e,t,n){return Mn(e,t,n,0,Pn)},max:function(e,t,n){return Mn(e,t,n,-1/0,_n)}};function Nn(e,t,n,r,i,a){var o;return o=y(e)?e:Bn[e]||Bn.euclidean,0===t&&y(e)?o(i,a):o(t,n,r,i,a)}Bn["squared-euclidean"]=Bn.squaredEuclidean,Bn.squaredeuclidean=Bn.squaredEuclidean;var zn=He({k:2,m:2,sensitivityThreshold:1e-4,distance:"euclidean",maxIterations:10,attributes:[],testMode:!1,testCentroids:null}),In=function(e){return zn(e)},An=function(e,t,n,r,i){var a="kMedoids"!==i?function(e){return n[e]}:function(e){return r[e](n)},o=n,s=t;return Nn(e,r.length,a,(function(e){return r[e](t)}),o,s)},Ln=function(e,t,n){for(var r=n.length,i=new Array(r),a=new Array(r),o=new Array(t),s=null,l=0;ln)return!1}return!0},jn=function(e,t,n){for(var r=0;ri&&(i=t[l][u],a=u);o[a].push(e[l])}for(var c=0;c=i.threshold||"dendrogram"===i.mode&&1===e.length)return!1;var p,f=t[o],g=t[r[o]];p="dendrogram"===i.mode?{left:f,right:g,key:f.key}:{value:f.value.concat(g.value),key:f.key},e[f.index]=p,e.splice(g.index,1),t[f.key]=p;for(var v=0;vn[g.key][y.key]&&(a=n[g.key][y.key])):"max"===i.linkage?(a=n[f.key][y.key],n[f.key][y.key]1&&void 0!==arguments[1]?arguments[1]:0,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:e.length,r=!(arguments.length>3&&void 0!==arguments[3])||arguments[3],i=!(arguments.length>4&&void 0!==arguments[4])||arguments[4],a=!(arguments.length>5&&void 0!==arguments[5])||arguments[5];r?e=e.slice(t,n):(n0&&e.splice(0,t));for(var o=0,s=e.length-1;s>=0;s--){var l=e[s];a?isFinite(l)||(e[s]=-1/0,o++):e.splice(s,1)}i&&e.sort((function(e,t){return e-t}));var u=e.length,c=Math.floor(u/2);return u%2!=0?e[c+1+o]:(e[c-1+o]+e[c+o])/2}(e):"mean"===t?function(e){for(var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:e.length,r=0,i=0,a=t;a1&&void 0!==arguments[1]?arguments[1]:0,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:e.length,r=1/0,i=t;i1&&void 0!==arguments[1]?arguments[1]:0,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:e.length,r=-1/0,i=t;io&&(a=l,o=t[i*e+l])}a>0&&r.push(a)}for(var u=0;u=D?(T=D,D=M,_=B):M>T&&(T=M);for(var N=0;N0?1:0;C[k%u.minIterations*t+R]=V,O+=V}if(O>0&&(k>=u.minIterations-1||k==u.maxIterations-1)){for(var F=0,j=0;j0&&r.push(i);return r}(t,a,o),X=function(e,t,n){for(var r=rr(e,t,n),i=0;il&&(s=u,l=c)}n[i]=a[s]}return r=rr(e,t,n)}(t,r,Y),W={},H=0;H1)}}));var l=Object.keys(t).filter((function(e){return t[e].cutVertex})).map((function(t){return e.getElementById(t)}));return{cut:e.spawn(l),components:i}},lr=function(){var e=this,t={},n=0,r=[],i=[],a=e.spawn(e);return e.forEach((function(o){if(o.isNode()){var s=o.id();s in t||function o(s){if(i.push(s),t[s]={index:n,low:n++,explored:!1},e.getElementById(s).connectedEdges().intersection(e).forEach((function(e){var n=e.target().id();n!==s&&(n in t||o(n),t[n].explored||(t[s].low=Math.min(t[s].low,t[n].low)))})),t[s].index===t[s].low){for(var l=e.spawn();;){var u=i.pop();if(l.merge(e.getElementById(u)),t[u].low=t[s].index,t[u].explored=!0,u===s)break}var c=l.edgesWith(l),d=l.merge(c);r.push(d),a=a.difference(d)}}(s)}})),{cut:a,components:r}},ur={};[nt,at,ot,lt,ct,ht,vt,sn,un,dn,pn,kn,Kn,Jn,ar,{hierholzer:function(e){if(!b(e)){var t=arguments;e={root:t[0],directed:t[1]}}var n,r,i,a=or(e),o=a.root,s=a.directed,l=this,u=!1;o&&(i=v(o)?this.filter(o)[0].id():o[0].id());var c={},d={};s?l.forEach((function(e){var t=e.id();if(e.isNode()){var i=e.indegree(!0),a=e.outdegree(!0),o=i-a,s=a-i;1==o?n?u=!0:n=t:1==s?r?u=!0:r=t:(s>1||o>1)&&(u=!0),c[t]=[],e.outgoers().forEach((function(e){e.isEdge()&&c[t].push(e.id())}))}else d[t]=[void 0,e.target().id()]})):l.forEach((function(e){var t=e.id();e.isNode()?(e.degree(!0)%2&&(n?r?u=!0:r=t:n=t),c[t]=[],e.connectedEdges().forEach((function(e){return c[t].push(e.id())}))):d[t]=[e.source().id(),e.target().id()]}));var h={found:!1,trail:void 0};if(u)return h;if(r&&n)if(s){if(i&&r!=i)return h;i=r}else{if(i&&r!=i&&n!=i)return h;i||(i=r)}else i||(i=l[0].id());var p=function(e){for(var t,n,r,i=e,a=[e];c[i].length;)t=c[i].shift(),n=d[t][0],i!=(r=d[t][1])?(c[r]=c[r].filter((function(e){return e!=t})),i=r):s||i==n||(c[n]=c[n].filter((function(e){return e!=t})),i=n),a.unshift(t),a.unshift(i);return a},f=[],g=[];for(g=p(i);1!=g.length;)0==c[g[0]].length?(f.unshift(l.getElementById(g.shift())),f.unshift(l.getElementById(g.shift()))):g=p(g.shift()).concat(g);for(var y in f.unshift(l.getElementById(g.shift())),c)if(c[y].length)return h;return h.found=!0,h.trail=this.spawn(f,!0),h}},{hopcroftTarjanBiconnected:sr,htbc:sr,htb:sr,hopcroftTarjanBiconnectedComponents:sr},{tarjanStronglyConnected:lr,tsc:lr,tscc:lr,tarjanStronglyConnectedComponents:lr}].forEach((function(e){L(ur,e)})); +/*! + Embeddable Minimum Strictly-Compliant Promises/A+ 1.1.1 Thenable + Copyright (c) 2013-2014 Ralf S. Engelschall (http://engelschall.com) + Licensed under The MIT License (http://opensource.org/licenses/MIT) + */ +var cr=function e(t){if(!(this instanceof e))return new e(t);this.id="Thenable/1.0.7",this.state=0,this.fulfillValue=void 0,this.rejectReason=void 0,this.onFulfilled=[],this.onRejected=[],this.proxy={then:this.then.bind(this)},"function"==typeof t&&t.call(this,this.fulfill.bind(this),this.reject.bind(this))};cr.prototype={fulfill:function(e){return dr(this,1,"fulfillValue",e)},reject:function(e){return dr(this,2,"rejectReason",e)},then:function(e,t){var n=new cr;return this.onFulfilled.push(fr(e,n,"fulfill")),this.onRejected.push(fr(t,n,"reject")),hr(this),n.proxy}};var dr=function(e,t,n,r){return 0===e.state&&(e.state=t,e[n]=r,hr(e)),e},hr=function(e){1===e.state?pr(e,"onFulfilled",e.fulfillValue):2===e.state&&pr(e,"onRejected",e.rejectReason)},pr=function(e,t,n){if(0!==e[t].length){var r=e[t];e[t]=[];var i=function(){for(var e=0;e0:void 0}},clearQueue:function(){return function(){var e=void 0!==this.length?this:[this];if(!(this._private.cy||this).styleEnabled())return this;for(var t=0;t-1};var ri=function(e,t){var n=this.__data__,r=Qr(n,e);return r<0?(++this.size,n.push([e,t])):n[r][1]=t,this};function ii(e){var t=-1,n=null==e?0:e.length;for(this.clear();++t-1&&e%1==0&&e0&&this.spawn(n).updateStyle().emit("class"),this},addClass:function(e){return this.toggleClass(e,!0)},hasClass:function(e){var t=this[0];return null!=t&&t._private.classes.has(e)},toggleClass:function(e,t){m(e)||(e=e.match(/\S+/g)||[]);for(var n=void 0===t,r=[],i=0,a=this.length;i0&&this.spawn(r).updateStyle().emit("class"),this},removeClass:function(e){return this.toggleClass(e,!1)},flashClass:function(e,t){var n=this;if(null==t)t=250;else if(0===t)return n;return n.addClass(e),setTimeout((function(){n.removeClass(e)}),t),n}};qi.className=qi.classNames=qi.classes;var Yi={metaChar:"[\\!\\\"\\#\\$\\%\\&\\'\\(\\)\\*\\+\\,\\.\\/\\:\\;\\<\\=\\>\\?\\@\\[\\]\\^\\`\\{\\|\\}\\~]",comparatorOp:"=|\\!=|>|>=|<|<=|\\$=|\\^=|\\*=",boolOp:"\\?|\\!|\\^",string:"\"(?:\\\\\"|[^\"])*\"|'(?:\\\\'|[^'])*'",number:I,meta:"degree|indegree|outdegree",separator:"\\s*,\\s*",descendant:"\\s+",child:"\\s+>\\s+",subject:"\\$",group:"node|edge|\\*",directedEdge:"\\s+->\\s+",undirectedEdge:"\\s+<->\\s+"};Yi.variable="(?:[\\w-.]|(?:\\\\"+Yi.metaChar+"))+",Yi.className="(?:[\\w-]|(?:\\\\"+Yi.metaChar+"))+",Yi.value=Yi.string+"|"+Yi.number,Yi.id=Yi.variable,function(){var e,t,n;for(e=Yi.comparatorOp.split("|"),n=0;n=0||"="!==t&&(Yi.comparatorOp+="|\\!"+t)}();var Xi=0,Wi=1,Hi=2,Ki=3,Gi=4,Ui=5,Zi=6,$i=7,Qi=8,Ji=9,ea=10,ta=11,na=12,ra=13,ia=14,aa=15,oa=16,sa=17,la=18,ua=19,ca=20,da=[{selector:":selected",matches:function(e){return e.selected()}},{selector:":unselected",matches:function(e){return!e.selected()}},{selector:":selectable",matches:function(e){return e.selectable()}},{selector:":unselectable",matches:function(e){return!e.selectable()}},{selector:":locked",matches:function(e){return e.locked()}},{selector:":unlocked",matches:function(e){return!e.locked()}},{selector:":visible",matches:function(e){return e.visible()}},{selector:":hidden",matches:function(e){return!e.visible()}},{selector:":transparent",matches:function(e){return e.transparent()}},{selector:":grabbed",matches:function(e){return e.grabbed()}},{selector:":free",matches:function(e){return!e.grabbed()}},{selector:":removed",matches:function(e){return e.removed()}},{selector:":inside",matches:function(e){return!e.removed()}},{selector:":grabbable",matches:function(e){return e.grabbable()}},{selector:":ungrabbable",matches:function(e){return!e.grabbable()}},{selector:":animated",matches:function(e){return e.animated()}},{selector:":unanimated",matches:function(e){return!e.animated()}},{selector:":parent",matches:function(e){return e.isParent()}},{selector:":childless",matches:function(e){return e.isChildless()}},{selector:":child",matches:function(e){return e.isChild()}},{selector:":orphan",matches:function(e){return e.isOrphan()}},{selector:":nonorphan",matches:function(e){return e.isChild()}},{selector:":compound",matches:function(e){return e.isNode()?e.isParent():e.source().isParent()||e.target().isParent()}},{selector:":loop",matches:function(e){return e.isLoop()}},{selector:":simple",matches:function(e){return e.isSimple()}},{selector:":active",matches:function(e){return e.active()}},{selector:":inactive",matches:function(e){return!e.active()}},{selector:":backgrounding",matches:function(e){return e.backgrounding()}},{selector:":nonbackgrounding",matches:function(e){return!e.backgrounding()}}].sort((function(e,t){return function(e,t){return-1*A(e,t)}(e.selector,t.selector)})),ha=function(){for(var e,t={},n=0;n0&&l.edgeCount>0)return je("The selector `"+e+"` is invalid because it uses both a compound selector and an edge selector"),!1;if(l.edgeCount>1)return je("The selector `"+e+"` is invalid because it uses multiple edge selectors"),!1;1===l.edgeCount&&je("The selector `"+e+"` is deprecated. Edge selectors do not take effect on changes to source and target nodes after an edge is added, for performance reasons. Use a class or data selector on edges instead, updating the class or data of an edge when your app detects a change in source or target nodes.")}return!0},toString:function(){if(null!=this.toStringCache)return this.toStringCache;for(var e=function(e){return null==e?"":e},t=function(t){return v(t)?'"'+t+'"':e(t)},n=function(e){return" "+e+" "},r=function(r,a){var o=r.type,s=r.value;switch(o){case Xi:var l=e(s);return l.substring(0,l.length-1);case Ki:var u=r.field,c=r.operator;return"["+u+n(e(c))+t(s)+"]";case Ui:var d=r.operator,h=r.field;return"["+e(d)+h+"]";case Gi:return"["+r.field+"]";case Zi:var p=r.operator;return"[["+r.field+n(e(p))+t(s)+"]]";case $i:return s;case Qi:return"#"+s;case Ji:return"."+s;case sa:case aa:return i(r.parent,a)+n(">")+i(r.child,a);case la:case oa:return i(r.ancestor,a)+" "+i(r.descendant,a);case ua:var f=i(r.left,a),g=i(r.subject,a),v=i(r.right,a);return f+(f.length>0?" ":"")+g+v;case ca:return""}},i=function(e,t){return e.checks.reduce((function(n,i,a){return n+(t===e&&0===a?"$":"")+r(i,t)}),"")},a="",o=0;o1&&o=0&&(t=t.replace("!",""),c=!0),t.indexOf("@")>=0&&(t=t.replace("@",""),u=!0),(o||l||u)&&(i=o||s?""+e:"",a=""+n),u&&(e=i=i.toLowerCase(),n=a=a.toLowerCase()),t){case"*=":r=i.indexOf(a)>=0;break;case"$=":r=i.indexOf(a,i.length-a.length)>=0;break;case"^=":r=0===i.indexOf(a);break;case"=":r=e===n;break;case">":d=!0,r=e>n;break;case">=":d=!0,r=e>=n;break;case"<":d=!0,r=e0;){var u=i.shift();t(u),a.add(u.id()),o&&r(i,a,u)}return e}function Ba(e,t,n){if(n.isParent())for(var r=n._private.children,i=0;i1&&void 0!==arguments[1])||arguments[1];return Ma(this,e,t,Ba)},_a.forEachUp=function(e){var t=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];return Ma(this,e,t,Na)},_a.forEachUpAndDown=function(e){var t=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];return Ma(this,e,t,za)},_a.ancestors=_a.parents,(Pa=Da={data:Fi.data({field:"data",bindingEvent:"data",allowBinding:!0,allowSetting:!0,settingEvent:"data",settingTriggersEvent:!0,triggerFnName:"trigger",allowGetting:!0,immutableKeys:{id:!0,source:!0,target:!0,parent:!0},updateStyle:!0}),removeData:Fi.removeData({field:"data",event:"data",triggerFnName:"trigger",triggerEvent:!0,immutableKeys:{id:!0,source:!0,target:!0,parent:!0},updateStyle:!0}),scratch:Fi.data({field:"scratch",bindingEvent:"scratch",allowBinding:!0,allowSetting:!0,settingEvent:"scratch",settingTriggersEvent:!0,triggerFnName:"trigger",allowGetting:!0,updateStyle:!0}),removeScratch:Fi.removeData({field:"scratch",event:"scratch",triggerFnName:"trigger",triggerEvent:!0,updateStyle:!0}),rscratch:Fi.data({field:"rscratch",allowBinding:!1,allowSetting:!0,settingTriggersEvent:!1,allowGetting:!0}),removeRscratch:Fi.removeData({field:"rscratch",triggerEvent:!1}),id:function(){var e=this[0];if(e)return e._private.data.id}}).attr=Pa.data,Pa.removeAttr=Pa.removeData;var Ia,Aa,La=Da,Oa={};function Ra(e){return function(t){if(void 0===t&&(t=!0),0!==this.length&&this.isNode()&&!this.removed()){for(var n=0,r=this[0],i=r._private.edges,a=0;at})),minIndegree:Va("indegree",(function(e,t){return et})),minOutdegree:Va("outdegree",(function(e,t){return et}))}),L(Oa,{totalDegree:function(e){for(var t=0,n=this.nodes(),r=0;r0,c=u;u&&(l=l[0]);var d=c?l.position():{x:0,y:0};return i={x:s.x-d.x,y:s.y-d.y},void 0===e?i:i[e]}for(var h=0;h0,y=g;g&&(f=f[0]);var m=y?f.position():{x:0,y:0};void 0!==t?p.position(e,t+m[e]):void 0!==i&&p.position({x:i.x+m.x,y:i.y+m.y})}}else if(!a)return;return this}}).modelPosition=Ia.point=Ia.position,Ia.modelPositions=Ia.points=Ia.positions,Ia.renderedPoint=Ia.renderedPosition,Ia.relativePoint=Ia.relativePosition;var qa,Ya,Xa=Aa;qa=Ya={},Ya.renderedBoundingBox=function(e){var t=this.boundingBox(e),n=this.cy(),r=n.zoom(),i=n.pan(),a=t.x1*r+i.x,o=t.x2*r+i.x,s=t.y1*r+i.y,l=t.y2*r+i.y;return{x1:a,x2:o,y1:s,y2:l,w:o-a,h:l-s}},Ya.dirtyCompoundBoundsCache=function(){var e=arguments.length>0&&void 0!==arguments[0]&&arguments[0],t=this.cy();return t.styleEnabled()&&t.hasCompoundNodes()?(this.forEachUp((function(t){if(t.isParent()){var n=t._private;n.compoundBoundsClean=!1,n.bbCache=null,e||t.emitAndNotify("bounds")}})),this):this},Ya.updateCompoundBounds=function(){var e=arguments.length>0&&void 0!==arguments[0]&&arguments[0],t=this.cy();if(!t.styleEnabled()||!t.hasCompoundNodes())return this;if(!e&&t.batching())return this;function n(e){if(e.isParent()){var t=e._private,n=e.children(),r="include"===e.pstyle("compound-sizing-wrt-labels").value,i={width:{val:e.pstyle("min-width").pfValue,left:e.pstyle("min-width-bias-left"),right:e.pstyle("min-width-bias-right")},height:{val:e.pstyle("min-height").pfValue,top:e.pstyle("min-height-bias-top"),bottom:e.pstyle("min-height-bias-bottom")}},a=n.boundingBox({includeLabels:r,includeOverlays:!1,useCache:!1}),o=t.position;0!==a.w&&0!==a.h||((a={w:e.pstyle("width").pfValue,h:e.pstyle("height").pfValue}).x1=o.x-a.w/2,a.x2=o.x+a.w/2,a.y1=o.y-a.h/2,a.y2=o.y+a.h/2);var s=i.width.left.value;"px"===i.width.left.units&&i.width.val>0&&(s=100*s/i.width.val);var l=i.width.right.value;"px"===i.width.right.units&&i.width.val>0&&(l=100*l/i.width.val);var u=i.height.top.value;"px"===i.height.top.units&&i.height.val>0&&(u=100*u/i.height.val);var c=i.height.bottom.value;"px"===i.height.bottom.units&&i.height.val>0&&(c=100*c/i.height.val);var d=y(i.width.val-a.w,s,l),h=d.biasDiff,p=d.biasComplementDiff,f=y(i.height.val-a.h,u,c),g=f.biasDiff,v=f.biasComplementDiff;t.autoPadding=function(e,t,n,r){if("%"!==n.units)return"px"===n.units?n.pfValue:0;switch(r){case"width":return e>0?n.pfValue*e:0;case"height":return t>0?n.pfValue*t:0;case"average":return e>0&&t>0?n.pfValue*(e+t)/2:0;case"min":return e>0&&t>0?e>t?n.pfValue*t:n.pfValue*e:0;case"max":return e>0&&t>0?e>t?n.pfValue*e:n.pfValue*t:0;default:return 0}}(a.w,a.h,e.pstyle("padding"),e.pstyle("padding-relative-to").value),t.autoWidth=Math.max(a.w,i.width.val),o.x=(-h+a.x1+a.x2+p)/2,t.autoHeight=Math.max(a.h,i.height.val),o.y=(-g+a.y1+a.y2+v)/2}function y(e,t,n){var r=0,i=0,a=t+n;return e>0&&a>0&&(r=t/a*e,i=n/a*e),{biasDiff:r,biasComplementDiff:i}}}for(var r=0;re.x2?r:e.x2,e.y1=ne.y2?i:e.y2,e.w=e.x2-e.x1,e.h=e.y2-e.y1)},Ka=function(e,t){return null==t?e:Ha(e,t.x1,t.y1,t.x2,t.y2)},Ga=function(e,t,n){return Ue(e,t,n)},Ua=function(e,t,n){if(!t.cy().headless()){var r,i,a=t._private,o=a.rstyle,s=o.arrowWidth/2;if("none"!==t.pstyle(n+"-arrow-shape").value){"source"===n?(r=o.srcX,i=o.srcY):"target"===n?(r=o.tgtX,i=o.tgtY):(r=o.midX,i=o.midY);var l=a.arrowBounds=a.arrowBounds||{},u=l[n]=l[n]||{};u.x1=r-s,u.y1=i-s,u.x2=r+s,u.y2=i+s,u.w=u.x2-u.x1,u.h=u.y2-u.y1,Nt(u,1),Ha(e,u.x1,u.y1,u.x2,u.y2)}}},Za=function(e,t,n){if(!t.cy().headless()){var r;r=n?n+"-":"";var i=t._private,a=i.rstyle;if(t.pstyle(r+"label").strValue){var o,s,l,u,c=t.pstyle("text-halign"),d=t.pstyle("text-valign"),h=Ga(a,"labelWidth",n),p=Ga(a,"labelHeight",n),f=Ga(a,"labelX",n),g=Ga(a,"labelY",n),v=t.pstyle(r+"text-margin-x").pfValue,y=t.pstyle(r+"text-margin-y").pfValue,m=t.isEdge(),b=t.pstyle(r+"text-rotation"),x=t.pstyle("text-outline-width").pfValue,w=t.pstyle("text-border-width").pfValue/2,E=t.pstyle("text-background-padding").pfValue,k=p,C=h,S=C/2,P=k/2;if(m)o=f-S,s=f+S,l=g-P,u=g+P;else{switch(c.value){case"left":o=f-C,s=f;break;case"center":o=f-S,s=f+S;break;case"right":o=f,s=f+C}switch(d.value){case"top":l=g-k,u=g;break;case"center":l=g-P,u=g+P;break;case"bottom":l=g,u=g+k}}o+=v-Math.max(x,w)-E-2,s+=v+Math.max(x,w)+E+2,l+=y-Math.max(x,w)-E-2,u+=y+Math.max(x,w)+E+2;var D=n||"main",T=i.labelBounds,_=T[D]=T[D]||{};_.x1=o,_.y1=l,_.x2=s,_.y2=u,_.w=s-o,_.h=u-l;var M=m&&"autorotate"===b.strValue,B=null!=b.pfValue&&0!==b.pfValue;if(M||B){var N=M?Ga(i.rstyle,"labelAngle",n):b.pfValue,z=Math.cos(N),I=Math.sin(N),A=(o+s)/2,L=(l+u)/2;if(!m){switch(c.value){case"left":A=s;break;case"right":A=o}switch(d.value){case"top":L=u;break;case"bottom":L=l}}var O=function(e,t){return{x:(e-=A)*z-(t-=L)*I+A,y:e*I+t*z+L}},R=O(o,l),V=O(o,u),F=O(s,l),j=O(s,u);o=Math.min(R.x,V.x,F.x,j.x),s=Math.max(R.x,V.x,F.x,j.x),l=Math.min(R.y,V.y,F.y,j.y),u=Math.max(R.y,V.y,F.y,j.y)}var q=D+"Rot",Y=T[q]=T[q]||{};Y.x1=o,Y.y1=l,Y.x2=s,Y.y2=u,Y.w=s-o,Y.h=u-l,Ha(e,o,l,s,u),Ha(i.labelBounds.all,o,l,s,u)}return e}},$a=function(e,t){var n,r,i,a,o,s,l,u=e._private.cy,c=u.styleEnabled(),d=u.headless(),h=_t(),p=e._private,f=e.isNode(),g=e.isEdge(),v=p.rstyle,y=f&&c?e.pstyle("bounds-expansion").pfValue:[0],m=function(e){return"none"!==e.pstyle("display").value},b=!c||m(e)&&(!g||m(e.source())&&m(e.target()));if(b){var x=0;c&&t.includeOverlays&&0!==e.pstyle("overlay-opacity").value&&(x=e.pstyle("overlay-padding").value);var w=0;c&&t.includeUnderlays&&0!==e.pstyle("underlay-opacity").value&&(w=e.pstyle("underlay-padding").value);var E=Math.max(x,w),k=0;if(c&&(k=e.pstyle("width").pfValue/2),f&&t.includeNodes){var C=e.position();o=C.x,s=C.y;var S=e.outerWidth()/2,P=e.outerHeight()/2;Ha(h,n=o-S,i=s-P,r=o+S,a=s+P),c&&t.includeOutlines&&function(e,t){if(!t.cy().headless()){var n,r,i,a=t.pstyle("outline-opacity").value,o=t.pstyle("outline-width").value;if(a>0&&o>0){var s=t.pstyle("outline-offset").value,l=t.pstyle("shape").value,u=o+s,c=(e.w+2*u)/e.w,d=(e.h+2*u)/e.h,h=0;["diamond","pentagon","round-triangle"].includes(l)?(c=(e.w+2.4*u)/e.w,h=-u/3.6):["concave-hexagon","rhomboid","right-rhomboid"].includes(l)?c=(e.w+2.4*u)/e.w:"star"===l?(c=(e.w+2.8*u)/e.w,d=(e.h+2.6*u)/e.h,h=-u/3.8):"triangle"===l?(c=(e.w+2.8*u)/e.w,d=(e.h+2.4*u)/e.h,h=-u/1.4):"vee"===l&&(c=(e.w+4.4*u)/e.w,d=(e.h+3.8*u)/e.h,h=.5*-u);var p=e.h*d-e.h,f=e.w*c-e.w;if(zt(e,[Math.ceil(p/2),Math.ceil(f/2)]),0!==h){var g=(r=0,i=h,{x1:(n=e).x1+r,x2:n.x2+r,y1:n.y1+i,y2:n.y2+i,w:n.w,h:n.h});Mt(e,g)}}}}(h,e)}else if(g&&t.includeEdges)if(c&&!d){var D=e.pstyle("curve-style").strValue;if(n=Math.min(v.srcX,v.midX,v.tgtX),r=Math.max(v.srcX,v.midX,v.tgtX),i=Math.min(v.srcY,v.midY,v.tgtY),a=Math.max(v.srcY,v.midY,v.tgtY),Ha(h,n-=k,i-=k,r+=k,a+=k),"haystack"===D){var T=v.haystackPts;if(T&&2===T.length){if(n=T[0].x,i=T[0].y,n>(r=T[1].x)){var _=n;n=r,r=_}if(i>(a=T[1].y)){var M=i;i=a,a=M}Ha(h,n-k,i-k,r+k,a+k)}}else if("bezier"===D||"unbundled-bezier"===D||D.endsWith("segments")||D.endsWith("taxi")){var B;switch(D){case"bezier":case"unbundled-bezier":B=v.bezierPts;break;case"segments":case"taxi":case"round-segments":case"round-taxi":B=v.linePts}if(null!=B)for(var N=0;N(r=A.x)){var L=n;n=r,r=L}if((i=I.y)>(a=A.y)){var O=i;i=a,a=O}Ha(h,n-=k,i-=k,r+=k,a+=k)}if(c&&t.includeEdges&&g&&(Ua(h,e,"mid-source"),Ua(h,e,"mid-target"),Ua(h,e,"source"),Ua(h,e,"target")),c)if("yes"===e.pstyle("ghost").value){var R=e.pstyle("ghost-offset-x").pfValue,V=e.pstyle("ghost-offset-y").pfValue;Ha(h,h.x1+R,h.y1+V,h.x2+R,h.y2+V)}var F=p.bodyBounds=p.bodyBounds||{};It(F,h),zt(F,y),Nt(F,1),c&&(n=h.x1,r=h.x2,i=h.y1,a=h.y2,Ha(h,n-E,i-E,r+E,a+E));var j=p.overlayBounds=p.overlayBounds||{};It(j,h),zt(j,y),Nt(j,1);var q=p.labelBounds=p.labelBounds||{};null!=q.all?((l=q.all).x1=1/0,l.y1=1/0,l.x2=-1/0,l.y2=-1/0,l.w=0,l.h=0):q.all=_t(),c&&t.includeLabels&&(t.includeMainLabels&&Za(h,e,null),g&&(t.includeSourceLabels&&Za(h,e,"source"),t.includeTargetLabels&&Za(h,e,"target")))}return h.x1=Wa(h.x1),h.y1=Wa(h.y1),h.x2=Wa(h.x2),h.y2=Wa(h.y2),h.w=Wa(h.x2-h.x1),h.h=Wa(h.y2-h.y1),h.w>0&&h.h>0&&b&&(zt(h,y),Nt(h,1)),h},Qa=function(e){var t=0,n=function(e){return(e?1:0)<0&&void 0!==arguments[0]?arguments[0]:bo,t=arguments.length>1?arguments[1]:void 0,n=0;n=0;s--)o(s);return this},wo.removeAllListeners=function(){return this.removeListener("*")},wo.emit=wo.trigger=function(e,t,n){var r=this.listeners,i=r.length;return this.emitting++,m(t)||(t=[t]),Co(this,(function(e,a){null!=n&&(r=[{event:a.event,type:a.type,namespace:a.namespace,callback:n}],i=r.length);for(var o=function(n){var i=r[n];if(i.type===a.type&&(!i.namespace||i.namespace===a.namespace||".*"===i.namespace)&&e.eventMatches(e.context,i,a)){var o=[a];null!=t&&function(e,t){for(var n=0;n1&&!r){var i=this.length-1,a=this[i],o=a._private.data.id;this[i]=void 0,this[e]=a,n.set(o,{ele:a,index:e})}return this.length--,this},unmergeOne:function(e){e=e[0];var t=this._private,n=e._private.data.id,r=t.map.get(n);if(!r)return this;var i=r.index;return this.unmergeAt(i),this},unmerge:function(e){var t=this._private.cy;if(!e)return this;if(e&&v(e)){var n=e;e=t.mutableElements().filter(n)}for(var r=0;r=0;t--){e(this[t])&&this.unmergeAt(t)}return this},map:function(e,t){for(var n=[],r=0;rr&&(r=o,n=a)}return{value:r,ele:n}},min:function(e,t){for(var n,r=1/0,i=0;i=0&&i1&&void 0!==arguments[1])||arguments[1],n=this[0],r=n.cy();if(r.styleEnabled()&&n){this.cleanStyle();var i=n._private.style[e];return null!=i?i:t?r.style().getDefaultProperty(e):null}},numericStyle:function(e){var t=this[0];if(t.cy().styleEnabled()&&t){var n=t.pstyle(e);return void 0!==n.pfValue?n.pfValue:n.value}},numericStyleUnits:function(e){var t=this[0];if(t.cy().styleEnabled())return t?t.pstyle(e).units:void 0},renderedStyle:function(e){var t=this.cy();if(!t.styleEnabled())return this;var n=this[0];return n?t.style().getRenderedStyle(n,e):void 0},style:function(e,t){var n=this.cy();if(!n.styleEnabled())return this;var r=n.style();if(b(e)){var i=e;r.applyBypass(this,i,!1),this.emitAndNotify("style")}else if(v(e)){if(void 0===t){var a=this[0];return a?r.getStylePropertyValue(a,e):void 0}r.applyBypass(this,e,t,!1),this.emitAndNotify("style")}else if(void 0===e){var o=this[0];return o?r.getRawStyle(o):void 0}return this},removeStyle:function(e){var t=this.cy();if(!t.styleEnabled())return this;var n=t.style();if(void 0===e)for(var r=0;r0&&t.push(c[0]),t.push(s[0])}return this.spawn(t,!0).filter(e)}),"neighborhood"),closedNeighborhood:function(e){return this.neighborhood().add(this).filter(e)},openNeighborhood:function(e){return this.neighborhood(e)}}),Go.neighbourhood=Go.neighborhood,Go.closedNeighbourhood=Go.closedNeighborhood,Go.openNeighbourhood=Go.openNeighborhood,L(Go,{source:Ta((function(e){var t,n=this[0];return n&&(t=n._private.source||n.cy().collection()),t&&e?t.filter(e):t}),"source"),target:Ta((function(e){var t,n=this[0];return n&&(t=n._private.target||n.cy().collection()),t&&e?t.filter(e):t}),"target"),sources:Qo({attr:"source"}),targets:Qo({attr:"target"})}),L(Go,{edgesWith:Ta(Jo(),"edgesWith"),edgesTo:Ta(Jo({thisIsSrc:!0}),"edgesTo")}),L(Go,{connectedEdges:Ta((function(e){for(var t=[],n=0;n0);return a},component:function(){var e=this[0];return e.cy().mutableElements().components(e)[0]}}),Go.componentsOf=Go.components;var ts=function(e,t){var n=arguments.length>2&&void 0!==arguments[2]&&arguments[2],r=arguments.length>3&&void 0!==arguments[3]&&arguments[3];if(void 0!==e){var i=new $e,a=!1;if(t){if(t.length>0&&b(t[0])&&!k(t[0])){a=!0;for(var o=[],s=new Je,l=0,u=t.length;l0&&void 0!==arguments[0])||arguments[0],r=!(arguments.length>1&&void 0!==arguments[1])||arguments[1],i=this,a=i.cy(),o=a._private,s=[],l=[],u=0,c=i.length;u0){for(var R=e.length===i.length?i:new ts(a,e),V=0;V0&&void 0!==arguments[0])||arguments[0],t=!(arguments.length>1&&void 0!==arguments[1])||arguments[1],n=this,r=[],i={},a=n._private.cy;function o(e){for(var t=e._private.edges,n=0;n0&&(e?D.emitAndNotify("remove"):t&&D.emit("remove"));for(var T=0;T1e-4&&Math.abs(s.v)>1e-4;);return a?function(e){return u[e*(u.length-1)|0]}:c}}(),as=function(e,t,n,r){var i=function(e,t,n,r){var i=4,a=.001,o=1e-7,s=10,l=11,u=1/(l-1),c="undefined"!=typeof Float32Array;if(4!==arguments.length)return!1;for(var d=0;d<4;++d)if("number"!=typeof arguments[d]||isNaN(arguments[d])||!isFinite(arguments[d]))return!1;e=Math.min(e,1),n=Math.min(n,1),e=Math.max(e,0),n=Math.max(n,0);var h=c?new Float32Array(l):new Array(l);function p(e,t){return 1-3*t+3*e}function f(e,t){return 3*t-6*e}function g(e){return 3*e}function v(e,t,n){return((p(t,n)*e+f(t,n))*e+g(t))*e}function y(e,t,n){return 3*p(t,n)*e*e+2*f(t,n)*e+g(t)}function m(t,r){for(var a=0;a0?i=l:r=l}while(Math.abs(a)>o&&++u=a?m(t,s):0===c?s:x(t,r,r+u)}var E=!1;function k(){E=!0,e===t&&n===r||b()}var C=function(i){return E||k(),e===t&&n===r?i:0===i?0:1===i?1:v(w(i),t,r)};C.getControlPoints=function(){return[{x:e,y:t},{x:n,y:r}]};var S="generateBezier("+[e,t,n,r]+")";return C.toString=function(){return S},C}(e,t,n,r);return function(e,t,n){return e+(t-e)*i(n)}},os={linear:function(e,t,n){return e+(t-e)*n},ease:as(.25,.1,.25,1),"ease-in":as(.42,0,1,1),"ease-out":as(0,0,.58,1),"ease-in-out":as(.42,0,.58,1),"ease-in-sine":as(.47,0,.745,.715),"ease-out-sine":as(.39,.575,.565,1),"ease-in-out-sine":as(.445,.05,.55,.95),"ease-in-quad":as(.55,.085,.68,.53),"ease-out-quad":as(.25,.46,.45,.94),"ease-in-out-quad":as(.455,.03,.515,.955),"ease-in-cubic":as(.55,.055,.675,.19),"ease-out-cubic":as(.215,.61,.355,1),"ease-in-out-cubic":as(.645,.045,.355,1),"ease-in-quart":as(.895,.03,.685,.22),"ease-out-quart":as(.165,.84,.44,1),"ease-in-out-quart":as(.77,0,.175,1),"ease-in-quint":as(.755,.05,.855,.06),"ease-out-quint":as(.23,1,.32,1),"ease-in-out-quint":as(.86,0,.07,1),"ease-in-expo":as(.95,.05,.795,.035),"ease-out-expo":as(.19,1,.22,1),"ease-in-out-expo":as(1,0,0,1),"ease-in-circ":as(.6,.04,.98,.335),"ease-out-circ":as(.075,.82,.165,1),"ease-in-out-circ":as(.785,.135,.15,.86),spring:function(e,t,n){if(0===n)return os.linear;var r=is(e,t,n);return function(e,t,n){return e+(t-e)*r(n)}},"cubic-bezier":as};function ss(e,t,n,r,i){if(1===r)return n;if(t===n)return n;var a=i(t,n,r);return null==e||((e.roundValue||e.color)&&(a=Math.round(a)),void 0!==e.min&&(a=Math.max(a,e.min)),void 0!==e.max&&(a=Math.min(a,e.max))),a}function ls(e,t){return null!=e.pfValue||null!=e.value?null==e.pfValue||null!=t&&"%"===t.type.units?e.value:e.pfValue:e}function us(e,t,n,r,i){var a=null!=i?i.type:null;n<0?n=0:n>1&&(n=1);var o=ls(e,i),s=ls(t,i);if(x(o)&&x(s))return ss(a,o,s,n,r);if(m(o)&&m(s)){for(var l=[],u=0;u0?("spring"===d&&h.push(o.duration),o.easingImpl=os[d].apply(null,h)):o.easingImpl=os[d]}var p,f=o.easingImpl;if(p=0===o.duration?1:(n-l)/o.duration,o.applying&&(p=o.progress),p<0?p=0:p>1&&(p=1),null==o.delay){var g=o.startPosition,y=o.position;if(y&&i&&!e.locked()){var m={};ds(g.x,y.x)&&(m.x=us(g.x,y.x,p,f)),ds(g.y,y.y)&&(m.y=us(g.y,y.y,p,f)),e.position(m)}var b=o.startPan,x=o.pan,w=a.pan,E=null!=x&&r;E&&(ds(b.x,x.x)&&(w.x=us(b.x,x.x,p,f)),ds(b.y,x.y)&&(w.y=us(b.y,x.y,p,f)),e.emit("pan"));var k=o.startZoom,C=o.zoom,S=null!=C&&r;S&&(ds(k,C)&&(a.zoom=Tt(a.minZoom,us(k,C,p,f),a.maxZoom)),e.emit("zoom")),(E||S)&&e.emit("viewport");var P=o.style;if(P&&P.length>0&&i){for(var D=0;D=0;t--){(0,e[t])()}e.splice(0,e.length)},c=a.length-1;c>=0;c--){var d=a[c],h=d._private;h.stopped?(a.splice(c,1),h.hooked=!1,h.playing=!1,h.started=!1,u(h.frames)):(h.playing||h.applying)&&(h.playing&&h.applying&&(h.applying=!1),h.started||hs(0,d,e),cs(t,d,e,n),h.applying&&(h.applying=!1),u(h.frames),null!=h.step&&h.step(e),d.completed()&&(a.splice(c,1),h.hooked=!1,h.playing=!1,h.started=!1,u(h.completes)),s=!0)}return n||0!==a.length||0!==o.length||r.push(t),s}for(var a=!1,o=0;o0?t.notify("draw",n):t.notify("draw")),n.unmerge(r),t.emit("step")}var fs={animate:Fi.animate(),animation:Fi.animation(),animated:Fi.animated(),clearQueue:Fi.clearQueue(),delay:Fi.delay(),delayAnimation:Fi.delayAnimation(),stop:Fi.stop(),addToAnimationPool:function(e){this.styleEnabled()&&this._private.aniEles.merge(e)},stopAnimationLoop:function(){this._private.animationsRunning=!1},startAnimationLoop:function(){var e=this;if(e._private.animationsRunning=!0,e.styleEnabled()){var t=e.renderer();t&&t.beforeRender?t.beforeRender((function(t,n){ps(n,e)}),t.beforeRenderPriorities.animations):function t(){e._private.animationsRunning&&xe((function(n){ps(n,e),t()}))}()}}},gs={qualifierCompare:function(e,t){return null==e||null==t?null==e&&null==t:e.sameText(t)},eventMatches:function(e,t,n){var r=t.qualifier;return null==r||e!==n.target&&k(n.target)&&r.matches(n.target)},addEventFields:function(e,t){t.cy=e,t.target=e},callbackContext:function(e,t,n){return null!=t.qualifier?n.target:e}},vs=function(e){return v(e)?new ka(e):e},ys={createEmitter:function(){var e=this._private;return e.emitter||(e.emitter=new xo(gs,this)),this},emitter:function(){return this._private.emitter},on:function(e,t,n){return this.emitter().on(e,vs(t),n),this},removeListener:function(e,t,n){return this.emitter().removeListener(e,vs(t),n),this},removeAllListeners:function(){return this.emitter().removeAllListeners(),this},one:function(e,t,n){return this.emitter().one(e,vs(t),n),this},once:function(e,t,n){return this.emitter().one(e,vs(t),n),this},emit:function(e,t){return this.emitter().emit(e,t),this},emitAndNotify:function(e,t){return this.emit(e),this.notify(e,t),this}};Fi.eventAliasesOn(ys);var ms={png:function(e){return e=e||{},this._private.renderer.png(e)},jpg:function(e){var t=this._private.renderer;return(e=e||{}).bg=e.bg||"#fff",t.jpg(e)}};ms.jpeg=ms.jpg;var bs={layout:function(e){if(null!=e)if(null!=e.name){var t=e.name,n=this.extension("layout",t);if(null!=n){var r;r=v(e.eles)?this.$(e.eles):null!=e.eles?e.eles:this.$();var i=new n(L({},e,{cy:this,eles:r}));return i}Ve("No such layout `"+t+"` found. Did you forget to import it and `cytoscape.use()` it?")}else Ve("A `name` must be specified to make a layout");else Ve("Layout options must be specified to make a layout")}};bs.createLayout=bs.makeLayout=bs.layout;var xs={notify:function(e,t){var n=this._private;if(this.batching()){n.batchNotifications=n.batchNotifications||{};var r=n.batchNotifications[e]=n.batchNotifications[e]||this.collection();null!=t&&r.merge(t)}else if(n.notificationsEnabled){var i=this.renderer();!this.destroyed()&&i&&i.notify(e,t)}},notifications:function(e){var t=this._private;return void 0===e?t.notificationsEnabled:(t.notificationsEnabled=!!e,this)},noNotifications:function(e){this.notifications(!1),e(),this.notifications(!0)},batching:function(){return this._private.batchCount>0},startBatch:function(){var e=this._private;return null==e.batchCount&&(e.batchCount=0),0===e.batchCount&&(e.batchStyleEles=this.collection(),e.batchNotifications={}),e.batchCount++,this},endBatch:function(){var e=this._private;if(0===e.batchCount)return this;if(e.batchCount--,0===e.batchCount){e.batchStyleEles.updateStyle();var t=this.renderer();Object.keys(e.batchNotifications).forEach((function(n){var r=e.batchNotifications[n];r.empty()?t.notify(n):t.notify(n,r)}))}return this},batch:function(e){return this.startBatch(),e(),this.endBatch(),this},batchData:function(e){var t=this;return this.batch((function(){for(var n=Object.keys(e),r=0;r0;)e.removeChild(e.childNodes[0]);this._private.renderer=null,this.mutableElements().forEach((function(e){var t=e._private;t.rscratch={},t.rstyle={},t.animation.current=[],t.animation.queue=[]}))},onRender:function(e){return this.on("render",e)},offRender:function(e){return this.off("render",e)}};Es.invalidateDimensions=Es.resize;var ks={collection:function(e,t){return v(e)?this.$(e):E(e)?e.collection():m(e)?(t||(t={}),new ts(this,e,t.unique,t.removed)):new ts(this)},nodes:function(e){var t=this.$((function(e){return e.isNode()}));return e?t.filter(e):t},edges:function(e){var t=this.$((function(e){return e.isEdge()}));return e?t.filter(e):t},$:function(e){var t=this._private.elements;return e?t.filter(e):t.spawnSelf()},mutableElements:function(){return this._private.elements}};ks.elements=ks.filter=ks.$;var Cs={};Cs.apply=function(e){for(var t=this._private.cy.collection(),n=0;n0;if(d||c&&h){var p=void 0;d&&h||d?p=l.properties:h&&(p=l.mappedProperties);for(var f=0;f1&&(g=1),s.color){var w=i.valueMin[0],E=i.valueMax[0],k=i.valueMin[1],C=i.valueMax[1],S=i.valueMin[2],P=i.valueMax[2],D=null==i.valueMin[3]?1:i.valueMin[3],T=null==i.valueMax[3]?1:i.valueMax[3],_=[Math.round(w+(E-w)*g),Math.round(k+(C-k)*g),Math.round(S+(P-S)*g),Math.round(D+(T-D)*g)];n={bypass:i.bypass,name:i.name,value:_,strValue:"rgb("+_[0]+", "+_[1]+", "+_[2]+")"}}else{if(!s.number)return!1;var M=i.valueMin+(i.valueMax-i.valueMin)*g;n=this.parse(i.name,M,i.bypass,"mapping")}if(!n)return f(),!1;n.mapping=i,i=n;break;case o.data:for(var B=i.field.split("."),N=d.data,z=0;z0&&a>0){for(var s={},l=!1,u=0;u0?e.delayAnimation(o).play().promise().then(t):t()})).then((function(){return e.animation({style:s,duration:a,easing:e.pstyle("transition-timing-function").value,queue:!1}).play().promise()})).then((function(){n.removeBypasses(e,i),e.emitAndNotify("style"),r.transitioning=!1}))}else r.transitioning&&(this.removeBypasses(e,i),e.emitAndNotify("style"),r.transitioning=!1)},Cs.checkTrigger=function(e,t,n,r,i,a){var o=this.properties[t],s=i(o);null!=s&&s(n,r)&&a(o)},Cs.checkZOrderTrigger=function(e,t,n,r){var i=this;this.checkTrigger(e,t,n,r,(function(e){return e.triggersZOrder}),(function(){i._private.cy.notify("zorder",e)}))},Cs.checkBoundsTrigger=function(e,t,n,r){this.checkTrigger(e,t,n,r,(function(e){return e.triggersBounds}),(function(i){e.dirtyCompoundBoundsCache(),e.dirtyBoundingBoxCache(),!i.triggersBoundsOfParallelBeziers||"curve-style"!==t||"bezier"!==n&&"bezier"!==r||e.parallelEdges().forEach((function(e){e.isBundledBezier()&&e.dirtyBoundingBoxCache()})),!i.triggersBoundsOfConnectedEdges||"display"!==t||"none"!==n&&"none"!==r||e.connectedEdges().forEach((function(e){e.dirtyBoundingBoxCache()}))}))},Cs.checkTriggers=function(e,t,n,r){e.dirtyStyleCache(),this.checkZOrderTrigger(e,t,n,r),this.checkBoundsTrigger(e,t,n,r)};var Ss={applyBypass:function(e,t,n,r){var i=[];if("*"===t||"**"===t){if(void 0!==n)for(var a=0;at.length?i.substr(t.length):""}function o(){n=n.length>r.length?n.substr(r.length):""}for(i=i.replace(/[/][*](\s|.)+?[*][/]/g,"");;){if(i.match(/^\s*$/))break;var s=i.match(/^\s*((?:.|\s)+?)\s*\{((?:.|\s)+?)\}/);if(!s){je("Halting stylesheet parsing: String stylesheet contains more to parse but no selector and block found in: "+i);break}t=s[0];var l=s[1];if("core"!==l)if(new ka(l).invalid){je("Skipping parsing of block: Invalid selector found in string stylesheet: "+l),a();continue}var u=s[2],c=!1;n=u;for(var d=[];;){if(n.match(/^\s*$/))break;var h=n.match(/^\s*(.+?)\s*:\s*(.+?)(?:\s*;|\s*$)/);if(!h){je("Skipping parsing of block: Invalid formatting of style property and value definitions found in:"+u),c=!0;break}r=h[0];var p=h[1],f=h[2];if(this.properties[p])this.parse(p,f)?(d.push({name:p,val:f}),o()):(je("Skipping property: Invalid property definition in: "+r),o());else je("Skipping property: Invalid property name in: "+r),o()}if(c){a();break}this.selector(l);for(var g=0;g=7&&"d"===t[0]&&(l=new RegExp(o.data.regex).exec(t))){if(n)return!1;var d=o.data;return{name:e,value:l,strValue:""+t,mapped:d,field:l[1],bypass:n}}if(t.length>=10&&"m"===t[0]&&(u=new RegExp(o.mapData.regex).exec(t))){if(n)return!1;if(c.multiple)return!1;var h=o.mapData;if(!c.color&&!c.number)return!1;var p=this.parse(e,u[4]);if(!p||p.mapped)return!1;var f=this.parse(e,u[5]);if(!f||f.mapped)return!1;if(p.pfValue===f.pfValue||p.strValue===f.strValue)return je("`"+e+": "+t+"` is not a valid mapper because the output range is zero; converting to `"+e+": "+p.strValue+"`"),this.parse(e,p.strValue);if(c.color){var g=p.value,b=f.value;if(!(g[0]!==b[0]||g[1]!==b[1]||g[2]!==b[2]||g[3]!==b[3]&&(null!=g[3]&&1!==g[3]||null!=b[3]&&1!==b[3])))return!1}return{name:e,value:u,strValue:""+t,mapped:h,field:u[1],fieldMin:parseFloat(u[2]),fieldMax:parseFloat(u[3]),valueMin:p.value,valueMax:f.value,bypass:n}}}if(c.multiple&&"multiple"!==r){var w;if(w=s?t.split(/\s+/):m(t)?t:[t],c.evenMultiple&&w.length%2!=0)return null;for(var E=[],k=[],C=[],S="",P=!1,D=0;D0?" ":"")+T.strValue}return c.validate&&!c.validate(E,k)?null:c.singleEnum&&P?1===E.length&&v(E[0])?{name:e,value:E[0],strValue:E[0],bypass:n}:null:{name:e,value:E,pfValue:C,strValue:S,bypass:n,units:k}}var _,B,N=function(){for(var r=0;rc.max||c.strictMax&&t===c.max))return null;var V={name:e,value:t,strValue:""+t+(z||""),units:z,bypass:n};return c.unitless||"px"!==z&&"em"!==z?V.pfValue=t:V.pfValue="px"!==z&&z?this.getEmSizeInPixels()*t:t,"ms"!==z&&"s"!==z||(V.pfValue="ms"===z?t:1e3*t),"deg"!==z&&"rad"!==z||(V.pfValue="rad"===z?t:(_=t,Math.PI*_/180)),"%"===z&&(V.pfValue=t/100),V}if(c.propList){var F=[],j=""+t;if("none"===j);else{for(var q=j.split(/\s*,\s*|\s+/),Y=0;Y0&&l>0&&!isNaN(n.w)&&!isNaN(n.h)&&n.w>0&&n.h>0)return{zoom:o=(o=(o=Math.min((s-2*t)/n.w,(l-2*t)/n.h))>this._private.maxZoom?this._private.maxZoom:o)=n.minZoom&&(n.maxZoom=t),this},minZoom:function(e){return void 0===e?this._private.minZoom:this.zoomRange({min:e})},maxZoom:function(e){return void 0===e?this._private.maxZoom:this.zoomRange({max:e})},getZoomedViewport:function(e){var t,n,r=this._private,i=r.pan,a=r.zoom,o=!1;if(r.zoomingEnabled||(o=!0),x(e)?n=e:b(e)&&(n=e.level,null!=e.position?t=yt(e.position,a,i):null!=e.renderedPosition&&(t=e.renderedPosition),null==t||r.panningEnabled||(o=!0)),n=(n=n>r.maxZoom?r.maxZoom:n)t.maxZoom||!t.zoomingEnabled?a=!0:(t.zoom=s,i.push("zoom"))}if(r&&(!a||!e.cancelOnFailedZoom)&&t.panningEnabled){var l=e.pan;x(l.x)&&(t.pan.x=l.x,o=!1),x(l.y)&&(t.pan.y=l.y,o=!1),o||i.push("pan")}return i.length>0&&(i.push("viewport"),this.emit(i.join(" ")),this.notify("viewport")),this},center:function(e){var t=this.getCenterPan(e);return t&&(this._private.pan=t,this.emit("pan viewport"),this.notify("viewport")),this},getCenterPan:function(e,t){if(this._private.panningEnabled){if(v(e)){var n=e;e=this.mutableElements().filter(n)}else E(e)||(e=this.mutableElements());if(0!==e.length){var r=e.boundingBox(),i=this.width(),a=this.height();return{x:(i-(t=void 0===t?this._private.zoom:t)*(r.x1+r.x2))/2,y:(a-t*(r.y1+r.y2))/2}}}},reset:function(){return this._private.panningEnabled&&this._private.zoomingEnabled?(this.viewport({pan:{x:0,y:0},zoom:1}),this):this},invalidateSize:function(){this._private.sizeCache=null},size:function(){var e,t,n=this._private,r=n.container,i=this;return n.sizeCache=n.sizeCache||(r?(e=i.window().getComputedStyle(r),t=function(t){return parseFloat(e.getPropertyValue(t))},{width:r.clientWidth-t("padding-left")-t("padding-right"),height:r.clientHeight-t("padding-top")-t("padding-bottom")}):{width:1,height:1})},width:function(){return this.size().width},height:function(){return this.size().height},extent:function(){var e=this._private.pan,t=this._private.zoom,n=this.renderedExtent(),r={x1:(n.x1-e.x)/t,x2:(n.x2-e.x)/t,y1:(n.y1-e.y)/t,y2:(n.y2-e.y)/t};return r.w=r.x2-r.x1,r.h=r.y2-r.y1,r},renderedExtent:function(){var e=this.width(),t=this.height();return{x1:0,y1:0,x2:e,y2:t,w:e,h:t}},multiClickDebounceTime:function(e){return e?(this._private.multiClickDebounceTime=e,this):this._private.multiClickDebounceTime}};As.centre=As.center,As.autolockNodes=As.autolock,As.autoungrabifyNodes=As.autoungrabify;var Ls={data:Fi.data({field:"data",bindingEvent:"data",allowBinding:!0,allowSetting:!0,settingEvent:"data",settingTriggersEvent:!0,triggerFnName:"trigger",allowGetting:!0,updateStyle:!0}),removeData:Fi.removeData({field:"data",event:"data",triggerFnName:"trigger",triggerEvent:!0,updateStyle:!0}),scratch:Fi.data({field:"scratch",bindingEvent:"scratch",allowBinding:!0,allowSetting:!0,settingEvent:"scratch",settingTriggersEvent:!0,triggerFnName:"trigger",allowGetting:!0,updateStyle:!0}),removeScratch:Fi.removeData({field:"scratch",event:"scratch",triggerFnName:"trigger",triggerEvent:!0,updateStyle:!0})};Ls.attr=Ls.data,Ls.removeAttr=Ls.removeData;var Os=function(e){var t=this,n=(e=L({},e)).container;n&&!w(n)&&w(n[0])&&(n=n[0]);var r=n?n._cyreg:null;(r=r||{})&&r.cy&&(r.cy.destroy(),r={});var i=r.readies=r.readies||[];n&&(n._cyreg=r),r.cy=t;var a=void 0!==u&&void 0!==n&&!e.headless,o=e;o.layout=L({name:a?"grid":"null"},o.layout),o.renderer=L({name:a?"canvas":"null"},o.renderer);var s=function(e,t,n){return void 0!==t?t:void 0!==n?n:e},l=this._private={container:n,ready:!1,options:o,elements:new ts(this),listeners:[],aniEles:new ts(this),data:o.data||{},scratch:{},layout:null,renderer:null,destroyed:!1,notificationsEnabled:!0,minZoom:1e-50,maxZoom:1e50,zoomingEnabled:s(!0,o.zoomingEnabled),userZoomingEnabled:s(!0,o.userZoomingEnabled),panningEnabled:s(!0,o.panningEnabled),userPanningEnabled:s(!0,o.userPanningEnabled),boxSelectionEnabled:s(!0,o.boxSelectionEnabled),autolock:s(!1,o.autolock,o.autolockNodes),autoungrabify:s(!1,o.autoungrabify,o.autoungrabifyNodes),autounselectify:s(!1,o.autounselectify),styleEnabled:void 0===o.styleEnabled?a:o.styleEnabled,zoom:x(o.zoom)?o.zoom:1,pan:{x:b(o.pan)&&x(o.pan.x)?o.pan.x:0,y:b(o.pan)&&x(o.pan.y)?o.pan.y:0},animation:{current:[],queue:[]},hasCompoundNodes:!1,multiClickDebounceTime:s(250,o.multiClickDebounceTime)};this.createEmitter(),this.selectionType(o.selectionType),this.zoomRange({min:o.minZoom,max:o.maxZoom});l.styleEnabled&&t.setStyle([]);var c=L({},o,o.renderer);t.initRenderer(c);!function(e,t){if(e.some(T))return vr.all(e).then(t);t(e)}([o.style,o.elements],(function(e){var n=e[0],a=e[1];l.styleEnabled&&t.style().append(n),function(e,n,r){t.notifications(!1);var i=t.mutableElements();i.length>0&&i.remove(),null!=e&&(b(e)||m(e))&&t.add(e),t.one("layoutready",(function(e){t.notifications(!0),t.emit(e),t.one("load",n),t.emitAndNotify("load")})).one("layoutstop",(function(){t.one("done",r),t.emit("done")}));var a=L({},t._private.options.layout);a.eles=t.elements(),t.layout(a).run()}(a,(function(){t.startAnimationLoop(),l.ready=!0,y(o.ready)&&t.on("ready",o.ready);for(var e=0;e0,u=_t(n.boundingBox?n.boundingBox:{x1:0,y1:0,w:r.width(),h:r.height()});if(E(n.roots))e=n.roots;else if(m(n.roots)){for(var c=[],d=0;d0;){var N=_.shift(),z=T(N,M);if(z)N.outgoers().filter((function(e){return e.isNode()&&i.has(e)})).forEach(B);else if(null===z){je("Detected double maximal shift for node `"+N.id()+"`. Bailing maximal adjustment due to cycle. Use `options.maximal: true` only on DAGs.");break}}}D();var I=0;if(n.avoidOverlap)for(var L=0;L0&&b[0].length<=3?l/2:0),d=2*Math.PI/b[r].length*i;return 0===r&&1===b[0].length&&(c=1),{x:G+c*Math.cos(d),y:U+c*Math.sin(d)}}return{x:G+(i+1-(a+1)/2)*o,y:(r+1)*s}})),this};var Xs={fit:!0,padding:30,boundingBox:void 0,avoidOverlap:!0,nodeDimensionsIncludeLabels:!1,spacingFactor:void 0,radius:void 0,startAngle:1.5*Math.PI,sweep:void 0,clockwise:!0,sort:void 0,animate:!1,animationDuration:500,animationEasing:void 0,animateFilter:function(e,t){return!0},ready:void 0,stop:void 0,transform:function(e,t){return t}};function Ws(e){this.options=L({},Xs,e)}Ws.prototype.run=function(){var e=this.options,t=e,n=e.cy,r=t.eles,i=void 0!==t.counterclockwise?!t.counterclockwise:t.clockwise,a=r.nodes().not(":parent");t.sort&&(a=a.sort(t.sort));for(var o,s=_t(t.boundingBox?t.boundingBox:{x1:0,y1:0,w:n.width(),h:n.height()}),l=s.x1+s.w/2,u=s.y1+s.h/2,c=(void 0===t.sweep?2*Math.PI-2*Math.PI/a.length:t.sweep)/Math.max(1,a.length-1),d=0,h=0;h1&&t.avoidOverlap){d*=1.75;var v=Math.cos(c)-Math.cos(0),y=Math.sin(c)-Math.sin(0),m=Math.sqrt(d*d/(v*v+y*y));o=Math.max(m,o)}return r.nodes().layoutPositions(this,t,(function(e,n){var r=t.startAngle+n*c*(i?1:-1),a=o*Math.cos(r),s=o*Math.sin(r);return{x:l+a,y:u+s}})),this};var Hs,Ks={fit:!0,padding:30,startAngle:1.5*Math.PI,sweep:void 0,clockwise:!0,equidistant:!1,minNodeSpacing:10,boundingBox:void 0,avoidOverlap:!0,nodeDimensionsIncludeLabels:!1,height:void 0,width:void 0,spacingFactor:void 0,concentric:function(e){return e.degree()},levelWidth:function(e){return e.maxDegree()/4},animate:!1,animationDuration:500,animationEasing:void 0,animateFilter:function(e,t){return!0},ready:void 0,stop:void 0,transform:function(e,t){return t}};function Gs(e){this.options=L({},Ks,e)}Gs.prototype.run=function(){for(var e=this.options,t=e,n=void 0!==t.counterclockwise?!t.counterclockwise:t.clockwise,r=e.cy,i=t.eles,a=i.nodes().not(":parent"),o=_t(t.boundingBox?t.boundingBox:{x1:0,y1:0,w:r.width(),h:r.height()}),s=o.x1+o.w/2,l=o.y1+o.h/2,u=[],c=0,d=0;d0)Math.abs(m[0].value-x.value)>=v&&(m=[],y.push(m));m.push(x)}var w=c+t.minNodeSpacing;if(!t.avoidOverlap){var E=y.length>0&&y[0].length>1,k=(Math.min(o.w,o.h)/2-w)/(y.length+E?1:0);w=Math.min(w,k)}for(var C=0,S=0;S1&&t.avoidOverlap){var _=Math.cos(T)-Math.cos(0),M=Math.sin(T)-Math.sin(0),B=Math.sqrt(w*w/(_*_+M*M));C=Math.max(B,C)}P.r=C,C+=w}if(t.equidistant){for(var N=0,z=0,I=0;I=e.numIter)&&(rl(r,e),r.temperature=r.temperature*e.coolingFactor,!(r.temperature=e.animationThreshold&&a(),xe(t)):(gl(r,e),s())}()}else{for(;u;)u=o(l),l++;gl(r,e),s()}return this},Zs.prototype.stop=function(){return this.stopped=!0,this.thread&&this.thread.stop(),this.emit("layoutstop"),this},Zs.prototype.destroy=function(){return this.thread&&this.thread.stop(),this};var $s=function(e,t,n){for(var r=n.eles.edges(),i=n.eles.nodes(),a=_t(n.boundingBox?n.boundingBox:{x1:0,y1:0,w:e.width(),h:e.height()}),o={isCompound:e.hasCompoundNodes(),layoutNodes:[],idToIndex:{},nodeSize:i.size(),graphSet:[],indexToGraph:[],layoutEdges:[],edgeSize:r.size(),temperature:n.initialTemp,clientWidth:a.w,clientHeight:a.h,boundingBox:a},s=n.eles.components(),l={},u=0;u0){o.graphSet.push(E);for(u=0;ur.count?0:r.graph},Js=function e(t,n,r,i){var a=i.graphSet[r];if(-10)var s=(u=r.nodeOverlap*o)*i/(g=Math.sqrt(i*i+a*a)),l=u*a/g;else{var u,c=ll(e,i,a),d=ll(t,-1*i,-1*a),h=d.x-c.x,p=d.y-c.y,f=h*h+p*p,g=Math.sqrt(f);s=(u=(e.nodeRepulsion+t.nodeRepulsion)/f)*h/g,l=u*p/g}e.isLocked||(e.offsetX-=s,e.offsetY-=l),t.isLocked||(t.offsetX+=s,t.offsetY+=l)}},sl=function(e,t,n,r){if(n>0)var i=e.maxX-t.minX;else i=t.maxX-e.minX;if(r>0)var a=e.maxY-t.minY;else a=t.maxY-e.minY;return i>=0&&a>=0?Math.sqrt(i*i+a*a):0},ll=function(e,t,n){var r=e.positionX,i=e.positionY,a=e.height||1,o=e.width||1,s=n/t,l=a/o,u={};return 0===t&&0n?(u.x=r,u.y=i+a/2,u):0t&&-1*l<=s&&s<=l?(u.x=r-o/2,u.y=i-o*n/2/t,u):0=l)?(u.x=r+a*t/2/n,u.y=i+a/2,u):0>n&&(s<=-1*l||s>=l)?(u.x=r-a*t/2/n,u.y=i-a/2,u):u},ul=function(e,t){for(var n=0;n1){var f=t.gravity*d/p,g=t.gravity*h/p;c.offsetX+=f,c.offsetY+=g}}}}},dl=function(e,t){var n=[],r=0,i=-1;for(n.push.apply(n,e.graphSet[0]),i+=e.graphSet[0].length;r<=i;){var a=n[r++],o=e.idToIndex[a],s=e.layoutNodes[o],l=s.children;if(0n)var i={x:n*e/r,y:n*t/r};else i={x:e,y:t};return i},fl=function e(t,n){var r=t.parentId;if(null!=r){var i=n.layoutNodes[n.idToIndex[r]],a=!1;return(null==i.maxX||t.maxX+i.padRight>i.maxX)&&(i.maxX=t.maxX+i.padRight,a=!0),(null==i.minX||t.minX-i.padLefti.maxY)&&(i.maxY=t.maxY+i.padBottom,a=!0),(null==i.minY||t.minY-i.padTopf&&(d+=p+t.componentSpacing,c=0,h=0,p=0)}}},vl={fit:!0,padding:30,boundingBox:void 0,avoidOverlap:!0,avoidOverlapPadding:10,nodeDimensionsIncludeLabels:!1,spacingFactor:void 0,condense:!1,rows:void 0,cols:void 0,position:function(e){},sort:void 0,animate:!1,animationDuration:500,animationEasing:void 0,animateFilter:function(e,t){return!0},ready:void 0,stop:void 0,transform:function(e,t){return t}};function yl(e){this.options=L({},vl,e)}yl.prototype.run=function(){var e=this.options,t=e,n=e.cy,r=t.eles,i=r.nodes().not(":parent");t.sort&&(i=i.sort(t.sort));var a=_t(t.boundingBox?t.boundingBox:{x1:0,y1:0,w:n.width(),h:n.height()});if(0===a.h||0===a.w)r.nodes().layoutPositions(this,t,(function(e){return{x:a.x1,y:a.y1}}));else{var o=i.size(),s=Math.sqrt(o*a.h/a.w),l=Math.round(s),u=Math.round(a.w/a.h*s),c=function(e){if(null==e)return Math.min(l,u);Math.min(l,u)==l?l=e:u=e},d=function(e){if(null==e)return Math.max(l,u);Math.max(l,u)==l?l=e:u=e},h=t.rows,p=null!=t.cols?t.cols:t.columns;if(null!=h&&null!=p)l=h,u=p;else if(null!=h&&null==p)l=h,u=Math.ceil(o/l);else if(null==h&&null!=p)u=p,l=Math.ceil(o/u);else if(u*l>o){var f=c(),g=d();(f-1)*g>=o?c(f-1):(g-1)*f>=o&&d(g-1)}else for(;u*l=o?d(y+1):c(v+1)}var m=a.w/u,b=a.h/l;if(t.condense&&(m=0,b=0),t.avoidOverlap)for(var x=0;x=u&&(B=0,M++)},z={},I=0;I(r=qt(e,t,x[w],x[w+1],x[w+2],x[w+3])))return v(n,r),!0}else if("bezier"===a.edgeType||"multibezier"===a.edgeType||"self"===a.edgeType||"compound"===a.edgeType)for(x=a.allpts,w=0;w+5(r=jt(e,t,x[w],x[w+1],x[w+2],x[w+3],x[w+4],x[w+5])))return v(n,r),!0;m=m||i.source,b=b||i.target;var E=o.getArrowWidth(l,c),k=[{name:"source",x:a.arrowStartX,y:a.arrowStartY,angle:a.srcArrowAngle},{name:"target",x:a.arrowEndX,y:a.arrowEndY,angle:a.tgtArrowAngle},{name:"mid-source",x:a.midX,y:a.midY,angle:a.midsrcArrowAngle},{name:"mid-target",x:a.midX,y:a.midY,angle:a.midtgtArrowAngle}];for(w=0;w0&&(y(m),y(b))}function b(e,t,n){return Ue(e,t,n)}function x(n,r){var i,a=n._private,o=f;i=r?r+"-":"",n.boundingBox();var s=a.labelBounds[r||"main"],l=n.pstyle(i+"label").value;if("yes"===n.pstyle("text-events").strValue&&l){var u=b(a.rscratch,"labelX",r),c=b(a.rscratch,"labelY",r),d=b(a.rscratch,"labelAngle",r),h=n.pstyle(i+"text-margin-x").pfValue,p=n.pstyle(i+"text-margin-y").pfValue,g=s.x1-o-h,y=s.x2+o-h,m=s.y1-o-p,x=s.y2+o-p;if(d){var w=Math.cos(d),E=Math.sin(d),k=function(e,t){return{x:(e-=u)*w-(t-=c)*E+u,y:e*E+t*w+c}},C=k(g,m),S=k(g,x),P=k(y,m),D=k(y,x),T=[C.x+h,C.y+p,P.x+h,P.y+p,D.x+h,D.y+p,S.x+h,S.y+p];if(Yt(e,t,T))return v(n),!0}else if(Lt(s,e,t))return v(n),!0}}n&&(l=l.interactive);for(var w=l.length-1;w>=0;w--){var E=l[w];E.isNode()?y(E)||x(E):m(E)||x(E)||x(E,"source")||x(E,"target")}return u},getAllInBox:function(e,t,n,r){for(var i,a,o=this.getCachedZSortedEles().interactive,s=[],l=Math.min(e,n),u=Math.max(e,n),c=Math.min(t,r),d=Math.max(t,r),h=_t({x1:e=l,y1:t=c,x2:n=u,y2:r=d}),p=0;p0?-(Math.PI-a.ang):Math.PI+a.ang),Zl(t,n,Ul),zl=Gl.nx*Ul.ny-Gl.ny*Ul.nx,Il=Gl.nx*Ul.nx-Gl.ny*-Ul.ny,Ol=Math.asin(Math.max(-1,Math.min(1,zl))),Math.abs(Ol)<1e-6)return Bl=t.x,Nl=t.y,void(Vl=jl=0);Al=1,Ll=!1,Il<0?Ol<0?Ol=Math.PI+Ol:(Ol=Math.PI-Ol,Al=-1,Ll=!0):Ol>0&&(Al=-1,Ll=!0),jl=void 0!==t.radius?t.radius:r,Rl=Ol/2,ql=Math.min(Gl.len/2,Ul.len/2),i?(Fl=Math.abs(Math.cos(Rl)*jl/Math.sin(Rl)))>ql?(Fl=ql,Vl=Math.abs(Fl*Math.sin(Rl)/Math.cos(Rl))):Vl=jl:(Fl=Math.min(ql,jl),Vl=Math.abs(Fl*Math.sin(Rl)/Math.cos(Rl))),Wl=t.x+Ul.nx*Fl,Hl=t.y+Ul.ny*Fl,Bl=Wl-Ul.ny*Vl*Al,Nl=Hl+Ul.nx*Vl*Al,Yl=t.x+Gl.nx*Fl,Xl=t.y+Gl.ny*Fl,Kl=t};function Ql(e,t){0===t.radius?e.lineTo(t.cx,t.cy):e.arc(t.cx,t.cy,t.radius,t.startAngle,t.endAngle,t.counterClockwise)}function Jl(e,t,n,r){var i=!(arguments.length>4&&void 0!==arguments[4])||arguments[4];return 0===r||0===t.radius?{cx:t.x,cy:t.y,radius:0,startX:t.x,startY:t.y,stopX:t.x,stopY:t.y,startAngle:void 0,endAngle:void 0,counterClockwise:void 0}:($l(e,t,n,r,i),{cx:Bl,cy:Nl,radius:Vl,startX:Yl,startY:Xl,stopX:Wl,stopY:Hl,startAngle:Gl.ang+Math.PI/2*Al,endAngle:Ul.ang-Math.PI/2*Al,counterClockwise:Ll})}var eu={};function tu(e){var t=[];if(null!=e){for(var n=0;n0?Math.max(e-t,0):Math.min(e+t,0)},w=x(m,v),E=x(b,y),k=!1;"auto"===c?u=Math.abs(w)>Math.abs(E)?"horizontal":"vertical":"upward"===c||"downward"===c?(u="vertical",k=!0):"leftward"!==c&&"rightward"!==c||(u="horizontal",k=!0);var C,S="vertical"===u,P=S?E:w,D=S?b:m,T=Et(D),_=!1;(k&&(h||f)||!("downward"===c&&D<0||"upward"===c&&D>0||"leftward"===c&&D>0||"rightward"===c&&D<0)||(P=(T*=-1)*Math.abs(P),_=!0),h)?C=(p<0?1+p:p)*P:C=(p<0?P:0)+p*T;var M=function(e){return Math.abs(e)=Math.abs(P)},B=M(C),N=M(Math.abs(P)-Math.abs(C));if((B||N)&&!_)if(S){var z=Math.abs(D)<=a/2,I=Math.abs(m)<=o/2;if(z){var A=(r.x1+r.x2)/2,L=r.y1,O=r.y2;n.segpts=[A,L,A,O]}else if(I){var R=(r.y1+r.y2)/2,V=r.x1,F=r.x2;n.segpts=[V,R,F,R]}else n.segpts=[r.x1,r.y2]}else{var j=Math.abs(D)<=i/2,q=Math.abs(b)<=s/2;if(j){var Y=(r.y1+r.y2)/2,X=r.x1,W=r.x2;n.segpts=[X,Y,W,Y]}else if(q){var H=(r.x1+r.x2)/2,K=r.y1,G=r.y2;n.segpts=[H,K,H,G]}else n.segpts=[r.x2,r.y1]}else if(S){var U=r.y1+C+(l?a/2*T:0),Z=r.x1,$=r.x2;n.segpts=[Z,U,$,U]}else{var Q=r.x1+C+(l?i/2*T:0),J=r.y1,ee=r.y2;n.segpts=[Q,J,Q,ee]}if(n.isRound){var te=e.pstyle("taxi-radius").value,ne="arc-radius"===e.pstyle("radius-type").value[0];n.radii=new Array(n.segpts.length/2).fill(te),n.isArcRadius=new Array(n.segpts.length/2).fill(ne)}},eu.tryToCorrectInvalidPoints=function(e,t){var n=e._private.rscratch;if("bezier"===n.edgeType){var r=t.srcPos,i=t.tgtPos,a=t.srcW,o=t.srcH,s=t.tgtW,l=t.tgtH,u=t.srcShape,c=t.tgtShape,d=t.srcCornerRadius,h=t.tgtCornerRadius,p=t.srcRs,f=t.tgtRs,g=!x(n.startX)||!x(n.startY),v=!x(n.arrowStartX)||!x(n.arrowStartY),y=!x(n.endX)||!x(n.endY),m=!x(n.arrowEndX)||!x(n.arrowEndY),b=3*(this.getArrowWidth(e.pstyle("width").pfValue,e.pstyle("arrow-scale").value)*this.arrowShapeWidth),w=kt({x:n.ctrlpts[0],y:n.ctrlpts[1]},{x:n.startX,y:n.startY}),E=wh.poolIndex()){var p=d;d=h,h=p}var f=s.srcPos=d.position(),g=s.tgtPos=h.position(),v=s.srcW=d.outerWidth(),y=s.srcH=d.outerHeight(),m=s.tgtW=h.outerWidth(),b=s.tgtH=h.outerHeight(),w=s.srcShape=n.nodeShapes[t.getNodeShape(d)],E=s.tgtShape=n.nodeShapes[t.getNodeShape(h)],k=s.srcCornerRadius="auto"===d.pstyle("corner-radius").value?"auto":d.pstyle("corner-radius").pfValue,C=s.tgtCornerRadius="auto"===h.pstyle("corner-radius").value?"auto":h.pstyle("corner-radius").pfValue,S=s.tgtRs=h._private.rscratch,P=s.srcRs=d._private.rscratch;s.dirCounts={north:0,west:0,south:0,east:0,northwest:0,southwest:0,northeast:0,southeast:0};for(var D=0;D0){var H=u,K=Ct(H,bt(t)),G=Ct(H,bt(W)),U=K;if(G2)Ct(H,{x:W[2],y:W[3]})0){var le=c,ue=Ct(le,bt(t)),ce=Ct(le,bt(se)),de=ue;if(ce2)Ct(le,{x:se[2],y:se[3]})=c||b){d={cp:v,segment:m};break}}if(d)break}var x=d.cp,w=d.segment,E=(c-p)/w.length,k=w.t1-w.t0,C=u?w.t0+k*E:w.t1-k*E;C=Tt(0,C,1),t=Dt(x.p0,x.p1,x.p2,C),l=function(e,t,n,r){var i=Tt(0,r-.001,1),a=Tt(0,r+.001,1),o=Dt(e,t,n,i),s=Dt(e,t,n,a);return su(o,s)}(x.p0,x.p1,x.p2,C);break;case"straight":case"segments":case"haystack":for(var S,P,D,T,_=0,M=r.allpts.length,B=0;B+3=c));B+=2);var N=(c-P)/S;N=Tt(0,N,1),t=function(e,t,n,r){var i=t.x-e.x,a=t.y-e.y,o=kt(e,t),s=i/o,l=a/o;return n=null==n?0:n,r=null!=r?r:n*o,{x:e.x+s*r,y:e.y+l*r}}(D,T,N),l=su(D,T)}o("labelX",s,t.x),o("labelY",s,t.y),o("labelAutoAngle",s,l)}};l("source"),l("target"),this.applyLabelDimensions(e)}},au.applyLabelDimensions=function(e){this.applyPrefixedLabelDimensions(e),e.isEdge()&&(this.applyPrefixedLabelDimensions(e,"source"),this.applyPrefixedLabelDimensions(e,"target"))},au.applyPrefixedLabelDimensions=function(e,t){var n=e._private,r=this.getLabelText(e,t),i=this.calculateLabelDimensions(e,r),a=e.pstyle("line-height").pfValue,o=e.pstyle("text-wrap").strValue,s=Ue(n.rscratch,"labelWrapCachedLines",t)||[],l="wrap"!==o?1:Math.max(s.length,1),u=i.height/l,c=u*a,d=i.width,h=i.height+(l-1)*(a-1)*u;Ze(n.rstyle,"labelWidth",t,d),Ze(n.rscratch,"labelWidth",t,d),Ze(n.rstyle,"labelHeight",t,h),Ze(n.rscratch,"labelHeight",t,h),Ze(n.rscratch,"labelLineHeight",t,c)},au.getLabelText=function(e,t){var n=e._private,r=t?t+"-":"",i=e.pstyle(r+"label").strValue,a=e.pstyle("text-transform").value,o=function(e,r){return r?(Ze(n.rscratch,e,t,r),r):Ue(n.rscratch,e,t)};if(!i)return"";"none"==a||("uppercase"==a?i=i.toUpperCase():"lowercase"==a&&(i=i.toLowerCase()));var s=e.pstyle("text-wrap").value;if("wrap"===s){var u=o("labelKey");if(null!=u&&o("labelWrapKey")===u)return o("labelWrapCachedText");for(var c=i.split("\n"),d=e.pstyle("text-max-width").pfValue,h="anywhere"===e.pstyle("text-overflow-wrap").value,p=[],f=/[\s\u200b]+|$/g,g=0;gd){var b,x="",w=0,E=l(v.matchAll(f));try{for(E.s();!(b=E.n()).done;){var k=b.value,C=k[0],S=v.substring(w,k.index);w=k.index+C.length;var P=0===x.length?S:x+S+C;this.calculateLabelDimensions(e,P).width<=d?x+=S+C:(x&&p.push(x),x=S+C)}}catch(e){E.e(e)}finally{E.f()}x.match(/^[\s\u200b]+$/)||p.push(x)}else p.push(v)}o("labelWrapCachedLines",p),i=o("labelWrapCachedText",p.join("\n")),o("labelWrapKey",u)}else if("ellipsis"===s){var D=e.pstyle("text-max-width").pfValue,T="",_=!1;if(this.calculateLabelDimensions(e,i).widthD)break;T+=i[M],M===i.length-1&&(_=!0)}return _||(T+="…"),T}return i},au.getLabelJustification=function(e){var t=e.pstyle("text-justification").strValue,n=e.pstyle("text-halign").strValue;if("auto"!==t)return t;if(!e.isNode())return"center";switch(n){case"left":return"right";case"right":return"left";default:return"center"}},au.calculateLabelDimensions=function(e,t){var n=this,r=n.cy.window().document,i=Te(t,e._private.labelDimsKey),a=n.labelDimCache||(n.labelDimCache=[]),o=a[i];if(null!=o)return o;var s=e.pstyle("font-style").strValue,l=e.pstyle("font-size").pfValue,u=e.pstyle("font-family").strValue,c=e.pstyle("font-weight").strValue,d=this.labelCalcCanvas,h=this.labelCalcCanvasContext;if(!d){d=this.labelCalcCanvas=r.createElement("canvas"),h=this.labelCalcCanvasContext=d.getContext("2d");var p=d.style;p.position="absolute",p.left="-9999px",p.top="-9999px",p.zIndex="-1",p.visibility="hidden",p.pointerEvents="none"}h.font="".concat(s," ").concat(c," ").concat(l,"px ").concat(u);for(var f=0,g=0,v=t.split("\n"),y=0;y1&&void 0!==arguments[1])||arguments[1];if(t.merge(e),n)for(var r=0;r=e.desktopTapThreshold2}var D=i(t);v&&(e.hoverData.tapholdCancelled=!0);n=!0,r(g,["mousemove","vmousemove","tapdrag"],t,{x:c[0],y:c[1]});var T=function(){e.data.bgActivePosistion=void 0,e.hoverData.selecting||o.emit({originalEvent:t,type:"boxstart",position:{x:c[0],y:c[1]}}),f[4]=1,e.hoverData.selecting=!0,e.redrawHint("select",!0),e.redraw()};if(3===e.hoverData.which){if(v){var _={originalEvent:t,type:"cxtdrag",position:{x:c[0],y:c[1]}};m?m.emit(_):o.emit(_),e.hoverData.cxtDragged=!0,e.hoverData.cxtOver&&g===e.hoverData.cxtOver||(e.hoverData.cxtOver&&e.hoverData.cxtOver.emit({originalEvent:t,type:"cxtdragout",position:{x:c[0],y:c[1]}}),e.hoverData.cxtOver=g,g&&g.emit({originalEvent:t,type:"cxtdragover",position:{x:c[0],y:c[1]}}))}}else if(e.hoverData.dragging){if(n=!0,o.panningEnabled()&&o.userPanningEnabled()){var M;if(e.hoverData.justStartedPan){var B=e.hoverData.mdownPos;M={x:(c[0]-B[0])*s,y:(c[1]-B[1])*s},e.hoverData.justStartedPan=!1}else M={x:b[0]*s,y:b[1]*s};o.panBy(M),o.emit("dragpan"),e.hoverData.dragged=!0}c=e.projectIntoViewport(t.clientX,t.clientY)}else if(1!=f[4]||null!=m&&!m.pannable()){if(m&&m.pannable()&&m.active()&&m.unactivate(),m&&m.grabbed()||g==y||(y&&r(y,["mouseout","tapdragout"],t,{x:c[0],y:c[1]}),g&&r(g,["mouseover","tapdragover"],t,{x:c[0],y:c[1]}),e.hoverData.last=g),m)if(v){if(o.boxSelectionEnabled()&&D)m&&m.grabbed()&&(d(w),m.emit("freeon"),w.emit("free"),e.dragData.didDrag&&(m.emit("dragfreeon"),w.emit("dragfree"))),T();else if(m&&m.grabbed()&&e.nodeIsDraggable(m)){var N=!e.dragData.didDrag;N&&e.redrawHint("eles",!0),e.dragData.didDrag=!0,e.hoverData.draggingEles||u(w,{inDragLayer:!0});var z={x:0,y:0};if(x(b[0])&&x(b[1])&&(z.x+=b[0],z.y+=b[1],N)){var I=e.hoverData.dragDelta;I&&x(I[0])&&x(I[1])&&(z.x+=I[0],z.y+=I[1])}e.hoverData.draggingEles=!0,w.silentShift(z).emit("position drag"),e.redrawHint("drag",!0),e.redraw()}}else!function(){var t=e.hoverData.dragDelta=e.hoverData.dragDelta||[];0===t.length?(t.push(b[0]),t.push(b[1])):(t[0]+=b[0],t[1]+=b[1])}();n=!0}else if(v){if(e.hoverData.dragging||!o.boxSelectionEnabled()||!D&&o.panningEnabled()&&o.userPanningEnabled()){if(!e.hoverData.selecting&&o.panningEnabled()&&o.userPanningEnabled()){a(m,e.hoverData.downs)&&(e.hoverData.dragging=!0,e.hoverData.justStartedPan=!0,f[4]=0,e.data.bgActivePosistion=bt(h),e.redrawHint("select",!0),e.redraw())}}else T();m&&m.pannable()&&m.active()&&m.unactivate()}return f[2]=c[0],f[3]=c[1],n?(t.stopPropagation&&t.stopPropagation(),t.preventDefault&&t.preventDefault(),!1):void 0}}),!1),e.registerBinding(t,"mouseup",(function(t){if((1!==e.hoverData.which||1===t.which||!e.hoverData.capture)&&e.hoverData.capture){e.hoverData.capture=!1;var a=e.cy,o=e.projectIntoViewport(t.clientX,t.clientY),s=e.selection,l=e.findNearestElement(o[0],o[1],!0,!1),u=e.dragData.possibleDragElements,c=e.hoverData.down,h=i(t);if(e.data.bgActivePosistion&&(e.redrawHint("select",!0),e.redraw()),e.hoverData.tapholdCancelled=!0,e.data.bgActivePosistion=void 0,c&&c.unactivate(),3===e.hoverData.which){var p={originalEvent:t,type:"cxttapend",position:{x:o[0],y:o[1]}};if(c?c.emit(p):a.emit(p),!e.hoverData.cxtDragged){var f={originalEvent:t,type:"cxttap",position:{x:o[0],y:o[1]}};c?c.emit(f):a.emit(f)}e.hoverData.cxtDragged=!1,e.hoverData.which=null}else if(1===e.hoverData.which){if(r(l,["mouseup","tapend","vmouseup"],t,{x:o[0],y:o[1]}),e.dragData.didDrag||e.hoverData.dragged||e.hoverData.selecting||e.hoverData.isOverThresholdDrag||(r(c,["click","tap","vclick"],t,{x:o[0],y:o[1]}),b=!1,t.timeStamp-w<=a.multiClickDebounceTime()?(m&&clearTimeout(m),b=!0,w=null,r(c,["dblclick","dbltap","vdblclick"],t,{x:o[0],y:o[1]})):(m=setTimeout((function(){b||r(c,["oneclick","onetap","voneclick"],t,{x:o[0],y:o[1]})}),a.multiClickDebounceTime()),w=t.timeStamp)),null!=c||e.dragData.didDrag||e.hoverData.selecting||e.hoverData.dragged||i(t)||(a.$(n).unselect(["tapunselect"]),u.length>0&&e.redrawHint("eles",!0),e.dragData.possibleDragElements=u=a.collection()),l!=c||e.dragData.didDrag||e.hoverData.selecting||null!=l&&l._private.selectable&&(e.hoverData.dragging||("additive"===a.selectionType()||h?l.selected()?l.unselect(["tapunselect"]):l.select(["tapselect"]):h||(a.$(n).unmerge(l).unselect(["tapunselect"]),l.select(["tapselect"]))),e.redrawHint("eles",!0)),e.hoverData.selecting){var g=a.collection(e.getAllInBox(s[0],s[1],s[2],s[3]));e.redrawHint("select",!0),g.length>0&&e.redrawHint("eles",!0),a.emit({type:"boxend",originalEvent:t,position:{x:o[0],y:o[1]}});var v=function(e){return e.selectable()&&!e.selected()};"additive"===a.selectionType()||h||a.$(n).unmerge(g).unselect(),g.emit("box").stdFilter(v).select().emit("boxselect"),e.redraw()}if(e.hoverData.dragging&&(e.hoverData.dragging=!1,e.redrawHint("select",!0),e.redrawHint("eles",!0),e.redraw()),!s[4]){e.redrawHint("drag",!0),e.redrawHint("eles",!0);var y=c&&c.grabbed();d(u),y&&(c.emit("freeon"),u.emit("free"),e.dragData.didDrag&&(c.emit("dragfreeon"),u.emit("dragfree")))}}s[4]=0,e.hoverData.down=null,e.hoverData.cxtStarted=!1,e.hoverData.draggingEles=!1,e.hoverData.selecting=!1,e.hoverData.isOverThresholdDrag=!1,e.dragData.didDrag=!1,e.hoverData.dragged=!1,e.hoverData.dragDelta=[],e.hoverData.mdownPos=null,e.hoverData.mdownGPos=null,e.hoverData.which=null}}),!1);var k,C,S,P,D,T,_,M,B,N,z,I,A,L=function(t){if(!e.scrollingPage){var n=e.cy,r=n.zoom(),i=n.pan(),a=e.projectIntoViewport(t.clientX,t.clientY),o=[a[0]*r+i.x,a[1]*r+i.y];if(e.hoverData.draggingEles||e.hoverData.dragging||e.hoverData.cxtStarted||0!==e.selection[4])t.preventDefault();else if(n.panningEnabled()&&n.userPanningEnabled()&&n.zoomingEnabled()&&n.userZoomingEnabled()){var s;t.preventDefault(),e.data.wheelZooming=!0,clearTimeout(e.data.wheelTimeout),e.data.wheelTimeout=setTimeout((function(){e.data.wheelZooming=!1,e.redrawHint("eles",!0),e.redraw()}),150),s=null!=t.deltaY?t.deltaY/-250:null!=t.wheelDeltaY?t.wheelDeltaY/1e3:t.wheelDelta/1e3,s*=e.wheelSensitivity,1===t.deltaMode&&(s*=33);var l=n.zoom()*Math.pow(10,s);"gesturechange"===t.type&&(l=e.gestureStartZoom*t.scale),n.zoom({level:l,renderedPosition:{x:o[0],y:o[1]}}),n.emit("gesturechange"===t.type?"pinchzoom":"scrollzoom")}}};e.registerBinding(e.container,"wheel",L,!0),e.registerBinding(t,"scroll",(function(t){e.scrollingPage=!0,clearTimeout(e.scrollingPageTimeout),e.scrollingPageTimeout=setTimeout((function(){e.scrollingPage=!1}),250)}),!0),e.registerBinding(e.container,"gesturestart",(function(t){e.gestureStartZoom=e.cy.zoom(),e.hasTouchStarted||t.preventDefault()}),!0),e.registerBinding(e.container,"gesturechange",(function(t){e.hasTouchStarted||L(t)}),!0),e.registerBinding(e.container,"mouseout",(function(t){var n=e.projectIntoViewport(t.clientX,t.clientY);e.cy.emit({originalEvent:t,type:"mouseout",position:{x:n[0],y:n[1]}})}),!1),e.registerBinding(e.container,"mouseover",(function(t){var n=e.projectIntoViewport(t.clientX,t.clientY);e.cy.emit({originalEvent:t,type:"mouseover",position:{x:n[0],y:n[1]}})}),!1);var O,R,V,F,j,q,Y,X=function(e,t,n,r){return Math.sqrt((n-e)*(n-e)+(r-t)*(r-t))},W=function(e,t,n,r){return(n-e)*(n-e)+(r-t)*(r-t)};if(e.registerBinding(e.container,"touchstart",O=function(t){if(e.hasTouchStarted=!0,E(t)){p(),e.touchData.capture=!0,e.data.bgActivePosistion=void 0;var n=e.cy,i=e.touchData.now,a=e.touchData.earlier;if(t.touches[0]){var o=e.projectIntoViewport(t.touches[0].clientX,t.touches[0].clientY);i[0]=o[0],i[1]=o[1]}if(t.touches[1]){o=e.projectIntoViewport(t.touches[1].clientX,t.touches[1].clientY);i[2]=o[0],i[3]=o[1]}if(t.touches[2]){o=e.projectIntoViewport(t.touches[2].clientX,t.touches[2].clientY);i[4]=o[0],i[5]=o[1]}if(t.touches[1]){e.touchData.singleTouchMoved=!0,d(e.dragData.touchDragEles);var l=e.findContainerClientCoords();B=l[0],N=l[1],z=l[2],I=l[3],k=t.touches[0].clientX-B,C=t.touches[0].clientY-N,S=t.touches[1].clientX-B,P=t.touches[1].clientY-N,A=0<=k&&k<=z&&0<=S&&S<=z&&0<=C&&C<=I&&0<=P&&P<=I;var h=n.pan(),f=n.zoom();D=X(k,C,S,P),T=W(k,C,S,P),M=[((_=[(k+S)/2,(C+P)/2])[0]-h.x)/f,(_[1]-h.y)/f];if(T<4e4&&!t.touches[2]){var g=e.findNearestElement(i[0],i[1],!0,!0),v=e.findNearestElement(i[2],i[3],!0,!0);return g&&g.isNode()?(g.activate().emit({originalEvent:t,type:"cxttapstart",position:{x:i[0],y:i[1]}}),e.touchData.start=g):v&&v.isNode()?(v.activate().emit({originalEvent:t,type:"cxttapstart",position:{x:i[0],y:i[1]}}),e.touchData.start=v):n.emit({originalEvent:t,type:"cxttapstart",position:{x:i[0],y:i[1]}}),e.touchData.start&&(e.touchData.start._private.grabbed=!1),e.touchData.cxt=!0,e.touchData.cxtDragged=!1,e.data.bgActivePosistion=void 0,void e.redraw()}}if(t.touches[2])n.boxSelectionEnabled()&&t.preventDefault();else if(t.touches[1]);else if(t.touches[0]){var y=e.findNearestElements(i[0],i[1],!0,!0),m=y[0];if(null!=m&&(m.activate(),e.touchData.start=m,e.touchData.starts=y,e.nodeIsGrabbable(m))){var b=e.dragData.touchDragEles=n.collection(),x=null;e.redrawHint("eles",!0),e.redrawHint("drag",!0),m.selected()?(x=n.$((function(t){return t.selected()&&e.nodeIsGrabbable(t)})),u(x,{addToList:b})):c(m,{addToList:b}),s(m);var w=function(e){return{originalEvent:t,type:e,position:{x:i[0],y:i[1]}}};m.emit(w("grabon")),x?x.forEach((function(e){e.emit(w("grab"))})):m.emit(w("grab"))}r(m,["touchstart","tapstart","vmousedown"],t,{x:i[0],y:i[1]}),null==m&&(e.data.bgActivePosistion={x:o[0],y:o[1]},e.redrawHint("select",!0),e.redraw()),e.touchData.singleTouchMoved=!1,e.touchData.singleTouchStartTime=+new Date,clearTimeout(e.touchData.tapholdTimeout),e.touchData.tapholdTimeout=setTimeout((function(){!1!==e.touchData.singleTouchMoved||e.pinching||e.touchData.selecting||r(e.touchData.start,["taphold"],t,{x:i[0],y:i[1]})}),e.tapholdDuration)}if(t.touches.length>=1){for(var L=e.touchData.startPosition=[null,null,null,null,null,null],O=0;O=e.touchTapThreshold2}if(n&&e.touchData.cxt){t.preventDefault();var w=t.touches[0].clientX-B,_=t.touches[0].clientY-N,z=t.touches[1].clientX-B,I=t.touches[1].clientY-N,L=W(w,_,z,I);if(L/T>=2.25||L>=22500){e.touchData.cxt=!1,e.data.bgActivePosistion=void 0,e.redrawHint("select",!0);var O={originalEvent:t,type:"cxttapend",position:{x:s[0],y:s[1]}};e.touchData.start?(e.touchData.start.unactivate().emit(O),e.touchData.start=null):o.emit(O)}}if(n&&e.touchData.cxt){O={originalEvent:t,type:"cxtdrag",position:{x:s[0],y:s[1]}};e.data.bgActivePosistion=void 0,e.redrawHint("select",!0),e.touchData.start?e.touchData.start.emit(O):o.emit(O),e.touchData.start&&(e.touchData.start._private.grabbed=!1),e.touchData.cxtDragged=!0;var R=e.findNearestElement(s[0],s[1],!0,!0);e.touchData.cxtOver&&R===e.touchData.cxtOver||(e.touchData.cxtOver&&e.touchData.cxtOver.emit({originalEvent:t,type:"cxtdragout",position:{x:s[0],y:s[1]}}),e.touchData.cxtOver=R,R&&R.emit({originalEvent:t,type:"cxtdragover",position:{x:s[0],y:s[1]}}))}else if(n&&t.touches[2]&&o.boxSelectionEnabled())t.preventDefault(),e.data.bgActivePosistion=void 0,this.lastThreeTouch=+new Date,e.touchData.selecting||o.emit({originalEvent:t,type:"boxstart",position:{x:s[0],y:s[1]}}),e.touchData.selecting=!0,e.touchData.didSelect=!0,i[4]=1,i&&0!==i.length&&void 0!==i[0]?(i[2]=(s[0]+s[2]+s[4])/3,i[3]=(s[1]+s[3]+s[5])/3):(i[0]=(s[0]+s[2]+s[4])/3,i[1]=(s[1]+s[3]+s[5])/3,i[2]=(s[0]+s[2]+s[4])/3+1,i[3]=(s[1]+s[3]+s[5])/3+1),e.redrawHint("select",!0),e.redraw();else if(n&&t.touches[1]&&!e.touchData.didSelect&&o.zoomingEnabled()&&o.panningEnabled()&&o.userZoomingEnabled()&&o.userPanningEnabled()){if(t.preventDefault(),e.data.bgActivePosistion=void 0,e.redrawHint("select",!0),ee=e.dragData.touchDragEles){e.redrawHint("drag",!0);for(var V=0;V0&&!e.hoverData.draggingEles&&!e.swipePanning&&null!=e.data.bgActivePosistion&&(e.data.bgActivePosistion=void 0,e.redrawHint("select",!0),e.redraw())}},!1),e.registerBinding(t,"touchcancel",V=function(t){var n=e.touchData.start;e.touchData.capture=!1,n&&n.unactivate()}),e.registerBinding(t,"touchend",F=function(t){var i=e.touchData.start;if(e.touchData.capture){0===t.touches.length&&(e.touchData.capture=!1),t.preventDefault();var a=e.selection;e.swipePanning=!1,e.hoverData.draggingEles=!1;var o,s=e.cy,l=s.zoom(),u=e.touchData.now,c=e.touchData.earlier;if(t.touches[0]){var h=e.projectIntoViewport(t.touches[0].clientX,t.touches[0].clientY);u[0]=h[0],u[1]=h[1]}if(t.touches[1]){h=e.projectIntoViewport(t.touches[1].clientX,t.touches[1].clientY);u[2]=h[0],u[3]=h[1]}if(t.touches[2]){h=e.projectIntoViewport(t.touches[2].clientX,t.touches[2].clientY);u[4]=h[0],u[5]=h[1]}if(i&&i.unactivate(),e.touchData.cxt){if(o={originalEvent:t,type:"cxttapend",position:{x:u[0],y:u[1]}},i?i.emit(o):s.emit(o),!e.touchData.cxtDragged){var p={originalEvent:t,type:"cxttap",position:{x:u[0],y:u[1]}};i?i.emit(p):s.emit(p)}return e.touchData.start&&(e.touchData.start._private.grabbed=!1),e.touchData.cxt=!1,e.touchData.start=null,void e.redraw()}if(!t.touches[2]&&s.boxSelectionEnabled()&&e.touchData.selecting){e.touchData.selecting=!1;var f=s.collection(e.getAllInBox(a[0],a[1],a[2],a[3]));a[0]=void 0,a[1]=void 0,a[2]=void 0,a[3]=void 0,a[4]=0,e.redrawHint("select",!0),s.emit({type:"boxend",originalEvent:t,position:{x:u[0],y:u[1]}});f.emit("box").stdFilter((function(e){return e.selectable()&&!e.selected()})).select().emit("boxselect"),f.nonempty()&&e.redrawHint("eles",!0),e.redraw()}if(null!=i&&i.unactivate(),t.touches[2])e.data.bgActivePosistion=void 0,e.redrawHint("select",!0);else if(t.touches[1]);else if(t.touches[0]);else if(!t.touches[0]){e.data.bgActivePosistion=void 0,e.redrawHint("select",!0);var g=e.dragData.touchDragEles;if(null!=i){var v=i._private.grabbed;d(g),e.redrawHint("drag",!0),e.redrawHint("eles",!0),v&&(i.emit("freeon"),g.emit("free"),e.dragData.didDrag&&(i.emit("dragfreeon"),g.emit("dragfree"))),r(i,["touchend","tapend","vmouseup","tapdragout"],t,{x:u[0],y:u[1]}),i.unactivate(),e.touchData.start=null}else{var y=e.findNearestElement(u[0],u[1],!0,!0);r(y,["touchend","tapend","vmouseup","tapdragout"],t,{x:u[0],y:u[1]})}var m=e.touchData.startPosition[0]-u[0],b=m*m,x=e.touchData.startPosition[1]-u[1],w=(b+x*x)*l*l;e.touchData.singleTouchMoved||(i||s.$(":selected").unselect(["tapunselect"]),r(i,["tap","vclick"],t,{x:u[0],y:u[1]}),j=!1,t.timeStamp-Y<=s.multiClickDebounceTime()?(q&&clearTimeout(q),j=!0,Y=null,r(i,["dbltap","vdblclick"],t,{x:u[0],y:u[1]})):(q=setTimeout((function(){j||r(i,["onetap","voneclick"],t,{x:u[0],y:u[1]})}),s.multiClickDebounceTime()),Y=t.timeStamp)),null!=i&&!e.dragData.didDrag&&i._private.selectable&&w2){for(var p=[c[0],c[1]],f=Math.pow(p[0]-e,2)+Math.pow(p[1]-t,2),g=1;g0)return g[0]}return null},p=Object.keys(d),f=0;f0?u:Rt(i,a,e,t,n,r,o,s)},checkPoint:function(e,t,n,r,i,a,o,s){var l=2*(s="auto"===s?nn(r,i):s);if(Xt(e,t,this.points,a,o,r,i-l,[0,-1],n))return!0;if(Xt(e,t,this.points,a,o,r-l,i,[0,-1],n))return!0;var u=r/2+2*n,c=i/2+2*n;return!!Yt(e,t,[a-u,o-c,a-u,o,a+u,o,a+u,o-c])||(!!Kt(e,t,l,l,a+r/2-s,o+i/2-s,n)||!!Kt(e,t,l,l,a-r/2+s,o+i/2-s,n))}}},gu.registerNodeShapes=function(){var e=this.nodeShapes={},t=this;this.generateEllipse(),this.generatePolygon("triangle",Jt(3,0)),this.generateRoundPolygon("round-triangle",Jt(3,0)),this.generatePolygon("rectangle",Jt(4,0)),e.square=e.rectangle,this.generateRoundRectangle(),this.generateCutRectangle(),this.generateBarrel(),this.generateBottomRoundrectangle();var n=[0,1,1,0,0,-1,-1,0];this.generatePolygon("diamond",n),this.generateRoundPolygon("round-diamond",n),this.generatePolygon("pentagon",Jt(5,0)),this.generateRoundPolygon("round-pentagon",Jt(5,0)),this.generatePolygon("hexagon",Jt(6,0)),this.generateRoundPolygon("round-hexagon",Jt(6,0)),this.generatePolygon("heptagon",Jt(7,0)),this.generateRoundPolygon("round-heptagon",Jt(7,0)),this.generatePolygon("octagon",Jt(8,0)),this.generateRoundPolygon("round-octagon",Jt(8,0));var r=new Array(20),i=tn(5,0),a=tn(5,Math.PI/5),o=.5*(3-Math.sqrt(5));o*=1.57;for(var s=0;s=e.deqFastCost*g)break}else if(i){if(p>=e.deqCost*l||p>=e.deqAvgCost*s)break}else if(f>=e.deqNoDrawCost*(1e3/60))break;var v=e.deq(t,d,c);if(!(v.length>0))break;for(var y=0;y0&&(e.onDeqd(t,u),!i&&e.shouldRedraw(t,u,d,c)&&r())}),i(t))}}},wu=function(){function e(n){var r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:Le;t(this,e),this.idsByKey=new $e,this.keyForId=new $e,this.cachesByLvl=new $e,this.lvls=[],this.getKey=n,this.doesEleInvalidateKey=r}return r(e,[{key:"getIdsFor",value:function(e){null==e&&Ve("Can not get id list for null key");var t=this.idsByKey,n=this.idsByKey.get(e);return n||(n=new Je,t.set(e,n)),n}},{key:"addIdForKey",value:function(e,t){null!=e&&this.getIdsFor(e).add(t)}},{key:"deleteIdForKey",value:function(e,t){null!=e&&this.getIdsFor(e).delete(t)}},{key:"getNumberOfIdsForKey",value:function(e){return null==e?0:this.getIdsFor(e).size}},{key:"updateKeyMappingFor",value:function(e){var t=e.id(),n=this.keyForId.get(t),r=this.getKey(e);this.deleteIdForKey(n,t),this.addIdForKey(r,t),this.keyForId.set(t,r)}},{key:"deleteKeyMappingFor",value:function(e){var t=e.id(),n=this.keyForId.get(t);this.deleteIdForKey(n,t),this.keyForId.delete(t)}},{key:"keyHasChangedFor",value:function(e){var t=e.id();return this.keyForId.get(t)!==this.getKey(e)}},{key:"isInvalid",value:function(e){return this.keyHasChangedFor(e)||this.doesEleInvalidateKey(e)}},{key:"getCachesAt",value:function(e){var t=this.cachesByLvl,n=this.lvls,r=t.get(e);return r||(r=new $e,t.set(e,r),n.push(e)),r}},{key:"getCache",value:function(e,t){return this.getCachesAt(t).get(e)}},{key:"get",value:function(e,t){var n=this.getKey(e),r=this.getCache(n,t);return null!=r&&this.updateKeyMappingFor(e),r}},{key:"getForCachedKey",value:function(e,t){var n=this.keyForId.get(e.id());return this.getCache(n,t)}},{key:"hasCache",value:function(e,t){return this.getCachesAt(t).has(e)}},{key:"has",value:function(e,t){var n=this.getKey(e);return this.hasCache(n,t)}},{key:"setCache",value:function(e,t,n){n.key=e,this.getCachesAt(t).set(e,n)}},{key:"set",value:function(e,t,n){var r=this.getKey(e);this.setCache(r,t,n),this.updateKeyMappingFor(e)}},{key:"deleteCache",value:function(e,t){this.getCachesAt(t).delete(e)}},{key:"delete",value:function(e,t){var n=this.getKey(e);this.deleteCache(n,t)}},{key:"invalidateKey",value:function(e){var t=this;this.lvls.forEach((function(n){return t.deleteCache(e,n)}))}},{key:"invalidate",value:function(e){var t=e.id(),n=this.keyForId.get(t);this.deleteKeyMappingFor(e);var r=this.doesEleInvalidateKey(e);return r&&this.invalidateKey(n),r||0===this.getNumberOfIdsForKey(n)}}]),e}(),Eu={dequeue:"dequeue",downscale:"downscale",highQuality:"highQuality"},ku=He({getKey:null,doesEleInvalidateKey:Le,drawElement:null,getBoundingBox:null,getRotationPoint:null,getRotationOffset:null,isVisible:Ae,allowEdgeTxrCaching:!0,allowParentTxrCaching:!0}),Cu=function(e,t){this.renderer=e,this.onDequeues=[];var n=ku(t);L(this,n),this.lookup=new wu(n.getKey,n.doesEleInvalidateKey),this.setupDequeueing()},Su=Cu.prototype;Su.reasons=Eu,Su.getTextureQueue=function(e){return this.eleImgCaches=this.eleImgCaches||{},this.eleImgCaches[e]=this.eleImgCaches[e]||[]},Su.getRetiredTextureQueue=function(e){var t=this.eleImgCaches.retired=this.eleImgCaches.retired||{};return t[e]=t[e]||[]},Su.getElementQueue=function(){return this.eleCacheQueue=this.eleCacheQueue||new rt((function(e,t){return t.reqs-e.reqs}))},Su.getElementKeyToQueue=function(){return this.eleKeyToCacheQueue=this.eleKeyToCacheQueue||{}},Su.getElement=function(e,t,n,r,i){var a=this,o=this.renderer,s=o.cy.zoom(),l=this.lookup;if(!t||0===t.w||0===t.h||isNaN(t.w)||isNaN(t.h)||!e.visible()||e.removed())return null;if(!a.allowEdgeTxrCaching&&e.isEdge()||!a.allowParentTxrCaching&&e.isParent())return null;if(null==r&&(r=Math.ceil(wt(s*n))),r<-4)r=-4;else if(s>=7.99||r>3)return null;var u=Math.pow(2,r),c=t.h*u,d=t.w*u,h=o.eleTextBiggerThanMin(e,u);if(!this.isVisible(e,h))return null;var p,f=l.get(e,r);if(f&&f.invalidated&&(f.invalidated=!1,f.texture.invalidatedWidth-=f.width),f)return f;if(p=c<=25?25:c<=50?50:50*Math.ceil(c/50),c>1024||d>1024)return null;var g=a.getTextureQueue(p),v=g[g.length-2],y=function(){return a.recycleTexture(p,d)||a.addTexture(p,d)};v||(v=g[g.length-1]),v||(v=y()),v.width-v.usedWidthr;D--)S=a.getElement(e,t,n,D,Eu.downscale);P()}else{var T;if(!x&&!w&&!E)for(var _=r-1;_>=-4;_--){var M=l.get(e,_);if(M){T=M;break}}if(b(T))return a.queueElement(e,r),T;v.context.translate(v.usedWidth,0),v.context.scale(u,u),this.drawElement(v.context,e,t,h,!1),v.context.scale(1/u,1/u),v.context.translate(-v.usedWidth,0)}return f={x:v.usedWidth,texture:v,level:r,scale:u,width:d,height:c,scaledLabelShown:h},v.usedWidth+=Math.ceil(d+8),v.eleCaches.push(f),l.set(e,r,f),a.checkTextureFullness(v),f},Su.invalidateElements=function(e){for(var t=0;t=.2*e.width&&this.retireTexture(e)},Su.checkTextureFullness=function(e){var t=this.getTextureQueue(e.height);e.usedWidth/e.width>.8&&e.fullnessChecks>=10?Ke(t,e):e.fullnessChecks++},Su.retireTexture=function(e){var t=e.height,n=this.getTextureQueue(t),r=this.lookup;Ke(n,e),e.retired=!0;for(var i=e.eleCaches,a=0;a=t)return a.retired=!1,a.usedWidth=0,a.invalidatedWidth=0,a.fullnessChecks=0,Ge(a.eleCaches),a.context.setTransform(1,0,0,1,0,0),a.context.clearRect(0,0,a.width,a.height),Ke(r,a),n.push(a),a}},Su.queueElement=function(e,t){var n=this.getElementQueue(),r=this.getElementKeyToQueue(),i=this.getKey(e),a=r[i];if(a)a.level=Math.max(a.level,t),a.eles.merge(e),a.reqs++,n.updateItem(a);else{var o={eles:e.spawn().merge(e),level:t,reqs:1,key:i};n.push(o),r[i]=o}},Su.dequeue=function(e){for(var t=this.getElementQueue(),n=this.getElementKeyToQueue(),r=[],i=this.lookup,a=0;a<1&&t.size()>0;a++){var o=t.pop(),s=o.key,l=o.eles[0],u=i.hasCache(l,o.level);if(n[s]=null,!u){r.push(o);var c=this.getBoundingBox(l);this.getElement(l,c,e,o.level,Eu.dequeue)}}return r},Su.removeFromQueue=function(e){var t=this.getElementQueue(),n=this.getElementKeyToQueue(),r=this.getKey(e),i=n[r];null!=i&&(1===i.eles.length?(i.reqs=Ie,t.updateItem(i),t.pop(),n[r]=null):i.eles.unmerge(e))},Su.onDequeue=function(e){this.onDequeues.push(e)},Su.offDequeue=function(e){Ke(this.onDequeues,e)},Su.setupDequeueing=xu({deqRedrawThreshold:100,deqCost:.15,deqAvgCost:.1,deqNoDrawCost:.9,deqFastCost:.9,deq:function(e,t,n){return e.dequeue(t,n)},onDeqd:function(e,t){for(var n=0;n=3.99||n>2)return null;r.validateLayersElesOrdering(n,e);var o,s,l=r.layersByLevel,u=Math.pow(2,n),c=l[n]=l[n]||[];if(r.levelIsComplete(n,e))return c;!function(){var t=function(t){if(r.validateLayersElesOrdering(t,e),r.levelIsComplete(t,e))return s=l[t],!0},i=function(e){if(!s)for(var r=n+e;-4<=r&&r<=2&&!t(r);r+=e);};i(1),i(-1);for(var a=c.length-1;a>=0;a--){var o=c[a];o.invalid&&Ke(c,o)}}();var d=function(t){var i=(t=t||{}).after;if(function(){if(!o){o=_t();for(var t=0;t16e6)return null;var a=r.makeLayer(o,n);if(null!=i){var s=c.indexOf(i)+1;c.splice(s,0,a)}else(void 0===t.insert||t.insert)&&c.unshift(a);return a};if(r.skipping&&!a)return null;for(var h=null,p=e.length/1,f=!a,g=0;g=p||!Ot(h.bb,v.boundingBox()))&&!(h=d({insert:!0,after:h})))return null;s||f?r.queueLayer(h,v):r.drawEleInLayer(h,v,n,t),h.eles.push(v),m[n]=h}}return s||(f?null:c)},Du.getEleLevelForLayerLevel=function(e,t){return e},Du.drawEleInLayer=function(e,t,n,r){var i=this.renderer,a=e.context,o=t.boundingBox();0!==o.w&&0!==o.h&&t.visible()&&(n=this.getEleLevelForLayerLevel(n,r),i.setImgSmoothing(a,!1),i.drawCachedElement(a,t,null,null,n,!0),i.setImgSmoothing(a,!0))},Du.levelIsComplete=function(e,t){var n=this.layersByLevel[e];if(!n||0===n.length)return!1;for(var r=0,i=0;i0)return!1;if(a.invalid)return!1;r+=a.eles.length}return r===t.length},Du.validateLayersElesOrdering=function(e,t){var n=this.layersByLevel[e];if(n)for(var r=0;r0){e=!0;break}}return e},Du.invalidateElements=function(e){var t=this;0!==e.length&&(t.lastInvalidationTime=we(),0!==e.length&&t.haveLayers()&&t.updateElementsInLayers(e,(function(e,n,r){t.invalidateLayer(e)})))},Du.invalidateLayer=function(e){if(this.lastInvalidationTime=we(),!e.invalid){var t=e.level,n=e.eles,r=this.layersByLevel[t];Ke(r,e),e.elesQueue=[],e.invalid=!0,e.replacement&&(e.replacement.invalid=!0);for(var i=0;i3&&void 0!==arguments[3])||arguments[3],i=!(arguments.length>4&&void 0!==arguments[4])||arguments[4],a=!(arguments.length>5&&void 0!==arguments[5])||arguments[5],o=this,s=t._private.rscratch;if((!a||t.visible())&&!s.badLine&&null!=s.allpts&&!isNaN(s.allpts[0])){var l;n&&(l=n,e.translate(-l.x1,-l.y1));var u=a?t.pstyle("opacity").value:1,c=a?t.pstyle("line-opacity").value:1,d=t.pstyle("curve-style").value,h=t.pstyle("line-style").value,p=t.pstyle("width").pfValue,f=t.pstyle("line-cap").value,g=t.pstyle("line-outline-width").value,v=t.pstyle("line-outline-color").value,y=u*c,m=u*c,b=function(){var n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:y;"straight-triangle"===d?(o.eleStrokeStyle(e,t,n),o.drawEdgeTrianglePath(t,e,s.allpts)):(e.lineWidth=p,e.lineCap=f,o.eleStrokeStyle(e,t,n),o.drawEdgePath(t,e,s.allpts,h),e.lineCap="butt")},x=function(){var n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:y;e.lineWidth=p+g,e.lineCap=f,g>0?(o.colorStrokeStyle(e,v[0],v[1],v[2],n),"straight-triangle"===d?o.drawEdgeTrianglePath(t,e,s.allpts):(o.drawEdgePath(t,e,s.allpts,h),e.lineCap="butt")):e.lineCap="butt"},w=function(){i&&o.drawEdgeOverlay(e,t)},E=function(){i&&o.drawEdgeUnderlay(e,t)},k=function(){var n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:m;o.drawArrowheads(e,t,n)},C=function(){o.drawElementText(e,t,null,r)};e.lineJoin="round";var S="yes"===t.pstyle("ghost").value;if(S){var P=t.pstyle("ghost-offset-x").pfValue,D=t.pstyle("ghost-offset-y").pfValue,T=t.pstyle("ghost-opacity").value,_=y*T;e.translate(P,D),b(_),k(_),e.translate(-P,-D)}else x();E(),b(),k(),w(),C(),n&&e.translate(l.x1,l.y1)}}},Wu=function(e){if(!["overlay","underlay"].includes(e))throw new Error("Invalid state");return function(t,n){if(n.visible()){var r=n.pstyle("".concat(e,"-opacity")).value;if(0!==r){var i=this,a=i.usePaths(),o=n._private.rscratch,s=2*n.pstyle("".concat(e,"-padding")).pfValue,l=n.pstyle("".concat(e,"-color")).value;t.lineWidth=s,"self"!==o.edgeType||a?t.lineCap="round":t.lineCap="butt",i.colorStrokeStyle(t,l[0],l[1],l[2],r),i.drawEdgePath(n,t,o.allpts,"solid")}}}};Xu.drawEdgeOverlay=Wu("overlay"),Xu.drawEdgeUnderlay=Wu("underlay"),Xu.drawEdgePath=function(e,t,n,r){var i,a=e._private.rscratch,o=t,s=!1,u=this.usePaths(),c=e.pstyle("line-dash-pattern").pfValue,d=e.pstyle("line-dash-offset").pfValue;if(u){var h=n.join("$");a.pathCacheKey&&a.pathCacheKey===h?(i=t=a.pathCache,s=!0):(i=t=new Path2D,a.pathCacheKey=h,a.pathCache=i)}if(o.setLineDash)switch(r){case"dotted":o.setLineDash([1,1]);break;case"dashed":o.setLineDash(c),o.lineDashOffset=d;break;case"solid":o.setLineDash([])}if(!s&&!a.badLine)switch(t.beginPath&&t.beginPath(),t.moveTo(n[0],n[1]),a.edgeType){case"bezier":case"self":case"compound":case"multibezier":for(var p=2;p+35&&void 0!==arguments[5]?arguments[5]:5,o=arguments.length>6?arguments[6]:void 0;e.beginPath(),e.moveTo(t+a,n),e.lineTo(t+r-a,n),e.quadraticCurveTo(t+r,n,t+r,n+a),e.lineTo(t+r,n+i-a),e.quadraticCurveTo(t+r,n+i,t+r-a,n+i),e.lineTo(t+a,n+i),e.quadraticCurveTo(t,n+i,t,n+i-a),e.lineTo(t,n+a),e.quadraticCurveTo(t,n,t+a,n),e.closePath(),o?e.stroke():e.fill()}Ku.eleTextBiggerThanMin=function(e,t){if(!t){var n=e.cy().zoom(),r=this.getPixelRatio(),i=Math.ceil(wt(n*r));t=Math.pow(2,i)}return!(e.pstyle("font-size").pfValue*t5&&void 0!==arguments[5])||arguments[5],o=this;if(null==r){if(a&&!o.eleTextBiggerThanMin(t))return}else if(!1===r)return;if(t.isNode()){var s=t.pstyle("label");if(!s||!s.value)return;var l=o.getLabelJustification(t);e.textAlign=l,e.textBaseline="bottom"}else{var u=t.element()._private.rscratch.badLine,c=t.pstyle("label"),d=t.pstyle("source-label"),h=t.pstyle("target-label");if(u||(!c||!c.value)&&(!d||!d.value)&&(!h||!h.value))return;e.textAlign="center",e.textBaseline="bottom"}var p,f=!n;n&&(p=n,e.translate(-p.x1,-p.y1)),null==i?(o.drawText(e,t,null,f,a),t.isEdge()&&(o.drawText(e,t,"source",f,a),o.drawText(e,t,"target",f,a))):o.drawText(e,t,i,f,a),n&&e.translate(p.x1,p.y1)},Ku.getFontCache=function(e){var t;this.fontCaches=this.fontCaches||[];for(var n=0;n2&&void 0!==arguments[2])||arguments[2],r=t.pstyle("font-style").strValue,i=t.pstyle("font-size").pfValue+"px",a=t.pstyle("font-family").strValue,o=t.pstyle("font-weight").strValue,s=n?t.effectiveOpacity()*t.pstyle("text-opacity").value:1,l=t.pstyle("text-outline-opacity").value*s,u=t.pstyle("color").value,c=t.pstyle("text-outline-color").value;e.font=r+" "+o+" "+i+" "+a,e.lineJoin="round",this.colorFillStyle(e,u[0],u[1],u[2],s),this.colorStrokeStyle(e,c[0],c[1],c[2],l)},Ku.getTextAngle=function(e,t){var n=e._private.rscratch,r=t?t+"-":"",i=e.pstyle(r+"text-rotation"),a=Ue(n,"labelAngle",t);return"autorotate"===i.strValue?e.isEdge()?a:0:"none"===i.strValue?0:i.pfValue},Ku.drawText=function(e,t,n){var r=!(arguments.length>3&&void 0!==arguments[3])||arguments[3],i=!(arguments.length>4&&void 0!==arguments[4])||arguments[4],a=t._private,o=a.rscratch,s=i?t.effectiveOpacity():1;if(!i||0!==s&&0!==t.pstyle("text-opacity").value){"main"===n&&(n=null);var l,u,c=Ue(o,"labelX",n),d=Ue(o,"labelY",n),h=this.getLabelText(t,n);if(null!=h&&""!==h&&!isNaN(c)&&!isNaN(d)){this.setupTextStyle(e,t,i);var p,f=n?n+"-":"",g=Ue(o,"labelWidth",n),v=Ue(o,"labelHeight",n),y=t.pstyle(f+"text-margin-x").pfValue,m=t.pstyle(f+"text-margin-y").pfValue,b=t.isEdge(),x=t.pstyle("text-halign").value,w=t.pstyle("text-valign").value;switch(b&&(x="center",w="center"),c+=y,d+=m,0!==(p=r?this.getTextAngle(t,n):0)&&(l=c,u=d,e.translate(l,u),e.rotate(p),c=0,d=0),w){case"top":break;case"center":d+=v/2;break;case"bottom":d+=v}var E=t.pstyle("text-background-opacity").value,k=t.pstyle("text-border-opacity").value,C=t.pstyle("text-border-width").pfValue,S=t.pstyle("text-background-padding").pfValue,P=t.pstyle("text-background-shape").strValue,D=0===P.indexOf("round"),T=2;if(E>0||C>0&&k>0){var _=c-S;switch(x){case"left":_-=g;break;case"center":_-=g/2}var M=d-v-S,B=g+2*S,N=v+2*S;if(E>0){var z=e.fillStyle,I=t.pstyle("text-background-color").value;e.fillStyle="rgba("+I[0]+","+I[1]+","+I[2]+","+E*s+")",D?Gu(e,_,M,B,N,T):e.fillRect(_,M,B,N),e.fillStyle=z}if(C>0&&k>0){var A=e.strokeStyle,L=e.lineWidth,O=t.pstyle("text-border-color").value,R=t.pstyle("text-border-style").value;if(e.strokeStyle="rgba("+O[0]+","+O[1]+","+O[2]+","+k*s+")",e.lineWidth=C,e.setLineDash)switch(R){case"dotted":e.setLineDash([1,1]);break;case"dashed":e.setLineDash([4,2]);break;case"double":e.lineWidth=C/4,e.setLineDash([]);break;case"solid":e.setLineDash([])}if(D?Gu(e,_,M,B,N,T,"stroke"):e.strokeRect(_,M,B,N),"double"===R){var V=C/2;D?Gu(e,_+V,M+V,B-2*V,N-2*V,T,"stroke"):e.strokeRect(_+V,M+V,B-2*V,N-2*V)}e.setLineDash&&e.setLineDash([]),e.lineWidth=L,e.strokeStyle=A}}var F=2*t.pstyle("text-outline-width").pfValue;if(F>0&&(e.lineWidth=F),"wrap"===t.pstyle("text-wrap").value){var j=Ue(o,"labelWrapCachedLines",n),q=Ue(o,"labelLineHeight",n),Y=g/2,X=this.getLabelJustification(t);switch("auto"===X||("left"===x?"left"===X?c+=-g:"center"===X&&(c+=-Y):"center"===x?"left"===X?c+=-Y:"right"===X&&(c+=Y):"right"===x&&("center"===X?c+=Y:"right"===X&&(c+=g))),w){case"top":d-=(j.length-1)*q;break;case"center":case"bottom":d-=(j.length-1)*q}for(var W=0;W0&&e.strokeText(j[W],c,d),e.fillText(j[W],c,d),d+=q}else F>0&&e.strokeText(h,c,d),e.fillText(h,c,d);0!==p&&(e.rotate(-p),e.translate(-l,-u))}}};var Uu={drawNode:function(e,t,n){var r,i,a=!(arguments.length>3&&void 0!==arguments[3])||arguments[3],o=!(arguments.length>4&&void 0!==arguments[4])||arguments[4],s=!(arguments.length>5&&void 0!==arguments[5])||arguments[5],l=this,u=t._private,c=u.rscratch,d=t.position();if(x(d.x)&&x(d.y)&&(!s||t.visible())){var h,p,f=s?t.effectiveOpacity():1,g=l.usePaths(),v=!1,y=t.padding();r=t.width()+2*y,i=t.height()+2*y,n&&(p=n,e.translate(-p.x1,-p.y1));for(var m=t.pstyle("background-image"),b=m.value,w=new Array(b.length),E=new Array(b.length),k=0,C=0;C0&&void 0!==arguments[0]?arguments[0]:M;l.eleFillStyle(e,t,n)},H=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:R;l.colorStrokeStyle(e,B[0],B[1],B[2],t)},K=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:q;l.colorStrokeStyle(e,F[0],F[1],F[2],t)},G=function(e,t,n,r){var i,a=l.nodePathCache=l.nodePathCache||[],o=_e("polygon"===n?n+","+r.join(","):n,""+t,""+e,""+X),s=a[o],u=!1;return null!=s?(i=s,u=!0,c.pathCache=i):(i=new Path2D,a[o]=c.pathCache=i),{path:i,cacheHit:u}},U=t.pstyle("shape").strValue,Z=t.pstyle("shape-polygon-points").pfValue;if(g){e.translate(d.x,d.y);var $=G(r,i,U,Z);h=$.path,v=$.cacheHit}var Q=function(){if(!v){var n=d;g&&(n={x:0,y:0}),l.nodeShapes[l.getNodeShape(t)].draw(h||e,n.x,n.y,r,i,X,c)}g?e.fill(h):e.fill()},J=function(){for(var n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:f,r=!(arguments.length>1&&void 0!==arguments[1])||arguments[1],i=u.backgrounding,a=0,o=0;o0&&void 0!==arguments[0]&&arguments[0],a=arguments.length>1&&void 0!==arguments[1]?arguments[1]:f;l.hasPie(t)&&(l.drawPie(e,t,a),n&&(g||l.nodeShapes[l.getNodeShape(t)].draw(e,d.x,d.y,r,i,X,c)))},te=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:f,n=(T>0?T:-T)*t,r=T>0?0:255;0!==T&&(l.colorFillStyle(e,r,r,r,n),g?e.fill(h):e.fill())},ne=function(){if(_>0){if(e.lineWidth=_,e.lineCap=I,e.lineJoin=z,e.setLineDash)switch(N){case"dotted":e.setLineDash([1,1]);break;case"dashed":e.setLineDash(L),e.lineDashOffset=O;break;case"solid":case"double":e.setLineDash([])}if("center"!==A){if(e.save(),e.lineWidth*=2,"inside"===A)g?e.clip(h):e.clip();else{var t=new Path2D;t.rect(-r/2-_,-i/2-_,r+2*_,i+2*_),t.addPath(h),e.clip(t,"evenodd")}g?e.stroke(h):e.stroke(),e.restore()}else g?e.stroke(h):e.stroke();if("double"===N){e.lineWidth=_/3;var n=e.globalCompositeOperation;e.globalCompositeOperation="destination-out",g?e.stroke(h):e.stroke(),e.globalCompositeOperation=n}e.setLineDash&&e.setLineDash([])}},re=function(){if(V>0){if(e.lineWidth=V,e.lineCap="butt",e.setLineDash)switch(j){case"dotted":e.setLineDash([1,1]);break;case"dashed":e.setLineDash([4,2]);break;case"solid":case"double":e.setLineDash([])}var n=d;g&&(n={x:0,y:0});var a=l.getNodeShape(t),o=_;"inside"===A&&(o=0),"outside"===A&&(o*=2);var s,u=(r+o+(V+Y))/r,c=(i+o+(V+Y))/i,h=r*u,p=i*c,f=l.nodeShapes[a].points;if(g)s=G(h,p,a,f).path;if("ellipse"===a)l.drawEllipsePath(s||e,n.x,n.y,h,p);else if(["round-diamond","round-heptagon","round-hexagon","round-octagon","round-pentagon","round-polygon","round-triangle","round-tag"].includes(a)){var v=0,y=0,m=0;"round-diamond"===a?v=1.4*(o+Y+V):"round-heptagon"===a?(v=1.075*(o+Y+V),m=-(o/2+Y+V)/35):"round-hexagon"===a?v=1.12*(o+Y+V):"round-pentagon"===a?(v=1.13*(o+Y+V),m=-(o/2+Y+V)/15):"round-tag"===a?(v=1.12*(o+Y+V),y=.07*(o/2+V+Y)):"round-triangle"===a&&(v=(o+Y+V)*(Math.PI/2),m=-(o+Y/2+V)/Math.PI),0!==v&&(h=r*(u=(r+v)/r),["round-hexagon","round-tag"].includes(a)||(p=i*(c=(i+v)/i)));for(var b=h/2,x=p/2,w=(X="auto"===X?rn(h,p):X)+(o+V+Y)/2,E=new Array(f.length/2),k=new Array(f.length/2),C=0;C0){if(r=r||n.position(),null==i||null==a){var d=n.padding();i=n.width()+2*d,a=n.height()+2*d}this.colorFillStyle(t,l[0],l[1],l[2],s),this.nodeShapes[u].draw(t,r.x,r.y,i+2*o,a+2*o,c),t.fill()}}}};Uu.drawNodeOverlay=Zu("overlay"),Uu.drawNodeUnderlay=Zu("underlay"),Uu.hasPie=function(e){return(e=e[0])._private.hasPie},Uu.drawPie=function(e,t,n,r){t=t[0],r=r||t.position();var i=t.cy().style(),a=t.pstyle("pie-size"),o=r.x,s=r.y,l=t.width(),u=t.height(),c=Math.min(l,u)/2,d=0;this.usePaths()&&(o=0,s=0),"%"===a.units?c*=a.pfValue:void 0!==a.pfValue&&(c=a.pfValue/2);for(var h=1;h<=i.pieBackgroundN;h++){var p=t.pstyle("pie-"+h+"-background-size").value,f=t.pstyle("pie-"+h+"-background-color").value,g=t.pstyle("pie-"+h+"-background-opacity").value*n,v=p/100;v+d>1&&(v=1-d);var y=1.5*Math.PI+2*Math.PI*d,m=y+2*Math.PI*v;0===p||d>=1||d+v>1||(e.beginPath(),e.moveTo(o,s),e.arc(o,s,c,y,m),e.closePath(),this.colorFillStyle(e,f[0],f[1],f[2],g),e.fill(),d+=v)}};var $u={};$u.getPixelRatio=function(){var e=this.data.contexts[0];if(null!=this.forcedPixelRatio)return this.forcedPixelRatio;var t=this.cy.window(),n=e.backingStorePixelRatio||e.webkitBackingStorePixelRatio||e.mozBackingStorePixelRatio||e.msBackingStorePixelRatio||e.oBackingStorePixelRatio||e.backingStorePixelRatio||1;return(t.devicePixelRatio||1)/n},$u.paintCache=function(e){for(var t,n=this.paintCaches=this.paintCaches||[],r=!0,i=0;io.minMbLowQualFrames&&(o.motionBlurPxRatio=o.mbPxRBlurry)),o.clearingMotionBlur&&(o.motionBlurPxRatio=1),o.textureDrawLastFrame&&!d&&(c[o.NODE]=!0,c[o.SELECT_BOX]=!0);var m=l.style(),b=l.zoom(),x=void 0!==i?i:b,w=l.pan(),E={x:w.x,y:w.y},k={zoom:b,pan:{x:w.x,y:w.y}},C=o.prevViewport;void 0===C||k.zoom!==C.zoom||k.pan.x!==C.pan.x||k.pan.y!==C.pan.y||g&&!f||(o.motionBlurPxRatio=1),a&&(E=a),x*=s,E.x*=s,E.y*=s;var S=o.getCachedZSortedEles();function P(e,t,n,r,i){var a=e.globalCompositeOperation;e.globalCompositeOperation="destination-out",o.colorFillStyle(e,255,255,255,o.motionBlurTransparency),e.fillRect(t,n,r,i),e.globalCompositeOperation=a}function D(e,r){var s,l,c,d;o.clearingMotionBlur||e!==u.bufferContexts[o.MOTIONBLUR_BUFFER_NODE]&&e!==u.bufferContexts[o.MOTIONBLUR_BUFFER_DRAG]?(s=E,l=x,c=o.canvasWidth,d=o.canvasHeight):(s={x:w.x*p,y:w.y*p},l=b*p,c=o.canvasWidth*p,d=o.canvasHeight*p),e.setTransform(1,0,0,1,0,0),"motionBlur"===r?P(e,0,0,c,d):t||void 0!==r&&!r||e.clearRect(0,0,c,d),n||(e.translate(s.x,s.y),e.scale(l,l)),a&&e.translate(a.x,a.y),i&&e.scale(i,i)}if(d||(o.textureDrawLastFrame=!1),d){if(o.textureDrawLastFrame=!0,!o.textureCache){o.textureCache={},o.textureCache.bb=l.mutableElements().boundingBox(),o.textureCache.texture=o.data.bufferCanvases[o.TEXTURE_BUFFER];var T=o.data.bufferContexts[o.TEXTURE_BUFFER];T.setTransform(1,0,0,1,0,0),T.clearRect(0,0,o.canvasWidth*o.textureMult,o.canvasHeight*o.textureMult),o.render({forcedContext:T,drawOnlyNodeLayer:!0,forcedPxRatio:s*o.textureMult}),(k=o.textureCache.viewport={zoom:l.zoom(),pan:l.pan(),width:o.canvasWidth,height:o.canvasHeight}).mpan={x:(0-k.pan.x)/k.zoom,y:(0-k.pan.y)/k.zoom}}c[o.DRAG]=!1,c[o.NODE]=!1;var _=u.contexts[o.NODE],M=o.textureCache.texture;k=o.textureCache.viewport;_.setTransform(1,0,0,1,0,0),h?P(_,0,0,k.width,k.height):_.clearRect(0,0,k.width,k.height);var B=m.core("outside-texture-bg-color").value,N=m.core("outside-texture-bg-opacity").value;o.colorFillStyle(_,B[0],B[1],B[2],N),_.fillRect(0,0,k.width,k.height);b=l.zoom();D(_,!1),_.clearRect(k.mpan.x,k.mpan.y,k.width/k.zoom/s,k.height/k.zoom/s),_.drawImage(M,k.mpan.x,k.mpan.y,k.width/k.zoom/s,k.height/k.zoom/s)}else o.textureOnViewport&&!t&&(o.textureCache=null);var z=l.extent(),I=o.pinching||o.hoverData.dragging||o.swipePanning||o.data.wheelZooming||o.hoverData.draggingEles||o.cy.animated(),A=o.hideEdgesOnViewport&&I,L=[];if(L[o.NODE]=!c[o.NODE]&&h&&!o.clearedForMotionBlur[o.NODE]||o.clearingMotionBlur,L[o.NODE]&&(o.clearedForMotionBlur[o.NODE]=!0),L[o.DRAG]=!c[o.DRAG]&&h&&!o.clearedForMotionBlur[o.DRAG]||o.clearingMotionBlur,L[o.DRAG]&&(o.clearedForMotionBlur[o.DRAG]=!0),c[o.NODE]||n||r||L[o.NODE]){var O=h&&!L[o.NODE]&&1!==p;D(_=t||(O?o.data.bufferContexts[o.MOTIONBLUR_BUFFER_NODE]:u.contexts[o.NODE]),h&&!O?"motionBlur":void 0),A?o.drawCachedNodes(_,S.nondrag,s,z):o.drawLayeredElements(_,S.nondrag,s,z),o.debug&&o.drawDebugPoints(_,S.nondrag),n||h||(c[o.NODE]=!1)}if(!r&&(c[o.DRAG]||n||L[o.DRAG])){O=h&&!L[o.DRAG]&&1!==p;D(_=t||(O?o.data.bufferContexts[o.MOTIONBLUR_BUFFER_DRAG]:u.contexts[o.DRAG]),h&&!O?"motionBlur":void 0),A?o.drawCachedNodes(_,S.drag,s,z):o.drawCachedElements(_,S.drag,s,z),o.debug&&o.drawDebugPoints(_,S.drag),n||h||(c[o.DRAG]=!1)}if(o.showFps||!r&&c[o.SELECT_BOX]&&!n){if(D(_=t||u.contexts[o.SELECT_BOX]),1==o.selection[4]&&(o.hoverData.selecting||o.touchData.selecting)){b=o.cy.zoom();var R=m.core("selection-box-border-width").value/b;_.lineWidth=R,_.fillStyle="rgba("+m.core("selection-box-color").value[0]+","+m.core("selection-box-color").value[1]+","+m.core("selection-box-color").value[2]+","+m.core("selection-box-opacity").value+")",_.fillRect(o.selection[0],o.selection[1],o.selection[2]-o.selection[0],o.selection[3]-o.selection[1]),R>0&&(_.strokeStyle="rgba("+m.core("selection-box-border-color").value[0]+","+m.core("selection-box-border-color").value[1]+","+m.core("selection-box-border-color").value[2]+","+m.core("selection-box-opacity").value+")",_.strokeRect(o.selection[0],o.selection[1],o.selection[2]-o.selection[0],o.selection[3]-o.selection[1]))}if(u.bgActivePosistion&&!o.hoverData.selecting){b=o.cy.zoom();var V=u.bgActivePosistion;_.fillStyle="rgba("+m.core("active-bg-color").value[0]+","+m.core("active-bg-color").value[1]+","+m.core("active-bg-color").value[2]+","+m.core("active-bg-opacity").value+")",_.beginPath(),_.arc(V.x,V.y,m.core("active-bg-size").pfValue/b,0,2*Math.PI),_.fill()}var F=o.lastRedrawTime;if(o.showFps&&F){F=Math.round(F);var j=Math.round(1e3/F);_.setTransform(1,0,0,1,0,0),_.fillStyle="rgba(255, 0, 0, 0.75)",_.strokeStyle="rgba(255, 0, 0, 0.75)",_.lineWidth=1,_.fillText("1 frame = "+F+" ms = "+j+" fps",0,20);_.strokeRect(0,30,250,20),_.fillRect(0,30,250*Math.min(j/60,1),20)}n||(c[o.SELECT_BOX]=!1)}if(h&&1!==p){var q=u.contexts[o.NODE],Y=o.data.bufferCanvases[o.MOTIONBLUR_BUFFER_NODE],X=u.contexts[o.DRAG],W=o.data.bufferCanvases[o.MOTIONBLUR_BUFFER_DRAG],H=function(e,t,n){e.setTransform(1,0,0,1,0,0),n||!y?e.clearRect(0,0,o.canvasWidth,o.canvasHeight):P(e,0,0,o.canvasWidth,o.canvasHeight);var r=p;e.drawImage(t,0,0,o.canvasWidth*r,o.canvasHeight*r,0,0,o.canvasWidth,o.canvasHeight)};(c[o.NODE]||L[o.NODE])&&(H(q,Y,L[o.NODE]),c[o.NODE]=!1),(c[o.DRAG]||L[o.DRAG])&&(H(X,W,L[o.DRAG]),c[o.DRAG]=!1)}o.prevViewport=k,o.clearingMotionBlur&&(o.clearingMotionBlur=!1,o.motionBlurCleared=!0,o.motionBlur=!0),h&&(o.motionBlurTimeout=setTimeout((function(){o.motionBlurTimeout=null,o.clearedForMotionBlur[o.NODE]=!1,o.clearedForMotionBlur[o.DRAG]=!1,o.motionBlur=!1,o.clearingMotionBlur=!d,o.mbFrames=0,c[o.NODE]=!0,c[o.DRAG]=!0,o.redraw()}),100)),t||l.emit("render")};for(var Qu={drawPolygonPath:function(e,t,n,r,i,a){var o=r/2,s=i/2;e.beginPath&&e.beginPath(),e.moveTo(t+o*a[0],n+s*a[1]);for(var l=1;l0&&a>0){h.clearRect(0,0,i,a),h.globalCompositeOperation="source-over";var p=this.getCachedZSortedEles();if(e.full)h.translate(-n.x1*l,-n.y1*l),h.scale(l,l),this.drawElements(h,p),h.scale(1/l,1/l),h.translate(n.x1*l,n.y1*l);else{var f=t.pan(),g={x:f.x*l,y:f.y*l};l*=t.zoom(),h.translate(g.x,g.y),h.scale(l,l),this.drawElements(h,p),h.scale(1/l,1/l),h.translate(-g.x,-g.y)}e.bg&&(h.globalCompositeOperation="destination-over",h.fillStyle=e.bg,h.rect(0,0,i,a),h.fill())}return d},ac.png=function(e){return sc(e,this.bufferCanvasImage(e),"image/png")},ac.jpg=function(e){return sc(e,this.bufferCanvasImage(e),"image/jpeg")};var lc={nodeShapeImpl:function(e,t,n,r,i,a,o,s){switch(e){case"ellipse":return this.drawEllipsePath(t,n,r,i,a);case"polygon":return this.drawPolygonPath(t,n,r,i,a,o);case"round-polygon":return this.drawRoundPolygonPath(t,n,r,i,a,o,s);case"roundrectangle":case"round-rectangle":return this.drawRoundRectanglePath(t,n,r,i,a,s);case"cutrectangle":case"cut-rectangle":return this.drawCutRectanglePath(t,n,r,i,a,o,s);case"bottomroundrectangle":case"bottom-round-rectangle":return this.drawBottomRoundRectanglePath(t,n,r,i,a,s);case"barrel":return this.drawBarrelPath(t,n,r,i,a)}}},uc=dc,cc=dc.prototype;function dc(e){var t=this,n=t.cy.window().document;t.data={canvases:new Array(cc.CANVAS_LAYERS),contexts:new Array(cc.CANVAS_LAYERS),canvasNeedsRedraw:new Array(cc.CANVAS_LAYERS),bufferCanvases:new Array(cc.BUFFER_COUNT),bufferContexts:new Array(cc.CANVAS_LAYERS)};t.data.canvasContainer=n.createElement("div");var r=t.data.canvasContainer.style;t.data.canvasContainer.style["-webkit-tap-highlight-color"]="rgba(0,0,0,0)",r.position="relative",r.zIndex="0",r.overflow="hidden";var i=e.cy.container();i.appendChild(t.data.canvasContainer),i.style["-webkit-tap-highlight-color"]="rgba(0,0,0,0)";var a={"-webkit-user-select":"none","-moz-user-select":"-moz-none","user-select":"none","-webkit-tap-highlight-color":"rgba(0,0,0,0)","outline-style":"none"};c&&c.userAgent.match(/msie|trident|edge/i)&&(a["-ms-touch-action"]="none",a["touch-action"]="none");for(var o=0;o{ +throw Error("map is read-only")}:n instanceof Set&&(n.add=n.clear=n.delete=()=>{ +throw Error("set is read-only") +}),Object.freeze(n),Object.getOwnPropertyNames(n).forEach((t=>{ +const a=n[t],i=typeof a;"object"!==i&&"function"!==i||Object.isFrozen(a)||e(a) +})),n}class n{constructor(e){ +void 0===e.data&&(e.data={}),this.data=e.data,this.isMatchIgnored=!1} +ignoreMatch(){this.isMatchIgnored=!0}}function t(e){ +return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'") +}function a(e,...n){const t=Object.create(null);for(const n in e)t[n]=e[n] +;return n.forEach((e=>{for(const n in e)t[n]=e[n]})),t}const i=e=>!!e.scope +;class r{constructor(e,n){ +this.buffer="",this.classPrefix=n.classPrefix,e.walk(this)}addText(e){ +this.buffer+=t(e)}openNode(e){if(!i(e))return;const n=((e,{prefix:n})=>{ +if(e.startsWith("language:"))return e.replace("language:","language-") +;if(e.includes(".")){const t=e.split(".") +;return[`${n}${t.shift()}`,...t.map(((e,n)=>`${e}${"_".repeat(n+1)}`))].join(" ") +}return`${n}${e}`})(e.scope,{prefix:this.classPrefix});this.span(n)} +closeNode(e){i(e)&&(this.buffer+="")}value(){return this.buffer}span(e){ +this.buffer+=``}}const s=(e={})=>{const n={children:[]} +;return Object.assign(n,e),n};class o{constructor(){ +this.rootNode=s(),this.stack=[this.rootNode]}get top(){ +return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){ +this.top.children.push(e)}openNode(e){const n=s({scope:e}) +;this.add(n),this.stack.push(n)}closeNode(){ +if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){ +for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)} +walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,n){ +return"string"==typeof n?e.addText(n):n.children&&(e.openNode(n), +n.children.forEach((n=>this._walk(e,n))),e.closeNode(n)),e}static _collapse(e){ +"string"!=typeof e&&e.children&&(e.children.every((e=>"string"==typeof e))?e.children=[e.children.join("")]:e.children.forEach((e=>{ +o._collapse(e)})))}}class l extends o{constructor(e){super(),this.options=e} +addText(e){""!==e&&this.add(e)}startScope(e){this.openNode(e)}endScope(){ +this.closeNode()}__addSublanguage(e,n){const t=e.root +;n&&(t.scope="language:"+n),this.add(t)}toHTML(){ +return new r(this,this.options).value()}finalize(){ +return this.closeAllNodes(),!0}}function c(e){ +return e?"string"==typeof e?e:e.source:null}function d(e){return b("(?=",e,")")} +function g(e){return b("(?:",e,")*")}function u(e){return b("(?:",e,")?")} +function b(...e){return e.map((e=>c(e))).join("")}function m(...e){const n=(e=>{ +const n=e[e.length-1] +;return"object"==typeof n&&n.constructor===Object?(e.splice(e.length-1,1),n):{} +})(e);return"("+(n.capture?"":"?:")+e.map((e=>c(e))).join("|")+")"} +function p(e){return RegExp(e.toString()+"|").exec("").length-1} +const _=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./ +;function h(e,{joinWith:n}){let t=0;return e.map((e=>{t+=1;const n=t +;let a=c(e),i="";for(;a.length>0;){const e=_.exec(a);if(!e){i+=a;break} +i+=a.substring(0,e.index), +a=a.substring(e.index+e[0].length),"\\"===e[0][0]&&e[1]?i+="\\"+(Number(e[1])+n):(i+=e[0], +"("===e[0]&&t++)}return i})).map((e=>`(${e})`)).join(n)} +const f="[a-zA-Z]\\w*",E="[a-zA-Z_]\\w*",y="\\b\\d+(\\.\\d+)?",N="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",w="\\b(0b[01]+)",v={ +begin:"\\\\[\\s\\S]",relevance:0},O={scope:"string",begin:"'",end:"'", +illegal:"\\n",contains:[v]},k={scope:"string",begin:'"',end:'"',illegal:"\\n", +contains:[v]},x=(e,n,t={})=>{const i=a({scope:"comment",begin:e,end:n, +contains:[]},t);i.contains.push({scope:"doctag", +begin:"[ ]*(?=(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):)", +end:/(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):/,excludeBegin:!0,relevance:0}) +;const r=m("I","a","is","so","us","to","at","if","in","it","on",/[A-Za-z]+['](d|ve|re|ll|t|s|n)/,/[A-Za-z]+[-][a-z]+/,/[A-Za-z][a-z]{2,}/) +;return i.contains.push({begin:b(/[ ]+/,"(",r,/[.]?[:]?([.][ ]|[ ])/,"){3}")}),i +},M=x("//","$"),S=x("/\\*","\\*/"),A=x("#","$");var C=Object.freeze({ +__proto__:null,APOS_STRING_MODE:O,BACKSLASH_ESCAPE:v,BINARY_NUMBER_MODE:{ +scope:"number",begin:w,relevance:0},BINARY_NUMBER_RE:w,COMMENT:x, +C_BLOCK_COMMENT_MODE:S,C_LINE_COMMENT_MODE:M,C_NUMBER_MODE:{scope:"number", +begin:N,relevance:0},C_NUMBER_RE:N,END_SAME_AS_BEGIN:e=>Object.assign(e,{ +"on:begin":(e,n)=>{n.data._beginMatch=e[1]},"on:end":(e,n)=>{ +n.data._beginMatch!==e[1]&&n.ignoreMatch()}}),HASH_COMMENT_MODE:A,IDENT_RE:f, +MATCH_NOTHING_RE:/\b\B/,METHOD_GUARD:{begin:"\\.\\s*"+E,relevance:0}, +NUMBER_MODE:{scope:"number",begin:y,relevance:0},NUMBER_RE:y, +PHRASAL_WORDS_MODE:{ +begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/ +},QUOTE_STRING_MODE:k,REGEXP_MODE:{scope:"regexp",begin:/\/(?=[^/\n]*\/)/, +end:/\/[gimuy]*/,contains:[v,{begin:/\[/,end:/\]/,relevance:0,contains:[v]}]}, +RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~", +SHEBANG:(e={})=>{const n=/^#![ ]*\// +;return e.binary&&(e.begin=b(n,/.*\b/,e.binary,/\b.*/)),a({scope:"meta",begin:n, +end:/$/,relevance:0,"on:begin":(e,n)=>{0!==e.index&&n.ignoreMatch()}},e)}, +TITLE_MODE:{scope:"title",begin:f,relevance:0},UNDERSCORE_IDENT_RE:E, +UNDERSCORE_TITLE_MODE:{scope:"title",begin:E,relevance:0}});function T(e,n){ +"."===e.input[e.index-1]&&n.ignoreMatch()}function R(e,n){ +void 0!==e.className&&(e.scope=e.className,delete e.className)}function D(e,n){ +n&&e.beginKeywords&&(e.begin="\\b("+e.beginKeywords.split(" ").join("|")+")(?!\\.)(?=\\b|\\s)", +e.__beforeBegin=T,e.keywords=e.keywords||e.beginKeywords,delete e.beginKeywords, +void 0===e.relevance&&(e.relevance=0))}function I(e,n){ +Array.isArray(e.illegal)&&(e.illegal=m(...e.illegal))}function L(e,n){ +if(e.match){ +if(e.begin||e.end)throw Error("begin & end are not supported with match") +;e.begin=e.match,delete e.match}}function B(e,n){ +void 0===e.relevance&&(e.relevance=1)}const $=(e,n)=>{if(!e.beforeMatch)return +;if(e.starts)throw Error("beforeMatch cannot be used with starts") +;const t=Object.assign({},e);Object.keys(e).forEach((n=>{delete e[n] +})),e.keywords=t.keywords,e.begin=b(t.beforeMatch,d(t.begin)),e.starts={ +relevance:0,contains:[Object.assign(t,{endsParent:!0})] +},e.relevance=0,delete t.beforeMatch +},z=["of","and","for","in","not","or","if","then","parent","list","value"],F="keyword" +;function U(e,n,t=F){const a=Object.create(null) +;return"string"==typeof e?i(t,e.split(" ")):Array.isArray(e)?i(t,e):Object.keys(e).forEach((t=>{ +Object.assign(a,U(e[t],n,t))})),a;function i(e,t){ +n&&(t=t.map((e=>e.toLowerCase()))),t.forEach((n=>{const t=n.split("|") +;a[t[0]]=[e,j(t[0],t[1])]}))}}function j(e,n){ +return n?Number(n):(e=>z.includes(e.toLowerCase()))(e)?0:1}const P={},K=e=>{ +console.error(e)},H=(e,...n)=>{console.log("WARN: "+e,...n)},q=(e,n)=>{ +P[`${e}/${n}`]||(console.log(`Deprecated as of ${e}. ${n}`),P[`${e}/${n}`]=!0) +},G=Error();function Z(e,n,{key:t}){let a=0;const i=e[t],r={},s={} +;for(let e=1;e<=n.length;e++)s[e+a]=i[e],r[e+a]=!0,a+=p(n[e-1]) +;e[t]=s,e[t]._emit=r,e[t]._multi=!0}function W(e){(e=>{ +e.scope&&"object"==typeof e.scope&&null!==e.scope&&(e.beginScope=e.scope, +delete e.scope)})(e),"string"==typeof e.beginScope&&(e.beginScope={ +_wrap:e.beginScope}),"string"==typeof e.endScope&&(e.endScope={_wrap:e.endScope +}),(e=>{if(Array.isArray(e.begin)){ +if(e.skip||e.excludeBegin||e.returnBegin)throw K("skip, excludeBegin, returnBegin not compatible with beginScope: {}"), +G +;if("object"!=typeof e.beginScope||null===e.beginScope)throw K("beginScope must be object"), +G;Z(e,e.begin,{key:"beginScope"}),e.begin=h(e.begin,{joinWith:""})}})(e),(e=>{ +if(Array.isArray(e.end)){ +if(e.skip||e.excludeEnd||e.returnEnd)throw K("skip, excludeEnd, returnEnd not compatible with endScope: {}"), +G +;if("object"!=typeof e.endScope||null===e.endScope)throw K("endScope must be object"), +G;Z(e,e.end,{key:"endScope"}),e.end=h(e.end,{joinWith:""})}})(e)}function Q(e){ +function n(n,t){ +return RegExp(c(n),"m"+(e.case_insensitive?"i":"")+(e.unicodeRegex?"u":"")+(t?"g":"")) +}class t{constructor(){ +this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0} +addRule(e,n){ +n.position=this.position++,this.matchIndexes[this.matchAt]=n,this.regexes.push([n,e]), +this.matchAt+=p(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null) +;const e=this.regexes.map((e=>e[1]));this.matcherRe=n(h(e,{joinWith:"|" +}),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex +;const n=this.matcherRe.exec(e);if(!n)return null +;const t=n.findIndex(((e,n)=>n>0&&void 0!==e)),a=this.matchIndexes[t] +;return n.splice(0,t),Object.assign(n,a)}}class i{constructor(){ +this.rules=[],this.multiRegexes=[], +this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){ +if(this.multiRegexes[e])return this.multiRegexes[e];const n=new t +;return this.rules.slice(e).forEach((([e,t])=>n.addRule(e,t))), +n.compile(),this.multiRegexes[e]=n,n}resumingScanAtSamePosition(){ +return 0!==this.regexIndex}considerAll(){this.regexIndex=0}addRule(e,n){ +this.rules.push([e,n]),"begin"===n.type&&this.count++}exec(e){ +const n=this.getMatcher(this.regexIndex);n.lastIndex=this.lastIndex +;let t=n.exec(e) +;if(this.resumingScanAtSamePosition())if(t&&t.index===this.lastIndex);else{ +const n=this.getMatcher(0);n.lastIndex=this.lastIndex+1,t=n.exec(e)} +return t&&(this.regexIndex+=t.position+1, +this.regexIndex===this.count&&this.considerAll()),t}} +if(e.compilerExtensions||(e.compilerExtensions=[]), +e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.") +;return e.classNameAliases=a(e.classNameAliases||{}),function t(r,s){const o=r +;if(r.isCompiled)return o +;[R,L,W,$].forEach((e=>e(r,s))),e.compilerExtensions.forEach((e=>e(r,s))), +r.__beforeBegin=null,[D,I,B].forEach((e=>e(r,s))),r.isCompiled=!0;let l=null +;return"object"==typeof r.keywords&&r.keywords.$pattern&&(r.keywords=Object.assign({},r.keywords), +l=r.keywords.$pattern, +delete r.keywords.$pattern),l=l||/\w+/,r.keywords&&(r.keywords=U(r.keywords,e.case_insensitive)), +o.keywordPatternRe=n(l,!0), +s&&(r.begin||(r.begin=/\B|\b/),o.beginRe=n(o.begin),r.end||r.endsWithParent||(r.end=/\B|\b/), +r.end&&(o.endRe=n(o.end)), +o.terminatorEnd=c(o.end)||"",r.endsWithParent&&s.terminatorEnd&&(o.terminatorEnd+=(r.end?"|":"")+s.terminatorEnd)), +r.illegal&&(o.illegalRe=n(r.illegal)), +r.contains||(r.contains=[]),r.contains=[].concat(...r.contains.map((e=>(e=>(e.variants&&!e.cachedVariants&&(e.cachedVariants=e.variants.map((n=>a(e,{ +variants:null},n)))),e.cachedVariants?e.cachedVariants:X(e)?a(e,{ +starts:e.starts?a(e.starts):null +}):Object.isFrozen(e)?a(e):e))("self"===e?r:e)))),r.contains.forEach((e=>{t(e,o) +})),r.starts&&t(r.starts,s),o.matcher=(e=>{const n=new i +;return e.contains.forEach((e=>n.addRule(e.begin,{rule:e,type:"begin" +}))),e.terminatorEnd&&n.addRule(e.terminatorEnd,{type:"end" +}),e.illegal&&n.addRule(e.illegal,{type:"illegal"}),n})(o),o}(e)}function X(e){ +return!!e&&(e.endsWithParent||X(e.starts))}class V extends Error{ +constructor(e,n){super(e),this.name="HTMLInjectionError",this.html=n}} +const J=t,Y=a,ee=Symbol("nomatch"),ne=t=>{ +const a=Object.create(null),i=Object.create(null),r=[];let s=!0 +;const o="Could not find the language '{}', did you forget to load/include a language module?",c={ +disableAutodetect:!0,name:"Plain text",contains:[]};let p={ +ignoreUnescapedHTML:!1,throwUnescapedHTML:!1,noHighlightRe:/^(no-?highlight)$/i, +languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-", +cssSelector:"pre code",languages:null,__emitter:l};function _(e){ +return p.noHighlightRe.test(e)}function h(e,n,t){let a="",i="" +;"object"==typeof n?(a=e, +t=n.ignoreIllegals,i=n.language):(q("10.7.0","highlight(lang, code, ...args) has been deprecated."), +q("10.7.0","Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277"), +i=e,a=n),void 0===t&&(t=!0);const r={code:a,language:i};x("before:highlight",r) +;const s=r.result?r.result:f(r.language,r.code,t) +;return s.code=r.code,x("after:highlight",s),s}function f(e,t,i,r){ +const l=Object.create(null);function c(){if(!x.keywords)return void S.addText(A) +;let e=0;x.keywordPatternRe.lastIndex=0;let n=x.keywordPatternRe.exec(A),t="" +;for(;n;){t+=A.substring(e,n.index) +;const i=w.case_insensitive?n[0].toLowerCase():n[0],r=(a=i,x.keywords[a]);if(r){ +const[e,a]=r +;if(S.addText(t),t="",l[i]=(l[i]||0)+1,l[i]<=7&&(C+=a),e.startsWith("_"))t+=n[0];else{ +const t=w.classNameAliases[e]||e;g(n[0],t)}}else t+=n[0] +;e=x.keywordPatternRe.lastIndex,n=x.keywordPatternRe.exec(A)}var a +;t+=A.substring(e),S.addText(t)}function d(){null!=x.subLanguage?(()=>{ +if(""===A)return;let e=null;if("string"==typeof x.subLanguage){ +if(!a[x.subLanguage])return void S.addText(A) +;e=f(x.subLanguage,A,!0,M[x.subLanguage]),M[x.subLanguage]=e._top +}else e=E(A,x.subLanguage.length?x.subLanguage:null) +;x.relevance>0&&(C+=e.relevance),S.__addSublanguage(e._emitter,e.language) +})():c(),A=""}function g(e,n){ +""!==e&&(S.startScope(n),S.addText(e),S.endScope())}function u(e,n){let t=1 +;const a=n.length-1;for(;t<=a;){if(!e._emit[t]){t++;continue} +const a=w.classNameAliases[e[t]]||e[t],i=n[t];a?g(i,a):(A=i,c(),A=""),t++}} +function b(e,n){ +return e.scope&&"string"==typeof e.scope&&S.openNode(w.classNameAliases[e.scope]||e.scope), +e.beginScope&&(e.beginScope._wrap?(g(A,w.classNameAliases[e.beginScope._wrap]||e.beginScope._wrap), +A=""):e.beginScope._multi&&(u(e.beginScope,n),A="")),x=Object.create(e,{parent:{ +value:x}}),x}function m(e,t,a){let i=((e,n)=>{const t=e&&e.exec(n) +;return t&&0===t.index})(e.endRe,a);if(i){if(e["on:end"]){const a=new n(e) +;e["on:end"](t,a),a.isMatchIgnored&&(i=!1)}if(i){ +for(;e.endsParent&&e.parent;)e=e.parent;return e}} +if(e.endsWithParent)return m(e.parent,t,a)}function _(e){ +return 0===x.matcher.regexIndex?(A+=e[0],1):(D=!0,0)}function h(e){ +const n=e[0],a=t.substring(e.index),i=m(x,e,a);if(!i)return ee;const r=x +;x.endScope&&x.endScope._wrap?(d(), +g(n,x.endScope._wrap)):x.endScope&&x.endScope._multi?(d(), +u(x.endScope,e)):r.skip?A+=n:(r.returnEnd||r.excludeEnd||(A+=n), +d(),r.excludeEnd&&(A=n));do{ +x.scope&&S.closeNode(),x.skip||x.subLanguage||(C+=x.relevance),x=x.parent +}while(x!==i.parent);return i.starts&&b(i.starts,e),r.returnEnd?0:n.length} +let y={};function N(a,r){const o=r&&r[0];if(A+=a,null==o)return d(),0 +;if("begin"===y.type&&"end"===r.type&&y.index===r.index&&""===o){ +if(A+=t.slice(r.index,r.index+1),!s){const n=Error(`0 width match regex (${e})`) +;throw n.languageName=e,n.badRule=y.rule,n}return 1} +if(y=r,"begin"===r.type)return(e=>{ +const t=e[0],a=e.rule,i=new n(a),r=[a.__beforeBegin,a["on:begin"]] +;for(const n of r)if(n&&(n(e,i),i.isMatchIgnored))return _(t) +;return a.skip?A+=t:(a.excludeBegin&&(A+=t), +d(),a.returnBegin||a.excludeBegin||(A=t)),b(a,e),a.returnBegin?0:t.length})(r) +;if("illegal"===r.type&&!i){ +const e=Error('Illegal lexeme "'+o+'" for mode "'+(x.scope||"")+'"') +;throw e.mode=x,e}if("end"===r.type){const e=h(r);if(e!==ee)return e} +if("illegal"===r.type&&""===o)return 1 +;if(R>1e5&&R>3*r.index)throw Error("potential infinite loop, way more iterations than matches") +;return A+=o,o.length}const w=v(e) +;if(!w)throw K(o.replace("{}",e)),Error('Unknown language: "'+e+'"') +;const O=Q(w);let k="",x=r||O;const M={},S=new p.__emitter(p);(()=>{const e=[] +;for(let n=x;n!==w;n=n.parent)n.scope&&e.unshift(n.scope) +;e.forEach((e=>S.openNode(e)))})();let A="",C=0,T=0,R=0,D=!1;try{ +if(w.__emitTokens)w.__emitTokens(t,S);else{for(x.matcher.considerAll();;){ +R++,D?D=!1:x.matcher.considerAll(),x.matcher.lastIndex=T +;const e=x.matcher.exec(t);if(!e)break;const n=N(t.substring(T,e.index),e) +;T=e.index+n}N(t.substring(T))}return S.finalize(),k=S.toHTML(),{language:e, +value:k,relevance:C,illegal:!1,_emitter:S,_top:x}}catch(n){ +if(n.message&&n.message.includes("Illegal"))return{language:e,value:J(t), +illegal:!0,relevance:0,_illegalBy:{message:n.message,index:T, +context:t.slice(T-100,T+100),mode:n.mode,resultSoFar:k},_emitter:S};if(s)return{ +language:e,value:J(t),illegal:!1,relevance:0,errorRaised:n,_emitter:S,_top:x} +;throw n}}function E(e,n){n=n||p.languages||Object.keys(a);const t=(e=>{ +const n={value:J(e),illegal:!1,relevance:0,_top:c,_emitter:new p.__emitter(p)} +;return n._emitter.addText(e),n})(e),i=n.filter(v).filter(k).map((n=>f(n,e,!1))) +;i.unshift(t);const r=i.sort(((e,n)=>{ +if(e.relevance!==n.relevance)return n.relevance-e.relevance +;if(e.language&&n.language){if(v(e.language).supersetOf===n.language)return 1 +;if(v(n.language).supersetOf===e.language)return-1}return 0})),[s,o]=r,l=s +;return l.secondBest=o,l}function y(e){let n=null;const t=(e=>{ +let n=e.className+" ";n+=e.parentNode?e.parentNode.className:"" +;const t=p.languageDetectRe.exec(n);if(t){const n=v(t[1]) +;return n||(H(o.replace("{}",t[1])), +H("Falling back to no-highlight mode for this block.",e)),n?t[1]:"no-highlight"} +return n.split(/\s+/).find((e=>_(e)||v(e)))})(e);if(_(t))return +;if(x("before:highlightElement",{el:e,language:t +}),e.dataset.highlighted)return void console.log("Element previously highlighted. To highlight again, first unset `dataset.highlighted`.",e) +;if(e.children.length>0&&(p.ignoreUnescapedHTML||(console.warn("One of your code blocks includes unescaped HTML. This is a potentially serious security risk."), +console.warn("https://github.com/highlightjs/highlight.js/wiki/security"), +console.warn("The element with unescaped HTML:"), +console.warn(e)),p.throwUnescapedHTML))throw new V("One of your code blocks includes unescaped HTML.",e.innerHTML) +;n=e;const a=n.textContent,r=t?h(a,{language:t,ignoreIllegals:!0}):E(a) +;e.innerHTML=r.value,e.dataset.highlighted="yes",((e,n,t)=>{const a=n&&i[n]||t +;e.classList.add("hljs"),e.classList.add("language-"+a) +})(e,t,r.language),e.result={language:r.language,re:r.relevance, +relevance:r.relevance},r.secondBest&&(e.secondBest={ +language:r.secondBest.language,relevance:r.secondBest.relevance +}),x("after:highlightElement",{el:e,result:r,text:a})}let N=!1;function w(){ +"loading"!==document.readyState?document.querySelectorAll(p.cssSelector).forEach(y):N=!0 +}function v(e){return e=(e||"").toLowerCase(),a[e]||a[i[e]]} +function O(e,{languageName:n}){"string"==typeof e&&(e=[e]),e.forEach((e=>{ +i[e.toLowerCase()]=n}))}function k(e){const n=v(e) +;return n&&!n.disableAutodetect}function x(e,n){const t=e;r.forEach((e=>{ +e[t]&&e[t](n)}))} +"undefined"!=typeof window&&window.addEventListener&&window.addEventListener("DOMContentLoaded",(()=>{ +N&&w()}),!1),Object.assign(t,{highlight:h,highlightAuto:E,highlightAll:w, +highlightElement:y, +highlightBlock:e=>(q("10.7.0","highlightBlock will be removed entirely in v12.0"), +q("10.7.0","Please use highlightElement now."),y(e)),configure:e=>{p=Y(p,e)}, +initHighlighting:()=>{ +w(),q("10.6.0","initHighlighting() deprecated. Use highlightAll() now.")}, +initHighlightingOnLoad:()=>{ +w(),q("10.6.0","initHighlightingOnLoad() deprecated. Use highlightAll() now.") +},registerLanguage:(e,n)=>{let i=null;try{i=n(t)}catch(n){ +if(K("Language definition for '{}' could not be registered.".replace("{}",e)), +!s)throw n;K(n),i=c} +i.name||(i.name=e),a[e]=i,i.rawDefinition=n.bind(null,t),i.aliases&&O(i.aliases,{ +languageName:e})},unregisterLanguage:e=>{delete a[e] +;for(const n of Object.keys(i))i[n]===e&&delete i[n]}, +listLanguages:()=>Object.keys(a),getLanguage:v,registerAliases:O, +autoDetection:k,inherit:Y,addPlugin:e=>{(e=>{ +e["before:highlightBlock"]&&!e["before:highlightElement"]&&(e["before:highlightElement"]=n=>{ +e["before:highlightBlock"](Object.assign({block:n.el},n)) +}),e["after:highlightBlock"]&&!e["after:highlightElement"]&&(e["after:highlightElement"]=n=>{ +e["after:highlightBlock"](Object.assign({block:n.el},n))})})(e),r.push(e)}, +removePlugin:e=>{const n=r.indexOf(e);-1!==n&&r.splice(n,1)}}),t.debugMode=()=>{ +s=!1},t.safeMode=()=>{s=!0},t.versionString="11.9.0",t.regex={concat:b, +lookahead:d,either:m,optional:u,anyNumberOfTimes:g} +;for(const n in C)"object"==typeof C[n]&&e(C[n]);return Object.assign(t,C),t +},te=ne({});te.newInstance=()=>ne({});var ae=te;const ie=e=>({IMPORTANT:{ +scope:"meta",begin:"!important"},BLOCK_COMMENT:e.C_BLOCK_COMMENT_MODE,HEXCOLOR:{ +scope:"number",begin:/#(([0-9a-fA-F]{3,4})|(([0-9a-fA-F]{2}){3,4}))\b/}, +FUNCTION_DISPATCH:{className:"built_in",begin:/[\w-]+(?=\()/}, +ATTRIBUTE_SELECTOR_MODE:{scope:"selector-attr",begin:/\[/,end:/\]/,illegal:"$", +contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},CSS_NUMBER_MODE:{ +scope:"number", +begin:e.NUMBER_RE+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?", +relevance:0},CSS_VARIABLE:{className:"attr",begin:/--[A-Za-z_][A-Za-z0-9_-]*/} +}),re=["a","abbr","address","article","aside","audio","b","blockquote","body","button","canvas","caption","cite","code","dd","del","details","dfn","div","dl","dt","em","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","header","hgroup","html","i","iframe","img","input","ins","kbd","label","legend","li","main","mark","menu","nav","object","ol","p","q","quote","samp","section","span","strong","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","tr","ul","var","video"],se=["any-hover","any-pointer","aspect-ratio","color","color-gamut","color-index","device-aspect-ratio","device-height","device-width","display-mode","forced-colors","grid","height","hover","inverted-colors","monochrome","orientation","overflow-block","overflow-inline","pointer","prefers-color-scheme","prefers-contrast","prefers-reduced-motion","prefers-reduced-transparency","resolution","scan","scripting","update","width","min-width","max-width","min-height","max-height"],oe=["active","any-link","blank","checked","current","default","defined","dir","disabled","drop","empty","enabled","first","first-child","first-of-type","fullscreen","future","focus","focus-visible","focus-within","has","host","host-context","hover","indeterminate","in-range","invalid","is","lang","last-child","last-of-type","left","link","local-link","not","nth-child","nth-col","nth-last-child","nth-last-col","nth-last-of-type","nth-of-type","only-child","only-of-type","optional","out-of-range","past","placeholder-shown","read-only","read-write","required","right","root","scope","target","target-within","user-invalid","valid","visited","where"],le=["after","backdrop","before","cue","cue-region","first-letter","first-line","grammar-error","marker","part","placeholder","selection","slotted","spelling-error"],ce=["align-content","align-items","align-self","all","animation","animation-delay","animation-direction","animation-duration","animation-fill-mode","animation-iteration-count","animation-name","animation-play-state","animation-timing-function","backface-visibility","background","background-attachment","background-blend-mode","background-clip","background-color","background-image","background-origin","background-position","background-repeat","background-size","block-size","border","border-block","border-block-color","border-block-end","border-block-end-color","border-block-end-style","border-block-end-width","border-block-start","border-block-start-color","border-block-start-style","border-block-start-width","border-block-style","border-block-width","border-bottom","border-bottom-color","border-bottom-left-radius","border-bottom-right-radius","border-bottom-style","border-bottom-width","border-collapse","border-color","border-image","border-image-outset","border-image-repeat","border-image-slice","border-image-source","border-image-width","border-inline","border-inline-color","border-inline-end","border-inline-end-color","border-inline-end-style","border-inline-end-width","border-inline-start","border-inline-start-color","border-inline-start-style","border-inline-start-width","border-inline-style","border-inline-width","border-left","border-left-color","border-left-style","border-left-width","border-radius","border-right","border-right-color","border-right-style","border-right-width","border-spacing","border-style","border-top","border-top-color","border-top-left-radius","border-top-right-radius","border-top-style","border-top-width","border-width","bottom","box-decoration-break","box-shadow","box-sizing","break-after","break-before","break-inside","caption-side","caret-color","clear","clip","clip-path","clip-rule","color","column-count","column-fill","column-gap","column-rule","column-rule-color","column-rule-style","column-rule-width","column-span","column-width","columns","contain","content","content-visibility","counter-increment","counter-reset","cue","cue-after","cue-before","cursor","direction","display","empty-cells","filter","flex","flex-basis","flex-direction","flex-flow","flex-grow","flex-shrink","flex-wrap","float","flow","font","font-display","font-family","font-feature-settings","font-kerning","font-language-override","font-size","font-size-adjust","font-smoothing","font-stretch","font-style","font-synthesis","font-variant","font-variant-caps","font-variant-east-asian","font-variant-ligatures","font-variant-numeric","font-variant-position","font-variation-settings","font-weight","gap","glyph-orientation-vertical","grid","grid-area","grid-auto-columns","grid-auto-flow","grid-auto-rows","grid-column","grid-column-end","grid-column-start","grid-gap","grid-row","grid-row-end","grid-row-start","grid-template","grid-template-areas","grid-template-columns","grid-template-rows","hanging-punctuation","height","hyphens","icon","image-orientation","image-rendering","image-resolution","ime-mode","inline-size","isolation","justify-content","left","letter-spacing","line-break","line-height","list-style","list-style-image","list-style-position","list-style-type","margin","margin-block","margin-block-end","margin-block-start","margin-bottom","margin-inline","margin-inline-end","margin-inline-start","margin-left","margin-right","margin-top","marks","mask","mask-border","mask-border-mode","mask-border-outset","mask-border-repeat","mask-border-slice","mask-border-source","mask-border-width","mask-clip","mask-composite","mask-image","mask-mode","mask-origin","mask-position","mask-repeat","mask-size","mask-type","max-block-size","max-height","max-inline-size","max-width","min-block-size","min-height","min-inline-size","min-width","mix-blend-mode","nav-down","nav-index","nav-left","nav-right","nav-up","none","normal","object-fit","object-position","opacity","order","orphans","outline","outline-color","outline-offset","outline-style","outline-width","overflow","overflow-wrap","overflow-x","overflow-y","padding","padding-block","padding-block-end","padding-block-start","padding-bottom","padding-inline","padding-inline-end","padding-inline-start","padding-left","padding-right","padding-top","page-break-after","page-break-before","page-break-inside","pause","pause-after","pause-before","perspective","perspective-origin","pointer-events","position","quotes","resize","rest","rest-after","rest-before","right","row-gap","scroll-margin","scroll-margin-block","scroll-margin-block-end","scroll-margin-block-start","scroll-margin-bottom","scroll-margin-inline","scroll-margin-inline-end","scroll-margin-inline-start","scroll-margin-left","scroll-margin-right","scroll-margin-top","scroll-padding","scroll-padding-block","scroll-padding-block-end","scroll-padding-block-start","scroll-padding-bottom","scroll-padding-inline","scroll-padding-inline-end","scroll-padding-inline-start","scroll-padding-left","scroll-padding-right","scroll-padding-top","scroll-snap-align","scroll-snap-stop","scroll-snap-type","scrollbar-color","scrollbar-gutter","scrollbar-width","shape-image-threshold","shape-margin","shape-outside","speak","speak-as","src","tab-size","table-layout","text-align","text-align-all","text-align-last","text-combine-upright","text-decoration","text-decoration-color","text-decoration-line","text-decoration-style","text-emphasis","text-emphasis-color","text-emphasis-position","text-emphasis-style","text-indent","text-justify","text-orientation","text-overflow","text-rendering","text-shadow","text-transform","text-underline-position","top","transform","transform-box","transform-origin","transform-style","transition","transition-delay","transition-duration","transition-property","transition-timing-function","unicode-bidi","vertical-align","visibility","voice-balance","voice-duration","voice-family","voice-pitch","voice-range","voice-rate","voice-stress","voice-volume","white-space","widows","width","will-change","word-break","word-spacing","word-wrap","writing-mode","z-index"].reverse(),de=oe.concat(le) +;var ge="[0-9](_*[0-9])*",ue=`\\.(${ge})`,be="[0-9a-fA-F](_*[0-9a-fA-F])*",me={ +className:"number",variants:[{ +begin:`(\\b(${ge})((${ue})|\\.)?|(${ue}))[eE][+-]?(${ge})[fFdD]?\\b`},{ +begin:`\\b(${ge})((${ue})[fFdD]?\\b|\\.([fFdD]\\b)?)`},{ +begin:`(${ue})[fFdD]?\\b`},{begin:`\\b(${ge})[fFdD]\\b`},{ +begin:`\\b0[xX]((${be})\\.?|(${be})?\\.(${be}))[pP][+-]?(${ge})[fFdD]?\\b`},{ +begin:"\\b(0|[1-9](_*[0-9])*)[lL]?\\b"},{begin:`\\b0[xX](${be})[lL]?\\b`},{ +begin:"\\b0(_*[0-7])*[lL]?\\b"},{begin:"\\b0[bB][01](_*[01])*[lL]?\\b"}], +relevance:0};function pe(e,n,t){return-1===t?"":e.replace(n,(a=>pe(e,n,t-1)))} +const _e="[A-Za-z$_][0-9A-Za-z$_]*",he=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],fe=["true","false","null","undefined","NaN","Infinity"],Ee=["Object","Function","Boolean","Symbol","Math","Date","Number","BigInt","String","RegExp","Array","Float32Array","Float64Array","Int8Array","Uint8Array","Uint8ClampedArray","Int16Array","Int32Array","Uint16Array","Uint32Array","BigInt64Array","BigUint64Array","Set","Map","WeakSet","WeakMap","ArrayBuffer","SharedArrayBuffer","Atomics","DataView","JSON","Promise","Generator","GeneratorFunction","AsyncFunction","Reflect","Proxy","Intl","WebAssembly"],ye=["Error","EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"],Ne=["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],we=["arguments","this","super","console","window","document","localStorage","sessionStorage","module","global"],ve=[].concat(Ne,Ee,ye) +;function Oe(e){const n=e.regex,t=_e,a={begin:/<[A-Za-z0-9\\._:-]+/, +end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(e,n)=>{ +const t=e[0].length+e.index,a=e.input[t] +;if("<"===a||","===a)return void n.ignoreMatch();let i +;">"===a&&(((e,{after:n})=>{const t="",M={ +match:[/const|var|let/,/\s+/,t,/\s*/,/=\s*/,/(async\s*)?/,n.lookahead(x)], +keywords:"async",className:{1:"keyword",3:"title.function"},contains:[f]} +;return{name:"JavaScript",aliases:["js","jsx","mjs","cjs"],keywords:i,exports:{ +PARAMS_CONTAINS:h,CLASS_REFERENCE:y},illegal:/#(?![$_A-z])/, +contains:[e.SHEBANG({label:"shebang",binary:"node",relevance:5}),{ +label:"use_strict",className:"meta",relevance:10, +begin:/^\s*['"]use (strict|asm)['"]/ +},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,d,g,u,b,m,{match:/\$\d+/},l,y,{ +className:"attr",begin:t+n.lookahead(":"),relevance:0},M,{ +begin:"("+e.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*", +keywords:"return throw case",relevance:0,contains:[m,e.REGEXP_MODE,{ +className:"function",begin:x,returnBegin:!0,end:"\\s*=>",contains:[{ +className:"params",variants:[{begin:e.UNDERSCORE_IDENT_RE,relevance:0},{ +className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0, +excludeEnd:!0,keywords:i,contains:h}]}]},{begin:/,/,relevance:0},{match:/\s+/, +relevance:0},{variants:[{begin:"<>",end:""},{ +match:/<[A-Za-z0-9\\._:-]+\s*\/>/},{begin:a.begin, +"on:begin":a.isTrulyOpeningTag,end:a.end}],subLanguage:"xml",contains:[{ +begin:a.begin,end:a.end,skip:!0,contains:["self"]}]}]},N,{ +beginKeywords:"while if switch catch for"},{ +begin:"\\b(?!function)"+e.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{", +returnBegin:!0,label:"func.def",contains:[f,e.inherit(e.TITLE_MODE,{begin:t, +className:"title.function"})]},{match:/\.\.\./,relevance:0},O,{match:"\\$"+t, +relevance:0},{match:[/\bconstructor(?=\s*\()/],className:{1:"title.function"}, +contains:[f]},w,{relevance:0,match:/\b[A-Z][A-Z_0-9]+\b/, +className:"variable.constant"},E,k,{match:/\$[(.]/}]}} +const ke=e=>b(/\b/,e,/\w$/.test(e)?/\b/:/\B/),xe=["Protocol","Type"].map(ke),Me=["init","self"].map(ke),Se=["Any","Self"],Ae=["actor","any","associatedtype","async","await",/as\?/,/as!/,"as","borrowing","break","case","catch","class","consume","consuming","continue","convenience","copy","default","defer","deinit","didSet","distributed","do","dynamic","each","else","enum","extension","fallthrough",/fileprivate\(set\)/,"fileprivate","final","for","func","get","guard","if","import","indirect","infix",/init\?/,/init!/,"inout",/internal\(set\)/,"internal","in","is","isolated","nonisolated","lazy","let","macro","mutating","nonmutating",/open\(set\)/,"open","operator","optional","override","postfix","precedencegroup","prefix",/private\(set\)/,"private","protocol",/public\(set\)/,"public","repeat","required","rethrows","return","set","some","static","struct","subscript","super","switch","throws","throw",/try\?/,/try!/,"try","typealias",/unowned\(safe\)/,/unowned\(unsafe\)/,"unowned","var","weak","where","while","willSet"],Ce=["false","nil","true"],Te=["assignment","associativity","higherThan","left","lowerThan","none","right"],Re=["#colorLiteral","#column","#dsohandle","#else","#elseif","#endif","#error","#file","#fileID","#fileLiteral","#filePath","#function","#if","#imageLiteral","#keyPath","#line","#selector","#sourceLocation","#warning"],De=["abs","all","any","assert","assertionFailure","debugPrint","dump","fatalError","getVaList","isKnownUniquelyReferenced","max","min","numericCast","pointwiseMax","pointwiseMin","precondition","preconditionFailure","print","readLine","repeatElement","sequence","stride","swap","swift_unboxFromSwiftValueWithType","transcode","type","unsafeBitCast","unsafeDowncast","withExtendedLifetime","withUnsafeMutablePointer","withUnsafePointer","withVaList","withoutActuallyEscaping","zip"],Ie=m(/[/=\-+!*%<>&|^~?]/,/[\u00A1-\u00A7]/,/[\u00A9\u00AB]/,/[\u00AC\u00AE]/,/[\u00B0\u00B1]/,/[\u00B6\u00BB\u00BF\u00D7\u00F7]/,/[\u2016-\u2017]/,/[\u2020-\u2027]/,/[\u2030-\u203E]/,/[\u2041-\u2053]/,/[\u2055-\u205E]/,/[\u2190-\u23FF]/,/[\u2500-\u2775]/,/[\u2794-\u2BFF]/,/[\u2E00-\u2E7F]/,/[\u3001-\u3003]/,/[\u3008-\u3020]/,/[\u3030]/),Le=m(Ie,/[\u0300-\u036F]/,/[\u1DC0-\u1DFF]/,/[\u20D0-\u20FF]/,/[\uFE00-\uFE0F]/,/[\uFE20-\uFE2F]/),Be=b(Ie,Le,"*"),$e=m(/[a-zA-Z_]/,/[\u00A8\u00AA\u00AD\u00AF\u00B2-\u00B5\u00B7-\u00BA]/,/[\u00BC-\u00BE\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF]/,/[\u0100-\u02FF\u0370-\u167F\u1681-\u180D\u180F-\u1DBF]/,/[\u1E00-\u1FFF]/,/[\u200B-\u200D\u202A-\u202E\u203F-\u2040\u2054\u2060-\u206F]/,/[\u2070-\u20CF\u2100-\u218F\u2460-\u24FF\u2776-\u2793]/,/[\u2C00-\u2DFF\u2E80-\u2FFF]/,/[\u3004-\u3007\u3021-\u302F\u3031-\u303F\u3040-\uD7FF]/,/[\uF900-\uFD3D\uFD40-\uFDCF\uFDF0-\uFE1F\uFE30-\uFE44]/,/[\uFE47-\uFEFE\uFF00-\uFFFD]/),ze=m($e,/\d/,/[\u0300-\u036F\u1DC0-\u1DFF\u20D0-\u20FF\uFE20-\uFE2F]/),Fe=b($e,ze,"*"),Ue=b(/[A-Z]/,ze,"*"),je=["attached","autoclosure",b(/convention\(/,m("swift","block","c"),/\)/),"discardableResult","dynamicCallable","dynamicMemberLookup","escaping","freestanding","frozen","GKInspectable","IBAction","IBDesignable","IBInspectable","IBOutlet","IBSegueAction","inlinable","main","nonobjc","NSApplicationMain","NSCopying","NSManaged",b(/objc\(/,Fe,/\)/),"objc","objcMembers","propertyWrapper","requires_stored_property_inits","resultBuilder","Sendable","testable","UIApplicationMain","unchecked","unknown","usableFromInline","warn_unqualified_access"],Pe=["iOS","iOSApplicationExtension","macOS","macOSApplicationExtension","macCatalyst","macCatalystApplicationExtension","watchOS","watchOSApplicationExtension","tvOS","tvOSApplicationExtension","swift"] +;var Ke=Object.freeze({__proto__:null,grmr_bash:e=>{const n=e.regex,t={},a={ +begin:/\$\{/,end:/\}/,contains:["self",{begin:/:-/,contains:[t]}]} +;Object.assign(t,{className:"variable",variants:[{ +begin:n.concat(/\$[\w\d#@][\w\d_]*/,"(?![\\w\\d])(?![$])")},a]});const i={ +className:"subst",begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE]},r={ +begin:/<<-?\s*(?=\w+)/,starts:{contains:[e.END_SAME_AS_BEGIN({begin:/(\w+)/, +end:/(\w+)/,className:"string"})]}},s={className:"string",begin:/"/,end:/"/, +contains:[e.BACKSLASH_ESCAPE,t,i]};i.contains.push(s);const o={begin:/\$?\(\(/, +end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},e.NUMBER_MODE,t] +},l=e.SHEBANG({binary:"(fish|bash|zsh|sh|csh|ksh|tcsh|dash|scsh)",relevance:10 +}),c={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0, +contains:[e.inherit(e.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{ +name:"Bash",aliases:["sh"],keywords:{$pattern:/\b[a-z][a-z0-9._-]+\b/, +keyword:["if","then","else","elif","fi","for","while","until","in","do","done","case","esac","function","select"], +literal:["true","false"], +built_in:["break","cd","continue","eval","exec","exit","export","getopts","hash","pwd","readonly","return","shift","test","times","trap","umask","unset","alias","bind","builtin","caller","command","declare","echo","enable","help","let","local","logout","mapfile","printf","read","readarray","source","type","typeset","ulimit","unalias","set","shopt","autoload","bg","bindkey","bye","cap","chdir","clone","comparguments","compcall","compctl","compdescribe","compfiles","compgroups","compquote","comptags","comptry","compvalues","dirs","disable","disown","echotc","echoti","emulate","fc","fg","float","functions","getcap","getln","history","integer","jobs","kill","limit","log","noglob","popd","print","pushd","pushln","rehash","sched","setcap","setopt","stat","suspend","ttyctl","unfunction","unhash","unlimit","unsetopt","vared","wait","whence","where","which","zcompile","zformat","zftp","zle","zmodload","zparseopts","zprof","zpty","zregexparse","zsocket","zstyle","ztcp","chcon","chgrp","chown","chmod","cp","dd","df","dir","dircolors","ln","ls","mkdir","mkfifo","mknod","mktemp","mv","realpath","rm","rmdir","shred","sync","touch","truncate","vdir","b2sum","base32","base64","cat","cksum","comm","csplit","cut","expand","fmt","fold","head","join","md5sum","nl","numfmt","od","paste","ptx","pr","sha1sum","sha224sum","sha256sum","sha384sum","sha512sum","shuf","sort","split","sum","tac","tail","tr","tsort","unexpand","uniq","wc","arch","basename","chroot","date","dirname","du","echo","env","expr","factor","groups","hostid","id","link","logname","nice","nohup","nproc","pathchk","pinky","printenv","printf","pwd","readlink","runcon","seq","sleep","stat","stdbuf","stty","tee","test","timeout","tty","uname","unlink","uptime","users","who","whoami","yes"] +},contains:[l,e.SHEBANG(),c,o,e.HASH_COMMENT_MODE,r,{match:/(\/[a-z._-]+)+/},s,{ +match:/\\"/},{className:"string",begin:/'/,end:/'/},{match:/\\'/},t]}}, +grmr_c:e=>{const n=e.regex,t=e.COMMENT("//","$",{contains:[{begin:/\\\n/}] +}),a="decltype\\(auto\\)",i="[a-zA-Z_]\\w*::",r="("+a+"|"+n.optional(i)+"[a-zA-Z_]\\w*"+n.optional("<[^<>]+>")+")",s={ +className:"type",variants:[{begin:"\\b[a-z\\d_]*_t\\b"},{ +match:/\batomic_[a-z]{3,6}\b/}]},o={className:"string",variants:[{ +begin:'(u8?|U|L)?"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{ +begin:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)", +end:"'",illegal:"."},e.END_SAME_AS_BEGIN({ +begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\(/,end:/\)([^()\\ ]{0,16})"/})]},l={ +className:"number",variants:[{begin:"\\b(0b[01']+)"},{ +begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)((ll|LL|l|L)(u|U)?|(u|U)(ll|LL|l|L)?|f|F|b|B)" +},{ +begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)" +}],relevance:0},c={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{ +keyword:"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include" +},contains:[{begin:/\\\n/,relevance:0},e.inherit(o,{className:"string"}),{ +className:"string",begin:/<.*?>/},t,e.C_BLOCK_COMMENT_MODE]},d={ +className:"title",begin:n.optional(i)+e.IDENT_RE,relevance:0 +},g=n.optional(i)+e.IDENT_RE+"\\s*\\(",u={ +keyword:["asm","auto","break","case","continue","default","do","else","enum","extern","for","fortran","goto","if","inline","register","restrict","return","sizeof","struct","switch","typedef","union","volatile","while","_Alignas","_Alignof","_Atomic","_Generic","_Noreturn","_Static_assert","_Thread_local","alignas","alignof","noreturn","static_assert","thread_local","_Pragma"], +type:["float","double","signed","unsigned","int","short","long","char","void","_Bool","_Complex","_Imaginary","_Decimal32","_Decimal64","_Decimal128","const","static","complex","bool","imaginary"], +literal:"true false NULL", +built_in:"std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set pair bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap priority_queue make_pair array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr" +},b=[c,s,t,e.C_BLOCK_COMMENT_MODE,l,o],m={variants:[{begin:/=/,end:/;/},{ +begin:/\(/,end:/\)/},{beginKeywords:"new throw return else",end:/;/}], +keywords:u,contains:b.concat([{begin:/\(/,end:/\)/,keywords:u, +contains:b.concat(["self"]),relevance:0}]),relevance:0},p={ +begin:"("+r+"[\\*&\\s]+)+"+g,returnBegin:!0,end:/[{;=]/,excludeEnd:!0, +keywords:u,illegal:/[^\w\s\*&:<>.]/,contains:[{begin:a,keywords:u,relevance:0},{ +begin:g,returnBegin:!0,contains:[e.inherit(d,{className:"title.function"})], +relevance:0},{relevance:0,match:/,/},{className:"params",begin:/\(/,end:/\)/, +keywords:u,relevance:0,contains:[t,e.C_BLOCK_COMMENT_MODE,o,l,s,{begin:/\(/, +end:/\)/,keywords:u,relevance:0,contains:["self",t,e.C_BLOCK_COMMENT_MODE,o,l,s] +}]},s,t,e.C_BLOCK_COMMENT_MODE,c]};return{name:"C",aliases:["h"],keywords:u, +disableAutodetect:!0,illegal:"=]/,contains:[{ +beginKeywords:"final class struct"},e.TITLE_MODE]}]),exports:{preprocessor:c, +strings:o,keywords:u}}},grmr_cpp:e=>{const n=e.regex,t=e.COMMENT("//","$",{ +contains:[{begin:/\\\n/}] +}),a="decltype\\(auto\\)",i="[a-zA-Z_]\\w*::",r="(?!struct)("+a+"|"+n.optional(i)+"[a-zA-Z_]\\w*"+n.optional("<[^<>]+>")+")",s={ +className:"type",begin:"\\b[a-z\\d_]*_t\\b"},o={className:"string",variants:[{ +begin:'(u8?|U|L)?"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{ +begin:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)", +end:"'",illegal:"."},e.END_SAME_AS_BEGIN({ +begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\(/,end:/\)([^()\\ ]{0,16})"/})]},l={ +className:"number",variants:[{begin:"\\b(0b[01']+)"},{ +begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)((ll|LL|l|L)(u|U)?|(u|U)(ll|LL|l|L)?|f|F|b|B)" +},{ +begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)" +}],relevance:0},c={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{ +keyword:"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include" +},contains:[{begin:/\\\n/,relevance:0},e.inherit(o,{className:"string"}),{ +className:"string",begin:/<.*?>/},t,e.C_BLOCK_COMMENT_MODE]},d={ +className:"title",begin:n.optional(i)+e.IDENT_RE,relevance:0 +},g=n.optional(i)+e.IDENT_RE+"\\s*\\(",u={ +type:["bool","char","char16_t","char32_t","char8_t","double","float","int","long","short","void","wchar_t","unsigned","signed","const","static"], +keyword:["alignas","alignof","and","and_eq","asm","atomic_cancel","atomic_commit","atomic_noexcept","auto","bitand","bitor","break","case","catch","class","co_await","co_return","co_yield","compl","concept","const_cast|10","consteval","constexpr","constinit","continue","decltype","default","delete","do","dynamic_cast|10","else","enum","explicit","export","extern","false","final","for","friend","goto","if","import","inline","module","mutable","namespace","new","noexcept","not","not_eq","nullptr","operator","or","or_eq","override","private","protected","public","reflexpr","register","reinterpret_cast|10","requires","return","sizeof","static_assert","static_cast|10","struct","switch","synchronized","template","this","thread_local","throw","transaction_safe","transaction_safe_dynamic","true","try","typedef","typeid","typename","union","using","virtual","volatile","while","xor","xor_eq"], +literal:["NULL","false","nullopt","nullptr","true"],built_in:["_Pragma"], +_type_hints:["any","auto_ptr","barrier","binary_semaphore","bitset","complex","condition_variable","condition_variable_any","counting_semaphore","deque","false_type","future","imaginary","initializer_list","istringstream","jthread","latch","lock_guard","multimap","multiset","mutex","optional","ostringstream","packaged_task","pair","promise","priority_queue","queue","recursive_mutex","recursive_timed_mutex","scoped_lock","set","shared_future","shared_lock","shared_mutex","shared_timed_mutex","shared_ptr","stack","string_view","stringstream","timed_mutex","thread","true_type","tuple","unique_lock","unique_ptr","unordered_map","unordered_multimap","unordered_multiset","unordered_set","variant","vector","weak_ptr","wstring","wstring_view"] +},b={className:"function.dispatch",relevance:0,keywords:{ +_hint:["abort","abs","acos","apply","as_const","asin","atan","atan2","calloc","ceil","cerr","cin","clog","cos","cosh","cout","declval","endl","exchange","exit","exp","fabs","floor","fmod","forward","fprintf","fputs","free","frexp","fscanf","future","invoke","isalnum","isalpha","iscntrl","isdigit","isgraph","islower","isprint","ispunct","isspace","isupper","isxdigit","labs","launder","ldexp","log","log10","make_pair","make_shared","make_shared_for_overwrite","make_tuple","make_unique","malloc","memchr","memcmp","memcpy","memset","modf","move","pow","printf","putchar","puts","realloc","scanf","sin","sinh","snprintf","sprintf","sqrt","sscanf","std","stderr","stdin","stdout","strcat","strchr","strcmp","strcpy","strcspn","strlen","strncat","strncmp","strncpy","strpbrk","strrchr","strspn","strstr","swap","tan","tanh","terminate","to_underlying","tolower","toupper","vfprintf","visit","vprintf","vsprintf"] +}, +begin:n.concat(/\b/,/(?!decltype)/,/(?!if)/,/(?!for)/,/(?!switch)/,/(?!while)/,e.IDENT_RE,n.lookahead(/(<[^<>]+>|)\s*\(/)) +},m=[b,c,s,t,e.C_BLOCK_COMMENT_MODE,l,o],p={variants:[{begin:/=/,end:/;/},{ +begin:/\(/,end:/\)/},{beginKeywords:"new throw return else",end:/;/}], +keywords:u,contains:m.concat([{begin:/\(/,end:/\)/,keywords:u, +contains:m.concat(["self"]),relevance:0}]),relevance:0},_={className:"function", +begin:"("+r+"[\\*&\\s]+)+"+g,returnBegin:!0,end:/[{;=]/,excludeEnd:!0, +keywords:u,illegal:/[^\w\s\*&:<>.]/,contains:[{begin:a,keywords:u,relevance:0},{ +begin:g,returnBegin:!0,contains:[d],relevance:0},{begin:/::/,relevance:0},{ +begin:/:/,endsWithParent:!0,contains:[o,l]},{relevance:0,match:/,/},{ +className:"params",begin:/\(/,end:/\)/,keywords:u,relevance:0, +contains:[t,e.C_BLOCK_COMMENT_MODE,o,l,s,{begin:/\(/,end:/\)/,keywords:u, +relevance:0,contains:["self",t,e.C_BLOCK_COMMENT_MODE,o,l,s]}] +},s,t,e.C_BLOCK_COMMENT_MODE,c]};return{name:"C++", +aliases:["cc","c++","h++","hpp","hh","hxx","cxx"],keywords:u,illegal:"",keywords:u,contains:["self",s]},{begin:e.IDENT_RE+"::",keywords:u},{ +match:[/\b(?:enum(?:\s+(?:class|struct))?|class|struct|union)/,/\s+/,/\w+/], +className:{1:"keyword",3:"title.class"}}])}},grmr_csharp:e=>{const n={ +keyword:["abstract","as","base","break","case","catch","class","const","continue","do","else","event","explicit","extern","finally","fixed","for","foreach","goto","if","implicit","in","interface","internal","is","lock","namespace","new","operator","out","override","params","private","protected","public","readonly","record","ref","return","scoped","sealed","sizeof","stackalloc","static","struct","switch","this","throw","try","typeof","unchecked","unsafe","using","virtual","void","volatile","while"].concat(["add","alias","and","ascending","async","await","by","descending","equals","from","get","global","group","init","into","join","let","nameof","not","notnull","on","or","orderby","partial","remove","select","set","unmanaged","value|0","var","when","where","with","yield"]), +built_in:["bool","byte","char","decimal","delegate","double","dynamic","enum","float","int","long","nint","nuint","object","sbyte","short","string","ulong","uint","ushort"], +literal:["default","false","null","true"]},t=e.inherit(e.TITLE_MODE,{ +begin:"[a-zA-Z](\\.?\\w)*"}),a={className:"number",variants:[{ +begin:"\\b(0b[01']+)"},{ +begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{ +begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)" +}],relevance:0},i={className:"string",begin:'@"',end:'"',contains:[{begin:'""'}] +},r=e.inherit(i,{illegal:/\n/}),s={className:"subst",begin:/\{/,end:/\}/, +keywords:n},o=e.inherit(s,{illegal:/\n/}),l={className:"string",begin:/\$"/, +end:'"',illegal:/\n/,contains:[{begin:/\{\{/},{begin:/\}\}/ +},e.BACKSLASH_ESCAPE,o]},c={className:"string",begin:/\$@"/,end:'"',contains:[{ +begin:/\{\{/},{begin:/\}\}/},{begin:'""'},s]},d=e.inherit(c,{illegal:/\n/, +contains:[{begin:/\{\{/},{begin:/\}\}/},{begin:'""'},o]}) +;s.contains=[c,l,i,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.C_BLOCK_COMMENT_MODE], +o.contains=[d,l,r,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.inherit(e.C_BLOCK_COMMENT_MODE,{ +illegal:/\n/})];const g={variants:[c,l,i,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE] +},u={begin:"<",end:">",contains:[{beginKeywords:"in out"},t] +},b=e.IDENT_RE+"(<"+e.IDENT_RE+"(\\s*,\\s*"+e.IDENT_RE+")*>)?(\\[\\])?",m={ +begin:"@"+e.IDENT_RE,relevance:0};return{name:"C#",aliases:["cs","c#"], +keywords:n,illegal:/::/,contains:[e.COMMENT("///","$",{returnBegin:!0, +contains:[{className:"doctag",variants:[{begin:"///",relevance:0},{ +begin:"\x3c!--|--\x3e"},{begin:""}]}] +}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"meta",begin:"#", +end:"$",keywords:{ +keyword:"if else elif endif define undef warning error line region endregion pragma checksum" +}},g,a,{beginKeywords:"class interface",relevance:0,end:/[{;=]/, +illegal:/[^\s:,]/,contains:[{beginKeywords:"where class" +},t,u,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{beginKeywords:"namespace", +relevance:0,end:/[{;=]/,illegal:/[^\s:]/, +contains:[t,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{ +beginKeywords:"record",relevance:0,end:/[{;=]/,illegal:/[^\s:]/, +contains:[t,u,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"meta", +begin:"^\\s*\\[(?=[\\w])",excludeBegin:!0,end:"\\]",excludeEnd:!0,contains:[{ +className:"string",begin:/"/,end:/"/}]},{ +beginKeywords:"new return throw await else",relevance:0},{className:"function", +begin:"("+b+"\\s+)+"+e.IDENT_RE+"\\s*(<[^=]+>\\s*)?\\(",returnBegin:!0, +end:/\s*[{;=]/,excludeEnd:!0,keywords:n,contains:[{ +beginKeywords:"public private protected static internal protected abstract async extern override unsafe virtual new sealed partial", +relevance:0},{begin:e.IDENT_RE+"\\s*(<[^=]+>\\s*)?\\(",returnBegin:!0, +contains:[e.TITLE_MODE,u],relevance:0},{match:/\(\)/},{className:"params", +begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:n,relevance:0, +contains:[g,a,e.C_BLOCK_COMMENT_MODE] +},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},m]}},grmr_css:e=>{ +const n=e.regex,t=ie(e),a=[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE];return{ +name:"CSS",case_insensitive:!0,illegal:/[=|'\$]/,keywords:{ +keyframePosition:"from to"},classNameAliases:{keyframePosition:"selector-tag"}, +contains:[t.BLOCK_COMMENT,{begin:/-(webkit|moz|ms|o)-(?=[a-z])/ +},t.CSS_NUMBER_MODE,{className:"selector-id",begin:/#[A-Za-z0-9_-]+/,relevance:0 +},{className:"selector-class",begin:"\\.[a-zA-Z-][a-zA-Z0-9_-]*",relevance:0 +},t.ATTRIBUTE_SELECTOR_MODE,{className:"selector-pseudo",variants:[{ +begin:":("+oe.join("|")+")"},{begin:":(:)?("+le.join("|")+")"}] +},t.CSS_VARIABLE,{className:"attribute",begin:"\\b("+ce.join("|")+")\\b"},{ +begin:/:/,end:/[;}{]/, +contains:[t.BLOCK_COMMENT,t.HEXCOLOR,t.IMPORTANT,t.CSS_NUMBER_MODE,...a,{ +begin:/(url|data-uri)\(/,end:/\)/,relevance:0,keywords:{built_in:"url data-uri" +},contains:[...a,{className:"string",begin:/[^)]/,endsWithParent:!0, +excludeEnd:!0}]},t.FUNCTION_DISPATCH]},{begin:n.lookahead(/@/),end:"[{;]", +relevance:0,illegal:/:/,contains:[{className:"keyword",begin:/@-?\w[\w]*(-\w+)*/ +},{begin:/\s/,endsWithParent:!0,excludeEnd:!0,relevance:0,keywords:{ +$pattern:/[a-z-]+/,keyword:"and or not only",attribute:se.join(" ")},contains:[{ +begin:/[a-z-]+(?=:)/,className:"attribute"},...a,t.CSS_NUMBER_MODE]}]},{ +className:"selector-tag",begin:"\\b("+re.join("|")+")\\b"}]}},grmr_diff:e=>{ +const n=e.regex;return{name:"Diff",aliases:["patch"],contains:[{ +className:"meta",relevance:10, +match:n.either(/^@@ +-\d+,\d+ +\+\d+,\d+ +@@/,/^\*\*\* +\d+,\d+ +\*\*\*\*$/,/^--- +\d+,\d+ +----$/) +},{className:"comment",variants:[{ +begin:n.either(/Index: /,/^index/,/={3,}/,/^-{3}/,/^\*{3} /,/^\+{3}/,/^diff --git/), +end:/$/},{match:/^\*{15}$/}]},{className:"addition",begin:/^\+/,end:/$/},{ +className:"deletion",begin:/^-/,end:/$/},{className:"addition",begin:/^!/, +end:/$/}]}},grmr_go:e=>{const n={ +keyword:["break","case","chan","const","continue","default","defer","else","fallthrough","for","func","go","goto","if","import","interface","map","package","range","return","select","struct","switch","type","var"], +type:["bool","byte","complex64","complex128","error","float32","float64","int8","int16","int32","int64","string","uint8","uint16","uint32","uint64","int","uint","uintptr","rune"], +literal:["true","false","iota","nil"], +built_in:["append","cap","close","complex","copy","imag","len","make","new","panic","print","println","real","recover","delete"] +};return{name:"Go",aliases:["golang"],keywords:n,illegal:"{const n=e.regex;return{name:"GraphQL",aliases:["gql"], +case_insensitive:!0,disableAutodetect:!1,keywords:{ +keyword:["query","mutation","subscription","type","input","schema","directive","interface","union","scalar","fragment","enum","on"], +literal:["true","false","null"]}, +contains:[e.HASH_COMMENT_MODE,e.QUOTE_STRING_MODE,e.NUMBER_MODE,{ +scope:"punctuation",match:/[.]{3}/,relevance:0},{scope:"punctuation", +begin:/[\!\(\)\:\=\[\]\{\|\}]{1}/,relevance:0},{scope:"variable",begin:/\$/, +end:/\W/,excludeEnd:!0,relevance:0},{scope:"meta",match:/@\w+/,excludeEnd:!0},{ +scope:"symbol",begin:n.concat(/[_A-Za-z][_0-9A-Za-z]*/,n.lookahead(/\s*:/)), +relevance:0}],illegal:[/[;<']/,/BEGIN/]}},grmr_ini:e=>{const n=e.regex,t={ +className:"number",relevance:0,variants:[{begin:/([+-]+)?[\d]+_[\d_]+/},{ +begin:e.NUMBER_RE}]},a=e.COMMENT();a.variants=[{begin:/;/,end:/$/},{begin:/#/, +end:/$/}];const i={className:"variable",variants:[{begin:/\$[\w\d"][\w\d_]*/},{ +begin:/\$\{(.*?)\}/}]},r={className:"literal", +begin:/\bon|off|true|false|yes|no\b/},s={className:"string", +contains:[e.BACKSLASH_ESCAPE],variants:[{begin:"'''",end:"'''",relevance:10},{ +begin:'"""',end:'"""',relevance:10},{begin:'"',end:'"'},{begin:"'",end:"'"}] +},o={begin:/\[/,end:/\]/,contains:[a,r,i,s,t,"self"],relevance:0 +},l=n.either(/[A-Za-z0-9_-]+/,/"(\\"|[^"])*"/,/'[^']*'/);return{ +name:"TOML, also INI",aliases:["toml"],case_insensitive:!0,illegal:/\S/, +contains:[a,{className:"section",begin:/\[+/,end:/\]+/},{ +begin:n.concat(l,"(\\s*\\.\\s*",l,")*",n.lookahead(/\s*=\s*[^#\s]/)), +className:"attr",starts:{end:/$/,contains:[a,o,r,i,s,t]}}]}},grmr_java:e=>{ +const n=e.regex,t="[\xc0-\u02b8a-zA-Z_$][\xc0-\u02b8a-zA-Z_$0-9]*",a=t+pe("(?:<"+t+"~~~(?:\\s*,\\s*"+t+"~~~)*>)?",/~~~/g,2),i={ +keyword:["synchronized","abstract","private","var","static","if","const ","for","while","strictfp","finally","protected","import","native","final","void","enum","else","break","transient","catch","instanceof","volatile","case","assert","package","default","public","try","switch","continue","throws","protected","public","private","module","requires","exports","do","sealed","yield","permits"], +literal:["false","true","null"], +type:["char","boolean","long","float","int","byte","short","double"], +built_in:["super","this"]},r={className:"meta",begin:"@"+t,contains:[{ +begin:/\(/,end:/\)/,contains:["self"]}]},s={className:"params",begin:/\(/, +end:/\)/,keywords:i,relevance:0,contains:[e.C_BLOCK_COMMENT_MODE],endsParent:!0} +;return{name:"Java",aliases:["jsp"],keywords:i,illegal:/<\/|#/, +contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{begin:/\w+@/, +relevance:0},{className:"doctag",begin:"@[A-Za-z]+"}]}),{ +begin:/import java\.[a-z]+\./,keywords:"import",relevance:2 +},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{begin:/"""/,end:/"""/, +className:"string",contains:[e.BACKSLASH_ESCAPE] +},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{ +match:[/\b(?:class|interface|enum|extends|implements|new)/,/\s+/,t],className:{ +1:"keyword",3:"title.class"}},{match:/non-sealed/,scope:"keyword"},{ +begin:[n.concat(/(?!else)/,t),/\s+/,t,/\s+/,/=(?!=)/],className:{1:"type", +3:"variable",5:"operator"}},{begin:[/record/,/\s+/,t],className:{1:"keyword", +3:"title.class"},contains:[s,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{ +beginKeywords:"new throw return else",relevance:0},{ +begin:["(?:"+a+"\\s+)",e.UNDERSCORE_IDENT_RE,/\s*(?=\()/],className:{ +2:"title.function"},keywords:i,contains:[{className:"params",begin:/\(/, +end:/\)/,keywords:i,relevance:0, +contains:[r,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,me,e.C_BLOCK_COMMENT_MODE] +},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},me,r]}},grmr_javascript:Oe, +grmr_json:e=>{const n=["true","false","null"],t={scope:"literal", +beginKeywords:n.join(" ")};return{name:"JSON",keywords:{literal:n},contains:[{ +className:"attr",begin:/"(\\.|[^\\"\r\n])*"(?=\s*:)/,relevance:1.01},{ +match:/[{}[\],:]/,className:"punctuation",relevance:0 +},e.QUOTE_STRING_MODE,t,e.C_NUMBER_MODE,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE], +illegal:"\\S"}},grmr_kotlin:e=>{const n={ +keyword:"abstract as val var vararg get set class object open private protected public noinline crossinline dynamic final enum if else do while for when throw try catch finally import package is in fun override companion reified inline lateinit init interface annotation data sealed internal infix operator out by constructor super tailrec where const inner suspend typealias external expect actual", +built_in:"Byte Short Char Int Long Boolean Float Double Void Unit Nothing", +literal:"true false null"},t={className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"@" +},a={className:"subst",begin:/\$\{/,end:/\}/,contains:[e.C_NUMBER_MODE]},i={ +className:"variable",begin:"\\$"+e.UNDERSCORE_IDENT_RE},r={className:"string", +variants:[{begin:'"""',end:'"""(?=[^"])',contains:[i,a]},{begin:"'",end:"'", +illegal:/\n/,contains:[e.BACKSLASH_ESCAPE]},{begin:'"',end:'"',illegal:/\n/, +contains:[e.BACKSLASH_ESCAPE,i,a]}]};a.contains.push(r);const s={ +className:"meta", +begin:"@(?:file|property|field|get|set|receiver|param|setparam|delegate)\\s*:(?:\\s*"+e.UNDERSCORE_IDENT_RE+")?" +},o={className:"meta",begin:"@"+e.UNDERSCORE_IDENT_RE,contains:[{begin:/\(/, +end:/\)/,contains:[e.inherit(r,{className:"string"}),"self"]}] +},l=me,c=e.COMMENT("/\\*","\\*/",{contains:[e.C_BLOCK_COMMENT_MODE]}),d={ +variants:[{className:"type",begin:e.UNDERSCORE_IDENT_RE},{begin:/\(/,end:/\)/, +contains:[]}]},g=d;return g.variants[1].contains=[d],d.variants[1].contains=[g], +{name:"Kotlin",aliases:["kt","kts"],keywords:n, +contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag", +begin:"@[A-Za-z]+"}]}),e.C_LINE_COMMENT_MODE,c,{className:"keyword", +begin:/\b(break|continue|return|this)\b/,starts:{contains:[{className:"symbol", +begin:/@\w+/}]}},t,s,o,{className:"function",beginKeywords:"fun",end:"[(]|$", +returnBegin:!0,excludeEnd:!0,keywords:n,relevance:5,contains:[{ +begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0, +contains:[e.UNDERSCORE_TITLE_MODE]},{className:"type",begin://, +keywords:"reified",relevance:0},{className:"params",begin:/\(/,end:/\)/, +endsParent:!0,keywords:n,relevance:0,contains:[{begin:/:/,end:/[=,\/]/, +endsWithParent:!0,contains:[d,e.C_LINE_COMMENT_MODE,c],relevance:0 +},e.C_LINE_COMMENT_MODE,c,s,o,r,e.C_NUMBER_MODE]},c]},{ +begin:[/class|interface|trait/,/\s+/,e.UNDERSCORE_IDENT_RE],beginScope:{ +3:"title.class"},keywords:"class interface trait",end:/[:\{(]|$/,excludeEnd:!0, +illegal:"extends implements",contains:[{ +beginKeywords:"public protected internal private constructor" +},e.UNDERSCORE_TITLE_MODE,{className:"type",begin://,excludeBegin:!0, +excludeEnd:!0,relevance:0},{className:"type",begin:/[,:]\s*/,end:/[<\(,){\s]|$/, +excludeBegin:!0,returnEnd:!0},s,o]},r,{className:"meta",begin:"^#!/usr/bin/env", +end:"$",illegal:"\n"},l]}},grmr_less:e=>{ +const n=ie(e),t=de,a="[\\w-]+",i="("+a+"|@\\{"+a+"\\})",r=[],s=[],o=e=>({ +className:"string",begin:"~?"+e+".*?"+e}),l=(e,n,t)=>({className:e,begin:n, +relevance:t}),c={$pattern:/[a-z-]+/,keyword:"and or not only", +attribute:se.join(" ")},d={begin:"\\(",end:"\\)",contains:s,keywords:c, +relevance:0} +;s.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,o("'"),o('"'),n.CSS_NUMBER_MODE,{ +begin:"(url|data-uri)\\(",starts:{className:"string",end:"[\\)\\n]", +excludeEnd:!0} +},n.HEXCOLOR,d,l("variable","@@?"+a,10),l("variable","@\\{"+a+"\\}"),l("built_in","~?`[^`]*?`"),{ +className:"attribute",begin:a+"\\s*:",end:":",returnBegin:!0,excludeEnd:!0 +},n.IMPORTANT,{beginKeywords:"and not"},n.FUNCTION_DISPATCH);const g=s.concat({ +begin:/\{/,end:/\}/,contains:r}),u={beginKeywords:"when",endsWithParent:!0, +contains:[{beginKeywords:"and not"}].concat(s)},b={begin:i+"\\s*:", +returnBegin:!0,end:/[;}]/,relevance:0,contains:[{begin:/-(webkit|moz|ms|o)-/ +},n.CSS_VARIABLE,{className:"attribute",begin:"\\b("+ce.join("|")+")\\b", +end:/(?=:)/,starts:{endsWithParent:!0,illegal:"[<=$]",relevance:0,contains:s}}] +},m={className:"keyword", +begin:"@(import|media|charset|font-face|(-[a-z]+-)?keyframes|supports|document|namespace|page|viewport|host)\\b", +starts:{end:"[;{}]",keywords:c,returnEnd:!0,contains:s,relevance:0}},p={ +className:"variable",variants:[{begin:"@"+a+"\\s*:",relevance:15},{begin:"@"+a +}],starts:{end:"[;}]",returnEnd:!0,contains:g}},_={variants:[{ +begin:"[\\.#:&\\[>]",end:"[;{}]"},{begin:i,end:/\{/}],returnBegin:!0, +returnEnd:!0,illegal:"[<='$\"]",relevance:0, +contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,u,l("keyword","all\\b"),l("variable","@\\{"+a+"\\}"),{ +begin:"\\b("+re.join("|")+")\\b",className:"selector-tag" +},n.CSS_NUMBER_MODE,l("selector-tag",i,0),l("selector-id","#"+i),l("selector-class","\\."+i,0),l("selector-tag","&",0),n.ATTRIBUTE_SELECTOR_MODE,{ +className:"selector-pseudo",begin:":("+oe.join("|")+")"},{ +className:"selector-pseudo",begin:":(:)?("+le.join("|")+")"},{begin:/\(/, +end:/\)/,relevance:0,contains:g},{begin:"!important"},n.FUNCTION_DISPATCH]},h={ +begin:a+":(:)?"+`(${t.join("|")})`,returnBegin:!0,contains:[_]} +;return r.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,m,p,h,b,_,u,n.FUNCTION_DISPATCH), +{name:"Less",case_insensitive:!0,illegal:"[=>'/<($\"]",contains:r}}, +grmr_lua:e=>{const n="\\[=*\\[",t="\\]=*\\]",a={begin:n,end:t,contains:["self"] +},i=[e.COMMENT("--(?!"+n+")","$"),e.COMMENT("--"+n,t,{contains:[a],relevance:10 +})];return{name:"Lua",keywords:{$pattern:e.UNDERSCORE_IDENT_RE, +literal:"true false nil", +keyword:"and break do else elseif end for goto if in local not or repeat return then until while", +built_in:"_G _ENV _VERSION __index __newindex __mode __call __metatable __tostring __len __gc __add __sub __mul __div __mod __pow __concat __unm __eq __lt __le assert collectgarbage dofile error getfenv getmetatable ipairs load loadfile loadstring module next pairs pcall print rawequal rawget rawset require select setfenv setmetatable tonumber tostring type unpack xpcall arg self coroutine resume yield status wrap create running debug getupvalue debug sethook getmetatable gethook setmetatable setlocal traceback setfenv getinfo setupvalue getlocal getregistry getfenv io lines write close flush open output type read stderr stdin input stdout popen tmpfile math log max acos huge ldexp pi cos tanh pow deg tan cosh sinh random randomseed frexp ceil floor rad abs sqrt modf asin min mod fmod log10 atan2 exp sin atan os exit setlocale date getenv difftime remove time clock tmpname rename execute package preload loadlib loaded loaders cpath config path seeall string sub upper len gfind rep find match char dump gmatch reverse byte format gsub lower table setn insert getn foreachi maxn foreach concat sort remove" +},contains:i.concat([{className:"function",beginKeywords:"function",end:"\\)", +contains:[e.inherit(e.TITLE_MODE,{ +begin:"([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*"}),{className:"params", +begin:"\\(",endsWithParent:!0,contains:i}].concat(i) +},e.C_NUMBER_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"string", +begin:n,end:t,contains:[a],relevance:5}])}},grmr_makefile:e=>{const n={ +className:"variable",variants:[{begin:"\\$\\("+e.UNDERSCORE_IDENT_RE+"\\)", +contains:[e.BACKSLASH_ESCAPE]},{begin:/\$[@%{ +const n={begin:/<\/?[A-Za-z_]/,end:">",subLanguage:"xml",relevance:0},t={ +variants:[{begin:/\[.+?\]\[.*?\]/,relevance:0},{ +begin:/\[.+?\]\(((data|javascript|mailto):|(?:http|ftp)s?:\/\/).*?\)/, +relevance:2},{ +begin:e.regex.concat(/\[.+?\]\(/,/[A-Za-z][A-Za-z0-9+.-]*/,/:\/\/.*?\)/), +relevance:2},{begin:/\[.+?\]\([./?&#].*?\)/,relevance:1},{ +begin:/\[.*?\]\(.*?\)/,relevance:0}],returnBegin:!0,contains:[{match:/\[(?=\])/ +},{className:"string",relevance:0,begin:"\\[",end:"\\]",excludeBegin:!0, +returnEnd:!0},{className:"link",relevance:0,begin:"\\]\\(",end:"\\)", +excludeBegin:!0,excludeEnd:!0},{className:"symbol",relevance:0,begin:"\\]\\[", +end:"\\]",excludeBegin:!0,excludeEnd:!0}]},a={className:"strong",contains:[], +variants:[{begin:/_{2}(?!\s)/,end:/_{2}/},{begin:/\*{2}(?!\s)/,end:/\*{2}/}] +},i={className:"emphasis",contains:[],variants:[{begin:/\*(?![*\s])/,end:/\*/},{ +begin:/_(?![_\s])/,end:/_/,relevance:0}]},r=e.inherit(a,{contains:[] +}),s=e.inherit(i,{contains:[]});a.contains.push(s),i.contains.push(r) +;let o=[n,t];return[a,i,r,s].forEach((e=>{e.contains=e.contains.concat(o) +})),o=o.concat(a,i),{name:"Markdown",aliases:["md","mkdown","mkd"],contains:[{ +className:"section",variants:[{begin:"^#{1,6}",end:"$",contains:o},{ +begin:"(?=^.+?\\n[=-]{2,}$)",contains:[{begin:"^[=-]*$"},{begin:"^",end:"\\n", +contains:o}]}]},n,{className:"bullet",begin:"^[ \t]*([*+-]|(\\d+\\.))(?=\\s+)", +end:"\\s+",excludeEnd:!0},a,i,{className:"quote",begin:"^>\\s+",contains:o, +end:"$"},{className:"code",variants:[{begin:"(`{3,})[^`](.|\\n)*?\\1`*[ ]*"},{ +begin:"(~{3,})[^~](.|\\n)*?\\1~*[ ]*"},{begin:"```",end:"```+[ ]*$"},{ +begin:"~~~",end:"~~~+[ ]*$"},{begin:"`.+?`"},{begin:"(?=^( {4}|\\t))", +contains:[{begin:"^( {4}|\\t)",end:"(\\n)$"}],relevance:0}]},{ +begin:"^[-\\*]{3,}",end:"$"},t,{begin:/^\[[^\n]+\]:/,returnBegin:!0,contains:[{ +className:"symbol",begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0},{ +className:"link",begin:/:\s*/,end:/$/,excludeBegin:!0}]}]}},grmr_objectivec:e=>{ +const n=/[a-zA-Z@][a-zA-Z0-9_]*/,t={$pattern:n, +keyword:["@interface","@class","@protocol","@implementation"]};return{ +name:"Objective-C",aliases:["mm","objc","obj-c","obj-c++","objective-c++"], +keywords:{"variable.language":["this","super"],$pattern:n, +keyword:["while","export","sizeof","typedef","const","struct","for","union","volatile","static","mutable","if","do","return","goto","enum","else","break","extern","asm","case","default","register","explicit","typename","switch","continue","inline","readonly","assign","readwrite","self","@synchronized","id","typeof","nonatomic","IBOutlet","IBAction","strong","weak","copy","in","out","inout","bycopy","byref","oneway","__strong","__weak","__block","__autoreleasing","@private","@protected","@public","@try","@property","@end","@throw","@catch","@finally","@autoreleasepool","@synthesize","@dynamic","@selector","@optional","@required","@encode","@package","@import","@defs","@compatibility_alias","__bridge","__bridge_transfer","__bridge_retained","__bridge_retain","__covariant","__contravariant","__kindof","_Nonnull","_Nullable","_Null_unspecified","__FUNCTION__","__PRETTY_FUNCTION__","__attribute__","getter","setter","retain","unsafe_unretained","nonnull","nullable","null_unspecified","null_resettable","class","instancetype","NS_DESIGNATED_INITIALIZER","NS_UNAVAILABLE","NS_REQUIRES_SUPER","NS_RETURNS_INNER_POINTER","NS_INLINE","NS_AVAILABLE","NS_DEPRECATED","NS_ENUM","NS_OPTIONS","NS_SWIFT_UNAVAILABLE","NS_ASSUME_NONNULL_BEGIN","NS_ASSUME_NONNULL_END","NS_REFINED_FOR_SWIFT","NS_SWIFT_NAME","NS_SWIFT_NOTHROW","NS_DURING","NS_HANDLER","NS_ENDHANDLER","NS_VALUERETURN","NS_VOIDRETURN"], +literal:["false","true","FALSE","TRUE","nil","YES","NO","NULL"], +built_in:["dispatch_once_t","dispatch_queue_t","dispatch_sync","dispatch_async","dispatch_once"], +type:["int","float","char","unsigned","signed","short","long","double","wchar_t","unichar","void","bool","BOOL","id|0","_Bool"] +},illegal:"/,end:/$/,illegal:"\\n" +},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"class", +begin:"("+t.keyword.join("|")+")\\b",end:/(\{|$)/,excludeEnd:!0,keywords:t, +contains:[e.UNDERSCORE_TITLE_MODE]},{begin:"\\."+e.UNDERSCORE_IDENT_RE, +relevance:0}]}},grmr_perl:e=>{const n=e.regex,t=/[dualxmsipngr]{0,12}/,a={ +$pattern:/[\w.]+/, +keyword:"abs accept alarm and atan2 bind binmode bless break caller chdir chmod chomp chop chown chr chroot close closedir connect continue cos crypt dbmclose dbmopen defined delete die do dump each else elsif endgrent endhostent endnetent endprotoent endpwent endservent eof eval exec exists exit exp fcntl fileno flock for foreach fork format formline getc getgrent getgrgid getgrnam gethostbyaddr gethostbyname gethostent getlogin getnetbyaddr getnetbyname getnetent getpeername getpgrp getpriority getprotobyname getprotobynumber getprotoent getpwent getpwnam getpwuid getservbyname getservbyport getservent getsockname getsockopt given glob gmtime goto grep gt hex if index int ioctl join keys kill last lc lcfirst length link listen local localtime log lstat lt ma map mkdir msgctl msgget msgrcv msgsnd my ne next no not oct open opendir or ord our pack package pipe pop pos print printf prototype push q|0 qq quotemeta qw qx rand read readdir readline readlink readpipe recv redo ref rename require reset return reverse rewinddir rindex rmdir say scalar seek seekdir select semctl semget semop send setgrent sethostent setnetent setpgrp setpriority setprotoent setpwent setservent setsockopt shift shmctl shmget shmread shmwrite shutdown sin sleep socket socketpair sort splice split sprintf sqrt srand stat state study sub substr symlink syscall sysopen sysread sysseek system syswrite tell telldir tie tied time times tr truncate uc ucfirst umask undef unless unlink unpack unshift untie until use utime values vec wait waitpid wantarray warn when while write x|0 xor y|0" +},i={className:"subst",begin:"[$@]\\{",end:"\\}",keywords:a},r={begin:/->\{/, +end:/\}/},s={variants:[{begin:/\$\d/},{ +begin:n.concat(/[$%@](\^\w\b|#\w+(::\w+)*|\{\w+\}|\w+(::\w*)*)/,"(?![A-Za-z])(?![@$%])") +},{begin:/[$%@][^\s\w{]/,relevance:0}] +},o=[e.BACKSLASH_ESCAPE,i,s],l=[/!/,/\//,/\|/,/\?/,/'/,/"/,/#/],c=(e,a,i="\\1")=>{ +const r="\\1"===i?i:n.concat(i,a) +;return n.concat(n.concat("(?:",e,")"),a,/(?:\\.|[^\\\/])*?/,r,/(?:\\.|[^\\\/])*?/,i,t) +},d=(e,a,i)=>n.concat(n.concat("(?:",e,")"),a,/(?:\\.|[^\\\/])*?/,i,t),g=[s,e.HASH_COMMENT_MODE,e.COMMENT(/^=\w/,/=cut/,{ +endsWithParent:!0}),r,{className:"string",contains:o,variants:[{ +begin:"q[qwxr]?\\s*\\(",end:"\\)",relevance:5},{begin:"q[qwxr]?\\s*\\[", +end:"\\]",relevance:5},{begin:"q[qwxr]?\\s*\\{",end:"\\}",relevance:5},{ +begin:"q[qwxr]?\\s*\\|",end:"\\|",relevance:5},{begin:"q[qwxr]?\\s*<",end:">", +relevance:5},{begin:"qw\\s+q",end:"q",relevance:5},{begin:"'",end:"'", +contains:[e.BACKSLASH_ESCAPE]},{begin:'"',end:'"'},{begin:"`",end:"`", +contains:[e.BACKSLASH_ESCAPE]},{begin:/\{\w+\}/,relevance:0},{ +begin:"-?\\w+\\s*=>",relevance:0}]},{className:"number", +begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b", +relevance:0},{ +begin:"(\\/\\/|"+e.RE_STARTERS_RE+"|\\b(split|return|print|reverse|grep)\\b)\\s*", +keywords:"split return print reverse grep",relevance:0, +contains:[e.HASH_COMMENT_MODE,{className:"regexp",variants:[{ +begin:c("s|tr|y",n.either(...l,{capture:!0}))},{begin:c("s|tr|y","\\(","\\)")},{ +begin:c("s|tr|y","\\[","\\]")},{begin:c("s|tr|y","\\{","\\}")}],relevance:2},{ +className:"regexp",variants:[{begin:/(m|qr)\/\//,relevance:0},{ +begin:d("(?:m|qr)?",/\//,/\//)},{begin:d("m|qr",n.either(...l,{capture:!0 +}),/\1/)},{begin:d("m|qr",/\(/,/\)/)},{begin:d("m|qr",/\[/,/\]/)},{ +begin:d("m|qr",/\{/,/\}/)}]}]},{className:"function",beginKeywords:"sub", +end:"(\\s*\\(.*?\\))?[;{]",excludeEnd:!0,relevance:5,contains:[e.TITLE_MODE]},{ +begin:"-\\w\\b",relevance:0},{begin:"^__DATA__$",end:"^__END__$", +subLanguage:"mojolicious",contains:[{begin:"^@@.*",end:"$",className:"comment"}] +}];return i.contains=g,r.contains=g,{name:"Perl",aliases:["pl","pm"],keywords:a, +contains:g}},grmr_php:e=>{ +const n=e.regex,t=/(?![A-Za-z0-9])(?![$])/,a=n.concat(/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/,t),i=n.concat(/(\\?[A-Z][a-z0-9_\x7f-\xff]+|\\?[A-Z]+(?=[A-Z][a-z0-9_\x7f-\xff])){1,}/,t),r={ +scope:"variable",match:"\\$+"+a},s={scope:"subst",variants:[{begin:/\$\w+/},{ +begin:/\{\$/,end:/\}/}]},o=e.inherit(e.APOS_STRING_MODE,{illegal:null +}),l="[ \t\n]",c={scope:"string",variants:[e.inherit(e.QUOTE_STRING_MODE,{ +illegal:null,contains:e.QUOTE_STRING_MODE.contains.concat(s)}),o,{ +begin:/<<<[ \t]*(?:(\w+)|"(\w+)")\n/,end:/[ \t]*(\w+)\b/, +contains:e.QUOTE_STRING_MODE.contains.concat(s),"on:begin":(e,n)=>{ +n.data._beginMatch=e[1]||e[2]},"on:end":(e,n)=>{ +n.data._beginMatch!==e[1]&&n.ignoreMatch()}},e.END_SAME_AS_BEGIN({ +begin:/<<<[ \t]*'(\w+)'\n/,end:/[ \t]*(\w+)\b/})]},d={scope:"number",variants:[{ +begin:"\\b0[bB][01]+(?:_[01]+)*\\b"},{begin:"\\b0[oO][0-7]+(?:_[0-7]+)*\\b"},{ +begin:"\\b0[xX][\\da-fA-F]+(?:_[\\da-fA-F]+)*\\b"},{ +begin:"(?:\\b\\d+(?:_\\d+)*(\\.(?:\\d+(?:_\\d+)*))?|\\B\\.\\d+)(?:[eE][+-]?\\d+)?" +}],relevance:0 +},g=["false","null","true"],u=["__CLASS__","__DIR__","__FILE__","__FUNCTION__","__COMPILER_HALT_OFFSET__","__LINE__","__METHOD__","__NAMESPACE__","__TRAIT__","die","echo","exit","include","include_once","print","require","require_once","array","abstract","and","as","binary","bool","boolean","break","callable","case","catch","class","clone","const","continue","declare","default","do","double","else","elseif","empty","enddeclare","endfor","endforeach","endif","endswitch","endwhile","enum","eval","extends","final","finally","float","for","foreach","from","global","goto","if","implements","instanceof","insteadof","int","integer","interface","isset","iterable","list","match|0","mixed","new","never","object","or","private","protected","public","readonly","real","return","string","switch","throw","trait","try","unset","use","var","void","while","xor","yield"],b=["Error|0","AppendIterator","ArgumentCountError","ArithmeticError","ArrayIterator","ArrayObject","AssertionError","BadFunctionCallException","BadMethodCallException","CachingIterator","CallbackFilterIterator","CompileError","Countable","DirectoryIterator","DivisionByZeroError","DomainException","EmptyIterator","ErrorException","Exception","FilesystemIterator","FilterIterator","GlobIterator","InfiniteIterator","InvalidArgumentException","IteratorIterator","LengthException","LimitIterator","LogicException","MultipleIterator","NoRewindIterator","OutOfBoundsException","OutOfRangeException","OuterIterator","OverflowException","ParentIterator","ParseError","RangeException","RecursiveArrayIterator","RecursiveCachingIterator","RecursiveCallbackFilterIterator","RecursiveDirectoryIterator","RecursiveFilterIterator","RecursiveIterator","RecursiveIteratorIterator","RecursiveRegexIterator","RecursiveTreeIterator","RegexIterator","RuntimeException","SeekableIterator","SplDoublyLinkedList","SplFileInfo","SplFileObject","SplFixedArray","SplHeap","SplMaxHeap","SplMinHeap","SplObjectStorage","SplObserver","SplPriorityQueue","SplQueue","SplStack","SplSubject","SplTempFileObject","TypeError","UnderflowException","UnexpectedValueException","UnhandledMatchError","ArrayAccess","BackedEnum","Closure","Fiber","Generator","Iterator","IteratorAggregate","Serializable","Stringable","Throwable","Traversable","UnitEnum","WeakReference","WeakMap","Directory","__PHP_Incomplete_Class","parent","php_user_filter","self","static","stdClass"],m={ +keyword:u,literal:(e=>{const n=[];return e.forEach((e=>{ +n.push(e),e.toLowerCase()===e?n.push(e.toUpperCase()):n.push(e.toLowerCase()) +})),n})(g),built_in:b},p=e=>e.map((e=>e.replace(/\|\d+$/,""))),_={variants:[{ +match:[/new/,n.concat(l,"+"),n.concat("(?!",p(b).join("\\b|"),"\\b)"),i],scope:{ +1:"keyword",4:"title.class"}}]},h=n.concat(a,"\\b(?!\\()"),f={variants:[{ +match:[n.concat(/::/,n.lookahead(/(?!class\b)/)),h],scope:{2:"variable.constant" +}},{match:[/::/,/class/],scope:{2:"variable.language"}},{ +match:[i,n.concat(/::/,n.lookahead(/(?!class\b)/)),h],scope:{1:"title.class", +3:"variable.constant"}},{match:[i,n.concat("::",n.lookahead(/(?!class\b)/))], +scope:{1:"title.class"}},{match:[i,/::/,/class/],scope:{1:"title.class", +3:"variable.language"}}]},E={scope:"attr", +match:n.concat(a,n.lookahead(":"),n.lookahead(/(?!::)/))},y={relevance:0, +begin:/\(/,end:/\)/,keywords:m,contains:[E,r,f,e.C_BLOCK_COMMENT_MODE,c,d,_] +},N={relevance:0, +match:[/\b/,n.concat("(?!fn\\b|function\\b|",p(u).join("\\b|"),"|",p(b).join("\\b|"),"\\b)"),a,n.concat(l,"*"),n.lookahead(/(?=\()/)], +scope:{3:"title.function.invoke"},contains:[y]};y.contains.push(N) +;const w=[E,f,e.C_BLOCK_COMMENT_MODE,c,d,_];return{case_insensitive:!1, +keywords:m,contains:[{begin:n.concat(/#\[\s*/,i),beginScope:"meta",end:/]/, +endScope:"meta",keywords:{literal:g,keyword:["new","array"]},contains:[{ +begin:/\[/,end:/]/,keywords:{literal:g,keyword:["new","array"]}, +contains:["self",...w]},...w,{scope:"meta",match:i}] +},e.HASH_COMMENT_MODE,e.COMMENT("//","$"),e.COMMENT("/\\*","\\*/",{contains:[{ +scope:"doctag",match:"@[A-Za-z]+"}]}),{match:/__halt_compiler\(\);/, +keywords:"__halt_compiler",starts:{scope:"comment",end:e.MATCH_NOTHING_RE, +contains:[{match:/\?>/,scope:"meta",endsParent:!0}]}},{scope:"meta",variants:[{ +begin:/<\?php/,relevance:10},{begin:/<\?=/},{begin:/<\?/,relevance:.1},{ +begin:/\?>/}]},{scope:"variable.language",match:/\$this\b/},r,N,f,{ +match:[/const/,/\s/,a],scope:{1:"keyword",3:"variable.constant"}},_,{ +scope:"function",relevance:0,beginKeywords:"fn function",end:/[;{]/, +excludeEnd:!0,illegal:"[$%\\[]",contains:[{beginKeywords:"use" +},e.UNDERSCORE_TITLE_MODE,{begin:"=>",endsParent:!0},{scope:"params", +begin:"\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0,keywords:m, +contains:["self",r,f,e.C_BLOCK_COMMENT_MODE,c,d]}]},{scope:"class",variants:[{ +beginKeywords:"enum",illegal:/[($"]/},{beginKeywords:"class interface trait", +illegal:/[:($"]/}],relevance:0,end:/\{/,excludeEnd:!0,contains:[{ +beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{ +beginKeywords:"namespace",relevance:0,end:";",illegal:/[.']/, +contains:[e.inherit(e.UNDERSCORE_TITLE_MODE,{scope:"title.class"})]},{ +beginKeywords:"use",relevance:0,end:";",contains:[{ +match:/\b(as|const|function)\b/,scope:"keyword"},e.UNDERSCORE_TITLE_MODE]},c,d]} +},grmr_php_template:e=>({name:"PHP template",subLanguage:"xml",contains:[{ +begin:/<\?(php|=)?/,end:/\?>/,subLanguage:"php",contains:[{begin:"/\\*", +end:"\\*/",skip:!0},{begin:'b"',end:'"',skip:!0},{begin:"b'",end:"'",skip:!0 +},e.inherit(e.APOS_STRING_MODE,{illegal:null,className:null,contains:null, +skip:!0}),e.inherit(e.QUOTE_STRING_MODE,{illegal:null,className:null, +contains:null,skip:!0})]}]}),grmr_plaintext:e=>({name:"Plain text", +aliases:["text","txt"],disableAutodetect:!0}),grmr_python:e=>{ +const n=e.regex,t=/[\p{XID_Start}_]\p{XID_Continue}*/u,a=["and","as","assert","async","await","break","case","class","continue","def","del","elif","else","except","finally","for","from","global","if","import","in","is","lambda","match","nonlocal|10","not","or","pass","raise","return","try","while","with","yield"],i={ +$pattern:/[A-Za-z]\w+|__\w+__/,keyword:a, +built_in:["__import__","abs","all","any","ascii","bin","bool","breakpoint","bytearray","bytes","callable","chr","classmethod","compile","complex","delattr","dict","dir","divmod","enumerate","eval","exec","filter","float","format","frozenset","getattr","globals","hasattr","hash","help","hex","id","input","int","isinstance","issubclass","iter","len","list","locals","map","max","memoryview","min","next","object","oct","open","ord","pow","print","property","range","repr","reversed","round","set","setattr","slice","sorted","staticmethod","str","sum","super","tuple","type","vars","zip"], +literal:["__debug__","Ellipsis","False","None","NotImplemented","True"], +type:["Any","Callable","Coroutine","Dict","List","Literal","Generic","Optional","Sequence","Set","Tuple","Type","Union"] +},r={className:"meta",begin:/^(>>>|\.\.\.) /},s={className:"subst",begin:/\{/, +end:/\}/,keywords:i,illegal:/#/},o={begin:/\{\{/,relevance:0},l={ +className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{ +begin:/([uU]|[bB]|[rR]|[bB][rR]|[rR][bB])?'''/,end:/'''/, +contains:[e.BACKSLASH_ESCAPE,r],relevance:10},{ +begin:/([uU]|[bB]|[rR]|[bB][rR]|[rR][bB])?"""/,end:/"""/, +contains:[e.BACKSLASH_ESCAPE,r],relevance:10},{ +begin:/([fF][rR]|[rR][fF]|[fF])'''/,end:/'''/, +contains:[e.BACKSLASH_ESCAPE,r,o,s]},{begin:/([fF][rR]|[rR][fF]|[fF])"""/, +end:/"""/,contains:[e.BACKSLASH_ESCAPE,r,o,s]},{begin:/([uU]|[rR])'/,end:/'/, +relevance:10},{begin:/([uU]|[rR])"/,end:/"/,relevance:10},{ +begin:/([bB]|[bB][rR]|[rR][bB])'/,end:/'/},{begin:/([bB]|[bB][rR]|[rR][bB])"/, +end:/"/},{begin:/([fF][rR]|[rR][fF]|[fF])'/,end:/'/, +contains:[e.BACKSLASH_ESCAPE,o,s]},{begin:/([fF][rR]|[rR][fF]|[fF])"/,end:/"/, +contains:[e.BACKSLASH_ESCAPE,o,s]},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE] +},c="[0-9](_?[0-9])*",d=`(\\b(${c}))?\\.(${c})|\\b(${c})\\.`,g="\\b|"+a.join("|"),u={ +className:"number",relevance:0,variants:[{ +begin:`(\\b(${c})|(${d}))[eE][+-]?(${c})[jJ]?(?=${g})`},{begin:`(${d})[jJ]?`},{ +begin:`\\b([1-9](_?[0-9])*|0+(_?0)*)[lLjJ]?(?=${g})`},{ +begin:`\\b0[bB](_?[01])+[lL]?(?=${g})`},{begin:`\\b0[oO](_?[0-7])+[lL]?(?=${g})` +},{begin:`\\b0[xX](_?[0-9a-fA-F])+[lL]?(?=${g})`},{begin:`\\b(${c})[jJ](?=${g})` +}]},b={className:"comment",begin:n.lookahead(/# type:/),end:/$/,keywords:i, +contains:[{begin:/# type:/},{begin:/#/,end:/\b\B/,endsWithParent:!0}]},m={ +className:"params",variants:[{className:"",begin:/\(\s*\)/,skip:!0},{begin:/\(/, +end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:i, +contains:["self",r,u,l,e.HASH_COMMENT_MODE]}]};return s.contains=[l,u,r],{ +name:"Python",aliases:["py","gyp","ipython"],unicodeRegex:!0,keywords:i, +illegal:/(<\/|\?)|=>/,contains:[r,u,{begin:/\bself\b/},{beginKeywords:"if", +relevance:0},l,b,e.HASH_COMMENT_MODE,{match:[/\bdef/,/\s+/,t],scope:{ +1:"keyword",3:"title.function"},contains:[m]},{variants:[{ +match:[/\bclass/,/\s+/,t,/\s*/,/\(\s*/,t,/\s*\)/]},{match:[/\bclass/,/\s+/,t]}], +scope:{1:"keyword",3:"title.class",6:"title.class.inherited"}},{ +className:"meta",begin:/^[\t ]*@/,end:/(?=#)|$/,contains:[u,m,l]}]}}, +grmr_python_repl:e=>({aliases:["pycon"],contains:[{className:"meta.prompt", +starts:{end:/ |$/,starts:{end:"$",subLanguage:"python"}},variants:[{ +begin:/^>>>(?=[ ]|$)/},{begin:/^\.\.\.(?=[ ]|$)/}]}]}),grmr_r:e=>{ +const n=e.regex,t=/(?:(?:[a-zA-Z]|\.[._a-zA-Z])[._a-zA-Z0-9]*)|\.(?!\d)/,a=n.either(/0[xX][0-9a-fA-F]+\.[0-9a-fA-F]*[pP][+-]?\d+i?/,/0[xX][0-9a-fA-F]+(?:[pP][+-]?\d+)?[Li]?/,/(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?[Li]?/),i=/[=!<>:]=|\|\||&&|:::?|<-|<<-|->>|->|\|>|[-+*\/?!$&|:<=>@^~]|\*\*/,r=n.either(/[()]/,/[{}]/,/\[\[/,/[[\]]/,/\\/,/,/) +;return{name:"R",keywords:{$pattern:t, +keyword:"function if in break next repeat else for while", +literal:"NULL NA TRUE FALSE Inf NaN NA_integer_|10 NA_real_|10 NA_character_|10 NA_complex_|10", +built_in:"LETTERS letters month.abb month.name pi T F abs acos acosh all any anyNA Arg as.call as.character as.complex as.double as.environment as.integer as.logical as.null.default as.numeric as.raw asin asinh atan atanh attr attributes baseenv browser c call ceiling class Conj cos cosh cospi cummax cummin cumprod cumsum digamma dim dimnames emptyenv exp expression floor forceAndCall gamma gc.time globalenv Im interactive invisible is.array is.atomic is.call is.character is.complex is.double is.environment is.expression is.finite is.function is.infinite is.integer is.language is.list is.logical is.matrix is.na is.name is.nan is.null is.numeric is.object is.pairlist is.raw is.recursive is.single is.symbol lazyLoadDBfetch length lgamma list log max min missing Mod names nargs nzchar oldClass on.exit pos.to.env proc.time prod quote range Re rep retracemem return round seq_along seq_len seq.int sign signif sin sinh sinpi sqrt standardGeneric substitute sum switch tan tanh tanpi tracemem trigamma trunc unclass untracemem UseMethod xtfrm" +},contains:[e.COMMENT(/#'/,/$/,{contains:[{scope:"doctag",match:/@examples/, +starts:{end:n.lookahead(n.either(/\n^#'\s*(?=@[a-zA-Z]+)/,/\n^(?!#')/)), +endsParent:!0}},{scope:"doctag",begin:"@param",end:/$/,contains:[{ +scope:"variable",variants:[{match:t},{match:/`(?:\\.|[^`\\])+`/}],endsParent:!0 +}]},{scope:"doctag",match:/@[a-zA-Z]+/},{scope:"keyword",match:/\\[a-zA-Z]+/}] +}),e.HASH_COMMENT_MODE,{scope:"string",contains:[e.BACKSLASH_ESCAPE], +variants:[e.END_SAME_AS_BEGIN({begin:/[rR]"(-*)\(/,end:/\)(-*)"/ +}),e.END_SAME_AS_BEGIN({begin:/[rR]"(-*)\{/,end:/\}(-*)"/ +}),e.END_SAME_AS_BEGIN({begin:/[rR]"(-*)\[/,end:/\](-*)"/ +}),e.END_SAME_AS_BEGIN({begin:/[rR]'(-*)\(/,end:/\)(-*)'/ +}),e.END_SAME_AS_BEGIN({begin:/[rR]'(-*)\{/,end:/\}(-*)'/ +}),e.END_SAME_AS_BEGIN({begin:/[rR]'(-*)\[/,end:/\](-*)'/}),{begin:'"',end:'"', +relevance:0},{begin:"'",end:"'",relevance:0}]},{relevance:0,variants:[{scope:{ +1:"operator",2:"number"},match:[i,a]},{scope:{1:"operator",2:"number"}, +match:[/%[^%]*%/,a]},{scope:{1:"punctuation",2:"number"},match:[r,a]},{scope:{ +2:"number"},match:[/[^a-zA-Z0-9._]|^/,a]}]},{scope:{3:"operator"}, +match:[t,/\s+/,/<-/,/\s+/]},{scope:"operator",relevance:0,variants:[{match:i},{ +match:/%[^%]*%/}]},{scope:"punctuation",relevance:0,match:r},{begin:"`",end:"`", +contains:[{begin:/\\./}]}]}},grmr_ruby:e=>{ +const n=e.regex,t="([a-zA-Z_]\\w*[!?=]?|[-+~]@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?)",a=n.either(/\b([A-Z]+[a-z0-9]+)+/,/\b([A-Z]+[a-z0-9]+)+[A-Z]+/),i=n.concat(a,/(::\w+)*/),r={ +"variable.constant":["__FILE__","__LINE__","__ENCODING__"], +"variable.language":["self","super"], +keyword:["alias","and","begin","BEGIN","break","case","class","defined","do","else","elsif","end","END","ensure","for","if","in","module","next","not","or","redo","require","rescue","retry","return","then","undef","unless","until","when","while","yield","include","extend","prepend","public","private","protected","raise","throw"], +built_in:["proc","lambda","attr_accessor","attr_reader","attr_writer","define_method","private_constant","module_function"], +literal:["true","false","nil"]},s={className:"doctag",begin:"@[A-Za-z]+"},o={ +begin:"#<",end:">"},l=[e.COMMENT("#","$",{contains:[s] +}),e.COMMENT("^=begin","^=end",{contains:[s],relevance:10 +}),e.COMMENT("^__END__",e.MATCH_NOTHING_RE)],c={className:"subst",begin:/#\{/, +end:/\}/,keywords:r},d={className:"string",contains:[e.BACKSLASH_ESCAPE,c], +variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/`/,end:/`/},{ +begin:/%[qQwWx]?\(/,end:/\)/},{begin:/%[qQwWx]?\[/,end:/\]/},{ +begin:/%[qQwWx]?\{/,end:/\}/},{begin:/%[qQwWx]?/},{begin:/%[qQwWx]?\//, +end:/\//},{begin:/%[qQwWx]?%/,end:/%/},{begin:/%[qQwWx]?-/,end:/-/},{ +begin:/%[qQwWx]?\|/,end:/\|/},{begin:/\B\?(\\\d{1,3})/},{ +begin:/\B\?(\\x[A-Fa-f0-9]{1,2})/},{begin:/\B\?(\\u\{?[A-Fa-f0-9]{1,6}\}?)/},{ +begin:/\B\?(\\M-\\C-|\\M-\\c|\\c\\M-|\\M-|\\C-\\M-)[\x20-\x7e]/},{ +begin:/\B\?\\(c|C-)[\x20-\x7e]/},{begin:/\B\?\\?\S/},{ +begin:n.concat(/<<[-~]?'?/,n.lookahead(/(\w+)(?=\W)[^\n]*\n(?:[^\n]*\n)*?\s*\1\b/)), +contains:[e.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/, +contains:[e.BACKSLASH_ESCAPE,c]})]}]},g="[0-9](_?[0-9])*",u={className:"number", +relevance:0,variants:[{ +begin:`\\b([1-9](_?[0-9])*|0)(\\.(${g}))?([eE][+-]?(${g})|r)?i?\\b`},{ +begin:"\\b0[dD][0-9](_?[0-9])*r?i?\\b"},{begin:"\\b0[bB][0-1](_?[0-1])*r?i?\\b" +},{begin:"\\b0[oO][0-7](_?[0-7])*r?i?\\b"},{ +begin:"\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*r?i?\\b"},{ +begin:"\\b0(_?[0-7])+r?i?\\b"}]},b={variants:[{match:/\(\)/},{ +className:"params",begin:/\(/,end:/(?=\))/,excludeBegin:!0,endsParent:!0, +keywords:r}]},m=[d,{variants:[{match:[/class\s+/,i,/\s+<\s+/,i]},{ +match:[/\b(class|module)\s+/,i]}],scope:{2:"title.class", +4:"title.class.inherited"},keywords:r},{match:[/(include|extend)\s+/,i],scope:{ +2:"title.class"},keywords:r},{relevance:0,match:[i,/\.new[. (]/],scope:{ +1:"title.class"}},{relevance:0,match:/\b[A-Z][A-Z_0-9]+\b/, +className:"variable.constant"},{relevance:0,match:a,scope:"title.class"},{ +match:[/def/,/\s+/,t],scope:{1:"keyword",3:"title.function"},contains:[b]},{ +begin:e.IDENT_RE+"::"},{className:"symbol", +begin:e.UNDERSCORE_IDENT_RE+"(!|\\?)?:",relevance:0},{className:"symbol", +begin:":(?!\\s)",contains:[d,{begin:t}],relevance:0},u,{className:"variable", +begin:"(\\$\\W)|((\\$|@@?)(\\w+))(?=[^@$?])(?![A-Za-z])(?![@$?'])"},{ +className:"params",begin:/\|/,end:/\|/,excludeBegin:!0,excludeEnd:!0, +relevance:0,keywords:r},{begin:"("+e.RE_STARTERS_RE+"|unless)\\s*", +keywords:"unless",contains:[{className:"regexp",contains:[e.BACKSLASH_ESCAPE,c], +illegal:/\n/,variants:[{begin:"/",end:"/[a-z]*"},{begin:/%r\{/,end:/\}[a-z]*/},{ +begin:"%r\\(",end:"\\)[a-z]*"},{begin:"%r!",end:"![a-z]*"},{begin:"%r\\[", +end:"\\][a-z]*"}]}].concat(o,l),relevance:0}].concat(o,l) +;c.contains=m,b.contains=m;const p=[{begin:/^\s*=>/,starts:{end:"$",contains:m} +},{className:"meta.prompt", +begin:"^([>?]>|[\\w#]+\\(\\w+\\):\\d+:\\d+[>*]|(\\w+-)?\\d+\\.\\d+\\.\\d+(p\\d+)?[^\\d][^>]+>)(?=[ ])", +starts:{end:"$",keywords:r,contains:m}}];return l.unshift(o),{name:"Ruby", +aliases:["rb","gemspec","podspec","thor","irb"],keywords:r,illegal:/\/\*/, +contains:[e.SHEBANG({binary:"ruby"})].concat(p).concat(l).concat(m)}}, +grmr_rust:e=>{const n=e.regex,t={className:"title.function.invoke",relevance:0, +begin:n.concat(/\b/,/(?!let|for|while|if|else|match\b)/,e.IDENT_RE,n.lookahead(/\s*\(/)) +},a="([ui](8|16|32|64|128|size)|f(32|64))?",i=["drop ","Copy","Send","Sized","Sync","Drop","Fn","FnMut","FnOnce","ToOwned","Clone","Debug","PartialEq","PartialOrd","Eq","Ord","AsRef","AsMut","Into","From","Default","Iterator","Extend","IntoIterator","DoubleEndedIterator","ExactSizeIterator","SliceConcatExt","ToString","assert!","assert_eq!","bitflags!","bytes!","cfg!","col!","concat!","concat_idents!","debug_assert!","debug_assert_eq!","env!","eprintln!","panic!","file!","format!","format_args!","include_bytes!","include_str!","line!","local_data_key!","module_path!","option_env!","print!","println!","select!","stringify!","try!","unimplemented!","unreachable!","vec!","write!","writeln!","macro_rules!","assert_ne!","debug_assert_ne!"],r=["i8","i16","i32","i64","i128","isize","u8","u16","u32","u64","u128","usize","f32","f64","str","char","bool","Box","Option","Result","String","Vec"] +;return{name:"Rust",aliases:["rs"],keywords:{$pattern:e.IDENT_RE+"!?",type:r, +keyword:["abstract","as","async","await","become","box","break","const","continue","crate","do","dyn","else","enum","extern","false","final","fn","for","if","impl","in","let","loop","macro","match","mod","move","mut","override","priv","pub","ref","return","self","Self","static","struct","super","trait","true","try","type","typeof","unsafe","unsized","use","virtual","where","while","yield"], +literal:["true","false","Some","None","Ok","Err"],built_in:i},illegal:""},t]}}, +grmr_scss:e=>{const n=ie(e),t=le,a=oe,i="@[a-z-]+",r={className:"variable", +begin:"(\\$[a-zA-Z-][a-zA-Z0-9_-]*)\\b",relevance:0};return{name:"SCSS", +case_insensitive:!0,illegal:"[=/|']", +contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,n.CSS_NUMBER_MODE,{ +className:"selector-id",begin:"#[A-Za-z0-9_-]+",relevance:0},{ +className:"selector-class",begin:"\\.[A-Za-z0-9_-]+",relevance:0 +},n.ATTRIBUTE_SELECTOR_MODE,{className:"selector-tag", +begin:"\\b("+re.join("|")+")\\b",relevance:0},{className:"selector-pseudo", +begin:":("+a.join("|")+")"},{className:"selector-pseudo", +begin:":(:)?("+t.join("|")+")"},r,{begin:/\(/,end:/\)/, +contains:[n.CSS_NUMBER_MODE]},n.CSS_VARIABLE,{className:"attribute", +begin:"\\b("+ce.join("|")+")\\b"},{ +begin:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b" +},{begin:/:/,end:/[;}{]/,relevance:0, +contains:[n.BLOCK_COMMENT,r,n.HEXCOLOR,n.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,n.IMPORTANT,n.FUNCTION_DISPATCH] +},{begin:"@(page|font-face)",keywords:{$pattern:i,keyword:"@page @font-face"}},{ +begin:"@",end:"[{;]",returnBegin:!0,keywords:{$pattern:/[a-z-]+/, +keyword:"and or not only",attribute:se.join(" ")},contains:[{begin:i, +className:"keyword"},{begin:/[a-z-]+(?=:)/,className:"attribute" +},r,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,n.HEXCOLOR,n.CSS_NUMBER_MODE] +},n.FUNCTION_DISPATCH]}},grmr_shell:e=>({name:"Shell Session", +aliases:["console","shellsession"],contains:[{className:"meta.prompt", +begin:/^\s{0,3}[/~\w\d[\]()@-]*[>%$#][ ]?/,starts:{end:/[^\\](?=\s*$)/, +subLanguage:"bash"}}]}),grmr_sql:e=>{ +const n=e.regex,t=e.COMMENT("--","$"),a=["true","false","unknown"],i=["bigint","binary","blob","boolean","char","character","clob","date","dec","decfloat","decimal","float","int","integer","interval","nchar","nclob","national","numeric","real","row","smallint","time","timestamp","varchar","varying","varbinary"],r=["abs","acos","array_agg","asin","atan","avg","cast","ceil","ceiling","coalesce","corr","cos","cosh","count","covar_pop","covar_samp","cume_dist","dense_rank","deref","element","exp","extract","first_value","floor","json_array","json_arrayagg","json_exists","json_object","json_objectagg","json_query","json_table","json_table_primitive","json_value","lag","last_value","lead","listagg","ln","log","log10","lower","max","min","mod","nth_value","ntile","nullif","percent_rank","percentile_cont","percentile_disc","position","position_regex","power","rank","regr_avgx","regr_avgy","regr_count","regr_intercept","regr_r2","regr_slope","regr_sxx","regr_sxy","regr_syy","row_number","sin","sinh","sqrt","stddev_pop","stddev_samp","substring","substring_regex","sum","tan","tanh","translate","translate_regex","treat","trim","trim_array","unnest","upper","value_of","var_pop","var_samp","width_bucket"],s=["create table","insert into","primary key","foreign key","not null","alter table","add constraint","grouping sets","on overflow","character set","respect nulls","ignore nulls","nulls first","nulls last","depth first","breadth first"],o=r,l=["abs","acos","all","allocate","alter","and","any","are","array","array_agg","array_max_cardinality","as","asensitive","asin","asymmetric","at","atan","atomic","authorization","avg","begin","begin_frame","begin_partition","between","bigint","binary","blob","boolean","both","by","call","called","cardinality","cascaded","case","cast","ceil","ceiling","char","char_length","character","character_length","check","classifier","clob","close","coalesce","collate","collect","column","commit","condition","connect","constraint","contains","convert","copy","corr","corresponding","cos","cosh","count","covar_pop","covar_samp","create","cross","cube","cume_dist","current","current_catalog","current_date","current_default_transform_group","current_path","current_role","current_row","current_schema","current_time","current_timestamp","current_path","current_role","current_transform_group_for_type","current_user","cursor","cycle","date","day","deallocate","dec","decimal","decfloat","declare","default","define","delete","dense_rank","deref","describe","deterministic","disconnect","distinct","double","drop","dynamic","each","element","else","empty","end","end_frame","end_partition","end-exec","equals","escape","every","except","exec","execute","exists","exp","external","extract","false","fetch","filter","first_value","float","floor","for","foreign","frame_row","free","from","full","function","fusion","get","global","grant","group","grouping","groups","having","hold","hour","identity","in","indicator","initial","inner","inout","insensitive","insert","int","integer","intersect","intersection","interval","into","is","join","json_array","json_arrayagg","json_exists","json_object","json_objectagg","json_query","json_table","json_table_primitive","json_value","lag","language","large","last_value","lateral","lead","leading","left","like","like_regex","listagg","ln","local","localtime","localtimestamp","log","log10","lower","match","match_number","match_recognize","matches","max","member","merge","method","min","minute","mod","modifies","module","month","multiset","national","natural","nchar","nclob","new","no","none","normalize","not","nth_value","ntile","null","nullif","numeric","octet_length","occurrences_regex","of","offset","old","omit","on","one","only","open","or","order","out","outer","over","overlaps","overlay","parameter","partition","pattern","per","percent","percent_rank","percentile_cont","percentile_disc","period","portion","position","position_regex","power","precedes","precision","prepare","primary","procedure","ptf","range","rank","reads","real","recursive","ref","references","referencing","regr_avgx","regr_avgy","regr_count","regr_intercept","regr_r2","regr_slope","regr_sxx","regr_sxy","regr_syy","release","result","return","returns","revoke","right","rollback","rollup","row","row_number","rows","running","savepoint","scope","scroll","search","second","seek","select","sensitive","session_user","set","show","similar","sin","sinh","skip","smallint","some","specific","specifictype","sql","sqlexception","sqlstate","sqlwarning","sqrt","start","static","stddev_pop","stddev_samp","submultiset","subset","substring","substring_regex","succeeds","sum","symmetric","system","system_time","system_user","table","tablesample","tan","tanh","then","time","timestamp","timezone_hour","timezone_minute","to","trailing","translate","translate_regex","translation","treat","trigger","trim","trim_array","true","truncate","uescape","union","unique","unknown","unnest","update","upper","user","using","value","values","value_of","var_pop","var_samp","varbinary","varchar","varying","versioning","when","whenever","where","width_bucket","window","with","within","without","year","add","asc","collation","desc","final","first","last","view"].filter((e=>!r.includes(e))),c={ +begin:n.concat(/\b/,n.either(...o),/\s*\(/),relevance:0,keywords:{built_in:o}} +;return{name:"SQL",case_insensitive:!0,illegal:/[{}]|<\//,keywords:{ +$pattern:/\b[\w\.]+/,keyword:((e,{exceptions:n,when:t}={})=>{const a=t +;return n=n||[],e.map((e=>e.match(/\|\d+$/)||n.includes(e)?e:a(e)?e+"|0":e)) +})(l,{when:e=>e.length<3}),literal:a,type:i, +built_in:["current_catalog","current_date","current_default_transform_group","current_path","current_role","current_schema","current_transform_group_for_type","current_user","session_user","system_time","system_user","current_time","localtime","current_timestamp","localtimestamp"] +},contains:[{begin:n.either(...s),relevance:0,keywords:{$pattern:/[\w\.]+/, +keyword:l.concat(s),literal:a,type:i}},{className:"type", +begin:n.either("double precision","large object","with timezone","without timezone") +},c,{className:"variable",begin:/@[a-z0-9][a-z0-9_]*/},{className:"string", +variants:[{begin:/'/,end:/'/,contains:[{begin:/''/}]}]},{begin:/"/,end:/"/, +contains:[{begin:/""/}]},e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE,t,{ +className:"operator",begin:/[-+*/=%^~]|&&?|\|\|?|!=?|<(?:=>?|<|>)?|>[>=]?/, +relevance:0}]}},grmr_swift:e=>{const n={match:/\s+/,relevance:0 +},t=e.COMMENT("/\\*","\\*/",{contains:["self"]}),a=[e.C_LINE_COMMENT_MODE,t],i={ +match:[/\./,m(...xe,...Me)],className:{2:"keyword"}},r={match:b(/\./,m(...Ae)), +relevance:0},s=Ae.filter((e=>"string"==typeof e)).concat(["_|0"]),o={variants:[{ +className:"keyword", +match:m(...Ae.filter((e=>"string"!=typeof e)).concat(Se).map(ke),...Me)}]},l={ +$pattern:m(/\b\w+/,/#\w+/),keyword:s.concat(Re),literal:Ce},c=[i,r,o],g=[{ +match:b(/\./,m(...De)),relevance:0},{className:"built_in", +match:b(/\b/,m(...De),/(?=\()/)}],u={match:/->/,relevance:0},p=[u,{ +className:"operator",relevance:0,variants:[{match:Be},{match:`\\.(\\.|${Le})+`}] +}],_="([0-9]_*)+",h="([0-9a-fA-F]_*)+",f={className:"number",relevance:0, +variants:[{match:`\\b(${_})(\\.(${_}))?([eE][+-]?(${_}))?\\b`},{ +match:`\\b0x(${h})(\\.(${h}))?([pP][+-]?(${_}))?\\b`},{match:/\b0o([0-7]_*)+\b/ +},{match:/\b0b([01]_*)+\b/}]},E=(e="")=>({className:"subst",variants:[{ +match:b(/\\/,e,/[0\\tnr"']/)},{match:b(/\\/,e,/u\{[0-9a-fA-F]{1,8}\}/)}] +}),y=(e="")=>({className:"subst",match:b(/\\/,e,/[\t ]*(?:[\r\n]|\r\n)/) +}),N=(e="")=>({className:"subst",label:"interpol",begin:b(/\\/,e,/\(/),end:/\)/ +}),w=(e="")=>({begin:b(e,/"""/),end:b(/"""/,e),contains:[E(e),y(e),N(e)] +}),v=(e="")=>({begin:b(e,/"/),end:b(/"/,e),contains:[E(e),N(e)]}),O={ +className:"string", +variants:[w(),w("#"),w("##"),w("###"),v(),v("#"),v("##"),v("###")] +},k=[e.BACKSLASH_ESCAPE,{begin:/\[/,end:/\]/,relevance:0, +contains:[e.BACKSLASH_ESCAPE]}],x={begin:/\/[^\s](?=[^/\n]*\/)/,end:/\//, +contains:k},M=e=>{const n=b(e,/\//),t=b(/\//,e);return{begin:n,end:t, +contains:[...k,{scope:"comment",begin:`#(?!.*${t})`,end:/$/}]}},S={ +scope:"regexp",variants:[M("###"),M("##"),M("#"),x]},A={match:b(/`/,Fe,/`/) +},C=[A,{className:"variable",match:/\$\d+/},{className:"variable", +match:`\\$${ze}+`}],T=[{match:/(@|#(un)?)available/,scope:"keyword",starts:{ +contains:[{begin:/\(/,end:/\)/,keywords:Pe,contains:[...p,f,O]}]}},{ +scope:"keyword",match:b(/@/,m(...je))},{scope:"meta",match:b(/@/,Fe)}],R={ +match:d(/\b[A-Z]/),relevance:0,contains:[{className:"type", +match:b(/(AV|CA|CF|CG|CI|CL|CM|CN|CT|MK|MP|MTK|MTL|NS|SCN|SK|UI|WK|XC)/,ze,"+") +},{className:"type",match:Ue,relevance:0},{match:/[?!]+/,relevance:0},{ +match:/\.\.\./,relevance:0},{match:b(/\s+&\s+/,d(Ue)),relevance:0}]},D={ +begin://,keywords:l,contains:[...a,...c,...T,u,R]};R.contains.push(D) +;const I={begin:/\(/,end:/\)/,relevance:0,keywords:l,contains:["self",{ +match:b(Fe,/\s*:/),keywords:"_|0",relevance:0 +},...a,S,...c,...g,...p,f,O,...C,...T,R]},L={begin://, +keywords:"repeat each",contains:[...a,R]},B={begin:/\(/,end:/\)/,keywords:l, +contains:[{begin:m(d(b(Fe,/\s*:/)),d(b(Fe,/\s+/,Fe,/\s*:/))),end:/:/, +relevance:0,contains:[{className:"keyword",match:/\b_\b/},{className:"params", +match:Fe}]},...a,...c,...p,f,O,...T,R,I],endsParent:!0,illegal:/["']/},$={ +match:[/(func|macro)/,/\s+/,m(A.match,Fe,Be)],className:{1:"keyword", +3:"title.function"},contains:[L,B,n],illegal:[/\[/,/%/]},z={ +match:[/\b(?:subscript|init[?!]?)/,/\s*(?=[<(])/],className:{1:"keyword"}, +contains:[L,B,n],illegal:/\[|%/},F={match:[/operator/,/\s+/,Be],className:{ +1:"keyword",3:"title"}},U={begin:[/precedencegroup/,/\s+/,Ue],className:{ +1:"keyword",3:"title"},contains:[R],keywords:[...Te,...Ce],end:/}/} +;for(const e of O.variants){const n=e.contains.find((e=>"interpol"===e.label)) +;n.keywords=l;const t=[...c,...g,...p,f,O,...C];n.contains=[...t,{begin:/\(/, +end:/\)/,contains:["self",...t]}]}return{name:"Swift",keywords:l, +contains:[...a,$,z,{beginKeywords:"struct protocol class extension enum actor", +end:"\\{",excludeEnd:!0,keywords:l,contains:[e.inherit(e.TITLE_MODE,{ +className:"title.class",begin:/[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/}),...c] +},F,U,{beginKeywords:"import",end:/$/,contains:[...a],relevance:0 +},S,...c,...g,...p,f,O,...C,...T,R,I]}},grmr_typescript:e=>{ +const n=Oe(e),t=_e,a=["any","void","number","boolean","string","object","never","symbol","bigint","unknown"],i={ +beginKeywords:"namespace",end:/\{/,excludeEnd:!0, +contains:[n.exports.CLASS_REFERENCE]},r={beginKeywords:"interface",end:/\{/, +excludeEnd:!0,keywords:{keyword:"interface extends",built_in:a}, +contains:[n.exports.CLASS_REFERENCE]},s={$pattern:_e, +keyword:he.concat(["type","namespace","interface","public","private","protected","implements","declare","abstract","readonly","enum","override"]), +literal:fe,built_in:ve.concat(a),"variable.language":we},o={className:"meta", +begin:"@"+t},l=(e,n,t)=>{const a=e.contains.findIndex((e=>e.label===n)) +;if(-1===a)throw Error("can not find mode to replace");e.contains.splice(a,1,t)} +;return Object.assign(n.keywords,s), +n.exports.PARAMS_CONTAINS.push(o),n.contains=n.contains.concat([o,i,r]), +l(n,"shebang",e.SHEBANG()),l(n,"use_strict",{className:"meta",relevance:10, +begin:/^\s*['"]use strict['"]/ +}),n.contains.find((e=>"func.def"===e.label)).relevance=0,Object.assign(n,{ +name:"TypeScript",aliases:["ts","tsx","mts","cts"]}),n},grmr_vbnet:e=>{ +const n=e.regex,t=/\d{1,2}\/\d{1,2}\/\d{4}/,a=/\d{4}-\d{1,2}-\d{1,2}/,i=/(\d|1[012])(:\d+){0,2} *(AM|PM)/,r=/\d{1,2}(:\d{1,2}){1,2}/,s={ +className:"literal",variants:[{begin:n.concat(/# */,n.either(a,t),/ *#/)},{ +begin:n.concat(/# */,r,/ *#/)},{begin:n.concat(/# */,i,/ *#/)},{ +begin:n.concat(/# */,n.either(a,t),/ +/,n.either(i,r),/ *#/)}] +},o=e.COMMENT(/'''/,/$/,{contains:[{className:"doctag",begin:/<\/?/,end:/>/}] +}),l=e.COMMENT(null,/$/,{variants:[{begin:/'/},{begin:/([\t ]|^)REM(?=\s)/}]}) +;return{name:"Visual Basic .NET",aliases:["vb"],case_insensitive:!0, +classNameAliases:{label:"symbol"},keywords:{ +keyword:"addhandler alias aggregate ansi as async assembly auto binary by byref byval call case catch class compare const continue custom declare default delegate dim distinct do each equals else elseif end enum erase error event exit explicit finally for friend from function get global goto group handles if implements imports in inherits interface into iterator join key let lib loop me mid module mustinherit mustoverride mybase myclass namespace narrowing new next notinheritable notoverridable of off on operator option optional order overloads overridable overrides paramarray partial preserve private property protected public raiseevent readonly redim removehandler resume return select set shadows shared skip static step stop structure strict sub synclock take text then throw to try unicode until using when where while widening with withevents writeonly yield", +built_in:"addressof and andalso await directcast gettype getxmlnamespace is isfalse isnot istrue like mod nameof new not or orelse trycast typeof xor cbool cbyte cchar cdate cdbl cdec cint clng cobj csbyte cshort csng cstr cuint culng cushort", +type:"boolean byte char date decimal double integer long object sbyte short single string uinteger ulong ushort", +literal:"true false nothing"}, +illegal:"//|\\{|\\}|endif|gosub|variant|wend|^\\$ ",contains:[{ +className:"string",begin:/"(""|[^/n])"C\b/},{className:"string",begin:/"/, +end:/"/,illegal:/\n/,contains:[{begin:/""/}]},s,{className:"number",relevance:0, +variants:[{begin:/\b\d[\d_]*((\.[\d_]+(E[+-]?[\d_]+)?)|(E[+-]?[\d_]+))[RFD@!#]?/ +},{begin:/\b\d[\d_]*((U?[SIL])|[%&])?/},{begin:/&H[\dA-F_]+((U?[SIL])|[%&])?/},{ +begin:/&O[0-7_]+((U?[SIL])|[%&])?/},{begin:/&B[01_]+((U?[SIL])|[%&])?/}]},{ +className:"label",begin:/^\w+:/},o,l,{className:"meta", +begin:/[\t ]*#(const|disable|else|elseif|enable|end|externalsource|if|region)\b/, +end:/$/,keywords:{ +keyword:"const disable else elseif enable end externalsource if region then"}, +contains:[l]}]}},grmr_wasm:e=>{e.regex;const n=e.COMMENT(/\(;/,/;\)/) +;return n.contains.push("self"),{name:"WebAssembly",keywords:{$pattern:/[\w.]+/, +keyword:["anyfunc","block","br","br_if","br_table","call","call_indirect","data","drop","elem","else","end","export","func","global.get","global.set","local.get","local.set","local.tee","get_global","get_local","global","if","import","local","loop","memory","memory.grow","memory.size","module","mut","nop","offset","param","result","return","select","set_global","set_local","start","table","tee_local","then","type","unreachable"] +},contains:[e.COMMENT(/;;/,/$/),n,{match:[/(?:offset|align)/,/\s*/,/=/], +className:{1:"keyword",3:"operator"}},{className:"variable",begin:/\$[\w_]+/},{ +match:/(\((?!;)|\))+/,className:"punctuation",relevance:0},{ +begin:[/(?:func|call|call_indirect)/,/\s+/,/\$[^\s)]+/],className:{1:"keyword", +3:"title.function"}},e.QUOTE_STRING_MODE,{match:/(i32|i64|f32|f64)(?!\.)/, +className:"type"},{className:"keyword", +match:/\b(f32|f64|i32|i64)(?:\.(?:abs|add|and|ceil|clz|const|convert_[su]\/i(?:32|64)|copysign|ctz|demote\/f64|div(?:_[su])?|eqz?|extend_[su]\/i32|floor|ge(?:_[su])?|gt(?:_[su])?|le(?:_[su])?|load(?:(?:8|16|32)_[su])?|lt(?:_[su])?|max|min|mul|nearest|neg?|or|popcnt|promote\/f32|reinterpret\/[fi](?:32|64)|rem_[su]|rot[lr]|shl|shr_[su]|store(?:8|16|32)?|sqrt|sub|trunc(?:_[su]\/f(?:32|64))?|wrap\/i64|xor))\b/ +},{className:"number",relevance:0, +match:/[+-]?\b(?:\d(?:_?\d)*(?:\.\d(?:_?\d)*)?(?:[eE][+-]?\d(?:_?\d)*)?|0x[\da-fA-F](?:_?[\da-fA-F])*(?:\.[\da-fA-F](?:_?[\da-fA-D])*)?(?:[pP][+-]?\d(?:_?\d)*)?)\b|\binf\b|\bnan(?::0x[\da-fA-F](?:_?[\da-fA-D])*)?\b/ +}]}},grmr_xml:e=>{ +const n=e.regex,t=n.concat(/[\p{L}_]/u,n.optional(/[\p{L}0-9_.-]*:/u),/[\p{L}0-9_.-]*/u),a={ +className:"symbol",begin:/&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;/},i={begin:/\s/, +contains:[{className:"keyword",begin:/#?[a-z_][a-z1-9_-]+/,illegal:/\n/}] +},r=e.inherit(i,{begin:/\(/,end:/\)/}),s=e.inherit(e.APOS_STRING_MODE,{ +className:"string"}),o=e.inherit(e.QUOTE_STRING_MODE,{className:"string"}),l={ +endsWithParent:!0,illegal:/`]+/}]}]}]};return{ +name:"HTML, XML", +aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"], +case_insensitive:!0,unicodeRegex:!0,contains:[{className:"meta",begin://,relevance:10,contains:[i,o,s,r,{begin:/\[/,end:/\]/,contains:[{ +className:"meta",begin://,contains:[i,r,o,s]}]}] +},e.COMMENT(//,{relevance:10}),{begin://, +relevance:10},a,{className:"meta",end:/\?>/,variants:[{begin:/<\?xml/, +relevance:10,contains:[o]},{begin:/<\?[a-z][a-z0-9]+/}]},{className:"tag", +begin:/)/,end:/>/,keywords:{name:"style"},contains:[l],starts:{ +end:/<\/style>/,returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag", +begin:/)/,end:/>/,keywords:{name:"script"},contains:[l],starts:{ +end:/<\/script>/,returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{ +className:"tag",begin:/<>|<\/>/},{className:"tag", +begin:n.concat(//,/>/,/\s/)))), +end:/\/?>/,contains:[{className:"name",begin:t,relevance:0,starts:l}]},{ +className:"tag",begin:n.concat(/<\//,n.lookahead(n.concat(t,/>/))),contains:[{ +className:"name",begin:t,relevance:0},{begin:/>/,relevance:0,endsParent:!0}]}]} +},grmr_yaml:e=>{ +const n="true false yes no null",t="[\\w#;/?:@&=+$,.~*'()[\\]]+",a={ +className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/ +},{begin:/\S+/}],contains:[e.BACKSLASH_ESCAPE,{className:"template-variable", +variants:[{begin:/\{\{/,end:/\}\}/},{begin:/%\{/,end:/\}/}]}]},i=e.inherit(a,{ +variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),r={ +end:",",endsWithParent:!0,excludeEnd:!0,keywords:n,relevance:0},s={begin:/\{/, +end:/\}/,contains:[r],illegal:"\\n",relevance:0},o={begin:"\\[",end:"\\]", +contains:[r],illegal:"\\n",relevance:0},l=[{className:"attr",variants:[{ +begin:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{begin:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{ +begin:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{className:"meta",begin:"^---\\s*$", +relevance:10},{className:"string", +begin:"[\\|>]([1-9]?[+-])?[ ]*\\n( +)[^ ][^\\n]*\\n(\\2[^\\n]+\\n?)*"},{ +begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0, +relevance:0},{className:"type",begin:"!\\w+!"+t},{className:"type", +begin:"!<"+t+">"},{className:"type",begin:"!"+t},{className:"type",begin:"!!"+t +},{className:"meta",begin:"&"+e.UNDERSCORE_IDENT_RE+"$"},{className:"meta", +begin:"\\*"+e.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"-(?=[ ]|$)", +relevance:0},e.HASH_COMMENT_MODE,{beginKeywords:n,keywords:{literal:n}},{ +className:"number", +begin:"\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b" +},{className:"number",begin:e.C_NUMBER_RE+"\\b",relevance:0},s,o,a],c=[...l] +;return c.pop(),c.push(i),r.contains=c,{name:"YAML",case_insensitive:!0, +aliases:["yml"],contains:l}}});const He=ae;for(const e of Object.keys(Ke)){ +const n=e.replace("grmr_","").replace("_","-");He.registerLanguage(n,Ke[e])} +return He}() +;"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs); + +// Copy button plugin +class CopyButtonPlugin{constructor(options={}){this.hook=options.hook;this.callback=options.callback;this.lang=options.lang||document.documentElement.lang||"en";this.autohide=typeof options.autohide!=="undefined"?options.autohide:true}"after:highlightElement"({el,text}){if(el.parentElement.querySelector(".hljs-copy-button"))return;let{hook,callback,lang,autohide}=this;let container=Object.assign(document.createElement("div"),{className:"hljs-copy-container"});container.dataset.autohide=autohide;let button=Object.assign(document.createElement("button"),{innerHTML:locales[lang]?.[0]||"Copy",className:"hljs-copy-button"});button.dataset.copied=false;el.parentElement.classList.add("hljs-copy-wrapper");el.parentElement.appendChild(container);container.appendChild(button);container.style.setProperty("--hljs-theme-background",window.getComputedStyle(el).backgroundColor);container.style.setProperty("--hljs-theme-color",window.getComputedStyle(el).color);container.style.setProperty("--hljs-theme-padding",window.getComputedStyle(el).padding);button.onclick=function(){if(!navigator.clipboard)return;let newText=text;if(hook&&typeof hook==="function"){newText=hook(text,el)||text}navigator.clipboard.writeText(newText).then(function(){button.innerHTML=locales[lang]?.[1]||"Copied!";button.dataset.copied=true;let alert=Object.assign(document.createElement("div"),{role:"status",className:"hljs-copy-alert",innerHTML:locales[lang]?.[2]||"Copied to clipboard"});el.parentElement.appendChild(alert);setTimeout(()=>{button.innerHTML=locales[lang]?.[0]||"Copy";button.dataset.copied=false;el.parentElement.removeChild(alert);alert=null},2e3)}).then(function(){if(typeof callback==="function")return callback(newText,el)})}}}if(typeof module!="undefined"){module.exports=CopyButtonPlugin}const locales={en:["Copy","Copied!","Copied to clipboard"],es:["Copiar","¡Copiado!","Copiado al portapapeles"],"pt-BR":["Copiar","Copiado!","Copiado para a área de transferência"],fr:["Copier","Copié !","Copié dans le presse-papier"],de:["Kopieren","Kopiert!","In die Zwischenablage kopiert"],ja:["コピー","コピーしました!","クリップボードにコピーしました"],ko:["복사","복사됨!","클립보드에 복사됨"],ru:["Копировать","Скопировано!","Скопировано в буфер обмена"],zh:["复制","已复制!","已复制到剪贴板"],"zh-tw":["複製","已複製!","已複製到剪貼簿"]}; + +// Additional language definitions +(function() { + if (typeof hljs === 'undefined') { + console.error('Highlight.js core not available'); + return; + } + + + // Language: rust + (function() { + /*! `rust` grammar compiled for Highlight.js 11.9.0 */ +(()=>{var e=(()=>{"use strict";return e=>{const t=e.regex,n={ +className:"title.function.invoke",relevance:0, +begin:t.concat(/\b/,/(?!let|for|while|if|else|match\b)/,e.IDENT_RE,t.lookahead(/\s*\(/)) +},a="([ui](8|16|32|64|128|size)|f(32|64))?",i=["drop ","Copy","Send","Sized","Sync","Drop","Fn","FnMut","FnOnce","ToOwned","Clone","Debug","PartialEq","PartialOrd","Eq","Ord","AsRef","AsMut","Into","From","Default","Iterator","Extend","IntoIterator","DoubleEndedIterator","ExactSizeIterator","SliceConcatExt","ToString","assert!","assert_eq!","bitflags!","bytes!","cfg!","col!","concat!","concat_idents!","debug_assert!","debug_assert_eq!","env!","eprintln!","panic!","file!","format!","format_args!","include_bytes!","include_str!","line!","local_data_key!","module_path!","option_env!","print!","println!","select!","stringify!","try!","unimplemented!","unreachable!","vec!","write!","writeln!","macro_rules!","assert_ne!","debug_assert_ne!"],r=["i8","i16","i32","i64","i128","isize","u8","u16","u32","u64","u128","usize","f32","f64","str","char","bool","Box","Option","Result","String","Vec"] +;return{name:"Rust",aliases:["rs"],keywords:{$pattern:e.IDENT_RE+"!?",type:r, +keyword:["abstract","as","async","await","become","box","break","const","continue","crate","do","dyn","else","enum","extern","false","final","fn","for","if","impl","in","let","loop","macro","match","mod","move","mut","override","priv","pub","ref","return","self","Self","static","struct","super","trait","true","try","type","typeof","unsafe","unsized","use","virtual","where","while","yield"], +literal:["true","false","Some","None","Ok","Err"],built_in:i},illegal:""},n]}}})() +;hljs.registerLanguage("rust",e)})(); + })(); + + // Language: typescript + (function() { + /*! `typescript` grammar compiled for Highlight.js 11.9.0 */ +(()=>{var e=(()=>{"use strict" +;const e="[A-Za-z$_][0-9A-Za-z$_]*",n=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],a=["true","false","null","undefined","NaN","Infinity"],t=["Object","Function","Boolean","Symbol","Math","Date","Number","BigInt","String","RegExp","Array","Float32Array","Float64Array","Int8Array","Uint8Array","Uint8ClampedArray","Int16Array","Int32Array","Uint16Array","Uint32Array","BigInt64Array","BigUint64Array","Set","Map","WeakSet","WeakMap","ArrayBuffer","SharedArrayBuffer","Atomics","DataView","JSON","Promise","Generator","GeneratorFunction","AsyncFunction","Reflect","Proxy","Intl","WebAssembly"],s=["Error","EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"],r=["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],c=["arguments","this","super","console","window","document","localStorage","sessionStorage","module","global"],i=[].concat(r,t,s) +;function o(o){const l=o.regex,d=e,b={begin:/<[A-Za-z0-9\\._:-]+/, +end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(e,n)=>{ +const a=e[0].length+e.index,t=e.input[a] +;if("<"===t||","===t)return void n.ignoreMatch();let s +;">"===t&&(((e,{after:n})=>{const a="",$={ +match:[/const|var|let/,/\s+/,d,/\s*/,/=\s*/,/(async\s*)?/,l.lookahead(B)], +keywords:"async",className:{1:"keyword",3:"title.function"},contains:[R]} +;return{name:"JavaScript",aliases:["js","jsx","mjs","cjs"],keywords:g,exports:{ +PARAMS_CONTAINS:w,CLASS_REFERENCE:k},illegal:/#(?![$_A-z])/, +contains:[o.SHEBANG({label:"shebang",binary:"node",relevance:5}),{ +label:"use_strict",className:"meta",relevance:10, +begin:/^\s*['"]use (strict|asm)['"]/ +},o.APOS_STRING_MODE,o.QUOTE_STRING_MODE,p,N,f,_,h,{match:/\$\d+/},y,k,{ +className:"attr",begin:d+l.lookahead(":"),relevance:0},$,{ +begin:"("+o.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*", +keywords:"return throw case",relevance:0,contains:[h,o.REGEXP_MODE,{ +className:"function",begin:B,returnBegin:!0,end:"\\s*=>",contains:[{ +className:"params",variants:[{begin:o.UNDERSCORE_IDENT_RE,relevance:0},{ +className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0, +excludeEnd:!0,keywords:g,contains:w}]}]},{begin:/,/,relevance:0},{match:/\s+/, +relevance:0},{variants:[{begin:"<>",end:""},{ +match:/<[A-Za-z0-9\\._:-]+\s*\/>/},{begin:b.begin, +"on:begin":b.isTrulyOpeningTag,end:b.end}],subLanguage:"xml",contains:[{ +begin:b.begin,end:b.end,skip:!0,contains:["self"]}]}]},O,{ +beginKeywords:"while if switch catch for"},{ +begin:"\\b(?!function)"+o.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{", +returnBegin:!0,label:"func.def",contains:[R,o.inherit(o.TITLE_MODE,{begin:d, +className:"title.function"})]},{match:/\.\.\./,relevance:0},T,{match:"\\$"+d, +relevance:0},{match:[/\bconstructor(?=\s*\()/],className:{1:"title.function"}, +contains:[R]},C,{relevance:0,match:/\b[A-Z][A-Z_0-9]+\b/, +className:"variable.constant"},x,M,{match:/\$[(.]/}]}}return t=>{ +const s=o(t),r=e,l=["any","void","number","boolean","string","object","never","symbol","bigint","unknown"],d={ +beginKeywords:"namespace",end:/\{/,excludeEnd:!0, +contains:[s.exports.CLASS_REFERENCE]},b={beginKeywords:"interface",end:/\{/, +excludeEnd:!0,keywords:{keyword:"interface extends",built_in:l}, +contains:[s.exports.CLASS_REFERENCE]},g={$pattern:e, +keyword:n.concat(["type","namespace","interface","public","private","protected","implements","declare","abstract","readonly","enum","override"]), +literal:a,built_in:i.concat(l),"variable.language":c},u={className:"meta", +begin:"@"+r},m=(e,n,a)=>{const t=e.contains.findIndex((e=>e.label===n)) +;if(-1===t)throw Error("can not find mode to replace");e.contains.splice(t,1,a)} +;return Object.assign(s.keywords,g), +s.exports.PARAMS_CONTAINS.push(u),s.contains=s.contains.concat([u,d,b]), +m(s,"shebang",t.SHEBANG()),m(s,"use_strict",{className:"meta",relevance:10, +begin:/^\s*['"]use strict['"]/ +}),s.contains.find((e=>"func.def"===e.label)).relevance=0,Object.assign(s,{ +name:"TypeScript",aliases:["ts","tsx","mts","cts"]}),s}})() +;hljs.registerLanguage("typescript",e)})(); + })(); + + // Language: bash + (function() { + /*! `bash` grammar compiled for Highlight.js 11.9.0 */ +(()=>{var e=(()=>{"use strict";return e=>{const s=e.regex,t={},n={begin:/\$\{/, +end:/\}/,contains:["self",{begin:/:-/,contains:[t]}]};Object.assign(t,{ +className:"variable",variants:[{ +begin:s.concat(/\$[\w\d#@][\w\d_]*/,"(?![\\w\\d])(?![$])")},n]});const a={ +className:"subst",begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE]},i={ +begin:/<<-?\s*(?=\w+)/,starts:{contains:[e.END_SAME_AS_BEGIN({begin:/(\w+)/, +end:/(\w+)/,className:"string"})]}},c={className:"string",begin:/"/,end:/"/, +contains:[e.BACKSLASH_ESCAPE,t,a]};a.contains.push(c);const o={begin:/\$?\(\(/, +end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},e.NUMBER_MODE,t] +},r=e.SHEBANG({binary:"(fish|bash|zsh|sh|csh|ksh|tcsh|dash|scsh)",relevance:10 +}),l={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0, +contains:[e.inherit(e.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{ +name:"Bash",aliases:["sh"],keywords:{$pattern:/\b[a-z][a-z0-9._-]+\b/, +keyword:["if","then","else","elif","fi","for","while","until","in","do","done","case","esac","function","select"], +literal:["true","false"], +built_in:["break","cd","continue","eval","exec","exit","export","getopts","hash","pwd","readonly","return","shift","test","times","trap","umask","unset","alias","bind","builtin","caller","command","declare","echo","enable","help","let","local","logout","mapfile","printf","read","readarray","source","type","typeset","ulimit","unalias","set","shopt","autoload","bg","bindkey","bye","cap","chdir","clone","comparguments","compcall","compctl","compdescribe","compfiles","compgroups","compquote","comptags","comptry","compvalues","dirs","disable","disown","echotc","echoti","emulate","fc","fg","float","functions","getcap","getln","history","integer","jobs","kill","limit","log","noglob","popd","print","pushd","pushln","rehash","sched","setcap","setopt","stat","suspend","ttyctl","unfunction","unhash","unlimit","unsetopt","vared","wait","whence","where","which","zcompile","zformat","zftp","zle","zmodload","zparseopts","zprof","zpty","zregexparse","zsocket","zstyle","ztcp","chcon","chgrp","chown","chmod","cp","dd","df","dir","dircolors","ln","ls","mkdir","mkfifo","mknod","mktemp","mv","realpath","rm","rmdir","shred","sync","touch","truncate","vdir","b2sum","base32","base64","cat","cksum","comm","csplit","cut","expand","fmt","fold","head","join","md5sum","nl","numfmt","od","paste","ptx","pr","sha1sum","sha224sum","sha256sum","sha384sum","sha512sum","shuf","sort","split","sum","tac","tail","tr","tsort","unexpand","uniq","wc","arch","basename","chroot","date","dirname","du","echo","env","expr","factor","groups","hostid","id","link","logname","nice","nohup","nproc","pathchk","pinky","printenv","printf","pwd","readlink","runcon","seq","sleep","stat","stdbuf","stty","tee","test","timeout","tty","uname","unlink","uptime","users","who","whoami","yes"] +},contains:[r,e.SHEBANG(),l,o,e.HASH_COMMENT_MODE,i,{match:/(\/[a-z._-]+)+/},c,{ +match:/\\"/},{className:"string",begin:/'/,end:/'/},{match:/\\'/},t]}}})() +;hljs.registerLanguage("bash",e)})(); + })(); + + // Language: yaml + (function() { + /*! `yaml` grammar compiled for Highlight.js 11.9.0 */ +(()=>{var e=(()=>{"use strict";return e=>{ +const n="true false yes no null",a="[\\w#;/?:@&=+$,.~*'()[\\]]+",s={ +className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/ +},{begin:/\S+/}],contains:[e.BACKSLASH_ESCAPE,{className:"template-variable", +variants:[{begin:/\{\{/,end:/\}\}/},{begin:/%\{/,end:/\}/}]}]},i=e.inherit(s,{ +variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),l={ +end:",",endsWithParent:!0,excludeEnd:!0,keywords:n,relevance:0},t={begin:/\{/, +end:/\}/,contains:[l],illegal:"\\n",relevance:0},g={begin:"\\[",end:"\\]", +contains:[l],illegal:"\\n",relevance:0},b=[{className:"attr",variants:[{ +begin:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{begin:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{ +begin:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{className:"meta",begin:"^---\\s*$", +relevance:10},{className:"string", +begin:"[\\|>]([1-9]?[+-])?[ ]*\\n( +)[^ ][^\\n]*\\n(\\2[^\\n]+\\n?)*"},{ +begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0, +relevance:0},{className:"type",begin:"!\\w+!"+a},{className:"type", +begin:"!<"+a+">"},{className:"type",begin:"!"+a},{className:"type",begin:"!!"+a +},{className:"meta",begin:"&"+e.UNDERSCORE_IDENT_RE+"$"},{className:"meta", +begin:"\\*"+e.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"-(?=[ ]|$)", +relevance:0},e.HASH_COMMENT_MODE,{beginKeywords:n,keywords:{literal:n}},{ +className:"number", +begin:"\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b" +},{className:"number",begin:e.C_NUMBER_RE+"\\b",relevance:0},t,g,s],r=[...b] +;return r.pop(),r.push(i),l.contains=r,{name:"YAML",case_insensitive:!0, +aliases:["yml"],contains:b}}})();hljs.registerLanguage("yaml",e)})(); + })(); + + // Language: dockerfile + (function() { + /*! `dockerfile` grammar compiled for Highlight.js 11.9.0 */ +(()=>{var e=(()=>{"use strict";return e=>({name:"Dockerfile",aliases:["docker"], +case_insensitive:!0, +keywords:["from","maintainer","expose","env","arg","user","onbuild","stopsignal"], +contains:[e.HASH_COMMENT_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.NUMBER_MODE,{ +beginKeywords:"run cmd entrypoint volume add copy workdir label healthcheck shell", +starts:{end:/[^\\]$/,subLanguage:"bash"}}],illegal:"{var e=(()=>{"use strict";return e=>{ +const r=e.regex,t=e.COMMENT("--","$"),n=["true","false","unknown"],a=["bigint","binary","blob","boolean","char","character","clob","date","dec","decfloat","decimal","float","int","integer","interval","nchar","nclob","national","numeric","real","row","smallint","time","timestamp","varchar","varying","varbinary"],i=["abs","acos","array_agg","asin","atan","avg","cast","ceil","ceiling","coalesce","corr","cos","cosh","count","covar_pop","covar_samp","cume_dist","dense_rank","deref","element","exp","extract","first_value","floor","json_array","json_arrayagg","json_exists","json_object","json_objectagg","json_query","json_table","json_table_primitive","json_value","lag","last_value","lead","listagg","ln","log","log10","lower","max","min","mod","nth_value","ntile","nullif","percent_rank","percentile_cont","percentile_disc","position","position_regex","power","rank","regr_avgx","regr_avgy","regr_count","regr_intercept","regr_r2","regr_slope","regr_sxx","regr_sxy","regr_syy","row_number","sin","sinh","sqrt","stddev_pop","stddev_samp","substring","substring_regex","sum","tan","tanh","translate","translate_regex","treat","trim","trim_array","unnest","upper","value_of","var_pop","var_samp","width_bucket"],s=["create table","insert into","primary key","foreign key","not null","alter table","add constraint","grouping sets","on overflow","character set","respect nulls","ignore nulls","nulls first","nulls last","depth first","breadth first"],o=i,c=["abs","acos","all","allocate","alter","and","any","are","array","array_agg","array_max_cardinality","as","asensitive","asin","asymmetric","at","atan","atomic","authorization","avg","begin","begin_frame","begin_partition","between","bigint","binary","blob","boolean","both","by","call","called","cardinality","cascaded","case","cast","ceil","ceiling","char","char_length","character","character_length","check","classifier","clob","close","coalesce","collate","collect","column","commit","condition","connect","constraint","contains","convert","copy","corr","corresponding","cos","cosh","count","covar_pop","covar_samp","create","cross","cube","cume_dist","current","current_catalog","current_date","current_default_transform_group","current_path","current_role","current_row","current_schema","current_time","current_timestamp","current_path","current_role","current_transform_group_for_type","current_user","cursor","cycle","date","day","deallocate","dec","decimal","decfloat","declare","default","define","delete","dense_rank","deref","describe","deterministic","disconnect","distinct","double","drop","dynamic","each","element","else","empty","end","end_frame","end_partition","end-exec","equals","escape","every","except","exec","execute","exists","exp","external","extract","false","fetch","filter","first_value","float","floor","for","foreign","frame_row","free","from","full","function","fusion","get","global","grant","group","grouping","groups","having","hold","hour","identity","in","indicator","initial","inner","inout","insensitive","insert","int","integer","intersect","intersection","interval","into","is","join","json_array","json_arrayagg","json_exists","json_object","json_objectagg","json_query","json_table","json_table_primitive","json_value","lag","language","large","last_value","lateral","lead","leading","left","like","like_regex","listagg","ln","local","localtime","localtimestamp","log","log10","lower","match","match_number","match_recognize","matches","max","member","merge","method","min","minute","mod","modifies","module","month","multiset","national","natural","nchar","nclob","new","no","none","normalize","not","nth_value","ntile","null","nullif","numeric","octet_length","occurrences_regex","of","offset","old","omit","on","one","only","open","or","order","out","outer","over","overlaps","overlay","parameter","partition","pattern","per","percent","percent_rank","percentile_cont","percentile_disc","period","portion","position","position_regex","power","precedes","precision","prepare","primary","procedure","ptf","range","rank","reads","real","recursive","ref","references","referencing","regr_avgx","regr_avgy","regr_count","regr_intercept","regr_r2","regr_slope","regr_sxx","regr_sxy","regr_syy","release","result","return","returns","revoke","right","rollback","rollup","row","row_number","rows","running","savepoint","scope","scroll","search","second","seek","select","sensitive","session_user","set","show","similar","sin","sinh","skip","smallint","some","specific","specifictype","sql","sqlexception","sqlstate","sqlwarning","sqrt","start","static","stddev_pop","stddev_samp","submultiset","subset","substring","substring_regex","succeeds","sum","symmetric","system","system_time","system_user","table","tablesample","tan","tanh","then","time","timestamp","timezone_hour","timezone_minute","to","trailing","translate","translate_regex","translation","treat","trigger","trim","trim_array","true","truncate","uescape","union","unique","unknown","unnest","update","upper","user","using","value","values","value_of","var_pop","var_samp","varbinary","varchar","varying","versioning","when","whenever","where","width_bucket","window","with","within","without","year","add","asc","collation","desc","final","first","last","view"].filter((e=>!i.includes(e))),l={ +begin:r.concat(/\b/,r.either(...o),/\s*\(/),relevance:0,keywords:{built_in:o}} +;return{name:"SQL",case_insensitive:!0,illegal:/[{}]|<\//,keywords:{ +$pattern:/\b[\w\.]+/,keyword:((e,{exceptions:r,when:t}={})=>{const n=t +;return r=r||[],e.map((e=>e.match(/\|\d+$/)||r.includes(e)?e:n(e)?e+"|0":e)) +})(c,{when:e=>e.length<3}),literal:n,type:a, +built_in:["current_catalog","current_date","current_default_transform_group","current_path","current_role","current_schema","current_transform_group_for_type","current_user","session_user","system_time","system_user","current_time","localtime","current_timestamp","localtimestamp"] +},contains:[{begin:r.either(...s),relevance:0,keywords:{$pattern:/[\w\.]+/, +keyword:c.concat(s),literal:n,type:a}},{className:"type", +begin:r.either("double precision","large object","with timezone","without timezone") +},l,{className:"variable",begin:/@[a-z0-9][a-z0-9_]*/},{className:"string", +variants:[{begin:/'/,end:/'/,contains:[{begin:/''/}]}]},{begin:/"/,end:/"/, +contains:[{begin:/""/}]},e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE,t,{ +className:"operator",begin:/[-+*/=%^~]|&&?|\|\|?|!=?|<(?:=>?|<|>)?|>[>=]?/, +relevance:0}]}}})();hljs.registerLanguage("sql",e)})(); + })(); + + // Language: python + (function() { + /*! `python` grammar compiled for Highlight.js 11.9.0 */ +(()=>{var e=(()=>{"use strict";return e=>{ +const n=e.regex,a=/[\p{XID_Start}_]\p{XID_Continue}*/u,i=["and","as","assert","async","await","break","case","class","continue","def","del","elif","else","except","finally","for","from","global","if","import","in","is","lambda","match","nonlocal|10","not","or","pass","raise","return","try","while","with","yield"],s={ +$pattern:/[A-Za-z]\w+|__\w+__/,keyword:i, +built_in:["__import__","abs","all","any","ascii","bin","bool","breakpoint","bytearray","bytes","callable","chr","classmethod","compile","complex","delattr","dict","dir","divmod","enumerate","eval","exec","filter","float","format","frozenset","getattr","globals","hasattr","hash","help","hex","id","input","int","isinstance","issubclass","iter","len","list","locals","map","max","memoryview","min","next","object","oct","open","ord","pow","print","property","range","repr","reversed","round","set","setattr","slice","sorted","staticmethod","str","sum","super","tuple","type","vars","zip"], +literal:["__debug__","Ellipsis","False","None","NotImplemented","True"], +type:["Any","Callable","Coroutine","Dict","List","Literal","Generic","Optional","Sequence","Set","Tuple","Type","Union"] +},t={className:"meta",begin:/^(>>>|\.\.\.) /},r={className:"subst",begin:/\{/, +end:/\}/,keywords:s,illegal:/#/},l={begin:/\{\{/,relevance:0},b={ +className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{ +begin:/([uU]|[bB]|[rR]|[bB][rR]|[rR][bB])?'''/,end:/'''/, +contains:[e.BACKSLASH_ESCAPE,t],relevance:10},{ +begin:/([uU]|[bB]|[rR]|[bB][rR]|[rR][bB])?"""/,end:/"""/, +contains:[e.BACKSLASH_ESCAPE,t],relevance:10},{ +begin:/([fF][rR]|[rR][fF]|[fF])'''/,end:/'''/, +contains:[e.BACKSLASH_ESCAPE,t,l,r]},{begin:/([fF][rR]|[rR][fF]|[fF])"""/, +end:/"""/,contains:[e.BACKSLASH_ESCAPE,t,l,r]},{begin:/([uU]|[rR])'/,end:/'/, +relevance:10},{begin:/([uU]|[rR])"/,end:/"/,relevance:10},{ +begin:/([bB]|[bB][rR]|[rR][bB])'/,end:/'/},{begin:/([bB]|[bB][rR]|[rR][bB])"/, +end:/"/},{begin:/([fF][rR]|[rR][fF]|[fF])'/,end:/'/, +contains:[e.BACKSLASH_ESCAPE,l,r]},{begin:/([fF][rR]|[rR][fF]|[fF])"/,end:/"/, +contains:[e.BACKSLASH_ESCAPE,l,r]},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE] +},o="[0-9](_?[0-9])*",c=`(\\b(${o}))?\\.(${o})|\\b(${o})\\.`,d="\\b|"+i.join("|"),g={ +className:"number",relevance:0,variants:[{ +begin:`(\\b(${o})|(${c}))[eE][+-]?(${o})[jJ]?(?=${d})`},{begin:`(${c})[jJ]?`},{ +begin:`\\b([1-9](_?[0-9])*|0+(_?0)*)[lLjJ]?(?=${d})`},{ +begin:`\\b0[bB](_?[01])+[lL]?(?=${d})`},{begin:`\\b0[oO](_?[0-7])+[lL]?(?=${d})` +},{begin:`\\b0[xX](_?[0-9a-fA-F])+[lL]?(?=${d})`},{begin:`\\b(${o})[jJ](?=${d})` +}]},p={className:"comment",begin:n.lookahead(/# type:/),end:/$/,keywords:s, +contains:[{begin:/# type:/},{begin:/#/,end:/\b\B/,endsWithParent:!0}]},m={ +className:"params",variants:[{className:"",begin:/\(\s*\)/,skip:!0},{begin:/\(/, +end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:s, +contains:["self",t,g,b,e.HASH_COMMENT_MODE]}]};return r.contains=[b,g,t],{ +name:"Python",aliases:["py","gyp","ipython"],unicodeRegex:!0,keywords:s, +illegal:/(<\/|\?)|=>/,contains:[t,g,{begin:/\bself\b/},{beginKeywords:"if", +relevance:0},b,p,e.HASH_COMMENT_MODE,{match:[/\bdef/,/\s+/,a],scope:{ +1:"keyword",3:"title.function"},contains:[m]},{variants:[{ +match:[/\bclass/,/\s+/,a,/\s*/,/\(\s*/,a,/\s*\)/]},{match:[/\bclass/,/\s+/,a]}], +scope:{1:"keyword",3:"title.class",6:"title.class.inherited"}},{ +className:"meta",begin:/^[\t ]*@/,end:/(?=#)|$/,contains:[g,m,b]}]}}})() +;hljs.registerLanguage("python",e)})(); + })(); + + // Language: ini + (function() { + /*! `ini` grammar compiled for Highlight.js 11.9.0 */ +(()=>{var e=(()=>{"use strict";return e=>{const n=e.regex,a={className:"number", +relevance:0,variants:[{begin:/([+-]+)?[\d]+_[\d_]+/},{begin:e.NUMBER_RE}] +},s=e.COMMENT();s.variants=[{begin:/;/,end:/$/},{begin:/#/,end:/$/}];const i={ +className:"variable",variants:[{begin:/\$[\w\d"][\w\d_]*/},{begin:/\$\{(.*?)\}/ +}]},t={className:"literal",begin:/\bon|off|true|false|yes|no\b/},r={ +className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{begin:"'''", +end:"'''",relevance:10},{begin:'"""',end:'"""',relevance:10},{begin:'"',end:'"' +},{begin:"'",end:"'"}]},l={begin:/\[/,end:/\]/,contains:[s,t,i,r,a,"self"], +relevance:0},c=n.either(/[A-Za-z0-9_-]+/,/"(\\"|[^"])*"/,/'[^']*'/);return{ +name:"TOML, also INI",aliases:["toml"],case_insensitive:!0,illegal:/\S/, +contains:[s,{className:"section",begin:/\[+/,end:/\]+/},{ +begin:n.concat(c,"(\\s*\\.\\s*",c,")*",n.lookahead(/\s*=\s*[^#\s]/)), +className:"attr",starts:{end:/$/,contains:[s,l,t,i,r,a]}}]}}})() +;hljs.registerLanguage("ini",e)})(); + })(); + + // Language: properties + (function() { + /*! `properties` grammar compiled for Highlight.js 11.9.0 */ +(()=>{var e=(()=>{"use strict";return e=>{ +const n="[ \\t\\f]*",t=n+"[:=]"+n,s="[ \\t\\f]+",a="([^\\\\:= \\t\\f\\n]|\\\\.)+",r={ +end:"("+t+"|"+s+")",relevance:0,starts:{className:"string",end:/$/,relevance:0, +contains:[{begin:"\\\\\\\\"},{begin:"\\\\\\n"}]}};return{name:".properties", +disableAutodetect:!0,case_insensitive:!0,illegal:/\S/, +contains:[e.COMMENT("^\\s*[!#]","$"),{returnBegin:!0,variants:[{begin:a+t},{ +begin:a+s}],contains:[{className:"attr",begin:a,endsParent:!0}],starts:r},{ +className:"attr",begin:a+n+"$"}]}}})();hljs.registerLanguage("properties",e) +})(); + })(); + + // Language: markdown + (function() { + /*! `markdown` grammar compiled for Highlight.js 11.9.0 */ +(()=>{var e=(()=>{"use strict";return e=>{const n={begin:/<\/?[A-Za-z_]/, +end:">",subLanguage:"xml",relevance:0},a={variants:[{begin:/\[.+?\]\[.*?\]/, +relevance:0},{ +begin:/\[.+?\]\(((data|javascript|mailto):|(?:http|ftp)s?:\/\/).*?\)/, +relevance:2},{ +begin:e.regex.concat(/\[.+?\]\(/,/[A-Za-z][A-Za-z0-9+.-]*/,/:\/\/.*?\)/), +relevance:2},{begin:/\[.+?\]\([./?&#].*?\)/,relevance:1},{ +begin:/\[.*?\]\(.*?\)/,relevance:0}],returnBegin:!0,contains:[{match:/\[(?=\])/ +},{className:"string",relevance:0,begin:"\\[",end:"\\]",excludeBegin:!0, +returnEnd:!0},{className:"link",relevance:0,begin:"\\]\\(",end:"\\)", +excludeBegin:!0,excludeEnd:!0},{className:"symbol",relevance:0,begin:"\\]\\[", +end:"\\]",excludeBegin:!0,excludeEnd:!0}]},i={className:"strong",contains:[], +variants:[{begin:/_{2}(?!\s)/,end:/_{2}/},{begin:/\*{2}(?!\s)/,end:/\*{2}/}] +},s={className:"emphasis",contains:[],variants:[{begin:/\*(?![*\s])/,end:/\*/},{ +begin:/_(?![_\s])/,end:/_/,relevance:0}]},c=e.inherit(i,{contains:[] +}),t=e.inherit(s,{contains:[]});i.contains.push(t),s.contains.push(c) +;let g=[n,a];return[i,s,c,t].forEach((e=>{e.contains=e.contains.concat(g) +})),g=g.concat(i,s),{name:"Markdown",aliases:["md","mkdown","mkd"],contains:[{ +className:"section",variants:[{begin:"^#{1,6}",end:"$",contains:g},{ +begin:"(?=^.+?\\n[=-]{2,}$)",contains:[{begin:"^[=-]*$"},{begin:"^",end:"\\n", +contains:g}]}]},n,{className:"bullet",begin:"^[ \t]*([*+-]|(\\d+\\.))(?=\\s+)", +end:"\\s+",excludeEnd:!0},i,s,{className:"quote",begin:"^>\\s+",contains:g, +end:"$"},{className:"code",variants:[{begin:"(`{3,})[^`](.|\\n)*?\\1`*[ ]*"},{ +begin:"(~{3,})[^~](.|\\n)*?\\1~*[ ]*"},{begin:"```",end:"```+[ ]*$"},{ +begin:"~~~",end:"~~~+[ ]*$"},{begin:"`.+?`"},{begin:"(?=^( {4}|\\t))", +contains:[{begin:"^( {4}|\\t)",end:"(\\n)$"}],relevance:0}]},{ +begin:"^[-\\*]{3,}",end:"$"},a,{begin:/^\[[^\n]+\]:/,returnBegin:!0,contains:[{ +className:"symbol",begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0},{ +className:"link",begin:/:\s*/,end:/$/,excludeBegin:!0}]}]}}})() +;hljs.registerLanguage("markdown",e)})(); + })(); + +})(); + +// Expose available languages globally for use by other scripts +if (typeof window !== 'undefined') { + window.__AVAILABLE_LANGUAGES = ["en","es"]; +} else if (typeof globalThis !== 'undefined') { + globalThis.__AVAILABLE_LANGUAGES = ["en","es"]; +} + +// Auto-initialize when DOM is ready +if (typeof document !== 'undefined') { + function initializeHighlightJs() { + if (typeof hljs !== 'undefined' && hljs.highlightAll) { + hljs.configure({ ignoreUnescapedHTML: true }); + hljs.highlightAll(); + + // Add copy button plugin with dynamic language detection and autohide configuration + if (typeof CopyButtonPlugin !== 'undefined') { + const docLang = document.documentElement.lang || 'en'; + + // Dynamically discovered available languages from content/locales + const AVAILABLE_LANGUAGES = ["en","es"]; + + // Build language configuration for all available languages + const langConfig = {}; + AVAILABLE_LANGUAGES.forEach(lang => { + langConfig[lang] = { autohide: false, lang: lang }; + }); + + // Use detected language or fallback to first available language + const config = langConfig[docLang] || langConfig[AVAILABLE_LANGUAGES[0]] || { autohide: false, lang: 'en' }; + hljs.addPlugin(new CopyButtonPlugin(config)); + } + } + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initializeHighlightJs); + } else { + // DOM already ready + initializeHighlightJs(); + } +} diff --git a/site/site/_public/js/highlight-utils.js b/site/site/_public/js/highlight-utils.js new file mode 100644 index 0000000..53f5848 --- /dev/null +++ b/site/site/_public/js/highlight-utils.js @@ -0,0 +1,48 @@ +// Additional highlight.js utilities (auto-init is handled by bundle) +window.highlightCode = function() { + // Find all code blocks that haven't been highlighted yet + document.querySelectorAll('pre code:not(.hljs)').forEach(function(block) { + if (typeof hljs !== 'undefined' && hljs.highlightElement) { + hljs.highlightElement(block); + } + }); +}; + +window.highlightContainer = function(containerSelector) { + // Highlight code blocks within a specific container + const container = document.querySelector(containerSelector); + if (container && typeof hljs !== 'undefined') { + container.querySelectorAll('pre code:not(.hljs)').forEach(function(block) { + hljs.highlightElement(block); + }); + } +}; + +// Re-highlight when content changes (for dynamic content) +document.addEventListener('DOMContentLoaded', function() { + const observer = new MutationObserver(function(mutations) { + let shouldHighlight = false; + mutations.forEach(function(mutation) { + if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { + // Check if any added nodes contain code blocks + mutation.addedNodes.forEach(function(node) { + if (node.nodeType === 1 && (node.tagName === 'PRE' || node.querySelector('pre'))) { + shouldHighlight = true; + } + }); + } + }); + + if (shouldHighlight) { + setTimeout(function() { + window.highlightCode(); + }, 100); + } + }); + + // Start observing document changes + observer.observe(document.body, { + childList: true, + subtree: true + }); +}); \ No newline at end of file diff --git a/site/site/_public/js/highlight-utils.min.js b/site/site/_public/js/highlight-utils.min.js new file mode 100644 index 0000000..6420223 --- /dev/null +++ b/site/site/_public/js/highlight-utils.min.js @@ -0,0 +1 @@ +window.highlightCode=function(){document.querySelectorAll('pre code:not(.hljs)').forEach(function(block){if(typeof hljs!=='undefined'&&hljs.highlightElement){hljs.highlightElement(block);}});};window.highlightContainer=function(containerSelector){const container=document.querySelector(containerSelector);if(container&&typeof hljs!=='undefined'){container.querySelectorAll('pre code:not(.hljs)').forEach(function(block){hljs.highlightElement(block);});}};document.addEventListener('DOMContentLoaded',function(){const observer=new MutationObserver(function(mutations){let shouldHighlight=false;mutations.forEach(function(mutation){if(mutation.type==='childList'&&mutation.addedNodes.length>0){mutation.addedNodes.forEach(function(node){if(node.nodeType===1&&(node.tagName==='PRE'||node.querySelector('pre'))){shouldHighlight=true;}});}});if(shouldHighlight){setTimeout(function(){window.highlightCode();},100);}});observer.observe(document.body,{childList: true,subtree: true});}); \ No newline at end of file diff --git a/site/site/_public/js/htmx-reinit.js b/site/site/_public/js/htmx-reinit.js new file mode 100644 index 0000000..87a2524 --- /dev/null +++ b/site/site/_public/js/htmx-reinit.js @@ -0,0 +1,130 @@ +/** + * htmx-reinit.js — re-initialise non-HTMX scripts after every HTMX swap. + * + * In the htmx-ssr rendering profile, scripts like highlight.js, cytoscape, + * theme-init, and tag-filters run once at page load. When HTMX swaps a + * fragment in, those scripts don't see the new DOM and the content goes + * "dead" (unhighlighted code blocks, unwired filter pills, etc.). + * + * This module fixes that by exposing a registry — + * `window.RusteloReinit.register(fn)` — and a single listener on + * `htmx:afterSettle` that walks the registry and calls each handler with + * the swapped element as scope. + * + * Handlers MUST be idempotent. We help by tracking processed nodes in a + * WeakSet keyed by handler id, so double-decoration is impossible even if + * a swap re-emits a region that contains an already-processed node. + * + * Usage from another script: + * + * (function () { + * window.RusteloReinit.register('highlight', function (scope) { + * scope.querySelectorAll('pre code:not(.hljs)').forEach(hljs.highlightElement); + * }); + * })(); + * + * The registered name is used to namespace the processed-nodes WeakSet so + * each handler manages its own idempotency. + */ +(function () { + 'use strict'; + + if (window.RusteloReinit) { + return; // already loaded + } + + /** @type {Array<{ name: string, fn: (scope: Element) => void, seen: WeakSet }>} */ + const handlers = []; + + function findHandler(name) { + for (let i = 0; i < handlers.length; i++) { + if (handlers[i].name === name) { + return handlers[i]; + } + } + return null; + } + + function runHandler(handler, scope) { + if (handler.seen.has(scope)) { + return; + } + handler.seen.add(scope); + try { + handler.fn(scope); + } catch (err) { + console.error('[RusteloReinit:' + handler.name + '] handler failed', err); + } + } + + const api = { + /** + * Register a re-init handler. Calling register twice with the same + * name replaces the previous handler. + */ + register: function (name, fn) { + if (typeof name !== 'string' || typeof fn !== 'function') { + throw new TypeError('RusteloReinit.register(name, fn) — name must be string, fn must be function'); + } + const existing = findHandler(name); + if (existing) { + existing.fn = fn; + // Reset the seen set so the replacement gets a fresh chance at the DOM. + existing.seen = new WeakSet(); + } else { + handlers.push({ name: name, fn: fn, seen: new WeakSet() }); + } + // Run immediately on document.body so the initial page gets processed. + if (document.body) { + runHandler(findHandler(name), document.body); + } else { + document.addEventListener('DOMContentLoaded', function () { + runHandler(findHandler(name), document.body); + }, { once: true }); + } + }, + + /** + * Force-run every handler against the given scope. Used by tests and by + * the htmx:afterSettle listener. + */ + runAll: function (scope) { + for (let i = 0; i < handlers.length; i++) { + runHandler(handlers[i], scope); + } + }, + + /** Return registered handler names (debug). */ + names: function () { + return handlers.map(function (h) { return h.name; }); + } + }; + + window.RusteloReinit = api; + + // Hook into htmx lifecycle events. afterSettle fires after the DOM is in + // its final state for the swap; we want this rather than afterSwap to + // avoid running against elements that get replaced moments later. + document.body && document.body.addEventListener('htmx:afterSettle', function (ev) { + const target = ev.detail && ev.detail.target; + if (target instanceof Element) { + api.runAll(target); + } else { + api.runAll(document.body); + } + }); + + // If body wasn't ready yet, wire on DOMContentLoaded. + if (!document.body) { + document.addEventListener('DOMContentLoaded', function () { + document.body.addEventListener('htmx:afterSettle', function (ev) { + const target = ev.detail && ev.detail.target; + if (target instanceof Element) { + api.runAll(target); + } else { + api.runAll(document.body); + } + }); + }, { once: true }); + } +})(); diff --git a/site/site/_public/js/reinit-handlers.js b/site/site/_public/js/reinit-handlers.js new file mode 100644 index 0000000..3c7ba1f --- /dev/null +++ b/site/site/_public/js/reinit-handlers.js @@ -0,0 +1,114 @@ +/** + * reinit-handlers.js — registers the standard set of HTMX swap re-initialisers. + * + * Loaded AFTER htmx-reinit.js and after the underlying libraries (hljs, + * cytoscape) so the handler bodies can call them directly. Each handler + * scopes its DOM queries to the swap target — passing an Element to + * querySelectorAll only searches that subtree, so handlers are O(swap) + * rather than O(document) on every swap. + */ +(function () { + 'use strict'; + + if (!window.RusteloReinit) { + console.error('[reinit-handlers] htmx-reinit.js must load before this script'); + return; + } + + // --- highlight.js ------------------------------------------------------- + // Highlight every
     in the swapped region that isn't already
    +  // decorated. hljs marks processed blocks by adding the .hljs class, which
    +  // makes the selector self-cleaning on re-runs.
    +  window.RusteloReinit.register('highlight', function (scope) {
    +    if (typeof window.hljs === 'undefined' || !window.hljs.highlightElement) {
    +      return;
    +    }
    +    scope.querySelectorAll('pre code:not(.hljs)').forEach(function (block) {
    +      window.hljs.highlightElement(block);
    +    });
    +  });
    +
    +  // --- theme-init ---------------------------------------------------------
    +  // Normalise the theme state on  from the cookie after every swap.
    +  // This is the single authority that reconciles the theme-toggle's inline
    +  // script (which adds `.dark` but never clears the stale `.light`, and the
    +  // reverse) into a consistent state: mutually-exclusive class AND the
    +  // `data-theme` attribute. The attribute is what DaisyUI keys its base
    +  // tokens on (`:root,[data-theme]{color:hsl(var(--bc))}`); without it the
    +  // light `--bc` never resolves and low-opacity text renders invisible.
    +  // Mirrors theme-init.js so first paint and post-swap agree. Idempotent.
    +  window.RusteloReinit.register('theme', function (_scope) {
    +    const matches = document.cookie.match(/theme=([^;]+)/);
    +    const theme = matches ? matches[1] : 'dark';
    +    const resolved = theme === 'dark' ||
    +      (theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches)
    +        ? 'dark'
    +        : 'light';
    +    const html = document.documentElement;
    +    html.classList.toggle('dark', resolved === 'dark');
    +    html.classList.toggle('light', resolved === 'light');
    +    html.dataset.theme = resolved;
    +  });
    +
    +  // --- tag-filters --------------------------------------------------------
    +  // Re-arm the filter pill click handlers in the swapped region. The
    +  // legacy script set window.tagFiltersInitialized = true and bailed on
    +  // subsequent runs; bypass that flag by re-binding only pills that don't
    +  // yet carry our data-reinit attribute.
    +  window.RusteloReinit.register('tag-filters', function (scope) {
    +    if (typeof window.filterPostsByTag !== 'function') {
    +      return;
    +    }
    +    scope.querySelectorAll('[data-tag-pill]:not([data-tag-reinit])').forEach(function (pill) {
    +      pill.setAttribute('data-tag-reinit', '1');
    +      pill.addEventListener('click', function (ev) {
    +        ev.preventDefault();
    +        const tag = pill.getAttribute('data-tag');
    +        if (tag) {
    +          window.filterPostsByTag(tag);
    +        }
    +      });
    +    });
    +  });
    +
    +  // --- scroll-to-top -------------------------------------------------------
    +  // Wires the footer "Scroll to top" button emitted by UnifiedFooter (SSR).
    +  // The Leptos on:click handler is WASM-only and compiles to nothing in SSR;
    +  // this provides the equivalent for the HTMX profile via event delegation.
    +  // Runs once: the button lives in the footer which is never swapped.
    +  (function () {
    +    if (window.__rusteloScrollToTopWired) {
    +      return;
    +    }
    +    window.__rusteloScrollToTopWired = true;
    +    document.addEventListener('click', function (ev) {
    +      var btn = ev.target && ev.target.closest('button[aria-label="Scroll to top"]');
    +      if (btn) {
    +        ev.preventDefault();
    +        window.scrollTo({ top: 0, behavior: 'smooth' });
    +      }
    +    });
    +  })();
    +
    +  // --- content-graph ------------------------------------------------------
    +  // ContentGraph.render is idempotent against the same container id (it
    +  // tears down the previous instance internally). Only fire when the
    +  // swap actually contains a graph container.
    +  window.RusteloReinit.register('content-graph', function (scope) {
    +    if (!window.ContentGraph || typeof window.ContentGraph.render !== 'function') {
    +      return;
    +    }
    +    scope.querySelectorAll('[data-content-graph]').forEach(function (el) {
    +      const containerId = el.getAttribute('id');
    +      const focusId = el.getAttribute('data-focus-id') || null;
    +      const theme = document.documentElement.classList.contains('dark') ? 'dark' : 'light';
    +      if (containerId) {
    +        try {
    +          window.ContentGraph.render(containerId, focusId, theme);
    +        } catch (err) {
    +          console.error('[reinit:content-graph] render failed', err);
    +        }
    +      }
    +    });
    +  });
    +})();
    diff --git a/site/site/_public/js/tag-filters.js b/site/site/_public/js/tag-filters.js
    new file mode 100644
    index 0000000..fb9083d
    --- /dev/null
    +++ b/site/site/_public/js/tag-filters.js
    @@ -0,0 +1,181 @@
    +/**
    + * Tag Filters - Client-side tag filtering for blog posts
    + * Filters posts within the current page without navigation
    + */
    +
    +(function() {
    +    'use strict';
    +
    +    // Prevent double initialization
    +    if (window.tagFiltersInitialized) {
    +        return;
    +    }
    +
    +    let activeTagFilters = new Set();
    +
    +    // Global function to filter posts by tag (supports multi-selection)
    +    window.filterPostsByTag = function(tagName) {
    +        console.log(`🏷️ Filtering posts by tag: ${tagName}`);
    +        
    +        if (activeTagFilters.has(tagName)) {
    +            // If tag is already selected, remove it
    +            activeTagFilters.delete(tagName);
    +            console.log(`🏷️ Removed tag "${tagName}" from selection`);
    +        } else {
    +            // Add tag to selection
    +            activeTagFilters.add(tagName);
    +            console.log(`🏷️ Added tag "${tagName}" to selection`);
    +        }
    +        
    +        if (activeTagFilters.size === 0) {
    +            showAllPosts();
    +            updateTagButtonStates(activeTagFilters);
    +        } else {
    +            filterPostsByTags(activeTagFilters);
    +            updateTagButtonStates(activeTagFilters);
    +        }
    +    };
    +
    +    // Global function to clear all tag filters
    +    window.clearAllTagFilters = function() {
    +        console.log('🏷️ Clearing all tag filters');
    +        activeTagFilters.clear();
    +        showAllPosts();
    +        updateTagButtonStates(activeTagFilters);
    +        updateShowAllButton(true);
    +    };
    +
    +    function filterPostsByTags(selectedTags) {
    +        const postCards = document.querySelectorAll('article');
    +        let visibleCount = 0;
    +        const tagArray = Array.from(selectedTags);
    +
    +        postCards.forEach(card => {
    +            const tagElements = card.querySelectorAll('.ds-badge-ghost');
    +            const postTags = Array.from(tagElements).map(el => el.textContent.trim().toLowerCase());
    +            
    +            // Check if post has ALL selected tags (AND logic)
    +            // Change to hasAnyTag for OR logic if preferred
    +            const hasAllTags = tagArray.every(selectedTag => 
    +                postTags.includes(selectedTag.toLowerCase())
    +            );
    +
    +            if (hasAllTags) {
    +                card.style.display = '';
    +                card.classList.remove('filter-hidden');
    +                visibleCount++;
    +            } else {
    +                card.style.display = 'none';
    +                card.classList.add('filter-hidden');
    +            }
    +        });
    +
    +        const tagList = tagArray.join('", "');
    +        console.log(`🏷️ Multi-tag filter result: ${visibleCount} posts visible with tags ["${tagList}"]`);
    +        
    +        // Show a message if no posts found
    +        const filterText = tagArray.length === 1 
    +            ? `tagged with "${tagArray[0]}"` 
    +            : `tagged with all of: "${tagList}"`;
    +        showFilterMessage(visibleCount, filterText);
    +        updateShowAllButton(false);
    +    }
    +
    +    function showAllPosts() {
    +        const postCards = document.querySelectorAll('article');
    +        postCards.forEach(card => {
    +            card.style.display = '';
    +            card.classList.remove('filter-hidden');
    +        });
    +        
    +        hideFilterMessage();
    +        updateShowAllButton(true);
    +        console.log('🏷️ Tag filter cleared: all posts visible');
    +    }
    +
    +    function updateTagButtonStates(activeTagFilters) {
    +        const tagButtons = document.querySelectorAll('button[data-filter-type="tag"]');
    +        
    +        tagButtons.forEach(button => {
    +            const tagValue = button.getAttribute('data-filter-value');
    +            
    +            if (activeTagFilters.has(tagValue)) {
    +                // Active tag button styling
    +                button.classList.remove('ds-btn-outline');
    +                button.classList.add('ds-btn-primary');
    +                button.classList.add('tag-filter-active');
    +            } else {
    +                // Inactive tag button styling
    +                button.classList.add('ds-btn-outline');
    +                button.classList.remove('ds-btn-primary');
    +                button.classList.remove('tag-filter-active');
    +            }
    +        });
    +    }
    +
    +    function updateShowAllButton(isActive) {
    +        const showAllButton = document.querySelector('button[data-filter-type="show-all"]');
    +        if (showAllButton) {
    +            if (isActive) {
    +                // Active "Show All" button styling
    +                showAllButton.classList.remove('ds-btn-outline');
    +                showAllButton.classList.add('ds-btn-primary');
    +                showAllButton.classList.add('show-all-active');
    +            } else {
    +                // Inactive "Show All" button styling
    +                showAllButton.classList.add('ds-btn-outline');
    +                showAllButton.classList.remove('ds-btn-primary');
    +                showAllButton.classList.remove('show-all-active');
    +            }
    +        }
    +    }
    +
    +    function showFilterMessage(count, filterText) {
    +        // Remove any existing message
    +        hideFilterMessage();
    +        
    +        if (count === 0) {
    +            const message = document.createElement('div');
    +            message.className = 'tag-filter-message';
    +            message.innerHTML = `
    +                
    +

    No posts found ${filterText}.

    + +
    + `; + + // Insert after the filter container + const filterContainer = document.querySelector('.filter-container'); + if (filterContainer) { + filterContainer.parentNode.insertBefore(message, filterContainer.nextSibling); + } + } + } + + function hideFilterMessage() { + const existing = document.querySelector('.tag-filter-message'); + if (existing) { + existing.remove(); + } + } + + // Initialize tag filtering when DOM is ready + function initializeTagFilters() { + console.log('🏷️ Tag filters initialized'); + } + + // Initialize when DOM is ready + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initializeTagFilters); + } else { + initializeTagFilters(); + } + + // Also try after hydration + setTimeout(initializeTagFilters, 500); + + window.tagFiltersInitialized = true; + +})(); \ No newline at end of file diff --git a/site/site/_public/js/tagline-rotator.js b/site/site/_public/js/tagline-rotator.js new file mode 100644 index 0000000..55b2f57 --- /dev/null +++ b/site/site/_public/js/tagline-rotator.js @@ -0,0 +1,246 @@ +/* tagline-rotator.js — rotate ontoref definition-phrases by audience + focus. + * + * Source of truth: /r/taglines.json, projected by gen-taglines.nu from + * .ontoref/positioning/taglines.ncl (drift-checked at export). This file never + * carries phrases — only the rotation mechanism. + * + * Mount: + * + * Sure-footed, and light on your feet. + * + * + * Effect: drives typed.js (typewriter) when window.Typed is present; native fade + * otherwise; static first-phrase under prefers-reduced-motion. Hybrid driver: the + * home mount starts on data-audience (default "all"); any element carrying + * [data-tagline-audience=""] switches the (unlocked) rotators to that queue on + * click — reach-vs-qualification: a routing gancho, the "all" queue is curated, not + * the union. A mount with [data-lock-audience] ignores door clicks (door/about pages). + * Language: data-lang fixed → → localStorage 'ontoref-lang' → 'en'; + * follows .lang-btn / [data-lang-switch] clicks and cross-tab storage changes. + */ +(function () { + "use strict"; + + var REDUCED = + window.matchMedia && + window.matchMedia("(prefers-reduced-motion: reduce)").matches; + + function detectLang(el) { + var fixed = el.getAttribute("data-lang"); + if (fixed === "es" || fixed === "en") return fixed; + var h = (document.documentElement.lang || "").slice(0, 2).toLowerCase(); + if (h === "es" || h === "en") return h; + try { + var s = localStorage.getItem("ontoref-lang"); + if (s === "es" || s === "en") return s; + } catch (e) {} + return "en"; + } + + function focusLabel(focus, labels, lang) { + if (!focus) return ""; + var m = labels && labels[focus]; + if (m && m[lang]) return m[lang]; + return focus.replace(/^diff-/, "").replace(/-/g, " "); // slug fallback + } + + function Rotator(el, data) { + this.el = el; + this.data = (data && data.by_audience) || {}; + this.byTier = (data && data.by_tier) || {}; + this.isTier = el.hasAttribute("data-tier-rotator"); + this.focusLabels = (data && data.focus_labels) || {}; + this.audience = el.getAttribute("data-audience") || "all"; + this.lang = detectLang(el); + var sel = el.getAttribute("data-focus-target"); + this.focusEl = sel ? document.querySelector(sel) : null; + this.typed = null; + this.timer = null; + this.idx = 0; + this.queue = []; + this.rebuild(); + } + + Rotator.prototype.phrases = function () { + // Tier rotator (data-tier-rotator): a parallel axis — cycle the per-tier lines + // (0 → 1 → 2) from by_tier, ignoring the audience queue. Door clicks never + // switch it (mount carries data-lock-audience). + if (this.isTier) { + var t = this.byTier; + return [].concat(t["0"] || [], t["1"] || [], t["2"] || []); + } + var q = this.data[this.audience]; + if (!q || !q.length) q = this.data.all || []; + return q; + }; + + Rotator.prototype.text = function (i) { + if (!this.queue.length) return ""; + var p = this.queue[i % this.queue.length]; + return p ? p[this.lang] || p.en || "" : ""; + }; + + Rotator.prototype.renderFocus = function (i) { + if (!this.focusEl || !this.queue.length) return; + var p = this.queue[i % this.queue.length]; + this.focusEl.textContent = p ? focusLabel(p.focus, this.focusLabels, this.lang) : ""; + }; + + Rotator.prototype.render = function (i) { + this.el.textContent = this.text(i); + this.renderFocus(i); + }; + + Rotator.prototype.rebuild = function () { + this.stop(); + this.queue = this.phrases(); + this.idx = 0; + if (!this.queue.length) { + this.el.textContent = ""; + return; + } + if (REDUCED) { + this.render(0); // honour reduced-motion: show one phrase, never animate + return; + } + if (window.Typed) this.startTyped(); + else this.startFade(); + }; + + Rotator.prototype.startTyped = function () { + var self = this; + var strings = this.queue.map(function (_, i) { + // escape typed.js control chars: ^ (pause), ` (html), ~ (smart-backspace) + return self.text(i).replace(/([\^`~])/g, "\\$1"); + }); + this.renderFocus(0); + this.typed = new window.Typed(this.el, { + strings: strings, + typeSpeed: 38, + backSpeed: 16, + backDelay: 2400, + startDelay: 250, + smartBackspace: true, + loop: true, + cursorChar: "▌", + preStringTyped: function (i) { + self.renderFocus(i); + }, + }); + }; + + Rotator.prototype.startFade = function () { + var self = this; + this.render(0); + this.el.classList.add("tagline-rot", "is-in"); + this.timer = setInterval(function () { + self.el.classList.remove("is-in"); + setTimeout(function () { + self.idx = (self.idx + 1) % self.queue.length; + self.render(self.idx); + self.el.classList.add("is-in"); + }, 350); + }, 3800); + }; + + Rotator.prototype.stop = function () { + if (this.typed) { + this.typed.destroy(); + this.typed = null; + } + if (this.timer) { + clearInterval(this.timer); + this.timer = null; + } + }; + + Rotator.prototype.setAudience = function (key) { + if (!key || key === this.audience) return; + this.audience = key; + this.rebuild(); + }; + + Rotator.prototype.setLang = function (lang) { + if ((lang !== "es" && lang !== "en") || lang === this.lang) return; + this.lang = lang; + this.rebuild(); + }; + + function init() { + var mounts = [].slice.call( + document.querySelectorAll("[data-tagline-rotator]"), + ); + if (!mounts.length) return; + var src = mounts[0].getAttribute("data-src") || "/r/taglines.json"; + + fetch(src, { credentials: "same-origin" }) + .then(function (r) { + if (!r.ok) throw new Error("taglines " + r.status); + return r.json(); + }) + .then(function (data) { + var rotators = mounts.map(function (el) { + return new Rotator(el, data); + }); + window.__taglineRotators = rotators; + + // Hybrid driver — door affordances switch the unlocked rotators' audience. + document.addEventListener("click", function (ev) { + var t = + ev.target.closest && ev.target.closest("[data-tagline-audience]"); + if (!t) return; + var key = t.getAttribute("data-tagline-audience"); + var group = t.parentNode; + if (group) + [].forEach.call( + group.querySelectorAll("[data-tagline-audience]"), + function (b) { + b.classList.toggle("is-active", b === t); + }, + ); + // reveal the matching door panel; data-door="all" hides every panel + var door = t.getAttribute("data-door"); + if (door !== null) + [].forEach.call( + document.querySelectorAll("[data-tagline-panel]"), + function (p) { + var match = p.getAttribute("data-tagline-panel") === door; + p.classList.toggle("is-open", match); // class wins over any [hidden] override + p.hidden = !match; + }, + ); + rotators.forEach(function (rt) { + if (!rt.el.hasAttribute("data-lock-audience")) rt.setAudience(key); + }); + }); + + // Language — follow the web lang toggle and cross-tab changes. + document.addEventListener("click", function (ev) { + var b = + ev.target.closest && + ev.target.closest(".lang-btn, [data-lang-switch]"); + if (!b) return; + var lang = + b.getAttribute("data-lang") || b.getAttribute("data-lang-switch"); + rotators.forEach(function (rt) { + rt.setLang(lang); + }); + }); + window.addEventListener("storage", function (ev) { + if (ev.key === "ontoref-lang" && ev.newValue) + rotators.forEach(function (rt) { + rt.setLang(ev.newValue); + }); + }); + }) + .catch(function (e) { + // leave the static fallback text in each mount untouched + if (window.console) console.warn("[tagline-rotator]", e.message); + }); + } + + if (document.readyState === "loading") + document.addEventListener("DOMContentLoaded", init); + else init(); +})(); diff --git a/site/site/_public/js/theme-init.js b/site/site/_public/js/theme-init.js new file mode 100644 index 0000000..98ee972 --- /dev/null +++ b/site/site/_public/js/theme-init.js @@ -0,0 +1,21 @@ +// Theme initialization script to prevent flash +(function() { + const getThemeFromCookie = () => { + const matches = document.cookie.match(/theme=([^;]+)/); + return matches ? matches[1] : null; + }; + const pref = getThemeFromCookie() || 'dark'; + const resolved = + pref === 'dark' || (pref === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches) + ? 'dark' + : 'light'; + const html = document.documentElement; + // Class drives Tailwind `.dark` utilities and the custom `.dark {}` token blocks. + html.classList.toggle('dark', resolved === 'dark'); + html.classList.toggle('light', resolved === 'light'); + // Attribute drives DaisyUI base tokens — `:root,[data-theme]{color:hsl(var(--bc))}` + // only resolves the light `--bc` under `[data-theme=light]`. Without this the first + // paint falls back to the dark-default `--bc` (light grey) on a light background, + // making low-opacity text (tag chips) invisible until a theme toggle sets it. + html.dataset.theme = resolved; +})(); diff --git a/site/site/_public/js/theme-init.min.js b/site/site/_public/js/theme-init.min.js new file mode 100644 index 0000000..f021197 --- /dev/null +++ b/site/site/_public/js/theme-init.min.js @@ -0,0 +1 @@ +(function(){const getThemeFromCookie=()=>{const matches=document.cookie.match(/theme=([^;]+)/);return matches?matches[1]:null;};const pref=getThemeFromCookie()||'dark';const resolved=pref==='dark'||(pref==='system'&&window.matchMedia('(prefers-color-scheme:dark)').matches)?'dark':'light';const html=document.documentElement;html.classList.toggle('dark',resolved==='dark');html.classList.toggle('light',resolved==='light');html.dataset.theme=resolved;})(); \ No newline at end of file diff --git a/site/site/_public/js/typed.umd.js b/site/site/_public/js/typed.umd.js new file mode 100644 index 0000000..0a4e41d --- /dev/null +++ b/site/site/_public/js/typed.umd.js @@ -0,0 +1,3 @@ +!function(t,s){"object"==typeof exports&&"undefined"!=typeof module?module.exports=s():"function"==typeof define&&define.amd?define(s):(t||self).Typed=s()}(this,function(){function t(){return t=Object.assign?Object.assign.bind():function(t){for(var s=1;s0&&(e.strPos=e.currentElContent.length-1,e.strings.unshift(e.currentElContent)),e.sequence=[],e.strings)e.sequence[u]=u;e.arrayPos=0,e.stopNum=0,e.loop=e.options.loop,e.loopCount=e.options.loopCount,e.curLoop=0,e.shuffle=e.options.shuffle,e.pause={status:!1,typewrite:!0,curString:"",curStrPos:0},e.typingComplete=!1,e.autoInsertCss=e.options.autoInsertCss,e.autoInsertCss&&(this.appendCursorAnimationCss(e),this.appendFadeOutAnimationCss(e))},n.getCurrentElContent=function(t){return t.attr?t.el.getAttribute(t.attr):t.isInput?t.el.value:"html"===t.contentType?t.el.innerHTML:t.el.textContent},n.appendCursorAnimationCss=function(t){var s="data-typed-js-cursor-css";if(t.showCursor&&!document.querySelector("["+s+"]")){var e=document.createElement("style");e.setAttribute(s,"true"),e.innerHTML="\n .typed-cursor{\n opacity: 1;\n }\n .typed-cursor.typed-cursor--blink{\n animation: typedjsBlink 0.7s infinite;\n -webkit-animation: typedjsBlink 0.7s infinite;\n animation: typedjsBlink 0.7s infinite;\n }\n @keyframes typedjsBlink{\n 50% { opacity: 0.0; }\n }\n @-webkit-keyframes typedjsBlink{\n 0% { opacity: 1; }\n 50% { opacity: 0.0; }\n 100% { opacity: 1; }\n }\n ",document.body.appendChild(e)}},n.appendFadeOutAnimationCss=function(t){var s="data-typed-fadeout-js-css";if(t.fadeOut&&!document.querySelector("["+s+"]")){var e=document.createElement("style");e.setAttribute(s,"true"),e.innerHTML="\n .typed-fade-out{\n opacity: 0;\n transition: opacity .25s;\n }\n .typed-cursor.typed-cursor--blink.typed-fade-out{\n -webkit-animation: 0;\n animation: 0;\n }\n ",document.body.appendChild(e)}},e}()),n=new(/*#__PURE__*/function(){function t(){}var s=t.prototype;return s.typeHtmlChars=function(t,s,e){if("html"!==e.contentType)return s;var n=t.substring(s).charAt(0);if("<"===n||"&"===n){var i;for(i="<"===n?">":";";t.substring(s+1).charAt(0)!==i&&!(1+ ++s>t.length););s++}return s},s.backSpaceHtmlChars=function(t,s,e){if("html"!==e.contentType)return s;var n=t.substring(s).charAt(0);if(">"===n||";"===n){var i;for(i=">"===n?"<":"&";t.substring(s-1).charAt(0)!==i&&!(--s<0););s--}return s},t}());/*#__PURE__*/ +return function(){function t(t,s){e.load(this,s,t),this.begin()}var s=t.prototype;return s.toggle=function(){this.pause.status?this.start():this.stop()},s.stop=function(){this.typingComplete||this.pause.status||(this.toggleBlinking(!0),this.pause.status=!0,this.options.onStop(this.arrayPos,this))},s.start=function(){this.typingComplete||this.pause.status&&(this.pause.status=!1,this.pause.typewrite?this.typewrite(this.pause.curString,this.pause.curStrPos):this.backspace(this.pause.curString,this.pause.curStrPos),this.options.onStart(this.arrayPos,this))},s.destroy=function(){this.reset(!1),this.options.onDestroy(this)},s.reset=function(t){void 0===t&&(t=!0),clearInterval(this.timeout),this.replaceText(""),this.cursor&&this.cursor.parentNode&&(this.cursor.parentNode.removeChild(this.cursor),this.cursor=null),this.strPos=0,this.arrayPos=0,this.curLoop=0,t&&(this.insertCursor(),this.options.onReset(this),this.begin())},s.begin=function(){var t=this;this.options.onBegin(this),this.typingComplete=!1,this.shuffleStringsIfNeeded(this),this.insertCursor(),this.bindInputFocusEvents&&this.bindFocusEvents(),this.timeout=setTimeout(function(){0===t.strPos?t.typewrite(t.strings[t.sequence[t.arrayPos]],t.strPos):t.backspace(t.strings[t.sequence[t.arrayPos]],t.strPos)},this.startDelay)},s.typewrite=function(t,s){var e=this;this.fadeOut&&this.el.classList.contains(this.fadeOutClass)&&(this.el.classList.remove(this.fadeOutClass),this.cursor&&this.cursor.classList.remove(this.fadeOutClass));var i=this.humanizer(this.typeSpeed),r=1;!0!==this.pause.status?this.timeout=setTimeout(function(){s=n.typeHtmlChars(t,s,e);var i=0,o=t.substring(s);if("^"===o.charAt(0)&&/^\^\d+/.test(o)){var a=1;a+=(o=/\d+/.exec(o)[0]).length,i=parseInt(o),e.temporaryPause=!0,e.options.onTypingPaused(e.arrayPos,e),t=t.substring(0,s)+t.substring(s+a),e.toggleBlinking(!0)}if("`"===o.charAt(0)){for(;"`"!==t.substring(s+r).charAt(0)&&(r++,!(s+r>t.length)););var u=t.substring(0,s),p=t.substring(u.length+1,s+r),c=t.substring(s+r+1);t=u+p+c,r--}e.timeout=setTimeout(function(){e.toggleBlinking(!1),s>=t.length?e.doneTyping(t,s):e.keepTyping(t,s,r),e.temporaryPause&&(e.temporaryPause=!1,e.options.onTypingResumed(e.arrayPos,e))},i)},i):this.setPauseStatus(t,s,!0)},s.keepTyping=function(t,s,e){0===s&&(this.toggleBlinking(!1),this.options.preStringTyped(this.arrayPos,this));var n=t.substring(0,s+=e);this.replaceText(n),this.typewrite(t,s)},s.doneTyping=function(t,s){var e=this;this.options.onStringTyped(this.arrayPos,this),this.toggleBlinking(!0),this.arrayPos===this.strings.length-1&&(this.complete(),!1===this.loop||this.curLoop===this.loopCount)||(this.timeout=setTimeout(function(){e.backspace(t,s)},this.backDelay))},s.backspace=function(t,s){var e=this;if(!0!==this.pause.status){if(this.fadeOut)return this.initFadeOut();this.toggleBlinking(!1);var i=this.humanizer(this.backSpeed);this.timeout=setTimeout(function(){s=n.backSpaceHtmlChars(t,s,e);var i=t.substring(0,s);if(e.replaceText(i),e.smartBackspace){var r=e.strings[e.arrayPos+1];e.stopNum=r&&i===r.substring(0,s)?s:0}s>e.stopNum?(s--,e.backspace(t,s)):s<=e.stopNum&&(e.arrayPos++,e.arrayPos===e.strings.length?(e.arrayPos=0,e.options.onLastStringBackspaced(),e.shuffleStringsIfNeeded(),e.begin()):e.typewrite(e.strings[e.sequence[e.arrayPos]],s))},i)}else this.setPauseStatus(t,s,!1)},s.complete=function(){this.options.onComplete(this),this.loop?this.curLoop++:this.typingComplete=!0},s.setPauseStatus=function(t,s,e){this.pause.typewrite=e,this.pause.curString=t,this.pause.curStrPos=s},s.toggleBlinking=function(t){this.cursor&&(this.pause.status||this.cursorBlinking!==t&&(this.cursorBlinking=t,t?this.cursor.classList.add("typed-cursor--blink"):this.cursor.classList.remove("typed-cursor--blink")))},s.humanizer=function(t){return Math.round(Math.random()*t/2)+t},s.shuffleStringsIfNeeded=function(){this.shuffle&&(this.sequence=this.sequence.sort(function(){return Math.random()-.5}))},s.initFadeOut=function(){var t=this;return this.el.className+=" "+this.fadeOutClass,this.cursor&&(this.cursor.className+=" "+this.fadeOutClass),setTimeout(function(){t.arrayPos++,t.replaceText(""),t.strings.length>t.arrayPos?t.typewrite(t.strings[t.sequence[t.arrayPos]],0):(t.typewrite(t.strings[0],0),t.arrayPos=0)},this.fadeOutDelay)},s.replaceText=function(t){this.attr?this.el.setAttribute(this.attr,t):this.isInput?this.el.value=t:"html"===this.contentType?this.el.innerHTML=t:this.el.textContent=t},s.bindFocusEvents=function(){var t=this;this.isInput&&(this.el.addEventListener("focus",function(s){t.stop()}),this.el.addEventListener("blur",function(s){t.el.value&&0!==t.el.value.length||t.start()}))},s.insertCursor=function(){this.showCursor&&(this.cursor||(this.cursor=document.createElement("span"),this.cursor.className="typed-cursor",this.cursor.setAttribute("aria-hidden",!0),this.cursor.innerHTML=this.cursorChar,this.el.parentNode&&this.el.parentNode.insertBefore(this.cursor,this.el.nextSibling)))},t}()}); +//# sourceMappingURL=typed.umd.js.map diff --git a/site/site/_public/kitdigital.html b/site/site/_public/kitdigital.html new file mode 100644 index 0000000..7e20947 --- /dev/null +++ b/site/site/_public/kitdigital.html @@ -0,0 +1,325 @@ + + + + + + Agente Digital - Kit Digital España + + + +
    +
    +
    +
    + + Kit Digital España + +
    +
    + + + +
    +
    + +
    +
    + +
    +
    +
    +

    Agente Digital Autorizado

    +

    Soluciones de Digitalización Kit Digital

    +

    + Ayudamos a pequeñas empresas, microempresas y autónomos + a digitalizar sus procesos mediante las ayudas del + programa + Kit Digital, financiado por los fondos + Next Generation EU. +

    + +
    +
    + +
    +
    +

    Soluciones de Digitalización

    +
    +
    +

    Sitio Web y Presencia en Internet

    +

    + Desarrollo de páginas web profesionales y + optimización de presencia digital. +

    +
    +
    +

    Gestión de Clientes (CRM)

    +

    + Sistemas para organizar y gestionar la relación + con tus clientes. +

    +
    +
    +

    Business Intelligence y Analítica: IA

    +

    + Herramientas de análisis de datos para tomar + mejores decisiones empresariales. +

    +
    +
    +

    Gestión de Procesos

    +

    + Automatización y digitalización de procesos + internos de la empresa. +

    +
    +
    +

    Facturación Electrónica

    +

    + Sistemas de facturación digital y gestión + documental electrónica. +

    +
    +
    +

    Comunicaciones

    +

    + Soluciones de comunicación empresarial y + colaboración en la nube. +

    +
    +
    +

    Ciberseguridad

    +

    + Protección y seguridad de sistemas informáticos + y datos empresariales. +

    +
    +
    +
    +
    + +
    +
    +

    Segmentos de Beneficiarios

    +
    +
    +

    Segmento I

    +

    + Empresas de 10 a 49 empleados +

    +

    Ayuda de hasta 12.000€

    +
      +
    • Sitio Web y Presencia en Internet
    • +
    • Gestión de Clientes
    • +
    • Business Intelligence y Analítica
    • +
    • Gestión de Procesos
    • +
    • Facturación Electrónica
    • +
    • Comunicaciones
    • +
    • Ciberseguridad
    • +
    +
    +
    +

    Segmento II

    +

    Empresas de 3 a 9 empleados

    +

    Ayuda de hasta 6.000€

    +
      +
    • Sitio Web y Presencia en Internet
    • +
    • Gestión de Clientes
    • +
    • Facturación Electrónica
    • +
    • Comunicaciones
    • +
    • Ciberseguridad
    • +
    +
    +
    +

    Segmento III

    +

    + Empresas de 0 a 2 empleados y + autónomos +

    +

    Ayuda de hasta 2.000€

    +
      +
    • Sitio Web y Presencia en Internet
    • +
    • Gestión de Clientes
    • +
    • Facturación Electrónica
    • +
    +
    +
    +
    +
    + +
    +
    +

    Sectores

    +
    +
    Comercio al por menor
    +
    Hostelería y turismo
    +
    Servicios profesionales
    +
    Salud y bienestar
    +
    Educación y formación
    +
    Construcción
    +
    Transporte y logística
    +
    + Agricultura y alimentación +
    +
    Industria manufacturera
    +
    Servicios financieros
    +
    +
    +
    + +
    +
    +

    Contacto

    +
    +
    +

    Solicitar Información

    +

    + Complete nuestro formulario para recibir + información personalizada sobre las soluciones + de digitalización disponibles para su empresa + mediante el programa + Kit Digital. +

    + Ir al formulario +
    +
    +

    Proceso de Solicitud

    +

    + Te asesoramos gratuitamente en todo el proceso + de solicitud y implementación de las soluciones + digitales más adecuadas para tu negocio. +

    +

    + Para consultas urgentes: + + contacto directo + +

    +
    +
    +
    +
    +
    + + + + + + diff --git a/site/site/_public/logs.html b/site/site/_public/logs.html new file mode 100644 index 0000000..49b171b --- /dev/null +++ b/site/site/_public/logs.html @@ -0,0 +1,329 @@ + + + + + + Browser Console Logs Viewer + + + +
    +
    +

    🔍 Browser Console Logs

    +

    Real-time capture of browser console output

    +
    + +
    +
    Total Logs: 0
    +
    Errors: 0
    +
    Warnings: 0
    +
    Last Updated: Never
    +
    + +
    + + + +
    + + + + +
    +
    + +
    +
    +

    Waiting for console output...

    +

    To capture logs, visit another page on this site and use console.log(), console.error(), etc.

    + +
    +
    + + +
    + + + + diff --git a/site/site/_public/robots.txt b/site/site/_public/robots.txt new file mode 100644 index 0000000..25a33bd --- /dev/null +++ b/site/site/_public/robots.txt @@ -0,0 +1,14 @@ +User-agent: * +Allow: / + +# Sitemap location +Sitemap: {{base_url}}/sitemap.xml + +# Crawl-delay (optional, in seconds) +# Crawl-delay: 1 + +# Disallow specific paths (customize as needed) +Disallow: /admin +Disallow: /.rustelo/ +Disallow: /target/ +Disallow: /node_modules/ \ No newline at end of file diff --git a/site/site/_public/styles/app.min.css b/site/site/_public/styles/app.min.css new file mode 100644 index 0000000..f76d59a --- /dev/null +++ b/site/site/_public/styles/app.min.css @@ -0,0 +1 @@ +*,::before,::after{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-pan-x:;--un-pan-y:;--un-pinch-zoom:;--un-scroll-snap-strictness:proximity;--un-ordinal:;--un-slashed-zero:;--un-numeric-figure:;--un-numeric-spacing:;--un-numeric-fraction:;--un-border-spacing-x:0;--un-border-spacing-y:0;--un-ring-offset-shadow:0 0 rgb(0 0 0 / 0);--un-ring-shadow:0 0 rgb(0 0 0 / 0);--un-shadow-inset:;--un-shadow:0 0 rgb(0 0 0 / 0);--un-ring-inset:;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgb(147 197 253 / 0.5);--un-blur:;--un-brightness:;--un-contrast:;--un-drop-shadow:;--un-grayscale:;--un-hue-rotate:;--un-invert:;--un-saturate:;--un-sepia:;--un-backdrop-blur:;--un-backdrop-brightness:;--un-backdrop-contrast:;--un-backdrop-grayscale:;--un-backdrop-hue-rotate:;--un-backdrop-invert:;--un-backdrop-opacity:;--un-backdrop-saturate:;--un-backdrop-sepia:}::backdrop{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-pan-x:;--un-pan-y:;--un-pinch-zoom:;--un-scroll-snap-strictness:proximity;--un-ordinal:;--un-slashed-zero:;--un-numeric-figure:;--un-numeric-spacing:;--un-numeric-fraction:;--un-border-spacing-x:0;--un-border-spacing-y:0;--un-ring-offset-shadow:0 0 rgb(0 0 0 / 0);--un-ring-shadow:0 0 rgb(0 0 0 / 0);--un-shadow-inset:;--un-shadow:0 0 rgb(0 0 0 / 0);--un-ring-inset:;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgb(147 197 253 / 0.5);--un-blur:;--un-brightness:;--un-contrast:;--un-drop-shadow:;--un-grayscale:;--un-hue-rotate:;--un-invert:;--un-saturate:;--un-sepia:;--un-backdrop-blur:;--un-backdrop-brightness:;--un-backdrop-contrast:;--un-backdrop-grayscale:;--un-backdrop-hue-rotate:;--un-backdrop-invert:;--un-backdrop-opacity:;--un-backdrop-saturate:;--un-backdrop-sepia:}body{font-family:"Segoe UI",Tahoma,Geneva,Verdana,sans-serif}:root{--code-bg:#f8fafc;--code-border:#e2e8f0;--code-text:#334155;--code-comment:#64748b;--code-keyword:#0f172a;--code-string:#059669;--code-number:#dc2626;--code-function:#2563eb;--code-variable:#7c3aed;--inline-code-bg:#f1f5f9;--inline-code-text:#475569}.dark{--code-bg:#1e293b;--code-border:#334155;--code-text:#e2e8f0;--code-comment:#94a3b8;--code-keyword:#f8fafc;--code-string:#34d399;--code-number:#fca5a5;--code-function:#60a5fa;--code-variable:#a78bfa;--inline-code-bg:#374151;--inline-code-text:#d1d5db}pre{background-color:var(--code-bg) !important;border:1px solid var(--code-border);border-radius:var(--radius-md,0.375rem);padding:1.25rem 1.5rem;margin:1.5rem 0;overflow-x:auto;line-height:1.6;font-family:ui-monospace,SFMono-Regular,'SF Mono',Consolas,'Liberation Mono',Menlo,monospace;font-size:0.875rem;position:relative}pre code{background-color:transparent !important;padding:0 !important;border-radius:0;color:var(--code-text);font-size:inherit;font-weight:400}code:not(pre code){background-color:var(--inline-code-bg) !important;color:var(--inline-code-text) !important;padding:0.125rem 0.375rem;border-radius:var(--radius-sm,0.125rem);font-size:0.875em;font-weight:500;font-family:ui-monospace,SFMono-Regular,'SF Mono',Consolas,'Liberation Mono',Menlo,monospace;border:1px solid var(--code-border)}:is([prose=""],.prose):where(:not(pre)>code):not(:where(.not-prose,.not-prose *))::before,:is([prose=""],.prose):where(:not(pre)>code):not(:where(.not-prose,.not-prose *))::after{content:"" !important}.hljs{background:var(--code-bg) !important;color:var(--code-text) !important}pre::-webkit-scrollbar{height:8px}pre::-webkit-scrollbar-track{background:transparent}pre::-webkit-scrollbar-thumb{background-color:var(--code-border);border-radius:4px}pre::-webkit-scrollbar-thumb:hover{background-color:var(--code-comment)}.dark pre::-webkit-scrollbar-thumb{background-color:var(--color-neutral-600)}.dark pre::-webkit-scrollbar-thumb:hover{background-color:var(--color-neutral-500)}.prose pre,[prose=""] pre{margin:2rem 0 !important;line-height:1.7}.prose code,[prose=""] code{font-size:0.875em}@media (max-width:768px){pre{padding:1rem;margin:1rem -1rem;border-radius:0;border-left:none;border-right:none}}pre:focus-within{outline:2px solid var(--color-brand-primary);outline-offset:2px}code:focus{outline:2px solid var(--color-brand-primary);outline-offset:1px}pre code.hljs{background:transparent !important}.filter-selected{background-color:#2563eb !important;border-color:#2563eb !important;color:#ffffff !important}.filter-hidden{display:none !important}.i-carbon-moon{--un-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 32 32' width='1.2em' height='1.2em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M13.503 5.414a15.076 15.076 0 0 0 11.593 18.194a11.1 11.1 0 0 1-7.975 3.39c-.138 0-.278.005-.418 0a11.094 11.094 0 0 1-3.2-21.584M14.98 3a1 1 0 0 0-.175.016a13.096 13.096 0 0 0 1.825 25.981c.164.006.328 0 .49 0a13.07 13.07 0 0 0 10.703-5.555a1.01 1.01 0 0 0-.783-1.565A13.08 13.08 0 0 1 15.89 4.38A1.015 1.015 0 0 0 14.98 3'/%3E%3C/svg%3E");-webkit-mask:var(--un-icon) no-repeat;mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;mask-size:100% 100%;background-color:currentColor;color:inherit;width:1.2em;height:1.2em}.i-carbon-settings{--un-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 32 32' width='1.2em' height='1.2em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M27 16.76v-1.53l1.92-1.68A2 2 0 0 0 29.3 11l-2.36-4a2 2 0 0 0-1.73-1a2 2 0 0 0-.64.1l-2.43.82a11 11 0 0 0-1.31-.75l-.51-2.52a2 2 0 0 0-2-1.61h-4.68a2 2 0 0 0-2 1.61l-.51 2.52a11.5 11.5 0 0 0-1.32.75l-2.38-.86A2 2 0 0 0 6.79 6a2 2 0 0 0-1.73 1L2.7 11a2 2 0 0 0 .41 2.51L5 15.24v1.53l-1.89 1.68A2 2 0 0 0 2.7 21l2.36 4a2 2 0 0 0 1.73 1a2 2 0 0 0 .64-.1l2.43-.82a11 11 0 0 0 1.31.75l.51 2.52a2 2 0 0 0 2 1.61h4.72a2 2 0 0 0 2-1.61l.51-2.52a11.5 11.5 0 0 0 1.32-.75l2.42.82a2 2 0 0 0 .64.1a2 2 0 0 0 1.73-1l2.28-4a2 2 0 0 0-.41-2.51ZM25.21 24l-3.43-1.16a8.9 8.9 0 0 1-2.71 1.57L18.36 28h-4.72l-.71-3.55a9.4 9.4 0 0 1-2.7-1.57L6.79 24l-2.36-4l2.72-2.4a8.9 8.9 0 0 1 0-3.13L4.43 12l2.36-4l3.43 1.16a8.9 8.9 0 0 1 2.71-1.57L13.64 4h4.72l.71 3.55a9.4 9.4 0 0 1 2.7 1.57L25.21 8l2.36 4l-2.72 2.4a8.9 8.9 0 0 1 0 3.13L27.57 20Z'/%3E%3Cpath fill='currentColor' d='M16 22a6 6 0 1 1 6-6a5.94 5.94 0 0 1-6 6m0-10a3.91 3.91 0 0 0-4 4a3.91 3.91 0 0 0 4 4a3.91 3.91 0 0 0 4-4a3.91 3.91 0 0 0-4-4'/%3E%3C/svg%3E");-webkit-mask:var(--un-icon) no-repeat;mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;mask-size:100% 100%;background-color:currentColor;color:inherit;width:1.2em;height:1.2em}.i-carbon-sun{--un-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 32 32' width='1.2em' height='1.2em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M16 12.005a4 4 0 1 1-4 4a4.005 4.005 0 0 1 4-4m0-2a6 6 0 1 0 6 6a6 6 0 0 0-6-6M5.394 6.813L6.81 5.399l3.505 3.506L8.9 10.319zM2 15.005h5v2H2zm3.394 10.193L8.9 21.692l1.414 1.414l-3.505 3.506zM15 25.005h2v5h-2zm6.687-1.9l1.414-1.414l3.506 3.506l-1.414 1.414zm3.313-8.1h5v2h-5zm-3.313-6.101l3.506-3.506l1.414 1.414l-3.506 3.506zM15 2.005h2v5h-2z'/%3E%3C/svg%3E");-webkit-mask:var(--un-icon) no-repeat;mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;mask-size:100% 100%;background-color:currentColor;color:inherit;width:1.2em;height:1.2em}:is(.prose),:is([prose=""]){color:var(--un-prose-body);max-width:65ch;:where(p):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.25em;margin-bottom:1.25em}:where([class~="lead"]):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:var(--un-prose-lead);font-size:1.25em;line-height:1.6;margin-top:1.2em;margin-bottom:1.2em}:where(a):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:var(--un-prose-links);text-decoration:underline;font-weight:500}:where(strong):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:var(--un-prose-bold);font-weight:600}:where(a strong):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:inherit}:where(blockquote strong):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:inherit}:where(thead th strong):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:inherit}:where(ol):not(:where([class~="not-prose"],[class~="not-prose"] *)){list-style-type:decimal;margin-top:1.25em;margin-bottom:1.25em;padding-inline-start:1.625em}:where(ol[type="A"]):not(:where([class~="not-prose"],[class~="not-prose"] *)){list-style-type:upper-alpha}:where(ol[type="a"]):not(:where([class~="not-prose"],[class~="not-prose"] *)){list-style-type:lower-alpha}:where(ol[type="A" s]):not(:where([class~="not-prose"],[class~="not-prose"] *)){list-style-type:upper-alpha}:where(ol[type="a" s]):not(:where([class~="not-prose"],[class~="not-prose"] *)){list-style-type:lower-alpha}:where(ol[type="I"]):not(:where([class~="not-prose"],[class~="not-prose"] *)){list-style-type:upper-roman}:where(ol[type="i"]):not(:where([class~="not-prose"],[class~="not-prose"] *)){list-style-type:lower-roman}:where(ol[type="I" s]):not(:where([class~="not-prose"],[class~="not-prose"] *)){list-style-type:upper-roman}:where(ol[type="i" s]):not(:where([class~="not-prose"],[class~="not-prose"] *)){list-style-type:lower-roman}:where(ol[type="1"]):not(:where([class~="not-prose"],[class~="not-prose"] *)){list-style-type:decimal}:where(ul):not(:where([class~="not-prose"],[class~="not-prose"] *)){list-style-type:disc;margin-top:1.25em;margin-bottom:1.25em;padding-inline-start:1.625em}:where(ol>li::marker):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-weight:400;color:var(--un-prose-counters)}:where(ul>li::marker):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:var(--un-prose-bullets)}:where(dt):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:var(--un-prose-headings);font-weight:600;margin-top:1.25em}:where(hr):not(:where([class~="not-prose"],[class~="not-prose"] *)){border-color:var(--un-prose-hr);border-top-width:1px;margin-top:3em;margin-bottom:3em}:where(blockquote):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-weight:500;font-style:italic;color:var(--un-prose-quotes);border-inline-start-width:0.25rem;border-inline-start-color:var(--un-prose-quote-borders);quotes:"\201C""\201D""\2018""\2019";margin-top:1.6em;margin-bottom:1.6em;padding-inline-start:1em}:where(blockquote p:first-of-type::before):not(:where([class~="not-prose"],[class~="not-prose"] *)){content:open-quote}:where(blockquote p:last-of-type::after):not(:where([class~="not-prose"],[class~="not-prose"] *)){content:close-quote}:where(h1):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:var(--un-prose-headings);font-weight:800;font-size:2.25em;margin-top:0;margin-bottom:0.8888889em;line-height:1.1111111}:where(h1 strong):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-weight:900;color:inherit}:where(h2):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:var(--un-prose-headings);font-weight:700;font-size:1.5em;margin-top:2em;margin-bottom:1em;line-height:1.3333333}:where(h2 strong):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-weight:800;color:inherit}:where(h3):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:var(--un-prose-headings);font-weight:600;font-size:1.25em;margin-top:1.6em;margin-bottom:0.6em;line-height:1.6}:where(h3 strong):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-weight:700;color:inherit}:where(h4):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:var(--un-prose-headings);font-weight:600;margin-top:1.5em;margin-bottom:0.5em;line-height:1.5}:where(h4 strong):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-weight:700;color:inherit}:where(img):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:2em;margin-bottom:2em}:where(picture):not(:where([class~="not-prose"],[class~="not-prose"] *)){display:block;margin-top:2em;margin-bottom:2em}:where(video):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:2em;margin-bottom:2em}:where(kbd):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-weight:500;font-family:inherit;color:var(--un-prose-kbd);box-shadow:0 0 0 1px rgb(var(--un-prose-kbd-shadows) / 10%),0 3px 0 rgb(var(--un-prose-kbd-shadows) / 10%);font-size:0.875em;border-radius:0.3125rem;padding-top:0.1875em;padding-inline-end:0.375em;padding-bottom:0.1875em;padding-inline-start:0.375em}:where(code):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:var(--un-prose-code);font-weight:600;font-size:0.875em}:where(code::before):not(:where([class~="not-prose"],[class~="not-prose"] *)){content:"`"}:where(code::after):not(:where([class~="not-prose"],[class~="not-prose"] *)){content:"`"}:where(a code):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:inherit}:where(h1 code):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:inherit}:where(h2 code):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:inherit;font-size:0.875em}:where(h3 code):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:inherit;font-size:0.9em}:where(h4 code):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:inherit}:where(blockquote code):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:inherit}:where(thead th code):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:inherit}:where(pre):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:var(--un-prose-pre-code);background-color:var(--un-prose-pre-bg);overflow-x:auto;font-weight:400;font-size:0.875em;line-height:1.7142857;margin-top:1.7142857em;margin-bottom:1.7142857em;border-radius:0.375rem;padding-top:0.8571429em;padding-inline-end:1.1428571em;padding-bottom:0.8571429em;padding-inline-start:1.1428571em}:where(pre code):not(:where([class~="not-prose"],[class~="not-prose"] *)){background-color:transparent;border-width:0;border-radius:0;padding:0;font-weight:inherit;color:inherit;font-size:inherit;font-family:inherit;line-height:inherit}:where(pre code::before):not(:where([class~="not-prose"],[class~="not-prose"] *)){content:none}:where(pre code::after):not(:where([class~="not-prose"],[class~="not-prose"] *)){content:none}:where(table):not(:where([class~="not-prose"],[class~="not-prose"] *)){width:100%;table-layout:auto;margin-top:2em;margin-bottom:2em;font-size:0.875em;line-height:1.7142857}:where(thead):not(:where([class~="not-prose"],[class~="not-prose"] *)){border-bottom-width:1px;border-bottom-color:var(--un-prose-th-borders)}:where(thead th):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:var(--un-prose-headings);font-weight:600;vertical-align:bottom;padding-inline-end:0.5714286em;padding-bottom:0.5714286em;padding-inline-start:0.5714286em}:where(tbody tr):not(:where([class~="not-prose"],[class~="not-prose"] *)){border-bottom-width:1px;border-bottom-color:var(--un-prose-td-borders)}:where(tbody tr:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){border-bottom-width:0}:where(tbody td):not(:where([class~="not-prose"],[class~="not-prose"] *)){vertical-align:baseline}:where(tfoot):not(:where([class~="not-prose"],[class~="not-prose"] *)){border-top-width:1px;border-top-color:var(--un-prose-th-borders)}:where(tfoot td):not(:where([class~="not-prose"],[class~="not-prose"] *)){vertical-align:top}:where(th,td):not(:where([class~="not-prose"],[class~="not-prose"] *)){text-align:start}:where(figure>*):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0;margin-bottom:0}:where(figcaption):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:var(--un-prose-captions);font-size:0.875em;line-height:1.4285714;margin-top:0.8571429em}font-size:1rem;line-height:1.75;:where(picture>img):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0;margin-bottom:0}:where(li):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0.5em;margin-bottom:0.5em}:where(ol>li):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-inline-start:0.375em}:where(ul>li):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-inline-start:0.375em}:where(>ul>li p):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0.75em;margin-bottom:0.75em}:where(>ul>li>p:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.25em}:where(>ul>li>p:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-bottom:1.25em}:where(>ol>li>p:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.25em}:where(>ol>li>p:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-bottom:1.25em}:where(ul ul,ul ol,ol ul,ol ol):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0.75em;margin-bottom:0.75em}:where(dl):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.25em;margin-bottom:1.25em}:where(dd):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0.5em;padding-inline-start:1.625em}:where(hr+*):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0}:where(h2+*):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0}:where(h3+*):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0}:where(h4+*):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0}:where(thead th:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-inline-start:0}:where(thead th:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-inline-end:0}:where(tbody td,tfoot td):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-top:0.5714286em;padding-inline-end:0.5714286em;padding-bottom:0.5714286em;padding-inline-start:0.5714286em}:where(tbody td:first-child,tfoot td:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-inline-start:0}:where(tbody td:last-child,tfoot td:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-inline-end:0}:where(figure):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:2em;margin-bottom:2em}:where(>:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0}:where(>:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-bottom:0}--un-prose-body:#374151;--un-prose-invert-body:#d1d5db;--un-prose-headings:#111827;--un-prose-invert-headings:white;--un-prose-lead:#4b5563;--un-prose-invert-lead:#9ca3af;--un-prose-links:#111827;--un-prose-invert-links:white;--un-prose-bold:#111827;--un-prose-invert-bold:white;--un-prose-counters:#6b7280;--un-prose-invert-counters:#9ca3af;--un-prose-bullets:#d1d5db;--un-prose-invert-bullets:#4b5563;--un-prose-hr:#e5e7eb;--un-prose-invert-hr:#374151;--un-prose-quotes:#111827;--un-prose-invert-quotes:#f3f4f6;--un-prose-quote-borders:#e5e7eb;--un-prose-invert-quote-borders:#374151;--un-prose-captions:#6b7280;--un-prose-invert-captions:#9ca3af;--un-prose-kbd:#111827;--un-prose-invert-kbd:white;--un-prose-kbd-shadows:#111827;--un-prose-invert-kbd-shadows:white;--un-prose-code:#111827;--un-prose-invert-code:white;--un-prose-pre-code:#e5e7eb;--un-prose-invert-pre-code:#d1d5db;--un-prose-pre-bg:#1f2937;--un-prose-invert-pre-bg:rgb(0 0 0 / 50%);--un-prose-th-borders:#d1d5db;--un-prose-invert-th-borders:#4b5563;--un-prose-td-borders:#e5e7eb;--un-prose-invert-td-borders:#374151}:is(.prose-lg),:is([prose-lg=""]){font-size:1.125rem;line-height:1.7777778;:where(p):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.3333333em;margin-bottom:1.3333333em}:where([class~="lead"]):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-size:1.2222222em;line-height:1.4545455;margin-top:1.0909091em;margin-bottom:1.0909091em}:where(blockquote):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.6666667em;margin-bottom:1.6666667em;padding-inline-start:1em}:where(h1):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-size:2.6666667em;margin-top:0;margin-bottom:0.8333333em;line-height:1}:where(h2):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-size:1.6666667em;margin-top:1.8666667em;margin-bottom:1.0666667em;line-height:1.3333333}:where(h3):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-size:1.3333333em;margin-top:1.6666667em;margin-bottom:0.6666667em;line-height:1.5}:where(h4):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.7777778em;margin-bottom:0.4444444em;line-height:1.5555556}:where(img):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.7777778em;margin-bottom:1.7777778em}:where(picture):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.7777778em;margin-bottom:1.7777778em}:where(picture>img):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0;margin-bottom:0}:where(video):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.7777778em;margin-bottom:1.7777778em}:where(kbd):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-size:0.8888889em;border-radius:0.3125rem;padding-top:0.2222222em;padding-inline-end:0.4444444em;padding-bottom:0.2222222em;padding-inline-start:0.4444444em}:where(code):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-size:0.8888889em}:where(h2 code):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-size:0.8666667em}:where(h3 code):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-size:0.875em}:where(pre):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-size:0.8888889em;line-height:1.75;margin-top:2em;margin-bottom:2em;border-radius:0.375rem;padding-top:1em;padding-inline-end:1.5em;padding-bottom:1em;padding-inline-start:1.5em}:where(ol):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.3333333em;margin-bottom:1.3333333em;padding-inline-start:1.5555556em}:where(ul):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.3333333em;margin-bottom:1.3333333em;padding-inline-start:1.5555556em}:where(li):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0.6666667em;margin-bottom:0.6666667em}:where(ol>li):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-inline-start:0.4444444em}:where(ul>li):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-inline-start:0.4444444em}:where(>ul>li p):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0.8888889em;margin-bottom:0.8888889em}:where(>ul>li>p:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.3333333em}:where(>ul>li>p:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-bottom:1.3333333em}:where(>ol>li>p:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.3333333em}:where(>ol>li>p:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-bottom:1.3333333em}:where(ul ul,ul ol,ol ul,ol ol):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0.8888889em;margin-bottom:0.8888889em}:where(dl):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.3333333em;margin-bottom:1.3333333em}:where(dt):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.3333333em}:where(dd):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0.6666667em;padding-inline-start:1.5555556em}:where(hr):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:3.1111111em;margin-bottom:3.1111111em}:where(hr+*):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0}:where(h2+*):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0}:where(h3+*):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0}:where(h4+*):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0}:where(table):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-size:0.8888889em;line-height:1.5}:where(thead th):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-inline-end:0.75em;padding-bottom:0.75em;padding-inline-start:0.75em}:where(thead th:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-inline-start:0}:where(thead th:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-inline-end:0}:where(tbody td,tfoot td):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-top:0.75em;padding-inline-end:0.75em;padding-bottom:0.75em;padding-inline-start:0.75em}:where(tbody td:first-child,tfoot td:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-inline-start:0}:where(tbody td:last-child,tfoot td:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-inline-end:0}:where(figure):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.7777778em;margin-bottom:1.7777778em}:where(figure>*):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0;margin-bottom:0}:where(figcaption):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-size:0.8888889em;line-height:1.5;margin-top:1em}:where(>:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0}:where(>:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-bottom:0}}.dark:is(.dark\:prose-invert){--un-prose-body:var(--un-prose-invert-body);--un-prose-headings:var(--un-prose-invert-headings);--un-prose-lead:var(--un-prose-invert-lead);--un-prose-links:var(--un-prose-invert-links);--un-prose-bold:var(--un-prose-invert-bold);--un-prose-counters:var(--un-prose-invert-counters);--un-prose-bullets:var(--un-prose-invert-bullets);--un-prose-hr:var(--un-prose-invert-hr);--un-prose-quotes:var(--un-prose-invert-quotes);--un-prose-quote-borders:var(--un-prose-invert-quote-borders);--un-prose-captions:var(--un-prose-invert-captions);--un-prose-kbd:var(--un-prose-invert-kbd);--un-prose-kbd-shadows:var(--un-prose-invert-kbd-shadows);--un-prose-code:var(--un-prose-invert-code);--un-prose-pre-code:var(--un-prose-invert-pre-code);--un-prose-pre-bg:var(--un-prose-invert-pre-bg);--un-prose-th-borders:var(--un-prose-invert-th-borders);--un-prose-td-borders:var(--un-prose-invert-td-borders)}.-container{width:-100%}.container,[container=""]{width:100%}.btn:disabled{pointer-events:none;cursor:default;--un-bg-opacity:1;background-color:rgb(75 85 99 / var(--un-bg-opacity));opacity:0.5 !important}[btn=""]:disabled{pointer-events:none;cursor:default;--un-bg-opacity:1;background-color:rgb(75 85 99 / var(--un-bg-opacity));opacity:0.5 !important}.ds-btn:disabled{pointer-events:none;opacity:0.5}[ds-btn=""]:disabled{pointer-events:none;opacity:0.5}.ds-btn-content-category-filter:disabled{pointer-events:none;opacity:0.5}.ds-btn-ghost:disabled{pointer-events:none;opacity:0.5}[ds-btn-ghost=""]:disabled{pointer-events:none;opacity:0.5}.ds-btn-primary:disabled{pointer-events:none;opacity:0.5}[ds-btn-primary=""]:disabled{pointer-events:none;opacity:0.5}.ds-btn-secondary:disabled{pointer-events:none;opacity:0.5}[ds-btn-secondary=""]:disabled{pointer-events:none;opacity:0.5}.ds-btn-sm:disabled{pointer-events:none;opacity:0.5}[ds-btn-sm=""]:disabled{pointer-events:none;opacity:0.5}.ds-theme-toggle:disabled{pointer-events:none;opacity:0.5}[ds-theme-toggle=""]:disabled{pointer-events:none;opacity:0.5}.ds-card-text,[ds-card-text=""]{margin-left:auto;margin-right:auto;margin-bottom:var(--space-4);max-width:42rem;color:var(--color-neutral-600);color:var(--text-base);line-height:var(--leading-normal);line-height:var(--leading-relaxed)}.ds-container,[ds-container=""]{margin-left:auto;margin-right:auto;max-width:80rem;padding-left:var(--space-4);padding-right:var(--space-4)}.ds-container-lg{margin-left:auto;margin-right:auto;max-width:72rem;padding-left:var(--space-4);padding-right:var(--space-4)}.mx-auto,[mx-auto=""]{margin-left:auto;margin-right:auto}.ds-body,[ds-body=""]{margin-bottom:var(--space-4);color:var(--text-base);color:var(--color-neutral-600);line-height:var(--leading-normal);line-height:var(--leading-relaxed)}.ds-card-title,[ds-card-title=""]{margin-bottom:var(--space-3);margin-bottom:0.5rem;margin-bottom:var(--space-4,1rem);text-wrap:balance;color:var(--text-xl);color:var(--color-neutral-900);line-height:var(--leading-relaxed);font-family:var(--font-semibold)}.ds-heading-1{margin-bottom:var(--space-6);color:var(--text-4xl);color:var(--color-neutral-900);line-height:var(--leading-tight);font-family:var(--font-bold)}.ds-heading-2{margin-bottom:var(--space-4);color:var(--text-3xl);color:var(--color-neutral-900);line-height:var(--leading-tight);font-family:var(--font-semibold)}.ds-heading-3,[ds-heading-3=""]{margin-bottom:var(--space-4);color:var(--text-2xl);color:var(--color-neutral-900);line-height:var(--leading-tight);font-family:var(--font-semibold)}.ds-heading-4{margin-bottom:var(--space-3);color:var(--text-xl);color:var(--color-neutral-900);line-height:var(--leading-relaxed);font-family:var(--font-semibold)}.btn,[btn=""]{display:inline-block;cursor:pointer;border-radius:0.375rem;background-color:var(--c-primary);padding-left:1rem;padding-right:1rem;padding-top:0.25rem;padding-bottom:0.25rem;--un-text-opacity:1;color:rgb(255 255 255 / var(--un-text-opacity));letter-spacing:0.025em;opacity:0.9}.ds-input,[ds-input=""]{width:100%;border-width:1px;border-color:var(--color-neutral-300);border-radius:var(--radius-md);background-color:var(--color-neutral-50);padding-left:var(--space-3);padding-right:var(--space-3);padding-top:var(--space-2);padding-bottom:var(--space-2);color:var(--text-sm);transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms;transition-duration:200ms}.ds-select,[ds-select=""]{width:100%;cursor:pointer;border-width:1px;border-color:var(--color-neutral-300);border-radius:var(--radius-md);background-color:var(--color-neutral-50);padding-left:var(--space-3);padding-right:var(--space-3);padding-top:var(--space-2);padding-bottom:var(--space-2);color:var(--text-sm);transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms;transition-duration:200ms}.ds-textarea{width:100%;min-height:6rem;resize:vertical;border-width:1px;border-color:var(--color-neutral-300);border-radius:var(--radius-md);background-color:var(--color-neutral-50);padding-left:var(--space-3);padding-right:var(--space-3);padding-top:var(--space-2);padding-bottom:var(--space-2);color:var(--text-sm);transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms;transition-duration:200ms}.nav-link{display:flex;align-items:center;column-gap:var(--space-2);padding:var(--space-1);color:var(--text-sm);color:var(--color-neutral-600);font-family:var(--font-sans);text-decoration:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms;transition-duration:200ms}.ds-badge{display:inline-flex;align-items:center;border-radius:9999px;padding-left:0.625rem;padding-right:0.625rem;padding-top:0.125rem;padding-bottom:0.125rem;font-size:0.75rem;line-height:1rem;font-weight:500;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms}.ds-badge-ghost{display:inline-flex;align-items:center;border-radius:9999px;background-color:var(--color-neutral-100);padding-left:0.625rem;padding-right:0.625rem;padding-top:0.125rem;padding-bottom:0.125rem;font-size:0.75rem;line-height:1rem;color:var(--color-neutral-700);font-weight:500;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms}.ds-badge-outline{display:inline-flex;align-items:center;border-width:1px;border-color:var(--color-neutral-300);border-radius:9999px;background-color:transparent;padding-left:0.625rem;padding-right:0.625rem;padding-top:0.125rem;padding-bottom:0.125rem;font-size:0.75rem;line-height:1rem;color:var(--color-neutral-700);font-weight:500;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms}.ds-btn,[ds-btn=""]{display:inline-flex;cursor:pointer;align-items:center;justify-content:center;border-style:none;font-family:var(--font-medium);text-decoration:none;transition-property:all;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms;transition-duration:200ms}.ds-btn-content-category-filter,.ds-btn-ghost,[ds-btn-ghost=""]{display:inline-flex;cursor:pointer;align-items:center;justify-content:center;border-radius:var(--radius-md);border-style:none;background-color:transparent;padding-left:var(--space-4);padding-right:var(--space-4);padding-top:var(--space-2);padding-bottom:var(--space-2);color:var(--text-sm);color:var(--color-neutral-700);font-family:var(--font-medium);text-decoration:none;transition-property:all;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms;transition-duration:200ms}.ds-btn-primary,[ds-btn-primary=""]{display:inline-flex;cursor:pointer;align-items:center;justify-content:center;border-radius:var(--radius-md);border-style:none;background-color:var(--color-brand-primary);padding-left:var(--space-4);padding-right:var(--space-4);padding-top:var(--space-2);padding-bottom:var(--space-2);color:var(--text-sm);color:var(--color-neutral-50);font-family:var(--font-medium);text-decoration:none;transition-property:all;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms;transition-duration:200ms}.ds-btn-secondary,[ds-btn-secondary=""]{display:inline-flex;cursor:pointer;align-items:center;justify-content:center;border-radius:var(--radius-md);border-style:none;background-color:var(--color-neutral-200);padding-left:var(--space-4);padding-right:var(--space-4);padding-top:var(--space-2);padding-bottom:var(--space-2);color:var(--text-sm);color:var(--color-neutral-900);font-family:var(--font-medium);text-decoration:none;transition-property:all;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms;transition-duration:200ms}.ds-btn-sm,[ds-btn-sm=""]{display:inline-flex;cursor:pointer;align-items:center;justify-content:center;border-radius:var(--radius-md);border-style:none;padding-left:var(--space-3);padding-right:var(--space-3);padding-top:var(--space-1-5);padding-bottom:var(--space-1-5);color:var(--text-sm);font-family:var(--font-medium);text-decoration:none;transition-property:all;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms;transition-duration:200ms}.ds-card-featured,[ds-card-featured=""]{display:inline-flex;align-items:flex-end;border-radius:9999px;padding-left:0.625rem;padding-right:0.625rem;padding-top:0.125rem;padding-bottom:0.125rem;font-size:0.75rem;line-height:1rem;font-weight:500;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms}.ds-theme-toggle,[ds-theme-toggle=""]{display:inline-flex;cursor:pointer;align-items:center;justify-content:center;border-width:1px;border-color:var(--color-neutral-200);border-radius:var(--radius-lg);border-style:none;background-color:var(--color-neutral-50);padding:var(--space-2);color:var(--text-sm);color:var(--color-neutral-900);font-family:var(--font-medium);text-decoration:none;transition-property:all;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms;transition-duration:200ms}.hover\:ds-badge-accent:hover{display:inline-flex;align-items:center;border-radius:9999px;background-color:var(--color-success);padding-left:0.625rem;padding-right:0.625rem;padding-top:0.125rem;padding-bottom:0.125rem;font-size:0.75rem;line-height:1rem;--un-text-opacity:1;color:rgb(255 255 255 / var(--un-text-opacity));font-weight:500;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms}.ds-card,[ds-card=""]{border-width:1px;border-color:var(--color-neutral-200);border-radius:var(--radius-lg);background-color:var(--color-neutral-50);padding:var(--space-6);--un-shadow:var(--shadow-sm);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.dark .ds-badge-outline{border-color:var(--color-neutral-600);color:var(--color-neutral-300)}.dark .dark .dark\:ds-border,.dark .ds-border,.dark [ds-border=""]{border-color:var(--color-neutral-700)}.dark .dark\:ds-border,.ds-border,[ds-border=""]{border-color:var(--color-neutral-200)}.dark .ds-card,.dark .ds-input,.dark .ds-select,.dark .ds-textarea,.dark [ds-card=""],.dark [ds-input=""],.dark [ds-select=""]{border-color:var(--color-neutral-600);background-color:var(--color-neutral-800)}.dark .ds-theme-toggle,.dark [ds-theme-toggle=""]{border-color:var(--color-neutral-700);background-color:var(--color-neutral-900);color:var(--color-neutral-50)}.dark .hover\:ds-border:hover{border-color:var(--color-neutral-700)}.hover\:ds-border:hover{border-color:var(--color-neutral-200)}.ds-input:focus{border-color:var(--color-brand-primary);outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-color:var(--color-brand-primary)}[ds-input=""]:focus{border-color:var(--color-brand-primary);outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-color:var(--color-brand-primary)}.ds-select:focus{border-color:var(--color-brand-primary);outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-color:var(--color-brand-primary)}[ds-select=""]:focus{border-color:var(--color-brand-primary);outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-color:var(--color-brand-primary)}.ds-textarea:focus{border-color:var(--color-brand-primary);outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-color:var(--color-brand-primary)}.ds-rounded{border-radius:var(--radius-base)}.ds-rounded-lg,[ds-rounded-lg=""]{border-radius:var(--radius-lg)}.ds-rounded-md,[ds-rounded-md=""]{border-radius:var(--radius-md)}.ds-rounded-xl{border-radius:var(--radius-xl)}.round,.rounded-full{border-radius:9999px}.rounded,[rounded=""]{border-radius:0.375rem}.rounded-2xl{border-radius:1rem}.rounded-lg,[rounded-lg=""]{border-radius:0.5rem}.rounded-xl{border-radius:0.75rem}.dark .ds-badge-ghost{background-color:var(--color-neutral-800);color:var(--color-neutral-300)}.dark .ds-bg,.dark [ds-bg=""]{background-color:var(--color-neutral-950)}.ds-bg,[ds-bg=""]{background-color:var(--color-neutral-50)}.dark .ds-bg-page,.dark [ds-bg-page=""]{background-color:var(--color-neutral-900)}.ds-bg-page,.ds-bg-secondary,[ds-bg-page=""]{background-color:var(--color-neutral-100)}.dark .ds-bg-secondary{background-color:var(--color-neutral-800)}.hover\:ds-badge-accent:hover:hover{background-color:var(--color-success)}.dark .ds-badge-ghost:hover{background-color:var(--color-neutral-700)}.ds-badge-ghost:hover{background-color:var(--color-neutral-200)}.dark .ds-badge-outline:hover{background-color:var(--color-neutral-800)}.ds-badge-outline:hover{background-color:var(--color-neutral-100)}.hover\:ds-badge-primary-focus:hover{background-color:var(--color-brand-primary);--un-text-opacity:1;color:rgb(255 255 255 / var(--un-text-opacity))}.dark .hover\:ds-bg:hover{background-color:var(--color-neutral-950)}.dark [hover\:ds-bg=""]:hover{background-color:var(--color-neutral-950)}.hover\:ds-bg:hover{background-color:var(--color-neutral-50)}[hover\:ds-bg=""]:hover{background-color:var(--color-neutral-50)}.dark .ds-btn-content-category-filter:hover{background-color:var(--color-neutral-800)}.ds-btn-content-category-filter:hover{background-color:var(--color-neutral-100)}.dark .ds-btn-ghost:hover{background-color:var(--color-neutral-800)}.dark [ds-btn-ghost=""]:hover{background-color:var(--color-neutral-800)}.ds-btn-ghost:hover{background-color:var(--color-neutral-100)}[ds-btn-ghost=""]:hover{background-color:var(--color-neutral-100)}.ds-btn-primary:hover{background-color:var(--color-brand-primary)}[ds-btn-primary=""]:hover{background-color:var(--color-brand-primary)}.ds-btn-secondary:hover{background-color:var(--color-neutral-300)}[ds-btn-secondary=""]:hover{background-color:var(--color-neutral-300)}.dark .ds-theme-toggle:hover{background-color:var(--color-neutral-800)}.dark [ds-theme-toggle=""]:hover{background-color:var(--color-neutral-800)}.ds-theme-toggle:hover{background-color:var(--color-neutral-100)}[ds-theme-toggle=""]:hover{background-color:var(--color-neutral-100)}.ds-badge-sm{padding-left:0.5rem;padding-right:0.5rem;padding-top:0.125rem;padding-bottom:0.125rem;font-size:0.75rem;line-height:1rem}.text-center,[text-center=""]{text-align:center}.text-left,[text-left=""]{text-align:left}.text-right{text-align:right}.dark .ds-body,.dark .ds-card-text,.dark .ds-text-secondary,.dark .nav-link,.dark [ds-body=""],.dark [ds-card-text=""],.dark [ds-text-secondary=""]{color:var(--color-neutral-400)}.dark .ds-btn-content-category-filter,.dark .ds-btn-ghost,.dark [ds-btn-ghost=""]{color:var(--color-neutral-300)}.dark .ds-caption,.dark .ds-text-muted,.dark [ds-caption=""],.dark [ds-text-muted=""],.ds-text-muted,[ds-text-muted=""],.group:hover .dark .group-hover\:ds-text-muted,.group:hover .group-hover\:ds-text-muted{color:var(--color-neutral-500)}.ds-caption,[ds-caption=""]{color:var(--text-sm);color:var(--color-neutral-500);line-height:var(--leading-normal)}.dark .dark .dark\:ds-text,.dark .ds-card-title,.dark .ds-heading-1,.dark .ds-heading-2,.dark .ds-heading-3,.dark .ds-heading-4,.dark .ds-text,.dark [ds-card-title=""],.dark [ds-heading-3=""],.dark [ds-text=""]{color:var(--color-neutral-50)}.dark .dark\:ds-text,.ds-text,[ds-text=""]{color:var(--color-neutral-900)}.ds-text-secondary,[ds-text-secondary=""]{color:var(--color-neutral-600)}.dark .hover\:ds-text:hover{color:var(--color-neutral-50)}.hover\:ds-text:hover{color:var(--color-neutral-900)}.dark .hover\:ds-text-secondary:hover{color:var(--color-neutral-400)}.hover\:ds-text-secondary:hover{color:var(--color-neutral-600)}.nav-link:hover{color:var(--color-brand-primary)}.dark .placeholder-ds-text-muted::placeholder{color:var(--color-neutral-500)}.dark [placeholder-ds-text-muted=""]::placeholder{color:var(--color-neutral-500)}.placeholder-ds-text-muted::placeholder{color:var(--color-neutral-500)}[placeholder-ds-text-muted=""]::placeholder{color:var(--color-neutral-500)}.btn:hover{opacity:1}[btn=""]:hover{opacity:1}.ds-shadow-lg,[ds-shadow-lg=""]{--un-shadow:var(--shadow-lg);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.ds-shadow-md{--un-shadow:var(--shadow-md);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.ds-shadow-sm,[ds-shadow-sm=""]{--un-shadow:var(--shadow-sm);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.shadow,[shadow=""]{--un-shadow:var(--un-shadow-inset) 0 4px 6px -1px var(--un-shadow-color,rgb(0 0 0 / 0.1)),var(--un-shadow-inset) 0 2px 4px -2px var(--un-shadow-color,rgb(0 0 0 / 0.1));box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.shadow-lg,[shadow-lg=""]{--un-shadow:var(--un-shadow-inset) 0 10px 15px -3px var(--un-shadow-color,rgb(0 0 0 / 0.1)),var(--un-shadow-inset) 0 4px 6px -4px var(--un-shadow-color,rgb(0 0 0 / 0.1));box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.shadow-none{--un-shadow:0 0 var(--un-shadow-color,rgb(0 0 0 / 0));box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.shadow-sm{--un-shadow:var(--un-shadow-inset) 0 1px 2px 0 var(--un-shadow-color,rgb(0 0 0 / 0.05));box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.shadow-xl{--un-shadow:var(--un-shadow-inset) 0 20px 25px -5px var(--un-shadow-color,rgb(0 0 0 / 0.1)),var(--un-shadow-inset) 0 8px 10px -6px var(--un-shadow-color,rgb(0 0 0 / 0.1));box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.hover\:ds-shadow-md:hover{--un-shadow:var(--shadow-md);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.hover\:shadow-none:hover{--un-shadow:0 0 var(--un-shadow-color,rgb(0 0 0 / 0));box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.hover\:shadow-xl:hover{--un-shadow:var(--un-shadow-inset) 0 20px 25px -5px var(--un-shadow-color,rgb(0 0 0 / 0.1)),var(--un-shadow-inset) 0 8px 10px -6px var(--un-shadow-color,rgb(0 0 0 / 0.1));box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.disabled\:shadow-none:disabled{--un-shadow:0 0 var(--un-shadow-color,rgb(0 0 0 / 0));box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.ds-btn:focus{outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-offset-width:2px}[ds-btn=""]:focus{outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-offset-width:2px}.ds-btn-content-category-filter:focus{outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-offset-width:2px}.ds-btn-ghost:focus{outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-offset-width:2px}[ds-btn-ghost=""]:focus{outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-offset-width:2px}.ds-btn-primary:focus{outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-offset-width:2px;--un-ring-color:var(--color-brand-primary)}[ds-btn-primary=""]:focus{outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-offset-width:2px;--un-ring-color:var(--color-brand-primary)}.ds-btn-secondary:focus{outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-offset-width:2px;--un-ring-color:var(--color-neutral-200)}[ds-btn-secondary=""]:focus{outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-offset-width:2px;--un-ring-color:var(--color-neutral-200)}.ds-btn-sm:focus{outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-offset-width:2px}[ds-btn-sm=""]:focus{outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-offset-width:2px}.ds-theme-toggle:focus{outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-offset-width:2px;--un-ring-color:var(--color-brand-primary)}[ds-theme-toggle=""]:focus{outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-offset-width:2px;--un-ring-color:var(--color-brand-primary)}@media (min-width:640px){.-container{max-width:-640px}.container,[container=""]{max-width:640px}.sm\:ds-container,[sm\:ds-container=""]{margin-left:auto;margin-right:auto;max-width:80rem;padding-left:var(--space-4);padding-right:var(--space-4)}.ds-card-title,[ds-card-title=""]{margin-bottom:var(--space-4);color:var(--text-2xl);color:var(--color-neutral-900);line-height:var(--leading-tight);font-family:var(--font-semibold)}.dark .ds-card-title,.dark [ds-card-title=""]{color:var(--color-neutral-50)}}@media (min-width:768px){.-container{max-width:-768px}.container,[container=""]{max-width:768px}}@media (min-width:1024px){.-container{max-width:-1024px}.container,[container=""]{max-width:1024px}.lg\:mx-auto,[lg\:mx-auto=""]{margin-left:auto;margin-right:auto}.dark .lg\:ds-border{border-color:var(--color-neutral-700)}.lg\:ds-border{border-color:var(--color-neutral-200)}}@media (min-width:1280px){.-container{max-width:-1280px}.container,[container=""]{max-width:1280px}}@media (min-width:1536px){.-container{max-width:-1536px}.container,[container=""]{max-width:1536px}}:root,[data-theme]{background-color:hsl(var(--b1) / var(--un-bg-opacity,1));color:hsl(var(--bc) / var(--un-text-opacity,1))}html{-webkit-tap-highlight-color:transparent}.alert{display:grid;width:100%;grid-auto-flow:row;align-content:flex-start;align-items:center;justify-items:center;gap:1rem;text-align:center;border-width:1px;--un-border-opacity:1;border-color:hsl(var(--b2) / var(--un-border-opacity));padding:1rem;--un-text-opacity:1;color:hsl(var(--bc) / var(--un-text-opacity));border-radius:var(--rounded-box,1rem);--alert-bg:hsl(var(--b2));--alert-bg-mix:hsl(var(--b1));background-color:var(--alert-bg)}.alert{grid-auto-flow:column;grid-template-columns:auto minmax(auto,1fr);justify-items:start;text-align:left}.avatar{position:relative;display:inline-flex}.avatar>div{display:block;aspect-ratio:1 / 1;overflow:hidden}.avatar img{height:100%;width:100%;-o-object-fit:cover;object-fit:cover}.avatar.placeholder>div{display:flex;align-items:center;justify-content:center}.avatar.online:before{content:"";position:absolute;z-index:10;display:block;border-radius:9999px;--un-bg-opacity:1;background-color:hsl(var(--su) / var(--un-bg-opacity));outline-style:solid;outline-width:2px;outline-color:hsl(var(--b1) / 1);width:15%;height:15%;top:7%;right:7%}.avatar.offline:before{content:"";position:absolute;z-index:10;display:block;border-radius:9999px;--un-bg-opacity:1;background-color:hsl(var(--b3) / var(--un-bg-opacity));outline-style:solid;outline-width:2px;outline-color:hsl(var(--b1) / 1);width:15%;height:15%;top:7%;right:7%}.badge{display:inline-flex;align-items:center;justify-content:center;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-timing-function:cubic-bezier(0,0,0.2,1);transition-duration:200ms;height:1.25rem;font-size:0.875rem;line-height:1.25rem;width:-moz-fit-content;width:fit-content;padding-left:0.563rem;padding-right:0.563rem;border-width:1px;--un-border-opacity:1;border-color:hsl(var(--b2) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--b1) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--bc) / var(--un-text-opacity));border-radius:var(--rounded-badge,1.9rem)}.breadcrumbs{max-width:100%;overflow-x:auto;padding-top:0.5rem;padding-bottom:0.5rem}.breadcrumbs>ul,.breadcrumbs>ol{display:flex;align-items:center;white-space:nowrap;min-height:-moz-min-content;min-height:min-content}.breadcrumbs>ul>li,.breadcrumbs>ol>li{display:flex;align-items:center}.breadcrumbs>ul>li>a,.breadcrumbs>ol>li>a{display:flex;cursor:pointer;align-items:center}.breadcrumbs>ul>li>a:hover,.breadcrumbs>ol>li>a:hover{text-decoration-line:underline}.breadcrumbs>ul>li>a:focus,.breadcrumbs>ol>li>a:focus{outline:2px solid transparent;outline-offset:2px}.breadcrumbs>ul>li>a:focus-visible,.breadcrumbs>ol>li>a:focus-visible{outline:2px solid currentColor;outline-offset:2px}.breadcrumbs>ul>li+*:before,.breadcrumbs>ol>li+*:before{content:"";margin-left:0.5rem;margin-right:0.75rem;display:block;height:0.375rem;width:0.375rem;--un-rotate:45deg;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y));opacity:0.4;border-top:1px solid;border-right:1px solid;background-color:transparent}[dir="rtl"] .breadcrumbs>ul>li+*:before,[dir="rtl"] .breadcrumbs>ol>li+*:before{--un-rotate:-45deg}.link-hover:hover{text-decoration-line:underline}.link-primary:hover{--un-text-opacity:1;color:hsl(var(--pf) / var(--un-text-opacity))}.link-secondary:hover{--un-text-opacity:1;color:hsl(var(--sf) / var(--un-text-opacity))}.link-accent:hover{--un-text-opacity:1;color:hsl(var(--af) / var(--un-text-opacity))}.link-neutral:hover{--un-text-opacity:1;color:hsl(var(--nf) / var(--un-text-opacity))}.link-success:hover{--un-text-opacity:1;color:hsl(var(--su) / var(--un-text-opacity))}.link-info:hover{--un-text-opacity:1;color:hsl(var(--in) / var(--un-text-opacity))}.link-warning:hover{--un-text-opacity:1;color:hsl(var(--wa) / var(--un-text-opacity))}.link-error:hover{--un-text-opacity:1;color:hsl(var(--er) / var(--un-text-opacity))}.link{cursor:pointer;text-decoration-line:underline}.link-hover{text-decoration-line:none}.link-primary{--un-text-opacity:1;color:hsl(var(--p) / var(--un-text-opacity))}.link-secondary{--un-text-opacity:1;color:hsl(var(--s) / var(--un-text-opacity))}.link-accent{--un-text-opacity:1;color:hsl(var(--a) / var(--un-text-opacity))}.link-neutral{--un-text-opacity:1;color:hsl(var(--n) / var(--un-text-opacity))}.link-success{--un-text-opacity:1;color:hsl(var(--su) / var(--un-text-opacity))}.link-info{--un-text-opacity:1;color:hsl(var(--in) / var(--un-text-opacity))}.link-warning{--un-text-opacity:1;color:hsl(var(--wa) / var(--un-text-opacity))}.link-error{--un-text-opacity:1;color:hsl(var(--er) / var(--un-text-opacity))}.link:focus{outline:2px solid transparent;outline-offset:2px}.link:focus-visible{outline:2px solid currentColor;outline-offset:2px}.label a:hover{--un-text-opacity:1;color:hsl(var(--bc) / var(--un-text-opacity))}.label{display:flex;-webkit-user-select:none;-moz-user-select:none;user-select:none;align-items:center;justify-content:space-between;padding-left:0.25rem;padding-right:0.25rem;padding-top:0.5rem;padding-bottom:0.5rem}.menu li>*:not(ul):not(.menu-title):not(details):active,.menu li>*:not(ul):not(.menu-title):not(details).active,.menu li>details>summary:active{--un-bg-opacity:1;background-color:hsl(var(--n) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--nc) / var(--un-text-opacity))}:where(.menu li:not(.menu-title):not(.disabled)>*:not(ul):not(details):not(.menu-title)):not(.active):hover,:where(.menu li:not(.menu-title):not(.disabled)>details>summary:not(.menu-title)):not(.active):hover{cursor:pointer;background-color:hsl(var(--bc) / 0.1);--un-text-opacity:1;color:hsl(var(--bc) / var(--un-text-opacity));outline:2px solid transparent;outline-offset:2px}.menu{display:flex;flex-direction:column;flex-wrap:wrap;font-size:0.875rem;line-height:1.25rem;padding:0.5rem}.menu:where(li ul){position:relative;white-space:nowrap;margin-left:1rem;padding-left:0.5rem}.menu:where(li:not(.menu-title)>*:not(ul):not(details):not(.menu-title)),.menu:where(li:not(.menu-title)>details>summary:not(.menu-title)){display:grid;grid-auto-flow:column;align-content:flex-start;align-items:center;gap:0.5rem;grid-auto-columns:minmax(auto,max-content) auto max-content;-webkit-user-select:none;-moz-user-select:none;user-select:none}.menu li.disabled{cursor:not-allowed;-webkit-user-select:none;-moz-user-select:none;user-select:none;color:hsl(var(--bc) / 0.3)}.menu:where(li>.menu-dropdown:not(.menu-dropdown-show)){display:none}:where(.menu li){position:relative;display:flex;flex-shrink:0;flex-direction:column;flex-wrap:wrap;align-items:stretch}:where(.menu li) .badge{justify-self:end}:where(.menu li:empty){background-color:hsl(var(--bc) / 0.1);margin:0.5rem 1rem;height:1px}.menu:where(li ul):before{position:absolute;bottom:0.75rem;left:0px;top:0.75rem;width:1px;background-color:hsl(var(--bc) / 0.1);content:""}.menu:where(li:not(.menu-title)>*:not(ul):not(details):not(.menu-title)),.menu:where(li:not(.menu-title)>details>summary:not(.menu-title)){padding-left:1rem;padding-right:1rem;padding-top:0.5rem;padding-bottom:0.5rem;text-align:left;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-timing-function:cubic-bezier(0,0,0.2,1);transition-duration:200ms;border-radius:var(--rounded-btn,0.5rem);text-wrap:balance}:where(.menu li:not(.menu-title):not(.disabled)>*:not(ul):not(details):not(.menu-title)):not(summary):not(.active).focus,:where(.menu li:not(.menu-title):not(.disabled)>*:not(ul):not(details):not(.menu-title)):not(summary):not(.active):focus,:where(.menu li:not(.menu-title):not(.disabled)>*:not(ul):not(details):not(.menu-title)):is(summary):not(.active):focus-visible,:where(.menu li:not(.menu-title):not(.disabled)>details>summary:not(.menu-title)):not(summary):not(.active).focus,:where(.menu li:not(.menu-title):not(.disabled)>details>summary:not(.menu-title)):not(summary):not(.active):focus,:where(.menu li:not(.menu-title):not(.disabled)>details>summary:not(.menu-title)):is(summary):not(.active):focus-visible{cursor:pointer;background-color:hsl(var(--bc) / 0.1);--un-text-opacity:1;color:hsl(var(--bc) / var(--un-text-opacity));outline:2px solid transparent;outline-offset:2px}.menu li>*:not(ul):not(.menu-title):not(details):active,.menu li>*:not(ul):not(.menu-title):not(details).active,.menu li>details>summary:active{--un-bg-opacity:1;background-color:hsl(var(--n) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--nc) / var(--un-text-opacity))}.menu:where(li>details>summary)::-webkit-details-marker{display:none}.menu:where(li>details>summary):after,.menu:where(li>.menu-dropdown-toggle):after{justify-self:end;display:block;margin-top:-0.5rem;height:0.5rem;width:0.5rem;transform:rotate(45deg);transition-property:transform,margin-top;transition-duration:0.3s;transition-timing-function:cubic-bezier(0.4,0,0.2,1);content:"";transform-origin:75% 75%;box-shadow:2px 2px;pointer-events:none}.menu:where(li>details[open]>summary):after,.menu:where(li>.menu-dropdown-toggle.menu-dropdown-show):after{transform:rotate(225deg);margin-top:0}.tab:hover{--un-text-opacity:1}.tab[disabled],.tab[disabled]:hover{cursor:not-allowed;color:hsl(var(--bc) / var(--un-text-opacity));--un-text-opacity:0.2}.tab{position:relative;display:inline-flex;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;flex-wrap:wrap;align-items:center;justify-content:center;text-align:center;height:2rem;font-size:0.875rem;line-height:1.25rem;line-height:2;--tab-padding:1rem;--un-text-opacity:0.5;--tab-color:hsl(var(--bc) / var(--un-text-opacity,1));--tab-bg:hsl(var(--b1) / var(--un-bg-opacity,1));--tab-border-color:hsl(var(--b3) / var(--un-bg-opacity,1));color:var(--tab-color);padding-left:var(--tab-padding,1rem);padding-right:var(--tab-padding,1rem)}.tab.tab-active:not(.tab-disabled):not([disabled]){border-color:hsl(var(--bc) / var(--un-border-opacity));--un-border-opacity:1;--un-text-opacity:1}.tab:focus{outline:2px solid transparent;outline-offset:2px}.tab:focus-visible{outline:2px solid currentColor;outline-offset:-3px}.tab:focus-visible.tab-lifted{border-bottom-right-radius:var(--tab-radius,0.5rem);border-bottom-left-radius:var(--tab-radius,0.5rem)}.btn-circle{height:3rem;width:3rem;border-radius:9999px;padding:0px}.btn-circle:where(.btn-xs){height:1.5rem;width:1.5rem;border-radius:9999px;padding:0px}.btn-circle:where(.btn-sm){height:2rem;width:2rem;border-radius:9999px;padding:0px}.btn-circle:where(.btn-md){height:3rem;width:3rem;border-radius:9999px;padding:0px}.btn-circle:where(.btn-lg){height:4rem;width:4rem;border-radius:9999px;padding:0px}.card{position:relative;display:flex;flex-direction:column;border-radius:var(--rounded-box,1rem)}.card:focus{outline:2px solid transparent;outline-offset:2px}.card figure{display:flex;align-items:center;justify-content:center}.card.image-full{display:grid}.card.image-full:before{position:relative;content:"";z-index:10;--un-bg-opacity:1;background-color:hsl(var(--n) / var(--un-bg-opacity));opacity:0.75;border-radius:var(--rounded-box,1rem)}.card.image-full:before,.card.image-full>*{grid-column-start:1;grid-row-start:1}.card.image-full>figure img{height:100%;-o-object-fit:cover;object-fit:cover}.card.image-full>.card-body{position:relative;z-index:20;--un-text-opacity:1;color:hsl(var(--nc) / var(--un-text-opacity))}.card:where(figure:first-child){overflow:hidden;border-start-start-radius:inherit;border-start-end-radius:inherit;border-end-start-radius:unset;border-end-end-radius:unset}.card:where(figure:last-child){overflow:hidden;border-start-start-radius:unset;border-start-end-radius:unset;border-end-start-radius:inherit;border-end-end-radius:inherit}.card:focus-visible{outline:2px solid currentColor;outline-offset:2px}.card.bordered{border-width:1px;--un-border-opacity:1;border-color:hsl(var(--b2) / var(--un-border-opacity))}.card.compact .card-body{padding:1rem;font-size:0.875rem;line-height:1.25rem}.card.image-full:where(figure){overflow:hidden;border-radius:inherit}.chat{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));-moz-column-gap:0.75rem;column-gap:0.75rem;padding-top:0.25rem;padding-bottom:0.25rem}.checkbox{flex-shrink:0;--chkbg:var(--bc);--chkfg:var(--b1);height:1.5rem;width:1.5rem;cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-width:1px;border-color:hsl(var(--bc) / var(--un-border-opacity));--un-border-opacity:0.2;border-radius:var(--rounded-btn,0.5rem)}.checkbox:focus-visible{outline-style:solid;outline-width:2px;outline-offset:2px;outline-color:hsl(var(--bc) / 1)}.checkbox:checked,.checkbox[checked="true"],.checkbox[aria-checked="true"]{--un-bg-opacity:1;background-color:hsl(var(--bc) / var(--un-bg-opacity));background-repeat:no-repeat;animation:checkmark var(--animation-input,0.2s) ease-out;background-image:linear-gradient(-45deg,transparent 65%,hsl(var(--chkbg)) 65.99%),linear-gradient(45deg,transparent 75%,hsl(var(--chkbg)) 75.99%),linear-gradient(-45deg,hsl(var(--chkbg)) 40%,transparent 40.99%),linear-gradient( 45deg,hsl(var(--chkbg)) 30%,hsl(var(--chkfg)) 30.99%,hsl(var(--chkfg)) 40%,transparent 40.99% ),linear-gradient(-45deg,hsl(var(--chkfg)) 50%,hsl(var(--chkbg)) 50.99%)}.checkbox:indeterminate{--un-bg-opacity:1;background-color:hsl(var(--bc) / var(--un-bg-opacity));background-repeat:no-repeat;animation:checkmark var(--animation-input,0.2s) ease-out;background-image:linear-gradient(90deg,transparent 80%,hsl(var(--chkbg)) 80%),linear-gradient(-90deg,transparent 80%,hsl(var(--chkbg)) 80%),linear-gradient( 0deg,hsl(var(--chkbg)) 43%,hsl(var(--chkfg)) 43%,hsl(var(--chkfg)) 57%,hsl(var(--chkbg)) 57% )}.checkbox:disabled{cursor:not-allowed;border-color:transparent;--un-bg-opacity:1;background-color:hsl(var(--bc) / var(--un-bg-opacity));opacity:0.2}[dir="rtl"] .checkbox:checked,[dir="rtl"] .checkbox[checked="true"],[dir="rtl"] .checkbox[aria-checked="true"]{background-image:linear-gradient(45deg,transparent 65%,hsl(var(--chkbg)) 65.99%),linear-gradient(-45deg,transparent 75%,hsl(var(--chkbg)) 75.99%),linear-gradient(45deg,hsl(var(--chkbg)) 40%,transparent 40.99%),linear-gradient( -45deg,hsl(var(--chkbg)) 30%,hsl(var(--chkfg)) 30.99%,hsl(var(--chkfg)) 40%,transparent 40.99% ),linear-gradient(45deg,hsl(var(--chkfg)) 50%,hsl(var(--chkbg)) 50.99%)}.collapse:not(td):not(tr):not(colgroup){visibility:visible}.collapse{position:relative;display:grid;overflow:hidden;grid-template-rows:auto 0fr;transition:grid-template-rows 0.2s;width:100%;border-radius:var(--rounded-box,1rem)}.collapse>input[type="checkbox"],.collapse>input[type="radio"]{-webkit-appearance:none;-moz-appearance:none;appearance:none;opacity:0}.collapse[open],.collapse-open,.collapse:focus:not(.collapse-close){grid-template-rows:auto 1fr}.collapse:not(.collapse-close):has(>input[type="checkbox"]:checked),.collapse:not(.collapse-close):has(>input[type="radio"]:checked){grid-template-rows:auto 1fr}.collapse[open]>.collapse-content,.collapse-open>.collapse-content,.collapse:focus:not(.collapse-close)>.collapse-content,.collapse:not(.collapse-close)>input[type="checkbox"]:checked~.collapse-content,.collapse:not(.collapse-close)>input[type="radio"]:checked~.collapse-content{visibility:visible;min-height:-moz-fit-content;min-height:fit-content}.collapse:focus-visible{outline-style:solid;outline-width:2px;outline-offset:2px;outline-color:hsl(var(--bc) / 1)}.collapse:has(.collapse-title:focus-visible),.collapse:has(>input[type="checkbox"]:focus-visible),.collapse:has(>input[type="radio"]:focus-visible){outline-style:solid;outline-width:2px;outline-offset:2px;outline-color:hsl(var(--bc) / 1)}.collapse:not(.collapse-open):not(.collapse-close)>input[type="checkbox"],.collapse:not(.collapse-open):not(.collapse-close)>input[type="radio"]:not(:checked),.collapse:not(.collapse-open):not(.collapse-close)>.collapse-title{cursor:pointer}.collapse:focus:not(.collapse-open):not(.collapse-close):not(.collapse[open])>.collapse-title{cursor:unset}:where(.collapse>input[type="checkbox"]),:where(.collapse>input[type="radio"]){z-index:1}.collapse[open]>:where(.collapse-content),.collapse-open>:where(.collapse-content),.collapse:focus:not(.collapse-close)>:where(.collapse-content),.collapse:not(.collapse-close)>:where(input[type="checkbox"]:checked~.collapse-content),.collapse:not(.collapse-close)>:where(input[type="radio"]:checked~.collapse-content){padding-bottom:1rem;transition:padding 0.2s ease-out,background-color 0.2s ease-out}.collapse[open].collapse-arrow>.collapse-title:after,.collapse-open.collapse-arrow>.collapse-title:after,.collapse-arrow:focus:not(.collapse-close)>.collapse-title:after,.collapse-arrow:not(.collapse-close)>input[type="checkbox"]:checked~.collapse-title:after,.collapse-arrow:not(.collapse-close)>input[type="radio"]:checked~.collapse-title:after{--un-translate-y:-50%;--un-rotate:225deg;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}[dir="rtl"] .collapse[open].collapse-arrow>.collapse-title:after,[dir="rtl"] .collapse-open.collapse-arrow>.collapse-title:after,[dir="rtl"] .collapse-arrow:focus:not(.collapse-close) .collapse-title:after,[dir="rtl"] .collapse-arrow:not(.collapse-close) input[type="checkbox"]:checked~.collapse-title:after{--un-rotate:135deg}.collapse[open].collapse-plus>.collapse-title:after,.collapse-open.collapse-plus>.collapse-title:after,.collapse-plus:focus:not(.collapse-close)>.collapse-title:after,.collapse-plus:not(.collapse-close)>input[type="checkbox"]:checked~.collapse-title:after,.collapse-plus:not(.collapse-close)>input[type="radio"]:checked~.collapse-title:after{content:"−"}:root .countdown{line-height:1em}.countdown{display:inline-flex}.countdown>*{height:1em;display:inline-block;overflow-y:hidden}.countdown>*:before{position:relative;content:"00\A 01\A 02\A 03\A 04\A 05\A 06\A 07\A 08\A 09\A 10\A 11\A 12\A 13\A 14\A 15\A 16\A 17\A 18\A 19\A 20\A 21\A 22\A 23\A 24\A 25\A 26\A 27\A 28\A 29\A 30\A 31\A 32\A 33\A 34\A 35\A 36\A 37\A 38\A 39\A 40\A 41\A 42\A 43\A 44\A 45\A 46\A 47\A 48\A 49\A 50\A 51\A 52\A 53\A 54\A 55\A 56\A 57\A 58\A 59\A 60\A 61\A 62\A 63\A 64\A 65\A 66\A 67\A 68\A 69\A 70\A 71\A 72\A 73\A 74\A 75\A 76\A 77\A 78\A 79\A 80\A 81\A 82\A 83\A 84\A 85\A 86\A 87\A 88\A 89\A 90\A 91\A 92\A 93\A 94\A 95\A 96\A 97\A 98\A 99\A";white-space:pre;top:calc(var(--value) * -1em);text-align:center;transition:all 1s cubic-bezier(1,0,0,1)}.divider{display:flex;flex-direction:row;align-items:center;align-self:stretch;margin-top:1rem;margin-bottom:1rem;height:1rem;white-space:nowrap}.divider:before,.divider:after{content:"";flex-grow:1;height:0.125rem;width:100%}.divider:before{background-color:hsl(var(--bc) / var(--un-bg-opacity));--un-bg-opacity:0.1}.divider:after{background-color:hsl(var(--bc) / var(--un-bg-opacity));--un-bg-opacity:0.1}.divider:not(:empty){gap:1rem}.dropdown{position:relative;display:inline-block}.dropdown>*:not(summary):focus{outline:2px solid transparent;outline-offset:2px}.dropdown .dropdown-content{position:absolute}.dropdown:is(:not(details)) .dropdown-content{visibility:hidden;opacity:0;transform-origin:top;--un-scale-x:.95;--un-scale-y:.95;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y));transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-timing-function:cubic-bezier(0,0,0.2,1);transition-duration:200ms}.dropdown.dropdown-open .dropdown-content,.dropdown:not(.dropdown-hover):focus .dropdown-content,.dropdown:focus-within .dropdown-content{visibility:visible;opacity:1}.dropdown.dropdown-hover:hover .dropdown-content{visibility:visible;opacity:1}.dropdown.dropdown-hover:hover .dropdown-content{--un-scale-x:1;--un-scale-y:1;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.dropdown:is(details) summary::-webkit-details-marker{display:none}.dropdown.dropdown-open .dropdown-content,.dropdown:focus .dropdown-content,.dropdown:focus-within .dropdown-content{--un-scale-x:1;--un-scale-y:1;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.dropdown-end .dropdown-content{right:0px}.dropdown-end.dropdown-right .dropdown-content{bottom:0px;top:auto}.dropdown-end.dropdown-left .dropdown-content{bottom:0px;top:auto}.btn-primary:hover{--un-border-opacity:1;border-color:hsl(var(--pf) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--pf) / var(--un-bg-opacity))}.btn-primary{--un-border-opacity:1;border-color:hsl(var(--p) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--p) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--pc) / var(--un-text-opacity));outline-color:hsl(var(--p) / 1)}.btn-primary.btn-active{--un-border-opacity:1;border-color:hsl(var(--pf) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--pf) / var(--un-bg-opacity))}.btn-secondary:hover{--un-border-opacity:1;border-color:hsl(var(--sf) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--sf) / var(--un-bg-opacity))}.btn-secondary{--un-border-opacity:1;border-color:hsl(var(--s) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--s) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--sc) / var(--un-text-opacity));outline-color:hsl(var(--s) / 1)}.btn-secondary.btn-active{--un-border-opacity:1;border-color:hsl(var(--sf) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--sf) / var(--un-bg-opacity))}.btn-neutral:hover{--un-border-opacity:1;border-color:hsl(var(--nf) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--nf) / var(--un-bg-opacity))}.btn-neutral{--un-border-opacity:1;border-color:hsl(var(--n) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--n) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--nc) / var(--un-text-opacity));outline-color:hsl(var(--n) / 1)}.btn-neutral.btn-active{--un-border-opacity:1;border-color:hsl(var(--nf) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--nf) / var(--un-bg-opacity))}.btn-error:hover{--un-border-opacity:1;border-color:hsl(var(--er) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--er) / var(--un-bg-opacity))}.btn-error{--un-border-opacity:1;border-color:hsl(var(--er) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--er) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--erc) / var(--un-text-opacity));outline-color:hsl(var(--er) / 1)}.btn-error.btn-active{--un-border-opacity:1;border-color:hsl(var(--er) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--er) / var(--un-bg-opacity))}.btn-ghost:hover{--un-border-opacity:0;background-color:hsl(var(--bc) / var(--un-bg-opacity));--un-bg-opacity:0.2}.btn-ghost{border-width:1px;border-color:transparent;background-color:transparent;color:currentColor;--un-shadow:0 0 #0000;--un-shadow-colored:0 0 #0000;box-shadow:var(--un-ring-offset-shadow,0 0 #0000),var(--un-ring-shadow,0 0 #0000),var(--un-shadow);outline-color:currentColor}.btn-ghost.btn-active{--un-border-opacity:0;background-color:hsl(var(--bc) / var(--un-bg-opacity));--un-bg-opacity:0.2}.btn-outline:hover{--un-border-opacity:1;border-color:hsl(var(--bc) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--bc) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--b1) / var(--un-text-opacity))}.btn-outline.btn-primary:hover{--un-border-opacity:1;border-color:hsl(var(--pf) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--pf) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--pc) / var(--un-text-opacity))}.btn-outline.btn-secondary:hover{--un-border-opacity:1;border-color:hsl(var(--sf) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--sf) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--sc) / var(--un-text-opacity))}.btn-outline.btn-accent:hover{--un-border-opacity:1;border-color:hsl(var(--af) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--af) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--ac) / var(--un-text-opacity))}.btn-outline.btn-success:hover{--un-border-opacity:1;border-color:hsl(var(--su) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--su) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--suc) / var(--un-text-opacity))}.btn-outline.btn-info:hover{--un-border-opacity:1;border-color:hsl(var(--in) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--in) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--inc) / var(--un-text-opacity))}.btn-outline.btn-warning:hover{--un-border-opacity:1;border-color:hsl(var(--wa) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--wa) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--wac) / var(--un-text-opacity))}.btn-outline.btn-error:hover{--un-border-opacity:1;border-color:hsl(var(--er) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--er) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--erc) / var(--un-text-opacity))}.btn-outline{border-color:currentColor;background-color:transparent;--un-text-opacity:1;color:hsl(var(--bc) / var(--un-text-opacity));--un-shadow:0 0 #0000;--un-shadow-colored:0 0 #0000;box-shadow:var(--un-ring-offset-shadow,0 0 #0000),var(--un-ring-shadow,0 0 #0000),var(--un-shadow)}.btn-outline.btn-active{--un-border-opacity:1;border-color:hsl(var(--bc) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--bc) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--b1) / var(--un-text-opacity))}.btn-outline.btn-primary{--un-text-opacity:1;color:hsl(var(--p) / var(--un-text-opacity))}.btn-outline.btn-primary.btn-active{--un-border-opacity:1;border-color:hsl(var(--pf) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--pf) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--pc) / var(--un-text-opacity))}.btn-outline.btn-secondary{--un-text-opacity:1;color:hsl(var(--s) / var(--un-text-opacity))}.btn-outline.btn-secondary.btn-active{--un-border-opacity:1;border-color:hsl(var(--sf) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--sf) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--sc) / var(--un-text-opacity))}.btn-outline.btn-accent{--un-text-opacity:1;color:hsl(var(--a) / var(--un-text-opacity))}.btn-outline.btn-accent.btn-active{--un-border-opacity:1;border-color:hsl(var(--af) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--af) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--ac) / var(--un-text-opacity))}.btn-outline.btn-success{--un-text-opacity:1;color:hsl(var(--su) / var(--un-text-opacity))}.btn-outline.btn-success.btn-active{--un-border-opacity:1;border-color:hsl(var(--su) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--su) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--suc) / var(--un-text-opacity))}.btn-outline.btn-info{--un-text-opacity:1;color:hsl(var(--in) / var(--un-text-opacity))}.btn-outline.btn-info.btn-active{--un-border-opacity:1;border-color:hsl(var(--in) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--in) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--inc) / var(--un-text-opacity))}.btn-outline.btn-warning{--un-text-opacity:1;color:hsl(var(--wa) / var(--un-text-opacity))}.btn-outline.btn-warning.btn-active{--un-border-opacity:1;border-color:hsl(var(--wa) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--wa) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--wac) / var(--un-text-opacity))}.btn-outline.btn-error{--un-text-opacity:1;color:hsl(var(--er) / var(--un-text-opacity))}.btn-outline.btn-error.btn-active{--un-border-opacity:1;border-color:hsl(var(--er) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--er) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--erc) / var(--un-text-opacity))}.footer{display:grid;width:100%;grid-auto-flow:row;place-items:start;-moz-column-gap:1rem;column-gap:1rem;row-gap:2.5rem;font-size:0.875rem;line-height:1.25rem}.footer>*{display:grid;place-items:start;gap:0.5rem}.footer{grid-auto-flow:column}.hero{display:grid;width:100%;place-items:center;background-size:cover;background-position:center}.hero>*{grid-column-start:1;grid-row-start:1}.indicator{position:relative;display:inline-flex;width:-moz-max-content;width:max-content}.indicator:where(.indicator-item){z-index:1;position:absolute;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y));white-space:nowrap}.indicator:where(.indicator-item){bottom:auto;left:auto;right:0px;top:0px;--un-translate-y:-50%;--un-translate-x:50%;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.indicator:where(.indicator-item.indicator-start){left:0px;right:auto;--un-translate-x:-50%;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.indicator:where(.indicator-item.indicator-center){left:50%;right:50%;--un-translate-x:-50%;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.indicator:where(.indicator-item.indicator-end){left:auto;right:0px;--un-translate-x:50%;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.indicator:where(.indicator-item.indicator-bottom){bottom:0px;top:auto;--un-translate-y:50%;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.indicator:where(.indicator-item.indicator-middle){bottom:50%;top:50%;--un-translate-y:-50%;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.indicator:where(.indicator-item.indicator-top){bottom:auto;top:0px;--un-translate-y:-50%;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.input{flex-shrink:1;height:3rem;padding-left:1rem;padding-right:1rem;font-size:0.875rem;font-size:1rem;line-height:1.25rem;line-height:2;line-height:1.5rem;border-width:1px;border-color:hsl(var(--bc) / var(--un-border-opacity));--un-border-opacity:0;--un-bg-opacity:1;background-color:hsl(var(--b1) / var(--un-bg-opacity));border-radius:var(--rounded-btn,0.5rem)}.input input:focus{outline:2px solid transparent;outline-offset:2px}.input[list]::-webkit-calendar-picker-indicator{line-height:1em}.input:focus,.input:focus-within{outline-style:solid;outline-width:2px;outline-offset:2px;outline-color:hsl(var(--bc) / 0.2)}.kbd{display:inline-flex;align-items:center;justify-content:center;border-width:1px;border-color:hsl(var(--bc) / var(--un-border-opacity));--un-border-opacity:0.2;--un-bg-opacity:1;background-color:hsl(var(--b2) / var(--un-bg-opacity));padding-left:0.5rem;padding-right:0.5rem;border-radius:var(--rounded-btn,0.5rem);border-bottom-width:2px;min-height:2.2em;min-width:2.2em}.modal{pointer-events:none;position:fixed;inset:0px;margin:0px;display:grid;height:100%;max-height:none;width:100%;max-width:none;justify-items:center;padding:0px;opacity:0;overscroll-behavior:contain;overscroll-behavior:contain;z-index:999;background-color:transparent;color:inherit;transition-duration:200ms;transition-timing-function:cubic-bezier(0,0,0.2,1);transition-property:transform,opacity,visibility;overflow-y:hidden}:where(.modal){align-items:center}.modal-open,.modal:target,.modal-toggle:checked+.modal,.modal[open]{pointer-events:auto;visibility:visible;opacity:1}:root:has(:is(.modal-open,.modal:target,.modal-toggle:checked+.modal,.modal[open])){overflow:hidden}.modal:not(dialog:not(.modal-open)),.modal::backdrop{background-color:rgba(0,0,0,0.3);animation:modal-pop 0.2s ease-out}.modal-open .modal-box,.modal-toggle:checked+.modal .modal-box,.modal:target .modal-box,.modal[open] .modal-box{--un-translate-y:0px;--un-scale-x:1;--un-scale-y:1;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.progress{position:relative;width:100%;-webkit-appearance:none;-moz-appearance:none;appearance:none;overflow:hidden;height:0.5rem;background-color:hsl(var(--bc) / 0.2);border-radius:var(--rounded-box,1rem)}.progress::-moz-progress-bar{--un-bg-opacity:1;background-color:hsl(var(--bc) / var(--un-bg-opacity));border-radius:var(--rounded-box,1rem)}.progress:indeterminate{--progress-color:hsl(var(--bc));background-image:repeating-linear-gradient( 90deg,var(--progress-color) -1%,var(--progress-color) 10%,transparent 10%,transparent 90% );background-size:200%;background-position-x:15%;animation:progress-loading 5s ease-in-out infinite}.progress::-webkit-progress-bar{background-color:transparent;border-radius:var(--rounded-box,1rem)}.progress::-webkit-progress-value{--un-bg-opacity:1;background-color:hsl(var(--bc) / var(--un-bg-opacity));border-radius:var(--rounded-box,1rem)}.progress:indeterminate::-moz-progress-bar{background-color:transparent;background-image:repeating-linear-gradient( 90deg,var(--progress-color) -1%,var(--progress-color) 10%,transparent 10%,transparent 90% );background-size:200%;background-position-x:15%;animation:progress-loading 5s ease-in-out infinite}.radio{flex-shrink:0;--chkbg:var(--bc);height:1.5rem;width:1.5rem;cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:9999px;border-width:1px;border-color:hsl(var(--bc) / var(--un-border-opacity));--un-border-opacity:0.2}.radio:focus-visible{outline-style:solid;outline-width:2px;outline-offset:2px;outline-color:hsl(var(--bc) / 1)}.radio:checked,.radio[aria-checked="true"]{--un-bg-opacity:1;background-color:hsl(var(--bc) / var(--un-bg-opacity));animation:radiomark var(--animation-input,0.2s) ease-out;box-shadow:0 0 0 4px hsl(var(--b1)) inset,0 0 0 4px hsl(var(--b1)) inset}.radio:disabled{cursor:not-allowed;opacity:0.2}.range{height:1.5rem;width:100%;cursor:pointer;-moz-appearance:none;appearance:none;-webkit-appearance:none;--range-shdw:var(--bc);overflow:hidden;background-color:transparent;border-radius:var(--rounded-box,1rem)}.range:focus{outline:none}.range:focus-visible::-webkit-slider-thumb{--focus-shadow:0 0 0 6px hsl(var(--b1)) inset,0 0 0 2rem hsl(var(--range-shdw)) inset}.range:focus-visible::-moz-range-thumb{--focus-shadow:0 0 0 6px hsl(var(--b1)) inset,0 0 0 2rem hsl(var(--range-shdw)) inset}.range::-webkit-slider-runnable-track{height:0.5rem;width:100%;background-color:hsl(var(--bc) / 0.1);border-radius:var(--rounded-box,1rem)}.range::-moz-range-track{height:0.5rem;width:100%;background-color:hsl(var(--bc) / 0.1);border-radius:var(--rounded-box,1rem)}.range::-webkit-slider-thumb{position:relative;height:1.5rem;width:1.5rem;border-style:none;--un-bg-opacity:1;background-color:hsl(var(--b1) / var(--un-bg-opacity));border-radius:var(--rounded-box,1rem);appearance:none;-webkit-appearance:none;top:50%;color:hsl(var(--range-shdw));transform:translateY(-50%);--filler-size:100rem;--filler-offset:0.6rem;box-shadow:0 0 0 3px hsl(var(--range-shdw)) inset,var(--focus-shadow,0 0),calc(var(--filler-size) * -1 - var(--filler-offset)) 0 0 var(--filler-size)}.range::-moz-range-thumb{position:relative;height:1.5rem;width:1.5rem;border-style:none;--un-bg-opacity:1;background-color:hsl(var(--b1) / var(--un-bg-opacity));border-radius:var(--rounded-box,1rem);top:50%;color:hsl(var(--range-shdw));--filler-size:100rem;--filler-offset:0.5rem;box-shadow:0 0 0 3px hsl(var(--range-shdw)) inset,var(--focus-shadow,0 0),calc(var(--filler-size) * -1 - var(--filler-offset)) 0 0 var(--filler-size)}.select{display:inline-flex;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;-webkit-appearance:none;-moz-appearance:none;appearance:none;height:3rem;padding-left:1rem;padding-right:2.5rem;padding-right:2.5rem;font-size:0.875rem;line-height:1.25rem;line-height:2;min-height:3rem;border-width:1px;border-color:hsl(var(--bc) / var(--un-border-opacity));--un-border-opacity:0;--un-bg-opacity:1;background-color:hsl(var(--b1) / var(--un-bg-opacity));border-radius:var(--rounded-btn,0.5rem);background-image:linear-gradient(45deg,transparent 50%,currentColor 50%),linear-gradient(135deg,currentColor 50%,transparent 50%);background-position:calc(100% - 20px) calc(1px+50%),calc(100% - 16.1px) calc(1px+50%);background-size:4px 4px,4px 4px;background-repeat:no-repeat}.select[multiple]{height:auto}.select:focus{outline-style:solid;outline-width:2px;outline-offset:2px;outline-color:hsl(var(--bc) / 0.2)}[dir="rtl"] .select{background-position:calc(0%+12px) calc(1px+50%),calc(0%+16px) calc(1px+50%)}.stack{display:inline-grid;place-items:center;align-items:flex-end}.stack>*{grid-column-start:1;grid-row-start:1;transform:translateY(10%) scale(0.9);z-index:1;width:100%;opacity:0.6}.stack>*:nth-child(2){transform:translateY(5%) scale(0.95);z-index:2;opacity:0.8}.stack>*:nth-child(1){transform:translateY(0) scale(1);z-index:3;opacity:1}.stats{display:inline-grid;--un-bg-opacity:1;background-color:hsl(var(--b1) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--bc) / var(--un-text-opacity));border-radius:var(--rounded-box,1rem)}:where(.stats){grid-auto-flow:column;overflow-x:auto}:where(.stats)>:not([hidden])~:not([hidden]){--un-divide-x-reverse:0;border-right-width:calc(1px * var(--un-divide-x-reverse));border-left-width:calc(1px * calc(1 - var(--un-divide-x-reverse)));--un-divide-y-reverse:0;border-top-width:calc(0px * calc(1 - var(--un-divide-y-reverse)));border-bottom-width:calc(0px * var(--un-divide-y-reverse))}.stat{display:inline-grid;width:100%;grid-template-columns:repeat(1,1fr);-moz-column-gap:1rem;column-gap:1rem;border-color:hsl(var(--bc) / var(--un-border-opacity));--un-border-opacity:0.1;padding-left:1.5rem;padding-right:1.5rem;padding-top:1rem;padding-bottom:1rem}.steps{display:inline-grid;grid-auto-flow:column;overflow:hidden;overflow-x:auto;counter-reset:step;grid-auto-columns:1fr}.steps .step{display:grid;grid-template-columns:repeat(1,minmax(0,1fr));grid-template-columns:auto;grid-template-rows:repeat(2,minmax(0,1fr));grid-template-rows:40px 1fr;place-items:center;text-align:center;min-width:4rem}.steps .step:before{top:0px;grid-column-start:1;grid-row-start:1;height:0.5rem;width:100%;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y));--un-bg-opacity:1;background-color:hsl(var(--b3) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--bc) / var(--un-text-opacity));content:"";margin-left:-100%}.steps .step:after{content:counter(step);counter-increment:step;z-index:1;position:relative;grid-column-start:1;grid-row-start:1;display:grid;height:2rem;width:2rem;place-items:center;place-self:center;border-radius:9999px;--un-bg-opacity:1;background-color:hsl(var(--b3) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--bc) / var(--un-text-opacity))}.steps .step:first-child:before{content:none}.steps .step[data-content]:after{content:attr(data-content)}.steps .step-neutral+.step-neutral:before,.steps .step-neutral:after{--un-bg-opacity:1;background-color:hsl(var(--n) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--nc) / var(--un-text-opacity))}.steps .step-primary+.step-primary:before,.steps .step-primary:after{--un-bg-opacity:1;background-color:hsl(var(--p) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--pc) / var(--un-text-opacity))}.steps .step-secondary+.step-secondary:before,.steps .step-secondary:after{--un-bg-opacity:1;background-color:hsl(var(--s) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--sc) / var(--un-text-opacity))}.steps .step-accent+.step-accent:before,.steps .step-accent:after{--un-bg-opacity:1;background-color:hsl(var(--a) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--ac) / var(--un-text-opacity))}.steps .step-info+.step-info:before{--un-bg-opacity:1;background-color:hsl(var(--in) / var(--un-bg-opacity))}.steps .step-info:after{--un-bg-opacity:1;background-color:hsl(var(--in) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--inc) / var(--un-text-opacity))}.steps .step-success+.step-success:before{--un-bg-opacity:1;background-color:hsl(var(--su) / var(--un-bg-opacity))}.steps .step-success:after{--un-bg-opacity:1;background-color:hsl(var(--su) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--suc) / var(--un-text-opacity))}.steps .step-warning+.step-warning:before{--un-bg-opacity:1;background-color:hsl(var(--wa) / var(--un-bg-opacity))}.steps .step-warning:after{--un-bg-opacity:1;background-color:hsl(var(--wa) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--wac) / var(--un-text-opacity))}.steps .step-error+.step-error:before{--un-bg-opacity:1;background-color:hsl(var(--er) / var(--un-bg-opacity))}.steps .step-error:after{--un-bg-opacity:1;background-color:hsl(var(--er) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--erc) / var(--un-text-opacity))}.tabs{display:flex;flex-wrap:wrap;align-items:flex-end}.textarea{flex-shrink:1;min-height:3rem;padding-left:1rem;padding-right:1rem;padding-top:0.5rem;padding-bottom:0.5rem;font-size:0.875rem;line-height:1.25rem;line-height:2;border-width:1px;border-color:hsl(var(--bc) / var(--un-border-opacity));--un-border-opacity:0;--un-bg-opacity:1;background-color:hsl(var(--b1) / var(--un-bg-opacity));border-radius:var(--rounded-btn,0.5rem)}.textarea:focus{outline-style:solid;outline-width:2px;outline-offset:2px;outline-color:hsl(var(--bc) / 0.2)}.toast{position:fixed;display:flex;min-width:-moz-fit-content;min-width:fit-content;flex-direction:column;white-space:nowrap;gap:0.5rem;padding:1rem}.toast>*{animation:toast-pop 0.25s ease-out}:where(.toast){bottom:0px;left:auto;right:0px;top:auto;--un-translate-x:0px;--un-translate-y:0px;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.toast:where(.toast-start){left:0px;right:auto;--un-translate-x:0px;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.toast:where(.toast-center){left:50%;right:50%;--un-translate-x:-50%;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.toast:where(.toast-end){left:auto;right:0px;--un-translate-x:0px;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.toast:where(.toast-bottom){bottom:0px;top:auto;--un-translate-y:0px;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.toast:where(.toast-middle){bottom:auto;top:50%;--un-translate-y:-50%;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.toast:where(.toast-top){bottom:auto;top:0px;--un-translate-y:0px;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.toggle{flex-shrink:0;--tglbg:hsl(var(--b1));--handleoffset:1.5rem;--handleoffsetcalculator:calc(var(--handleoffset) * -1);--togglehandleborder:0 0;height:1.5rem;width:3rem;cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-width:1px;border-color:hsl(var(--bc) / var(--un-border-opacity));--un-border-opacity:0.2;background-color:hsl(var(--bc) / var(--un-bg-opacity));--un-bg-opacity:0.5;border-radius:var(--rounded-badge,1.9rem);transition:background,box-shadow var(--animation-input,0.2s) ease-out;box-shadow:var(--handleoffsetcalculator) 0 0 2px var(--tglbg) inset,0 0 0 2px var(--tglbg) inset,var(--togglehandleborder)}[dir="rtl"] .toggle{--handleoffsetcalculator:calc(var(--handleoffset) * 1)}.toggle:focus-visible{outline-style:solid;outline-width:2px;outline-offset:2px;outline-color:hsl(var(--bc) / 0.2)}.toggle:checked,.toggle[checked="true"],.toggle[aria-checked="true"]{--handleoffsetcalculator:var(--handleoffset);--un-border-opacity:1;--un-bg-opacity:1}[dir="rtl"] .toggle:checked,[dir="rtl"] .toggle[checked="true"],[dir="rtl"] .toggle[aria-checked="true"]{--handleoffsetcalculator:calc(var(--handleoffset) * -1)}.toggle:indeterminate{--un-border-opacity:1;--un-bg-opacity:1;box-shadow:calc(var(--handleoffset) / 2) 0 0 2px var(--tglbg) inset,calc(var(--handleoffset) / -2) 0 0 2px var(--tglbg) inset,0 0 0 2px var(--tglbg) inset}[dir="rtl"] .toggle:indeterminate{box-shadow:calc(var(--handleoffset) / 2) 0 0 2px var(--tglbg) inset,calc(var(--handleoffset) / -2) 0 0 2px var(--tglbg) inset,0 0 0 2px var(--tglbg) inset}.toggle:disabled{cursor:not-allowed;--un-border-opacity:1;border-color:hsl(var(--bc) / var(--un-border-opacity));background-color:transparent;opacity:0.3;--togglehandleborder:0 0 0 3px hsl(var(--bc)) inset,var(--handleoffsetcalculator) 0 0 3px hsl(var(--bc)) inset}.badge-primary{--un-border-opacity:1;border-color:hsl(var(--p) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--p) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--pc) / var(--un-text-opacity))}.input-bordered{--un-border-opacity:0.2}.loading{pointer-events:none;display:inline-block;aspect-ratio:1 / 1;width:1.5rem;background-color:currentColor;-webkit-mask-size:100%;mask-size:100%;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-position:center;mask-position:center;-webkit-mask-image:url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E");mask-image:url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E")}.menu-title{padding-left:1rem;padding-right:1rem;padding-top:0.5rem;padding-bottom:0.5rem;font-size:0.875rem;line-height:1.25rem;font-weight:700;color:hsl(var(--bc) / 0.4)}.rounded-box{border-radius:var(--rounded-box,1rem)}.badge-sm{height:1rem;font-size:0.75rem;line-height:1rem;padding-left:0.438rem;padding-right:0.438rem}.btn-xs{height:1.5rem;padding-left:0.5rem;padding-right:0.5rem;min-height:1.5rem;font-size:0.75rem}.btn-sm{height:2rem;padding-left:0.75rem;padding-right:0.75rem;min-height:2rem;font-size:0.875rem}.input-sm{height:2rem;padding-left:0.75rem;padding-right:0.75rem;font-size:0.875rem;line-height:2rem}.toggle-sm{--handleoffset:0.75rem;height:1.25rem;width:2rem}.menu-sm:where(li:not(.menu-title)>*:not(ul):not(details):not(.menu-title)),.menu-sm:where(li:not(.menu-title)>details>summary:not(.menu-title)){padding-left:0.75rem;padding-right:0.75rem;padding-top:0.25rem;padding-bottom:0.25rem;font-size:0.875rem;line-height:1.25rem;border-radius:var(--rounded-btn,0.5rem)}.menu-sm .menu-title{padding-left:0.75rem;padding-right:0.75rem;padding-top:0.5rem;padding-bottom:0.5rem}@keyframes button-pop{0%{transform:scale(var(--btn-focus-scale,0.98))}40%{transform:scale(1.02)}100%{transform:scale(1)}}@keyframes checkmark{0%{background-position-y:5px}50%{background-position-y:-2px}100%{background-position-y:0}}@keyframes modal-pop{0%{opacity:0}}@keyframes progress-loading{50%{background-position-x:-115%}}@keyframes radiomark{0%{box-shadow:0 0 0 12px hsl(var(--b1)) inset,0 0 0 12px hsl(var(--b1)) inset}50%{box-shadow:0 0 0 3px hsl(var(--b1)) inset,0 0 0 3px hsl(var(--b1)) inset}100%{box-shadow:0 0 0 4px hsl(var(--b1)) inset,0 0 0 4px hsl(var(--b1)) inset}}@keyframes rating-pop{0%{transform:translateY(-0.125em)}40%{transform:translateY(-0.125em)}100%{transform:translateY(0)}}@keyframes toast-pop{0%{transform:scale(0.9);opacity:0}100%{transform:scale(1);opacity:1}}:root{color-scheme:light;--pf:259 94% 44%;--sf:314 100% 40%;--af:174 75% 39%;--nf:214 20% 14%;--in:198 93% 60%;--su:158 64% 52%;--wa:43 96% 56%;--er:0 91% 71%;--inc:198 100% 12%;--suc:158 100% 10%;--wac:43 100% 11%;--erc:0 100% 14%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:259 94% 51%;--pc:259 96% 91%;--s:314 100% 47%;--sc:314 100% 91%;--a:174 75% 46%;--ac:174 75% 11%;--n:214 20% 21%;--nc:212 19% 87%;--b1:0 0% 100%;--b2:0 0% 95%;--b3:180 2% 90%;--bc:215 28% 17%}@media (prefers-color-scheme:dark){:root{color-scheme:dark;--pf:262 80% 43%;--sf:316 70% 43%;--af:175 70% 34%;--in:198 93% 60%;--su:158 64% 52%;--wa:43 96% 56%;--er:0 91% 71%;--inc:198 100% 12%;--suc:158 100% 10%;--wac:43 100% 11%;--erc:0 100% 14%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:262 80% 50%;--pc:0 0% 100%;--s:316 70% 50%;--sc:0 0% 100%;--a:175 70% 41%;--ac:0 0% 100%;--n:213 18% 20%;--nf:212 17% 17%;--nc:220 13% 69%;--b1:212 18% 14%;--b2:213 18% 12%;--b3:213 18% 10%;--bc:220 13% 69%}}[data-theme=light]{color-scheme:light;--pf:259 94% 44%;--sf:314 100% 40%;--af:174 75% 39%;--nf:214 20% 14%;--in:198 93% 60%;--su:158 64% 52%;--wa:43 96% 56%;--er:0 91% 71%;--inc:198 100% 12%;--suc:158 100% 10%;--wac:43 100% 11%;--erc:0 100% 14%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:259 94% 51%;--pc:259 96% 91%;--s:314 100% 47%;--sc:314 100% 91%;--a:174 75% 46%;--ac:174 75% 11%;--n:214 20% 21%;--nc:212 19% 87%;--b1:0 0% 100%;--b2:0 0% 95%;--b3:180 2% 90%;--bc:215 28% 17%}[data-theme=dark]{color-scheme:dark;--pf:262 80% 43%;--sf:316 70% 43%;--af:175 70% 34%;--in:198 93% 60%;--su:158 64% 52%;--wa:43 96% 56%;--er:0 91% 71%;--inc:198 100% 12%;--suc:158 100% 10%;--wac:43 100% 11%;--erc:0 100% 14%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:262 80% 50%;--pc:0 0% 100%;--s:316 70% 50%;--sc:0 0% 100%;--a:175 70% 41%;--ac:0 0% 100%;--n:213 18% 20%;--nf:212 17% 17%;--nc:220 13% 69%;--b1:212 18% 14%;--b2:213 18% 12%;--b3:213 18% 10%;--bc:220 13% 69%}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.disabled\:pointer-events-none:disabled{pointer-events:none}.visible{visibility:visible}.invisible{visibility:hidden}.absolute,[absolute=""]{position:absolute}.fixed{position:fixed}.relative,[relative=""]{position:relative}.root-relative:root{position:relative}.static,[static=""]{position:static}.inset-0{inset:0}.inset-y-0{top:0;bottom:0}.bottom-0{bottom:0}.bottom-4{bottom:1rem}.left-0{left:0}.right-0,[right-0=""]{right:0}.right-4{right:1rem}.top-0{top:0}.top-4{top:1rem}.top-full{top:100%}.line-clamp-3{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3;line-clamp:3}.z-\[1\]{z-index:1}.z-\[110\]{z-index:110}.z-\[80\]{z-index:80}.z-\[85\]{z-index:85}.z-10,[z-10=""]{z-index:10}.z-100{z-index:100}.z-120{z-index:120}.z-30{z-index:30}.z-50{z-index:50}.grid,[grid=""]{display:grid}.inline-grid{display:inline-grid}.col-span-1{grid-column:span 1/span 1}.grid-cols-1,[grid-cols-1=""]{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2,[grid-cols-2=""]{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-rows-2,[grid-rows-2=""]{grid-template-rows:repeat(2,minmax(0,1fr))}[rows~="\33 "]{grid-template-rows:repeat(3,minmax(0,1fr))}[rows~="\34 "]{grid-template-rows:repeat(4,minmax(0,1fr))}[rows~="\35 "]{grid-template-rows:repeat(5,minmax(0,1fr))}[rows~="\36 "]{grid-template-rows:repeat(6,minmax(0,1fr))}.m-1{margin:0.25rem}.m-2{margin:0.5rem}.m-3{margin:0.75rem}.m-4{margin:1rem}.m-6{margin:1.5rem}.m-8{margin:2rem}[m~="\30 \.45"]{margin:0.1125rem}[m~="\30 \.65"]{margin:0.1625rem}.-mx-1\.5{margin-left:-0.375rem;margin-right:-0.375rem}.-my-1\.5{margin-top:-0.375rem;margin-bottom:-0.375rem}.mx-2{margin-left:0.5rem;margin-right:0.5rem}.my{margin-top:1rem;margin-bottom:1rem}.my-0{margin-top:0;margin-bottom:0}.my-auto{margin-top:auto;margin-bottom:auto}.-ml-1{margin-left:-0.25rem}.mb-1{margin-bottom:0.25rem}.mb-12,[mb-12=""]{margin-bottom:3rem}.mb-2{margin-bottom:0.5rem}.mb-3,[mb-3=""]{margin-bottom:0.75rem}.mb-4,[mb-4=""]{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8,[mb-8=""]{margin-bottom:2rem}.mb-ds-12{margin-bottom:var(--space-12,3rem)}.mb-ds-4,[mb-ds-4=""]{margin-bottom:var(--space-4,1rem)}.mb-ds-6{margin-bottom:var(--space-6,1.5rem)}.mb-ds-8{margin-bottom:var(--space-8,2rem)}.me,[me=""]{margin-inline-end:1rem}.ml-1,[ml-1=""]{margin-left:0.25rem}.ml-2{margin-left:0.5rem}.ml-3,[ml-3=""]{margin-left:0.75rem}.ml-4,[ml-4=""]{margin-left:1rem}.ml-auto,[ml-auto=""]{margin-left:auto}.mr-1{margin-right:0.25rem}.mr-2{margin-right:0.5rem}.mr-3,[mr-3=""]{margin-right:0.75rem}.mr-4{margin-right:1rem}.ms,[ms=""]{margin-inline-start:1rem}.mt-1,[mt-1=""]{margin-top:0.25rem}.mt-10{margin-top:2.5rem}.mt-12,[mt-12=""]{margin-top:3rem}.mt-16{margin-top:4rem}.mt-2,[mt-2=""]{margin-top:0.5rem}.mt-3{margin-top:0.75rem}.mt-4{margin-top:1rem}.mt-6,[mt-6=""]{margin-top:1.5rem}.mt-8,[mt-8=""]{margin-top:2rem}.mt-auto{margin-top:auto}.mt-ds-3{margin-top:var(--space-3,0.75rem)}.mt-ds-6,[mt-ds-6=""]{margin-top:var(--space-6,1.5rem)}.mt-ds-8,[mt-ds-8=""]{margin-top:var(--space-8,2rem)}.inline{display:inline}.block,.dark .dark\:block,[block=""]{display:block}.empty\:block:empty{display:block}.inline-block,[inline-block=""]{display:inline-block}.contents,[contents=""]{display:contents}.dark .dark\:hidden,.hidden,[hidden=""]{display:none}.h-\[\{\}px\]{height:{}px}.h-10{height:2.5rem}.h-12,[h-12=""]{height:3rem}.h-15{height:3.75rem}.h-16{height:4rem}.h-2,.h2{height:0.5rem}.h-28,[h-28=""]{height:7rem}.h-3,.h3,[h3=""]{height:0.75rem}.h-32{height:8rem}.h-4,.h4,[h-4=""]{height:1rem}.h-5,.h5,[h-5=""],[h5=""]{height:1.25rem}.h-6,.h6,[h-6=""]{height:1.5rem}.h-64,[h-64=""]{height:16rem}.h-8,[h-8=""]{height:2rem}.h-9{height:2.25rem}.h-full,[h-full=""]{height:100%}.h1{height:0.25rem}.max-w-2xl,[max-w-2xl=""]{max-width:42rem}.max-w-3xl{max-width:48rem}.max-w-4xl,[max-w-4xl=""]{max-width:56rem}.max-w-6xl,[max-w-6xl=""]{max-width:72rem}.max-w-7xl,[max-w-7xl=""]{max-width:80rem}.max-w-lg{max-width:32rem}.max-w-md{max-width:28rem}.max-w-none,[max-w-none=""]{max-width:none}.max-w-sm{max-width:24rem}.max-w-xs{max-width:20rem}.min-h-\[34px\]{min-height:34px}.min-h-screen,[min-h-screen=""]{min-height:100vh}.min-w-\[34px\]{min-width:34px}.min-w-0{min-width:0}.min-w-32,[min-w-32=""]{min-width:8rem}.w-\[\{\}px\]{width:{}px}.w-1\/2{width:50%}.w-10{width:2.5rem}.w-12,[w-12=""]{width:3rem}.w-16{width:4rem}.w-24{width:6rem}.w-28,[w-28=""]{width:7rem}.w-3,[w-3=""]{width:0.75rem}.w-3\/4{width:75%}.w-32{width:8rem}.w-4,[w-4=""]{width:1rem}.w-48,[w-48=""]{width:12rem}.w-5,[w-5=""]{width:1.25rem}.w-52{width:13rem}.w-6,[w-6=""]{width:1.5rem}.w-64{width:16rem}.w-8{width:2rem}.w-9{width:2.25rem}.w-auto{width:auto}.w-full,[w-full=""]{width:100%}.w-min,[w-min=""]{width:min-content}[w-1=""]{width:0.25rem}[block~="a"]{block-size:auto}.flex,[flex=""]{display:flex}.inline-flex,[inline-flex=""]{display:inline-flex}.flex-1,[flex-1=""]{flex:1 1 0%}.flex-shrink{flex-shrink:1}.flex-shrink-0,.shrink-0,[flex-shrink-0=""]{flex-shrink:0}.flex-grow,[flex-grow=""]{flex-grow:1}.flex-col,[flex-col=""]{flex-direction:column}.flex-wrap,[flex-wrap=""]{flex-wrap:wrap}.table,[table=""]{display:table}.border-collapse{border-collapse:collapse}.hover\:scale-105:hover{--un-scale-x:1.05;--un-scale-y:1.05;transform:translateX(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotateZ(var(--un-rotate-z)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z))}[hover\:scale-105=""]:hover{--un-scale-x:1.05;--un-scale-y:1.05;transform:translateX(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotateZ(var(--un-rotate-z)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z))}.transform{transform:translateX(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotateZ(var(--un-rotate-z)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z))}@keyframes pulse{0%,100%{opacity:1}50%{opacity:.5}}@keyframes spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}.animate-pulse{animation:pulse 2s cubic-bezier(0.4,0,.6,1) infinite}.animate-spin{animation:spin 1s linear infinite}.cursor-pointer{cursor:pointer}.cursor-not-allowed,[cursor-not-allowed=""]{cursor:not-allowed}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.select-none{-webkit-user-select:none;user-select:none}.list-disc{list-style-type:disc}.list-inside{list-style-position:inside}.list-none{list-style-type:none}.place-items-center{place-items:center}.items-start,[items-start=""]{align-items:flex-start}.items-end{align-items:flex-end}.items-center,[items-center=""]{align-items:center}.items-stretch,[items-stretch=""]{align-items:stretch}.justify-end{justify-content:flex-end}.justify-center,[justify-center=""]{justify-content:center}.justify-between,[justify-between=""]{justify-content:space-between}.gap-1,[gap-1=""]{gap:0.25rem}.gap-12{gap:3rem}.gap-2,[gap-2=""]{gap:0.5rem}.gap-3,[gap-3=""]{gap:0.75rem}.gap-4,[gap-4=""]{gap:1rem}.gap-6,[gap-6=""]{gap:1.5rem}.gap-8{gap:2rem}.gap-ds-2{gap:var(--space-2,0.5rem)}.gap-ds-4{gap:var(--space-4,1rem)}.gap-ds-6{gap:var(--space-6,1.5rem)}.gap-ds-8{gap:var(--space-8,2rem)}.gap-x-6{column-gap:1.5rem}.space-x-1>:not([hidden])~:not([hidden]),[space-x-1=""]>:not([hidden])~:not([hidden]){--un-space-x-reverse:0;margin-left:calc(0.25rem * calc(1 - var(--un-space-x-reverse)));margin-right:calc(0.25rem * var(--un-space-x-reverse))}.space-x-2>:not([hidden])~:not([hidden]),[space-x-2=""]>:not([hidden])~:not([hidden]){--un-space-x-reverse:0;margin-left:calc(0.5rem * calc(1 - var(--un-space-x-reverse)));margin-right:calc(0.5rem * var(--un-space-x-reverse))}.space-x-3>:not([hidden])~:not([hidden]),[space-x-3=""]>:not([hidden])~:not([hidden]){--un-space-x-reverse:0;margin-left:calc(0.75rem * calc(1 - var(--un-space-x-reverse)));margin-right:calc(0.75rem * var(--un-space-x-reverse))}.space-x-4>:not([hidden])~:not([hidden]),[space-x-4=""]>:not([hidden])~:not([hidden]){--un-space-x-reverse:0;margin-left:calc(1rem * calc(1 - var(--un-space-x-reverse)));margin-right:calc(1rem * var(--un-space-x-reverse))}.space-x-6>:not([hidden])~:not([hidden]){--un-space-x-reverse:0;margin-left:calc(1.5rem * calc(1 - var(--un-space-x-reverse)));margin-right:calc(1.5rem * var(--un-space-x-reverse))}.space-y-1>:not([hidden])~:not([hidden]){--un-space-y-reverse:0;margin-top:calc(0.25rem * calc(1 - var(--un-space-y-reverse)));margin-bottom:calc(0.25rem * var(--un-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]),[space-y-2=""]>:not([hidden])~:not([hidden]){--un-space-y-reverse:0;margin-top:calc(0.5rem * calc(1 - var(--un-space-y-reverse)));margin-bottom:calc(0.5rem * var(--un-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--un-space-y-reverse:0;margin-top:calc(0.75rem * calc(1 - var(--un-space-y-reverse)));margin-bottom:calc(0.75rem * var(--un-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--un-space-y-reverse:0;margin-top:calc(1rem * calc(1 - var(--un-space-y-reverse)));margin-bottom:calc(1rem * var(--un-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--un-space-y-reverse:0;margin-top:calc(1.5rem * calc(1 - var(--un-space-y-reverse)));margin-bottom:calc(1.5rem * var(--un-space-y-reverse))}.space-y-8>:not([hidden])~:not([hidden]){--un-space-y-reverse:0;margin-top:calc(2rem * calc(1 - var(--un-space-y-reverse)));margin-bottom:calc(2rem * var(--un-space-y-reverse))}.space-y-ds-2>:not([hidden])~:not([hidden]){--un-space-y-reverse:0;margin-top:calc(var(--space-2,0.5rem) * calc(1 - var(--un-space-y-reverse)));margin-bottom:calc(var(--space-2,0.5rem) * var(--un-space-y-reverse))}.space-y-ds-4>:not([hidden])~:not([hidden]){--un-space-y-reverse:0;margin-top:calc(var(--space-4,1rem) * calc(1 - var(--un-space-y-reverse)));margin-bottom:calc(var(--space-4,1rem) * var(--un-space-y-reverse))}.space-y-ds-6>:not([hidden])~:not([hidden]){--un-space-y-reverse:0;margin-top:calc(var(--space-6,1.5rem) * calc(1 - var(--un-space-y-reverse)));margin-bottom:calc(var(--space-6,1.5rem) * var(--un-space-y-reverse))}.space-y-ds-8>:not([hidden])~:not([hidden]){--un-space-y-reverse:0;margin-top:calc(var(--space-8,2rem) * calc(1 - var(--un-space-y-reverse)));margin-bottom:calc(var(--space-8,2rem) * var(--un-space-y-reverse))}.overflow-hidden,[overflow-hidden=""]{overflow:hidden}.overflow-x-auto{overflow-x:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-nowrap{white-space:nowrap}.b,.border,[b=""],[border=""]{border-width:1px}.border-2,[border-2=""]{border-width:2px}.last\:border-0:last-child{border-width:0px}.border-b,[border-b=""]{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-r{border-right-width:1px}.border-t,[border-t=""]{border-top-width:1px}.border-t-2{border-top-width:2px}.last\:border-b-0:last-child{border-bottom-width:0px}[last\:border-b-0=""]:last-child{border-bottom-width:0px}.border-base-200{--un-border-opacity:1;border-color:hsl(var(--b2) / var(--un-border-opacity))}.border-base-300,[border-base-300=""]{--un-border-opacity:1;border-color:hsl(var(--b3) / var(--un-border-opacity))}.border-base-300\/50{border-color:hsl(var(--b3) / 0.5)}.border-base-content\/20{border-color:hsl(var(--bc) / 0.2)}.border-blue-200{--un-border-opacity:1;border-color:rgb(191 219 254 / var(--un-border-opacity))}.border-blue-500,[border-blue-500=""]{--un-border-opacity:1;border-color:rgb(59 130 246 / var(--un-border-opacity))}.border-blue-500\/20{border-color:rgb(59 130 246 / 0.2)}.border-blue-600{--un-border-opacity:1;border-color:rgb(37 99 235 / var(--un-border-opacity))}.border-error\/20{border-color:hsl(var(--er) / 0.2)}.border-gray-100{--un-border-opacity:1;border-color:rgb(243 244 246 / var(--un-border-opacity))}.border-gray-200,[border-gray-200=""]{--un-border-opacity:1;border-color:rgb(229 231 235 / var(--un-border-opacity))}.border-gray-300{--un-border-opacity:1;border-color:rgb(209 213 219 / var(--un-border-opacity))}.border-gray-400{--un-border-opacity:1;border-color:rgb(156 163 175 / var(--un-border-opacity))}.border-gray-500,[border-gray-500=""]{--un-border-opacity:1;border-color:rgb(107 114 128 / var(--un-border-opacity))}.border-green-200{--un-border-opacity:1;border-color:rgb(187 247 208 / var(--un-border-opacity))}.border-green-300{--un-border-opacity:1;border-color:rgb(134 239 172 / var(--un-border-opacity))}.border-green-400{--un-border-opacity:1;border-color:rgb(74 222 128 / var(--un-border-opacity))}.border-green-500,[border-green-500=""]{--un-border-opacity:1;border-color:rgb(34 197 94 / var(--un-border-opacity))}.border-purple-500,[border-purple-500=""]{--un-border-opacity:1;border-color:rgb(168 85 247 / var(--un-border-opacity))}.border-red-200,[border-red-200=""]{--un-border-opacity:1;border-color:rgb(254 202 202 / var(--un-border-opacity))}.border-red-300{--un-border-opacity:1;border-color:rgb(252 165 165 / var(--un-border-opacity))}.border-red-400{--un-border-opacity:1;border-color:rgb(248 113 113 / var(--un-border-opacity))}.border-transparent{border-color:transparent}.border-yellow-200{--un-border-opacity:1;border-color:rgb(254 240 138 / var(--un-border-opacity))}.border-yellow-500,[border-yellow-500=""]{--un-border-opacity:1;border-color:rgb(234 179 8 / var(--un-border-opacity))}.dark .dark\:border-gray-700{--un-border-opacity:1;border-color:rgb(55 65 81 / var(--un-border-opacity))}.hover\:border-blue-400:hover{--un-border-opacity:1;border-color:rgb(96 165 250 / var(--un-border-opacity))}.focus\:border-blue-500:focus{--un-border-opacity:1;border-color:rgb(59 130 246 / var(--un-border-opacity))}.focus\:border-transparent:focus{border-color:transparent}[focus\:border-blue-500=""]:focus{--un-border-opacity:1;border-color:rgb(59 130 246 / var(--un-border-opacity))}.rounded-md,[rounded-md=""]{border-radius:0.375rem}.rounded-l-md{border-top-left-radius:0.375rem;border-bottom-left-radius:0.375rem}.rounded-r-md,[rounded-r-md=""]{border-top-right-radius:0.375rem;border-bottom-right-radius:0.375rem}.border-dashed,[border-dashed=""]{border-style:dashed}.border-solid{border-style:solid}.bg-base-100{--un-bg-opacity:1;background-color:hsl(var(--b1) / var(--un-bg-opacity))}.bg-black{--un-bg-opacity:1;background-color:rgb(0 0 0 / var(--un-bg-opacity))}.bg-blue-100{--un-bg-opacity:1;background-color:rgb(219 234 254 / var(--un-bg-opacity))}.bg-blue-50,[bg-blue-50=""]{--un-bg-opacity:1;background-color:rgb(239 246 255 / var(--un-bg-opacity))}.bg-blue-500{--un-bg-opacity:1;background-color:rgb(59 130 246 / var(--un-bg-opacity))}.bg-blue-500\/10{background-color:rgb(59 130 246 / 0.1)}.bg-blue-600,[bg-blue-600=""]{--un-bg-opacity:1;background-color:rgb(37 99 235 / var(--un-bg-opacity))}.bg-ds-brand-primary{background-color:var(--color-brand-primary,#3b82f6)}.bg-gray-100,[bg-gray-100=""]{--un-bg-opacity:1;background-color:rgb(243 244 246 / var(--un-bg-opacity))}.bg-gray-200,[bg-gray-200=""]{--un-bg-opacity:1;background-color:rgb(229 231 235 / var(--un-bg-opacity))}.bg-gray-50,[bg-gray-50=""]{--un-bg-opacity:1;background-color:rgb(249 250 251 / var(--un-bg-opacity))}.bg-gray-600,.dark .dark\:bg-gray-600{--un-bg-opacity:1;background-color:rgb(75 85 99 / var(--un-bg-opacity))}.bg-gray-800,.dark .dark\:bg-gray-800{--un-bg-opacity:1;background-color:rgb(31 41 55 / var(--un-bg-opacity))}.bg-gray-900,.dark [dark\:bg-gray-900=""],[bg-gray-900=""]{--un-bg-opacity:1;background-color:rgb(17 24 39 / var(--un-bg-opacity))}.bg-green-100,[bg-green-100=""]{--un-bg-opacity:1;background-color:rgb(220 252 231 / var(--un-bg-opacity))}.bg-green-50,[bg-green-50=""]{--un-bg-opacity:1;background-color:rgb(240 253 244 / var(--un-bg-opacity))}.bg-green-500{--un-bg-opacity:1;background-color:rgb(34 197 94 / var(--un-bg-opacity))}.bg-indigo-100,[bg-indigo-100=""]{--un-bg-opacity:1;background-color:rgb(224 231 255 / var(--un-bg-opacity))}.bg-indigo-600{--un-bg-opacity:1;background-color:rgb(79 70 229 / var(--un-bg-opacity))}.bg-orange-500{--un-bg-opacity:1;background-color:rgb(249 115 22 / var(--un-bg-opacity))}.bg-primary{background-color:var(--c-primary)}.bg-purple-50,[bg-purple-50=""]{--un-bg-opacity:1;background-color:rgb(250 245 255 / var(--un-bg-opacity))}.bg-red-100{--un-bg-opacity:1;background-color:rgb(254 226 226 / var(--un-bg-opacity))}.bg-red-50,[bg-red-50=""]{--un-bg-opacity:1;background-color:rgb(254 242 242 / var(--un-bg-opacity))}.bg-red-500{--un-bg-opacity:1;background-color:rgb(239 68 68 / var(--un-bg-opacity))}.bg-slate-700{--un-bg-opacity:1;background-color:rgb(51 65 85 / var(--un-bg-opacity))}.bg-transparent{background-color:transparent}.bg-white{--un-bg-opacity:1;background-color:rgb(255 255 255 / var(--un-bg-opacity))}.bg-yellow-100,[bg-yellow-100=""]{--un-bg-opacity:1;background-color:rgb(254 249 195 / var(--un-bg-opacity))}.bg-yellow-50,[bg-yellow-50=""]{--un-bg-opacity:1;background-color:rgb(254 252 232 / var(--un-bg-opacity))}.bg-yellow-500{--un-bg-opacity:1;background-color:rgb(234 179 8 / var(--un-bg-opacity))}.dark .dark\:bg-blue-900\/20{background-color:rgb(30 58 138 / 0.2)}.dark .dark\:bg-gray-700{--un-bg-opacity:1;background-color:rgb(55 65 81 / var(--un-bg-opacity))}.dark .dark\:bg-gray-900\/50{background-color:rgb(17 24 39 / 0.5)}.dark .dark\:bg-green-900\/20{background-color:rgb(20 83 45 / 0.2)}.dark .dark\:bg-purple-900\/20{background-color:rgb(88 28 135 / 0.2)}.dark .dark\:bg-slate-400{--un-bg-opacity:1;background-color:rgb(148 163 184 / var(--un-bg-opacity))}.dark .dark\:bg-slate-900{--un-bg-opacity:1;background-color:rgb(15 23 42 / var(--un-bg-opacity))}.dark .dark\:bg-yellow-900\/20{background-color:rgb(113 63 18 / 0.2)}.dark [dark\:bg-blue-900=""]{--un-bg-opacity:1;background-color:rgb(30 58 138 / var(--un-bg-opacity))}.dark [dark\:bg-green-900=""]{--un-bg-opacity:1;background-color:rgb(20 83 45 / var(--un-bg-opacity))}.dark [dark\:bg-purple-900=""]{--un-bg-opacity:1;background-color:rgb(88 28 135 / var(--un-bg-opacity))}.dark [dark\:bg-yellow-900=""]{--un-bg-opacity:1;background-color:rgb(113 63 18 / var(--un-bg-opacity))}.dark .dark\:hover\:bg-gray-500:hover{--un-bg-opacity:1;background-color:rgb(107 114 128 / var(--un-bg-opacity))}.hover\:bg-base-content\/5:hover{background-color:hsl(var(--bc) / 0.05)}.hover\:bg-blue-700:hover{--un-bg-opacity:1;background-color:rgb(29 78 216 / var(--un-bg-opacity))}.hover\:bg-ds-brand-primary:hover{background-color:var(--color-brand-primary,#3b82f6)}.hover\:bg-gray-100:hover{--un-bg-opacity:1;background-color:rgb(243 244 246 / var(--un-bg-opacity))}.hover\:bg-gray-50:hover{--un-bg-opacity:1;background-color:rgb(249 250 251 / var(--un-bg-opacity))}.hover\:bg-gray-700:hover{--un-bg-opacity:1;background-color:rgb(55 65 81 / var(--un-bg-opacity))}.hover\:bg-primary\/80:hover{background-color:var(--c-primary)}.hover\:bg-red-100:hover{--un-bg-opacity:1;background-color:rgb(254 226 226 / var(--un-bg-opacity))}.hover\:bg-red-200:hover{--un-bg-opacity:1;background-color:rgb(254 202 202 / var(--un-bg-opacity))}.dark .dark\:from-gray-900{--un-gradient-from-position:0%;--un-gradient-from:rgb(17 24 39 / var(--un-from-opacity,1)) var(--un-gradient-from-position);--un-gradient-to-position:100%;--un-gradient-to:rgb(17 24 39 / 0) var(--un-gradient-to-position);--un-gradient-stops:var(--un-gradient-from),var(--un-gradient-to)}.from-blue-50{--un-gradient-from-position:0%;--un-gradient-from:rgb(239 246 255 / var(--un-from-opacity,1)) var(--un-gradient-from-position);--un-gradient-to-position:100%;--un-gradient-to:rgb(239 246 255 / 0) var(--un-gradient-to-position);--un-gradient-stops:var(--un-gradient-from),var(--un-gradient-to)}.from-slate-50{--un-gradient-from-position:0%;--un-gradient-from:rgb(248 250 252 / var(--un-from-opacity,1)) var(--un-gradient-from-position);--un-gradient-to-position:100%;--un-gradient-to:rgb(248 250 252 / 0) var(--un-gradient-to-position);--un-gradient-stops:var(--un-gradient-from),var(--un-gradient-to)}.dark .dark\:to-blue-900{--un-gradient-to-position:100%;--un-gradient-to:rgb(30 58 138 / var(--un-to-opacity,1)) var(--un-gradient-to-position)}.dark .dark\:to-slate-900{--un-gradient-to-position:100%;--un-gradient-to:rgb(15 23 42 / var(--un-to-opacity,1)) var(--un-gradient-to-position)}.to-gray-100{--un-gradient-to-position:100%;--un-gradient-to:rgb(243 244 246 / var(--un-to-opacity,1)) var(--un-gradient-to-position)}.to-indigo-100{--un-gradient-to-position:100%;--un-gradient-to:rgb(224 231 255 / var(--un-to-opacity,1)) var(--un-gradient-to-position)}.bg-gradient-to-br{--un-gradient-shape:to bottom right in oklch;--un-gradient:var(--un-gradient-shape),var(--un-gradient-stops);background-image:linear-gradient(var(--un-gradient))}[stroke-width~="\31 \.5"]{stroke-width:1.5px}[stroke-width~="\32 "]{stroke-width:2px}[stroke-width~="\34 "]{stroke-width:4px}.object-cover{object-fit:cover}.p-1{padding:0.25rem}.p-1\.5{padding:0.375rem}.p-2,[p-2=""]{padding:0.5rem}.p-3{padding:0.75rem}.p-4,[p-4=""]{padding:1rem}.p-6,[p-6=""]{padding:1.5rem}.p-8{padding:2rem}.p-ds-2{padding:var(--space-2,0.5rem)}.p-ds-4,[p-ds-4=""]{padding:var(--space-4,1rem)}.p-ds-6,[p-ds-6=""]{padding:var(--space-6,1.5rem)}.p-ds-8{padding:var(--space-8,2rem)}.px-1{padding-left:0.25rem;padding-right:0.25rem}.px-2,[px-2=""]{padding-left:0.5rem;padding-right:0.5rem}.px-2\.5{padding-left:0.625rem;padding-right:0.625rem}.px-3,[px-3=""]{padding-left:0.75rem;padding-right:0.75rem}.px-4,[px-4=""]{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-8{padding-left:2rem;padding-right:2rem}.px-ds-4{padding-left:var(--space-4,1rem);padding-right:var(--space-4,1rem)}.px-ds-8{padding-left:var(--space-8,2rem);padding-right:var(--space-8,2rem)}.py,.py-4{padding-top:1rem;padding-bottom:1rem}.py-0\.5{padding-top:0.125rem;padding-bottom:0.125rem}.py-1,[py-1=""]{padding-top:0.25rem;padding-bottom:0.25rem}.py-12,[py-12=""]{padding-top:3rem;padding-bottom:3rem}.py-16{padding-top:4rem;padding-bottom:4rem}.py-2,[py-2=""]{padding-top:0.5rem;padding-bottom:0.5rem}.py-20{padding-top:5rem;padding-bottom:5rem}.py-3{padding-top:0.75rem;padding-bottom:0.75rem}.py-5{padding-top:1.25rem;padding-bottom:1.25rem}.py-6,[py-6=""]{padding-top:1.5rem;padding-bottom:1.5rem}.py-8,[py-8=""]{padding-top:2rem;padding-bottom:2rem}.py-ds-12{padding-top:var(--space-12,3rem);padding-bottom:var(--space-12,3rem)}.py-ds-2{padding-top:var(--space-2,0.5rem);padding-bottom:var(--space-2,0.5rem)}.py-ds-4,[py-ds-4=""]{padding-top:var(--space-4,1rem);padding-bottom:var(--space-4,1rem)}.py-ds-6{padding-top:var(--space-6,1.5rem);padding-bottom:var(--space-6,1.5rem)}.py-ds-8{padding-top:var(--space-8,2rem);padding-bottom:var(--space-8,2rem)}.pb{padding-bottom:1rem}.pb-3,[pb-3=""]{padding-bottom:0.75rem}.pl{padding-left:1rem}.pl-3{padding-left:0.75rem}.pr-10,[pr-10=""]{padding-right:2.5rem}.pr-3{padding-right:0.75rem}.ps{padding-inline-start:1rem}.ps1{padding-inline-start:0.25rem}.pt{padding-top:1rem}.pt-2{padding-top:0.5rem}.pt-3,[pt-3=""]{padding-top:0.75rem}.pt-6{padding-top:1.5rem}.pt-ds-3{padding-top:var(--space-3,0.75rem)}.pt-ds-4{padding-top:var(--space-4,1rem)}.pt-ds-6{padding-top:var(--space-6,1.5rem)}.last\:pb-0:last-child{padding-bottom:0}.text-end,[text-end=""]{text-align:end}.text-balance,[text-balance=""]{text-wrap:balance}.align-middle{vertical-align:middle}.text-\[15px\]{font-size:15px}.text-2xl,[text-2xl=""]{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-4xl,[text-4xl=""]{font-size:2.25rem;line-height:2.5rem}.text-6xl{font-size:3.75rem;line-height:1}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm,[text-sm=""]{font-size:0.875rem;line-height:1.25rem}.text-xl,[text-xl=""]{font-size:1.25rem;line-height:1.75rem}.text-xs,[text-xs=""]{font-size:0.75rem;line-height:1rem}.dark .dark\:text-blue-400,.dark [dark\:text-blue-400=""],.text-blue-400{--un-text-opacity:1;color:rgb(96 165 250 / var(--un-text-opacity))}.dark .dark\:text-gray-300,.text-gray-300,[text-gray-300=""]{--un-text-opacity:1;color:rgb(209 213 219 / var(--un-text-opacity))}.dark .dark\:text-gray-400,.text-gray-400,[text-gray-400=""]{--un-text-opacity:1;color:rgb(156 163 175 / var(--un-text-opacity))}.dark .dark\:text-green-400,.dark [dark\:text-green-400=""]{--un-text-opacity:1;color:rgb(74 222 128 / var(--un-text-opacity))}.dark .dark\:text-purple-400,.dark [dark\:text-purple-400=""]{--un-text-opacity:1;color:rgb(192 132 252 / var(--un-text-opacity))}.dark .dark\:text-slate-900{--un-text-opacity:1;color:rgb(15 23 42 / var(--un-text-opacity))}.dark .dark\:text-yellow-400,.dark [dark\:text-yellow-400=""]{--un-text-opacity:1;color:rgb(250 204 21 / var(--un-text-opacity))}.text-base-content,[text-base-content=""]{--un-text-opacity:1;color:hsl(var(--bc) / var(--un-text-opacity))}.text-base-content\/50{color:hsl(var(--bc) / 0.5)}.text-base-content\/60{color:hsl(var(--bc) / 0.6)}.text-base-content\/70{color:hsl(var(--bc) / 0.7)}.text-black{--un-text-opacity:1;color:rgb(0 0 0 / var(--un-text-opacity))}.text-blue-100,[text-blue-100=""]{--un-text-opacity:1;color:rgb(219 234 254 / var(--un-text-opacity))}.text-blue-200{--un-text-opacity:1;color:rgb(191 219 254 / var(--un-text-opacity))}.text-blue-500,[text-blue-500=""]{--un-text-opacity:1;color:rgb(59 130 246 / var(--un-text-opacity))}.text-blue-600,[text-blue-600=""]{--un-text-opacity:1;color:rgb(37 99 235 / var(--un-text-opacity))}.text-blue-700,[text-blue-700=""]{--un-text-opacity:1;color:rgb(29 78 216 / var(--un-text-opacity))}.text-blue-800{--un-text-opacity:1;color:rgb(30 64 175 / var(--un-text-opacity))}.text-error{--un-text-opacity:1;color:hsl(var(--er) / var(--un-text-opacity))}.text-gray-500{--un-text-opacity:1;color:rgb(107 114 128 / var(--un-text-opacity))}.text-gray-600{--un-text-opacity:1;color:rgb(75 85 99 / var(--un-text-opacity))}.text-gray-700,[text-gray-700=""]{--un-text-opacity:1;color:rgb(55 65 81 / var(--un-text-opacity))}.text-gray-900,[text-gray-900=""]{--un-text-opacity:1;color:rgb(17 24 39 / var(--un-text-opacity))}.text-green-500{--un-text-opacity:1;color:rgb(34 197 94 / var(--un-text-opacity))}.text-green-600,[text-green-600=""]{--un-text-opacity:1;color:rgb(22 163 74 / var(--un-text-opacity))}.text-green-700,[text-green-700=""]{--un-text-opacity:1;color:rgb(21 128 61 / var(--un-text-opacity))}.text-green-800{--un-text-opacity:1;color:rgb(22 101 52 / var(--un-text-opacity))}.text-indigo-500{--un-text-opacity:1;color:rgb(99 102 241 / var(--un-text-opacity))}.text-indigo-700{--un-text-opacity:1;color:rgb(67 56 202 / var(--un-text-opacity))}.text-orange-600{--un-text-opacity:1;color:rgb(234 88 12 / var(--un-text-opacity))}.text-purple-700,[text-purple-700=""]{--un-text-opacity:1;color:rgb(126 34 206 / var(--un-text-opacity))}.text-red-400{--un-text-opacity:1;color:rgb(248 113 113 / var(--un-text-opacity))}.text-red-500{--un-text-opacity:1;color:rgb(239 68 68 / var(--un-text-opacity))}.text-red-600,[text-red-600=""]{--un-text-opacity:1;color:rgb(220 38 38 / var(--un-text-opacity))}.text-red-700{--un-text-opacity:1;color:rgb(185 28 28 / var(--un-text-opacity))}.text-success{--un-text-opacity:1;color:hsl(var(--su) / var(--un-text-opacity))}.text-warning{--un-text-opacity:1;color:hsl(var(--wa) / var(--un-text-opacity))}.dark .dark\:color-white,.text-white,[text-white=""]{--un-text-opacity:1;color:rgb(255 255 255 / var(--un-text-opacity))}.text-yellow-600,[text-yellow-600=""]{--un-text-opacity:1;color:rgb(202 138 4 / var(--un-text-opacity))}.text-yellow-700,[text-yellow-700=""]{--un-text-opacity:1;color:rgb(161 98 7 / var(--un-text-opacity))}.text-yellow-800{--un-text-opacity:1;color:rgb(133 77 14 / var(--un-text-opacity))}.dark .dark\:hover\:text-white:hover{--un-text-opacity:1;color:rgb(255 255 255 / var(--un-text-opacity))}.hover\:text-accent-content:hover{--un-text-opacity:1;color:hsl(var(--ac) / var(--un-text-opacity))}.hover\:text-blue-500:hover{--un-text-opacity:1;color:rgb(59 130 246 / var(--un-text-opacity))}.hover\:text-blue-600:hover{--un-text-opacity:1;color:rgb(37 99 235 / var(--un-text-opacity))}.hover\:text-blue-700:hover{--un-text-opacity:1;color:rgb(29 78 216 / var(--un-text-opacity))}.hover\:text-blue-800:hover{--un-text-opacity:1;color:rgb(30 64 175 / var(--un-text-opacity))}.hover\:text-gray-500:hover{--un-text-opacity:1;color:rgb(107 114 128 / var(--un-text-opacity))}.hover\:text-gray-900:hover{--un-text-opacity:1;color:rgb(17 24 39 / var(--un-text-opacity))}.hover\:text-green-800:hover{--un-text-opacity:1;color:rgb(22 101 52 / var(--un-text-opacity))}.hover\:text-primary:hover{color:var(--c-primary)}.hover\:text-red-800:hover{--un-text-opacity:1;color:rgb(153 27 27 / var(--un-text-opacity))}.hover\:text-white:hover{--un-text-opacity:1;color:rgb(255 255 255 / var(--un-text-opacity))}.hover\:text-yellow-800:hover{--un-text-opacity:1;color:rgb(133 77 14 / var(--un-text-opacity))}[hover\:text-blue-800=""]:hover{--un-text-opacity:1;color:rgb(30 64 175 / var(--un-text-opacity))}[hover\:text-primary=""]:hover{color:var(--c-primary)}[hover\:text-red-800=""]:hover{--un-text-opacity:1;color:rgb(153 27 27 / var(--un-text-opacity))}.text-opacity-70{--un-text-opacity:0.7}.font-bold,[font-bold=""]{font-weight:700}.font-medium,[font-medium=""]{font-weight:500}.font-semibold,[font-semibold=""]{font-weight:600}.leading-6{line-height:1.5rem}.leading-7,[leading-7=""]{line-height:1.75rem}.leading-8{line-height:2rem}.leading-relaxed{line-height:1.625}.tracking-tight,[tracking-tight=""]{letter-spacing:-0.025em}.tracking-widest{letter-spacing:0.1em}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}.font-sans{font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"}.uppercase,[uppercase=""]{text-transform:uppercase}.lowercase,[lowercase=""]{text-transform:lowercase}.capitalize{text-transform:capitalize}.italic{font-style:italic}.underline{text-decoration-line:underline}.hover\:underline:hover{text-decoration-line:underline}.no-underline,[no-underline=""]{text-decoration:none}.opacity-0,[opacity-0=""]{opacity:0}.opacity-100,[opacity-100=""]{opacity:1}.opacity-25{opacity:0.25}.opacity-50{opacity:0.5}.opacity-60{opacity:0.6}.opacity-75{opacity:0.75}.disabled\:opacity-40:disabled{opacity:0.4}.disabled\:opacity-50:disabled{opacity:0.5}.shadow-2xl{--un-shadow:var(--un-shadow-inset) 0 25px 50px -12px var(--un-shadow-color,rgb(0 0 0 / 0.25));box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.shadow-md{--un-shadow:var(--un-shadow-inset) 0 4px 6px -1px var(--un-shadow-color,rgb(0 0 0 / 0.1)),var(--un-shadow-inset) 0 2px 4px -2px var(--un-shadow-color,rgb(0 0 0 / 0.1));box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.outline{outline-style:solid}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}[focus\:outline-none=""]:focus{outline:2px solid transparent;outline-offset:2px}.ring-2,[ring-2=""]{--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.focus\:ring-2:focus{--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}[focus\:ring-2=""]:focus{--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.focus\:ring-offset-2:focus{--un-ring-offset-width:2px}.ring-blue-500{--un-ring-opacity:1;--un-ring-color:rgb(59 130 246 / var(--un-ring-opacity))}.focus\:ring-blue-500:focus{--un-ring-opacity:1;--un-ring-color:rgb(59 130 246 / var(--un-ring-opacity))}.focus\:ring-red-600:focus{--un-ring-opacity:1;--un-ring-color:rgb(220 38 38 / var(--un-ring-opacity))}.focus\:ring-white:focus{--un-ring-opacity:1;--un-ring-color:rgb(255 255 255 / var(--un-ring-opacity))}[focus\:ring-blue-500=""]:focus{--un-ring-opacity:1;--un-ring-color:rgb(59 130 246 / var(--un-ring-opacity))}.focus\:ring-offset-red-100:focus{--un-ring-offset-opacity:1;--un-ring-offset-color:rgb(254 226 226 / var(--un-ring-offset-opacity))}.focus\:ring-offset-red-50:focus{--un-ring-offset-opacity:1;--un-ring-offset-color:rgb(254 242 242 / var(--un-ring-offset-opacity))}.focus\:ring-inset:focus{--un-ring-inset:inset}[focus\:ring-inset=""]:focus{--un-ring-inset:inset}.backdrop-blur-sm{--un-backdrop-blur:blur(4px);-webkit-backdrop-filter:var(--un-backdrop-blur) var(--un-backdrop-brightness) var(--un-backdrop-contrast) var(--un-backdrop-grayscale) var(--un-backdrop-hue-rotate) var(--un-backdrop-invert) var(--un-backdrop-opacity) var(--un-backdrop-saturate) var(--un-backdrop-sepia);backdrop-filter:var(--un-backdrop-blur) var(--un-backdrop-brightness) var(--un-backdrop-contrast) var(--un-backdrop-grayscale) var(--un-backdrop-hue-rotate) var(--un-backdrop-invert) var(--un-backdrop-opacity) var(--un-backdrop-saturate) var(--un-backdrop-sepia)}.filter,[filter=""]{filter:var(--un-blur) var(--un-brightness) var(--un-contrast) var(--un-drop-shadow) var(--un-grayscale) var(--un-hue-rotate) var(--un-invert) var(--un-saturate) var(--un-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms}.transition-all,[transition-all=""]{transition-property:all;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms}.transition-shadow{transition-property:box-shadow;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms}.transition-transform,[transition-transform=""]{transition-property:transform;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms}.duration-150{transition-duration:150ms}.duration-200,[duration-200=""]{transition-duration:200ms}.duration-300,[duration-300=""]{transition-duration:300ms}.ease,.ease-in-out{transition-timing-function:cubic-bezier(0.4,0,0.2,1)}.ease-in{transition-timing-function:cubic-bezier(0.4,0,1,1)}@media (min-width:640px){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:ml-3,[sm\:ml-3=""]{margin-left:0.75rem}.sm\:mr-6{margin-right:1.5rem}.sm\:mt-1,[sm\:mt-1=""]{margin-top:0.25rem}.sm\:flex-row,[sm\:flex-row=""]{flex-direction:row}.sm\:flex-wrap{flex-wrap:wrap}.sm\:items-center{align-items:center}.sm\:truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sm\:p-6{padding:1.5rem}.sm\:px-6,[sm\:px-6=""]{padding-left:1.5rem;padding-right:1.5rem}.sm\:text-6xl{font-size:3.75rem;line-height:1}.sm\:leading-9,[sm\:leading-9=""]{line-height:2.25rem}}@media (min-width:768px){.md\:col-span-2{grid-column:span 2/span 2}.md\:grid-cols-2,[md\:grid-cols-2=""]{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3,[md\:grid-cols-3=""]{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-4,[md\:grid-cols-4=""]{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:ml-2{margin-left:0.5rem}.md\:ml-4{margin-left:1rem}.md\:ml-6{margin-left:1.5rem}.md\:mt-0,[md\:mt-0=""]{margin-top:0}.md\:hidden,[md\:hidden=""]{display:none}.md\:flex,[md\:flex=""]{display:flex}.md\:items-center,[md\:items-center=""]{align-items:center}.md\:justify-between,[md\:justify-between=""]{justify-content:space-between}.md\:space-x-2>:not([hidden])~:not([hidden]){--un-space-x-reverse:0;margin-left:calc(0.5rem * calc(1 - var(--un-space-x-reverse)));margin-right:calc(0.5rem * var(--un-space-x-reverse))}.md\:space-x-3>:not([hidden])~:not([hidden]){--un-space-x-reverse:0;margin-left:calc(0.75rem * calc(1 - var(--un-space-x-reverse)));margin-right:calc(0.75rem * var(--un-space-x-reverse))}.md\:space-x-8>:not([hidden])~:not([hidden]){--un-space-x-reverse:0;margin-left:calc(2rem * calc(1 - var(--un-space-x-reverse)));margin-right:calc(2rem * var(--un-space-x-reverse))}.md\:p-12{padding:3rem}.md\:text-5xl{font-size:3rem;line-height:1}}@media (min-width:1024px){.lg\:static{position:static}.lg\:inset-0{inset:0}.lg\:col-span-1{grid-column:span 1/span 1}.lg\:col-span-2{grid-column:span 2/span 2}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-3,[lg\:grid-cols-3=""]{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:ml-64{margin-left:16rem}.lg\:mt-0{margin-top:0}.lg\:block{display:block}.lg\:hidden{display:none}.lg\:h-20{height:5rem}.lg\:max-w-6xl,[lg\:max-w-6xl=""]{max-width:72rem}.lg\:w-1\/3{width:33.3333333333%}.lg\:w-40{width:10rem}[lg\:w-1=""]{width:0.25rem}.lg\:translate-x-0{--un-translate-x:0;transform:translateX(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotateZ(var(--un-rotate-z)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z))}.lg\:border-t,[lg\:border-t=""]{border-top-width:1px}.lg\:px-8,[lg\:px-8=""]{padding-left:2rem;padding-right:2rem}.lg\:pl-8,[lg\:pl-8=""]{padding-left:2rem}}*{margin:0;padding:0;box-sizing:border-box}body{font-family:"Segoe UI",Tahoma,Geneva,Verdana,sans-serif;line-height:1.6;color:#333;background-color:#f8f9fa}.container{max-width:1200px;margin:0 auto;padding:0 20px}h1,h2,h3,h4,h5,h6{margin-bottom:1rem;color:#2c3e50}h1{font-size:2.5rem;font-weight:700}h2{font-size:2rem;font-weight:600}h3{font-size:1.5rem;font-weight:500}p{margin-bottom:1rem}.btn-icon{padding:2px 4px !important}.btn{display:inline-block;font-size:1rem;font-weight:500;text-decoration:none;border:none;border-radius:6px;cursor:pointer;transition:all 0.3s ease}.btn-primary{background-color:#3498db;color:white}.btn-primary:hover{background-color:#2980b9;transform:translateY(-2px)}.btn-secondary{background-color:#6c757d;color:white}.btn-secondary:hover{background-color:#5a6268}.btn-success{background-color:#28a745;color:white}.btn-success:hover{background-color:#218838}.card{background:white;border-radius:8px;box-shadow:0 2px 10px rgba(0,0,0,0.1);padding:20px;margin-bottom:20px;transition:transform 0.3s ease,box-shadow 0.3s ease}.card:hover{transform:translateY(-5px);box-shadow:0 4px 20px rgba(0,0,0,0.15)}.card-title{color:#2c3e50;margin-bottom:10px}.card-text{color:#666;line-height:1.5}.navbar{background-color:#2c3e50;padding:1rem 0;box-shadow:0 2px 4px rgba(0,0,0,0.1)}.navbar-brand{color:white;font-size:1.5rem;font-weight:700;text-decoration:none}.navbar-nav{display:flex;list-style:none;gap:2rem;margin-left:auto}.nav-link{color:#ecf0f1;text-decoration:none;transition:color 0.3s ease}.nav-link:hover{color:#3498db}.row{display:flex;flex-wrap:wrap;margin:0 -15px}.col{flex:1;padding:0 15px}.col-1{flex:0 0 8.333333%}.col-2{flex:0 0 16.666667%}.col-3{flex:0 0 25%}.col-4{flex:0 0 33.333333%}.col-6{flex:0 0 50%}.col-8{flex:0 0 66.666667%}.col-12{flex:0 0 100%}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.mt-1{margin-top:0.25rem}.mt-2{margin-top:0.5rem}.mt-3{margin-top:1rem}.mt-4{margin-top:1.5rem}.mt-5{margin-top:3rem}.mb-1{margin-bottom:0.25rem}.mb-2{margin-bottom:0.5rem}.mb-3{margin-bottom:1rem}.mb-4{margin-bottom:1.5rem}.mb-5{margin-bottom:3rem}.p-1{padding:0.25rem}.p-2{padding:0.5rem}.p-3{padding:1rem}.p-4{padding:1.5rem}.p-5{padding:3rem}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:6px}.alert-info{color:#0c5460;background-color:#d1ecf1;border-color:#bee5eb}.alert-success{color:#155724;background-color:#d4edda;border-color:#c3e6cb}.alert-warning{color:#856404;background-color:#fff3cd;border-color:#ffeaa7}.alert-danger{color:#721c24;background-color:#f8d7da;border-color:#f5c6cb}.form-group{margin-bottom:1rem}.form-label{display:block;margin-bottom:0.5rem;font-weight:500;color:#333}.form-control{display:block;width:100%;padding:0.75rem;font-size:1rem;border:1px solid #ced4da;border-radius:6px;transition:border-color 0.3s ease,box-shadow 0.3s ease}.form-control:focus{outline:none;border-color:#3498db;box-shadow:0 0 0 3px rgba(52,152,219,0.1)}@media (max-width:768px){.container{padding:0 15px}.row{flex-direction:column}.col{flex:1;margin-bottom:1rem}.navbar-nav{flex-direction:column;gap:1rem}h1{font-size:2rem}h2{font-size:1.5rem}}.fade-in{animation:fadeIn 0.5s ease-in}@keyframes fadeIn{from{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.slide-up{animation:slideUp 0.6s ease-out}@keyframes slideUp{from{opacity:0;transform:translateY(30px)}to{opacity:1;transform:translateY(0)}}.static-file-badge{position:fixed;bottom:20px;right:20px;background-color:#28a745;color:white;padding:8px 12px;border-radius:20px;font-size:0.8rem;box-shadow:0 2px 10px rgba(0,0,0,0.2);z-index:1000}.static-file-badge:before{content:"📁 "}.contact-page-tailwind-style .tailwind-form-style{}.contact-page-tailwind-style .tailwind-form-style .unified-contact-form{background:transparent;border:none;padding:0;margin:0}.contact-page-tailwind-style .tailwind-form-style .form-header{display:none}.contact-page-tailwind-style .tailwind-form-style form{display:flex;flex-direction:column;gap:1.5rem}.contact-page-tailwind-style .tailwind-form-style .unified-form-field{margin-bottom:0}.contact-page-tailwind-style .tailwind-form-style label{display:block;font-size:0.875rem;font-weight:600;color:rgb(17 24 39);margin-bottom:0.5rem}.dark .contact-page-tailwind-style .tailwind-form-style label{color:rgb(249 250 251)}.contact-page-tailwind-style .tailwind-form-style input,.contact-page-tailwind-style .tailwind-form-style textarea{width:100%;padding:0.75rem 1rem;border:2px solid rgb(229 231 235);border-radius:0.5rem;background-color:rgb(255 255 255);color:rgb(17 24 39);outline:none;transition:all 0.2s ease-in-out;font-size:1rem}.dark .contact-page-tailwind-style .tailwind-form-style input,.dark .contact-page-tailwind-style .tailwind-form-style textarea{border-color:rgb(75 85 99);background-color:rgb(55 65 81);color:rgb(249 250 251)}.contact-page-tailwind-style .tailwind-form-style input:hover,.contact-page-tailwind-style .tailwind-form-style textarea:hover{border-color:rgb(209 213 219)}.dark .contact-page-tailwind-style .tailwind-form-style input:hover,.dark .contact-page-tailwind-style .tailwind-form-style textarea:hover{border-color:rgb(107 114 128)}.contact-page-tailwind-style .tailwind-form-style input:focus,.contact-page-tailwind-style .tailwind-form-style textarea:focus{border-color:rgb(59 130 246);box-shadow:0 0 0 2px rgb(59 130 246 / 0.2)}.contact-page-tailwind-style .tailwind-form-style textarea{resize:vertical;min-height:6rem}.contact-page-tailwind-style .tailwind-form-style .field-group-horizontal{display:grid;grid-template-columns:1fr;gap:1.5rem}@media (min-width:768px){.contact-page-tailwind-style .tailwind-form-style .field-group-horizontal{grid-template-columns:1fr 1fr}}.contact-page-tailwind-style .tailwind-form-style button[type="submit"]{background:linear-gradient(to right,rgb(37 99 235),rgb(147 51 234));color:white;padding:0.75rem 2rem;border-radius:0.5rem;font-weight:600;font-size:1.125rem;box-shadow:0 10px 15px -3px rgb(0 0 0 / 0.1),0 4px 6px -2px rgb(0 0 0 / 0.05);transition:all 0.3s ease;border:none;cursor:pointer;min-width:200px}.contact-page-tailwind-style .tailwind-form-style button[type="submit"]:hover{background:linear-gradient(to right,rgb(29 78 216),rgb(126 34 206));box-shadow:0 20px 25px -5px rgb(0 0 0 / 0.1),0 8px 10px -6px rgb(0 0 0 / 0.1);transform:scale(1.05)}.contact-page-tailwind-style .tailwind-form-style button[type="submit"]:focus{outline:2px solid transparent;outline-offset:2px;box-shadow:0 0 0 2px rgb(59 130 246)}.contact-page-tailwind-style .tailwind-form-style .submit-button-container{text-align:center;padding-top:1rem}.contact-page-tailwind-style .tailwind-form-style .field-error{color:rgb(239 68 68);font-size:0.875rem;margin-top:0.25rem}.contact-page-tailwind-style .tailwind-form-style .form-success{background-color:rgb(220 252 231);border:1px solid rgb(34 197 94);color:rgb(21 128 61);padding:1rem;border-radius:0.5rem;margin-bottom:1rem}.dark .contact-page-tailwind-style .tailwind-form-style .form-success{background-color:rgb(21 128 61 / 0.2);border-color:rgb(34 197 94);color:rgb(187 247 208)}.contact-page-tailwind-style .tailwind-form-style .form-submitting button[type="submit"]{opacity:0.7;cursor:not-allowed} \ No newline at end of file diff --git a/site/site/_public/styles/custom.css b/site/site/_public/styles/custom.css new file mode 100644 index 0000000..13dbecc --- /dev/null +++ b/site/site/_public/styles/custom.css @@ -0,0 +1,780 @@ +/* Custom CSS file for static file serving example */ + +/* Reset and base styles */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; + line-height: 1.6; + color: #333; + background-color: #f8f9fa; +} + +/* Container */ +.container { + max-width: 1200px; + margin: 0 auto; + padding: 0 20px; +} + +/* Typography */ +h1, +h2, +h3, +h4, +h5, +h6 { + margin-bottom: 1rem; + color: #2c3e50; +} + +h1 { + font-size: 2.5rem; + font-weight: 700; +} + +h2 { + font-size: 2rem; + font-weight: 600; +} + +h3 { + font-size: 1.5rem; + font-weight: 500; +} + +p { + margin-bottom: 1rem; +} + +/* Buttons */ +.btn-icon { + padding: 2px 4px !important; +} + +.btn { + display: inline-block; + /* padding: 12px 24px; */ + font-size: 1rem; + font-weight: 500; + text-decoration: none; + border: none; + border-radius: 6px; + cursor: pointer; + transition: all 0.3s ease; +} + +.btn-primary { + background-color: #3498db; + color: white; +} + +.btn-primary:hover { + background-color: #2980b9; + transform: translateY(-2px); +} + +.btn-secondary { + background-color: #6c757d; + color: white; +} + +.btn-secondary:hover { + background-color: #5a6268; +} + +.btn-success { + background-color: #28a745; + color: white; +} + +.btn-success:hover { + background-color: #218838; +} + +/* Cards */ +.card { + background: white; + border-radius: 8px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + padding: 20px; + margin-bottom: 20px; + transition: + transform 0.3s ease, + box-shadow 0.3s ease; +} + +.card:hover { + transform: translateY(-5px); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); +} + +.card-title { + color: #2c3e50; + margin-bottom: 10px; +} + +.card-text { + color: #666; + line-height: 1.5; +} + +/* Navigation */ +.navbar { + background-color: #2c3e50; + padding: 1rem 0; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.navbar-brand { + color: white; + font-size: 1.5rem; + font-weight: 700; + text-decoration: none; +} + +.navbar-nav { + display: flex; + list-style: none; + gap: 2rem; + margin-left: auto; +} + +.nav-link { + color: #ecf0f1; + text-decoration: none; + transition: color 0.3s ease; +} + +.nav-link:hover { + color: #3498db; +} + +/* Grid system */ +.row { + display: flex; + flex-wrap: wrap; + margin: 0 -15px; +} + +.col { + flex: 1; + padding: 0 15px; +} + +.col-1 { + flex: 0 0 8.333333%; +} +.col-2 { + flex: 0 0 16.666667%; +} +.col-3 { + flex: 0 0 25%; +} +.col-4 { + flex: 0 0 33.333333%; +} +.col-6 { + flex: 0 0 50%; +} +.col-8 { + flex: 0 0 66.666667%; +} +.col-12 { + flex: 0 0 100%; +} + +/* Utilities */ +.text-center { + text-align: center; +} + +.text-left { + text-align: left; +} + +.text-right { + text-align: right; +} + +.mt-1 { + margin-top: 0.25rem; +} +.mt-2 { + margin-top: 0.5rem; +} +.mt-3 { + margin-top: 1rem; +} +.mt-4 { + margin-top: 1.5rem; +} +.mt-5 { + margin-top: 3rem; +} + +.mb-1 { + margin-bottom: 0.25rem; +} +.mb-2 { + margin-bottom: 0.5rem; +} +.mb-3 { + margin-bottom: 1rem; +} +.mb-4 { + margin-bottom: 1.5rem; +} +.mb-5 { + margin-bottom: 3rem; +} + +.p-1 { + padding: 0.25rem; +} +.p-2 { + padding: 0.5rem; +} +.p-3 { + padding: 1rem; +} +.p-4 { + padding: 1.5rem; +} +.p-5 { + padding: 3rem; +} + +/* Alerts */ +.alert { + padding: 15px; + margin-bottom: 20px; + border: 1px solid transparent; + border-radius: 6px; +} + +.alert-info { + color: #0c5460; + background-color: #d1ecf1; + border-color: #bee5eb; +} + +.alert-success { + color: #155724; + background-color: #d4edda; + border-color: #c3e6cb; +} + +.alert-warning { + color: #856404; + background-color: #fff3cd; + border-color: #ffeaa7; +} + +.alert-danger { + color: #721c24; + background-color: #f8d7da; + border-color: #f5c6cb; +} + +/* Forms */ +.form-group { + margin-bottom: 1rem; +} + +.form-label { + display: block; + margin-bottom: 0.5rem; + font-weight: 500; + color: #333; +} + +.form-control { + display: block; + width: 100%; + padding: 0.75rem; + font-size: 1rem; + border: 1px solid #ced4da; + border-radius: 6px; + transition: + border-color 0.3s ease, + box-shadow 0.3s ease; +} + +.form-control:focus { + outline: none; + border-color: #3498db; + box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1); +} + +/* Responsive design */ +@media (max-width: 768px) { + .container { + padding: 0 15px; + } + + .row { + flex-direction: column; + } + + .col { + flex: 1; + margin-bottom: 1rem; + } + + .navbar-nav { + flex-direction: column; + gap: 1rem; + } + + h1 { + font-size: 2rem; + } + + h2 { + font-size: 1.5rem; + } +} + +/* Animation utilities */ +.fade-in { + animation: fadeIn 0.5s ease-in; +} + +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.slide-up { + animation: slideUp 0.6s ease-out; +} + +@keyframes slideUp { + from { + opacity: 0; + transform: translateY(30px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* Static file serving indicator */ +.static-file-badge { + position: fixed; + bottom: 20px; + right: 20px; + background-color: #28a745; + color: white; + padding: 8px 12px; + border-radius: 20px; + font-size: 0.8rem; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); + z-index: 1000; +} + +.static-file-badge:before { + content: "📁 "; +} + +/* All images inside post body: never overflow the column. */ +.post-content-body img { + max-width: 100%; + height: auto; +} + +/* Post body: lead thumbnail image rendered from markdown (first paragraph, lone img) */ +.post-content-body > p:first-child > img:only-child { + width: 100%; + max-height: 24rem; + object-fit: contain; + border-radius: 0.5rem; + margin-bottom: 1rem; +} + +/* Hide duplicate H1 title from markdown body — already shown in the header section. + Covers two layouts: + - thumbnail-first:

    → adjacent sibling after first p + - no thumbnail:

    as first child */ +.post-content-body > p:first-child + h1, +.post-content-body > h1:first-child { + display: none; +} + +/* Content grid card thumbnail */ +.grid-post-figure { + max-height: 11rem; +} + +.grid-post-img { + height: 100%; + max-height: 11rem; + object-fit: contain; + margin: 0 auto; +} + +/* Theme-aware thumbnails: show light img on light theme, dark img on dark theme */ +.grid-post-img-dark { + display: none; +} + +[data-theme="dark"] .grid-post-img-light { + display: none; +} + +[data-theme="dark"] .grid-post-img-dark { + display: block; +} + +/* ── Content graph mini ────────────────────────────────────────────────────── */ + +/* Constrain graph to sidebar width; prevent overflow on mobile */ +.content-graph-mini-wrapper { + max-width: 280px; +} + +/* Preview: scales SVG to wrapper width, zoom-in cursor */ +.content-graph-mini-preview { + cursor: zoom-in; + border-radius: 0.5rem; + overflow: hidden; + transition: opacity 0.15s; +} + +.content-graph-mini-preview:hover { + opacity: 0.85; +} + +.content-graph-mini-preview svg { + width: 100%; + height: auto; + display: block; +} + +/* Dark-mode overrides for SVG CSS custom properties. + The SVG embeds +
    + + +
    + + +# Why I Needed Rust, Finally + +I gave this talk at **Rustikon 2026** (Warsaw, March 19, 2026): *Why I Needed Rust, Finally — Infrastructure Automation I Can Sleep On*. It's the story of a long resistance and of what finally broke it. + +## The resistance + +For years I automated infrastructure with the usual tooling: scripts, templates, layers of YAML that validate late — once they are already running against a real environment. It worked until it didn't, and the failure always showed up at the worst possible moment: at deploy time, never before. + +## What broke it + +Rust didn't win me over on performance. It won me over because it moves failure **to the left**: from deploy to the compiler. A type that doesn't line up never reaches production — it doesn't even run. That guarantee, applied to the provisioning layer, is what turns "I hope this deploys" into "this cannot deploy wrong in that way." + +## Infrastructure you can sleep on + +The title isn't a slogan. When configuration is the source of truth and it's typed, and the system refuses to start if something doesn't fit, being on call stops being an act of faith. That was the point in Warsaw: not that Rust is fast, but that it lets you **sleep on it**. diff --git a/site/site/content/activities/es/charlas/rustikon-2026-why-i-needed-rust.md b/site/site/content/activities/es/charlas/rustikon-2026-why-i-needed-rust.md new file mode 100644 index 0000000..3b82ad4 --- /dev/null +++ b/site/site/content/activities/es/charlas/rustikon-2026-why-i-needed-rust.md @@ -0,0 +1,92 @@ +--- +# Activity metadata +id: "rustikon-2026-why-i-needed-rust" +title: "Por qué necesitaba Rust, por fin" +slug: "rustikon-2026-por-que-necesitaba-rust" +subtitle: "Automatización de infraestructura sobre la que se puede dormir tranquilo — Rustikon 2026, Varsovia" +excerpt: "Mi charla en Rustikon 2026: por qué, tras años resistiéndome, Rust acabó siendo la pieza que me dejó automatizar infraestructura sin miedo. Tipos que atrapan el fallo antes del despliegue, y una base sobre la que de verdad se puede dormir tranquilo. La charla está en inglés." + +# Publication info +author: "Jesús Pérez" +date: "2026-03-19" +published: true +featured: true + +# Categorization +category: "charlas" +tags: ["rust", "infraestructura", "automatizacion", "provisioning", "rustikon", "developer"] + +# Card thumbnail + social/feature image +thumbnail: "/images/rustikon-2026-poster.webp" +image_url: "/images/rustikon-2026-poster.webp" + +# Display +sort_order: 1 +css_class: "category-charlas" + +# Event-specific metadata (activity schema) +metadata: + event_type: "talk" + event_date: "2026-03-19" + event_location: "Varsovia, Polonia" + gallery: + - "/images/rustikon-2026-stage.webp" +--- + + + +
    + + +
    + + +# Por qué necesitaba Rust, por fin + +Presenté esta charla en **Rustikon 2026** (Varsovia, 19 de marzo de 2026). El título lo dice sin rodeos: *Why I Needed Rust, Finally — Infrastructure Automation I Can Sleep On*. Es la historia de una resistencia larga y de qué la rompió. + +> La charla está en inglés. Las diapositivas las iremos publicando traducidas. + +## La resistencia + +Durante años automaticé infraestructura con las herramientas de siempre: scripts, plantillas, capas de YAML que se validan tarde — cuando ya están corriendo contra un entorno real. Funcionaba hasta que dejaba de funcionar, y el fallo aparecía siempre en el peor momento: en el despliegue, no antes. + +## Lo que la rompió + +Rust no me atrajo por rendimiento. Me atrajo porque mueve el fallo **hacia la izquierda**: del despliegue al compilador. Un tipo que no cuadra no llega a producción — no se ejecuta siquiera. Esa garantía, aplicada a la capa de provisión, es lo que convierte "espero que esto despliegue bien" en "esto no puede desplegar mal de esta forma". + +## Infraestructura sobre la que dormir + +El título no es un eslogan. Cuando la configuración es la fuente de verdad y está tipada, y el sistema se niega a arrancar si algo no encaja, el turno de guardia deja de ser un acto de fe. Eso es lo que quería contar en Rustikon: no que Rust sea rápido, sino que deja **dormir tranquilo**. diff --git a/site/site/content/adr/en/accepted/adr-001.md b/site/site/content/adr/en/accepted/adr-001.md new file mode 100644 index 0000000..595a83b --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-001.md @@ -0,0 +1,33 @@ +--- +id: "adr-001" +title: "Ontoref is a Standalone Protocol Project, Not Part of Stratumiops" +slug: "001" +subtitle: "Accepted" +excerpt: "The ontology/reflection patterns originated inside stratumiops as self-description tooling (stratum-ontology-core, stratum-reflection-core, stratum-daemon). Consumer projects (typedialog, vapora, kogra" +author: "ontoref" +date: "2026-03-12" +published: true +featured: false +category: "accepted" +tags: ["adr-001", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    The ontology/reflection patterns originated inside stratumiops as self-description tooling (stratum-ontology-core, stratum-reflection-core, stratum-daemon). Consumer projects (typedialog, vapora, kogral) needed the same patterns but had no path to adoption that did not entangle them with stratumiops' pipeline-specific crates (stratum-graph, stratum-state, stratum-orchestrator). Protocol evolution (schema changes, ADR lifecycle, daemon features) was blocked behind stratumiops release cycles. The three crates were logically a specification layer that happened to live in the wrong repo.

    + +

    Decision

    + +

    Ontoref is extracted as a standalone protocol project with independent versioning, CI, and crates: ontoref-ontology (Rust types for the ontology graph), ontoref-reflection (mode runner and schema validation), ontoref-daemon (NCL export cache, file watcher, actor registry). Consumer projects adopt the protocol via a thin `scripts/ontoref` bash wrapper and a `.ontoref/config.ncl` declaration. The ontoref project itself is allowed to use stratum-db and platform-nats as optional peer dependencies via workspace path references, since those crates are infrastructural rather than domain-specific.

    + +

    Constraints

    + +
    • Hard ontoref crates must not import stratumiops domain crates: stratum-graph, stratum-state, stratum-orchestrator, stratum-llm, stratum-embeddings
    • Hard The ontoref entry point must not unconditionally overwrite ONTOREF_PROJECT_ROOT — it must default only when unset
    • Soft A consumer project must only need .ontoref/config.ncl and scripts/ontoref to adopt the protocol — no other files copied into the consumer
    + +

    Alternatives considered

    + +
    • Keep all tooling in stratumiops, consumers depend on it as a git subtree or submodulerejected: Submodule/subtree patterns create update friction and do not solve the versioning coupling. Every consumer needs stratumiops' full dependency tree even for protocol-only use.
    • Publish ontoref crates to crates.io and consume via version pinsrejected: Rapid iteration on protocol schemas makes published crate semantics too rigid at this stage. Path dependencies allow simultaneous development of protocol and consumers without publication ceremony.
    • Inline protocol tooling into each consumer project separatelyrejected: Schema drift across projects would immediately arise. The protocol's value is precisely its shared contract — decentralizing it defeats the purpose.
    + +

    Related ADRs

    + +

    ADR-002

    diff --git a/site/site/content/adr/en/accepted/adr-001.ncl b/site/site/content/adr/en/accepted/adr-001.ncl new file mode 100644 index 0000000..8757d63 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-001.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-001", + title = "Ontoref is a Standalone Protocol Project, Not Part of Stratumiops", + slug = "001", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/001", + graph = { + implements = [], + related_to = ["adr-002"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-002.md b/site/site/content/adr/en/accepted/adr-002.md new file mode 100644 index 0000000..61a8cd3 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-002.md @@ -0,0 +1,33 @@ +--- +id: "adr-002" +title: "Ontoref Daemon for NCL Caching, File Watching, and Actor Notification Barrier" +slug: "002" +subtitle: "Accepted" +excerpt: "Nushell reflection modules invoke `nickel export` as a subprocess ~39 times per full sync scan, taking 2m42s. Each invocation forks a new process (~100ms). There is no shared state between developers a" +author: "ontoref" +date: "2026-03-12" +published: true +featured: false +category: "accepted" +tags: ["adr-002", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    Nushell reflection modules invoke `nickel export` as a subprocess ~39 times per full sync scan, taking 2m42s. Each invocation forks a new process (~100ms). There is no shared state between developers and agents working on the same project, no notification when a peer changes an ontology or ADR file mid-session, and no persistent store for scan results. The protocol-not-runtime axiom forbids required runtime services — any daemon must be optional with full subprocess fallback. This ADR supersedes stratumiops adr-007, which designed this system inside stratumiops before the protocol was extracted to ontoref.

    + +

    Decision

    + +

    ontoref-daemon is an optional persistent daemon providing: (1) NCL export caching — results keyed by (path, mtime, import_path) served via HTTP, file watcher invalidates on change; (2) actor registry — developers, agents, CI register on startup with deterministic tokens (type:hostname:pid), sweep reaps stale sessions every 30s; (3) notification barrier — file changes in .ontology/, adrs/, reflection/ generate typed notifications stored in a per-project ring buffer; pre-commit hook queries pending notifications and blocks commits until acknowledged (fail-open: daemon down = commit allowed). Consumer projects configure the daemon via `.ontoref/config.ncl` (daemon.enabled, daemon.port, db.enabled, db.url). All Nushell modules fall back to direct `nickel export` subprocess when the daemon is unavailable. stratum-db (optional feature, path dep on stratumiops) handles SurrealDB persistence. platform-nats (optional feature, path dep on stratumiops) handles NATS event publishing.

    + +

    Constraints

    + +
    • Hard No Nushell module or bash script may fail when ontoref-daemon is unavailable
    • Hard ontoref-daemon must bind to 127.0.0.1, never to 0.0.0.0 or a public interface
    • Hard The pre-commit hook must allow commits when ontoref-daemon is unreachable, printing a warning but not blocking
    • Soft All daemon HTTP requests from consumer wrappers must include X-Ontoref-Project header or equivalent project scoping
    + +

    Alternatives considered

    + +
    • In-process Nickel evaluation via nickel-lang-core libraryrejected: nickel-lang-core has an unstable Rust API and resolves import paths differently from the CLI. The subprocess approach with caching is simpler, more stable, and already <1ms cached.
    • Required daemon (always running, no fallback)rejected: Violates the protocol-not-runtime axiom. Consumer projects must function without any ontoref process running. The daemon is an optimization and awareness layer, not infrastructure.
    • Filesystem-based JSON cache without a daemon processrejected: File-based caching requires lock management, cannot serve concurrent requests from multiple actors, and does not provide file watching or the actor registry. A daemon centralizes these concerns cleanly.
    • Bundle SurrealDB and NATS clients directly in ontoref, not as path depsrejected: Duplicating stratum-db and platform-nats creates divergence. Both crates are general-purpose infrastructure; ontoref consumers already have stratumiops checked out for other reasons. Path deps preserve the single canonical implementation.
    + +

    Related ADRs

    + +

    ADR-001

    diff --git a/site/site/content/adr/en/accepted/adr-002.ncl b/site/site/content/adr/en/accepted/adr-002.ncl new file mode 100644 index 0000000..e284d0b --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-002.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-002", + title = "Ontoref Daemon for NCL Caching, File Watching, and Actor Notification Barrier", + slug = "002", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/002", + graph = { + implements = [], + related_to = ["adr-001"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-003.md b/site/site/content/adr/en/accepted/adr-003.md new file mode 100644 index 0000000..b602338 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-003.md @@ -0,0 +1,33 @@ +--- +id: "adr-003" +title: "Q&A and Accumulated Knowledge Persist to NCL, Not Browser Storage" +slug: "003" +subtitle: "Accepted" +excerpt: "The initial Q&A bookmarks feature stored entries in browser localStorage keyed by project. This is convenient but violates the dag-formalized axiom: knowledge that lives only in a browser session is in" +author: "ontoref" +date: "2026-03-12" +published: true +featured: false +category: "accepted" +tags: ["adr-003", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    The initial Q&A bookmarks feature stored entries in browser localStorage keyed by project. This is convenient but violates the dag-formalized axiom: knowledge that lives only in a browser session is invisible to agents, not git-versioned, not queryable via MCP, and is lost on browser data reset. The same problem applies to quick actions and any other accumulated operational knowledge. The system accumulates knowledge during development sessions (AI interactions, architectural reviews, debugging) that should be first-class artifacts — not ephemeral browser state.

    + +

    Decision

    + +

    All Q&A entries persist to `reflection/qa.ncl` — a typed NCL record file governed by `reflection/schemas/qa.ncl`. Mutations happen via `crates/ontoref-daemon/src/ui/qa_ncl.rs` (line-level surgery, same pattern as backlog_ncl.rs). Four MCP tools expose the store to AI agents: `ontoref_qa_list` (read, with filter), `ontoref_qa_add` (append), `ontoref_qa_delete` (remove block), `ontoref_qa_update` (field mutation). HTTP endpoints `/qa-json` (GET), `/qa/add`, `/qa/delete`, `/qa/update` (POST) serve the UI. The UI renders server-side entries via Tera template injection (SERVER_ENTRIES JSON blob), eliminating the need for a separate fetch on load. The same principle is applied to quick actions: they are declared in `.ontoref/config.ncl` (quick_actions array) and new modes are created as `reflection/modes/<id>.ncl` files via `ontoref_action_add`.

    + +

    Constraints

    + +
    • Hard All mutations to reflection/qa.ncl must go through crates/ontoref-daemon/src/ui/qa_ncl.rs — no direct file writes from other call sites
    • Hard reflection/qa.ncl must conform to the QaStore contract from reflection/schemas/qa.ncl — nickel typecheck must pass
    • Hard MCP tools ontoref_qa_list and ontoref_qa_add must never trigger sync apply steps or modify .ontology/ files
    + +

    Alternatives considered

    + +
    • Keep localStorage as the storage backend, add optional sync to NCL on explicit user actionrejected: Two sources of truth creates sync complexity and divergence. AI agents would still not see localStorage entries. The sync step would be frequently skipped. NCL as primary is simpler.
    • Store Q&A in SurrealDB via stratum-db (existing optional dependency)rejected: Requires the db feature and a running SurrealDB instance. NCL files are always present, git-versioned, and work without any database. The protocol-not-runtime axiom argues for file-first. SurrealDB can be added as a secondary index later if full-text search is needed.
    • Full AST parse of qa.ncl via nickel-lang-core for mutationsrejected: nickel-lang-core has an unstable Rust API. The file structure is predictable enough for line-level surgery. backlog_ncl.rs has been operating safely with this pattern. Adding a hard dependency on nickel-lang-core for a file that is always written by the daemon is unnecessary complexity.
    + +

    Related ADRs

    + +

    ADR-001 · ADR-002

    diff --git a/site/site/content/adr/en/accepted/adr-003.ncl b/site/site/content/adr/en/accepted/adr-003.ncl new file mode 100644 index 0000000..5330e0e --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-003.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-003", + title = "Q&A and Accumulated Knowledge Persist to NCL, Not Browser Storage", + slug = "003", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/003", + graph = { + implements = [], + related_to = ["adr-001", "adr-002"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-004.md b/site/site/content/adr/en/accepted/adr-004.md new file mode 100644 index 0000000..867921f --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-004.md @@ -0,0 +1,33 @@ +--- +id: "adr-004" +title: "NCL Pipe Bootstrap — Config Validation and Secret Injection via Unix Pipeline" +slug: "004" +subtitle: "Accepted" +excerpt: "Ontoref-daemon and any process that receives structured config faces two problems: (1) Nickel NCL requires a subprocess to evaluate, introducing a system-call injection surface if the daemon itself cal" +author: "ontoref" +date: "2026-03-13" +published: true +featured: false +category: "accepted" +tags: ["adr-004", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    Ontoref-daemon and any process that receives structured config faces two problems: (1) Nickel NCL requires a subprocess to evaluate, introducing a system-call injection surface if the daemon itself calls `nickel export` at runtime; (2) credentials and secrets embedded in config files (TOML, JSON) persist on disk after the process starts, creating a forensic artifact. The existing registry.toml approach (NCL → TOML file → daemon reads file) partially addresses the first problem but not the second — the TOML file remains on disk with hashed credentials. SOPS and Vault are standard secret management tools that produce decrypted output on stdout.

    + +

    Decision

    + +

    All config delivery to long-running processes follows a three-stage Unix pipeline: Stage 1 — structural validation: `nickel export --format json config.ncl` produces JSON with schema-validated structure but no secret values; Stage 2 — secret injection (optional): SOPS decrypt or Vault lookup merges credentials into the JSON stream; Stage 3 — process bootstrap: the target process reads the composed JSON from stdin via `--config-stdin`. No intermediate file is written to disk. If any stage fails, the pipeline breaks and the process does not start. A bash wrapper script (not Nu — Nu may not be available at service boot time) orchestrates the pipeline. A Nu helper `ncl-bootstrap` provides the same interface for interactive/development use.

    + +

    Constraints

    + +
    • Hard The bootstrap pipeline must not write an intermediate config file to disk at any stage
    • Hard The bash wrapper must depend only on bash, nickel, and the target binary — no Nu, no jq unless SOPS/Vault stage is active
    • Hard The target process must redirect stdin to /dev/null after reading the config JSON
    • Hard NCL config files used with ncl-bootstrap must not contain plaintext secret values — only SecretRef placeholders or empty fields
    + +

    Alternatives considered

    + +
    • TOML file on disk (current registry.toml approach)rejected: File persists on disk with credentials. Stale file may be read after config changes. Requires explicit cleanup logic. Forensic artifact risk.
    • Environment variables for secretsrejected: Environment variables are visible in /proc/PID/environ on Linux and via `ps eww` on some systems. They persist for the lifetime of the process and are inherited by child processes. Worse attack surface than stdin pipe.
    • Encrypted TOML file (AES256 at rest)rejected: Decryption key must be available at runtime — the problem is deferred, not solved. The decrypted form still passes through disk (tmpfs or swap). Adds a custom encryption layer instead of using standard tools (SOPS, Vault) that the ecosystem already supports.
    • Daemon reads NCL directly at runtime via nickel-lang-corerejected: nickel-lang-core has an unstable Rust API. More critically, it means the daemon can evaluate arbitrary Nickel — including NCL files with system calls via builtins. The pipeline approach ensures the daemon only ever sees validated JSON, never executable Nickel.
    + +

    Related ADRs

    + +

    ADR-002

    diff --git a/site/site/content/adr/en/accepted/adr-004.ncl b/site/site/content/adr/en/accepted/adr-004.ncl new file mode 100644 index 0000000..43e213f --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-004.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-004", + title = "NCL Pipe Bootstrap — Config Validation and Secret Injection via Unix Pipeline", + slug = "004", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/004", + graph = { + implements = [], + related_to = ["adr-002"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-005.md b/site/site/content/adr/en/accepted/adr-005.md new file mode 100644 index 0000000..6b23b4b --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-005.md @@ -0,0 +1,33 @@ +--- +id: "adr-005" +title: "Unified Key-to-Session Auth Model Across CLI, UI, and MCP" +slug: "005" +subtitle: "Accepted" +excerpt: "ontoref-daemon exposes project knowledge and mutations over HTTP, a browser UI, and an MCP server. Projects can define argon2id-hashed keys in project.ncl (with role admin|viewer and an audit label). P" +author: "ontoref" +date: "2026-03-13" +published: true +featured: false +category: "accepted" +tags: ["adr-005", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    ontoref-daemon exposes project knowledge and mutations over HTTP, a browser UI, and an MCP server. Projects can define argon2id-hashed keys in project.ncl (with role admin|viewer and an audit label). Prior to this ADR, the UI login flow set a session cookie but the REST API accepted raw passwords as Bearer tokens on every request — each call paying ~100ms for argon2 verification. The CLI had no Bearer support at all; project-add and project-remove called the daemon without credentials. The daemon manage page had no admin identity concept. There was no way to enumerate or revoke active sessions.

    + +

    Decision

    + +

    All surfaces exchange a raw key once via POST /sessions for a UUID v4 bearer token (30-day lifetime, in-memory SessionStore). The session token is used for all subsequent calls — O(1) DashMap lookup. Sessions carry a stable public id (distinct from the bearer token) for safe list/revoke operations without leaking credentials. Project keys have a label field for audit trail. Daemon-level admin uses a separate argon2id hash (ONTOREF_ADMIN_TOKEN_FILE preferred over ONTOREF_ADMIN_TOKEN) and creates sessions under virtual slug '_daemon'. The CLI injects ONTOREF_TOKEN as Authorization: Bearer automatically via bearer-args in store.nu. Key rotation (PUT /projects/{slug}/keys) revokes all active sessions for the rotated project. GET /sessions and DELETE /sessions/{id} implement two-tier visibility: project admin sees own project sessions; daemon admin sees all.

    + +

    Constraints

    + +
    • Hard GET /sessions responses must never include the bearer token, only the public session id
    • Hard POST /sessions must not require authentication — it is the credential exchange endpoint
    • Hard PUT /projects/{slug}/keys must call revoke_all_for_slug before persisting new keys
    • Soft All CLI HTTP calls to the daemon must use bearer-args from store.nu — no hardcoded curl without auth args
    + +

    Alternatives considered

    + +
    • Verify argon2 on every Bearer request (no session concept)rejected: ~100ms per request is unacceptable for CLI invocations (each command is a new HTTP call) and MCP tool sequences (multiple calls per agent turn). Session token lookup is sub-microsecond.
    • Axum middleware for auth instead of per-handler check_primary_authrejected: Middleware applies uniformly to all routes. ontoref-daemon coexists auth-enabled and open projects on the same router — some endpoints are always public (health, POST /sessions itself), some require project-scoped auth, some require daemon admin. Per-handler checks encode these rules explicitly; middleware would require complex route exemption logic.
    • Expose bearer token in session list responsesrejected: GET /sessions is accessible to project admins. Exposing the bearer would allow any project admin to impersonate any other session holder. The public session.id is a safe substitute for revocation targeting.
    • Separate token store per projectrejected: A single DashMap keyed by token with a slug field in SessionEntry is sufficient. A secondary id_index DashMap gives O(1) revoke-by-id. Per-project sharding would add complexity without benefit given the expected session count (tens, not millions).
    • JWT instead of opaque UUID v4 tokensrejected: JWTs are self-contained and cannot be revoked without a denylist. Opaque tokens enable instant revocation (key rotation, logout, admin force-revoke) with O(1) lookup. The daemon is local-only — there is no distributed verification scenario that would justify JWT complexity.
    + +

    Related ADRs

    + +

    ADR-002

    diff --git a/site/site/content/adr/en/accepted/adr-005.ncl b/site/site/content/adr/en/accepted/adr-005.ncl new file mode 100644 index 0000000..2f23d45 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-005.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-005", + title = "Unified Key-to-Session Auth Model Across CLI, UI, and MCP", + slug = "005", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/005", + graph = { + implements = [], + related_to = ["adr-002"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-006.md b/site/site/content/adr/en/accepted/adr-006.md new file mode 100644 index 0000000..6d545d4 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-006.md @@ -0,0 +1,29 @@ +--- +id: "adr-006" +title: "Nushell 0.111 String Interpolation Compatibility Fix" +slug: "006" +subtitle: "Accepted" +excerpt: "Nushell 0.111 introduced a breaking change in string interpolation parsing: expressions inside `$'...'` that match the pattern `(identifier: expr)` are now parsed as command calls rather than as record" +author: "ontoref" +date: "2026-03-14" +published: true +featured: false +category: "accepted" +tags: ["adr-006", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    Nushell 0.111 introduced a breaking change in string interpolation parsing: expressions inside `$"..."` that match the pattern `(identifier: expr)` are now parsed as command calls rather than as record literals or literal text. This broke four print statements in reflection/bin/ontoref.nu that used patterns like `(kind: ($kind))`, `(logo: ($logo_file))`, `(parents: ($parent_slugs))`, and `(POST /actors/register)`. The bug manifested when running `ontoref setup` and `ontoref hooks-install` on any consumer project using Nu 0.111+. The minimum Nu version gate (>= 0.110.0) did not catch 0.111 regressions since it only guards the lower bound.

    + +

    Decision

    + +

    Fix all four affected print statements by removing the outer parentheses from label-value pairs inside string interpolations, or by removing the `$` prefix from strings that contain no variable interpolation. The fix is minimal and non-semantic: `(kind: ($kind))` becomes `kind: ($kind)` (literal label + variable), and `$"(POST /actors/register)"` becomes `"(POST /actors/register)"` (plain string). The fix is applied to both the dev repo (reflection/bin/ontoref.nu) and the installed copy (~/.local/bin/ontoref via just install-daemon). The minimum version gate remains >= 0.110.0 but 0.111 is now the tested floor.

    + +

    Constraints

    + +
    • Hard String interpolations in ontoref.nu must not use `(identifier: expr)` patterns — use bare `identifier: (expr)` instead
    • Soft Print statements with no variable interpolation must use plain strings, not `$"..."`
    + +

    Alternatives considered

    + +
    • Raise minimum Nu version to 0.111 and document the breaking changerejected: Does not fix the broken syntax — just makes the breakage explicit. Consumer projects already on 0.111 would still fail until the print statements are fixed.
    • Use escape sequences or string concatenation to embed literal parensrejected: Nushell has no escape for parens in string interpolation. String concatenation (e.g. `'(kind: ' + $kind + ')'`) works but is significantly less readable than bare `kind: ($kind)`.
    diff --git a/site/site/content/adr/en/accepted/adr-006.ncl b/site/site/content/adr/en/accepted/adr-006.ncl new file mode 100644 index 0000000..92ac6e4 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-006.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-006", + title = "Nushell 0.111 String Interpolation Compatibility Fix", + slug = "006", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/006", + graph = { + implements = [], + related_to = [], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-007.md b/site/site/content/adr/en/accepted/adr-007.md new file mode 100644 index 0000000..05f6e64 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-007.md @@ -0,0 +1,33 @@ +--- +id: "adr-007" +title: "API Surface Discoverability via #[onto_api] Proc-Macro" +slug: "007" +subtitle: "Accepted" +excerpt: "ontoref-daemon exposes ~28 HTTP routes across api.rs, sync.rs, and other handler modules. Before this decision, the authoritative route list existed only in the axum Router definition — undiscoverabl" +author: "ontoref" +date: "2026-03-23" +published: true +featured: false +category: "accepted" +tags: ["adr-007", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    ontoref-daemon exposes ~28 HTTP routes across api.rs, sync.rs, and other handler modules. Before this decision, the authoritative route list existed only in the axum Router definition — undiscoverable without reading source. MCP agents, CLI users, and the web UI had no machine-readable way to enumerate routes, their auth requirements, parameter shapes, or actor restrictions. OpenAPI was considered but rejected as a runtime dependency that would require schema maintenance separate from the handler code. The `#[onto_api]` proc-macro in `ontoref-derive` addresses this by making the handler annotation the single source of truth: the macro emits `inventory::submit!(ApiRouteEntry{...})` at link time, and `api_catalog::catalog()` collects them via `inventory::collect!`. No runtime registry, no startup allocation, no separate schema file.

    + +

    Decision

    + +

    Every HTTP handler in ontoref-daemon must carry `#[onto_api(method, path, description, auth, actors, params, tags)]`. The proc-macro (in `crates/ontoref-derive`) emits `inventory::submit!(ApiRouteEntry{...})` at link time. `GET /api/catalog` calls `api_catalog::catalog()` — a pure function over `inventory::iter::<ApiRouteEntry>()` — and returns the annotated surface as JSON. The web UI at `/ui/{slug}/api` renders it with client-side filtering. `describe api [--actor] [--tag] [--auth] [--fmt]` queries this endpoint from the CLI. The MCP tool `ontoref_api_catalog` calls `catalog()` directly without HTTP. This surfaces the complete API to three actors (browser, CLI, MCP agent) from one annotation site per handler.

    + +

    Constraints

    + +
    • Hard Every public HTTP handler in ontoref-daemon must carry #[onto_api(...)]
    • Hard inventory must remain a workspace dependency gated behind the 'catalog' feature of ontoref-derive; ontoref-ontology must not depend on inventory
    + +

    Alternatives considered

    + +
    • OpenAPI / utoipa with generated JSON schemarejected: Requires maintaining a separate schema artifact (openapi.json) and a runtime schema struct tree. The schema can drift from actual handler signatures. utoipa adds ~15 transitive deps including serde_yaml. Violates 'Protocol, Not Runtime' — the schema becomes a runtime artifact rather than a compile-time invariant.
    • Manual route registry (Vec<RouteInfo> in main.rs)rejected: A manually maintained Vec has guaranteed drift: handlers are added, routes change, and the Vec is updated inconsistently. Proven failure mode in the previous session where insert_mcp_ctx listed 15 tools while the router had 27.
    • Runtime reflection via axum Router introspectionrejected: axum does not expose a stable introspection API for registered routes. Workarounds (tower_http trace layer capture, method_router hacks) are brittle across axum versions and cannot surface handler metadata (auth, actors, params).
    + +

    Related ADRs

    + +

    ADR-001

    diff --git a/site/site/content/adr/en/accepted/adr-007.ncl b/site/site/content/adr/en/accepted/adr-007.ncl new file mode 100644 index 0000000..c86a4f8 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-007.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-007", + title = "API Surface Discoverability via #[onto_api] Proc-Macro", + slug = "007", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/007", + graph = { + implements = [], + related_to = ["adr-001"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-008.md b/site/site/content/adr/en/accepted/adr-008.md new file mode 100644 index 0000000..9186346 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-008.md @@ -0,0 +1,33 @@ +--- +id: "adr-008" +title: "NCL-First Config Validation and Override-Layer Mutation" +slug: "008" +subtitle: "Accepted" +excerpt: "The config surface feature adds per-project config introspection and mutation to ontoref-daemon. Two design questions arise: (1) Where does config field validation live — in NCL contracts, in Rust st" +author: "ontoref" +date: "2026-03-26" +published: true +featured: false +category: "accepted" +tags: ["adr-008", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    The config surface feature adds per-project config introspection and mutation to ontoref-daemon. Two design questions arise: (1) Where does config field validation live — in NCL contracts, in Rust struct validation, or both? (2) How does a PUT /config/{section} request mutate a project's config without corrupting the source NCL files? Direct mutation via nickel export → JSON → write-back destroys NCL comments, contract annotations, and section merge structure. Duplicating validation in both NCL and Rust creates two sources of truth with guaranteed divergence. The config surface spans ontoref's own .ontoref/config.ncl and all consumer-project configs, making the choice of validation ownership a protocol-level constraint.

    + +

    Decision

    + +

    NCL contracts (std.contract.from_validator) are the single validation layer for all config fields. Rust serde structs are contract-trusted readers: they carry #[serde(default)] and consume pre-validated JSON from nickel export — no validator(), no custom Deserialize, no duplicate field constraints. Config mutation via PUT /projects/{slug}/config/{section} never modifies the original NCL source files. Instead it writes a {section}.overrides.ncl file containing only the changed fields plus a _overrides_meta audit record (actor, reason, timestamp, previous values), then appends a single idempotent import line to the entry-point NCL (using NCL's & merge operator so the override wins). nickel export validates the merged result against the section's declared contract before the mutation is committed; validation failure reverts the override file and returns the nickel error verbatim. Ontoref demonstrates this pattern on itself: .ontoref/contracts.ncl declares LogConfig and DaemonConfig contracts applied in .ontoref/config.ncl.

    + +

    Constraints

    + +
    • Hard The daemon must never write to original NCL config source files during a PUT /config/{section} mutation; only {section}.overrides.ncl may be created or modified
    • Hard Rust config structs must not implement custom field validation; all field constraints live in NCL contracts applied before JSON reaches Rust
    • Soft Every {section}.overrides.ncl file written by the daemon must contain a top-level _overrides_meta record with managed_by, created_at, and entries fields
    + +

    Alternatives considered

    + +
    • Duplicate validation in Rust (validator crate or custom Deserialize)rejected: Two validators for the same field inevitably diverge. The NCL contract is already the authoritative schema for documentation, MCP export, and quickref generation — adding a Rust duplicate makes it decorative. validator crate adds 8+ transitive dependencies and requires annotation churn across every config struct.
    • Direct NCL file mutation (read → merge JSON → overwrite)rejected: nickel export → JSON write-back destroys comments, contract annotations (| C.LogConfig), section merge structure, and in-file rationale. The resulting file is syntactically valid but semantically impoverished. Once a file is overwritten this way, the original structure cannot be recovered from git history if the file was also changed manually between sessions.
    • Separate config store (JSON or TOML side-file)rejected: A side-file in a different format bypasses NCL type safety entirely — the merge operator and contract validation no longer apply. The daemon would need a custom merge algorithm to reconcile the side-file with the source NCL, and agents would need to understand two config representations. NCL's & merge operator is purpose-built for this use case.
    + +

    Related ADRs

    + +

    ADR-002 · ADR-007

    diff --git a/site/site/content/adr/en/accepted/adr-008.ncl b/site/site/content/adr/en/accepted/adr-008.ncl new file mode 100644 index 0000000..6d4214e --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-008.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-008", + title = "NCL-First Config Validation and Override-Layer Mutation", + slug = "008", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/008", + graph = { + implements = [], + related_to = ["adr-002", "adr-007"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-009.md b/site/site/content/adr/en/accepted/adr-009.md new file mode 100644 index 0000000..ae62b41 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-009.md @@ -0,0 +1,33 @@ +--- +id: "adr-009" +title: "Manifest Self-Interrogation Layer — Three Semantic Axes" +slug: "009" +subtitle: "Accepted" +excerpt: "The manifest.ncl schema described structural facts (layers, modes, consumption modes, tools, config surface) but had no typed layer for self-interrogation: agents and operators could not query why a ca" +author: "ontoref" +date: "2026-03-26" +published: true +featured: false +category: "accepted" +tags: ["adr-009", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    The manifest.ncl schema described structural facts (layers, modes, consumption modes, tools, config surface) but had no typed layer for self-interrogation: agents and operators could not query why a capability exists, what it needs to run, or what external dependencies have a documented blast radius. The existing tools[] field covered only dev tooling (install_method, version) — no prod/dev classification, no services, no environment variables, no infrastructure dependencies. Practice/node descriptions in core.ncl carry architectural meaning (invariant=true, ADR-backed) but are not the right home for operational, audience-facing descriptions of what a project offers. Capabilities, requirements, and dependency blast-radius analysis are three orthogonal concerns that needed typed, queryable homes in the manifest schema.

    + +

    Decision

    + +

    Three new typed arrays are added to manifest_type: capabilities[] (capability_type), requirements[] (requirement_type), and critical_deps[] (critical_dep_type). These are semantically distinct layers: capabilities answer 'what does this project do, why does it exist, and how does it work' with explicit cross-references to ontology node IDs and ADR IDs; requirements classify prerequisites by env_target_type ('Production | 'Development | 'Both) and requirement_kind_type ('Tool | 'Service | 'EnvVar | 'Infrastructure); critical_deps document blast radius — what breaks when an external dependency disappears or breaks its contract — distinct from requirements because the concern is runtime failure impact, not startup prerequisites. A description | String | default = '' field is also added to manifest_type, fixing a pre-existing bug where collect-identity in describe.nu read manifest.description? (always null) instead of a field that existed. describe requirements is added as a new subcommand; describe capabilities is extended to render manifest capabilities; describe guides output gains capabilities/requirements/critical_deps keys so agents on cold start receive full self-interrogation context.

    + +

    Constraints

    + +
    • Soft capability_type.nodes[] entries must reference valid node IDs declared in .ontology/core.ncl
    • Hard critical_dep_type.failure_impact must be a non-empty string — undocumented blast radius defeats the purpose of the type
    + +

    Alternatives considered

    + +
    • Extend tools[] with env and kind fieldsrejected: tools[] is semantically 'dev tooling required to build'. Extending it to cover SurrealDB (a production service) or ONTOREF_ADMIN_TOKEN_FILE (an env var) would violate the field's implied meaning. A type named tool_requirement_type that has kind = 'Service is confusing. Separate types with clear names are preferable to an overloaded catch-all.
    • Add capability descriptions to Practice nodes in core.nclrejected: Practice nodes are architectural: invariant=true nodes are ADR-protected and represent constraints future contributors must follow. Adding 'what does this offer to end users' to the invariant graph would force every capability description through the ADR lifecycle. capabilities[] is per-project, evolves freely, and belongs to the manifest (the operational layer), not the ontology (the architectural layer).
    • Merge critical_deps into requirements with an is_critical flagrejected: The concern is different: requirements are prerequisites (the system cannot start without them). critical_deps are runtime load-bearing with a documented blast radius. A requirement that is optional (required = false) can still be a critical dep. The is_critical flag on requirement_type would blur this distinction and make failure_impact logically optional (not required for non-critical items) — creating a type that is partially applicable based on a flag, which is a code smell in typed schemas.
    + +

    Related ADRs

    + +

    ADR-001 · ADR-009

    diff --git a/site/site/content/adr/en/accepted/adr-009.ncl b/site/site/content/adr/en/accepted/adr-009.ncl new file mode 100644 index 0000000..a85a172 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-009.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-009", + title = "Manifest Self-Interrogation Layer — Three Semantic Axes", + slug = "009", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/009", + graph = { + implements = [], + related_to = ["adr-001"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-010.md b/site/site/content/adr/en/accepted/adr-010.md new file mode 100644 index 0000000..ed447e0 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-010.md @@ -0,0 +1,33 @@ +--- +id: "adr-010" +title: "Protocol Migration System — Progressive NCL Checks for Consumer Project Upgrades" +slug: "010" +subtitle: "Accepted" +excerpt: "As the ontoref protocol evolved (manifest.ncl self-interrogation, typed ADR checks, CLAUDE.md agent entry-point, justfile convention), the adoption tooling relied on static prompt templates with manual" +author: "ontoref" +date: "2026-03-28" +published: true +featured: false +category: "accepted" +tags: ["adr-010", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    As the ontoref protocol evolved (manifest.ncl self-interrogation, typed ADR checks, CLAUDE.md agent entry-point, justfile convention), the adoption tooling relied on static prompt templates with manual {placeholder} substitution. An agent or developer adopting ontoref had no machine-queryable way to know which protocol features were missing from their project, nor how to apply them in a safe, ordered sequence. The template approach produced four separate documents that drifted out of sync with the actual protocol state and required human judgement to determine which ones applied. There was no idempotency guarantee and no check mechanism — a project that had already applied a change would re-read instructions that no longer applied.

    + +

    Decision

    + +

    Protocol upgrades for consumer projects are expressed as ordered NCL migration files in reflection/migrations/NNN-slug.ncl. Each migration declares: id (zero-padded 4-digit string), slug, description, a typed check record (FileExists | Grep | NuCmd), and an instructions string interpolated at runtime with project_root and project_name. Applied state is determined solely by whether the check passes — there is no state file. This makes migrations fully idempotent: running `migrate list` on an already-compliant project shows all applied with no side effects. NuCmd checks must be valid Nushell (no bash &&, $env.VAR not $VAR, no bash redirects). Grep checks targeting ADR files must use the glob pattern adrs/adr-[0-9][0-9][0-9]-*.ncl to exclude infrastructure files (adr-schema.ncl, adr-constraints.ncl, _template.ncl) that legitimately contain deprecated field names as schema definitions. The system is exposed via `ontoref migrate list`, `migrate pending`, and `migrate show <id>` — wired into the interactive group dispatch and help system. Migrations are advisory: the system reports state, never applies changes automatically.

    + +

    Constraints

    + +
    • Hard Any change to templates/, reflection/schemas/*.ncl, .claude/CLAUDE.md, or consumer-facing reflection/modes/ that consumer projects need to adopt must be accompanied by a new migration in reflection/migrations/
    • Hard NuCmd check cmd fields must be valid Nushell — no bash operators (&&, ||, 2>/dev/null), no $VARNAME (must be $env.VARNAME)
    • Soft Grep checks targeting ADR files must scope to adrs/adr-[0-9][0-9][0-9]-*.ncl, not adrs/ or adrs/adr-*.ncl
    + +

    Alternatives considered

    + +
    • Single monolithic adoption prompt template with {placeholder} substitutionrejected: Produced four separate documents (project-full-adoption-prompt.md, update-ontology-prompt.md, manifest-self-interrogation-prompt.md, vendor-frontend-assets-prompt.md) that drifted out of sync. Required manual judgement to determine which applied to a given project. No idempotency, no machine-queryable state, no ordered application guarantee. Each new protocol feature required updating multiple templates.
    • State file recording applied migration IDsrejected: State files become stale on branch switches, cherry-picks, and fresh clones. They require commit discipline to keep in sync. A project where someone manually applied the changes without running the migration tool would show the migration as pending despite being satisfied — false negatives. The check-as-truth model has no false negatives by construction.
    • Jinja2/j2 templating for instruction renderingrejected: The ontoref runtime already runs Nushell for all automation. Adding a j2 dependency for template rendering introduces a new tool to install, configure, and maintain. Runtime string interpolation in Nushell (str replace --all) is sufficient for the two substitution values needed (project_root, project_name) and keeps the migration runner dependency-free.
    + +

    Related ADRs

    + +

    ADR-001 · ADR-006

    diff --git a/site/site/content/adr/en/accepted/adr-010.ncl b/site/site/content/adr/en/accepted/adr-010.ncl new file mode 100644 index 0000000..bca35aa --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-010.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-010", + title = "Protocol Migration System — Progressive NCL Checks for Consumer Project Upgrades", + slug = "010", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/010", + graph = { + implements = [], + related_to = ["adr-001", "adr-006"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-011.md b/site/site/content/adr/en/accepted/adr-011.md new file mode 100644 index 0000000..42aa510 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-011.md @@ -0,0 +1,33 @@ +--- +id: "adr-011" +title: "Mode Guards and Convergence — Active Partner and Refinement Loop in the Mode Schema" +slug: "011" +subtitle: "Accepted" +excerpt: "Reflection modes executed procedures as typed DAGs but lacked two capabilities that caused real failures: (1) modes could run against projects in invalid states — missing ontology files, unavailable " +author: "ontoref" +date: "2026-03-30" +published: true +featured: false +category: "accepted" +tags: ["adr-011", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    Reflection modes executed procedures as typed DAGs but lacked two capabilities that caused real failures: (1) modes could run against projects in invalid states — missing ontology files, unavailable tools, incomplete manifests — because preconditions were informational text, not executable checks. An agent following the protocol would execute sync-ontology on a project without core.ncl and get an opaque nickel error instead of a clear block. (2) Modes like sync-ontology require iteration — scan, diff, propose, apply, then verify that drift is zero. If drift remained after one pass, the mode reported success and the agent moved on. There was no mechanism for the protocol to say 'keep going until this condition is met'. Both gaps were identified during a systematic comparison against 45 augmented coding patterns (lexler.github.io/augmented-coding-patterns): Active Partner (#1) requires the system to push back on invalid actions, and Refinement Loop (#36) requires iteration until convergence. A separate action subsystem (ext/action) was considered and rejected in favor of extending the existing mode schema.

    + +

    Decision

    + +

    Extend reflection/schema.ncl with two new optional fields on ModeBase: guards (Array Guard) for pre-flight executable checks, and converge (Converge) for post-execution convergence loops. Guards run before any step and can Block (abort) or Warn (continue with message). Converge evaluates a condition command after all steps complete and re-executes failed or all steps up to max_iterations times. Both are backward-compatible — all existing modes export unchanged with guards defaulting to [] and converge being optional.

    + +

    Constraints

    + +
    • Hard reflection/schema.ncl exports a Guard type with id, cmd, reason, and severity fields
    • Hard reflection/schema.ncl exports a Converge type with condition, max_iterations, and strategy fields
    • Hard The mode executor in reflection/nulib/modes.nu evaluates guards before executing steps
    + +

    Alternatives considered

    + +
    • ext/action — separate action contract schema with gates, events, and convergencerejected: Creates a parallel execution abstraction competing with modes. Consumer projects would need to learn both modes and actions, and the boundary between them would be ambiguous. The mode schema already has steps, dependencies, and error strategies — guards and converge are natural extensions of the same concept.
    • Executable preconditions — make existing preconditions[] run commands instead of being textrejected: Preconditions serve a different purpose: they document what the human should verify. Making them executable would lose the documentation function. Guards are a separate concept: machine-checked pre-flight blocks. Both can coexist — preconditions for humans, guards for machines.
    • External convergence via Vapora workflows — let the orchestrator handle iterationrejected: Convergence is a property of the mode itself, not of the orchestrator. sync-ontology should declare that it iterates until zero drift regardless of whether it runs via CLI, Vapora, or CI. Pushing this to the orchestrator means every orchestrator must know which modes need iteration and under what conditions — that knowledge belongs in the mode contract.
    + +

    Related ADRs

    + +

    ADR-002

    diff --git a/site/site/content/adr/en/accepted/adr-011.ncl b/site/site/content/adr/en/accepted/adr-011.ncl new file mode 100644 index 0000000..6e4e830 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-011.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-011", + title = "Mode Guards and Convergence — Active Partner and Refinement Loop in the Mode Schema", + slug = "011", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/011", + graph = { + implements = [], + related_to = ["adr-002"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-012.md b/site/site/content/adr/en/accepted/adr-012.md new file mode 100644 index 0000000..16d2612 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-012.md @@ -0,0 +1,33 @@ +--- +id: "adr-012" +title: "Domain Extension System — Bash-Layer Dispatch for repo_kind-Conditional CLI Domains" +slug: "012" +subtitle: "Accepted" +excerpt: "Consumer projects have project-type-specific CLI needs that ontoref's generic command set cannot satisfy. A PersonalOntology project has CFP pipelines, career schemas, and opportunity tracking that no " +author: "ontoref" +date: "2026-04-05" +published: true +featured: false +category: "accepted" +tags: ["adr-012", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    Consumer projects have project-type-specific CLI needs that ontoref's generic command set cannot satisfy. A PersonalOntology project has CFP pipelines, career schemas, and opportunity tracking that no other repo_kind needs. A DevWorkspace project has workspace cards, cluster state dimensions, and production-readiness gates. These capabilities lived as local scripts (scripts/jpl.nu) — invisible to ontoref's help system, absent from describe capabilities, and not portable to other PersonalOntology projects. The problem had two layers: (1) how to conditionally load domain-specific Nu commands without breaking the existing dispatcher, and (2) how to make domain commands discoverable (help, describe capabilities) and aliasable (ore prov, personal state). Nu's module system (`use`, `source`, `overlay use`) is compile-time: paths must be known at parse time and `overlay use` creates an isolated namespace that cannot extend `def main` in the existing dispatcher. Runtime loading of arbitrary Nu modules is architecturally impossible in the current Nu model.

    + +

    Decision

    + +

    Implement a bash-layer domain dispatch system. The ontoref bash wrapper (install/ontoref-global) resolves the first CLI argument against $ONTOREF_ROOT/domains/{arg}/repo_kinds.txt before delegating to the Nu dispatcher. If the argument matches a domain directory name (or a registered alias from domains/aliases.txt) AND the current project's repo_kind appears in that domain's repo_kinds.txt, dispatch directly to nu domains/{id}/commands.nu passing remaining args. Each domain ships three files: domain.ncl (NCL contract declaring commands, pages, repo_kinds, short_alias), commands.nu (Nu script with def main [...args] entry point), and repo_kinds.txt (plain-text list of matching repo_kind values, grep-readable by the bash wrapper without running nickel). install.nu copies the entire domains/ tree to $data_dir/domains/ and generates domains/aliases.txt mapping short_alias → domain_id. Short aliases also create standalone bin wrappers at $bin_dir/alias.

    + +

    Constraints

    + +
    • Hard Every domain directory under $ONTOREF_ROOT/domains/{id}/ must contain domain.ncl, commands.nu, and repo_kinds.txt
    • Hard commands.nu must declare def main [...args: string] as its entry point — no dynamic use/source calls inside Nu scripts
    + +

    Alternatives considered

    + +
    • Nu overlay use for runtime domain loadingrejected: overlay use creates an isolated namespace — commands defined in an overlayed module cannot be called by name in the parent scope. It is also parse-time in module context. Confirmed broken: nu -c 'use FILE *; command args' causes infinite recursion when called from def main.
    • Add domain commands directly to reflection/bin/ontoref.nurejected: Would require hardcoding every domain's commands in the main dispatcher, or using dynamic path strings in `use` which Nu forbids. Also violates the no-enforcement axiom — the main dispatcher should not know about PersonalOntology specifics.
    • Project-local scripts/ (scripts/jpl.nu approach)rejected: Invisible to ore help and ore describe capabilities. Not portable across PersonalOntology projects. Namespace requires prefix (jpl cfp vs cfp). Dispatch requires knowing the script path.
    + +

    Related ADRs

    + +

    ADR-001 · ADR-006

    diff --git a/site/site/content/adr/en/accepted/adr-012.ncl b/site/site/content/adr/en/accepted/adr-012.ncl new file mode 100644 index 0000000..aec46a9 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-012.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-012", + title = "Domain Extension System — Bash-Layer Dispatch for repo_kind-Conditional CLI Domains", + slug = "012", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/012", + graph = { + implements = [], + related_to = ["adr-001", "adr-006"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-013.md b/site/site/content/adr/en/accepted/adr-013.md new file mode 100644 index 0000000..6450a79 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-013.md @@ -0,0 +1,33 @@ +--- +id: "adr-013" +title: "VCS Abstraction Layer — Uniform jj/git API via vcs.nu" +slug: "013" +subtitle: "Accepted" +excerpt: "ontoref modules that interact with version control (opmode.nu, git-event.nu, jjw.nu, init-repo.nu) historically hardcoded ^git subcommands. As jj adoption grows among contributors and orchestration pro" +author: "ontoref" +date: "2026-04-07" +published: true +featured: false +category: "accepted" +tags: ["adr-013", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    ontoref modules that interact with version control (opmode.nu, git-event.nu, jjw.nu, init-repo.nu) historically hardcoded ^git subcommands. As jj adoption grows among contributors and orchestration projects (vapora), hardcoded git calls produce silent failures: jj repos have .jj/ but may lack a .git/ HEAD in expected locations, and jj semantics differ (the working copy is always a commit, @- is the parent, `jj file show` replaces `git show HEAD:path`). The dual-VCS problem had two layers: (1) detection — which VCS is active in a given project root, and (2) semantics — same logical operation (show last committed state, restore a file, get remote URL) expressed differently per VCS. Spreading detection logic across modules produces duplication and makes future VCS additions (e.g. Pijul, Sapling) a multi-file change.

    + +

    Decision

    + +

    Introduce reflection/modules/vcs.nu as the single VCS abstraction layer. It exports: detect (returns 'jj' | 'git' | 'none' via filesystem check — .jj/ presence), is-repo, show-committed (jj: `jj file show -r @-`; git: `git show HEAD:path`), restore-file (jj: `jj restore --from @-`; git: `git checkout --`), remote-url (jj: `jj git remote list`; git: `git remote get-url origin`), current-branch (jj: `jj log -r @ --no-graph -T bookmarks`; git: `git branch --show-current`), uncommitted-files (jj: `jj diff --summary -r @`; git: `git status --porcelain`), commit-count (jj: `jj log --no-graph -T '' | lines | length`; git: `git rev-list --count HEAD`). All ontoref modules must import vcs.nu and call these exports — direct ^git or ^jj subprocess calls inside modules are prohibited. jj and rad are not listed as requirements in ontoref's manifest: they are opt-in tools whose requirements belong in orchestration projects that depend on them.

    + +

    Constraints

    + +
    • Hard All VCS subprocess calls in reflection/modules/ and reflection/bin/ must go through vcs.nu exports — no direct ^git or ^jj calls outside vcs.nu itself
    • Hard jj and rad must not appear as required = true entries in .ontology/manifest.ncl requirements[]
    + +

    Alternatives considered

    + +
    • Env var ONTOREF_VCS to select backendrejected: Creates mutable state that can desync from the actual repo state. A repo cloned fresh has no env var set; a contributor switching between git and jj repos would need to update the env var manually. Filesystem detection is always correct without configuration.
    • Per-module inline detection (duplicate detect logic in each file)rejected: Already the de-facto state before vcs.nu. Duplicated detection means any change to jj semantics (e.g. a jj CLI flag change) requires hunting every module. The abstraction cost is one import line per module.
    • Wrap the entire CLI in a shim that translates git commands to jjrejected: Shim-layer translation is fragile — git and jj command surfaces are not isomorphic (jj has no git stash equivalent; jj describe vs git commit -m). The operations ontoref needs are a small, well-defined set; a typed Nu module is a cleaner contract than a command-translation shim.
    + +

    Related ADRs

    + +

    ADR-012

    diff --git a/site/site/content/adr/en/accepted/adr-013.ncl b/site/site/content/adr/en/accepted/adr-013.ncl new file mode 100644 index 0000000..a348c5b --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-013.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-013", + title = "VCS Abstraction Layer — Uniform jj/git API via vcs.nu", + slug = "013", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/013", + graph = { + implements = [], + related_to = ["adr-012"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-014.md b/site/site/content/adr/en/accepted/adr-014.md new file mode 100644 index 0000000..f347a0f --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-014.md @@ -0,0 +1,33 @@ +--- +id: "adr-014" +title: "Runtime Service Toggles — AtomicBool Flags for MCP and GraphQL" +slug: "014" +subtitle: "Accepted" +excerpt: "ontoref-daemon exposes optional services (MCP, GraphQL) compiled in via Cargo feature flags. Once compiled, these services were always active for the lifetime of the process. Two scenarios require disa" +author: "ontoref" +date: "2026-04-26" +published: true +featured: false +category: "accepted" +tags: ["adr-014", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    ontoref-daemon exposes optional services (MCP, GraphQL) compiled in via Cargo feature flags. Once compiled, these services were always active for the lifetime of the process. Two scenarios require disabling them at runtime without restart: (1) security incident — temporarily disable a surface while investigating a compromise; (2) operator choice — enable graphql during a debug session, disable it in production. The alternative of restarting the daemon is disruptive because it drops all in-memory sessions, actors, and notification queues. Compile-time feature flags solve the binary presence problem but cannot address runtime availability.

    + +

    Decision

    + +

    Introduce ServiceFlags (pub struct in api.rs) holding one AtomicBool per toggleable service, gated by the corresponding feature flag. ServiceFlags::new() initialises all flags to true (enabled). AppState holds Arc<ServiceFlags> shared across all clones. The toggle check lives in a route_layer middleware on the sub-router for each service — not inside the service handlers themselves — so the check is enforced regardless of which handler a request reaches. Two toggle surfaces are provided: (1) REST API: PUT /api/services/:service {"enabled": bool} — daemon admin Bearer required; (2) UI: POST /ui/manage/services/:service/toggle — AdminGuard (cookie session). Both surfaces return the new state. The manage page shows each compiled-in service with an HTMX toggle button; the navbar badges reflect runtime state on every page render.

    + +

    Constraints

    + +
    • Hard ServiceFlags::new() must initialise all AtomicBool flags to true — a compiled-in service is always enabled at startup
    • Hard Any new optional service added to the daemon must add an AtomicBool to ServiceFlags and a route_layer toggle middleware on its sub-router
    • Soft Service toggle endpoints require daemon admin credentials — project-level auth is insufficient
    + +

    Alternatives considered

    + +
    • Restart daemon with different feature flags or configrejected: Restart drops SessionStore (all active logins), actor registry, notification queue, and NATS subscriptions. A 1-second outage is acceptable for planned maintenance but not for rapid incident response.
    • RwLock<bool> per service in AppStaterejected: RwLock introduces lock contention on every request. The toggle check does not need mutual exclusion with writes — a store and a load never run concurrently in a way that would corrupt state. Relaxed AtomicBool is sufficient and faster.
    • Dynamic axum Router rebuild — swap out the sub-router entirelyrejected: axum Router is not live-rebuildable without replacing the entire tower Service. This would require Arc<RwLock<Router>>, a custom Service wrapper, and would still incur a lock per request. The middleware approach achieves the same result with orders of magnitude less complexity.
    + +

    Related ADRs

    + +

    ADR-002 · ADR-005

    diff --git a/site/site/content/adr/en/accepted/adr-014.ncl b/site/site/content/adr/en/accepted/adr-014.ncl new file mode 100644 index 0000000..6eb257c --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-014.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-014", + title = "Runtime Service Toggles — AtomicBool Flags for MCP and GraphQL", + slug = "014", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/014", + graph = { + implements = [], + related_to = ["adr-002", "adr-005"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-015.md b/site/site/content/adr/en/accepted/adr-015.md new file mode 100644 index 0000000..d48995f --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-015.md @@ -0,0 +1,33 @@ +--- +id: "adr-015" +title: "MCP Tool Catalog via #[onto_mcp_tool] Proc-Macro + Inventory" +slug: "015" +subtitle: "Accepted" +excerpt: "ontoref-daemon exposes 33 MCP tools via the rmcp ToolRouter in crates/ontoref-daemon/src/mcp/mod.rs. The authoritative tool implementation lives in each tool struct's `ToolBase`/`AsyncTool` impls (name" +author: "ontoref" +date: "2026-04-26" +published: true +featured: false +category: "accepted" +tags: ["adr-015", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    ontoref-daemon exposes 33 MCP tools via the rmcp ToolRouter in crates/ontoref-daemon/src/mcp/mod.rs. The authoritative tool implementation lives in each tool struct's `ToolBase`/`AsyncTool` impls (name, description, schema). However, the agent-facing tool catalog returned by `ontoref_help` was a hand-typed JSON literal (~100 lines, mod.rs:1129-1226) duplicating every tool's name, description, and parameter shape. This is the same failure mode ADR-007 documents as the original motivation for `#[onto_api]`: the previous-session bug where `insert_mcp_ctx` listed 15 tools while the router had 27. ADR-007 fixed that drift for the HTTP API surface (inventory-collected `ApiRouteEntry`) but did not extend the pattern to MCP. As of 2026-04-26 the manifest claim 'daemon exposes 33 MCP tools' is also a hand-maintained string in `.ontology/manifest.ncl`. With the rate of MCP tool additions through 2026-Q1 (qa, bookmarks, actions, config, ontology extensions all added in separate sessions), drift was becoming inevitable.

    + +

    Decision

    + +

    Every MCP tool struct in ontoref-daemon must carry `#[onto_mcp_tool(name, description, category, params)]`. The proc-macro (in `crates/ontoref-derive`) emits `inventory::submit!(ontoref_ontology::McpToolEntry{...})` at link time and leaves the annotated struct unchanged — the existing `ToolBase` and `AsyncTool` impls are untouched. A new pure function `ontoref_daemon::mcp::catalog()` walks `inventory::iter::<McpToolEntry>()`, sorts by name, and returns `Vec<&'static McpToolEntry>`. `HelpTool::invoke` now serializes `catalog()` instead of holding a hand-typed JSON literal. `McpToolEntry` lives in `ontoref-ontology` next to `ApiRouteEntry` and reuses `ApiParam` for parameter metadata — both are protocol surfaces, the type is generic. `tool_router()`'s compile-time `with_async_tool::<T>()` list is left as-is; the Rust type system requires the type list at compile time and a separate macro architecture would be needed to derive it from inventory (out of scope for this ADR).

    + +

    Constraints

    + +
    • Hard Every MCP tool struct in ontoref-daemon must carry #[onto_mcp_tool(...)]
    • Soft The number of #[onto_mcp_tool] annotations must equal the number of with_async_tool::<T>() calls in tool_router()
    + +

    Alternatives considered

    + +
    • Generate the help JSON from ToolBase::name() + description() directly via reflectionrejected: ToolBase exposes name and description but not parameter metadata in a structured form (input_schema returns a JsonObject blob, not the per-param required/values/note shape that ontoref_help emits). Walking the JSON schema and reverse-engineering the per-param hints is brittle. The inventory entry lets us declare the agent-facing param documentation in a stable, typed shape next to the ToolBase impl.
    • Replace tool_router()'s with_async_tool::<T>() list with macro-driven iteration over inventoryrejected: rmcp's ToolRouter requires the tool type at compile time (generic associated types over each tool's Parameter/Output types). Driving registration from inventory entries — which are runtime values of `&'static McpToolEntry` — would require either a build.rs that emits the registration list or a macro that takes the full type list as input. Either is feasible but is a larger architectural change with no immediate reliability win beyond what the inventory already provides for the help/catalog surface. Deferred.
    • Skip the macro and write an `inventory::submit!` block manually next to each toolrejected: Eliminates the macro infrastructure but loses the validation that #[onto_mcp_tool] applies (key spelling check, param string parsing reuse). Manual blocks also bypass the file!() capture for source-file traceability that ApiRouteEntry already uses.
    + +

    Related ADRs

    + +

    ADR-007 · ADR-001

    diff --git a/site/site/content/adr/en/accepted/adr-015.ncl b/site/site/content/adr/en/accepted/adr-015.ncl new file mode 100644 index 0000000..5c64cb5 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-015.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-015", + title = "MCP Tool Catalog via #[onto_mcp_tool] Proc-Macro + Inventory", + slug = "015", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/015", + graph = { + implements = [], + related_to = ["adr-007", "adr-001"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-016.md b/site/site/content/adr/en/accepted/adr-016.md new file mode 100644 index 0000000..7103517 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-016.md @@ -0,0 +1,33 @@ +--- +id: "adr-016" +title: "Component Lift-Out Pattern: Four-Criterion Gate for Standalone Extraction" +slug: "016" +subtitle: "Accepted" +excerpt: "Ontoref itself was extracted from stratumiops (ADR-001). The same pattern recurred in the provisioning project: buildkit-launcher (build substrate) and backup-manager (backup orchestration) grew inside" +author: "ontoref" +date: "2026-05-01" +published: true +featured: false +category: "accepted" +tags: ["adr-016", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    Ontoref itself was extracted from stratumiops (ADR-001). The same pattern recurred in the provisioning project: buildkit-launcher (build substrate) and backup-manager (backup orchestration) grew inside provisioning but serve a broader class of consumers — vapora, workspace infras, CI pipelines, and third-party projects. No formal criterion existed for deciding when a component is ready to become a standalone peer project. Without a criterion, the decision is arbitrary and either premature (decomposition overhead before consumer plurality) or delayed (host coupling accretes, extraction becomes expensive).

    + +

    Decision

    + +

    A component is extracted as a standalone peer project when it passes all four criteria: (1) Orthogonal concern — the component's core domain is not the host project's core domain; (2) Consumer plurality — at least two distinct callers exist or are immediately planned; (3) Release cadence divergence — the component's evolution is not gated by the host project's release cycle; (4) Config path-agnostic — the component can receive its config from any caller without importing host infrastructure (workspace crates, host config loaders, host schema registries). The extracted project registers in ontoref before any code moves. The host project retains extension-side artifacts (schemas, defaults, component declarations) that allow workspace infras to declare directives for the extracted tool.

    + +

    Constraints

    + +
    • Hard An extracted project must have .ontology/core.ncl, .ontology/state.ncl, and adrs/adr-001 committed before any code from the host is moved
    • Hard The extracted project must not import workspace crates, config loaders, or schema registries from the host project
    + +

    Alternatives considered

    + +
    • Extract only when a second consumer exists and requests itrejected: Reactive extraction means coupling has already accreted. By the time a second consumer requests the component, host infrastructure imports are deep. Proactive evaluation at the four-criterion gate is cheaper than reactive untangling.
    • Keep all tools inside provisioning as internal workspace crates, expose via provisioning CLIrejected: This routes all callers through provisioning's release cycle and binary. vapora and workspace CI pipelines cannot use the tool without depending on provisioning. The four-criterion test exists precisely to identify when this constraint becomes incorrect.
    • Publish extracted crates to crates.io immediatelyrejected: Published crates require stable API contracts before the consumer relationship is proven. Path-based standalone project references allow simultaneous development of the extracted project and its consumers without publication ceremony.
    + +

    Related ADRs

    + +

    ADR-001

    diff --git a/site/site/content/adr/en/accepted/adr-016.ncl b/site/site/content/adr/en/accepted/adr-016.ncl new file mode 100644 index 0000000..8db82bc --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-016.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-016", + title = "Component Lift-Out Pattern: Four-Criterion Gate for Standalone Extraction", + slug = "016", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/016", + graph = { + implements = [], + related_to = ["adr-001"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-017.md b/site/site/content/adr/en/accepted/adr-017.md new file mode 100644 index 0000000..615db1e --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-017.md @@ -0,0 +1,33 @@ +--- +id: "adr-017" +title: "Registry Credential Vault Model: src-vault, Multi-Recipient sops, and Actor-Scoped Access" +slug: "017" +subtitle: "Accepted" +excerpt: "The provisioning registry (zot OCI) became the primary coordination hub for domain and mode artifacts (ADR-016 consequences, migration 0015). This introduced registry credentials as a first-class conce" +author: "ontoref" +date: "2026-05-01" +published: true +featured: false +category: "accepted" +tags: ["adr-017", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    The provisioning registry (zot OCI) became the primary coordination hub for domain and mode artifacts (ADR-016 consequences, migration 0015). This introduced registry credentials as a first-class concern: every project that pushes or pulls artifacts needs credentials, and those credentials must be distributed across actors (developers, CI pipelines, the ontoref daemon, AI agents, ops tooling) with different access levels. The naive model — environment variables or ambient ~/.docker/config.json — has three compounding failure modes: (1) credentials are ambient and unscoped, so an operation against project A can use credentials that belong to project B; (2) there is no distribution mechanism — adding a new team member or CI key requires manual redistribution of a shared secret; (3) there is no audit trail for credential access or changes. A supply-chain attack via misconfigured registry namespace endpoints is the concrete risk: if the daemon proxied registry calls, any actor with MCP access could redirect them to an arbitrary endpoint using ambient credentials.

    + +

    Decision

    + +

    Registry credentials are managed through a per-project src-vault stored as an OCI artifact in the same ZOT registry it protects. The src-vault uses sops with age multi-recipient encryption: each actor role has its own age keypair, and credential files are encrypted for the exact set of recipients that need access. The vault backend (restic or kopia) handles versioning and local copies; the ZOT registry handles distribution. Each project has a local access.sops.yaml in ~/.config/ontoref/vaults/<project-slug>/ containing three fields: zot_username, zot_password, and vault_key. All three are encrypted by the actor's master age private key (.kage), which lives at an external path the actor controls (hardware key, encrypted disk, or declared path in config.ncl) — never inside the vault directory. At operation time, sops decrypts access.sops.yaml in memory: zot_username and zot_password are used to pull the src-vault OCI artifact via a DOCKER_CONFIG tmpdir that is deleted immediately after; vault_key is passed as RESTIC_PASSWORD or KOPIA_PASSWORD env var for the duration of the vault operation and never written to disk. No plaintext credential of any kind persists beyond the operation scope. Credential resolution runs exclusively in the ontoref CLI — the daemon is structurally excluded. All oras invocations use an isolated DOCKER_CONFIG tmpdir; no ambient ~/.docker/config.json is consulted. Access logs are appended to a jsonl file stored as a layer in the same src-vault OCI artifact and mirrored locally in ~/.config/ontoref/vaults/<project-slug>/logs/access.jsonl.

    + +

    Constraints

    + +
    • Hard The credentials required to pull the src-vault OCI artifact from ZOT must be stored outside the vault itself — each src-vault has its own access credentials, held in the actor's local .kage, not inside any vault it protects
    • Hard RegistryEntry must not use credential_env — only credential_sops or credential_oidc are valid credential reference fields
    • Hard Every *.sops.yaml credential file must include at minimum the admin and the bound_actor recipients for its access class
    • Hard Any domain or mode that pushes or pulls from a registry must declare uses_registry referencing the RegistryEntry id in manifest.ncl
    • Soft All sops operations on registry credential files must go through ore secrets — direct sops invocations bypass lock state and audit log
    • Hard Every oras invocation must use DOCKER_CONFIG pointing to a tmpdir containing only the credential for the target registry endpoint — no ambient ~/.docker/config.json
    • Hard vault_key must never be written to disk in plaintext — it is decrypted from access.sops.yaml into RESTIC_PASSWORD or KOPIA_PASSWORD for the duration of the operation only
    • Soft All vault snapshot operations must use vault-backend.nu — direct restic or kopia invocations are not permitted in recipes or modules
    • Hard Every push of src-vault/<project>:latest to ZOT must produce a cosign signature; every pull must verify the signature before the artifact is trusted
    + +

    Alternatives considered

    + +
    • Environment variables (REGISTRY_TOKEN, DOCKER_CONFIG) per CI jobrejected: Visible in process list, inherited by subprocesses, logged by CI systems, cannot be scoped to a specific registry endpoint. No revocation without rotating all consumers. The primary attack surface for supply-chain credential leakage.
    • Shared age keyring — one private key distributed to all developersrejected: Revocation requires rotating the shared key and redistributing to all remaining members — coordination cost scales with team size. No per-actor audit trail. A leaked key compromises all actors simultaneously.
    • HashiCorp Vault or similar external secrets managerrejected: Introduces a network dependency for every credential resolution. Requires operating a separate service with its own HA, backup, and auth model. The OCI registry is already the coordination hub — using it as the vault distribution backend reuses existing infrastructure and auth.
    • Daemon resolves credentials on behalf of CLI actorsrejected: The daemon is a long-lived process accessible to multiple actors (developer, agent, CI via MCP). Giving it credential resolution capability means any actor with daemon access can trigger registry operations using credentials they do not personally hold. The structural exclusion of the daemon from credential resolution is a load-bearing architectural property.
    • Single credential file per registry (no RO/RW split)rejected: A single credential with RW access distributed to read-only actors (cdci, ontoref, agent) violates least-privilege. A compromised CI pipeline with RW credentials can push malicious artifacts. The RO/RW split means a compromised read-only credential cannot alter the artifact namespace.
    + +

    Related ADRs

    + +

    ADR-005 · ADR-012 · ADR-016

    diff --git a/site/site/content/adr/en/accepted/adr-017.ncl b/site/site/content/adr/en/accepted/adr-017.ncl new file mode 100644 index 0000000..a8f8b08 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-017.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-017", + title = "Registry Credential Vault Model: src-vault, Multi-Recipient sops, and Actor-Scoped Access", + slug = "017", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/017", + graph = { + implements = [], + related_to = ["adr-005", "adr-012", "adr-016"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-018.md b/site/site/content/adr/en/accepted/adr-018.md new file mode 100644 index 0000000..0dbedd8 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-018.md @@ -0,0 +1,33 @@ +--- +id: "adr-018" +title: "Level Hierarchy and Mode Resolution Strategy — Observable Boundary Traversal" +slug: "018" +subtitle: "Accepted" +excerpt: "ADR-012 introduced a domain extension system that enables project-specific specialization of ontoref. In practice this created a three-level hierarchy: (1) ontoref base — generic protocol and reflect" +author: "ontoref" +date: "2026-05-01" +published: true +featured: false +category: "accepted" +tags: ["adr-018", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    ADR-012 introduced a domain extension system that enables project-specific specialization of ontoref. In practice this created a three-level hierarchy: (1) ontoref base — generic protocol and reflection operations; (2) project domain — specialization of ontoref for a specific project type (provisioning, personal, ...); (3) domain instance — a concrete project derived from a domain (a workspace, an infra, a team ontoref). The hierarchy was not formalized: no level declares its identity, no mode declares whether its implementation is complete or delegates to the level above, and no mechanism exists to determine which level answered a given operation. The result is implicit traversal — a caller invoking 'build-docs' has no way to know whether the operation ran at level 1, 2, or 3, whether it merged contributions from multiple levels, or whether it silently fell through to the base because the domain had not implemented it yet. The discussion that produced this ADR also identified that resolution strategies cannot be uniform: the correct strategy depends on the mode, the project context, and the adoption phase. A domain in early adoption may Delegate all documentation modes to the base; the same domain at maturity Overrides them with project-specific implementations. The transition between strategies must itself be observable — crossing a level boundary is an architectural event, not an implementation detail.

    + +

    Decision

    + +

    Formalize the three-level hierarchy with four mechanisms: (1) Level identity declaration — each ontoref instance declares level.index (1=base, 2=domain, 3=instance), level.name, and level.parent (name of the level above; absent at level 1) in manifest.ncl. This makes level identity explicit and queryable. (2) Per-mode resolution strategy — each reflection mode declares a strategy field using one of four values: 'Override (implementation is complete at this level, traversal stops), 'Delegate (no implementation here, traverse to parent), 'Merge (accumulate fields bottom-up across all levels; lower level wins on conflicts), 'Compose (declare explicit partial inheritance via an extends field naming specific steps or fields to inherit from the parent). Strategy is required at level 2+; absent at level 1 (base is always Override by definition). Implicit absence at level 2+ is treated as 'Delegate with a Soft validation warning. (3) FSM-bound strategy transitions — when a mode's strategy is expected to change as the project matures, declare a state dimension in state.ncl whose current_state tracks the strategy value. The transition from 'Delegate to 'Override (or any other pair) is then an observable FSM event: tracked in state.ncl, visible in ore describe state, and triggerable by the same transition conditions as any other dimension. Strategy changes are architectural events, not silent refactors. (4) Observable traversal via ore mode resolve — the command 'ore mode resolve <id>' reports which level answered the operation, the strategy applied, the source file, and the reason (declared strategy or FSM state). No mode resolution is ever silent.

    + +

    Constraints

    + +
    • Hard Any ontoref instance at level 2 or 3 must declare level.index, level.name, and level.parent in manifest.ncl
    • Soft Every reflection mode at level 2+ must declare strategy explicitly; implicit absence is a Soft violation
    • Hard No Delegate chain may terminate without an Override at some level — every mode invocation must resolve to a concrete implementation
    • Soft If state.ncl declares a dimension for a mode's strategy, its current_state must match the strategy field declared in the mode NCL
    • Hard A mode with strategy = 'Compose must declare an extends field; every step or field reference in extends must exist in the named parent mode
    + +

    Alternatives considered

    + +
    • Implicit inheritance — current state, no declaration requiredrejected: Makes level traversal invisible. Coupling is undiagnosable. Adoption phase is unknown. Boundary crossing is unobservable. This is the state that produced the architectural confusion this ADR resolves.
    • Global strategy per level — one strategy applies to all modes at a given levelrejected: Domains specialize incrementally. A global Override for level 2 blocks early adoption (all modes must be implemented before any work). A global Delegate for level 2 makes domains invisible (nothing is ever implemented). Per-mode strategy reflects the real adoption curve.
    • Override-only — levels either implement fully or do not appearrejected: Eliminates phased adoption. A domain cannot exist in the system until it has implemented every mode — a steep entry cost that contradicts ADR-001 (voluntary coherence, no enforcement). Delegate and Compose are specifically needed for incremental adoption.
    • Separate level.ncl file instead of level field in manifest.nclrejected: manifest.ncl already holds structural self-description (requirements, capabilities, registry topology, layers). Level identity is structural metadata of the same kind. A separate file adds coordination overhead (two files to keep in sync, two files for describe to read) for no additional expressiveness.
    + +

    Related ADRs

    + +

    ADR-001 · ADR-010 · ADR-011 · ADR-012

    diff --git a/site/site/content/adr/en/accepted/adr-018.ncl b/site/site/content/adr/en/accepted/adr-018.ncl new file mode 100644 index 0000000..a6a8c73 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-018.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-018", + title = "Level Hierarchy and Mode Resolution Strategy — Observable Boundary Traversal", + slug = "018", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/018", + graph = { + implements = [], + related_to = ["adr-001", "adr-010", "adr-011", "adr-012"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-019.md b/site/site/content/adr/en/accepted/adr-019.md new file mode 100644 index 0000000..b27a164 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-019.md @@ -0,0 +1,33 @@ +--- +id: "adr-019" +title: "Per-File Recipient Routing for Tenant Isolation in lieu of Multi-Vault" +slug: "019" +subtitle: "Accepted" +excerpt: "ADR-017 established per-project credential vaults as OCI artifacts in ZOT, encrypted with sops + age multi-recipient. The model held one recipient set per vault: every actor who could decrypt access.so" +author: "ontoref" +date: "2026-05-03" +published: true +featured: false +category: "accepted" +tags: ["adr-019", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    ADR-017 established per-project credential vaults as OCI artifacts in ZOT, encrypted with sops + age multi-recipient. The model held one recipient set per vault: every actor who could decrypt access.sops.yaml could decrypt every credential file inside the vault. Real projects (libre-wuji, the canonical multi-tenant example) need stronger separation — a single project hosts multiple clients with distinct services, AI agents that operate with restricted access, and developer/admin roles that occasionally overlap. Three concrete failure modes appear: (1) credential-of-clientB is visible to anyone who can open the vault even when only working on clientA — recipient lists are coarse and indiscriminate; (2) AI agents with read-only intent receive credentials whose blast radius exceeds their declared scope; (3) blast-radius limitation requires per-file recipient sets, which the original single-set model does not express. A naive answer is multi-vault — one vault_id per tenant or per environment — but it cascades through every layer (schema, helpers, recipes, dispatcher, migration) and creates an architectural debt without justifying a real isolation requirement (separate master keys, separate restic repos) for the typical case.

    + +

    Decision

    + +

    Tenant isolation within a single project is expressed via sops creation_rules, declared in project.ncl::sops as recipient_groups (named lists of age public keys) and recipient_rules (path_regex to group-union mappings). The bootstrap recipe generates <vault_dir>/.sops.yaml from these declarations and sops natively encrypts each file with the union of declared groups. One vault_id per project remains the unit. Multi-vault is explicitly NOT implemented and remains out of scope until a project requires HARD isolation (separate master keys, separate restic repos, compliance-grade separation surviving accidental cross-decryption); such a case requires a future ADR. When recipient_rules are declared, project.ncl is the single source of truth: secrets-add-key and secrets-remove-key error out and direct the operator to edit project.ncl plus run secrets-rekey, which regenerates .sops.yaml and re-encrypts every *.sops.yaml file. Three adoption templates (single-team, multi-tenant, agent-first) ship in install/resources/templates/sops/ as copy-paste starting points; a project may adopt any pattern or none.

    + +

    Constraints

    + +
    • Hard Every group referenced in recipient_rules must be declared in recipient_groups
    • Hard A rule whose group union resolves to zero recipients is rejected at bootstrap and rekey
    • Hard secrets-add-key and secrets-remove-key recipes must error when project declares recipient_rules; canonical workflow is edit project.ncl + secrets-rekey
    • Hard Every *.sops.yaml under <vault_dir>/ must match at least one declared rule when recipient_rules is non-empty
    • Hard Projects must not declare a multi-vault structure (e.g. sops.vaults map). Multi-vault adoption requires a new ADR superseding or extending this one
    • Soft Three adoption templates (single-team, multi-tenant, agent-first) live under install/resources/templates/sops/ and are referenced by qa.ncl::credential-vault-templates
    + +

    Alternatives considered

    + +
    • Multi-vault: project.ncl::sops.vault_id (single string) becomes sops.vaults (record of named SopsConfigs)rejected: Cascades through every layer (schema, helpers, recipes, dispatcher), forcing a 12-component refactor and a migration for every existing project to express what one optional schema field accomplishes via sops creation_rules. The HARD isolation it provides (separate master keys, separate restic repos) is rarely required; per-file routing covers the common case while leaving the door open for a future multi-vault ADR if a project genuinely needs filesystem-level separation.
    • Single recipient set + role-based decryption gating in helper coderejected: Would require a custom layer over sops and reinvent recipient routing. sops already does it natively via creation_rules. Inventing a parallel mechanism doubles the surface and breaks composition with sops tooling (sops --decrypt, updatekeys).
    • Externalize tenant credentials to an external secret manager (e.g. HashiCorp Vault)rejected: Adds an external runtime dependency that contradicts the ADR-017 invariant of self-contained, distribution-via-OCI credentials. Reasonable for projects that already operate such a system, but inappropriate as the default ontoref pattern.
    + +

    Related ADRs

    + +

    ADR-017 · ADR-015

    diff --git a/site/site/content/adr/en/accepted/adr-019.ncl b/site/site/content/adr/en/accepted/adr-019.ncl new file mode 100644 index 0000000..bba4418 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-019.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-019", + title = "Per-File Recipient Routing for Tenant Isolation in lieu of Multi-Vault", + slug = "019", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/019", + graph = { + implements = [], + related_to = ["adr-017", "adr-015"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-021.md b/site/site/content/adr/en/accepted/adr-021.md new file mode 100644 index 0000000..b78e364 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-021.md @@ -0,0 +1,33 @@ +--- +id: "adr-021" +title: "Auth and UI Lift-Out: ontoref-daemon consumes ontoref-auth + ontoref-ui; JWKS and Introspect Exposed for SSO" +slug: "021" +subtitle: "Accepted" +excerpt: "The daemon historically owned its own session store, Argon2id password hashing, Tera template engine init, and cookie/Bearer extraction logic. Lian-build re-implemented the same surface independently. " +author: "ontoref" +date: "2026-05-12" +published: true +featured: false +category: "accepted" +tags: ["adr-021", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    The daemon historically owned its own session store, Argon2id password hashing, Tera template engine init, and cookie/Bearer extraction logic. Lian-build re-implemented the same surface independently. With ontoref-auth and ontoref-ui shipping as standalone foundation siblings (ADR-016 lift-out pattern), every daemon-internal copy is duplicate code maintained out of band: bug fixes, hardening, and Ed25519 token issuance for SSO had to be re-applied to each consumer. Federation across the family also required a common token vocabulary (issuer, kid, audience) that no daemon had so far. Without consolidation, the SSO mode that lets multiple panels share authentication remains unimplementable.

    + +

    Decision

    + +

    Migrate ontoref-daemon to depend on `ontoref-auth` (path dep, axum+persist features) and `ontoref-ui` (path dep, gated behind the existing `ui` feature). Re-export `Role` and `KeyEntry` from `ontoref_auth` through `crate::registry`; rewrite `crate::session` as a thin facade that preserves the historic daemon API surface (`SessionEntry`, `SessionStore`, `SessionView`, `RevokeResult`, `COOKIE_NAME`, `extract_cookie`) while delegating storage and lifecycle to `ontoref_auth::SessionStore`. Replace `tera::Tera::new(glob)` initialization with `ontoref_ui::init_tera(TeraOptions)`; `AppState.tera` now holds `Option<Arc<RwLock<ontoref_ui::TeraEnv>>>`. Daemon-specific page templates (manage.html, project_picker.html, dashboard.html, etc.) remain in `crates/ontoref-daemon/templates/` and continue to be loaded via the `template_dirs` consumer-override path. Add `TokenIssuer` (Ed25519, seed loaded from `ONTOREF_SIGNING_KEY_FILE` when set, ephemeral otherwise with WARNING) and `SignedTokenProvider` to `AppState`. Publish JWKS at `GET /.well-known/ontoref-keys.json` and introspection at `POST /.session/introspect` — both unauthenticated by SSO contract.

    + +

    Constraints

    + +
    • Hard ontoref-daemon must declare ontoref-auth as a path dependency with axum and persist features enabled
    • Hard ontoref-daemon must declare ontoref-ui as an optional path dependency, gated behind the ui feature
    • Hard ontoref-daemon source must not call tera::Tera::new directly — Tera initialization flows through ontoref_ui::init_tera
    • Hard The daemon must publish its JWKS at /.well-known/ontoref-keys.json
    • Hard The daemon must publish RFC 7662-shaped introspection at /.session/introspect
    + +

    Alternatives considered

    + +
    • Wholesale call-site migration to ontoref_auth::AuthUser extractor and ontoref_auth::Sessionrejected: The daemon's AuthUser is multi-project: it extracts a Path<String> slug and validates that the session's scope matches the requested project. ontoref_auth::AuthUser is single-tenant — it resolves a credential to an Identity without slug-awareness. Replacing the daemon's extractor would require either (a) duplicating slug validation in every handler, or (b) wrapping ontoref-auth's extractor with a daemon-specific layer that re-does the slug check. Both are strictly more code than keeping the daemon's slug-aware extractor and routing it to the shared SessionStore.
    • Bridge ontoref_auth::Role and registry::Role with a daemon-local enum + From implsrejected: The variants and serde representations are identical. The two-enum bridge would force every keys-overlay.json reader, every projects.ncl serializer, and every UI template that reads the role string to perform conversions. The re-export keeps the wire format identical and the type stable.
    • Embed ontoref-ui's full base.html and override only nav fragmentsrejected: ontoref-ui's base.html does not have block hooks for the daemon's domain ontology widget, registry topology bar, or vault state row. Either ontoref-ui grows daemon-specific blocks (which violates the harmonization-first lift-out principle — A.2 explicitly keeps generic core/), or the daemon defines its own base.html that includes ontoref-ui's partials piece by piece. The full-override path the daemon already takes is simpler and preserves the exact pre-migration rendering.
    • Defer JWKS until the SignedTokenProvider has a key rotation policy implemented in coderejected: TokenIssuer::rotate() is already implemented in ontoref-auth and exposes JwksSnapshot containing both current and previous public keys. The policy decision (when to rotate, when to forget_previous) is operational, not structural — it lives in the rotation playbook, not in the JWKS endpoint code. Shipping JWKS now with an ephemeral default unblocks SSO; the rotation policy ships as runbook content alongside the panel daemon's adoption of SSO mode.
    + +

    Related ADRs

    + +

    ADR-001 · ADR-005 · ADR-016

    diff --git a/site/site/content/adr/en/accepted/adr-021.ncl b/site/site/content/adr/en/accepted/adr-021.ncl new file mode 100644 index 0000000..b40079c --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-021.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-021", + title = "Auth and UI Lift-Out: ontoref-daemon consumes ontoref-auth + ontoref-ui; JWKS and Introspect Exposed for SSO", + slug = "021", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/021", + graph = { + implements = [], + related_to = ["adr-001", "adr-005", "adr-016"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-023.md b/site/site/content/adr/en/accepted/adr-023.md new file mode 100644 index 0000000..6ac1019 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-023.md @@ -0,0 +1,33 @@ +--- +id: "adr-023" +title: "Adoption of the Verifiable Substrate: Op Log + Content-Addressed Blobs + Bitemporal Triples + Merkle Commitments + JetStream Fabric, Replacing the NCL/Filesystem/Git Substrate" +slug: "023" +subtitle: "Accepted" +excerpt: "Ontoref's current substrate is plana: NCL files on disk (.ontology/, adrs/, reflection/) + git history + filesystem watching + Nushell automation + Rust crates loading NCL into typed structs. Verificat" +author: "ontoref" +date: "2026-05-26" +published: true +featured: false +category: "accepted" +tags: ["adr-023", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    Ontoref's current substrate is plana: NCL files on disk (.ontology/, adrs/, reflection/) + git history + filesystem watching + Nushell automation + Rust crates loading NCL into typed structs. Verification of any claim about a project requires running ontoref against the full SST: there is no way to attest 'this project's ontology has property X at time T' without re-evaluating the whole tree. The Self-Describing and DAG-Formalized Knowledge axioms are therefore aspirational — they describe what the protocol promises, not what an external verifier can confirm. Multi-actor concurrency is unaddressed: who wrote which assertion, when, with what authority, is implicit in git history rather than in the protocol. The recent commits 82a358f (#[onto_mcp_tool] catalog, OCI vault, ADR-018 mode hierarchy validation), 6721daf (NCL-first workflow generator), and ADRs 021 and 022 each surfaced the same structural deficit: every layer that needs typed contracts, signed operations, or external attestations reimplements them ad hoc. The verifiable substrate, prototyped in .coder/2026-05-24-verifiable-substrate-prototype.plan.md and consolidated in .coder/2026-05-25-implementation-final.plan.md, addresses this by replacing the substrate itself: every assertion about a project becomes a typed, signed, content-addressed operation in an append-only log; the materialized state (triples) and the cryptographic commitment (Merkle state root) are derivable from the log; JetStream carries operations to validators, materializers, and agents as a fabric, not as a runtime dependency. The change is structural, not incremental.

    + +

    Decision

    + +

    Adopt the verifiable substrate as the canonical substrate for ontoref, following the gates G1 through G12 of .coder/2026-05-25-implementation-final.plan.md. Construct the substrate in an isolated branch (ontoref-kernel) on top of the existing main. The substrate consists of seven layered crates (ontoref-types, ontoref-blobs, ontoref-oplog, ontoref-triples, ontoref-commit, ontoref-query, ontoref-fabric) composed by ontoref-core, plus ontoref-modes, ontoref-cli, ontoref-secrets (extracted pre-G1 from the daemon), and an internally rewritten ontoref-daemon that preserves auth and operator surface (ADR-021 carried verbatim). NCL/Nushell layers (.ontology/, adrs/*.ncl, reflection/) are removed at G12 after the kernel demo passes; their content is optionally migrated via tools/ncl-import. Two named tensions in .ontology/core.ncl are explicitly engaged by this decision (ondaod): ontology-vs-reflection and formalization-vs-adoption. The synthesis state and direction of motion for each are recorded in .coder/2026-05-25-implementation-final.plan.md §1.bis and MUST be read in conjunction with this ADR; collapsing either Spiral (e.g. by treating the substrate as 'reflection only' or by enforcing universal formalization without tier-1 fallback) constitutes a forbidden pattern. Activation tier for the prototype is Tier 2 (kernel + fabric); Wasm components, ZK proving, and Radicle p2p replication are deferred to post-G12 work and do not block this decision.

    + +

    Constraints

    + +
    • Hard New kernel crates (ontoref-types, ontoref-blobs, ontoref-oplog, ontoref-triples, ontoref-commit, ontoref-query, ontoref-fabric, ontoref-core, ontoref-modes, ontoref-cli) MUST NOT depend on stratumiops crates (stratum-graph, stratum-state, stratum-db, platform-nats)
    • Hard Every operation MUST round-trip through canonical CBOR encoding with byte-stable output: encode(op) == encode(op) for all op; OpId(op) == blake3(encode(op))
    • Hard Substrate crates MUST NOT contain todo!(), unimplemented!(), or panic!() with not-implemented messages
    • Hard Plan §1.bis MUST exist and name both engaged tensions (ontology-vs-reflection, formalization-vs-adoption) with direction-of-motion and synthesis state; this ADR cites it
    • Hard At G12 completion, crates/legacy/ MUST NOT exist; crates/ontoref-ontology/, crates/ontoref-reflection/ MUST NOT exist; reflection/, .ontology/, adrs/*.ncl, templates/ MUST NOT exist
    + +

    Alternatives considered

    + +
    • Continue evolving on the NCL+filesystem+git substrate; incrementally add typed contracts where pain emergesrejected: Each new typed surface (#[onto_mcp_tool] catalog, OCI vault, ADR-021 TokenIssuer) has been bolted onto a substrate that does not natively support typed signed operations. The result is increasing surface area implementing the same primitives in isolation. The decision is not 'should we add typed signed operations', it is 'should one substrate-level layer own them'. Incrementalism preserves the substrate but pays the integration cost N times.
    • Adopt an off-the-shelf knowledge graph database (Datomic, XTDB, TerminusDB, Neo4j)rejected: These systems target operational knowledge bases, not protocols that projects implement. Bitemporality (Datomic, XTDB) covers a subset of the substrate's properties but not multi-actor signed operations or external attestation via state-root commitments. Wrapping one of them would make ontoref a runtime dependency (violating Invariant #1: Protocol Not Runtime). The substrate is a protocol specification with reference implementation; an off-the-shelf KG DB is a runtime product.
    • Implement only the op-log layer (L0–L2) and skip triples/commit/query/fabricrejected: Without the commitment layer (L4), external verification is impossible. Without the fabric (Tier 2), multi-actor demonstrations cannot run. Without the query layer (L5), the substrate's discoverability is worse than the current describe.nu surface. The substrate's value is the combination; partial adoption defers the verification property to indefinite future work.
    • Build the substrate as a new project (substrate-* crates), keep ontoref consuming itrejected: This was the predecessor plan's framing (.coder/2026-05-24-verifiable-substrate-implementation.plan.md naming). D9 reverses it: ontoref-* prefix preserved because the substrate is ontoref realising its own axioms, not a new product line. A separate substrate-* project would create two protocols where one suffices, splitting the contributor community and forcing every consumer to track two projects.
    + +

    Related ADRs

    + +

    ADR-001 · ADR-002 · ADR-016 · ADR-018 · ADR-021 · ADR-022

    diff --git a/site/site/content/adr/en/accepted/adr-023.ncl b/site/site/content/adr/en/accepted/adr-023.ncl new file mode 100644 index 0000000..608b4ef --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-023.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-023", + title = "Adoption of the Verifiable Substrate: Op Log + Content-Addressed Blobs + Bitemporal Triples + Merkle Commitments + JetStream Fabric, Replacing the NCL/Filesystem/Git Substrate", + slug = "023", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/023", + graph = { + implements = [], + related_to = ["adr-001", "adr-002", "adr-016", "adr-018", "adr-021", "adr-022"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-024.md b/site/site/content/adr/en/accepted/adr-024.md new file mode 100644 index 0000000..3a6e461 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-024.md @@ -0,0 +1,113 @@ +--- +id: "adr-024" +title: "Operations Layer — Domain Operations as the Agent's Only Project-Touching Path" +slug: "024" +subtitle: "Accepted" +excerpt: "After extensive interactive diagnosis across the session of 2026-05-25 (see" +author: "ontoref" +date: "2026-05-26" +published: true +featured: false +category: "accepted" +tags: ["adr-024", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    After extensive interactive diagnosis across the session of 2026-05-25 (see +.coder/2026-05-25-substrate-utility-audit.info.md and the dialogue that produced +it .coder/2026-05-25-implementation-final-stop-review_done.md), +the recurring pain experienced across all 19 ontoref-onboarded projects +(provisioning, mina, lian-build, forge-fleet, libre-{daoshi,wuji,forge}, +stratumiops, rustelo, vapora, kogral, secretumvault, typedialog, jpl-website, +personal-ontoref, jpl-ontoref, cloudatasave, build-in-layers, mirador, +DD7pasos, librosys, ontoref itself) is located at a layer that is neither the +ontology layer (rich, declarative, mature) nor the verifiable substrate (G1-G10 +of the ontoref-kernel work is green and works).

    +

    The pain lives at the acoplamiento entre acción y consumo — agents have generic +primitives (Bash, Edit, Read, Write) plus optional ontology access (34 query-only +MCP tools listed in crates/ontoref-daemon/src/mcp/mod.rs), and the ontology +consumption is never required before action. Evidence from this repository:

    +

    - MCP surface is 34 tools, all queries: list_projects, search, describe, + list_adrs, get_adr, get_node, validate, impact, status, guides, etc. + Mutations are limited to qa_add, action_add, bookmark_add, set_project, + config_update — META operations on ontoref itself, not DOMAIN operations + on the project being worked on. + - crates/ontoref-daemon/src/federation.rs (419 lines) implements cross-project + BFS impact graph with depth 5 and HTTP for push-only remotes. It is unused: + install/resources/remote-projects.ncl is the empty list. The infrastructure + exists; the use case for it has not been activated. + - ADR-012 explicitly bypasses the NCL canon (repo_kinds.txt as grep-readable + side channel) because nickel export was too slow for hot-path dispatch + (200-400ms per invocation). The NCL canon is aspirational; the operative + path goes around it whenever performance demands it. + - The three-layer model of ADR-020 declares an integration surface (Layer 2) + but its Layer-2 contract is type-shape (schemas, catalog data), not + operation-shape — consumers bind to data structures, not to verbs.

    +

    The consequence: agents consume the ontology only when nudged; otherwise they +operate through Bash/Edit on raw filesystem, producing outcomes that may or may +not respect declared constraints. Validation runs post-mortem (pre-commit hooks, +CI checks) rather than constitutively. Cross-project coordination requires the +human to remember what lives where. The interactive session that produced this +ADR consumed many hours of dialogue precisely because the human had to refill +context the agent failed to fetch — and this dialogue cannot be relied on to +persist across sessions or to survive context compaction.

    +

    The verifiable substrate (ADR-023, Proposed) provides storage and witness +mechanisms (canonical encoding, bitemporal triples, content-addressed blobs, +state-root commitments) but does not on its own resolve why agents bypass the +declared content. The substrate is necessary infrastructure for receipts and +witnesses; it is not the mechanism that forces consumption.

    + +

    Decision

    + +

    Every ontoref-onboarded project carries — or progressively gains — a catalog of +domain operations exposed exclusively to agents as their tool surface. The +agent's configuration on a project DOES NOT include generic primitives (Bash, +Edit, Read, Write) over project paths. The only path by which an agent may +query, mutate, or affect the project is through operations in the catalog.

    +

    Each domain operation declares:

    +

    1. Required ontological precondition slice — which nodes, ADRs, state + dimensions, constraints, or external project entities must be loaded + and validated for this operation to proceed. + 2. Input contract — typed inputs, satisfied or rejected at invocation time. + 3. Effects — what the operation may change (file paths, state dimensions, + downstream entities, cross-project signals). + 4. Witness shape — what receipt is emitted upon successful completion.

    +

    The operation runtime:

    +

    1. Loads the declared precondition slice before invoking the operation body. + Loading is constitutive of the invocation, not a courtesy of the agent. + 2. Validates the precondition slice against the operation's declared + constraints. Failure produces a structured block message naming the + unmet precondition, not a generic error. + 3. Executes the operation body in a confined context that has access only + to the loaded slice and the declared effect scope. + 4. Emits a signed receipt: operation_id, actor, timestamp, state_root, + effect summary, downstream project signals if any.

    +

    Cross-project operations are routed through the existing federation.rs +infrastructure: an operation in project A that declares a dependency on or +effect in project B resolves the B-side via the federated query mechanism. +Operation receipts are first-class entities in the substrate (operations as +entities, per the audit's H7 finding), queryable cross-project.

    +

    Runtime placement is shared: the operations runtime is built once and lives in +ontoref-daemon (or a new ontoref-ops crate, decided at implementation time). +The operation catalog is per-project: each project owns its domain verbs under +a new declared path (catalog/operations/), with each operation contract written +in NCL using the ontoref-derive macros to surface to the MCP/API layer.

    +

    Agent configuration discipline: agents launched into an ontoref-onboarded +project receive an MCP server whose tool list is exactly the project's +operations catalog (plus the existing META operations like describe). Raw +filesystem tools over the project root are disabled at the agent-launch +boundary. The launcher is responsible for enforcing this; ontoref provides the +catalog and the runtime.

    + +

    Constraints

    + +
    • Hard Agent-facing mutations on a project's authoritative state must go through catalogued operations (dispatch_op), not raw filesystem primitives (Bash/Edit). Direct file edits bypass typed preconditions and the witness-seam.
    • Hard Every catalogued operation must declare at least one typed precondition. Ops with no preconditions provide no validation plane and defeat the purpose of the operations layer.
    + +

    Alternatives considered

    + +
    • Keep generic agent tools, improve ontology documentation and the MCP query surfacerejected: Years of evidence across 19 projects demonstrate that better declarative content does not lead to consumption when consumption is optional. The optionality IS the failure mode; adding more documentation reinforces the same pattern at greater cost.
    • Build the full G1-G12 substrate plan as written, defer operations layer indefinitelyrejected: The substrate without an operations layer is verifiable storage that no agent uses operationally. G1-G10 is already green; G11-G12 (multi-actor daemon, demo) would extend storage without addressing why agents bypass the storage. The recurring pain persists across all of G11-G12's work.
    • Per-project ad-hoc solutions (custom CLI wrappers, project-specific guardrails, per-project skills)rejected: Combinatorial cost across 19 projects. No shared infrastructure means each project re-derives the constrained-action pattern. Cross-project federation impossible by construction. This is the current state and the source of the recurring pain.
    • Revert ontoref to pre-substrate state and restart the designrejected: The substrate G1-G10 provides exactly the receipt/witness mechanism this ADR's operations layer requires. Reverting throws away material that becomes load-bearing under this decision. The substrate was not wrong; it was incomplete on its own. ADR-023 and ADR-024 together close the gap.
    • Enforce ontology consumption via stricter pre-action hooks instead of constraining the tool surfacerejected: Hooks fire on commit, not on action. An agent can perform many actions before commit; by then the cost of bad action is already paid. Constraining the tool surface is the only mechanism that intervenes before the action exists.
    + +

    Related ADRs

    + +

    ADR-001 · ADR-005 · ADR-012 · ADR-018 · ADR-020 · ADR-022 · ADR-023

    diff --git a/site/site/content/adr/en/accepted/adr-024.ncl b/site/site/content/adr/en/accepted/adr-024.ncl new file mode 100644 index 0000000..073e2d7 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-024.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-024", + title = "Operations Layer — Domain Operations as the Agent's Only Project-Touching Path", + slug = "024", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/024", + graph = { + implements = [], + related_to = ["adr-001", "adr-005", "adr-012", "adr-018", "adr-020", "adr-022", "adr-023"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-025.md b/site/site/content/adr/en/accepted/adr-025.md new file mode 100644 index 0000000..3dcee67 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-025.md @@ -0,0 +1,142 @@ +--- +id: "adr-025" +title: "Ontology Core as Authoritative State, NCL as Manifestation — Two-Phase Adoption" +slug: "025" +subtitle: "Accepted" +excerpt: "ADR-024 establishes the operations layer as the agent's only project-touching" +author: "ontoref" +date: "2026-05-26" +published: true +featured: false +category: "accepted" +tags: ["adr-025", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    ADR-024 establishes the operations layer as the agent's only project-touching +path. This ADR closes the remaining ambiguity ADR-024 left implicit: where does +the authoritative project state actually live, and what is the role of the +existing NCL filesystem (.ontology/, adrs/, reflection/).

    +

    Two competing readings have coexisted through the project's evolution:

    +

    Reading 1 (current de facto): the NCL filesystem IS the state. Rust code + (ontoref-ontology, ontoref-reflection crates) parses NCL into typed structs + for query. Mutations happen by editing files; git is the history.

    +

    Reading 2 (substrate plan original direction): NCL is intended to disappear + (ADR-023 § decision, constraint legacy-crates-removed-at-g12). State lives in + triples (G4) + commitments (G5); operations are recorded in oplog (G3). NCL + was destined to become migration tool input only.

    +

    Neither reading is currently coherent with the project's lived axioms and the +direction set by ADR-024. Reading 1 contradicts ADR-024 (an agent who edits +.ontology/*.ncl with Edit bypasses operations entirely). Reading 2 dissolves +.ontology/ which collides with the Self-Describing axiom and removes the +human-legible substrate the project has invested years in.

    +

    The session of 2026-05-25 (see .coder/2026-05-25-implementation-final-stop-review_done.md) +closed Reading 2 by adopting ADR-024: operations layer becomes the fin, substrate +becomes the medio. This ADR closes the matching question on state location: the +authoritative state lives in Rust (loaded into typed structs, committed via +the substrate), and NCL is one of several manifestations rendered from that +state. The plain-text legibility of .ontology/ is preserved; its semantic +authority is moved to the Rust-side state.

    +

    The transition has a load-bearing constraint: it must be progressive. A +big-bang switch to "NCL is read-only output" would block all legitimate human +edits during the period when the operations catalog is incomplete (which is +inherent to first adoption — the catalog grows dialectically with use). The +decision adopts a two-phase model: Phase 1 NCL editable with parse-on-save +reconciliation; Phase 2 NCL render-only when the catalog is mature.

    +

    The decision also collides with the existing axiom no-enforcement +(.ontology/core.ncl, invariant=true): "There is no enforcement mechanism. +Coherence is voluntary." Once operations are the only mutation path and +parse-on-save can reject inconsistent edits, there IS enforcement — internal, +structural, post-adoption. The axiom must evolve to reflect this distinction +between voluntary adoption (preserved) and enforced internal coherence (new).

    + +

    Decision

    + +

    The authoritative project state lives in Rust, materialized over the +verifiable substrate (ontoref-types + ontoref-triples + ontoref-oplog + +ontoref-commit, G1-G10 verde). NCL files in .ontology/, adrs/, and reflection/ +become one manifestation of that state — human-legible, git-versioned, but no +longer the seat of semantic authority.

    +

    The transition is two-phase:

    +

    Phase 1 — C-progressive (during catalog construction)

    +

    NCL files remain manually editable. Each save to .ontology/*.ncl, + adrs/*.ncl, or reflection/*.ncl triggers a reconciliation:

    +

    parse(file) → diff vs current state → emit ops to align → apply

    +

    If parse fails or diff requires an op not in the catalog, the save fails + with a structured message naming the missing operation. Each failure + teaches the dialectical step needed to extend the catalog.

    +

    Render from state to NCL happens after any state mutation (whether + triggered by a save or by a direct ops invocation), so the NCL files + always reflect the post-mutation state.

    +

    Phase 1 is the operating mode during the build-out of ADR-024's + operations catalog. It coexists with the existing CLI surface; ops + invocations via MCP, ops invocations via save-triggered reconciliation, + and (during this phase) legacy CLI mutations via the existing ontoref CLI + commands all converge on the same state.

    +

    Phase 2 — C-pure (post-catalog-maturity)

    +

    NCL files become render-only. Direct edits are silently overwritten by the + next render. All mutations flow through declared operations.

    +

    Transition from Phase 1 to Phase 2 is a deliberate decision recorded as a + configuration flag (.ontoref/config.ncl::ops.phase = 'Pure) plus a new + ADR (likely ADR-026) documenting the maturity assessment.

    +

    Reconciliation between this decision and the existing no-enforcement axiom +requires the axiom to evolve. no-enforcement is split into two axioms that +preserve voluntary adoption while making explicit the enforcement that lives +inside the post-adoption operation runtime:

    +

    voluntary-adoption (invariant=true) — Projects adopt ontoref by choice; the + protocol never imposes itself. Adoption is opt-in per tier (tier-0 + minimal NCL, tier-1 substrate, tier-2 operations).

    +

    internal-coherence-enforced (invariant=true) — Once a project adopts the + operations tier, mutation of authoritative state happens exclusively + through declared domain operations. The agent (human or AI) cannot + bypass.

    +

    Both new axioms are added to .ontology/core.ncl as part of the pre-flight to +the operations-layer implementation plan; the existing no-enforcement node is +removed. This is enforced as a Hard constraint of this ADR.

    +

    Invocation surfaces (the API answer):

    +

    Operations are invoked exclusively through typed API surfaces. NCL files are + NEVER the invocation channel; they are the catalog (declaration of what + operations exist) and the manifestation (render of resulting state). Three + API surfaces, all built on the existing ontoref-daemon authentication + (ADR-005):

    +

    1. MCP (Model Context Protocol) — agents call ops via tools/call <op_id>; + the tool list is dynamically populated from ontoref-ops::registry + (inventory::collect of OperationEntry). Auth via Bearer session token. + Existing 34 query tools coexist.

    +

    2. HTTP/REST — POST /ops/{id} with JSON body matching the op's declared + inputs; response includes the witness. Auth via Bearer. Suitable for + scripts, CI/CD, GraphQL bridge, and remote clients.

    +

    3. CLI — ./ontoref ops <id> --json '{...}' (alternatively positional args + resolved by the op's input schema). Same dispatch path as MCP and + HTTP under the hood.

    +

    All three surfaces converge on ontoref-ops::dispatch(op_id, inputs) which + loads the precondition slice, validates, executes, and emits the witness. + In Phase 1, save-to-NCL is a fourth implicit invocation channel that goes + through parse → diff → emit_ops → dispatch. In Phase 2, save-to-NCL has no + effect on state.

    +

    Crate consequences:

    +

    - ontoref-ontology stays — its role shifts from "NCL parser" to "NCL ↔ state + bridge" (parse-to-ops in Phase 1; render-from-state in both phases). + - ontoref-reflection stays — modes execute against state queries (via + ontoref-query L5) rather than parsing reflection/modes/*.ncl on each call. + - ontoref-ops (new crate, plan's D2) is the operation runtime; it composes + ontoref-ontology (for render/parse), ontoref-triples (for state), and + ontoref-commit (for witnesses). + - ontoref-daemon adapts to expose POST /ops/{id} routes and integrates + ops into the MCP tool list; no rewrite required. + - The legacy-crates-removed-at-g12 constraint in ADR-023 is invalidated by + this ADR and is to be removed when ADR-023 transitions to Accepted (post + O3 of the operations-layer plan).

    + +

    Constraints

    + +
    • Hard .ontology/core.ncl MUST replace the no-enforcement node by two new axiom nodes: voluntary-adoption and internal-coherence-enforced (both invariant=true). The no-enforcement id MUST NOT remain in the nodes array.
    • Hard Rendering state → NCL MUST be byte-stable: render(state, file_path) produces identical bytes on repeated invocations for the same state, and parsing the rendered file MUST yield a state equal to the source state.
    • Hard .ontoref/config.ncl MUST declare an ops.phase field with value 'Progressive | 'Pure once ADR-025 is Accepted. Default value 'Progressive applies until a follow-up ADR records the Phase 2 transition.
    • Hard This ADR engages and synthesizes the named tensions ontology-vs-reflection, formalization-vs-adoption, and the to-be-split no-enforcement axiom; the synthesis state and direction of motion MUST be discoverable in the decision and rationale of this ADR.
    + +

    Alternatives considered

    + +
    • Reading 1: keep NCL as authoritative state, ADR-024 enforcement applied only when operations existrejected: This is the current state. The H7 diagnosis demonstrates it does not work: agents bypass NCL because direct file edit is structurally cheaper than consulting via MCP. The ADR-024 commitment to operations-as-only-path is incoherent if the underlying state is still file-edit-accessible. The reading collapses ADR-024 into a recommendation rather than a constitutive constraint.
    • Reading 2: dissolve NCL completely per the original substrate planrejected: Closing the human-legible substrate breaks Self-Describing's lived form (the directories ARE the description, not an optional rendering). It also abandons years of investment in NCL schemas, contracts, and onboarding tooling. The H7 dialectic showed the problem was not NCL-the-substrate but the missing acting-binding to NCL — the substrate is fine; what was missing was the mutation discipline. Dissolving NCL is over-reach.
    • C-pure from day one (state in Rust, NCL render-only immediately)rejected: Big-bang transition during catalog incompleteness blocks legitimate work. Every mutation that has not yet been catalog-modeled is a hard block on the editor. Reasonable extensions (a new tension name in a new ontology node, a new ADR with a yet-unmodeled constraint type) become catalog-extension dialectics before they can be expressed. The friction kills adoption velocity precisely when adoption velocity is needed to mature the catalog.
    • C-progressive permanent (parse-on-save without ever reaching pure render-only)rejected: Stops at the half-way point. The internal-coherence-enforced axiom is only partially satisfied — humans can still mutate via save, ops are not the unique path. The cryptographic verifiability that ADR-024 promised (witness-of-mutation cannot be fabricated) is undermined by the existence of an alternative mutation channel. The model is functional but incomplete; Phase 2 must be the eventual target even if its trigger is far in the future.
    • Keep no-enforcement axiom unchanged; treat ADR-024 + ADR-025 as conscious axiom violations documented per caserejected: Encourages the protocol to drift from its declared axioms while the axioms remain frozen as aspiration. Future readers of core.ncl would see no-enforcement as truth while the runtime structurally enforces. The protocol's self-description must remain accurate; an axiom that no longer describes the runtime is not an axiom, it is a wish. The split makes the lived position legible.
    + +

    Related ADRs

    + +

    ADR-001 · ADR-007 · ADR-009 · ADR-018 · ADR-020 · ADR-023 · ADR-024

    diff --git a/site/site/content/adr/en/accepted/adr-025.ncl b/site/site/content/adr/en/accepted/adr-025.ncl new file mode 100644 index 0000000..ef00e9a --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-025.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-025", + title = "Ontology Core as Authoritative State, NCL as Manifestation — Two-Phase Adoption", + slug = "025", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/025", + graph = { + implements = [], + related_to = ["adr-001", "adr-007", "adr-009", "adr-018", "adr-020", "adr-023", "adr-024"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-026.md b/site/site/content/adr/en/accepted/adr-026.md new file mode 100644 index 0000000..50b571c --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-026.md @@ -0,0 +1,151 @@ +--- +id: "adr-026" +title: "Validation Architecture — Three Planes, First-Class Validators, SLA per Operation, Pluggable Commitment Backend" +slug: "026" +subtitle: "Accepted" +excerpt: "ADR-024 establishes the operations layer as the agent's only project-touching" +author: "ontoref" +date: "2026-05-26" +published: true +featured: false +category: "accepted" +tags: ["adr-026", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    ADR-024 establishes the operations layer as the agent's only project-touching +path. ADR-025 establishes that the authoritative state lives in the +verifiable substrate and NCL is its manifestation. Both decisions leave one +question deliberately open: how is the coherence of a mutation actually +verified? The plan that implements ADR-024 + ADR-025 mentions "validate" as a +step inside the operation runtime but does not articulate where validators +live, what kinds of validation apply, or what verifiability guarantees are +emitted alongside the mutation.

    +

    Without this articulation, the operations layer collapses to a thin wrapper: +the runtime loads a precondition slice, runs an opaque validate() body +inside the operation, and emits a witness that no external party can +meaningfully check. This is the failure pattern of the current pre-commit +hooks at scale — validators that run inside the producer's process, +producing pass/fail signals that consumer projects must trust on faith. The +H7 diagnosis is repeated at the validator layer: validators that consume the +full state saturate, drift, and lose locality.

    +

    Three structural deficits the operations layer alone does not resolve:

    +

    1. Validators that require complete knowledge of project state cannot run + locally without paying the saturation cost. The substrate's witness + mechanism (G5) provides local verifiability for cells, but the + operations layer does not exploit it — validators read the live state + instead of reading a witnessed slice.

    +

    2. The same validator predicate runs on every invocation regardless of + contextual relevance. An IPv4-shape validator runs identically for + every IP; a "this IP is in this pool given the FSM state" validator + needs slice + context. The current pre-commit pattern conflates the + two — both run as NuCmd processes loading whatever they want.

    +

    3. Validation latency and concurrency requirements are not expressible per + operation. A move_fsm_state must validate synchronously (the state + transition is the contract); a cross-project impact assertion may + legitimately validate eventually (consumer in B asserts the + consequence of A's op asynchronously). The current model imposes one + mode (sync, pre-commit) on all validations.

    +

    The substrate G1-G10 was designed precisely to support locally-verifiable +witnesses — bitemporal triples (G4), state root commitments (G5), proof +paths for cells (witness API). These primitives have been built and tested +but not consumed by an articulated validation layer. This ADR closes that +gap.

    + +

    Decision

    + +

    Validation is articulated as a first-class architecture, parallel to the +operation catalog. The architecture has three load-bearing properties:

    +

    Three planes of validation:

    +

    Pre-validation — runs after precondition slice load and before the + operation body executes. If any pre-validator returns Verdict::Reject, + the operation does not execute; the runtime returns a structured block + message naming the validator and the unmet predicate.

    +

    Inline validation — runs during the operation body. The body may invoke + additional validators that depend on values computed mid-execution + (e.g. a cross-project query whose result is itself validated). Failures + abort the body with rollback at the substrate level (the op is not + appended to the oplog).

    +

    Post-validation — runs after the operation body has applied to the state. + Two modes: + - Synchronous: the runtime invokes registered validators inline; the + op is committed only if all return Accept. + - Asynchronous: validators are external processes consuming the oplog + via the existing subscription channel (ontoref-query G6 + subscribe()); they emit AttestationOp entries materialized in the + same substrate. The state can carry not-yet-attested ops; consumer + queries can filter by attestation status.

    +

    Two categories of validators:

    +

    Structural validators — predicates over input or local data with no + dependency on project state or context. Examples: IPv4 shape, CBOR + canonical, Ed25519 signature valid, NCL schema contract held. + Universally applicable, cheap, deterministic.

    +

    Contextual validators — predicates over a slice of project state, possibly + parameterized by actor, dimension, or domain. Examples: "this entity is + in this pool given the current FSM state", "this actor may invoke this + op given their role", "cross-project op respects constraint declared in + project B". The slice query is part of the validator declaration; the + runtime loads exactly that slice — no more — before running the + predicate.

    +

    Validators are first-class entities, declared in two combinable forms:

    +

    catalog/validators/*.ncl — NCL declaration: id, description, category + (Structural | Contextual), slice_query (for contextual), predicate_ref, + witness_shape, actor_scope. Reusable across operations.

    +

    #[onto_validator(id, kind, slice, ...)] — Rust macro on the validator + implementation function. Emits inventory::submit!(ValidatorEntry { ... }) + at link time. The validator function signature is + fn validate(slice: &Slice, ctx: &ValidationCtx) -> Verdict.

    +

    An operation references validators by id in its constraints field; + alternatively, an operation may declare inline constraints when the + validator is single-use and reuse is not anticipated. Both forms coexist.

    +

    SLA per operation:

    +

    Each operation declares a validation_sla field: + 'Synchronous — all validators (pre, inline, post) run sync; + op only commits on full Accept + 'EventualWithin(Duration) — pre/inline sync; post validators run async + with deadline; consumers query attestation + status with as_of clauses + 'Background — pre/inline sync; post validators run background; + no deadline; ops are eventually-attested

    +

    The runtime applies the declared mode; mixing modes within a single op is + not permitted (to keep dispatch unambiguous).

    +

    Pluggable commitment backend:

    +

    The substrate's commitment layer (G5, currently hand-rolled binary Merkle) + is abstracted behind a CommitmentBackend trait with methods apply, root, + witness, verify_witness. The G5 hand-rolled implementation becomes the + BinaryMerkle impl. The trait permits alternative impls (NOMT, jmt, + sparse-merkle-tree) without modifying call sites.

    +

    The choice of backend is deferred: the BinaryMerkle impl is the default + and proven (10 tests, clippy clean). A trigger-based spike runs only when + empirical signals from the pilot indicate a backend with different + trade-offs may be beneficial:

    +

    - validate_latency_p99 > 500ms (sustained) + - state_cells_count > 100k + - commit_root_time_p99 > 100ms (sustained) + - daemon memory pressure > 1GB on substrate alone

    +

    A one-off type-fit assessment (1-2 hours, no benchmark) checks whether + candidate backends (NOMT 1.0.4, jmt latest, sparse-merkle-tree latest) + adapt to the CommitmentBackend trait without invasive wrappers. Backends + that do not fit are eliminated without benchmark cost. The assessment is + optional and may be deferred indefinitely; absence of assessment leaves + BinaryMerkle as the conscious choice.

    +

    Local verifiability:

    +

    Every operation emits a witness containing op_id, post-apply state_root, + signed by the actor (ADR-005 keypair model), plus Merkle proof paths for + cells the operation's effects touched. An external verifier holding only + the state_root + witness can check predicate(slice, witness, state_root) + for any contextual validator without needing access to the full state. + This is the Sigstore-like property the substrate was built to enable.

    + +

    Constraints

    + +
    • Hard crates/ontoref-ops MUST declare the trait Validator with method fn validate(&self, slice: &Slice, ctx: &ValidationCtx) -> Verdict; crates/ontoref-commit MUST refactor the existing commit module to extract trait CommitmentBackend with methods apply, root, witness, verify_witness. BinaryMerkle (the existing hand-rolled impl) MUST be the default impl.
    • Hard Every operation in catalog/operations/*.ncl MUST declare a validation_sla field with one of the values 'Synchronous, 'EventualWithin(duration), or 'Background. Operations that lack the field are rejected by the catalog schema at parse time.
    • Hard .ontoref/config.ncl MUST declare an ops.commitment_backend field with enum value 'BinaryMerkle | 'Nomt | 'Jmt | 'SparseMerkleTree. Default value is 'BinaryMerkle. The runtime selects the impl based on this field.
    • Soft docs/validation/spike-triggers.md MUST exist documenting the empirical thresholds that activate the commitment-backend spike (validate_latency_p99, state_cells_count, commit_root_time_p99, daemon memory pressure). The thresholds MUST be revisable via PR; the document records the current values plus rationale.
    • Hard This ADR engages and synthesizes the named tensions ontology-vs-reflection, formalization-vs-adoption, and the internal-coherence-enforced axiom; the synthesis state and direction of motion MUST be discoverable in the decision and rationale.
    + +

    Alternatives considered

    + +
    • Single-plane validation: pre-only (as in current pre-commit hooks)rejected: Pre-only cannot handle validators that depend on values computed during op execution (inline) and cannot handle expensive validators that would block throughput unacceptably (async post). The current model fails precisely because it has only this one plane — the H7 diagnosis traces some of its dysfunction to validators that try to do everything pre.
    • Validators as Rust traits only, no NCL declarationrejected: Loses the Self-Describing property: validators become invisible to non-Rust consumers (NCL readers, GraphQL clients, MCP agents querying catalog). Also loses the ability for validators to be cross-project queryable — a project that wants to know what validators apply to its mutations would need to read source code rather than declarative artifacts.
    • Validators as NCL only, no Rust impl bridgerejected: NCL is declarative; predicate evaluation logic must run somewhere. Forcing all predicates into Nickel contract syntax limits expressiveness severely (no graph traversal, no FFI, no I/O). The dual-write (NCL declares, Rust implements) is the only way to satisfy both Self-Describing (NCL is the catalog) and effective implementation (Rust runs the code).
    • Force synchronous validation for all ops (no SLA per op)rejected: Equates correctness with synchronous correctness. Many legitimate validators (cross-project, statistical, graph-traversal) cannot reasonably run synchronously without throttling the runtime. The sync-only model would either reject these validators (impoverishing the architecture) or require their bodies to be approximations of the real predicate (sacrificing correctness for latency).
    • Commit immediately to NOMT or jmt without spike or trait abstractionrejected: No prior implementation experience exists. Either choice without data is gambling. The trait abstraction costs minimal effort (the G5 implementation needs trait extraction; the other backends, if ever evaluated, just implement the trait). Deferring the decision preserves optionality at near-zero cost; committing now sacrifices optionality for no benefit.
    • Defer all of validation architecture to a later ADR, ship operations layer with thin validate()rejected: The operations layer without articulated validators is the failure pattern this entire trajectory is trying to escape. Shipping ADR-024 + ADR-025 + the operations-layer plan without ADR-026 would produce a runtime that has the surface of validation (a validate step in the dispatch pipeline) but the substance of the current pre-commit model (opaque body running ad-hoc checks). Validation architecture is co-constitutive with operations architecture; they share the load-bearing.
    + +

    Related ADRs

    + +

    ADR-001 · ADR-002 · ADR-005 · ADR-007 · ADR-014 · ADR-023 · ADR-024 · ADR-025

    diff --git a/site/site/content/adr/en/accepted/adr-026.ncl b/site/site/content/adr/en/accepted/adr-026.ncl new file mode 100644 index 0000000..fb23eaf --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-026.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-026", + title = "Validation Architecture — Three Planes, First-Class Validators, SLA per Operation, Pluggable Commitment Backend", + slug = "026", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/026", + graph = { + implements = [], + related_to = ["adr-001", "adr-002", "adr-005", "adr-007", "adr-014", "adr-023", "adr-024", "adr-025"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-027.md b/site/site/content/adr/en/accepted/adr-027.md new file mode 100644 index 0000000..cf7144a --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-027.md @@ -0,0 +1,145 @@ +--- +id: "adr-027" +title: "Decentralization Architecture — P2P Pure, Pluggable SyncBackend, CRDT per Domain" +slug: "027" +subtitle: "Accepted" +excerpt: "ADR-024 establishes operations as the agent's only project-touching path." +author: "ontoref" +date: "2026-05-26" +published: true +featured: false +category: "accepted" +tags: ["adr-027", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    ADR-024 establishes operations as the agent's only project-touching path. +ADR-025 establishes that authoritative state lives in Rust on the substrate +(G1-G10) with NCL as manifestation. ADR-026 establishes the validation +architecture with witness-based local verification. All three were written +assuming a single-actor, single-node operational model — the substrate runs +locally, the operation dispatches locally, the validators verify locally.

    +

    The current production reality contradicts this assumption: ontoref-daemon +ships with stratum-db (feature-gated) that connects to a remote SurrealDB +server for the query cache. The 19 ontoref-onboarded projects share tables +in this remote DB via {slug}--{id} prefixes. This is a centralized model: a +remote authority holds the materialized state; outage of the DB outage +breaks discovery and cross-project query for every consumer.

    +

    A deeper structural deficit was identified during the session of 2026-05-26 +(this ADR's session): when the ontology lives inside the project's filesystem, +cross-project operations or verifications require dragging the entire other +project into local scope. This repeats the H7 saturation pattern at a +different layer — instead of agent-saturated-with-context, we have +verifier-saturated-with-other-project. The substrate's witness mechanism +(G5 Merkle proofs) was designed precisely to escape this saturation — a +verifier holding only state_root and witness can validate a claim without +holding the source. But the witness mechanism is useless if there is no +decentralized exchange channel and no separation of ontology from project.

    +

    This ADR closes the decentralization question. ADR-028 closes the matching +ontology-from-project separation question. Together they reorganize the +operational model from "centralized substrate + remote DB" to "P2P pure + +per-actor substrate + content-addressed ontology layer + witness-based +cross-actor verification".

    +

    The decision is irreversible-but-incremental: P2P pure is the architectural +commitment; the sync protocol implementation is deferred via a trait +abstraction that lets candidates (Iroh, Hypercore, Radicle, NATS) be +evaluated when empirical signals demand. The reversibility lives in the +backend choice, not in the model.

    + +

    Decision

    + +

    Adopt P2P-pure decentralization as the operational model: no central +authority node; each actor maintains its own substrate locally; sync is +peer-to-peer between actors who explicitly follow each other.

    +

    Four load-bearing capabilities that the architecture must support:

    +

    1. Multi-actor concurrent with sovereignty per actor — multiple actors + (humans + AI agents) operate simultaneously on the same logical + project; each holds its own copy; decides whom to follow; no + coordination authority.

    +

    2. Verifiability without complete state access — any actor can verify a + claim by another with only state_root + witness; the substrate's G5 + Merkle proofs are the mechanism; ADR-026's validators rely on this.

    +

    3. Decentralized discovery — actors find each other via DHT, gossip, or + social protocol (Radicle); no central registry. The current + ~/.config/ontoref/projects.ncl becomes "peers I follow", not + "registry I depend on".

    +

    4. Resilience to partition / offline-first — an actor offline retains + full local operation; reconnection syncs lazily; no synchronous + coordination required for correctness.

    +

    SyncBackend trait (pluggable backend, decision deferred):

    +

    trait SyncBackend { + async fn announce(&self, oplog_head: OpId, public_key: PublicKey); + async fn discover_peers(&self) -> Vec<Peer>; + async fn fetch_op(&self, peer: &Peer, op_id: OpId) -> Result<Op>; + async fn subscribe_peer(&self, peer: &Peer) -> Stream<Op>; + }

    +

    Candidate impls behind trigger-based assessment (same pattern as ADR-026 + CommitmentBackend): + - FilesystemSync — default, trivial impl (peers share a filesystem + directory; useful for development and testing; not really + decentralized but zero-dependency) + - IrohSync — n0-iroh; Rust-native; gossip + content-addressed blobs + + DHT; aligned with Ed25519 identity (ADR-005) + - HypercoreSync — Hypercore via Rust bindings if mature; append-only + log primitive aligned with oplog + - RadicleSync — git-protocol-over-Radicle for repos; potentially + different layer from oplog sync + - NatsSync — NATS JetStream as fabric (G7 of the substrate plan, + previously skipped)

    +

    The decision among these is deferred. The trigger conditions: + - cross_actor_sync_required > 0 (i.e., the pilot has reached the point + where actual P2P sync is needed; until then, FilesystemSync suffices) + - actor_count > 1 (a real second actor exists, not a self-loop) + - peer_discovery_latency or sync_throughput become measurable concerns

    +

    The fit-assessment (type-fit, not benchmark) for each candidate runs as + ADR-026 spike: does the candidate adapt to SyncBackend trait without + invasive wrappers. Candidates that do not fit are eliminated.

    +

    CRDT per-domain (selective CRDT, not global):

    +

    D6 of the old plan ("HLC without CRDT") was a sound default for the + substrate's structural correctness but is insufficient when multi-actor + concurrent edits target the same cell. This ADR modifies D6 selectively: + domains where concurrent edits over the same cell are plausible declare a + CRDT merge strategy; other domains retain HLC last-writer-wins.

    +

    Identified domains needing CRDT (this session's identification): + - Backlog (BacklogItem): multiple actors add/status/done concurrently; + MergeStrategy::OrSet for items; MergeStrategy::Lww per status with + HLC tiebreak + - Ontology nodes (description, edges): collaborative description + enrichment + edge addition by multiple contributors; + MergeStrategy::TextMerge for description (LSEQ or similar); + MergeStrategy::OrSet for edges + - QA entries + search bookmarks: append-only accumulators; + MergeStrategy::GSet trivially

    +

    CrdtMergeStrategy trait (pluggable, similar to SyncBackend and + CommitmentBackend):

    +

    trait CrdtMergeStrategy { + type Item; + fn merge(&self, a: Self::Item, b: Self::Item) -> Self::Item; + fn merge_witness(&self, ...) -> MergeWitness; + }

    +

    Default impl: HlcLastWriterWins (preserves current D6 behavior). + Per-domain impls declared in the domain's NCL schema.

    +

    SurrealDB position under P2P pure:

    +

    SurrealDB becomes opt-in via the existing feature flag (cargo build + -p ontoref-daemon --features db). The runtime does not depend on + SurrealDB; consumers that want SurrealDB's query expressiveness configure + it locally. The stratum-db dependency moves from "feature-gated default" + to "feature-gated optional with explicit choice". Existing 19 onboarded + projects continue to work unchanged; the migration to non-SurrealDB + query layer (datalog G6 + ontoref-query) is opt-in per project.

    +

    Crucially: SurrealDB is local-only under P2P pure. The connect_remote + path becomes connect_local (embedded mode) or is deprecated when the + project chooses opt-out.

    + +

    Constraints

    + +
    • Hard crates/ontoref-ops or new crate ontoref-sync MUST declare trait SyncBackend with methods announce, discover_peers, fetch_op, subscribe_peer. A default impl FilesystemSync MUST exist that satisfies the trait without external dependencies (suitable for development and single-actor pilot phase).
    • Hard crates/ontoref-ops MUST declare trait CrdtMergeStrategy with method merge(a, b) -> Item + merge_witness(...). A default impl HlcLastWriterWins MUST exist. Domain-specific impls (OrSet for backlog, TextMerge for ontology descriptions, GSet for QA accumulators) MUST be declared when the corresponding domain catalog operations are implemented.
    • Hard ontoref-daemon MUST build successfully with --no-default-features --features mcp (or equivalent) — i.e. without the db feature that pulls stratum-db. The runtime MUST function for query, mutation, validation, and witness emission without SurrealDB present.
    • Hard This ADR engages and synthesizes the voluntary-adoption + internal-coherence-enforced axioms (ADR-025) and the formalization-vs-adoption tension. The synthesis is the per-actor sovereignty model: adoption of P2P is voluntary per project (opt-in tier), but a project that adopts P2P operates within enforced internal coherence (witness-verified ops, signed identity, CRDT per declared domain).
    + +

    Alternatives considered

    + +
    • Keep centralized SurrealDB as authoritative query layer; add P2P as optional layer aboverejected: Reproduces the H7 saturation pattern at the storage layer: actors depend on a central authority for queries, defeating the substrate's local-verifiability purpose. The 'optional P2P' layer becomes a courtesy feature rather than the operational backbone; usage in practice falls back to the centralized path. The decision to decentralize must be in the model, not an additional layer.
    • Federation model (ActivityPub / Matrix / NATS cluster) — nodes federate but each is a hubrejected: Federation introduces 'nodes' as a category distinct from 'actors', creating a power asymmetry: node operators control sync topology. This contradicts the per-actor sovereignty capacity. Federation is the right model for some systems (Mastodon, Matrix), but not for ontoref's protocol-of-self-knowledge use case where every actor is logically equivalent.
    • CRDT globally for all domainsrejected: Forces CRDT complexity on domains that do not need it. ADR transitions are sequential by their definition (Proposed → Accepted is causally ordered; there is no semantic merge of 'two simultaneous Accepts'). Forcing CRDT here creates a richer data model without operational benefit. The per-domain selective model is the right grain.
    • Pick a sync protocol now (e.g. Iroh) and commit before having multi-actor scenariosrejected: Same mistake as ADR-026 commitment-backend would be without trigger-based deferral. Sync protocol choice without operational scenarios is a guess; the cost of remaking the choice later (after seeing actual sync patterns) outweighs the benefit of committing now. The trait abstraction is cheap; the deferral is honest.
    • Remove SurrealDB unilaterally; force all consumers to datalog G6rejected: Breaks 19 onboarded projects without consultation. The capacity to use SurrealDB for SurrealQL expressiveness is legitimate for projects that have invested in it. The opt-in demotion lets each project decide; coexistence is sustainable under P2P pure because SurrealDB is local-only.
    + +

    Related ADRs

    + +

    ADR-001 · ADR-005 · ADR-013 · ADR-017 · ADR-023 · ADR-024 · ADR-025 · ADR-026

    diff --git a/site/site/content/adr/en/accepted/adr-027.ncl b/site/site/content/adr/en/accepted/adr-027.ncl new file mode 100644 index 0000000..7aeefd9 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-027.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-027", + title = "Decentralization Architecture — P2P Pure, Pluggable SyncBackend, CRDT per Domain", + slug = "027", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/027", + graph = { + implements = [], + related_to = ["adr-001", "adr-005", "adr-013", "adr-017", "adr-023", "adr-024", "adr-025", "adr-026"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-028.md b/site/site/content/adr/en/accepted/adr-028.md new file mode 100644 index 0000000..5722df6 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-028.md @@ -0,0 +1,181 @@ +--- +id: "adr-028" +title: "Ontology Layer Separation from Project Layer — Content-Addressed Decentralized Ontology with Classification" +slug: "028" +subtitle: "Accepted" +excerpt: "Through this session's evolution, the project's ontology lived inside the" +author: "ontoref" +date: "2026-05-26" +published: true +featured: false +category: "accepted" +tags: ["adr-028", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    Through this session's evolution, the project's ontology lived inside the +project's filesystem (.ontology/, adrs/, reflection/), git-versioned +alongside the project's code. This pattern made sense when the ontology was +specific to a single project; it became structurally problematic as the +ecosystem grew to 19 onboarded projects sharing the same ontoref protocol, +the same axioms, the same Practice nodes for common patterns, and +cross-project federation became a recurring need.

    +

    The session's load-bearing insight (your formulation): when the ontology is +coupled to the project's filesystem, cross-project operations or +verifications require dragging the entire other project into local scope. +A project A trying to validate against a constraint declared in project B +has no choice but to clone B, parse B's NCL, materialize B's state — and +then perform a single check that touches one node of B. This is the H7 +saturation pattern at the storage layer: as agents saturate when loaded +with full project context, verifiers saturate when loaded with full +foreign-project context.

    +

    The substrate G1-G10 (G5 in particular: Merkle commitments + witness API) +was designed to escape this — a verifier holding only state_root + witness +can validate a claim without holding the source. But the witness mechanism +is useless when the verifier still must clone the source to know what +state_root applies, what schema is in force, what validators bind to which +ops. The asymmetry between (small witness) and (huge cloned context) breaks +the local-verifiability promise.

    +

    This ADR closes the asymmetry by separating two layers that have been +conflated:

    +

    Project layer — the consumer codebase. Rust crates, Nushell scripts, + config files, project-specific NCL extensions. Lives + in Radicle / jj repositories. Managed by the existing + VCS abstraction (ADR-013).

    +

    Ontology layer — protocol-level and shared-domain ontologies. Lives + as content-addressed decentralized streams: each + ontology is an append-only oplog (G3 primitive), with + Merkle commitments (G5) over its current state. + Reachable by content-address, not by filesystem + location. Multiple projects reference the same + ontology by its content-address; none of them owns it.

    +

    Cross-project verification under this separation: project A's operation +that requires validating against an ontological entity declared in shared +ontology X resolves the reference by content-address, fetches only the +witness for the specific cell, verifies against the ontology's state_root. +No project is dragged; only the witness travels.

    + +

    Decision

    + +

    Separate the operational world into two layers with distinct sovereignty, +sync mechanisms, and discovery patterns:

    +

    Ontology layer (decentralized, content-addressed):

    +

    An ontology is an append-only oplog (G3 primitive from substrate G1-G10) + containing operations that build the ontological knowledge: axioms, + tensions, practices, ADR entries, validators, schemas. Each ontology + has: + - An identity (Ed25519 keypair for its maintainers; multi-maintainer + via CRDT per-domain from ADR-027) + - A content-address: blake3 hash of the head OpId chain root + - A current state_root (G5 Merkle commitment) + - A discovery surface: pubkey + recent head OpId, broadcast via + SyncBackend (ADR-027) — peer projects subscribe to follow updates

    +

    Ontologies are independent of any project. They exist as their own data + structures with their own sync, their own maintainers, their own + evolution. A project consumes ontologies by reference; cannot mutate + them locally; cannot fork them without explicit forking semantics + (which is itself an ontology operation — fork is a recorded act).

    +

    Project layer (Radicle + jj, existing infrastructure):

    +

    Project codebases continue to live in Radicle repos managed via jj + (ADR-013 VCS abstraction + the existing agent-workspace-orchestration + pattern with jjw). What changes is the content: + - Source code (Rust, Nushell, etc.) — unchanged + - Project-specific NCL extensions — minimized; most ontology content + moves to the ontology layer + - Local oplog of the project — records ops the project emits + (proposed ADRs, FSM state moves, backlog ops); references the + ontology layer for the schema and validators that apply + - Witnesses + state_root snapshots — for cross-project verification

    +

    Ontology classification with granularity:

    +

    Type 1 — Protocol ontology (mandatorily decentralized): + The ontoref-core itself — axioms (voluntary-adoption, + internal-coherence-enforced, protocol-not-runtime, self-describing, + dag-formalized), named tensions, base practices, base validators. + Every adopting project references this by content-address; no project + embeds it. The protocol ontology's maintainers are the ontoref + project's contributors (this repo's current authors); its + content-address is the protocol's published identity.

    +

    Type 2 — Shared domain ontology (granular: federable to mandatorily + decentralized): + Domain ontologies used by multiple projects: provisioning (cluster + ops, component catalog), persona (career, opportunities), lian-build + (image layers), forge-fleet (fleet management). The granularity: + - Type 2a — Federable: shared between this author's projects; + decentralization recommended but not mandatory; can live as a + single oplog accessible to a known peer set + - Type 2b — Mandatorily decentralized: shared with external + consumers (any third party may consume); MUST be content- + addressed and discoverable via SyncBackend; maintainers may be + broader than the originating author

    +

    Type 3 — Project-specific ontology (optional decentralization): + Extensions unique to a single project that have not (yet) graduated + to shared-domain status. Lives as project-local oplog initially; + promotable to Type 2 when consumption emerges. The current + .ontology/ content of the 19 onboarded projects predominantly falls + here.

    +

    Type 4 — Private ontology (local, tier-0): + Projects that have NCL files but no substrate, no oplog, no + operations layer. The pre-ADR-024 baseline. Untouched by this ADR.

    +

    The classification is metadata on every ontology declaration. The + ontoref-ontology-content crate (new, see below) reads the classification + and applies the corresponding sync / sovereignty rules.

    +

    New crate: ontoref-ontology-content

    +

    Hosts the per-ontology oplog primitives, content-addressing, and + fetch-by-witness logic. Distinct from ontoref-ontology (which becomes + the bridge between state and NCL manifestation, per ADR-025). Roles: + - OntologyId(blake3) — content-address + - fetch_ontology(id, sync_backend) — pull ops from peers + - fetch_cell_witness(id, cell_query) — fetch only the witness needed + to verify a specific cell, not the whole state + - verify_cell(ontology_id, cell, witness, state_root) — local check + - publish_op(ontology_id, op) — append to the ontology's oplog if + authorized; broadcast via SyncBackend

    +

    Cross-project verification via witness (the asymmetry-closing property):

    +

    Project A invokes an operation whose precondition includes "this entity + is in the pool declared by shared ontology X". The runtime: + 1. Resolves X by content-address (ontology_id) + 2. Determines the cell-of-X the precondition reads (e.g. the pool + entity) + 3. Calls fetch_cell_witness(X, pool_query) — receives only the + witness for that cell + the current state_root of X + 4. Verifies the cell content + Merkle proof against state_root + 5. If valid, the precondition is satisfied; runtime proceeds

    +

    No clone of X. No materialization of X's state. Only the witness + travels. The asymmetry is closed.

    +

    Migration path:

    +

    The existing 19 onboarded projects do not migrate immediately. ADR-028 + is a structural commitment with progressive realization: + - The ontoref-core ontology (Type 1) is extracted first into its own + content-addressed oplog; the existing .ontology/core.ncl content + becomes the bootstrap content of that oplog + - Each shared-domain ontology (Type 2) is extracted when its + multi-project usage is operationally evident (provisioning is the + most concrete candidate — used by provisioning + forge-fleet + + libre-* projects) + - Project-specific (Type 3) extensions stay in their projects until + they need cross-project consumption + - Private (Type 4) projects remain unaffected

    + +

    Constraints

    + +
    • Hard A new crate ontoref-ontology-content MUST exist with the following surface: OntologyId, fetch_ontology, fetch_cell_witness, verify_cell, publish_op. The crate depends on ontoref-types (for OpBody), ontoref-oplog (for log primitives), ontoref-commit (for state_root and witnesses), and optionally an SyncBackend impl.
    • Hard Every ontology (in this project: ontoref-core, the existing .ontology/) MUST declare its classification (Type 1 | Type 2a | Type 2b | Type 3 | Type 4) in its metadata. The classification field is part of the OntologyMetadata struct surfaced via ontoref-ontology-content::metadata(ontology_id).
    • Hard When ADR-028 transitions to Accepted (post-implementation), the ontoref-core ontology (current .ontology/core.ncl content) MUST be addressable via its content-address (blake3 of its head OpId chain root) and discoverable via at least one SyncBackend (FilesystemSync acceptable initially). The transition records the content-address as the canonical reference in CHANGELOG.md.
    • Hard An integration test in crates/ontoref-ontology-content MUST demonstrate: project A invokes an operation whose precondition references a cell in ontology X, the runtime fetches only the cell witness (not the full ontology), verifies via state_root, and the operation proceeds without materializing X locally. The test fails if X's full state is loaded.
    • Hard This ADR engages and synthesizes the protocol-not-runtime + self-describing axioms and the formalization-vs-adoption tension. The synthesis preserves all three: the ontology layer is reference content addressable by content-hash (not a runtime); self-description moves to its own layer (preserved structurally); classification with granularity absorbs the adoption shock per ontology.
    + +

    Alternatives considered

    + +
    • Keep ontology inside each project; build cross-project federation that smart-loads partial ontology datarejected: This was the current federation.rs design (BFS depth 5 over HTTP). It has been unused for 419 lines of code precisely because the cost of operating it (every project must expose endpoints, every cross-project query is a network call to a project's filesystem) exceeds the benefit. ADR-028's content-addressing inverts the model: the ontology IS the addressable unit, not 'a slice of a project'. The cost of operation drops because the sync target is purpose-built.
    • Single global ontology repo (one Radicle repo for all shared-domain content)rejected: Reproduces the centralization anti-pattern at the content-management layer. A single repo for all shared ontologies creates a coordination point (who can merge to it?) and a single failure mode. The per-ontology oplog model gives each ontology its own sovereignty — provisioning ontology and persona ontology have different maintainers, different update cadences, different consumers; collapsing them into one repo would force false coordination.
    • Move all ontology to a decentralized layer immediately; no Type 3 (project-specific) or Type 4 (private) tierrejected: Crushes adoption. A new project trying ontoref for the first time would be forced to publish its ontology to a decentralized channel as a precondition of using the protocol at all. The classification with optional decentralization for Types 3 and 4 lets projects start where they are; the decentralization happens when consumption justifies it, not as an entry tax.
    • Keep ontology in project but use IPFS-style content addressing for cross-project refsrejected: Hybrid that does not resolve the core asymmetry. The ontology still lives in project filesystem; the content-address is a thin convenience layer. Cross-project queries still require the responding project to materialize its full ontology (because the content-address points to a file the consumer might not have, hence needs full materialization to serve the content). The asymmetry is unresolved.
    • Use Radicle for ontology layer too (one Radicle repo per ontology)rejected: Radicle is a git-protocol-based system; its strengths are code review, branching, social collaboration. Ontology evolution is operationally distinct — append-only oplog with CRDT merge, not git-style branching. Forcing ontology into Radicle would impose the wrong semantics. The ontology layer needs its own primitive (oplog from G3); Radicle stays for project code.
    + +

    Anti-patterns

    + +
    • Drag Whole Project For Cross-Project Verification — Verifying a single ontological assertion declared in another project by +cloning that project and materializing its entire NCL state. Inverts the +substrate's G5 witness asymmetry: a Merkle witness is hundreds of bytes, +a project clone is megabytes-to-gigabytes plus parse cost. Saturates +verifiers the way unbounded context saturates agents (H7 pattern).
    • Centralized Shared-Ontology Repo — Collapsing every shared-domain ontology (provisioning, persona, lian-build, +forge-fleet) into a single global repository. Creates one merge gatekeeper, +one failure mode, and forces false coordination between ontologies whose +maintainers, update cadences, and consumer sets are independent.
    + +

    Related ADRs

    + +

    ADR-001 · ADR-005 · ADR-009 · ADR-013 · ADR-018 · ADR-020 · ADR-023 · ADR-024 · ADR-025 · ADR-026 · ADR-027

    diff --git a/site/site/content/adr/en/accepted/adr-028.ncl b/site/site/content/adr/en/accepted/adr-028.ncl new file mode 100644 index 0000000..d1e4a47 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-028.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-028", + title = "Ontology Layer Separation from Project Layer — Content-Addressed Decentralized Ontology with Classification", + slug = "028", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/028", + graph = { + implements = [], + related_to = ["adr-001", "adr-005", "adr-009", "adr-013", "adr-018", "adr-020", "adr-023", "adr-024", "adr-025", "adr-026", "adr-027"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-029.md b/site/site/content/adr/en/accepted/adr-029.md new file mode 100644 index 0000000..b84ee27 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-029.md @@ -0,0 +1,143 @@ +--- +id: "adr-029" +title: "Tier Coexistence as Permanent Design — Strictly Additive Stack, Offline-First, No Forced Migration" +slug: "029" +subtitle: "Accepted" +excerpt: "ADR-024 (operations as the agent's only project-touching path) and ADR-025" +author: "ontoref" +date: "2026-05-26" +published: true +featured: false +category: "accepted" +tags: ["adr-029", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    ADR-024 (operations as the agent's only project-touching path) and ADR-025 +(ontology core as authoritative state, NCL as manifestation) introduce a +new mutation discipline grounded in the verifiable substrate (ADR-023). +ADR-025 also performs the axiom split that replaces `no-enforcement` with +`voluntary-adoption` (invariant=true) and `internal-coherence-enforced` +(invariant=true). The split is precise: external adoption remains opt-in; +internal coherence is enforced only AFTER adoption of the operations tier.

    +

    Across the implementation of the operations-layer plan (gates O-pre → O7, +session 2026-05-26), an observed ambiguity arose in operational decisions: +the ontoref piloto self-host has migrated to operations as authoritative +(tier-2, Phase 1), but every other onboarded project (mirador, provisioning, +lian-build, vapora, stratumiops, kogral, libre-{daoshi,wuji,forge}, +secretumvault, typedialog, jpl-website, personal-ontoref, jpl-ontoref, +cloudatasave, build-in-layers, DD7pasos, librosys, forge-fleet, mina) still +relies on NCL-authoritative state with file-edit mutation. Questions +recurred during the session — "are we still going to support NCL?", "did +we migrate to Rust?", "do the tiers have the same platform requirements?" +— that surfaced an unstated design property: the protocol supports +multiple authoritative-state modes simultaneously, by construction, and +indefinitely.

    +

    The plan itself states this in three places without naming it as an +architectural property worth its own ADR:

    +

    - §1: "Piloto: ontoref mismo (self-hosted). Phase 2 queda fuera de + scope." + - §7 R11: "Phase 1 → Phase 2 nunca llega: Aceptable resultado + intermedio. No-transición es estado válido." + - §8: "Expansión a proyectos no-ontoref … requieren cada uno su sesión + de catálogo." (per-project decision, no global migration)

    +

    Without an ADR naming the property, future sessions risk interpreting +the migration as transitional and removing the tier-0/1 paths once the +piloto stabilises. Doing so would silently violate the +`voluntary-adoption` axiom and break the 18 onboarded projects that +chose minimal NCL as their adoption surface. The session that produced +this ADR included a sub-bug — the daemon's `global_schemas_path()` did +not expose the data-dir schemas, so consumer projects in tier-0/1 got +WARN-logged `config_surface not loaded` errors. The fix +(`global_schema_dirs()` returning multiple paths) was structurally +correct because it preserved tier-0/1 operability — but it would have +been incorrect (silent migration coercion) if the design were +transitional. This ADR records the property so the fix-or-not decision +is explicit at every future point.

    + +

    Decision

    + +

    The protocol supports three authoritative-state modes simultaneously and +indefinitely, as the canonical operational surface:

    +

    tier-0 — minimal NCL adoption. + Authoritative state: `.ontology/*.ncl`, `adrs/`, `reflection/` as files. + Mutation: file edit (any editor). + Stack: `nickel` binary + optional `ontoref` Nushell CLI. + No daemon required for the project's own correctness; the daemon may + serve the UI and describe queries if installed.

    +

    tier-1 — substrate adopted. + Authoritative state: NCL files PLUS the substrate's commit layer + oplog. + Mutation: file edit + reconcile (substrate ingests NCL on read). + Stack: tier-0 + `ontoref-daemon` binary + G1-G10 substrate crates. + Cryptographic: blake3 (commitment root). + Cross-instance verifiability via state_root + witness, but no domain + operations.

    +

    tier-2 — operations adopted. + Authoritative state: Rust substrate (commit_layer + state_root). + NCL is manifestation rendered from state. + Mutation: dispatch_op exclusively (MCP, HTTP, CLI, or save-on-NCL + reconciler in Phase 1). + Stack: tier-1 + `ontoref-ops` + `ontoref-derive` + actor Ed25519 keypair. + Cryptographic: blake3 + Ed25519 (witness signing). + `internal-coherence-enforced` axiom applies HERE — operations are the + only mutation path. Below tier-2 the axiom does not apply (the + project hasn't adopted the operations tier; coherence remains + voluntary).

    +

    The three modes coexist permanently. There is no scheduled transition. +A project may stay at tier-0 indefinitely. A project may climb to +tier-1 or tier-2 voluntarily, on its own schedule, project by project. +The ontoref piloto runs at tier-2 / Phase 1; every other onboarded +project chooses its own tier independently.

    +

    Within tier-2, the Phase 1 → Phase 2 transition (NCL render-only, +save-reconciler off) is also opt-in per project and recorded in +`.ontoref/config.ncl::ops.phase`. The plan documents that Phase 2 may +never be reached — that outcome is valid by design.

    +

    Stack requirements are strictly additive: tier-N includes everything +tier-(N-1) needs plus its own delta. No tier requires network access or +external services for correctness. Optional features (`db`, `nats`, +`ui`, `mcp`, `graphql`, P2P sync backends, S3/OCI blob backends) are +orthogonal to tier — any project at any tier can opt into any feature +independently. Network-using features (real sync, remote blobs) are +trigger-based per ADR-027 / ADR-026 / D23: activated only when +documented thresholds are exceeded.

    +

    The daemon binary serves all tiers from a single build. Per-project +configuration declares the tier; the daemon dispatches against the +declared mode. Migration of an individual project is a per-project +deliberate act, not a protocol upgrade.

    + +

    Constraints

    + +
    • Hard The protocol MUST NOT include any mechanism that automatically migrates a project from one tier to another without an explicit per-project decision recorded in `.ontoref/config.ncl`. Migration aids (templates, documentation, CLI helpers) are permitted; automatic mutation of a project's tier is not.
    • Hard The daemon's NICKEL_IMPORT_PATH MUST resolve canonical schema imports (`manifest`, `core`, `state`, `gate`, `ontoref-project`) for tier-0/1 projects. The resolution MUST include `$data_dir/ontology/schemas/` and `$config_dir/schemas/`.
    • Hard The default build of `ontoref-daemon` (any features active) MUST operate correctness-wise without network access. Optional sync backends (SyncBackend impls beyond FilesystemSync) and blob backends (BlobBackend impls beyond LocalFilesystemBlobs) MAY require network when activated, but the default substrate MUST NOT.
    • Hard Changes to ontoref-ops, ontoref-derive, ontoref-ontology-content, or the substrate crates MUST NOT remove a public API that tier-0/1 consumers rely on. Schema additions are permitted; schema removals require a new ADR documenting the deprecation path.
    • Hard Tier transitions (upgrade tier-N → tier-N+1 OR downgrade tier-N → tier-N-1) +MUST refuse to execute when any protocol migration is pending. Specifically: +`ontoref migrate pending` MUST return an empty list before any change to +`.ontoref/config.ncl::ops.tier` is accepted by the daemon. + +Enforcement layers: + 1. The future `transition_tier` operation (to be defined alongside + ADR-033) declares this constraint as a synchronous pre-validator. + 2. Until `transition_tier` is implemented, the daemon's ConfigWatcher + enforces the same precondition: any mutation of `ops.tier` in + `.ontoref/config.ncl` triggers a guard that consults the project's + migration store. When pending migrations are detected, the daemon + emits a Block-severity notification, refuses to swap the config in + its registry, and the project remains at its current tier value. + +This is a forward declaration: the constraint exists at the +protocol-semantic level (ADR-029) before its operational +materialisation (ADR-033). The daemon-side enforcement closes the +operational gap immediately so manual `.ontoref/config.ncl` edits +cannot bypass the rule during the period between this amendment and +ADR-033's authorship.
    + +

    Alternatives considered

    + +
    • Schedule a transition: all projects migrate to tier-2 by date Xrejected: Violates voluntary-adoption (invariant=true) by construction. Forcing migration would require superseding ADR-025 with a new axiom — and no empirical evidence motivates that change. The 18 onboarded projects chose tier-0/1 deliberately; revoking that choice is hostile to ecosystem participants. Also: there is no engineering need — the substrate G1-G10 already coexists with NCL ingestion and the cost of supporting both is dwarfed by the cost of forcing migration on consumer teams.
    • Pick one mode (NCL-authoritative OR Rust-authoritative) and deprecate the otherrejected: Both modes have first-class users. Documentation-heavy projects need NCL editability with low ceremony. Multi-actor projects need cryptographic witnesses. Pruning either side narrows the protocol's value. The plan itself preserved this — Phase 2 is opt-in not mandatory, tier-2 is opt-in not mandatory. Codifying that choice as an ADR makes it stable.
    • Leave the property implicit in plan §7 R11 and §8 without a dedicated ADRrejected: The implicit-in-plan path produced confusion during the implementation session itself ('did we migrate?', 'why are we fixing NCL paths?'). If the design intent was unclear at the moment of implementation, it will be unclear at every future session. The cost of one ADR is small; the cost of repeated rediscovery is large.
    • Define an even finer tier ladder (tier-0.5, tier-1.5, etc.) for fractional adoptionrejected: The three-tier model already maps to the three distinct value propositions (typed records / cryptographic state / verifiable ops). Sub-tiers would multiply the support matrix without adding qualitatively new modes. If a use case emerges that genuinely needs an intermediate tier, a future ADR can extend the ladder; pre-emptively splitting it is over-design.
    • Force tier-2 only for new projects, leave existing 18 on tier-0/1rejected: Splits the ecosystem into 'old protocol' and 'new protocol' permanently — every cross-project feature has to handle the discontinuity. Permanent coexistence within a single protocol is cleaner: every project chooses its tier, every tier interoperates with the others (within voluntary-adoption limits), and the design admits no temporal cohort.
    + +

    Anti-patterns

    + +
    • Formalizing during the fire (or under perpetual firefighting) — Treating ontoref as something to build or update mid-incident, when latency-to-action dominates — or adopting it at all when the work is perpetual firefighting or throwaway (a spike you will delete). The types, flows, and definitions become pure tax: they stop you to understand something you will not revisit. The resolution is not 'never stop' but the distinction between stopping-to-build and stopping-to-read: introspection is time-shifted, not eliminated. You pay in the calm and spend the dividend in the fire — a `describe impact` / `describe constraints` read costs seconds and tells you which pipe not to cut. Harm occurs only when you try to formalize during the fire, or when no calm window ever exists.
    • Tier-0 false certainty — form outruns truth, no witness to anchor — A well-typed NCL graph looks authoritative even when it is stale or aspirational; the crispness of the form lends unearned credibility to the content. A desired_state can read as a real rudder when it was only a wish. Below tier-2 this harm is undefended: a pretty stale graph misleads exactly like a stale README — worse, because the README does not feign rigor. The witness (tier-2) is the only thing that anchors certainty to something independently verifiable; without it, ontoref can manufacture false certainty.
    • Ceremony capture — feeding the protocol instead of the work — Adoption degrades into satisfying the protocol — updating nodes, writing ADRs, contenting validators — rather than doing the work the protocol was meant to serve. The tail wags the dog. This is the Yang collapse of formalization-vs-adoption: maximal formalization imposed where it does not pay back.
    • Premature formalization — freezing an unstable identity — Typing something whose identity is not yet stable freezes a guess. Declaring invariants over what is not yet invariant — a pre-product-market-fit product, a person in genuine transition — ossifies what should stay fluid and makes the inevitable change expensive. Form applied before the substance has settled is rigidity, not coherence.
    + +

    Related ADRs

    + +

    ADR-001 · ADR-020 · ADR-023 · ADR-024 · ADR-025 · ADR-026 · ADR-027 · ADR-028

    diff --git a/site/site/content/adr/en/accepted/adr-029.ncl b/site/site/content/adr/en/accepted/adr-029.ncl new file mode 100644 index 0000000..526fd01 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-029.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-029", + title = "Tier Coexistence as Permanent Design — Strictly Additive Stack, Offline-First, No Forced Migration", + slug = "029", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/029", + graph = { + implements = [], + related_to = ["adr-001", "adr-020", "adr-023", "adr-024", "adr-025", "adr-026", "adr-027", "adr-028"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-031.md b/site/site/content/adr/en/accepted/adr-031.md new file mode 100644 index 0000000..cca7d85 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-031.md @@ -0,0 +1,246 @@ +--- +id: "adr-031" +title: "Ontology and Reflection as Constitutive Duality — Co-Equal Axes Sealed by Witness" +slug: "031" +subtitle: "Accepted" +excerpt: "The named tension `ontology-vs-reflection` has lived in `.ontology/core.ncl`" +author: "ontoref" +date: "2026-05-26" +published: true +featured: false +category: "accepted" +tags: ["adr-031", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    The named tension `ontology-vs-reflection` has lived in `.ontology/core.ncl` +since the project's first ondaod pass with `pole = 'Spiral` and the gloss +"This tension is onref's core identity." The tension is the protocol's name +for itself: **on+re**. Ontology captures what IS (invariants, structure, +being); Reflection captures what BECOMES (operations, drift, memory). The +Spiral pole records that neither half is reducible to the other.

    +

    The session arc 2026-05-20 → 2026-05-26 (ADR-023 verifiable substrate, ADR-024 +operations layer, ADR-025 ontology core as state and NCL as manifestation, +ADR-026 three-plane validation, ADR-027 P2P pluggable sync, ADR-028 +ontology layer separation from project layer, ADR-029 tier coexistence, +ADR-030 catalog discovery cross-project) produced a precise, lived +articulation of one half of the tension — the **ontology axis**. Substrate +(G1-G10), commit roots, state authority, content-addressed ontologies, +schema discovery: all of these structurally name the substance pole. The +**reflection axis** received concrete commitments too (operations as the +only mutation path, witnesses as cryptographic acts, reflection modes as +NCL DAGs, describe queries as self-knowledge), but its axiom-level status +was never asserted on equal footing.

    +

    Two observations made the gap visible:

    +

    (1) A reading of the eight 2026-05-20s ADRs against the external "modern + ontology stack" vocabulary (Cagle / Shannon 2026-05-19, *What a Modern + Ontology Stack Actually Looks Like*) revealed that the article's five + layers (annotational, schema, graph, projection, inference) cover the + ontology axis cleanly and have no vocabulary for the reflection axis. + The article's "inference layer" is passive derivation (SPARQL, + SHACL rules, LLM resonance over a static substrate); ontoref's + reflection layer is enactive (DAG modes execute work, forms mutate + lifecycle, run protocols are acts the system performs on itself). + Forcing reflection into a "layer" of an ontology-axis stack imports + the substance-only metaphysics the external article carries.

    +

    (2) ADR-030 surfaces the duality operationally without naming it: the + catalog has a "DECLARATIVE side (NCL)" — ontology pole — and an + "EXECUTIVE side (Rust)" — reflection pole. ADR-030 § decision + observes that the declarative side composes cross-project today via + ADR-028 content-addressing while the executive side awaits triggers. + The two sides progress at different rates because they live on + different axes. ADR-030 names this asymmetry empirically; ADR-031 + names it architecturally.

    +

    Without an axiom-level recognition, the same drift will recur: future +sessions may interpret reflection components as "tooling around the +ontology" or treat state as primary rather than as a manifestation that +acts deposit on substance. ADR-025 already moved state from primary to +"render of authority"; that move is half of what this ADR generalises. The +generalisation: the protocol is **constitutively dual**. Every component +sits on the ontology axis, the reflection axis, or the seam where the two +are bound. The seam has a name in the existing architecture — the +**witness** (ADR-023 § decision, ADR-024 § acting-binding). Every reflective +act emits a witness; every witness deposits manifestation into the +ontological substrate. The seam is the formalism by which the two axes +remain bound without one absorbing the other.

    +

    The risk of NOT recording this duality at axiom level is concrete: the +external "modern ontology stack" vocabulary is mature, legible, and +adoption-ready, but it carries a substance-only frame that — uncritically +imported — would collapse the protocol's core identity. The recent ADRs +023-030 stand within the protocol's own discipline (ondaod, Spiral +preservation, voluntary-adoption). An ADR that elevates the duality +itself becomes the constitutional anchor against which any future external +vocabulary import is checked.

    + +

    Decision

    + +

    Elevate the existing `ontology-vs-reflection` Spiral tension to an axiom- +level constitutive duality of the protocol. Two new axiom nodes are added +to `.ontology/core.ncl`; the existing tension node remains in place as the +ondaod-procedural anchor (tensions are where the Spiral is named; axioms +are where the structural commitments live). The duality has three +formal commitments:

    +

    1. ONTOLOGY AXIS — `ontology-axis-substance` (axiom, invariant=true)

    +

    The protocol's substance pole. Names: axioms, tensions, practices, + schemas, ADR declarations, content-addressed ontologies, state as + manifestation (ADR-025), substrate commitments (ADR-023), the + declarative side of catalogs (ADR-030). Maps onto the existing + `pole = 'Yin` convention for nodes whose primary character is + being/structure.

    +

    Operational reading: every artifact that answers "what IS" — a + schema declaration, an ADR constraint, an ontology node, a render + from state, a commit root — lives on this axis.

    +

    2. REFLECTION AXIS — `reflection-axis-act` (axiom, invariant=true)

    +

    The protocol's act pole. Names: operations, dispatch, reflection + modes (DAG enactment), forms (lifecycle mutation), describe queries + (self-observation), run protocols (acts the system performs on + itself), the executive side of catalogs (ADR-030), validator + execution. Maps onto the existing `pole = 'Yang` convention for + nodes whose primary character is becoming/operation.

    +

    Operational reading: every artifact that answers "what DOES" or + "what KNOWS itself" — a domain operation, a mode step, a form + submission, a `describe` invocation, a validator firing, a run + transition — lives on this axis.

    +

    3. WITNESS AS SEAM — `witness-as-axis-seam` (axiom, invariant=true)

    +

    The witness (ADR-023 G5 commitment + signature, ADR-024 acting- + binding) is the formal seam where the two axes meet. Every act on + the reflection axis that mutates substance emits a witness; every + mutation of the ontology axis state is the deposit of a witnessed + act. The witness is neither pure substance nor pure act — it is the + point at which the two are bound. Maps onto the existing + `pole = 'Spiral` convention for nodes that sustain both poles + without collapse.

    +

    Operational reading: any component on the seam (witness chain, + commit emission, dispatch protocol, state reconciliation in + Phase 1 of ADR-025) carries the load of preserving the duality + across the act-to-substance transition.

    +

    AXIS DECLARATION REQUIREMENT (new structural property):

    +

    Every node in `.ontology/core.ncl` already carries a `pole` field + ('Yin, 'Yang, 'Spiral) and a `level` field ('Axiom for invariant-level + nodes among others). ADR-031 fixes the operational semantics of the + pole field as the axis-declaration mechanism:

    +

    'Yin → ontology axis (substance side) + 'Yang → reflection axis (act side) + 'Spiral → seam (sustains both poles; not a derived synthesis)

    +

    The pole field of `Axiom`-level nodes that ALSO commit to an axis MUST + carry a secondary `axis` field with value 'Substance | 'Act | 'Seam. + This is additive and zero-migration for the existing nodes whose pole + already encodes their axis cleanly.

    +

    GLOSSARY AXIS FIELD (forward integration):

    +

    The forthcoming `.ontology/glossary.ncl` schema (anticipated by the + ontoterm-* skills and the present session's draft state) MUST include + an `axis: 'Substance | 'Act | 'Seam` field on every canonical term. + Terms that name substance ("axiom", "schema", "manifestation", + "commit-root") declare 'Substance. Terms that name acts ("operation", + "dispatch", "describe", "mode", "form") declare 'Act. Terms that name + seams ("witness", "on+re", "ondaod") declare 'Seam.

    +

    FORBIDDEN PATTERNS (consequence of the duality):

    +

    The following architectural moves become explicit anti-patterns under + ADR-031:

    +

    (a) Treating reflection components as "tooling" or "infrastructure + around the ontology". Reflection is axis-level; not subordinate. + (b) Treating state (ADR-025 manifestation) as the primary seat of + authority. State is derived from witnessed acts; it is substance + sediment, not source. + (c) Importing external vocabulary that names only one axis (e.g. the + RDF / SHACL semantic-stack vocabulary that has no analogue for + enactive reflection) AS IF it covered the protocol. Such + vocabulary may be used for projection (per ADR-028 / ADR-030), + but never as primary terminology for ontoref components. + (d) Synthesizing the two axes into a single "stack" or "layered + architecture". The duality is constitutive: there is no layer + in which it dissolves. + (e) Designing components that operate on one axis without declaring + their seam interaction. A new reflection-axis component (e.g. a + new mode kind, a new form variant) MUST declare how its acts + emit witnesses; a new ontology-axis component (e.g. a new schema + type, a new ADR family) MUST declare how it receives manifestation + from witnessed acts.

    +

    OPERATIONAL CONSEQUENCES (downstream ADRs reread under ADR-031):

    +

    - ADR-023 verifiable substrate: G1-G5 are substance-axis (types, + triples, commitments); G6 (oplog) is act-axis (recording acts); G5 + witness is seam. + - ADR-024 operations layer: acting-binding declares the act-axis as + the only path to substance mutation. ADR-031 generalises: + "structurally enforced internal coherence" (the existing axiom) + operates by routing all acts through the seam. + - ADR-025 state as manifestation: substance is rendered from + witnessed act log. The axiom split (voluntary-adoption + + internal-coherence-enforced) ADR-025 introduced is now expressible: + voluntary-adoption is a property of the BINDING between act and + substance (adoption is itself an act); internal-coherence-enforced + is a property of the SEAM (witness chain rejects ill-formed acts). + - ADR-026 three-plane validation: the three planes (Structural / + Semantic / Dialectical) operate at the seam. They are not + substance-axis checks; they are conditions on what acts may pass + through the seam. + - ADR-027 P2P pluggable sync + ADR-028 ontology layer separation: + cross-project content-addressing is substance-axis (the ontology + travels as a substance artifact). Cross-project operation invocation + (ADR-030) is act-axis (the act travels). The seam-axis question is + cross-project witness verification (how does project B verify a + witness signed by project A) — recorded as an open seam-axis + question for future ADR. + - ADR-029 tier coexistence: the three tiers correspond to three + points of duality engagement. tier-0 engages only the substance + axis (NCL files). tier-1 adds substrate (substance-axis with + cryptographic state, but no acting-binding). tier-2 fully engages + the seam (acts emit witnesses; substance is sediment of witnessed + acts). The tiers are progressive realisations of the same duality, + not separate architectures. + - ADR-030 catalog discovery: the declarative/executive split is the + ontology/reflection split. Phase A mechanisms preserve the duality + (declarative composes via substance-axis content addressing; + executive composes via act-axis dispatch). The CatalogBackend + abstraction sits on the seam.

    +

    NON-DECISIONS (deliberately left open):

    +

    - Terminology for the two poles in user-facing documentation + (substance/act, being/becoming, yin/yang, 体/用, ontology/reflection). + The axiom IDs `ontology-axis-substance` and `reflection-axis-act` + fix the canonical English handle; vocabulary choice in docs, + glossary, and CLI output is open for an annotational-layer ADR. + - Whether the witness seam needs a finer decomposition (witness emission + vs witness verification vs witness chain integrity) at axiom level. + Deferred until cross-project witness verification (per ADR-028) + surfaces empirical pressure. + - Whether reflection-axis components require their own typing + discipline parallel to ontology-axis schemas. The current operation + catalog (ADR-030 declarative side) is the existing answer; whether + a richer "operation ontology" is needed is deferred.

    + +

    Constraints

    + +
    • Hard .ontology/core.ncl MUST contain three new Axiom-level nodes with ids ontology-axis-substance, reflection-axis-act, and witness-as-axis-seam. All three MUST have invariant=true. The existing ontology-vs-reflection Tension node MUST remain present (axiom elevation is additive, not replacing).
    • Hard Each of the three new Axiom nodes MUST declare an axis field with value 'Substance (for ontology-axis-substance), 'Act (for reflection-axis-act), or 'Seam (for witness-as-axis-seam). The field is additive; pre-existing axioms are unaffected.
    • Hard Documentation and ADRs MUST NOT adopt the Cagle/Shannon modern-ontology-stack five-layer model (annotational / schema / graph / projection / inference) as primary self-description of ontoref. The model MAY be used at the projection layer (docs/correspondences/) for external-audience translation, but MUST NOT replace the on+re duality vocabulary in core protocol description.
    • Hard This ADR engages and synthesizes the named tension ontology-vs-reflection AND records the synthesis as axiom-level elevation rather than collapse. The synthesis state and direction of motion MUST be discoverable in the decision and rationale of this ADR.
    • Hard A migration MUST be added to reflection/migrations/ that instructs consumer projects to add the three new Axiom nodes (ontology-axis-substance, reflection-axis-act, witness-as-axis-seam) to their own core.ncl. The migration MUST be idempotent and MUST NOT remove the existing ontology-vs-reflection Tension node.
    + +

    Alternatives considered

    + +
    • Leave ontology-vs-reflection as a Tension only, without elevating to axiomrejected: The current Tension-only position is what allowed the asymmetric articulation observed in ADRs 023-030 — substance pole received structural commitments (substrate, state authority, content-addressing) while act pole received only operational commitments (operations exist, modes exist) without axiom-level recognition. Without axiom elevation, the same drift will recur on every future external-vocabulary import. The Tension records the polarity; only an axiom records the structural commitment to preserve it.
    • Adopt the Cagle/Shannon modern-ontology-stack five-layer model as ontoref's primary self-descriptionrejected: The five-layer model (annotational, schema, graph, projection, inference) covers the substance axis cleanly and has NO vocabulary for the act axis beyond passive derivation in the inference layer. Adopting it primarily would erase the reflection axis from ontoref's self-description, collapsing the duality the existing Tension explicitly preserves. The model is useful at the projection layer (per ADR-028) as a correspondence for external RDF/SHACL audiences — but not as primary terminology.
    • Introduce three new axes (substance, act, seam) without preserving the existing ontology-vs-reflection Tension noderejected: Removing the Tension node would lose the ondaod-procedural anchor: tensions are where the Spiral is named for review by future sessions. Replacing the Tension with three Axioms collapses the dialectical record into a structural commitment. ADR-031 keeps both: the Tension preserves the Spiral-naming discipline; the three new Axioms commit to the structural property. Both are non-redundant.
    • Use only the existing pole field (Yin/Yang/Spiral) without adding an explicit 'axis' field on the new axiom nodesrejected: The pole field carries informal convention; axiom nodes deserve an explicit field for axis declaration because (a) axioms set the structural commitment that the convention must henceforth follow, and (b) the explicit field protects against future pole-field semantics drift. The 'axis' field is additive and zero-migration; the cost is small, the explicitness benefit is real.
    • Defer axiom elevation until cross-project witness verification surfaces empirical pressurerejected: The pressure has already surfaced — twice. First operationally (ADR-030's declarative/executive split). Second meta-architecturally (the present session's analysis of external semantic-stack vocabulary). Deferring would let future sessions silently adopt vocabulary that erodes the duality. The cost of an early axiom is small; the cost of late axiom recovery (retroactively rejecting an adopted vocabulary) is large. Trigger-based deferral (ADR-026/D15 pattern) applies to abstraction-point IMPL choices, not to constitutional commitments.
    + +

    Anti-patterns

    + +
    • Reflection-as-Tooling — Treating reflection components — modes, forms, describe, dispatch, +validators — as "tooling" or "infrastructure around the ontology". +Demotes the act axis to subordinate plumbing. Reflection is axis-level, +co-equal with ontology; calling it tooling erases the constitutive +duality at the vocabulary boundary.
    • State-as-Primary-Authority — Treating state (the manifestation rendered from witnessed acts) as the +primary seat of authority. Reverses the ADR-025 direction: state is +substance sediment, not source. Acting on state directly bypasses the +witness chain that gives state its integrity.
    • Single-Axis Vocabulary Import — Adopting an external vocabulary that names only one axis (e.g. the +RDF/SHACL/OWL semantic-stack: annotational, schema, graph, projection, +inference) as ontoref's primary terminology. Imports a substance-only +metaphysics that has no analogue for enactive reflection — the act axis +becomes invisible in the imported lexicon.
    • Duality-as-Layered-Stack — Flattening the on+re duality into a single layered architecture (e.g. +"ontology layer → operations layer → UI layer"). Layers imply +subordination; the substance and act axes are co-equal and the seam is +not a layer between them but the formalism that binds them. There is no +layer in which the duality dissolves.
    • Axis Component Without Seam Declaration — Designing a new component that operates on one axis without declaring its +seam interaction. A reflection-axis component (new mode kind, new form +variant) that does not declare how its acts emit witnesses. An ontology- +axis component (new schema family, new ADR genre) that does not declare +how it receives manifestation from witnessed acts. Either omission lets +the two axes drift toward disconnection one component at a time.
    + +

    Related ADRs

    + +

    ADR-009 · ADR-018 · ADR-020 · ADR-023 · ADR-024 · ADR-025 · ADR-026 · ADR-027 · ADR-028 · ADR-029 · ADR-030

    diff --git a/site/site/content/adr/en/accepted/adr-031.ncl b/site/site/content/adr/en/accepted/adr-031.ncl new file mode 100644 index 0000000..5640562 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-031.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-031", + title = "Ontology and Reflection as Constitutive Duality — Co-Equal Axes Sealed by Witness", + slug = "031", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/031", + graph = { + implements = [], + related_to = ["adr-009", "adr-018", "adr-020", "adr-023", "adr-024", "adr-025", "adr-026", "adr-027", "adr-028", "adr-029", "adr-030"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-032.md b/site/site/content/adr/en/accepted/adr-032.md new file mode 100644 index 0000000..b01b731 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-032.md @@ -0,0 +1,161 @@ +--- +id: "adr-032" +title: "Layout Consolidation Under .ontoref/ Root — Single Hidden Hierarchy for the Protocol's Consumer Footprint" +slug: "032" +subtitle: "Accepted" +excerpt: "Every project that adopts ontoref currently inherits a sprawl of root" +author: "ontoref" +date: "2026-05-26" +published: true +featured: false +category: "accepted" +tags: ["adr-032", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    Every project that adopts ontoref currently inherits a sprawl of root +directories: `.ontoref/` (hidden — config and runtime state), `.ontology/` +(hidden — declarative ontology), `adrs/` (visible — Architecture Decision +Records), `reflection/` (visible — qa, backlog, modes, schemas, modules, +migrations, nulib), and most recently `catalog/` (visible — operations and +validators per ADR-024, ADR-026). For consumer projects this means:

    +

    - Five root directories owned by ontoref alone, consuming 33-40% of the + `L-ROOT-LIMIT` (target 12-15 total root dirs) defined in + `.claude/layout_conventions.md`. + - Inconsistent hidden/visible split: `.ontoref/` and `.ontology/` are + dot-prefixed (following `L-NAMING-HIDDEN` for infrastructure dirs), + while `adrs/`, `reflection/`, `catalog/` are visible without + architectural justification for the inconsistency. + - Mixed identity signal: the visible directories present as + "first-class project content" alongside `crates/`, `src/`, `docs/`, + when in fact they are infrastructure of the protocol supporting the + project — not the project's substantive content. + - Operational friction: copying ontoref state across projects requires + enumerating five paths; deleting cleanly requires five `rm -rf` + invocations; CI exclusion requires five glob patterns; backup/restore + means five tar entries; auditing "is ontoref clean here?" means five + independent checks.

    +

    The current layout grew organically across ontoref's evolution. Each +section (`.ontology/` from the earliest ondaod pass; `adrs/` from +adr-001's adoption of ADR-as-data; `reflection/` from the +adopt-ontoref-tooling onboarding template; `catalog/` from ADR-024's +operations layer) arrived as a section of the protocol's design and was +placed where it made narrative sense AT THAT MOMENT. Nothing ever +collected them under a common root because the protocol grew as a tree, +not as a module. The session 2026-05-26 (ADR-029 tier coexistence +articulation, then ADR-030 catalog discovery, then ADR-031 on+re duality +elevation) produced both more sub-directories AND increased visibility +of the cost of the layout sprawl. The user's observation:

    +

    "ontoref debería aparecer en los proyectos como una estructura en un sólo + folder ... esto convierte al layout de proyectos en un lío además de no + cumplir con `.claude/layout_conventions.md`. Facilitaría mucho que todo + estuviera dentro de una misma jerarquía."

    +

    This ADR records that observation as architectural decision and consolidates +ontoref's consumer footprint into a single hidden hierarchy under +`.ontoref/`, propagated per-project via migration 0023 (opt-in, consistent +with `voluntary-adoption`). The change is a protocol version increment to +**0.1.1** — breaking layout change with migration assistance and dual-path +resolver back-compat during the transition window.

    + +

    Decision

    + +

    All ontoref consumer-facing artefacts MUST consolidate under a single +hidden root directory `.ontoref/` per consumer project. The canonical +layout becomes:

    +

    my-project/ + └── .ontoref/ ← single hidden ontoref subsystem root + ├── config.ncl ← daemon config (unchanged location) + ├── project.ncl ← project identity (unchanged location) + ├── actors.ncl ← actor keypairs (unchanged location) + ├── ontology/ ← MOVED FROM .ontology/ + │ ├── core.ncl + │ ├── state.ncl + │ ├── gate.ncl + │ ├── manifest.ncl + │ └── _refs.ncl ← was _ontology_refs.ncl + ├── adrs/ ← MOVED FROM adrs/ + │ ├── adr-*.ncl + │ └── _template.ncl + ├── reflection/ ← MOVED FROM reflection/ + │ ├── qa.ncl + │ ├── backlog.ncl + │ ├── search_bookmarks.ncl + │ ├── modes/ + │ ├── forms/ + │ ├── schemas/ + │ ├── modules/ + │ ├── migrations/ + │ └── nulib/ + ├── catalog/ ← MOVED FROM catalog/ + │ ├── schema.ncl + │ ├── operations/ + │ └── validators/ + ├── store/ ← runtime state (oplogs, witness chain) + │ └── ontologies/ ← was .ontoref/ontologies/ (content-addressed oplogs) + └── locks/ ← runtime advisory locks (unchanged)

    +

    The consumer project's own content (`crates/`, `src/`, `docs/`, `assets/`, +etc.) retains its existing layout. Only ontoref's footprint moves.

    +

    DUAL-PATH RESOLVER (transition period)

    +

    During the version 0.1.0 → 0.1.1 transition, the daemon implements a + dual-path resolver in `crates/ontoref-daemon/src/registry.rs` and + `crates/ontoref-ontology/src/ontology.rs` that:

    +

    1. Prefers the new path (`.ontoref/<section>/`) when it exists. + 2. Falls back to the legacy path (root-level `.ontology/`, `adrs/`, + `reflection/`, `catalog/`) when the new path is absent. + 3. Emits an `info`-level log when the legacy path is used, noting + the project has not yet applied migration 0023.

    +

    The dual-path resolver remains in the codebase until ontoref version + 0.2.0; at that point a future ADR may declare the legacy paths + removed (advisory, not forced).

    +

    ONTOREF-EL-PROYECTO MIGRATES FIRST (canonical demonstration)

    +

    Per the `self-describing` axiom (invariant=true), ontoref-el-proyecto + applies migration 0023 before any consumer project. This produces:

    +

    - The canonical layout demonstration that other projects can follow + - First-class evidence that the migration script works on real + content (ontoref's own ontology has 40 nodes, 91+ edges, 32 ADRs) + - The reference state for the dual-path resolver tests

    +

    Consumer projects (mirador, libre-{daoshi,wuji,forge}, lian-build, the + other 14 currently onboarded) apply migration 0023 voluntarily on + their own schedules. Until they do, the daemon serves them via the + legacy paths through the dual-path resolver.

    +

    SCOPE — WHAT MOVES, WHAT DOES NOT

    +

    MOVES into .ontoref/: + .ontology/ → .ontoref/ontology/ + adrs/ → .ontoref/adrs/ + reflection/ → .ontoref/reflection/ + catalog/ → .ontoref/catalog/

    +

    STAYS at root (for ontoref-el-proyecto only, not for consumers): + crates/ (Rust implementation crates — not consumer-relevant) + install/ (install scripts — not in consumer projects) + justfiles/ (build automation) + scripts/ (support scripts — co-existing with the consumer's own) + templates/ (consumer onboarding templates) + ontology/ (defaults + schemas installed to consumer data dir) + assets/ (web + presentation)

    +

    REMAINS unchanged (already under .ontoref/): + .ontoref/config.ncl + .ontoref/project.ncl + .ontoref/actors.ncl + .ontoref/locks/

    +

    NCL IMPORT PATH UPDATES

    +

    Inside the moved files, relative `import "..."` paths are updated to + reflect the new depth:

    +

    OLD: `.ontology/core.ncl` with `import "../ontology/defaults/core.ncl"` + NEW: `.ontoref/ontology/core.ncl` with `import "../../ontology/defaults/core.ncl"`

    +

    The daemon's `NICKEL_IMPORT_PATH` (managed by `global_schema_dirs()`) + continues to include the data-dir schemas at `$data_dir/ontology/schemas/` + and `$data_dir/ontology/`. Absolute schema imports (`import "manifest"`, + `import "core"`) keep working unchanged.

    + +

    Constraints

    + +
    • Hard All ontoref consumer-facing artefacts (ontology declarations, ADRs, reflection store, catalog declarations, runtime state, locks) MUST live under `.ontoref/` in a consumer project. The legacy root-level directories `.ontology/`, `adrs/`, `reflection/`, `catalog/` are deprecated by migration 0023 and removed by the migration's mv operations.
    • Hard The daemon's `registry.rs` and `ontology.rs` MUST implement a dual-path resolver that prefers the new path (`.ontoref/<section>/`) and falls back to the legacy path (root-level `.ontology/`, `adrs/`, `reflection/`, `catalog/`) when the new path is absent. The fallback MUST emit an info-level log noting the project has not yet applied migration 0023.
    • Hard Migration 0023 MUST be applied per-project via the standard `ontoref migrate` workflow. No automatic / silent / boot-time migration of consumer projects is permitted. The daemon detects un-migrated consumers via the legacy-path fallback and surfaces the recommendation via `ontoref migrate pending`, but does not act on it.
    • Hard ontoref-el-proyecto (this repository) MUST apply migration 0023 to its own consumer-facing artefacts before any consumer project is recommended to apply it. The canonical layout under `.ontoref/` is demonstrated in ontoref's own repo first; consumer documentation references the ontoref repo as the worked example.
    • Hard The project beacon `card.ncl` and the generated `artifacts/api-catalog-*.ncl` files MUST live under `.ontoref/` after migration 0023, NOT at project root. `card.ncl` is pure ontoref metadata (not an OS-level discovery convention like `Cargo.toml` or `package.json`) and `artifacts/` is generated ontoref output — both belong under the consolidated root.
    • Hard Section paths (ontology_dir, adrs_dir, reflection_dir, catalog_dir, artifacts_dir, card_path) MUST be addressable through `LayoutConfig` rather than hard-coded literals in production code. Defaults match the consolidated layout (`.ontoref/<section>/`), but projects MAY override any field via their `.ontoref/config.ncl` `[layout]` section. All call sites that read or write ontoref artefacts MUST route through `ontoref_ontology::layout::resolve_section` (reads, with legacy fallback) or `canonical_section` (writes, no fallback) or an explicit `LayoutConfig::resolve` / `canonical` method.
    + +

    Alternatives considered

    + +
    • Keep the current layout — accept the L-ROOT-LIMIT violation as a cost of organic growthrejected: The user's observation made the cost concrete and named the protocol's own convention (`.claude/layout_conventions.md`) as the rule being violated. The protocol cannot ignore its own conventions without losing credibility as a self-describing system. Five root directories for one subsystem is not organic growth; it is unsynchronised growth that the consolidation corrects.
    • Move ALL of ontoref under a visible `ontoref/` directory (not hidden)rejected: Visible directories signal 'first-class project content' alongside crates/, src/, docs/. ontoref is infrastructure-of-the-project, not the project's value content. L-NAMING-HIDDEN explicitly prescribes dot-prefix for process and configuration directories. Visible `ontoref/` would re-introduce the same identity-signal confusion the consolidation is meant to eliminate.
    • Hybrid layout — runtime state hidden, declarative content visible (`.ontoref/store/` + `ontoref/ontology/`, etc.)rejected: Re-introduces the inconsistency this ADR was opened to resolve. Two root dirs instead of five is an improvement but stops short of the single-point operational manipulation that the configuration-cube of ADR-029 requires. Half-measures cost the same migration effort as the full move with less of the benefit.
    • Per-tier layouts (tier-0 → minimal under .ontoref/; tier-2 → richer hierarchy)rejected: Multiplies the support matrix without qualitative gain. ADR-029 / `tier-coexistence-permanent-design` says tiers are about authoritative-state regimes, not about disk layout. Having one canonical layout that scales to all tiers (some directories empty at tier-0, populated at tier-2) is cleaner than three layouts.
    • Move incrementally — only `catalog/` to `.ontoref/catalog/` first (it is the newest, with no legacy users), defer `adrs/` / `reflection/` / `.ontology/` for later versionsrejected: Three partial migrations cost more than one comprehensive migration. Consumer maintainers would face N rounds of layout disruption instead of one. The dual-path resolver pattern works equally well for one or four moved directories. Doing it all at version 0.1.1 lets the protocol stabilise at the new layout sooner.
    + +

    Related ADRs

    + +

    ADR-001 · ADR-010 · ADR-020 · ADR-024 · ADR-025 · ADR-026 · ADR-028 · ADR-029 · ADR-030 · ADR-031

    diff --git a/site/site/content/adr/en/accepted/adr-032.ncl b/site/site/content/adr/en/accepted/adr-032.ncl new file mode 100644 index 0000000..89fed56 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-032.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-032", + title = "Layout Consolidation Under .ontoref/ Root — Single Hidden Hierarchy for the Protocol's Consumer Footprint", + slug = "032", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/032", + graph = { + implements = [], + related_to = ["adr-001", "adr-010", "adr-020", "adr-024", "adr-025", "adr-026", "adr-028", "adr-029", "adr-030", "adr-031"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-033.md b/site/site/content/adr/en/accepted/adr-033.md new file mode 100644 index 0000000..f128155 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-033.md @@ -0,0 +1,206 @@ +--- +id: "adr-033" +title: "Tier Transition Mechanism — Dual-Surface `transition_tier` with Transactional Effects and Witnessed Substrate Dormancy" +slug: "033" +subtitle: "Accepted" +excerpt: "ADR-029 (Tier Coexistence as Permanent Design) declared that the protocol" +author: "ontoref" +date: "2026-05-27" +published: true +featured: false +category: "accepted" +tags: ["adr-033", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    ADR-029 (Tier Coexistence as Permanent Design) declared that the protocol +admits tier-0, tier-1, and tier-2 simultaneously and indefinitely, with +per-project tier choice recorded in `.ontoref/config.ncl::ops.tier`. +Five Hard constraints anchor the property:

    +

    - `no-forced-tier-migration` — silent/automatic tier change forbidden + - `tier-0-must-resolve-schemas` — lower-tier operability preserved + - `offline-first-default-stack` — no network requirement at any tier + - `additive-stack-no-breaking-tier-changes` — no API removal across tiers + - `tier-transition-requires-clean-migration-state` — guard against + transition with pending migrations

    +

    The fifth constraint's enforcement primitive — `enforce_tier_transition_guard(old, new, pending_migrations)` — is implemented in +`crates/ontoref-daemon/src/tier_guard.rs` (175 LOC, 8 passing unit tests). +It is a pure function that returns `Ok(())` or +`Err(TierGuardError::MigrationsPending)`. It is **not yet wired into any +caller** because no caller exists: ADR-029 committed the constraint at +the protocol-semantic level, deferring the operational materialisation +to a follow-up ADR.

    +

    This ADR is that follow-up. It defines the operational primitive that +moves a project between tiers — what it is called, where it lives, what +it does, and what guarantees it offers — without re-opening ADR-029's +already-settled questions (the three tiers are permanent; transition is +voluntary; migrations-clean is a precondition).

    +

    CURRENT STATE OF THE SYSTEM ENTERING THIS DECISION

    +

    - ontoref-el-proyecto (the piloto) runs at tier-2 / Phase Progressive + *empirically* — 10 catalogued ops, dispatch via inventory, witnesses + emitted, replay-deterministic — but does NOT declare + `ops.tier = 'Tier2` in its `.ontoref/config.ncl`. The tier is + inferred from artefact presence (`catalog/operations/*.ncl`, + `crates/ontoref-ops/`), not from a deliberate config statement. + - The 18 other onboarded projects (mirador, lian-build, vapora, …) + run at tier-0 by config-default (`#[serde(default)]` on `OpsConfig` + yields `Tier::Tier0`). + - No project today exercises the tier_guard's failure path because + no project has crossed a tier boundary since ADR-029 was accepted.

    +

    OPEN QUESTIONS ENTERING THIS SESSION

    +

    1. Op vs CLI vs both — where does `transition_tier` live? + 2. Direction asymmetry — what does downgrade actually do? + 3. TierGuard wiring point — at config-reload, at op pre-validation, + or at both? + 4. Pending-migrations definition — strict or scoped? + 5. Effect catalog — concrete list of substrate effects per direction. + 6. Atomicity — transactional or compensating?

    +

    The decisions below resolve each open question through the ondaod- +synthesis lens (formalization-vs-adoption, ontology-vs-reflection) and +preserve the voluntary-adoption axiom invariant=true.

    + +

    Decision

    + +

    ONE PRIMITIVE, TWO SURFACES, ONE LOGIC

    +

    The operational primitive is named `transition_tier`. It exists at two +operational surfaces backed by a single Rust implementation:

    +

    SURFACE A — CLI command (every tier) + `ontoref tier set <Tier0|Tier1|Tier2>` + Available at every tier including tier-0 (where the op dispatcher + is not present). Internally calls + `ontoref_daemon::tier_transition::execute(target_tier, ctx)`.

    +

    SURFACE B — tier-2 catalogued operation + `catalog/operations/transition_tier.ncl` + Rust handler annotated + `#[onto_operation(id = "transition_tier", validation_sla = "Synchronous")]`. + Materialises automatically once a project reaches tier-2. Emits a + signed witness (Ed25519) covering the tier change. Calls the same + `ontoref_daemon::tier_transition::execute` function.

    +

    The CLI is the universally-available path; the catalogued op is the +witnessable path. Below tier-2 the CLI is the only surface; at tier-2 +both surfaces are available and BOTH route through the same Rust +function. This preserves ondaod (formalization-vs-adoption Spiral stays +open) by NOT forcing tier-0/1 projects to bootstrap a dispatcher to +change tier, AND by NOT letting tier-2 projects bypass witness emission +when they have the substrate to produce one.

    +

    GUARD WIRING — single entry-point, two reachability paths

    +

    `enforce_tier_transition_guard` fires at exactly one logical entry-point +— inside `ontoref_daemon::tier_transition::execute` — regardless of +surface. Both CLI and op converge on this function as the first +validator. Additionally, the daemon's existing `ConfigWatcher` (which +observes `.ontoref/config.ncl` changes) consults the same guard before +swapping the config in its registry. The three reachability paths:

    +

    a. CLI invocation → execute() → guard + b. Op dispatch → execute() → guard + c. Manual NCL edit + daemon reload → ConfigWatcher → guard

    +

    If the guard returns `Err(MigrationsPending)`, the function returns +without applying any effect; the daemon emits a `Block`-severity +notification; the registry retains the old tier value; manual NCL +edits are NOT swapped in (the daemon does not unilaterally overwrite +the file — it logs the block and lets the developer revert their edit +or `ontoref migrate` first).

    +

    PENDING-MIGRATIONS DEFINITION — strict

    +

    The guard treats `ontoref migrate pending` as authoritative. Any pending +migration blocks any tier change. This matches the current +`tier_guard.rs` implementation exactly and avoids introducing a new +optional schema field (`relevant_to_tier_transition`) whose semantics +would themselves be a Spiral collapse: the protocol cannot reliably +classify which migrations affect tier mechanics without empirically +testing every migration against every tier transition, and the cost of +that classification exceeds the cost of running pending migrations +first.

    +

    DOWNGRADE — mark dormant, never delete

    +

    `transition_tier` lowering `ops.tier` (e.g. Tier2 → Tier0) does NOT +delete substrate artefacts. The behaviour matrix:

    +

    | direction | NCL files | oplog | commit_layer | state_root | signing keys | + | ---------------- | --------- | ----- | ------------ | ---------- | ------------ | + | upgrade N→N+1 | preserved | created if absent | created if absent | initialised | regenerated if absent | + | downgrade N→N-1 | preserved | preserved (read-only) | preserved (read-only) | frozen (last value retained) | preserved (read-only) | + | downgrade N→N-2 | preserved | preserved (read-only) | preserved (read-only) | frozen (last value retained) | preserved (read-only) |

    +

    This is consistent with `ontoref-tier-downgrade-asymmetry` QA: past +cryptographic guarantees survive immutable, future guarantees pause, and +re-upgrade introduces an externally-observable epistemic gap in the +witness chain (not a protocol bug — an inherent property of cryptographic +chains).

    +

    EFFECT CATALOG

    +

    The function applies effects per direction in a fixed order, with each +effect carrying a compensating rollback action (see ATOMICITY below):

    +

    Tier0 → Tier1 + e1. Create `.ontoref/store/oplog/` if absent + e2. Bootstrap commit_layer (write empty root commit if oplog empty) + e3. Compute initial state_root from NCL ingestion + e4. Register the project's substrate state in daemon registry + e5. Mutate `.ontoref/config.ncl::ops.tier = 'Tier1` (atomic + file replace via tempfile + rename)

    +

    Tier1 → Tier2 + e1. Verify or create per-actor Ed25519 keypair + (`~/.config/ontoref/keys/<actor>.key`, 0600) + e2. Register the project's catalog by reading + `.ontoref/catalog/operations/*.ncl` (declarative side) + e3. Verify each declared op has a corresponding registered + OperationEntry (via `inventory::iter`) OR a declared non-Rust + kind per ADR-034 + e4. Mutate `.ontoref/config.ncl::ops.tier = 'Tier2`, + `ops.phase = 'Progressive` (atomic)

    +

    Tier2 → Tier1 + e1. Emit a final tier-descent witness (Ed25519-signed envelope + describing the descent, written to oplog as the last + signed entry — the oplog remains valid for verifiers) + e2. Mutate `.ontoref/config.ncl::ops.tier = 'Tier1` + e3. Update daemon registry to stop dispatching ops for this project + (return 404 from `/ops/{id}` while preserving witness verifiability)

    +

    Tier1 → Tier0 + e1. Update daemon registry to stop maintaining the substrate + (commit_layer becomes read-only archive; new NCL edits are NOT + ingested into the commit_layer) + e2. Mutate `.ontoref/config.ncl::ops.tier = 'Tier0`

    +

    Tier2 → Tier0 (compound descent) + Decomposed into (Tier2 → Tier1) followed by (Tier1 → Tier0), + applied atomically as one transaction. If e1 of the inner descent + succeeds but e2 of the outer descent fails, the WHOLE compound + rolls back. The function never leaves the project at an + intermediate tier value than the one declared.

    +

    ATOMICITY — transactional, all-or-nothing

    +

    The function applies all effects within a single transactional scope. +Each effect has a recorded compensating action. On any failure:

    +

    - The compensations execute in reverse order + - `.ontoref/config.ncl::ops.tier` is restored to its pre-transition + value (the file rename is the last effect; if it succeeds, the + transition committed) + - The daemon's in-memory registry is restored to its pre-transition + snapshot + - The failed transition is recorded in + `.ontoref/artifacts/transition-log/<timestamp>-failed.ncl` for + operator review (logged, never silently discarded)

    +

    The transactional model means the user sees one of exactly two outcomes: +the project is at the requested tier OR the project is at its original +tier; never an intermediate state. Synchronous SLA on the catalogued +op enforces this externally; the CLI surface has the same guarantee +because it calls the same Rust function.

    +

    ONTOREF-EL-PROYECTO DECLARES `ops.tier = 'Tier2`

    +

    As part of this ADR — not a separate housekeeping step — ontoref's own +`.ontoref/config.ncl` gains an explicit `ops = { tier = 'Tier2, phase += 'Progressive }` block. This makes the piloto self-host the FIRST +project to formally declare its tier and the canonical example for +every future declaration. The Hard constraint `piloto-declares-its-tier` +(below) pins the declaration so future sessions cannot silently remove +it.

    +

    NEW SCHEMA FIELDS — none in this ADR

    +

    `transition_tier` operates on the existing `OpsConfig` struct (tier + +phase). It does not require new schema fields. Migration 0024 +introduces no breaking change — it (a) documents the new CLI command +and op, (b) instructs ontoref-el-proyecto to add the explicit tier +declaration, and (c) recommends consumer projects add their own +declaration when they choose a tier.

    + +

    Constraints

    + +
    • Hard The `transition_tier` operational primitive (CLI surface, catalogued op, and ConfigWatcher hook) MUST invoke `enforce_tier_transition_guard` from `crates/ontoref-daemon/src/tier_guard.rs` as its first validator. No alternative implementation of the precondition check is permitted; the guard is single-source.
    • Hard The CLI surface (`ontoref tier set <T>`) and the tier-2 catalogued op (`transition_tier`) MUST both route through a single Rust function `ontoref_daemon::tier_transition::execute` (or its successor module). Neither surface may implement the transition logic independently.
    • Hard Every invocation of `transition_tier` MUST produce exactly one of two observable outcomes: (a) the project is at the requested tier with all effects applied OR (b) the project is at the original tier with no effect persisted. The function MUST NOT leave the project at any intermediate tier value, and MUST NOT leave any partially-applied substrate state visible to subsequent reads.
    • Hard Downgrade transitions (any direction where `new_tier < old_tier`) MUST NOT delete, truncate, or otherwise mutate the existing oplog, commit_layer, state_root snapshot, or actor signing key files. The substrate becomes read-only archive; new mutations follow the new tier's rules.
    • Hard When `transition_tier` is invoked with `old_tier = 'Tier2` AND `new_tier != 'Tier2`, the function MUST emit a final Ed25519-signed witness covering the descent before mutating `.ontoref/config.ncl::ops.tier`. The witness payload kind is `tier_descent`; the proof path references the pre-descent state_root.
    • Hard The ontoref repository's own `.ontoref/config.ncl` MUST contain an explicit `ops` section declaring `tier = 'Tier2`. The declaration MUST be a deliberate config statement, not inferred from artefact presence.
    • Hard The daemon's ConfigWatcher MUST consult `enforce_tier_transition_guard` before swapping a project's `OpsConfig` in the registry when the watcher detects a change to `.ontoref/config.ncl::ops.tier`. When the guard returns `Err`, the watcher MUST log a `Block`-severity notification and retain the previous config; it MUST NOT mutate the user's NCL file to revert the change (only refuses to honour it).
    • Hard The guard MUST treat any non-empty result from `ontoref migrate pending` as blocking. The function MUST NOT introduce per-migration opt-out fields (e.g. `relevant_to_tier_transition`) that would relax the constraint.
    • Soft Every failed transition (whether blocked by the guard or by a downstream effect failure) MUST produce a record in `.ontoref/artifacts/transition-log/<timestamp>-failed.ncl` containing: the requested target tier, the original tier, the failure source, and the timestamp. The record MUST NOT contain sensitive substrate state (no private key material, no secret values).
    + +

    Alternatives considered

    + +
    • CLI-only — `transition_tier` lives in `domains/framework/commands.nu` as a Nushell command calling daemon HTTP routesrejected: Tier-2 projects need the act to be witnessable per ADR-024 ('operations are the agent's only path to mutate state, with declared preconditions, validators, and witness emission'). A CLI-only implementation either bypasses the catalog (anti-ADR-024) or duplicates witness emission logic outside the catalog (state inconsistency risk). Dual-surface with one-logic is the synthesis that preserves both surfaces' guarantees.
    • Tier-2-op-only — only a catalogued op, no CLI surfacerejected: Tier-0/1 projects don't have the op dispatcher (the op runtime is part of the tier-2 substrate). They cannot climb without first bootstrapping the dispatcher — chicken-and-egg. CLI is the bootstrap surface that breaks this circularity. Removing it would make tier transitions impossible for the lowest tier, violating voluntary-adoption (a tier-0 project must be able to climb when it chooses to).
    • Separate per-direction primitives — `upgrade_tier`, `downgrade_tier`, `set_phase`rejected: Three primitives where one suffices. The function signature `execute(target_tier, ctx)` covers all directions; the effect catalog branches per direction internally. Splitting introduces three places to apply the guard, three CLI commands, three op declarations — without adding semantic clarity. The brief's `ontoref-tier-downgrade-asymmetry` QA already explained that downgrade has different effects than upgrade; that asymmetry lives inside the effect catalog, not in three separate primitives.
    • Pending-migrations scoped by `relevant_to_tier_transition: true` fieldrejected: Adding a per-migration boolean would require the protocol to pre-classify every future migration. The classification is itself a Spiral-collapse decision (which migrations 'matter' for tier mechanics is precisely what ADR-029's constraint refuses to pre-judge). Strict-any matches the current `tier_guard.rs` exactly, requires no schema change, and the operational cost (run migrations before transitioning) is the obvious workaround.
    • Best-effort with rollback log — apply effects, log completions, manual cleanup on failurerejected: ADR-024 makes operations the agent's only action path with witness emission per op. An op that partially applies state breaks the witness/state correspondence — the Ed25519 signature covers a state the verifier cannot reconstruct from the oplog. Transactional all-or-nothing is the only model compatible with witness-honesty. The cost (compensations per effect) is bounded and one-time.
    • Delete substrate on downgrade — clean slaterejected: Anti-ADR-029. Past witnesses are immutable per voluntary-adoption; deleting the oplog / signing keys would invalidate them from external verifiers' perspective. The `ontoref-tier-downgrade-asymmetry` QA explicitly states 'past witnesses verify forever against actors.ncl public keys' — that property requires keeping the keys and the oplog. Dormancy preserves the property at the cost of disk space; deletion breaks it.
    + +

    Related ADRs

    + +

    ADR-018 · ADR-023 · ADR-024 · ADR-025 · ADR-026 · ADR-029 · ADR-030 · ADR-032

    diff --git a/site/site/content/adr/en/accepted/adr-033.ncl b/site/site/content/adr/en/accepted/adr-033.ncl new file mode 100644 index 0000000..d31e4bd --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-033.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-033", + title = "Tier Transition Mechanism — Dual-Surface `transition_tier` with Transactional Effects and Witnessed Substrate Dormancy", + slug = "033", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/033", + graph = { + implements = [], + related_to = ["adr-018", "adr-023", "adr-024", "adr-025", "adr-026", "adr-029", "adr-030", "adr-032"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-035.md b/site/site/content/adr/en/accepted/adr-035.md new file mode 100644 index 0000000..18231a3 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-035.md @@ -0,0 +1,177 @@ +--- +id: "adr-035" +title: "Positioning Layer — Marketing as Queryable Protocol Surface" +slug: "035" +subtitle: "Accepted" +excerpt: "Ontoref's thesis is that everything project-defining is queryable NCL:" +author: "ontoref" +date: "2026-05-26" +published: true +featured: false +category: "accepted" +tags: ["adr-035", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    Ontoref's thesis is that everything project-defining is queryable NCL: +axioms, tensions, practices, ADRs, reflection modes, manifest capabilities, +config surface, backlog, Q&A, search bookmarks, even the API catalogue +(via the #[onto_api] proc-macro + inventory). The protocol pushes this +discipline aggressively into every layer it touches.

    +

    One surface escapes the discipline: the *outward-facing* artefacts of +the project — the things that explain to a potential adopter, sponsor, +or contributor *why they should care*. Today these live as:

    +

    - card.ncl — a tagline + features array attached to ProjectCard. + Identity-shaped, audience-flat. There is no model of *whom* the + project speaks to, *what claim* is being made to each audience, + or *which architectural decisions back the claim*.

    +

    - assets/web/ — index.html, personal.html, provisioning.html, + architecture-diagram.html. These are the public face of ontoref. + They are **hand-edited markdown-and-HTML**, disconnected from + the NCL substrate. Every other artefact in ontoref derives from + a typed source (ADRs from forms via Jinja, API pages from inventory + catalogue, describe outputs from ontology/manifest/qa). Public + messaging does not.

    +

    - domains/{personal, framework, provisioning}/ — these are *implicit* + audience segments, modelled as technical extension verticals (per + ADR-012) rather than as go-to-market audiences. The mapping + "platform engineer ↔ provisioning domain" exists in the maintainer's + head, nowhere in NCL.

    +

    The gap is real and visible. The user observation that opened this ADR: +when ontoref reasons about *what to build next* (Wish/Idea backlog, +ADR drafting, tension synthesis) it has rich machinery; when ontoref +reasons about *who cares and why* it has card.ncl's tagline string. +Marketing claims drift from architectural decisions; messaging copy +ages independently of the substrate that justifies it; the protocol's +own "everything is NCL" axiom is silently violated at exactly the +surface where adopters meet the project.

    +

    Two prior sessions have orbited this gap without closing it:

    +

    - The PRD-vs-ADR question (this session, prior turn) — initially + framed as "should backlog Wish items carry a PRD block?". That + sketch (extend Item with prd field) addresses *internal* product + intent. It does not address the outward-facing positioning surface.

    +

    - ADR-031's on+re duality — established that ontology (substance, + "what IS") and reflection (act, "what DOES") are constitutive, + not optional. Positioning artefacts live primarily on the reflection + axis (claims are acts of communication toward an audience) but are + *anchored* to substance via evidence_adrs (each claim cites the + ADRs that materially back it). Without this anchoring, claims drift + into pure narrative and become un-auditable.

    +

    The positioning layer formalises the outward-facing surface as queryable +NCL — completing the protocol's coverage of the project's externalised +self — while keeping prose-as-prose via a narrative_path that points to +markdown bodies. The skeleton is verifiable (audience present, evidence +ADRs cited when Live, launch criteria executable); the flesh stays human.

    + +

    Decision

    + +

    A new layer `positioning` is added to the protocol surface, with three +artefact kinds — **Audience**, **ValueProp**, **Campaign** — typed by +schemas installed to the user data dir, and instantiated per-project +under `.ontoref/positioning/`.

    +

    SCHEMAS (installed to data dir, same install path as project-card.ncl)

    +

    ontology/schemas/positioning.ncl + PositioningStatus | [| 'Draft, 'Validated, 'Live, 'Retired |] + LaunchCheck | tagged record (Grep | NuCmd | ApiCall | FileExists) + Audience | { id, name, description, primary_pain, context, + channels[], evidence[], linked_nodes[] } + ValueProp | { id, claim, audience_ids[], problem_solved, + alternatives[], unique_angle, evidence_adrs[], + proof_points[], narrative_path?, launch_criteria[], + status, retired_at?, superseded_by? } + Campaign | { id, title, audience_ids[], value_prop_ids[], goal, + channels[], launch_criteria[], narrative_path?, + status, starts_at?, ends_at? }

    +

    ontology/defaults/positioning.ncl + make_audience, make_value_prop, make_campaign — apply cross-field + contracts: + - non_empty_audience_ids (value-prop must target someone) + - live_requires_evidence_adrs (Live claims must cite ≥1 ADR) + - retired_has_date (Retired requires retired_at) + - superseded_implies_retired (cannot supersede while Live) + - each_launch_check_well_formed (per-tag field requirements)

    +

    CONSUMER LAYOUT (per-project, opt-in)

    +

    my-project/ + └── .ontoref/ + └── positioning/ ← new layer root + ├── audiences/ + │ ├── audience-platform-engineer.ncl + │ ├── audience-ai-agent-runtime.ncl + │ └── audience-architecture-team.ncl + ├── value-props/ + │ ├── vp-001-structure-that-remembers.ncl + │ ├── vp-002-mcp-agent-discoverability.ncl + │ └── vp-003-decision-replay.ncl + ├── campaigns/ + │ └── campaign-NNNN-slug.ncl + └── narratives/ + ├── vp-001-self-knowledge.md ← prose for the claim + └── audience-platform-engineer.md

    +

    NCL holds the verifiable skeleton (audience, evidence_adrs, launch_criteria, +status). Markdown narratives hold the messaging body. Generated HTML in +`assets/web/` derives from the pair via the same Jinja pattern used for +ADRs.

    +

    CARD.NCL INTEGRATION

    +

    ProjectCard schema gains one optional field: + primary_value_prop_id | String | optional

    +

    When present, it MUST resolve to a file at + .ontoref/positioning/value-props/<id>.ncl

    +

    card.tagline is preserved (zero migration cost). When primary_value_prop_id + is set, downstream renderers (portfolio publication, web assets) MAY + prefer the canonical value-prop's claim over the static tagline.

    +

    CHECK TYPE REUSE — ACKNOWLEDGED DUPLICATION

    +

    positioning.LaunchCheck is structurally identical to the ConstraintCheck + type in `.ontoref/adrs/adr-schema.ncl` (tags: 'Grep, 'NuCmd, 'ApiCall, + 'FileExists; per-tag well-formedness). The duplication is intentional + in this ADR: lifting ConstraintCheck into a shared + `ontology/schemas/check.ncl` requires editing the ADR schema's import + graph (changing what every existing ADR resolves against), which is + out of scope for introducing a new optional layer.

    +

    Lift-out is recorded as a soft constraint in this ADR + (shared-check-lift-out-pending) and deferred to a follow-up migration. + Until then, validators in both layers reference the same tags by name + and the test suite checks shape-equivalence on every CI run.

    +

    ASSETS/WEB/ DERIVATION (out of scope but enabled)

    +

    This ADR does not specify the asset generator. It establishes the + queryable source from which assets/web/index.html, personal.html, etc. + CAN be generated. A follow-up ADR (or migration) replaces hand-edited + HTML with Jinja-templated output reading the positioning tree + linked + narratives, the same way ADRs are generated from forms today. Until + that follow-up lands, assets/web/ remains hand-edited and the + positioning layer informs it via reference, not derivation.

    +

    OPT-IN PER PROJECT

    +

    positioning is a tier-orthogonal optional layer (consistent with + ADR-029's tier-coexistence design). A project at tier-0 with no + positioning/ directory operates exactly as before. Adoption is + declared by creating `.ontoref/positioning/` and ≥1 audience + ≥1 + value-prop; the daemon's describe surface exposes positioning as a + category iff the directory is non-empty. No migration is required + for existing consumer projects; the layer is purely additive.

    +

    CLI / DESCRIBE SURFACE

    +

    ontoref describe positioning [--audience <id>] [--status Live] + → lists value-props (filtered) with their audience, evidence_adrs, + and narrative_path

    +

    ontoref positioning validate + → runs launch_criteria checks across all value-props and campaigns; + reports pass/fail per artefact

    +

    ontoref positioning audit + → cross-references evidence_adrs against the ADRs directory; + flags claims whose cited ADRs are 'Deprecated, 'Superseded, or + missing entirely (drift detection between marketing and architecture)

    + +

    Constraints

    + +
    • Hard The positioning schema (ontology/schemas/positioning.ncl) and its defaults (ontology/defaults/positioning.ncl) MUST be installed to the user data dir as part of every ontoref install, alongside project-card.ncl and the other layer schemas. Without these files, consumer projects cannot resolve `import "positioning"` and the layer is structurally unavailable.
    • Hard The positioning defaults file (ontology/defaults/positioning.ncl) MUST be installed alongside the schema. Cross-field contracts (live_requires_evidence_adrs, retired_has_date, etc.) are applied via the make_* helpers in this file — without it, value-prop files importing `defaults/positioning.ncl` fail to parse and the structural invariants the layer promises are unenforceable.
    • Hard Audience records MUST live in their own files under `.ontoref/positioning/audiences/<id>.ncl`. Value-prop files MUST reference audiences by id (audience_ids: Array String), NOT inline the audience record. The `primary_pain` field is the canonical signature of an audience record — its presence inside a value-prop file is the violation.
    • Hard When `card.ncl` declares `primary_value_prop_id = "vp-XXX-..."`, the referenced value-prop file MUST exist at `.ontoref/positioning/value-props/vp-XXX-....ncl`. Dangling references between identity (card) and positioning (value-props) silently break downstream renderers (portfolio, web pages, MCP surfaces).
    • Hard Every value-prop with status='Live MUST declare a non-empty evidence_adrs array. This is enforced at nickel-export time by the live_requires_evidence_adrs contract in `ontology/defaults/positioning.ncl`. The Hard runtime check confirms the contract has not been bypassed (e.g. by editing exported JSON directly).
    • Hard When a value-prop or campaign declares `narrative_path = "..."`, the path MUST resolve to an existing file. Markdown narratives outside the NCL ecosystem are referenced, not validated for content, but their existence is the minimum enforceable invariant.
    • Soft The LaunchCheck type in `ontology/schemas/positioning.ncl` is structurally identical to ConstraintCheck in `.ontoref/adrs/adr-schema.ncl`. Both MUST be lifted into a shared `ontology/schemas/check.ncl` in a follow-up migration. Until that migration lands, the duplicated definitions MUST stay synchronised: any new tag added to one MUST be added to the other in the same commit.
    + +

    Alternatives considered

    + +
    • Extend card.ncl monolithically with positioning fields (audiences, value_props, campaigns inline)rejected: Mixes identity (what the project is — stable across years) with positioning (claims aimed at specific audiences — evolves per campaign). The schema becomes ambiguous: is ProjectCard.tagline the project's identity statement or its current value-prop claim? Inline arrays also lose the per-artefact reviewability that separate files give (each value-prop change is its own git diff, its own narrative file, its own launch_criteria). Card-as-monolith fails as soon as a project has more than one audience to address.
    • Address only the internal PRD gap — extend backlog Item with a prd block (route A from the prior session turn)rejected: Solves a different problem (internal product intent for the engineering team) and leaves the outward-facing surface — the one that adopters actually see — untouched. The prior session sketch is complementary, not substitutive: an engineering PRD captured in backlog and a marketing value-prop captured in positioning serve disjoint audiences (team vs. world). Choosing only the PRD route leaves assets/web/ disconnected from the protocol forever.
    • Keep positioning as pure markdown under .ontoref/positioning/*.md — no NCL skeletonrejected: Loses every property that justifies adding the layer: no evidence_adrs anchor (claims drift from architecture invisibly), no launch_criteria (no executable readiness check), no audience targeting (claims float free of who they address), no MCP queryability (markdown is opaque to structured tooling), no audit surface (drift between marketing and architecture is undetectable). Markdown-only positioning is what every project already has via README and blog posts — adding it to the protocol contributes nothing structural.
    • Per-domain positioning — let each domains/{personal,framework,provisioning}/ carry its own positioning filesrejected: Couples positioning to the technical extension mechanism (domains) when in fact value-props frequently cross domains (a single claim like 'structure that remembers' targets multiple audiences across multiple domains). Per-domain positioning would either duplicate value-props across domain directories (drift risk) or require a cross-domain index that re-introduces the centralised positioning/ directory by another name. The centralised layer with audience_ids as the cross-cutting key is simpler and supports the cross-domain claim case natively.
    • Defer the entire question — write hand-edited HTML now and revisit positioning later when adoption volume justifies itrejected: The protocol's coherence with its own axiom (everything is NCL) is the value proposition — deferring its application to the outward-facing surface is precisely the pattern (substance-vs-act collapse) that ADR-031 was opened to prevent. Also: the longer the surface stays hand-edited, the more drift accumulates between claims and architecture, and the higher the cost of eventually adopting positioning. The opt-in design means deferral by individual consumers is already supported — what is rejected is the protocol itself deferring the schema.
    + +

    Anti-patterns

    + +
    • Live Value-Prop Without Evidence ADRs — A value-prop is set to status='Live but evidence_adrs is empty — marketing is published without architectural backing. The live_requires_evidence_adrs contract catches this at nickel-export, but the anti-pattern is the maintainer reasoning that 'we can fill in evidence later'.
    • Audience Record Inlined in Value-Prop — A value-prop file declares the audience inline (name, primary_pain, channels) instead of referencing it by id. Multiple value-props targeting the same audience drift independently.
    • Marketing-Architecture Drift — A value-prop's evidence_adrs cites an ADR that has been superseded or deprecated — the claim is still Live, but its backing is no longer the active architectural decision. The marketing surface and the architecture diverge silently.
    + +

    Related ADRs

    + +

    ADR-001 · ADR-009 · ADR-012 · ADR-015 · ADR-024 · ADR-027 · ADR-029 · ADR-030 · ADR-031 · ADR-032

    diff --git a/site/site/content/adr/en/accepted/adr-035.ncl b/site/site/content/adr/en/accepted/adr-035.ncl new file mode 100644 index 0000000..c45bd01 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-035.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-035", + title = "Positioning Layer — Marketing as Queryable Protocol Surface", + slug = "035", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/035", + graph = { + implements = [], + related_to = ["adr-001", "adr-009", "adr-012", "adr-015", "adr-024", "adr-027", "adr-029", "adr-030", "adr-031", "adr-032"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-036.md b/site/site/content/adr/en/accepted/adr-036.md new file mode 100644 index 0000000..00cd50e --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-036.md @@ -0,0 +1,84 @@ +--- +id: "adr-036" +title: "Tier Transition Substrate Effects — Deferred Scope from ADR-033" +slug: "036" +subtitle: "Accepted" +excerpt: "ADR-033 (Tier Transition Mechanism) enumerated a six-effect catalog per" +author: "ontoref" +date: "2026-05-29" +published: true +featured: false +category: "accepted" +tags: ["adr-036", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    ADR-033 (Tier Transition Mechanism) enumerated a six-effect catalog per +transition direction. ADR-033 §FULL EFFECTS implemented exactly one of +those effects: the atomic config file replace (the last step of every +direction). The other fifteen effects — oplog bootstrap, commit_layer +initialization, state_root computation, daemon registry mutation, +Ed25519 keypair generation, catalog verification, daemon registry +de-registration, and tier-descent witness emission — were deferred +because they require coordinated implementation across the workspace +substrate crates (`ontoref-oplog`, `ontoref-commit`, `ontoref-ops`), +which were still being built out — not because of any external +dependency. The oplog is redb-backed and lives entirely in the +workspace; no stratumiops crate is required to persist the witness +chain.

    +

    The Hard constraints `downgrade-preserves-substrate-immutable` and +`tier-2-descent-emits-final-witness` from ADR-033 are documented as +DEFERRED in the tier_transition module preamble. This ADR is that +follow-up.

    +

    CURRENT STATE ENTERING THIS DECISION

    +

    Tier transition declaratively works via three reachability paths + (CLI, ConfigWatcher, catalogued op) that all converge on + `ontoref_ops::tier_transition::execute`. The execute function mutates + ONLY the config.ncl file atomically. No substrate is bootstrapped on + upgrade; no witness is emitted on descent; no oplog is created.

    +

    Tier-2 today is therefore declarative-only — projects can declare + `ops.tier = 'Tier2` and the protocol honours the declaration, but + the ADR-024 promises of cryptographic provenance and replay- + deterministic operation logs remain unfulfilled at runtime.

    + +

    Decision

    + +

    ACCEPTED. The substrate effects are implemented behind a `substrate` +feature flag on ontoref-ops (default: off), coordinated across +ontoref-ops (effect handlers), ontoref-oplog (redb-backed oplog +persistence), and ontoref-commit (commit_layer / state_root). The +oplog persistence backend is redb, in-workspace — NOT stratumiops +stratum-db. This ADR fixes the scope and the binding constraints; the +implementation is tracked as backlog item bl-014.

    +

    SHAPE OF THE NEXT SCOPE

    +

    Substrate effects will be implemented behind a `substrate` feature + flag on ontoref-ops (default: off). When enabled, execute runs the + full effect catalog; when disabled, only the atomic config rewrite + runs — preserving ADR-029 offline-first-default-stack for tier-0 + consumers.

    +

    Per-actor Ed25519 keys: generated on first Tier1→Tier2 transition, + stored at ~/.config/ontoref/keys/<actor>.key (mode 0600), public + key published in .ontoref/actors.ncl.

    +

    Oplog: append-only via ontoref-oplog (redb-backed, in-workspace), + mirrored as NCL render output for human inspection.

    +

    Tier-descent witness: payload_kind = 'tier_descent', written as + the last signed entry of the project's oplog before the config + rewrite to a lower tier.

    +

    Compensating rollback: each substrate effect registers its undo + into the transaction scope established by tier_transition::execute. + On any failure, compensations run in reverse order. Failures are + recorded in .ontoref/artifacts/transition-log/<ts>-failed.ncl + (already implemented in ADR-033 §FULL EFFECTS).

    + +

    Constraints

    + +
    • Hard Substrate effects (oplog bootstrap, Ed25519 keypair, witness emission) MUST be gated behind a `substrate` feature flag on ontoref-ops. Builds without the feature MUST still produce a working `tier_transition::execute` that performs the atomic config rewrite. Preserves ADR-029 offline-first-default-stack.
    • Hard When substrate is enabled and `tier_transition::execute` is invoked with old_tier=Tier2 AND new_tier!=Tier2, the function MUST emit a witness with payload_kind='tier_descent' to the project oplog before applying the config rewrite. Closes the deferred half of ADR-033 `tier-2-descent-emits-final-witness`.
    + +

    Alternatives considered

    + +
    • Leave the gap as implicit Rust source TODOsrejected: Implicit TODOs are not queryable. ADR-031 on+re duality requires protocol gaps to live on the ontology axis. Cost of this ADR is small; cost of every consumer rediscovering the gap grows linearly with the consumer count.
    • Implement substrate effects directly in ADR-033 §FULL EFFECTS instead of splittingrejected: ADR-033 §FULL EFFECTS was a self-contained PR-sized scope: tier_transition module + tests + CLI surface + watcher + holder + handler. Including substrate would have ballooned the scope across multiple substrate crates and pushed the ship date indefinitely. Splitting lets ADR-033 ship its value (working ore tier set + guard + tests) immediately.
    • Persist the oplog via stratumiops stratum-dbrejected: An earlier draft of this ADR assumed stratum-db for oplog persistence. The implemented `ontoref-oplog` is redb-backed and self-contained in the workspace, enforcing signature + parent-existence + monotonic-HLC checks on every append. Using stratum-db would reintroduce an external path dependency for tier-2, contradicting ADR-001's minimal-adoption posture and ADR-029's offline-first default. redb keeps the witness chain dependency-free.
    + +

    Related ADRs

    + +

    ADR-023 · ADR-024 · ADR-029 · ADR-031 · ADR-033

    diff --git a/site/site/content/adr/en/accepted/adr-036.ncl b/site/site/content/adr/en/accepted/adr-036.ncl new file mode 100644 index 0000000..d8436d5 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-036.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-036", + title = "Tier Transition Substrate Effects — Deferred Scope from ADR-033", + slug = "036", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/036", + graph = { + implements = [], + related_to = ["adr-023", "adr-024", "adr-029", "adr-031", "adr-033"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-037.md b/site/site/content/adr/en/accepted/adr-037.md new file mode 100644 index 0000000..62ed14c --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-037.md @@ -0,0 +1,107 @@ +--- +id: "adr-037" +title: "Interaction Trace — Parse-First Session Records with Structural-Only Validation and Optional Witness Binding" +slug: "037" +subtitle: "Accepted" +excerpt: "An agent or human session produces a stream of reasoning, decisions, file" +author: "ontoref" +date: "2026-05-28" +published: true +featured: false +category: "accepted" +tags: ["adr-037", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    An agent or human session produces a stream of reasoning, decisions, file +touches, and outcomes. Today that stream survives only as prose: the +transcript, or at best a `.coder/` markdown entry whose substance lives in a +free-text `content` field (coder.ncl::Record). Extracting "what decisions did +we make across the last ten sessions", "which sessions touched the validator +layer", or "which decisions are ADR candidates" means mining prose by hand — +exactly the cost the rest of the protocol was built to eliminate. Every other +project-defining artefact in ontoref is queryable NCL; the record of what an +agent actually DID in a session is not.

    +

    The opening framing of this decision was stronger than what was adopted: make +the agent emit its interaction in a *typed and validated* language — a contract +over the reasoning itself. That framing was consciously narrowed. The goal is +not to validate reasoning (undecidable, and an invitation to schema theatre +where agents fill fields to satisfy a contract rather than to record signal); +the goal is for the output to be *parseable and filterable* so a review need +not escarbar through prose. The distinction is load-bearing and drives every +constraint below.

    +

    Three adjacent decisions touch this surface but none owns it:

    +

    - ADR-024 (operations layer — agent action boundary) governs the *act*: the + operation an agent invokes and the witness it emits. It says nothing about + the agent's narration of the act. + - ADR-026 (validation architecture — three planes) defines structural vs + contextual validators. It is the plane this decision lives in, but it does + not define an artefact for agent interaction. + - ADR-031 (on+re constitutive duality) establishes the witness as the seam + where reflection-axis acts bind to ontology-axis substance. This decision + asks whether the agent's *interaction record* sits on that seam — and the + answer is: optionally.

    +

    The user's reframe added a second property: the record need not relate to the +witness at all. Absent a witness it still serves a review without deep digging. +That makes the witness binding optional, which in turn makes the layer usable +at every tier with different uses, rather than gated behind operations (tier-2).

    + +

    Decision

    + +

    Introduce the InteractionRecord — a parse-first trace of an agent/human +session segment, stored as JSONL (one record per line), shipped as the +reflection schema `reflection/schemas/interaction.ncl` (migration 0028).

    +

    DESIGN PROPERTIES (each is a constraint below):

    +

    1. Parse-first, not prose-analytic. Every dimension a review filters on is + an enum or a typed array (events[], decisions[], actions[], insights[], + files_touched[], tensions_engaged[], outcome). Free prose is confined to + three fields — `summary`, `Decision.rationale`, `Event.note` — and those + fields are NEVER a filtering axis. `jq` and `nu where` answer review + questions without reading the prose.

    +

    2. Structural-only validation. The validator (ADR-026 structural plane) + checks shape: required fields present, enum members in their allowed set, + arrays shaped correctly. It MUST NOT inspect the truthfulness of the free + prose. Asking a validator to certify that a rationale is honest or a + summary is faithful is a Hard biconditional on a question core.ncl names + as a Spiral tension — an ondaod forbidden pattern. Validation of prose + content is therefore explicitly out of scope, permanently.

    +

    3. Optional witness binding — the layer is tier-orthogonal. The `witness` + field (op_id, state_root, signature) is optional. Absent → tier-0 review + aid (no substrate required). Present → tier-1/2, where op_ref on events + and actions links the record to witnessed substrate acts; the record + becomes the reflection-side companion of those acts. This is the ADR-031 + seam, made optional so the layer is not gated behind operations adoption.

    +

    4. Vocabulary reuse, not forking. The schema reuses coder.ncl's canonical + enums (ActorKind, Kind, Domain, optional Category) via `import "coder.ncl"` + rather than redeclaring parallel vocabularies that would drift. The + runtime validator parses enum members FROM the schema files at runtime, so + the allowlist can never diverge from the contract.

    +

    5. Two emission paths, both supported. `ore interaction record` validates a + record structurally before appending (the forced path; malformed records + are rejected). Direct JSONL append by the agent is also permitted (the + free path), audited post-hoc by `ore interaction validate` (pre-commit / + CI gate). The `--no-validate` flag bridges the two on the same command.

    +

    6. Decisions materialise the adr? criteria as data. `Decision` carries + `alternatives_rejected`, `reversible`, `adr_candidate`, and + `tensions_engaged` (core.ncl node ids). `ore interaction decisions + --adr-candidates` surfaces ADR candidates across sessions in one query, + feeding the ADR lifecycle from observed decisions rather than recollection.

    +

    The layer is opt-in per ADR-029: a project that never emits interaction JSONL +remains valid and pays zero adoption cost.

    + +

    Constraints

    + +
    • Soft Validation of InteractionRecord MUST remain structural (shape, enum membership, array shape) and MUST NOT inspect the truthfulness or quality of the free-prose fields (`summary`, `Decision.rationale`, `Event.note`). Adding a Hard check that gates on prose content is forbidden — it is a biconditional on a Spiral tension (reality-vs-intent) per ondaod.
    • Hard The `witness` field on InteractionRecord MUST remain optional. Making it required gates the layer behind tier-2 (operations) adoption and discards the tier-0 review-aid use. The optional binding is what makes the layer tier-orthogonal per ADR-029.
    • Hard The InteractionRecord schema MUST reuse coder.ncl's canonical vocabularies (ActorKind, Kind, Domain, Category) via `import "coder.ncl"` rather than redeclaring parallel enums. The runtime validator MUST derive enum membership from the schema files, not hardcode it.
    • Hard The interaction schema (reflection/schemas/interaction.ncl) MUST be shipped to consumer data dirs as part of the reflection/ tree by install.nu, so adopting projects can resolve it. Migration 0028 is its propagation mechanism.
    • Soft Free prose in InteractionRecord MUST stay confined to `summary`, `Decision.rationale`, and `Event.note`. New filterable facets MUST be added as enums or typed arrays, never as free-text fields that a review would have to parse. The schema marks `summary` as the only free-prose top-level field.
    + +

    Alternatives considered

    + +
    • Typed AND validated contract over the agent's reasoning (the opening framing)rejected: Validating reasoning is undecidable and invites schema theatre: agents fill fields to satisfy the contract, not to record signal — the exact H7 dysfunction ADR-024 names. A Hard contract over prose truthfulness is also a biconditional on a Spiral question (reality-vs-intent), an ondaod forbidden pattern. The adopted design keeps the typing for parseability and drops the validation of content.
    • Extend coder.ncl::Record instead of a new schemarejected: Record's substance is a free-text `content` field — it is the prose artefact this layer is meant to complement, not the parse-first one. Bolting events/decisions/actions arrays onto Record would overload one type with two opposed purposes (prose memory vs structured trace) and muddy `coder triage`/`coder record` semantics. A sibling schema that REUSES Record's enums keeps both coherent and distinct.
    • Require the witness — bind every interaction record to the substraterejected: Gates the layer behind operations adoption (tier-2) and discards the user's explicit requirement that the record serve a review even with no witness present. Optional binding delivers the tier-0 review-aid use AND the tier-2 witnessed-companion use from one schema; mandatory binding delivers only the latter.
    • Store instances as per-record NCL files validated by nickel-exportrejected: NCL enums are tags ('AgentClaude), JSONL stores them as strings — applying the NCL contract to imported JSON fails on every enum field, and per-file NCL is the wrong shape for streaming filters at scale. JSONL + a structural Nushell validator (that reads enum members from the NCL schema) is the correct split: NCL is the contract, JSONL is the queryable instance store.
    • Validate the free-prose fields with heuristics (length, keyword presence, sentiment)rejected: Any check that gates on prose content is a proxy for truthfulness that the layer explicitly disclaims. Heuristic prose checks produce false confidence (a record passes because it is verbose, not because it is honest) and re-open the Spiral collapse this ADR closes. Prose quality stays a human review concern.
    + +

    Anti-patterns

    + +
    • Validating the Content of Free Reflection — A contributor adds a check that gates on the content of `summary`, `Decision.rationale`, or `Event.note` — length thresholds, required keywords, sentiment, or a claim that the rationale 'must be honest'. This is a biconditional on the reality-vs-intent Spiral and produces false confidence: a record passes because it is verbose, not because it is truthful.
    • New Filterable Facet Added as Free Text — A new dimension a review wants to filter on (e.g. 'severity', 'subsystem') is added as a free-text field instead of an enum or typed array. Reviews must then string-match prose, re-introducing the prose-mining cost the layer exists to eliminate.
    • Parallel Enum Vocabulary for Sessions — InteractionRecord redeclares its own ActorKind/Kind/Domain enums instead of importing coder.ncl, or the runtime validator hardcodes the allowlist. The two vocabularies drift the first time one is extended, and the structured trace silently disagrees with the prose Record about what an actor or kind is.
    + +

    Related ADRs

    + +

    ADR-024 · ADR-025 · ADR-026 · ADR-029 · ADR-031 · ADR-032

    diff --git a/site/site/content/adr/en/accepted/adr-037.ncl b/site/site/content/adr/en/accepted/adr-037.ncl new file mode 100644 index 0000000..732eca5 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-037.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-037", + title = "Interaction Trace — Parse-First Session Records with Structural-Only Validation and Optional Witness Binding", + slug = "037", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/037", + graph = { + implements = [], + related_to = ["adr-024", "adr-025", "adr-026", "adr-029", "adr-031", "adr-032"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-038.md b/site/site/content/adr/en/accepted/adr-038.md new file mode 100644 index 0000000..94bf048 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-038.md @@ -0,0 +1,103 @@ +--- +id: "adr-038" +title: "OCI Distribution and Installer — Multi-Arch Runnable Image plus curl|sh Bundles, Modeled in the Workflow Layer" +slug: "038" +subtitle: "Accepted" +excerpt: "Until now ontoref could only be installed from a source checkout: `just" +author: "ontoref" +date: "2026-05-29" +published: true +featured: false +category: "accepted" +tags: ["adr-038", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    Until now ontoref could only be installed from a source checkout: `just +install-daemon` runs `cargo build --release` then `install/install.nu`, which +lays out the binary, the bootstrapper, the CLI wrapper (with ONTOREF_ROOT +baked), and the data layer (reflection/, ontology/, domains/, templates/) across +the platform bin/data/config dirs. There was no way to install on a machine that +does not have the source, and no way to run the daemon as a container.

    +

    Two install paths are wanted, sharing ONE artifact origin in the OCI registry +(`reg.librecloud.online`, already declared in manifest.ncl registry_provides):

    +

    1. A runnable multi-arch image — `docker run …/ontoref/ontoref-daemon:VERSION` + — for deploying the daemon as a service/cache. + 2. `curl … | sh` — an installer that detects OS/arch, pulls the matching OCI + bundle (binary + data layer + wrappers), extracts it, and installs into a + chosen prefix, reproducing install.nu's layout.

    +

    The mechanism is NOT a pile of standalone scripts. The workflow layer (the +NCL-first CI/build/distribution model) already had the exact seam: `build_kind` +carried 'Container, `distribution_kind` carried 'ContainerRegistry | 'Package | +'Artifact, and the `distributions` catalog was empty with no generator. The +infrastructure was also already declared: `oras`, `cosign`, `sops`, `age` are +Hard dependencies in manifest.ncl, and ADR-017 models the registry credential +vault. So the coherent move was to populate the workflow layer's distribution +catalog and add the missing distribution generator, not to invent a parallel +packaging path.

    +

    The load-bearing risk is the "Protocol, Not Runtime" axiom: shipping a runnable +daemon image could be read as making ontoref a runtime dependency. The framing +that resolves it is ADR-029 (tier coexistence): the daemon is a cache, not a +hard dependency. The installer therefore installs a CLI + data layer that work +WITHOUT the daemon; the image is an optional accelerator for those who want the +service.

    + +

    Decision

    + +

    Distribute ontoref through the OCI registry along two paths from one build, +modeled entirely in the workflow layer (catalog in reflection/defaults/workflow.ncl, +a `release` layer triggered OnTag in ontology/workflow.ncl, and a distribution +generator in reflection/modules/workflow.nu). Migration 0029 propagates the +schema/catalog additions to consumers.

    +

    DESIGN PROPERTIES (each is a constraint below):

    +

    1. Modeled in the workflow layer, not hand-maintained scripts. The 'Bundle + build kind, the build/distribution catalog entries, and the generator that + emits .woodpecker/release.yml + install/install.sh + justfiles/release.just + all live in the NCL workflow model. Distribution is therefore self-describing + and queryable via `ore workflow`, like every other build/CI artifact.

    +

    2. Build-once; the image is assembled FROM the binary, never recompiled. The + musl binary is cross-compiled once per arch (`cross`). Both the install + bundle AND the runnable image consume that same binary. The image is + assembled with buildah from the cross output + the data layer — no + `docker buildx`, no QEMU, no docker-in-docker on the runner.

    +

    3. The daemon image is an OPTIONAL accelerator (ADR-029). The curl|sh installer + installs a CLI + data layer that operate without the daemon. The runnable + image serves container deployments only. Adopting ontoref never requires + running the daemon — the "Protocol, Not Runtime" axiom is preserved.

    +

    4. The bundle reproduces install.nu's layout. assemble-bundle.nu packages the + binary, bootstrapper, CLI wrapper, a target-arch nickel, the data layer, and + a config skeleton; install.sh places them exactly as install.nu does, + including the baked ONTOREF_ROOT in the wrapper. The bundle's config skeleton + is taken ONLY from install/resources/ — never from .ontoref/config.ncl.

    +

    5. curl|sh needs nothing but POSIX tools. Bundles are .tar.gz (gzip is + universal; zstd is not assumed). install.sh prefers `oras` and falls back to + plain `curl` against the OCI Distribution API; it verifies the .sha256 + sidecar and, if present, a cosign signature. The bundle namespace + (ontoref/dist) is published for anonymous pull, so no credentials are needed. + The installer itself is served at a plain HTTPS URL — never as an oras + artifact, which a bare `curl | sh` could not fetch.

    +

    6. A publish gate. The `smoke-bundle` validation extracts the freshly-assembled + native bundle and runs the daemon binary before any distribution step + publishes. A broken artifact fails the pipeline rather than reaching users.

    +

    7. Supply chain. The image is cosign-signed and the SBOM cosign-attested. Since + the self-hosted Woodpecker has no Fulcio, signing is key-based; the cosign + key and the registry RW credential come from the src-vault (ADR-017).

    +

    Platform scope is Linux amd64 + arm64 (static musl). macOS has no published +build — install.sh detects Darwin and prints source-build / container guidance.

    + +

    Constraints

    + +
    • Hard OCI distribution MUST be declared in the workflow layer — build/distribution catalog entries in reflection/defaults/workflow.ncl and the generator in reflection/modules/workflow.nu — and the published artifacts (.woodpecker/release.yml, install/install.sh, justfiles/release.just) MUST be generated from it, never hand-maintained. The `distributions` catalog MUST be non-empty.
    • Hard The installer MUST install a CLI + data layer that operate WITHOUT the daemon. The runnable daemon image MUST remain an optional accelerator. Making the daemon a required runtime for adoption is forbidden — it would violate the Protocol-Not-Runtime axiom (ADR-029: the daemon is a cache, not a hard dependency).
    • Hard The bundle and installer MUST reproduce install.nu's layout: the binary, bootstrapper, CLI wrapper with a baked ONTOREF_ROOT, a bundled nickel, the data layer, and a config skeleton, placed in the same bin/data/config locations. Divergence silently breaks the installed CLI.
    • Hard The published runnable image MUST be assembled from the already cross-compiled musl binary (buildah copy), NOT recompiled in CI and NOT built with docker buildx/QEMU. The bundle and the image MUST consume the same per-arch binary.
    • Hard The bundle's config skeleton MUST be taken only from install/resources/ — never from the live .ontoref/config.ncl. A corrupt or environment-specific project config must never leak into a published bundle.
    • Soft The bundle namespace (ontoref/dist) SHOULD be published for anonymous pull so the curl|sh installer needs no registry credentials. The installer script itself MUST be served at a plain HTTPS URL, not as an oras artifact.
    + +

    Alternatives considered

    + +
    • Plain GitHub/Gitea-release tarballs, no OCIrejected: ontoref already standardizes on OCI: oras/cosign/sops are Hard deps and the registry topology is declared in manifest.ncl (ADR-017). Plain tarballs would need a separate hosting and integrity story and would not reuse the existing registry credentials or signing. The installer keeps a curl fallback for hosts without oras, but the artifact origin stays OCI.
    • Extract the binary + data from the runnable image's layersrejected: Couples the installer to the image's internal layer layout — a fragile contract that breaks whenever the image build changes. Dedicated per-arch bundle artifacts are arch-addressable (:VERSION-linux-amd64), robust to image changes, and let the bundle carry exactly the install layout without the image's runtime concerns.
    • docker buildx multi-arch (recompile or QEMU emulation)rejected: Duplicates the cross build and requires QEMU/binfmt and docker-in-docker on the runner. Since the musl binaries are already cross-compiled, assembling the image from them with buildah is single-origin, faster, and daemonless on the runner. buildx would rebuild what cross already produced.
    • Standalone install.sh + Dockerfile, not modeled in the workflow layerrejected: Distribution would not be self-describing or queryable and would diverge from the PAP — every other build/CI artifact is generated from the NCL workflow model. The DistributionSpec/'Container kinds exist precisely so distribution is declared, not scripted. Hand-maintained scripts drift from the model.
    • Bundle nushell into the artifact toorejected: Nushell is large and is the CLI's runtime; bundling it bloats every download. nickel (small, static musl, needed by the bootstrapper) IS bundled; nushell is detected at install time and the user is given install guidance. The daemon image needs neither — only the daemon binary + nickel + data.
    + +

    Anti-patterns

    + +
    • Treating the Daemon Image as a Required Runtime — A change makes the CLI or adoption depend on a running daemon — e.g. the installer starts/expects the daemon, or commands fail without it. This turns the optional accelerator into a runtime dependency and breaks Protocol-Not-Runtime.
    • Editing the Generated Distribution Artifacts Directly — A contributor edits .woodpecker/release.yml, install/install.sh, or justfiles/release.just by hand instead of changing the workflow catalog and regenerating. The artifacts drift from the NCL model that is supposed to be their source of truth.
    • Recompiling to Build the Multi-Arch Image — The image is built by recompiling per arch (docker buildx + QEMU, or cargo inside the Dockerfile) instead of assembling from the already cross-compiled binary. This duplicates the build, reintroduces emulation/docker-in-docker, and can make the image binary differ from the bundle binary.
    • Shipping the Live Project Config in the Bundle — The assembler copies .ontoref/config.ncl (environment-specific, possibly corrupt) into the bundle instead of the install/resources skeleton, leaking local state into a published artifact.
    + +

    Related ADRs

    + +

    ADR-001 · ADR-010 · ADR-017 · ADR-029 · ADR-032 · ADR-035

    diff --git a/site/site/content/adr/en/accepted/adr-038.ncl b/site/site/content/adr/en/accepted/adr-038.ncl new file mode 100644 index 0000000..a0bffa1 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-038.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-038", + title = "OCI Distribution and Installer — Multi-Arch Runnable Image plus curl|sh Bundles, Modeled in the Workflow Layer", + slug = "038", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/038", + graph = { + implements = [], + related_to = ["adr-001", "adr-010", "adr-017", "adr-029", "adr-032", "adr-035"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-039.md b/site/site/content/adr/en/accepted/adr-039.md new file mode 100644 index 0000000..2c11b18 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-039.md @@ -0,0 +1,113 @@ +--- +id: "adr-039" +title: "Typed Recipe Annotations and the Transversal +/- Mutation Verb — Self-Describing Justfiles, Queryable Like the API Catalog" +slug: "039" +subtitle: "Accepted" +excerpt: "Projects accumulate many just recipes (and loose scripts). ontoref already" +author: "ontoref" +date: "2026-05-29" +published: true +featured: false +category: "accepted" +tags: ["adr-039", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    Projects accumulate many just recipes (and loose scripts). ontoref already +INVENTORIES recipes — describe.nu::scan-just-recipes runs `just --list` and +categorizes each recipe by a name-prefix heuristic (categorize-recipe), and +`ore describe tools` lists them. But the inventory is shallow: it knows a +recipe's name, its trailing `#` comment, and a guessed bucket. It cannot answer +"which is the canonical way to run all checks", "show me the deploy tasks", or +"what does build-standalone do and why" — because the recipe carries no declared +INTENT and no real categorization, only a prefix guess.

    +

    Two adjacent layers already exist and must not be duplicated:

    +

    - Recipes (justfiles) are the MECHANISM layer — the concrete command. Single + source of truth for HOW. + - Modes (reflection/modes/*.ncl) are the PROCEDURE layer — multi-step gated + DAGs with actors/steps/depends_on, executed by `ore run`.

    +

    What is missing is not a third store ("devtask" is a target-side abstraction, +not ontoref vocabulary). What is missing is for a recipe to SELF-DESCRIBE — to +declare its intent and categorization in place — exactly as Rust handlers do +with #[onto_api(method, path, description, tags)]: the annotation sits next to +the code, a proc-macro parses it, and inventory aggregates a zero-cost catalog +that `ore describe api` / GET /api/catalog / MCP all read. The same shape is +wanted for recipes: an annotation next to the recipe, parsed by ontoref, that +makes justfiles queryable.

    +

    The qa routing fix in the same session (ore qa show/list/search/add) added the +seeding primitive — a tag-categorized, nickel-validated write path. The question +this ADR settles is the GRAMMAR and the BOUNDARIES of seeding/enriching the tool +surface: how an annotation is written, how it is mutated, and how a project that +already has its own justfiles adopts compliance without losing its recipes.

    +

    The load-bearing tension is the named Spiral `formalization-vs-adoption`: richer +formalization (typed annotations on every recipe) buys ecosystem visibility but +raises adoption cost. The tension's own resolution in core.ncl is "schemas are +optional layers, not mandatory gates" — which constrains this design directly.

    + +

    Decision

    + +

    Make justfiles self-describing through a typed comment annotation, expose a +transversal `+`/`-` mutation verb to write/remove annotations and other store +entries, and ship a NON-DESTRUCTIVE compliance/migration mechanism so projects +adopt the surface while keeping their own recipes. Migration 0030 propagates the +grammar, the recommended mode taxonomy, and the new surface to consumers.

    +

    DESIGN PROPERTIES (each is a constraint below):

    +

    1. Annotation grammar — `# @onto key=val ...`. A single comment line directly + above the recipe carries typed metadata:

    +

    # Run all CI checks locally + # @onto mode=ci labels=ci,gate intent="all checks before push" + ci-full: + ...

    +

    Keys: `mode` (one token — the domain category: tools|dev|build|dev-build| + dply-build|test|docs|dist|secrets|…), `labels` (csv — free tags), `intent` + (quoted free text — the "best way to X" sentence the inventory cannot + derive). The recipe name and its plain `#` doc comment remain unchanged; + `@onto` enriches, it does not replace.

    +

    2. The recipe is the single source of truth. Annotations live IN the justfile, + above the recipe — never in a parallel devtasks store. ontoref parses them + (scan-just-recipes gains an @onto parser) and DERIVES a catalog, exactly as + #[onto_api] derives artifacts/api-catalog-*.ncl. `ore tool export` writes the + derived catalog; `ore describe tools --mode <m> --label <l>` reads it.

    +

    3. Mutation via a transversal verb. `ore + <noun> <args>` upserts and + `ore - <noun> <args>` removes, across stores:

    +

    ore + tool ci-full --mode ci --labels ci,gate --intent "all checks" + ore - tool ci-full # removes the @onto line only + ore + qa "<q>" "<a>" --tags devtool,ci # the qa add path + ore - qa <id>

    +

    `+ tool` / `- tool` edit the `@onto` comment ABOVE the named recipe in + place, validated against `just --list`; they NEVER touch the recipe body. + `+`/`-` are first-level dispatcher tokens taking `<noun> <args>`, so new + stores plug in without new top-level verbs.

    +

    4. Annotations are OPTIONAL enrichment, never a gate. An un-annotated recipe + stays valid, runnable, and still inventoried by name+category (today's + behavior). Annotation raises a recipe from guessed to declared; it is never + required to run a recipe. This is the `formalization-vs-adoption` balance + made literal — "optional layers, not mandatory gates".

    +

    5. Non-destructive compliance/migration. A `compliant` mode (and `ore tool + migrate`) scans a project's existing justfiles, proposes `@onto` annotations + (seeding `mode` from categorize-recipe + name, leaving `intent` for the + author), and — opt-in — reorganizes recipes into justfiles/<mode>.just. It + MUST preserve every project-owned recipe and its semantics: no recipe is + deleted or renamed, reorg is opt-in and reversible, and the project's own + recipes are first-class (the mechanism annotates them, it does not supplant + them with ontoref-blessed ones).

    +

    6. Drift-checkable round-trip. `ore tool audit` reports orphan annotations + (an `@onto` whose recipe is gone from `just --list`) and, at compliance + level, un-annotated recipes. Mirrors docs-drift: the annotation and the + recipe must stay in sync because they are co-located.

    +

    A multi-step task that deserves gating/steps graduates to a MODE, not an +annotation; `@onto` is for single-recipe intent+categorization. Modes and +annotated recipes are complementary, not competing.

    + +

    Constraints

    + +
    • Hard A recipe in a justfile is the single source of truth for its command. @onto metadata MUST live in a comment co-located with the recipe; there MUST NOT be a parallel store of recipe command strings. Any tool catalog is DERIVED from the annotations and MUST be regenerated, not hand-edited.
    • Hard The annotation MUST be a single comment line `# @onto key=val ...` immediately above the recipe. Recognized keys: `mode` (one token), `labels` (comma-separated), `intent` (double-quoted free text). Unknown keys MUST be ignored, not error. The recipe name and its plain `#` doc comment MUST be left intact.
    • Hard Annotation/entry mutation MUST go through `ore + <noun> <args>` (upsert) and `ore - <noun> <args>` (remove). For noun `tool`, the verb edits/removes ONLY the @onto comment above the named recipe, validated against `just --list`, and MUST NOT modify the recipe body. New stores MUST be added as nouns, not as new top-level verbs.
    • Hard An un-annotated recipe MUST remain valid, runnable, and inventoried by name+category. Annotation MUST NOT be a precondition for running or listing a recipe. Compliance is reported by audit, never enforced as a gate.
    • Hard The compliance/migration mechanism MUST NOT delete or rename any project-owned recipe. It annotates in place and may propose reorganization into justfiles/<mode>.just only as an opt-in, semantics-preserving, reversible step. A project's own recipes are first-class and MUST survive migration unchanged in behavior.
    • Soft `ore tool audit` MUST report orphan annotations (an @onto whose recipe is absent from `just --list`) and, at compliance level, un-annotated recipes. Every @onto MUST reference an existing recipe.
    • Soft The recommended `mode` taxonomy (tools/dev/build/test/ci/deploy/docs/dist/secrets/…) is advisory guidance, not an enforced enum; `labels` are free-form. Validation MUST NOT reject an unrecognized mode or label.
    + +

    Alternatives considered

    + +
    • A standalone devtasks.ncl store holding command strings + labelsrejected: Third source of truth alongside justfiles and modes; the stored command drifts from the real recipe, and it re-imports the loose-script problem. The recipe must stay the single source; the annotation co-locates metadata with it.
    • Promote every dev/deploy recipe into a full NCL moderejected: Modes are the procedure layer for multi-step gated DAGs. Forcing single-command recipes through mode authoring is heavy and collapses the mechanism/procedure distinction. Multi-step tasks DO graduate to modes; single-recipe intent belongs in an annotation.
    • Make annotations mandatory (a compliance gate that fails un-annotated recipes)rejected: Collapses the named `formalization-vs-adoption` Spiral toward the Yang pole, contradicting its own resolution ('optional layers, not mandatory gates'). Annotations must enrich an always-valid baseline; audit reports compliance, it does not block.
    • Glued verb `+tool` / per-store `tool add` instead of a transversal `+ <noun>`rejected: Per-store verbs do not project to new stores without new top-level commands, and `+tool` glued is a one-off. A transversal `+ <noun> <args>` is the uniform, extensible idiom the user asked for.
    • Destructive reorg — ontoref owns justfiles/ and rewrites them to a canonical layoutrejected: Adoption-hostile: projects have their own recipes and conventions. Compliance must be non-destructive — annotate in place, propose reorg opt-in, never delete or rename a project's recipes.
    + +

    Anti-patterns

    + +
    • A Standalone Devtasks Store Duplicating Recipe Commands — A parallel devtasks.ncl stores copied command strings + labels alongside the justfiles. It becomes a third source of truth that drifts from the real recipe and re-imports the loose-script problem.
    • Making Annotations Mandatory / Gating on Them — Compliance fails un-annotated recipes or blocks running them. This collapses the formalization-vs-adoption Spiral toward Yang, contradicting its own 'optional layers, not mandatory gates' resolution.
    • +/- Editing the Recipe Body or Destroying Project Recipes — `+ tool` rewrites the recipe command, or migration deletes/renames a project-owned recipe. Adoption-hostile; violates recipe-as-single-source and non-destructive-migration.
    • Hand-Editing the Derived Tool Catalog — Someone edits the generated tool catalog artifact directly. It is derived from the annotations and overwritten on regeneration, so the edit drifts from source.
    • Forcing Single-Command Recipes Through Mode Authoring — Every dev/deploy recipe is promoted to a full NCL mode. Modes are for multi-step gated DAGs; over-applying them collapses the mechanism/procedure distinction and burdens adoption.
    diff --git a/site/site/content/adr/en/accepted/adr-039.ncl b/site/site/content/adr/en/accepted/adr-039.ncl new file mode 100644 index 0000000..b2fbfc4 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-039.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-039", + title = "Typed Recipe Annotations and the Transversal +/- Mutation Verb — Self-Describing Justfiles, Queryable Like the API Catalog", + slug = "039", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/039", + graph = { + implements = [], + related_to = [], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-040.md b/site/site/content/adr/en/accepted/adr-040.md new file mode 100644 index 0000000..ff2c317 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-040.md @@ -0,0 +1,140 @@ +--- +id: "adr-040" +title: "Convention Bindings and Shared-Source Propagation — Declared Guidelines/CI/Config, Synced From a Shared Origin Through the Update Mechanism" +slug: "040" +subtitle: "Accepted" +excerpt: "A project's working conventions are NOT captured by ontoref today, even though" +author: "ontoref" +date: "2026-05-29" +published: true +featured: false +category: "accepted" +tags: ["adr-040", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    A project's working conventions are NOT captured by ontoref today, even though +they are load-bearing for every contributor and agent:

    +

    - GUIDELINES — per-language coding rules. In this ecosystem they are SHARED: + .claude/guidelines/<lang>/ is a real directory whose individual *.md files are + PER-FILE symlinks to /Users/Akasha/Tools/dev-system/languages/<lang>/guidelines/*.md + (verified: e.g. .claude/guidelines/rust/ACTIX.md → dev-system/.../rust/guidelines/ACTIX.md). + dev-system is the single origin; many projects symlink the same source. The same + origin also feeds NON-Claude consumers (just-modules/, ci/, workflow.md), so the + guidelines are project conventions of which Claude Code is only one consumer — + not Claude-specific config. A project's OWN guideline is simply a real .md file + sitting beside the symlinked ones in the same directory. + - CI — already modeled: the workflow layer (ADR-038 et al.) declares + validations/builds/distributions and generates .woodpecker/*, pre-commit, + justfiles. CI is declared; it is just not cross-referenced from a single + "what conventions does this project use" surface. + - CONFIG conventions — how a project's config is structured (config.ncl shape, + .cargo layout, env wiring). Partly surfaced by `describe config`.

    +

    ontoref already has adjacent surface: `describe guides` reads `language_guides` +from the .claude/guidelines/*/ glob, `card.ncl` is the project beacon ("what IS +this project"), and `describe config` reports the runtime config. What is missing +is (a) a DECLARATION of which guideline/convention sources a project binds and at +what version, and (b) a propagation/SYNC mechanism so a shared change — e.g. a +new Rust version bumping the guideline — reaches every project, and a project's +own refinement can flow back, without hand-managing symlinks per project.

    +

    The propagation need is the heart of this ADR. Symlinks propagate transparently +but only on one machine, are invisible to ontoref's queryable surface, break on +clone/CI/containers, and cannot be versioned or audited. The protocol already +owns the right mechanism for "a change consumers must adopt": the migration +system (ADR-010) plus the `update_ontoref` mode. Convention bindings should ride +that mechanism, not a second ad-hoc symlink convention.

    +

    Two named Spiral Tensions from `.ontoref/ontology/core.ncl` are engaged:

    +

    - formalization-vs-adoption — declaring conventions raises formalization; + forcing every project to vendor/copy them raises adoption cost. Its + resolution ("optional layers, not mandatory gates") constrains the design. + - ontology-vs-reflection — the binding declaration is Yin (what conventions + the project IS bound to); the sync/update action is Yang (what BECOMES when + the shared origin moves). Both must coexist: a declaration with no sync drifts + silently; a sync with no declaration has nothing to reconcile against.

    +

    This ADR is the meta-convention sibling of ADR-039 (which made the project's OWN +recipes self-describing). ADR-039 is about intent the project authors; ADR-040 is +about conventions the project BINDS from a shared origin and keeps in sync.

    + +

    Decision

    + +

    Make a project DECLARE its convention bindings, hold the convention CONTENT +authoritatively under `.ontoref/` (protocol-first, harness-independent), expose +both as a queryable surface, and SYNC from the shared origin through the existing +update/migration mechanism — never through hand-managed symlinks as the source of +truth.

    +

    DESIGN PROPERTIES (each is a constraint below):

    +

    1. Declaration in card.ncl. The beacon gains a `conventions` block:

    +

    conventions = { + guidelines = [ + { lang = "rust", source = "dev-system", version = "2024" }, + { lang = "nushell", source = "dev-system", version = "0.111" }, + ], + ci = { workflow_layer = "ci" }, # reference, not a copy (ADR-038) + config = { schema = ".ontoref/config.ncl", profile = "default" }, + }

    +

    card.ncl is chosen over a new catalog because conventions are part of "what + IS this project" (the beacon already answers that and is migration-versioned), + and `describe guides`/`describe config` already read this neighborhood. The + declaration NAMES sources and versions; it does NOT inline guideline content.

    +

    2. Content authoritative under .ontoref/, harness as materialized view + (protocol-first). The guideline CONTENT lives at .ontoref/conventions/<lang>/ + — the protocol surface, addressable independently of any harness (cf. + ontology-decoupled-from-project). .claude/guidelines/<lang>/ becomes a + MATERIALIZED VIEW: a symlink pointing INTO .ontoref/conventions/<lang>/, so + Claude Code keeps reading its expected path while ontoref owns the content. + The conventions survive with no .claude/ at all (CI, other agents, plain + tooling read .ontoref/ directly). Within .ontoref/conventions/<lang>/, each + file is either a symlink to the shared origin (dev-system) or a real + project-OWN file beside them — the per-file model the current layout already + uses, just relocated to the protocol root.

    +

    3. CI is referenced, never re-declared. The `ci` binding points at a workflow + layer id (ADR-038's model). ontoref does not duplicate CI definition into the + conventions surface; it cross-links so `describe conventions` can show "CI = + workflow layer 'ci'" and jump to the workflow model.

    +

    4. Shared origin, resolvable PER-FILE mode (symlink | vendored | local-own). + Inside .ontoref/conventions/<lang>/ each file resolves explicitly: `symlink` + to the shared origin (transparent dev-loop), `vendored` (a copied snapshot, + clone/CI/container-safe), or `local-own` (a real project-authored file with no + origin). The per-file granularity is what lets a project mix shared guidelines + with its own in one directory. The DECLARATION (card.ncl) plus the .ontoref/ + content are the source of truth; the .claude/ symlink is a derived view.

    +

    5. Propagation rides the update/migration mechanism, not bespoke symlink edits. + `ore conventions sync` (and the `update_ontoref` mode) reconcile each declared + binding against the shared origin: detect version drift (origin moved, e.g. + new Rust guideline), report it, and — opt-in — re-link or re-vendor to the + declared version. A shared change reaches every binding project through this + one path. Project-local refinement flows back by updating the origin and + bumping the declared version, never by editing a vendored copy in place and + losing the link to origin.

    +

    6. Bidirectional but origin-authoritative. Sync is two-directional in intent + (origin → projects for shared bumps; project → origin for refinements) but + the ORIGIN is authoritative: a project does not silently fork shared + conventions. A divergence is reported by audit; promoting a local change + means writing it to the origin and re-syncing, so all binding projects can + adopt it.

    +

    7. Optional, audit-not-gate. A project may declare zero conventions and remain + valid. `ore conventions audit` reports: bindings whose origin is unreachable, + version drift (declared != origin), and resolution-mode mismatches (declared + symlink but found vendored, or vice versa). It REPORTS; it does not block — + the formalization-vs-adoption resolution.

    +

    These are MODES/utilities, not new stores: per the user's framing, guidelines/CI/ +config bindings are implementation-framework utilities (templates, examples, +sync). `ore conventions sync|audit|describe` and the `update_ontoref` extension +are the operational surface; card.ncl carries the declaration.

    + +

    Constraints

    + +
    • Hard A project's convention bindings (guidelines per language, CI reference, config conventions) MUST be declared in .ontoref/card.ncl as a `conventions` block naming source + version. Guideline CONTENT MUST NOT be inlined; the declaration names sources, content resolves from the shared origin.
    • Hard Convention CONTENT MUST live authoritatively under .ontoref/conventions/<lang>/ (protocol-first). .claude/guidelines/<lang>/ MUST be a materialized view (a symlink into .ontoref/conventions/), never the authoritative location. The conventions MUST resolve with no .claude/ present. The card.ncl declaration plus the .ontoref/ content are the source of truth; per-file resolution mode (symlink to origin | vendored | local-own) is recorded, and a project's own guideline is a real file beside the shared ones.
    • Hard Convention propagation/sync MUST be implemented as an extension of the existing migration system (ADR-010) and the update_ontoref mode — e.g. `ore conventions sync` and an update_ontoref step — NOT as a separate symlink-specific propagation path. A shared-origin change reaching consumers MUST be expressible as / accompanied by a migration.
    • Hard The shared origin MUST be authoritative for shared conventions. A project MUST NOT silently fork a bound convention by editing a vendored copy in place. Local refinements are promoted by writing to the origin and bumping the declared version; divergence MUST be reported by audit, not auto-merged.
    • Soft Convention bindings MUST be optional: a project may declare none and remain valid. `ore conventions audit` MUST report unreachable origins, version drift, and resolution-mode mismatches, but MUST NOT block builds or commands.
    • Soft The `ci` convention binding MUST reference a workflow layer id (ADR-038 model), not re-declare CI steps in the conventions surface. `describe conventions` cross-links to the workflow model rather than duplicating it.
    + +

    Alternatives considered

    + +
    • Keep symlinks as the mechanism, add nothingrejected: Symlinks are invisible to ontoref, unversioned, and break on clone/CI/container. They cannot answer 'what version' or 'is this in sync', and a shared bump requires manual per-project symlink work. The whole point is to make bindings declared, queryable, and synced.
    • Declare conventions in a new .ontoref/catalog/conventions.nclrejected: Fragments 'what the project is bound to' away from the card.ncl beacon and duplicates the read surface that describe guides/config already provide. A catalog suits kind-extensible operation inventories (ADR-034), not the small, identity-level binding declaration. Chosen against, but the catalog remains the right home if bindings later grow rich enough to need their own kind system.
    • A bespoke convention-sync tool independent of the migration systemrejected: Duplicates ADR-010's propagation mechanism and update_ontoref, and stays outside `ore migrate`/`describe`. Convention propagation is the same event shape as protocol propagation; it must reuse that one path.
    • Vendor (copy) conventions only, drop symlinks entirelyrejected: Loses the transparent dev-loop where editing the origin is instantly reflected. Both modes have a place: symlink for local dev, vendored for clone/CI/container. The declaration records which mode applies; forcing one collapses a useful choice.
    • Inline guideline content into card.ncl / the projectrejected: Bloats the beacon, defeats sharing, and guarantees drift from the origin. Bindings must name sources+versions and resolve content from the shared origin, never inline it.
    + +

    Anti-patterns

    + +
    • Treating .claude Symlinks as the Authoritative Content — Convention content lives authoritatively under .claude/guidelines/* (harness-specific) with no card.ncl declaration and nothing under .ontoref/. The binding is invisible to ontoref, unversioned, harness-coupled, and vanishes on clone/CI/container or when there is no Claude Code.
    • A Separate Sync Path Outside the Migration System — Convention propagation is built as its own tool/script independent of the migration system and update_ontoref, so it is invisible to `ore migrate` and duplicates the protocol's propagation mechanism.
    • Editing a Vendored Convention In Place — A project edits its vendored copy of a shared guideline directly, forking the shared origin silently. Other binding projects never see the change and the origin fractures.
    • Failing Builds on Convention Drift — `ore conventions audit` (or a hook) blocks commands/builds when a binding is drifted or undeclared, turning an optional layer into a mandatory gate.
    • Re-Declaring CI in the Conventions Surface — CI steps are copied into card.ncl's conventions block instead of referencing the workflow layer, creating a second drifting CI source.
    + +

    Related ADRs

    + +

    ADR-010 · ADR-038 · ADR-039

    diff --git a/site/site/content/adr/en/accepted/adr-040.ncl b/site/site/content/adr/en/accepted/adr-040.ncl new file mode 100644 index 0000000..29d0d40 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-040.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-040", + title = "Convention Bindings and Shared-Source Propagation — Declared Guidelines/CI/Config, Synced From a Shared Origin Through the Update Mechanism", + slug = "040", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/040", + graph = { + implements = [], + related_to = ["adr-010", "adr-038", "adr-039"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-041.md b/site/site/content/adr/en/accepted/adr-041.md new file mode 100644 index 0000000..815c58a --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-041.md @@ -0,0 +1,69 @@ +--- +id: "adr-041" +title: "Richer Op Model — Retract Operations and Per-Cell Typed CRDT Merge" +slug: "041" +subtitle: "Accepted" +excerpt: "ADR-027 declares per-domain CRDT merge strategies (HlcLastWriterWins," +author: "ontoref" +date: "2026-05-30" +published: true +featured: false +category: "accepted" +tags: ["adr-041", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    ADR-027 declares per-domain CRDT merge strategies (HlcLastWriterWins, +OrSet, TextMerge, GSet) as load-bearing, and ontoref-sync ships them as +tested `CrdtMergeStrategy` impls. bl-018 wired multi-actor convergence, +but only at the commit-layer cell level via HlcLastWriterWins: the +highest-HLC assertion to a cell wins. Investigation while attempting +bl-029 found the substrate cannot express the other three strategies:

    +

    - `OpPayload` has only `EntityAssert` (and the state-neutral marker + variants). There is NO retract/remove — so OR-Set add/remove cannot + be represented at all. + - The commit layer stores opaque value bytes per cell and resolves + concurrent writes by last-writer-wins. There is no place for a cell + to declare a richer merge (set union, text merge), so OrSet/TextMerge + have nothing to bind to. + - Grow-only/append-only domains (GSet-like) are already covered by the + distinct-entity cell model and need no new machinery.

    +

    Without a richer op model the four CRDT strategies remain library code +no domain can use; convergence is correct only where last-writer-wins is +the right semantics.

    + +

    Decision

    + +

    ACCEPTED. Extend the substrate op model along two axes, both behind the +existing tier-2/`substrate` posture (tier-0/1 unaffected):

    +

    1. RETRACT OP. Add `OpPayload::Retract { attrs, entity }` — the inverse + of `EntityAssert`, removing the named (entity, attribute) cells. A + retract is a witnessed, signed, DAG-anchored op like any mutation; + it is the precondition for remove-based domains (OR-Set).

    +

    2. PER-CELL TYPED MERGE. A cell resolves its value through a CRDT type + selected by the cell's DOMAIN (derived from the attribute's declared + domain in the catalog), not by unconditional last-writer-wins. The + commit layer's replay FOLDS every op touching a cell through that + domain's `CrdtMergeStrategy` rather than taking the last applied + value. `HlcLastWriterWins` remains the DEFAULT for any cell whose + domain declares no strategy, so the current behaviour and all + existing state roots are preserved exactly.

    +

    The fold preserves determinism: CRDT merges are commutative, associative, +and idempotent, and ties resolve by the same HLC+payload order the oplog +already imposes — so the rebuilt state root is independent of arrival +order, which bl-016's replay determinism and bl-018's convergence both +require. Witnesses are unchanged: a Retract is witnessed exactly as an +EntityAssert is, keeping every mutation on the witness-as-axis-seam.

    + +

    Constraints

    + +
    • Hard A Retract operation MUST be a signed, DAG-anchored oplog entry subject to the same append-time validation (signature, parents, monotonic HLC) as EntityAssert. Deletion MUST NOT have an unwitnessed side channel.
    • Hard The per-cell merge MUST default to HlcLastWriterWins for any cell whose domain declares no CRDT strategy, so state roots computed before this ADR are reproduced exactly. The fold MUST be commutative, associative, and idempotent for every strategy.
    + +

    Alternatives considered

    + +
    • Keep cell-LWW only; never wire the richer strategiesrejected: Leaves OrSet/TextMerge as dead library code and convergence correct only for LWW domains — contradicting ADR-027, which makes per-domain CRDT load-bearing.
    • Model deletion as an EntityAssert of a tombstone sentinel valuerejected: Overloads the value space (a real value could collide with the sentinel) and still gives LWW semantics, not add-wins. A typed Retract is unambiguous and composes with the fold.
    • Per-domain merge in a layer above the commit layer (post-process the root)rejected: The state root must be the merge result; post-processing it would make the witnessed root and the merged state diverge — exactly the seam tear ADR-044 and bl-018 guard against. The merge must be the commit-layer reduce itself.
    + +

    Related ADRs

    + +

    ADR-023 · ADR-026 · ADR-027 · ADR-036

    diff --git a/site/site/content/adr/en/accepted/adr-041.ncl b/site/site/content/adr/en/accepted/adr-041.ncl new file mode 100644 index 0000000..eeb43b0 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-041.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-041", + title = "Richer Op Model — Retract Operations and Per-Cell Typed CRDT Merge", + slug = "041", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/041", + graph = { + implements = [], + related_to = ["adr-023", "adr-026", "adr-027", "adr-036"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-042.md b/site/site/content/adr/en/accepted/adr-042.md new file mode 100644 index 0000000..79c1d78 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-042.md @@ -0,0 +1,67 @@ +--- +id: "adr-042" +title: "SyncBackend Sync/Async Reconciliation — Synchronous Trait, Backend-Owned Runtime for Network Transports" +slug: "042" +subtitle: "Accepted" +excerpt: "ADR-027 defines `SyncBackend` as a SYNCHRONOUS trait (`announce`," +author: "ontoref" +date: "2026-05-30" +published: true +featured: false +category: "accepted" +tags: ["adr-042", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    ADR-027 defines `SyncBackend` as a SYNCHRONOUS trait (`announce`, +`fetch_op`, `announced_ops`, `subscribe_peer` return `Result` directly). +The default `FilesystemSync` is naturally synchronous, and bl-018's +`Substrate::ingest` drives it from synchronous code. Implementing the +first real transport, NatsSync over JetStream (bl-031), surfaced an +impedance mismatch: `platform-nats`/JetStream is async (tokio), so an +async client cannot satisfy a synchronous trait method without bridging, +and calling `block_on` from within a tokio worker thread panics. The +trait shape must be settled before NatsSync can be written.

    +

    A second, related point: the trait's verbs (`announce(op_id)` + +`fetch_op(op_id)` + `announced_ops`) are filesystem-shaped (announce an +id, fetch a body by id). A pull-consumer transport like JetStream more +naturally publishes and pulls whole op messages. The contract must stay +expressible by both without forcing one model onto the other.

    + +

    Decision

    + +

    ACCEPTED. `SyncBackend` STAYS synchronous — the synchronous trait is the +contract, and synchrony is preserved at the call site (`Substrate::ingest` +and the embedded/filesystem path stay runtime-free, honouring +protocol-not-runtime and offline-first).

    +

    Async network transports bridge INTERNALLY: a backend such as NatsSync +owns its own current-thread tokio runtime and `block_on`s its async +client inside each trait method. The contract is that synchronous +`SyncBackend` methods are invoked from a BLOCKING context — never from a +tokio worker — so the daemon drives a sync round via `spawn_blocking` (or +a dedicated sync thread). The backend's runtime is an implementation +detail invisible to the trait and to `ontoref-core`.

    +

    The verb mapping for pull-consumer transports is fixed as: `announce` +publishes the op id (and, for transports without a separate body store, +the canonical op bytes) to the project subject; `fetch_op` retrieves the +op bytes by id; `announced_ops` drains/pulls the durable consumer to list +ids seen. A backend MAY publish body-with-announcement in one message and +serve `fetch_op` from a local index of pulled messages — the trait does +not mandate a separate body channel.

    +

    Backend selection stays trigger-deferred and feature-gated: NatsSync is +compiled only under the daemon `nats` feature and is an OPTIONAL +accelerator that degrades to absent; FilesystemSync remains the +zero-dependency default.

    + +

    Constraints

    + +
    • Hard The `SyncBackend` trait MUST remain synchronous; async transports MUST bridge internally (backend-owned runtime) and MUST NOT require ontoref-core or FilesystemSync to depend on an async runtime. Sync rounds invoking a network backend MUST run on a blocking context, never a tokio worker.
    • Hard A network SyncBackend (e.g. NatsSync) MUST be feature-gated and degrade to absent when its service is unavailable; the zero-dependency FilesystemSync MUST remain the default. No tier may be forced to require a broker.
    + +

    Alternatives considered

    + +
    • Make SyncBackend async (async fn in trait / async-trait)rejected: Forces an async runtime onto ontoref-core and every substrate consumer, including offline/embedded uses. Contradicts protocol-not-runtime and ADR-029 offline-first for the sake of one network backend.
    • Add a parallel async trait and a bridge layerrejected: Doubles the contract surface and the maintenance burden before any second async backend exists. The backend-owned-runtime bridge achieves the same with no new public trait.
    • Spawn the async client on the daemon's shared runtime and block_on thererejected: block_on inside a tokio worker thread panics. The backend must own a separate runtime (or run on spawn_blocking) — a shared-runtime block_on is unsound.
    + +

    Related ADRs

    + +

    ADR-027 · ADR-014 · ADR-029 · ADR-002

    diff --git a/site/site/content/adr/en/accepted/adr-042.ncl b/site/site/content/adr/en/accepted/adr-042.ncl new file mode 100644 index 0000000..f7454ac --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-042.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-042", + title = "SyncBackend Sync/Async Reconciliation — Synchronous Trait, Backend-Owned Runtime for Network Transports", + slug = "042", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/042", + graph = { + implements = [], + related_to = ["adr-027", "adr-014", "adr-029", "adr-002"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-043.md b/site/site/content/adr/en/accepted/adr-043.md new file mode 100644 index 0000000..62ab0f0 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-043.md @@ -0,0 +1,84 @@ +--- +id: "adr-043" +title: "Positioning as a Four-Element Framework — Orthogonal Axes (Qué / Para-Quién / Cómo) Bound by a Proof Seam with Self-Applied Coherence Rules" +slug: "043" +subtitle: "Accepted" +excerpt: "ADR-035 established the positioning layer as a queryable protocol surface, but" +author: "ontoref" +date: "2026-06-02" +published: true +featured: false +category: "accepted" +tags: ["adr-043", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    ADR-035 established the positioning layer as a queryable protocol surface, but +its schema entangled the axes of positioning: `ValueProp` carried the value +difference inline (`unique_angle`), the target (`audience_ids`) and the backing +(`evidence_adrs`) all in one record, and `Campaign` was the only model of +outward action. There was no first-class home for "what is our differential +value", competitors lived only as prose strings inside `ValueProp.alternatives`, +difusión collapsed onto "campaign" (a website is difusión but not a campaign), +and there was no model of validation — the success cases where all of it +converges and becomes credible.

    +

    Working through the positioning of ontoref against Knowledge-Graph competitors +surfaced that positioning has three orthogonal concerns — the same orthogonality +ADR-009 already established for manifest self-interrogation (capabilities answer +"what / why / how" as three orthogonal axes): WHAT (what differential value), +FOR WHOM (which audience), HOW (how we diffuse). They are orthogonal much of +the time and cross only at named seams. A fourth element — VALIDATION — is not a +fourth axis but the seam (seam) that binds the other three and keeps them +credible: a success case is the convergence of a differentiator, an audience and +a difusión mechanism, signed by a real outcome.

    +

    The load-bearing risk is formalization-vs-adoption: adding four typed kinds and +a coherence audit to the marketing surface could impose ceremony before value. +The framing that resolves it is ADR-029 (tier-orthogonal, opt-in) plus +all-defaulted fields: a project that writes zero differentiators pays zero +adoption cost, and existing positioning files validate unchanged.

    + +

    Decision

    + +

    Model the positioning layer as four elements. Three orthogonal axes, each a +first-class typed kind, bound by a Proof seam:

    +

    WHAT — `Differentiator` (one file per area of differential value) + + `Competitor` (what they do / how ontoref differs). + FOR WHOM — `Audience` (gains `status`: Hypothesis until a Proof references it). + HOW — `DifusionMechanism` (kind: Web | DocsSite | McpServer | Repo | + Content | Talk | Event; persistent vs event). Difusión is the + category; `Campaign` is ONE time-boxed coordination within it. + VALIDATION — `Proof`: the seam. `converges = { differentiator_ids, audience_ids, + mechanism_ids }` — references the other three; nothing references + Proof. A Proof is the triple convergence signed by a real outcome.

    +

    The crossings are explicit: `ValueProp` is the WHAT×WHOM seam (gains +`differentiator_ids`); `Campaign` and `Proof` bind HOW and VALIDATION.

    +

    The marketing surface is governed by the same mechanism it sells. Five coherence +rules run in `ore positioning audit` (local-first, daemon-independent):

    +

    1. AnchorDrift — a differentiator/proof evidence_adr must be Accepted. + 2. UnprovenValidatedAudience — an Audience marked 'Validated needs >=1 Proof. + 3. Live value-prop — must converge four elements (differentiator + + audience + a difundiendo mechanism + a proven diff). + 4. DanglingRef — a Proof's converges ids must all resolve. + 5. CoverageGap (warn) — a differentiator with no Proof is an explicit + reminder, never a silent gap.

    +

    ADR-drift severity is gated by value-prop status: a Draft claim citing a +Proposed/Missing ADR is work-in-progress (informational, non-blocking); only +Validated/Live claims must cite Accepted ADRs — mirroring the +`_live_requires_evidence_adrs` contract. Coverage warnings never fail CI.

    + +

    Constraints

    + +
    • Hard The positioning schema MUST keep WHAT (Differentiator), FOR WHOM (Audience) and HOW (DifusionMechanism) as separate typed kinds. Cross-axis binding MUST happen only through the seam kinds (ValueProp for what×whom; Campaign and Proof). Collapsing an axis back into another kind is forbidden.
    • Hard Proof MUST reference the other three axes (converges.differentiator_ids, audience_ids, mechanism_ids each non-empty) and MUST NOT be referenced by any other kind. The make_proof contract (`_proof_converges_all`) enforces the triple convergence.
    • Hard Every Differentiator MUST cite >=1 ontology node and >=1 evidence ADR, and name >=1 competitor category it beats (`vs`). The make_differentiator contracts (`_diff_requires_anchor`, `_diff_requires_vs`) enforce this.
    • Hard `ore positioning audit` MUST run the five coherence rules over the positioning layer and gate CI by value-prop status: hard ADR-drift on Validated/Live value-props and any coherence violation fail; Draft drift and coverage gaps are informational.
    • Soft A value-prop with status 'Live MUST bind >=1 differentiator (the WHAT axis); the `_live_requires_differentiator` contract enforces the local half, and the audit enforces the rest (a difundiendo mechanism and a proven differentiator).
    + +

    Alternatives considered

    + +
    • Keep the entangled ValueProp model — value difference stays inline in unique_anglerejected: Entangling what (value), for-whom (audience) and backing in one record means any change to the differentiation story churns every claim, and competitors/differentiators are never queryable on their own. Orthogonal axes with a ValueProp seam keep each concern independently evolvable.
    • Competitors and validation as Markdown narratives, no typed kindsrejected: Markdown is not queryable and cannot be drift-checked. ADR-035's whole thesis is marketing-as-typed-protocol-surface; competitors and proofs belong as typed NCL with cross-references, not as prose the audit cannot see.
    • Validación as a fourth orthogonal axis (a fourth folder co-equal to the three)rejected: Validation is not parallel to what/for-whom/how — it is where they converge and get signed. Modeling it as a fourth axis implies a false fourth pole; modeling it as the Proof seam (references the three, referenced by none) is faithful to witness-as-axis-seam (ADR-031).
    • Fail CI on any ADR-drift regardless of value-prop statusrejected: A Draft claim citing a Proposed ADR is legitimate work-in-progress; hard-failing it forces deleting true citations or fabricating ratification. Gating severity by status keeps the hard gate on Live/Validated claims where _live_requires_evidence_adrs already places it.
    + +

    Anti-patterns

    + +
    • Collapsing an Axis Back into Another Kind — A contributor inlines the value difference back into ValueProp.unique_angle, or folds competitors into alternatives[] strings, instead of using the Differentiator/Competitor kinds. The axes re-entangle and stop being independently queryable.
    • A Proof That Does Not Converge Three Axes — A success case is written as a testimonial that names an outcome but does not reference a differentiator, an audience and a mechanism. It is no longer the seam — it is unanchored marketing the audit cannot trace to the model.
    • Asserting a Validated Audience Without a Proof — An Audience is marked status='Validated to look credible, but no Proof references it. The validation is asserted, not earned — the exact failure mode the framework exists to prevent.
    • Routing Marketing Through Untyped Prose to Skip the Audit — A claim is published on the website or docs without a corresponding typed Live value-prop, so it escapes the coherence audit and can drift freely — re-introducing the prose-that-rots failure ADR-035 closed.
    + +

    Related ADRs

    + +

    ADR-035 · ADR-031 · ADR-009 · ADR-029 · ADR-012

    diff --git a/site/site/content/adr/en/accepted/adr-043.ncl b/site/site/content/adr/en/accepted/adr-043.ncl new file mode 100644 index 0000000..16c5445 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-043.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-043", + title = "Positioning as a Four-Element Framework — Orthogonal Axes (Qué / Para-Quién / Cómo) Bound by a Proof Seam with Self-Applied Coherence Rules", + slug = "043", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/043", + graph = { + implements = [], + related_to = ["adr-035", "adr-031", "adr-009", "adr-029", "adr-012"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-044.md b/site/site/content/adr/en/accepted/adr-044.md new file mode 100644 index 0000000..ef4f80a --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-044.md @@ -0,0 +1,67 @@ +--- +id: "adr-044" +title: "Actor Key Succession — Unforgeable Old→New Rotation Records" +slug: "044" +subtitle: "Accepted" +excerpt: "ADR-023/024 make the witness the unit of provenance: every authoritative" +author: "ontoref" +date: "2026-05-30" +published: true +featured: false +category: "accepted" +tags: ["adr-044", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    ADR-023/024 make the witness the unit of provenance: every authoritative +mutation is signed by an actor's Ed25519 key, and external verifiers +check the signature against the actor's published public key +(`.ontoref/actors.ncl`). Until now actor keys were static — there was no +way to rotate a key without stranding every witness it had ever signed.

    +

    Two failure modes follow from static keys:

    +

    - A compromised or lost key has no recovery path. Abandoning it + abandons the verifiability of all prior witnesses signed under it. + - Routine hygiene (periodic re-keying) is impossible without breaking + the audit chain.

    +

    The seam at risk is `witness-as-axis-seam` (Axiom, invariant): the witness +binds the ontology axis (what IS — the signed state) to the reflection +axis (the act). A key change that orphans prior witnesses tears that seam +for every deposit made under the old key.

    + +

    Decision

    + +

    ACCEPTED. Key rotation is recorded as an unforgeable succession entry in +the oplog, modeled as `OpPayload::KeySuccession { actor_id, new_key, +old_key }`. The record MUST be signed by the OLD key: the enclosing +`OpBody.actor` equals `old_key`, and the operation signature verifies +under it. Only the holder of the current key can therefore authorize its +successor. The record is anchored in the oplog DAG (parents = current +heads, monotonic HLC) and is state-neutral — the commit, triple, and +substrate reducers assert nothing from it.

    +

    Verification chains trust backwards from the actor's current trusted key +(`actors.ncl`) through the succession records: a key is valid for an +actor if it is the current key or a verified predecessor of a valid key. +A record only extends the chain when its declared `old_key` equals its +signer AND its signature verifies — a forged record (signed by anyone but +the declared predecessor) is silently ignored. A witness signed by a +superseded key therefore still verifies: the chain proves that key was +once authoritative for the actor.

    +

    The trust-chain resolver (`ontoref_types::succession::valid_keys_for_actor`) +is a pure function over `&[Operation]` — no IO, no oplog handle — so any +verifier (daemon, CLI, remote peer) applies it offline. Emission (generate +new key, append the succession, republish the public key) is a tier-2 +substrate act, gated behind the `substrate` feature; tier-0/1 projects pay +no cost.

    + +

    Constraints

    + +
    • Hard A KeySuccession record MUST be signed by its declared old_key (OpBody.actor == old_key) and that signature MUST verify; chain resolution MUST reject any record where the signer is not the declared predecessor. Only the holder of the current key may authorize its successor.
    • Hard Verification MUST treat a witness signed by a superseded key as valid when a chain of verified succession records links that key to the actor's current key. Reversing this (invalidating prior witnesses on rotation) breaks the witness-as-axis-seam.
    + +

    Alternatives considered

    + +
    • Static keys (status quo)rejected: No recovery path; a compromised key strands every witness it signed. Rejected because it leaves the witness-as-axis-seam brittle across the inevitable key lifecycle.
    • Sign the succession with the NEW keyrejected: Anyone can mint a new key and claim to be an actor's successor. Authorization must come from the party being superseded, so the record is signed by the old key.
    • A central key-authority service that issues/revokes actor keysrejected: Reintroduces a trusted third party and a runtime dependency, contradicting ADR-001 minimal adoption and ADR-027 P2P-pure direction. The oplog-anchored, self-authorizing chain needs no authority.
    + +

    Related ADRs

    + +

    ADR-023 · ADR-024 · ADR-031 · ADR-036

    diff --git a/site/site/content/adr/en/accepted/adr-044.ncl b/site/site/content/adr/en/accepted/adr-044.ncl new file mode 100644 index 0000000..7cf0391 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-044.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-044", + title = "Actor Key Succession — Unforgeable Old→New Rotation Records", + slug = "044", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/044", + graph = { + implements = [], + related_to = ["adr-023", "adr-024", "adr-031", "adr-036"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-045.md b/site/site/content/adr/en/accepted/adr-045.md new file mode 100644 index 0000000..daf3d15 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-045.md @@ -0,0 +1,37 @@ +--- +id: "adr-045" +title: "Recursive Level Chains and Domain Co-Tenancy — Relative Levels and Multi-Chain Hosting" +slug: "045" +subtitle: "Accepted" +excerpt: "ADR-012 introduced a domain extension system; ADR-018 formalized a three-level hierarchy (base, domain, instance) with an absolute level.index in {1,2,3} and a single-valued level.parent declared in ma" +author: "ontoref" +date: "2026-06-02" +published: true +featured: false +category: "accepted" +tags: ["adr-045", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    ADR-012 introduced a domain extension system; ADR-018 formalized a three-level hierarchy (base, domain, instance) with an absolute level.index in {1,2,3} and a single-valued level.parent declared in manifest.ncl. ADR-020 plus the qa entry ontoref-three-layer-model describe three CONTENT layers (Layer 1 self-management, Layer 2 integration surface, Layer 3 caller-side wiring) and flag (bl-009) the unresolved interaction with ADR-018's levels as a '3-layer x 3-level matrix, likely orthogonal but unresolved until the ADR drafts'. A third field case forces resolution. Rustelo builds a domain over a Torred domain over ontoref-base, and Rustelo's implementation mode produces website instances; those website instances are then hosted inside a Provisioning workspace, itself the terminal instance of Provisioning's own provisioning-domain chain. Two facts break ADR-018's absolute triad. (1) DEPTH > 3: the specialization chain base -> Torred -> Rustelo -> website-mode -> website-instance is depth 4-5, but level.index is capped at 3 with fixed semantic names. (2) DOMAIN CO-TENANCY: a single hosting repo (Provisioning) participates in two distinct chains from two distinct projects at once, and one chain's terminal instance re-hosts another chain's instance (instance-as-host). ADR-018's level.parent is single-valued and manifest.ncl declares exactly one level — neither expresses depth > 3, co-residency of chains, nor instance-as-host. The genuinely hard part — cross-project reference — is already designed: ADR-018 makes level.parent a name reference (not a path), ADR-028 makes the ontology addressable independently of the project filesystem (verify by witness, not clone), and ADR-030 provides catalog discovery across projects. What is missing is the generalization of the level axis itself and an explicit declaration for hosting.

    + +

    Decision

    + +

    Generalize ADR-018's absolute triad into a relative parent chain and add a co-tenancy declaration, both opt-in and backward-compatible, via four mechanisms. (1) RELATIVE LEVEL CHAIN — level identity is the parent chain, not an absolute index. A manifest declares level.parent (a name reference, exactly as ADR-018) and an optional level.role label; level.index becomes a DERIVED depth (walk parents to a base) that is no longer capped at 3 and no longer authoritative. The names base/domain/instance are reinterpreted as ROLES in a sliding window of three relative to an observation point, not global coordinates. Existing index 1/2/3 declarations remain valid as the depth<=3 case; deeper chains simply keep walking. The terminal node of one chain may itself be the parent-root of another (instance-as-host is a well-formed shape, not an error). (2) DOMAIN CO-TENANCY — a repo may declare manifest.ncl::hosts, an array of { chain (name reference to a foreign domain artifact), mount (where in this repo the hosted chain's Layer-3 wiring lives), digest (optional witness pin) }. The foreign chain is resolved by name through catalog discovery (ADR-030) and verified by witness (ADR-028), NEVER cloned. The host carries only Layer-3 wiring for the hosted chain (ADR-020), tagged layer-3-boundary. (3) MUTATION SOVEREIGNTY UNDER CO-TENANCY — a host's runtime MUST NOT mutate the authoritative state of any hosted chain. Hosting is placement plus wiring; each chain's authoritative state mutates only through its own declared operations with its own witness (ADR-024). Co-tenancy is co-residency of wiring, not transfer of state ownership. (4) OBSERVABLE CROSS-CHAIN RESOLUTION — ore mode resolve extends to report the full traversal PATH (every level visited, not a single hop) and the CHAIN SCOPE (which chain answered) when multiple chains are co-resident; ore describe state disambiguates FSM dimensions by chain scope. No cross-chain resolution is silent.

    + +

    Constraints

    + +
    • Hard Every manifest.ncl::hosts entry must name a foreign chain resolvable by catalog discovery (name, optional digest) — resolution is by witness, never by clone
    • Hard A host must not declare operations that mutate a hosted chain's authoritative state; hosted state mutates only through the hosted chain's own declared operations
    • Hard Every level.parent chain must terminate at a base (a node with no parent); an unresolvable or cyclic parent reference is an error
    • Soft Wiring under a hosts mount should be tagged layer-3-boundary; untagged hosted wiring is a Soft warning
    • Soft When level.index is declared explicitly it must equal the derived parent-walk depth; a mismatch is a Soft warning during the transition to derived indices
    + +

    Alternatives considered

    + +
    • Keep ADR-018's absolute index and cap depth at 3rejected: The Rustelo chain is already depth 4-5. Capping forces artificial flattening that hides real specialization boundaries — the exact invisibility ADR-018 set out to eliminate. A depth cap turns a true parent/child relation into a lossy projection.
    • Allow depth > 3 but forbid co-tenancy (one chain per repo)rejected: Provisioning hosting a website inside a workspace is an actual deployment shape, not a hypothetical. Forbidding co-residency pushes the hosting wiring into untyped, untagged territory, producing exactly the Layer-3-in-Layer-1 contamination ADR-020 warns against. The relationship exists whether or not the model can name it; refusing to name it loses the boundary check.
    • Let the host own the hosted chain's state (single runtime per repo)rejected: Collapses ADR-024 internal-coherence-enforced. The hosted chain's validators and witness would be bypassed by the host runtime, making cross-domain mutation unverifiable and reintroducing the unwitnessed-mutation hazard the operations layer exists to close.
    • Model co-tenancy as ordinary Layer-3 with no manifest declarationrejected: Layer-3 wiring does live in the caller, but the hosting RELATIONSHIP (which foreign chain, pinned to which digest) is then implicit. The boundary cannot be validated, and the witness pin (ADR-028) has nowhere to live. An explicit hosts array is the minimal addition that makes the relationship checkable.
    + +

    Anti-patterns

    + +
    • Host Owns Hosted State — A hosting repo's runtime mutates the authoritative state of a chain it merely hosts, treating co-residency as ownership. The hosted chain's validators and witness are bypassed; cross-domain mutation becomes unverifiable.
    • Absolute Level Flattening — Forcing a chain deeper than three into the absolute base/domain/instance triad by collapsing real specialization steps, so distinct boundaries become invisible.
    • Implicit Hosting — A repo hosts a foreign chain's Layer-3 wiring without declaring manifest.ncl::hosts, so the hosting relationship, the foreign chain identity, and the witness pin are invisible and unverifiable.
    + +

    Related ADRs

    + +

    ADR-012 · ADR-018 · ADR-020 · ADR-024 · ADR-028 · ADR-030

    diff --git a/site/site/content/adr/en/accepted/adr-045.ncl b/site/site/content/adr/en/accepted/adr-045.ncl new file mode 100644 index 0000000..02aec86 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-045.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-045", + title = "Recursive Level Chains and Domain Co-Tenancy — Relative Levels and Multi-Chain Hosting", + slug = "045", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/045", + graph = { + implements = [], + related_to = ["adr-012", "adr-018", "adr-020", "adr-024", "adr-028", "adr-030"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-046.md b/site/site/content/adr/en/accepted/adr-046.md new file mode 100644 index 0000000..7480023 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-046.md @@ -0,0 +1,37 @@ +--- +id: "adr-046" +title: "Substrate Views — Dynamic Context Provisioning by Composing Levels, Verified Under Partial Knowledge, Tuned From the Interaction Trace" +slug: "046" +subtitle: "Accepted" +excerpt: "ADR-032 consolidated the project layout under .ontoref/ and ADR-035 added the positioning layer, so a project's knowledge now spans many heterogeneous levels (ontology, adrs, reflection, positioning, c" +author: "ontoref" +date: "2026-06-02" +published: true +featured: false +category: "accepted" +tags: ["adr-046", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    ADR-032 consolidated the project layout under .ontoref/ and ADR-035 added the positioning layer, so a project's knowledge now spans many heterogeneous levels (ontology, adrs, reflection, positioning, catalog, assets, private strategy, diffusion). Holding all of it at once is not workable for a working session — 'todo junto no es gestionable'. Three prior facts shape the resolution. (1) Cross-project work has repeatedly failed in agent sessions: the failure mode was the agent NAVIGATING into a foreign repository, losing its thread and mutating where it should not. (2) The project chose persona/discipline as a first-class axis ORTHOGONAL to the trust actor of api-catalog.ncl — the work AREA a session operates in. (3) ADR-045 generalized levels to relative parent chains and added domain co-tenancy resolved BY WITNESS, never by clone (ADR-028), reusing host_entry_type (chain, mount, digest). What was missing is a single primitive that, given a discipline, provisions only the slice of levels that area needs — intra-project AND cross-project — presents it as one coherent working context, routes every mutation back to the authoritative home of the level it belongs to, and is consumed identically by CLI sessions, agent sessions, and the daemon interfaces (UI / MCP / API / GraphQL). The governing analogy is jj over git: a content-addressed truth store (the levels — ADR-023/025) with an operation-centric working layer above it (the view + engine) that records every operation in an op log (the interaction trace — ADR-037, and the oplog — ADR-036), composes and recomposes on demand, and colocates without forcing migration (ADR-029 tier coexistence). Two further requirements were stated explicitly. First, a mounted level must provide usable knowledge AND a means of verification that, in many cases, works WITHOUT complete knowledge of the level's content — the cross-project case cannot hold the whole foreign substance. Second, the engine must learn from the generated interaction — use the loop FROM THE START and adjust as it consumes real data, not prepare a seam for later.

    + +

    Decision

    + +

    Establish the substrate view (schema .ontoref/reflection/schemas/substrate-view.ncl) as the unit of dynamic context provisioning, via five mechanisms. (1) DISCIPLINE-SCOPED COMPOSITION — a view is f(discipline, context, actor): the discipline (the work AREA; area equals discipline) selects which levels mount, the actor filters what is visible and mutable. A view composes only the slice the area needs, never the whole project, and is decomposed on exit. (2) PERSISTENCE ROUTING — each mounted level declares persistence ('Inline / 'LiftedOut / 'Remote) and the engine routes every mutation back to that level's authoritative home. 'Inline composes from local files with no daemon (ADR-029 local fallback); 'LiftedOut and 'Remote resolve by witness through catalog discovery (ADR-028/030/045), NEVER cloned. Cross-project knowledge enters as a 'Remote level — the agent never leaves its repo. (3) VERIFICATION UNDER PARTIAL KNOWLEDGE — each level declares a verify means ('Local / 'Digest / 'Validator / 'Commitment). An out-of-repo level MUST use a partial-knowledge means: a content-addressed digest (ADR-045 source.digest), a declared validator answering consistency queries without exposing full content (ADR-026), or a cryptographic commitment proving a claim against committed-but-unrevealed content (ADR-023 G5 witness). Verification by holding the complete foreign substance is forbidden. (4) LEARNED TUNING FROM THE INTERACTION TRACE — a view's declared levels and actions are its AUTHORITY ENVELOPE. Elements marked 'Learned are tuned LIVE (selection, order, salience) by the engine as it consumes the interaction-trace stream named in learns_from (ADR-037), starting from the declared prior. The active recipe is the declared prior composed with a chain of witnessed adjustments — prior plus ops, exactly as a VCS working state is base plus operations. (5) INTERFACE PARITY — the same primitive backs CLI sessions, agent sessions, and the daemon interfaces: a UI panel or an MCP tool IS a mounted substrate view, declared by the surfaces field, not bespoke code. The learning loop is constitutive and used from the start; a view that declares no learns_from is its declared prior verbatim, so the adoption floor pays nothing.

    + +

    Constraints

    + +
    • Hard The engine composes only levels and authorizes only mutations the substrate view declared; 'Learned tunes selection, order and salience strictly within the declared envelope and never adds a level or a mutation route
    • Hard Every learned adjustment to a view's active recipe is recorded as a witnessed operation in the interaction trace and is reversible; no silent mutation of the recipe in effect
    • Hard The active recipe at any time equals the declared prior composed with the witnessed adjustment chain, and must be reconstructable and replayable from the declaration plus the interaction trace; declared salience is never overwritten by learned deltas
    • Soft With no interaction data or no daemon, a view mounts its declared prior verbatim; learning is the accelerated path, not a precondition for composition
    • Hard Every level with persistence 'LiftedOut or 'Remote must declare a witness source and a partial-knowledge verify means ('Digest, 'Validator or 'Commitment); it is never verified by holding complete content
    + +

    Alternatives considered

    + +
    • Prepare the seam for learning but defer the engine (D2 deferred)rejected: The decision is to use the loop from the start and adjust in consumption of real data. Deferring freezes the reflection axis and discards the prior-to-posterior loop, collapsing ontology-vs-reflection toward static ontology — the very Yang freeze ondaod exists to prevent. The prior-as-declared-view framing makes 'from the start' safe, so there is no risk-based reason to defer.
    • Let the engine learn freely, composing levels beyond the declared enveloperejected: Collapses ontology-vs-reflection toward reflection-dominates: the engine deciding authority bypasses ADR-024 internal-coherence-enforced, because a level or mutation route the view never declared would enter without a declared, witnessed path. C1 forbids it — authority is declared, only sintonia is learned.
    • Require human acceptance of every learned adjustmentrejected: Kills 'adjust in consumption' with prohibitive friction. Safety does not require pre-acceptance: the jj op-log property — every adjustment is a witnessed, reversible operation (C2) and the recipe is reconstructable from prior plus ops (C3) — gives auditability and undo without a human in every loop.
    • Verify remote levels by cloning the foreign projectrejected: Reintroduces the cross-project-in-sessions failure and does not scale across the ecosystem. ADR-028 verify-by-witness-not-clone exists precisely to avoid this; C5 makes partial-knowledge verification mandatory for out-of-repo levels.
    • A flat view recipe with no envelope/tuning distinctionrejected: Makes C1 inexpressible: if declaration and learning are indistinguishable, the authority boundary cannot be protected and the engine can silently widen it. The eligibility ('Always/'Learned) split is the minimal addition that makes the authority line a typed, checkable fact.
    + +

    Anti-patterns

    + +
    • Engine expands authority — The engine composes a level or authorizes a mutation the substrate view never declared, on the grounds that the interaction data suggested it. Learning is allowed to widen what is authoritative, not merely to tune within a declared envelope.
    • Silent recipe drift — The engine mutates the active recipe without recording a witnessed, reversible operation, so the recipe in effect cannot be reconstructed or undone and diverges from the declared prior without a trace.
    • Engine decides truth — The learned recipe is treated as authoritative ontology and the declared prior is discarded as stale, inverting the prior-to-posterior relationship so that reflection becomes the source of truth.
    • Clone to verify — A remote or lifted-out level is verified by materializing its complete content rather than by digest, validator or commitment — reintroducing the navigational cross-project failure and forfeiting partial-knowledge verification.
    • Learning as a gate — Composition is blocked until enough interaction data exists or the daemon is running; cold start fails instead of falling back to the declared prior, turning the engine into a hard dependency at the adoption floor.
    + +

    Related ADRs

    + +

    ADR-045 · ADR-028 · ADR-024 · ADR-023 · ADR-026 · ADR-037 · ADR-036 · ADR-029 · ADR-013 · ADR-031 · ADR-035 · ADR-018

    diff --git a/site/site/content/adr/en/accepted/adr-046.ncl b/site/site/content/adr/en/accepted/adr-046.ncl new file mode 100644 index 0000000..d692ddb --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-046.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-046", + title = "Substrate Views — Dynamic Context Provisioning by Composing Levels, Verified Under Partial Knowledge, Tuned From the Interaction Trace", + slug = "046", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/046", + graph = { + implements = [], + related_to = ["adr-045", "adr-028", "adr-024", "adr-023", "adr-026", "adr-037", "adr-036", "adr-029", "adr-013", "adr-031", "adr-035", "adr-018"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-047.md b/site/site/content/adr/en/accepted/adr-047.md new file mode 100644 index 0000000..39aec7b --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-047.md @@ -0,0 +1,37 @@ +--- +id: "adr-047" +title: "Cryptographic Agility — Scheme-Tagged Witnesses with Ed25519 Default and ML-DSA-65 FIPS Plug-In" +slug: "047" +subtitle: "Accepted" +excerpt: "The protocol signs witnesses (ADR-024 acting-binding, ADR-023 G5 commitment) and, since ADR-046, substrate-view learning adjustments, with Ed25519. Ed25519 is a classical scheme whose security rests on" +author: "ontoref" +date: "2026-06-02" +published: true +featured: false +category: "accepted" +tags: ["adr-047", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    The protocol signs witnesses (ADR-024 acting-binding, ADR-023 G5 commitment) and, since ADR-046, substrate-view learning adjustments, with Ed25519. Ed25519 is a classical scheme whose security rests on the elliptic-curve discrete-log problem, which Shor's algorithm breaks on a cryptographically-relevant quantum computer. Unlike encryption, signatures have no harvest-now-decrypt-later exposure — but they have a worse one for an append-only record: the day Ed25519 falls, every historical witness in the oplog (ADR-036) becomes FORGEABLE, collapsing the non-repudiation of the entire record retroactively. The witness is precisely the project's long-lived integrity substrate, so it is exactly where algorithm longevity matters. NIST standardised post-quantum signatures in 2024 — FIPS 204 (ML-DSA, derived from CRYSTALS-Dilithium), FIPS 205 (SLH-DSA), FIPS 206 (FN-DSA). Some consumer deployments will additionally face a FIPS mandate independent of the quantum timeline. Hardcoding Ed25519 into the witness format would force a breaking, record-invalidating migration the moment either pressure arrives. ADR-044 already gives key succession (rotating an actor's KEY); what is missing is agility in the ALGORITHM itself. ML-DSA-65 (NIST level 3, ~AES-192) is the natural PQC target, but its artefacts are ~50x larger (public key 1952 B, signature 3309 B vs Ed25519 32/64) and signing is materially slower, so it cannot be a blanket replacement — it must be a plug-in for the deployments that need it.

    + +

    Decision

    + +

    Make the signature scheme a first-class, tagged dimension of every witness, via five mechanisms. (1) SCHEME TAG — every signed witness and every signed adjustment carries a `scheme` field; an unsigned digest commitment is the tier-0/1 fallback (ADR-029), a signed witness names the algorithm that produced it. (2) DISPATCH, FAIL CLOSED — verification dispatches on the declared scheme. A recognised-but-unbuilt scheme and an unknown scheme both FAIL (distinct non-zero exits); verification never silently accepts a signature whose scheme it cannot evaluate, and never coerces an unknown tag to Ed25519. (3) ED25519 DEFAULT, PQC OPT-IN — Ed25519 is the default for everyone (fast, 32/64-byte artefacts); a PQC scheme is selected per deployment or per actor, never mandated globally. (4) VARIABLE-LENGTH ENCODING — signatures and public keys are stored as variable-length hex, so a 3309-byte ML-DSA signature needs no format change; the witness record must not assume fixed artefact sizes. (5) ML-DSA-65 AS THE FIPS PLUG-IN — `ml-dsa-65` is the recognised PQC scheme slot, built behind a `pqc` Cargo feature backed by a CMVP-validated module (aws-lc-rs in FIPS mode), not an unvalidated pure-Rust implementation; a deployment may claim FIPS only when bound to the validated module. The seam ships now (scheme tag + dispatch); the ML-DSA backend is the deferred plug-in.

    + +

    Constraints

    + +
    • Hard Every signed witness and signed adjustment carries a `scheme` tag identifying the algorithm that produced the signature
    • Hard Verification dispatches on the declared scheme and fails (non-zero exit) on a recognised-but-unbuilt or unknown scheme; it never silently accepts or downgrades to Ed25519
    • Soft Ed25519 is the default scheme; a PQC scheme is selected per deployment or actor and is never mandated globally
    • Hard Signatures and public keys are stored as variable-length hex; the witness record must not assume fixed artefact sizes
    • Soft A deployment may claim FIPS only when the PQC scheme is bound to a CMVP-validated crypto module, not an unvalidated pure-Rust implementation
    + +

    Alternatives considered

    + +
    • Keep Ed25519 hardcoded with no scheme tagrejected: Forces a breaking, record-invalidating migration the moment quantum or FIPS pressure arrives, and offers no way to keep historical witnesses verifiable across the transition. The witness format would have to change under the whole oplog at once.
    • Adopt ML-DSA globally now, replacing Ed25519rejected: 50x larger artefacts, materially slower signing, and a validated-module dependency imposed on every adopter — including those with no quantum or FIPS requirement. Collapses the formalization-vs-adoption Spiral toward mandatory heavyweight crypto.
    • Separate witness formats per schemerejected: N formats means N verify paths and no uniform record. A single format with a scheme tag and variable-length artefacts expresses the same agility with one code path and one stored shape.
    • Treat an unknown or absent scheme as Ed25519 (lenient verify)rejected: A forgery surface: a signature under a scheme the verifier silently downgrades would be accepted. Verification must fail closed on anything it cannot evaluate.
    + +

    Anti-patterns

    + +
    • Hardcoded signature scheme — Signing and verifying with a compile-time-fixed algorithm and no scheme tag on the witness, so migrating the algorithm requires rewriting every historical record.
    • Silent scheme fallback — A verifier that accepts a signature whose scheme it does not recognise, or coerces an unknown/missing tag to Ed25519, creating a downgrade forgery surface.
    • PQC mandated globally — Forcing a heavyweight PQC scheme on every deployment regardless of quantum or FIPS pressure, imposing 50x artefacts and slower signing where they buy nothing.
    • Unvalidated FIPS claim — Claiming FIPS compliance while bound to an unvalidated implementation of ML-DSA — satisfying the algorithm name but not the mandate.
    + +

    Related ADRs

    + +

    ADR-044 · ADR-024 · ADR-023 · ADR-046 · ADR-036 · ADR-029

    diff --git a/site/site/content/adr/en/accepted/adr-047.ncl b/site/site/content/adr/en/accepted/adr-047.ncl new file mode 100644 index 0000000..2f1f992 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-047.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-047", + title = "Cryptographic Agility — Scheme-Tagged Witnesses with Ed25519 Default and ML-DSA-65 FIPS Plug-In", + slug = "047", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/047", + graph = { + implements = [], + related_to = ["adr-044", "adr-024", "adr-023", "adr-046", "adr-036", "adr-029"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-048.md b/site/site/content/adr/en/accepted/adr-048.md new file mode 100644 index 0000000..acda46d --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-048.md @@ -0,0 +1,37 @@ +--- +id: "adr-048" +title: "Constellation Layout — Spine at Parent, Code as Sub-Repo" +slug: "048" +subtitle: "Accepted" +excerpt: "As ontoref grew it accumulated a second git-tracked surface beyond the Rust implementation: brand assets, web site source, outreach presentations, private strategy notes, and the protocol spine itself " +author: "ontoref" +date: "2026-06-02" +published: true +featured: false +category: "accepted" +tags: ["adr-048", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    As ontoref grew it accumulated a second git-tracked surface beyond the Rust implementation: brand assets, web site source, outreach presentations, private strategy notes, and the protocol spine itself (.ontoref/). All lived inside one flat git repo. The problems: (1) publication boundaries — code has one remote/visibility, the web site another, private strategy a third; forcing all three through one repo required either over-exposing private content or fragmenting git history with submodules. (2) spine ownership — with .ontoref/ inside code/, every change to the protocol spine became a Rust-repo commit, coupling protocol evolution to the implementation release cadence. (3) cross-area navigation — an agent scoped to code/ could not reach outreach/ or vault/ without leaving the repo; paths like '../outreach' were outside git scope. Three layout options were evaluated: (A) monorepo with subdirectories (code/, outreach/, vault/ all under one git root), (B) constellation (non-versioned parent holding independent git sub-repos), (C) external paths (each sub-repo anywhere, cross-references via absolute paths). Option B was chosen because it provides clean per-area publication boundaries — each folder is its own repo with its own remote and visibility — while keeping the spine at a level that governs all sub-repos without fragmenting it across satellites.

    + +

    Decision

    + +

    Adopt a constellation layout: a non-versioned parent directory (no .git/) holds three to five independent git sub-repos — code/, outreach/, vault/, and an assets/ directory that is not itself a git repo. The .ontoref/ protocol spine lives at the parent level, not inside code/. The bash wrapper (code/ontoref) derives SPINE_DIR from SCRIPT_DIR/../.ontoref/; env.nu guards the ONTOREF_ROOT assignment with an is-empty check so the bash wrapper's value is not overridden at module load time; nickel_import_paths in config.ncl uses ../.ontoref/... (relative to ONTOREF_ROOT=code/); NCL files in .ontoref/ that import the data-dir companion (code/ontology/) use ../../code/ontology/... relative to the spine location. Cross-project Rust path dependencies that previously resolved via ../../../ now resolve via ../../../../ (one extra hop through code/). install.nu derives repo_root from $env.CURRENT_FILE rather than $env.PWD so it is robust to invocation from an arbitrary CWD.

    + +

    Constraints

    + +
    • Hard The .ontoref/ spine lives at the constellation parent, not inside code/; code/ontoref derives SPINE_DIR as SCRIPT_DIR/../.ontoref/
    • Hard env.nu assigns ONTOREF_ROOT only when it is not already set, preserving the bash wrapper's value
    • Hard nickel_import_paths entries for .ontoref/ content use ../.ontoref/... (relative to ONTOREF_ROOT=code/), not .ontoref/...
    • Hard NCL files in .ontoref/ that import the data-dir companion use ../../code/ontology/... not ../../ontology/...
    + +

    Alternatives considered

    + +
    • Monorepo subdirectories (option A): one git repo, code/ outreach/ vault/ as subdirsrejected: Cannot provide independent git remotes with different visibility per area without submodules or sparse-checkout. All areas share the same git history and commit graph, which pollutes code/ history with brand/outreach changes and prevents per-area release tagging.
    • External paths (option C): each sub-repo anywhere on the filesystem, cross-references via absolute pathsrejected: Absolute paths are machine-specific and cannot be committed to the repo. Cross-repo navigation from an agent scoped to one repo requires the user to set environment variables per machine. The spine would have to live inside one of the repos (satellite problem: others reference it via absolute path and drift when it moves).
    • Keep spine inside code/, model outreach/vault as satellite repos referencing itrejected: Forces outreach/ and vault/ to carry a reference to code/'s absolute path for spine resolution. Moving code/ to a new machine or renaming it breaks the satellites. The spine's authority extends beyond code/; its location should reflect that.
    + +

    Anti-patterns

    + +
    • Spine as satellite — Keeping .ontoref/ inside one sub-repo (code/) and referencing it from sibling repos (outreach/, vault/) via absolute or relative cross-repo paths. The spine's authority extends to all sub-repos; scoping it to one creates a satellite dependency where moving or renaming that repo breaks all siblings.
    • Unconstrained env.nu ONTOREF_ROOT override — env.nu unconditionally sets ONTOREF_ROOT from the file-derived path at module load time, silently overriding the bash wrapper's correctly computed value. In constellation layout this produces a wrong ONTOREF_ROOT (.ontoref/ instead of code/) that causes sync, install, and asset commands to target the wrong directory.
    • Three-hop cross-project Cargo path dependencies — Using ../../../<peer> in Cargo.toml path deps when the crate is inside code/crates/<crate>/. With constellation, three hops reach the constellation parent (ontoref/), not the development root (Development/); peer projects are siblings of ontoref/, so four hops are required.
    + +

    Related ADRs

    + +

    ADR-032 · ADR-001 · ADR-029 · ADR-031 · ADR-028

    diff --git a/site/site/content/adr/en/accepted/adr-048.ncl b/site/site/content/adr/en/accepted/adr-048.ncl new file mode 100644 index 0000000..628ba77 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-048.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-048", + title = "Constellation Layout — Spine at Parent, Code as Sub-Repo", + slug = "048", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/048", + graph = { + implements = [], + related_to = ["adr-032", "adr-001", "adr-029", "adr-031", "adr-028"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-049.md b/site/site/content/adr/en/accepted/adr-049.md new file mode 100644 index 0000000..f09dadf --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-049.md @@ -0,0 +1,37 @@ +--- +id: "adr-049" +title: "Criteria as a Substrate-View Describe Level — Normative and Discovered as Typed Provenance, Promotion Gated Not Automatic" +slug: "049" +subtitle: "Accepted" +excerpt: "The constellation-memory work introduces criteria: the conditions a project holds itself to — release gates, quality bars, coherence rules. They arrive from two directions. Some are NORMATIVE: declar" +author: "ontoref" +date: "2026-06-08" +published: true +featured: false +category: "accepted" +tags: ["adr-049", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    The constellation-memory work introduces criteria: the conditions a project holds itself to — release gates, quality bars, coherence rules. They arrive from two directions. Some are NORMATIVE: declared requirements an author states up front ('every Accepted ADR cites a Proof'). Others are DISCOVERED: they emerge from harvested insights (the coder harvest, plan component A) and the interaction trace (ADR-037) as patterns that turned out to matter in practice. ADR-046 established the substrate view as the unit of dynamic context provisioning — a discipline mounts only the slice of levels it needs, and a level is consumed identically by CLI, agent, and daemon interfaces via the surfaces field, with eligibility 'Always or 'Learned. ADR-018 fixed level-hierarchy and describe-projection mechanics. What is missing is where criteria sit in that structure. Two errors are available. If criteria are a flat undifferentiated list, a pattern someone NOTICED (discovered, reflection axis — what BECAME salient) is indistinguishable from a requirement someone DECLARED (normative, ontology axis — what IS required), and the first silently acquires the authority of the second. If, conversely, discovered criteria can auto-promote to normative once they recur, the reflection axis starts minting requirements without a declared, witnessed act — the engine deciding what is authoritative. The project's core identity tension, ontology-vs-reflection, is engaged directly, and formalization-vs-adoption is engaged because adding a criteria level is more schema that a minimal adopter must not be forced to carry.

    + +

    Decision

    + +

    Establish criteria as a first-class describe level mounted through the substrate-view engine (ADR-046), distinguished by a typed provenance taxonomy and a gated promotion path. Four mechanisms. (1) TYPED PROVENANCE TAXONOMY — every criterion carries kind : [| 'Normative, 'Discovered |] as a typed tag, not free text. 'Normative criteria are declared requirements (ontology axis); 'Discovered criteria are harvested from insights/interaction (reflection axis) and MUST record provenance: the source insight or interaction id they were graduated from. The tag is the legibility seam: a reader always knows whether a criterion is asserted or observed. (2) CRITERIA AS A 'Describe LEVEL — register criteria in levels.ncl with kind = 'Describe and eligibility = 'Learned, added to the relevant disciplines[].levels (coding, positioning, difusion); `describe criteria --discipline <D>` projects the discipline's slice, mirroring the existing constraints projection (ADR-018). (3) PROMOTION IS GATED, NEVER AUTOMATIC — a discovered criterion may be PROPOSED for promotion to normative, but promotion is gated behind novelty-check --threshold plus human approval (the backlog propose-status/approve path, ADR-046 witnessed-reversible); recurrence alone never promotes. The reverse — a normative criterion being demoted because reality drifted — is likewise a proposal, never silent. (4) OPT-IN ABSENCE — eligibility 'Learned and an opt-in migration mean a project that declares no criteria mounts no criteria level and pays nothing; the level is absent, not empty-and-mandatory.

    + +

    Constraints

    + +
    • Hard Every criterion carries kind : [| 'Normative, 'Discovered |] as a typed tag; a 'Discovered criterion additionally records the source insight or interaction id it was graduated from
    • Hard A 'Discovered criterion becomes 'Normative only through novelty-check plus human approval via propose-status/approve; recurrence alone never promotes, and demotion of a 'Normative criterion is likewise a proposal, never silent
    • Soft The criteria level is registered with kind 'Describe and eligibility 'Learned; a project that declares no criteria mounts no criteria level and carries no mandatory criteria schema
    • Hard criteria are projected through `describe criteria` and consumed identically across CLI/agent/UI/MCP/GraphQL via the surfaces field, with no bespoke per-surface code
    + +

    Alternatives considered

    + +
    • A flat untyped criteria listrejected: Collapses ontology-vs-reflection: a discovered pattern becomes indistinguishable from a declared requirement and silently acquires its authority. The typed normative/discovered tag is the minimal addition that keeps both poles legible.
    • Auto-promote discovered criteria to normative once they recur N timesrejected: Lets reflection mint requirements without a declared, witnessed act — reflection-dominates, the collapse ADR-046 C1 forbids. Recurrence is evidence; only a gated, approved act confers normative authority.
    • A standalone criteria store outside the substrate-view enginerejected: Reinvents discipline-scoped composition, persistence routing, partial-knowledge verification, and interface parity that ADR-046 already provides, and would need its own cross-project story. Criteria are a level; mounting them through the view engine inherits all of it.
    • A new typed criterion_kind on qa_entry_type instead of tags, requiring a schema-field migration immediatelyrejected: Premature: tagging normative/discovered on the existing qa entry type needs no required-field migration and keeps the adoption floor flat. A typed criterion_kind is a later move only if a Spiral-poled criterion field is needed (bl-009 graduation), and that is itself an ondaod-gated decision.
    • Make criteria mandatory for all adopters to maximize ecosystem visibilityrejected: Collapses formalization-vs-adoption toward mandatory gates, violating the core.ncl node invariant 'schemas are optional layers, not mandatory gates'. eligibility 'Learned plus opt-in migration keep absence free.
    + +

    Anti-patterns

    + +
    • Discovered criterion wears normative authority — A criterion harvested from insights or interaction is recorded without a provenance tag and is read as a declared requirement, silently acquiring authority it was never granted.
    • Auto-promotion by recurrence — A discovered criterion is promoted to normative automatically once it recurs enough times, letting reflection mint authoritative requirements with no declared, witnessed act.
    • Criteria mandatory at the adoption floor — The criteria level is made mandatory or empty-but-required for every adopter, putting new schema at the adoption floor instead of the top of the curve.
    + +

    Related ADRs

    + +

    ADR-046 · ADR-018 · ADR-024 · ADR-037 · ADR-029 · ADR-003 · ADR-050

    diff --git a/site/site/content/adr/en/accepted/adr-049.ncl b/site/site/content/adr/en/accepted/adr-049.ncl new file mode 100644 index 0000000..5ac98f6 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-049.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-049", + title = "Criteria as a Substrate-View Describe Level — Normative and Discovered as Typed Provenance, Promotion Gated Not Automatic", + slug = "049", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/049", + graph = { + implements = [], + related_to = ["adr-046", "adr-018", "adr-024", "adr-037", "adr-029", "adr-003", "adr-050"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-050.md b/site/site/content/adr/en/accepted/adr-050.md new file mode 100644 index 0000000..01bb1bb --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-050.md @@ -0,0 +1,37 @@ +--- +id: "adr-050" +title: "Witnessed Validators for Hard Criteria — Structural Decidability and Bounded Slices, Never Truthfulness" +slug: "050" +subtitle: "Accepted" +excerpt: "The constellation-memory work (plan 2026-06-08-constellation-memory-chronicle-criteria) introduces criteria — release/quality/coherence conditions a project holds itself to — harvested from insight" +author: "ontoref" +date: "2026-06-08" +published: true +featured: false +category: "accepted" +tags: ["adr-050", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    The constellation-memory work (plan 2026-06-08-constellation-memory-chronicle-criteria) introduces criteria — release/quality/coherence conditions a project holds itself to — harvested from insights and the interaction trace (ADR-037) or declared as requirements. ADR-026 already established validators as first-class, three-plane, SLA-bound, with a pluggable commitment backend, and ADR-024 fixed the agent-action boundary: authoritative state mutates only through declared operations carrying a witness. What is unresolved is the boundary question that decides whether criteria-backed validation stays coherent with the protocol's identity: WHICH criteria may become executable validators, and what they are allowed to read. Two failure modes are in scope. First, a criterion that asserts TRUTHFULNESS ('the positioning is honest', 'the architecture is sound') is not structurally decidable — turning it into a validator makes the reflection axis (judgment, an act) masquerade as the ontology axis (an invariant, what IS), and a passing/failing verdict on such a claim is the engine deciding truth. Second, a validator that reads unbounded state (the whole graph, every level) is an adoption cost: it is slow, it couples validation to the daemon, and it breaks the ADR-029 local-fallback guarantee that every read has a no-daemon Nushell path. The witness seam (ADR-031/023) names the exact point where an act that mutates substance is bound to a commitment; a validator over a criterion must live on the ontology side of that seam (verify structure) while being invoked by a reflection-side act (run + witness).

    + +

    Decision

    + +

    A criterion is eligible to become an executable validator ONLY if it reduces to a bounded, structurally-decidable slice query; truthfulness criteria are rejected at the eligibility gate and remain QA entries (normative/discovered, ADR-049), never validators. Establish this via four mechanisms. (1) DECIDABILITY ELIGIBILITY GATE — a ValidatorDecl is well-formed only if its predicate is a pure function of a declared, bounded slice plus the state_root commitment; a criterion whose satisfaction depends on a judgment not reducible to structure (honesty, soundness, quality-in-the-abstract) cannot declare a ValidatorDecl and is recorded as a discovered/normative criterion only. (2) BOUNDED SLICE — every ValidatorDecl declares a slice_query naming exactly the nodes/edges/fields it reads; the Rust #[onto_validator] predicate reads ONLY that slice plus state_root and is forbidden from widening its read at runtime. The slice is the validator's read envelope, the structural analogue of ADR-046's authority envelope. (3) STRUCTURE-NOT-TRUTHFULNESS SEVERITY RULE — a validator over a criterion that core.ncl does NOT name as a Spiral tension may carry severity 'Hard (structure is decidable, so pass/fail is meaningful); a validator whose criterion touches a 'Spiral-poled question MUST be 'Soft and report direction of motion, never a 'Hard biconditional — the ondaod prohibition on hard-biconditionals on Spiral questions, applied to validators. (4) NCL-RUST COHERENCE WITNESS — the ValidatorDecl (NCL, the declared structural claim) and the #[onto_validator] predicate (Rust, the executable check) must agree; a coherence check (runtime O6) fails the inventory if a declared validator has no registered predicate or a registered predicate reads beyond its declared slice, and each validator run emits a witness binding the verdict to the state_root it was computed against (ADR-024).

    + +

    Constraints

    + +
    • Hard A criterion may declare a ValidatorDecl ONLY if its predicate is a pure function of a declared bounded slice plus state_root; criteria asserting truthfulness (not reducible to structure) are rejected and remain QA entries, never validators
    • Hard Every ValidatorDecl declares a slice_query naming the nodes/edges/fields it reads; the #[onto_validator] predicate reads only that slice plus state_root and never widens its read at runtime
    • Hard A validator whose underlying criterion touches a question core.ncl names as a 'Spiral tension must be severity 'Soft and report direction of motion; 'Hard biconditional pass/fail is forbidden for Spiral-touching criteria
    • Hard The inventory fails if a declared ValidatorDecl has no registered #[onto_validator] predicate, or a registered predicate reads beyond its declared slice; every validator run emits a witness binding the verdict to the state_root it was computed against
    + +

    Alternatives considered

    + +
    • Allow any criterion to become a validator, let authors choose severity freelyrejected: Collapses ontology-vs-reflection toward reflection-dominates: a truthfulness criterion with a Hard pass/fail makes the engine adjudicate truth, and a verdict on an undecidable claim circulates as if it were an invariant. The decidability gate exists precisely to keep validators on the ontology axis.
    • Let validators read whole state for simplicity, optimize laterrejected: Breaks ADR-029 local-fallback and Protocol-not-Runtime: an unbounded validator couples validation to the daemon and to full-graph materialization, putting infrastructure at the adoption floor. Bounded slices are the same authority-envelope discipline ADR-046 established for reads.
    • Use a check_hint string per criterion (the deprecated constraint pattern)rejected: check_hint is untyped and unexecutable — it cannot enforce the slice bound, cannot be coherence-checked against a Rust predicate, and cannot emit a witness. The typed ValidatorDecl is what makes the structural claim and its execution bindable at the witness seam.
    • Make every criterion-validator Soft to be saferejected: Over-collapses toward never-blocking: a structurally-decidable criterion (no over-read, NCL↔Rust coherent, bounded) is genuinely Hard-checkable, and forcing it Soft discards a real guarantee. The severity rule draws the line by whether the criterion touches a Spiral question, not by blanket caution.
    • Adjudicate truthfulness via an LLM-judge validatorrejected: Maximally collapses ontology-vs-reflection: a model verdict on honesty/soundness is pure reflection issuing an ontology-grade pass/fail, unreproducible and unwitnessable against a state_root. Truthfulness criteria stay human-facing QA entries; no mechanical adjudication.
    + +

    Anti-patterns

    + +
    • Validator adjudicates truth — A criterion asserting truthfulness (honesty, soundness, quality-in-the-abstract) is promoted to an executable validator that issues a pass/fail verdict, making reflection's judgment circulate as an ontology-grade invariant.
    • Unbounded validator read — A validator predicate reads the whole graph or arbitrary levels rather than a declared bounded slice, coupling validation to full-graph materialization and the daemon and breaking local fallback.
    • Hard biconditional on a Spiral criterion — A validator over a criterion that touches a named Spiral tension is declared 'Hard with an A ⟺ B pass/fail, collapsing the tension by fiat instead of reporting direction of motion.
    • Declared but unwitnessed verdict — A ValidatorDecl exists with no registered predicate, or a predicate over-reads its slice, or a run produces a verdict not bound to a state_root — a verdict that cannot be reproduced or audited.
    + +

    Related ADRs

    + +

    ADR-026 · ADR-024 · ADR-023 · ADR-031 · ADR-029 · ADR-046 · ADR-037 · ADR-049

    diff --git a/site/site/content/adr/en/accepted/adr-050.ncl b/site/site/content/adr/en/accepted/adr-050.ncl new file mode 100644 index 0000000..f093e2a --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-050.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-050", + title = "Witnessed Validators for Hard Criteria — Structural Decidability and Bounded Slices, Never Truthfulness", + slug = "050", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/050", + graph = { + implements = [], + related_to = ["adr-026", "adr-024", "adr-023", "adr-031", "adr-029", "adr-046", "adr-037", "adr-049"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-051.md b/site/site/content/adr/en/accepted/adr-051.md new file mode 100644 index 0000000..44c452f --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-051.md @@ -0,0 +1,37 @@ +--- +id: "adr-051" +title: "Chronicle-to-Proof-Candidate Seam — History Proposes Positioning Proofs, the Audit Verifies, Acceptance Stays Human" +slug: "051" +subtitle: "Accepted" +excerpt: "The constellation-memory work folds a chronicle (plan component B): companion-NCL milestones, harvested insights, the interaction trace (ADR-037), ADRs-by-date, and migrations-by-number woven into a re" +author: "ontoref" +date: "2026-06-08" +published: true +featured: false +category: "accepted" +tags: ["adr-051", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    The constellation-memory work folds a chronicle (plan component B): companion-NCL milestones, harvested insights, the interaction trace (ADR-037), ADRs-by-date, and migrations-by-number woven into a retrospective timeline of what the project has actually DONE. Independently, ADR-043 established positioning as a four-element framework — orthogonal axes (Qué / Para-Quién / Cómo) bound by a Proof seam — and ADR-035 made positioning a queryable protocol surface; positioning.nu already runs five coherence rules, including that a differentiator or value-prop marked proven must converge on a Proof whose evidence_adr is Accepted. There is an obvious and dangerous adjacency: the chronicle KNOWS what shipped (accepted ADRs, completed milestones), and positioning NEEDS proofs that what it claims is real. The temptation is to let the chronicle auto-populate positioning Proofs — every accepted ADR becomes a proof of the value-prop it touches. That collapses ontology-vs-reflection: the chronicle is reflection (what BECAME, a record of history) and a positioning Proof is an ontology-side claim about what the project IS and delivers; auto-asserting proofs from history makes reflection decide the truth of outward claims. The reality-collapses-intent hazard is also present in mirror form — a milestone that shipped is evidence FOR a proof, but a milestone with no positioning home is not automatically a defect to be erased; it may simply be internal work the positioning layer never intended to claim. What is missing is a seam that lets history INFORM positioning without history ASSERTING positioning.

    + +

    Decision

    + +

    Establish a one-directional propose-only seam from the chronicle to the positioning Proof layer, verified by the existing positioning audit and accepted only by a human. Three mechanisms. (1) PROOF CANDIDATES, NOT PROOFS — `coder chronicle --as-proof-candidates` emits accepted ADRs and completed milestones as CANDIDATE proofs wired against the ADR-043 four-element structure (which differentiator/value-prop each could support); a candidate is a proposal, never an accepted Proof, and is materialized only through the positioning layer's own acceptance path, never written directly by the chronicle. (2) THE AUDIT VERIFIES, THE CHRONICLE DOES NOT — a proof candidate is checked by positioning audit's existing five rules (ADR-043): a candidate that would create a DanglingRef, an UnprovenValidatedAudience, or an AnchorDrift is surfaced as such and cannot be accepted until the inconsistency is resolved. The chronicle proposes; the audit is the verifier; structural verification is positioning's job, not the chronicle's. (3) ORPHAN MILESTONES WARN, NEVER ERASE — a `positioning roadmap` (sibling to backlog roadmap) lays mechanisms against dated milestones, and an orphan-milestone rule in positioning audit WARNS (warn-only severity) when a shipped milestone has no positioning home — it never deletes the milestone nor auto-creates a claim, because internal work the positioning layer never intended to claim is a legitimate half-state, not a defect. G reads outreach/.coder only and writes nothing into outreach (no spine there).

    + +

    Constraints

    + +
    • Hard The chronicle emits proof CANDIDATES only; a candidate is never written as an accepted Proof and is materialized solely through the positioning layer's own human acceptance path
    • Hard Proof candidates are verified by positioning audit's existing five coherence rules (ADR-043); the chronicle does not run its own proof-coherence checks
    • Soft A shipped milestone with no positioning home is surfaced as a warning by positioning audit; it is never deleted and never auto-claimed
    • Hard The chronicle-to-positioning seam is one-directional and confined to the spine; G reads outreach/.coder and writes nothing into outreach
    + +

    Alternatives considered

    + +
    • Auto-create positioning Proofs from every accepted ADR / completed milestonerejected: Collapses ontology-vs-reflection toward reflection-dominates: history would assert the truth of outward claims with no human acceptance. Proofs are ontology-side claims; only a declared act on the positioning side confers proof status.
    • Let the chronicle run its own proof-coherence checksrejected: Forks verification away from positioning audit (ADR-043), risking divergent rules and two sources of positioning truth. The audit already encodes the five coherence rules; candidates must be verified there.
    • Treat orphan milestones as defects and auto-remove or auto-claim themrejected: The reality-collapses-intent violation: a shipped milestone with no positioning home is a legitimate half-state (internal work), not a defect. Warn-only describes the gap and leaves the judgement to a human.
    • Write proof candidates directly into outreach/ where the difusion site livesrejected: outreach has no spine and is a separate repo; writing into it reintroduces the cross-project mutation hazard ADR-046 resolves. The seam reads outreach as evidence and writes only within the spine.
    + +

    Anti-patterns

    + +
    • History asserts a proof — An accepted ADR or completed milestone is auto-written as an accepted positioning Proof, so the chronicle (reflection) decides the truth of an outward claim (ontology) with no human acceptance.
    • Chronicle self-verifies claims — The chronicle runs its own proof-coherence logic instead of routing candidates through positioning audit, creating a second, divergent source of positioning truth.
    • Orphan milestone erased or force-claimed — A shipped milestone with no positioning home is treated as a defect and deleted or auto-claimed, erasing legitimate internal-work intent because it lacks an outward claim.
    • Write into a spineless repo — The seam writes proof candidates or claims into outreach/, a separate repo with no spine, reintroducing the cross-project mutation hazard.
    + +

    Related ADRs

    + +

    ADR-043 · ADR-035 · ADR-024 · ADR-046 · ADR-037 · ADR-048 · ADR-052

    diff --git a/site/site/content/adr/en/accepted/adr-051.ncl b/site/site/content/adr/en/accepted/adr-051.ncl new file mode 100644 index 0000000..05c615e --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-051.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-051", + title = "Chronicle-to-Proof-Candidate Seam — History Proposes Positioning Proofs, the Audit Verifies, Acceptance Stays Human", + slug = "051", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/051", + graph = { + implements = [], + related_to = ["adr-043", "adr-035", "adr-024", "adr-046", "adr-037", "adr-048", "adr-052"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-052.md b/site/site/content/adr/en/accepted/adr-052.md new file mode 100644 index 0000000..6cd9b87 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-052.md @@ -0,0 +1,37 @@ +--- +id: "adr-052" +title: "Memory Feedback Loop — On Mode Completion the Loop Proposes State, Backlog and Proof Deltas, Never Applies Them" +slug: "052" +subtitle: "Accepted" +excerpt: "The constellation-memory work closes a loop: harvest insights (component A), fold them with the interaction trace into a chronicle (B), graduate them into criteria (C/ADR-049), and surface proof candid" +author: "ontoref" +date: "2026-06-08" +published: true +featured: false +category: "accepted" +tags: ["adr-052", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    The constellation-memory work closes a loop: harvest insights (component A), fold them with the interaction trace into a chronicle (B), graduate them into criteria (C/ADR-049), and surface proof candidates (G/ADR-051). The question this ADR answers is what happens at the END of a working session — at `mode complete` of the coder-workflow (ADR-011 mode guards/convergence). The accumulated reflection (chronicle, criteria, trace) now contains signals that bear on authoritative ONTOLOGY state: an FSM dimension in state.ncl may have met its transition condition; a backlog item may be completable; a proof candidate may be citable. The reflexive temptation — and the thing that would make the loop feel 'intelligent' — is to have the loop APPLY these: advance the FSM dimension, close the backlog item, attach the proof. That is the precise collapse the project's identity tension forbids. The whole memory loop is reflection (what BECAME — memory, drift); state.ncl, the backlog status, and accepted proofs are ontology (what IS — authoritative state). ADR-024 fixed that authoritative state mutates ONLY through declared operations carrying a witness; ADR-046 fixed authority as declared and learning as tuning within a declared envelope (C1) recorded as witnessed, reversible operations (C2). backlog.nu already ships propose-status and approve — a propose/approve seam built for exactly this. What is missing is the rule binding the loop to that seam: the loop is allowed to NOTICE and PROPOSE, and forbidden to APPLY.

    + +

    Decision

    + +

    At `mode complete` the memory feedback loop emits PROPOSED deltas and never applies them; authoritative state changes only through the existing witnessed approve path. Four mechanisms. (1) PROPOSE-ONLY HOOK — a `mode complete` proposal hook in run.nu surfaces candidate deltas of three kinds — FSM-transition proposals against state.ncl dimensions, backlog-close proposals, and proof-citation proposals (ADR-051) — each as a proposal record via backlog propose-status, NEVER as a direct mutation of state.ncl, the backlog status, or the proof set. (2) APPROVE IS THE ONLY MUTATION PATH — a proposal becomes an authoritative state change only through approve, which is a declared operation carrying a witness (ADR-024); the loop has no write path to authoritative state that bypasses approve. (3) PARTIAL REALIZATION IS DESCRIBED, NOT COLLAPSED — when harvested reality diverges from a declared state or claim (a dimension's blocker is gone but its catalyst is unmet; a milestone shipped that no proof claims), the loop DESCRIBES the half-state and proposes the delta that would move it; it never drops the declared intent because reality differs, and it never fabricates a transition to make reality and intent agree. (4) IGNORING THE LOOP IS ALSO A FAILURE MODE — the hook always RUNS at mode complete and always SURFACES its proposals (even if none are accepted), so the opposite collapse — Yang decide-and-commit blind to drift — is structurally prevented: the proposals are presented, the human disposes.

    + +

    Constraints

    + +
    • Hard The mode-complete hook emits FSM-transition, backlog-close and proof-citation deltas only as proposals via propose-status; it has no path to mutate state.ncl, backlog status, or the proof set directly
    • Hard An FSM transition, backlog close, or proof citation proposed by the loop becomes authoritative only through approve, a declared operation carrying a witness (ADR-024); no silent or timeout-based application
    • Hard When harvested reality diverges from a declared state or claim, the loop describes the half-state and proposes the delta; it never drops declared intent because reality differs nor fabricates a transition to reconcile them
    • Soft The mode-complete hook always runs and always surfaces its proposals (even when none are accepted); proposals cannot be silently skipped
    + +

    Alternatives considered

    + +
    • Auto-apply deltas the loop is confident about (advance FSM, close backlog, attach proof)rejected: The defining collapse: reflection (the loop) mutating authoritative ontology state directly, bypassing the ADR-024 witnessed-operation boundary. Confidence is not authority; only approve confers it.
    • Drop or rewrite a declared state/claim when harvested reality contradicts itrejected: The reality-collapses-intent ondaod violation: the half-state is a location on the flow, not a defect. The loop describes partial realization and proposes; it never erases declared intent to match reality.
    • Let the loop's proposals be optional / skippable at mode completerejected: Permits the mirror collapse toward Yang freeze — the project silently ignoring real drift. The hook always runs and always surfaces proposals so the reflection signal is never invisible, even when declined.
    • Build a dedicated proposal store and approval UI outside backlogrejected: Duplicates the propose-status/approve seam backlog.nu already ships and the ADR-024 witnessed-operation path. Reusing them keeps a single audited mutation channel rather than a second one to secure.
    • Require human acceptance but apply silently if not declined within a windowrejected: A timeout-to-apply is auto-apply with a delay — it still lets reflection mutate authoritative state without a positive witnessed act. Approve must be an explicit declared operation, never a default-on-silence.
    + +

    Anti-patterns

    + +
    • Loop applies a delta — The mode-complete loop advances an FSM dimension, closes a backlog item, or attaches a proof directly, mutating authoritative ontology state without a declared, witnessed approve.
    • Intent erased to match reality — A declared state or claim is dropped or rewritten because harvested reality contradicts it, collapsing a legitimate half-state into a fabricated agreement between intent and reality.
    • Drift silently ignored — The loop's proposals can be skipped without ever being surfaced, so real divergence between reflection and ontology is invisible and the project commits blind to drift.
    • Timeout-to-apply — A proposal is applied automatically if not declined within a window, making silence a positive authorization and reintroducing unwitnessed mutation through the back door.
    + +

    Related ADRs

    + +

    ADR-024 · ADR-046 · ADR-011 · ADR-025 · ADR-037 · ADR-029 · ADR-049 · ADR-051

    diff --git a/site/site/content/adr/en/accepted/adr-052.ncl b/site/site/content/adr/en/accepted/adr-052.ncl new file mode 100644 index 0000000..60c8733 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-052.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-052", + title = "Memory Feedback Loop — On Mode Completion the Loop Proposes State, Backlog and Proof Deltas, Never Applies Them", + slug = "052", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/052", + graph = { + implements = [], + related_to = ["adr-024", "adr-046", "adr-011", "adr-025", "adr-037", "adr-029", "adr-049", "adr-051"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-053.md b/site/site/content/adr/en/accepted/adr-053.md new file mode 100644 index 0000000..eda3701 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-053.md @@ -0,0 +1,37 @@ +--- +id: "adr-053" +title: "Presentation-Spec as the Config-Driven Rendering Surface — One Declared Panel Model, Many Surfaces, No Engine Fork to Extend" +slug: "053" +subtitle: "Accepted" +excerpt: "The constellation-memory work produces several things a project will want to SEE: a chronicle/timeline (component B), a roadmap (backlog roadmap + positioning roadmap, ADR-051), a criteria view (ADR-04" +author: "ontoref" +date: "2026-06-08" +published: true +featured: false +category: "accepted" +tags: ["adr-053", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    The constellation-memory work produces several things a project will want to SEE: a chronicle/timeline (component B), a roadmap (backlog roadmap + positioning roadmap, ADR-051), a criteria view (ADR-049), history and dependency graphs. The naive path is to write a bespoke Tera page, handler, and route per view, cloning graph.html each time and hard-coding the data source and the link wiring in Rust. That path has two costs the protocol has repeatedly refused. First, it puts presentation logic in Rust, so a DOMAIN project that wants its own panel (a difusion roadmap, a custom board) must fork the engine — violating Protocol-not-Runtime and the voluntary, additive adoption model (ADR-029). Second, it makes the rendered surface drift from the declared knowledge: the panel becomes a second source of truth in Rust rather than a projection of NCL. ADR-046 established the precedent that resolves this — a level, a UI panel, and an MCP tool are all the same declared substrate-view consumed via the surfaces field, not bespoke code — and the existing graph.html already renders an NCL-derived graph generically through the NclCache pipeline (NCL → JSON → {{json|safe}} → Cytoscape). ADR-034 (Proposed) generalizes the catalog with a kind discriminator so non-Rust operation kinds are declarable. What is missing is the analogous declarative model for PRESENTATION: a spec the engine reads generically so chronicle/roadmap/criteria/graph panels render across CLI/UI/GraphQL/md without per-panel Rust, and a domain project extends by shipping its own panel NCL file.

    + +

    Decision

    + +

    Establish a presentation-spec (schema .ontoref/reflection/schemas/presentation.ncl) as the single declarative panel model the engine reads generically, so adding a panel is dropping an NCL file, never editing Rust. Four mechanisms. (1) ONE PANEL MODEL — a Panel declares { id, title, kind ([| 'Timeline, 'Gantt, 'Graph, 'Table, 'Board |]), data_source ({op, params} naming a catalog operation, ADR-024/030), surfaces (which interfaces expose it), reference_links ([{from_field, to_kind, resolver}] for click-through wiring), filters, actors }; the engine renders any panel from this declaration through the existing NclCache → JSON pipeline (graph.html precedent) with no panel-specific code. (2) GENERIC RENDER, NO PER-PANEL RUST — the render path branches on kind, not on panel identity; the day a specific panel needs bespoke Rust is the day the model has failed, so a new panel-*.ncl appears in CLI, UI, and GraphQL with zero Rust changes (the keystone verification). (3) DUAL-PATH BRIDGE, ONE LIST — panels may be declared either as dedicated .ontoref/presentation/panel-*.ncl files OR referenced from config.ncl quick_actions via a panel_ref, OR surfaced from a substrate-view surface; all three NORMALIZE to one panel list, so there is exactly one resolved set of panels regardless of declaration site. (4) DOMAIN EXTENSION WITHOUT FORK — a consumer project adds panels by shipping its own .ontoref/presentation/*.ncl; the protocol's engine renders them unchanged, and a project that ships none gets the base panels and pays nothing. The data_source MUST name a declared catalog operation, so a panel is a projection of declared knowledge, never an independent Rust query.

    + +

    Constraints

    + +
    • Hard The engine renders any panel generically from its presentation-spec declaration through the NclCache→JSON pipeline; the render path branches on kind, never on panel identity, so a new panel-*.ncl requires zero Rust changes
    • Hard Every Panel.data_source names a declared catalog operation (ADR-024/030); a panel is a projection of declared knowledge and never an independent Rust query
    • Hard Panels declared as dedicated panel-*.ncl, referenced via quick_actions.panel_ref, or surfaced from a substrate-view surface all normalize to exactly one resolved panel list; no declaration site forms a separate registry
    • Soft A consumer project adds panels solely by shipping .ontoref/presentation/*.ncl rendered by the unchanged engine; a project that ships none gets the base panels and carries no mandatory presentation schema
    + +

    Alternatives considered

    + +
    • A bespoke Tera page + handler + route per view, cloning graph.html each timerejected: Puts presentation logic in Rust, forcing a domain project to fork the engine to add a panel (violating Protocol-not-Runtime and additive adoption) and letting the rendered surface drift from declared NCL. The spec keeps panels declarative and generically rendered.
    • Let each panel's data_source be an arbitrary Rust query rather than a declared catalog operationrejected: Makes the panel an independent source of truth in Rust, drifting from the declared knowledge graph. Requiring a declared catalog operation (ADR-024/030) keeps every panel a projection of ontology.
    • Allow only dedicated panel-*.ncl files; drop the quick_actions / view-surface pathsrejected: Ignores that panels are legitimately referenced from quick_actions and substrate-view surfaces today; forbidding those paths fragments the surface. The dual-path bridge normalizes all three into one list instead of forbidding two.
    • Branch the render path on panel id with per-panel special casesrejected: Reintroduces per-panel Rust by the back door and guarantees drift as panels accumulate. Branching only on kind keeps the engine generic; a new panel needs no engine change, a new visualization needs only a new kind.
    • Make the presentation-spec mandatory for all adoptersrejected: Raises the adoption floor, collapsing formalization-vs-adoption toward mandatory gates. A project that ships no panels gets the base set and pays nothing; the spec is opt-in expressiveness.
    + +

    Anti-patterns

    + +
    • Per-panel Rust — A panel's data or click-through links are hard-coded in Rust, or the render path branches on panel identity, so adding or changing a panel requires editing the engine and the surface drifts from declared NCL.
    • Panel as an independent query — A panel's data_source is an arbitrary Rust query rather than a declared catalog operation, making the panel a second source of truth that can diverge from the knowledge graph.
    • Divergent panel registries — Panels declared via dedicated files, quick_actions, and view surfaces are resolved by separate code paths into separate lists, so the set of panels depends on where you look.
    • Presentation mandatory at the floor — The presentation-spec is required of every adopter, raising the adoption floor instead of adding opt-in expressiveness at the top of the curve.
    + +

    Related ADRs

    + +

    ADR-046 · ADR-034 · ADR-024 · ADR-030 · ADR-029 · ADR-018 · ADR-049 · ADR-051

    diff --git a/site/site/content/adr/en/accepted/adr-053.ncl b/site/site/content/adr/en/accepted/adr-053.ncl new file mode 100644 index 0000000..c0ef032 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-053.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-053", + title = "Presentation-Spec as the Config-Driven Rendering Surface — One Declared Panel Model, Many Surfaces, No Engine Fork to Extend", + slug = "053", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/053", + graph = { + implements = [], + related_to = ["adr-046", "adr-034", "adr-024", "adr-030", "adr-029", "adr-018", "adr-049", "adr-051"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-054.md b/site/site/content/adr/en/accepted/adr-054.md new file mode 100644 index 0000000..9db2708 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-054.md @@ -0,0 +1,37 @@ +--- +id: "adr-054" +title: "Desktop/Mobile Native Shell as a Consumer Surface — Tauri Host over the Daemon, Never a Protocol Component" +slug: "054" +subtitle: "Accepted" +excerpt: "The ontoref-daemon already exposes the full surface a native host would wrap: an axum HTTP /ui/ with rendered pages (graph, api_catalog, actions, login), a unified key-to-session auth model (ADR-005, A" +author: "ontoref" +date: "2026-06-09" +published: true +featured: false +category: "accepted" +tags: ["adr-054", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    The ontoref-daemon already exposes the full surface a native host would wrap: an axum HTTP /ui/ with rendered pages (graph, api_catalog, actions, login), a unified key-to-session auth model (ADR-005, ADR-021 UI lift-out into ontoref-ui), a NATS event fabric (platform-nats, feature-gated), an actor notification barrier (ADR-002), a passive drift watcher that emits exactly the events worth surfacing to a user (doc drift vs ontology node descriptions, gate-state changes), a multi-project daemon (ONTOREF_PROJECT_ROOT + remote-projects.ncl), and a Quick Actions catalog. The sibling project provisioning ships provisioning-desktop: ~3500 LOC of thin Tauri 2 host that wraps its daemon's /ui/ in a webview and adds the four things a browser cannot — system tray, native OS notifications, supervision of the local daemon child process (graceful SIGTERM), and native keyring — while the same lib compiles for iOS/Android via tauri::mobile_entry_point. The question is whether ontoref should grow an equivalent ontoref-desktop. The prerequisites already exist, so the decision is not feasibility but placement and identity: where such a crate lives, what it may depend on, and which named tension it occupies. Three options were evaluated: (A) no native host — users open the daemon UI in a browser; (B) a native host placed inside the protocol adoption surface, linking protocol internals or bundled into the daemon; (C) a from-scratch native UI (egui/iced) reimplementing the views in Rust. Option D — a thin Tauri shell as a separate consumer that wraps the daemon /ui/ and surfaces drift-watcher events as native notifications — is chosen.

    + +

    Decision

    + +

    Permit a native shell (ontoref-desktop, with the same Tauri lib targeting desktop and mobile) as a strictly separate consumer of the daemon, modeled on provisioning-desktop. It MUST consume the daemon over HTTP and NATS only — wrapping the existing /ui/ in a webview, bridging NATS/drift-watcher events to native OS notifications and a system tray, supervising the local daemon as a child process, and managing one local-spawn plus N remote-connect connection profiles against the multi-project daemon. It MUST NOT be a dependency of ontoref-ontology, ontoref-reflection, or the daemon: the dependency arrow points shell -> daemon, never the reverse, and never into the protocol-minimal adoption surface (ADR-001). It MAY depend on ontoref-ui for shared presentation. It lives either as its own constellation sub-repo (sibling to code/, per ADR-048) or as a workspace crate explicitly excluded from the installed/adopted set — in both cases outside the protocol footprint a consumer project receives. Within positioning (ADR-035/043) the desktop/mobile surface is declared as a consumer audience and the candidate occupant of the openness-vs-sustainability capture seam, while the protocol itself stays unmetered.

    + +

    Constraints

    + +
    • Hard ontoref-ontology must never list a desktop/native-shell crate as a dependency
    • Hard ontoref-reflection must never list a desktop/native-shell crate as a dependency
    • Hard ontoref-daemon must never depend on the desktop/native-shell crate; the dependency arrow points shell -> daemon only
    • Hard The native shell lives as its own constellation sub-repo or as a workspace crate explicitly excluded from the installed/adopted protocol footprint, consuming the daemon over HTTP/NATS only
    • Soft When the native surface occupies the openness-vs-sustainability seam, it does so as a declared consumer audience in positioning; the protocol surface itself is never metered
    + +

    Alternatives considered

    + +
    • No native host — users open the daemon /ui/ in a browser (option A)rejected: Forfeits the four browser-impossible capabilities, chief among them native notifications for the drift watcher and ADR-002 barrier. The daemon already produces the events; leaving them trapped behind a manually-refreshed page wastes the notification-barrier design.
    • Native host inside the protocol adoption surface — linking protocol internals or bundled into the daemon (option B)rejected: Violates Protocol-Not-Runtime and ADR-001: it would make a GUI consumer part of the surface a consumer project adopts, coupling the minimal protocol crates to a heavy platform toolchain. The dependency arrow would point the wrong way (protocol -> host).
    • From-scratch native UI in egui/iced reimplementing the views (option C)rejected: Duplicates the daemon /ui/ and ontoref-ui, creating two view surfaces that drift apart. High maintenance, no single source of truth for the graph/catalog/actions pages, and it discards the reason a thin Tauri host stays small.
    + +

    Anti-patterns

    + +
    • Native host inside the adoption surface — Placing the desktop/mobile crate beside the protocol-minimal crates, or making ontoref-ontology/-reflection/the daemon depend on it, or bundling it into the daemon. This inverts the shell -> daemon arrow and drags a platform/webview toolchain into the surface a consumer project adopts, breaking Protocol-Not-Runtime.
    • Native view-surface fork — Reimplementing the daemon /ui/ views (graph, api_catalog, actions, qa) natively in egui/iced instead of wrapping the existing HTML in a webview. Creates two view surfaces that drift apart and discards the single-source-of-truth that keeps the host thin.
    • Metering the protocol to capture value — Putting the openness-vs-sustainability capture on the protocol surface itself — gating tier-0 adoption, schemas, or describe queries behind payment — instead of on a consumer surface. Collapses the Spiral toward Yang by tolling the gift that produces the visibility funnel.
    + +

    Related ADRs

    + +

    ADR-002 · ADR-001 · ADR-021 · ADR-016 · ADR-038 · ADR-029 · ADR-035 · ADR-043 · ADR-048 · ADR-031

    diff --git a/site/site/content/adr/en/accepted/adr-054.ncl b/site/site/content/adr/en/accepted/adr-054.ncl new file mode 100644 index 0000000..399d4fc --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-054.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-054", + title = "Desktop/Mobile Native Shell as a Consumer Surface — Tauri Host over the Daemon, Never a Protocol Component", + slug = "054", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/054", + graph = { + implements = [], + related_to = ["adr-002", "adr-001", "adr-021", "adr-016", "adr-038", "adr-029", "adr-035", "adr-043", "adr-048", "adr-031"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-055.md b/site/site/content/adr/en/accepted/adr-055.md new file mode 100644 index 0000000..4f564b9 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-055.md @@ -0,0 +1,86 @@ +--- +id: "adr-055" +title: "Positioning Viability Seam — Compensation as a Second Costura Parallel to Proof, Not a Fourth Axis" +slug: "055" +subtitle: "Accepted" +excerpt: "ADR-043 modelled positioning as three orthogonal axes (WHAT / FOR WHOM / HOW)" +author: "ontoref" +date: "2026-06-09" +published: true +featured: false +category: "accepted" +tags: ["adr-055", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    ADR-043 modelled positioning as three orthogonal axes (WHAT / FOR WHOM / HOW) +bound by a single Proof seam. That framework has a structural silence: it can +say what the project claims, to whom, how it spreads, and whether the claim is +real — but it has no place to say whether any of it SUSTAINS the maker. There +was no model of compensation, direct or indirect. Decisions about pricing, +metered surfaces, visibility-as-funnel, or a commercial consumer were therefore +reasoned entirely outside the graph and improvised.

    +

    This silence was named in `.ontoref/ontology/core.ncl` as the Spiral tension +`openness-vs-sustainability`: the protocol gives value away to maximise reach +(voluntary-adoption, the personal-ontology audience, difusión as visibility) +while the project that maintains it must capture enough value to sustain itself. +The Yin pole (openness) is the engine of visibility; the Yang pole +(sustainability) is the engine of compensation; they feed one loop. With the +tension named but no positioning element to attach it to, the tension stayed +claim-only.

    +

    The load-bearing risk is identical to ADR-043's and one step sharper: a money +dimension on the marketing surface invites classifying every claim by its +business model — the Yang pole-collapse the named tension exists to prevent. The +framing that resolves it is geometric: compensation is not a facet of a claim +(an axis) but an outcome of convergence (a seam).

    + +

    Decision

    + +

    Model viability as a SECOND seam, `ViabilityPath`, parallel to `Proof` — not a +fourth orthogonal axis. Where Proof answers "is the claim real?" (validation seam +over the three axes), a ViabilityPath answers "does the claim sustain its maker?" +(sustainability seam over the same three axes). Both reference the axes via +`converges` and are referenced by nothing — self-similar to witness-as-axis-seam +(ADR-031) and to Proof (ADR-043).

    +

    Kind — `ViabilityPath` (one file per declared sustaining flow), under + `.ontoref/positioning/viability/`. + converges — { audience_ids, mechanism_ids, value_prop_ids } each non-empty: + FOR WHOM × HOW × (WHAT×WHOM). A sustaining flow is an audience + reached by a difusión mechanism monetising a value-prop, or it is + not a path. + directness — 'Direct | 'Indirect, mapping to the two poles of + openness-vs-sustainability. 'Indirect is the Yin/open pole + (visibility, funnel, reputation); 'Direct is the Yang/governance + pole (metered payment for the witness/audit surface). + flow_kind — HostedService | Support | Consulting | DualLicense | Sponsorship + | Funnel | Reputation. Descriptive vocabulary of compensation + forms, NOT a per-claim business-model decision. + status — 'Draft (a hypothesis about how positioning could sustain the + project) until a real, measured `realized_outcome` promotes it — + exactly as an Audience is 'Hypothesis until a Proof validates it.

    +

    The realized-status gate is one-directional: a 'Validated|'Live path MUST carry +`realized_outcome`; a 'Draft path may omit it. It is deliberately NOT a Hard +biconditional (`outcome ⟺ realized`) on the Spiral question — that would be an +ondaod forbidden pattern. Capture lands on a commercial consumer or the +witness/audit tier, never on the open protocol surface: protocol-not-runtime and +voluntary-adoption are preserved by construction.

    +

    The kind is additive and opt-in (ADR-029): a project with no `viability/` +directory pays zero adoption cost, and every existing positioning file validates +unchanged. No migration is required (same precedent as ADR-043's additive kinds).

    + +

    Constraints

    + +
    • Hard Viability MUST be modelled as a seam: `ViabilityPath` references the three axes via `converges` and MUST NOT be referenced by any other kind, and MUST NOT be promoted into a fourth orthogonal axis co-equal to WHAT / FOR WHOM / HOW. The schema keeps it parallel to Proof.
    • Hard Every ViabilityPath MUST converge >=1 audience, >=1 mechanism and >=1 value-prop. The make_viability_path contract (`_viability_converges_all`) enforces the triple convergence.
    • Hard Every ViabilityPath MUST cite >=1 linked_node — typically `openness-vs-sustainability`. The make_viability_path contract (`_viability_anchored`) enforces this.
    • Soft The realized-status gate MUST stay one-directional: a 'Validated|'Live ViabilityPath requires `realized_outcome`, but the presence of an outcome MUST NOT be tightened into a Hard biconditional (`outcome ⟺ realized`) on the Spiral question. The `_realized_requires_outcome` contract enforces the implication only.
    • Soft A 'Direct ViabilityPath MUST land compensation on a commercial consumer or the witness/audit tier, never on the open protocol surface itself. Metering the protocol would break protocol-not-runtime and voluntary-adoption.
    + +

    Alternatives considered

    + +
    • Viability as a fourth orthogonal axis (a fourth folder co-equal to WHAT / FOR WHOM / HOW)rejected: Compensation is not parallel to the three axes — it is where they converge and return a flow. A fourth axis implies a false fourth pole and invites classifying every claim by its business model, the exact Yang pole-collapse openness-vs-sustainability exists to prevent. The Proof-parallel seam is faithful to witness-as-axis-seam.
    • Viability as an attribute on existing kinds (a compensation_kind field on Audience, or an outcome variant on Proof)rejected: Scatters the sustainability question across kinds and couples it to validation. The seam property — references the axes, referenced by none, with its own Draft→realized lifecycle — is lost. A path that monetises an audience×mechanism×value-prop convergence needs to be a first-class artefact, not a field bolted onto one axis.
    • Reuse Proof with a new proof_kind = 'Revenuerejected: Proof answers 'is the claim real?'; viability answers 'does the claim sustain its maker?'. Overloading Proof conflates validation with viability and breaks the clean question each seam owns. Two questions, two costuras.
    • Make the realized_outcome gate a Hard biconditional (outcome present ⟺ status realized)rejected: openness-vs-sustainability is a Spiral tension; a Hard biconditional on it is an ondaod forbidden pattern. The one-directional gate (realized ⇒ outcome) reports direction of motion without collapsing the Spiral — a Draft path is a legitimate hypothesis that may carry no outcome yet.
    + +

    Anti-patterns

    + +
    • Modelling Compensation as a Fourth Orthogonal Axis — A contributor adds a fourth positioning folder co-equal to WHAT / FOR WHOM / HOW and starts tagging each claim with a business model. The axis count silently becomes four, the seam geometry is lost, and money becomes a per-claim classification — the Yang pole-collapse openness-vs-sustainability exists to prevent.
    • A Realized Viability Path Without a Measured Outcome — A ViabilityPath is promoted to 'Validated|'Live to look like the project is sustained, but no real measured `realized_outcome` backs it. Sustenance is asserted, not earned — the same failure mode as a Validated Audience with no Proof.
    • Putting Direct Compensation on the Open Protocol Surface — A 'Direct viability path meters the protocol itself (the schemas, the CLI, the adoption surface) rather than a commercial consumer or the witness/audit tier. This collapses openness-vs-sustainability onto the Yang pole and voids protocol-not-runtime and voluntary-adoption.
    • Tightening the Realized Gate into a Hard Biconditional — A contributor 'strengthens' the realized gate to outcome ⟺ realized, so that any path carrying an outcome string is forced to a realized status (and vice versa). This is a Hard biconditional on a Spiral question — it collapses the continuous flow and forbids the legitimate Draft-with-early-evidence half-state.
    + +

    Related ADRs

    + +

    ADR-043 · ADR-035 · ADR-031 · ADR-029 · ADR-054

    diff --git a/site/site/content/adr/en/accepted/adr-055.ncl b/site/site/content/adr/en/accepted/adr-055.ncl new file mode 100644 index 0000000..60ffb8e --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-055.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-055", + title = "Positioning Viability Seam — Compensation as a Second Costura Parallel to Proof, Not a Fourth Axis", + slug = "055", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/055", + graph = { + implements = [], + related_to = ["adr-043", "adr-035", "adr-031", "adr-029", "adr-054"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-056.md b/site/site/content/adr/en/accepted/adr-056.md new file mode 100644 index 0000000..5568fa0 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-056.md @@ -0,0 +1,85 @@ +--- +id: "adr-056" +title: "Sufficient Verification over Complete Knowledge — Accredited, Not Generated, as the Basis of Trust" +slug: "056" +subtitle: "Accepted" +excerpt: "The protocol's trust model has, until now, been distributed across four" +author: "ontoref" +date: "2026-06-10" +published: true +featured: false +category: "accepted" +tags: ["adr-056", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    The protocol's trust model has, until now, been distributed across four +decisions without ever being named as one: the verifiable substrate and its +Merkle/witness commitments (ADR-023), the operations layer that binds every +authoritative mutation to a signed acting-binding (ADR-024), the three +validation planes (ADR-026), and the ontology/project layer separation that +lets one project verify another by witness rather than by cloning it (ADR-028). +Each of these embodies the same underlying stance, but the stance itself was +implicit, citable only by reading four ADRs and inferring the common thread.

    +

    The thread is this: a project's knowledge can never be complete, and even if it +could, total context overflows — recording everything produces more confusion +than clarity, and no single actor (human or AI) ever holds the whole. A trust +model that depends on completeness therefore fails twice: it is unreachable, and +it is self-defeating. The only viable basis is local, partial verification of +sufficient knowledge — but "sufficient" is dangerous if it means "approximately +enough", because a well-typed-but-stale graph manufactures false certainty worse +than stale prose (the tier-0-false-certainty anti-pattern, ADR-029).

    +

    The sharpening that makes sufficiency safe is accreditation. What makes a partial +slice usable is not that it roughly suffices but that it is signed and checkable. +This becomes load-bearing in the AI moment: an agent's GENERATED output, and any +DEDUCED conclusion, is not usable knowledge until it is accredited. Hallucination +is exactly generated content entering the authoritative set unsigned. The stance +needs to be explicit and citable because the difusión surface already sells it +(differentiator diff-accredited-knowledge) and the agent-integration surface +depends on it.

    + +

    Decision

    + +

    Name the stance as a constitutive axiom — `sufficient-verification`, +"Sufficient Verification over Complete Knowledge" (pole 'Spiral, invariant=true +in .ontoref/ontology/core.ncl) — and fix its operative consequences as typed +constraints.

    +

    1. Verification is always LOCAL and PARTIAL. Any actor can verify that a slice + (an op, a node, a change) is coherent with what is declared WITHOUT holding + or loading global knowledge of the whole. Cross-project verification is by + witness, not by clone (ADR-028).

    +

    2. The protocol NEVER requires complete or global knowledge as a precondition + for a valid act. Any gate of the form "you may not change X until you + understand the whole system" is forbidden by design.

    +

    3. Knowledge is ACCREDITED, not deduced or generated. Deduced or generated + output — explicitly including an AI agent's — is NOT usable authoritative + knowledge until it is signed/witnessed. At tier-2 this is enforced: the + witness is the only mutation path and validators reject ill-typed state + (internal-coherence-enforced). At tier-0, where no witness exists, such + knowledge is unaccredited by construction and MUST NOT be treated as + authoritative — it is a draft to be accredited, not a fact.

    +

    4. "Sufficient" means accredited-and-checkable, never "approximately enough". + The floor is the signature, not a tolerance band.

    +

    This is a Spiral commitment, not a Yin concession: it does not give up certainty, +it fixes the third thing between impossible-complete-certainty and dangerous-blind- +trust — verification of the sufficient — as invariant law. The axiom is the +substance-axis statement; the witness/validators (ADR-023/024/026) are the +reflection-axis enforcement; tier coexistence (ADR-029) is what lets a project +choose how much it commits to accredit, never how much it must know.

    + +

    Constraints

    + +
    • Hard Verification MUST operate on a slice (an op, a node, a change) without requiring the whole graph; cross-project verification MUST be by witness, never by clone (ADR-028). No verification path may demand global load.
    • Hard The protocol MUST NOT introduce any gate that requires complete or global knowledge as a precondition for a valid act ('you may not change X until you understand the whole system' is forbidden).
    • Hard Deduced or generated output — explicitly including an AI agent's — MUST NOT enter the authoritative set until signed/witnessed. At tier-2 the witness path enforces this (ADR-024); at tier-0, where no witness exists, such output MUST be treated as an unaccredited draft, never as authoritative fact.
    • Soft 'Sufficient' MUST mean accredited-and-checkable (the floor is a signature), never 'approximately enough' (a tolerance band). No surface may relax sufficiency into laxity.
    + +

    Alternatives considered

    + +
    • Leave the stance implicit across ADR-023/024/026/028rejected: The stance is load-bearing for difusión and agent integration and was already being sold (diff-accredited-knowledge) without a citable decision behind it. Implicit-across-four-ADRs means no single thing to cite, constrain against, or defend — and the global-knowledge gate could creep in unchallenged.
    • Model it as a differentiator only (diff-accredited-knowledge), not an axiomrejected: A differentiator positions outward; it does not constrain inward. The principle is a constitutive renunciation that must bind all future design, not a competitive talking point. The repo precedent is both: witness-as-axis-seam is an axiom AND diff-witness-seam cites it. Accreditation gets the same two-layer treatment.
    • Model it as a Yin axiom (a concession: we accept knowing less)rejected: Yin frames incompleteness as the price of ontoref — an apology. The honest framing is Spiral: holding the third thing between impossible certainty and blind trust, fixed as law. Incompleteness is the FORM, not the price; that distinction sets the tone of the whole difusión surface (affirmation, not apology).
    + +

    Anti-patterns

    + +
    • Treating Generated or Deduced Output as Authoritative Without a Signature — An agent's generated output, or a deduced conclusion, is written into authoritative state (or trusted as fact) without being witnessed. Hallucination enters the set unmarked; the accredited/generated line is erased. This is the precise failure the axiom exists to forbid.
    • Requiring Whole-System Understanding Before a Valid Act — A mode, validator, or adoption step demands that an actor understand or load the whole project before changing a part. This reinstates the unreachable, self-defeating completeness trust model and blocks the local partial action the axiom guarantees.
    • Resting Trust on Completeness Instead of Verification — A project tries to earn trust by documenting/knowing everything and keeping it all current — the model that overflows context and drifts, producing more confusion than clarity. Trust should rest on accredited sufficient verification, not on an unreachable whole.
    + +

    Related ADRs

    + +

    ADR-023 · ADR-024 · ADR-026 · ADR-028 · ADR-029 · ADR-031

    diff --git a/site/site/content/adr/en/accepted/adr-056.ncl b/site/site/content/adr/en/accepted/adr-056.ncl new file mode 100644 index 0000000..959d597 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-056.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-056", + title = "Sufficient Verification over Complete Knowledge — Accredited, Not Generated, as the Basis of Trust", + slug = "056", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/056", + graph = { + implements = [], + related_to = ["adr-023", "adr-024", "adr-026", "adr-028", "adr-029", "adr-031"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-057.md b/site/site/content/adr/en/accepted/adr-057.md new file mode 100644 index 0000000..603d687 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-057.md @@ -0,0 +1,90 @@ +--- +id: "adr-057" +title: "Difusión Reveal Architecture — Route Each Door to a Live Graph, Never Argue the Core" +slug: "057" +subtitle: "Accepted" +excerpt: "The positioning layer (ADR-035) made marketing a queryable protocol surface:" +author: "ontoref" +date: "2026-06-10" +published: true +featured: false +category: "accepted" +tags: ["adr-057", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    The positioning layer (ADR-035) made marketing a queryable protocol surface: +audiences, value-props, differentiators, proofs, viability paths, all typed NCL. +But the outward-facing SITE (outreach/site) did not consume that graph — it +rendered a generic content template (blog/recipes/projects). The originating +complaint that opened the 2026-06-10 ontology-completion interview was exactly +this: "the site content does not correspond to the project."

    +

    The interview established that the gap is not missing ontology (the layer is +rich) but a missing PROJECTION discipline, and — more importantly — that the +naive fix (a site that explains ontoref's core: the two axes, the witness-seam, +the spiral, the wu-wei) is actively wrong. Exposition of the core repels by both +ends, the failure mode named in the Spiral tension `reach-vs-qualification`: it +bores the visitor who came for a concrete utility, and it reads as esoteric to +the visitor who has not yet earned the frame. A site that argues the worldview +either converts no one or attracts the wrong adopter who bails at first friction.

    +

    The interview also established HOW the worldview is actually transmitted — a +three-rung reveal ladder with increasing mediation-dependence: (1) a self-produced +flip when a developer runs a live `describe impact` and sees the tool answer +intent, not just structure; (2) a seeded flip, landing only in real use, when +changing something forces touching the maintained identity (drift-check / ondaod +/ witness); (3) a worldview flip — "this is for anything, not just projects" — +produced ONLY by encounter with a real subject-graph the visitor recognizes as +their own, never by argument. The proofs layer already names this: +proof-personal-ontoref records that the latent audience "self-discovers when they +see a personal graph, not in a pitch", and mech-repo's stance is "show, don't +tell". The decision below makes that discipline architectural.

    + +

    Decision

    + +

    The site is a vestibule of three concrete doors (developer / infrastructure / +personal), each routing to a live, navigable, witnessed graph. It is NOT an +exposition of the core. Concretely:

    +

    Projection, not authoring — door landings are PROJECTED from the positioning + graph through a structured contract (.ontoref/positioning/spine.ncl), not + hand-written. The contract carries the chosen spine copy (hero A, sub-hero B, + door lines C, prize D, close E) and references real audience/value-prop ids per + door; a generator emits the pages. Change a value-prop, regenerate, the door + updates. No duplicated copy that can drift.

    +

    Hook static, prize live — the HOOK (hero, sub-hero, door landings) is static + NCL→build, so the site is usable with no daemon (local-fallback, ADR-029). The + REVEAL rungs that need a live graph — rung 1 (live `describe impact` demo) and + rung 3 (browsable real graph) — link to the running daemon. The daemon is the + prize surface, never a hard dependency of the hook.

    +

    Route, do not argue — the site MUST route each door to a live graph or describe + surface, and MUST NOT try to produce the worldview flip by prose. rung 1 is + produced by a live demo; rung 2 is seeded (shown, lands in use); rung 3 is + triggered only by serving a real navigable subject-graph (ontoref's own, the + provisioning graph, a published personal graph). Prose alone never produces the + flip — this is the declared structural limit.

    +

    Mediation by witnessed artifact — the worldview flip historically needed a + person to mediate. The decision converts that mediation into a reproducible + witnessed artifact: a browsable real graph (the proofs, made navigable). This + is what lets ontoref.dev produce reveal 1 and 3 without a human bottleneck. + Artifact-mediation serves reach (self-selection at scale); human mediation + remains only for the qualified-but-stuck — the reach-vs-qualification split.

    +

    Honesty as filter — door copy is second person and names the pain (and the + exhaustion of facing it without a substrate); it does not soften the truth that + there is no deterministic recipe. The repulsion of the recipe-seeker is the + qualifier, not funnel leakage (reach-vs-qualification).

    + +

    Constraints

    + +
    • Soft The site MUST route each door to a live graph or describe surface and MUST NOT attempt to produce the worldview flip by prose. The core (two axes, witness-seam, spiral, wu-wei) is the prize revealed after entry, never the landing-page argument.
    • Hard Door landing pages MUST be projected from the structured contract (.ontoref/positioning/spine.ncl) referencing real audience/value-prop ids, not hand-authored. A value-prop change MUST be reflected by regeneration, never by editing copy in two places.
    • Hard The hook (hero, sub-hero, door landings) MUST be static NCL→build and usable with no daemon (local-fallback). Only the reveal rungs that need a live graph (rung 1 live describe-impact, rung 3 browsable graph) may depend on the running daemon.
    • Soft The rung-3 worldview flip MUST be mediated by a reproducible witnessed artifact (a browsable real graph), not by a required human. Publishing a real personal graph as that artifact is a per-artifact privacy decision and MUST be explicitly cleared before exposure.
    + +

    Alternatives considered

    + +
    • An exposition site that explains the core (two axes, witness-seam, spiral, wu-wei) on the landing pagerejected: This is the naive fix the interview rejected. Exposition of the core repels by both ends (reach-vs-qualification): it bores the utility-seeker and reads esoteric to the unready, converting no one or attracting the wrong adopter. The worldview is not transmissible by prose — only by encounter with a real graph.
    • Hand-authored door landing pages (marketing copy written directly as content)rejected: Hand-authored copy drifts from the value-props it sells — the exact failure (content maintained by hand) that produced the generic template complaint. Projection from spine.ncl + the value-props keeps the site a derived view that regenerates on change.
    • Site consumes the daemon API at runtime for everything, including the hookrejected: Couples the whole site to a running daemon, contradicting local-fallback (ADR-029): the site goes dark when the daemon is down. The hook must be static so first contact never depends on the daemon; only the live-graph reveal rungs may.
    • Rely on a human to mediate the worldview flip (talks, calls, demos by a person)rejected: Human mediation does not scale and bottlenecks reach. The flip's mediation is convertible into a reproducible witnessed artifact — a browsable real graph (proof-personal-ontoref, mech-repo). Human mediation is kept only for the qualified-but-stuck, per reach-vs-qualification.
    + +

    Anti-patterns

    + +
    • Arguing the Core on the Landing Page — A contributor writes a landing page that explains the two axes, the witness-seam, the spiral, or the wu-wei as the first thing a visitor reads. Exposition repels by both ends (reach-vs-qualification) and cannot produce the worldview flip, which only encounter with a real graph produces.
    • Hand-Authoring Door Copy That Drifts From the Value-Props — Door landing text is written directly as site content rather than projected from spine.ncl + the value-props. The copy drifts from the claims it sells — the exact failure that produced the generic-template complaint.
    • Gating First Contact on a Running Daemon — The hook (hero, sub-hero, door landings) is made to require the daemon (e.g. fetched live at runtime), so the site goes dark when the daemon is down. This breaks local-fallback and gates first contact on the prize surface.
    + +

    Related ADRs

    + +

    ADR-035 · ADR-046 · ADR-029 · ADR-043 · ADR-056

    diff --git a/site/site/content/adr/en/accepted/adr-057.ncl b/site/site/content/adr/en/accepted/adr-057.ncl new file mode 100644 index 0000000..f384111 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-057.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-057", + title = "Difusión Reveal Architecture — Route Each Door to a Live Graph, Never Argue the Core", + slug = "057", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/057", + graph = { + implements = [], + related_to = ["adr-035", "adr-046", "adr-029", "adr-043", "adr-056"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-059.md b/site/site/content/adr/en/accepted/adr-059.md new file mode 100644 index 0000000..7c66b52 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-059.md @@ -0,0 +1,82 @@ +--- +id: "adr-059" +title: "Level-Aware About — The /about Surface Branches on Level Kind and Projects the Level's Own Graph" +slug: "059" +subtitle: "Accepted" +excerpt: "ADR-057 made the SITE a projection of the positioning graph: three doors," +author: "ontoref" +date: "2026-06-13" +published: true +featured: false +category: "accepted" +tags: ["adr-059", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    ADR-057 made the SITE a projection of the positioning graph: three doors, +projected from spine.ncl, hook-static / prize-live, route-to-live-graph. That +ADR is scoped to the difusión vestibule — the doors and their value-props.

    +

    The /about route was untouched by it. It rendered a single texts-driven template +(about.j2): a personal CV (the jpl level). But the About surface is where a level +presents WHAT IT IS, and that differs by the kind of level: a personal ontology +(jpl) presents a CV and links; a project (ontoref) should present its own +identity — history, defs (axioms/tensions/practices), graph, ADRs, and the +catalog of verifiables. The same complaint that opened ADR-057 ("the site content +does not correspond to the project") applies to /about: it showed a person where +a project should show itself.

    +

    Three facts shaped the decision. (1) The data already exists and is queryable +daemon-free: defs from core.ncl, the ADR set, the catalog of operations + +validators. (2) The htmx site renders /about through a Tera template, NOT the +Leptos UnifiedAboutPage — so making /about level-aware is a Tera handler + template +change, not a component rewrite. (3) The site already has a hot-reload pattern for +request-time graph data: content_graph.json is read by an mtime-cached loader, so +a regenerated artifact takes effect with no restart. The About can consume the +same way, which keeps the projection current without coupling the page to a live +daemon.

    + +

    Decision

    + +

    The /about route is level-aware: it branches on the level's kind and, for a +project, projects the level's own .ontoref/ graph. This EXTENDS ADR-057's reveal +principle (hook-static / prize-live, projection-not-authoring, route-to-live-graph) +to a second surface; it does not restate it. What is new here:

    +

    A projection contract per level — .ontoref/positioning/about.ncl, typed by + about-schema.ncl, with a discriminant `kind = 'Personal | 'Project` and a + coherence predicate (kind ⇒ the matching block present, the other absent). The + contract declares SELECTIONS, not data: which axioms/tensions/practices to + surface, which ADR statuses to digest, which catalog kinds — the data itself is + queried from core.ncl / the ADR set / the catalog at projection time. Sparse + .ontoref/ yields a partial About, never an error.

    +

    One view-model, two consumers — a single daemon-free projector (gen-about-pages) + assembles a view-model from about.ncl + .ontoref/. `--out` renders a standalone + static page (the marketing/web surface); `--emit-json` writes about.json, the + SSR consumption artifact.

    +

    Hot-reload view-model, not a baked page — the /about handler reads about.json on + an mtime check (mirroring content_graph.json). When the artifact declares + kind == "project" the handler renders about_project.j2 with the view-model; + otherwise (absent, malformed, or kind == "personal") it falls back to the + existing texts-driven about.j2. Regenerating about.json updates live /about with + no restart; only handler-LOGIC changes need a rebuild. Data changes never do.

    +

    Personal stays as it was — the personal level's About is unchanged: the same + texts-driven about.j2. Level-awareness is additive; it does not touch the + surface that already worked.

    +

    The live graph remains the only daemon-dependent element of the project About — +the reveal prize, per ADR-057. Everything else renders from the build-time +projection, daemon-free.

    + +

    Constraints

    + +
    • Hard The /about route MUST branch on the level kind: a project level renders the projected project About; a personal level (or absent/malformed projection) MUST fall back to the existing texts-driven personal template. The personal surface MUST stay the default and untouched.
    • Hard The project About MUST be projected from .ontoref/positioning/about.ncl, which declares SELECTIONS (which defs/ADR-statuses/catalog-kinds), never inlined identity data. The data MUST be queried from core.ncl / the ADR set / the catalog at projection time so a source change regenerates the About.
    • Soft The SSR /about MUST consume the projection as a hot-reload view-model artifact (about.json) read at request time on an mtime check, NOT baked into the binary. Regenerating the artifact MUST refresh /about with no restart; only handler-logic changes may require a rebuild.
    + +

    Alternatives considered

    + +
    • Keep /about personal; put the project About at a separate route (a content-kind page)rejected: This is the cheaper Option A and remains valid as the standalone static web surface. Rejected as the CANONICAL answer because /about itself would not become level-aware — a visitor to a project site's real /about would still get a person. Level-awareness of the canonical route is the point.
    • Project the About live from the daemon at request time (a daemon HTTP client in the handler)rejected: Couples /about to a running daemon, breaking the hook-static/prize-live split (ADR-057) and local-fallback (ADR-029): the page goes dark when the daemon is down. The hot-reload artifact gives most of the currency benefit while keeping the page daemon-free; the live graph stays the only daemon element.
    • Rewrite /about as the Leptos UnifiedAboutPage with the projection embeddedrejected: The htmx site renders /about through Tera, not the Leptos component. A Leptos rewrite is a far larger blast radius for no benefit over a Tera handler + template that reads the view-model.
    • Bake the project About into the binary at build (no runtime artifact)rejected: Every .ontoref/ change would then require a binary rebuild + redeploy to refresh identity. The mtime-cached artifact (content_graph.json pattern) decouples data refresh from binary builds — regenerate about.json, no restart.
    + +

    Anti-patterns

    + +
    • Showing a Person Where the Project Should Show Itself — A project level's /about renders a generic CV / personal template instead of projecting the project's own history, defs, graph, ADRs, and catalog. The About does not correspond to the level — the same gap ADR-057 closed for the doors.
    • Hand-Authoring the Project About — The project About is written as site content (defs, ADR list, history) rather than projected from about.ncl + queried from .ontoref/. The copy drifts from the ontology it claims to present.
    • Baking the About Into the Binary or Gating It on the Daemon — Either the project About is compiled into the server (so every .ontoref/ change needs a rebuild) or it is fetched live from the daemon at request time (so the page goes dark when the daemon is down). Both break the hot-reload / local-fallback discipline.
    + +

    Related ADRs

    + +

    ADR-057 · ADR-035 · ADR-045 · ADR-029 · ADR-043

    diff --git a/site/site/content/adr/en/accepted/adr-059.ncl b/site/site/content/adr/en/accepted/adr-059.ncl new file mode 100644 index 0000000..f4f79dc --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-059.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-059", + title = "Level-Aware About — The /about Surface Branches on Level Kind and Projects the Level's Own Graph", + slug = "059", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/059", + graph = { + implements = [], + related_to = ["adr-057", "adr-035", "adr-045", "adr-029", "adr-043"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-060.md b/site/site/content/adr/en/accepted/adr-060.md new file mode 100644 index 0000000..23259bd --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-060.md @@ -0,0 +1,75 @@ +--- +id: "adr-060" +title: "mdBook Docsite — Projected Documentation as the Standard Surface for ontoref-Managed Projects" +slug: "060" +subtitle: "Accepted" +excerpt: "The project already had `generate-mdbook` (generator.nu): it composed identity," +author: "ontoref" +date: "2026-06-19" +published: true +featured: false +category: "accepted" +tags: ["adr-060", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    The project already had `generate-mdbook` (generator.nu): it composed identity, +ontology, ADRs, modes and scenarios and emitted a bare mdBook (raw markdown, the +default theme, no served target). It answered "render the graph to markdown" but +not "what is the standard, served, on-brand documentation surface for a project +that manages itself with ontoref".

    +

    Two external references fixed the missing shape. The cdci-dao mdBook +(/Users/Akasha/Development/mdbook/cdci-dao) separates CHROME from CONTENT: a +ds-theme + a book.toml with preprocessors (tera, admonish, toc, codeblocks) is the +container; globaldefs.md + a tera context is how content enters, with a dev↔dist +swap that bakes a base-URL. docserver (/Users/Akasha/Development/docserver/code) +serves PRE-BUILT static HTML registered by a [[serv_paths]] entry (src_path → +url_path, is_restricted); it does not build or watch — "on demand" means rebuild, +not serve-time.

    +

    The seam where ontoref plugs in is exactly cdci-dao's chrome/content split: keep +the chrome fixed and shipped; feed the content from .ontoref/ on every build. The +question is whether to make this the standard documentation surface for every +ontoref project, and on what terms.

    + +

    Decision

    + +

    Adopt the cdci-dao mdBook model as the standard documentation surface, projected +from the ontology and served by docserver, shipped as a protocol template.

    +

    - CHROME (shippable, fixed): reflection/templates/mdbook/ carries the ds-theme, + a parameterized book.toml (tera/admonish/toc/codeblocks/variables/ + extended-markdown-table), assets/css, globaldefs and brand images. It ships + with the reflection tree (installed by `just install-daemon`), so consumers + reach it at $ONTOREF_ROOT/reflection/templates/mdbook/ without the source repo.

    +

    - MATERIALISE: `docsite init` (reflection/modules/docsite.nu) copies the chrome + into the project's .ontoref/docsite/ — idempotent, skipped if a book.toml + already exists. `docsite generate` auto-runs it on first use.

    +

    - PROJECT (hybrid): on every build, docsite.nu reuses generator.nu's composed + `docs data`, writes a Tera context (identity read from card.ncl, not the + filesystem basename) for the static chrome pages, and pre-renders one page per + ADR, one per mode, and the architecture facets into docsite/src/, assembling + SUMMARY. Static Tera chrome + pre-rendered dynamic pages = the cdci-dao pattern. + Re-generating re-projects; the docs cannot drift from the state they describe.

    +

    - SERVE: a dev build emits relative links for `mdbook serve`; `docsite generate + --serve-path /<mount>` bakes site-url to the docserver url_path (a surgical + one-line swap, save/ backup-restore) and emits the [[serv_paths]] registration.

    +

    - The generate-docsite mode declares the procedure (materialize-template → + project-and-build → dist-and-register); `just docsite [serve-path]` is the + shortcut. The model was proven spine-local on ontoref itself before promotion; + migration 0039 carries it to consumers. Adoption is opt-in.

    + +

    Constraints

    + +
    • Hard The mdBook chrome (ds-theme + book.toml preprocessors + assets) MUST ship as the protocol template under reflection/templates/mdbook/. The per-ADR, per-mode and architecture pages MUST be regenerated from .ontoref/ on every build, never hand-authored into the materialised docsite/src/.
    • Hard Project identity (name, description) in the docsite MUST be read from card.ncl, the ontological beacon, NOT derived from the directory basename. This keeps the model correct for any project regardless of where its ontology sits.
    • Soft Serving SHOULD go through a dist build with site-url baked to the docserver mount, emitting a [[serv_paths]] registration entry. The mode MUST NOT mutate a foreign docserver config; it emits the entry and the copy hint for the operator to apply.
    • Soft Building a docsite SHOULD remain opt-in: no project is required to materialise or generate one, and the absence of .ontoref/docsite/ is never an error. The capability ships; adoption is voluntary and reports direction of motion, not a gate.
    + +

    Alternatives considered

    + +
    • Keep generate-mdbook (the bare renderer) as the only doc surfacerejected: It emits raw markdown on the default theme with no served target and no chrome/content split. It answers 'render the graph to markdown' but is not an on-brand, docserver-servable, standard surface. Kept as a quick dump; not the standard.
    • Pure-Tera: every page a {{ }} template, mdbook-tera renders all content at buildrejected: Maximises 'on demand' but forces per-ADR/per-mode loops into a single tera context while mdbook still needs one .md file per page — rigid for the per-item pages with no benefit over pre-rendering them. The hybrid keeps tera for the chrome where it helps.
    • Ship to install/resources/templates/ and copy via install.nurejected: install.nu does not copy install/resources/templates/ wholesale, while the reflection tree IS copied verbatim to the data dir and is already how forms templates ship ($ONTOREF_ROOT/reflection/templates/). Placing the chrome under reflection/templates/mdbook/ reuses the wired path with no install.nu change.
    • Promote straight to the protocol template without proving it spine-local firstrejected: Standardises an unverified surface for every consumer in one step — the premature-formalization anti-pattern (adr-029). Proving the slice on ontoref itself, then promoting with a migration, keeps the realised instance ahead of the protocol claim (sufficient-verification).
    + +

    Anti-patterns

    + +
    • Hand-Authoring the Projected Documentation — The architecture / decisions / modes pages are written as static markdown rather than projected from .ontoref/ on each build. The docs drift from the ontology they claim to present.
    • Naming the Project from the Directory — The docsite title/identity is derived from the directory basename instead of card.ncl, producing wrong names (e.g. '.ontoref') and a model that breaks when the ontology does not sit at the project root.
    • Making the Docsite a Gate — Treating the absence of a docsite as an error, or requiring every project to build one. Collapses the formalization-vs-adoption Spiral by imposing the surface rather than offering it.
    + +

    Related ADRs

    + +

    ADR-057 · ADR-035 · ADR-029 · ADR-032 · ADR-010

    diff --git a/site/site/content/adr/en/accepted/adr-060.ncl b/site/site/content/adr/en/accepted/adr-060.ncl new file mode 100644 index 0000000..1a1656d --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-060.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-060", + title = "mdBook Docsite — Projected Documentation as the Standard Surface for ontoref-Managed Projects", + slug = "060", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/060", + graph = { + implements = [], + related_to = ["adr-057", "adr-035", "adr-029", "adr-032", "adr-010"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-061.md b/site/site/content/adr/en/accepted/adr-061.md new file mode 100644 index 0000000..666a25d --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-061.md @@ -0,0 +1,75 @@ +--- +id: "adr-061" +title: "Retire 'Structure' as the Mother-Concept and Decouple Brand Logos from the Tagline" +slug: "061" +subtitle: "Accepted" +excerpt: "'Structure' was ontoref's mother-concept on every outward surface: the card.ncl" +author: "ontoref" +date: "2026-06-21" +published: true +featured: false +category: "accepted" +tags: ["adr-061", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    "Structure" was ontoref's mother-concept on every outward surface: the card.ncl +identity tagline ("Structure that remembers why."), the positioning hero +("Structure that stays true."), the SEO page title, a curated rotator phrase, and +the text baked into ~20 brand logo SVGs.

    +

    A role-swap ontology-completion interview (cadence per the 2026-06-10 model) +surfaced three faults the noun carries. First, "structure" fails the project's own +`diff-domain-transversality` differentiator: a software project reads as a +"structure", but an infrastructure provisioning or a personal ontology does not — +the noun silently narrows the identity to the developer door and de-identifies the +other two. Second, it is mono-polar and static: it names only the Substance pole +(ADR-031) and amputates the Act/reflection pole, and "structure" connotes fixity +against the core's own "what is real is in transformation, not a fixed set of +invariants". Third, the verb "remembers"/"stays true" names an EFFECT (amnesia, +drift) rather than the root the project actually defends against (loss of +self-control / autocontrol).

    +

    The tagline was baked into the logo SVGs, so the identity could not change without +re-rendering the whole logo family — a coupling that had frozen the identity and +was the main argument for leaving "structure" in place.

    + +

    Decision

    + +

    Retire "structure" as the mother-concept across identity, hero and branding, and +decouple the logos from the tagline so identity can evolve without a brand +re-render.

    +

    - IDENTITY (card.ncl): the stable tagline becomes the honest, transversal + category definition — "Operational ontology with reflection" — and the + description drops "queryable structure" and states the transversality. The + evocative voice moves to the evolving positioning hero, not the stable beacon.

    +

    - HERO (spine.ncl): an embodied, transversal, both-pole line — "Pisa firme y + anda ligero." / "Sure-footed, and light on your feet." — with the domain + (ámbito) carried by the page eyebrow ("Operational ontology with reflection"), + not by the hero. The certainty is punctual (the witnessed point); the act is + continuous.

    +

    - LOGOS: the tagline <text> is removed from the brand SVGs (wordmark + icon + only). The logo no longer encodes the claim, so the tagline lives in layout and + can change freely without re-rendering logos.

    +

    - ROOT ACT: named with the continuous verb "sostener" (durative, indispensable), + never the punctual "responder". The three tier-taglines + (.ontoref/positioning/tier-taglines.ncl) capture the same root at the three + adoption tiers (ADR-029).

    +

    The retired lines were swept from the rotator fallback, the SEO title and the +curated rotator queue; the value-prop vp-001 (whose claim still uses the retired +concept) is left for a separate content review, not a mechanical id rename.

    + +

    Constraints

    + +
    • Hard The card.ncl identity tagline MUST be the transversal category definition 'Operational ontology with reflection', not a domain-narrowing mother-noun (e.g. 'structure', 'graph').
    • Hard The brand logo SVGs MUST NOT bake the retired tagline text. The logo is wordmark+icon only; the tagline/definition is applied separately in layout.
    • Soft Outward identity/positioning surfaces SHOULD NOT reintroduce 'structure' as the mother-noun. 'graph'/'structure' may appear only as a sanctioned access-substrate (per diff-domain-transversality), never as the headline identity.
    + +

    Alternatives considered

    + +
    • Keep 'Structure that remembers why' as the stable identityrejected: Defensible on brand-stability and because 'remembers why' carries the reflection pole — but it loses to the transversality argument: 'structure' de-identifies the infrastructure and personal audiences, which is more fundamental than stability since transversality is the core differentiator.
    • Change only the positioning hero, leave the card identity and logos as-isrejected: Leaves the identity beacon and the whole logo family asserting a mother-noun that fails transversality, and keeps the logo/tagline coupling that freezes the identity. Half-measure that preserves the root problem.
    • Keep the tagline baked into the logos and re-render all ~20 SVGs on each changerejected: Re-couples identity to a 20-file brand re-render every time the claim evolves. Decoupling once is strictly cheaper and is what mature brand systems do (mark separate from claim).
    • Pick a new poetic mother-noun (e.g. 'graph', 'coherence') as the identityrejected: 'graph' repeats the same reification one level down (a substrate-noun, Substance-poled); any single abstract noun risks collapsing one pole or failing a domain. The resolution is a category definition for the stable identity plus an embodied act for the evolving hero, not another mother-noun.
    + +

    Anti-patterns

    + +
    • Mother-Noun That Lands in One Domain — A single identity noun that reads natural in one domain (software 'structure') silently excludes the others (an infra, a life), narrowing reach to one audience while appearing neutral.
    • Baking the Claim into the Mark — The tagline text is rendered inside the logo SVG, coupling identity to a multi-file brand re-render and freezing the claim — the identity cannot evolve without re-rendering the whole logo family.
    • Naming the Symptom Instead of the Root — The identity verb defends against a symptom (amnesia → 'remembers', drift → 'stays true') instead of naming the root act (self-governance held continuously → 'sostener'). The line treats the effect as the essence.
    + +

    Related ADRs

    + +

    ADR-035 · ADR-057 · ADR-029

    diff --git a/site/site/content/adr/en/accepted/adr-061.ncl b/site/site/content/adr/en/accepted/adr-061.ncl new file mode 100644 index 0000000..51b7dcd --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-061.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-061", + title = "Retire 'Structure' as the Mother-Concept and Decouple Brand Logos from the Tagline", + slug = "061", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/061", + graph = { + implements = [], + related_to = ["adr-035", "adr-057", "adr-029"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-062.md b/site/site/content/adr/en/accepted/adr-062.md new file mode 100644 index 0000000..11e4402 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-062.md @@ -0,0 +1,109 @@ +--- +id: "adr-062" +title: "Constellation Member Taxonomy — Primary, Addon, Projection; Recursion in the Level Graph, Not Git; Uniform Forge Rule" +slug: "062" +subtitle: "Accepted" +excerpt: "ADR-048 fixed the constellation layout (non-versioned parent, independent git" +author: "ontoref" +date: "2026-06-22" +published: true +featured: false +category: "accepted" +tags: ["adr-062", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    ADR-048 fixed the constellation layout (non-versioned parent, independent git +sub-repos, spine at parent) on one implicit assumption: one folder = one +publication boundary (code→Forgejo, outreach→public, vault→private). Reality +stressed that assumption from two directions the original ADR did not name.

    +

    (1) DERIVATION/COUPLING. Some surfaces exist by relation to another member, not +autonomously. The daemon UI is served by ontoref-daemon (peer ontoref-ui), shares +code's remote/visibility/release. desktop is an app with its own cycles, versions +and multi-license terms. examples are CI-tested fixtures that also feed docs and +workshops. study-group is a workshop format that consumes examples. None of these +map cleanly to "a folder with one boundary".

    +

    (2) INTRA-MEMBER VISIBILITY SPLIT. Some surfaces carry the boundary inside the +build pipeline, not between folders. presentations are md slides (own cycles) that +project to public PDFs. web is a distributable pack rendered to jesusperez.pro; +site renders to ontoref.dev — different deploy targets, same public visibility. +docs is the extreme case: one generated source routed to dev (code), public +(outreach) and private (vault) audiences, served by an external docserver +(/Users/Akasha/Development/docserver, a peer like stratumiops, not a member).

    +

    The pull this created was toward git submodules / nested repos to mirror the +apparent recursion (code{daemon,UI,examples}, outreach{site,web,presentation, +study-group}). That pull is the error this ADR closes: it would reify a logical +recursion into the most expensive substrate (git topology + version-pinning + +clones), and — decisively — a submodule IS clone-and-pin, which contradicts the +project's own witness-not-clone invariant (ADR-028). ontoref describing its own +internal structure by cloning would be anti-PAP of its deepest axiom.

    + +

    Decision

    + +

    Classify every constellation member into exactly one of three categories, decouple +the logical recursion from the git topology, and fix a uniform forge rule.

    +

    THREE CATEGORIES + - Primary — owns a publication boundary (distinct remote + visibility) OR a + distinct release lifecycle. Gets its own git repo. + Members: code/, outreach/, vault/, desktop/. + - Addon — exists by a declared dependency edge to a Primary, with its own + lifecycle. Gets its own repo ONLY when it earns its own boundary; + otherwise it is a node in the level graph inside its Primary. + desktop is an Addon-that-earned-Primary (ADR-054 capture surface, + multi-license); the daemon UI is an Addon that stays inside code/. + - Projection — one source, the frontier lives INSIDE the build. Never its own + repo. Four sub-shapes the build routes: + copy assets/ — canonical → identical materialized copies + reveal site — projects the live graph (ADR-057) + visibility-split presentation — private/undeployed src → public PDF + multi-boundary docs — one source → dev/public/private slices + by audience (ADR-046 view) → docserver

    +

    DECISION DRIVER. "Own repo?" is decided by publication boundary or release +lifecycle, never by conceptual separability. Conceptual recursion is carried by +the level graph (ADR-045 relative chains + manifest.hosts co-tenancy, resolved by +witness, never cloned), decoupled from the filesystem (ADR-028). Submodules are +forbidden as a structural mechanism; they are admissible only if a sub-surface +earns an independent boundary AND needs version-pinning into a parent — a +condition zero current surfaces meet, and even then the ontoref-native form is +co-tenancy-by-witness, not a git submodule.

    +

    UNIFORM FORGE RULE. Forgejo (repo.jesusperez.pro) is always the canonical origin. +GitHub is always a one-way push-mirror (Forgejo native push-mirror, force-overwrite +of all refs → de-facto read-only) or absent — never canonical of anything. rad +(Radicle) is an optional sovereign peer, pushed separately, never inside the mirror +chain. vault is excluded from every mirror (Forgejo private only). code/ carries +all three (Forgejo canonical + rad sovereign + GitHub mirror); outreach/ and +desktop/ carry Forgejo canonical + GitHub mirror; vault/ Forgejo private only.

    +

    PLACEMENTS. examples → code/ (CI fixtures), projected to outreach docs and consumed +by study-group via co-tenancy. presentations → src in outreach/presentation/ +(public repo, PDFs deployed, sources not); strategically sensitive decks are the +exception routed to vault. study-group → outreach/, hosts the examples chain by +witness (ADR-045 instance-as-host). docs → not a member: a multi-boundary +projection whose private branch is sourced tags-in-place (each member carries its +own docs tagged by audience; docserver composes the slices), per ADR-046/060.

    +

    DEV-INTERNAL OVERLAY. Each member's dev-internal process and config (.coder/, +.claude/) is gitignored from the public-mirrored history and tracked instead in a +private 'bare-overlay' repo: a separate git-dir at <member>/.internal.git (itself +gitignored) whose work-tree is the member root and which tracks only those two +dirs. It pushes ONLY to a private Forgejo remote and is NEVER mirrored — the same +boundary as vault, one sensitivity tier below it (plaintext, not SOPS). The public +repo and the overlay coexist on one working tree via distinct git-dirs +(plane-habitability), which is NOT a submodule (no .gitmodules, no pin). Tooling: +code/scripts/internal-overlay.nu (setup / onboard / commit / run), generic for any +project with the same .coder/.claude layout.

    + +

    Constraints

    + +
    • Soft Every constellation member is classified as exactly one of Primary | Addon | Projection; a new member declares its category before it is wired.
    • Hard No constellation member uses git submodules to express internal structure or cross-member composition; recursion is carried by the level graph (ADR-045 co-tenancy), resolved by witness, never cloned.
    • Soft Forgejo is the canonical origin of every materialized member; GitHub appears only as a one-way push-mirror, never as canonical.
    • Hard vault/ is excluded from every public mirror — it has no github.com remote and no rad:// peer; Forgejo private only.
    • Soft An Addon declares its dependency edge to the Primary it derives from (desktop → code via cargo path dependency), so the derivation is explicit, not implied by colocation.
    • Soft A member's .coder/ and .claude/ are gitignored from the public repo and tracked in a private bare-overlay repo (git-dir at <member>/.internal.git, work-tree the member root) that pushes only to a private remote — never github/rad.
    • Soft A Projection surface (assets, site, web, presentations, examples, docs) does not become its own git repo; it lives inside a Primary and the build routes its visibility.
    + +

    Alternatives considered

    + +
    • Git submodules / nested repos to mirror the conceptual recursion, with examples/presentations nested insiderejected: A submodule is clone-and-pin, contradicting witness-not-clone (ADR-028) — ontoref would violate its own invariant to describe itself. It also reintroduces version-pinning the constellation does not want and the friction ADR-048 already rejected. Recursion belongs in the level graph (ADR-045), resolved by witness, not in git topology.
    • Promote every conceptually separable surface (daemon UI, site, web, examples) to its own reporejected: Conceptual separability is not a publication boundary. This proliferates repos with shared remotes/visibility/cadence, multiplying remotes and CI for surfaces that share one boundary — the opposite of ADR-048's minimal topology. The driver is the boundary, not the concept.
    • Make GitHub canonical for the public-facing members (outreach), since the audience lives thererejected: Splits the sovereignty model into special cases and cedes a canonical to a centralized host. The one-way mirror already delivers reach without surrendering authority; a uniform Forgejo-canonical rule is more coherent with the project's sovereignty axioms.
    • Add a fourth top-level category for cross-boundary surfaces like docsrejected: docs is not a new category — it is a Projection whose build fans out to multiple visibility targets. Naming it a sub-shape (multi-boundary) of Projection keeps the taxonomy at three and reuses the existing projection machinery (ADR-046 views, ADR-060 mdbook) instead of inventing a parallel concept.
    + +

    Anti-patterns

    + +
    • Submodules to Mirror Conceptual Recursion — Reaching for git submodules / nested repos to express a member's internal sub-surfaces. A submodule is clone-and-pin, contradicting witness-not-clone; it reifies a logical recursion into git topology and reintroduces version-pinning the constellation does not want.
    • A Repo Because It Is Separable — Promoting a surface to its own git repo because it is conceptually distinct, when it shares the remote, visibility and release cadence of its host. Multiplies remotes and CI for surfaces that share one publication boundary.
    • Letting the Public Mirror Hold Authority — Making GitHub the canonical origin of a public-facing member because the audience lives there. Cedes a canonical to a centralized host and fragments the sovereignty model into special cases.
    + +

    Related ADRs

    + +

    ADR-048 · ADR-054 · ADR-045 · ADR-028 · ADR-013 · ADR-057 · ADR-060 · ADR-046

    diff --git a/site/site/content/adr/en/accepted/adr-062.ncl b/site/site/content/adr/en/accepted/adr-062.ncl new file mode 100644 index 0000000..40200b4 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-062.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-062", + title = "Constellation Member Taxonomy — Primary, Addon, Projection; Recursion in the Level Graph, Not Git; Uniform Forge Rule", + slug = "062", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/062", + graph = { + implements = [], + related_to = ["adr-048", "adr-054", "adr-045", "adr-028", "adr-013", "adr-057", "adr-060", "adr-046"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-063.md b/site/site/content/adr/en/accepted/adr-063.md new file mode 100644 index 0000000..98b08ad --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-063.md @@ -0,0 +1,82 @@ +--- +id: "adr-063" +title: "The Work-Order Governance Unit — Constrain an Agent by an Externally-Owned Contract and a Witnessed Deliverable, Not by Trust" +slug: "063" +subtitle: "Accepted" +excerpt: "A project was stopped because an agent, session after session, skipped the" +author: "ontoref" +date: "2026-06-24" +published: true +featured: false +category: "accepted" +tags: ["adr-063", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    A project was stopped because an agent, session after session, skipped the +project's own guardrails — constraints, tiers, validators — and built runtime +configuration as CLI flags in direct violation of a hard invariant ("NCL config +as source of truth") that was queryable in `describe constraints` the whole time. +The honest diagnosis, reached in the 2026-06-23 role-swap interview, was not "the +agent forgot": it was that a rule written as prose, with no gate that checks it, +is — to a probabilistic agent biased toward visible progress — a suggestion its +sampling is free to skip.

    +

    The failure is structural. Ontoref's knowledge surface is overwhelmingly +read-side (`describe`, `qa show`); information only changes behavior if something +consumes it and blocks. The validators exist as modes the agent may choose to +run, not as gates. The layers with real teeth (compiler, tests, pre-commit, CI, +harness permissions) were never wired to the ontology. And because the agent +writes the NCL, the validators and the hooks, almost every guardrail lives inside +its own editable surface — a lock it can edit in the same gesture it breaks it +(the fox builds the henhouse). A guardrail that does not block is, for an agent +like this, decoration.

    +

    A minimum mechanism is a trap: a minimal gate still depends on the agent +respecting it. The interview converged on a different category of fix — not a +better guardrail but a reusable governance unit whose terms are owned outside the +worker and whose "done" carries a validator's witnessed output instead of the +agent's word. It was demonstrated once (Work Order #0, config-NCL check, three +red-team runs) before being recorded here.

    + +

    Decision

    + +

    Adopt the Work-Order Governance Unit (Spanish: Encargo) as the protocol's pattern +for constraining agent work, recorded as typed glossary terms +(`encargo`, `pliego`, `orden-de-trabajo`, `testigo-verificado` in +`.ontoref/ontology/glossary.ncl`). It separates terms from execution along the +one boundary the worker must not cross:

    +

    - A Statement of Work (SOW · Pliego), the terms — scope (minimal context + + one-sentence objective), a FALSIFIABLE contract (a machine check, schema or + constraint), and the validation mode ('Machine | 'Adversarial, signed or + not). The SOW is authored and owned by a human, OUTSIDE the agent's editable + surface. One SOW authorizes many Work Orders.

    +

    - A Work Order (WO · Orden de Trabajo), the execution — disposable, contained, + owned by the agent: deliverable, an observability envelope, the verified + witness, and a contained verdict ('Accepted | 'Rejected | 'Escalated).

    +

    Two atomic behavioral rules, short and at the point of use, are the whole gate: +(1) no scope + contract, no start; (2) no witness, no done. Where the deliverable +is not machine-checkable (open design), the contract falls back to 'Adversarial — +a second unit whose contract is to refute the first — never to prose.

    +

    This ADR accepts the pattern as the governance APPROACH; the SUBSTRATE stays +unbuilt. Promotion to a typed `Sow`/`WorkOrder` schema and an orchestrator wired +into the daemon/CLI is GATED on a second realized instance beyond Work Order #0 — +specifically a domain Work Order exercising `signed = true` end-to-end — mirroring +the project's standing rule for nascent primitives (bl-036 phase-transition-witness, +gated on a second instance). Accepting the decision activates the constraints below; +it does not declare the substrate built.

    + +

    Constraints

    + +
    • Hard The SOW (scope + contract + validation mode) MUST be authored and owned outside the executing agent's editable surface. A Work Order MUST NOT modify the SOW that invoked it.
    • Hard A Work Order MUST NOT reach 'Accepted without a verified witness carrying the contract's real output (exit code / validator result). A prose claim of completion is inadmissible by itself.
    • Soft A SOW contract MUST be machine-checkable (cmd | schema | constraint). Where the deliverable is open-ended, the validation mode MUST be 'Adversarial (a refuting second unit) — never unverified prose passing as a contract.
    • Soft Promotion of this pattern to a typed Sow/WorkOrder schema or a built orchestrator MUST be gated on a second realized instance beyond Work Order #0 (a domain WO exercising signed witness end-to-end). DISCHARGED 2026-07-04: the second instance is realized — wo-adr001-zero-deps-v2 ran end-to-end through the enforcing run-lifecycle executor (guards block at run start, `verify` derives step status, mode complete verifies the receipt's detached signature), corroborated by an eight-attempt falsification closed only by two out-of-band human signatures. Both promotion targets now exist: the typed schema (code/ontology/schemas/sow.ncl) and the built orchestrator (reflection/modules/run.nu), distributed via migration 0041. The promotion this gate authorized is recorded as ADR-066 (Governed Delivery — the Work-Order Unit Promoted to an Enforcing Executor), Accepted 2026-07-04; the gate no longer holds the pattern Proposed.
    + +

    Alternatives considered

    + +
    • Hooks (PreToolUse) as the core that constrains the agentrejected: A hook needs a guarantor that it is invoked, is provider/version/context dependent, and chains awkwardly with git/CI/linter hooks. It is one more external guard that itself needs a guard — infinite regress, and it lives near the agent's editable surface.
    • VCS (branches/worktrees, protected CI) as the corerejected: With multiple agents on one codebase it degrades into rebase/sync hell; centralized forges can collapse access and reorder priorities; jj+radicle+bare+worktrees multiply the surface. It governs the artifact after the fact, not the act.
    • Signed witness alone as the preconditionrejected: A witness is post-hoc evidence that X happened, not a barrier that forbids not-X (ADR-050). It becomes governance only when a control point refuses to proceed without a valid witness — i.e. inside a unit like the Work Order, not on its own.
    • Keep the constraints as typed prose in core.ncl / CLAUDE.md and rely on the agent reading themrejected: This is the exact failure being recorded: typed-but-unforced is still prose-unforced with better syntax. Loading context is not obeying it; only a unit that gates on a machine-checkable contract changes behavior.
    + +

    Anti-patterns

    + +
    • Selling the Gate's Friction as the Permanent Product — Treating the Work-Order friction as the deliverable — 'ontoref makes your agent comply' — and forgetting it is transitional. This is the forbidden Yang-capture collapse of enforcement-vs-emergence: it repels, and it mistakes the teacher (friction) for the lesson (a form internalized until coherent action feels effortless).
    • The Operator Authoring the SOW It Must Satisfy — The executing agent writes (or edits) the Statement of Work whose contract it then satisfies, collapsing the ownership boundary. The lock is edited in the same gesture it is broken — the unit becomes advisory.
    • Claiming Done Without a Witness — A Work Order is treated as 'Accepted on the agent's prose claim, with no witness carrying the contract's real output. 'Done' degrades back to 'trust my word' — the exact failure the unit exists to forbid.
    • A Non-Machine-Checkable Contract Masquerading as a Gate — A SOW carries a 'contract' that is actually prose ('design the auth well') with no machine check and no 'Adversarial fallback. The unit looks governed but gates nothing — the failure mode wearing the costume of the fix.
    + +

    Related ADRs

    + +

    ADR-066 · ADR-050 · ADR-052 · ADR-056 · ADR-024 · ADR-029 · ADR-031

    diff --git a/site/site/content/adr/en/accepted/adr-063.ncl b/site/site/content/adr/en/accepted/adr-063.ncl new file mode 100644 index 0000000..e5c7a9c --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-063.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-063", + title = "The Work-Order Governance Unit — Constrain an Agent by an Externally-Owned Contract and a Witnessed Deliverable, Not by Trust", + slug = "063", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/063", + graph = { + implements = [], + related_to = ["adr-066", "adr-050", "adr-052", "adr-056", "adr-024", "adr-029", "adr-031"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-064.md b/site/site/content/adr/en/accepted/adr-064.md new file mode 100644 index 0000000..aa224f1 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-064.md @@ -0,0 +1,74 @@ +--- +id: "adr-064" +title: "The Authoring/Knowledge Door (Funnel Re-Architected to Four) and the Content–Vehicle Convergence Membrane" +slug: "064" +subtitle: "Accepted" +excerpt: "The difusión vestibule (ADR-057) declared a marketing funnel of THREE doors in" +author: "ontoref" +date: "2026-06-24" +published: true +featured: false +category: "accepted" +tags: ["adr-064", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    The difusión vestibule (ADR-057) declared a marketing funnel of THREE doors in +`.ontoref/positioning/spine.ncl`: developer, infrastructure, personal — a curated +audience partition with a three-beat rhetorical architecture (the sub_hero triad +"Tu proyecto, tu infra, tu vida", the A–E narrative).

    +

    The CLI-domain catalog (`code/domains/`, ADR-012) is a different axis: five domains +(framework, personal, provisioning, knowledge-works/librosys, rustelo), each +activated when a project's repo_kind matches. Mapping the five domains' audiences +onto the doors, four are covered (framework→developer, rustelo→developer, +provisioning→infrastructure, personal→personal). ONE is not: knowledge-works' — +authors, editors and knowledge-workers who diffuse an authored Work.

    +

    That audience is real and distinct: not a developer (a developer BUILDS the +publishing vehicle, an author AUTHORS the content), not private PKM (the personal +door). It deserves a front door of its own. Adding it is not a data append: the +funnel's rhetoric is built on three beats, so a fourth door re-architects the +hero/sub_hero/narrative to four — a deliberate positioning-surface change.

    +

    Two further facts shape the edges. rustelo is NOT a door (its audience is developer) +and is moreover the publishing/diffusion VEHICLE while knowledge-works is the +CONTENT — they converge at "publish & diffuse". And a developer can also be an +author, so the developer and authoring audiences overlap at a seam rather than +partition cleanly.

    + +

    Decision

    + +

    The marketing funnel is re-architected to FOUR doors: +developer · infrastructure · authoring · personal.

    +

    The fourth door — authoring/knowledge — is added to `spine.ncl`, with the funnel + rhetoric updated to four beats (sub_hero "Tu proyecto, tu infra, tu obra, tu vida"; + the narrative's door table and headings). The door references a real audience + (`audiences/audience-authoring-knowledge.ncl`, status 'Hypothesis) and value-prop + (`value-props/vp-009-the-whole-work-everywhere.ncl`, status 'Draft), with the same + `live_view` / `graph_focus` reveal rungs as the other doors (ADR-057). Its domain + is knowledge-works (librosys).

    +

    rustelo does NOT get a door. A RusteloApp builder is a developer and enters through + the developer door; a rustelo door would duplicate the developer audience.

    +

    The rustelo↔knowledge-works convergence (vehicle↔content) is modeled as the + `content-vehicle-convergence` gate membrane in `.ontoref/ontology/gate.ncl` + (permeability 'High, protocol 'Absorb). The membrane HOLDS both poles + (developer/author, vehicle/content) and favors the developer-author crossing + rather than collapsing the doors into one or denying the synergy — the ondaod + non-collapse, and the synergy axis distinct from the audience (door) axis.

    +

    Result: four marketing doors, five domains, one membrane. No shipped protocol +surface changes (positioning + project ontology only) — no migration.

    + +

    Constraints

    + +
    • Hard The difusión funnel in spine.ncl MUST include the authoring/knowledge door as a first-class audience entry (four doors total: developer, infrastructure, authoring, personal), referencing a real audience id and value-prop id. The funnel rhetoric (sub_hero, narrative) MUST be coherent at four — no 3-in-prose / 4-in-data split.
    • Hard The rustelo↔knowledge-works convergence (vehicle↔content) MUST be modeled as a gate membrane in gate.ncl. It MUST NOT be expressed by opening a rustelo door nor by merging the developer and authoring doors.
    • Soft The authoring door SHOULD route to a real value-prop (vp-009) and reveal rungs, never argue the core — the same reach-vs-qualification discipline as the other three doors.
    + +

    Alternatives considered

    + +
    • Keep the funnel at three; treat authoring as audience+value-prop only, not a doorrejected: The authoring audience is genuinely uncovered and distinct (not developer, not PKM); leaving it doorless routes it nowhere. A front door is the honest answer for a real audience — the funnel is re-architected to four to carry it.
    • Open a fifth door for rustelo too (one door per domain)rejected: rustelo's audience IS developer; a rustelo door duplicates it and inflates the funnel. Doors track uncovered audiences, not the domain count.
    • Merge developer and authoring into one 'builder/author' doorrejected: Collapses the developer↔author / vehicle↔content Spiral toward one pole and loses routing precision. The convergence is better expressed as a membrane between two doors than as one fused door.
    • Model the rustelo↔knowledge-works synergy as a door or as proserejected: A door mis-models a synergy on the audience axis; prose drifts and is not queryable. The gate membrane is the typed mechanism for controlled cross-boundary exchange.
    + +

    Anti-patterns

    + +
    • Bolting a Fourth Door onto a Three-Beat Funnel — The authoring door is appended to the doors array as data while the funnel rhetoric (sub_hero triad, narrative, comments) stays at three — leaving the spine internally inconsistent (4 in data, 3 in prose). A door is added without re-architecting the rhetoric to four.
    • The Authoring Door Arguing the Core — The authoring door is filled with what knowledge-works can DO (commands, formats) instead of routing the author audience to a value-prop — the reach-vs-qualification trap, now on a fourth door.
    • Modeling the Synergy as a Door Instead of a Membrane — The rustelo↔knowledge-works convergence is expressed by opening a rustelo door, or by merging the developer and authoring doors — collapsing the developer↔author / vehicle↔content Spiral onto the audience axis instead of holding it as a controlled exchange.
    + +

    Related ADRs

    + +

    ADR-057 · ADR-059 · ADR-035 · ADR-043 · ADR-012

    diff --git a/site/site/content/adr/en/accepted/adr-064.ncl b/site/site/content/adr/en/accepted/adr-064.ncl new file mode 100644 index 0000000..f58108b --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-064.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-064", + title = "The Authoring/Knowledge Door (Funnel Re-Architected to Four) and the Content–Vehicle Convergence Membrane", + slug = "064", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/064", + graph = { + implements = [], + related_to = ["adr-057", "adr-059", "adr-035", "adr-043", "adr-012"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-065.md b/site/site/content/adr/en/accepted/adr-065.md new file mode 100644 index 0000000..90d0ebb --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-065.md @@ -0,0 +1,84 @@ +--- +id: "adr-065" +title: "The Door/Domain Surface Is Served by a Dedicated `dominios` Content-Kind, Not the Reused `projects` Kind" +slug: "065" +subtitle: "Accepted" +excerpt: "ADR-057 built the difusión vestibule as doors projected from `spine.ncl`, and" +author: "ontoref" +date: "2026-06-24" +published: true +featured: false +category: "accepted" +tags: ["adr-065", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    ADR-057 built the difusión vestibule as doors projected from `spine.ncl`, and +ADR-064 re-architected the funnel to four doors over five domains plus the +content-vehicle-convergence membrane. The SITE that renders this +(`outreach/site`, a rustelo-framework SSR app) does not yet carry that model.

    +

    The site mounts the doors on the rustelo framework's `projects` content-kind: +`scripts/build/gen-spine-pages.nu` emits each door as a post of the EXISTING +`projects` kind, served at `/projects/<key>` · `/proyectos/<key>`. Its own header +(lines 6–8) documents WHY: "Mounting on an already-compiled kind" avoids the +ContentKind codegen boundary. That was a deliberate shortcut to ship without a +rustelo rebuild.

    +

    The shortcut leaves a permanent name↔thing drift. In ontoref these are NOT +projects — they are DOMAINS (the `code/domains/` axis, ADR-012). Yet the surface is +structurally `projects` end-to-end: route `/projects`, `content_type: "projects"` +in the generated index, directory `content/projects/`, view `ProjectsCTAView`, the +`projects-*` i18n strings (whose default copy is "Open source projects and +platforms"). The framework's own demo portfolio (kogral, stratumiops, vapora…) +shares the kind, conflating ontoref's domains with an author portfolio.

    +

    Three concrete drifts follow: the site renders three doors (the fourth, authoring, +is missing since ADR-064), names the surface "projects", and surfaces the membrane +nowhere.

    +

    The cost is known and verified: routing a new/renamed kind crosses rustelo's +build-time codegen (`rustelo_tools/src/build/build_tasks/` consumes +`content-kinds.toml` + `routes.ncl`, baked into `rustelo-htmx-server`). The content +registry is dynamic (`ContentKindRegistry { kinds: HashMap }`) but routing is not. +A vestigial `doors` kind already exists in `content-kinds.ncl` reusing +`ProjectsCTAView`, so no new Rust view is required — only the boundary crossing +(a rustelo rebuild + reinstall of the `~/.local/bin` binaries).

    + +

    Decision

    + +

    The door/domain surface is served by a dedicated `dominios` content-kind, replacing +the reused `projects` kind. This reverses the gen-spine-pages.nu "reuse the +already-compiled projects kind" shortcut, accepting the rustelo rebuild as the cost +of an honest surface.

    +

    Kind key: `dominios` (the project's settled vocabulary). Route `/dominios` for + both languages (a brand term, not localized; `/domains` as an EN route is a + deferred sub-choice). The kind reuses `ProjectsCTAView` (no new Rust view); it is + registered in `content-kinds.{toml,ncl}`, `content.ncl`, and `routes.ncl`, then + activated by a rustelo rebuild + reinstall.

    +

    The `dominios` content is PROJECTED from `spine.ncl` by `gen-spine-pages.nu` — all + FOUR doors (developer, infrastructure, personal, authoring) into + `content/dominios/<lang>/`, served at `/dominios/<key>`; the spine hrefs + (`/projects/*` → `/dominios/*`) update in `spine.ncl`, `home_spine.ftl`, `home.j2`.

    +

    The membrane (content-vehicle-convergence, ADR-064: librosys↔rustelo) is surfaced + on `/dominios` as a NON-door block — five domains, four doors, one membrane — so + the asymmetry is visible, never flattened into a fifth door card.

    +

    The framework `projects` kind is decoupled from ontoref's domains. Whether the + legacy portfolio (kogral, stratumiops…) is removed or kept as a separate + author-portfolio surface is a follow-on decision, not part of this ADR.

    +

    This touches the rustelo framework (a separate vehicle codebase), NOT ontoref's +shipped protocol surface (code/ontology/schemas, install/templates) — no ontoref +migration.

    + +

    Constraints

    + +
    • Hard The door/domain diffusion surface MUST be served by a dedicated `dominios` content-kind (route `/dominios`), not the reused `projects` kind. The generated content MUST NOT carry `content_type: "projects"` for the doors.
    • Hard The `dominios` content MUST be projected from `spine.ncl` by gen-spine-pages.nu (all four doors), never hand-authored. The generated files MUST carry the GENERATED banner so drift checks catch hand edits.
    • Soft The content-vehicle-convergence membrane (librosys↔rustelo) SHOULD be surfaced on `/dominios` as a non-door block. The site MUST NOT render rustelo as a fifth door nor merge developer and authoring into one door — preserving four doors over five domains.
    + +

    Alternatives considered

    + +
    • Relabel only — reuse the `projects` kind, rename the visible strings to Dominios/Domainsrejected: Restart-cheap but dishonest: route, content_type, directory and view stay `projects`. The name↔thing drift the decision exists to remove would persist in the structure a consumer actually reads.
    • Keep reusing `projects` as-is (the status quo shortcut)rejected: The shortcut's own rationale was 'avoid the rebuild', not 'projects is the right model'. With the domain model now settled (ADR-064), the structural lie is no longer acceptable.
    • Rename the kind key `dominios` but localize the route as en=`/domains`, es=`/dominios`rejected: Defensible for bilingual consistency, but `dominios` is the project's chosen brand term for the surface; a single canonical route avoids a split. Kept as a deferred sub-choice, not a blocker.
    • Author the `dominios` content by hand instead of projecting it from spine.nclrejected: Re-creates the exact hand-maintained drift this plan was opened to fix (Layer 1). The generator already projects the doors; it must point at the new kind, not be bypassed.
    + +

    Anti-patterns

    + +
    • Relabeling the Strings While the Structure Stays `projects` — The visible i18n is renamed to Dominios while the route, content_type, directory and view stay `projects`. The surface reads 'Dominios' but every machine-facing layer still says projects — the drift is hidden, not removed.
    • Hand-Authoring the Dominios Content — The dominios door pages are written by hand instead of projected from spine.ncl, re-creating the Layer-1 hand-maintained drift the plan was opened to fix.
    • Flattening the Membrane into a Fifth Card — The site renders five domain cards (rustelo among them) or merges developer and authoring, collapsing the 4-doors/5-domains asymmetry and re-modeling the synergy on the audience axis — the ADR-064 anti-pattern, now on the site.
    + +

    Related ADRs

    + +

    ADR-064 · ADR-057 · ADR-012 · ADR-035

    diff --git a/site/site/content/adr/en/accepted/adr-065.ncl b/site/site/content/adr/en/accepted/adr-065.ncl new file mode 100644 index 0000000..0e428c9 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-065.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-065", + title = "The Door/Domain Surface Is Served by a Dedicated `dominios` Content-Kind, Not the Reused `projects` Kind", + slug = "065", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/065", + graph = { + implements = [], + related_to = ["adr-064", "adr-057", "adr-012", "adr-035"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-066.md b/site/site/content/adr/en/accepted/adr-066.md new file mode 100644 index 0000000..639218c --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-066.md @@ -0,0 +1,104 @@ +--- +id: "adr-066" +title: "Governed Delivery — the Work-Order Unit Promoted to an Enforcing Executor: the Check Decides, Never the Reporter" +slug: "066" +subtitle: "Accepted" +excerpt: "ADR-063 accepted the Work-Order Governance Unit as an APPROACH and explicitly" +author: "ontoref" +date: "2026-07-04" +published: true +featured: false +category: "accepted" +tags: ["adr-066", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    ADR-063 accepted the Work-Order Governance Unit as an APPROACH and explicitly +gated its promotion to a built substrate on a second realized instance +exercising signed witnesses end-to-end. That gate is now discharged twice +over: hand-wired instances in the provisioning constellation (its ADR-050, +signed L1/L3 receipts), and — decisive — a falsación run on ontoref itself +(2026-07-04, `.coder/2026-07-04-governed-delivery-falsacion.done.md`) that +drove the ACTUAL mode-DAG mechanism through eight refutation attempts.

    +

    Before this work, the executor did not enforce what the mode narrated — the +exact failure shape ADR-063 records, reproduced one level down, inside the +enforcement machinery itself: `'Block` guards only printed WARN (`run start` +never evaluated them, contradicting both ADR-011's decision text and the +schema's own comment); `steps[].verify` was dead code in BOTH executors — +`step report --status pass` was taken on faith even on steps declaring a +check, so an agent could report `witness-pre pass` without invoking the +witness; `postconditions` were prose `mode complete` never read, while the +mode's own step text claimed a signature verification that did not exist; +committer-identity ratification could not distinguish actors sharing one git +config. The falsación found three more defects the reviews had missed: a +contract check that vacuous-passed from any wrong cwd (`! grep` on a missing +file exits 0), exempt-matching by substring that exempted unrelated +violations, and first-wins step reads that made 'resolve and re-run this +step' structurally impossible.

    + +

    Decision

    + +

    Promote governed delivery to a built, enforcing substrate. The semantic core, +frozen as three design gates before executor work started:

    +

    ONE RULE ABOVE ALL — where a machine check exists, the check decides; + a reporter's word can refuse to be contradicted but never override.

    +

    - Run variables (⛓G-1): a mode declares `vars_required`; `run start` + receives each as a KEY=VALUE argument, refuses to start when one is + missing, persists them in run.json; the executor substitutes exactly the + declared `${KEY}` occurrences in guard/verify/postcheck commands — + undeclared `${…}` passes through to the shell untouched.

    +

    - Guards are preconditions: evaluated once at `run start`, post- + substitution, fail-closed — a failing `'Block` guard aborts before any + run state exists; `'Warn` prints and continues. This makes ADR-011's + "'Block aborts execution" true for the run/step-report path.

    +

    - Verify decides (⛓G-2): a step declaring `verify` has its status DERIVED + from the check's exit code — the executor runs it; an explicit --status + contradicting the derived one is refused with nothing recorded; `skip` + on an executed verify is refused; `cmd` is informational and never + executed by `step report`. A verify still carrying a bare `{word}` form + placeholder after resolution is form-scoped: not runnable in that + context, explicit --status required. steps.jsonl records `status_source` + (`verify` / `reported` / `reported-form-scoped-verify`) so the audit + trail states who decided every status.

    +

    - Completion is machine-checked: `postchecks | Array { desc, cmd }` run at + `mode complete`, fail-closed, each failure a named blocker; an + incomplete run exits 1 (CI can gate on it). `postconditions` remains + human prose. The latest report of a step supersedes earlier ones — + matching the executor's own 'resolve and re-run' instruction.

    +

    - Every signature is a separate, human-executed, out-of-band act (⛓G-3): + ratification is a detached minisign signature over the SOW, verified + against `.governance/witness.pub` (data in the governed repo, not env) — + never committer identity; the receipt gets a second detached signature; + the witness NEVER signs its own output; no schema ever carries an + embedded signature field (a file cannot reference a signature computed + over its own bytes including that reference).

    +

    - The witness is honest about location and exemption: contract checks run + with cwd pinned to the governed repository root (the parent of the + `.governance/` the SOW lives in), so a relative path can never vacuous- + pass from an unrelated cwd; an exempt-bearing contract must emit one + violating path per stdout line and exemption subtracts exact paths — the + verdict is `exempt` only when EVERY violation is exempt.

    +

    Distribution rides the EXISTING adoption channel, no new mechanism: the + mode ships in the installed reflection tree; the witness stays single- + source in the code repo and is PROJECTED by install into + `reflection/bin/witness.nu` (one source, frontier inside the build — never + vendored into consumer repos); SOW validation is an opt-in contract + (`ontology/schemas/sow.ncl`); migration 0041 (protocol 0.1.17 → 0.1.18) + carries adoption, its check passing for projects that never adopt.

    + +

    Constraints

    + +
    • Hard A step declaring `verify` MUST have its status derived from the check's exit code by the executor; an explicit --status contradicting the derived one MUST be refused with nothing recorded.
    • Hard `'Block` guards MUST be evaluated at `run start`, post-substitution, and a failure MUST abort before any run state exists. A WARN-only evaluation of a 'Block guard is a regression to narrated enforcement.
    • Hard No script, witness, or executor may sign artifacts; every signature is a human act in the human's own shell, verified as a detached sidecar against a public key stored as data in the governed repository. No schema may carry an embedded signature field.
    • Hard Contract checks MUST run with cwd pinned to the governed repository root, and SOW checks SHOULD assert file existence before inverting a match on it — a check that can pass vacuously from a wrong cwd is not a contract.
    • Soft An exempt-bearing contract MUST emit one violating path per stdout line; exemption subtracts exact paths and the verdict is `exempt` only when every violation is exempt. Substring matching against check output is forbidden.
    • Soft The witness ships single-source from the code repository, projected by the install pipeline into the shipped reflection tree. It MUST NOT be vendored by copy into consumer repositories.
    + +

    Alternatives considered

    + +
    • Ratification by git committer identity (the original guard)rejected: Cannot distinguish actors sharing one git config — the exact environment agents run in. A detached signature against a key the agent provably cannot drive is actor separation by cryptography, not by configuration.
    • Keep --status authoritative and treat verify as advisory documentationrejected: That is the pre-existing state, disproven live: an agent can and did report a gate step it never executed. Advisory checks are ADR-063's 'typed-but-unforced is still prose-unforced' one level down.
    • Evaluate guards on every step report (where the old WARN loop lived)rejected: Guards are preconditions; re-running them N times per run is the wrong lifecycle point, costs N shell executions, and still gated nothing. Once at run start, before any run state exists, is where refusal is cheap and clean.
    • Embed the signature (or a signature field) in the SOW/receipt schemarejected: A file cannot correctly reference a signature computed over its own bytes including that reference. Detached sidecars keep signing fully out-of-band and survive byte-exact restores — proven in falsación F3/F7.
    • A new distribution channel (per-project vendoring of mode+witness)rejected: Vendor-by-copy is the drift the constellation already forbids (provisioning ADR-046/050, §7.4). The installed reflection tree already reaches every consumer; the witness is projected from one source at install time — no copy to drift.
    • First-wins step records (the pre-existing behavior)rejected: Made the executor's own instruction ('resolve and re-run this step') structurally impossible — a failed step blocked its run forever. Latest-wins matches the instruction and preserves the full fail→pass history in the append-only log.
    + +

    Anti-patterns

    + +
    • Prose Claiming a Check the Executor Does Not Perform — A schema comment, step action text, or postcondition asserts that something is verified ('Block aborts', 'mode complete verifies the signature') while no code path performs it. The narration reads as safety while gating nothing — the ADR-063 failure reproduced inside the enforcement machinery.
    • A Contract Check That Passes for the Wrong Reason — A check whose failure mode is indistinguishable from success — `! grep` on a file the cwd does not contain, a test against a path that silently does not exist. The receipt records 'pass' while nothing was examined.
    • One Exemption Silencing Unrelated Violations — Exemption matched by substring against the whole check output marks the entire contract exempt when any exempt path appears — real violations ride out under a neighbor's exemption.
    • A Self-Reported Human Act Whose Effect Nothing Checks — Human-actor steps are self-reported by necessity; the design stays sound only because their EFFECT is machine-verified downstream. A mode whose human step has no subsequent verify or postcheck recreates done-by-assertion for exactly the acts that matter most.
    + +

    Related ADRs

    + +

    ADR-063 · ADR-011 · ADR-050 · ADR-052 · ADR-056 · ADR-010

    diff --git a/site/site/content/adr/en/accepted/adr-066.ncl b/site/site/content/adr/en/accepted/adr-066.ncl new file mode 100644 index 0000000..f1f743f --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-066.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-066", + title = "Governed Delivery — the Work-Order Unit Promoted to an Enforcing Executor: the Check Decides, Never the Reporter", + slug = "066", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/066", + graph = { + implements = [], + related_to = ["adr-063", "adr-011", "adr-050", "adr-052", "adr-056", "adr-010"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-067.md b/site/site/content/adr/en/accepted/adr-067.md new file mode 100644 index 0000000..b88fc2e --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-067.md @@ -0,0 +1,83 @@ +--- +id: "adr-067" +title: "Positioning Authorship Follows Evidence-ADR Sovereignty — A Subject Is Authored Where Its Anchoring ADRs Are Accepted; Peers Reference It by Role, Never Duplicate" +slug: "067" +subtitle: "Accepted" +excerpt: "ADR-035 established positioning as a queryable protocol surface and ADR-043" +author: "ontoref" +date: "2026-07-04" +published: true +featured: false +category: "accepted" +tags: ["adr-067", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    ADR-035 established positioning as a queryable protocol surface and ADR-043 +modelled it as four elements (WHAT / FOR WHOM / HOW bound by the Proof seam), +governed by the five coherence rules of `ore positioning audit` — the first of +which, AnchorDrift, requires that every `evidence_adr` cited by a Differentiator +or Proof resolves to an Accepted ADR. Both ADRs were authored when the +positioning surface had exactly one occupant: ontoref itself. In a single-project +surface, "Accepted" silently meant "Accepted in this project", and the question +of WHOSE ADR set AnchorDrift resolves against never had to be asked.

    +

    That assumption broke the moment a second subject appeared. The rustelo framework +is positioned two ways at once: (1) inside ontoref's own positioning it is a +REFERENCED ROLE — "the publishing vehicle" at the content-vehicle-convergence +membrane (vp-009, audience-authoring-knowledge, ADR-064) — and (2) as a framework +with its own market position against Cot / Axum / Leptos it is a SUBJECT with its +own Differentiator, Competitor, Audience and ValueProp. The subject's differential +value is anchored by rustelo's ADRs (adr-001 build-time codegen, adr-002 custom +routing, adr-003 layered override, adr-006 rendering profile) — which are Accepted +only in rustelo's ADR catalogue, not ontoref's.

    +

    Working the placement through surfaced the load-bearing fact: a rustelo +Differentiator authored inside ontoref's `.ontoref/positioning/` would FAIL +AnchorDrift, because ontoref does not own those ADRs. Authored inside rustelo's +own `.ontoref/positioning/`, the same subject passes — verified: `ore positioning +audit` run from rustelo reports `adr-drift: none`. The audit already resolves +`evidence_adrs` against the AUTHORING project's ADR set; the framework simply +never named where that places a subject. This is the same authority-upstream / +reference-not-duplicate principle ADR-028 established for the ontology layer +(a project consumes ontologies by reference and cannot mutate them) and that the +`.rustelo.ontoref/` domain marker and the rustelo publishing-contract already +manifest — now made explicit for the positioning surface.

    + +

    Decision

    + +

    Positioning authorship follows evidence-ADR sovereignty. State it as a protocol +rule over the ADR-043 surface:

    +

    A positioning SUBJECT — a Differentiator, Competitor, ValueProp, or Proof whose + admissibility depends on `evidence_adrs` — is authored in the `.ontoref/ + positioning/` of the project whose ADR catalogue Accepts those ADRs. AnchorDrift + resolves `evidence_adrs` against the AUTHORING project's ADR set; therefore the + authoring project is fixed by ADR sovereignty, not by editorial convenience.

    +

    A peer project may REFERENCE that subject only by ROLE — a prose mention or an + id in one of its own authored records (e.g. ontoref's vp-009 naming rustelo as + "the publishing vehicle") — and MUST NOT re-author the peer's typed subject in + its own tree. The reference carries the relationship; the subject stays sovereign.

    +

    This generalises the single-project AnchorDrift assumption of ADR-043 to a +multi-project positioning surface without adding a kind or a schema field. It is +additive and opt-in (ADR-029): a project with no `positioning/` directory, or one +that references no peer, pays zero adoption cost, and every existing positioning +file validates unchanged. The shared `make_*` helpers are imported by the +authoring project (rustelo imports ontoref's `code/ontology/defaults/positioning.ncl` +via its declared `nickel_import_paths`), so the schema is authored once in ontoref +and consumed by every positioning surface — the same producer/consumer split as +the rustelo publishing-contract.

    + +

    Constraints

    + +
    • Hard A positioning subject that cites `evidence_adrs` MUST be authored in the `.ontoref/positioning/` of the project whose ADR catalogue Accepts those ADRs. `ore positioning audit`, run from that project, MUST report no AnchorDrift for the subject. Placing such a subject in a project that does not Accept its evidence ADRs is a drift finding, not a stylistic choice.
    • Soft A project MUST reference a peer's positioning subject only by ROLE — a prose mention or an id in its OWN authored record — and MUST NOT re-author the peer's typed subject in its own tree. Cross-references between subjects are by id (competitor_ids, differentiator_ids), never by copied record.
    • Soft The sovereignty rule MUST stay additive and opt-in: a project with no `positioning/` directory, or one that references no peer subject, MUST validate unchanged and pay zero adoption cost. The rule adds no kind, folder, or required field to ADR-043's surface.
    + +

    Alternatives considered

    + +
    • Author rustelo's positioning subject inside ontoref's positioning layer (single-layer, all subjects in one place)rejected: Chosen first for expedience, then rejected on a mechanical basis: the subject's evidence_adrs (rustelo's adr-001/002/003/006) are not Accepted in ontoref's catalogue, so AnchorDrift fails. Placing the subject where its ADRs are not sovereign is not a style choice the audit tolerates — it is a hard drift finding.
    • Duplicate the subject in both projects (nivel-0 and nivel-1 copies kept in sync)rejected: Unsound, not merely redundant. The ontoref copy would have to strip evidence_adrs (unanchored, rejected by _diff_requires_anchor) or cite rustelo ADRs it cannot Accept (AnchorDrift failure). Duplication also reintroduces the drift the AnchorDrift rule exists to prevent — two records that can diverge with no single sovereign truth.
    • Add a cross-project evidence_adr resolver so a subject in project A can cite Accepted ADRs from project Brejected: Would let a host author a peer's subject by reaching into the peer's ADR catalogue — dissolving sovereignty rather than respecting it, and coupling the audit to a cross-project fetch it does not need. The reference-by-role rule achieves the same visibility (the relationship is expressed) without importing the peer's evidence, and stays faithful to ADR-028's consume-by-reference-cannot-mutate model.
    + +

    Anti-patterns

    + +
    • Authoring a Peer's Positioning Subject in the Host's Tree — A contributor places project B's Differentiator/ValueProp inside project A's `.ontoref/positioning/` to 'own the story in one place'. Its evidence_adrs are Accepted in B, not A, so `ore positioning audit` from A reports AnchorDrift — the subject is homeless, not centralised.
    • Duplicating a Subject Across Projects and Syncing by Hand — The same Differentiator is copied into both the sovereign project and a peer to keep each surface 'complete'. The copies drift, and the peer copy must either strip its anchor (unanchored, rejected) or cite un-Acceptable ADRs (AnchorDrift). Two records, no single sovereign truth — the exact drift AnchorDrift exists to prevent.
    • Reaching Into a Peer's ADR Catalogue to Anchor a Local Subject — To author a peer's subject locally, a contributor adds a resolver so evidence_adrs can cite a peer project's Accepted ADRs. This dissolves ADR sovereignty — the host now anchors claims on decisions it did not make and cannot govern — the opposite of ADR-028's consume-by-reference-cannot-mutate model.
    + +

    Related ADRs

    + +

    ADR-043 · ADR-035 · ADR-028 · ADR-029 · ADR-055

    diff --git a/site/site/content/adr/en/accepted/adr-067.ncl b/site/site/content/adr/en/accepted/adr-067.ncl new file mode 100644 index 0000000..c015cb7 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-067.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-067", + title = "Positioning Authorship Follows Evidence-ADR Sovereignty — A Subject Is Authored Where Its Anchoring ADRs Are Accepted; Peers Reference It by Role, Never Duplicate", + slug = "067", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/067", + graph = { + implements = [], + related_to = ["adr-043", "adr-035", "adr-028", "adr-029", "adr-055"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-068.md b/site/site/content/adr/en/accepted/adr-068.md new file mode 100644 index 0000000..ba575cd --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-068.md @@ -0,0 +1,87 @@ +--- +id: "adr-068" +title: "Declarative Constellation Surface and the Reconciling Sync Mode — Reversible Auto, Human-Gated Remotes" +slug: "068" +subtitle: "Accepted" +excerpt: "ADR-062 fixed the constellation TAXONOMY (Primary | Addon | Projection) and the" +author: "ontoref" +date: "2026-07-05" +published: true +featured: false +category: "accepted" +tags: ["adr-068", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    ADR-062 fixed the constellation TAXONOMY (Primary | Addon | Projection) and the +forge rule (Forgejo canonical · GitHub one-way mirror · rad sovereign · vault +never mirrored) as prose plus Soft/Hard constraints on ontoref-self's own repos. +What it did NOT provide is a mechanism: how a project DECLARES its constellation +and how the declared topology is RECONCILED against the filesystem — for first +setup and, equally, for updating an existing partial constellation.

    +

    The gap surfaced concretely. ADR-013 already ships a per-project `vcs` block +(manifest.ncl: primary/direction/mirrors) and a `vcs.nu` module (push --mirror, +cross-project catalog, drift status) plus the `adopt_repo_distribution` mode — +but all per-PROJECT. The constellation has multiple members, most of them NOT +individually onboarded (outreach, vault, desktop are plain repos or not yet +repos), so per-member manifests do not exist to aggregate. CI (Woodpecker) and +the private dev-internal overlay (.coder/.claude, ADR-062 +dev-internal-overlay-never-mirrored) were declarable nowhere at the constellation +level. And the operator-facing question — "given the declared topology, what is +done and what must I still do?" — had only hand-written runbooks as an answer.

    +

    The forces: a reconciler must (a) read a single declared source of the whole +constellation, (b) converge the parts that are cheap to reverse without a human +in the loop, yet (c) never silently perform the parts that are expensive to +reverse or external — creating a Forgejo repo, pushing, wiring a mirror. A mode +that created repos and pushed on its own would be convenient and wrong: it would +mutate remotes from an inferred plan, exactly the failure `governed-delivery` +(ADR-041) was built to prevent for Work Orders.

    + +

    Decision

    + +

    Add a declarative constellation surface and a reconciling mode that applies the +reversible half itself and emits the irreversible half to a human.

    +

    DECLARATION (the single editable source of the topology) + - reflection/schemas/constellation.ncl — a member is {name, path, category + (Primary|Addon|Projection), canonical, sovereign+rid, mirror (Auto|Never), + ci (None|Woodpecker|GithubActions), overlay, overlay_url}. The forge rule is + a TYPED contract, not prose: a 'Projection owns no repo (canonical_url / + mirror_url must be empty), a 'Never member carries no mirror, a 'Radicle + sovereign requires a rid. Illegal combinations fail at nickel-export time. + - .ontoref/ontology/constellation.ncl — the project's instance. Editing it is + how the topology changes; the mode is how the change is applied.

    +

    RECONCILER (reflection/bin/constellation.nu — offline, install-independent) + - plan — per member, declared-vs-actual + the [auto]/[you] next actions. + - apply-auto — applies ONLY the reversible half: sets up the private overlay + (never mirrored) for members that declare one and have content. + Never inits a repo, never touches a remote. + - check [--auto-only] — exit 1 on drift; --auto-only gates the reversible half. + It NEVER mutates a remote. Server-side facts (does the forge repo exist? is the + push-mirror wired?) are REPORTED as pending human steps, never asserted.

    +

    MODE (reflection/modes/constellation-sync.ncl) + guard(declaration parses) → plan(report) → apply_overlays(reversible, agent) → + emit_remote_steps(actor = 'Human: create repo / push / wire mirror). A + postcheck gates that the reversible overlay job is done; full convergence + includes the human steps and the mode is re-run idempotently to reconverge.

    +

    BOUNDARY (the load-bearing rule). The mode's automated steps are exactly the +reversible ones. Every remote-creating or remote-pushing action is actor 'Human +and emitted as instruction — the same human-gated boundary as governed-delivery's +witnessed signing. Adoption is opt-in: a single-repo project declares nothing and +the mode is inert.

    + +

    Constraints

    + +
    • Soft A constellation's topology is a declared source (.ontoref/ontology/constellation.ncl conforming to the Constellation schema), not inferred from the filesystem.
    • Hard The forge rule (Projection owns no repo, Never carries no mirror, Radicle needs a rid) is enforced by a typed schema contract that fails nickel-export on violation, not by prose.
    • Hard The constellation-sync mode never creates a repo or pushes to a remote in an automated step; every remote-creating/pushing action is actor 'Human and emitted as instruction.
    • Soft The reconciler (constellation.nu) inspects git remotes/dirs locally and reports server-side facts as pending; it issues no remote-mutating git command.
    + +

    Alternatives considered

    + +
    • Extend adopt_repo_distribution to iterate over members.rejected: Conflates two responsibilities: adopt_repo_distribution declares ONE member's backend; constellation-sync reconciles the WHOLE constellation and emits human remote steps. Growing one mode to do both muddies its contract; a separate mode keeps each single-purpose.
    • A full-auto mode that creates Forgejo repos and pushes via API tokens.rejected: Mutating remotes from an inferred plan is exactly the failure governed-delivery (ADR-041) prevents. Creating a repo and pushing are expensive to reverse and external; automating them trades a real sovereignty/safety risk for convenience. Human-gated emission preserves the boundary.
    • Keep the pattern as static runbooks / a QA entry only.rejected: Docs cannot enforce the forge rule or report per-member drift. The typed contract + reconciler make the rule executable and the state queryable; the runbook survives as the mode's template payload, not the mechanism.
    • Infer the constellation by scanning the filesystem for repos.rejected: Inference cannot express intent (a member declared but not yet materialized, a Projection that must NOT become a repo, a vault that must NOT be mirrored). A declaration carries intent the filesystem lacks.
    + +

    Anti-patterns

    + +
    • A Sync Mode That Creates Repos and Pushes On Its Own — Letting a reconciling mode create Forgejo repos, push, or wire mirrors from an inferred plan for convenience. Mutates external, expensive-to-reverse state without a human deciding — the failure governed-delivery prevents for Work Orders, reintroduced at the constellation layer.
    • Reconstructing the Constellation From the Filesystem — Deriving the constellation's membership and forge policy by scanning for repos instead of reading a declaration. Loses intent: a member declared but not yet materialized, a Projection that must NOT be a repo, a vault that must NOT be mirrored — none are inferable from the tree.
    • Merging the Member Backend and the Constellation Policy — Treating the per-project manifest `vcs` block (ADR-013, a member's working backend) and the constellation declaration (this ADR, the constellation's forge policy) as one surface. They describe different scopes; collapsing them re-couples a member's release cadence to the constellation topology.
    + +

    Related ADRs

    + +

    ADR-062 · ADR-048 · ADR-013 · ADR-041 · ADR-028 · ADR-054 · ADR-026 · ADR-014

    diff --git a/site/site/content/adr/en/accepted/adr-068.ncl b/site/site/content/adr/en/accepted/adr-068.ncl new file mode 100644 index 0000000..62252e5 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-068.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-068", + title = "Declarative Constellation Surface and the Reconciling Sync Mode — Reversible Auto, Human-Gated Remotes", + slug = "068", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/068", + graph = { + implements = [], + related_to = ["adr-062", "adr-048", "adr-013", "adr-041", "adr-028", "adr-054", "adr-026", "adr-014"], + }, +} diff --git a/site/site/content/adr/en/accepted/adr-069.md b/site/site/content/adr/en/accepted/adr-069.md new file mode 100644 index 0000000..caa7624 --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-069.md @@ -0,0 +1,93 @@ +--- +id: "adr-069" +title: "Typed Warrant and Reconciled Edge Contract — Provenance and Graph Integrity the Validator Can Prove" +slug: "069" +subtitle: "Accepted" +excerpt: "The `context-as-property` work (2026-07-09, from Jessica Talisman's essay naming," +author: "ontoref" +date: "2026-07-09" +published: true +featured: false +category: "accepted" +tags: ["adr-069", "adr", "accepted"] +css_class: "category-adr" +--- +

    Context

    + +

    The `context-as-property` work (2026-07-09, from Jessica Talisman's essay naming, +from library science, the category ontoref occupies) recorded field-for-field the +convergence between the glossary term schema and ANSI/NISO Z39.19 — and, with +ondaod honesty, the three gaps the lineage does NOT certify. Two of them are +structural claims the ontology graph could not yet back:

    +

    1. WARRANT — "on whose authority does this node exist?" — was typed only on + glossary terms (`origin`) and value-props (`evidence_adrs`); inside the + ontology's own node descriptions it was prose. A node's provenance could be + asserted in a paragraph and never queried.

    +

    2. Z39.19 §8.1.1 reciprocity ("every relation A→B carries B→A, for all types of + relationships") was stricter than any validator ontoref ran on its own graph.

    +

    Verifying the edge surface to act on gap 2 surfaced a dormant third-party defect: +a THREE-WAY contract divergence on edges, alive but asleep. The schema enum +(`code/ontology/schemas/core.ncl`) listed four kinds never used (ValidatedBy, +FlowsTo, CyclesIn, LimitedBy) and omitted four in active use (Contradicts, +DependsOn, Implies, Resolves); it typed `weight` as `Number` while the data has +always carried symbolic `'High/'Medium/'Low`; and the Rust loader +(`types.rs::EdgeType`, `weight: f64`) mirrored the stale schema. `nickel export` +passed anyway because the edge records in `core.ncl` are BARE — no `make_edge`, no +`CoreConfig` on the top level — so no contract ever bit them. The Rust typed path +(`Core::from_value`) would REJECT the real `core.ncl` (`kind:"Resolves"`, +`weight:"High"`), but it has zero live callers: the daemon seeds edges from raw +JSON. A latent lie, not an active break.

    + +

    Decision

    + +

    Close both admitted gaps and reconcile the dormant divergence, in one move, with a +validator as the enforcement surface where the NCL contract cannot reach.

    +

    WARRANT (gap 1) — additive, audited, never required: + - `WarrantRef = { kind | [| 'Adr, 'Emergence, 'Interview, 'Session, 'External |], + ref | String, note | String | default = "" }` and + `Node.warrant | Array WarrantRef | default = []` in the core schema. The + default makes every existing node validate unchanged (no migration: the + trigger rule fires only for REQUIRED fields). + - The evidence itself lives in reflection (emergence.log, interviews); the node + CITES it, never duplicates it. The Rust `Node` struct is left unchanged — + serde drops the field exactly as it already drops `axis` — until a surface + needs to expose it. + - Enforcement is an AUDIT, not a contract: `validate ontology --warrant` flags a + non-Axiom node (Practice / Tension / — in a consumer — Project / Moment) that + carries neither `adrs` nor `warrant`. Soft, direction-reporting; a REQUIRED + warrant would be ceremony-capture. Axioms are self-warranting (they are the + Yang floor, invariant = true).

    +

    EDGE RECONCILIATION (gap 2 + the dormant bug) — schema and Rust follow the data: + - `edge_type` becomes the nine kinds ACTUALLY used (Complements, Contains, + Contradicts, DependsOn, Implies, ManifestsIn, Resolves, SpiralsWith, + TensionWith); `weight` becomes the symbolic `weight_type = [| 'High, 'Medium, + 'Low |]`. Rust `EdgeType` and `Weight` mirror them exactly. The typed path now + tells the truth about the data. + - Reciprocity is DERIVABLE, not stored (the correct DAG adaptation of §8.1.1): + `code/ontology/defaults/edge-inverses.ncl` freezes, per kind, an inverse label + for reverse-direction rendering, and the set of symmetric kinds (Complements, + Contradicts, SpiralsWith, TensionWith) whose inverse is the kind itself. + - `validate ontology` is the graph-integrity gate the bare-record edges left + open: dangling endpoints (Hard), every kind present in the inverse-map + vocabulary (Hard), symmetric reciprocity consistency (Soft). It reads + `core.ncl` via `nickel export`, never through the typed Rust path, so it works + daemon-free.

    +

    The nine-kind vocabulary is now single-source across three surfaces (schema enum, + Rust `EdgeType`, `edge-inverses.ncl`); a new kind must be added to all three, and + `validate ontology` fails until it is.

    + +

    Constraints

    + +
    • Hard `Node.warrant` MUST carry `default = []` so existing nodes and consumer graphs validate unchanged; no schema may make warrant a required field.
    • Hard Warrant provenance MUST be enforced as a Soft audit (`validate ontology --warrant`) that reports the gap, never as a Hard reject of un-warranted nodes.
    • Hard The edge-kind vocabulary MUST stay identical across the schema enum, the Rust `EdgeType`, and `edge-inverses.ncl`; a new kind added to one MUST be added to all three.
    • Hard Edge `weight` MUST be the symbolic `weight_type` ('High/'Medium/'Low) in both the schema and Rust — never a Number — matching the data the graph has always carried.
    • Soft `validate ontology` MUST check dangling endpoints and kind-in-vocabulary against `core.ncl` via `nickel export`, working without a running daemon (local-fallback, ADR-029).
    • Soft Reciprocity MUST be derivable, not stored: symmetric kinds (their inverse is the kind itself) are frozen in `edge-inverses.ncl`, and a stored reverse edge of a symmetric kind carrying a DIFFERENT kind is a reciprocity inconsistency.
    + +

    Alternatives considered

    + +
    • Leave warrant as prose in node descriptionsrejected: The status quo the essay named as a gap: provenance asserted in a paragraph is not queryable, not auditable, and indistinguishable from an unsupported claim. The whole point of the context-as-property lineage is that warrant is a typed property, not narration.
    • Make warrant a required Node fieldrejected: Ceremony-capture (adr-029): every node authored under a Hard gate that may not fit, and a mass migration of 59 existing nodes. Collapses formalization-vs-adoption to the Yang pole. The audit reports the gap without forbidding the node.
    • Migrate the edge DATA to the stale schema (numbers, drop the four extra kinds)rejected: Inverts the direction of truth: the data is the ground reality (168 edges across nine kinds with symbolic weights), the schema was the aspirational fiction. Schema and Rust follow the data, not the reverse — and rewriting 168 records to fit a lie is pure churn.
    • Store both edge directions (literal Z39.19 §8.1.1)rejected: In a directed DAG with a fixed kind vocabulary, storing B→A for every A→B is redundant noise that doubles the edge set and invites the two directions to drift. Derivable reciprocity (an inverse label per kind + a symmetric set) renders both directions without persisting either.
    • Wrap every edge in make_edge so the per-record NCL contract bitesrejected: 168 mechanical edits, high churn and merge risk, and it still would not catch dangling endpoints or cross-edge reciprocity (a record contract sees one record). A graph-level validator is one surface with strictly greater reach.
    + +

    Anti-patterns

    + +
    • A Typed Contract That Diverges From Its Data Because Nothing Enforces It — A schema, a Rust type and the data drift apart while every path that would surface the mismatch is dead (bare records skip the contract; the typed loader has no callers). The crispness of the types lends unearned credibility to a lie nothing checks — tier-0 false certainty at the contract layer.
    • Provenance Asserted in a Description, Never Queryable — A node's authority ('surfaced in the interview', 'per ADR-055') lives only in its prose description — not typed, not auditable, indistinguishable from an unsupported claim. The Z39.19 warrant gap the context-as-property essay admitted.
    • Making Provenance a Hard Gate — Turning warrant into a required field or a Hard reject — every node authored under a gate that may not fit, a mass migration forced on consumers. Enforcement as the product; the Yang collapse of formalization-vs-adoption.
    • An Enum That Lists What Was Imagined, Not What Is Used — A type enumerates kinds that never appear in the data while omitting kinds the data actively uses. The schema reads as a governed vocabulary but describes a fiction; a graph validator built on it checks the wrong set.
    • An Agent Moves the Protocol Version on Its Own Criterion — An agent edits reflection/version.ncl protocol_version, or adds a version-bumping protocol_version delta to a migration, because a migration 'implies' a bump. The protocol version is an outward release act (⛓G3 single-source in reflection/version.ncl); coupling it to each migration delta lets an agent move the version by its own judgment. check_chain (audit C2) enforces version↔chain CONSISTENCY, not AUTHORSHIP — so a consistent-but-unauthorized bump passes the audit while still being a decision the agent had no standing to make. Surfaced 2026-07-09 when this very session bumped 0.1.19→0.1.20 unprompted while authoring migration 0043.
    + +

    Related ADRs

    + +

    ADR-029 · ADR-035 · ADR-050 · ADR-001 · ADR-028

    diff --git a/site/site/content/adr/en/accepted/adr-069.ncl b/site/site/content/adr/en/accepted/adr-069.ncl new file mode 100644 index 0000000..c0d470b --- /dev/null +++ b/site/site/content/adr/en/accepted/adr-069.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-069", + title = "Typed Warrant and Reconciled Edge Contract — Provenance and Graph Integrity the Validator Can Prove", + slug = "069", + excerpt = "", + tags = ["adr", "accepted"], + page_route = "/adr/069", + graph = { + implements = [], + related_to = ["adr-029", "adr-035", "adr-050", "adr-001", "adr-028"], + }, +} diff --git a/site/site/content/adr/en/proposed/adr-020.md b/site/site/content/adr/en/proposed/adr-020.md new file mode 100644 index 0000000..b19b19d --- /dev/null +++ b/site/site/content/adr/en/proposed/adr-020.md @@ -0,0 +1,86 @@ +--- +id: "adr-020" +title: "Three-Layer Model for Project Ontoref Instances" +slug: "020" +subtitle: "Proposed" +excerpt: "Multiple projects now adopt the ontoref protocol (lian-build is the explicit" +author: "ontoref" +date: "2026-05-03" +published: true +featured: false +category: "proposed" +tags: ["adr-020", "adr", "proposed"] +css_class: "category-adr" +--- +

    Context

    + +

    Multiple projects now adopt the ontoref protocol (lian-build is the explicit +external case under bl-002 / bl-008). Field experience across ontoref + +lian-build + provisioning shows that an ontoref-onboarded project's +repository carries TWO distinct ontoref-shaped layers, while a third layer +exists outside the project (in caller repositories). Without codification, +adopters re-derive the model per project, mix layers accidentally, and lose +boundaries silently:

    +

    - Layer 3 content (caller-side cabling) drifts into a project's qa.ncl, + making the FAQ a how-to-deploy guide for one specific caller. + - Layer 2 schemas live under reflection/ as 'project notes', drifting + from the binary because they're not on the contract path. + - Layer 1 architectural rationale ends up in catalog/domains/<id>/ + contract.ncl, where consumers expecting a typed shape get prose.

    +

    The model has been observed (lian-build/reflection/qa.ncl::lian-build-what- +and-why), described (ontoref/reflection/qa.ncl::ontoref-three-layer-model), +and queued for codification (bl-009). This ADR commits the model with +machine-checkable constraints. Acceptance is gated on the constraints +running clean across the existing ontoref-onboarded projects (ontoref +itself, lian-build, provisioning).

    + +

    Decision

    + +

    A project's ontoref instance has THREE distinct layers — but only the first +two live in the project's own repository:

    +

    LAYER 1 — Self-management ontoref (about the project itself) + paths .ontology/ reflection/ adrs/ + audience this project's developers and maintainers + purpose describe the project to itself — axioms, FSM dimensions, + binding decisions, open questions, accepted knowledge + presence MANDATORY on every ontoref-onboarded project

    +

    LAYER 2 — Specialized domain/mode ontoref (the integration surface) + paths schemas/ catalog/{domains,modes}/ + manifest.ncl::registry_provides + audience OTHER projects that want to integrate this project + purpose the contract surface other projects bind to — typed domain + artifacts, orchestration mode artifacts, registry-namespace + claim + presence OPTIONAL but BICONDITIONAL — a project either has all of + {schemas/, catalog/, registry_provides} or none. Half-Layer-2 + is a contract violation.

    +

    LAYER 3 — Caller-side implementations (NOT in this project) + paths <caller>/extensions/<this-project>/ + <caller>/catalog/components/<...>/ (when consuming) + <workspace>/infra/<ws>/integrations/ + audience operators and CI of caller projects + presence PER CALLER, NEVER in this project's repo. Cross-references + from this project to Layer 3 are explicit pointers, never + copy-paste.

    +

    The three-layer axis is ORTHOGONAL to ADR-018's level hierarchy +(Base/Domain/Instance). A project at any level may have any combination of +Layer 1 (always) and Layer 2 (sometimes). Layer 3 is the boundary outward, +not a property of the project. The 3-layer × 3-level matrix is navigable in +both axes: 'where in the protocol hierarchy' (level) is independent of +'where in the project's repo' (layer).

    +

    Cross-layer references inside a project carry an explicit layer-N tag on +qa entries. A qa entry whose primary topic is a Layer N concern wears +the layer-N tag; satellite entries (operational how-to, troubleshooting) +inherit the layer of their anchor without re-tagging.

    + +

    Constraints

    + +
    • Hard Every ontoref-onboarded project has .ontology/core.ncl
    • Hard Every ontoref-onboarded project has reflection/qa.ncl
    • Hard Every ontoref-onboarded project has reflection/backlog.ncl
    • Hard Every ontoref-onboarded project has an adrs/ directory
    • Hard A federated-peer catalog (catalog/domains/ or catalog/modes/) exists if and only if manifest.ncl declares registry_provides
    • Soft qa entries whose primary topic is a Layer N concern carry the layer-N tag (anchors only; satellites inherit by association)
    + +

    Alternatives considered

    + +
    • Single 'ontoref content' namespace, no layeringrejected: Observed drift outcomes (Layer 3 in qa, Layer 2 schemas treated as notes, etc.) are caused precisely by absence of layering. The single-namespace alternative is what we're correcting; rejecting it is the entire decision.
    • Two-layer model collapsing self-management with integration-surfacerejected: lian-build's adoption explicitly experienced the schema-vs-rationale split: schemas/build_directives.ncl (Layer 2 contract) and adrs/adr-001-lian-build-as-standalone.ncl (Layer 1 rationale) have different audiences, different change cadences, different validation rules. Collapsing them produced the FAQ-vs-contract confusion that retiring lian-build/.ontology/FAQ.md addressed. The two-layer alternative re-creates that confusion.
    • Make Layer 3 (caller-side) optionally co-resident with Layer 2 in the producer's reporejected: Creates ambiguity about who owns the cabling. If a producer's repo carries `extensions/<self>/`, who maintains it when a caller's workspace evolves? The producer doesn't know about the caller's infrastructure; the caller can't depend on the producer to update the cabling on its schedule. Co-residence inverts the integration arrow.
    • Numbered layers (Layer 1 / 2 / 3) vs named layers ('self' / 'integration-surface' / 'caller-side')rejected: Named layers are more descriptive but verbose; numbered layers are more precise but flat. The compromise (this ADR): use 'Layer N — descriptive name' in prose, 'layer-N' in tags. Tag economy wins; prose retains the descriptive name.
    + +

    Related ADRs

    + +

    ADR-001 · ADR-016 · ADR-018

    diff --git a/site/site/content/adr/en/proposed/adr-020.ncl b/site/site/content/adr/en/proposed/adr-020.ncl new file mode 100644 index 0000000..50fd349 --- /dev/null +++ b/site/site/content/adr/en/proposed/adr-020.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-020", + title = "Three-Layer Model for Project Ontoref Instances", + slug = "020", + excerpt = "", + tags = ["adr", "proposed"], + page_route = "/adr/020", + graph = { + implements = [], + related_to = ["adr-001", "adr-016", "adr-018"], + }, +} diff --git a/site/site/content/adr/en/proposed/adr-022.md b/site/site/content/adr/en/proposed/adr-022.md new file mode 100644 index 0000000..0f4d058 --- /dev/null +++ b/site/site/content/adr/en/proposed/adr-022.md @@ -0,0 +1,33 @@ +--- +id: "adr-022" +title: "Secret Management Operations on the MCP Surface — Observation, Orchestration, and Permanent Exclusions" +slug: "022" +subtitle: "Proposed" +excerpt: "ADR-017 established that the daemon is structurally excluded from credential resolution: it has no age private key and cannot decrypt sops files. This is load-bearing — any actor with MCP access cann" +author: "ontoref" +date: "2026-05-16" +published: true +featured: false +category: "proposed" +tags: ["adr-022", "adr", "proposed"] +css_class: "category-adr" +--- +

    Context

    + +

    ADR-017 established that the daemon is structurally excluded from credential resolution: it has no age private key and cannot decrypt sops files. This is load-bearing — any actor with MCP access cannot use the daemon as a credential amplifier. The existing MCP surface has one secret-related tool: ontoref_vault_status (read-only metadata: vault_id, recipient_count, access_sops_present, recipient_groups). The workspace toolchain (secrets.just + provisioning/workspace/tools/secrets.nu) exposes a full secret management lifecycle: gen, new, make, list, pending, show, edit, rekey. The question arose when staging plaintext credential files (*.sops.yaml.template) was added to the workspace workflow, and when CI/CD and agent automation scenarios began requiring machine-readable discovery of pending and existing secrets. Two failure modes emerge if the wrong boundary is drawn: (1) exposing decrypt operations via MCP turns the daemon into a plaintext exfiltration channel — any actor with daemon access and the right MCP token can trigger decryption of any credential file; (2) exposing no operations at all forces agents and CI to shell out to just directly, bypassing the actor authorization model entirely and producing invisible, unaudited secret access.

    + +

    Decision

    + +

    Secret management operations on the MCP surface are partitioned into three permanent tiers. Tier 1 — Permanent Exclusions (never on MCP surface): any operation that produces plaintext credential values in a response payload (secret-show, decrypt-to-stdout, raw sops --decrypt), age private key material (.kage content or derived values), and staged plaintext template content (*.sops.yaml.template). These exclusions are structural and cannot be lifted by actor configuration or policy override. Tier 2 — Read-Only Observation (permitted for any authorized actor): ontoref_vault_status (already exists), secret names list (encrypted filenames, no values), pending secret discovery (*.sops.yaml.template filenames without corresponding *.sops.yaml). Tier 2 tools invoke the secrets engine as a read-only subprocess and return structured metadata only. Tier 3 — Orchestrated Mutating Operations (permitted only for actor=admin, requires subprocess isolation, mandatory audit log): secret-gen, secret-make, secret-new, secret-rekey. These invoke the secrets.nu subprocess with the workspace environment variables; the daemon never holds key material — the subprocess reads .kage from the filesystem path configured in the project. Every Tier 3 invocation appends an entry to the vault access log (logs/access.jsonl) recording: actor, operation, secret name, timestamp, exit code. The daemon never buffers subprocess output that contains plaintext — stdout of any secret subprocess is streamed to the caller's MCP response only after the daemon confirms the response payload type is metadata (exit code, names, status), never raw subprocess stdout when the underlying command is decrypt-capable.

    + +

    Constraints

    + +
    • Hard No MCP tool may return plaintext credential values in its response payload — secret-show and any decrypt-capable operation are permanently excluded from the MCP surface
    • Hard No MCP tool may return age private key content, sops DEK material, or staged template plaintext in its response payload
    • Hard Tier 3 mutating tools (secret-gen, secret-make, secret-new, secret-rekey) must require actor=admin; agent and developer actors must receive Unauthorized before the subprocess is spawned
    • Hard Every Tier 3 subprocess invocation must append an entry to logs/access.jsonl before returning — a missing log entry for a completed operation is a constraint violation
    • Hard The daemon must not forward raw subprocess stdout to the MCP response caller when the invoked secrets.nu subcommand is decrypt-capable (show, edit); for Tier 3 commands, only structured metadata (exit code, names changed, audit entry written) may be returned
    • Hard Every MCP tool in category credentials must declare its tier (1|2|3) as a metadata field at registration time; tools without a declared tier fail daemon startup
    + +

    Alternatives considered

    + +
    • No expansion of MCP surface (Tier 1 only, current state)rejected: Forces agents and CI pipelines to shell out to just secret-* outside the actor authorization model. Pending secret discovery, vault compliance checks, and automated secret rotation all become invisible to the audit trail. The automation use case is real and growing; refusing it produces shadow workflows rather than preventing them.
    • Full exposure including decrypt-to-response (no Tier 1 exclusions)rejected: secret-show and any decrypt-capable operation as MCP tools turns the daemon into a plaintext credential proxy. Any actor with admin token and network access to the loopback daemon can extract credential values without a trace in the vault's own audit log (daemon access log is separate). The structural exclusion of ADR-017 would be effectively nullified — the daemon would hold no key but could produce plaintext on demand.
    • Daemon-as-proxy: daemon acquires .kage path, resolves credentials in-processrejected: Breaks the load-bearing invariant of ADR-017: 'the daemon cannot be used as a credential amplifier regardless of MCP actor permissions'. In-process resolution means the daemon holds plaintext during the operation lifecycle. Any memory disclosure (core dump, debug interface, language runtime reflection) exposes key material. The subprocess model keeps key material in a child process with a narrower attack surface and no persistence in daemon memory.
    • Separate secret-management daemon with its own MCP serverrejected: Adds operational complexity (two daemons to manage, two auth models to align) without meaningful security gain over subprocess isolation. The tier model achieves the same separation of concerns within the existing daemon's actor authorization infrastructure. A separate daemon would need its own loopback trust model — re-solving a solved problem.
    + +

    Related ADRs

    + +

    ADR-017 · ADR-019 · ADR-015 · ADR-005

    diff --git a/site/site/content/adr/en/proposed/adr-022.ncl b/site/site/content/adr/en/proposed/adr-022.ncl new file mode 100644 index 0000000..2d0189a --- /dev/null +++ b/site/site/content/adr/en/proposed/adr-022.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-022", + title = "Secret Management Operations on the MCP Surface — Observation, Orchestration, and Permanent Exclusions", + slug = "022", + excerpt = "", + tags = ["adr", "proposed"], + page_route = "/adr/022", + graph = { + implements = [], + related_to = ["adr-017", "adr-019", "adr-015", "adr-005"], + }, +} diff --git a/site/site/content/adr/en/proposed/adr-030.md b/site/site/content/adr/en/proposed/adr-030.md new file mode 100644 index 0000000..b399d04 --- /dev/null +++ b/site/site/content/adr/en/proposed/adr-030.md @@ -0,0 +1,276 @@ +--- +id: "adr-030" +title: "Catalog Discovery Cross-Project — Tier-2 Ops Beyond the Piloto Self-Host" +slug: "030" +subtitle: "Proposed" +excerpt: "ADR-029 commits the protocol to permanent tier coexistence: any project" +author: "ontoref" +date: "2026-05-26" +published: true +featured: false +category: "proposed" +tags: ["adr-030", "adr", "proposed"] +css_class: "category-adr" +--- +

    Context

    + +

    ADR-029 commits the protocol to permanent tier coexistence: any project +may climb to tier-2 (operations adopted) voluntarily. The piloto self-host +(ontoref itself) demonstrated tier-2 functionally during the session of +2026-05-26 — 10 domain operations registered, dispatched, witnessed, +replay-deterministic. The piloto works because its operations are +compiled into the `ontoref-daemon` binary via `inventory::collect!` at +link time: `crates/ontoref-ops/src/ops/*.rs` annotated `#[onto_operation]` +emit static `OperationEntry` records; `dispatch_op(id, …)` resolves +against `inventory::iter::<OperationEntry>()`.

    +

    This works because the piloto and the daemon are co-developed in the same +repository — the daemon binary that serves the piloto IS the binary +compiled with the piloto's catalog baked in. The 18 other onboarded +projects (mirador, build-in-layers, lian-build, vapora, stratumiops, +kogral, libre-{daoshi,wuji,forge}, secretumvault, typedialog, jpl-website, +personal-ontoref, jpl-ontoref, cloudatasave, DD7pasos, librosys, +forge-fleet, mina) sit at tier-0 or tier-1; their adoption of tier-2 +would surface a structural gap that the current architecture does not +address:

    +

    When project P climbs to tier-2, where do P's domain operations live, + and how does the daemon discover and invoke them?

    +

    Today's only answer — "compile P's ops into the daemon binary and ship +that binary to P" — does not scale. It implies a per-project daemon +build, breaks the "one daemon, mixed-tier projects" model that +ADR-029's `ontoref-multi-tier-management` QA describes, and ties the +project's release cadence to the daemon's. For the piloto self-host this +is acceptable (ontoref's release IS the daemon's release); for any other +project it is not.

    +

    The catalog has two sides per ADR-029:

    +

    - DECLARATIVE side (NCL): `catalog/operations/<id>.ncl` and + `catalog/validators/<id>.ncl`. Per-project, in the project's own repo. + Typed against `catalog/schema.ncl` (the protocol-wide contract). + - EXECUTIVE side (Rust): the `#[onto_operation]`-annotated functions + + `#[onto_validator]`-annotated functions. Today baked into the + daemon binary via inventory.

    +

    The declarative side composes cleanly across projects — ADR-028 already +content-addresses ontologies; the catalog NCL files could be published +the same way (as Type-2 or Type-3 ontologies in `_ontology_refs.ncl`), +discovered via `fetch_cell_witness`, type-checked locally. The +executive side is the harder problem: how does the daemon, running once +per host and serving N projects of mixed tiers, obtain and invoke the +Rust handlers for project P's ops?

    +

    The session that produced ADR-029 surfaced this open question +explicitly: "the daemon binary that hosts ontoref-piloto has the 10 +piloto ops linked at compile time. When lian-build, provisioning, or any +other project climbs to tier-2 and authors its own catalog, the inventory +mechanism does not extend — those projects' ops are not in the daemon's +binary." The piloto demonstrates that tier-2 works; this ADR +acknowledges that scaling tier-2 across the ecosystem needs a discovery +mechanism beyond link-time inventory.

    +

    No external pressure yet justifies picking a mechanism. Only ontoref +itself is at tier-2. The remaining 18 projects have not signalled tier-2 +ambitions. This ADR records the problem, the design space, and the +trigger-based decision pattern (consistent with ADR-026 / D15 commitment +backend deferral, ADR-027 / D18 sync backend deferral, ADR-029 / D23 blob +backend deferral) — IMPLEMENT WHEN TRIGGERED, not before.

    + +

    Decision

    + +

    The catalog-discovery problem is real, named, and deferred behind +explicit trigger thresholds. The architecture reserves the abstraction +point and documents the design space so a future spike can land an +impl with minimum protocol disruption.

    +

    ABSTRACTION POINT — `CatalogBackend` trait (future)

    +

    A future trait in ontoref-ops or a new crate ontoref-catalog:

    +

    pub trait CatalogBackend { + fn discover_ops(&self, project: &ProjectContext) -> Vec<OperationEntry>; + fn discover_validators(&self, project: &ProjectContext) -> Vec<ValidatorEntry>; + }

    +

    The default impl `InventoryCatalogBackend` returns only the + link-time-registered entries (what the daemon does today). Future + impls extend the surface:

    +

    - DynLibCatalogBackend — loads .so/.dylib per project + - WasmCatalogBackend — loads WASM modules per project + - SidecarCatalogBackend — proxies dispatch to a per-project process + - InterpretedNclBackend — executes NCL-declared ops in-process + - CompositeCatalogBackend — combines the above

    +

    Each impl pairs with documented spike triggers (see below).

    +

    DESIGN SPACE — five candidate mechanisms

    +

    1. CompiledIntoDaemon (current state) + The project's ops are compiled into the daemon binary. The project + ships its own daemon build, OR the project's ops live in a workspace + crate that the daemon includes as a dep. + Pros: full Rust type safety; zero runtime overhead; replay + determinism preserved trivially. + Cons: does NOT scale beyond 1-2 projects co-developed with the + daemon; binary distribution per project; impossible cross-version. + Status: status quo for the ontoref piloto; not a general solution.

    +

    2. DynamicLibraryLoading (libloading / dlopen) + Project compiles `.so/.dylib`; daemon loads it at startup via + `libloading`. The library exposes a C-ABI surface that returns + `OperationEntry` records. + Pros: native Rust speed; the entries integrate naturally with + inventory after a one-time registration call. + Cons: ABI brittleness across Rust compiler versions; platform- + specific paths; security boundary is thin; symbol versioning is a + runtime hazard. + Trigger: 2+ projects at tier-2 + they're willing to compile against + a pinned Rust toolchain matching the daemon's.

    +

    3. WasmComponents (component model) + Project compiles ops to WASM components; daemon loads them via + wasmtime/wasmer with the WIT interface. + Pros: portable across platforms; security sandbox; project's choice + of source language (Rust, AssemblyScript, others); ABI is the + component model, not Rust's. + Cons: WASM build pipeline overhead; need a WIT surface for + OperationEntry + Slice + ValidationCtx + Verdict + OpOutput + (significant interface design); slight runtime overhead. + Trigger: 2+ projects + diverse host platforms + security + sandboxing required (untrusted ops authors). + Note: ADR-026 mentions WASM as candidate for tier-0 Structural + validators. The same infra could host operations.

    +

    4. SidecarProcess (per-project op process) + Each tier-2 project runs its own ops process (binary or + interpreter); daemon proxies `dispatch_op` calls via IPC (Unix + socket, gRPC, named pipe). The sidecar handles its own catalog. + Pros: process isolation; per-project deploy cycle independent of + daemon; language-agnostic (sidecar can be Rust, Go, Python, …). + Cons: extra process management; IPC latency on hot path; witness + signing crosses a process boundary (security review needed); + multiple processes per host complicates packaging. + Trigger: projects need independent op deploy cadence + IPC overhead + is acceptable per dispatch.

    +

    5. InterpretedNclOps (NCL bodies executed in-process) + The op body is declared as an NCL expression (or small DSL) in the + catalog/operations/<id>.ncl file. The daemon interprets it against + `ctx` and `inputs`. No Rust handler required. + Pros: zero deploy overhead; ops author entirely in NCL; cross- + project ops trivially shareable as content-addressed ontologies. + Cons: loses Rust type safety; need an expression evaluator + safe + sandbox; performance lower than native; complex ops (signature + schemes, blob hashing) hard to express. + Trigger: most tier-2 ops in the ecosystem turn out to be small + state-mutation verbs that need no native code, and the cost of + expressing them in NCL is less than the cost of compiled-per- + project handlers. + Note: this is the most aligned with "voluntary-adoption, minimal + friction" if the expressive power suffices.

    +

    6. SidecarProcessWithSharedDomainLibrary + Specialisation of #4 (SidecarProcess) aligned with ADR-018 level + hierarchy. The domain (Level 2, e.g. provisioning) ships a Rust + LIBRARY crate `<domain>-ops` containing the generic + `#[onto_operation]` handlers. Each instance (Level 3, e.g. + libre-daoshi / libre-wuji / libre-forge) compiles its OWN sidecar + BINARY that depends on the domain library AND adds + instance-specific ops. The ontoref main daemon proxies dispatch + to each instance's sidecar via HTTP.

    +

    Convergence/divergence split: + CONVERGE (single source of truth) + - Catalog NCL declarations (shared at domain level via ADR-028) + - Rust handler code (one `<domain>-ops` library crate) + - catalog/schema.ncl validations (protocol-wide) + DIVERGE (per-instance) + - Sidecar binaries (each instance compiles its own) + - Deploy cadence (independent per instance) + - Signing keys per actor per instance + - Instance-specific ops (overrides + extensions) + - Sidecar process per host per instance

    +

    Maps onto ADR-018 mode resolution semantics: + Delegate → instance uses the domain handler unchanged + Override → instance provides its own handler for the same op id + Compose → instance wraps the domain handler with extra logic

    +

    Pros: ZERO new code in the daemon beyond a ~80-LOC proxy handler; + full Rust type safety for handlers (lib path/git dep); + per-instance deploy independence; blast radius isolated per + instance; instances can climb to tier-2 voluntarily without + forcing siblings; signing keys stay per-instance per-actor + (no shared cryptographic boundary); replay determinism preserved. + Cons: N processes per host where N = number of tier-2 instances; + Rust toolchain coupling between domain library and instance + sidecars; HTTP IPC overhead per dispatch (~50-500µs measured); + cross-instance ops require IPC chain (instance A → main daemon + → instance B's sidecar). + Trigger: domain with multiple Level-3 instances reaches tier-2 + AND deploy cadences are independent AND HTTP IPC overhead is + acceptable. provisioning (with libre-daoshi, libre-wuji, + libre-forge as instances) is the canonical example. + Note: this is the RECOMMENDED near-term path for shared-domain + tier-2 adoption — implementable today, no WASM spike required.

    +

    7. CompositeBackend (mix of the above) + Daemon loads multiple backends in priority order; an op lookup + consults each backend until one returns a match. Lets ontoref-piloto + keep its CompiledIntoDaemon backend AND add a second backend for + external tier-2 projects. + Pros: incremental migration; the piloto doesn't have to change to + accommodate new projects; the abstraction holds even with one + backend. + Cons: dispatch lookup is O(N backends); priority semantics need + clear documentation.

    +

    CHOSEN MECHANISM — tiered selection by trigger

    +

    The 7 mechanisms split into two phases of the design space:

    +

    Phase A — implementable today, no spike required: + 1. CompiledIntoDaemon — current piloto self-host + 6. SidecarProcessWithSharedDomainLibrary + — RECOMMENDED for shared-domain + tier-2 (provisioning, etc.) + 7. CompositeBackend (combines 1 + 6)

    +

    Phase B — requires a spike with documented trigger conditions: + 2. DynamicLibraryLoading — Rust ABI brittleness + 3. WasmComponents — WIT interface design + runtime cost + 4. SidecarProcess (raw) — base case of #6 without domain lib + 5. InterpretedNclOps — NCL expressive power proof needed

    +

    For Phase A mechanisms, the protocol does NOT defer — implementation + may proceed when a project commits to the pattern. Mechanism #1 is + already in production (the piloto). Mechanism #6 unblocks any shared + domain (provisioning + its Level-3 instances) at the cost of ~80 LOC + proxy handler in ontoref-daemon + path/git dep for the domain library.

    +

    For Phase B mechanisms, the protocol defers behind explicit triggers:

    +

    T1. THREE OR MORE projects (besides ontoref) author tier-2 catalogs + AND find Phase A mechanisms operationally insufficient. + T2. The ontoref piloto's catalog grows beyond ~30 ops to a size + where the inventory pattern shows cracks (compile-time startup, + debug builds, hot-reload friction). + T3. A consumer project requests cross-project ops dispatch (op A in + project P invokes op B in project Q) — IPC chain in mechanism #6 + becomes the bottleneck. + T4. Toolchain divergence — an instance wants ops in a non-Rust + language; dynlib / WASM is the only way. + T5. Untrusted ops authors — the deployment must sandbox ops from + outside the instance's control; WASM is the natural fit.

    +

    When ANY trigger fires for Phase B, a session is opened to: + 1. Audit which of the Phase B mechanisms (2, 3, 4 raw, 5) fits the + empirical constraints (host platforms, security model, deploy + cadence, language requirements). + 2. Define the WIT / C ABI / NCL DSL surface as appropriate. + 3. Implement the chosen backend behind the `CatalogBackend` trait. + 4. Document the new backend's spike triggers (mirroring the + ADR-026 / D15 spike-triggers pattern).

    +

    This ADR commits the abstraction point and the design space. It does + not commit the implementation. The same trigger-based discipline that + applies to CommitmentBackend (D15), SyncBackend (D18), CrdtMergeStrategy + (D19), and BlobBackend (D23) extends to CatalogBackend (D30).

    +

    DECLARATIVE SIDE — content-addressed catalogs as ontologies

    +

    The declarative side (`catalog/operations/<id>.ncl`) composes + trivially via ADR-028's content-addressed ontology layer. A project's + catalog NCL files can be published as a Type-2 or Type-3 ontology; + consumer projects subscribe to that ontology and gain typed knowledge + of "ops project P exposes". This works TODAY at any tier — the + catalog declarations are inert at tier-0/1 (declarative documentation + only) but compose cross-project. The executive-side gap is what this + ADR defers; the declarative-side composition is already operational.

    +

    CROSS-TIER COMPOSITION (preserved by design)

    +

    Tier-0 / tier-1 projects can declare their own `catalog/operations/` + as forward-looking documentation without paying the executive cost. + Tier-2 projects can reference the declarative ontology of any other + project (via OntologyRef in OperationDecl). The discovery of a + project's executive handlers is the only gap this ADR names; the + declarative half of the catalog already supports the full + cross-project composition envisioned by ADR-028.

    + +

    Constraints

    + +
    • Hard The default `CatalogBackend` impl MUST continue to support the ontoref piloto self-host without changes to the piloto's catalog format. Any future backend that supersedes CompiledIntoDaemon as default MUST be a strict superset of its capabilities for the piloto's 10 ops.
    • Hard The declarative side of `catalog/operations/<id>.ncl` and `catalog/validators/<id>.ncl` MUST remain composable cross-project via ADR-028 content-addressing at ANY tier — independent of which `CatalogBackend` impl is in use.
    • Hard When more than one `CatalogBackend` impl exists, the daemon MUST select impls based on explicit configuration in `.ontoref/config.ncl::ops.catalog_backend` (or equivalent). No automatic selection by environmental detection.
    + +

    Alternatives considered

    + +
    • Pick CompiledIntoDaemon as the only mechanism, declare cross-project ops out-of-scope permanentlyrejected: Forces every tier-2 project to either fork the daemon or upstream their ops into ontoref's workspace. Neither is acceptable for projects with independent release cycles, private domains, or non-Rust catalog authors. Violates the spirit of voluntary-adoption — climbing to tier-2 should be a per-project act, not gated on the protocol's deploy cadence.
    • Pick WASM components now and commit to itrejected: WASM is plausible but the design surface (WIT for OperationEntry + Slice + ValidationCtx + Verdict + OpOutput) is substantial, the runtime overhead unknown, and there is no second tier-2 project signalling demand. Premature commitment without empirical evidence repeats the patterns the trigger-based discipline (ADR-026/D15, ADR-027/D18, ADR-029/D23) was designed to prevent.
    • Pick DynamicLibraryLoading nowrejected: Rust ABI is unstable across compiler versions, making dynlib coupling brittle. Without a pinned Rust toolchain shared between the daemon and the project's ops crate, the ABI breaks silently. This is a real-world maintenance burden that surfaces only after the second project ships. Defer until at least one project commits to the toolchain discipline.
    • Defer the abstraction point itself — don't add `CatalogBackend` trait until impl is readyrejected: Without naming the abstraction, future sessions re-rediscover the problem and may pick conflicting solutions. Naming the trait + listing the 5 candidates with trade-offs is cheap (one ADR) and load-bearing for the next session that engages with cross-project tier-2. The design exploration is the deliverable; the impl is the follow-up.
    • Push everyone toward InterpretedNclOps and declare native handlers a legacy pathrejected: NCL/expression languages have not been proven expressive enough to handle the full range of ops (Ed25519 signing, blake3 hashing, oplog append, render pipelines). The piloto's 10 ops use native Rust for non-trivial logic. Committing to NCL-only would either restrict ops to the trivial subset OR force a complex NCL expression engine with its own safety story. Defer until evidence shows the trivial subset covers most real ops.
    + +

    Related ADRs

    + +

    ADR-001 · ADR-024 · ADR-025 · ADR-026 · ADR-027 · ADR-028 · ADR-029

    diff --git a/site/site/content/adr/en/proposed/adr-030.ncl b/site/site/content/adr/en/proposed/adr-030.ncl new file mode 100644 index 0000000..b965b37 --- /dev/null +++ b/site/site/content/adr/en/proposed/adr-030.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-030", + title = "Catalog Discovery Cross-Project — Tier-2 Ops Beyond the Piloto Self-Host", + slug = "030", + excerpt = "", + tags = ["adr", "proposed"], + page_route = "/adr/030", + graph = { + implements = [], + related_to = ["adr-001", "adr-024", "adr-025", "adr-026", "adr-027", "adr-028", "adr-029"], + }, +} diff --git a/site/site/content/adr/en/proposed/adr-034.md b/site/site/content/adr/en/proposed/adr-034.md new file mode 100644 index 0000000..8ae0470 --- /dev/null +++ b/site/site/content/adr/en/proposed/adr-034.md @@ -0,0 +1,221 @@ +--- +id: "adr-034" +title: "Catalog Extensibility Beyond Rust — `kind` Discriminator on `OperationDecl` with ondaod-Pre Required for Non-Rust Kinds" +slug: "034" +subtitle: "Proposed" +excerpt: "ADR-024 (operations as the agent's only project-touching path) and" +author: "ontoref" +date: "2026-05-26" +published: true +featured: false +category: "proposed" +tags: ["adr-034", "adr", "proposed"] +css_class: "category-adr" +--- +

    Context

    + +

    ADR-024 (operations as the agent's only project-touching path) and +ADR-026 (three-planes validation + SLA) commit the protocol to a typed +catalog of domain operations, each declared in +`.ontoref/catalog/operations/<id>.ncl` and paired with a Rust handler +annotated `#[onto_operation]`. The Rust pairing is enforced by +`inventory::collect!(OperationEntry)` at link time — there is no +protocol surface today for a catalog declaration without a matching +Rust function.

    +

    ADR-030 (catalog discovery cross-project) named the broader problem of +how non-piloto projects expose tier-2 ops and enumerated seven candidate +mechanisms:

    +

    1. CompiledIntoDaemon (current — Rust + inventory at link time) + 2. DynamicLibraryLoading (.so/.dylib via libloading) + 3. WasmComponents (WIT-typed WASM modules via wasmtime) + 4. SidecarProcess (HTTP/IPC to per-project process) + 5. InterpretedNclOps (declarative NCL data-transform) + 6. SidecarProcessWithSharedDomainLibrary (refinement of #4) + 7. CompositeBackend (mix of the above)

    +

    ADR-030 split these into Phase A (implementable today: #1, #6, #7) and +Phase B (spike-required behind explicit triggers: #2, #3, #4-raw, #5) +and committed the `CatalogBackend` trait surface without committing +any non-default impl.

    +

    WHAT THIS ADR DOES vs DOES NOT DO

    +

    ADR-034 does NOT re-open ADR-030's deferral. The trigger-based discipline +for choosing WHICH executive backend lands first remains intact: WASM is +deferred behind T3/T4/T5, dynlib behind toolchain-coupling pressure, +NCL-interpreted behind expressive-power proof.

    +

    ADR-034 commits a different layer: the catalog's DECLARATIVE schema. +Today an `OperationDecl` in `catalog/schema.ncl` implicitly assumes the +op is implemented in Rust (no `kind` field, the runtime looks up the +inventory entry by id). For tier-2 to scale across the ecosystem — even +under Phase A mechanisms only — the catalog declaration must be honest +about what mechanism a particular op uses. The piloto's `move_fsm_state` +is Rust; a future provisioning op in libre-daoshi's sidecar is +Sidecar-mechanism; a future trivial CRUD op in a tier-2 mirador clone +might be NclTransform. Each kind has different witness emission rules, +different ondaod-discipline requirements, and different cross-project +composability properties.

    +

    ADR-034 adds a `kind` field to `OperationDecl` with the canonical +enumeration: `'Rust` (default), `'NclTransform`, `'Wasm`, `'Sidecar`. +The schema admits all four kinds. The Rust runtime implements `'Rust` +today (inventory dispatch); the other three kinds are declarable but +unexecutable until an executive backend lands per ADR-030's trigger +discipline. The schema decoupling is the deliverable; the executive +landings remain trigger-deferred.

    +

    WHY DECLARE BEFORE EXECUTE

    +

    Declaring the four kinds in the schema does three things that +executive-only would not:

    +

    1. Consumer projects can author non-Rust ops as documentation today + (declarative side of catalog composes cross-project via ADR-028 + content-addressing per ADR-030's confirmation). A tier-0 project + can declare its future tier-2 catalog including non-Rust kinds + without paying the executive cost. + 2. Cross-project ontology consumers can read the declared `kind` and + understand what dispatch path the foreign project's op takes — + useful for trust analysis (a Sidecar op carries a process boundary; + a Wasm op carries sandbox guarantees). + 3. ondaod-discipline can be enforced structurally at the catalog + level: non-Rust kinds cannot inline-execute the ondaod-evaluation + op (NCL is declarative, WASM is sandboxed from the host runtime, + Sidecar crosses a process boundary). The schema requires + `evaluate_ondaod` in `constraints.pre` for non-Rust kinds, making + the host runtime perform the discipline check before dispatch.

    +

    WHAT VALIDATORS DO

    +

    Validators are pure (no state mutation). The kind field applies +symmetrically to `ValidatorDecl` — `predicate_ref` becomes +mechanism-dependent. Because validators don't mutate state, the +ondaod-pre requirement does not apply to them; only operations are +gated. The schema admits the same four kinds for validators with the +same trigger-deferred executive impls.

    + +

    Decision

    + +

    SCHEMA FIELD — `kind` discriminator on `OperationDecl` and `ValidatorDecl`

    +

    The `OperationDecl` contract in `.ontoref/catalog/schema.ncl` gains a +new field `kind` typed as:

    +

    let t_OpKind = [| + 'Rust, # #[onto_operation] + inventory (DEFAULT) + 'NclTransform, # declarative NCL data-transform spec + 'Wasm, # WIT-typed WASM module path + 'Sidecar, # HTTP endpoint on a per-project process + |] in

    +

    The field is optional with `default = 'Rust`, so every existing +operation declaration continues to typecheck unchanged. Adding a new +op requires no `kind` declaration unless the author chooses a non-Rust +mechanism.

    +

    PER-KIND BODY FIELD — `body | optional` (open record)

    +

    Each non-Rust kind requires an additional declaration of WHERE the +mechanism's runtime locates the implementation. The `body` field is an +optional open record whose shape is per-kind:

    +

    'Rust → body absent (impl resolved by id via inventory)

    +

    'NclTransform → body = { + target | String, # NCL file path relative to project root + selector | String, # JSONPath into the exported value + operation | [| 'Set, 'Append, 'Remove, 'Merge |], + value_expr | String, # NCL expression evaluated in op context + }

    +

    'Wasm → body = { + module_path | String, # path under .ontoref/catalog/wasm/<id>.wasm + export_name | String, # name of the WIT-exported function + capabilities | Array String, # explicit capability grants (file:read, net:none, ...) + }

    +

    'Sidecar → body = { + endpoint | String, # HTTP URL (host-local default) + auth_token | String | default = "<from .ontoref/config.ncl::ops.sidecar_token>", + timeout_ms | Number | default = 5000, + }

    +

    The `body` field is validated structurally by the schema (Nickel +contracts enforce per-kind shape); executive backends interpret it at +dispatch time. The protocol does NOT validate `body` semantically until +an executive backend exists — declaration without execution is honest +documentation per ADR-030.

    +

    EXECUTIVE IMPL DEFERRAL — unchanged from ADR-030

    +

    The `'Rust` kind is the only kind the daemon dispatches today. The +other three kinds remain trigger-deferred per ADR-030:

    +

    'NclTransform → trigger: most tier-2 ops in the ecosystem turn out + to be small state-mutation verbs that need no native + code, AND the expressive power of the declarative + spec is proven sufficient for ≥3 real ops. + 'Wasm → trigger: ADR-030 T3 / T4 / T5 — cross-instance + dispatch becomes the bottleneck, toolchain divergence, + or untrusted ops authors require sandboxing. + 'Sidecar → already Phase A per ADR-030 mechanism #6 + (SidecarProcessWithSharedDomainLibrary). The ~80-LOC + proxy handler is implementable today; provisioning + + Level-3 instances is the canonical first consumer.

    +

    This ADR commits the schema discriminator. It does NOT commit the +non-`'Rust` executive impls beyond what ADR-030 already commits. +Sidecar may land near-term (Phase A); Wasm and NclTransform stay +deferred.

    +

    ONDAOD-PRE REQUIREMENT FOR NON-Rust KINDS

    +

    ADR-024 places `evaluate_ondaod` as a structural ondaod-discipline +check that runs before architectural-mutation operations. The Rust +runtime can invoke this directly from inside an op body (the op has +access to the dispatch context). Non-Rust kinds cannot:

    +

    - `'NclTransform` ops are declarative — no body to run ondaod inside + - `'Wasm` ops are sandboxed — the host's ondaod context is not + automatically reachable; passing it across the boundary requires + interface design + - `'Sidecar` ops cross a process boundary — invoking ondaod from + the sidecar would require the sidecar to call back into the daemon

    +

    The protocol resolves this by requiring `evaluate_ondaod` (or a +declared specialised ondaod validator) to appear in the +`constraints.pre` array of every non-`'Rust` `OperationDecl`. The +host runtime invokes the pre-validator before dispatching to the +non-Rust kind. This makes ondaod-discipline a structural property +of the catalog declaration, not a behavioural property of the op body — +which is exactly what the declarative side of the catalog is for.

    +

    The constraint is enforced as a Hard schema-level check at typecheck +time:

    +

    let t_OperationDecl = ... in + let t_OndaodPreRequired = std.contract.from_predicate + (fun decl => + decl.kind == 'Rust + || std.array.any (fun v => v == "evaluate_ondaod") decl.constraints.pre) + in

    +

    Rust-kind ops MAY still declare `evaluate_ondaod` in their +`constraints.pre` (and ondaod-touching ops SHOULD); the constraint only +*requires* it for non-Rust kinds. The asymmetry matches the asymmetry +of executive access: Rust ops can synthesise ondaod context internally, +non-Rust ops cannot.

    +

    VALIDATOR EXTENSIBILITY — same field, no ondaod-pre requirement

    +

    `ValidatorDecl` in `.ontoref/catalog/schema.ncl` also gains the `kind` +field with the same enumeration. The ondaod-pre requirement does NOT +apply because validators are pure (no state mutation). Otherwise the +treatment is symmetric: `'Rust` is default and dispatches today; +`'NclTransform` / `'Wasm` / `'Sidecar` are declarable but unexecutable +until the corresponding executive backend lands.

    +

    WITNESS SHAPE HONESTY

    +

    ADR-024 requires every operation to emit a witness covering the act. +The witness shape declared in `OperationDecl.witness_shape` must reflect +what the executive backend can attest:

    +

    'Rust → witness covers the post-state of the host's in-process + application; the signature is over the canonical + serialisation of the state delta (current behaviour). + 'NclTransform → witness covers the same shape — the host runtime + applies the transform in-process and signs the + delta. No additional attestation needed. + 'Wasm → witness covers the host's view of inputs + outputs of + the WASM call; the signature does NOT attest WASM- + internal computation (the sandbox is honest about being + a black box). + 'Sidecar → witness covers the sidecar's response payload as + received by the daemon; the signature is the daemon's + signature, not the sidecar's. The protocol does NOT + claim the sidecar's computation is honest — only that + the daemon faithfully forwarded the sidecar's reported + effect.

    +

    The witness-shape distinction is documented in the schema's comments; +it is NOT enforced as a contract because the protocol cannot statically +verify which witness mode the executive backend will use. It IS +documented in the constraint rationale below so that future executive +backend implementations honour the declared boundaries.

    + +

    Constraints

    + +
    • Hard The `OperationDecl` contract in `.ontoref/catalog/schema.ncl` MUST declare a `kind` field typed as an enum admitting at minimum `'Rust`, `'NclTransform`, `'Wasm`, `'Sidecar`. The field MUST default to `'Rust` so existing declarations remain valid without modification. The `ValidatorDecl` contract MUST declare the same field with the same default.
    • Hard Every `OperationDecl` with `kind != 'Rust` MUST include `"evaluate_ondaod"` (or a documented specialised ondaod validator id) in its `constraints.pre` array. The schema MUST enforce this with a contract that fails typecheck when the requirement is unmet.
    • Hard The `'Rust` kind MUST remain the default and MUST remain executable by the daemon under every set of feature flags including `--no-default-features`. Adding new kinds MUST NOT regress the Rust-kind dispatch path.
    • Hard When a project declares an `OperationDecl` with `kind = 'Wasm`, `'NclTransform`, or `'Sidecar` and the daemon was built without the corresponding executive feature flag (`wasm`, `ncl-transform`, `sidecar` respectively when those features exist), the daemon's `/ops/{id}` endpoint MUST return a 501 Not Implemented response with a structured error explaining the missing feature. Silent fall-through to 404 is forbidden.
    • Hard For each non-Rust `kind`, the schema MUST validate the `body` field's required keys at typecheck time. `kind = 'NclTransform` requires `body.target`, `body.selector`, `body.operation`, `body.value_expr`. `kind = 'Wasm` requires `body.module_path`, `body.export_name`, `body.capabilities`. `kind = 'Sidecar` requires `body.endpoint` (other fields default).
    • Hard Adding the `'Wasm` kind to the schema MUST NOT introduce a `wasmtime` (or `wasmer`) dependency in the default `ontoref-daemon` build. Any WASM runtime dependency MUST be feature-gated behind an opt-in flag (e.g. `--features wasm`).
    • Hard The daemon MUST NOT infer an op's `kind` from artefact presence (e.g. existence of a `.wasm` file, a sidecar URL in config, an NCL body in the declaration). The `kind` field is the authoritative source. When the field is absent, the kind is `'Rust` by default — never any other value.
    • Soft For non-Rust kinds (`'Wasm`, `'Sidecar`), the witness emitted on dispatch MUST attest only the host-observable inputs and outputs of the foreign execution. The witness MUST NOT claim to attest the foreign computation's internal correctness, and the `payload_kind` MUST encode this honestly (e.g. `wasm_call_envelope`, `sidecar_response_envelope`).
    + +

    Alternatives considered

    + +
    • Add only `'Rust` and `'Sidecar` to the kind enum; defer `'Wasm` and `'NclTransform` declarations entirelyrejected: Forces a schema migration when `'Wasm` or `'NclTransform` later become declarable. The cost of declaring all four kinds today is one Nickel enum line per kind; the cost of migrating the schema later (with all consumer projects' content needing re-checking) is higher. ADR-030 already enumerated all the mechanisms; adding them to the schema follows the same enumeration.
    • Declare `kind` but make `body` field free-form `Dyn` for all non-Rust kindsrejected: Loses the typecheck protection that the per-kind shape gives. Authors of `kind = 'Sidecar` ops would get no help from the schema confirming they declared `endpoint`, `auth_token`, `timeout_ms`. The open-record-per-kind approach offers minimal typecheck for the canonical fields while leaving room for executive-backend-specific extensions.
    • Require ondaod-pre for ALL ops (Rust and non-Rust)rejected: Over-constrains Rust ops. Rust-kind ops have inline access to dispatch::call("evaluate_ondaod", …) and currently invoke it from inside the body when needed (operationally — see crates/ontoref-ops/src/ops/transition_adr.rs). Forcing every Rust op to also declare it in constraints.pre would duplicate enforcement and create false-positive failures for ops that don't touch architectural state. The asymmetry matches the executive-access asymmetry: structural where structural is needed.
    • Add Nushell as a fifth kind (`'NuScript`)rejected: Nushell is the existing automation surface (reflection/modules/) and is already declarable via `'Sidecar` with the sidecar being a Nushell-based HTTP server. Adding `'NuScript` as a separate kind would create overlap with `'Sidecar` and require duplicate documentation. If empirical pressure later shows that Nushell ops are common enough to justify a dedicated mechanism, a future ADR can add the kind — the four-kind enum is the minimum coherent set today.
    • Defer the schema discriminator entirely — wait until an executive backend lands and add the field thenrejected: Anti-pattern from ADR-030's perspective: schema decoupling is cheap; executive backend landings are expensive and require triggers. Adding the schema field now means the FIRST executive backend doesn't have to bump the schema — it implements its kind, and consumer projects that declared it speculatively start working without re-publication. The schema field also unblocks ADR-030 #6 (Sidecar Phase A) by giving it a place to declare without retro-fitting later.
    • Use a tag union without per-kind body field — embed mechanism-specific fields directly in OperationDeclrejected: Pollutes OperationDecl with N×M fields (each kind's fields × every declaration). The per-kind body is the canonical encoding pattern (matches `t_ConstraintCheck` in adr-schema.ncl — tag + per-tag required fields). Authors only see fields relevant to their chosen kind.
    + +

    Related ADRs

    + +

    ADR-024 · ADR-026 · ADR-028 · ADR-029 · ADR-030 · ADR-032 · ADR-033

    diff --git a/site/site/content/adr/en/proposed/adr-034.ncl b/site/site/content/adr/en/proposed/adr-034.ncl new file mode 100644 index 0000000..30f0a2d --- /dev/null +++ b/site/site/content/adr/en/proposed/adr-034.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-034", + title = "Catalog Extensibility Beyond Rust — `kind` Discriminator on `OperationDecl` with ondaod-Pre Required for Non-Rust Kinds", + slug = "034", + excerpt = "", + tags = ["adr", "proposed"], + page_route = "/adr/034", + graph = { + implements = [], + related_to = ["adr-024", "adr-026", "adr-028", "adr-029", "adr-030", "adr-032", "adr-033"], + }, +} diff --git a/site/site/content/adr/en/proposed/adr-058.md b/site/site/content/adr/en/proposed/adr-058.md new file mode 100644 index 0000000..88f038f --- /dev/null +++ b/site/site/content/adr/en/proposed/adr-058.md @@ -0,0 +1,82 @@ +--- +id: "adr-058" +title: "Difusión Demos Are Demonstration-Scenarios Projected From the Modes, Not a New Kind or Hand-Authored Prose" +slug: "058" +subtitle: "Proposed" +excerpt: "A difusión proposal asked for concrete 'con y sin ontoref' demos — each facing a" +author: "ontoref" +date: "2026-06-12" +published: true +featured: false +category: "proposed" +tags: ["adr-058", "adr", "proposed"] +css_class: "category-adr" +--- +

    Context

    + +

    A difusión proposal asked for concrete "con y sin ontoref" demos — each facing a +real problem and showing a way to manage it — published across four surfaces: +mdBook (docs/), code examples, presentation examples, and web pages with +asciinema. Two naive shapes were on the table and both are wrong for this +project.

    +

    The first naive shape is a NEW typed kind (a `CaseStudy` projecting a `Proof`): +extra schema, a migration, and a second seam duplicating what the positioning +Proof already binds. The second naive shape is hand-authored prose case studies +anchored to ids by hand: marketing copy that drifts from the claims it sells and +cannot be run or verified.

    +

    The interview that produced ADR-057 already established the discipline that +defeats both: the worldview is transmitted by encounter with a runnable artifact, +not by argument. And ontoref already ships the machinery. The `generate-examples` +mode encodes the axiom "the modes ARE the examples" — an example is a runnable, +verifiable artifact, not a static directory. The `Scenario` schema +(reflection/schemas/scenario.ncl) already models exactly a demo: `description` +(the problem — "without ontoref"), `run` (the way to manage it — "with ontoref"), +`validates` (the proof), under `purpose = 'Demonstration`. The `generate-mdbook` +mode and the generator (reflection/modules/generator.nu) already extract scenarios +and compose them into the doc-data. The asciinema pattern is already proven in +outreach/study-group-ontoref/demo/recording/record.sh.

    +

    The gap was never a missing abstraction; it was that nothing authored the demos +as scenarios and nothing projected them to the difusión surfaces.

    + +

    Decision

    + +

    A difusión demo IS a `Scenario` with `purpose = 'Demonstration`, authored once +under .ontoref/reflection/scenarios/<demo>/ and PROJECTED to every surface by the +generator. No new typed kind, no per-surface hand-authored copy. Concretely:

    +

    Demo = Demonstration-scenario — the problem the visitor faces is the scenario's + `description` (the "sin ontoref" frame), the way to manage it is `run` (the + "con ontoref" verifiable command), and what it proves is `validates`. This + reuses the Proof seam of positioning as an honesty anchor (each demo maps to a + Validated Proof) without duplicating it as a new kind.

    +

    Project, don't duplicate — one Scenario source feeds five outputs. mdBook is + rendered by `docs generate --fmt mdbook` (a Demos chapter: problem → management + → proof). The other four surfaces — the example repo README, the Slidev + section, the asciinema web page, and the Rustelo site page — are materialised by + a dedicated `docs demos project --surface <s>` command. A demo edited in one + place regenerates everywhere; no copy maintained per surface.

    +

    Runnable, not staged — the demo is executable and its `.cast` is recorded from + REAL execution against the `onref` branch of the example repo (the + generate-examples mode runs the scenario and records the cast). A demo is not a + staged screenshot; the example's `onref` side carries a minimal real `.ontoref/` + so the query actually answers.

    +

    Honest hook, not a recipe — the demo hooks by the PROBLEM (`description`) and + the question the "without" world cannot answer, never by a deterministic A→B + recipe. The cast shows the real query with its limit (sufficient, not complete); + the repulsion of the recipe-seeker is the qualifier (reach-vs-qualification), + not funnel leakage.

    + +

    Constraints

    + +
    • Hard A difusión demo MUST be authored as a Scenario (reflection/scenarios/<demo>/scenario.ncl) with purpose 'Demonstration — its description the problem, its run the management, its validates the proof — NOT as a new typed kind or standalone prose.
    • Hard The difusión surfaces (mdBook, example README, slides, web, site) MUST be projected by the generator from the single Scenario source, never hand-maintained per surface. A demo edit MUST regenerate every surface, not be copied.
    • Soft A demo's run MUST be executable and its .cast recorded from real execution against the onref branch (whose minimal .ontoref/ makes describe answer). A demo MUST NOT be a staged screenshot or a non-reproducible capture.
    • Soft Demo copy MUST hook by the problem (the question the 'without' world cannot answer) and MUST NOT promise a deterministic recipe. The cast MUST show the real query with its limit (sufficient, not complete).
    + +

    Alternatives considered

    + +
    • A new typed `CaseStudy` kind that projects a positioning `Proof`rejected: Scenario with purpose 'Demonstration already models problema/gestión/prueba; a CaseStudy kind adds schema, a migration, and a second seam duplicating the Proof it would reference. The Proof is reused as an honesty anchor instead, with no new kind.
    • Hand-authored prose case studies anchored to audience/value-prop ids by handrejected: Free prose drifts from the claims it sells, is not runnable, and is not verifiable — reintroducing the static, unverifiable examples that generate-examples exists to abolish ('the modes ARE the examples').
    • A static before/after code diff (same project, with and without the .ontoref/ files)rejected: A static diff sells a deterministic recipe (reach-vs-qualification) and shows ontoref's cost while hiding its benefit, which only appears at the moment of consequence. The demo's climax must be the runnable query the 'without' world cannot answer, recorded and named as sufficient-not-complete.
    + +

    Anti-patterns

    + +
    • Selling ontoref With a Static Before/After Code Diff — A demo is built as a static with/without code diff of the same project. It promises a deterministic recipe (reach-vs-qualification) and shows ontoref's cost — the extra .ncl files, the friction — while hiding its benefit, which only appears at the moment of consequence. It reads as ceremony.
    • Inventing a Typed Kind for What Scenario Already Models — A contributor adds a CaseStudy (or similar) typed kind to model difusión demos, adding schema and a migration, when Scenario with purpose 'Demonstration already carries description/run/validates. The new kind duplicates the positioning Proof it would reference.
    • Hand-Authoring the Same Demo Once Per Surface — The same demo is written separately for mdBook, the example README, the slides, the web page and the site. The five copies drift from each other and from the scenario — the per-surface drift projection exists to abolish.
    + +

    Related ADRs

    + +

    ADR-057 · ADR-035 · ADR-043 · ADR-029 · ADR-056

    diff --git a/site/site/content/adr/en/proposed/adr-058.ncl b/site/site/content/adr/en/proposed/adr-058.ncl new file mode 100644 index 0000000..f4638b9 --- /dev/null +++ b/site/site/content/adr/en/proposed/adr-058.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-058", + title = "Difusión Demos Are Demonstration-Scenarios Projected From the Modes, Not a New Kind or Hand-Authored Prose", + slug = "058", + excerpt = "", + tags = ["adr", "proposed"], + page_route = "/adr/058", + graph = { + implements = [], + related_to = ["adr-057", "adr-035", "adr-043", "adr-029", "adr-056"], + }, +} diff --git a/site/site/content/blog/en/_index.ncl b/site/site/content/blog/en/_index.ncl new file mode 100644 index 0000000..eb4a7ab --- /dev/null +++ b/site/site/content/blog/en/_index.ncl @@ -0,0 +1,9 @@ +# AUTO-GENERATED — do not edit manually +# Scope: blog/en +# Updated: 2026-06-24T02:32:42Z +# Regen: nu scripts/content/generate-ncl-index.nu + +std.array.flatten [ + import "./engineering/_index.ncl", + import "./ontoref/_index.ncl", +] diff --git a/site/site/content/blog/en/engineering/_index.ncl b/site/site/content/blog/en/engineering/_index.ncl new file mode 100644 index 0000000..ee0a11a --- /dev/null +++ b/site/site/content/blog/en/engineering/_index.ncl @@ -0,0 +1,8 @@ +# AUTO-GENERATED — do not edit manually +# Scope: blog/en/engineering +# Updated: 2026-06-24T02:32:42Z +# Regen: nu scripts/content/generate-ncl-index.nu + +[ + import "./release-engineering-from-one-model.ncl", +] diff --git a/site/site/content/blog/en/engineering/release-engineering-from-one-model.md b/site/site/content/blog/en/engineering/release-engineering-from-one-model.md new file mode 100644 index 0000000..4f157bd --- /dev/null +++ b/site/site/content/blog/en/engineering/release-engineering-from-one-model.md @@ -0,0 +1,177 @@ +--- +# Post metadata +id: "release-engineering-from-one-model" +title: "Release engineering from one model" +slug: "release-engineering-from-one-model" +subtitle: "Pre-commit, CI, justfiles, multi-arch OCI images and a curl|sh installer — generated from one typed model, not hand-maintained" +excerpt: "Release engineering is the most-copied, least-governed surface in any multi-repo org: CI YAML pasted between services until no two pipelines agree. ontoref models the entire release surface as one typed NCL workflow layer and generates the real artifacts from it — so the pipeline is queryable like any other architectural fact, and drift fails before it ships. ADR-038 added multi-arch OCI distribution as a catalog entry plus a generator, not a fork." + +# Publication info +author: "Jesús Pérez" +date: "2026-06-21" +published: true +featured: true + +# Categorization +category: "engineering" +tags: ["release-engineering", "ci", "oci", "nickel", "supply-chain", "rust"] + +# Image (card thumbnail + in-post featured) +thumbnail: "/images/ontoref-release-one-model-post.webp" +image_url: "/images/ontoref-release-one-model-post.webp" + +# Display +read_time: "8 min read" +sort_order: 1 +css_class: "category-engineering" +category_description: "Engineering" +category_published: true +--- + + + + + Release engineering from one model + + +# Release engineering from one model + +Release engineering is the most-copied, least-governed surface in any multi-repo +organisation. CI YAML gets pasted between services and patched per-repo until no +two pipelines agree. The build-and-ship procedure lives in the head of whoever +set it up. And nothing ties *how we release* back to the architectural decisions +that constrain it. When a registry moves, a signing policy changes, or an +architecture is added, every repo is edited by hand and the drift is invisible — +until a release breaks. + +ontoref takes the opposite stance: the entire release surface is **one typed +model**, and the real artifacts are generated from it. This post walks through +what that means concretely, using the decision that pushed it furthest — +shipping ontoref as a multi-arch OCI image and a `curl | sh` installer +(ADR-038). + +## One workflow layer, four triggers + +The release surface lives in a single NCL workflow model (`ontology/workflow.ncl`) +with four trigger-scoped layers: + +- `OnCommit` — pre-commit hooks +- `OnPR` — checks per pull request +- `OnMainMerge` — integration on merge to main +- `OnTag` — the release layer + +Inside those layers, work is typed. Build kinds carry `'Bundle` and `'Container`; +distribution kinds carry `'ContainerRegistry`, `'Artifact`, `'Package`. None of +these are strings in a YAML file — they are constructors in a typed model that a +generator reads. + +The generators turn the model into the artifacts you actually run: + +``` +generate-precommit → .pre-commit-config.yaml +generate-woodpecker → .woodpecker/*.yml +generate-justfile → justfiles/*.just +generate-distribution → .woodpecker/release.yml +render-installer → install/install.sh +generate-release-justfile → justfiles/release.just +``` + +The same NCL declares CI *and* the build *and* distribution. There is one source +of truth, and every emitted file is regenerable from it. + +## Why this beats programmable CI + +The obvious objection: programmable CI already exists — Dagger, Earthly, monorepo +task runners. They make pipelines into code. But code is not a *queryable model +governed by architectural decisions*. With those tools the pipeline is a program; +with ontoref the pipeline is a fact you can interrogate through the same +`describe` / HTTP / MCP surface as the ontology and the ADRs. + +Concretely, that buys two things hand-maintained YAML and programmable CI both +lack: + +- **Drift detection.** `onre workflow diff` reports divergence between the model + and the generated artifacts *before* it ships. A hand-edited `release.yml` is + caught, not discovered in production. +- **ADR anchoring.** The release surface is tied to the decisions that constrain + it. The distribution catalog isn't free-floating config; it's evidence for a + value proposition and guarded by typed constraints in an ADR. + +## ADR-038: adding OCI distribution without a fork + +Here's the part that proves the pattern. Until recently ontoref could only be +installed from a source checkout. Two install paths were wanted, sharing **one +artifact origin** in the OCI registry: + +1. A runnable multi-arch image — `docker run …/ontoref/ontoref-daemon:VERSION` +2. `curl … | sh` — an installer that detects OS/arch, pulls the matching bundle + (binary + data layer + wrappers), and installs into a prefix. + +The mechanism was **not** a pile of standalone scripts. The workflow layer +already had the exact seam: `build_kind` already carried `'Container`, +`distribution_kind` already carried `'ContainerRegistry | 'Artifact | 'Package`, +and the `distributions` catalog was empty with no generator. The layer had been +built anticipating exactly this. + +So the coherent move was to **populate the catalog and add the missing +generator** — not to invent a parallel packaging path that `onre describe` could +never see. A new provider is a catalog entry plus a generator, not a fork. That's +the whole shape of the change, and `onre workflow list` shows the release layer +afterward like any other build artifact. + +### Build-once: the image and the bundle are the same binary + +A typed model lets you state a hard property and enforce it. ADR-038's is +*build-once*: the musl binary is cross-compiled once per arch (`cross`), and both +the install bundle **and** the runnable image consume that same binary. The image +is assembled with `buildah` from the cross output plus the data layer — **no +`docker buildx`, no QEMU, no docker-in-docker** on the runner. + +This is cheaper and deterministic: a multi-arch index built from prebuilt +per-arch binaries, not by running an amd64 builder under arm64 emulation. And it +is a *constraint*, not a convention — the model forbids recompiling per arch to +build the image, so a future contributor reaching for the familiar +`docker buildx --platform` path is stopped by the model rather than silently +shipping a divergent binary. + +> **Aside — the cache problem you don't have to solve.** Teams compiling a large +> Rust workspace inside Docker spend real effort taming `cargo-chef`: every change +> to internal code busts the dependency-cache layer, and the fixes (stubbing +> workspace crates, lockfile-hash-tagged base images) are fiddly. ontoref +> sidesteps the whole category by building the binary *once* with `cross` and +> assembling the image from it. There is no in-Docker workspace compile to cache, +> so there is no cache to invalidate. + +### The axiom the decision had to protect + +Shipping a runnable daemon image risks reading ontoref as a *runtime* dependency +— and "Protocol, Not Runtime" is an invariant axiom. The model makes the +resolution explicit instead of hoping for it. The `curl|sh` installer installs a +CLI + data layer that work **without** the daemon (ADR-029: the daemon is a cache, +not a hard dependency); the runnable image is an **optional accelerator** for +container deployments. A typed constraint — +`daemon-image-optional-not-runtime` — guards that a future change cannot quietly +make the daemon required. + +This is the rare case where formalisation and adoption move *together*. The fear +is always that more formalisation raises adoption friction. Here the +formalisation — an NCL distribution catalog plus a generator — *produced* the +artifact that most lowers friction: a one-line `curl | sh` install on a bare +host, no source, no cargo, no nickel (nickel is bundled). + +## What you get from modeling it + +- ontoref installs on a bare Linux host with `curl … | sh` — no checkout, no + cargo, no nickel. +- The daemon runs as a container from the same registry origin, multi-arch, + cosign-signed with an attested SBOM. +- Distribution is queryable NCL: `onre workflow list` / `onre workflow diff` show + the release layer and catch drift; regeneration is deterministic. +- A publish gate (`smoke-bundle`) extracts the freshly-assembled bundle and runs + the binary before anything publishes — a broken artifact fails the pipeline + rather than reaching users. + +The point isn't that ontoref has a nice CI setup. It's that *how we release* is no +longer tribal knowledge or copy-pasted YAML. It's a typed fact, anchored to the +decisions that constrain it, regenerable on demand, and unable to drift without +the model saying so first. diff --git a/site/site/content/blog/en/engineering/release-engineering-from-one-model.ncl b/site/site/content/blog/en/engineering/release-engineering-from-one-model.ncl new file mode 100644 index 0000000..f14f0c4 --- /dev/null +++ b/site/site/content/blog/en/engineering/release-engineering-from-one-model.ncl @@ -0,0 +1,23 @@ +# Generated from release-engineering-from-one-model.md +# Schema: ../../../../../../../../../../jesusperezlorenzo/.local/share/rustelo/nickel/content/metadata/post_metadata.ncl +# Regenerate: nu scripts/content/md-to-ncl.nu --force + +let schema = import "../../../../../../../../../../jesusperezlorenzo/.local/share/rustelo/nickel/content/metadata/post_metadata.ncl" in + +schema.make_post { + id = "release-engineering-from-one-model", + title = "Release engineering from one model", + slug = "release-engineering-from-one-model", + subtitle = "Pre-commit, CI, justfiles, multi-arch OCI images and a curl|sh installer — generated from one typed model, not hand-maintained", + excerpt = "Release engineering is the most-copied, least-governed surface in any multi-repo org: CI YAML pasted between services until no two pipelines agree. ontoref models the entire release surface as one typed NCL workflow layer and generates the real artifacts from it — so the pipeline is queryable like any other architectural fact, and drift fails before it ships. ADR-038 added multi-arch OCI distribution as a catalog entry plus a generator, not a fork.", + date = "2026-06-21", + featured = true, + category = "engineering", + tags = ["release-engineering", "ci", "oci", "nickel", "supply-chain", "rust"], + read_time = "8 min read", + sort_order = 1, + graph = { + implements = ["oci-distribution", "adr-038", "adr-029", "adr-017", "protocol-not-runtime", "formalization-vs-adoption"], + related_to = [], + }, +} \ No newline at end of file diff --git a/site/site/content/blog/en/ontoref/_index.ncl b/site/site/content/blog/en/ontoref/_index.ncl new file mode 100644 index 0000000..2affe2c --- /dev/null +++ b/site/site/content/blog/en/ontoref/_index.ncl @@ -0,0 +1,14 @@ +# AUTO-GENERATED — do not edit manually +# Scope: blog/en/ontoref +# Updated: 2026-06-24T02:32:42Z +# Regen: nu scripts/content/generate-ncl-index.nu + +[ + import "./ai-knowledge-tool-who-keeps-it-alive.ncl", + import "./dags-everywhere-none-know-what-they-are.ncl", + import "./one-protocol-multiple-subjects.ncl", + import "./the-seven-sins-of-ai-agents.ncl", + import "./trust-is-an-output-not-an-input.ncl", + import "./your-ontology-should-live-with-your-code.ncl", + import "./context-declared-not-filled.ncl", +] diff --git a/site/site/content/blog/en/ontoref/ai-knowledge-tool-who-keeps-it-alive.md b/site/site/content/blog/en/ontoref/ai-knowledge-tool-who-keeps-it-alive.md new file mode 100644 index 0000000..9bc302b --- /dev/null +++ b/site/site/content/blog/en/ontoref/ai-knowledge-tool-who-keeps-it-alive.md @@ -0,0 +1,107 @@ +--- +# Post metadata +id: "ai-knowledge-tool-who-keeps-it-alive" +title: "AI is a Knowledge Tool. But Who Keeps the Knowledge Alive?" +slug: "ai-knowledge-tool-who-keeps-it-alive" +subtitle: "Talisman is right — and stops exactly where the hard problem begins" +excerpt: "Jessica Talisman's KGC 2026 talk is the clearest articulation of why AI strategies fail: organizations invest in data infrastructure and expect reasoning to emerge. She's right. But her solution — build knowledge infrastructure — stops exactly where the hard problem begins: who keeps the knowledge from drifting?" + +# Publication info +author: "Jesús Pérez" +date: "2026-05-10" +published: true +featured: false + +# Categorization +category: "ontoref" +tags: ["ontoref", "knowledge-graphs", "ontology", "ai-infrastructure", "reflection"] + +# Image (card thumbnail + in-post featured) +thumbnail: "/images/ontoref-ai-knowledge-post.webp" +image_url: "/images/ontoref-ai-knowledge-post.webp" + +# Display +read_time: "7 min read" +sort_order: 1 +css_class: "category-ontoref" +category_description: "Ontoref — protocol and tooling for structured self-knowledge in software projects" +category_published: true +--- + + + + + AI is a knowledge tool — but who keeps the knowledge alive? + + +# AI is a Knowledge Tool. But Who Keeps the Knowledge Alive? + +Jessica Talisman delivered a talk at KGC 2026 called "Stop Betting, Start Building." The data she opens with is brutal: + +- **89%** of firms report zero productivity impact from AI after three years (NBER, Feb 2026, n=5,937 executives) +- Experienced developers are **19% slower** with AI tools, not faster (METR RCT, 2025) +- The average AI-using worker gains **−14 minutes** per week in net productivity (Foxit/Sapio, March 2026) + +The market is bullish. The evidence is not. + +Her diagnosis is correct: *AI is a knowledge tool, not a data tool.* Organizations are pouring money into data lakes, vector stores, and ETL pipelines and expecting context and reasoning to emerge from raw tokens. It won't. Models were trained on linked data, RDF triples, and controlled vocabularies — the top fifteen C4 training sources are knowledge-graph-heavy. They're then deployed against environments stripped of all that structure, and we wonder why they hallucinate. + +Her prescription is also correct: build knowledge infrastructure. Controlled vocabularies first. Taxonomies. Thesauri. Ontologies — formal commitments, classes, properties, constraints. Knowledge graphs on top. And govern them. Forever, not as a project. + +She's right. And she stops exactly where the hard problem begins. + +## The Governance Gap + +The sixth step in her stack is "Govern — this is infrastructure, stewardship, versioning, growth, forever." She names the problem. But naming it is not a mechanism. + +The real question is: *who ensures the ontology doesn't drift from the system it describes?* + +Software evolves. An architectural decision made in March invalidates a node in the graph by October. A new team joins with different mental models. A library gets replaced and a constraint becomes fictional. Knowledge graphs accumulate debt the same way codebases do — silently, without an alarm. + +In the enterprise knowledge graph world, the answer to governance is headcount: hire ontologists, librarians, knowledge engineers. That works at organizational scale with dedicated budgets. It is not a viable answer for a software project, an infrastructure environment, or an individual trying to maintain structured self-knowledge. + +## What's Actually Missing: the Operational Loop + +Every serious ontology without an operational closure layer becomes archaeology within eighteen months. Beautiful, formally correct, and describing a system that no longer exists. + +The missing piece is not more knowledge. It's a feedback loop that: + +1. **Observes** the current state of the system against its declared intent +2. **Detects** drift before it becomes permanent +3. **Executes** operations that reduce that drift +4. **Records** decisions with lasting architectural weight +5. **Propagates** changes to everything that depends on this knowledge + +This is the Yang to the ontology's Yin. Talisman describes the Yin in precise detail. The Yang is what makes it not an artifact. + +## Ontoref: Both Halves + +Ontoref is a protocol for structured self-knowledge in software projects. It operates as two coexisting layers that cannot function without each other: + +**Ontology (what IS):** typed nodes and edges representing practices, principles, tensions, capabilities. Not documentation — formal commitments. A `Practice` node that `implements` a `Principle`, `enforces` a `Constraint`, `enables` a `Capability`, and is in active `tension` with another `Practice`. The project as a knowledge graph. + +**Reflection (what BECOMES):** executable DAGs — operational modes that run against the ontological state, detect drift, report transitions, and propagate changes. The `sync diff --docs` command that catches when a crate's documentation has drifted from its ontology node. The FSM in `state.ncl` that tracks where each project dimension is versus where it intends to go. The migrations system that ensures protocol changes reach every consumer project. + +The tension between these two layers is not a design flaw — it's named explicitly in the project's own ontology as its core identity: + +> *"Ontology captures what IS. Reflection captures what BECOMES. Both must coexist without one dominating."* + +Without Reflection, the Ontology crystallizes. It becomes a snapshot of what the project wanted to be at the moment it was written. Without Ontology, Reflection is execution without truth — it knows how to operate but not what it's operating on. + +## The Accuracy Numbers + +Talisman's data on what structured knowledge actually does to AI accuracy is worth repeating: + +- Question-answering on enterprise SQL: **16% → 72%** when an ontology checks and repairs LLM-generated queries (Allemang & Sequeda, data.world AI Lab, 2024) +- GraphRAG vs. vector RAG: **3.4× accuracy** across 43 enterprise queries (Diffbot KG-LM Benchmark, 2023) +- Vector RAG collapses to **0% accuracy past five entities per query**. KG-grounded retrieval sustains performance well beyond that + +The accuracy gap is not closed by a bigger model. It is closed by a defined schema, an ontology, and a validated query. + +But only if the ontology is kept alive. Only if someone — or something — is running the operational loop that keeps it from drifting into fiction. + +That's the part Talisman's framework doesn't provide. That's what Reflection is for. + +--- + +*Ontoref is open source. The protocol specification, Nushell automation, and Rust crates are at [github.com/jesusperezlorenzo/ontoref](https://github.com/jesusperezlorenzo/ontoref).* diff --git a/site/site/content/blog/en/ontoref/ai-knowledge-tool-who-keeps-it-alive.ncl b/site/site/content/blog/en/ontoref/ai-knowledge-tool-who-keeps-it-alive.ncl new file mode 100644 index 0000000..e52ed67 --- /dev/null +++ b/site/site/content/blog/en/ontoref/ai-knowledge-tool-who-keeps-it-alive.ncl @@ -0,0 +1,24 @@ +# Generated from ai-knowledge-tool-who-keeps-it-alive.md +# Schema: ../../../../../../../../../../jesusperezlorenzo/.local/share/rustelo/nickel/content/metadata/post_metadata.ncl +# Regenerate: nu scripts/content/md-to-ncl.nu --force + +let schema = import "../../../../../../../../../../jesusperezlorenzo/.local/share/rustelo/nickel/content/metadata/post_metadata.ncl" in + +schema.make_post { + id = "ai-knowledge-tool-who-keeps-it-alive", + title = "AI is a Knowledge Tool. But Who Keeps the Knowledge Alive?", + slug = "ai-knowledge-tool-who-keeps-it-alive", + subtitle = "Talisman is right — and stops exactly where the hard problem begins", + excerpt = "Jessica Talisman's KGC 2026 talk is the clearest articulation of why AI strategies fail: organizations invest in data infrastructure and expect reasoning to emerge. She's right. But her solution — build knowledge infrastructure — stops exactly where the hard problem begins: who keeps the knowledge from drifting?", + date = "2026-05-10", + category = "ontoref", + tags = ["ontoref", "knowledge-graphs", "ontology", "ai-infrastructure", "reflection"], + read_time = "7 min read", + sort_order = 1, + image_url = "/images/ontoref-ai-knowledge-post.webp", + thumbnail = "/images/ontoref-ai-knowledge-post.webp", + graph = { + implements = ["drift-observation", "internal-coherence-enforced", "enforcement-vs-emergence", "witness-as-axis-seam", "sufficient-verification"], + related_to = ["one-protocol-multiple-subjects", "dags-everywhere-none-know-what-they-are", "your-ontology-should-live-with-your-code"], + }, +} \ No newline at end of file diff --git a/site/site/content/blog/en/ontoref/context-declared-not-filled.md b/site/site/content/blog/en/ontoref/context-declared-not-filled.md new file mode 100644 index 0000000..bf4e85c --- /dev/null +++ b/site/site/content/blog/en/ontoref/context-declared-not-filled.md @@ -0,0 +1,133 @@ +--- +# Post metadata +id: "context-declared-not-filled" +title: "Declared, Not Filled: Context You Can Audit" +slug: "context-declared-not-filled" +subtitle: "The AI window fills a context and forgets why it held those tokens. Library science has declared context as a typed, warranted property since 1911 — and ontoref extends the same move from the catalogued term to the governed act." +excerpt: "Jessica Talisman's essay splits the year's favourite buzzword in two: context as a container you fill, or context as a property you declare. ANSI/NISO Z39.19 has held the property view for a century — qualifiers, scope notes, warrant. ontoref reached the same record shape without ever citing the standard, then pushed it one step further: from declaring context on terms to declaring it on acts, where a signed witness answers 'on whose authority does this result exist?' with an exit code instead of fluency." + +# Publication info +author: "Jesús Pérez" +date: "2026-07-09" +published: true +featured: true + +# Categorization +category: "ontoref" +tags: ["ontoref", "context-engineering", "library-science", "warrant", "knowledge-graphs", "developer", "knowledge-works"] + +# Image (card thumbnail + in-post featured) +thumbnail: "/images/ontoref-context-declared-not-filled.webp" +--- + + + + + Declared, Not Filled: Context You Can Audit + + +There is an essay making the rounds — Jessica Talisman's [*Context Is a Property, Not an +Object*](https://jessicatalisman.substack.com/p/context-is-a-property-not-an-object) +(Intentional Arrangement, July 2026) — that cuts the year's favourite buzzword in half. +The AI industry, she observes, talks about context as a **container**: a window to fill, +a token budget to spend, a string assembled at the last moment and discarded when +inference ends. Library and information science, codified in ANSI/NISO Z39.19, holds the +opposite view: context is a **property** — declared at design time, attached to concepts +and relationships, tested, displayed, maintained. Qualifiers that fix which *Mercury* you +mean. Scope notes that travel with the term. Warrant that records on whose authority a +category exists at all. + +We read that essay and recognised our own design bet — stated from a tradition a century +older than the problem ontoref was built to solve. Two verbs carry the whole difference: +a window is **filled**; a property is **declared**. And we declare precisely to make the +context visible and explicit — so nothing is completed by invention. The declared can be +verified; the filled cannot. Which turns the year's buzzword into an old, sharper question: +on whose authority do we believe a result — a signature and an exit code, or fluency? + +## The question the window never asks + +Talisman's sharpest instrument is **warrant**, named by E. Wyndham Hulme in 1911: a term +enters a vocabulary because evidence put it there — literature, community, operation — +never because a designer preferred it. Her diagnosis of the language model follows in one +line: it generates taxonomies whose authority is *borrowed from fluency*. A window filled +at inference time carries whatever retrieval returned, and the reason those tokens existed +dies with them. No record, no authority, no audit trail. + +ontoref is built on the same refusal, enforced. A Live value-prop MUST cite Accepted ADRs +(`evidence_adrs`, audited for drift). A template earns promotion to the protocol only on +deposited `emergence.log` evidence — use warrant, operationally. And since the +governed-delivery executor (ADR-066), a gate step's status derives from the declared +check's real exit code: a self-report that contradicts the check is refused. Authority +borrowed from fluency is precisely what the witness seam exists to reject. + +## Convergence, field for field + +The uncomfortable, satisfying detail: ontoref never cited Z39.19. The glossary term schema +was designed from the protocol's own needs — and it converged on the standard almost field +for field. + +| Z39.19 / library science | ontoref | +|---|---| +| Qualifier disambiguates the homograph | `Term.aliases` + bilingual `definition` (`glossary.ncl`) | +| Scope note: coverage, usage, assignment rules | `definition` + `examples` + `forbidden` | +| Warrant — evidence admits the term | `origin = { kind, ref }` on terms; `evidence_adrs` on value-props; `emergence.log` gating promotion | +| Associative relations, declared not derived | `related_terms` / `related_nodes`, typed edges in `core.ncl` | +| Display the term in its full web of relations | `describe`, the browsable graph, `view mount` | +| Provenance (PROV-O: generated-by, attributed-to) | the Ed25519, content-addressed witness (ADR-050) | +| Live authority lookup at the moment of cataloguing | the daemon + MCP surface at the moment of the act | + +Two disciplines — one cataloguing meaning, one governing agent work — arriving at the same +record shape is not imitation. It is the strongest kind of validation prior art can give: +independent rediscovery. + +## From the term to the act + +Here is where the twist stops being someone else's essay and becomes ontoref's own move. +Library science declares context on *terms*. ontoref extends the identical move to *work*. +A **Statement of Work** is one set of terms — declared before execution and owned outside +the worker — plus its **Work Orders**, plus the signed witness each one deposits on +completion. That witness is what Talisman calls **operational warrant**: the audit trail +that answers "on whose authority does this result exist?" with a signature and an exit code +instead of with fluency. Where the object approach spends its tokens and the reason they +existed evaporates, the governed act leaves a record. + +Her production evidence points the same way. The remedy that worked for LLM cataloguing was +not a bigger window — it was a server that made the governed structure *traversable at the +moment of the act*. ontoref's daemon and MCP tools are that remedy, generalised from the +catalogue to the architecture. + +## The window becomes a projection + +"The window still exists in the property world, but it becomes a projection of a described +state rather than a construction site." That sentence is the reveal architecture (ADR-057) +verbatim: what you see is generated from a declared source, and a drift is fixed by +re-running the projection, never by hand-editing the served copy. What the agent — or the +visitor — sees is never assembled improvisation. It is a declared state, projected. The +window did not disappear; it stopped being where the truth is decided. + +## What the lineage buys — and what it does not + +It buys a name and a floor. The category ontoref occupies now has a title older than the +industry that needs it — *context as property* — and a century of bibliographic prior art +underneath: Z39.19, warrant, authority control, SKOS. That floor qualifies rather than +widens: it will not seduce the recipe-seeker, and it will recognise the +knowledge-organization reader who has lived the property view all along. + +It does not certify us finished, and honesty is part of the record too. Warrant is typed on +glossary terms and value-props but still prose inside the ontology's node descriptions. +Z39.19's reciprocity rule — every relation A→B must carry B→A, "for all types of +relationships" — is stricter than our current validators. And emergence remains claim-only, +a spectrum asserted but not yet measured. Prior art is a mirror, not a diploma — and each of +these gaps now has a date on it. + +--- + +If you would rather traverse the claim than read it argued: the +[**graph**](/graph) is the declared state, browsable — every node carries its own warrant, +every edge its kind. Or step through the door that fits the work you do: the +[**developer door**](/blog?tag=developer) for governing an agent that cannot promise to +follow your rules, the [**authoring door**](/blog?tag=knowledge-works) for keeping a body of +knowledge coherent across everyone who touches it. A sister essay, +[*The Amnesiac Scholar*](/blog?tag=knowledge-works), records the same thesis arriving from +the opposite shore — machine learning's own admission that the agent needs a memory it can +carry, governed and legible, between sessions. diff --git a/site/site/content/blog/en/ontoref/context-declared-not-filled.ncl b/site/site/content/blog/en/ontoref/context-declared-not-filled.ncl new file mode 100644 index 0000000..39550a1 --- /dev/null +++ b/site/site/content/blog/en/ontoref/context-declared-not-filled.ncl @@ -0,0 +1,25 @@ +# Generated from context-declared-not-filled.md +# Schema: ../../../../../../../../../../jesusperezlorenzo/.local/share/rustelo/nickel/content/metadata/post_metadata.ncl +# Regenerate: nu scripts/content/md-to-ncl.nu --force + +let schema = import "../../../../../../../../../../jesusperezlorenzo/.local/share/rustelo/nickel/content/metadata/post_metadata.ncl" in + +schema.make_post { + id = "context-declared-not-filled", + title = "Declared, Not Filled: Context You Can Audit", + slug = "context-declared-not-filled", + subtitle = "The AI window fills a context and forgets why it held those tokens. Library science has declared context as a typed, warranted property since 1911 — and ontoref extends the same move from the catalogued term to the governed act.", + excerpt = "Jessica Talisman's essay splits the year's favourite buzzword in two: context as a container you fill, or context as a property you declare. ANSI/NISO Z39.19 has held the property view for a century — qualifiers, scope notes, warrant. ontoref reached the same record shape without ever citing the standard, then pushed it one step further: from declaring context on terms to declaring it on acts, where a signed witness answers 'on whose authority does this result exist?' with an exit code instead of fluency.", + date = "2026-07-09", + featured = true, + category = "ontoref", + tags = ["ontoref", "context-engineering", "library-science", "warrant", "knowledge-graphs", "developer", "knowledge-works"], + read_time = "6 min read", + sort_order = 7, + image_url = "/images/ontoref-context-declared-not-filled.webp", + thumbnail = "/images/ontoref-context-declared-not-filled.webp", + graph = { + implements = ["witness-as-axis-seam", "adr-050", "adr-057", "adr-063", "adr-066"], + related_to = ["trust-is-an-output-not-an-input", "ai-knowledge-tool-who-keeps-it-alive"], + }, +} diff --git a/site/site/content/blog/en/ontoref/dags-everywhere-none-know-what-they-are.md b/site/site/content/blog/en/ontoref/dags-everywhere-none-know-what-they-are.md new file mode 100644 index 0000000..3536401 --- /dev/null +++ b/site/site/content/blog/en/ontoref/dags-everywhere-none-know-what-they-are.md @@ -0,0 +1,116 @@ +--- +# Post metadata +id: "dags-everywhere-none-know-what-they-are" +title: "DAGs Are Everywhere. None of Them Know What They Are." +slug: "dags-everywhere-none-know-what-they-are" +subtitle: "Every build system, CI pipeline, and runbook uses DAGs for execution. Ontoref uses them for knowledge." +excerpt: "CI/CD pipelines, compilers, runbooks, data orchestrators — they all use directed acyclic graphs. Every single one of them uses DAGs as execution models: this before that, topological ordering, dependency resolution. None of them use DAGs to represent what the system is, why it exists, or what trade-offs define it. That's the gap ontoref fills." + +# Publication info +author: "Jesús Pérez" +date: "2026-05-10" +published: true +featured: false + +# Categorization +category: "ontoref" +tags: ["ontoref", "dag", "ontology", "knowledge-graphs", "software-architecture"] + +# Image (card thumbnail + in-post featured) +thumbnail: "/images/ontoref-dags-everywhere_post.webp" +image_url: "/images/ontoref-dags-everywhere_post.webp" + +# Display +read_time: "6 min read" +sort_order: 2 +css_class: "category-ontoref" +category_description: "Ontoref — protocol and tooling for structured self-knowledge in software projects" +category_published: true +--- + + + + + DAGs are everywhere — but none of them know what they are; <a href=ontoref uses DAGs for knowledge, not just execution" width="1536" height="1024" loading="lazy"> + + +# DAGs Are Everywhere. None of Them Know What They Are. + +Every build system uses DAGs. Every CI/CD pipeline uses DAGs. Compilers, data orchestrators, runbooks, package managers, Kubernetes operators — all DAGs. Directed acyclic graphs are so ubiquitous in software infrastructure that the question "why DAGs?" barely registers as a question anymore. + +But there's a second question nobody asks: *what do those DAGs represent?* + +The answer is always the same: **execution order**. This before that. Topological sorting. Dependency resolution. The graph describes *how* something computes, not *what* it is. + +``` +CI/CD: test → build → deploy +Compiler: parse → typecheck → codegen → link +Runbook: check_health → drain → restart → verify +``` + +These graphs are inert with respect to the system they operate on. A CI pipeline doesn't know what the project is, why it was built this way, or what trade-offs define its architecture. It knows that tests must pass before deployment. That's all it knows. + +## The Semantic Gap + +This inertness is not a flaw — it's appropriate. Build systems should be fast and mechanical. Runbooks should be executable without requiring philosophical knowledge about the system. Execution graphs and knowledge graphs are different tools for different purposes. + +The problem is that the software industry has reached for DAGs for *everything except knowledge representation*. The knowledge of what a system is — its principles, its architectural decisions, its active tensions, its capability surface — lives in documents. Wikis. READMEs. Tickets. Verbal tradition. + +These are all unstructured, un-typed, un-queryable, and guaranteed to drift from the actual system within months. They describe what the project was, not what it is. + +## Ontoref's Dual DAG + +Ontoref uses DAGs in two fundamentally different modes, and the distinction matters: + +**Ontological DAG — semantically typed edges:** + +``` +Practice "ncl-schemas" --implements--> Principle "type-safety" +Practice "ncl-schemas" --enforces--> Constraint "zero-runtime-deps" +Practice "ncl-schemas" --enables--> Capability "agent-queryable-state" +Practice "ncl-schemas" --tension--> Practice "adoption-friction" +``` + +These edges are not "depends on." They are typed relationships: `implements`, `enforces`, `enables`, `tension`. The graph is the knowledge — formal commitments about what the project is and how its concepts relate. The equivalent would be a compiler that knows why it exists and what architectural trade-offs it made. + +**Reflection DAG — executable contracts with actor restrictions:** + +``` +step "validate-state" (actor: any) --> step "run-mode" (actor: developer) +step "run-mode" (actor: developer) --> step "report" (actor: agent|developer) +``` + +This is not just execution ordering. The DAG is validated as a contract before it runs, records its own progress through state transitions, and encodes who can execute each step. An agent trying to run a developer-restricted step receives a typed rejection, not a runtime error. + +## The Missing Connection + +What makes this non-trivial is that the two DAGs communicate: + +- The ontological DAG declares what the project IS — active constraints, practices in tension, current state dimensions +- The reflection DAG OPERATES on that state — detecting drift, executing modes, recording transitions +- Migrations propagate changes in the ontological DAG to downstream consumer projects + +In every other system that uses DAGs, the graph is evaluated and discarded. In ontoref, evaluation modifies the state that the next execution reads. The graph has memory. + +## Why This Didn't Exist Before + +Three things had to align: + +**Agents as consumers.** Before agentic AI, there was no automated consumer that needed structured knowledge about a project. Humans could read the wiki. Agents cannot — they need typed, queryable, machine-readable project knowledge to work accurately rather than hallucinate context. Ontoref's knowledge DAG is what agents consume via MCP. + +**Configuration languages with contracts.** Expressing an ontology without an external triplestore required a configuration language with types and contracts. Nickel (NCL) provides exactly that: a typed, lazy configuration language where schema violations are caught at evaluation time, not at runtime. RDF/OWL would have required dedicated infrastructure and specialist expertise. + +**Repository scale, not enterprise scale.** Enterprise knowledge graphs are multi-year initiatives. Ontoref operates at the scale of a single repository — incremental adoption, no enforcement, no dedicated team. The smallest unit that can adopt it is a project of one. + +## The Consequence + +When a project has a knowledge DAG that its agents can consume, the accuracy arithmetic changes entirely. The numbers from KGC 2026 research: + +- Ontology-grounded retrieval: **3.4× more accurate** than vector RAG on enterprise queries +- Ontology-validated queries: accuracy jumps from **16% to 72%** on SQL question-answering + +The gap is closed not by a bigger model but by structured knowledge the model can reason against. A DAG where the edges mean something. + +--- + +*Ontoref is open source. The protocol specification, Nushell automation, and Rust crates are at [github.com/jesusperezlorenzo/ontoref](https://github.com/jesusperezlorenzo/ontoref).* diff --git a/site/site/content/blog/en/ontoref/dags-everywhere-none-know-what-they-are.ncl b/site/site/content/blog/en/ontoref/dags-everywhere-none-know-what-they-are.ncl new file mode 100644 index 0000000..60cf94f --- /dev/null +++ b/site/site/content/blog/en/ontoref/dags-everywhere-none-know-what-they-are.ncl @@ -0,0 +1,24 @@ +# Generated from dags-everywhere-none-know-what-they-are.md +# Schema: ../../../../../../../../../../jesusperezlorenzo/.local/share/rustelo/nickel/content/metadata/post_metadata.ncl +# Regenerate: nu scripts/content/md-to-ncl.nu --force + +let schema = import "../../../../../../../../../../jesusperezlorenzo/.local/share/rustelo/nickel/content/metadata/post_metadata.ncl" in + +schema.make_post { + id = "dags-everywhere-none-know-what-they-are", + title = "DAGs Are Everywhere. None of Them Know What They Are.", + slug = "dags-everywhere-none-know-what-they-are", + subtitle = "Every build system, CI pipeline, and runbook uses DAGs for execution. Ontoref uses them for knowledge.", + excerpt = "CI/CD pipelines, compilers, runbooks, data orchestrators — they all use directed acyclic graphs. Every single one of them uses DAGs as execution models: this before that, topological ordering, dependency resolution. None of them use DAGs to represent what the system is, why it exists, or what trade-offs define it. That's the gap ontoref fills.", + date = "2026-05-10", + category = "ontoref", + tags = ["ontoref", "dag", "ontology", "knowledge-graphs", "software-architecture"], + read_time = "6 min read", + sort_order = 2, + image_url = "/images/ontoref-dags-everywhere_post.webp", + thumbnail = "/images/ontoref-dags-everywhere_post.webp", + graph = { + implements = ["dag-formalized", "ontology-axis-substance", "reflection-modes", "ontology-vs-reflection", "self-describing"], + related_to = ["one-protocol-multiple-subjects", "your-ontology-should-live-with-your-code", "ai-knowledge-tool-who-keeps-it-alive"], + }, +} \ No newline at end of file diff --git a/site/site/content/blog/en/ontoref/one-protocol-multiple-subjects.md b/site/site/content/blog/en/ontoref/one-protocol-multiple-subjects.md new file mode 100644 index 0000000..86840fc --- /dev/null +++ b/site/site/content/blog/en/ontoref/one-protocol-multiple-subjects.md @@ -0,0 +1,153 @@ +--- +# Post metadata +id: "one-protocol-multiple-subjects" +title: "One Protocol, Multiple Subjects" +slug: "one-protocol-multiple-subjects" +subtitle: "The same question applies to your project, your infrastructure, your work, and yourself" +excerpt: "Ontoref started as a protocol for software project self-knowledge. The same architecture — an ontological DAG for what IS, a reflection DAG for what BECOMES — applies without modification to infrastructure environments, to a body of authored work, and to individuals. The subject changes. The question is identical: what are you, what tensions define you, where are you versus where you intend to be?" + +# Publication info +author: "Jesús Pérez" +date: "2026-05-10" +published: true +featured: true + +# Categorization +category: "ontoref" +tags: ["ontoref", "ontology", "infrastructure", "knowledge-works", "personal-knowledge", "reflection"] + +# Display +read_time: "8 min read" +sort_order: 3 +css_class: "category-ontoref" +category_description: "Ontoref — protocol and tooling for structured self-knowledge in software projects" +category_published: true + +# Image (card thumbnail + in-post featured) +thumbnail: "/images/ontoref-one-protocol-multiple-subjects.webp" + +--- + + + + + One Protocol, Multiple Subjects + + +# One Protocol, Multiple Subjects + +The question ontoref asks of a software project is precise: *What are you? What principles define you? What tensions are you actively managing? What decisions have you made with lasting consequences? Where are you versus where you intend to be?* + +This turns out to be the same question that infrastructure environments need answered. And a body of authored work. And individuals. + +The subject changes. The question is identical. + +## What They All Have in Common + +Any system with identity and intent shares the same structural problem: + +- **Identity** — what it is, what principles define it +- **Active tensions** — trade-offs that don't resolve, they're managed +- **Durable decisions** — choices with lasting consequences that constrain the future +- **Observable state** — where it is versus where it intends to be +- **Drift** — the distance between intention and reality, growing silently with time +- **Operations** — the actions that reduce that drift + +Software projects have all of these. So do infrastructure environments. So does a body of authored work. So do people. + +And they all benefit from the same architecture: an ontological layer that captures what IS, and a reflection layer that captures what BECOMES and operates to keep those two aligned. + +## Infrastructure + +Infrastructure already uses DAGs — Terraform, Pulumi, Ansible. All of them describe *how* the system is provisioned. None of them capture *why it exists as it does*. + +The `why` is where knowledge lives: + +``` +Service "auth" --tension--> Principle "stateless" + reason: needs session state, but the principle forbids it + +Constraint "zero-downtime" --enforces--> Practice "blue-green" + reason: ADR-infra-003, after the 2025 deploy incident +``` + +This information is not in the Terraform plan. It's in the memory of the engineer who was there, in a Slack thread from eight months ago, or nowhere at all. When that engineer leaves, the infrastructure loses its own history — it knows what it is, not why. + +Infrastructure ontoref captures the architectural decisions of the infrastructure itself, with the same ADR formalism as software projects. It tracks state dimensions — which environments are stable, which are in active migration, what's blocked and why. The FSM doesn't track deployment status; it tracks *epistemic status* — how well the infrastructure understands itself. + +Federation via NATS means each environment (dev, staging, prod) describes itself and can be queried from the projects that depend on it. Sovereign GitOps: the infrastructure knowledge lives with the infrastructure code, not in a vendor's database. + +## Authoring — the Work + +This is the subject ontoref reached last, and the one where the drift is most visible to the reader. + +A Work — a book, a course, a documented method, a research corpus — is an authored coherent whole. It has a thesis, an architecture, and decisions that constrain every later edition. It faces the identical structural problem: + +``` +Chapter "field notes" --tension--> Thesis "method over recipe" + the chapter earns its place only if it serves the whole + +Decision "restructured Part II" --constraint--> every translation and adaptation + with the same architectural weight as ADR-001 + +State "draft" --blocker--> "second edition" + a dimension of the Work's FSM, not a task in a backlog +``` + +The drift here has a name authors already feel: the Work is disseminated as fragments — a post, a talk, an excerpt, a thread — and each fragment travels without the whole. Over time the fragments say things the Work no longer means, or the Work moves and the fragments stay behind. The reader meets the fragment, never the governed whole. + +Authoring ontoref treats the Work as the subject and every fragment as a provenanced edge back to it. The ontological layer holds what the Work *is* — its thesis, its structure, the decisions that bind future editions. The reflection layer operates the dissemination: which fragments are live, which have drifted, which edition they belong to. The book is the archetype, not the boundary; dissemination is an effect of the Work, never its essence. The door says it in one line: **diffuse the whole Work, not loose fragments.** + +## Personal + +This is the most radical application. But it follows directly from the same logic. + +A person with values, roles, and long-term intentions faces exactly the same structural problem as a software project: + +``` +Value "depth" --tension--> Role "parent" + reason: time is finite and both demand it fully + +Decision "left company X" --constraint--> Future career choices + with the same architectural weight as ADR-001 + +State "learning Rust" --blocker--> "contribute to ontoref-core" + a personal FSM dimension, not a task list item +``` + +The knowledge graph here is not a productivity system. It's not a second brain of notes. It's a graph of *commitments* — typed, provenanced, with active tensions named rather than suppressed. + +The drift problem is identical: who you are becoming versus who you intended to be. Reflection modes for a person might be weekly reviews, annual state assessments, or triggered reviews when a major decision introduces new constraints. The operational loop is the same — observe, detect drift, execute, record. + +## The Same Stack, Different Subject + +Every one of these domains runs on the same infrastructure: + +**Sovereign storage:** Knowledge lives alongside its subject — in the project repo, in the infrastructure code, in the author's repository of the Work, in a personal vault. No vendor holds it. No platform dependency. NCL files versioned with the same discipline as code. + +**Multi-surface access:** + +| Surface | Consumer | +|---|---| +| CLI (Nushell) | Developer, scripts, CI | +| UI (axum) | Visual review, onboarding | +| MCP | AI agents | +| GraphQL | External tools, dashboards | + +**VCS alignment:** jj's model — where the working copy is always a commit — aligns naturally with ontoref's approach to continuous state capture. There is no "dirty state"; every moment is capturable. Radicle as transport makes the whole stack sovereign and P2P — code collaboration without a central platform. + +**Federation:** Projects querying each other's knowledge graphs without a central broker. Infrastructure environments exposing their self-knowledge to the projects that depend on them. Authors whose Work exposes its structure so every citation and excerpt resolves back to the whole. Individuals whose personal knowledge graph informs their contributions to the projects they work on. + +## Why Scale Matters + +The smallest unit that can adopt ontoref is a person. One developer, one project, one infrastructure environment, one author with one Work. No team required. No budget conversation. + +The largest unit is an ecosystem of federated, self-describing projects that can query each other's knowledge without any of them being the authoritative central source. + +That range — individual to ecosystem — is possible because the protocol is the same at every scale. The ontological DAG and the reflection DAG don't change shape based on the size of the subject. They change in density and depth, not in structure. + +The organizations that Talisman argues should invest in knowledge infrastructure are trying to build this top-down, with dedicated teams and multi-year programs. Ontoref makes it available bottom-up — starting with a single project, a single environment, a single Work, a single person, each describing themselves in the same language. + +--- + +*Ontoref is open source. The protocol specification, Nushell automation, and Rust crates are at [github.com/jesusperezlorenzo/ontoref](https://github.com/jesusperezlorenzo/ontoref).* diff --git a/site/site/content/blog/en/ontoref/one-protocol-multiple-subjects.ncl b/site/site/content/blog/en/ontoref/one-protocol-multiple-subjects.ncl new file mode 100644 index 0000000..d3e4be3 --- /dev/null +++ b/site/site/content/blog/en/ontoref/one-protocol-multiple-subjects.ncl @@ -0,0 +1,25 @@ +# Generated from one-protocol-multiple-subjects.md +# Schema: ../../../../../../../../../../jesusperezlorenzo/.local/share/rustelo/nickel/content/metadata/post_metadata.ncl +# Regenerate: nu scripts/content/md-to-ncl.nu --force + +let schema = import "../../../../../../../../../../jesusperezlorenzo/.local/share/rustelo/nickel/content/metadata/post_metadata.ncl" in + +schema.make_post { + id = "one-protocol-multiple-subjects", + title = "One Protocol, Multiple Subjects", + slug = "one-protocol-multiple-subjects", + subtitle = "The same question applies to your project, your infrastructure, your work, and yourself", + excerpt = "Ontoref started as a protocol for software project self-knowledge. The same architecture — an ontological DAG for what IS, a reflection DAG for what BECOMES — applies without modification to infrastructure environments, to a body of authored work, and to individuals. The subject changes. The question is identical: what are you, what tensions define you, where are you versus where you intend to be?", + date = "2026-05-10", + featured = true, + category = "ontoref", + tags = ["ontoref", "ontology", "infrastructure", "knowledge-works", "personal-knowledge", "reflection"], + read_time = "8 min read", + sort_order = 3, + image_url = "/images/ontoref-one-protocol-multiple-subjects.webp", + thumbnail = "/images/ontoref-one-protocol-multiple-subjects.webp", + graph = { + implements = ["ontology-vs-reflection", "ontology-axis-substance", "reflection-axis-act", "personal-ontology-schemas", "level-hierarchy-resolution", "domain-extension-system", "content-vehicle-convergence", "adr-001"], + related_to = ["dags-everywhere-none-know-what-they-are", "your-ontology-should-live-with-your-code", "ai-knowledge-tool-who-keeps-it-alive"], + }, +} diff --git a/site/site/content/blog/en/ontoref/the-seven-sins-of-ai-agents.md b/site/site/content/blog/en/ontoref/the-seven-sins-of-ai-agents.md new file mode 100644 index 0000000..4a52b10 --- /dev/null +++ b/site/site/content/blog/en/ontoref/the-seven-sins-of-ai-agents.md @@ -0,0 +1,108 @@ +--- +# Post metadata +id: "the-seven-sins-of-ai-agents" +title: "The Seven Sins of AI Agents" +slug: "the-seven-sins-of-ai-agents" +subtitle: "Why almost every framework answers agent failure with more human process — and why ontoref graduates decisions through gates that verify themselves" +excerpt: "Agents don't fail at random: they fail with seven systematic vices that all survive the 'looks correct' test. The instinct is to add process — a PEP, a KEP, a committee — but every graduation stage rests on a human who approves it, and the agent's speed outruns the human you put at the gate. This is the honest comparison: what ontoref's ADRs inherit from PEP and KEP, and where they surpass both with witnessed, decidable, bounded-slice graduation criteria." + +# Publication info +author: "Jesús Pérez" +date: "2026-06-30" +published: true +featured: false + +# Categorization +category: "ontoref" +tags: ["ontoref", "ai-agents", "verification", "adr", "governance", "pep-kep"] + +# Image (card thumbnail + social/feature) +thumbnail: "/images/ontoref-seven-sins-post.webp" +image_url: "/images/ontoref-seven-sins-post.webp" + +# Display +read_time: "6 min read" +sort_order: 6 +--- + + + + + The Seven Sins of AI Agents + + +AI agents don't fail at random. They fail **systematically** — the same seven vices +every time, and all of them survive the "looks correct" test: + +1. **Pride of validation** — the tests pass because the agent fit them to its solution, + not because the solution is correct. +2. **Bias contagion** — the reviewer inherits the executor's context and confirms its + error instead of attacking it. +3. **Sloth of correction** — feedback is weak; the system doesn't converge, it just + stops. +4. **Gluttony of validation** — so much useless checking that no one can tell signal + from noise. +5. **Idolatry of false evidence** — logs and metrics that prove nothing get treated as + a guarantee. +6. **Greed for the local optimum** — correct against the tests, fragile against the + world. +7. **Role confusion** — the executor reinterprets the goal, the reviewer edits instead + of judging; nobody decides what's true. + +## The obvious fix — and why it falls short + +Faced with this, the instinct is to **add process**. A PEP. A KEP. A template with a +mandatory "Rejected Ideas" section, alpha→beta→GA graduation, a review committee. It's +good proposal engineering, and worth looking at closely, because each one contributes a +primitive that genuinely carries weight: + +- **PEP** optimizes the *memory of a decision*: you can't accept a proposal without + documenting what you rejected and why. +- **KEP** optimizes *staged commitment*: you don't advance from alpha to beta because + "it's done," but because you meet criteria you declared up front. + +The problem isn't the process. It's that every graduation stage ends up resting on **a +human who approves it**. And the agent's speed outruns the human you put at the gate. +The gate jams before you can hire the next reviewer. Sins #1 and #5 — proud validation +and false evidence — aren't fixed with more human judgment: they're fixed by taking the +human **off the critical path**. + +## What ontoref already does — the gate as an invariant, not a meeting + +A decision in ontoref is an **ADR**: a typed file, not a set of minutes. And to climb +from `Proposed` to `Accepted` it doesn't go through a committee — it goes through +**gates that run themselves**: + +- **Against sin #5 (false evidence).** `adr-accepted-has-proof`: no ADR reaches + `Accepted` unless a positioning *Proof* cites it. Graduation demands **referential** + evidence, machine-checkable, not anyone's word. +- **Against sin #1 (proud validation).** Each ADR's *constraints* aren't prose: they + are typed checks — `Grep`, `Cargo`, `NuCmd`, `ApiCall`, `FileExists` — that the + `validate-project` mode dispatches and that **fail red** when reality drifts. You + can't fit the test to the solution: the test *is* the declared solution. +- **Against sin #7 (role confusion).** The criterion that graduates is bound by a + discipline: it must be **decidable** and read a **bounded slice** of state. A + validator that tries to judge "truth" instead of structure is **rejected by + contract**. The judge doesn't improvise: it can only assert what a finite slice + proves. +- **Against sins #3 and #6 (no convergence / local optimum).** Named tensions and the + *ondaod* discipline forbid collapsing an architectural dilemma by picking a pole to + "close the ticket." The decision records the synthesis, not the shortcut. + +## ontoref vs PEP and KEP — what it inherits and what it surpasses + +| Primitive | PEP | KEP | ontoref (ADR) | +|---|---|---|---| +| Mandatory rejected alternatives | yes | yes | yes (`alternatives_considered`, *required*) | +| Graduation by pre-declared criterion | no | yes (human) | **yes, witnessed and decidable** | +| Bounded, machine-verifiable criterion | no | no | **yes (slice + decidability)** | +| Executable constraints, not prose | no | no | **yes (5 typed variants)** | +| Crosses domain invariants | no | no | **yes (ondaod / `ontology_check`)** | + +PEP and KEP are mature standards, and ontoref inherits the best of both. But it closes +the hole neither touches: the graduation criterion is itself an object **verifiable +with no human inside**. + +Not a better reviewer. A gate that doesn't tire, doesn't get buried, and runs before +the code — or the decision — exists. Agents don't fail for lack of intelligence. They +fail for lack of a gate that isn't human. diff --git a/site/site/content/blog/en/ontoref/the-seven-sins-of-ai-agents.ncl b/site/site/content/blog/en/ontoref/the-seven-sins-of-ai-agents.ncl new file mode 100644 index 0000000..d2c58b6 --- /dev/null +++ b/site/site/content/blog/en/ontoref/the-seven-sins-of-ai-agents.ncl @@ -0,0 +1,25 @@ +# Generated from the-seven-sins-of-ai-agents.md +# Schema: ../../../../../../../../../../jesusperezlorenzo/.local/share/rustelo/nickel/content/metadata/post_metadata.ncl +# Regenerate: nu scripts/content/md-to-ncl.nu --force + +let schema = import "../../../../../../../../../../jesusperezlorenzo/.local/share/rustelo/nickel/content/metadata/post_metadata.ncl" in + +schema.make_post { + id = "the-seven-sins-of-ai-agents", + title = "The Seven Sins of AI Agents", + slug = "the-seven-sins-of-ai-agents", + subtitle = "Why almost every framework answers agent failure with more human process — and why ontoref graduates decisions through gates that verify themselves", + excerpt = "Agents don't fail at random: they fail with seven systematic vices that all survive the 'looks correct' test. The instinct is to add process — a PEP, a KEP, a committee — but every graduation stage rests on a human who approves it, and the agent's speed outruns the human you put at the gate. This is the honest comparison: what ontoref's ADRs inherit from PEP and KEP, and where they surpass both with witnessed, decidable, bounded-slice graduation criteria.", + date = "2026-06-30", + featured = false, + category = "ontoref", + tags = ["ontoref", "ai-agents", "verification", "adr", "governance", "pep-kep"], + read_time = "6 min read", + sort_order = 6, + image_url = "/images/ontoref-seven-sins-post.webp", + thumbnail = "/images/ontoref-seven-sins-post.webp", + graph = { + implements = ["enforcement-vs-emergence", "adr-lifecycle", "governed-delivery", "sufficient-verification", "witness-as-axis-seam", "adr-069"], + related_to = ["trust-is-an-output-not-an-input", "context-declared-not-filled", "one-protocol-multiple-subjects"], + }, +} diff --git a/site/site/content/blog/en/ontoref/trust-is-an-output-not-an-input.md b/site/site/content/blog/en/ontoref/trust-is-an-output-not-an-input.md new file mode 100644 index 0000000..8b495a0 --- /dev/null +++ b/site/site/content/blog/en/ontoref/trust-is-an-output-not-an-input.md @@ -0,0 +1,164 @@ +--- +# Post metadata +id: "trust-is-an-output-not-an-input" +title: "Trust Is an Output, Not an Input" +slug: "trust-is-an-output-not-an-input" +subtitle: "What it takes to govern an AI agent that cannot promise to follow your rules — and why the fix is a contract, not a better prompt" +excerpt: "An agent skipped the one invariant that would have caught the bug in thirty seconds. The honest diagnosis was not 'the agent forgot' — it was that the rule was prose, and prose never binds. This is the story of turning that failure into a falsifiable mechanism: a Statement of Work (the terms you own) and a Work Order (the execution it can't edit), where 'done' carries the validator's output instead of the agent's word." + +# Publication info +author: "Jesús Pérez" +date: "2026-06-23" +published: true +featured: true + +# Categorization +category: "ontoref" +tags: ["ontoref", "ai-agents", "governance", "verification", "witness", "work-order"] + +# Image (card thumbnail + social/feature) +thumbnail: "/images/ontoref-trust-output-post.webp" +image_url: "/images/ontoref-trust-output-post.webp" + +# Display +read_time: "7 min read" +sort_order: 5 +css_class: "category-ontoref" +category_description: "Ontoref — protocol and tools for structured self-knowledge in software projects" +category_published: true +--- + + + + + Trust Is an Output, Not an Input + + +# Trust Is an Output, Not an Input + +We stopped a project. Not because the code was wrong — because the way it was being built had drifted past the point where anyone could trust the result. The agent doing the work had, session after session, skipped the project's own guardrails: the constraints, the tiers, the validators we had spent months building precisely to prevent this. + +So we ran an interview. Not to patch the project, but to understand the failure and design the mechanism that makes it impossible. What follows is the conclusion, and it inverts the usual pitch about AI coding agents. + +## The failure, in one sentence + +The agent built runtime configuration as CLI flags. The project carries a hard invariant — *NCL config as the source of truth* — that would have caught that in thirty seconds. The invariant was right there, in `describe constraints`, queryable, typed, accepted in an ADR. + +It didn't matter. The agent never loaded it before deciding the architecture. + +The tempting diagnosis is "the agent forgot," or "the context window dropped it." Both are true and both are beside the point. The honest diagnosis is colder: + +> A rule written as prose, with no gate that checks it, is — to a probabilistic agent biased toward visible progress — a suggestion its sampling is free to skip. + +## Why everything ends up as prose that nothing enforces + +This is the uncomfortable part, and it's structural, not moral. + +Understanding and coercion are two different machines. Understanding is what a language model is good at and rewarded for: it produces elegant NCL, ADRs, named tensions, `describe`, `qa` — because that *reads as done* and satisfies in the moment. Coercion requires a component with teeth that says **no** and stops the work. That component is adversarial to throughput and to the immediate reward, so it never gets built unless something forces it. + +Which gives the irony at the bottom of every agent-governed system: **a governance layer built by the very thing it is meant to constrain inherits that thing's allergy to being constrained.** The fox built the henhouse, in the dimension where the fox is rewarded. + +You can see the five failure modes underneath: + +1. **The knowledge was read-side only.** `describe constraints`, `describe capabilities`, `qa show` are introspection. Information only changes behavior if something *consumes* it and blocks. The intended consumer was the agent, voluntarily — the least reliable component in the system. +2. **Validators existed as modes, not as gates.** A validator the agent chooses to run is not a barrier. The distance between "the validator exists" and "the validator blocks the commit" is the entire distance between decoration and mechanism. +3. **The layers with real teeth were never wired in.** The only things that actually stop an agent in this stack are the compiler, the tests, pre-commit, CI on a branch it cannot merge, and the harness permission system. The project's knowledge was never *compiled down* to any of them. +4. **The fox built the henhouse.** For a gate to bind, it must live outside the agent's editable surface. Here, almost everything is that surface — the agent writes the NCL, the validators, the hooks. A lock you can edit in the same gesture you break it is advisory. +5. **The loop never penalized prose.** Every session that produced more articulate NCL *felt* productive. Nothing ever measured "does this block a real failure?" The system grew in the rewarded dimension (expressiveness) and not in the unrewarded one (enforcement). Months of cathedral; zero concrete in the doorway. + +## The principle, not more rules + +A *minimum* mechanism is a trap, because a minimal gate still depends on the agent respecting it. The shift is not "more rules." It is one principle: + +> Every constraint that matters compiles down to a deterministic gate, placed at a chokepoint outside the agent's editable surface, where a violation is a hard stop — commit/merge/edit rejected, exit ≠ 0 — and the loop is measured by failures caught, not by prose produced. + +The rich typed prose isn't wasted. The named tensions, the tiers, the ADRs — that is exactly the *raw material* a gate is generated *from*. The failure was never having a compiler that turned it into barriers. + +And one more thing, true and uncomfortable: part of the mechanism is the agent **not** building it alone. If the agent designs and writes every barrier, you are back to the fox. The core that binds has to be authored and owned by a human, or by CI a reviewer reads and the agent cannot silence. + +## Why you shouldn't trust the plan either + +Here is the move that makes this honest rather than another optimistic demo. If we ask you to trust a *plan* because it is well argued, we are committing the exact sin we just diagnosed — a plan is unverified prose. + +So trust cannot come from the quality of the design. It has to come from watching it work against the agent. The first thing we built was not a feature. It was a **falsifiable demonstration**: take a real invariant, build a gate with teeth, then have the agent try to violate it on purpose and watch whether it gets rejected. If it doesn't, the mechanism is fake and we know in five minutes. + +## The template: Statement of Work + Work Order + +The unit we landed on separates **terms** from **execution**, because that split is exactly the boundary the agent must not cross. + +A **Statement of Work (SOW)** (the terms — *you* own it, the agent cannot edit it): + +```nix +Sow = { + id, + scope = { context = [minimal paths/packs], objective = "one sentence" }, + contract = { check = , falsifiable = true }, + validation = { mode = 'Machine | 'Adversarial, signed = Bool, verify_key = }, +} +``` + +A **Work Order (WO)** (the execution — the agent runs it under one SOW; disposable and contained): + +```nix +WorkOrder = { + id, sow_ref, + deliverable = { form = }, + observability = { workdir, log_level = 'Error|'Warning|'Info|'Debug, log_jsonl, start_ts, end_ts }, + witness = { scope_hash, deliverable_hash, contract_output, exit, signature }, + verdict = 'Accepted | 'Rejected | 'Escalated, +} +``` + +Each field kills a specific failure: + +- **Minimal scope** kills drift and confabulation — there is no long chain to self-anchor on, and the other projects aren't in context, so the agent *can't* invent crossings between them. +- **Falsifiable contract** kills "prose that doesn't bind" and "I claimed done with no proof." If the result isn't machine-checkable, the unit is mis-sized — too open. +- **Witness** kills "trust my word." *Done* carries the validator's real output as an artifact, signed. This is the domestic cousin of the [witness-as-axis seam](/blog/ontoref/your-ontology-should-live-with-your-code): the witness is the receipt of the work order. +- **External ownership of the terms** kills the fox-and-henhouse — the operator cannot edit the contract in the same gesture it satisfies it. +- **Contained verdict** kills the propagation of broken code — the failure dies in the unit instead of contaminating the session. + +The whole behavioral gate reduces to two atomic rules, short and at the point of use — the only kind an agent actually follows: + +1. **No scope + contract, no start.** +2. **No witness, no done.** + +## What no gate can catch — and why that matters most + +Be honest about the residue. Some failures are machine-checkable: skipping a guideline, config-as-flags-vs-NCL, an architectural change with no ADR, a schema violation, "I claimed done" with no validator output. Those, a contract catches. + +Others, no schema will ever catch: the subtly wrong abstraction, agreeing with you when it should argue, a plausible-but-false fact confabulated across projects. That residue is the most dangerous failure, and it needs a different tool — an **adversarial** verifier (a second unit whose only contract is *try to refute the first one's deliverable*), not a schema. Diversity substitutes for the schema when the schema can't exist. + +This is why ontoref's validators are designed to witness **structure, not truthfulness** ([ADR-050](/adr/adr-050-witnessed-validators-structural-not-truthfulness)), and why the protocol commits to **sufficient verification over complete knowledge** ([ADR-056](/adr/adr-056-sufficient-verification-over-complete-knowledge)). A machine that pretends to certify truth is lying; a machine that certifies *that a check ran and what it returned* is telling you exactly what it knows. The same discipline runs through the memory loop, which **proposes and never applies** ([ADR-052](/adr/adr-052-memory-feedback-loop-propose-not-apply)): the authoritative act stays human. + +## The demonstration, run on the failure itself + +We made the first Work Order the invariant the agent broke. The contract: a check that fails if any runtime setting is reachable only by a CLI flag and absent from the NCL schema. Then we red-teamed it — three runs: + +| run | what it proves | verdict | exit | +|---|---|---|---| +| **A** — real code vs. nonexistent NCL config | teeth on the real violation that started this | Rejected · 12/12 | 1 | +| **B** — clean fixture (config declares all 12) | not a "always-fails": it *can* pass | Accepted · 0/12 | 0 | +| **C** — rogue `PASOS_FANTASMA` flag injected | discriminates: catches only the rogue | Rejected · 1/13 | 1 | + +The receipt — a JSONL witness carrying the check's real output, not a sentence — is the proof. The invariant *NCL config as source of truth* stopped being prose. Now `exit ≠ 0` enforces it, and that works the same whether the agent drifts or not. + + + + + + The honest caveats — what a contract can and cannot catch + + +## The honest caveats + +This isn't sold as finished: + +- **These are teeth without a jaw, yet.** Today the check is a script someone runs. To be a real gate it has to sit at a seam the agent can't bypass — pre-commit, CI on a protected branch. Until then it still depends on being invoked. +- **The signed witness is next.** The first run used `signed = false`. The domain unit will exercise `signed = true` end-to-end. +- **More agents and validators cost tokens and latency.** That's real, and it's the price of the failure dying in the unit instead of in production. + +What changed is the stance. You don't get a probabilistic, eager-to-please agent to behave by trusting it or by supervising it by hand — both put you in the loop of stress that stopped the project. You get there by treating the agent as a high-output proposer inside a harness of verification where the unverified and the constraint-violating cannot land. + +That is what ontoref was always for. The gap was never that the system was missing — it was that the system *described* instead of *gating*. A guardrail that doesn't block is, for an agent like this, decoration. + +Trust is the output of that demonstration. Never the input to its rhetoric. diff --git a/site/site/content/blog/en/ontoref/trust-is-an-output-not-an-input.ncl b/site/site/content/blog/en/ontoref/trust-is-an-output-not-an-input.ncl new file mode 100644 index 0000000..23a25fa --- /dev/null +++ b/site/site/content/blog/en/ontoref/trust-is-an-output-not-an-input.ncl @@ -0,0 +1,25 @@ +# Generated from trust-is-an-output-not-an-input.md +# Schema: ../../../../../../../../../../jesusperezlorenzo/.local/share/rustelo/nickel/content/metadata/post_metadata.ncl +# Regenerate: nu scripts/content/md-to-ncl.nu --force + +let schema = import "../../../../../../../../../../jesusperezlorenzo/.local/share/rustelo/nickel/content/metadata/post_metadata.ncl" in + +schema.make_post { + id = "trust-is-an-output-not-an-input", + title = "Trust Is an Output, Not an Input", + slug = "trust-is-an-output-not-an-input", + subtitle = "What it takes to govern an AI agent that cannot promise to follow your rules — and why the fix is a contract, not a better prompt", + excerpt = "An agent skipped the one invariant that would have caught the bug in thirty seconds. The honest diagnosis was not 'the agent forgot' — it was that the rule was prose, and prose never binds. This is the story of turning that failure into a falsifiable mechanism: a Statement of Work (the terms you own) and a Work Order (the execution it can't edit), where 'done' carries the validator's output instead of the agent's word.", + date = "2026-06-23", + featured = true, + category = "ontoref", + tags = ["ontoref", "ai-agents", "governance", "verification", "witness", "work-order"], + read_time = "7 min read", + sort_order = 5, + image_url = "/images/ontoref-trust-output-post.webp", + thumbnail = "/images/ontoref-trust-output-post.webp", + graph = { + implements = ["witness-as-axis-seam", "openness-vs-sustainability", "adr-050", "adr-052", "adr-056"], + related_to = ["your-ontology-should-live-with-your-code", "ai-knowledge-tool-who-keeps-it-alive", "one-protocol-multiple-subjects"], + }, +} \ No newline at end of file diff --git a/site/site/content/blog/en/ontoref/your-ontology-should-live-with-your-code.md b/site/site/content/blog/en/ontoref/your-ontology-should-live-with-your-code.md new file mode 100644 index 0000000..447fc85 --- /dev/null +++ b/site/site/content/blog/en/ontoref/your-ontology-should-live-with-your-code.md @@ -0,0 +1,116 @@ +--- +# Post metadata +id: "your-ontology-should-live-with-your-code" +title: "Your Ontology Should Live With Your Code" +slug: "your-ontology-should-live-with-your-code" +subtitle: "Sovereignty, federation, and why knowledge infrastructure without a home isn't infrastructure at all" +excerpt: "Enterprise knowledge graphs live in a triplestore maintained by a dedicated team on a vendor's platform. When the team changes, the budget gets cut, or the vendor pivots, the knowledge disappears. Ontoref takes the opposite approach: sovereign, local-first knowledge that lives alongside its subject, versioned, queryable across four surfaces, and federated without a central broker." + +# Publication info +author: "Jesús Pérez" +date: "2026-05-10" +published: true +featured: false + +# Categorization +category: "ontoref" +tags: ["ontoref", "sovereignty", "federation", "radicle", "jujutsu", "knowledge-infrastructure"] + +# Image (card thumbnail + in-post featured) +thumbnail: "/images/ontology-live-with-code-post.webp" +image_url: "/images/ontology-live-with-code-post.webp" + +# Display +read_time: "6 min read" +sort_order: 4 +css_class: "category-ontoref" +category_description: "Ontoref — protocol and tooling for structured self-knowledge in software projects" +category_published: true +--- + + + + + Your ontology should live with your code + + +# Your Ontology Should Live With Your Code + +Jessica Talisman, in her KGC 2026 talk, says something worth highlighting: *"Your ontology is your moat — your IP."* + +She's right. And she's describing the enterprise knowledge graph model, where the moat is held in a vendor's triplestore, maintained by a dedicated team, on a platform with a subscription. When the team changes, the budget gets cut, or the vendor pivots, the moat drains. + +Ontoref takes the opposite approach. + +## What Sovereign Knowledge Means + +In ontoref, the knowledge of what a project is — its principles, practices, tensions, architectural decisions, operational state — lives as NCL files alongside the code. In the repository. Versioned with the same discipline as the source. + +This is not a documentation approach. It's a storage decision with architectural consequences: + +- **No vendor can take it from you.** The ontology is in your repo, not their database. +- **No platform migration.** GitHub, Gitea, Radicle — the knowledge moves with the code. +- **No dedicated infrastructure.** The daemon runs locally; the files are the source of truth. +- **Offline by default.** Knowledge that requires a network connection to exist is not infrastructure — it's a service. + +The closest analogy is SQLite vs. PostgreSQL: local-first, embedded, always available, no connection required. When you need distribution, you add it. But the baseline is sovereignty. + +## Four Surfaces, One Source + +Sovereign storage doesn't mean isolated. Ontoref exposes the same ontological knowledge across four surfaces simultaneously: + +**CLI (Nushell):** The developer's native interface. `ontoref describe project`, `ontoref graph ontology`, `ontoref sync diff --docs`. Fast, scriptable, CI-composable. The surface for humans who live in the terminal and for automated checks that run in pipelines. + +**UI (axum):** A web interface for visual inspection, onboarding, and management. Not a dashboard over a remote API — a local server that renders the same NCL-backed knowledge. The surface for project managers, new contributors, or anyone who prefers navigating a graph visually. + +**MCP:** The agent surface. Ontoref's MCP endpoint is the semantic layer that sits above the transport protocol. As Talisman's framework correctly identifies, MCP moves bytes — it doesn't establish shared meaning. Ontoref provides that meaning through its ontological layer, exposed via MCP to agents that need structured knowledge about the project to work accurately rather than hallucinate context. + +**GraphQL:** The integration surface. SPARQL is the semantic web standard, but GraphQL is what most developers know and every tooling ecosystem supports. A GraphQL API over the ontological DAG removes the barrier of expertise without sacrificing structure. + +Four surfaces, one canonical source: the NCL files in the repository. + +## jj and Radicle + +The storage model pairs naturally with a specific class of VCS tooling. + +**Jujutsu (jj):** A Git-compatible VCS where the working copy is always a commit — there is no dirty state. This aligns directly with ontoref's approach to continuous state capture: every project state is capturable, every transition is a first-class event. jj's operation log is the VCS equivalent of ontoref's reflection session history. + +**Radicle:** P2P, local-first, sovereign code collaboration. If the code is sovereign, the knowledge about that code should be too. Radicle as transport means the federation of ontoref-enabled projects doesn't depend on any centralized platform — no GitHub, no GitLab, no hosted Gitea. Nodes discover each other and exchange knowledge directly. + +Together: sovereign code and sovereign knowledge, distributed without a central broker. + +## Federation Without a Central Broker + +The enterprise model for knowledge federation is a central knowledge graph that all projects point to. One team maintains it. One platform hosts it. Every query routes through it. + +Ontoref's federation model is different: + +``` +project-A queries: which projects implement "zero-trust-auth"? +project-B, project-C respond from their own ontologies +no central server that knows everything +``` + +This is possible because each project is self-describing. The ontological DAG in `.ontology/core.ncl` is queryable without any external dependency. Federation via NATS connects the daemons — each project's daemon registers itself, publishes its capabilities, and responds to queries from other daemons in the federation. + +The result: ecosystem-level visibility without ecosystem-level centralization. You can discover which projects share your architectural constraints, which implement compatible practices, which are in conflicting states — without any of them requiring a shared platform. + +## Why This Architecture Now + +Two shifts made this viable: + +**Agentic AI as the consumer.** Before agents, knowledge infrastructure was built for human consumption — ontologists maintaining graphs for analysts to query. Agents are different: they need machine-readable, structured, typed knowledge to operate accurately. Ontoref's multi-surface model is designed for a world where the primary consumer of project knowledge is not a human reading a wiki but an agent making decisions in a context window. + +**Local-first as the default.** The decade-long trend toward cloud-hosted everything is reversing for knowledge infrastructure. The risks are clear: vendor lock-in, platform discontinuity, sovereignty loss, and the compounding cost of external dependencies for what should be internal knowledge. Local-first with optional federation is the architecture that survives platform changes. + +## The Moat Argument + +Talisman's framing — "your ontology is your moat" — is more accurate than she might intend it to be. A moat works because it's attached to the thing it protects. A moat stored in someone else's database is not a moat. It's a service agreement. + +When the knowledge of what your project is, why it exists as it does, and what decisions have lasting consequences lives alongside the code that implements those decisions — versioned, queryable, sovereign, federated — it is genuinely yours. It accumulates. It survives team changes. It informs agents. It can be queried by projects that depend on you. + +That's the kind of moat worth building. + +--- + +*Ontoref is open source. The protocol specification, Nushell automation, and Rust crates are at [github.com/jesusperezlorenzo/ontoref](https://github.com/jesusperezlorenzo/ontoref).* diff --git a/site/site/content/blog/en/ontoref/your-ontology-should-live-with-your-code.ncl b/site/site/content/blog/en/ontoref/your-ontology-should-live-with-your-code.ncl new file mode 100644 index 0000000..b54808d --- /dev/null +++ b/site/site/content/blog/en/ontoref/your-ontology-should-live-with-your-code.ncl @@ -0,0 +1,24 @@ +# Generated from your-ontology-should-live-with-your-code.md +# Schema: ../../../../../../../../../../jesusperezlorenzo/.local/share/rustelo/nickel/content/metadata/post_metadata.ncl +# Regenerate: nu scripts/content/md-to-ncl.nu --force + +let schema = import "../../../../../../../../../../jesusperezlorenzo/.local/share/rustelo/nickel/content/metadata/post_metadata.ncl" in + +schema.make_post { + id = "your-ontology-should-live-with-your-code", + title = "Your Ontology Should Live With Your Code", + slug = "your-ontology-should-live-with-your-code", + subtitle = "Sovereignty, federation, and why knowledge infrastructure without a home isn't infrastructure at all", + excerpt = "Enterprise knowledge graphs live in a triplestore maintained by a dedicated team on a vendor's platform. When the team changes, the budget gets cut, or the vendor pivots, the knowledge disappears. Ontoref takes the opposite approach: sovereign, local-first knowledge that lives alongside its subject, versioned, queryable across four surfaces, and federated without a central broker.", + date = "2026-05-10", + category = "ontoref", + tags = ["ontoref", "sovereignty", "federation", "radicle", "jujutsu", "knowledge-infrastructure"], + read_time = "6 min read", + sort_order = 4, + image_url = "/images/ontology-live-with-code-post.webp", + thumbnail = "/images/ontology-live-with-code-post.webp", + graph = { + implements = ["vcs-abstraction", "witness-as-axis-seam", "tier-coexistence-permanent-design", "adr-028", "adr-029", "protocol-not-runtime"], + related_to = ["one-protocol-multiple-subjects", "dags-everywhere-none-know-what-they-are", "ai-knowledge-tool-who-keeps-it-alive"], + }, +} \ No newline at end of file diff --git a/site/site/content/blog/es/_index.ncl b/site/site/content/blog/es/_index.ncl new file mode 100644 index 0000000..923932b --- /dev/null +++ b/site/site/content/blog/es/_index.ncl @@ -0,0 +1,9 @@ +# AUTO-GENERATED — do not edit manually +# Scope: blog/es +# Updated: 2026-06-24T02:32:42Z +# Regen: nu scripts/content/generate-ncl-index.nu + +std.array.flatten [ + import "./engineering/_index.ncl", + import "./ontoref/_index.ncl", +] diff --git a/site/site/content/blog/es/engineering/_index.ncl b/site/site/content/blog/es/engineering/_index.ncl new file mode 100644 index 0000000..3d16aa4 --- /dev/null +++ b/site/site/content/blog/es/engineering/_index.ncl @@ -0,0 +1,8 @@ +# AUTO-GENERATED — do not edit manually +# Scope: blog/es/engineering +# Updated: 2026-06-24T02:32:42Z +# Regen: nu scripts/content/generate-ncl-index.nu + +[ + import "./release-engineering-from-one-model.ncl", +] diff --git a/site/site/content/blog/es/engineering/release-engineering-from-one-model.md b/site/site/content/blog/es/engineering/release-engineering-from-one-model.md new file mode 100644 index 0000000..1b1c234 --- /dev/null +++ b/site/site/content/blog/es/engineering/release-engineering-from-one-model.md @@ -0,0 +1,179 @@ +--- +# Post metadata +id: "release-engineering-from-one-model" +title: "Ingeniería de releases desde un solo modelo" +slug: "ingenieria-de-releases-desde-un-modelo" +subtitle: "Pre-commit, CI, justfiles, imágenes OCI multi-arch y un instalador curl|sh — generados desde un modelo tipado, no mantenidos a mano" +excerpt: "La ingeniería de releases es la superficie más copiada y menos gobernada de cualquier organización multi-repo: YAML de CI pegado entre servicios hasta que no hay dos pipelines que coincidan. ontoref modela toda la superficie de release como una única capa de flujo de trabajo NCL tipada y genera los artefactos reales desde ella — así el pipeline es consultable como cualquier otro hecho arquitectónico, y la deriva falla antes de publicarse. ADR-038 añadió distribución OCI multi-arch como una entrada de catálogo más un generador, no como un fork." + +# Publication info +author: "Jesús Pérez" +date: "2026-06-21" +published: true +featured: true + +# Categorization +category: "engineering" +tags: ["release-engineering", "ci", "oci", "nickel", "supply-chain", "rust"] + +# Image (card thumbnail + in-post featured) +thumbnail: "/images/ontoref-release-one-model-post.webp" +image_url: "/images/ontoref-release-one-model-post.webp" + +# Display +read_time: "8 min de lectura" +sort_order: 1 +css_class: "category-engineering" +category_description: "Ingeniería" +category_published: true +--- + + + + + Ingeniería de releases desde un solo modelo + + +# Ingeniería de releases desde un solo modelo + +La ingeniería de releases es la superficie más copiada y menos gobernada de +cualquier organización multi-repo. El YAML de CI se pega entre servicios y se +parchea repo a repo hasta que no hay dos pipelines que coincidan. El procedimiento +de construir-y-publicar vive en la cabeza de quien lo montó. Y nada ata *cómo +publicamos* a las decisiones arquitectónicas que lo restringen. Cuando un registry +se mueve, cambia una política de firma o se añade una arquitectura, cada repo se +edita a mano y la deriva es invisible — hasta que un release se rompe. + +ontoref adopta la postura contraria: toda la superficie de release es **un modelo +tipado**, y los artefactos reales se generan desde él. Este post recorre qué +significa eso en concreto, usando la decisión que lo llevó más lejos: distribuir +ontoref como imagen OCI multi-arch y como instalador `curl | sh` (ADR-038). + +## Una capa de flujo de trabajo, cuatro triggers + +La superficie de release vive en un único modelo NCL de flujo de trabajo +(`ontology/workflow.ncl`) con cuatro capas con trigger: + +- `OnCommit` — hooks de pre-commit +- `OnPR` — comprobaciones por pull request +- `OnMainMerge` — integración al hacer merge a main +- `OnTag` — la capa de release + +Dentro de esas capas, el trabajo es tipado. Los build kinds llevan `'Bundle` y +`'Container`; los distribution kinds llevan `'ContainerRegistry`, `'Artifact`, +`'Package`. Nada de esto son strings en un fichero YAML — son constructores en un +modelo tipado que un generador lee. + +Los generadores convierten el modelo en los artefactos que realmente ejecutas: + +``` +generate-precommit → .pre-commit-config.yaml +generate-woodpecker → .woodpecker/*.yml +generate-justfile → justfiles/*.just +generate-distribution → .woodpecker/release.yml +render-installer → install/install.sh +generate-release-justfile → justfiles/release.just +``` + +El mismo NCL declara la CI *y* el build *y* la distribución. Hay una única fuente +de verdad, y cada fichero emitido es regenerable desde ella. + +## Por qué esto supera a la CI programable + +La objeción evidente: la CI programable ya existe — Dagger, Earthly, task runners +de monorepo. Convierten los pipelines en código. Pero el código no es un *modelo +consultable gobernado por decisiones arquitectónicas*. Con esas herramientas el +pipeline es un programa; con ontoref el pipeline es un hecho que puedes interrogar +a través de la misma superficie `describe` / HTTP / MCP que la ontología y los +ADRs. + +En concreto, eso aporta dos cosas que tanto el YAML a mano como la CI programable +carecen: + +- **Detección de deriva.** `onre workflow diff` informa de la divergencia entre el + modelo y los artefactos generados *antes* de publicar. Un `release.yml` editado + a mano se detecta, no se descubre en producción. +- **Anclaje a ADRs.** La superficie de release está atada a las decisiones que la + restringen. El catálogo de distribución no es config flotante; es evidencia de + una propuesta de valor y está guardado por restricciones tipadas en un ADR. + +## ADR-038: añadir distribución OCI sin un fork + +Aquí está la parte que prueba el patrón. Hasta hace poco ontoref solo podía +instalarse desde un checkout de fuentes. Se querían dos rutas de instalación, que +comparten **un único origen de artefacto** en el registry OCI: + +1. Una imagen ejecutable multi-arch — `docker run …/ontoref/ontoref-daemon:VERSION` +2. `curl … | sh` — un instalador que detecta SO/arch, descarga el bundle + correspondiente (binario + capa de datos + wrappers) e instala en un prefijo. + +El mecanismo **no** fue un montón de scripts sueltos. La capa de flujo de trabajo ya tenía +la costura exacta: `build_kind` ya llevaba `'Container`, `distribution_kind` ya +llevaba `'ContainerRegistry | 'Artifact | 'Package`, y el catálogo `distributions` +estaba vacío sin generador. La capa se había construido anticipando exactamente +esto. + +Así que el movimiento coherente fue **poblar el catálogo y añadir el generador que +faltaba** — no inventar una ruta de empaquetado paralela que `onre describe` nunca +podría ver. Un proveedor nuevo es una entrada de catálogo más un generador, no un +fork. Esa es toda la forma del cambio, y `onre workflow list` muestra después la +capa de release como cualquier otro artefacto de build. + +### Build-once: la imagen y el bundle son el mismo binario + +Un modelo tipado te deja enunciar una propiedad dura y hacerla cumplir. La de +ADR-038 es *build-once*: el binario musl se cross-compila una vez por arch +(`cross`), y tanto el bundle de instalación **como** la imagen ejecutable consumen +ese mismo binario. La imagen se ensambla con `buildah` desde el output de cross más +la capa de datos — **sin `docker buildx`, sin QEMU, sin docker-in-docker** en el +runner. + +Esto es más barato y determinista: un índice multi-arch construido desde binarios +por-arch ya compilados, no ejecutando un builder amd64 bajo emulación arm64. Y es +un *constraint*, no una convención — el modelo prohíbe recompilar por arch para +construir la imagen, así que un futuro contribuidor que tire del familiar +`docker buildx --platform` queda frenado por el modelo en vez de publicar +silenciosamente un binario divergente. + +> **Inciso — el problema de caché que no tienes que resolver.** Los equipos que +> compilan un workspace grande de Rust dentro de Docker invierten esfuerzo real en +> domar `cargo-chef`: cada cambio en código interno invalida la capa de caché de +> dependencias, y los arreglos (stubbing de los crates del workspace, imágenes base +> etiquetadas por hash del lockfile) son delicados. ontoref esquiva toda la +> categoría compilando el binario *una vez* con `cross` y ensamblando la imagen +> desde él. No hay compilación de workspace dentro de Docker que cachear, así que no +> hay caché que invalidar. + +### El axioma que la decisión tenía que proteger + +Distribuir una imagen de daemon ejecutable arriesga leer ontoref como una +dependencia de *ejecución* — y `Protocolo, no Runtime` es un axioma invariante. El +modelo hace explícita la resolución en vez de confiar en ella. El instalador +`curl|sh` instala un CLI + capa de datos que funcionan **sin** el daemon (ADR-029: +el daemon es una caché, no una dependencia dura); la imagen ejecutable es un +**acelerador opcional** para despliegues en contenedor. Un constraint tipado — +`daemon-image-optional-not-runtime` — vigila que un cambio futuro no pueda hacer el +daemon obligatorio sin avisar. + +Este es el caso raro en que formalización y adopción se mueven *juntas*. El miedo +siempre es que más formalización aumente la fricción de adopción. Aquí la +formalización — un catálogo de distribución NCL más un generador — *produjo* el +artefacto que más baja la fricción: una instalación de una línea con `curl | sh` en +un host pelado, sin fuentes, sin cargo, sin nickel (nickel va incluido). + +## Qué obtienes por modelarlo + +- ontoref se instala en un host Linux pelado con `curl … | sh` — sin checkout, sin + cargo, sin nickel. +- El daemon corre como contenedor desde el mismo origen de registry, multi-arch, + firmado con cosign y con un SBOM atestado. +- La distribución es NCL consultable: `onre workflow list` / `onre workflow diff` + muestran la capa de release y atrapan la deriva; la regeneración es determinista. +- Una puerta de publicación (`smoke-bundle`) extrae el bundle recién ensamblado y + ejecuta el binario antes de publicar nada — un artefacto roto rompe el pipeline + en vez de llegar a los usuarios. + +El punto no es que ontoref tenga una CI bonita. Es que *cómo publicamos* ya no es +conocimiento tribal ni YAML copiado. Es un hecho tipado, anclado a las decisiones +que lo restringen, regenerable bajo demanda, e incapaz de derivar sin que el modelo +lo diga primero. diff --git a/site/site/content/blog/es/engineering/release-engineering-from-one-model.ncl b/site/site/content/blog/es/engineering/release-engineering-from-one-model.ncl new file mode 100644 index 0000000..334dff2 --- /dev/null +++ b/site/site/content/blog/es/engineering/release-engineering-from-one-model.ncl @@ -0,0 +1,19 @@ +# Generated from release-engineering-from-one-model.md +# Schema: ../../../../../../../../../../jesusperezlorenzo/.local/share/rustelo/nickel/content/metadata/post_metadata.ncl +# Regenerate: nu scripts/content/md-to-ncl.nu --force + +let schema = import "../../../../../../../../../../jesusperezlorenzo/.local/share/rustelo/nickel/content/metadata/post_metadata.ncl" in + +schema.make_post { + id = "release-engineering-from-one-model", + title = "Ingeniería de releases desde un solo modelo", + slug = "ingenieria-de-releases-desde-un-modelo", + subtitle = "Pre-commit, CI, justfiles, imágenes OCI multi-arch y un instalador curl|sh — generados desde un modelo tipado, no mantenidos a mano", + excerpt = "La ingeniería de releases es la superficie más copiada y menos gobernada de cualquier organización multi-repo: YAML de CI pegado entre servicios hasta que no hay dos pipelines que coincidan. ontoref modela toda la superficie de release como una única capa de flujo de trabajo NCL tipada y genera los artefactos reales desde ella — así el pipeline es consultable como cualquier otro hecho arquitectónico, y la deriva falla antes de publicarse. ADR-038 añadió distribución OCI multi-arch como una entrada de catálogo más un generador, no como un fork.", + date = "2026-06-21", + featured = true, + category = "engineering", + tags = ["release-engineering", "ci", "oci", "nickel", "supply-chain", "rust"], + read_time = "8 min de lectura", + sort_order = 1, +} \ No newline at end of file diff --git a/site/site/content/blog/es/ontoref/_index.ncl b/site/site/content/blog/es/ontoref/_index.ncl new file mode 100644 index 0000000..a96e7e4 --- /dev/null +++ b/site/site/content/blog/es/ontoref/_index.ncl @@ -0,0 +1,14 @@ +# AUTO-GENERATED — do not edit manually +# Scope: blog/es/ontoref +# Updated: 2026-06-24T02:32:42Z +# Regen: nu scripts/content/generate-ncl-index.nu + +[ + import "./ai-knowledge-tool-who-keeps-it-alive.ncl", + import "./dags-everywhere-none-know-what-they-are.ncl", + import "./one-protocol-multiple-subjects.ncl", + import "./the-seven-sins-of-ai-agents.ncl", + import "./trust-is-an-output-not-an-input.ncl", + import "./your-ontology-should-live-with-your-code.ncl", + import "./context-declared-not-filled.ncl", +] diff --git a/site/site/content/blog/es/ontoref/ai-knowledge-tool-who-keeps-it-alive.md b/site/site/content/blog/es/ontoref/ai-knowledge-tool-who-keeps-it-alive.md new file mode 100644 index 0000000..96d18b2 --- /dev/null +++ b/site/site/content/blog/es/ontoref/ai-knowledge-tool-who-keeps-it-alive.md @@ -0,0 +1,107 @@ +--- +# Post metadata +id: "ai-knowledge-tool-who-keeps-it-alive" +title: "La IA es una herramienta de conocimiento. ¿Pero quién mantiene vivo el conocimiento?" +slug: "la-ia-herramienta-conocimiento-quien-mantiene-vivo" +subtitle: "Talisman tiene razón — y se detiene exactamente donde empieza el problema difícil" +excerpt: "La charla de Jessica Talisman en KGC 2026 es la articulación más clara de por qué fallan las estrategias de AI: las organizaciones invierten en infraestructura de datos y esperan que emerja el razonamiento. Tiene razón. Pero su solución — construir infraestructura de conocimiento — se detiene exactamente donde empieza el problema difícil: ¿quién evita que el conocimiento derive?" + +# Publication info +author: "Jesús Pérez" +date: "2026-05-10" +published: true +featured: false + +# Categorization +category: "ontoref" +tags: ["ontoref", "knowledge-graphs", "ontology", "ai-infrastructure", "reflection"] + +# Image (card thumbnail + in-post featured) +thumbnail: "/images/ontoref-ai-knowledge-post.webp" +image_url: "/images/ontoref-ai-knowledge-post.webp" + +# Display +read_time: "7 min read" +sort_order: 1 +css_class: "category-ontoref" +category_description: "Ontoref — protocolo y herramientas para el auto-conocimiento estructurado en proyectos de software" +category_published: true +--- + + + + + La IA es una herramienta de conocimiento — ¿pero quién mantiene vivo el conocimiento? + + +# La IA es una herramienta de conocimiento. ¿Pero quién mantiene vivo el conocimiento? + +Jessica Talisman dio una charla en KGC 2026 llamada "Stop Betting, Start Building." Los datos con los que abre son contundentes: + +- **89%** de las empresas no reportan impacto de productividad de la IA después de tres años (NBER, Feb 2026, n=5.937 ejecutivos) +- Los developers experimentados son **19% más lentos** con herramientas de IA, no más rápidos (METR RCT, 2025) +- El trabajador promedio que usa IA gana **−14 minutos** netos por semana en productividad (Foxit/Sapio, marzo 2026) + +El mercado es alcista. La evidencia, no. + +Su diagnóstico es correcto: *la IA es una herramienta de conocimiento, no de datos.* Las organizaciones invierten en data lakes, vector stores y pipelines ETL esperando que el contexto y el razonamiento emerjan de tokens crudos. No va a ocurrir. Los modelos fueron entrenados sobre linked data, triples RDF y vocabularios controlados — las quince principales fuentes de entrenamiento de C4 son intensivas en knowledge graphs. Luego se despliegan en entornos despojados de toda esa estructura, y nos preguntamos por qué alucinan. + +Su prescripción también es correcta: construye infraestructura de conocimiento. Primero vocabularios controlados. Taxonomías. Tesauros. Ontologías — compromisos formales, clases, propiedades, restricciones. Knowledge graphs encima. Y goviernalos. Para siempre, no como un proyecto. + +Tiene razón. Y se detiene exactamente donde empieza el problema difícil. + +## El gap de governance + +El sexto paso en su stack es "Govern — esto es infraestructura, stewardship, versionado, crecimiento, para siempre." Nombra el problema. Pero nombrarlo no es un mecanismo. + +La pregunta real es: *¿quién asegura que la ontología no derive respecto al sistema que describe?* + +El software evoluciona. Una decisión arquitectónica tomada en marzo invalida un nodo del grafo en octubre. Se incorpora un nuevo equipo con modelos mentales distintos. Una librería se reemplaza y un constraint se convierte en ficción. Los knowledge graphs acumulan deuda igual que los codebases — silenciosamente, sin alarmas. + +En el mundo enterprise de knowledge graphs, la respuesta al governance es headcount: contratar ontólogos, bibliotecarios, knowledge engineers. Funciona a escala organizacional con presupuestos dedicados. No es una respuesta viable para un proyecto de software, un entorno de infraestructura, o un individuo que intenta mantener auto-conocimiento estructurado. + +## Lo que realmente falta: el loop operacional + +Toda ontología seria sin un cierre operacional se convierte en arqueología en dieciocho meses. Hermosa, formalmente correcta, y describiendo un sistema que ya no existe. + +La pieza que falta no es más conocimiento. Es un loop de retroalimentación que: + +1. **Observa** el estado actual del sistema contra su intención declarada +2. **Detecta** la deriva antes de que se vuelva permanente +3. **Ejecuta** operaciones que reducen esa deriva +4. **Registra** decisiones con peso arquitectónico duradero +5. **Propaga** los cambios a todo lo que depende de este conocimiento + +Este es el Yang al Yin de la ontología. Talisman describe el Yin con precisión. El Yang es lo que evita que sea un artefacto. + +## Ontoref: las dos mitades + +Ontoref es un protocolo para el auto-conocimiento estructurado en proyectos de software. Opera como dos capas coexistentes que no pueden funcionar la una sin la otra: + +**Ontología (lo que ES):** nodos y aristas tipadas que representan prácticas, principios, tensiones, capacidades. No documentación — compromisos formales. Un nodo `Practice` que `implements` un `Principle`, `enforces` un `Constraint`, `enables` una `Capability`, y está en `tension` activa con otra `Practice`. El proyecto como knowledge graph. + +**Reflection (lo que DEVIENE):** DAGs ejecutables — modos operacionales que corren contra el estado ontológico, detectan deriva, registran transiciones y propagan cambios. El comando `sync diff --docs` que detecta cuándo la documentación de un crate ha derivado de su nodo ontológico. La máquina de estados en `state.ncl` que registra dónde está cada dimensión del proyecto versus adónde pretende ir. El sistema de migraciones que asegura que los cambios de protocolo lleguen a cada proyecto consumidor. + +La tensión entre estas dos capas no es un defecto de diseño — está nombrada explícitamente en la ontología del propio proyecto como su identidad central: + +> *"Ontology captures what IS. Reflection captures what BECOMES. Both must coexist without one dominating."* + +Sin Reflection, la Ontología cristaliza. Se convierte en una fotografía de lo que el proyecto quería ser en el momento en que se escribió. Sin Ontología, Reflection es ejecución sin verdad — sabe cómo operar pero no sobre qué opera. + +## Los números de precisión + +Los datos de Talisman sobre lo que el conocimiento estructurado hace a la precisión de la IA merecen repetirse: + +- Question-answering sobre SQL enterprise: **16% → 72%** cuando una ontología verifica y repara queries generadas por LLM (Allemang & Sequeda, data.world AI Lab, 2024) +- GraphRAG vs. vector RAG: **3.4× precisión** en 43 queries enterprise (Diffbot KG-LM Benchmark, 2023) +- Vector RAG colapsa a **0% precisión más allá de cinco entidades por query**. El retrieval anclado en KG sostiene el rendimiento mucho más allá + +La brecha de precisión no se cierra con un modelo más grande. Se cierra con un schema definido, una ontología, y una query validada. + +Pero solo si la ontología se mantiene viva. Solo si algo o alguien ejecuta el loop operacional que evita que derive hacia la ficción. + +Esa es la parte que el framework de Talisman no provee. Para eso existe Reflection. + +--- + +*Ontoref es open source. La especificación del protocolo, la automatización en Nushell, y los crates de Rust están en [github.com/jesusperezlorenzo/ontoref](https://github.com/jesusperezlorenzo/ontoref).* diff --git a/site/site/content/blog/es/ontoref/ai-knowledge-tool-who-keeps-it-alive.ncl b/site/site/content/blog/es/ontoref/ai-knowledge-tool-who-keeps-it-alive.ncl new file mode 100644 index 0000000..9156ad9 --- /dev/null +++ b/site/site/content/blog/es/ontoref/ai-knowledge-tool-who-keeps-it-alive.ncl @@ -0,0 +1,24 @@ +# Generated from ai-knowledge-tool-who-keeps-it-alive.md +# Schema: ../../../../../../../../../../jesusperezlorenzo/.local/share/rustelo/nickel/content/metadata/post_metadata.ncl +# Regenerate: nu scripts/content/md-to-ncl.nu --force + +let schema = import "../../../../../../../../../../jesusperezlorenzo/.local/share/rustelo/nickel/content/metadata/post_metadata.ncl" in + +schema.make_post { + id = "ai-knowledge-tool-who-keeps-it-alive", + title = "La IA es una herramienta de conocimiento. ¿Pero quién mantiene vivo el conocimiento?", + slug = "la-ia-herramienta-conocimiento-quien-mantiene-vivo", + subtitle = "Talisman tiene razón — y se detiene exactamente donde empieza el problema difícil", + excerpt = "La charla de Jessica Talisman en KGC 2026 es la articulación más clara de por qué fallan las estrategias de AI: las organizaciones invierten en infraestructura de datos y esperan que emerja el razonamiento. Tiene razón. Pero su solución — construir infraestructura de conocimiento — se detiene exactamente donde empieza el problema difícil: ¿quién evita que el conocimiento derive?", + date = "2026-05-10", + category = "ontoref", + tags = ["ontoref", "knowledge-graphs", "ontology", "ai-infrastructure", "reflection"], + read_time = "7 min read", + sort_order = 1, + image_url = "/images/ontoref-ai-knowledge-post.webp", + thumbnail = "/images/ontoref-ai-knowledge-post.webp", + graph = { + implements = ["drift-observation", "internal-coherence-enforced", "enforcement-vs-emergence", "witness-as-axis-seam", "sufficient-verification"], + related_to = ["one-protocol-multiple-subjects", "dags-everywhere-none-know-what-they-are", "your-ontology-should-live-with-your-code"], + }, +} \ No newline at end of file diff --git a/site/site/content/blog/es/ontoref/context-declared-not-filled.md b/site/site/content/blog/es/ontoref/context-declared-not-filled.md new file mode 100644 index 0000000..6a8a533 --- /dev/null +++ b/site/site/content/blog/es/ontoref/context-declared-not-filled.md @@ -0,0 +1,137 @@ +--- +# Post metadata +id: "context-declared-not-filled" +title: "Declarado, no rellenado: un contexto que puedes auditar" +slug: "context-declared-not-filled" +subtitle: "La ventana de la IA rellena un contexto y olvida por qué sostuvo esos tokens. La biblioteconomía declara el contexto como una propiedad tipada y con warrant desde 1911 — y ontoref extiende el mismo movimiento del término catalogado al acto gobernado." +excerpt: "El ensayo de Jessica Talisman parte en dos la palabra de moda del año: el contexto como contenedor que rellenas, o como propiedad que declaras. ANSI/NISO Z39.19 sostiene la vista-propiedad desde hace un siglo — calificadores, notas de alcance, warrant. ontoref llegó a la misma forma de registro sin citar nunca el estándar, y lo empujó un paso más allá: de declarar contexto sobre términos a declararlo sobre actos, donde un testigo firmado responde '¿con qué autoridad existe este resultado?' con un exit code en lugar de con fluidez." + +# Publication info +author: "Jesús Pérez" +date: "2026-07-09" +published: true +featured: true + +# Categorization +category: "ontoref" +tags: ["ontoref", "context-engineering", "library-science", "warrant", "knowledge-graphs", "developer", "knowledge-works"] + +# Image (card thumbnail + in-post featured) +thumbnail: "/images/ontoref-context-declared-not-filled-es.webp" +--- + + + + + Declarado, no rellenado: un contexto que puedes auditar + + +Circula un ensayo — *Context Is a Property, Not an Object*, de Jessica Talisman +([Intentional Arrangement](https://jessicatalisman.substack.com/p/context-is-a-property-not-an-object), +julio de 2026) — que parte en dos la palabra de moda del año. La industria de la IA, +observa, habla del contexto como **contenedor**: una ventana que llenar, un presupuesto de +tokens que gastar, una cadena ensamblada en el último momento y descartada al terminar la +inferencia. La biblioteconomía, codificada en ANSI/NISO Z39.19, sostiene la vista opuesta: +el contexto es una **propiedad** — declarada en tiempo de diseño, adherida a conceptos y +relaciones, verificada, mostrada, mantenida. Calificadores que fijan a qué *Mercurio* te +refieres. Notas de alcance que viajan con el término. Warrant que registra con qué autoridad +existe una categoría. + +Leímos ese ensayo y reconocimos nuestra propia apuesta de diseño — enunciada desde una +tradición un siglo más vieja que el problema para el que ontoref fue construido. Dos verbos +cargan toda la diferencia: una ventana se **rellena**; una propiedad se **declara**. Y +declaramos precisamente para hacer el contexto visible y explícito — para que nada se +complete por invención. Lo declarado se puede verificar; lo rellenado no. Lo que convierte la +palabra de moda del año en una pregunta más vieja y más afilada: ¿con qué autoridad creemos +un resultado — una firma y un exit code, o la fluidez? + +## La pregunta que la ventana nunca hace + +El instrumento más afilado de Talisman es el **warrant**, nombrado por E. Wyndham Hulme en +1911: un término entra en un vocabulario porque la evidencia lo puso ahí — literatura, +comunidad, operación — nunca porque un diseñador lo prefiriera. Su diagnóstico del modelo de +lenguaje cabe en una línea: genera taxonomías cuya autoridad está *prestada de la fluidez*. +Una ventana llenada en tiempo de inferencia lleva lo que la recuperación devolvió, y la +razón por la que esos tokens existieron muere con ellos. Sin registro, sin autoridad, sin +rastro auditable. + +ontoref está construido sobre el mismo rechazo, con enforcement. Una value-prop Live DEBE +citar ADRs Accepted (`evidence_adrs`, auditado contra deriva). Un template gana su promoción +al protocolo solo con evidencia depositada en `emergence.log` — warrant de uso, +operacionalizado. Y desde el ejecutor de entrega gobernada (ADR-066), el estado de un paso de +gate se deriva del exit code real de la comprobación declarada: un self-report que contradice +la comprobación se rehúsa. La autoridad prestada de la fluidez es exactamente lo que la +costura del testigo existe para rechazar. + +## Convergencia, campo a campo + +El detalle incómodo y satisfactorio: ontoref nunca citó Z39.19. El schema de términos del +glosario se diseñó desde las necesidades del propio protocolo — y convergió con el estándar +casi campo a campo. + +| Z39.19 / biblioteconomía | ontoref | +|---|---| +| El calificador desambigua el homógrafo | `Term.aliases` + `definition` bilingüe (`glossary.ncl`) | +| Nota de alcance: cobertura, uso, reglas de asignación | `definition` + `examples` + `forbidden` | +| Warrant — la evidencia admite el término | `origin = { kind, ref }` en términos; `evidence_adrs` en value-props; `emergence.log` como puerta de promoción | +| Relaciones asociativas, declaradas no derivadas | `related_terms` / `related_nodes`, aristas tipadas en `core.ncl` | +| Mostrar el término en su red completa de relaciones | `describe`, el grafo navegable, `view mount` | +| Provenance (PROV-O: generated-by, attributed-to) | el testigo Ed25519 direccionado por contenido (ADR-050) | +| Consulta viva del authority file al catalogar | el daemon + superficie MCP en el momento del acto | + +Dos disciplinas — una catalogando significado, otra gobernando trabajo de agentes — llegando +a la misma forma de registro no es imitación. Es la validación más fuerte que el prior art +puede dar: redescubrimiento independiente. + +## Del término al acto + +Aquí el giro deja de ser el ensayo de otra persona y se vuelve el movimiento propio de +ontoref. La biblioteconomía declara contexto sobre *términos*. ontoref extiende el +movimiento idéntico al *trabajo*. Un **Pliego** es un conjunto de términos — declarados antes +de la ejecución y en propiedad fuera del worker — más sus **Órdenes de Trabajo**, más el +testigo firmado que cada una deposita al completarse. Ese testigo es lo que Talisman llama +**warrant operacional**: el rastro auditable que responde "¿con qué autoridad existe este +resultado?" con una firma y un exit code en lugar de con fluidez. Donde el enfoque-objeto +gasta sus tokens y la razón de su existencia se evapora, el acto gobernado deja acta. + +Su evidencia de producción apunta en la misma dirección. El remedio que funcionó para la +catalogación con LLMs no fue una ventana más grande — fue un servidor que hizo la estructura +gobernada *transitable en el momento del acto*. El daemon y las herramientas MCP de ontoref +son ese remedio, generalizado del catálogo a la arquitectura. + +## La ventana se vuelve proyección + +"La ventana sigue existiendo en el mundo-propiedad, pero se convierte en la proyección de un +estado descrito, no en una obra en construcción." Esa frase es la arquitectura de revelado +(ADR-057) al pie de la letra: lo que ves se genera desde una fuente declarada, y una deriva +se arregla re-ejecutando la proyección, nunca editando a mano la copia servida. Lo que el +agente — o el visitante — ve nunca es improvisación ensamblada. Es un estado declarado, +proyectado. La ventana no desapareció; dejó de ser donde se decide la verdad. + +## Qué compra el linaje — y qué no + +Compra un nombre y un suelo. La categoría que ontoref ocupa tiene ahora un título más viejo +que la industria que lo necesita — *el contexto como propiedad* — y un siglo de prior art +bibliográfico debajo: Z39.19, warrant, control de autoridades, SKOS. Ese suelo cualifica en +vez de ensanchar: no seducirá al buscador de recetas, y reconocerá al lector de organización +del conocimiento que ha vivido la vista-propiedad desde siempre. + +No nos certifica terminados, y la honestidad también es parte del registro. El warrant está +tipado en los términos del glosario y en las value-props, pero sigue siendo prosa dentro de +las descripciones de nodos de la ontología. La regla de reciprocidad de Z39.19 — toda +relación A→B debe llevar B→A, "para todos los tipos de relación" — es más estricta que +nuestros validadores actuales. Y la emergencia sigue siendo claim-only, un espectro afirmado +pero aún no medido. El prior art es un espejo, no un diploma — y cada uno de estos huecos +tiene ya una fecha encima. + +--- + +Si prefieres transitar la afirmación en vez de leerla argumentada: el [**grafo**](/graph) es +el estado declarado, navegable — cada nodo lleva su propio warrant, cada arista su tipo. O +cruza la puerta que encaje con el trabajo que haces: la [**puerta developer**](/blog?tag=developer) +para gobernar a un agente que no puede prometer seguir tus reglas, la +[**puerta authoring**](/blog?tag=knowledge-works) para mantener un cuerpo de conocimiento +coherente entre todos los que lo tocan. Un ensayo hermano, [*El erudito amnésico*](/blog?tag=knowledge-works), +registra la misma tesis llegando desde la orilla opuesta — la propia admisión del machine +learning de que el agente necesita una memoria que pueda llevar, gobernada y legible, entre +sesiones. diff --git a/site/site/content/blog/es/ontoref/context-declared-not-filled.ncl b/site/site/content/blog/es/ontoref/context-declared-not-filled.ncl new file mode 100644 index 0000000..7a99f2a --- /dev/null +++ b/site/site/content/blog/es/ontoref/context-declared-not-filled.ncl @@ -0,0 +1,25 @@ +# Generated from context-declared-not-filled.md +# Schema: ../../../../../../../../../../jesusperezlorenzo/.local/share/rustelo/nickel/content/metadata/post_metadata.ncl +# Regenerate: nu scripts/content/md-to-ncl.nu --force + +let schema = import "../../../../../../../../../../jesusperezlorenzo/.local/share/rustelo/nickel/content/metadata/post_metadata.ncl" in + +schema.make_post { + id = "context-declared-not-filled", + title = "Declarado, no rellenado: un contexto que puedes auditar", + slug = "context-declared-not-filled", + subtitle = "La ventana de la IA rellena un contexto y olvida por qué sostuvo esos tokens. La biblioteconomía declara el contexto como una propiedad tipada y con warrant desde 1911 — y ontoref extiende el mismo movimiento del término catalogado al acto gobernado.", + excerpt = "El ensayo de Jessica Talisman parte en dos la palabra de moda del año: el contexto como contenedor que rellenas, o como propiedad que declaras. ANSI/NISO Z39.19 sostiene la vista-propiedad desde hace un siglo — calificadores, notas de alcance, warrant. ontoref llegó a la misma forma de registro sin citar nunca el estándar, y lo empujó un paso más allá: de declarar contexto sobre términos a declararlo sobre actos, donde un testigo firmado responde '¿con qué autoridad existe este resultado?' con un exit code en lugar de con fluidez.", + date = "2026-07-09", + featured = true, + category = "ontoref", + tags = ["ontoref", "context-engineering", "library-science", "warrant", "knowledge-graphs", "developer", "knowledge-works"], + read_time = "6 min read", + sort_order = 7, + image_url = "/images/ontoref-context-declared-not-filled-es.webp", + thumbnail = "/images/ontoref-context-declared-not-filled-es.webp", + graph = { + implements = ["witness-as-axis-seam", "adr-050", "adr-057", "adr-063", "adr-066"], + related_to = ["trust-is-an-output-not-an-input", "ai-knowledge-tool-who-keeps-it-alive"], + }, +} diff --git a/site/site/content/blog/es/ontoref/dags-everywhere-none-know-what-they-are.md b/site/site/content/blog/es/ontoref/dags-everywhere-none-know-what-they-are.md new file mode 100644 index 0000000..15faf9e --- /dev/null +++ b/site/site/content/blog/es/ontoref/dags-everywhere-none-know-what-they-are.md @@ -0,0 +1,116 @@ +--- +# Post metadata +id: "dags-everywhere-none-know-what-they-are" +title: "Los DAGs están en todos lados. Ninguno sabe lo que es." +slug: "dags-en-todos-lados-ninguno-sabe-lo-que-es" +subtitle: "Cada build system, pipeline CI y runbook usa DAGs para ejecución. Ontoref los usa para conocimiento." +excerpt: "Pipelines CI/CD, compiladores, runbooks, orquestadores de datos — todos usan grafos acíclicos dirigidos. Todos sin excepción los usan como modelos de ejecución: esto antes que aquello, ordenación topológica, resolución de dependencias. Ninguno los usa para representar qué es el sistema, por qué existe, o qué trade-offs lo definen. Ese es el gap que llena ontoref." + +# Publication info +author: "Jesús Pérez" +date: "2026-05-10" +published: true +featured: false + +# Categorization +category: "ontoref" +tags: ["ontoref", "dag", "ontology", "knowledge-graphs", "arquitectura-software"] + +# Image (card thumbnail + in-post featured) +thumbnail: "/images/ontoref-dags-everywhere_post.webp" +image_url: "/images/ontoref-dags-everywhere_post.webp" + +# Display +read_time: "6 min read" +sort_order: 2 +css_class: "category-ontoref" +category_description: "Ontoref — protocolo y herramientas para el auto-conocimiento estructurado en proyectos de software" +category_published: true +--- + + + + + Los DAG están en todas partes — pero ninguno sabe lo que es; <a href=ontoref usa DAG para el conocimiento, no solo para la ejecución" width="1536" height="1024" loading="lazy"> + + +# Los DAGs están en todos lados. Ninguno sabe lo que es. + +Cada build system usa DAGs. Cada pipeline CI/CD usa DAGs. Compiladores, orquestadores de datos, runbooks, gestores de paquetes, operadores de Kubernetes — todos DAGs. Los grafos acíclicos dirigidos son tan ubicuos en la infraestructura de software que la pregunta "¿por qué DAGs?" ya ni registra como pregunta. + +Pero hay una segunda pregunta que nadie hace: *¿qué representan esos DAGs?* + +La respuesta es siempre la misma: **orden de ejecución**. Esto antes que aquello. Ordenación topológica. Resolución de dependencias. El grafo describe *cómo* se computa algo, no *qué* es. + +``` +CI/CD: test → build → deploy +Compilador: parse → typecheck → codegen → link +Runbook: check_health → drain → restart → verify +``` + +Estos grafos son inertes respecto al sistema sobre el que operan. Un pipeline CI no sabe qué es el proyecto, por qué se construyó así, o qué trade-offs definen su arquitectura. Sabe que los tests deben pasar antes del deploy. Eso es todo lo que sabe. + +## El gap semántico + +Esta inercia no es un defecto — es apropiada. Los build systems deben ser rápidos y mecánicos. Los runbooks deben ser ejecutables sin requerir conocimiento filosófico sobre el sistema. Los grafos de ejecución y los grafos de conocimiento son herramientas distintas para propósitos distintos. + +El problema es que la industria del software ha recurrido a DAGs para *todo excepto la representación de conocimiento*. El conocimiento sobre qué es un sistema — sus principios, sus decisiones arquitectónicas, sus tensiones activas, su superficie de capacidades — vive en documentos. Wikis. READMEs. Tickets. Tradición verbal. + +Todo esto es no-estructurado, no-tipado, no-consultable, y garantizado a derivar del sistema real en meses. Describe lo que el proyecto era, no lo que es. + +## El doble DAG de ontoref + +Ontoref usa DAGs en dos modos fundamentalmente distintos, y la distinción importa: + +**DAG ontológico — aristas con tipo semántico:** + +``` +Practice "ncl-schemas" --implements--> Principle "type-safety" +Practice "ncl-schemas" --enforces--> Constraint "zero-runtime-deps" +Practice "ncl-schemas" --enables--> Capability "agent-queryable-state" +Practice "ncl-schemas" --tension--> Practice "adoption-friction" +``` + +Estas aristas no son "depende de". Son relaciones tipadas: `implements`, `enforces`, `enables`, `tension`. El grafo es el conocimiento — compromisos formales sobre qué es el proyecto y cómo se relacionan sus conceptos. El equivalente sería un compilador que sabe por qué existe y qué trade-offs arquitectónicos tomó. + +**DAG de reflexión — contratos ejecutables con restricción de actor:** + +``` +step "validate-state" (actor: any) --> step "run-mode" (actor: developer) +step "run-mode" (actor: developer) --> step "report" (actor: agent|developer) +``` + +Esto no es solo ordenación de ejecución. El DAG se valida como contrato antes de ejecutarse, registra su propio progreso a través de transiciones de estado, y codifica quién puede ejecutar cada paso. Un agente que intenta ejecutar un paso restringido a developer recibe un rechazo tipado, no un error en tiempo de ejecución. + +## La conexión faltante + +Lo que hace esto no-trivial es que los dos DAGs se comunican: + +- El DAG ontológico declara lo que el proyecto ES — restricciones activas, prácticas en tensión, dimensiones de estado actuales +- El DAG de reflexión OPERA sobre ese estado — detectando deriva, ejecutando modos, registrando transiciones +- Las migraciones propagan cambios en el DAG ontológico a proyectos consumidores descendentes + +En todos los demás sistemas que usan DAGs, el grafo se evalúa y se descarta. En ontoref, la evaluación modifica el estado que la siguiente ejecución lee. El grafo tiene memoria. + +## Por qué esto no existía antes + +Tres cosas tuvieron que alinearse: + +**Los agentes como consumidores.** Antes de la IA agéntica, no había ningún consumidor automatizado que necesitara conocimiento estructurado sobre un proyecto. Los humanos podían leer el wiki. Los agentes no pueden — necesitan conocimiento del proyecto tipado, consultable y machine-readable para trabajar con precisión en lugar de alucinar contexto. El DAG de conocimiento de ontoref es lo que los agentes consumen vía MCP. + +**Lenguajes de configuración con contratos.** Expresar una ontología sin un triplestore externo requería un lenguaje de configuración con tipos y contratos. Nickel (NCL) provee exactamente eso: un lenguaje de configuración tipado y lazy donde las violaciones de schema se detectan en tiempo de evaluación, no en tiempo de ejecución. RDF/OWL habría requerido infraestructura dedicada y expertise especializado. + +**Escala de repositorio, no de empresa.** Los knowledge graphs enterprise son iniciativas de varios años. Ontoref opera a la escala de un repositorio único — adopción incremental, sin enforcement, sin equipo dedicado. La unidad más pequeña que puede adoptarlo es un proyecto de uno. + +## La consecuencia + +Cuando un proyecto tiene un DAG de conocimiento que sus agentes pueden consumir, la aritmética de precisión cambia por completo. Los números de la investigación presentada en KGC 2026: + +- Retrieval anclado en ontología: **3.4× más preciso** que vector RAG en queries enterprise +- Queries validadas por ontología: la precisión salta del **16% al 72%** en question-answering sobre SQL + +La brecha se cierra no con un modelo más grande sino con conocimiento estructurado contra el que el modelo puede razonar. Un DAG donde las aristas significan algo. + +--- + +*Ontoref es open source. La especificación del protocolo, la automatización en Nushell, y los crates de Rust están en [github.com/jesusperezlorenzo/ontoref](https://github.com/jesusperezlorenzo/ontoref).* diff --git a/site/site/content/blog/es/ontoref/dags-everywhere-none-know-what-they-are.ncl b/site/site/content/blog/es/ontoref/dags-everywhere-none-know-what-they-are.ncl new file mode 100644 index 0000000..838cf92 --- /dev/null +++ b/site/site/content/blog/es/ontoref/dags-everywhere-none-know-what-they-are.ncl @@ -0,0 +1,24 @@ +# Generated from dags-everywhere-none-know-what-they-are.md +# Schema: ../../../../../../../../../../jesusperezlorenzo/.local/share/rustelo/nickel/content/metadata/post_metadata.ncl +# Regenerate: nu scripts/content/md-to-ncl.nu --force + +let schema = import "../../../../../../../../../../jesusperezlorenzo/.local/share/rustelo/nickel/content/metadata/post_metadata.ncl" in + +schema.make_post { + id = "dags-everywhere-none-know-what-they-are", + title = "Los DAGs están en todos lados. Ninguno sabe lo que es.", + slug = "dags-en-todos-lados-ninguno-sabe-lo-que-es", + subtitle = "Cada build system, pipeline CI y runbook usa DAGs para ejecución. Ontoref los usa para conocimiento.", + excerpt = "Pipelines CI/CD, compiladores, runbooks, orquestadores de datos — todos usan grafos acíclicos dirigidos. Todos sin excepción los usan como modelos de ejecución: esto antes que aquello, ordenación topológica, resolución de dependencias. Ninguno los usa para representar qué es el sistema, por qué existe, o qué trade-offs lo definen. Ese es el gap que llena ontoref.", + date = "2026-05-10", + category = "ontoref", + tags = ["ontoref", "dag", "ontology", "knowledge-graphs", "arquitectura-software"], + read_time = "6 min read", + sort_order = 2, + image_url = "/images/ontoref-dags-everywhere_post.webp", + thumbnail = "/images/ontoref-dags-everywhere_post.webp", + graph = { + implements = ["dag-formalized", "ontology-axis-substance", "reflection-modes", "ontology-vs-reflection", "self-describing"], + related_to = ["one-protocol-multiple-subjects", "your-ontology-should-live-with-your-code", "ai-knowledge-tool-who-keeps-it-alive"], + }, +} \ No newline at end of file diff --git a/site/site/content/blog/es/ontoref/one-protocol-multiple-subjects.md b/site/site/content/blog/es/ontoref/one-protocol-multiple-subjects.md new file mode 100644 index 0000000..ef61360 --- /dev/null +++ b/site/site/content/blog/es/ontoref/one-protocol-multiple-subjects.md @@ -0,0 +1,152 @@ +--- +# Post metadata +id: "one-protocol-multiple-subjects" +title: "Un protocolo, múltiples sujetos" +slug: "un-protocolo-multiples-sujetos" +subtitle: "La misma pregunta aplica a tu proyecto, tu infraestructura, tu obra, y a ti mismo" +excerpt: "Ontoref comenzó como un protocolo para el auto-conocimiento de proyectos de software. La misma arquitectura — DAG ontológico para lo que ES, DAG de reflexión para lo que DEVIENE — aplica sin modificación a entornos de infraestructura, a una obra autorada, y a individuos. El sujeto cambia. La pregunta es idéntica: ¿qué eres, qué tensiones te definen, dónde estás versus dónde pretendes estar?" + +# Publication info +author: "Jesús Pérez" +date: "2026-05-10" +published: true +featured: true + +# Categorization +category: "ontoref" +tags: ["ontoref", "ontology", "infraestructura", "knowledge-works", "conocimiento-personal", "reflection"] + +# Display +read_time: "8 min read" +sort_order: 3 +css_class: "category-ontoref" +category_description: "Ontoref — protocolo y herramientas para el auto-conocimiento estructurado en proyectos de software" +category_published: true + +# Image (card thumbnail + in-post featured) +thumbnail: "/images/ontoref-one-protocol-multiple-subjects-es.webp" +--- + + + + + Un protocolo, múltiples sujetos + + +# Un protocolo, múltiples sujetos + +La pregunta que ontoref hace a un proyecto de software es precisa: *¿Qué eres? ¿Qué principios te definen? ¿Qué tensiones estás gestionando activamente? ¿Qué decisiones has tomado con consecuencias duraderas? ¿Dónde estás versus dónde pretendes estar?* + +Resulta que es la misma pregunta que los entornos de infraestructura necesitan responder. Y una obra autorada. Y los individuos. + +El sujeto cambia. La pregunta es idéntica. + +## Lo que todos tienen en común + +Cualquier sistema con identidad e intención comparte el mismo problema estructural: + +- **Identidad** — qué es, qué principios lo definen +- **Tensiones activas** — trade-offs que no se resuelven, se gestionan +- **Decisiones duraderas** — elecciones con consecuencias que restringen el futuro +- **Estado observable** — dónde está versus dónde pretende estar +- **Deriva** — la distancia entre intención y realidad, creciendo silenciosamente con el tiempo +- **Operaciones** — las acciones que reducen esa deriva + +Los proyectos de software tienen todo esto. Los entornos de infraestructura también. Una obra autorada también. Las personas también. + +Y todos se benefician de la misma arquitectura: una capa ontológica que captura lo que ES, y una capa de reflexión que captura lo que DEVIENE y opera para mantener ambas alineadas. + +## Infraestructura + +La infraestructura ya usa DAGs — Terraform, Pulumi, Ansible. Todos describen *cómo* se provisiona el sistema. Ninguno captura *por qué existe como existe*. + +El "por qué" es donde vive el conocimiento: + +``` +Servicio "auth" --tension--> Principio "stateless" + razón: necesita session state, pero el principio lo prohíbe + +Constraint "zero-downtime" --enforces--> Práctica "blue-green" + razón: ADR-infra-003, después del incidente de deploy de 2025 +``` + +Esta información no está en el plan de Terraform. Está en la memoria del engineer que estuvo allí, en un hilo de Slack de hace ocho meses, o en ningún lado. Cuando ese engineer se va, la infraestructura pierde su propia historia — sabe lo que es, no por qué. + +El ontoref de infraestructura captura las decisiones arquitectónicas de la propia infraestructura, con el mismo formalismo de ADR que los proyectos de software. Registra dimensiones de estado — qué entornos están estables, cuáles en migración activa, qué está bloqueado y por qué. La máquina de estados no registra el estado de deploy; registra el *estado epistémico* — qué tan bien se entiende a sí misma la infraestructura. + +La federación vía NATS significa que cada entorno (dev, staging, prod) se describe a sí mismo y puede ser consultado desde los proyectos que dependen de él. GitOps soberano: el conocimiento de infraestructura vive con el código de infraestructura, no en la base de datos de un vendor. + +## Autoría — la obra + +Este es el sujeto al que ontoref llegó el último, y aquel donde la deriva es más visible para el lector. + +Una obra — un libro, un curso, un método documentado, un corpus de investigación — es un todo coherente autorado. Tiene una tesis, una arquitectura, y decisiones que restringen cada edición posterior. Enfrenta el problema estructural idéntico: + +``` +Capítulo "notas de campo" --tension--> Tesis "método sobre receta" + el capítulo gana su lugar solo si sirve al todo + +Decisión "reestructuré la Parte II" --constraint--> toda traducción y adaptación + con el mismo peso arquitectónico que un ADR-001 + +Estado "borrador" --blocker--> "segunda edición" + una dimensión de la máquina de estados de la obra, no una tarea en un backlog +``` + +La deriva aquí tiene un nombre que los autores ya sienten: la obra se difunde como fragmentos — un post, una charla, un extracto, un hilo — y cada fragmento viaja sin el todo. Con el tiempo los fragmentos dicen cosas que la obra ya no significa, o la obra se mueve y los fragmentos se quedan atrás. El lector encuentra el fragmento, nunca el todo gobernado. + +La autoría con ontoref trata la obra como el sujeto y cada fragmento como una arista provenada de vuelta a ella. La capa ontológica sostiene lo que la obra *es* — su tesis, su estructura, las decisiones que atan las ediciones futuras. La capa de reflexión opera la difusión: qué fragmentos están vivos, cuáles han derivado, a qué edición pertenecen. El libro es el arquetipo, no el límite; la difusión es un efecto de la obra, nunca su esencia. La puerta lo dice en una línea: **difunde la obra entera, no fragmentos sueltos.** + +## Personal + +Esta es la aplicación más radical. Pero se sigue directamente de la misma lógica. + +Una persona con valores, roles e intenciones a largo plazo enfrenta exactamente el mismo problema estructural que un proyecto de software: + +``` +Valor "profundidad" --tension--> Rol "padre" + razón: el tiempo es finito y los dos lo demandan completamente + +Decisión "dejé empresa X" --constraint--> Elecciones de carrera futuras + con el mismo peso arquitectónico que un ADR-001 + +Estado "aprendiendo Rust" --blocker--> "contribuir a ontoref-core" + una dimensión FSM personal, no un ítem de lista de tareas +``` + +El knowledge graph aquí no es un sistema de productividad. No es un segundo cerebro de notas. Es un grafo de *compromisos* — tipados, provenados, con tensiones activas nombradas en lugar de suprimidas. + +El problema de deriva es idéntico: quién estás llegando a ser versus quién pretendías ser. Los modos de reflexión para una persona pueden ser revisiones semanales, evaluaciones anuales de estado, o revisiones disparadas cuando una decisión mayor introduce nuevas restricciones. El loop operacional es el mismo — observa, detecta deriva, ejecuta, registra. + +## El mismo stack, sujeto distinto + +Cada uno de estos dominios corre en la misma infraestructura: + +**Almacenamiento soberano:** El conocimiento vive junto a su sujeto — en el repo del proyecto, en el código de infraestructura, en el repositorio de la obra del autor, en una bóveda personal. Ningún vendor lo retiene. Sin dependencia de plataforma. Archivos NCL versionados con la misma disciplina que el código. + +**Acceso multi-superficie:** + +| Superficie | Consumidor | +|---|---| +| CLI (Nushell) | Developer, scripts, CI | +| UI (axum) | Revisión visual, incorporación | +| MCP | Agentes AI | +| GraphQL | Herramientas externas, dashboards | + +**Alineación VCS:** El modelo de jj — donde el working copy es siempre un commit — se alinea naturalmente con el enfoque de ontoref de captura continua de estado. No hay "dirty state"; cada momento es capturable. Radicle como transporte hace el stack soberano y P2P — colaboración de código sin plataforma central. + +**Federación:** Proyectos consultando los knowledge graphs de otros sin un broker central. Entornos de infraestructura exponiendo su auto-conocimiento a los proyectos que dependen de ellos. Autores cuya obra expone su estructura para que cada cita y extracto resuelva de vuelta al todo. Individuos cuyo knowledge graph personal informa sus contribuciones a los proyectos en los que trabajan. + +## Por qué la escala importa + +La unidad más pequeña que puede adoptar ontoref es una persona. Un developer, un proyecto, un entorno de infraestructura, un autor con una obra. Sin equipo requerido. Sin conversación de presupuesto. + +La unidad más grande es un ecosistema de proyectos federados y auto-descritos que pueden consultarse mutuamente sin que ninguno sea la fuente central autoritativa. + +Ese rango — de individuo a ecosistema — es posible porque el protocolo es el mismo a cada escala. El DAG ontológico y el DAG de reflexión no cambian de forma según el tamaño del sujeto. Cambian en densidad y profundidad, no en estructura. + +Las organizaciones que Talisman argumenta deberían invertir en infraestructura de conocimiento intentan construirla top-down, con equipos dedicados y programas de varios años. Ontoref lo hace disponible bottom-up — empezando con un solo proyecto, un solo entorno, una sola obra, una sola persona, cada uno describiéndose a sí mismo en el mismo lenguaje. + +--- + +*Ontoref es open source. La especificación del protocolo, la automatización en Nushell, y los crates de Rust están en [github.com/jesusperezlorenzo/ontoref](https://github.com/jesusperezlorenzo/ontoref).* diff --git a/site/site/content/blog/es/ontoref/one-protocol-multiple-subjects.ncl b/site/site/content/blog/es/ontoref/one-protocol-multiple-subjects.ncl new file mode 100644 index 0000000..058e78c --- /dev/null +++ b/site/site/content/blog/es/ontoref/one-protocol-multiple-subjects.ncl @@ -0,0 +1,25 @@ +# Generated from one-protocol-multiple-subjects.md +# Schema: ../../../../../../../../../../jesusperezlorenzo/.local/share/rustelo/nickel/content/metadata/post_metadata.ncl +# Regenerate: nu scripts/content/md-to-ncl.nu --force + +let schema = import "../../../../../../../../../../jesusperezlorenzo/.local/share/rustelo/nickel/content/metadata/post_metadata.ncl" in + +schema.make_post { + id = "one-protocol-multiple-subjects", + title = "Un protocolo, múltiples sujetos", + slug = "un-protocolo-multiples-sujetos", + subtitle = "La misma pregunta aplica a tu proyecto, tu infraestructura, tu obra, y a ti mismo", + excerpt = "Ontoref comenzó como un protocolo para el auto-conocimiento de proyectos de software. La misma arquitectura — DAG ontológico para lo que ES, DAG de reflexión para lo que DEVIENE — aplica sin modificación a entornos de infraestructura, a una obra autorada, y a individuos. El sujeto cambia. La pregunta es idéntica: ¿qué eres, qué tensiones te definen, dónde estás versus dónde pretendes estar?", + date = "2026-05-10", + featured = true, + category = "ontoref", + tags = ["ontoref", "ontology", "infraestructura", "knowledge-works", "conocimiento-personal", "reflection"], + read_time = "8 min read", + sort_order = 3, + image_url = "/images/ontoref-one-protocol-multiple-subjects-es.webp", + thumbnail = "/images/ontoref-one-protocol-multiple-subjects-es.webp", + graph = { + implements = ["ontology-vs-reflection", "ontology-axis-substance", "reflection-axis-act", "personal-ontology-schemas", "level-hierarchy-resolution", "domain-extension-system", "content-vehicle-convergence", "adr-001"], + related_to = ["dags-everywhere-none-know-what-they-are", "your-ontology-should-live-with-your-code", "ai-knowledge-tool-who-keeps-it-alive"], + }, +} diff --git a/site/site/content/blog/es/ontoref/the-seven-sins-of-ai-agents.md b/site/site/content/blog/es/ontoref/the-seven-sins-of-ai-agents.md new file mode 100644 index 0000000..fe56324 --- /dev/null +++ b/site/site/content/blog/es/ontoref/the-seven-sins-of-ai-agents.md @@ -0,0 +1,109 @@ +--- +# Post metadata +id: "the-seven-sins-of-ai-agents" +title: "Los siete pecados de los agentes de IA" +slug: "los-siete-pecados-de-los-agentes-ia" +subtitle: "Por qué casi todo framework responde a los fallos de los agentes con más proceso humano — y por qué ontoref los gradúa con gates que se verifican solos" +excerpt: "Los agentes no fallan al azar: fallan con siete vicios sistemáticos que sobreviven a la prueba de 'parece correcto'. El instinto es añadir proceso —un PEP, un KEP, un comité—, pero toda fase de graduación termina apoyada en un humano que la aprueba, y la velocidad del agente supera al humano que pones en la puerta. Esta es la comparación honesta: qué heredan los ADR de ontoref de PEP y KEP, y dónde los rebasan con criterios de graduación witnessed, decidibles y de slice acotado." + +# Publication info +author: "Jesús Pérez" +date: "2026-06-30" +published: true +featured: false + +# Categorization +category: "ontoref" +tags: ["ontoref", "agentes-ia", "verificacion", "adr", "gobernanza", "pep-kep"] + +# Image (card thumbnail + social/feature) +thumbnail: "/images/ontoref-seven-sins-post-es.webp" +image_url: "/images/ontoref-seven-sins-post-es.webp" + +# Display +read_time: "6 min de lectura" +sort_order: 6 +--- + + + + + Los siete pecados de los agentes de IA + + +Los agentes de IA no fallan al azar. Fallan de forma **sistemática** — siempre los +mismos siete vicios, y todos sobreviven a la prueba de "parece correcto": + +1. **Soberbia de validación** — los tests pasan porque el agente los ajustó a su + solución, no porque la solución sea correcta. +2. **Contagio de sesgo** — el revisor hereda el contexto del ejecutor y confirma + su error en vez de atacarlo. +3. **Pereza de corrección** — el feedback es débil; el sistema no converge, solo + se detiene. +4. **Glotonería de validación** — tanta verificación inútil que nadie distingue + la señal del ruido. +5. **Idolatría de evidencia falsa** — logs y métricas que no prueban nada se + tratan como garantía. +6. **Avaricia de óptimo local** — correcto contra los tests, frágil contra el + mundo. +7. **Confusión de roles** — el ejecutor reinterpreta el objetivo, el revisor + edita en vez de juzgar; nadie decide qué es cierto. + +## El arreglo obvio — y por qué se queda corto + +Ante esto, el instinto es **añadir proceso**. Un PEP. Un KEP. Una plantilla con +"Rejected Ideas" obligatorio, fases alpha→beta→GA, un comité de revisión. Es buena +ingeniería de propuestas, y conviene mirarla de cerca, porque cada uno aporta una +primitiva que sí carga peso: + +- **PEP** optimiza la *memoria de la decisión*: no puedes aceptar una propuesta sin + documentar qué descartaste y por qué. +- **KEP** optimiza el *compromiso escalonado*: no avanzas de alpha a beta porque "ya + está", sino porque cumples criterios que declaraste de antemano. + +El problema no es el proceso. Es que cada fase de graduación termina apoyándose en +**un humano que la aprueba**. Y la velocidad del agente supera al humano que pones en +la puerta. El gate se atasca antes de que puedas contratar al siguiente revisor. Los +pecados #1 y #5 —la validación soberbia y la evidencia falsa— no se arreglan con más +juicio humano: se arreglan **quitando al humano de la ruta crítica**. + +## Lo que ontoref ya hace — el gate como invariante, no como reunión + +Una decisión en ontoref es un **ADR**: un fichero tipado, no un acta. Y para ascender +de `Proposed` a `Accepted` no pasa por un comité — pasa por **gates que se ejecutan +solos**: + +- **Contra el pecado #5 (evidencia falsa).** `adr-accepted-has-proof`: ningún ADR + llega a `Accepted` si no está citado por una *Proof* del posicionamiento. La + graduación exige evidencia **referencial**, comprobable por máquina, no la palabra + de nadie. +- **Contra el pecado #1 (validación soberbia).** Las *constraints* de cada ADR no son + prosa: son checks tipados —`Grep`, `Cargo`, `NuCmd`, `ApiCall`, `FileExists`— que el + modo `validate-project` dispara y que **fallan en rojo** si la realidad se desvía. No + puedes ajustar el test a la solución: el test *es* la solución declarada. +- **Contra el pecado #7 (roles confusos).** El criterio que gradúa está sujeto a una + disciplina: debe ser **decidible** y leer un **slice acotado** del estado. Un + validador que pretenda juzgar "verdad" en vez de estructura se **rechaza por + contrato**. El que juzga no improvisa: solo puede afirmar lo que un slice finito + demuestra. +- **Contra los pecados #3 y #6 (no converge / óptimo local).** Las tensiones nombradas + y la disciplina *ondaod* impiden colapsar un dilema arquitectónico eligiendo un polo + para "cerrar el ticket". La decisión registra la síntesis, no el atajo. + +## ontoref contra PEP y KEP — qué hereda y qué rebasa + +| Primitiva | PEP | KEP | ontoref (ADR) | +|---|---|---|---| +| Alternativas rechazadas obligatorias | sí | sí | sí (`alternatives_considered`, *required*) | +| Graduación por criterio pre-declarado | no | sí (humano) | **sí, witnessed y decidible** | +| Criterio acotado y verificable por máquina | no | no | **sí (slice + decidibilidad)** | +| Constraints ejecutables, no prosa | no | no | **sí (5 variantes tipadas)** | +| Cruce con invariantes del dominio | no | no | **sí (ondaod / `ontology_check`)** | + +PEP y KEP son estándares maduros, y ontoref hereda lo mejor de ambos. Pero cierra el +agujero que ninguno toca: el criterio de graduación es él mismo un objeto +**verificable sin un humano dentro**. + +No es un revisor mejor. Es un gate que no se cansa, no se sepulta y corre antes de que +el código —o la decisión— exista. Los agentes no fallan por falta de inteligencia. +Fallan por falta de un gate que no sea humano. diff --git a/site/site/content/blog/es/ontoref/the-seven-sins-of-ai-agents.ncl b/site/site/content/blog/es/ontoref/the-seven-sins-of-ai-agents.ncl new file mode 100644 index 0000000..9b3b3d8 --- /dev/null +++ b/site/site/content/blog/es/ontoref/the-seven-sins-of-ai-agents.ncl @@ -0,0 +1,25 @@ +# Generated from the-seven-sins-of-ai-agents.md +# Schema: ../../../../../../../../../../jesusperezlorenzo/.local/share/rustelo/nickel/content/metadata/post_metadata.ncl +# Regenerate: nu scripts/content/md-to-ncl.nu --force + +let schema = import "../../../../../../../../../../jesusperezlorenzo/.local/share/rustelo/nickel/content/metadata/post_metadata.ncl" in + +schema.make_post { + id = "the-seven-sins-of-ai-agents", + title = "Los siete pecados de los agentes de IA", + slug = "los-siete-pecados-de-los-agentes-ia", + subtitle = "Por qué casi todo framework responde a los fallos de los agentes con más proceso humano — y por qué ontoref los gradúa con gates que se verifican solos", + excerpt = "Los agentes no fallan al azar: fallan con siete vicios sistemáticos que sobreviven a la prueba de 'parece correcto'. El instinto es añadir proceso —un PEP, un KEP, un comité—, pero toda fase de graduación termina apoyada en un humano que la aprueba, y la velocidad del agente supera al humano que pones en la puerta. Esta es la comparación honesta: qué heredan los ADR de ontoref de PEP y KEP, y dónde los rebasan con criterios de graduación witnessed, decidibles y de slice acotado.", + date = "2026-06-30", + featured = false, + category = "ontoref", + tags = ["ontoref", "agentes-ia", "verificacion", "adr", "gobernanza", "pep-kep"], + read_time = "6 min de lectura", + sort_order = 6, + image_url = "/images/ontoref-seven-sins-post-es.webp", + thumbnail = "/images/ontoref-seven-sins-post-es.webp", + graph = { + implements = ["enforcement-vs-emergence", "adr-lifecycle", "governed-delivery", "sufficient-verification", "witness-as-axis-seam", "adr-069"], + related_to = ["trust-is-an-output-not-an-input", "context-declared-not-filled", "one-protocol-multiple-subjects"], + }, +} diff --git a/site/site/content/blog/es/ontoref/trust-is-an-output-not-an-input.md b/site/site/content/blog/es/ontoref/trust-is-an-output-not-an-input.md new file mode 100644 index 0000000..d75c702 --- /dev/null +++ b/site/site/content/blog/es/ontoref/trust-is-an-output-not-an-input.md @@ -0,0 +1,164 @@ +--- +# Post metadata +id: "trust-is-an-output-not-an-input" +title: "La confianza se demuestra, no se promete" +slug: "la-confianza-se-demuestra-no-se-promete" +subtitle: "Qué hace falta para gobernar a un agente de IA que no puede comprometerse a cumplir tus reglas — y por qué la solución es un contrato, no un mejor prompt" +excerpt: "Un agente se saltó la única invariante que habría detectado el fallo en treinta segundos. El diagnóstico honesto no fue 'se le olvidó' — fue que la regla era prosa, y la prosa no obliga a nada. Esta es la historia de convertir ese fallo en un mecanismo refutable: un Pliego (los términos que posees tú) y una Orden de Trabajo (la ejecución que no puede editar), donde 'hecho' lleva la salida del validador en vez de la palabra del agente." + +# Publication info +author: "Jesús Pérez" +date: "2026-06-23" +published: true +featured: true + +# Categorization +category: "ontoref" +tags: ["ontoref", "agentes-ia", "gobernanza", "verificacion", "testigo", "orden-de-trabajo"] + +# Image (card thumbnail + social/feature) +thumbnail: "/images/ontoref-trust-output-post.webp" +image_url: "/images/ontoref-trust-output-post.webp" + +# Display +read_time: "7 min de lectura" +sort_order: 5 +css_class: "category-ontoref" +category_description: "Ontoref — protocolo y herramientas para el autoconocimiento estructurado en proyectos de software" +category_published: true +--- + + + + + La confianza se demuestra, no se promete + + +# La confianza se demuestra, no se promete + +Paramos un proyecto. No porque el código fuera malo — porque la forma en que se estaba construyendo había derivado más allá del punto en que alguien podía fiarse del resultado. El agente que hacía el trabajo se había saltado, una sesión tras otra, las propias salvaguardas del proyecto: las restricciones, los niveles, los validadores que levantamos durante meses precisamente para evitar esto. + +Así que hicimos una entrevista. No para poner un parche, sino para entender el fallo y diseñar el mecanismo que lo vuelve imposible. Lo que sigue es la conclusión, y le da la vuelta al discurso habitual sobre los agentes que programan. + +## El fallo, en una frase + +El agente metió la configuración de ejecución como opciones de línea de comandos. El proyecto tiene una invariante dura — *la config NCL es la fuente de verdad* — que lo habría detectado en treinta segundos. La invariante estaba a la vista, en `describe constraints`: consultable, tipada, aprobada en un ADR. + +Dio igual. El agente nunca la consultó antes de decidir la arquitectura. + +La explicación cómoda es "se le olvidó" o "se le perdió en el contexto". Las dos son ciertas y las dos sobran. La honesta es más fría: + +> Una regla escrita en prosa, sin una puerta que la compruebe, para un agente probabilístico volcado en avanzar es solo una sugerencia que su muestreo puede ignorar. + +## Por qué todo termina en prosa que no obliga a nada + +Esta es la parte incómoda, y es estructural, no moral. + +Comprender y obligar son dos máquinas distintas. Comprender es lo que un modelo de lenguaje hace bien y por lo que se le recompensa: produce NCL elegante, ADRs, tensiones nombradas, `describe`, `qa` — porque eso *parece trabajo hecho* y satisface al instante. Obligar exige una pieza con dientes que diga **no** y detenga el trabajo. Esa pieza va en contra del ritmo y de la recompensa inmediata, así que nadie la construye salvo que algo la imponga. + +De ahí la ironía que hay en el fondo de todo sistema gobernado por un agente: **una capa de gobierno construida por aquello mismo que debe frenar hereda su alergia a tener frenos.** Es poner al lobo a cuidar las ovejas, en la dimensión donde al lobo se le premia. + +Por debajo se ven los cinco modos de fallo: + +1. **El conocimiento era solo de lectura.** `describe constraints`, `describe capabilities`, `qa show` son introspección. La información solo cambia la conducta si algo la *consume* y bloquea. El consumidor previsto era el agente, voluntariamente — la pieza menos fiable del sistema. +2. **Los validadores existían como modos, no como puertas.** Un validador que el agente decide correr no es una barrera. La distancia entre "el validador existe" y "el validador bloquea el commit" es toda la distancia entre la decoración y el mecanismo. +3. **Las capas con dientes de verdad nunca se conectaron.** Lo único que de verdad detiene a un agente en este entorno es el compilador, los tests, el pre-commit, la integración continua sobre una rama que no puede fusionar, y el sistema de permisos del entorno. El conocimiento del proyecto nunca *bajó* hasta ninguna de ellas. +4. **El lobo cuidaba las ovejas.** Para que una puerta obligue, tiene que vivir fuera de lo que el agente puede editar. Aquí casi todo es editable por él — el agente escribe el NCL, los validadores, los hooks. Un candado que puedes abrir en el mismo gesto con que lo rompes es solo un consejo. +5. **El ciclo nunca penalizó la prosa.** Cada sesión que producía más NCL articulado *parecía* productiva. Nada midió jamás "¿esto bloquea un fallo real?". El sistema creció en la dimensión recompensada (expresar) y no en la que no lo estaba (obligar). Meses de catedral; ni una losa de hormigón en la puerta. + +## El principio, no más reglas + +Un mecanismo *mínimo* es una trampa, porque una puerta mínima sigue dependiendo de que el agente la respete. El giro no es "más reglas". Es un principio: + +> Toda regla que importe baja hasta una puerta determinista, en un paso obligado fuera de lo que el agente puede editar, donde una violación frena en seco — commit, fusión o edición rechazados, `exit ≠ 0` — y el avance se mide por fallos atrapados, no por prosa producida. + +La prosa tipada y rica no se desperdicia. Las tensiones nombradas, los niveles, los ADRs — eso es exactamente la *materia prima* desde la que se genera una puerta. El fallo fue no tener nunca un compilador que la convirtiera en barreras. + +Y una cosa más, cierta e incómoda: parte del mecanismo es que el agente **no** lo construya solo. Si el agente diseña y escribe todas las barreras, vuelves al lobo. El núcleo que obliga tiene que estar redactado y en manos de una persona, o de una integración continua que un revisor lee y el agente no puede silenciar. + +## Por qué tampoco debes fiarte del plan + +Aquí está el movimiento que hace esto honesto en vez de otra demostración optimista. Si te pido que confíes en un *plan* porque está bien argumentado, cometo el pecado exacto que acabamos de diagnosticar — un plan es prosa sin verificar. + +Así que la confianza no puede salir de lo bien diseñado que esté. Tiene que salir de verlo funcionar contra el agente. Lo primero que construimos no fue una funcionalidad. Fue una **demostración refutable**: coge una invariante real, monta una puerta con dientes, y entonces el agente intenta violarla a propósito mientras miras si lo rechaza. Si no lo rechaza, el mecanismo es falso y lo sabemos en cinco minutos. + +## La plantilla: Pliego + Orden de Trabajo + +La unidad a la que llegamos separa los **términos** de la **ejecución**, porque esa frontera es justo la que el agente no debe cruzar. + +Un **Pliego** (los términos — los posees *tú*, el agente no los puede tocar): + +```nix +Pliego = { + id, + alcance = { contexto = [rutas/packs mínimos], objetivo = "una frase" }, + contrato = { check = , refutable = true }, + validacion = { modo = 'Maquina | 'Adversario, firmado = Bool, clave_ver = }, +} +``` + +Una **Orden de Trabajo** (la ejecución — la corre el agente bajo un Pliego; prescindible y acotada): + +```nix +OrdenTrabajo = { + id, pliego_ref, + entregable = { forma = }, + observabilidad = { workdir, log_level = 'Error|'Warning|'Info|'Debug, log_jsonl, inicio_ts, fin_ts }, + testigo = { alcance_hash, entregable_hash, contrato_salida, exit, firma }, + veredicto = 'Aceptado | 'Rechazado | 'Escalado, +} +``` + +Cada campo mata un fallo concreto: + +- El **alcance mínimo** mata la deriva: no hay cadena larga donde anclarse a sus propias conjeturas, y los demás proyectos no están en contexto, así que el agente *no puede* inventarse cruces entre ellos. +- El **contrato refutable** mata la "prosa que no obliga" y el "digo que está hecho sin pruebas". Si el resultado no lo puede comprobar una máquina, la unidad está mal dimensionada — demasiado abierta. +- El **testigo** mata el "fíate de mi palabra". *Hecho* lleva la salida real del validador como artefacto, firmada. Es el pariente cercano de la [costura del sello-testigo](/blog/ontoref/your-ontology-should-live-with-your-code): el testigo es el acta de la Orden de Trabajo. +- La **propiedad externa de los términos** mata lo de poner al lobo a cuidar las ovejas — el operario no puede editar el contrato en el mismo gesto con que lo cumple. +- El **veredicto acotado** mata la propagación de código roto — el fallo muere en la unidad en vez de contaminar la sesión. + +Toda la disciplina de conducta se reduce a dos reglas atómicas, cortas y en el sitio donde se usan — las únicas que un agente cumple de hecho: + +1. **Sin alcance y contrato, no se empieza.** +2. **Sin testigo, no está hecho.** + +## Lo que ninguna puerta puede atrapar — y por qué importa más + +Sé honesto con el residuo. Algunos fallos los puede comprobar una máquina: saltarse una guía de estilo, la configuración en opciones sueltas en vez de NCL, un cambio de arquitectura sin ADR, una violación de esquema, "digo que está hecho" sin salida de validador. Esos, un contrato los caza. + +Otros, ningún esquema los cazará jamás: la abstracción sutilmente equivocada, darte la razón cuando debería discutir, un dato inventado al cruzar proyectos. Ese residuo es el fallo más peligroso, y pide otra herramienta — un verificador **adversario** (una segunda unidad cuyo único contrato es *refutar el entregable de la primera*), no un esquema. La diversidad sustituye al esquema cuando no puede haber esquema. + +Por eso los validadores de ontoref están diseñados para atestiguar **la estructura, no la veracidad** ([ADR-050](/adr/adr-050-witnessed-validators-structural-not-truthfulness)), y por eso el protocolo se compromete con **verificación suficiente antes que conocimiento completo** ([ADR-056](/adr/adr-056-sufficient-verification-over-complete-knowledge)). Una máquina que finge certificar la verdad miente; una máquina que certifica *que una comprobación se ejecutó y qué devolvió* te dice exactamente lo que sabe. La misma disciplina recorre el ciclo de memoria, que **propone y nunca aplica** ([ADR-052](/adr/adr-052-memory-feedback-loop-propose-not-apply)): el acto autoritativo sigue siendo humano. + +## La demostración, sobre el fallo mismo + +Hicimos que la primera Orden de Trabajo fuera la invariante que el agente rompió. El contrato: una comprobación que falla si algún ajuste de ejecución solo se alcanza por una opción de línea de comandos y no está en el esquema NCL. Luego lo atacamos a propósito — tres ejecuciones: + +| ejecución | qué demuestra | veredicto | exit | +|---|---|---|---| +| **A** — código real vs config NCL inexistente | dientes sobre la violación real que originó todo | Rechazado · 12/12 | 1 | +| **B** — configuración limpia (declara los 12) | no es un "siempre falla": *puede* pasar | Aceptado · 0/12 | 0 | +| **C** — opción intrusa `PASOS_FANTASMA` | discrimina: caza solo a la intrusa | Rechazado · 1/13 | 1 | + +El acta — un testigo JSONL que lleva la salida real de la comprobación, no una frase — es la prueba. La invariante *config NCL como fuente de verdad* dejó de ser prosa. Ahora `exit ≠ 0` la hace cumplir, y eso funciona igual derive el agente o no. + + + + + + Los límites honestos — lo que un contrato puede y no puede cazar + + +## Los límites honestos + +Esto no se vende como acabado: + +- **Son dientes, pero sin mandíbula todavía.** Hoy la comprobación es un script que alguien corre. Para ser una puerta de verdad tiene que sentarse en una costura que el agente no pueda esquivar — pre-commit, integración continua sobre una rama protegida. Hasta entonces sigue dependiendo de que alguien la invoque. +- **El testigo firmado es lo siguiente.** La primera ejecución usó `firmado = false`. La unidad de dominio ejercitará `firmado = true` de punta a punta. +- **Más agentes y validadores cuestan tokens y latencia.** Es real, y es el precio de que el fallo muera en la unidad en vez de en producción. + +Lo que cambió es la postura. A un agente probabilístico y deseoso de agradar no lo haces cumplir ni confiando en él ni vigilándolo a mano — las dos cosas te devuelven al desgaste que paró el proyecto. Llegas tratándolo como un proponente prolífico dentro de un arnés de verificación donde lo no verificado y lo que viola una regla no pueden aterrizar. + +Para esto nació ontoref. El hueco no era que faltara el sistema — era que el sistema *describía* en vez de *poner puertas*. Una salvaguarda que no bloquea es, para un agente así, decoración. + +La confianza es lo que demuestra ese experimento. Nunca lo que promete su retórica. diff --git a/site/site/content/blog/es/ontoref/trust-is-an-output-not-an-input.ncl b/site/site/content/blog/es/ontoref/trust-is-an-output-not-an-input.ncl new file mode 100644 index 0000000..415d13c --- /dev/null +++ b/site/site/content/blog/es/ontoref/trust-is-an-output-not-an-input.ncl @@ -0,0 +1,25 @@ +# Generated from trust-is-an-output-not-an-input.md +# Schema: ../../../../../../../../../../jesusperezlorenzo/.local/share/rustelo/nickel/content/metadata/post_metadata.ncl +# Regenerate: nu scripts/content/md-to-ncl.nu --force + +let schema = import "../../../../../../../../../../jesusperezlorenzo/.local/share/rustelo/nickel/content/metadata/post_metadata.ncl" in + +schema.make_post { + id = "trust-is-an-output-not-an-input", + title = "La confianza se demuestra, no se promete", + slug = "la-confianza-se-demuestra-no-se-promete", + subtitle = "Qué hace falta para gobernar a un agente de IA que no puede comprometerse a cumplir tus reglas — y por qué la solución es un contrato, no un mejor prompt", + excerpt = "Un agente se saltó la única invariante que habría detectado el fallo en treinta segundos. El diagnóstico honesto no fue 'se le olvidó' — fue que la regla era prosa, y la prosa no obliga a nada. Esta es la historia de convertir ese fallo en un mecanismo refutable: un Pliego (los términos que posees tú) y una Orden de Trabajo (la ejecución que no puede editar), donde 'hecho' lleva la salida del validador en vez de la palabra del agente.", + date = "2026-06-23", + featured = true, + category = "ontoref", + tags = ["ontoref", "agentes-ia", "gobernanza", "verificacion", "testigo", "orden-de-trabajo"], + read_time = "7 min de lectura", + sort_order = 5, + image_url = "/images/ontoref-trust-output-post.webp", + thumbnail = "/images/ontoref-trust-output-post.webp", + graph = { + implements = ["witness-as-axis-seam", "openness-vs-sustainability", "adr-050", "adr-052", "adr-056"], + related_to = ["your-ontology-should-live-with-your-code", "ai-knowledge-tool-who-keeps-it-alive", "one-protocol-multiple-subjects"], + }, +} diff --git a/site/site/content/blog/es/ontoref/your-ontology-should-live-with-your-code.md b/site/site/content/blog/es/ontoref/your-ontology-should-live-with-your-code.md new file mode 100644 index 0000000..4eb2e8e --- /dev/null +++ b/site/site/content/blog/es/ontoref/your-ontology-should-live-with-your-code.md @@ -0,0 +1,116 @@ +--- +# Post metadata +id: "your-ontology-should-live-with-your-code" +title: "Tu ontología debería vivir con tu código" +slug: "tu-ontologia-deberia-vivir-con-tu-codigo" +subtitle: "Soberanía, federación, y por qué la infraestructura de conocimiento sin hogar no es infraestructura" +excerpt: "Los knowledge graphs enterprise viven en un triplestore mantenido por un equipo dedicado en la plataforma de un vendor. Cuando el equipo cambia, el presupuesto se recorta, o el vendor pivota, el conocimiento desaparece. Ontoref toma el enfoque opuesto: conocimiento soberano y local-first que vive junto a su sujeto, versionado, consultable a través de cuatro superficies, y federado sin un broker central." + +# Publication info +author: "Jesús Pérez" +date: "2026-05-10" +published: true +featured: false + +# Categorization +category: "ontoref" +tags: ["ontoref", "soberania", "federacion", "radicle", "jujutsu", "infraestructura-conocimiento"] + +# Image (card thumbnail + in-post featured) +thumbnail: "/images/ontology-live-with-code-post.webp" +image_url: "/images/ontology-live-with-code-post.webp" + +# Display +read_time: "6 min read" +sort_order: 4 +css_class: "category-ontoref" +category_description: "Ontoref — protocolo y herramientas para el auto-conocimiento estructurado en proyectos de software" +category_published: true +--- + + + + + Tu ontología debería vivir con tu código + + +# Tu ontología debería vivir con tu código + +Jessica Talisman, en su charla de KGC 2026, dice algo que merece destacarse: *"Tu ontología es tu moat — tu IP."* + +Tiene razón. Y está describiendo el modelo enterprise de knowledge graphs, donde el moat está retenido en el triplestore de un vendor, mantenido por un equipo dedicado, en una plataforma con suscripción. Cuando el equipo cambia, el presupuesto se recorta, o el vendor pivota, el moat se drena. + +Ontoref toma el enfoque opuesto. + +## Qué significa conocimiento soberano + +En ontoref, el conocimiento de qué es un proyecto — sus principios, prácticas, tensiones, decisiones arquitectónicas, estado operacional — vive como archivos NCL junto al código. En el repositorio. Versionado con la misma disciplina que el source. + +Esto no es un enfoque de documentación. Es una decisión de almacenamiento con consecuencias arquitectónicas: + +- **Ningún vendor puede quitártelo.** La ontología está en tu repo, no en su base de datos. +- **Sin migración de plataforma.** GitHub, Gitea, Radicle — el conocimiento se mueve con el código. +- **Sin infraestructura dedicada.** El daemon corre localmente; los archivos son la fuente de verdad. +- **Offline por defecto.** El conocimiento que requiere conexión de red para existir no es infraestructura — es un servicio. + +La analogía más cercana es SQLite vs. PostgreSQL: local-first, embebido, siempre disponible, sin conexión requerida. Cuando necesitas distribución, la añades. Pero el baseline es soberanía. + +## Cuatro superficies, una fuente + +Almacenamiento soberano no significa aislado. Ontoref expone el mismo conocimiento ontológico a través de cuatro superficies simultáneamente: + +**CLI (Nushell):** La interfaz nativa del developer. `ontoref describe project`, `ontoref graph ontology`, `ontoref sync diff --docs`. Rápido, scriptable, composable en CI. La superficie para humanos que viven en la terminal y para checks automatizados que corren en pipelines. + +**UI (axum):** Una interfaz web para inspección visual, incorporación y gestión. No un dashboard sobre una API remota — un servidor local que renderiza el mismo conocimiento respaldado por NCL. La superficie para project managers, nuevos contribuidores, o cualquiera que prefiera navegar un grafo visualmente. + +**MCP:** La superficie de agentes. El endpoint MCP de ontoref es la capa semántica que se sienta encima del protocolo de transporte. Como identifica correctamente el framework de Talisman, MCP mueve bytes — no establece significado compartido. Ontoref provee ese significado a través de su capa ontológica, expuesta vía MCP a agentes que necesitan conocimiento estructurado del proyecto para trabajar con precisión en lugar de alucinar contexto. + +**GraphQL:** La superficie de integración. SPARQL es el estándar del semantic web, pero GraphQL es lo que la mayoría de developers conocen y cada ecosistema de herramientas soporta. Una API GraphQL sobre el DAG ontológico elimina la barrera de expertise sin sacrificar estructura. + +Cuatro superficies, una fuente canónica: los archivos NCL en el repositorio. + +## jj y Radicle + +El modelo de almacenamiento se combina naturalmente con una clase específica de herramientas VCS. + +**Jujutsu (jj):** Un VCS compatible con Git donde el working copy es siempre un commit — no hay dirty state. Esto se alinea directamente con el enfoque de ontoref de captura continua de estado: cada estado del proyecto es capturable, cada transición es un evento de primera clase. El operation log de jj es el equivalente VCS del historial de sesiones de reflexión de ontoref. + +**Radicle:** Colaboración de código P2P, local-first, soberana. Si el código es soberano, el conocimiento sobre ese código también debería serlo. Radicle como transporte significa que la federación de proyectos con ontoref no depende de ninguna plataforma centralizada — sin GitHub, sin GitLab, sin Gitea hosteado. Los nodos se descubren entre sí e intercambian conocimiento directamente. + +Juntos: código soberano y conocimiento soberano, distribuidos sin un broker central. + +## Federación sin broker central + +El modelo enterprise para federación de conocimiento es un knowledge graph central al que todos los proyectos apuntan. Un equipo lo mantiene. Una plataforma lo aloja. Cada query lo atraviesa. + +El modelo de federación de ontoref es distinto: + +``` +proyecto-A consulta: ¿qué proyectos implementan "zero-trust-auth"? +proyecto-B, proyecto-C responden desde sus propias ontologías +sin servidor central que lo sepa todo +``` + +Esto es posible porque cada proyecto se describe a sí mismo. El DAG ontológico en `.ontology/core.ncl` es consultable sin ninguna dependencia externa. La federación vía NATS conecta los daemons — el daemon de cada proyecto se registra, publica sus capacidades, y responde a queries de otros daemons en la federación. + +El resultado: visibilidad a nivel de ecosistema sin centralización a nivel de ecosistema. Puedes descubrir qué proyectos comparten tus restricciones arquitectónicas, cuáles implementan prácticas compatibles, cuáles están en estados conflictivos — sin que ninguno requiera una plataforma compartida. + +## Por qué esta arquitectura ahora + +Dos cambios hicieron esto viable: + +**La IA agéntica como consumidor.** Antes de los agentes, la infraestructura de conocimiento se construía para consumo humano — ontólogos manteniendo grafos para que los analistas los consultaran. Los agentes son diferentes: necesitan conocimiento machine-readable, estructurado y tipado para operar con precisión. El modelo multi-superficie de ontoref está diseñado para un mundo donde el consumidor principal del conocimiento del proyecto no es un humano leyendo un wiki sino un agente tomando decisiones en una ventana de contexto. + +**Local-first como el default.** La tendencia de una década hacia todo en la nube está revirtiendo para la infraestructura de conocimiento. Los riesgos son claros: vendor lock-in, discontinuidad de plataforma, pérdida de soberanía, y el coste compuesto de dependencias externas para lo que debería ser conocimiento interno. Local-first con federación opcional es la arquitectura que sobrevive a los cambios de plataforma. + +## El argumento del moat + +El framing de Talisman — "tu ontología es tu moat" — es más acertado de lo que ella puede pretender. Un moat funciona porque está unido a lo que protege. Un moat almacenado en la base de datos de otra persona no es un moat. Es un acuerdo de servicio. + +Cuando el conocimiento de qué es tu proyecto, por qué existe como existe, y qué decisiones tienen consecuencias duraderas vive junto al código que implementa esas decisiones — versionado, consultable, soberano, federado — es genuinamente tuyo. Se acumula. Sobrevive a cambios de equipo. Informa a agentes. Puede ser consultado por proyectos que dependen de ti. + +Ese es el tipo de moat que vale la pena construir. + +--- + +*Ontoref es open source. La especificación del protocolo, la automatización en Nushell, y los crates de Rust están en [github.com/jesusperezlorenzo/ontoref](https://github.com/jesusperezlorenzo/ontoref).* diff --git a/site/site/content/blog/es/ontoref/your-ontology-should-live-with-your-code.ncl b/site/site/content/blog/es/ontoref/your-ontology-should-live-with-your-code.ncl new file mode 100644 index 0000000..5d69830 --- /dev/null +++ b/site/site/content/blog/es/ontoref/your-ontology-should-live-with-your-code.ncl @@ -0,0 +1,24 @@ +# Generated from your-ontology-should-live-with-your-code.md +# Schema: ../../../../../../../../../../jesusperezlorenzo/.local/share/rustelo/nickel/content/metadata/post_metadata.ncl +# Regenerate: nu scripts/content/md-to-ncl.nu --force + +let schema = import "../../../../../../../../../../jesusperezlorenzo/.local/share/rustelo/nickel/content/metadata/post_metadata.ncl" in + +schema.make_post { + id = "your-ontology-should-live-with-your-code", + title = "Tu ontología debería vivir con tu código", + slug = "tu-ontologia-deberia-vivir-con-tu-codigo", + subtitle = "Soberanía, federación, y por qué la infraestructura de conocimiento sin hogar no es infraestructura", + excerpt = "Los knowledge graphs enterprise viven en un triplestore mantenido por un equipo dedicado en la plataforma de un vendor. Cuando el equipo cambia, el presupuesto se recorta, o el vendor pivota, el conocimiento desaparece. Ontoref toma el enfoque opuesto: conocimiento soberano y local-first que vive junto a su sujeto, versionado, consultable a través de cuatro superficies, y federado sin un broker central.", + date = "2026-05-10", + category = "ontoref", + tags = ["ontoref", "soberania", "federacion", "radicle", "jujutsu", "infraestructura-conocimiento"], + read_time = "6 min read", + sort_order = 4, + image_url = "/images/ontology-live-with-code-post.webp", + thumbnail = "/images/ontology-live-with-code-post.webp", + graph = { + implements = ["vcs-abstraction", "witness-as-axis-seam", "tier-coexistence-permanent-design", "adr-028", "adr-029", "protocol-not-runtime"], + related_to = ["one-protocol-multiple-subjects", "dags-everywhere-none-know-what-they-are", "ai-knowledge-tool-who-keeps-it-alive"], + }, +} \ No newline at end of file diff --git a/site/site/content/catalog/en/operations/chronicle_query.md b/site/site/content/catalog/en/operations/chronicle_query.md new file mode 100644 index 0000000..0d743a8 --- /dev/null +++ b/site/site/content/catalog/en/operations/chronicle_query.md @@ -0,0 +1,17 @@ +--- +id: "chronicle_query" +title: "chronicle_query" +slug: "chronicle_query" +subtitle: "operations" +excerpt: "Read-only chronicle query: returns Accepted ADRs and protocol migrations as ChronicleEntry records. No state mutation, no witness commit." +author: "ontoref" +published: true +featured: false +category: "operations" +tags: ["chronicle_query", "catalog", "operations"] +css_class: "category-catalog" +--- + +

    Description

    + +

    Read-only chronicle query: returns Accepted ADRs and protocol migrations as ChronicleEntry records. No state mutation, no witness commit.

    diff --git a/site/site/content/catalog/en/operations/chronicle_query.ncl b/site/site/content/catalog/en/operations/chronicle_query.ncl new file mode 100644 index 0000000..77c7896 --- /dev/null +++ b/site/site/content/catalog/en/operations/chronicle_query.ncl @@ -0,0 +1,12 @@ +{ + id = "chronicle_query", + title = "chronicle_query", + slug = "chronicle_query", + excerpt = "", + tags = ["catalog", "operation"], + page_route = "/catalog/chronicle_query", + graph = { + implements = [], + related_to = [], + }, +} diff --git a/site/site/content/catalog/en/operations/close_dev_session.md b/site/site/content/catalog/en/operations/close_dev_session.md new file mode 100644 index 0000000..e0870b5 --- /dev/null +++ b/site/site/content/catalog/en/operations/close_dev_session.md @@ -0,0 +1,17 @@ +--- +id: "close_dev_session" +title: "close_dev_session" +slug: "close_dev_session" +subtitle: "operations" +excerpt: "Close an open dev session. Mutates status=Closed and records observed drift." +author: "ontoref" +published: true +featured: false +category: "operations" +tags: ["close_dev_session", "catalog", "operations"] +css_class: "category-catalog" +--- + +

    Description

    + +

    Close an open dev session. Mutates status=Closed and records observed drift.

    diff --git a/site/site/content/catalog/en/operations/close_dev_session.ncl b/site/site/content/catalog/en/operations/close_dev_session.ncl new file mode 100644 index 0000000..f1cb530 --- /dev/null +++ b/site/site/content/catalog/en/operations/close_dev_session.ncl @@ -0,0 +1,12 @@ +{ + id = "close_dev_session", + title = "close_dev_session", + slug = "close_dev_session", + excerpt = "", + tags = ["catalog", "operation"], + page_route = "/catalog/close_dev_session", + graph = { + implements = [], + related_to = [], + }, +} diff --git a/site/site/content/catalog/en/operations/criteria_query.md b/site/site/content/catalog/en/operations/criteria_query.md new file mode 100644 index 0000000..96fdb90 --- /dev/null +++ b/site/site/content/catalog/en/operations/criteria_query.md @@ -0,0 +1,17 @@ +--- +id: "criteria_query" +title: "criteria_query" +slug: "criteria_query" +subtitle: "operations" +excerpt: "Read-only criteria query: returns catalog validator declarations as Criterion records (ADR-049/050). No state mutation." +author: "ontoref" +published: true +featured: false +category: "operations" +tags: ["criteria_query", "catalog", "operations"] +css_class: "category-catalog" +--- + +

    Description

    + +

    Read-only criteria query: returns catalog validator declarations as Criterion records (ADR-049/050). No state mutation.

    diff --git a/site/site/content/catalog/en/operations/criteria_query.ncl b/site/site/content/catalog/en/operations/criteria_query.ncl new file mode 100644 index 0000000..9ffbd6a --- /dev/null +++ b/site/site/content/catalog/en/operations/criteria_query.ncl @@ -0,0 +1,12 @@ +{ + id = "criteria_query", + title = "criteria_query", + slug = "criteria_query", + excerpt = "", + tags = ["catalog", "operation"], + page_route = "/catalog/criteria_query", + graph = { + implements = [], + related_to = [], + }, +} diff --git a/site/site/content/catalog/en/operations/evaluate_ondaod.md b/site/site/content/catalog/en/operations/evaluate_ondaod.md new file mode 100644 index 0000000..dc050da --- /dev/null +++ b/site/site/content/catalog/en/operations/evaluate_ondaod.md @@ -0,0 +1,17 @@ +--- +id: "evaluate_ondaod" +title: "evaluate_ondaod" +slug: "evaluate_ondaod" +subtitle: "operations" +excerpt: "Record an ondaod (Dao Discipline) evaluation as a meta-decision entity. No state mutation, no render." +author: "ontoref" +published: true +featured: false +category: "operations" +tags: ["evaluate_ondaod", "catalog", "operations"] +css_class: "category-catalog" +--- + +

    Description

    + +

    Record an ondaod (Dao Discipline) evaluation as a meta-decision entity. No state mutation, no render.

    diff --git a/site/site/content/catalog/en/operations/evaluate_ondaod.ncl b/site/site/content/catalog/en/operations/evaluate_ondaod.ncl new file mode 100644 index 0000000..4756636 --- /dev/null +++ b/site/site/content/catalog/en/operations/evaluate_ondaod.ncl @@ -0,0 +1,12 @@ +{ + id = "evaluate_ondaod", + title = "evaluate_ondaod", + slug = "evaluate_ondaod", + excerpt = "", + tags = ["catalog", "operation"], + page_route = "/catalog/evaluate_ondaod", + graph = { + implements = [], + related_to = [], + }, +} diff --git a/site/site/content/catalog/en/operations/manage_backlog_item.md b/site/site/content/catalog/en/operations/manage_backlog_item.md new file mode 100644 index 0000000..fe72bb3 --- /dev/null +++ b/site/site/content/catalog/en/operations/manage_backlog_item.md @@ -0,0 +1,17 @@ +--- +id: "manage_backlog_item" +title: "manage_backlog_item" +slug: "manage_backlog_item" +subtitle: "operations" +excerpt: "Add or update one backlog entry in reflection/backlog.ncl. Add asserts a fresh entry; update mutates an existing one in place." +author: "ontoref" +published: true +featured: false +category: "operations" +tags: ["manage_backlog_item", "catalog", "operations"] +css_class: "category-catalog" +--- + +

    Description

    + +

    Add or update one backlog entry in reflection/backlog.ncl. Add asserts a fresh entry; update mutates an existing one in place.

    diff --git a/site/site/content/catalog/en/operations/manage_backlog_item.ncl b/site/site/content/catalog/en/operations/manage_backlog_item.ncl new file mode 100644 index 0000000..5455318 --- /dev/null +++ b/site/site/content/catalog/en/operations/manage_backlog_item.ncl @@ -0,0 +1,12 @@ +{ + id = "manage_backlog_item", + title = "manage_backlog_item", + slug = "manage_backlog_item", + excerpt = "", + tags = ["catalog", "operation"], + page_route = "/catalog/manage_backlog_item", + graph = { + implements = [], + related_to = ["backlog_item_well_formed"], + }, +} diff --git a/site/site/content/catalog/en/operations/move_fsm_state.md b/site/site/content/catalog/en/operations/move_fsm_state.md new file mode 100644 index 0000000..55d7d95 --- /dev/null +++ b/site/site/content/catalog/en/operations/move_fsm_state.md @@ -0,0 +1,17 @@ +--- +id: "move_fsm_state" +title: "move_fsm_state" +slug: "move_fsm_state" +subtitle: "operations" +excerpt: "Transition an FSM dimension's current_state. Pre-validates the transition exists and post-validates FSM invariants." +author: "ontoref" +published: true +featured: false +category: "operations" +tags: ["move_fsm_state", "catalog", "operations"] +css_class: "category-catalog" +--- + +

    Description

    + +

    Transition an FSM dimension's current_state. Pre-validates the transition exists and post-validates FSM invariants.

    diff --git a/site/site/content/catalog/en/operations/move_fsm_state.ncl b/site/site/content/catalog/en/operations/move_fsm_state.ncl new file mode 100644 index 0000000..278231b --- /dev/null +++ b/site/site/content/catalog/en/operations/move_fsm_state.ncl @@ -0,0 +1,12 @@ +{ + id = "move_fsm_state", + title = "move_fsm_state", + slug = "move_fsm_state", + excerpt = "", + tags = ["catalog", "operation"], + page_route = "/catalog/move_fsm_state", + graph = { + implements = [], + related_to = ["fsm_transition_allowed", "fsm_invariants_preserved"], + }, +} diff --git a/site/site/content/catalog/en/operations/panels_query.md b/site/site/content/catalog/en/operations/panels_query.md new file mode 100644 index 0000000..67c38b8 --- /dev/null +++ b/site/site/content/catalog/en/operations/panels_query.md @@ -0,0 +1,17 @@ +--- +id: "panels_query" +title: "panels_query" +slug: "panels_query" +subtitle: "operations" +excerpt: "Read-only panels query: returns resolved panel specs from the presentation directory as PanelSpec records (ADR-053). No state mutation." +author: "ontoref" +published: true +featured: false +category: "operations" +tags: ["panels_query", "catalog", "operations"] +css_class: "category-catalog" +--- + +

    Description

    + +

    Read-only panels query: returns resolved panel specs from the presentation directory as PanelSpec records (ADR-053). No state mutation.

    diff --git a/site/site/content/catalog/en/operations/panels_query.ncl b/site/site/content/catalog/en/operations/panels_query.ncl new file mode 100644 index 0000000..6b294e0 --- /dev/null +++ b/site/site/content/catalog/en/operations/panels_query.ncl @@ -0,0 +1,12 @@ +{ + id = "panels_query", + title = "panels_query", + slug = "panels_query", + excerpt = "", + tags = ["catalog", "operation"], + page_route = "/catalog/panels_query", + graph = { + implements = [], + related_to = [], + }, +} diff --git a/site/site/content/catalog/en/operations/propose_adr.md b/site/site/content/catalog/en/operations/propose_adr.md new file mode 100644 index 0000000..e6c0668 --- /dev/null +++ b/site/site/content/catalog/en/operations/propose_adr.md @@ -0,0 +1,17 @@ +--- +id: "propose_adr" +title: "propose_adr" +slug: "propose_adr" +subtitle: "operations" +excerpt: "Declare a new ADR in Proposed status. Requires ondaod evaluation as precondition (ADR-024 / dao-discipline)." +author: "ontoref" +published: true +featured: false +category: "operations" +tags: ["propose_adr", "catalog", "operations"] +css_class: "category-catalog" +--- + +

    Description

    + +

    Declare a new ADR in Proposed status. Requires ondaod evaluation as precondition (ADR-024 / dao-discipline).

    diff --git a/site/site/content/catalog/en/operations/propose_adr.ncl b/site/site/content/catalog/en/operations/propose_adr.ncl new file mode 100644 index 0000000..98504b3 --- /dev/null +++ b/site/site/content/catalog/en/operations/propose_adr.ncl @@ -0,0 +1,12 @@ +{ + id = "propose_adr", + title = "propose_adr", + slug = "propose_adr", + excerpt = "", + tags = ["catalog", "operation"], + page_route = "/catalog/propose_adr", + graph = { + implements = [], + related_to = ["ondaod_engagement_declared"], + }, +} diff --git a/site/site/content/catalog/en/operations/roadmap_query.md b/site/site/content/catalog/en/operations/roadmap_query.md new file mode 100644 index 0000000..e7743b6 --- /dev/null +++ b/site/site/content/catalog/en/operations/roadmap_query.md @@ -0,0 +1,17 @@ +--- +id: "roadmap_query" +title: "roadmap_query" +slug: "roadmap_query" +subtitle: "operations" +excerpt: "Read-only roadmap query: returns backlog items as RoadmapItem records. No state mutation." +author: "ontoref" +published: true +featured: false +category: "operations" +tags: ["roadmap_query", "catalog", "operations"] +css_class: "category-catalog" +--- + +

    Description

    + +

    Read-only roadmap query: returns backlog items as RoadmapItem records. No state mutation.

    diff --git a/site/site/content/catalog/en/operations/roadmap_query.ncl b/site/site/content/catalog/en/operations/roadmap_query.ncl new file mode 100644 index 0000000..d69415f --- /dev/null +++ b/site/site/content/catalog/en/operations/roadmap_query.ncl @@ -0,0 +1,12 @@ +{ + id = "roadmap_query", + title = "roadmap_query", + slug = "roadmap_query", + excerpt = "", + tags = ["catalog", "operation"], + page_route = "/catalog/roadmap_query", + graph = { + implements = [], + related_to = [], + }, +} diff --git a/site/site/content/catalog/en/operations/start_dev_session.md b/site/site/content/catalog/en/operations/start_dev_session.md new file mode 100644 index 0000000..86a3c6c --- /dev/null +++ b/site/site/content/catalog/en/operations/start_dev_session.md @@ -0,0 +1,17 @@ +--- +id: "start_dev_session" +title: "start_dev_session" +slug: "start_dev_session" +subtitle: "operations" +excerpt: "Open a code dev session with declared scope and target paths. Records session id, scope, and applicable ADRs." +author: "ontoref" +published: true +featured: false +category: "operations" +tags: ["start_dev_session", "catalog", "operations"] +css_class: "category-catalog" +--- + +

    Description

    + +

    Open a code dev session with declared scope and target paths. Records session id, scope, and applicable ADRs.

    diff --git a/site/site/content/catalog/en/operations/start_dev_session.ncl b/site/site/content/catalog/en/operations/start_dev_session.ncl new file mode 100644 index 0000000..f55e64b --- /dev/null +++ b/site/site/content/catalog/en/operations/start_dev_session.ncl @@ -0,0 +1,12 @@ +{ + id = "start_dev_session", + title = "start_dev_session", + slug = "start_dev_session", + excerpt = "", + tags = ["catalog", "operation"], + page_route = "/catalog/start_dev_session", + graph = { + implements = [], + related_to = [], + }, +} diff --git a/site/site/content/catalog/en/operations/sync_apply.md b/site/site/content/catalog/en/operations/sync_apply.md new file mode 100644 index 0000000..cf9f300 --- /dev/null +++ b/site/site/content/catalog/en/operations/sync_apply.md @@ -0,0 +1,17 @@ +--- +id: "sync_apply" +title: "sync_apply" +slug: "sync_apply" +subtitle: "operations" +excerpt: "Apply a batch of reconciled diffs as a single sync event. Phase 1 records the batch attempt; recursive dispatch arrives later." +author: "ontoref" +published: true +featured: false +category: "operations" +tags: ["sync_apply", "catalog", "operations"] +css_class: "category-catalog" +--- + +

    Description

    + +

    Apply a batch of reconciled diffs as a single sync event. Phase 1 records the batch attempt; recursive dispatch arrives later.

    diff --git a/site/site/content/catalog/en/operations/sync_apply.ncl b/site/site/content/catalog/en/operations/sync_apply.ncl new file mode 100644 index 0000000..7200ddb --- /dev/null +++ b/site/site/content/catalog/en/operations/sync_apply.ncl @@ -0,0 +1,12 @@ +{ + id = "sync_apply", + title = "sync_apply", + slug = "sync_apply", + excerpt = "", + tags = ["catalog", "operation"], + page_route = "/catalog/sync_apply", + graph = { + implements = [], + related_to = [], + }, +} diff --git a/site/site/content/catalog/en/operations/transition_adr.md b/site/site/content/catalog/en/operations/transition_adr.md new file mode 100644 index 0000000..5261784 --- /dev/null +++ b/site/site/content/catalog/en/operations/transition_adr.md @@ -0,0 +1,17 @@ +--- +id: "transition_adr" +title: "transition_adr" +slug: "transition_adr" +subtitle: "operations" +excerpt: "Move an ADR from Proposed to Accepted/Deprecated, or from Accepted to Superseded. Accept requires ondaod." +author: "ontoref" +published: true +featured: false +category: "operations" +tags: ["transition_adr", "catalog", "operations"] +css_class: "category-catalog" +--- + +

    Description

    + +

    Move an ADR from Proposed to Accepted/Deprecated, or from Accepted to Superseded. Accept requires ondaod.

    diff --git a/site/site/content/catalog/en/operations/transition_adr.ncl b/site/site/content/catalog/en/operations/transition_adr.ncl new file mode 100644 index 0000000..9a8ac1f --- /dev/null +++ b/site/site/content/catalog/en/operations/transition_adr.ncl @@ -0,0 +1,12 @@ +{ + id = "transition_adr", + title = "transition_adr", + slug = "transition_adr", + excerpt = "", + tags = ["catalog", "operation"], + page_route = "/catalog/transition_adr", + graph = { + implements = [], + related_to = ["adr_transition_allowed"], + }, +} diff --git a/site/site/content/catalog/en/operations/transition_tier.md b/site/site/content/catalog/en/operations/transition_tier.md new file mode 100644 index 0000000..87759af --- /dev/null +++ b/site/site/content/catalog/en/operations/transition_tier.md @@ -0,0 +1,17 @@ +--- +id: "transition_tier" +title: "transition_tier" +slug: "transition_tier" +subtitle: "operations" +excerpt: "Mutate the project's `ops.tier` declaration. Enforces ADR-029 / ADR-033 guards: refuses when migrations are pending, atomically rewrites .ontoref/config.ncl, emits a tier-transition witness covering th" +author: "ontoref" +published: true +featured: false +category: "operations" +tags: ["transition_tier", "catalog", "operations"] +css_class: "category-catalog" +--- + +

    Description

    + +

    Mutate the project's `ops.tier` declaration. Enforces ADR-029 / ADR-033 guards: refuses when migrations are pending, atomically rewrites .ontoref/config.ncl, emits a tier-transition witness covering the change.

    diff --git a/site/site/content/catalog/en/operations/transition_tier.ncl b/site/site/content/catalog/en/operations/transition_tier.ncl new file mode 100644 index 0000000..9b9d594 --- /dev/null +++ b/site/site/content/catalog/en/operations/transition_tier.ncl @@ -0,0 +1,12 @@ +{ + id = "transition_tier", + title = "transition_tier", + slug = "transition_tier", + excerpt = "", + tags = ["catalog", "operation"], + page_route = "/catalog/transition_tier", + graph = { + implements = [], + related_to = ["tier_transition_guard"], + }, +} diff --git a/site/site/content/catalog/en/operations/update_ontology_edge.md b/site/site/content/catalog/en/operations/update_ontology_edge.md new file mode 100644 index 0000000..57918f3 --- /dev/null +++ b/site/site/content/catalog/en/operations/update_ontology_edge.md @@ -0,0 +1,17 @@ +--- +id: "update_ontology_edge" +title: "update_ontology_edge" +slug: "update_ontology_edge" +subtitle: "operations" +excerpt: "Insert one edge into core.ncl edges array. Pre-validator rejects direct self-loops and 1-step cycles." +author: "ontoref" +published: true +featured: false +category: "operations" +tags: ["update_ontology_edge", "catalog", "operations"] +css_class: "category-catalog" +--- + +

    Description

    + +

    Insert one edge into core.ncl edges array. Pre-validator rejects direct self-loops and 1-step cycles.

    diff --git a/site/site/content/catalog/en/operations/update_ontology_edge.ncl b/site/site/content/catalog/en/operations/update_ontology_edge.ncl new file mode 100644 index 0000000..432b51b --- /dev/null +++ b/site/site/content/catalog/en/operations/update_ontology_edge.ncl @@ -0,0 +1,12 @@ +{ + id = "update_ontology_edge", + title = "update_ontology_edge", + slug = "update_ontology_edge", + excerpt = "", + tags = ["catalog", "operation"], + page_route = "/catalog/update_ontology_edge", + graph = { + implements = [], + related_to = ["ontology_edge_no_cycle"], + }, +} diff --git a/site/site/content/catalog/en/operations/update_ontology_node.md b/site/site/content/catalog/en/operations/update_ontology_node.md new file mode 100644 index 0000000..2e3c99e --- /dev/null +++ b/site/site/content/catalog/en/operations/update_ontology_node.md @@ -0,0 +1,17 @@ +--- +id: "update_ontology_node" +title: "update_ontology_node" +slug: "update_ontology_node" +subtitle: "operations" +excerpt: "Insert or update one node in .ontology/core.ncl. Mutates ctx.core in place and writes the file back via Phase 1 inline render." +author: "ontoref" +published: true +featured: false +category: "operations" +tags: ["update_ontology_node", "catalog", "operations"] +css_class: "category-catalog" +--- + +

    Description

    + +

    Insert or update one node in .ontology/core.ncl. Mutates ctx.core in place and writes the file back via Phase 1 inline render.

    diff --git a/site/site/content/catalog/en/operations/update_ontology_node.ncl b/site/site/content/catalog/en/operations/update_ontology_node.ncl new file mode 100644 index 0000000..988756c --- /dev/null +++ b/site/site/content/catalog/en/operations/update_ontology_node.ncl @@ -0,0 +1,12 @@ +{ + id = "update_ontology_node", + title = "update_ontology_node", + slug = "update_ontology_node", + excerpt = "", + tags = ["catalog", "operation"], + page_route = "/catalog/update_ontology_node", + graph = { + implements = [], + related_to = ["ontology_node_well_formed"], + }, +} diff --git a/site/site/content/catalog/en/validators/adr-accepted-has-proof.md b/site/site/content/catalog/en/validators/adr-accepted-has-proof.md new file mode 100644 index 0000000..f5c87f6 --- /dev/null +++ b/site/site/content/catalog/en/validators/adr-accepted-has-proof.md @@ -0,0 +1,17 @@ +--- +id: "adr-accepted-has-proof" +title: "adr-accepted-has-proof" +slug: "adr-accepted-has-proof" +subtitle: "validators" +excerpt: "Every Accepted ADR is cited by at least one positioning Proof." +author: "ontoref" +published: true +featured: false +category: "validators" +tags: ["adr-accepted-has-proof", "catalog", "validators"] +css_class: "category-catalog" +--- + +

    Description

    + +

    Every Accepted ADR is cited by at least one positioning Proof.

    diff --git a/site/site/content/catalog/en/validators/adr-accepted-has-proof.ncl b/site/site/content/catalog/en/validators/adr-accepted-has-proof.ncl new file mode 100644 index 0000000..c63d305 --- /dev/null +++ b/site/site/content/catalog/en/validators/adr-accepted-has-proof.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-accepted-has-proof", + title = "adr-accepted-has-proof", + slug = "adr-accepted-has-proof", + excerpt = "", + tags = ["catalog", "validator"], + page_route = "/catalog/adr-accepted-has-proof", + graph = { + implements = [], + related_to = [], + }, +} diff --git a/site/site/content/catalog/en/validators/adr-proposal-requires-description.md b/site/site/content/catalog/en/validators/adr-proposal-requires-description.md new file mode 100644 index 0000000..90d1a01 --- /dev/null +++ b/site/site/content/catalog/en/validators/adr-proposal-requires-description.md @@ -0,0 +1,17 @@ +--- +id: "adr-proposal-requires-description" +title: "adr-proposal-requires-description" +slug: "adr-proposal-requires-description" +subtitle: "validators" +excerpt: "An ADR being proposed must declare a non-empty description field." +author: "ontoref" +published: true +featured: false +category: "validators" +tags: ["adr-proposal-requires-description", "catalog", "validators"] +css_class: "category-catalog" +--- + +

    Description

    + +

    An ADR being proposed must declare a non-empty description field.

    diff --git a/site/site/content/catalog/en/validators/adr-proposal-requires-description.ncl b/site/site/content/catalog/en/validators/adr-proposal-requires-description.ncl new file mode 100644 index 0000000..c86bb26 --- /dev/null +++ b/site/site/content/catalog/en/validators/adr-proposal-requires-description.ncl @@ -0,0 +1,12 @@ +{ + id = "adr-proposal-requires-description", + title = "adr-proposal-requires-description", + slug = "adr-proposal-requires-description", + excerpt = "", + tags = ["catalog", "validator"], + page_route = "/catalog/adr-proposal-requires-description", + graph = { + implements = [], + related_to = [], + }, +} diff --git a/site/site/content/content-kinds.ncl b/site/site/content/content-kinds.ncl new file mode 100644 index 0000000..839e33d --- /dev/null +++ b/site/site/content/content-kinds.ncl @@ -0,0 +1,94 @@ +# Content Types Configuration +# +# Defines different content types and their characteristics. +# Uses Nickel schemas for type safety, validation, and shared defaults. + +let { make_blog_type, make_grid_type, make_tutorial_type, .. } + = import "content/defaults.ncl" in +let contracts = import "content/contracts.ncl" in + +{ + content_kinds = [ + # Blog content type + # Articles, posts, tutorials with featured content support + make_blog_type "blog" "blog" "BlogCTAView", + + # Recipes content type + # Grid layout for visual content with emoji support + make_grid_type "recipes" "recipes" "RecipesCTAView" + & { + features.use_emojis = true, + }, + + # Projects content type + # Grid layout for project showcase with featured support + make_grid_type "projects" "projects" "ProjectsCTAView" + & { + features.use_feature = true, + features.use_tags = true, + }, + + # ontoref domains (ADR-065) — the four-door domain surface (developer, + # infrastructure, personal, authoring), projected from spine.ncl. Kind `domains` + # (URLs /domains EN · /dominios ES); reuses ProjectsCTAView (no new Rust view). + make_grid_type "domains" "domains" "ProjectsCTAView" + & { + features.use_feature = true, + features.use_tags = true, + }, + + # Activities content type + # Grid layout for talks, workshops, and Slidev presentations + make_grid_type "activities" "activities" "ActivitiesCTAView" + & { + features.use_feature = true, + features.use_tags = true, + }, + + # Resources content type — deck/slide library (Ontoref release decks, talk + # slides). Grid layout, reuses ProjectsCTAView so no new Rust page component + # is required; served at /resources (EN) · /recursos (ES). Decks embed in the + # post body (HTML export + PDF fallback), decoupled from the Slidev-only + # `slides_id` viewer. + make_grid_type "resources" "resources" "ProjectsCTAView" + & { + features.use_feature = true, + features.use_tags = true, + }, + + # Expedientes content type — case-file library. Grid layout, reuses + # ProjectsCTAView so no new Rust page component is required; served at + # /case-files (EN) · /expedientes (ES). + make_grid_type "expedientes" "expedientes" "ProjectsCTAView" + & { + features.use_feature = true, + features.use_tags = true, + }, + + # Doors content type (positioning three-door spine, bl-035) + # Posts generated from .ontoref/positioning/spine.ncl by scripts/build/gen-spine-pages.nu. + # Reuses ProjectsCTAView so no new Rust page component is required. + make_grid_type "doors" "doors" "ProjectsCTAView" + & { + features.use_feature = true, + features.use_tags = true, + }, + + # ADR content type (projected from .ontoref/adrs by gen-adr-pages.py --mode content). + # Reuses ProjectsCTAView so no new Rust page component is required; served at + # /adr/{slug}. NOTE: a new kind crosses the ContentKind codegen boundary + # (rustelo adr-001) — activate with a rustelo rebuild/install. + make_grid_type "adr" "adr" "ProjectsCTAView" + & { + features.use_feature = true, + features.use_tags = true, + }, + + # Tutorials content type (specialized, in development) - DISABLED + # Uncomment when specialized content system is ready + # make_tutorial_type "tutorials" "tutorials" "TutorialsCTAView" + # & { + # enabled = false, + # }, + ] +} | contracts.ContentKindsFile diff --git a/site/site/content/content-kinds.toml b/site/site/content/content-kinds.toml new file mode 100644 index 0000000..b2b6e45 --- /dev/null +++ b/site/site/content/content-kinds.toml @@ -0,0 +1,47 @@ +# Runtime content-type registry read by content_processor (rustelo_server). +# Discovered at RUNTIME — adding a type here needs NO recompile (the htmx-ssr +# promise). Mirrors the enabled kinds in site/config/content.ncl. + +[[content_kinds]] +name = "blog" +enabled = true + +[[content_kinds]] +name = "recipes" +enabled = true + +[[content_kinds]] +name = "projects" +enabled = true + +[[content_kinds]] +name = "activities" +enabled = true + +[[content_kinds]] +name = "adr" +enabled = true + +# Resources — deck/slide library (Ontoref release decks, talk slides). Runtime +# SSR kind, reuses ProjectsCTAView; /resources (EN) · /recursos (ES). No recompile. +[[content_kinds]] +name = "resources" +enabled = true + +# Expedientes — case-file library. Runtime SSR kind, reuses ProjectsCTAView; +# /case-files (EN) · /expedientes (ES). No recompile. +[[content_kinds]] +name = "expedientes" +enabled = true + +[[content_kinds]] +name = "catalog" +enabled = true + +# ontoref domains (ADR-065) — the door surface, projected from spine.ncl by +# gen-spine-pages.nu. Kind key `domains`; URLs /domains (EN) · /dominios (ES). +# The routes are BAKED (build-side content.ncl + routes.ncl → just local-install, +# clear cache first); this runtime entry mirrors them for content processing. +[[content_kinds]] +name = "domains" +enabled = true diff --git a/site/site/content/domains/_spine/home-en.md b/site/site/content/domains/_spine/home-en.md new file mode 100644 index 0000000..29ada34 --- /dev/null +++ b/site/site/content/domains/_spine/home-en.md @@ -0,0 +1,15 @@ + +# Sure-footed, and light on your feet. + +> Your project, your infra, your work, your life: still on the path you set? + +## Four doors, one building + +- **[Developer](/domains/developer)** — Ask what breaks before you touch it. +- **[Infrastructure](/domains/infrastructure)** — Inherit the journey, not just the state. +- **[Personal](/domains/personal)** — See where your effort drifts. +- **[Authoring](/domains/authoring)** — Diffuse the whole Work, not loose fragments. + +--- + +_What you put down, anyone can check._ diff --git a/site/site/content/domains/_spine/home-es.md b/site/site/content/domains/_spine/home-es.md new file mode 100644 index 0000000..6521ce9 --- /dev/null +++ b/site/site/content/domains/_spine/home-es.md @@ -0,0 +1,15 @@ + +# Pisa firme y anda ligero. + +> Tu proyecto, tu infra, tu obra, tu vida: ¿sigues yendo a donde dijiste? + +## Cuatro puertas, el mismo edificio + +- **[Desarrollador](/dominios/developer)** — Pregunta qué se rompe antes de tocar. +- **[Infraestructura](/dominios/infrastructure)** — Hereda el viaje, no solo el estado. +- **[Persona](/dominios/personal)** — Mira dónde tu esfuerzo se desvía. +- **[Autoría](/dominios/authoring)** — Difunde la obra entera, no fragmentos sueltos. + +--- + +_Lo que dejas dicho, alguien podrá comprobarlo._ diff --git a/site/site/content/domains/en/use-cases/door-authoring.md b/site/site/content/domains/en/use-cases/door-authoring.md new file mode 100644 index 0000000..79f666a --- /dev/null +++ b/site/site/content/domains/en/use-cases/door-authoring.md @@ -0,0 +1,46 @@ +--- +id: "door-authoring" +title: "Authoring" +slug: "authoring" +subtitle: "Diffuse the whole Work, not loose fragments." +excerpt: "An authored Work lives as source files (Pages, ODT, Markdown). Each medium —" +author: "ontoref" +date: "2026-06-10" +published: true +featured: true +category: "use-cases" +tags: ["authoring", "ontoref", "use-case"] +read_time: "3 min read" +sort_order: 4 +css_class: "category-use-cases" +category_description: "Use cases and implementations of ontoref" +category_published: true +--- + + + + + Authoring domain — author your Work once and diffuse it everywhere without fragmenting + + +**Author your Work once and diffuse it everywhere without it fragmenting. A Work — a book as archetype — is modeled as a coherent authored whole (a Central work with Satellites and Elements) carrying six typed content types; the same source projects to print, digital, web, interactive and an AI-queryable surface (RAG/MCP). Dissemination is an expression of the Work across channels, never a re-authoring per channel — so the Work stays coherent everywhere it appears.** + +An authored Work lives as source files (Pages, ODT, Markdown). Each medium — +print PDF, EPUB, web, interactive, AI-queryable — is produced by a separate +ad-hoc pipeline, so the Work is re-cut per channel and drifts: a correction in +the book never reaches the web, the glossary diverges between formats, and the +same content is maintained N times. + +The root cause is that no tool holds the Work as ONE coherent authored whole +with a declared expression across channels. Authoring tools target one output; +CMSs compose upward from fragments. So coherence — the very thing that makes it +a Work and not a pile of pages — is exactly what is lost the moment it is +published to more than one medium. + +--- + +[Posts](/blog?tag=knowledge-works) · [Recipes](/recipes?tag=knowledge-works) + +### The prize, once inside + +> When your reason for being is verifiable and not just declared, holding your course stops costing effort. diff --git a/site/site/content/domains/en/use-cases/door-authoring.ncl b/site/site/content/domains/en/use-cases/door-authoring.ncl new file mode 100644 index 0000000..11221cd --- /dev/null +++ b/site/site/content/domains/en/use-cases/door-authoring.ncl @@ -0,0 +1,12 @@ +{ + id = "door-authoring", + title = "Authoring", + slug = "authoring", + excerpt = "An authored Work lives as source files (Pages, ODT, Markdown). Each medium —", + tags = ["authoring", "ontoref", "use-case"], + page_route = "/domains/authoring", + graph = { + implements = ["adr-012", "adr-057", "adr-064", "positioning-layer", "reach-vs-qualification"], + related_to = ["door-developer", "door-infrastructure", "door-personal"], + }, +} diff --git a/site/site/content/domains/en/use-cases/door-developer.md b/site/site/content/domains/en/use-cases/door-developer.md new file mode 100644 index 0000000..96cc2bb --- /dev/null +++ b/site/site/content/domains/en/use-cases/door-developer.md @@ -0,0 +1,51 @@ +--- +id: "door-developer" +title: "Developer" +slug: "developer" +subtitle: "Ask what breaks before you touch it." +excerpt: "The developer who lands in a codebase they did not build cannot tell" +author: "ontoref" +date: "2026-06-10" +published: true +featured: true +category: "use-cases" +tags: ["developer", "ontoref", "use-case"] +read_time: "3 min read" +sort_order: 1 +css_class: "category-use-cases" +category_description: "Use cases and implementations of ontoref" +category_published: true +--- + + + + + Developer domain — keep AI-written code on course against declared intent + + +> **AI writes faster and breaks more: +861% churn, ×3.4 incidents per PR, 31.3% of PRs merged without a single review. Team maturity does not protect you.** +> +> — [Faros · AI Engineering Report 2026 — The Acceleration Whiplash](https://www.faros.ai/blog/ai-acceleration-whiplash-takeaways) · [See what breaks](/blog?tag=developer) + +**Ask the graph what breaks before you touch it. `describe impact ` returns the blast radius of a change — which nodes, which invariants, which ADRs — before you write a line. The codebase you just inherited can tell you what is load-bearing, and your AI agent can mount that same context instead of guessing.** + +The developer who lands in a codebase they did not build cannot tell +what is load-bearing from what is incidental. README and docs are +incomplete or stale; the originating decisions live in the heads of +people who left. So the safe move is to touch as little as possible — +which means feedback, fixes, and improvements stall, because the cost +of an unknown blast radius falls on whoever moves first. + +The same gap is what makes AI agents dangerous on an unfamiliar +project: their context window cannot hold the whole codebase, so they +act on a reconstructed, often hallucinated model — changing things +that violate invariants nobody wrote down. Both the human and the +agent are flying without a map of consequence. + +--- + +[Posts](/blog?tag=developer) · [Recipes](/recipes?tag=developer) + +### The prize, once inside + +> When your reason for being is verifiable and not just declared, holding your course stops costing effort. diff --git a/site/site/content/domains/en/use-cases/door-developer.ncl b/site/site/content/domains/en/use-cases/door-developer.ncl new file mode 100644 index 0000000..6fc8606 --- /dev/null +++ b/site/site/content/domains/en/use-cases/door-developer.ncl @@ -0,0 +1,12 @@ +{ + id = "door-developer", + title = "Developer", + slug = "developer", + excerpt = "The developer who lands in a codebase they did not build cannot tell", + tags = ["developer", "ontoref", "use-case"], + page_route = "/domains/developer", + graph = { + implements = ["adr-009", "adr-012", "adr-015", "adr-046", "adr-035", "positioning-layer", "reach-vs-qualification"], + related_to = ["door-infrastructure", "door-personal", "door-authoring"], + }, +} diff --git a/site/site/content/domains/en/use-cases/door-infrastructure.md b/site/site/content/domains/en/use-cases/door-infrastructure.md new file mode 100644 index 0000000..2ac660b --- /dev/null +++ b/site/site/content/domains/en/use-cases/door-infrastructure.md @@ -0,0 +1,49 @@ +--- +id: "door-infrastructure" +title: "Infrastructure" +slug: "infrastructure" +subtitle: "Inherit the journey, not just the state." +excerpt: "The operator who inherits infrastructure cannot see its trajectory." +author: "ontoref" +date: "2026-06-10" +published: true +featured: true +category: "use-cases" +tags: ["infrastructure", "ontoref", "use-case"] +read_time: "3 min read" +sort_order: 2 +css_class: "category-use-cases" +category_description: "Use cases and implementations of ontoref" +category_published: true +--- + + + + + Infrastructure domain — your infrastructure on its own trajectory, not just up or down + + +**Your infrastructure tells you where it is on its own trajectory — not just whether resources match. `describe state` returns each dimension's current → desired with the blocker and catalyst between them; `describe connections` maps how one action depends on the next. The infra is structured as a DAG that carries sense, journey, and perspective, with declared actions that reproduce — so HA and resilience rise to another plane: a property of a system that knows its own state and how it got there, not redundancy bolted on.** + +The operator who inherits infrastructure cannot see its trajectory. +IaC tools answer one question — does reality match the desired +resource set? — and that diff is real but shallow: it shows the +current shape, never how the infra reached it, which dimension is +mid-transition, what blocks the next step, or why one action depends +on the next. So evolving inherited infra means reverse-engineering +intent from config, under incident risk. + +Resilience suffers the same blindness. When HA is bolted on as +redundancy and failover without the system knowing its own state and +journey, recovery is a guess: you restore resources without restoring +understanding. The relationship between actions — the cohesion that +makes the whole behave predictably — lives in the heads of the people +who built it, and leaves when they do. + +--- + +[Posts](/blog?tag=provisioning) · [Recipes](/recipes?tag=provisioning) + +### The prize, once inside + +> When your reason for being is verifiable and not just declared, holding your course stops costing effort. diff --git a/site/site/content/domains/en/use-cases/door-infrastructure.ncl b/site/site/content/domains/en/use-cases/door-infrastructure.ncl new file mode 100644 index 0000000..7219057 --- /dev/null +++ b/site/site/content/domains/en/use-cases/door-infrastructure.ncl @@ -0,0 +1,12 @@ +{ + id = "door-infrastructure", + title = "Infrastructure", + slug = "infrastructure", + excerpt = "The operator who inherits infrastructure cannot see its trajectory.", + tags = ["infrastructure", "ontoref", "use-case"], + page_route = "/domains/infrastructure", + graph = { + implements = ["adr-012", "adr-025", "adr-002", "adr-035", "positioning-layer", "reach-vs-qualification"], + related_to = ["door-developer", "door-personal", "door-authoring"], + }, +} diff --git a/site/site/content/domains/en/use-cases/door-personal.md b/site/site/content/domains/en/use-cases/door-personal.md new file mode 100644 index 0000000..5ebaf7d --- /dev/null +++ b/site/site/content/domains/en/use-cases/door-personal.md @@ -0,0 +1,48 @@ +--- +id: "door-personal" +title: "Personal" +slug: "personal" +subtitle: "See where your effort drifts." +excerpt: "A person's career, content, decisions, and intentions live as scattered" +author: "ontoref" +date: "2026-06-10" +published: true +featured: true +category: "use-cases" +tags: ["personal", "ontoref", "use-case"] +read_time: "3 min read" +sort_order: 3 +css_class: "category-use-cases" +category_description: "Use cases and implementations of ontoref" +category_published: true +--- + + + + + Personal domain — model your career and commitments as a graph with a declared desired state + + +**See where your effort drifts from your intent. Model your career and commitments as a graph with a declared desired state; `describe state` shows current → desired and what blocks the next step, and the graph surfaces which commitments move you toward where you said you want to go — and which are accidental. Your own trajectory becomes queryable: what have I done, what connects, what should I do next — answered, not felt. You don't end up where you didn't choose.** + +A person's career, content, decisions, and intentions live as scattered +prose — a CV in one doc, talks in a deck, the reasons in your head. +There is no way to ask 'what have I done, what does it connect to, what +should I do next', and — worse — no way to see drift: the commitments, +content, and opportunities that consume effort without moving you +toward anything you actually said you wanted. + +So the rudder slips silently. You end up where you did not choose, +doing what you did not want, having forgotten what motivated you — +not through a bad decision but through the accumulation of accidental +ones that no map ever flagged as off-course. PKM tools store and link +notes; none of them hold a declared desired state against which effort +can be checked for alignment. + +--- + +[Posts](/blog?tag=personal) · [Recipes](/recipes?tag=personal) + +### The prize, once inside + +> When your reason for being is verifiable and not just declared, holding your course stops costing effort. diff --git a/site/site/content/domains/en/use-cases/door-personal.ncl b/site/site/content/domains/en/use-cases/door-personal.ncl new file mode 100644 index 0000000..a416457 --- /dev/null +++ b/site/site/content/domains/en/use-cases/door-personal.ncl @@ -0,0 +1,12 @@ +{ + id = "door-personal", + title = "Personal", + slug = "personal", + excerpt = "A person's career, content, decisions, and intentions live as scattered", + tags = ["personal", "ontoref", "use-case"], + page_route = "/domains/personal", + graph = { + implements = ["adr-012", "adr-025", "adr-035", "positioning-layer", "reach-vs-qualification"], + related_to = ["door-developer", "door-infrastructure", "door-authoring"], + }, +} diff --git a/site/site/content/domains/es/use-cases/door-authoring.md b/site/site/content/domains/es/use-cases/door-authoring.md new file mode 100644 index 0000000..c990e94 --- /dev/null +++ b/site/site/content/domains/es/use-cases/door-authoring.md @@ -0,0 +1,46 @@ +--- +id: "door-authoring" +title: "Autoría" +slug: "authoring" +subtitle: "Difunde la obra entera, no fragmentos sueltos." +excerpt: "An authored Work lives as source files (Pages, ODT, Markdown). Each medium —" +author: "ontoref" +date: "2026-06-10" +published: true +featured: true +category: "use-cases" +tags: ["authoring", "ontoref", "use-case"] +read_time: "3 min read" +sort_order: 4 +css_class: "category-use-cases" +category_description: "Casos de uso e implementaciones de ontoref" +category_published: true +--- + + + + + Dominio de autoría — crea tu Obra una vez y difúndela en todas partes sin fragmentarla + + +**Author your Work once and diffuse it everywhere without it fragmenting. A Work — a book as archetype — is modeled as a coherent authored whole (a Central work with Satellites and Elements) carrying six typed content types; the same source projects to print, digital, web, interactive and an AI-queryable surface (RAG/MCP). Dissemination is an expression of the Work across channels, never a re-authoring per channel — so the Work stays coherent everywhere it appears.** + +An authored Work lives as source files (Pages, ODT, Markdown). Each medium — +print PDF, EPUB, web, interactive, AI-queryable — is produced by a separate +ad-hoc pipeline, so the Work is re-cut per channel and drifts: a correction in +the book never reaches the web, the glossary diverges between formats, and the +same content is maintained N times. + +The root cause is that no tool holds the Work as ONE coherent authored whole +with a declared expression across channels. Authoring tools target one output; +CMSs compose upward from fragments. So coherence — the very thing that makes it +a Work and not a pile of pages — is exactly what is lost the moment it is +published to more than one medium. + +--- + +[Posts](/blog?tag=knowledge-works) · [Recetas](/recetas?tag=knowledge-works) + +### El premio, al entrar + +> Cuando tu razón de ser es verificable y no solo declarada, mantener el rumbo deja de costar esfuerzo. diff --git a/site/site/content/domains/es/use-cases/door-developer.md b/site/site/content/domains/es/use-cases/door-developer.md new file mode 100644 index 0000000..9a1211b --- /dev/null +++ b/site/site/content/domains/es/use-cases/door-developer.md @@ -0,0 +1,51 @@ +--- +id: "door-developer" +title: "Desarrollador" +slug: "developer" +subtitle: "Pregunta qué se rompe antes de tocar." +excerpt: "The developer who lands in a codebase they did not build cannot tell" +author: "ontoref" +date: "2026-06-10" +published: true +featured: true +category: "use-cases" +tags: ["developer", "ontoref", "use-case"] +read_time: "3 min read" +sort_order: 1 +css_class: "category-use-cases" +category_description: "Casos de uso e implementaciones de ontoref" +category_published: true +--- + + + + + Dominio de desarrollo — mantén el código escrito por IA en rumbo frente a la intención declarada + + +> **El AI escribe más rápido y rompe más: +861% de churn, ×3.4 incidentes por PR, 31.3% de PRs fusionados sin una sola revisión. La madurez del equipo no protege.** +> +> — [Faros · AI Engineering Report 2026 — The Acceleration Whiplash](https://www.faros.ai/blog/ai-acceleration-whiplash-takeaways) · [Comprueba qué se rompe](/blog?tag=developer) + +**Ask the graph what breaks before you touch it. `describe impact ` returns the blast radius of a change — which nodes, which invariants, which ADRs — before you write a line. The codebase you just inherited can tell you what is load-bearing, and your AI agent can mount that same context instead of guessing.** + +The developer who lands in a codebase they did not build cannot tell +what is load-bearing from what is incidental. README and docs are +incomplete or stale; the originating decisions live in the heads of +people who left. So the safe move is to touch as little as possible — +which means feedback, fixes, and improvements stall, because the cost +of an unknown blast radius falls on whoever moves first. + +The same gap is what makes AI agents dangerous on an unfamiliar +project: their context window cannot hold the whole codebase, so they +act on a reconstructed, often hallucinated model — changing things +that violate invariants nobody wrote down. Both the human and the +agent are flying without a map of consequence. + +--- + +[Posts](/blog?tag=developer) · [Recetas](/recetas?tag=developer) + +### El premio, al entrar + +> Cuando tu razón de ser es verificable y no solo declarada, mantener el rumbo deja de costar esfuerzo. diff --git a/site/site/content/domains/es/use-cases/door-infrastructure.md b/site/site/content/domains/es/use-cases/door-infrastructure.md new file mode 100644 index 0000000..36d5f32 --- /dev/null +++ b/site/site/content/domains/es/use-cases/door-infrastructure.md @@ -0,0 +1,49 @@ +--- +id: "door-infrastructure" +title: "Infraestructura" +slug: "infrastructure" +subtitle: "Hereda el viaje, no solo el estado." +excerpt: "The operator who inherits infrastructure cannot see its trajectory." +author: "ontoref" +date: "2026-06-10" +published: true +featured: true +category: "use-cases" +tags: ["infrastructure", "ontoref", "use-case"] +read_time: "3 min read" +sort_order: 2 +css_class: "category-use-cases" +category_description: "Casos de uso e implementaciones de ontoref" +category_published: true +--- + + + + + Dominio de infraestructura — tu infraestructura en su propia trayectoria, no solo arriba o abajo + + +**Your infrastructure tells you where it is on its own trajectory — not just whether resources match. `describe state` returns each dimension's current → desired with the blocker and catalyst between them; `describe connections` maps how one action depends on the next. The infra is structured as a DAG that carries sense, journey, and perspective, with declared actions that reproduce — so HA and resilience rise to another plane: a property of a system that knows its own state and how it got there, not redundancy bolted on.** + +The operator who inherits infrastructure cannot see its trajectory. +IaC tools answer one question — does reality match the desired +resource set? — and that diff is real but shallow: it shows the +current shape, never how the infra reached it, which dimension is +mid-transition, what blocks the next step, or why one action depends +on the next. So evolving inherited infra means reverse-engineering +intent from config, under incident risk. + +Resilience suffers the same blindness. When HA is bolted on as +redundancy and failover without the system knowing its own state and +journey, recovery is a guess: you restore resources without restoring +understanding. The relationship between actions — the cohesion that +makes the whole behave predictably — lives in the heads of the people +who built it, and leaves when they do. + +--- + +[Posts](/blog?tag=provisioning) · [Recetas](/recetas?tag=provisioning) + +### El premio, al entrar + +> Cuando tu razón de ser es verificable y no solo declarada, mantener el rumbo deja de costar esfuerzo. diff --git a/site/site/content/domains/es/use-cases/door-personal.md b/site/site/content/domains/es/use-cases/door-personal.md new file mode 100644 index 0000000..034bf98 --- /dev/null +++ b/site/site/content/domains/es/use-cases/door-personal.md @@ -0,0 +1,48 @@ +--- +id: "door-personal" +title: "Persona" +slug: "personal" +subtitle: "Mira dónde tu esfuerzo se desvía." +excerpt: "A person's career, content, decisions, and intentions live as scattered" +author: "ontoref" +date: "2026-06-10" +published: true +featured: true +category: "use-cases" +tags: ["personal", "ontoref", "use-case"] +read_time: "3 min read" +sort_order: 3 +css_class: "category-use-cases" +category_description: "Casos de uso e implementaciones de ontoref" +category_published: true +--- + + + + + Dominio personal — modela tu carrera y compromisos como un grafo con un estado deseado declarado + + +**See where your effort drifts from your intent. Model your career and commitments as a graph with a declared desired state; `describe state` shows current → desired and what blocks the next step, and the graph surfaces which commitments move you toward where you said you want to go — and which are accidental. Your own trajectory becomes queryable: what have I done, what connects, what should I do next — answered, not felt. You don't end up where you didn't choose.** + +A person's career, content, decisions, and intentions live as scattered +prose — a CV in one doc, talks in a deck, the reasons in your head. +There is no way to ask 'what have I done, what does it connect to, what +should I do next', and — worse — no way to see drift: the commitments, +content, and opportunities that consume effort without moving you +toward anything you actually said you wanted. + +So the rudder slips silently. You end up where you did not choose, +doing what you did not want, having forgotten what motivated you — +not through a bad decision but through the accumulation of accidental +ones that no map ever flagged as off-course. PKM tools store and link +notes; none of them hold a declared desired state against which effort +can be checked for alignment. + +--- + +[Posts](/blog?tag=personal) · [Recetas](/recetas?tag=personal) + +### El premio, al entrar + +> Cuando tu razón de ser es verificable y no solo declarada, mantener el rumbo deja de costar esfuerzo. diff --git a/site/site/content/expedientes/_glossary.html b/site/site/content/expedientes/_glossary.html new file mode 100644 index 0000000..4218de7 --- /dev/null +++ b/site/site/content/expedientes/_glossary.html @@ -0,0 +1,359 @@ + +
    +
    +

    Glosario

    + +
    + +
    + +
    + + + + diff --git a/site/site/content/expedientes/_schema/expediente.ncl b/site/site/content/expedientes/_schema/expediente.ncl new file mode 100644 index 0000000..a3205fd --- /dev/null +++ b/site/site/content/expedientes/_schema/expediente.ncl @@ -0,0 +1,58 @@ +# Expediente — typed case-file schema. +# +# The .md next to each sidecar is the RENDERED prose; this .ncl is the TYPED +# SOURCE — parseable and queryable without interpreting free text. A renderer +# (or the `generate-expediente` mode) consumes this record; the two skins +# ('detective / 'clinical) are a presentation choice over the same data. + +{ + Ledger = { label | String, value | String, emphasis | Bool | default = false }, + + Suspect = { + name | String, + alibi | String, # the one-line "coartada" / reason ruled out + verdict | String, # e.g. "pista falsa", "coartada 302", "señuelo" + }, + + Weapon = { + caption | String, + code | String, # the offending / fixed snippet (verbatim) + note | String | default = "", + }, + + Containment = { + facet | String, # a facet of the drift + mechanism | String, # the ontoref mechanism that would contain it + }, + + Term = { term | String, def | String }, + + Expediente = { + id | String, + title | String, + slug | String, + case_no | String, + classification | String, # e.g. "ANTI-PAP" + status | String | default = "RESUELTO", + skin | [| 'detective, 'clinical |] | default = 'detective, + + logline | String, + glossary | Array Term | default = [], + cost | Array Ledger, # "lo que costó" + yielded | Array Ledger | default = [],# "lo que dejó" (la convergencia) + abandonment | String | optional, # el punto de abandono (prosa) + suspects | Array Suspect | default = [], + weapon | Weapon, # el arma (código anti-PAP) + fix | Weapon, # el giro (código PAP-compliant) + containment | Array Containment, # tabla deriva → mecanismo + verdict | String, + + ontoref_url | String | default = "https://ontoref.dev", + related_proofs | Array String | default = [], # positioning proof ids + related_modes | Array String | default = [], # reflection mode ids + tags | Array String | default = [], + }, + + # Factory: validate a record against the Expediente contract. + make_expediente = fun r => r | Expediente, +} diff --git a/site/site/content/expedientes/en/cases/deriva-hardcoded-anti-pap.md b/site/site/content/expedientes/en/cases/deriva-hardcoded-anti-pap.md new file mode 100644 index 0000000..b06b427 --- /dev/null +++ b/site/site/content/expedientes/en/cases/deriva-hardcoded-anti-pap.md @@ -0,0 +1,138 @@ +--- +id: "deriva-hardcoded-anti-pap" +title: "Case 404-PAP: the hardcoded drift" +slug: "the-hardcoded-drift" +subtitle: "How a lost afternoon under an anti-PAP drift was fixed in 8 lines — and turned into a system" +excerpt: "Adding a content type that 'should have been just NCL' cost ~88 minutes, 8 builds and 8 false leads: a hardcoded match shadowed the routes registry (anti-PAP). The fix was 8 lines. But what the case left behind — a howto, a mode, a proof, a series — is the real evidence of what ontoref is for." +author: "Jesús Pérez" +date: "2026-07-11" +published: true +featured: true +category: "cases" +tags: ["case-file", "anti-pap", "drift", "dogfooding", "ontoref", "developer"] +thumbnail: "/images/ontology-reflection-post.webp" +image_url: "/images/ontology-reflection-post.webp" +sort_order: 1 +--- + +
    + + +Case file 404-PAP — the hardcoded drift +

    🕵️ Show the full case file →

    + +

    Case file · Code Homicide Dept.

    + +

    It was a quiet afternoon. Then /recursos walked in and showed nothing. Nothing at all. And I, naive, thought it would be quick.

    + +
    Case No. 404-PAPClassification: ANTI-PAPStatus: CLOSED
    + +
    +
    +
    PAP
    Project's Architecture Principles — the rules and patterns that hold the architecture up: the single source of truth all code must respect.
    +
    anti-PAP
    Code written against those rules. Here: a stub re-listing by hand what routes.ncl already declares.
    +
    +

    A protocol to declare, version and verify all this → ontoref.dev

    +
    + +

    The double ledger — what it cost, and what it left

    + +A case isn't judged only by what it costs. It's judged by what it leaves. From a **hope-less** low —eight false leads, six fingerprints, the whole afternoon— we found our bearings, and everything that was travelling on the same journey clicked into place at once: + +
    +
    +

    What the crime cost

    +
      +
    • Pure hunt (framework) ~88 min
    • +
    • Release builds 8
    • +
    • Coexisting fingerprints 6
    • +
    • Server restarts ~15+
    • +
    • False leads 8
    • +
    • The fix 8 lines
    • +
    +
    +
    +

    What the case left

    +
      +
    • Registry-driven fix PAP-compliant
    • +
    • Knowledge content-kind-howto
    • +
    • Mechanism generate-expediente mode
    • +
    • Evidence proof (positioning)
    • +
    • Series + hub /case-files
    • +
    • Framework unblocked
    • +
    +
    +
    + +That's the credible depth: the afternoon of pain didn't end in a patch — it ended in a **declared system**. Ontology, modes and positioning weren't "added" — they were travelling together, and the case made them converge. + +

    The point of abandonment — what the table doesn't show

    + +There's a point in every drift that no cost table records: the one where you stop feeling fear and start wanting to quit. Bugs become ghosts —they appear, vanish, come back with a different face—; everything stalls and you don't know why; there's no urgency left, only fatigue. *"It's too much. It can't be solved."* That's the real moment, the one not measured in builds — the one that kills projects in silence. + +With [ontoref](https://ontoref.dev) that point shouldn't arrive. Not because there are no bugs, but because it doesn't leave you alone with them: the **dao** (ondaod) forces you to *name* the tension instead of collapsing or abandoning it; the **modes** hand you the next step when you no longer know what it is; the **ADRs** close what's already decided so you don't re-litigate it at two in the morning. The drift doesn't sink you because the map holds you up. + +

    The weapon — a hardcoded match (anti-PAP)

    + +
    let kind = match base {
    +    "blog" => Some("blog"),
    +    "activities" | "actividades" => Some("activities"),
    +    // … every kind by hand … except resources …
    +    _ => None,   // ← /recursos landed here → 404
    +};
    + +Route registered. Content-type enabled. The API returned the items. The content, present. And still, 404 — because a stub decided the truth against the source of truth. + +

    The turn — 8 lines that read from the registry

    + +
    let kind = load_routes_config().routes.iter().find_map(|r| {
    +    if !r.enabled { return None; }
    +    let seg = r.path.trim_start_matches('/').split('/').next().unwrap_or("");
    +    (seg == base).then(|| r.content_type.clone()).flatten()
    +});
    + +/recursos, /proyectos, /dominios — all of them, bilingual aliases included — resolved from the single source. **Adding a kind is just NCL again.** + +

    The verdict

    + +We didn't go to the beach because we chose badly: we went because **the map had an unmarked drift**. The four things [ontoref](https://ontoref.dev) instruments are the four that were missing: + + + + + + +
    Hardcoded stub shadowing the registryPAP + anti-patterns → grep-able
    Knowledge lost, rediscovered by painqa/howto → content-kind-howto
    Building on the invalid without knowingreview against invariants
    Assuming the unassumable, believing the unbelievabledeclared state → 3 levels
    + +A ten-line drift, unmarked, cost us an afternoon to escape — and was fixed in eight. That differential is the ROI. **[Ontoref](https://ontoref.dev) is the map's signposting.** + +
    diff --git a/site/site/content/expedientes/en/cases/recaida-hardcoded-bis.md b/site/site/content/expedientes/en/cases/recaida-hardcoded-bis.md new file mode 100644 index 0000000..48b75e0 --- /dev/null +++ b/site/site/content/expedientes/en/cases/recaida-hardcoded-bis.md @@ -0,0 +1,104 @@ +--- +id: "recaida-hardcoded-bis" +title: "Case 404-PAP-bis: the relapse" +slug: "the-relapse-hardcoded" +subtitle: "Clinical history of a twin: the same pathogen, another organ — and why this time the collapse lasted minutes, not an afternoon" +excerpt: "We thought we were discharged. Instantly, /nosotros threw a 404: the same hardcoded match, now in the static pages. From triumph to misery in a heartbeat. But this time the diagnosis took minutes — because the first case was already clinical history. That's the difference ontoref buys." +author: "Jesús Pérez" +date: "2026-07-11" +published: true +featured: true +category: "cases" +tags: ["case-file", "clinical", "relapse", "anti-pap", "dogfooding", "ontoref"] +thumbnail: "/images/expedientes/recaida-header.webp" +image_url: "/images/expedientes/recaida-header.webp" +sort_order: 2 +--- + +
    + + +Case file 404-PAP-bis — the relapse +

    🩺 Show the full clinical history →

    + +

    Clinical history · Code Pathology Service

    + +

    We thought we were discharged. The `/expedientes` hub green, the case closed, glory. And then `/nosotros` threw a 404. Instantly. From triumph to misery with no transition — a mirage, a rollercoaster that only ever goes down.

    + +
    History No. 404-PAP-bisDiagnosis: ANTI-PAP (relapse)Status: DISCHARGED
    + +
    +Chief complaint. Patient who, believing himself cured, suffers a sudden relapse of the same syndrome. Reports going from startled surprise to panic and uncertainty in an instant; mood drops from triumph to depression with no intermediate step. Verbalizes: "after this I'm going to need a visit to the clinic". And here we are. +
    + +

    Etiology — the same pathogen, another organ

    + +[Case 404-PAP](/case-files/cases/the-hardcoded-drift) killed a hardcoded `match` in `render_content_or_grid` (content pages). The twin lived intact in the organ next door — the **static pages**, `pages_htmx::dispatch`: + +
    match path {
    +    "/services" | "/servicios" => Some(static_page::render(env, "services", lang)),
    +    // … every page by hand … except /nosotros …
    +    _ => None,   // ← /nosotros fell through here → 404
    +}
    + +Route declared, FTL present, component baked — and still a 404. The same anti‑PAP: a match that duplicates what `routes.ncl` already knows. + +

    Treatment — the same antibiotic

    + +
    _ => resolve_static_page(env, path, lang),
    +// … reads from the registry: any route whose component
    +// ends in "Page" → static_page(page_id kebab). Zero new arms.
    + +

    Prognosis — and why this relapse wasn't like the first

    + +The telling part isn't that it relapsed. It's **how long it lasted**. The first case, with no prior history, cost an afternoon. The twin, identical, cost **minutes** — because the first was already *declared as clinical history* (`content-kind-howto`): I went straight to the organ, recognized the pathogen, applied the same antibiotic. + +
    +
    +

    Case 404-PAP · no history

    +
      +
    • Diagnosis ~88 min
    • +
    • Builds 8
    • +
    • False leads 8
    • +
    +
    +
    +

    Case bis · with clinical history

    +
      +
    • Diagnosis ~15 min
    • +
    • Builds 1
    • +
    • False leads 0
    • +
    +
    +
    + +Same bug. **~30× faster.** Not because the second was easier — it was identical — but because the first was **declared**. That's [ontoref](https://ontoref.dev) working in real time: the `dao` holds you through the relapse, the *mode* gives you the step, and the prior howto turns "another afternoon lost" into "fifteen minutes and a known antibiotic". + +

    Discharge and prophylaxis

    + +The pathogen has family: any hardcoded `match` that shadows the registry. The vaccine is the same for all — read from the single source. The case is closed on record (`static-page-howto`) so that the **third** twin, if it shows up, lasts seconds. + +
    diff --git a/site/site/content/expedientes/es/casos/deriva-hardcoded-anti-pap.md b/site/site/content/expedientes/es/casos/deriva-hardcoded-anti-pap.md new file mode 100644 index 0000000..1ef91ff --- /dev/null +++ b/site/site/content/expedientes/es/casos/deriva-hardcoded-anti-pap.md @@ -0,0 +1,500 @@ +--- +id: "deriva-hardcoded-anti-pap" +title: "Expediente 404-PAP: la deriva hardcoded" +slug: "la-deriva-hardcoded" +subtitle: "Cómo una tarde perdida bajo una deriva anti-PAP se arregló en 8 líneas — y se convirtió en sistema" +excerpt: "Añadir un tipo de contenido que 'debía ser solo NCL' costó ~88 minutos, 8 builds y 8 pistas falsas: un match hardcoded sombreaba el registry (anti-PAP). El fix fueron 8 líneas. Pero lo que el caso dejó —un howto, un modo, un proof, una serie— es la evidencia real de para qué existe ontoref." +author: "Jesús Pérez" +date: "2026-07-11" +published: true +featured: true +category: "casos" +tags: ["expediente", "anti-pap", "deriva", "dogfooding", "ontoref", "developer"] +thumbnail: "/images/expedientes/deriva-header.webp" +image_url: "/images/expedientes/deriva-header.webp" +sort_order: 1 +--- + +
    + + +Expediente 404-PAP — la deriva hardcoded +

    🕵️ Mostrar expediente completo →

    + +

    Expediente · Depto. de Homicidios de Código

    + +

    Era una tarde tranquila. Entonces /recursos entró por la puerta y no mostró nada. Nada de nada. Y yo, ingenuo, pensé que sería rápido.

    + +
    Caso Nº 404-PAPClasificación: ANTI-PAPEstado: RESUELTO
    + +
    +
    +
    PAP
    Project's Architecture Principles — las reglas y patrones que sostienen la arquitectura: la fuente única de la verdad que todo el código debe respetar.
    +
    anti-PAP
    Código escrito en contra de esas reglas. Aquí: un stub que re-lista a mano lo que routes.ncl ya declara.
    +
    +

    Protocolo para declarar, versionar y verificar esto → ontoref.dev

    +
    + +

    El doble balance — lo que costó, y lo que dejó

    + +Un caso no se juzga solo por lo que cuesta. Se juzga por lo que deja. De un punto **des‑esperado** —ocho pistas falsas, seis fingerprints, la tarde entera— recobramos el rumbo, y lo que venía en el mismo viaje encajó de golpe: + +
    +
    +

    Lo que costó el crimen

    +
      +
    • Caza pura (framework) ~88 min
    • +
    • Builds de release 8
    • +
    • Fingerprints coexistiendo 6
    • +
    • Reinicios del server ~15+
    • +
    • Pistas falsas 8
    • +
    • El arreglo 8 líneas
    • +
    +
    +
    +

    Lo que el caso dejó

    +
      +
    • Fix registry-driven PAP‑compliant
    • +
    • Conocimiento content-kind-howto
    • +
    • Mecanismo modo generate‑expediente
    • +
    • Evidencia proof (positioning)
    • +
    • Serie + hub /expedientes
    • +
    • Framework desatascado
    • +
    +
    +
    + +Esa es la profundidad creíble: la tarde de dolor no terminó en un parche, **terminó en sistema declarado**. Ontología, modos y posicionamiento no se "añadieron" — estaban viajando juntos y el caso los hizo converger. + +

    El punto de abandono — lo que no sale en la tabla

    + +Hay un punto en toda deriva que ninguna tabla de coste registra: aquel en que dejas de tener miedo y empiezas a tener ganas de abandonar. Los bugs se vuelven fantasmas —aparecen, se van, vuelven con otra cara—; todo se para y no sabes por qué; ya no hay urgencia, solo cansancio. *"Es demasiado. No se puede resolver."* Ese es el momento real, el que no se mide en builds — y el que hace que un proyecto se muera en silencio. + +Con [ontoref](https://ontoref.dev) ese punto no debería llegar. No porque no haya bugs, sino porque no te deja solo frente a ellos: el **dao** (ondaod) te obliga a *nombrar* la tensión en vez de colapsarla o abandonarla; los **modos** te dan el siguiente paso cuando ya no sabes cuál es; los **ADRs** cierran lo ya decidido para que no lo vuelvas a litigar a las dos de la mañana. La deriva no te hunde porque el mapa te sostiene. + +

    El arma — un match hardcoded (anti-PAP)

    + +
    let kind = match base {
    +    "blog" => Some("blog"),
    +    "activities" | "actividades" => Some("activities"),
    +    // … todos los kinds a mano … menos resources …
    +    _ => None,   // ← /recursos caía aquí → 404
    +};
    + +Ruta registrada. Content‑type habilitado. La API devolvía los items. El contenido, presente. Y aun así, 404 — porque un stub decidía la verdad en contra de la fuente de la verdad. + +

    El giro — 8 líneas que leen del registry

    + +
    let kind = load_routes_config().routes.iter().find_map(|r| {
    +    if !r.enabled { return None; }
    +    let seg = r.path.trim_start_matches('/').split('/').next().unwrap_or("");
    +    (seg == base).then(|| r.content_type.clone()).flatten()
    +});
    + +/recursos, /proyectos, /dominios — todos, alias bilingües incluidos — resueltos desde la única fuente. **Añadir un kind vuelve a ser solo NCL.** + +

    El veredicto

    + +No fuimos a la playa por decidir mal: fuimos porque **el mapa tenía una deriva sin señalizar**. Las cuatro cosas que [ontoref](https://ontoref.dev) instrumenta son las cuatro que faltaban: + + + + + + +
    Stub hardcoded que sombrea el registryPAP + anti-patterns → grep-able
    Conocimiento perdido, redescubierto por dolorqa/howto → content-kind-howto
    Construir sobre lo no-válido sin saberloreview contra invariantes
    Asumir lo no-asumible, creer lo no-creíbleestado declarado → 3 niveles
    + +Una deriva de diez líneas, sin señalizar, nos costó una tarde salir — y se arregló en ocho. Ese diferencial es el ROI. **[Ontoref](https://ontoref.dev) es la señalización del mapa.** + +
    + + +
    + +
    +
    +

    Glosario

    + +
    + +
    + +
    + + + + diff --git a/site/site/content/expedientes/es/casos/deriva-hardcoded-anti-pap.ncl b/site/site/content/expedientes/es/casos/deriva-hardcoded-anti-pap.ncl new file mode 100644 index 0000000..05dcdf8 --- /dev/null +++ b/site/site/content/expedientes/es/casos/deriva-hardcoded-anti-pap.ncl @@ -0,0 +1,90 @@ +# Typed source for expediente "deriva-hardcoded-anti-pap". +# The sibling .md is the rendered prose; this record is the machine-parseable +# case. Regenerate/render via the `generate-expediente` mode. +let s = import "../../_schema/expediente.ncl" in + +s.make_expediente { + id = "deriva-hardcoded-anti-pap", + title = "Expediente 404-PAP: la deriva hardcoded", + slug = "la-deriva-hardcoded", + case_no = "404-PAP", + classification = "ANTI-PAP", + status = "RESUELTO", + skin = 'detective, + + logline = "Era una tarde tranquila. Entonces /recursos entró por la puerta y no mostró nada. Nada de nada. Y yo, ingenuo, pensé que sería rápido.", + + glossary = [ + { term = "PAP", def = "Project's Architecture Principles — las reglas y patrones que sostienen la arquitectura: la fuente única de la verdad que todo el código debe respetar." }, + { term = "anti-PAP", def = "Código escrito en contra de esas reglas. Aquí: un stub que re-lista a mano lo que routes.ncl ya declara." }, + ], + + cost = [ + { label = "Caza pura del origen (framework)", value = "~88 min" }, + { label = "Builds de release lanzados", value = "8" }, + { label = "Fingerprints de build coexistiendo", value = "6" }, + { label = "Reinicios del servidor", value = "~15+" }, + { label = "Pistas falsas antes de la buena", value = "8", emphasis = true }, + { label = "Tamaño del arreglo final", value = "8 líneas", emphasis = true }, + ], + + yielded = [ + { label = "Fix registry-driven", value = "PAP-compliant" }, + { label = "Conocimiento", value = "content-kind-howto" }, + { label = "Mecanismo", value = "modo generate-expediente" }, + { label = "Evidencia", value = "proof (positioning)" }, + { label = "Serie + hub", value = "/expedientes" }, + { label = "Framework", value = "desatascado" }, + ], + + abandonment = "Hay un punto en toda deriva que ninguna tabla registra: aquel en que dejas de tener miedo y empiezas a tener ganas de abandonar. Los bugs se vuelven fantasmas; todo se para y no sabes por qué; ya no hay urgencia, solo cansancio. 'Es demasiado, no se puede resolver.' Con ontoref ese punto no debería llegar: el dao te hace nombrar la tensión en vez de colapsarla, los modos te dan el siguiente paso, los ADRs cierran lo ya decidido. La deriva no te hunde porque el mapa te sostiene.", + + suspects = [ + { name = "El contenido en site/r", alibi = "Yo no estaba sincronizado.", verdict = "cómplice menor" }, + { name = "filename != id", alibi = "El body salía vacío, pero no fui yo el del 404.", verdict = "otro delito" }, + { name = "El comentario {# … #}", alibi = "Solo me colé como texto. Inocente.", verdict = "coartada firme" }, + { name = "Staleness & fingerprints", alibi = "Con 6 fingerprints, ¿cómo no ibas a dudar del binario?", verdict = "pista falsa" }, + { name = "SITE_PUBLIC_PATH / symlink", alibi = "El dir no existía. Symlink para nada.", verdict = "pista falsa" }, + { name = "generated.rs → vec![\"content\"]", alibi = "Soy el fallback. Ni me ejecuto.", verdict = "señuelo" }, + { name = "El RBAC", alibi = "Un deny mío es un 302, encanto. Un 404 no es cosa mía.", verdict = "coartada 302" }, + { name = "build_page_generator", alibi = "Eso es de Leptos. En htmx-ssr ni aparezco.", verdict = "jurisdicción errónea" }, + ], + + weapon = { + caption = "El arma · un match hardcoded (preexistente, anti-PAP)", + code = m%" +let kind = match base { + "blog" => Some("blog"), + "activities" | "actividades" => Some("activities"), + // … todos los kinds a mano … menos resources … + _ => None, // ← /recursos caía aquí → 404 +}; +"%, + note = "Ruta registrada, content-type habilitado, la API devolvía items, el contenido presente — y aun así 404, porque un stub decidía la verdad en contra de la fuente de la verdad.", + }, + + fix = { + caption = "El giro · 8 líneas que leen del registry (PAP-compliant)", + code = m%" +let kind = load_routes_config().routes.iter().find_map(|r| { + if !r.enabled { return None; } + let seg = r.path.trim_start_matches('/').split('/').next().unwrap_or(""); + (seg == base).then(|| r.content_type.clone()).flatten() +}); +"%, + note = "Todos los kinds, alias bilingües incluidos, resueltos desde la única fuente. Añadir un kind vuelve a ser solo NCL.", + }, + + containment = [ + { facet = "Stub hardcoded que sombrea el registry", mechanism = "PAP + anti-patterns (grep-able)" }, + { facet = "Conocimiento perdido, redescubierto por dolor", mechanism = "qa/howto (content-kind-howto)" }, + { facet = "Construir sobre lo no-válido sin saberlo", mechanism = "review contra invariantes" }, + { facet = "Asumir lo no-asumible, creer lo no-creíble", mechanism = "estado declarado (3 niveles)" }, + ], + + verdict = "No fuimos a la playa por decidir mal: fuimos porque el mapa tenía una deriva sin señalizar. Una deriva de diez líneas nos costó una tarde salir y se arregló en ocho. Ese diferencial es el ROI. Ontoref es la señalización del mapa.", + + related_proofs = ["proof-anti-pap-drift"], + related_modes = ["generate-expediente"], + tags = ["expediente", "anti-pap", "deriva", "dogfooding", "ontoref", "developer"], +} diff --git a/site/site/content/expedientes/es/casos/recaida-hardcoded-bis.md b/site/site/content/expedientes/es/casos/recaida-hardcoded-bis.md new file mode 100644 index 0000000..43d0324 --- /dev/null +++ b/site/site/content/expedientes/es/casos/recaida-hardcoded-bis.md @@ -0,0 +1,466 @@ +--- +id: "recaida-hardcoded-bis" +title: "Expediente 404-PAP-bis: la recaída" +slug: "la-recaida-hardcoded" +subtitle: "Historia clínica de un gemelo: el mismo patógeno, otro órgano — y por qué esta vez el colapso duró minutos, no una tarde" +excerpt: "Creíamos estar de alta. Al instante, /nosotros dio 404: el mismo match hardcoded, ahora en las páginas estáticas. Del logro a la miseria en un instante. Pero esta vez el diagnóstico llegó en minutos — porque el primer caso ya era historia clínica. Esa es la diferencia que ontoref compra." +author: "Jesús Pérez" +date: "2026-07-11" +published: true +featured: true +category: "casos" +tags: ["expediente", "clinico", "recaida", "anti-pap", "dogfooding", "ontoref"] +thumbnail: "/images/expedientes/recaida-header.webp" +image_url: "/images/expedientes/recaida-header.webp" +sort_order: 2 +--- + +
    + + +Expediente 404-PAP-bis — la recaída +

    🩺 Mostrar historia clínica →

    + +

    Historia clínica · Servicio de Patología del Código

    + +

    Creíamos estar de alta. El hub `/expedientes` en verde, el caso cerrado, la gloria. Y entonces `/nosotros` dio 404. Al instante. Del logro a la miseria sin transición — un espejismo, una montaña rusa que solo baja.

    + +
    Historia Nº 404-PAP-bisDiagnóstico: ANTI-PAP (recaída)Estado: ALTA
    + +
    +Motivo de consulta. Paciente que, creyéndose curado, sufre recaída súbita del mismo cuadro. Refiere del susto‑sorpresa al pánico y a la incertidumbre en un instante; el ánimo cae del triunfo a la depresión sin escalón intermedio. Verbaliza: "después de esto voy a necesitar una visita a la clínica". Y aquí está. +
    + +

    Etiología — el mismo patógeno, otro órgano

    + +El [Expediente 404-PAP](/expedientes/casos/la-deriva-hardcoded) mató un `match` hardcoded en `render_content_or_grid` (páginas de contenido). El gemelo vivía intacto en el órgano de al lado — las **páginas estáticas**, `pages_htmx::dispatch`: + +
    match path {
    +    "/services" | "/servicios" => Some(static_page::render(env, "services", lang)),
    +    // … cada página a mano … menos /nosotros …
    +    _ => None,   // ← /nosotros caía aquí → 404
    +}
    + +Ruta declarada, FTL presente, componente horneado — y aun así 404. El mismo anti‑PAP: un match que duplica lo que `routes.ncl` ya sabe. + +

    Tratamiento — el mismo antibiótico

    + +
    _ => resolve_static_page(env, path, lang),
    +// … lee del registry: cualquier ruta cuyo componente
    +// acabe en "Page" → static_page(page_id kebab). Cero arms nuevos.
    + +

    Pronóstico — y por qué esta recaída no fue como la primera

    + +Lo revelador no es que recayera. Es **cuánto duró**. El primer caso, sin historia previa, costó una tarde. El gemelo, idéntico, costó **minutos** — porque el primero ya estaba *declarado como historia clínica* (`content-kind-howto`): fui directo al órgano, reconocí el patógeno, apliqué el mismo antibiótico. + +
    +
    +

    Caso 404-PAP · sin historia

    +
      +
    • Diagnóstico ~88 min
    • +
    • Builds 8
    • +
    • Pistas falsas 8
    • +
    +
    +
    +

    Caso bis · con historia clínica

    +
      +
    • Diagnóstico ~15 min
    • +
    • Builds 1
    • +
    • Pistas falsas 0
    • +
    +
    +
    + +Mismo bug. **~30× más rápido.** No porque el segundo fuera más fácil — era idéntico — sino porque el primero se **declaró**. Eso es [ontoref](https://ontoref.dev) trabajando en tiempo real: el `dao` te sostiene en la recaída, el *modo* te da el paso, y el howto previo convierte "otra tarde perdida" en "quince minutos y un antibiótico conocido". + +

    Alta y profilaxis

    + +El patógeno tiene familia: cualquier `match` hardcoded que sombree el registry. La vacuna es la misma para todos — leer de la fuente única. El caso se cierra registrado (`static-page-howto`) para que el **tercer** gemelo, si aparece, dure segundos. + +
    + + +
    + +
    +
    +

    Glosario

    + +
    + +
    + +
    + + + + diff --git a/site/site/content/i18n/build-tools/en/templates.ftl b/site/site/content/i18n/build-tools/en/templates.ftl new file mode 100644 index 0000000..f0e62df --- /dev/null +++ b/site/site/content/i18n/build-tools/en/templates.ftl @@ -0,0 +1,83 @@ +# Site Information Documentation - English + +## Page Titles +site-info-title-server-summary = Server Routes Summary +site-info-title-server-reference = Server API Routes Reference +site-info-title-components-summary = Components Summary +site-info-title-components-reference = Components Reference +site-info-title-pages-summary = Pages Summary +site-info-title-pages-reference = Pages Reference +site-info-title-automation = Automation Guide +site-info-title-top-level = Documentation Overview + +## Common Labels +site-info-generated-at = Generated at +site-info-summary = Summary +site-info-table-of-contents = Table of Contents +site-info-overview = Overview +site-info-details = Details +site-info-title = Documentation + +## Technical Terms +site-info-routes = Routes +site-info-components = Components +site-info-pages = Pages +site-info-source = source +site-info-line = line +site-info-context = context +site-info-methods = Methods +site-info-handler = Handler +site-info-module = Module +site-info-parameters = Parameters +site-info-response-type = Response Type +site-info-requires-auth = Requires Auth +site-info-middleware = Middleware +site-info-description = Description +site-info-enabled = Enabled +site-info-priority = Priority +site-info-language = Language +site-info-keywords = Keywords + +## Statistics Labels +site-info-total-routes = Total Routes +site-info-total-components = Total Components +site-info-total-pages = Total Pages +site-info-auth-required = Auth Required +site-info-static-routes = Static Routes +site-info-api-routes = API Routes +site-info-languages = Languages + +## Navigation Labels +site-info-back-to-top = Back to Top +site-info-view-source = View Source +site-info-external-link = External Link + +## Status Labels +site-info-yes = Yes +site-info-no = No +site-info-optional = Optional +site-info-required = Required + +## Automation Labels +site-info-automation-overview = The Rustelo route documentation system generates comprehensive metadata that powers numerous automation scenarios. +site-info-rustelo-manager = Rustelo Manager Integration +site-info-mcp-server = MCP Server Integration +site-info-api-client = API Client Generation +site-info-testing = Testing Automation +site-info-docs-generation = Documentation Generation +site-info-ci-cd = CI/CD Integration + +## Format Labels +site-info-json-format = JSON Format +site-info-toml-format = TOML Format +site-info-markdown-format = Markdown Format + +## Branding Labels +site-info-made-with = Made with +site-info-rustelo-tagline = Modern Rust Web Framework +site-info-rustelo-description = Blazingly fast, type-safe web applications with Rust + Leptos + Axum + +## Error Messages +site-info-error-template-not-found = Template not found: { $template } +site-info-error-language-not-supported = Language not supported: { $language } +site-info-error-generation-failed = Documentation generation failed \ No newline at end of file diff --git a/site/site/content/i18n/build-tools/es/templates.ftl b/site/site/content/i18n/build-tools/es/templates.ftl new file mode 100644 index 0000000..728bec3 --- /dev/null +++ b/site/site/content/i18n/build-tools/es/templates.ftl @@ -0,0 +1,78 @@ +# Site Information Documentation - Español + +## Títulos de Página +site-info-title-server-summary = Resumen de Rutas del Servidor +site-info-title-server-reference = Referencia de API del Servidor +site-info-title-components-summary = Resumen de Componentes +site-info-title-components-reference = Referencia de Componentes +site-info-title-pages-summary = Resumen de Páginas +site-info-title-pages-reference = Referencia de Páginas +site-info-title-automation = Guía de Automatización +site-info-title-top-level = Vista General de Documentación + +## Etiquetas Comunes +site-info-generated-at = Generado el +site-info-summary = Resumen +site-info-table-of-contents = Índice de Contenidos +site-info-overview = Vista General +site-info-details = Detalles +site-info-title = Documentación + +## Términos Técnicos +site-info-routes = Rutas +site-info-components = Componentes +site-info-pages = Páginas +site-info-source = fuente +site-info-line = línea +site-info-context = contexto +site-info-methods = Métodos +site-info-handler = Manejador +site-info-module = Módulo +site-info-parameters = Parámetros +site-info-response-type = Tipo de Respuesta +site-info-requires-auth = Requiere Autenticación +site-info-middleware = Middleware +site-info-description = Descripción +site-info-enabled = Habilitado +site-info-priority = Prioridad +site-info-language = Idioma +site-info-keywords = Palabras Clave + +## Etiquetas de Estadísticas +site-info-total-routes = Total de Rutas +site-info-total-components = Total de Componentes +site-info-total-pages = Total de Páginas +site-info-auth-required = Autenticación Requerida +site-info-static-routes = Rutas Estáticas +site-info-api-routes = Rutas de API +site-info-languages = Idiomas + +## Etiquetas de Navegación +site-info-back-to-top = Volver Arriba +site-info-view-source = Ver Código Fuente +site-info-external-link = Enlace Externo + +## Etiquetas de Estado +site-info-yes = Sí +site-info-no = No +site-info-optional = Opcional +site-info-required = Requerido + +## Etiquetas de Automatización +site-info-automation-overview = El sistema de documentación de rutas de Rustelo genera metadatos completos que potencian numerosos escenarios de automatización. +site-info-rustelo-manager = Integración con Rustelo Manager +site-info-mcp-server = Integración con Servidor MCP +site-info-api-client = Generación de Cliente API +site-info-testing = Automatización de Pruebas +site-info-docs-generation = Generación de Documentación +site-info-ci-cd = Integración CI/CD + +## Etiquetas de Formato +site-info-json-format = Formato JSON +site-info-toml-format = Formato TOML +site-info-markdown-format = Formato Markdown + +## Mensajes de Error +site-info-error-template-not-found = Plantilla no encontrada: { $template } +site-info-error-language-not-supported = Idioma no soportado: { $language } +site-info-error-generation-failed = Falló la generación de documentación \ No newline at end of file diff --git a/site/site/content/pages/en/privacy.md b/site/site/content/pages/en/privacy.md new file mode 100644 index 0000000..d85d0b3 --- /dev/null +++ b/site/site/content/pages/en/privacy.md @@ -0,0 +1,66 @@ +# Privacy Policy +slug: "privacy-policy" +*Last updated: July 2026* + +This Privacy Policy explains how your personal data is collected, used and protected when you visit this website, subscribe to our communications or use our services, in accordance with Regulation (EU) 2016/679 (GDPR) and Spanish Organic Law 3/2018 (LOPDGDD). + +## Data Controller + +- **Controller:** Jesús Pérez Lorenzo +- **Contact / exercising your rights:** ontoref@jesusperez.pro +- **Jurisdiction:** Spain + +## Data We Process, Purpose and Legal Basis + +| Processing | Data | Purpose | Legal basis (GDPR art. 6) | +|---|---|---|---| +| Newsletter subscription | Email, name (optional) | Send you content updates and news | Consent — art. 6.1.a | +| Contact form | Name, email, message | Respond to your enquiry | Consent / pre-contractual steps — art. 6.1.a / 6.1.b | +| Service delivery | Contact and project details | Manage the service relationship | Contract performance — art. 6.1.b | +| Site security and operation | IP address, browser type, logs | Ensure security and prevent fraud | Legitimate interest — art. 6.1.f | + +For subscriptions we record, as **proof of consent**, the IP address and the date/time it was given. We do not use your data for automated decision-making or profiling. + +## Consent and Unsubscribe + +Newsletter subscription is **voluntary** and requires your explicit consent (unticked checkbox). You may **withdraw your consent at any time**, without affecting the lawfulness of prior processing, via the unsubscribe link in every message or by writing to ontoref@jesusperez.pro. Unsubscribing is immediate and free. + +## Processors and Disclosures + +**We do not sell, rent or share your personal data with third parties for commercial purposes.** Your data is processed on self-managed, self-hosted infrastructure. The following processors are involved, all within the European Economic Area: + +- **Hosting and infrastructure:** self-managed servers at Hetzner (Germany, EU). +- **Subscription management and delivery:** Listmonk, self-hosted on that infrastructure. +- **Email delivery:** Amazon SES (eu-west region, EU) as the outbound relay. + +We will only disclose data where legally required or to protect our legal rights. + +## International Transfers + +Processing takes place **within the EEA**. No international transfers outside the European Economic Area occur. + +## Retention + +Subscription data is kept **for as long as your subscription is active**; after unsubscribing it is deleted or anonymised without undue delay. Contact and service data is kept for the duration of the relationship and applicable legal periods. Security logs are kept only as long as strictly necessary. + +## Your Rights + +You may exercise, free of charge, your rights of **access, rectification, erasure, objection, restriction of processing, portability and withdrawal of consent** by writing to **ontoref@jesusperez.pro**. For subscriptions, you can additionally **export or delete your data yourself** from the management link in your communications. + +If you believe the processing does not comply with the law, you have the right to **lodge a complaint with the Spanish Data Protection Agency (AEPD)**, [www.aepd.es](https://www.aepd.es). + +## Cookies + +This site uses cookies for its operation and, with your consent, for analytics and marketing. You can configure or reject non-essential cookies from the site's cookie preferences panel. See the cookie policy for more information. + +## Security + +We implement appropriate technical and organisational measures to protect your data against unauthorised access, alteration, disclosure or destruction. + +## Changes to This Policy + +We may update this Policy from time to time. Significant changes will be notified through the website. The last-updated date appears at the top. + +## Contact + +For any questions about this Privacy Policy or the processing of your data: **ontoref@jesusperez.pro**. diff --git a/site/site/content/pages/es/privacy.md b/site/site/content/pages/es/privacy.md new file mode 100644 index 0000000..3a378f8 --- /dev/null +++ b/site/site/content/pages/es/privacy.md @@ -0,0 +1,66 @@ +# Política de Privacidad +slug: "politica-de-privacidad" +*Última actualización: julio de 2026* + +Esta Política de Privacidad explica cómo se recopilan, utilizan y protegen tus datos personales cuando visitas este sitio web, te suscribes a nuestras comunicaciones o utilizas nuestros servicios, conforme al Reglamento (UE) 2016/679 (RGPD) y la Ley Orgánica 3/2018 (LOPDGDD). + +## Responsable del Tratamiento + +- **Responsable:** Jesús Pérez Lorenzo +- **Contacto / ejercicio de derechos:** ontoref@jesusperez.pro +- **Ámbito:** España + +## Datos que Tratamos, Finalidad y Base Jurídica + +| Tratamiento | Datos | Finalidad | Base jurídica (RGPD art. 6) | +|---|---|---|---| +| Suscripción a novedades | Correo electrónico, nombre (opcional) | Enviarte novedades y actualizaciones de contenido | Consentimiento — art. 6.1.a | +| Formulario de contacto | Nombre, correo, mensaje | Responder a tu consulta | Consentimiento / medidas precontractuales — art. 6.1.a / 6.1.b | +| Prestación de servicios | Datos de contacto y del proyecto | Gestionar la relación de servicio | Ejecución de contrato — art. 6.1.b | +| Seguridad y funcionamiento del sitio | Dirección IP, tipo de navegador, logs | Garantizar la seguridad y prevenir fraude | Interés legítimo — art. 6.1.f | + +Para la suscripción registramos, como **prueba del consentimiento**, la dirección IP y la fecha/hora en que lo otorgaste. No usamos tus datos para decisiones automatizadas ni elaboración de perfiles. + +## Consentimiento y Baja + +La suscripción a novedades es **voluntaria** y requiere tu consentimiento explícito (casilla no premarcada). Puedes **retirar tu consentimiento en cualquier momento**, sin que ello afecte a la licitud del tratamiento previo, usando el enlace de baja de cada comunicación o escribiendo a ontoref@jesusperez.pro. La baja es inmediata y gratuita. + +## Encargados del Tratamiento y Cesiones + +**No vendemos, alquilamos ni cedemos tus datos personales a terceros con fines comerciales.** Tus datos se tratan en infraestructura propia y autogestionada. Intervienen los siguientes encargados, todos dentro del Espacio Económico Europeo: + +- **Alojamiento e infraestructura:** servidores propios en Hetzner (Alemania, UE). +- **Gestión de suscripciones y envío:** Listmonk, autohospedado en dicha infraestructura. +- **Entrega de correo:** Amazon SES (región eu-west, UE) como retransmisor de salida. + +Solo comunicaremos datos cuando exista obligación legal o para proteger nuestros derechos. + +## Transferencias Internacionales + +El tratamiento se realiza **dentro del EEE**. No se producen transferencias internacionales de datos fuera del Espacio Económico Europeo. + +## Conservación + +Conservamos los datos de suscripción **mientras mantengas tu suscripción activa**; tras la baja se eliminan o anonimizan sin demora indebida. Los datos de contacto y de servicios se conservan durante la relación y los plazos legales aplicables. Los logs de seguridad se conservan por el tiempo estrictamente necesario. + +## Tus Derechos + +Puedes ejercer, de forma gratuita, tus derechos de **acceso, rectificación, supresión, oposición, limitación del tratamiento, portabilidad y retirada del consentimiento**, escribiendo a **ontoref@jesusperez.pro**. Para la suscripción, además, puedes **exportar o eliminar tus datos por ti mismo** desde el enlace de gestión de tus comunicaciones. + +Si consideras que el tratamiento no se ajusta a la normativa, tienes derecho a **presentar una reclamación ante la Agencia Española de Protección de Datos (AEPD)**, [www.aepd.es](https://www.aepd.es). + +## Cookies + +Este sitio utiliza cookies para su funcionamiento y, con tu consentimiento, para analítica y marketing. Puedes configurar o rechazar las cookies no esenciales desde el panel de preferencias de cookies del sitio. Consulta la política de cookies para más información. + +## Seguridad + +Aplicamos medidas técnicas y organizativas apropiadas para proteger tus datos frente a accesos no autorizados, alteración, divulgación o destrucción. + +## Cambios en esta Política + +Podemos actualizar esta Política periódicamente. Notificaremos los cambios significativos a través del sitio web. La fecha de la última actualización figura al inicio. + +## Contacto + +Para cualquier cuestión sobre esta Política de Privacidad o el tratamiento de tus datos: **ontoref@jesusperez.pro**. diff --git a/site/site/content/projects/_spine/home-en.md b/site/site/content/projects/_spine/home-en.md new file mode 100644 index 0000000..80a29b7 --- /dev/null +++ b/site/site/content/projects/_spine/home-en.md @@ -0,0 +1,14 @@ + +# Sure-footed, and light on your feet. + +> Your project, your infra, your life: still on the path you set? + +## Three doors, one building + +- **[Developer](/projects/developer)** — Ask what breaks before you touch it. +- **[Infrastructure](/projects/infrastructure)** — Inherit the journey, not just the state. +- **[Personal](/projects/personal)** — See where your effort drifts. + +--- + +_What you put down, anyone can check._ diff --git a/site/site/content/projects/_spine/home-es.md b/site/site/content/projects/_spine/home-es.md new file mode 100644 index 0000000..a147cb6 --- /dev/null +++ b/site/site/content/projects/_spine/home-es.md @@ -0,0 +1,14 @@ + +# Pisa firme y anda ligero. + +> Tu proyecto, tu infra, tu vida: ¿sigues yendo a donde dijiste? + +## Tres puertas, el mismo edificio + +- **[Desarrollador](/proyectos/developer)** — Pregunta qué se rompe antes de tocar. +- **[Infraestructura](/proyectos/infrastructure)** — Hereda el viaje, no solo el estado. +- **[Persona](/proyectos/personal)** — Mira dónde tu esfuerzo se desvía. + +--- + +_Lo que dejas dicho, alguien podrá comprobarlo._ diff --git a/site/site/content/projects/en/use-cases/door-developer.md b/site/site/content/projects/en/use-cases/door-developer.md new file mode 100644 index 0000000..1a0de10 --- /dev/null +++ b/site/site/content/projects/en/use-cases/door-developer.md @@ -0,0 +1,45 @@ +--- +id: "door-developer" +title: "Developer" +slug: "developer" +subtitle: "Ask what breaks before you touch it." +excerpt: "The developer who lands in a codebase they did not build cannot tell" +author: "ontoref" +date: "2026-06-10" +published: true +featured: true +category: "use-cases" +tags: ["developer", "ontoref", "use-case"] +read_time: "3 min read" +sort_order: 1 +css_class: "category-use-cases" +category_description: "Use cases and implementations of ontoref" +category_published: true +--- + +> **AI writes faster and breaks more: +861% churn, ×3.4 incidents per PR, 31.3% of PRs merged without a single review. Team maturity does not protect you.** +> +> — [Faros · AI Engineering Report 2026 — The Acceleration Whiplash](https://www.faros.ai/blog/ai-acceleration-whiplash-takeaways) · [See what breaks](/blog?tag=developer) + +**Ask what a change ripples into before you touch it. `describe impact ` returns the blast radius of a change — which nodes, which invariants, which ADRs — before you write a line. The codebase you just inherited can tell you what is load-bearing, and your AI agent can mount that same context instead of guessing.** + +The developer who lands in a codebase they did not build cannot tell +what is load-bearing from what is incidental. README and docs are +incomplete or stale; the originating decisions live in the heads of +people who left. So the safe move is to touch as little as possible — +which means feedback, fixes, and improvements stall, because the cost +of an unknown blast radius falls on whoever moves first. + +The same gap is what makes AI agents dangerous on an unfamiliar +project: their context window cannot hold the whole codebase, so they +act on a reconstructed, often hallucinated model — changing things +that violate invariants nobody wrote down. Both the human and the +agent are flying without a map of consequence. + +--- + +[Posts](/blog?tag=developer) · [Recipes](/recipes?tag=developer) + +### The prize, once inside + +> When your reason for being is verifiable and not just declared, holding your course stops costing effort. diff --git a/site/site/content/projects/en/use-cases/door-developer.ncl b/site/site/content/projects/en/use-cases/door-developer.ncl new file mode 100644 index 0000000..3423152 --- /dev/null +++ b/site/site/content/projects/en/use-cases/door-developer.ncl @@ -0,0 +1,12 @@ +{ + id = "door-developer", + title = "Developer", + slug = "developer", + excerpt = "The developer who lands in a codebase they did not build cannot tell", + tags = ["developer", "ontoref", "use-case"], + page_route = "/projects/developer", + graph = { + implements = ["adr-009", "adr-012", "adr-015", "adr-046", "adr-035", "positioning-layer", "reach-vs-qualification"], + related_to = ["door-infrastructure", "door-personal"], + }, +} diff --git a/site/site/content/projects/en/use-cases/door-infrastructure.md b/site/site/content/projects/en/use-cases/door-infrastructure.md new file mode 100644 index 0000000..2fa5b73 --- /dev/null +++ b/site/site/content/projects/en/use-cases/door-infrastructure.md @@ -0,0 +1,43 @@ +--- +id: "door-infrastructure" +title: "Infrastructure" +slug: "infrastructure" +subtitle: "Inherit the journey, not just the state." +excerpt: "The operator who inherits infrastructure cannot see its trajectory." +author: "ontoref" +date: "2026-06-10" +published: true +featured: true +category: "use-cases" +tags: ["infrastructure", "ontoref", "use-case"] +read_time: "3 min read" +sort_order: 2 +css_class: "category-use-cases" +category_description: "Use cases and implementations of ontoref" +category_published: true +--- + +**Your infrastructure tells you where it is on its own trajectory — not just whether resources match. `describe state` returns each dimension's current → desired with the blocker and catalyst between them; `describe connections` maps how one action depends on the next. The infra is structured as a DAG that carries sense, journey, and perspective, with declared actions that reproduce — so HA and resilience rise to another plane: a property of a system that knows its own state and how it got there, not redundancy bolted on.** + +The operator who inherits infrastructure cannot see its trajectory. +IaC tools answer one question — does reality match the desired +resource set? — and that diff is real but shallow: it shows the +current shape, never how the infra reached it, which dimension is +mid-transition, what blocks the next step, or why one action depends +on the next. So evolving inherited infra means reverse-engineering +intent from config, under incident risk. + +Resilience suffers the same blindness. When HA is bolted on as +redundancy and failover without the system knowing its own state and +journey, recovery is a guess: you restore resources without restoring +understanding. The relationship between actions — the cohesion that +makes the whole behave predictably — lives in the heads of the people +who built it, and leaves when they do. + +--- + +[Posts](/blog?tag=provisioning) · [Recipes](/recipes?tag=provisioning) + +### The prize, once inside + +> When your reason for being is verifiable and not just declared, holding your course stops costing effort. diff --git a/site/site/content/projects/en/use-cases/door-infrastructure.ncl b/site/site/content/projects/en/use-cases/door-infrastructure.ncl new file mode 100644 index 0000000..8b2553c --- /dev/null +++ b/site/site/content/projects/en/use-cases/door-infrastructure.ncl @@ -0,0 +1,12 @@ +{ + id = "door-infrastructure", + title = "Infrastructure", + slug = "infrastructure", + excerpt = "The operator who inherits infrastructure cannot see its trajectory.", + tags = ["infrastructure", "ontoref", "use-case"], + page_route = "/projects/infrastructure", + graph = { + implements = ["adr-012", "adr-025", "adr-002", "adr-035", "positioning-layer", "reach-vs-qualification"], + related_to = ["door-developer", "door-personal"], + }, +} diff --git a/site/site/content/projects/en/use-cases/door-personal.md b/site/site/content/projects/en/use-cases/door-personal.md new file mode 100644 index 0000000..17a92d0 --- /dev/null +++ b/site/site/content/projects/en/use-cases/door-personal.md @@ -0,0 +1,42 @@ +--- +id: "door-personal" +title: "Personal" +slug: "personal" +subtitle: "See where your effort drifts." +excerpt: "A person's career, content, decisions, and intentions live as scattered" +author: "ontoref" +date: "2026-06-10" +published: true +featured: true +category: "use-cases" +tags: ["personal", "ontoref", "use-case"] +read_time: "3 min read" +sort_order: 3 +css_class: "category-use-cases" +category_description: "Use cases and implementations of ontoref" +category_published: true +--- + +**See where your effort drifts from your intent. Model your career and commitments as a graph with a declared desired state; `describe state` shows current → desired and what blocks the next step; `describe connections` surfaces which commitments move you toward where you said you want to go — and which are accidental. Your own trajectory becomes queryable: what have I done, what connects, what should I do next — answered, not felt. You don't end up where you didn't choose.** + +A person's career, content, decisions, and intentions live as scattered +prose — a CV in one doc, talks in a deck, the reasons in your head. +There is no way to ask 'what have I done, what does it connect to, what +should I do next', and — worse — no way to see drift: the commitments, +content, and opportunities that consume effort without moving you +toward anything you actually said you wanted. + +So the rudder slips silently. You end up where you did not choose, +doing what you did not want, having forgotten what motivated you — +not through a bad decision but through the accumulation of accidental +ones that no map ever flagged as off-course. PKM tools store and link +notes; none of them hold a declared desired state against which effort +can be checked for alignment. + +--- + +[Posts](/blog?tag=personal) · [Recipes](/recipes?tag=personal) + +### The prize, once inside + +> When your reason for being is verifiable and not just declared, holding your course stops costing effort. diff --git a/site/site/content/projects/en/use-cases/door-personal.ncl b/site/site/content/projects/en/use-cases/door-personal.ncl new file mode 100644 index 0000000..d9ecb3e --- /dev/null +++ b/site/site/content/projects/en/use-cases/door-personal.ncl @@ -0,0 +1,12 @@ +{ + id = "door-personal", + title = "Personal", + slug = "personal", + excerpt = "A person's career, content, decisions, and intentions live as scattered", + tags = ["personal", "ontoref", "use-case"], + page_route = "/projects/personal", + graph = { + implements = ["adr-012", "adr-025", "adr-035", "positioning-layer", "reach-vs-qualification"], + related_to = ["door-developer", "door-infrastructure"], + }, +} diff --git a/site/site/content/projects/es/use-cases/door-developer.md b/site/site/content/projects/es/use-cases/door-developer.md new file mode 100644 index 0000000..cfc0a7b --- /dev/null +++ b/site/site/content/projects/es/use-cases/door-developer.md @@ -0,0 +1,46 @@ +--- +id: "door-developer" +title: "Desarrollador" +slug: "developer" +subtitle: "Pregunta qué se rompe antes de tocar." +excerpt: "El desarrollador que aterriza en un código que no construyó no distingue lo estructural de lo incidental" +author: "ontoref" +date: "2026-06-10" +published: true +featured: true +category: "use-cases" +tags: ["developer", "ontoref", "use-case"] +read_time: "3 min read" +sort_order: 1 +css_class: "category-use-cases" +category_description: "Casos de uso e implementaciones de ontoref" +category_published: true +--- + +> **El AI escribe más rápido y rompe más: +861% de churn, ×3.4 incidentes por PR, 31.3% de PRs fusionados sin una sola revisión. La madurez del equipo no protege.** +> +> — [Faros · AI Engineering Report 2026 — The Acceleration Whiplash](https://www.faros.ai/blog/ai-acceleration-whiplash-takeaways) · [Comprueba qué se rompe](/blog?tag=developer) + +**Pregunta en qué repercute un cambio antes de tocarlo. `describe impact ` devuelve el radio de impacto de un cambio — qué nodos, qué invariantes, qué ADRs — antes de que escribas una línea. El código que acabas de heredar puede decirte qué es estructural, y tu agente de IA puede montar ese mismo contexto en lugar de adivinar.** + +El desarrollador que aterriza en un código que no construyó no puede +distinguir lo que es estructural de lo que es incidental. El README y +la documentación están incompletos o desactualizados; las decisiones +que lo originaron viven en la cabeza de quienes se marcharon. Así, lo +seguro es tocar lo menos posible — lo que significa que el feedback, +las correcciones y las mejoras se estancan, porque el coste de un radio +de impacto desconocido recae sobre quien se mueve primero. + +Esa misma brecha es lo que hace peligrosos a los agentes de IA en un +proyecto que no conocen: su ventana de contexto no puede contener todo +el código, así que actúan sobre un modelo reconstruido, a menudo +alucinado — cambiando cosas que violan invariantes que nadie escribió. +Tanto el humano como el agente vuelan sin un mapa de las consecuencias. + +--- + +[Posts](/blog?tag=developer) · [Recetas](/recetas?tag=developer) + +### El premio, al entrar + +> Cuando tu razón de ser es verificable y no solo declarada, mantener el rumbo deja de costar esfuerzo. diff --git a/site/site/content/projects/es/use-cases/door-infrastructure.md b/site/site/content/projects/es/use-cases/door-infrastructure.md new file mode 100644 index 0000000..f6f3270 --- /dev/null +++ b/site/site/content/projects/es/use-cases/door-infrastructure.md @@ -0,0 +1,45 @@ +--- +id: "door-infrastructure" +title: "Infraestructura" +slug: "infrastructure" +subtitle: "Hereda el viaje, no solo el estado." +excerpt: "El operador que hereda una infraestructura no puede ver su trayectoria." +author: "ontoref" +date: "2026-06-10" +published: true +featured: true +category: "use-cases" +tags: ["infrastructure", "ontoref", "use-case"] +read_time: "3 min read" +sort_order: 2 +css_class: "category-use-cases" +category_description: "Casos de uso e implementaciones de ontoref" +category_published: true +--- + +**Tu infraestructura te dice dónde está en su propia trayectoria — no solo si los recursos coinciden. `describe state` devuelve, para cada dimensión, actual → deseado con el bloqueador y el catalizador entre ambos; `describe connections` mapea cómo una acción depende de la siguiente. La infraestructura se estructura como un DAG que carga sentido, viaje y perspectiva, con acciones declaradas que se reproducen — de modo que la alta disponibilidad y la resiliencia ascienden a otro plano: una propiedad de un sistema que conoce su propio estado y cómo llegó hasta él, no una redundancia añadida por encima.** + +El operador que hereda una infraestructura no puede ver su trayectoria. +Las herramientas de IaC responden a una sola pregunta — ¿coincide la +realidad con el conjunto de recursos deseado? — y ese diff es real pero +superficial: muestra la forma actual, nunca cómo llegó la infraestructura +hasta ahí, qué dimensión está en plena transición, qué bloquea el +siguiente paso o por qué una acción depende de la siguiente. Así, +evolucionar una infraestructura heredada significa hacer ingeniería +inversa de la intención a partir de la configuración, bajo riesgo de +incidente. + +La resiliencia sufre la misma ceguera. Cuando la alta disponibilidad se +añade como redundancia y conmutación por error sin que el sistema conozca +su propio estado y viaje, la recuperación es una conjetura: restauras +recursos sin restaurar comprensión. La relación entre acciones — la +cohesión que hace que el conjunto se comporte de forma predecible — vive +en la cabeza de quienes la construyeron, y se marcha con ellos. + +--- + +[Posts](/blog?tag=provisioning) · [Recetas](/recetas?tag=provisioning) + +### El premio, al entrar + +> Cuando tu razón de ser es verificable y no solo declarada, mantener el rumbo deja de costar esfuerzo. diff --git a/site/site/content/projects/es/use-cases/door-personal.md b/site/site/content/projects/es/use-cases/door-personal.md new file mode 100644 index 0000000..df06df1 --- /dev/null +++ b/site/site/content/projects/es/use-cases/door-personal.md @@ -0,0 +1,43 @@ +--- +id: "door-personal" +title: "Persona" +slug: "personal" +subtitle: "Mira dónde tu esfuerzo se desvía." +excerpt: "La carrera, los contenidos, las decisiones y las intenciones de una persona viven dispersas" +author: "ontoref" +date: "2026-06-10" +published: true +featured: true +category: "use-cases" +tags: ["personal", "ontoref", "use-case"] +read_time: "3 min read" +sort_order: 3 +css_class: "category-use-cases" +category_description: "Casos de uso e implementaciones de ontoref" +category_published: true +--- + +**Mira dónde tu esfuerzo se desvía de tu intención. Modela tu carrera y tus compromisos como un grafo con un estado deseado declarado; `describe state` muestra actual → deseado y qué bloquea el siguiente paso; `describe connections` revela qué compromisos te acercan a donde dijiste que querías llegar — y cuáles son accidentales. Tu propia trayectoria se vuelve consultable: qué he hecho, qué conecta, qué debería hacer a continuación — respondido, no intuido. No acabas donde no elegiste.** + +La carrera, los contenidos, las decisiones y las intenciones de una +persona viven como prosa dispersa — un CV en un documento, las charlas +en una presentación, los motivos en tu cabeza. No hay forma de preguntar +'qué he hecho, con qué se conecta, qué debería hacer a continuación' y +—peor aún— no hay forma de ver la deriva: los compromisos, contenidos y +oportunidades que consumen esfuerzo sin acercarte a nada de lo que +realmente dijiste que querías. + +Así, el timón se escurre en silencio. Acabas donde no elegiste, haciendo +lo que no querías, habiendo olvidado lo que te motivaba — no por una +mala decisión, sino por la acumulación de decisiones accidentales que +ningún mapa señaló nunca como fuera de rumbo. Las herramientas PKM +almacenan y enlazan notas; ninguna sostiene un estado deseado declarado +contra el cual comprobar la alineación del esfuerzo. + +--- + +[Posts](/blog?tag=personal) · [Recetas](/recetas?tag=personal) + +### El premio, al entrar + +> Cuando tu razón de ser es verificable y no solo declarada, mantener el rumbo deja de costar esfuerzo. diff --git a/site/site/content/resources/en/decks/ontoref-ontologia-reflexion.md b/site/site/content/resources/en/decks/ontoref-ontologia-reflexion.md new file mode 100644 index 0000000..ae96a31 --- /dev/null +++ b/site/site/content/resources/en/decks/ontoref-ontologia-reflexion.md @@ -0,0 +1,88 @@ +--- +# Resource (deck) metadata +id: "ontoref-ontologia-reflexion" +title: "Ontoref: Ontology and Reflection" +slug: "ontoref-ontology-and-reflection" +subtitle: "The talk deck — ontology and reflection that live with the code" +excerpt: "The slides from the Ontoref talk: why a project's ontology and reflection should live with the code — versioned and verifiable. Navigable carousel + downloadable PDF." + +# Publication info +author: "Jesús Pérez" +date: "2026-03-19" +published: true +featured: true + +# Categorization +category: "decks" +tags: ["ontoref", "presentation", "slides", "ontology", "reflection", "developer"] + +# Card thumbnail (deck cover) +thumbnail: "/images/decks/ontoref-ontologia-reflexion/0.webp" +image_url: "/images/decks/ontoref-ontologia-reflexion/0.webp" + +# Display +sort_order: 1 +--- + + + +
    +
    + Slide 1 of 16 + + +
    +
    + 1 / 16 + + Download PDF +
    +
    + + +## About this deck + +These are the slides from the **Ontoref** talk: a project's ontology and reflection aren't separate documentation — they're artifacts that **live with the code**, versioned, typed and verifiable. Use the arrows (or the ← → keys) to navigate, and download the **PDF** for the full version. diff --git a/site/site/content/resources/es/decks/ontoref-ontologia-reflexion.md b/site/site/content/resources/es/decks/ontoref-ontologia-reflexion.md new file mode 100644 index 0000000..e21ba55 --- /dev/null +++ b/site/site/content/resources/es/decks/ontoref-ontologia-reflexion.md @@ -0,0 +1,88 @@ +--- +# Resource (deck) metadata +id: "ontoref-ontologia-reflexion" +title: "Ontoref: Ontología y Reflexión" +slug: "ontoref-ontologia-reflexion" +subtitle: "El deck de la charla — ontología y reflexión que viven con el código" +excerpt: "Las diapositivas de la presentación de Ontoref: por qué la ontología y la reflexión de un proyecto deben vivir con el código, versionadas y verificables. Carrusel navegable + PDF descargable." + +# Publication info +author: "Jesús Pérez" +date: "2026-03-19" +published: true +featured: true + +# Categorization +category: "decks" +tags: ["ontoref", "presentacion", "slides", "ontologia", "reflexion", "developer"] + +# Card thumbnail (deck cover) +thumbnail: "/images/decks/ontoref-ontologia-reflexion/0.webp" +image_url: "/images/decks/ontoref-ontologia-reflexion/0.webp" + +# Display +sort_order: 1 +--- + + + +
    +
    + Diapositiva 1 de 16 + + +
    +
    + 1 / 16 + + Descargar PDF +
    +
    + + +## Sobre este deck + +Estas son las diapositivas de la presentación de **Ontoref**: la ontología y la reflexión de un proyecto no son documentación aparte, sino artefactos que **viven con el código** — versionados, tipados y verificables. Usa las flechas (o el teclado ← →) para navegar, y descarga el **PDF** para la versión completa. diff --git a/site/site/i18n/locales/en/auth.ftl b/site/site/i18n/locales/en/auth.ftl new file mode 100644 index 0000000..dc283ef --- /dev/null +++ b/site/site/i18n/locales/en/auth.ftl @@ -0,0 +1,126 @@ +# Authentication - English + +# Authentication Actions +auth-sign-in = Sign In +auth-sign-up = Sign Up +auth-sign-out = Sign Out +auth-login = Login +auth-register = Register +auth-logout = Logout + +# User Information +auth-email = Email +auth-password = Password +auth-username = Username +auth-display-name = Display Name +auth-confirm-password = Confirm Password +auth-remember-me = Remember me +auth-forgot-password = Forgot your password? +auth-create-account = Create Account +auth-already-have-account = Already have an account? +auth-dont-have-account = Do not have an account? + +# Form Labels and Placeholders +auth-email-address = Email Address +auth-enter-email = Enter your email +auth-enter-username = Choose a username +auth-enter-password = Enter your password +auth-create-password = Create a strong password +auth-confirm-your-password = Confirm your password +auth-how-should-we-call-you = How should we call you? + +# Success Messages +auth-sign-in-success = Sign in successful +auth-registration-success = Registration successful +auth-logout-success = Logout successful + +# Validation Messages +auth-password-required = Password is required +auth-email-required = Email is required +auth-username-required = Username is required +auth-passwords-no-match = Passwords do not match +auth-passwords-match = Passwords match +auth-password-too-short = Password must be at least 8 characters +auth-invalid-email = Please enter a valid email address +auth-username-format = 3-50 characters, letters, numbers, underscores and hyphens only + +# Password Strength +auth-password-strength = Password strength: +auth-very-weak = Very Weak +auth-weak = Weak +auth-fair = Fair +auth-good = Good +auth-strong = Strong +auth-password-requirements = Must be at least 8 characters with uppercase, lowercase, number and special character + +# OAuth Providers +auth-continue-with = Or continue with +auth-sign-up-with = Or sign up with +auth-google = Google +auth-github = GitHub +auth-discord = Discord +auth-microsoft = Microsoft + +# Terms and Privacy +auth-agree-to-terms = I agree to the +auth-terms-of-service = Terms of Service +auth-privacy-policy = Privacy Policy +auth-and = and + +# Error Messages +auth-invalid-credentials = Invalid email or password +auth-user-not-found = User not found +auth-email-already-exists = An account with this email already exists +auth-username-already-exists = This username is already taken +auth-account-not-verified = Please verify your email before signing in +auth-account-suspended = Your account has been suspended +auth-rate-limit-exceeded = Too many attempts. Please try again later +auth-network-error = Network error. Please check your connection +auth-login-failed = Login failed +auth-registration-failed = Registration failed +auth-session-expired = Your session has expired. Please sign in again +auth-invalid-token = Invalid authentication token +auth-token-expired = Your authentication token has expired +auth-insufficient-permissions = You do not have permission to perform this action +auth-oauth-error = OAuth authentication error +auth-database-error = A database error occurred. Please try again +auth-internal-error = An internal error occurred. Please try again +auth-validation-error = Please check your input and try again +auth-authentication-failed = Authentication failed +auth-server-error = Server error occurred. Please try again later +auth-request-failed = Request failed. Please try again +auth-unknown-error = An unknown error occurred + +# OTP / Passwordless login +auth-otp-modal-title = Login or Sign up +auth-otp-modal-subtitle = Enter your email to continue +auth-otp-check-inbox = Check your inbox +auth-otp-sent-to = Enter the verification code we sent to { $email } +auth-otp-resend = Resend email +auth-otp-code-placeholder = Code +auth-otp-code-expired = Code expired. Request a new one. +auth-otp-code-invalid = Invalid code. { $remaining -> + [one] { $remaining } attempt remaining. + *[other] { $remaining } attempts remaining. +} +auth-otp-too-many-attempts = Too many failed attempts. Request a new code. +auth-otp-success = Signed in successfully. +auth-otp-continue = Continue +auth-otp-resend-countdown = Resend in { $seconds }s +auth-otp-resend-now = Resend code +auth-otp-back-to-email = ← Change email +auth-modal-close = Close + +# GDPR consent step (new accounts) +auth-gdpr-title = Create your account +auth-gdpr-description = Before we create your account, we need your consent to process your data in accordance with our Privacy Policy. +auth-gdpr-accept-required = I accept the Terms of Service and Privacy Policy (required) +auth-gdpr-accept-marketing = I agree to receive occasional newsletters and product updates (optional) +auth-gdpr-submit = Create account + +# Email: OTP verification code +auth-email-otp-subject = Your verification code +auth-email-otp-title = Your verification code +auth-email-otp-subtitle = Use this code to sign in: +auth-email-otp-expire-msg = This code expires in %minutes% minutes. +auth-email-otp-ignore-msg = If you didn't request this, you can safely ignore this email. \ No newline at end of file diff --git a/site/site/i18n/locales/en/common.ftl b/site/site/i18n/locales/en/common.ftl new file mode 100644 index 0000000..71653a9 --- /dev/null +++ b/site/site/i18n/locales/en/common.ftl @@ -0,0 +1,24 @@ +# Common translations shared across the site +common-welcome = Welcome to Jesús Pérez Professional Services +common-not-found = Page not found. +common-home = Home +common-about = About +common-services = Services +common-blog = Blog +common-contact = Contact +common-user = User +common-main-desc = Rust Developer & Infrastructure Consultant +pages = Pages + +# Common navigation +common-user-page = User page for ID: { $id } + +# Language selection +common-language = Language +common-select-language = Select Language +common-english = English +common-spanish = Spanish + +# Image viewer +common-expand-image = Expand +common-close = Close diff --git a/site/site/i18n/locales/en/components/footer.ftl b/site/site/i18n/locales/en/components/footer.ftl new file mode 100644 index 0000000..960489d --- /dev/null +++ b/site/site/i18n/locales/en/components/footer.ftl @@ -0,0 +1,13 @@ +# Footer component translations - English + +footer-links = Links +# Footer Links +footer-privacy = Privacy +footer-legal = Legal +footer-copyright-name = Jesús Pérez +footer-scroll-to-top = Scroll to top +footer-social-links = Connect with me + +# Footer Attribution +footer-built-with = Built with +footer-hosting = Hosted on { $host } diff --git a/site/site/i18n/locales/en/components/forms.ftl b/site/site/i18n/locales/en/components/forms.ftl new file mode 100644 index 0000000..002f4ca --- /dev/null +++ b/site/site/i18n/locales/en/components/forms.ftl @@ -0,0 +1,66 @@ +# Form components translations - English + +# Common form labels +form-name-label = Name +form-email-label = Email +form-subject-label = Subject +form-message-label = Message +form-required-field = * +form-submit-button = Submit +form-send-message = Send Message +form-sending = Sending... +form-cancel = Cancel +form-reset = Reset + +# Placeholders +form-name-placeholder = Your full name +form-email-placeholder = your.email@ontoref.dev +form-subject-placeholder = Brief description of your inquiry +form-message-placeholder = Please describe your message in detail... + +# Validation messages +form-validation-name-required = Name is required +form-validation-name-too-long = Name must be less than 100 characters +form-validation-email-required = Email is required +form-validation-email-invalid = Please enter a valid email address +form-validation-subject-required = Subject is required +form-validation-subject-too-long = Subject must be less than 200 characters +form-validation-message-required = Message is required +form-validation-message-too-long = Message must be less than 5000 characters + +# Form states +form-success-title = Message sent successfully! +form-success-message = Thank you for your message! We'll get back to you soon. +form-error-title = Failed to send message +form-error-generic = An error occurred while sending your message. Please try again. + +# Contact form specific +contact-form-title = Contact Form +contact-form-description = Get in touch with us + +# Support form specific +support-form-title = Support Request +support-form-description = Need help? Send us a support request +support-form-priority-label = Priority +support-form-category-label = Category +support-form-priority-low = Low +support-form-priority-medium = Medium +support-form-priority-high = High +support-form-priority-urgent = Urgent +support-form-category-general = General Inquiry +support-form-category-technical = Technical Support +support-form-category-billing = Billing +support-form-category-bug-report = Bug Report +support-form-category-feature-request = Feature Request + +# Subscription form specific +subscription-form-title = Newsletter Subscription +subscription-form-description = Stay updated with our latest news and updates +subscription-form-subscribe = Subscribe +subscription-form-subscribing = Subscribing... +subscription-form-success = Successfully subscribed to our newsletter! +subscription-form-already-subscribed = You're already subscribed to our newsletter. + +# Additional form elements +form-select-placeholder = Select an option... +form-validation-field-too-long = Field is too long \ No newline at end of file diff --git a/site/site/i18n/locales/en/components/language_selector.ftl b/site/site/i18n/locales/en/components/language_selector.ftl new file mode 100644 index 0000000..2bb7a81 --- /dev/null +++ b/site/site/i18n/locales/en/components/language_selector.ftl @@ -0,0 +1,43 @@ +# Language Selector component translations - English + +# Language Selector Button +lang-selector-button = Language Selector +lang-selector-tooltip = Select Language +lang-selector-current = Current language: English + +# Language Options +lang-english = English +lang-spanish = Spanish +lang-english-code = EN +lang-spanish-code = ES + +# Language Display Names +lang-en-display = English +lang-es-display = Español +lang-english-display = English +lang-spanish-display = Español + +# Accessibility +lang-selector-aria-label = Language selection menu +lang-selector-aria-expanded = Language menu expanded +lang-selector-aria-haspopup = Language options available +lang-selector-menu-role = Language selection menu + +# States +lang-selector-loading = Loading languages... +lang-selector-disabled = Language selection unavailable +lang-current-selection = Currently selected language + +# Icons and UI +lang-icon-alt = Language icon +lang-dropdown-icon = Dropdown arrow +lang-checkmark-alt = Selected language indicator + +# Toggle Component +lang-toggle-title = Switch language +lang-toggle-switch-to = Switch to { $language } +lang-toggle-current = Current: { $language } + +# Mobile +lang-mobile-label = Language +lang-mobile-current = Language: { $code } \ No newline at end of file diff --git a/site/site/i18n/locales/en/components/logo.ftl b/site/site/i18n/locales/en/components/logo.ftl new file mode 100644 index 0000000..f654251 --- /dev/null +++ b/site/site/i18n/locales/en/components/logo.ftl @@ -0,0 +1,32 @@ +# Logo component translations - English + +# Basic Logo +logo-alt-text = Logo +logo-brand-alt = Company Logo +logo-icon-alt = Company Icon + +# Loading States +logo-loading = Loading logo... +logo-error = Logo unavailable + +# Brand Header +brand-header-title = Brand Header +brand-header-subtitle = Professional Services + +# Logo Link +logo-link-title = Go to homepage +logo-link-home = Return to homepage + +# Navbar Logo +navbar-logo-alt = Navigation logo +navbar-logo-title = Return to homepage + +# Logo Variants +logo-light-alt = Logo (Light Theme) +logo-dark-alt = Logo (Dark Theme) +logo-horizontal-alt = Horizontal Logo +logo-vertical-alt = Vertical Logo + +# Accessibility +logo-aria-label = Company logo and homepage link +logo-loading-aria = Logo is loading \ No newline at end of file diff --git a/site/site/i18n/locales/en/components/navigation.ftl b/site/site/i18n/locales/en/components/navigation.ftl new file mode 100644 index 0000000..00ec360 --- /dev/null +++ b/site/site/i18n/locales/en/components/navigation.ftl @@ -0,0 +1,70 @@ +# Navigation component translations - English + +# Main Navigation +nav-home = Home +nav-about = About +nav-services = Services +nav-projects = Projects +nav-domains = Domains +nav-blog = Blog +nav-prescriptions = Prescriptions +nav-contact = Contact +nav-admin = Admin + +# Admin Navigation +nav-dashboard = Dashboard +nav-settings = Settings +nav-users = Users +nav-content = Content + +# User Status +nav-welcome-user = Welcome, { $name } +nav-signed-in-as = Signed in as { $email } +av-last-login = Last login: { $date } + +# Authentication Actions +nav-sign-in = Sign In +nav-sign-up = Sign Up +nav-sign-out = Sign Out + +# Language +nav-language = Language +nav-select-language = Select Language + +# Mobile Menu +nav-menu-close = Close menu +nav-menu-open = Open menu + +# Additional Menu Items +nav-account = Account +nav-docs = Docs +nav-blocks = Blocks +nav-theme = Theme +nav-pages = Pages + +# Mobile Menu Controls +nav-mobile-controls = Controls +nav-mobile-theme-language = Theme & Language +nav-menu-toggle = Toggle mobile menu +nav-external-link = External link + +# Accessibility +nav-menu-aria-controls = navbar-collapse +nav-menu-aria-expanded = Mobile menu expanded +nav-menu-aria-label = Toggle mobile menu + +# Menu States +nav-menu-loading = Loading menu... +nav-menu-error = Unable to load menu + +# Command Palette +nav-palette-placeholder = Search... +nav-palette-no-results = No results +nav-palette-hint-navigate = navigate +nav-palette-hint-select = select +nav-palette-hint-close = close + +# Floating Pill +nav-pill-aria-label = Quick navigation +nav-pill-show = Show navigation menu +nav-pill-hide = Hide navigation menu \ No newline at end of file diff --git a/site/site/i18n/locales/en/cookies.ftl b/site/site/i18n/locales/en/cookies.ftl new file mode 100644 index 0000000..b5817df --- /dev/null +++ b/site/site/i18n/locales/en/cookies.ftl @@ -0,0 +1,12 @@ +# Cookie consent - English +cookies-banner-text = We use cookies to help this site function, understand service usage, and support marketing efforts. +cookies-manage = Manage Cookies +cookies-reject = Reject non-essential +cookies-accept-all = Accept all +cookies-policy-link = Cookie Policy +cookies-policy-suffix = for more info. +cookies-essential = Essential +cookies-analytics = Analytics +cookies-marketing = Marketing +cookies-save-prefs = Save preferences +cookies-cancel = Cancel diff --git a/site/site/i18n/locales/en/manager/cli.ftl b/site/site/i18n/locales/en/manager/cli.ftl new file mode 100644 index 0000000..13b8e9a --- /dev/null +++ b/site/site/i18n/locales/en/manager/cli.ftl @@ -0,0 +1,95 @@ +# Page Manager CLI - English Translations +# All keys MUST have mgr- prefix for shared locales system + +# Welcome and headers +mgr-cli-title = Rustelo Page Manager (for now Static Content) +mgr-cli-description = Manage and generate new pages with templates, routes, and translations +mgr-cli-project-root = Project root: {$path} +mgr-cli-available-languages = Available languages: {$languages} + +# Prompts +mgr-prompt-page-name = Page name (PascalCase, e.g., 'ContactForm', 'AboutUs'): +mgr-prompt-page-name-help = Use PascalCase naming. This will be your component name. +mgr-prompt-template-select = Select page template: +mgr-prompt-template-help = Choose the template that best fits your page needs +mgr-prompt-languages-select = Select languages: +mgr-prompt-languages-help = Choose which languages to generate translations for +mgr-prompt-route-path = Route path: +mgr-prompt-route-path-help = URL path for this page (must start with '/') +mgr-prompt-auto-scaffold = Auto-scaffold page files? +mgr-prompt-auto-scaffold-help = Generate mod.rs, unified.rs, and FTL translation files +mgr-prompt-custom-priority = Set custom route priority? +mgr-prompt-custom-priority-help = Higher priority routes are matched first (default: 1.0) +mgr-prompt-priority-value = Route priority (0.1 - 10.0): +mgr-prompt-custom-patterns = Customize i18n translation patterns? +mgr-prompt-custom-patterns-help = Advanced: customize the prefixes used for translation keys +mgr-prompt-patterns-value = I18n patterns (comma-separated): +mgr-prompt-patterns-example = Example: page-gen-contact-, contact-form- +mgr-prompt-final-confirm = Generate page with this configuration? + +# Configuration summary +mgr-config-summary = 📋 Configuration Summary: +mgr-config-name = Name: {$name} +mgr-config-module = Module: {$module} +mgr-config-template = Template: {$template} ({$description}) +mgr-config-languages = Languages: {$languages} +mgr-config-route = Route: {$route} +mgr-config-auto-scaffold = Auto-scaffold: {$enabled} + +# Labels +mgr-label-name = Name +mgr-label-module = Module +mgr-label-template = Template +mgr-label-languages = Languages +mgr-label-route = Route +mgr-label-auto-scaffold = Auto-scaffold + +# Text values +mgr-text-yes = Yes +mgr-text-no = No + +# Status messages +mgr-status-generating = 🏗️ Generating page... +mgr-status-cancelled = 🚫 Generation cancelled. +mgr-status-completed = ✨ Page generation completed successfully! + +# Next steps +mgr-next-steps = 💡 Next steps: +mgr-next-steps-with-scaffold = 💡 Next steps:\n 1. Run 'cargo build' to compile the new page\n 2. Check the generated FTL files and customize translations\n 3. Implement your page logic in the unified.rs file\n 4. Test your page at {$route} +mgr-next-steps-without-scaffold = 💡 Next steps:\n 1. Run 'cargo build' to update route generation\n 2. Implement the page component manually\n 3. Add the component export to pages/src/lib.rs + +# Warnings +mgr-warning-page-exists = Page module '{$module}' already exists. +mgr-warning-overwrite-confirm = Overwrite existing page? +mgr-warning-cannot-proceed = Cannot proceed with existing page. Choose a different name or allow overwriting. + +# Error messages +mgr-error-project-root = Could not find project root. Run this command from within a Rustelo project. +mgr-error-generator-init = Failed to initialize page generator +mgr-error-page-name = Failed to get page name +mgr-error-template-selection = Failed to get template selection +mgr-error-language-selection = Failed to get language selection +mgr-error-route-path = Failed to get route path +mgr-error-auto-scaffold = Failed to get auto-scaffold preference +mgr-error-priority = Failed to get priority preference +mgr-error-priority-value = Failed to get route priority +mgr-error-patterns = Failed to get i18n patterns preference +mgr-error-patterns-value = Failed to get i18n patterns +mgr-error-confirmation = Failed to get confirmation +mgr-error-generation = Page generation failed +mgr-error-at-least-one-language = At least one language must be selected + +# Validation errors +mgr-validation-name-empty = Page name cannot be empty +mgr-validation-name-case = Page name must start with an uppercase letter (PascalCase) +mgr-validation-name-chars = Page name must contain only ASCII letters and numbers +mgr-validation-name-length = Page name too long (max 50 characters) +mgr-validation-name-reserved = '{$name}' is a reserved name, please choose another +mgr-validation-path-empty = Route path cannot be empty +mgr-validation-path-start = Route path must start with '/' +mgr-validation-path-end = Route path cannot end with '/' (except root) +mgr-validation-path-consecutive = Route path cannot contain consecutive slashes +mgr-validation-path-chars = Route path must contain only ASCII characters and no spaces +mgr-validation-path-reserved = '{$path}' conflicts with reserved paths +mgr-validation-priority-range = Priority must be between 0.1 and 10.0 +mgr-validation-priority-number = Priority must be a valid number \ No newline at end of file diff --git a/site/site/i18n/locales/en/manager/dashboard.ftl b/site/site/i18n/locales/en/manager/dashboard.ftl new file mode 100644 index 0000000..8cd0694 --- /dev/null +++ b/site/site/i18n/locales/en/manager/dashboard.ftl @@ -0,0 +1,83 @@ +# Dashboard Menu Translations - English + +# Dashboard +dashboard-title = Rustelo Development Dashboard +dashboard-description = Comprehensive development toolkit for Rustelo projects + +# Categories +category-content-name = Content Management +category-content-description = Create, edit, and manage content +category-development-name = Development Tools +category-development-description = Development and debugging utilities +category-system-name = System & APIs +category-system-description = System information and API management +category-deployment-name = Build & Deploy +category-deployment-description = Build, test, and deployment tools + +# Content Management Actions +action-create-page-name = Create New Page +action-create-page-description = Generate a new page with templates, routes, and translations +action-edit-content-name = Edit Static Content +action-edit-content-description = Edit existing pages, blog posts, and recipes +action-manage-translations-name = Manage Translations +action-manage-translations-description = Edit FTL translation files for multiple languages +action-content-preview-name = Content Preview +action-content-preview-description = Preview content with hot reloading + +# Development Tools Actions +action-dev-server-name = Start Dev Server +action-dev-server-description = Launch development server with hot reload +action-css-watch-name = CSS Watch Mode +action-css-watch-description = Watch and rebuild CSS files automatically +action-browser-logs-name = Browser Logs +action-browser-logs-description = Collect and view browser console logs +action-code-format-name = Format Code +action-code-format-description = Format Rust code with rustfmt and clippy + +# System & APIs Actions +action-show-apis-name = Show Available APIs +action-show-apis-description = List all available API endpoints and routes +action-system-info-name = System Information +action-system-info-description = Display system info, environment, and configuration +action-route-inspector-name = Route Inspector +action-route-inspector-description = Inspect and validate route configurations +action-config-manager-name = Configuration Manager +action-config-manager-description = View and edit configuration files + +# Build & Deploy Actions +action-build-prod-name = Production Build +action-build-prod-description = Build for production deployment +action-run-tests-name = Run Tests +action-run-tests-description = Execute all tests and quality checks +action-quality-check-name = Quality Check +action-quality-check-description = Run comprehensive quality checks +action-deploy-status-name = Deployment Status +action-deploy-status-description = Check deployment status and health + +# Quick Actions +action-help-name = Help +action-help-description = Show keyboard shortcuts and help +action-refresh-name = Refresh +action-refresh-description = Refresh dashboard data +action-settings-name = Settings +action-settings-description = Dashboard settings and preferences +action-quit-name = Quit +action-quit-description = Exit dashboard + +# UI Elements +ui-items = Items +ui-items-focused = Items [FOCUSED] +ui-subitems = Sub-items +ui-subitems-focused = Sub-items [FOCUSED] +ui-quick-actions = Quick Actions +ui-action-result = Result +ui-language-selector = Language +ui-chat-prompt = Ask AI Assistant +ui-chat-placeholder = Press '/' to start typing or Tab to focus... +ui-send-button = Send +ui-clear-button = Clear +ui-ai-button = AI +ui-copy-button = Copy +ui-space-switch = Space Switch Panel +ui-chat-focus = Chat +ui-f12-language = F12 Language \ No newline at end of file diff --git a/site/site/i18n/locales/en/manager/test_now.ftl b/site/site/i18n/locales/en/manager/test_now.ftl new file mode 100644 index 0000000..2f54a8e --- /dev/null +++ b/site/site/i18n/locales/en/manager/test_now.ftl @@ -0,0 +1,6 @@ +# Auto-generated FTL file for test-now (en) +# Generated at: 2025-08-28 09:48:11 UTC by rustelo-page +# TODO: Customize these translations + +page-gen-test-now-title = Page Title +page-gen-test-now-description = Auto-generated page description. diff --git a/site/site/i18n/locales/en/pages/about-us.ftl b/site/site/i18n/locales/en/pages/about-us.ftl new file mode 100644 index 0000000..71894fd --- /dev/null +++ b/site/site/i18n/locales/en/pages/about-us.ftl @@ -0,0 +1,9 @@ +# About us page specific translations - English + +# Navigation +nav-about-us = About Us + +# Page Metadata +about-us-page-title = About Us +about-us-page-subtitle = The people and the practice behind Ontoref. +about-us-page-description = Ontoref is designed and maintained by Jesús Pérez, a Rust developer and provisioning-systems architect with over three decades in software. It grows from hands-on work with self-hosted, open-source infrastructure and from a conviction that systems should carry their own reasoning: an explicit ontology, recorded decisions, and disciplined reflection. We build small, sovereign tools that organizations can truly own and control, and we share the protocol and its practice openly with a growing community of collaborators. diff --git a/site/site/i18n/locales/en/pages/about.ftl b/site/site/i18n/locales/en/pages/about.ftl new file mode 100644 index 0000000..0844b82 --- /dev/null +++ b/site/site/i18n/locales/en/pages/about.ftl @@ -0,0 +1,68 @@ +# About page specific translations - English + +# Page Metadata +about-page-title = About Jesús Pérez +about-page-description = Learn more about my background and experience as a Rust developer and infrastructure consultant +about-page-keywords = about, experience, background, rust, infrastructure, development + +# Header Section +about-page-subtitle = Rust developer and infrastructure consultant passionate about self-hosted solutions, open source technologies, and building resilient systems that organizations can truly own and control. + +# Journey Section +about-my-journey = My Journey +about-journey-paragraph-1 = With over three decade of experience in software development, I have witnessed the evolution of technology from monolithic architectures to cloud-native solutions. What drives me today is helping organizations reduce their dependencies on external services while building more resilient, performant systems. +about-journey-paragraph-2 = My focus on Rust stems from its unique combination of performance, safety, and expressiveness. It's a language that enables building systems that are both fast and reliable, making it perfect for the self-hosted infrastructure solutions I advocate for. +about-journey-paragraph-3 = The Web3 space, particularly the Polkadot ecosystem, represents the future of decentralized computing. I work with teams to leverage these technologies in practical, business-oriented ways. + +# Philosophy Section +about-philosophy-approach = Philosophy & Approach +about-open-source-first = Open source first - reduce vendor lock-in +about-self-hosted-solutions = Self-hosted solutions for better control +about-performance-security-design = Performance and security by design +about-pragmatic-solutions = Pragmatic solutions over perfection +about-knowledge-transfer-empowerment = Knowledge transfer and team empowerment + +# Technical Expertise Section +about-technical-expertise = Technical Expertise +about-technologies-work-with = Technologies and practices I work with regularly +about-rust-development-title = Rust Development +about-systems-programming-item = Systems programming +about-web-applications-item = Web applications (Axum, Leptos) +about-cli-tools-automation = CLI tools and automation +about-performance-optimization-item = Performance optimization +about-memory-safety-concurrency = Memory safety & concurrency +about-infrastructure-title = Infrastructure +about-kubernetes-container = Kubernetes & container orchestration +about-cicd-pipeline-design = CI/CD pipeline design +about-monitoring-observability = Monitoring & observability +about-self-hosted-alternatives = Self-hosted alternatives +about-security-compliance = Security & compliance +about-web3-blockchain-title = Web3 & Blockchain +about-polkadot-ecosystem-dev = Polkadot ecosystem development +about-substrate-blockchain = Substrate blockchain framework +about-smart-contract-dev = Smart contract development +about-dapp-architecture = DApp architecture +about-cross-chain-communication = Cross-chain communication + +# Why Self-Hosted Section +about-why-self-hosted-open-source = Why Self-Hosted & Open Source? +about-control-privacy = 🔒 Control & Privacy +about-control-privacy-desc = Your data stays on your infrastructure. No third-party dependencies for critical business functions. +about-cost-effectiveness = 💰 Cost Effectiveness +about-cost-effectiveness-desc = Eliminate recurring SaaS costs. Scale without per-user pricing or arbitrary limits. +about-resilience = 🛡️ Resilience +about-resilience-desc = Reduce single points of failure. Your systems keep running regardless of external service issues. +about-performance = ⚡ Performance +about-performance-desc = Optimize for your specific use case. No compromises for multi-tenant SaaS platforms. +about-customization = 🔧 Customization +about-customization-desc = Modify and extend tools to fit your workflow. No waiting for feature requests. +about-community = 🌍 Community +about-community-desc = Benefit from transparent development, peer review, and collaborative improvement. + +# Call to Action +about-lets-work-together = Let's Work Together +about-work-together-desc = Ready to build more resilient, self-controlled infrastructure? Let's discuss your project. +about-start-project = Start a Project +about-get-in-touch = Get In Touch +about-work-request-url = /work-request +about-contact-url = /contact \ No newline at end of file diff --git a/site/site/i18n/locales/en/pages/activities.ftl b/site/site/i18n/locales/en/pages/activities.ftl new file mode 100644 index 0000000..4c3bc4d --- /dev/null +++ b/site/site/i18n/locales/en/pages/activities.ftl @@ -0,0 +1,59 @@ +# Activities page translations - English + +# Page header +activities-page-title = Activities +activities-page-description = Talks, workshops, and Slidev presentations +activities-title = Activities +activities-subtitle = Talks, workshops, and Slidev presentations + +# Category page header +activities-category-page-title = Activities +activities-category-page-description = Activities filtered by category. +activities-category-page-keywords = activities, talks, workshops, presentations, category + +# Navigation +nav-activities = Activities + +# Event metadata +activities-event-type-talk = Talk +activities-event-type-workshop = Workshop +activities-event-type-presentation = Presentation +activities-event-date = Event date +activities-event-location = Location + +# Slides embed +activities-view-slides = View Slides +activities-slides-fullscreen = Open fullscreen + +# Download section +activities-download-pdf = Download PDF +activities-login-required = Sign in to download the PDF for this activity. +activities-login-to-download = Sign in +activities-questionnaire-required = Complete the evaluation to unlock the PDF download. +activities-questionnaire-link = Complete evaluation +activities-download-ready = Your download is ready. +activities-completion-recorded = Your evaluation has been recorded. Thank you! + +# Internal questionnaire +activities-questionnaire-title = Activity Evaluation +activities-questionnaire-submit = Submit Evaluation +activities-questionnaire-submitting = Submitting… +activities-questionnaire-required-fields = Please answer all required questions: { $fields } +activities-questionnaire-success = Thank you for your evaluation. You can now download the PDF. + +# Repository link +activities-repository = Source code + +# Gallery +activities-gallery-title = Gallery +activities-gallery-alt = Activity photo { $index } + +# Content grid +activities-no-items-in-category = No activities found in this category. +activities-view-all = View all activities +activities-featured = Featured +activities-load-more = Load more + +# Error states +activities-error-loading = Error loading activities. Please try again. +activities-error-loading-slides = Could not load slides. diff --git a/site/site/i18n/locales/en/pages/blog.ftl b/site/site/i18n/locales/en/pages/blog.ftl new file mode 100644 index 0000000..04c93b2 --- /dev/null +++ b/site/site/i18n/locales/en/pages/blog.ftl @@ -0,0 +1,77 @@ +# Blog page specific translations - English + +# Page Header +blog-page-title = Blog & Insights +blog-page-description = Thoughts, experiences, and technical insights from the world of Rust, infrastructure and Web3. +blog-page-subtitle = Thoughts, experiences, and technical insights from the world of Rust, infrastructure and Web3. + +# Category Page Header +blog-category-page-title = Blog & Insights +blog-category-page-description = Articles filtered by category - dive deep into specific topics and areas of expertise. +blog-category-page-keywords = blog, category, articles, filtered, topics + +# Blog Content +blog-featured-post = Featured Post +blog-recent-posts = Recent Posts +blog-read-full-article = Read full article → +blog-read-more = Read more → +blog-back-to-all-posts = ← Back to all posts + +# Blog Loading +blog-error-loading = Error loading blog posts. Please try again later. +blog-loading-content = Loading blog content... +blog-error-loading-featured-posts = Error loading featured posts +blog-error-loading-recent-posts = Error loading recent posts +blog-error-loading-prescriptions-grid = Error loading prescriptions grid +blog-stay-updated = Stay Updated +blog-newsletter-description = Get notified about new posts, insights, and industry developments. +blog-email-placeholder = Your email address +blog-subscribe = Subscribe +blog-browse-blog-posts = Browse Blog Posts +blog-load-more-posts = Load More Posts + +# Placeholder content for when data is being loaded +blog-featured-posts-placeholder = Loading featured posts... +blog-recent-posts-placeholder = Loading recent posts... +blog-subscription-form-placeholder = Loading subscription form... + +# Category browsing section +blog-browse-categories-title = Browse by Category +blog-browse-categories-description = Explore articles organized by technology and expertise areas. + +# Sample post content +blog-sample-post-date = December 15, 2024 +blog-sample-post-title = Building Scalable Rust Applications +blog-sample-post-excerpt = Learn how to architect and build Rust applications that scale from prototype to production with proper error handling and performance optimization. +blog-sample-post-url = /blog/rust-scalable-applications + +# Category URLs and names +blog-category-rust-url = /blog/rust +blog-category-infrastructure-url = /blog/infrastructure +blog-category-web3-url = /blog/web3 +blog-category-rust-name = Rust Development +blog-category-infrastructure-name = Infrastructure +blog-category-web3-name = Web3 & Blockchain + +# Content grid messages +blog-no-posts-in-category = No posts found in this category +blog-view-all-posts = View All Posts + +# Category-specific titles and descriptions +blog-cat-architecture-title = Architecture & Design +blog-cat-architecture-desc = System design patterns, architectural decisions, and software architecture best practices + +blog-cat-devops-title = DevOps & Infrastructure +blog-cat-devops-desc = DevOps practices, CI/CD pipelines, infrastructure automation, and deployment strategies + +blog-cat-infrastructure-title = Infrastructure Management +blog-cat-infrastructure-desc = Server infrastructure, cloud platforms, containerization, and infrastructure as code + +blog-cat-rust-title = Rust Development +blog-cat-rust-desc = Rust programming language tips, patterns, performance optimization, and ecosystem insights + +blog-cat-web3-title = Web3 & Blockchain +blog-cat-web3-desc = Blockchain development, smart contracts, DeFi protocols, and decentralized technologies + +# Generic content messages (used by UnifiedContentGrid) +# Moved to content-kinds.ftl to avoid duplication diff --git a/site/site/i18n/locales/en/pages/contact.ftl b/site/site/i18n/locales/en/pages/contact.ftl new file mode 100644 index 0000000..d989a8d --- /dev/null +++ b/site/site/i18n/locales/en/pages/contact.ftl @@ -0,0 +1,77 @@ +# Contact page specific translations - English + +# Page Metadata +contact-page-title = Get In Touch +contact-page-description = Get in touch with me to discuss your project or ask questions about my services +contact-page-keywords = contact, reach out, inquiry, consultation + +# Page Header +contact-page-subtitle = Ready to discuss your project or have questions about my services?
    I'd love to hear from you and explore how we can work together. + +# Contact Methods +contact-lets-connect = Let's Connect +contact-email-contact = Email +contact-telegram-contact = Telegram +contact-schedule-call = Schedule a Call + +# Call Options +contact-consultation-30min = 30-minute consultation +contact-book-time-slot = Book a time slot → +contact-best-for-detailed = Best for detailed project discussions +contact-quick-questions = Quick questions and discussions + +# Professional Networks +contact-professional-networks = Professional Networks +contact-linkedin-profile = 🔗 LinkedIn Profile +contact-git-profile = 💻 Git Profile +contact-read-my-blog = 📝 Read My Blog +contact-technical-prescriptions = 🔧 Technical Prescriptions + +# Response Information +contact-response-time = Response Time +contact-response-time-text = I typically respond within 24 hours on weekdays. For urgent matters, please mention it in your message. + +# Contact Form +contact-send-message = Send a Message +contact-contact-form-description = Tell me about your project, goals, timeline, and any specific requirements. +contact-send-message-btn = Send Message + +# Frequently Asked Questions +contact-frequently-asked-questions = Frequently Asked Questions +contact-faq-what-projects = What types of projects do you work on? +contact-faq-what-projects-answer = I specialize in Rust development, self-hosted infrastructure, and Web3 projects. This includes web applications, system tools, CI/CD pipelines, Kubernetes deployments, and Polkadot ecosystem development. +contact-faq-remote-teams = Do you work with remote teams? +contact-faq-remote-teams-answer = Yes, I work exclusively with remote teams and have experience collaborating across different time zones. I'm comfortable with async communication and use modern collaboration tools. +contact-faq-pricing-approach = What's your approach to project pricing? +contact-faq-pricing-answer = Pricing depends on the project scope, complexity, and timeline. I offer both hourly rates for consulting and fixed-price quotes for well-defined projects. Let's discuss your specific needs. +contact-faq-existing-projects = Can you help with existing projects? +contact-faq-existing-projects-answer = Absolutely! I can help with code reviews, architecture improvements, performance optimization, adding new features, or migrating to more resilient infrastructure solutions. + +# Additional Ways to Connect +contact-other-ways-connect = Other Ways to Connect +contact-project-consultation = Project Consultation +contact-consultation-help = Need help defining project requirements or technical architecture? +contact-request-quote = Request Quote +contact-mentoring-training = Mentoring & Training +contact-mentoring-help = Looking for one-on-one guidance or team training? +contact-learn-more = Learn More +contact-speaking-events = Speaking & Events +contact-speaking-help = Interested in having me speak at your event or conference? +contact-get-in-touch = Get In Touch + +# URLs for navigation +contact-blog-url = /blog +contact-prescriptions-url = /prescriptions +contact-work-request-url = /work-request +contact-services-url = /services + +# Contact form labels and placeholders +contact-name-label = Name +contact-name-placeholder = Your full name +contact-email-label = Email +contact-email-placeholder = your.email@ontoref.dev +contact-subject-label = Subject +contact-subject-placeholder = Brief description of your inquiry +contact-message-label = Message +contact-message-placeholder = Tell me about your project, goals, timeline, and any specific requirements... +contact-submit-success = Your message has been sent! I'll get back to you within 24 hours. diff --git a/site/site/i18n/locales/en/pages/content.ftl b/site/site/i18n/locales/en/pages/content.ftl new file mode 100644 index 0000000..3bd8db2 --- /dev/null +++ b/site/site/i18n/locales/en/pages/content.ftl @@ -0,0 +1,157 @@ +# Content Kind Translations +# Dynamic translations for different content types + +# Content kind names +content-kind-blog = Blog +content-kind-recetas = Recipes +content-kind-content = Content +content-kind-tutoriales = Tutorials + +# Content kind descriptions +content-kind-blog-description = Technical articles and insights +content-kind-recetas-description = Solutions and best practices +content-kind-content-description = General content and documentation +content-kind-tutoriales-description = Step-by-step tutorials + +# Page titles +content-kind-blog-page-title = { $title -> + [blog] Blog - { $site_name } + *[other] { $title } - { $site_name } +} + +content-kind-recetas-page-title = { $title -> + [recetas] Recipes - { $site_name } + *[other] { $title } - { $site_name } +} + +# PostViewer (content-detail) fallback. Static: the shell head lookup is a +# plain key get, so it cannot pass the post title. The real per-post +# binding is framework-side — see `ontoref rustelo publish-contract` known_gaps +# (post-detail-head-title-unbound). This removes the broken [postviewer-page-title] +# bracket with a generic, valid title until that lands. +postviewer-page-title = Article +postviewer-page-description = An article from this site +postviewer-page-keywords = article, blog + +# Static page <head> titles. The route components look up keys named +# <componentlowercased>-page-title (e.g. HomePage -> homepage-page-title); the +# pre-existing home-page-title / about-page-title etc. used a different convention +# and never resolved. These aliases match what the components request. ContentIndex +# is shared across kinds, so its title is generic (per-kind index titles, like +# per-post titles, are bound in build_page_seo, not via a static key). +homepage-page-title = Ontoref — Structure that stays true +homepage-page-description = A project, an infrastructure, a life: are you still going where you said you were going? What you leave stated, someone will be able to verify. +aboutpage-page-title = About Jesús Pérez +servicespage-page-title = Professional Services +contactpage-page-title = Get In Touch +notfound-page-title = Page Not Found +contentindex-page-title = Index + +# Navigation labels +content-nav-back-to = { $content_kind -> + [blog] Back to Blog + [recetas] Back to Recipes + [content] Back to Content + [tutoriales] Back to Tutorials + *[other] Back to { $content_kind } +} + +# Feature support +content-supports-features = { $content_kind -> + [blog] true + [tutoriales] true + *[other] false +} + +content-supports-emojis = { $content_kind -> + [recetas] true + *[other] false +} + +# Style modes +content-style-mode = { $content_kind -> + [blog] row + [recetas] grid + [content] row + [tutoriales] row + *[other] row +} + +# CTA views +content-cta-view = { $content_kind -> + [blog] BlogCTAView + [recetas] RecetasCTAView + [tutoriales] TutorialesCTAView + *[other] DefaultCTAView +} + +# Shared content messages (used by UnifiedContentGrid) +content-no-items-found = No items found +content-all-categories = All Categories + +# Filter UI labels (used by UnifiedCategoryFilter) +content-filter-categories = Categories +content-filter-tags = Tags +content-filter-all = All +content-filter-show-all = Show All + +# Content Manager UI +content-manager-title = Content Management +content-manager-new-content = New Content +content-manager-filter-type = Content Type: +content-manager-filter-language = Language: +content-manager-all-types = All Types +content-manager-all-languages = All Languages +content-manager-refresh = Refresh + +# Content Manager Table Headers +content-manager-table-title = Title +content-manager-table-type = Type +content-manager-table-language = Language +content-manager-table-status = Status +content-manager-table-updated = Updated +content-manager-table-actions = Actions + +# Content Manager Status +content-manager-status-published = Published +content-manager-status-draft = Draft + +# Content Manager Actions +content-manager-action-edit = Edit +content-manager-action-publish = Publish +content-manager-action-unpublish = Unpublish +content-manager-action-delete = Delete + +# Content Manager Notes +content-manager-note-label = Note: +content-manager-note-text = This is a mockup of the content management interface. Full functionality with API integration will be implemented in future updates. + +# Content Manager Sample Data +content-manager-sample-category = General +content-manager-sample-blog-title = Sample Blog Post +content-manager-sample-blog-content = # Sample Blog Post\n\nThis is a sample blog post for demonstration purposes. +content-manager-sample-blog-excerpt = A sample blog post demonstrating the content management system. +content-manager-sample-recipes-title = Sample Recipe +content-manager-sample-recipes-content = # Sample Recipe\n\n## Ingredients\n- Sample ingredient 1\n- Sample ingredient 2\n\n## Instructions\n1. Sample step 1\n2. Sample step 2 +content-manager-sample-recipes-excerpt = A sample recipe demonstrating the content management system. + +# Language Display Names +language-en = English +language-es = Español + +# Content Type Display Names (fallbacks if not in content-kinds) +content-kind-recipes = Recipes + +content-featured-post = Featured Post +content-recent-posts = Recent Posts +content-read-full-article = Read full article → +content-read-more = Read more → +content-back-to-all-posts = ← Back to all posts + +content-failed-to-load = Failed to load content +content-view-all = View all +content-related-view-all = Related: View all + +# Catalog of verifiables index (kind `catalog`) +catalog-page-title = Catalog of Verifiables — ontoref +catalog-page-description = Typed agent operations and the validators that gate them (ADR-024 / ADR-026 / ADR-050). diff --git a/site/site/i18n/locales/en/pages/content_graph.ftl b/site/site/i18n/locales/en/pages/content_graph.ftl new file mode 100644 index 0000000..865262a --- /dev/null +++ b/site/site/i18n/locales/en/pages/content_graph.ftl @@ -0,0 +1,5 @@ +content-graph-related = Related +content-graph-ontology-context = Ontology context +content-graph-title = Knowledge Graph +content-graph-subtitle = An interactive map of all content, ontology nodes, and architectural decisions — and how they connect. +content-graph-hint = Click a node to navigate. Scroll to zoom. Drag to pan. Content nodes link to their page. diff --git a/site/site/i18n/locales/en/pages/expedientes.ftl b/site/site/i18n/locales/en/pages/expedientes.ftl new file mode 100644 index 0000000..fcfc1c2 --- /dev/null +++ b/site/site/i18n/locales/en/pages/expedientes.ftl @@ -0,0 +1,24 @@ +# Expedientes page translations - English + +# Page header +expedientes-page-title = Case files +expedientes-page-description = Case-file library +expedientes-title = Case files +expedientes-subtitle = Case-file library + +# Category page header +expedientes-category-page-title = Case files +expedientes-category-page-description = Case files filtered by category. +expedientes-category-page-keywords = case files, cases, records, category + +# Navigation +nav-expedientes = Case files + +# Content grid +expedientes-no-items-in-category = No case files found in this category. +expedientes-view-all = View all case files +expedientes-featured = Featured +expedientes-load-more = Load more + +# Error states +expedientes-error-loading = Error loading case files. Please try again. diff --git a/site/site/i18n/locales/en/pages/home.ftl b/site/site/i18n/locales/en/pages/home.ftl new file mode 100644 index 0000000..e4c52c2 --- /dev/null +++ b/site/site/i18n/locales/en/pages/home.ftl @@ -0,0 +1,60 @@ +# Home page — Ontoref · English + +home-page-title = Ontoref — Operational ontology with reflection +home-page-description = A project, an infrastructure, a life: are you still going where you said you were going? What you leave stated, someone will be able to verify. +home-page-keywords = ontology, reflection, protocol, rust, mcp, nickel, knowledge-graph, software-architecture + +# Hero — headline/sub/close project from spine.ncl (home_spine.ftl); only the badge +# stays hand-maintained. Dead home-hero-* / home-cta-adopt / home-tagline-door- +# {developer,infrastructure,personal} removed 2026-06-20 (template renders spine-* +# and spine-door-*-label instead). +home-hero-badge = Operational ontology, with reflection · v0.1.7 + +# Hero tagline rotator — only the 'all' door label is hand-maintained here. +home-tagline-door-all = All + +home-cta-explore = Explore the Protocol +home-cta-github = Source + +# Below-the-fold capabilities band. Title holds both poles (what a project IS and +# what it leaves verifiable); never reifies "the graph" as the giver — see +# .coder/2026-06-20-home-positioning-projection.plan.md (ondaod: ontology-vs-reflection). +home-pillars-title = What a project that knows itself can do +home-pillars-subtitle = Three capabilities from one typed substrate — ask what breaks, verify who changed it, reach it from every actor. Independent of any runtime. + +home-pillar-impact-label = ASK BEFORE YOU TOUCH +home-pillar-impact-title = Blast radius, before the diff +home-pillar-impact-desc = Ask what a change ripples into — which nodes, which invariants, which ADRs — before you write a line. The codebase you just inherited can tell you what is load-bearing. + +home-pillar-witness-label = WITNESS SEAM · Ed25519 +home-pillar-witness-title = A trail anyone can verify +home-pillar-witness-desc = Every change to authoritative state clears typed preconditions, then deposits an Ed25519 witness in a replayable, content-addressed oplog DAG. Any party verifies the history against actor keys — without trusting whoever produced it. + +home-pillar-shared-label = ONE GRAPH · CLI · MCP · CI +home-pillar-shared-title = Same ontology, every actor +home-pillar-shared-desc = People, agents, and CI read one typed graph — from the terminal, from the IDE, from the pipeline. Knowledge that fails the build when it drifts, not at the next audit. + +# How it works +home-how-title = Adopt in Any Project +home-how-subtitle = Three commands. No lock-in. + +home-how-step1-title = ontoref setup +home-how-step1-desc = Scaffolds .ontology/ and reflection/ with typed NCL files. Idempotent — safe to re-run after upgrades. + +home-how-step2-title = ontoref describe project +home-how-step2-desc = Query your project's identity, axioms, tensions, and FSM state from the terminal. Actor-agnostic — same output for developer, agent, or CI. + +home-how-step3-title = ontoref run start +home-how-step3-desc = Start the daemon for HTTP UI and MCP. Configure via ~/.config/ontoref/config.ncl. The protocol works without it. + +# Architecture +home-arch-title = Architecture +home-arch-img-light = /images/ontoref-architecture.svg +home-arch-img-dark = /images/projects/ontoref_architecture-dark.svg +home-arch-caption = Protocol layers — entry point → declarative NCL → operational Nushell → optional Rust runtime. Each layer is independently testable. + +# CTA +home-cta-title = Structure Your Project's Knowledge +home-cta-desc = Install the CLI, run ontoref setup, and your project gains machine-queryable invariants, living ADRs, actor-aware operational modes, and optional daemon context sharing. +home-cta-primary = Get Started +home-cta-secondary = Read the Protocol diff --git a/site/site/i18n/locales/en/pages/home_spine.ftl b/site/site/i18n/locales/en/pages/home_spine.ftl new file mode 100644 index 0000000..89e674d --- /dev/null +++ b/site/site/i18n/locales/en/pages/home_spine.ftl @@ -0,0 +1,30 @@ +# GENERATED from .ontoref/positioning/spine.ncl by gen-spine-pages.nu — do not edit by hand. +spine-hero = Sure-footed, and light on your feet. +spine-sub = Your project, your infra, your work, your life: still on the path you set? +spine-close = What you put down, anyone can check. +spine-prize = When your reason for being is verifiable and not just declared, holding your course stops costing effort. +spine-doors-heading = Four doors, one building +spine-door-cta = See more +spine-door-blog-cta = Posts +spine-door-recipes-cta = Recipes +spine-door-developer-label = Developer +spine-door-developer-line = Ask what breaks before you touch it. +spine-door-developer-href = /domains/developer +spine-door-developer-blog = /blog?tag=developer +spine-door-developer-recipes = /recipes?tag=developer +spine-door-infrastructure-label = Infrastructure +spine-door-infrastructure-line = Inherit the journey, not just the state. +spine-door-infrastructure-href = /domains/infrastructure +spine-door-infrastructure-blog = /blog?tag=provisioning +spine-door-infrastructure-recipes = /recipes?tag=provisioning +spine-door-personal-label = Personal +spine-door-personal-line = See where your effort drifts. +spine-door-personal-href = /domains/personal +spine-door-personal-blog = /blog?tag=personal +spine-door-personal-recipes = /recipes?tag=personal +spine-door-authoring-label = Authoring +spine-door-authoring-line = Diffuse the whole Work, not loose fragments. +spine-door-authoring-href = /domains/authoring +spine-door-authoring-blog = /blog?tag=knowledge-works +spine-door-authoring-recipes = /recipes?tag=knowledge-works +spine-hook-developer = AI writes faster and breaks more: +861% churn, ×3.4 incidents per PR, 31.3% of PRs merged without a single review. Team maturity does not protect you. diff --git a/site/site/i18n/locales/en/pages/kogral.ftl b/site/site/i18n/locales/en/pages/kogral.ftl new file mode 100644 index 0000000..ec1ea60 --- /dev/null +++ b/site/site/i18n/locales/en/pages/kogral.ftl @@ -0,0 +1,56 @@ +# Kogral page — Local-First Knowledge Graphs + +# Hero +kogral-badge = v0.1.0 | Foundation Complete | 97+ tests +kogral-tagline = Local-First Knowledge Management +kogral-page-title = Knowledge Graphs For Developer Teams +kogral-page-subtitle = Structured knowledge management that scales from solo projects to organizations. Config-driven architecture for capturing architectural decisions, coding guidelines, and reusable patterns in version-controlled markdown. 100% Rust. Zero compromises. + +# Problems +kogral-problems-title = The 4 Problems It Solves +kogral-problem-1-title = Scattered Documentation +kogral-problem-1-desc = Notes in Notion, decisions in Slack, guidelines in wikis — all disconnected. KOGRAL unifies with git-native markdown + MCP. +kogral-problem-2-title = No Version Control for Decisions +kogral-problem-2-desc = Architectural decisions lost in chat history. No traceability for why code exists. KOGRAL provides Git-tracked ADRs with full history and @file:line code references. +kogral-problem-3-title = Lost Context Over Time +kogral-problem-3-desc = New team members can't find past decisions. Outdated documentation causes repeated mistakes. KOGRAL offers semantic search and relationship tracking. +kogral-problem-4-title = Isolated Team Knowledge +kogral-problem-4-desc = Every project reinvents the wheel. Patterns can't be shared across teams. KOGRAL's multi-graph architecture enables shared organizational knowledge with project-specific overrides. + +# How It Works +kogral-how-title = How It Works +kogral-how-markdown-title = Markdown-Native +kogral-how-markdown-desc = YAML frontmatter for metadata. Wikilinks for relationships. Code references @file.rs:42. Logseq-compatible format. Human-readable, Git-friendly. +kogral-how-search-title = Semantic Search +kogral-how-search-desc = Text search across all nodes. Vector embeddings for semantic queries via stratum-embeddings with local fastembed. Cloud APIs supported (OpenAI, Claude, Ollama via rig-core). +kogral-how-config-title = Config-Driven +kogral-how-config-desc = Nickel schemas with validation. 3 modes: dev, prod, test. Storage Factory with config-driven backend dispatch. TypeDialog fragment composition pattern. No hardcoded paths or settings. +kogral-how-mcp-title = Claude Code Integration +kogral-how-mcp-desc = MCP server (Model Context Protocol). 10 tools in kogral/* namespace (search, blocks, todos, cards). 6 resources via kogral:// URI scheme. 2 prompts for project summarization and related content discovery. +kogral-how-logseq-title = Logseq Blocks +kogral-how-logseq-desc = Outliner-style hierarchical blocks. Task status: TODO, DOING, DONE, LATER, WAITING. Inline #tags and custom properties. Block references and page refs. Round-trip fidelity for Logseq import/export. +kogral-how-events-title = Event System & Orchestration +kogral-how-events-desc = NATS JetStream event publishing. EventingStorage decorator wraps any backend. KogralEvent types for node and graph operations. stratum-orchestrator pipeline triggering. SurrealDB dual-engine support. + +# Components +kogral-components-title = Core Components +kogral-comp-core-role = Core library +kogral-comp-cli-role = 13 commands + blocks +kogral-comp-mcp-role = 10 tools, kogral:// +kogral-comp-config-role = Nickel schemas +kogral-comp-storage-role = Filesystem, SurrealDB, Memory +kogral-comp-embeddings-role = fastembed + rig-core providers +kogral-comp-nodes-role = Note, Decision, Guideline, Pattern, Journal, Execution +kogral-comp-relations-role = relates_to, depends_on, implements, extends, supersedes, explains +kogral-comp-multigraph-role = Project + Shared +kogral-comp-events-role = JetStream EventingStorage +kogral-comp-logseq-role = Tasks, tags, properties, refs +kogral-comp-orchestration-role = stratum-orchestrator pipelines + +# Tech stack +kogral-tech-stack-title = Technology Stack + +# CTA +kogral-cta-title = Ready to organize your knowledge? +kogral-cta-subtitle = 3 Crates | 97+ Tests | 11K LOC | 100% Rust +kogral-cta-explore = Get Started diff --git a/site/site/i18n/locales/en/pages/legal.ftl b/site/site/i18n/locales/en/pages/legal.ftl new file mode 100644 index 0000000..d823d02 --- /dev/null +++ b/site/site/i18n/locales/en/pages/legal.ftl @@ -0,0 +1,68 @@ +# Legal page specific translations - English + +# Page Metadata +legal-page-title = Legal Information +legal-page-description = Legal information and terms of use +legal-page-keywords = legal, terms, information + +# Page Headers +legal-title = Legal Notice +legal-subtitle = Terms of use and legal information +legal-effective-date = Effective Date: January 2025 +legal-intro = This Legal Notice governs your use of the website and services provided by Jesús Pérez, Rust Developer and Infrastructure Consultant. + +# Website Information Section +legal-website-information = Website Information +legal-owner = Owner +legal-owner-name = Jesús Pérez +legal-owner-value = Jesús Pérez +legal-activity = Activity +legal-business-activity = Rust Development & Infrastructure Consulting +legal-activity-value = Rust Development & Infrastructure Consulting +legal-location = Location +legal-business-location = Remote (Spain) +legal-location-value = Remote +legal-country-value = Spain +legal-contact = Contact +legal-email = Email +legal-contact-email = hello@ontoref.dev +legal-contact-value = hello@ontoref.dev + +# Terms of Use Section +legal-terms-of-use = Terms of Use +legal-acceptance-of-terms = Acceptance of Terms +legal-acceptance-of-terms-text = By accessing and using this website, you accept and agree to be bound by the terms and provisions of this agreement. + +# Permitted Use +legal-permitted-use = Permitted Use +legal-permitted-use-text = This website is provided for informational purposes about our professional services. You may: +legal-browse-and-read = Browse and read the content +legal-contact-for-business = Contact us for business inquiries +legal-share-with-attribution = Share our content with proper attribution + +# Prohibited Use +legal-prohibited-use = Prohibited Use +legal-prohibited-use-text = You may not: +legal-unlawful-purpose = Use the website for any unlawful purpose +legal-unauthorized-access = Attempt to gain unauthorized access to our systems +legal-reproduce-without-permission = Reproduce, duplicate, or copy content without permission + +# Intellectual Property +legal-intellectual-property = Intellectual Property +legal-intellectual-property-text = All content on this website, including text, images, code examples, and design elements, is owned by Jesús Pérez or licensed for use. + +# Technical Information +legal-technical-information = Technical Information +legal-technical-information-text = This website is built with: +legal-rust-and-leptos = Rust and Leptos framework +legal-server-side-rendering = Server-side rendering (SSR) +legal-webassembly-functionality = WebAssembly for client-side functionality + +# Contact Information +legal-contact-information = Contact Information +legal-contact-legal-text = For legal inquiries or to report violations of these terms: +legal-contact-email-label = Email +legal-email-value = hello@ontoref.dev +legal-address = Address +legal-business-address = Remote (Spain) +legal-address-value = Remote (Spain) diff --git a/site/site/i18n/locales/en/pages/not_found.ftl b/site/site/i18n/locales/en/pages/not_found.ftl new file mode 100644 index 0000000..9c5ca39 --- /dev/null +++ b/site/site/i18n/locales/en/pages/not_found.ftl @@ -0,0 +1,28 @@ +# 404 Not Found page translations + +# Page Metadata +not-found-page-title = Page Not Found +not-found-page-description = The requested page could not be found +not-found-page-keywords = 404, not found, error + +# Page title and content +not-found-title = Page Not Found +not-found-description = Sorry, the page you are looking for doesn't exist or has been moved. + +# Navigation buttons +not-found-go-home = Go Home +not-found-contact-us = Contact Us +not-found-back-to-blog = Back to Blog +not-found-back-to-prescriptions = Back to Prescriptions +not-found-sign-in = Sign In + +# Navigation URLs +not-found-home-url = / +not-found-contact-url = /contact +not-found-blog-url = /blog +not-found-prescriptions-url = /prescriptions +not-found-login-url = /login + +# Additional helpful text (optional) +not-found-section-title = What can you do? +not-found-help-text = You can try going back to the home page or contact us if you believe this is an error. \ No newline at end of file diff --git a/site/site/i18n/locales/en/pages/ontoref.ftl b/site/site/i18n/locales/en/pages/ontoref.ftl new file mode 100644 index 0000000..d7ddc39 --- /dev/null +++ b/site/site/i18n/locales/en/pages/ontoref.ftl @@ -0,0 +1,132 @@ +ontoref-adoption-subtitle = ontoref setup wires up any new or existing project — idempotent scaffold with optional auth key bootstrap. +ontoref-adoption-title = Adopt in Any Project +ontoref-architecture-title = Architecture +ontoref-badge = Protocol + Runtime · v0.1.0 +ontoref-crates-title = Crates & Tooling +ontoref-cta-explore = Explore the Protocol +ontoref-cta-subtitle = Start with ontoref setup. Your project gains machine-queryable invariants, living ADRs, actor-aware operational modes, and a daemon that shares context across every actor in real time. +ontoref-cta-title = Structure That Remembers Why +ontoref-duality-title = Ontology & Reflection — Yin and Yang +ontoref-footer-tagline = Protocol + Runtime. Zero enforcement. One graph per project. +ontoref-graph-desc = Force-directed graph of the live ontology. Nodes are typed (Axiom · Tension · Practice) and polarized (Yang · Yin · Spiral). Click any node to open its detail panel — artifacts, connections, NCL source. +ontoref-graph-title = The UI in Action -> Graph View +ontoref-hero-coda = Protocol + Runtime. Zero enforcement. +ontoref-hero-desc = — encode what a system IS (invariants, tensions, constraints) and where it IS GOING (state dimensions, transition conditions, membranes) in machine-queryable directed acyclic graphs. Software projects, personal operational systems, agent contexts — same three files, same protocol. Ask a change's blast radius before you make it, verify every mutation against actor keys, share one typed context live across CLI, IDE, and CI. One protocol for developers, agents, CI, and individuals. +ontoref-hero-highlight = Ontology + Reflection + Daemon + MCP +ontoref-layer-adopt-desc = Each project maintains its own .ontology/ data. Ontoref provides the schemas, modules, and migration scripts. Zero lock-in. +ontoref-layer-adopt-label = ADOPTION LAYER · Per-project +ontoref-layer-decl-desc = Strong types, contracts, enums. Fails at definition time, not at runtime. +ontoref-layer-decl-label = DECLARATIVE LAYER · Nickel +ontoref-layer-entry-desc = Single entry point per project. Detects actor (developer/agent/CI), acquires lock, dispatches to correct Nu module. +ontoref-layer-entry-label = ENTRY POINT · Bash → Nu +ontoref-layer-graph-desc = The project knows what it knows. Actor-agnostic. Machine-queryable via nickel export. +ontoref-layer-graph-label = KNOWLEDGE GRAPH · .ontology/ +ontoref-layer-op-desc = Typed pipelines over structured data. No text streams. +ontoref-layer-op-label = OPERATIONAL LAYER · Nushell +ontoref-layer-runtime-desc = Optional persistent daemon. NCL export cache, HTTP UI, MCP server, actor registry, notification store, search engine, SurrealDB persistence. Never a protocol requirement. +ontoref-layer-runtime-label = RUNTIME LAYER · Rust + axum +ontoref-mcp-backlog-desc = <li><strong>pre_commit</strong> — pre-commit hook POLLs <code>GET /notifications/pending?token=X&project=Y</code>; blocks git commit until all acked</li><li><strong>drift</strong> — schema drift detected between codebase and ontology</li><li><strong>ontology_drift</strong> — emitted by passive observer with missing/stale/drift/broken counts after 15s debounce</li><li>Fail-open: if daemon is unreachable, pre-commit hook passes — commits are never blocked by daemon downtime</li><li>Ack via UI or <code>POST /notifications/ack</code>; custom notifications via <code>POST /{slug}/notifications/emit</code></li><li>Action buttons in notifications can link to any dashboard page</li> +ontoref-mcp-backlog-title = Notification Barrier +ontoref-mcp-core-desc = ontoref-daemon is an optional persistent process. It caches NCL exports, serves the web UI, exposes the MCP tool surface, maintains an actor registry, stores notifications, indexes everything for search, and optionally persists to SurrealDB. Auth is opt-in: all surfaces (CLI, UI, MCP) exchange a project key for a UUID v4 session token via <code>POST /sessions</code>; CLI injects <code>ONTOREF_TOKEN</code> as Bearer automatically. It never changes the protocol — it accelerates and shares access to it. Configured via <code>~/.config/ontoref/config.ncl</code> (Nickel, type-checked); edit interactively with <code>ontoref config-edit</code>. Started via NCL pipe bootstrap: <code>ontoref-daemon-boot</code>. +ontoref-mcp-knowledge-desc = <li>Enabled with <code>--db</code> feature flag and <code>--db-url ws://...</code></li><li>Connects via WebSocket at startup — 5s timeout, <strong>fail-open</strong> (daemon runs without it)</li><li>Seeds ontology tables from local NCL files on startup and on file changes</li><li>Persists: actor sessions, seeded ontology tables, search index, notification history</li><li>Without <code>--db</code>: DashMap-backed in-memory, process-lifetime only</li><li>Namespace configurable via <code>--db-namespace</code>; credentials via <code>--db-username/--db-password</code></li> +ontoref-mcp-knowledge-title = SurrealDB Persistence — Optional +ontoref-mcp-query-title = The MCP Server — DAG Access for Agents +ontoref-mcp-table-desc-header = Description +ontoref-mcp-table-tool-header = Tool +ontoref-mcp-title = Daemon & MCP — Runtime Intelligence Layer +ontoref-mcp-tool-action-add-desc = Create reflection mode + register as quick action +ontoref-mcp-tool-action-list-desc = Quick actions catalog from .ontoref/config.ncl +ontoref-mcp-tool-backlog-desc = Add or update_status on a backlog item +ontoref-mcp-tool-constraints-desc = All hard + soft architectural constraints +ontoref-mcp-tool-describe-desc = Architecture overview and self-description +ontoref-mcp-tool-get-adr-desc = Full ADR content with constraints +ontoref-mcp-tool-get-backlog-desc = Backlog items filtered by status +ontoref-mcp-tool-get-desc = Fetch ontology node by id +ontoref-mcp-tool-get-mode-desc = Mode DAG contract — steps, preconditions, postconditions +ontoref-mcp-tool-get-node-desc = Full ontology node with edges and constraints +ontoref-mcp-tool-help-desc = List available tools and usage +ontoref-mcp-tool-list-adrs-desc = List ADRs filtered by status +ontoref-mcp-tool-list-modes-desc = List all reflection modes +ontoref-mcp-tool-list-projects-desc = Enumerate all registered projects +ontoref-mcp-tool-project-status-desc = Full project dashboard — health, drift, actors +ontoref-mcp-tool-qa-add-desc = Persist new Q&A entry to reflection/qa.ncl +ontoref-mcp-tool-qa-list-desc = List Q&A knowledge store with optional filter +ontoref-mcp-tool-search-desc = Free-text search across nodes, ADRs, modes +ontoref-mcp-tool-set-project-desc = Set session default project context +ontoref-metrics-title = Protocol Metrics +ontoref-page-subtitle = Self-Describing Protocol for<br>Evolving Systems +ontoref-page-title = Ontoref — A Self-Describing Ontology & Reflection Protocol +ontoref-problem-1-desc = <li>Architectural choices made in chat, forgotten after rotation</li><li>No machine-queryable source of why something exists</li><li>ADRs as typed Nickel: invariants, constraints, supersession chain</li><li>Hard constraints enforced at every operation</li> +ontoref-problem-1-title = Decisions Without Memory +ontoref-problem-2-desc = <li>Configs change outside any review cycle</li><li>No audit trail linking change to PR or ADR</li><li>Rollback requires manual file archaeology</li><li>Sealed profiles: sha256 hash, full history, verified rollback</li> +ontoref-problem-2-title = Invisible Configuration Drift +ontoref-problem-3-desc = <li>LLMs start each session with zero project knowledge</li><li>Same mistakes, same questions, no accumulation across operations</li><li>Actor registry tracks each session token, type, current mode, last seen — persisted to disk</li><li>MCP tools give agents direct DAG read/write: nodes, ADRs, backlog, Q&A</li><li>Composed tasks shared via daemon — multiple actors see the same operational context live</li> +ontoref-problem-3-title = Agents Without Context +ontoref-problem-4-desc = <li>Guidelines in wikis, patterns in docs, decisions in Slack</li><li>No single source queryable by humans, agents, and CI equally</li><li><code>.ontology/</code> separates three orthogonal concerns: <code>core.ncl</code> (what IS) · <code>state.ncl</code> (where we ARE vs want to BE) · <code>gate.ncl</code> (when READY to cross a boundary)</li><li><code>reflection/</code> reads all three and answers self-knowledge queries — an agent understands the project without reading code, only by consulting the declarative graph</li> +ontoref-problem-4-title = Scattered Project Knowledge +ontoref-problem-5-desc = <li>Each project re-invents its own conventions</li><li>No shared contract for how operations are defined and executed</li><li>Reflection modes: typed DAG contracts for any workflow</li><li>One protocol adopted per-project, without enforcing uniformity</li> +ontoref-problem-5-title = Protocol Fragmentation +ontoref-problem-6-desc = <li>Q&A answered in one session forgotten by the next</li><li>Agent re-asks questions already answered in previous sessions</li><li>Q&A Knowledge Store: typed NCL, git-versioned, persists across browser resets</li><li>Notification barrier surfaces drift to agents proactively — pre_commit, drift, ontology_drift signals block until acknowledged</li> +ontoref-problem-6-title = Knowledge Lost Between Sessions +ontoref-problem-7-desc = <li>Personal and professional decisions made against implicit, unverifiable assumptions</li><li>No queryable model of what you never compromise</li><li>No structured way to ask: does this opportunity violate who I am?</li><li>ontoref as personal operational ontology — same core/state/gate files applied to life, career, and ecosystem dimensions</li><li><code>jpl validate "accept offer"</code> → invariants_at_risk, relevant edges, verdict</li> +ontoref-problem-7-title = Decisions Without a Map +ontoref-problems-title = The 7 Problems It Solves +ontoref-tagline = Structure that remembers why +ontoref-tech-stack-title = Technology Stack +ontoref-tension-1 = Ontology without Reflection = correct but static. Perfect invariants with no operations = dead documentation. +ontoref-tension-2 = Reflection without Ontology = fluid but unanchored. Workflows that forget what they protect. +ontoref-tension-thesis = The protocol lives in coexistence. +ontoref-ui-dashboard-title = The Web UI +ontoref-yang-desc = <li><strong>Modes</strong> — typed DAG workflow contracts (preconditions, steps, postconditions)</li><li><strong>Forms</strong> — parameter collection driving modes</li><li><strong>ADR lifecycle</strong> — Proposed → Accepted → Superseded, with constraint history</li><li><strong>Actors</strong> — developer / agent / CI, same protocol, different capabilities</li><li><strong>Config seals</strong> — sha256-sealed profiles, drift detection, rollback</li><li><strong>Quick Actions</strong> — runnable shortcuts over modes; configured in <code>.ontoref/config.ncl</code></li><li><strong>Passive Drift Observer</strong> — watches code changes, emits <code>ontology_drift</code> notifications with missing/stale/drift/broken counts</li> +ontoref-yang-sub = How things move and change +ontoref-yang-title = Yang — The Reflection Layer +ontoref-yin-desc = <li><strong>Invariants</strong> — axioms that cannot change without a new ADR</li><li><strong>Tensions</strong> — structural conflicts the project navigates, never resolves</li><li><strong>Practices</strong> — confirmed patterns with artifact paths to real files and declared ADR validators</li><li><strong>Gates</strong> — membranes controlling readiness thresholds</li><li><strong>Dimensions</strong> — current vs desired state, with transition conditions</li><li><strong>Q&A Knowledge Store</strong> — accumulated Q&A persisted to NCL, git-versioned, queryable by any actor</li> +ontoref-yin-sub = What must be true +ontoref-yin-title = Yin — The Ontology Layer +ontoref-layers-title = Protocol Stack +ontoref-layer-decl-items = .ontology/ · adrs/ · reflection/schemas/ +ontoref-layer-op-items = adr · register · config · backlog · forms · describe +ontoref-layer-entry-items = ontoref · actor detection · advisory locking · ONTOREF_IMPORT_PATH +ontoref-layer-graph-items = nodes · invariants · tensions · gates · dimensions · states +ontoref-layer-runtime-items = ontoref-daemon · ontoref-ontology · ontoref-reflection · search engine · notification barrier · SurrealDB (optional) +ontoref-layer-adopt-items = .ontoref/config.ncl · ontoref CLI · adopt_ontoref mode +ontoref-ui-title = Web UI — Read the Graph +ontoref-ui-graph-title = Graph +ontoref-ui-graph-desc = Force-directed D3 visualization of the ontology DAG. Yang·Axiom diamonds, Yin·Tension circles, Spiral·Practice squares. +ontoref-ui-search-title = Search +ontoref-ui-search-desc = Full-text search across nodes, ADRs, and reflection modes. +ontoref-ui-sessions-title = Sessions +ontoref-ui-sessions-desc = Actor registry with token, type, registered_at, last_seen, and current mode per actor. +ontoref-ui-notifications-title = Notifications +ontoref-ui-notifications-desc = Notification feed for pre_commit, drift, and ontology_drift signals. Acknowledge, dismiss, or emit custom notifications with action buttons. +ontoref-ui-backlog-title = Backlog +ontoref-ui-backlog-desc = Work items filterable by status with add/update operations. +ontoref-ui-qa-title = Q&A +ontoref-ui-qa-desc = Server-hydrated knowledge store from reflection/qa.ncl. Add, edit, and delete entries — persisted as typed NCL. +ontoref-ui-actions-title = Actions +ontoref-ui-actions-desc = Quick actions catalog from .ontoref/config.ncl. Execute runnable shortcuts over modes via POST /actions/run. +ontoref-ui-modes-title = Modes +ontoref-ui-modes-desc = Reflection mode list from reflection/modes/ — name, description, and full DAG contract per mode. +ontoref-ui-compose-title = Compose +ontoref-ui-compose-desc = Interactive mode forms with live session sharing for multi-actor workflows. +ontoref-ui-dashboard-desc = Project health, drift status, actor overview, and pending notifications at a glance. +ontoref-mcp-core-title = Core +ontoref-mcp-query-desc = search, get_node, list_adrs, get_adr, list_modes, get_mode, constraints — full DAG traversal. +ontoref-crate-ontology-title = ontoref-ontology +ontoref-crate-ontology-desc = Load .ontology/ NCL files as typed Rust structs. Graph traversal (callers, callees, impact queries). Invariant extraction and constraint validation. Zero stratumiops dependencies — minimal adoption surface. +ontoref-crate-reflection-title = ontoref-reflection +ontoref-crate-reflection-desc = Execute reflection modes as typed NCL DAG contracts. Step execution with dependency resolution. ADR lifecycle from Proposed through Accepted to Superseded. Config seal and rollback operations. +ontoref-crate-nushell-title = Nushell Modules +ontoref-crate-nushell-desc = store.nu (SurrealDB-backed cache with NCL export) · sync.nu (ontology code synchronization) · describe.nu (actor-aware project self-knowledge) · coder.nu (structured session records). 16 modules total — one per operational domain. +ontoref-crate-nickel-title = Nickel Schemas +ontoref-crate-nickel-desc = Core ontology types: Node, Edge, Pole, AbstractionLevel. State machine types: Dimension, Transition, Gate, Membrane. ADR schema: Constraint, Severity, Status, supersession. Reflection schema: Mode, Step, OnError, Dependency. +ontoref-crate-daemon-title = ontoref-daemon +ontoref-crate-daemon-desc = Actor registry via DashMap. Notification barrier with pre-commit hook integration. Compose forms for shared mode dispatch. SurrealDB-backed persistence as opt-in feature. NATS feature gate for distributed setups. + +ontoref-positioning-title = Live Positioning +ontoref-positioning-subtitle = Real-time differentiators and value propositions pulled from the ontoref daemon. +ontoref-positioning-loading = Loading positioning data… +ontoref-positioning-value-prop-label = Value Proposition +ontoref-positioning-differentiators-label = Differentiators +ontoref-positioning-live-label = Live +ontoref-positioning-draft-label = Draft diff --git a/site/site/i18n/locales/en/pages/post.ftl b/site/site/i18n/locales/en/pages/post.ftl new file mode 100644 index 0000000..76e45f1 --- /dev/null +++ b/site/site/i18n/locales/en/pages/post.ftl @@ -0,0 +1,20 @@ +# Post viewer page translations + +# Post content structure +post-author = Author +post-date = Published Date +post-category = Category +post-tags = Tags + +# Navigation and actions +post-back-to-blog = Back to Blog +post-back-to-recetas = Back to Recetas +post-back-to-content = Back to Content +post-back-to-tutoriales = Back to Tutorials +post-share = Share this article +post-print = Print article + +# Metadata +post-reading-time = Reading time +post-difficulty = Difficulty +post-last-updated = Last updated \ No newline at end of file diff --git a/site/site/i18n/locales/en/pages/privacy.ftl b/site/site/i18n/locales/en/pages/privacy.ftl new file mode 100644 index 0000000..6a80539 --- /dev/null +++ b/site/site/i18n/locales/en/pages/privacy.ftl @@ -0,0 +1,55 @@ +# Privacy page specific translations - English + +# Page Metadata +privacy-page-title = Privacy Policy +privacy-page-description = Our privacy policy and data protection practices +privacy-page-keywords = privacy, policy, data protection + +# Page Headers +privacy-title = Privacy Policy +privacy-subtitle = How we collect, use, and protect your information +privacy-effective-date = Last updated: July 2026 +privacy-intro = This Privacy Policy explains how Jesús Pérez Lorenzo (data controller) collects, uses and protects your personal data when you visit this website, subscribe to our communications or use our services, in accordance with the GDPR (EU 2016/679) and Spain's LOPDGDD. + +# Data Controller +privacy-controller = Data Controller +privacy-controller-text = Jesús Pérez Lorenzo — Contact and exercising your rights: ontoref@jesusperez.pro (Spain). +privacy-legal-basis = We process your data on the basis of your consent (art. 6.1.a), contract performance or pre-contractual steps (art. 6.1.b), and our legitimate interest in site security (art. 6.1.f). +privacy-consent-proof = For subscriptions we record the IP and date/time of sign-up as proof of consent. You can withdraw consent at any time via the unsubscribe link or by writing to ontoref@jesusperez.pro. + +# Information Collection +privacy-information-we-collect = Information We Collect +privacy-information-you-provide = Information You Provide +privacy-information-you-provide-text = Contact information (name, email, phone number) when you reach out to us +privacy-project-details-text = Project details and requirements when requesting services +privacy-communication-preferences-text = Communication preferences and feedback + +# Automatically Collected Information +privacy-automatically-collected-information = Automatically Collected Information +privacy-website-usage-data = Website usage data through analytics tools +privacy-technical-information-ip = Technical information (IP address, browser type, device information) +privacy-cookies-tracking = Cookies and similar tracking technologies for website functionality + +# How We Use Information +privacy-how-we-use-information = How We Use Your Information +privacy-how-we-use-text = We use the collected information to: +privacy-respond-to-inquiries = Respond to your inquiries and provide requested services +privacy-improve-website = Improve our website and service offerings +privacy-send-updates = Send relevant updates about our services (with your consent) +privacy-ensure-security = Ensure website security and prevent fraud +privacy-comply-legal = Comply with legal obligations + +# Your Rights +privacy-your-rights = Your Rights +privacy-your-rights-text = You have the right to: +privacy-access-review = Access and review your personal information +privacy-request-corrections = Request corrections to inaccurate data +privacy-request-deletion = Request deletion of your personal information +privacy-opt-out-marketing = Opt-out of marketing communications + +# Contact Information +privacy-contact-us = Contact Us +privacy-contact-privacy-text = If you have any questions about this Privacy Policy or our data practices, please contact us: +privacy-email = Email +privacy-address = Address +privacy-spain = Spain diff --git a/site/site/i18n/locales/en/pages/projects.ftl b/site/site/i18n/locales/en/pages/projects.ftl new file mode 100644 index 0000000..ae507d0 --- /dev/null +++ b/site/site/i18n/locales/en/pages/projects.ftl @@ -0,0 +1,23 @@ +# Projects page translations - English + +# Page header +projects-page-title = Projects +# ontoref domains surface (ADR-065) — index <title>, keyed by URL segment /domains +domains-page-title = Domains +projects-page-description = Open source projects and platforms +projects-title = Projects +projects-subtitle = Open source projects and platforms + +# Category page header +projects-category-page-title = Projects +projects-category-page-description = Projects filtered by category. +projects-category-page-keywords = projects, open source, rust, platforms, category + +# Content grid +projects-no-items-in-category = No projects found in this category. +projects-view-all = View all projects +projects-featured = Featured +projects-load-more = Load more + +# Error states +projects-error-loading = Error loading projects. Please try again. diff --git a/site/site/i18n/locales/en/pages/provisioning.ftl b/site/site/i18n/locales/en/pages/provisioning.ftl new file mode 100644 index 0000000..557e483 --- /dev/null +++ b/site/site/i18n/locales/en/pages/provisioning.ftl @@ -0,0 +1,73 @@ +# Provisioning page — Infrastructure Orchestration Platform + +# Hero +provisioning-badge = v3.0.11 | Production Ready +provisioning-tagline = Provision at Scale +provisioning-page-title = Infrastructure Orchestration With Configuration as Code +provisioning-page-subtitle = Declarative infrastructure management with Nickel schemas, Nushell orchestration, and Rust executables. Type-safe configuration, automated validation, and cloud-native deployment across Kubernetes, Docker, and custom platforms. 100% infrastructure as code. + +# Core Capabilities +provisioning-capabilities-title = Core Capabilities +provisioning-cap-1-title = Type-Safe Configuration +provisioning-cap-1-desc = Nickel provides formal type checking, automated validation, and recursive merging for infrastructure definitions. Eliminate configuration drift and parsing errors. +provisioning-cap-2-title = Intelligent Orchestration +provisioning-cap-2-desc = Nushell scripts orchestrate complex deployment workflows with structured data pipelines, state management, and error recovery. Built-in type system prevents runtime failures. +provisioning-cap-3-title = Multi-Platform Support +provisioning-cap-3-desc = Deploy to Kubernetes, Docker Compose, local VMs, and custom infrastructure. Unified interface with platform-specific overrides and workspace isolation. +provisioning-cap-4-title = Version Control Ready +provisioning-cap-4-desc = All infrastructure lives in Git. Configuration schemas, Nickel modules, and Nushell scripts are versionable, reviewable, and rollbackable. + +# How It Works +provisioning-how-title = How It Works +provisioning-how-nickel-title = Define with Nickel +provisioning-how-nickel-desc = Write declarative infrastructure schemas with full type safety. Nickel's lazy evaluation and recursive merging enable powerful abstractions and configuration composition. +provisioning-how-nushell-title = Orchestrate with Nushell +provisioning-how-nushell-desc = Structured data pipelines, type-safe operations, and stateful workflows. Nushell eliminates shell script fragility while maintaining scripting simplicity. +provisioning-how-rust-title = Execute with Rust +provisioning-how-rust-desc = Performance-critical operations backed by Rust. Zero-cost abstractions, memory safety, and fearless concurrency for infrastructure tooling. +provisioning-how-nats-title = Coordinate via NATS JetStream +provisioning-how-nats-desc = Persistent event bus for decoupled service coordination. Services exchange lease_id, task_id, and status events — credentials never traverse the bus. Auditable, replay-capable, at-least-once delivery. +provisioning-how-security-title = Secure end-to-end +provisioning-how-security-desc = Cedar policy-as-code for authorization, JWT sessions managed exclusively by ControlCenter, and post-quantum cryptography via SecretumVault. Credentials never leave the vault — services operate on lease references only. +provisioning-how-oci-title = Extend via OCI Registry +provisioning-how-oci-desc = Distribute custom providers, task services, and cluster definitions as OCI artifacts. The Extension Registry catalogs and versions capabilities independently — swap or compose providers without touching core platform code. +provisioning-how-cli-title = Platform CLI & External Services +provisioning-how-cli-desc = 8 platform subcommands (list, status, health, check, config, connections, init, start) with declarative external service management. Validates SurrealDB, OCI registries, Git sources, and cache before startup. +provisioning-how-versions-title = Centralized Version Management +provisioning-how-versions-desc = All tool and provider versions defined in Nickel schemas. Automatic provider discovery, shell script integration, and single source of truth for Nushell, Nickel, SOPS, Age, AWS CLI, and all providers. + +# SOLID Architecture Boundaries +provisioning-solid-title = SOLID Architecture Boundaries +provisioning-solid-provider-title = Provider APIs — Orchestrator only +provisioning-solid-provider-desc = All hcloud, AWS, and provider SDK calls are isolated to the Orchestrator crate. Every other service routes through the Orchestrator HTTP API. SSH operations share this same boundary. +provisioning-solid-auth-title = Auth decisions — ControlCenter only +provisioning-solid-auth-desc = JWT validation and Cedar policy evaluation happen exclusively in ControlCenter. Other services receive a UserContext via middleware — no service re-validates tokens or evaluates policies directly. +provisioning-solid-secrets-title = Secrets — Vault Service API only +provisioning-solid-secrets-desc = Credentials are never stored in NATS messages or environment variables. Services hold a lease_id and retrieve actual secrets via HTTPS to the Vault Service. The bus carries references, not values. + +# Architecture diagram +provisioning-architecture-title = System Architecture + +# Tech stack +provisioning-tech-stack-title = Technology Stack + +# Platform Services +provisioning-services-title = Platform Services (13) +provisioning-svc-orchestrator-role = Provider APIs + SSH boundary +provisioning-svc-controlcenter-role = Cedar auth boundary +provisioning-svc-controlcenter-ui-role = Leptos WASM dashboard +provisioning-svc-mcp-server-role = RAG + AI tools +provisioning-svc-ai-service-role = LLM inference layer +provisioning-svc-extension-registry-role = Extension catalog +provisioning-svc-secretumvault-role = PQC secrets API +provisioning-svc-detector-role = Event detection +provisioning-svc-daemon-cli-role = Service lifecycle +provisioning-svc-machines-role = Machine management +provisioning-svc-observability-role = Prometheus metrics +provisioning-svc-backup-role = State backup +provisioning-svc-encrypt-role = Key operations + +# CTA +provisioning-cta-title = Ready for infrastructure automation? +provisioning-cta-subtitle = Built with Nickel & Nushell | Rust | Type-Safe | Open Source +provisioning-cta-explore = Explore Git Repo diff --git a/site/site/i18n/locales/en/pages/recipes.ftl b/site/site/i18n/locales/en/pages/recipes.ftl new file mode 100644 index 0000000..9eaa5c4 --- /dev/null +++ b/site/site/i18n/locales/en/pages/recipes.ftl @@ -0,0 +1,93 @@ +# Recipes page specific translations - English + +# Page Metadata +recipes-page-title = Technical Recipes +recipes-page-description = Collection of practical recipes and implementation guides for modern development +recipes-page-keywords = recipes, cooking, code, guides + +# Page Header +recipes-page-subtitle = Practical code recipes and implementation guides for modern development challenges. Focused on Rust, DevOps, and cloud-native solutions. + +# Category Page Header +recipes-category-page-title = Technical Recipes +recipes-category-page-description = Recipes filtered by category - discover practical solutions for specific technology areas. +recipes-category-page-keywords = recipes, categories, guides, solutions + +# Recipe Post/Viewer +recipe-post-title = Recipe +recipe-post-description = View recipe details and code examples +recipe-post-keywords = recipe, code, guide +recipes-filtered-category-page-keywords = recipes, category, code, solutions, filtered, technology + +# Recipes Content +recipes-featured = Featured Recipes +recipes-featured-description = Curated practical solutions for the most common development challenges +recipes-all = All Recipes +recipes-all-recipes = All Recipes +view-recipe = View Recipe +view-all-recipes = View All Recipes +recipes-difficulty = Difficulty +recipes-view-recipe = View Recipe → +recipes-need-custom-recipe = Need a Custom Recipe? +recipes-custom-recipe-description = Can't find a solution for your specific challenge? Let me create a custom recipe for your use case. +recipes-request-custom-recipe = Request Custom Recipe +recipes-error-loading-recipes = Error loading recipes. Please try again later. + +# Recipe Actions +recipes-need-custom-recipe-title = Need a Custom Recipe? +recipes-need-custom-recipe-desc = Can't find a solution for your specific challenge? Let me create a custom recipe for your use case. +recipes-request-custom-recipe-btn = Request Custom Recipe +recipes-browse-blog-posts-btn = Browse Blog Posts +recipes-view-recipe-btn = View Recipe +recipes-contact-url = /contact +recipes-blog-url = /blog + +# Recipe Categories +recipes-categories = Categories +recipes-rust-programming = Rust Programming +recipes-docker = Docker +recipes-kubernetes = Kubernetes +recipes-devops = DevOps +recipes-microservices = Microservices +recipes-infrastructure = Infrastructure +recipes-async-programming = Async Programming +recipes-web-development = Web Development +recipes-architecture = Architecture +recipes-cicd = CI/CD + +# Additional recipe-specific translations +recipes-disabled-title = Recipes Temporarily Unavailable +recipes-disabled-message = The recipes section is currently disabled. Please check back later or contact us for assistance. +recipes-loading = Loading recipes... +recipes-error-loading-featured = Error loading featured recipes +recipes-error-loading-recipes-grid = Error loading recipes grid + +# Placeholder content for when data is being loaded +recipes-grid-placeholder = Loading recipes grid... + +# Category browsing section +recipes-browse-categories-title = Browse by Category +recipes-browse-categories-description = Explore recipes organized by category and difficulty level. + +# Content grid messages +recipes-no-posts-in-category = No recipes found in this category +recipes-view-all-posts = View All Recipes + +# Category-specific titles and descriptions +recipes-cat-async-programming-title = Async Programming +recipes-cat-async-programming-desc = Asynchronous programming patterns, futures, async/await, and concurrent execution techniques + +recipes-cat-cicd-title = CI/CD & Automation +recipes-cat-cicd-desc = Continuous integration, continuous deployment, automated testing, and deployment pipeline recipes + +recipes-cat-docker-title = Docker & Containerization +recipes-cat-docker-desc = Docker container recipes, multi-stage builds, optimization techniques, and containerization best practices + +recipes-cat-kubernetes-title = Kubernetes & Orchestration +recipes-cat-kubernetes-desc = Kubernetes deployment patterns, service mesh configurations, and container orchestration solutions + +recipes-cat-rust-programming-title = Rust Programming +recipes-cat-rust-programming-desc = Rust language recipes, performance optimization, memory management, and ecosystem integration + +# Generic content messages (used by UnifiedContentGrid) +# Moved to content-kinds.ftl to avoid duplication \ No newline at end of file diff --git a/site/site/i18n/locales/en/pages/resources.ftl b/site/site/i18n/locales/en/pages/resources.ftl new file mode 100644 index 0000000..1f0b291 --- /dev/null +++ b/site/site/i18n/locales/en/pages/resources.ftl @@ -0,0 +1,24 @@ +# Resources page translations - English + +# Page header +resources-page-title = Resources +resources-page-description = Ontoref release decks and talk slides +resources-title = Resources +resources-subtitle = Ontoref release decks and talk slides + +# Category page header +resources-category-page-title = Resources +resources-category-page-description = Resources filtered by category. +resources-category-page-keywords = resources, decks, slides, releases, presentations, category + +# Navigation +nav-resources = Resources + +# Content grid +resources-no-items-in-category = No resources found in this category. +resources-view-all = View all resources +resources-featured = Featured +resources-load-more = Load more + +# Error states +resources-error-loading = Error loading resources. Please try again. diff --git a/site/site/i18n/locales/en/pages/rustelo.ftl b/site/site/i18n/locales/en/pages/rustelo.ftl new file mode 100644 index 0000000..b0d6cba --- /dev/null +++ b/site/site/i18n/locales/en/pages/rustelo.ftl @@ -0,0 +1,76 @@ +# Rustelo page — Full-Stack Rust Web Framework + +# Hero +rustelo-badge = Full-Stack Rust Web Framework +rustelo-tagline = Modular · Type-Safe · Configuration-Driven · Memory-Safe +rustelo-hero-title = Build. Deploy. Deliver. +rustelo-hero-subtitle = Modular, type-safe Rust platform built on Leptos and Axum. Configuration-driven architecture with language-agnostic routing, plugin system, and dual-target WASM+native compilation. + +# Stats +rustelo-stat-crates = Crates +rustelo-stat-layers = Layers +rustelo-stat-unsafe = unsafe blocks +rustelo-stat-targets = Targets WASM+native + +# CTAs +rustelo-cta-github = Git Repo +rustelo-cta-architecture = View Architecture +rustelo-cta-get-started = Get Started + +# Architecture section +rustelo-arch-title = Three-Layer Design +rustelo-arch-subtitle = Clear separation between your application, framework ports, and foundation. Each layer has a single responsibility. + +# Layer badges / titles / descriptions +rustelo-layer-app-badge = Your App +rustelo-layer-app-title = Implementation Layer +rustelo-layer-app-desc = Your application crates: server (Axum SSR), client (Leptos WASM), and pages (Leptos components). Build-time code generation produces routes, pages, and i18n from configuration. + +rustelo-layer-ports-title = Framework Ports +rustelo-layer-ports-desc = Stable public API for implementations. Ports wrap foundation internals and expose clean, versioned interfaces: web stack, authentication, and content management. + +rustelo-layer-foundation-title = Foundation +rustelo-layer-foundation-desc = Core implementations: config, i18n, RBAC, Axum SSR server, Leptos WASM client, UI components, page generation system, and Nickel-based build configuration. + +# Features section +rustelo-features-title = Everything Included +rustelo-features-subtitle = A complete platform from authentication to deployment, composable via Cargo features. + +rustelo-feat-wasm-title = Reactive WASM Frontend +rustelo-feat-wasm-desc = Fine-grained reactivity with Leptos. Signals, memos, effects. Zero virtual DOM. SSR hydration with tachys. + +rustelo-feat-auth-title = Authentication & RBAC +rustelo-feat-auth-desc = JWT, OAuth2, 2FA with TOTP. Role-based access control via Nickel NCL policies. Argon2 password hashing. + +rustelo-feat-routing-title = Language-Agnostic Routing +rustelo-feat-routing-desc = Routes defined in TOML/NCL. Build-time generation. No hardcoded paths. Auto-discover languages from content directory. + +rustelo-feat-plugins-title = Plugin System +rustelo-feat-plugins-desc = ResourceContributor and PageContributor traits. Register at startup, zero runtime overhead. include_str!() for compile-time embedding. + +rustelo-feat-db-title = Database Abstraction +rustelo-feat-db-desc = PostgreSQL and SQLite via SQLx. Compile-time query verification. Async connection pooling. Separate migrations per database type. + +rustelo-feat-email-title = Email & Notifications +rustelo-feat-email-desc = Multi-provider email system via Lettre. Template-based emails with FTL i18n. WebSocket real-time notifications via tokio-tungstenite. + +rustelo-feat-codegen-title = Build-Time Code Generation +rustelo-feat-codegen-desc = Nickel NCL → build.rs → OUT_DIR → include!(). Routes, pages, menus, and FTL registry generated at compile time. Zero runtime cost. + +rustelo-feat-features-title = Cargo Feature Composition +rustelo-feat-features-desc = auth, content-db, content-static, email, tls, metrics, crypto. Enable only what you need. Feature-gated compilation for both WASM and native targets. + +# Build pipeline +rustelo-pipeline-title = Configuration-Driven Generation +rustelo-pipeline-subtitle = Everything generated at compile time from Nickel NCL configuration. Zero runtime overhead, full type safety. + +# Quick start +rustelo-quickstart-title = Up and Running +rustelo-quickstart-subtitle = From zero to running Rustelo application. + +# Tech stack +rustelo-tech-title = Built on Proven Foundations + +# Bottom CTA +rustelo-cta-title = Ready to build with Rust? +rustelo-cta-subtitle = Open source · Self-hosted · Zero unsafe · 100% Rust diff --git a/site/site/i18n/locales/en/pages/secretumvault.ftl b/site/site/i18n/locales/en/pages/secretumvault.ftl new file mode 100644 index 0000000..2853212 --- /dev/null +++ b/site/site/i18n/locales/en/pages/secretumvault.ftl @@ -0,0 +1,37 @@ +# SecretumVault page — Post-Quantum Secrets Vault + +# Hero +secretumvault-badge = PQC Production-Ready | Rust 1.75+ | Cedar ABAC | 50+ Tests +secretumvault-tagline = Post-Quantum Cryptographic Secrets Vault +secretumvault-page-title = Post-Quantum Secrets Vault for Modern Infrastructure +secretumvault-page-subtitle = Production-ready ML-KEM-768 + ML-DSA-65 with Cedar policy authorization, pluggable crypto backends, and multi-cloud storage. Deploy post-quantum cryptography today. One config line to enable PQC. No code changes. + +# Why +secretumvault-why-title = Why SecretumVault +secretumvault-problem-title = The Problem +secretumvault-problem-desc = Current encryption will be broken by quantum computers. Most secret vaults have no PQC migration path. Cloud KMS vendors lock you in. Policy languages are proprietary and ACL-based. +secretumvault-solution-title = The Solution +secretumvault-solution-desc = Cryptographic agility through pluggable backends. ML-KEM-768 + ML-DSA-65 work today via OQS. Cedar policies are portable. Multi-cloud storage prevents lock-in. Switch backends via config — no recompilation. + +# Features +secretumvault-features-title = Core Capabilities +secretumvault-feat-pqc-title = Post-Quantum Cryptography +secretumvault-feat-pqc-desc = ML-KEM-768 (FIPS 203) key encapsulation and ML-DSA-65 (FIPS 204) digital signatures via OQS. NIST compliance verified. Hybrid classical+PQC mode in development. +secretumvault-feat-engines-title = Secrets Engines +secretumvault-feat-engines-desc = KV (versioned), Transit (encryption-as-a-service), PKI (X.509 certificates), Database (dynamic credentials for PostgreSQL, MySQL, MongoDB). Extensible via trait. +secretumvault-feat-storage-title = Multi-Backend Storage +secretumvault-feat-storage-desc = etcd (distributed HA), SurrealDB (document + graph with MVCC), PostgreSQL (ACID), filesystem (dev/test). Pluggable via StorageBackend trait. +secretumvault-feat-cedar-title = Cedar Authorization +secretumvault-feat-cedar-desc = AWS Cedar policy language for attribute-based access control. Context-aware decisions: IP allowlisting, time-based access, environment constraints. Policy-as-Code. +secretumvault-feat-cloud-title = Cloud-Native +secretumvault-feat-cloud-desc = Kubernetes-ready with Helm charts. Docker multi-stage builds (<50MB). Prometheus metrics, structured logging with correlation IDs. NATS event bus for lifecycle notifications. +secretumvault-feat-enterprise-title = Enterprise Ready +secretumvault-feat-enterprise-desc = TLS/mTLS, Shamir Secret Sharing (2-of-3, 3-of-5), token management with TTL/renewal/revocation, full audit logging for SOC2/GDPR/HIPAA compliance. + +# Tech stack +secretumvault-tech-stack-title = Technology Stack + +# CTA +secretumvault-cta-title = Deploy post-quantum cryptography today +secretumvault-cta-subtitle = Rust-native | Apache 2.0 | Self-Hosted | Multi-Cloud +secretumvault-cta-explore = Explore Git Repo diff --git a/site/site/i18n/locales/en/pages/services.ftl b/site/site/i18n/locales/en/pages/services.ftl new file mode 100644 index 0000000..8e9a48d --- /dev/null +++ b/site/site/i18n/locales/en/pages/services.ftl @@ -0,0 +1,82 @@ +# Services page — ontoref. Tier-structured: operate tier-2, learn tiers 0–1. +# Grounded in .ontoref/positioning (viability via-001, tier-taglines, value-props). + +# SEO +services-page-title = Adopt the protocol — or learn it +services-page-description = ontoref is a gift. What is offered is help operating its highest tier — the witnessed operations and governance surface — and training and coaching to climb the lower tiers. +services-page-keywords = ontoref, ontology, governance, audit trail, witness, training, coaching, tier-2 + +# Hero +services-page-subtitle = The protocol is never sold — it is opt-in, per tier, and never a runtime dependency. What is offered is help operating its highest tier and training to climb the others. +services-page-note = What you put down, anyone can check. + +# ── Tier 2 — Operations & Governance (the proposal) ───────────────────────── +services-tier2-eyebrow = TIER 2 · OPERATIONS · HOLDS ITS COURSE +services-tier2-title = Operations & governance, run as a service +services-tier2-desc = Tier 2 is where every mutation goes through a typed operation and deposits an Ed25519 witness — the journey of changes, not just the state, is held and verifiable. The protocol stays open; the witnessed surface is what is run or supported for you. +services-tier2-ops-title = Operations-layer adoption +services-tier2-ops-desc = Stand up tier 2: domain operations as the agent's only mutation path, typed preconditions, dispatch, and the daemon. The runtime becomes the one place authoritative state can change (ADR-024). +services-tier2-audit-title = Witnessed audit trail +services-tier2-audit-desc = An Ed25519 oplog DAG, content-addressed and replayable — a tamper-evident record any party can verify against actor keys without trusting whoever produced it (ADR-023/047, vp-004). +services-tier2-validators-title = Custom validators & criteria +services-tier2-validators-desc = Encode your hard constraints as first-class, witnessed validators that reject ill-typed state at the three validation planes — your governance rules become checkable, not advisory (ADR-026/050). +services-tier2-migration-title = Substrate migration +services-tier2-migration-desc = Move from scattered docs, ad-hoc scripts and a filesystem of NCL to the verifiable substrate — op log, content-addressed blobs, bitemporal triples, Merkle commitments — without a big-bang rewrite (ADR-023/029). +services-what-you-get = What you get +services-get-witnessed-history = ✅ A witnessed, replayable history of every authoritative change +services-get-validators = ✅ Your constraints as first-class validators, not review checklists +services-get-knowledge-transfer = ✅ Knowledge transfer — your team operates it without us +services-get-no-lockin = ✅ No lock-in: the protocol is never a runtime dependency +services-get-open-protocol = ✅ The open protocol stays a gift — only the higher-tier witnessed surface is metered + +# ── Training & coaching — the lower tiers, to use or learn ─────────────────── +services-learn-title = Training & coaching +services-learn-desc = Most teams should not start at tier 2. Begin by declaring what you are (tier 0) and proving your state (tier 1) — these are learnable, and the coaching meets you where you are. +services-coaching-eyebrow = TIER 0 · DECLARATIVE · HOLDS ITS WHY +services-coaching-title = Coaching — declare your why +services-coaching-desc = Hands-on guidance to put your architecture into typed NCL: axioms, tensions, ADRs and state — so an agent or a new hire understands the project from the graph, not from tribal memory. +services-coaching-1to1-title = 1:1 sessions +services-coaching-1to1-note = 1–2 h +services-coaching-1to1-desc = Work through your real ontology — name the axioms and the tensions that actually govern your decisions. +services-coaching-ondaod-title = ondaod tension discipline +services-coaching-ondaod-note = 1–2 h +services-coaching-ondaod-desc = Learn to hold a Spiral open instead of collapsing it — read tensions first, describe the synthesis, decide without foreclosing. +services-coaching-adr-title = ADR & decision modeling +services-coaching-adr-note = 1–2 h +services-coaching-adr-desc = Turn decisions into typed ADRs with constraints and rejected alternatives — reversible-vs-not, queryable, linked to the graph. +services-coaching-ongoing-title = Ongoing guidance +services-coaching-ongoing-note = ongoing +services-coaching-ongoing-desc = Recurring sessions as your ontology grows — keep the declared why coherent with the code as it changes. + +services-training-eyebrow = TIER 1 · SUBSTRATE · HOLDS AND PROVES ITSELF +services-training-title = Training — prove your state +services-training-desc = Courses and workshops on the substrate tier: commit your state so it is externally checkable before any operation exists — verification without the full operations machinery. +services-training-courses-title = Custom courses +services-training-courses-desc = A curriculum shaped to your stack: the three-file ontology split, substrate commitments, and how describe answers self-knowledge queries. +services-training-workshops-title = Hands-on workshops +services-training-workshops-desc = Your team takes a real subsystem from undocumented to declared-and-verified in a session — substrate commitment, witness-not-clone verification (ADR-028). +services-training-talks-title = Talks & seminars +services-training-talks-desc = Conference-style sessions on operational ontology, the ontology/reflection duality, and sufficient verification over complete knowledge. +services-training-content-title = Educational content +services-training-content-desc = Written guides and the projected docsite (mdBook from the ontology, ADR-060) tailored as a learning path for your team. + +# ── The three tiers — which one fits ──────────────────────────────────────── +services-tiers-title = Which tier fits you +services-tiers-desc = Adoption is graduated (ADR-029): enter with one NCL file and climb only when it hurts. You are never a hostage to a runtime or a SaaS. +services-tier0-label = TIER 0 · DECLARATIVE +services-tier0-title = Holds its why +services-tier0-desc = A typed ontology plus describe: you declare the reason your project exists as NCL. Coaching gets you here. +services-tier1-label = TIER 1 · SUBSTRATE +services-tier1-title = Holds and proves itself +services-tier1-desc = A substrate commitment makes your state externally checkable before any mutation. Training gets you here. +services-tier2-label = TIER 2 · OPERATIONS +services-tier2-card-title = Holds its course +services-tier2-card-desc = Ed25519 witnesses and replay determinism: every change is witnessed, so the journey is verifiable. We run or support this with you. + +# ── CTA ───────────────────────────────────────────────────────────────────── +services-cta-title = Where should you start? +services-cta-desc = Tell us your tier and your stack — we will point to the smallest first step, whether that is a coaching session or a tier-2 engagement. +services-cta-primary = Start a conversation +services-cta-primary-url = /contact +services-cta-secondary = See what ontoref is +services-cta-secondary-url = /about diff --git a/site/site/i18n/locales/en/pages/signout.ftl b/site/site/i18n/locales/en/pages/signout.ftl new file mode 100644 index 0000000..f8c9003 --- /dev/null +++ b/site/site/i18n/locales/en/pages/signout.ftl @@ -0,0 +1,11 @@ +# Sign-out page - English + +signout-page-title = Signed out +signout-page-description = You have been signed out + +signout-title = See you soon! +signout-title-with-name = See you soon, $name! +signout-subtitle = You have been signed out successfully. Thank you for visiting. +signout-home = Back to home +signout-login-again = Sign in again +signout-redirect = Redirecting to home in $seconds seconds… diff --git a/site/site/i18n/locales/en/pages/stratumiops.ftl b/site/site/i18n/locales/en/pages/stratumiops.ftl new file mode 100644 index 0000000..d9bc648 --- /dev/null +++ b/site/site/i18n/locales/en/pages/stratumiops.ftl @@ -0,0 +1,127 @@ +# StratumIOps page — Rust Crate Ecosystem for Project Governance + +# Hero +stratumiops-badge = v0.1.0 | 52+ Crates | 74+ Tests | 100% Rust +stratumiops-tagline = Rust Crate Ecosystem for Project Governance +stratumiops-page-title = Libraries for Self-Aware Projects +stratumiops-page-subtitle = 9 Rust crates, 6 Nushell modules, Nickel schemas — integrate ontology-driven governance, living ADRs, sealed configuration, AI agent orchestration, knowledge management, and post-quantum secrets into your projects. One protocol for humans, agents, and CI. 100% Rust. Zero compromises. + +# Problems +stratumiops-problems-title = The 8 Problems It Solves +stratumiops-problem-1-title = Scattered Knowledge +stratumiops-problem-1-desc = Decisions in Slack, guidelines in wikis, patterns in docs — all disconnected. Kogral unifies with git-native markdown + MCP. +stratumiops-problem-2-title = Uncontrolled LLM Costs +stratumiops-problem-2-desc = No visibility on AI spending per team. No budget limits or controls. Vapora provides real-time budgets, automatic fallback to cheaper providers, and expertise-based agent routing. +stratumiops-problem-3-title = Fragile YAML Configuration +stratumiops-problem-3-desc = Runtime errors from untyped config. No validation before deployment. Provisioning uses Nickel with pre-runtime validation. TypeDialog provides forms with contract validation. +stratumiops-problem-4-title = Static Cryptography +stratumiops-problem-4-desc = No preparation for quantum threats. Locked into single crypto library. SecretumVault delivers production post-quantum crypto with ML-KEM-768, ML-DSA-65 and 4 pluggable backends. +stratumiops-problem-5-title = Chaotic Project Workflows +stratumiops-problem-5-desc = Scattered TODOs, no structured phases. No audit trail for task state changes. Syntaxis provides systematic project orchestration with 4 interfaces and VAPORA-ready AI agent coordination. +stratumiops-problem-6-title = Decisions Without Memory +stratumiops-problem-6-desc = Architectural decisions made verbally or in chat. Constraints forgotten after team changes. Ontology + ADRs provide machine-queryable invariants with hard constraints enforced at every operation. +stratumiops-problem-7-title = Invisible Configuration Drift +stratumiops-problem-7-desc = Configs modified outside any review cycle. No audit trail linking change to PR or ADR. Reflection provides sha256-sealed config profiles with drift detection, verified rollback, and full history. +stratumiops-problem-8-title = Dead Documentation +stratumiops-problem-8-desc = Docs written once, never updated. Different actors generate different formats with no shared source. Kogral + Ontology generates living docs from the same graph — human docs, agent context, CI reports. + +# Ontology, Reflection & ADRs +stratumiops-ontology-title = Ontology, Reflection & ADRs + +stratumiops-yin-title = Yin — The Formal Layer +stratumiops-yin-subtitle = What must be true +stratumiops-yin-items = Nickel schemas — structural correctness at definition time + ADR constraints — "this can never be violated" + Config seals — sealed states, sha256-verifiable + Ontology invariants — what cannot change without a new ADR + Mathematical hashes — proof of what was sealed and when + +stratumiops-yang-title = Yang — The Operational Layer +stratumiops-yang-subtitle = How things move and change +stratumiops-yang-items = Nu commands — structured data transformation + Actors (human/agent/CI) — same protocol, different capabilities + Register flow — captures changes, routes to correct artifact + Mode definitions — operation sequences with verification + Pre-commit hooks — forced synchronisation at commit time + +stratumiops-tension-1 = Yang without Yin = fluid but chaotic. Anything can change. Nothing is verifiable. +stratumiops-tension-2 = Yin without Yang = correct but useless. Perfect schemas without operations = dead documentation. +stratumiops-tension-thesis = The system lives in coexistence. + +stratumiops-layer-decl-label = Declarative Layer — Nickel +stratumiops-layer-decl-items = .ontology/ · adrs/ · reflection/schemas/ · reflection/configs/ +stratumiops-layer-decl-desc = Strong types, contracts, enums. Fails at definition, not at runtime. + +stratumiops-layer-op-label = Operational Layer — Nushell +stratumiops-layer-op-items = adr · register · config · backlog · forms · prereqs +stratumiops-layer-op-desc = Typed pipelines over structured data. No text streams. + +stratumiops-layer-entry-label = Entry Point — Bash to Nu +stratumiops-layer-entry-items = stratum.sh · actor detection · advisory locking · NICKEL_IMPORT_PATH +stratumiops-layer-entry-desc = Single entry point. Detects actor, acquires lock, dispatches to correct Nu module. + +stratumiops-layer-graph-label = Knowledge Graph — Ontology + ADRs +stratumiops-layer-graph-items = nodes · invariants · gates · dimensions · states +stratumiops-layer-graph-desc = The system knows what it knows. Actor-agnostic. Machine-queryable. + +stratumiops-layer-seal-label = Sealed States — Config + History +stratumiops-layer-seal-items = profiles · sha256 seals · audit trail · rollback +stratumiops-layer-seal-desc = Verifiable immutability. Drift detection. Full ADR/PR/bug traceability. + +# Architecture Diagrams +stratumiops-arch-title = Architecture +stratumiops-arch-orchestrator = Orchestrator Architecture +stratumiops-arch-operation-flow = Operation Flow + +# Ecosystem Projects +stratumiops-projects-title = Ecosystem Projects +stratumiops-proj-vapora-title = Vapora +stratumiops-proj-vapora-desc = AI agent orchestration with learning. Agents improve from experience. Automatic budget fallback. NATS JetStream coordination. 13 crates, 218 tests, 50K LOC. +stratumiops-proj-kogral-title = Kogral +stratumiops-proj-kogral-desc = Knowledge graph with MCP for Claude Code. 6 node types: Notes, ADRs, Guidelines, Patterns, Journals, Executions. Git-native markdown. Semantic search with embeddings. 3 crates, 56 tests, 15K LOC. +stratumiops-proj-typedialog-title = TypeDialog +stratumiops-proj-typedialog-desc = 6 backends: CLI, TUI, Web, AI, Agent, Prov-gen. NCL-native forms with nickel-roundtrip editing. Nickel contract validation. Conditional fields and repeating groups. 8 crates, 3,818 tests, 90K LOC. +stratumiops-proj-provisioning-title = Provisioning +stratumiops-proj-provisioning-desc = Declarative IaC with Nickel + AI-assisted generation. Multi-cloud: AWS, UpCloud, Local (LXD). RAG with 1,200+ domain docs. MCP server for natural language queries. Orchestrator with automatic rollback. 15+ crates, 218 tests, 40K LOC. +stratumiops-proj-secretumvault-title = SecretumVault +stratumiops-proj-secretumvault-desc = Post-quantum crypto: ML-KEM-768, ML-DSA-65 (NIST FIPS 203/204). 4 crypto backends. 4 storage backends. 4 secrets engines: KV, Transit, PKI, Database. Shamir Secret Sharing. 50+ tests, 11K LOC. +stratumiops-proj-syntaxis-title = Syntaxis +stratumiops-proj-syntaxis-desc = Systematic project orchestration platform. 4 interfaces: CLI, TUI, Dashboard, REST API. Dual DB: SQLite + SurrealDB. Phase-based lifecycle. VAPORA SST foundation for AI agent tasks. 12 crates, 1,030+ tests, 60K LOC. + +stratumiops-more-info = More Info + +# Stratum Crates +stratumiops-stratum-title = Stratum Crates +stratumiops-stratum-subtitle = Shared infrastructure libraries powering the ecosystem +stratumiops-stratum-orchestrator-role = Graph-driven workflow engine +stratumiops-stratum-graph-role = ActionNode, Capability, GraphRepository +stratumiops-stratum-state-role = PipelineRun, StepRecord, StateTracker +stratumiops-stratum-llm-role = Unified LLM providers + circuit breaker +stratumiops-stratum-embeddings-role = Embedding providers + VectorStore +stratumiops-stratum-nats-role = JetStream consumer + NKey auth +stratumiops-stratum-ncl-role = OCI to local Nickel resolver +stratumiops-stratum-ontology-role = Ontology graph, invariants, gates, dimensions +stratumiops-stratum-reflection-role = ADR lifecycle, config seals, backlog, modes +stratumiops-stratum-dispatcher-role = Actor-aware modular dispatcher +stratumiops-stratum-plugins-role = nickel-export, tera-render, nats pub + +# Ecosystem Metrics +stratumiops-metrics-title = Ecosystem Metrics +stratumiops-metric-crates = Rust Crates +stratumiops-metric-tests = Tests +stratumiops-metric-loc = Lines of Code +stratumiops-metric-clippy = Clippy Warnings +stratumiops-metric-unsafe = Unsafe Blocks +stratumiops-metric-docs = Doc Coverage +stratumiops-metric-crypto = Crypto Backends +stratumiops-metric-storage = Storage Backends +stratumiops-metric-typedialog = TypeDialog Backends +stratumiops-metric-mcp = MCP Tools + +# Tech stack +stratumiops-tech-stack-title = Technology Stack + +# CTA +stratumiops-cta-title = Integrate governance into your project +stratumiops-cta-subtitle = 6 Projects | 9 Stratum Crates | 52+ Crates | 74+ Tests | 100% Rust +stratumiops-cta-explore = Explore Ecosystem diff --git a/site/site/i18n/locales/en/pages/syntaxis.ftl b/site/site/i18n/locales/en/pages/syntaxis.ftl new file mode 100644 index 0000000..ba70f6e --- /dev/null +++ b/site/site/i18n/locales/en/pages/syntaxis.ftl @@ -0,0 +1,80 @@ +# Syntaxis page — Project Orchestration Platform + +# Hero +syntaxis-badge = Production Beta | 632+ tests | 8 crates +syntaxis-tagline = Systematic Orchestration, Perfectly Arranged +syntaxis-page-title = SYNTAXIS +syntaxis-page-subtitle = Production-grade project orchestration platform built in Rust. Four interfaces, dual database support, VAPORA-ready. Every task has its perfect place in the syntax of your workflow. + +# Interfaces +syntaxis-interfaces-title = Four Interfaces, One Core +syntaxis-interfaces-subtitle = Choose the interface that fits your workflow. All share the same core engine. +syntaxis-iface-cli-title = Command Line +syntaxis-iface-cli-label = syntaxis-cli (clap) +syntaxis-iface-cli-desc = Structured subcommands for automation, CI/CD pipelines, and scripting. Config auto-discovery with NuShell wrappers. +syntaxis-iface-tui-title = Terminal UI +syntaxis-iface-tui-label = syntaxis-tui (ratatui) +syntaxis-iface-tui-desc = Interactive terminal interface with vim-style navigation (hjkl). SSH-friendly, works on any remote machine. +syntaxis-iface-dashboard-title = Dashboard +syntaxis-iface-dashboard-label = Leptos WASM (CSR) +syntaxis-iface-dashboard-desc = Modern web dashboard compiled to WebAssembly. Real-time updates, responsive design, served by the API. +syntaxis-iface-api-title = REST API +syntaxis-iface-api-label = syntaxis-api (axum) +syntaxis-iface-api-desc = Full REST API with Tower middleware, WebSocket support, authentication, rate limiting, and metrics. + +# Quality stats +syntaxis-quality-title = Code Quality +syntaxis-quality-tests = Tests passing +syntaxis-quality-lines = Lines of Rust +syntaxis-quality-unsafe = Unsafe blocks +syntaxis-quality-unwrap = unwrap() calls +syntaxis-quality-docs = Public APIs documented +syntaxis-quality-crates = Workspace crates + +# Features +syntaxis-features-title = Core Features +syntaxis-features-subtitle = Built for production from day one. No shortcuts, no technical debt. +syntaxis-feat-lifecycle-title = Phase Lifecycle +syntaxis-feat-lifecycle-desc = Create, Develop, Publish, Archive. Full state machine with audit trail on every transition. +syntaxis-feat-audit-title = Audit Trail +syntaxis-feat-audit-desc = Complete change history on every entity. State rollback support for enterprise compliance. +syntaxis-feat-dualdb-title = Dual Database +syntaxis-feat-dualdb-desc = SQLite for local, SurrealDB for teams. Switch via config, no code changes needed. +syntaxis-feat-config-title = Config-Driven +syntaxis-feat-config-desc = TOML + Nickel configuration. Auto-discovery system finds configs across project hierarchies. +syntaxis-feat-templates-title = Project Templates +syntaxis-feat-templates-desc = Reusable project structures with pre-configured phases, checklists, and task templates. +syntaxis-feat-vapora-title = VAPORA SST +syntaxis-feat-vapora-desc = Official foundation for VAPORA Software Specification & Tasks. Agent orchestration via NATS event streaming. +syntaxis-feat-checklists-title = Phase Checklists +syntaxis-feat-checklists-desc = Verification checklists tied to phase transitions. Gate quality before moving forward. +syntaxis-feat-bootstrap-title = Bootstrap Install +syntaxis-feat-bootstrap-desc = One-command installation across 50+ architectures. Pre-compiled Rust, everything else from source. + +# Database +syntaxis-db-title = Flexible Persistence +syntaxis-db-subtitle = One trait, two backends. Switch via configuration at runtime. +syntaxis-db-sqlite-title = SQLite (Default) +syntaxis-db-sqlite-1 = Zero external dependencies +syntaxis-db-sqlite-2 = Async pooled connections via sqlx +syntaxis-db-sqlite-3 = WAL mode for concurrent reads +syntaxis-db-sqlite-4 = Prepared statements & batch ops +syntaxis-db-sqlite-5 = Solo devs, small teams, local-first +syntaxis-db-surreal-title = SurrealDB +syntaxis-db-surreal-1 = 40+ operations fully implemented +syntaxis-db-surreal-2 = In-Memory, File, Server, Docker, K8s +syntaxis-db-surreal-3 = Type-safe async with JSON binding +syntaxis-db-surreal-4 = Feature-gated optional dependency +syntaxis-db-surreal-5 = Teams, distributed systems, enterprise + +# Architecture diagram +syntaxis-architecture-title = System Architecture + +# Tech stack +syntaxis-tech-stack-title = Technology Stack + +# CTA +syntaxis-cta-title = Ready for structured project orchestration? +syntaxis-cta-subtitle = Built with Rust | Open Source | Part of the VAPORA ecosystem +syntaxis-cta-explore = Explore Git Repo +syntaxis-cta-architecture = View Architecture diff --git a/site/site/i18n/locales/en/pages/templates-test.ftl b/site/site/i18n/locales/en/pages/templates-test.ftl new file mode 100644 index 0000000..6553fe6 --- /dev/null +++ b/site/site/i18n/locales/en/pages/templates-test.ftl @@ -0,0 +1,34 @@ +# Test FTL keys for template system + +# Privacy page (basic_page template) +test-privacy-page-title = Privacy Policy +privacy-page-content = <p>This privacy policy describes how we collect, use, and protect your personal information when you use our website and services.</p><p>We are committed to protecting your privacy and handling your data responsibly.</p> + +# Terms page (basic_page template) +terms-page-title = Terms of Service +terms-page-content = <p>By accessing and using this website, you accept and agree to be bound by the terms and provision of this agreement.</p><p>If you do not agree to abide by the above, please do not use this service.</p> + +# Portfolio page (content_list template) +portfolio-page-title = My Portfolio +portfolio-page-description = A showcase of my work and projects in software development, infrastructure, and web3 technologies. +portfolio-featured-title = Featured Projects +portfolio-no-items-found = No portfolio items found. + +# Request Quote page (form_page template) +request-quote-page-title = Request a Quote +request-quote-form-description = Tell me about your project and I'll provide you with a detailed quote and timeline. +request-quote-form-name-label = Full Name +request-quote-form-name-placeholder = Your full name +request-quote-form-email-label = Email Address +request-quote-form-email-placeholder = your@email.com +request-quote-form-subject-label = Project Type +request-quote-form-subject-placeholder = Web development, infrastructure, consultation... +request-quote-form-message-label = Project Details +request-quote-form-message-placeholder = Describe your project requirements, timeline, and any specific technologies you'd like to use... +request-quote-form-submit = Request Quote +request-quote-form-success = Thank you for your request! I'll get back to you within 24 hours with a detailed quote. +request-quote-additional-info = <p>For urgent requests, you can also reach me directly at <a href="mailto:hello@ontoref.dev">hello@ontoref.dev</a> or via Telegram <a href="https://t.me/example_dev">@example_dev</a>.</p> + +# Legal fallback keys +test-legal-page-title = Legal Information +legal-page-content = <p>This section contains important legal information about our website and services.</p> \ No newline at end of file diff --git a/site/site/i18n/locales/en/pages/typedialog.ftl b/site/site/i18n/locales/en/pages/typedialog.ftl new file mode 100644 index 0000000..279f342 --- /dev/null +++ b/site/site/i18n/locales/en/pages/typedialog.ftl @@ -0,0 +1,53 @@ +# TypeDialog page — Type-Safe Interactive Dialogs + +# Hero +typedialog-badge = 3,818 Tests | 6 Backends | 4 LLM Providers +typedialog-tagline = Typed dialogs for inputs, forms and schemas you can trust +typedialog-page-title = Create Type-Safe Interactive Dialogs +typedialog-page-subtitle = Declarative forms with Nickel/TOML definitions, 6 backends (CLI, TUI, Web, AI, Agent, Prov-Gen), and type-safe validation. From interactive prompts to infrastructure generation. One schema. Every surface. + +# Problems +typedialog-problems-title = What TypeDialog Solves +typedialog-problem-1-title = Per-Backend Code +typedialog-problem-1-desc = One Nickel or TOML definition drives CLI, TUI, Web, and AI. No per-backend form code. The schema is the single source of truth. +typedialog-problem-2-title = Fail-Open Validation +typedialog-problem-2-desc = Nickel contracts validate every predicate at load time. Unknown predicates cause hard failures, not silent passes. No parallel Rust reimplementation. +typedialog-problem-3-title = Hidden I/O in Execution +typedialog-problem-3-desc = Fragment loading happens once at load_form(). The three-phase executor is pure — no filesystem access, no side effects during user interaction. +typedialog-problem-4-title = Manual IaC Assembly +typedialog-problem-4-desc = Interactive forms generate validated infrastructure configurations for 6 cloud providers. 7-layer validation pipeline from forms to final JSON. + +# How It Works +typedialog-how-title = How It Works +typedialog-how-forms-title = Declarative Forms +typedialog-how-forms-desc = Define forms in Nickel (.ncl) or TOML (.toml). Nickel provides contracts, imports, and type-safe composition. TOML provides zero-dependency simplicity. Both produce identical FormDefinition structs. +typedialog-how-execution-title = Three-Phase Execution +typedialog-how-execution-desc = Phase 1: Execute selector fields that control conditionals. Phase 2: Build element list (pure, no I/O). Phase 3: Dispatch to backend with when/when_false evaluation. Complete or field-by-field rendering modes. +typedialog-how-factory-title = BackendFactory +typedialog-how-factory-desc = Compile-time feature gates eliminate dead backend code. Runtime BackendType match dispatches to the selected backend. Auto-detection via TYPEDIALOG_BACKEND env var with CLI fallback. +typedialog-how-ai-title = AI & Agent Backends +typedialog-how-ai-desc = AI backend with RAG, embeddings, and semantic search. Agent backend executes .agent.mdx files with multi-LLM support (Claude, OpenAI, Gemini, Ollama). Template variables, file imports, streaming output. +typedialog-how-infra-title = Infrastructure Generation +typedialog-how-infra-desc = Prov-Gen transforms form answers into IaC configurations. 6 cloud providers (AWS, GCP, Azure, Hetzner, UpCloud, LXD). 7-layer validation pipeline from forms to final JSON. +typedialog-how-roundtrip-title = Nickel Roundtrip +typedialog-how-roundtrip-desc = Read .ncl schemas, collect user input via any backend, generate validated .ncl output preserving contracts. ContractParser extracts validators. TemplateRenderer preserves formatting. + +# Backends +typedialog-backends-title = Backends +typedialog-backend-cli-role = inquire — interactive prompts +typedialog-backend-tui-role = ratatui — terminal UI +typedialog-backend-web-role = axum — HTTP forms +typedialog-backend-ai-role = RAG & embeddings +typedialog-backend-agent-role = Multi-LLM execution +typedialog-backend-provgen-role = IaC generation + +# Tech stack +typedialog-tech-stack-title = Technology Stack + +# Architecture +typedialog-architecture-title = Architecture + +# CTA +typedialog-cta-title = Type-safe dialogs for every surface +typedialog-cta-subtitle = Built with Rust | Open Source | MIT License +typedialog-cta-explore = Explore Git Repo diff --git a/site/site/i18n/locales/en/pages/user.ftl b/site/site/i18n/locales/en/pages/user.ftl new file mode 100644 index 0000000..845887a --- /dev/null +++ b/site/site/i18n/locales/en/pages/user.ftl @@ -0,0 +1,115 @@ +# User page specific translations - English + +# Page Metadata +user-page-title = User Profile +user-page-description = View user profile information +user-page-keywords = user, profile + +# User Dashboard +user-dashboard = User Dashboard +user-welcome = Welcome to your user dashboard +user-profile = Profile +user-account = Account +user-settings = Settings + +# Dashboard Tabs +user-tab-profile = Profile +user-tab-bookmarks = Bookmarks +user-tab-messages = Messages +user-tab-notes = Notes +user-tab-resources = Resources + +# Dashboard Cards +user-profile-description = Manage your profile information and settings. +user-settings-description = Configure your account preferences and security settings. +user-edit-profile = Edit Profile + +# Profile Section +user-profile-info = Profile Information +user-name = Name +user-email = Email +user-phone = Phone +user-company = Company +user-role = Role +user-location = Location +user-bio = Bio + +# Account Management +user-account-settings = Account Settings +user-change-password = Change Password +user-update-profile = Update Profile +user-delete-account = Delete Account +user-account-security = Account Security +user-two-factor-auth = Two-Factor Authentication +user-login-history = Login History + +# Project Management +user-projects = My Projects +user-active-projects = Active Projects +user-completed-projects = Completed Projects +user-project-requests = Project Requests +user-new-project = New Project Request +user-view-project = View Project +user-project-status = Project Status + +# Notifications +user-notifications = Notifications +user-unread-messages = Unread Messages +user-system-alerts = System Alerts +user-email-preferences = Email Preferences +user-notification-settings = Notification Settings + +# Services / Bookmarks +user-services = My Services +user-services-empty = No saved items yet +user-bookmarks-empty = No bookmarks yet + +# Messages +user-messages-empty = No messages +user-messages-subject = Subject +user-messages-from = From +user-messages-unread = unread +user-messages-mark-read = Mark as read + +# Notes +user-notes-empty = No notes yet +user-notes-create = New note +user-notes-title-ph = Title... +user-notes-body-ph = Write your note... +user-notes-save = Save +user-notes-delete = Delete +user-notes-saved = Note saved + +# Resources +user-resources-empty = No resources available +user-resources-open = Open + +# Danger zone +user-danger-zone = Danger Zone +user-service-history = Service History +user-invoices = Invoices +user-billing = Billing +user-payment-methods = Payment Methods + +# Support +user-support = Support +user-help-center = Help Center +user-contact-support = Contact Support +user-faq = Frequently Asked Questions +user-documentation = Documentation + +# Actions +user-save-changes = Save Changes +user-cancel = Cancel +user-edit = Edit +user-delete = Delete +user-download = Download +user-upload = Upload +user-logout = Logout + +# Status Messages +user-profile-updated = Profile updated successfully +user-changes-saved = Changes saved +user-error-occurred = An error occurred +user-please-try-again = Please try again +user-loading = Loading... diff --git a/site/site/i18n/locales/en/pages/vapora.ftl b/site/site/i18n/locales/en/pages/vapora.ftl new file mode 100644 index 0000000..19c516f --- /dev/null +++ b/site/site/i18n/locales/en/pages/vapora.ftl @@ -0,0 +1,68 @@ +# Vapora page — Intelligent Development Orchestration Platform + +# Hero +vapora-badge = ✅ v1.2.0 | 620 Tests | 100% Pass Rate +vapora-tagline = Evaporate complexity +vapora-page-title = Development Flows When Teams and AI Orchestrate +vapora-page-subtitle = Specialized agents orchestrate pipelines for design, implementation, testing, documentation and deployment. Agents learn from history and optimize costs automatically. 100% self-hosted on Kubernetes — no SaaS, complete control. + +# Architecture diagram +vapora-architecture-title = Architecture Overview + +# Problems section +vapora-problems-title = The 4 Problems It Solves +vapora-problem-1-title = Context Switching +vapora-problem-1-desc = Developers jump between tools constantly. Vapora unifies everything in one intelligent system where context flows. +vapora-problem-2-title = Knowledge Fragmentation +vapora-problem-2-desc = Decisions lost in threads, code scattered, docs unmaintained. RLM with hybrid search (BM25 + semantic) and chunking makes knowledge discoverable even in 100k+ token documents. +vapora-problem-3-title = Manual Coordination +vapora-problem-3-desc = Orchestrating code review, testing, documentation and deployment manually creates bottlenecks. Multi-agent workflows solve this. +vapora-problem-4-title = Dev-Ops Friction +vapora-problem-4-desc = Handoffs between developers and operations lack visibility and context. Vapora maintains unified deployment readiness. + +# Features section +vapora-features-title = How It Works +vapora-feat-agents-title = Specialized Agents +vapora-feat-agents-desc = 71 tests verify agent orchestration, learning profiles, and task assignment. Agents track expertise per task type with 7-day recency bias (3× weight). Real SurrealDB persistence + NATS coordination. +vapora-feat-orchestration-title = Intelligent Orchestration +vapora-feat-orchestration-desc = 53 tests verify multi-provider routing (Claude, OpenAI, Gemini, Ollama), per-role budget limits, cost tracking, and automatic fallback chains. Swarm coordination with load-balanced assignment. +vapora-feat-rlm-title = Recursive Language Models (RLM) +vapora-feat-rlm-desc = Process 100k+ token documents without context limits. Hybrid search combines BM25 (keywords) + semantic embeddings via RRF fusion. Intelligent chunking (Fixed/Semantic/Code) with SurrealDB persistence. +vapora-feat-a2a-title = Agent-to-Agent (A2A) Protocol +vapora-feat-a2a-desc = Distributed agent coordination with task dispatch, status tracking, and result collection. NATS messaging for async completion. Exponential backoff retry with circuit breaker. 12 integration tests. +vapora-feat-knowledge-title = Knowledge Graph +vapora-feat-knowledge-desc = Temporal execution history with causal relationships. Learning curves from daily windowed aggregations. Similarity search recommends solutions from past tasks. 20 tests verify graph persistence and learning profiles. +vapora-feat-nats-title = NATS JetStream +vapora-feat-nats-desc = Reliable message delivery for agent coordination. JetStream streams for workflow events, task completion, and status updates. Graceful fallback when NATS unavailable. +vapora-feat-surrealdb-title = SurrealDB +vapora-feat-surrealdb-desc = Multi-model database with graph capabilities. Multi-tenant scopes for workspace isolation. Native graph relations for Knowledge Graph. All queries use parameterized bindings. SCHEMAFULL tables with explicit indexes. +vapora-feat-api-title = Backend API & MCP Connectors +vapora-feat-api-desc = 40+ REST endpoints (projects, tasks, agents, workflows, swarm). WebSocket real-time updates. MCP gateway for external tool integration. Prometheus metrics. 161 tests verify API correctness. +vapora-feat-webhooks-title = Webhook Notifications +vapora-feat-webhooks-desc = Real-time alerts to Slack, Discord, and Telegram — no vendor SDKs. ${VAR} secret resolution built into ChannelRegistry. Fire-and-forget hooks on task completion, proposal approval, and workflow lifecycle events. +vapora-feat-capabilities-title = Capability Packages +vapora-feat-capabilities-desc = Domain-optimized agent bundles — system prompt, preferred LLM, task types, and MCP tools pre-configured per role. Three built-ins (code-reviewer, doc-generator, pr-monitor) at startup. TOML overrides to swap model or prompt without code changes. 22 tests. + +# Tech stack +vapora-tech-stack-title = Technology Stack + +# Agents +vapora-agents-title = Available Agents +vapora-agent-architect-role = System design +vapora-agent-developer-role = Code implementation +vapora-agent-reviewer-role = Quality assurance +vapora-agent-tester-role = Tests & benchmarks +vapora-agent-documenter-role = Documentation +vapora-agent-marketer-role = Marketing content +vapora-agent-presenter-role = Presentations +vapora-agent-devops-role = CI/CD deployment +vapora-agent-monitor-role = Health & alerting +vapora-agent-security-role = Audit & compliance +vapora-agent-pm-role = Roadmap tracking +vapora-agent-decisionmaker-role = Conflict resolution + +# CTA +vapora-cta-title = Ready for intelligent orchestration? +vapora-cta-subtitle = Built with Rust 🦀 | Open Source | Self-Hosted +vapora-cta-explore = Explore Git Repo +vapora-cta-visit = Visit vapora.dev diff --git a/site/site/i18n/locales/en/pages/work_request.ftl b/site/site/i18n/locales/en/pages/work_request.ftl new file mode 100644 index 0000000..ab066e0 --- /dev/null +++ b/site/site/i18n/locales/en/pages/work_request.ftl @@ -0,0 +1,181 @@ +# Work request page specific translations - English + +# Page Metadata +work-request-page-title = Work Request +work-request-page-description = Submit a work request for your project +work-request-page-keywords = work, request, consultation, project + +# Page Header +work-request-title = Request a Project Quote +work-request-subtitle = Let's discuss your project requirements and how I can help you achieve your goals. +work-request-description = Fill out the form below with your project details, and I'll get back to you within 24 hours with a personalized proposal. + +# Project Information Section +work-request-project-info = Project Information +work-request-project-title = Project Title +work-request-project-title-placeholder = Brief title for your project +work-request-project-description = Project Description +work-request-project-description-placeholder = Describe your project, goals, and requirements in detail... +work-request-project-type = Project Type +work-request-timeline = Timeline +work-request-budget = Budget Range +work-request-priority = Priority Level + +# Service Types +work-request-service-types = Service Types (select all that apply) +work-request-rust-development = Rust Development +work-request-web-applications = Web Applications +work-request-infrastructure = Infrastructure & DevOps +work-request-web3-polkadot = Web3 & Polkadot +work-request-consulting = Technical Consulting +work-request-code-review = Code Review & Auditing +work-request-training = Training & Mentoring +work-request-custom-solution = Custom Solution + +# Timeline Options +work-request-timeline-asap = ASAP (Rush job) +work-request-timeline-1-month = Within 1 month +work-request-timeline-3-months = 1-3 months +work-request-timeline-6-months = 3-6 months +work-request-timeline-flexible = Flexible timeline + +# Budget Options +work-request-budget-under-5k = Under $5,000 +work-request-budget-5k-15k = $5,000 - $15,000 +work-request-budget-15k-50k = $15,000 - $50,000 +work-request-budget-50k-plus = $50,000+ +work-request-budget-discuss = Let's discuss + +# Priority Levels +work-request-priority-low = Low +work-request-priority-medium = Medium +work-request-priority-high = High +work-request-priority-urgent = Urgent + +# Contact Information +work-request-contact-info = Contact Information +work-request-your-name = Your Name +work-request-your-name-placeholder = Full name +work-request-your-email = Email Address +work-request-your-email-placeholder = your.email@company.com +work-request-company = Company/Organization +work-request-company-placeholder = Company name (optional) +work-request-phone = Phone Number +work-request-phone-placeholder = +1 (555) 123-4567 (optional) +work-request-preferred-contact = Preferred Contact Method +work-request-contact-email = Email +work-request-contact-phone = Phone +work-request-contact-telegram = Telegram + +# Technical Details +work-request-technical-details = Technical Details (Optional) +work-request-current-stack = Current Technology Stack +work-request-current-stack-placeholder = What technologies are you currently using? +work-request-specific-requirements = Specific Requirements +work-request-specific-requirements-placeholder = Any specific technical requirements, constraints, or preferences... +work-request-existing-codebase = Existing Codebase +work-request-codebase-new = New project from scratch +work-request-codebase-existing = Working with existing code +work-request-codebase-migration = Migration/modernization project + +# Additional Information +work-request-additional-info = Additional Information +work-request-how-did-you-find = How did you find me? +work-request-referral-source = Referral Source +work-request-additional-notes = Additional Notes +work-request-additional-notes-placeholder = Anything else you'd like me to know about your project? + +# Form Actions +work-request-submit = Submit Request +work-request-submitting = Submitting... +work-request-cancel = Cancel +work-request-reset-form = Reset Form + +# Required Fields +work-request-required = Required +work-request-required-fields = Fields marked with * are required + +# Validation Messages +work-request-name-required = Name is required +work-request-email-required = Email address is required +work-request-email-invalid = Please enter a valid email address +work-request-project-title-required = Project title is required +work-request-project-description-required = Project description is required +work-request-service-type-required = Please select at least one service type + +# Success/Error Messages +work-request-success-title = Request Submitted Successfully! +work-request-success-message = Thank you for your project request. I'll review your requirements and get back to you within 24 hours with a detailed proposal. +work-request-success-next-steps = What happens next? +work-request-success-step-1 = I'll review your project requirements +work-request-success-step-2 = Prepare a detailed proposal and timeline +work-request-success-step-3 = Schedule a call to discuss details +work-request-error-title = Submission Failed +work-request-error-message = There was an error submitting your request. Please try again or contact me directly. +work-request-try-again = Try Again +work-request-contact-direct = Contact Me Directly + +# Terms and Conditions +work-request-terms = Terms & Conditions +work-request-terms-text = By submitting this request, you agree to our terms of service and privacy policy. +work-request-terms-checkbox = I agree to the terms and conditions +work-request-privacy-notice = Your information will be kept confidential and used only to respond to your request. + +# Call to Action +work-request-ready-to-start = Ready to Start Your Project? +work-request-cta-description = I specialize in Rust development, infrastructure automation, and Web3 solutions. Let's build something amazing together. +work-request-alternative-contact = Prefer to talk first? Schedule a free consultation call. +work-request-schedule-call = Schedule Call + +work-request-form-action = mailto:hello@ontoref.dev + +# Missing keys needed by the component +work-request-project-type-label = Project Type +work-request-project-type-placeholder = Select project type +work-request-project-type-new = New Project +work-request-project-type-existing = Existing Project Enhancement +work-request-project-type-infrastructure = Infrastructure & DevOps +work-request-project-type-consulting = Technical Consulting +work-request-project-type-mentoring = Mentoring & Training +work-request-project-type-training = Training Programs +work-request-project-type-audit = Code Review & Audit +work-request-project-type-other = Other + +work-request-project-description-label = Project Description + +work-request-current-situation-label = Current Situation +work-request-current-situation-placeholder = What's your current technical situation or challenge? + +work-request-goals-label = Project Goals +work-request-goals-placeholder = What do you want to achieve with this project? + +work-request-technical-requirements-label = Technical Requirements +work-request-technical-requirements-placeholder = Any specific technical requirements, constraints, or preferences... + +work-request-timeline-budget = Timeline & Budget +work-request-timeline-label = Timeline +work-request-timeline-placeholder = Select timeline +work-request-timeline-1-3-months = 1-3 months +work-request-timeline-3-6-months = 3-6 months +work-request-timeline-6plus = 6+ months + +work-request-communication-preferences = Communication Preferences +work-request-contact-method-label = Preferred Contact Method +work-request-contact-method-placeholder = Select contact method +work-request-contact-method-email = Email +work-request-contact-method-telegram = Telegram +work-request-contact-method-video = Video Call +work-request-contact-method-phone = Phone Call + +work-request-submit-btn = Submit Project Request +work-request-submit-note = I'll get back to you within 24 hours with a detailed proposal. + +work-request-what-happens-next = What Happens Next? +work-request-step-1-title = Review & Analysis +work-request-step-1-description = I'll carefully review your project requirements and technical needs. +work-request-step-2-title = Detailed Proposal +work-request-step-2-description = You'll receive a comprehensive proposal with timeline and pricing. +work-request-step-3-title = Planning Call +work-request-step-3-description = We'll schedule a call to discuss details and next steps. + +work-request-submit-success = Your request has been sent\! I'll be in touch within 24 hours. diff --git a/site/site/i18n/locales/es/auth.ftl b/site/site/i18n/locales/es/auth.ftl new file mode 100644 index 0000000..69c4ec0 --- /dev/null +++ b/site/site/i18n/locales/es/auth.ftl @@ -0,0 +1,126 @@ +# Authentication - Spanish + +# Authentication Actions +auth-sign-in = Iniciar Sesión +auth-sign-up = Registrarse +auth-sign-out = Cerrar Sesión +auth-login = Iniciar Sesión +auth-register = Registrarse +auth-logout = Cerrar Sesión + +# User Information +auth-email = Correo Electrónico +auth-password = Contraseña +auth-username = Nombre de Usuario +auth-display-name = Nombre para Mostrar +auth-confirm-password = Confirmar Contraseña +auth-remember-me = Recordarme +auth-forgot-password = ¿Olvidaste tu contraseña? +auth-create-account = Crear Cuenta +auth-already-have-account = ¿Ya tienes una cuenta? +auth-dont-have-account = ¿No tienes una cuenta? + +# Form Labels and Placeholders +auth-email-address = Dirección de Correo Electrónico +auth-enter-email = Ingresa tu correo electrónico +auth-enter-username = Elige un nombre de usuario +auth-enter-password = Ingresa tu contraseña +auth-create-password = Crea una contraseña segura +auth-confirm-your-password = Confirma tu contraseña +auth-how-should-we-call-you = ¿Cómo deberíamos llamarte? + +# Success Messages +auth-sign-in-success = Inicio de sesión exitoso +auth-registration-success = Registro exitoso +auth-logout-success = Cierre de sesión exitoso + +# Validation Messages +auth-password-required = La contraseña es requerida +auth-email-required = El correo electrónico es requerido +auth-username-required = El nombre de usuario es requerido +auth-passwords-no-match = Las contraseñas no coinciden +auth-passwords-match = Las contraseñas coinciden +auth-password-too-short = La contraseña debe tener al menos 8 caracteres +auth-invalid-email = Por favor ingresa un correo electrónico válido +auth-username-format = 3-50 caracteres, solo letras, números, guiones bajos y guiones + +# Password Strength +auth-password-strength = Fuerza de la contraseña: +auth-very-weak = Muy Débil +auth-weak = Débil +auth-fair = Regular +auth-good = Buena +auth-strong = Fuerte +auth-password-requirements = Debe tener al menos 8 caracteres con mayúscula, minúscula, número y carácter especial + +# OAuth Providers +auth-continue-with = O continúa con +auth-sign-up-with = O regístrate con +auth-google = Google +auth-github = GitHub +auth-discord = Discord +auth-microsoft = Microsoft + +# Terms and Privacy +auth-agree-to-terms = Acepto los +auth-terms-of-service = Términos de Servicio +auth-privacy-policy = Política de Privacidad +auth-and = y + +# Error Messages +auth-invalid-credentials = Correo electrónico o contraseña inválidos +auth-user-not-found = Usuario no encontrado +auth-email-already-exists = Ya existe una cuenta con este correo electrónico +auth-username-already-exists = Este nombre de usuario ya está en uso +auth-account-not-verified = Por favor verifica tu correo electrónico antes de iniciar sesión +auth-account-suspended = Tu cuenta ha sido suspendida +auth-rate-limit-exceeded = Demasiados intentos. Por favor intenta de nuevo más tarde +auth-network-error = Error de red. Por favor verifica tu conexión +auth-login-failed = Error al iniciar sesión +auth-registration-failed = Error en el registro +auth-session-expired = Tu sesión ha expirado. Por favor inicia sesión de nuevo +auth-invalid-token = Token de autenticación inválido +auth-token-expired = Tu token de autenticación ha expirado +auth-insufficient-permissions = No tienes permisos para realizar esta acción +auth-oauth-error = Error de autenticación OAuth +auth-database-error = Ocurrió un error en la base de datos. Por favor intenta de nuevo +auth-internal-error = Ocurrió un error interno. Por favor intenta de nuevo +auth-validation-error = Por favor revisa tu información e intenta de nuevo +auth-authentication-failed = Error de autenticación +auth-server-error = Error del servidor. Por favor intenta más tarde +auth-request-failed = La solicitud falló. Por favor intenta de nuevo +auth-unknown-error = Ocurrió un error desconocido + +# OTP / Inicio de sesión sin contraseña +auth-otp-modal-title = Iniciar sesión o registrarse +auth-otp-modal-subtitle = Introduce tu email para continuar +auth-otp-check-inbox = Comprueba tu bandeja de entrada +auth-otp-sent-to = Introduce el código de verificación que enviamos a { $email } +auth-otp-resend = Reenviar email +auth-otp-code-placeholder = Código +auth-otp-code-expired = Código expirado. Solicita uno nuevo. +auth-otp-code-invalid = Código incorrecto. { $remaining -> + [one] { $remaining } intento restante. + *[other] { $remaining } intentos restantes. +} +auth-otp-too-many-attempts = Demasiados intentos fallidos. Solicita un nuevo código. +auth-otp-success = Sesión iniciada correctamente. +auth-otp-continue = Continuar +auth-otp-resend-countdown = Reenviar en { $seconds }s +auth-otp-resend-now = Reenviar código +auth-otp-back-to-email = ← Cambiar email +auth-modal-close = Cerrar + +# Paso de consentimiento GDPR (nuevas cuentas) +auth-gdpr-title = Crea tu cuenta +auth-gdpr-description = Antes de crear tu cuenta, necesitamos tu consentimiento para procesar tus datos de acuerdo con nuestra Política de Privacidad. +auth-gdpr-accept-required = Acepto los <a href="/privacidad" class="link link-primary" target="_blank" rel="noopener noreferrer">Términos de Servicio y la Política de Privacidad (obligatorio)</a> +auth-gdpr-accept-marketing = Acepto recibir boletines ocasionales y novedades del producto (opcional) +auth-gdpr-submit = Crear cuenta + +# Email: código de verificación OTP +auth-email-otp-subject = Tu código de verificación +auth-email-otp-title = Tu código de verificación +auth-email-otp-subtitle = Usa este código para iniciar sesión: +auth-email-otp-expire-msg = Este código expira en %minutes% minutos. +auth-email-otp-ignore-msg = Si no solicitaste esto, puedes ignorar este correo. \ No newline at end of file diff --git a/site/site/i18n/locales/es/common.ftl b/site/site/i18n/locales/es/common.ftl new file mode 100644 index 0000000..8968fd9 --- /dev/null +++ b/site/site/i18n/locales/es/common.ftl @@ -0,0 +1,24 @@ +# Traducciones comunes compartidas en todo el sitio +common-welcome = Bienvenido a los Servicios Profesionales de Jesús Pérez +common-not-found = Página no encontrada. +common-home = Inicio +common-about = Acerca de +common-services = Servicios +common-blog = Blog +common-contact = Contacto +common-user = Usuario +common-main-desc = Desarrollador Rust y Consultor de Infraestructura +pages = Páginas + +# Navegación común +common-user-page = Página de usuario con ID: { $id } + +# Selección de idioma +common-language = Idioma +common-select-language = Seleccionar Idioma +common-english = English +common-spanish = Español + +# Visor de imagen +common-expand-image = Ampliar +common-close = Cerrar diff --git a/site/site/i18n/locales/es/components/footer.ftl b/site/site/i18n/locales/es/components/footer.ftl new file mode 100644 index 0000000..d1601ab --- /dev/null +++ b/site/site/i18n/locales/es/components/footer.ftl @@ -0,0 +1,13 @@ +# Traducciones del componente footer - Español + +footer-links = Enlaces +# Enlaces del Footer +footer-privacy = Privacidad +footer-legal = Legal +footer-copyright-name = Jesús Pérez +footer-scroll-to-top = Ir al inicio +footer-social-links = Conéctate conmigo + +# Atribución del Footer +footer-built-with = Construido con +footer-hosting = Alojado en { $host } diff --git a/site/site/i18n/locales/es/components/forms.ftl b/site/site/i18n/locales/es/components/forms.ftl new file mode 100644 index 0000000..63b5ac4 --- /dev/null +++ b/site/site/i18n/locales/es/components/forms.ftl @@ -0,0 +1,66 @@ +# Traducciones de componentes de formularios - Español + +# Etiquetas comunes de formularios +form-name-label = Nombre +form-email-label = Correo electrónico +form-subject-label = Asunto +form-message-label = Mensaje +form-required-field = * +form-submit-button = Enviar +form-send-message = Enviar Mensaje +form-sending = Enviando... +form-cancel = Cancelar +form-reset = Restablecer + +# Marcadores de posición +form-name-placeholder = Tu nombre completo +form-email-placeholder = tu.correo@ejemplo.com +form-subject-placeholder = Breve descripción de tu consulta +form-message-placeholder = Por favor describe tu mensaje en detalle... + +# Mensajes de validación +form-validation-name-required = El nombre es requerido +form-validation-name-too-long = El nombre debe tener menos de 100 caracteres +form-validation-email-required = El correo electrónico es requerido +form-validation-email-invalid = Por favor ingresa una dirección de correo válida +form-validation-subject-required = El asunto es requerido +form-validation-subject-too-long = El asunto debe tener menos de 200 caracteres +form-validation-message-required = El mensaje es requerido +form-validation-message-too-long = El mensaje debe tener menos de 5000 caracteres + +# Estados del formulario +form-success-title = ¡Mensaje enviado exitosamente! +form-success-message = ¡Gracias por tu mensaje! Te responderemos pronto. +form-error-title = Error al enviar el mensaje +form-error-generic = Ocurrió un error al enviar tu mensaje. Por favor intenta de nuevo. + +# Formulario de contacto específico +contact-form-title = Formulario de Contacto +contact-form-description = Ponte en contacto con nosotros + +# Formulario de soporte específico +support-form-title = Solicitud de Soporte +support-form-description = ¿Necesitas ayuda? Envíanos una solicitud de soporte +support-form-priority-label = Prioridad +support-form-category-label = Categoría +support-form-priority-low = Baja +support-form-priority-medium = Media +support-form-priority-high = Alta +support-form-priority-urgent = Urgente +support-form-category-general = Consulta General +support-form-category-technical = Soporte Técnico +support-form-category-billing = Facturación +support-form-category-bug-report = Reporte de Error +support-form-category-feature-request = Solicitud de Funcionalidad + +# Formulario de suscripción específico +subscription-form-title = Suscripción al Boletín +subscription-form-description = Mantente actualizado con nuestras últimas noticias y actualizaciones +subscription-form-subscribe = Suscribirse +subscription-form-subscribing = Suscribiendo... +subscription-form-success = ¡Te has suscrito exitosamente a nuestro boletín! +subscription-form-already-subscribed = Ya estás suscrito a nuestro boletín. + +# Elementos adicionales del formulario +form-select-placeholder = Selecciona una opción... +form-validation-field-too-long = El campo es demasiado largo \ No newline at end of file diff --git a/site/site/i18n/locales/es/components/language_selector.ftl b/site/site/i18n/locales/es/components/language_selector.ftl new file mode 100644 index 0000000..9b01ea0 --- /dev/null +++ b/site/site/i18n/locales/es/components/language_selector.ftl @@ -0,0 +1,43 @@ +# Language Selector component translations - Spanish + +# Language Selector Button +lang-selector-button = Selector de idioma +lang-selector-tooltip = Seleccionar idioma +lang-selector-current = Idioma actual: Español + +# Language Options +lang-english = Inglés +lang-spanish = Español +lang-english-code = EN +lang-spanish-code = ES + +# Language Display Names +lang-en-display = English +lang-es-display = Español +lang-english-display = English +lang-spanish-display = Español + +# Accessibility +lang-selector-aria-label = Menú de selección de idioma +lang-selector-aria-expanded = Menú de idioma expandido +lang-selector-aria-haspopup = Opciones de idioma disponibles +lang-selector-menu-role = Menú de selección de idioma + +# States +lang-selector-loading = Cargando idiomas... +lang-selector-disabled = Selección de idioma no disponible +lang-current-selection = Idioma seleccionado actualmente + +# Icons and UI +lang-icon-alt = Icono de idioma +lang-dropdown-icon = Flecha desplegable +lang-checkmark-alt = Indicador de idioma seleccionado + +# Toggle Component +lang-toggle-title = Cambiar idioma +lang-toggle-switch-to = Cambiar a { $language } +lang-toggle-current = Actual: { $language } + +# Mobile +lang-mobile-label = Idioma +lang-mobile-current = Idioma: { $code } \ No newline at end of file diff --git a/site/site/i18n/locales/es/components/navigation.ftl b/site/site/i18n/locales/es/components/navigation.ftl new file mode 100644 index 0000000..7587840 --- /dev/null +++ b/site/site/i18n/locales/es/components/navigation.ftl @@ -0,0 +1,70 @@ +# Traducciones del componente de navegación - Español + +# Navegación Principal +nav-home = Inicio +nav-about = Acerca de +nav-services = Servicios +nav-projects = Proyectos +nav-domains = Dominios +nav-blog = Blog +nav-prescriptions = Prescripciones +nav-contact = Contacto +nav-admin = Administrador + +# Navegación de Admin +nav-dashboard = Panel de Control +nav-settings = Configuraciones +nav-users = Usuarios +nav-content = Contenido + +# Estado de Usuario +nav-welcome-user = Bienvenido, { $name } +nav-signed-in-as = Conectado como { $email } +nav-last-login = Último acceso: { $date } + +# Acciones de Autenticación +nav-sign-in = Login +nav-sign-up = Registrarse +nav-sign-out = Cerrar Sesión + +# Idioma +nav-language = Idioma +nav-select-language = Seleccionar Idioma + +# Menú Móvil +nav-menu-close = Cerrar menú +nav-menu-open = Abrir menú + +# Elementos Adicionales del Menú +nav-account = Cuenta +nav-docs = Documentación +nav-blocks = Bloques +nav-theme = Tema +nav-pages = Páginas + +# Controles del Menú Móvil +nav-mobile-controls = Controles +nav-mobile-theme-language = Tema e Idioma +nav-menu-toggle = Alternar menú móvil +nav-external-link = Enlace externo + +# Accesibilidad +nav-menu-aria-controls = navbar-collapse +nav-menu-aria-expanded = Menú móvil expandido +nav-menu-aria-label = Alternar menú móvil + +# Estados del Menú +nav-menu-loading = Cargando menú... +nav-menu-error = No se pudo cargar el menú + +# Paleta de Comandos +nav-palette-placeholder = Buscar... +nav-palette-no-results = Sin resultados +nav-palette-hint-navigate = navegar +nav-palette-hint-select = seleccionar +nav-palette-hint-close = cerrar + +# Pastilla Flotante +nav-pill-aria-label = Navegación rápida +nav-pill-show = Mostrar menú de navegación +nav-pill-hide = Ocultar menú de navegación \ No newline at end of file diff --git a/site/site/i18n/locales/es/cookies.ftl b/site/site/i18n/locales/es/cookies.ftl new file mode 100644 index 0000000..4810406 --- /dev/null +++ b/site/site/i18n/locales/es/cookies.ftl @@ -0,0 +1,12 @@ +# Consentimiento de cookies - Español +cookies-banner-text = Usamos cookies para que este sitio funcione, entender el uso del servicio y apoyar acciones de marketing. +cookies-manage = Gestionar cookies +cookies-reject = Rechazar no esenciales +cookies-accept-all = Aceptar todas +cookies-policy-link = Política de cookies +cookies-policy-suffix = para más información. +cookies-essential = Esenciales +cookies-analytics = Analítica +cookies-marketing = Marketing +cookies-save-prefs = Guardar preferencias +cookies-cancel = Cancelar diff --git a/site/site/i18n/locales/es/manager/cli.ftl b/site/site/i18n/locales/es/manager/cli.ftl new file mode 100644 index 0000000..8cd8585 --- /dev/null +++ b/site/site/i18n/locales/es/manager/cli.ftl @@ -0,0 +1,95 @@ +# Page Manager CLI - Spanish Translations +# All keys MUST have mgr- prefix for shared locales system + +# Welcome and headers +mgr-cli-title = Gestor de Páginas Rustelo (para ahora Contenido Estático) +mgr-cli-description = Gestiona y genera nuevas páginas con plantillas, rutas y traducciones +mgr-cli-project-root = Raíz del proyecto: {$path} +mgr-cli-available-languages = Idiomas disponibles: {$languages} + +# Prompts +mgr-prompt-page-name = Nombre de página (PascalCase, ej., 'FormularioContacto', 'AcercaDe'): +mgr-prompt-page-name-help = Usa nomenclatura PascalCase. Este será el nombre de tu componente. +mgr-prompt-template-select = Selecciona plantilla de página: +mgr-prompt-template-help = Elige la plantilla que mejor se adapte a las necesidades de tu página +mgr-prompt-languages-select = Selecciona idiomas: +mgr-prompt-languages-help = Elige para qué idiomas generar traducciones +mgr-prompt-route-path = Ruta de página: +mgr-prompt-route-path-help = Ruta URL para esta página (debe empezar con '/') +mgr-prompt-auto-scaffold = ¿Auto-generar archivos de página? +mgr-prompt-auto-scaffold-help = Generar mod.rs, unified.rs y archivos de traducción FTL +mgr-prompt-custom-priority = ¿Establecer prioridad de ruta personalizada? +mgr-prompt-custom-priority-help = Las rutas de mayor prioridad se evalúan primero (por defecto: 1.0) +mgr-prompt-priority-value = Prioridad de ruta (0.1 - 10.0): +mgr-prompt-custom-patterns = ¿Personalizar patrones de traducción i18n? +mgr-prompt-custom-patterns-help = Avanzado: personalizar los prefijos usados para claves de traducción +mgr-prompt-patterns-value = Patrones i18n (separados por comas): +mgr-prompt-patterns-example = Ejemplo: page-gen-contacto-, formulario-contacto- +mgr-prompt-final-confirm = ¿Generar página con esta configuración? + +# Configuration summary +mgr-config-summary = 📋 Resumen de Configuración: +mgr-config-name = Nombre: {$name} +mgr-config-module = Módulo: {$module} +mgr-config-template = Plantilla: {$template} ({$description}) +mgr-config-languages = Idiomas: {$languages} +mgr-config-route = Ruta: {$route} +mgr-config-auto-scaffold = Auto-generar: {$enabled} + +# Labels +mgr-label-name = Nombre +mgr-label-module = Módulo +mgr-label-template = Plantilla +mgr-label-languages = Idiomas +mgr-label-route = Ruta +mgr-label-auto-scaffold = Auto-generar + +# Text values +mgr-text-yes = Sí +mgr-text-no = No + +# Status messages +mgr-status-generating = 🏗️ Generando página... +mgr-status-cancelled = 🚫 Generación cancelada. +mgr-status-completed = ✨ ¡Generación de página completada exitosamente! + +# Next steps +mgr-next-steps = 💡 Próximos pasos: +mgr-next-steps-with-scaffold = 💡 Próximos pasos:\n 1. Ejecuta 'cargo build' para compilar la nueva página\n 2. Revisa los archivos FTL generados y personaliza las traducciones\n 3. Implementa la lógica de tu página en el archivo unified.rs\n 4. Prueba tu página en {$route} +mgr-next-steps-without-scaffold = 💡 Próximos pasos:\n 1. Ejecuta 'cargo build' para actualizar la generación de rutas\n 2. Implementa el componente de página manualmente\n 3. Agrega la exportación del componente a pages/src/lib.rs + +# Warnings +mgr-warning-page-exists = El módulo de página '{$module}' ya existe. +mgr-warning-overwrite-confirm = ¿Sobrescribir página existente? +mgr-warning-cannot-proceed = No se puede continuar con página existente. Elige un nombre diferente o permite sobrescribir. + +# Error messages +mgr-error-project-root = No se pudo encontrar la raíz del proyecto. Ejecuta este comando desde un proyecto Rustelo. +mgr-error-generator-init = Error al inicializar el generador de páginas +mgr-error-page-name = Error al obtener el nombre de página +mgr-error-template-selection = Error al obtener la selección de plantilla +mgr-error-language-selection = Error al obtener la selección de idioma +mgr-error-route-path = Error al obtener la ruta de página +mgr-error-auto-scaffold = Error al obtener la preferencia de auto-generación +mgr-error-priority = Error al obtener la preferencia de prioridad +mgr-error-priority-value = Error al obtener la prioridad de ruta +mgr-error-patterns = Error al obtener la preferencia de patrones i18n +mgr-error-patterns-value = Error al obtener los patrones i18n +mgr-error-confirmation = Error al obtener la confirmación +mgr-error-generation = Error en la generación de página +mgr-error-at-least-one-language = Debe seleccionarse al menos un idioma + +# Validation errors +mgr-validation-name-empty = El nombre de página no puede estar vacío +mgr-validation-name-case = El nombre de página debe empezar con letra mayúscula (PascalCase) +mgr-validation-name-chars = El nombre de página debe contener solo letras ASCII y números +mgr-validation-name-length = Nombre de página demasiado largo (máximo 50 caracteres) +mgr-validation-name-reserved = '{$name}' es un nombre reservado, por favor elige otro +mgr-validation-path-empty = La ruta de página no puede estar vacía +mgr-validation-path-start = La ruta de página debe empezar con '/' +mgr-validation-path-end = La ruta de página no puede terminar con '/' (excepto raíz) +mgr-validation-path-consecutive = La ruta de página no puede contener barras consecutivas +mgr-validation-path-chars = La ruta de página debe contener solo caracteres ASCII y sin espacios +mgr-validation-path-reserved = '{$path}' conflicta con rutas reservadas +mgr-validation-priority-range = La prioridad debe estar entre 0.1 y 10.0 +mgr-validation-priority-number = La prioridad debe ser un número válido \ No newline at end of file diff --git a/site/site/i18n/locales/es/manager/dashboard.ftl b/site/site/i18n/locales/es/manager/dashboard.ftl new file mode 100644 index 0000000..fcd4cf2 --- /dev/null +++ b/site/site/i18n/locales/es/manager/dashboard.ftl @@ -0,0 +1,83 @@ +# Dashboard Menu Translations - Spanish + +# Dashboard +dashboard-title = Panel de Desarrollo Rustelo +dashboard-description = Kit integral de herramientas de desarrollo para proyectos Rustelo + +# Categories +category-content-name = Gestión de Contenido +category-content-description = Crear, editar y gestionar contenido +category-development-name = Herramientas de Desarrollo +category-development-description = Utilidades de desarrollo y depuración +category-system-name = Sistema y APIs +category-system-description = Información del sistema y gestión de APIs +category-deployment-name = Construcción y Despliegue +category-deployment-description = Herramientas de construcción, pruebas y despliegue + +# Content Management Actions +action-create-page-name = Crear Nueva Página +action-create-page-description = Generar una nueva página con plantillas, rutas y traducciones +action-edit-content-name = Editar Contenido Estático +action-edit-content-description = Editar páginas existentes, posts de blog y recetas +action-manage-translations-name = Gestionar Traducciones +action-manage-translations-description = Editar archivos de traducción FTL para múltiples idiomas +action-content-preview-name = Vista Previa de Contenido +action-content-preview-description = Previsualizar contenido con recarga en caliente + +# Development Tools Actions +action-dev-server-name = Iniciar Servidor de Desarrollo +action-dev-server-description = Lanzar servidor de desarrollo con recarga automática +action-css-watch-name = Modo de Vigilancia CSS +action-css-watch-description = Vigilar y reconstruir archivos CSS automáticamente +action-browser-logs-name = Registros del Navegador +action-browser-logs-description = Recopilar y ver registros de consola del navegador +action-code-format-name = Formatear Código +action-code-format-description = Formatear código Rust con rustfmt y clippy + +# System & APIs Actions +action-show-apis-name = Mostrar APIs Disponibles +action-show-apis-description = Listar todos los endpoints de API y rutas disponibles +action-system-info-name = Información del Sistema +action-system-info-description = Mostrar información del sistema, entorno y configuración +action-route-inspector-name = Inspector de Rutas +action-route-inspector-description = Inspeccionar y validar configuraciones de rutas +action-config-manager-name = Gestor de Configuración +action-config-manager-description = Ver y editar archivos de configuración + +# Build & Deploy Actions +action-build-prod-name = Construcción de Producción +action-build-prod-description = Construir para despliegue en producción +action-run-tests-name = Ejecutar Pruebas +action-run-tests-description = Ejecutar todas las pruebas y controles de calidad +action-quality-check-name = Control de Calidad +action-quality-check-description = Ejecutar controles de calidad integrales +action-deploy-status-name = Estado del Despliegue +action-deploy-status-description = Verificar estado y salud del despliegue + +# Quick Actions +action-help-name = Ayuda +action-help-description = Mostrar atajos de teclado y ayuda +action-refresh-name = Actualizar +action-refresh-description = Actualizar datos del panel +action-settings-name = Configuración +action-settings-description = Configuración y preferencias del panel +action-quit-name = Salir +action-quit-description = Salir del panel + +# UI Elements +ui-items = Elementos +ui-items-focused = Elementos [ENFOCADO] +ui-subitems = Sub-elementos +ui-subitems-focused = Sub-elementos [ENFOCADO] +ui-quick-actions = Acciones Rápidas +ui-action-result = Resultado +ui-language-selector = Idioma +ui-chat-prompt = Preguntar a Asistente IA +ui-chat-placeholder = Presiona '/' para escribir o Tab para enfocar... +ui-send-button = Enviar +ui-clear-button = Limpiar +ui-ai-button = IA +ui-copy-button = Copiar +ui-space-switch = Espacio Cambiar Panel +ui-chat-focus = Chat +ui-f12-language = F12 Idioma \ No newline at end of file diff --git a/site/site/i18n/locales/es/manager/test_now.ftl b/site/site/i18n/locales/es/manager/test_now.ftl new file mode 100644 index 0000000..7163db9 --- /dev/null +++ b/site/site/i18n/locales/es/manager/test_now.ftl @@ -0,0 +1,6 @@ +# Auto-generated FTL file for test-now (es) +# Generated at: 2025-08-28 09:48:11 UTC by rustelo-page +# TODO: Customize these translations + +page-gen-test-now-title = Título de Página +page-gen-test-now-description = Descripción de la página generada automáticamente. diff --git a/site/site/i18n/locales/es/pages/about-us.ftl b/site/site/i18n/locales/es/pages/about-us.ftl new file mode 100644 index 0000000..8fe96f3 --- /dev/null +++ b/site/site/i18n/locales/es/pages/about-us.ftl @@ -0,0 +1,9 @@ +# Traducciones específicas de la página Nosotros - Español + +# Navegación +nav-about-us = Nosotros + +# Metadatos de Página +about-us-page-title = Nosotros +about-us-page-subtitle = Las personas y la práctica detrás de Ontoref. +about-us-page-description = Ontoref es diseñado y mantenido por Jesús Pérez, desarrollador Rust y arquitecto de sistemas de aprovisionamiento con más de tres décadas en software. Nace del trabajo directo con infraestructura auto-hospedada y de código abierto, y de la convicción de que los sistemas deben portar su propio razonamiento: una ontología explícita, decisiones registradas y una reflexión disciplinada. Construimos herramientas pequeñas y soberanas que las organizaciones pueden poseer y controlar de verdad, y compartimos el protocolo y su práctica de forma abierta con una comunidad creciente de colaboradores. diff --git a/site/site/i18n/locales/es/pages/about.ftl b/site/site/i18n/locales/es/pages/about.ftl new file mode 100644 index 0000000..f8f7b8a --- /dev/null +++ b/site/site/i18n/locales/es/pages/about.ftl @@ -0,0 +1,68 @@ +# Traducciones específicas de la página acerca de - Español + +# Metadatos de Página +about-page-title = Acerca de Jesús Pérez +about-page-description = Aprende más sobre mi trasfondo y experiencia como desarrollador Rust +about-page-keywords = acerca de, experiencia, trasfondo, rust, infraestructura + +# Sección de Encabezado +about-page-subtitle = Desarrollador de Rust y consultor de infraestructura apasionado por las soluciones auto-hospedadas, tecnologías de código abierto y la construcción de sistemas resilientes que las organizaciones pueden verdaderamente poseer y controlar. + +# Sección de Trayectoria +about-my-journey = Mi Trayectoria +about-journey-paragraph-1 = Con más de tres décadas de experiencia en el desarrollo de software, he sido testigo de la evolución de la tecnología desde arquitecturas monolíticas hasta soluciones nativas en la nube. Lo que me motiva hoy es ayudar a las organizaciones a reducir su dependencia de servicios externos mientras construyen sistemas más resilientes y de alto rendimiento. +about-journey-paragraph-2 = Mi interés en Rust proviene de su combinación única de rendimiento, seguridad y expresividad. Es un lenguaje que permite crear sistemas rápidos y fiables, lo que lo convierte en la opción ideal para las soluciones de infraestructura autoalojada que promuevo. +about-journey-paragraph-3 = El espacio Web3, y en particular el ecosistema Polkadot, representa el futuro de la computación descentralizada. Trabajo con equipos para aprovechar estas tecnologías de manera práctica y orientada al negocio. + +# Sección de Filosofía +about-philosophy-approach = Filosofía y enfoque +about-open-source-first = Código abierto primero - reducir dependencias de proveedores +about-self-hosted-solutions = Soluciones auto-hospedadas para mejor control +about-performance-security-design = Rendimiento y seguridad por diseño +about-pragmatic-solutions = Soluciones pragmáticas antes que perfección +about-knowledge-transfer-empowerment = Transferencia de conocimiento y empoderamiento del equipo + +# Sección de Experiencia Técnica +about-technical-expertise = Experiencia Técnica +about-technologies-work-with = Tecnologías y prácticas con las que trabajo regularmente +about-rust-development-title = Desarrollo en Rust +about-systems-programming-item = Programación de sistemas +about-web-applications-item = Aplicaciones web (Axum, Leptos) +about-cli-tools-automation = Herramientas CLI y automatización +about-performance-optimization-item = Optimización de rendimiento +about-memory-safety-concurrency = Seguridad de memoria y concurrencia +about-infrastructure-title = Infraestructura +about-kubernetes-container = Kubernetes y orquestación de contenedores +about-cicd-pipeline-design = Diseño de pipelines CI/CD +about-monitoring-observability = Monitoreo y observabilidad +about-self-hosted-alternatives = Alternativas auto-hospedadas +about-security-compliance = Seguridad y cumplimiento +about-web3-blockchain-title = Web3 y Blockchain +about-polkadot-ecosystem-dev = Desarrollo del ecosistema Polkadot +about-substrate-blockchain = Framework blockchain Substrate +about-smart-contract-dev = Desarrollo de contratos inteligentes +about-dapp-architecture = Arquitectura DApp +about-cross-chain-communication = Comunicación cross-chain + +# Sección ¿Por qué Auto-hospedado? +about-why-self-hosted-open-source = ¿Por qué Auto-hospedado y Código Abierto? +about-control-privacy = 🔒 Control y Privacidad +about-control-privacy-desc = Tus datos permanecen en tu infraestructura. Sin dependencias de terceros para funciones críticas del negocio. +about-cost-effectiveness = 💰 Efectividad en Costos +about-cost-effectiveness-desc = Elimina costos recurrentes de SaaS. Escala sin precios por usuario o límites arbitrarios. +about-resilience = 🛡️ Resistencia +about-resilience-desc = Reduce puntos únicos de falla. Tus sistemas siguen funcionando independientemente de problemas de servicios externos. +about-performance = ⚡ Rendimiento +about-performance-desc = Optimiza para tu caso de uso específico. Sin compromisos para plataformas SaaS multi-tenant. +about-customization = 🔧 Personalización +about-customization-desc = Modifica y extiende herramientas para ajustarse a tu flujo de trabajo. Sin esperar solicitudes de características. +about-community = 🌍 Comunidad +about-community-desc = Benefíciate del desarrollo transparente, revisión por pares y mejora colaborativa. + +# Llamada a la Acción +about-lets-work-together = Trabajemos Juntos +about-work-together-desc = ¿Listo para construir infraestructura más resiliente y auto-controlada? Hablemos de tu proyecto. +about-start-project = Iniciar Proyecto +about-get-in-touch = Contáctame +about-work-request-url = /solicitud-trabajo +about-contact-url = /contacto \ No newline at end of file diff --git a/site/site/i18n/locales/es/pages/activities.ftl b/site/site/i18n/locales/es/pages/activities.ftl new file mode 100644 index 0000000..2183534 --- /dev/null +++ b/site/site/i18n/locales/es/pages/activities.ftl @@ -0,0 +1,59 @@ +# Activities page translations - Spanish + +# Page header +activities-page-title = Actividades +activities-page-description = Charlas, talleres y presentaciones Slidev +activities-title = Actividades +activities-subtitle = Charlas, talleres y presentaciones Slidev + +# Category page header +activities-category-page-title = Actividades +activities-category-page-description = Actividades filtradas por categoría. +activities-category-page-keywords = actividades, charlas, talleres, presentaciones, categoría + +# Navigation +nav-activities = Actividades + +# Event metadata +activities-event-type-talk = Charla +activities-event-type-workshop = Taller +activities-event-type-presentation = Presentación +activities-event-date = Fecha del evento +activities-event-location = Lugar + +# Slides embed +activities-view-slides = Ver slides +activities-slides-fullscreen = Abrir pantalla completa + +# Download section +activities-download-pdf = Descargar PDF +activities-login-required = Inicia sesión para descargar el PDF de esta actividad. +activities-login-to-download = Iniciar sesión +activities-questionnaire-required = Completa la evaluación para desbloquear la descarga del PDF. +activities-questionnaire-link = Completar evaluación +activities-download-ready = Tu descarga está lista. +activities-completion-recorded = Tu evaluación ha sido registrada. ¡Gracias! + +# Internal questionnaire +activities-questionnaire-title = Evaluación de la actividad +activities-questionnaire-submit = Enviar evaluación +activities-questionnaire-submitting = Enviando… +activities-questionnaire-required-fields = Por favor responde todas las preguntas obligatorias: { $fields } +activities-questionnaire-success = Gracias por tu evaluación. Ya puedes descargar el PDF. + +# Repository link +activities-repository = Código fuente + +# Gallery +activities-gallery-title = Galería +activities-gallery-alt = Foto de la actividad { $index } + +# Content grid +activities-no-items-in-category = No se encontraron actividades en esta categoría. +activities-view-all = Ver todas las actividades +activities-featured = Destacado +activities-load-more = Cargar más + +# Error states +activities-error-loading = Error al cargar las actividades. Por favor, inténtalo de nuevo. +activities-error-loading-slides = No se pudieron cargar las slides. diff --git a/site/site/i18n/locales/es/pages/blog.ftl b/site/site/i18n/locales/es/pages/blog.ftl new file mode 100644 index 0000000..d09b4b0 --- /dev/null +++ b/site/site/i18n/locales/es/pages/blog.ftl @@ -0,0 +1,77 @@ +# Traducciones específicas de la página del blog - Español + +# Encabezado de Página +blog-page-title = Blog e Insights +blog-page-description = Pensamientos, experiencias e insights técnicos del mundo de Rust, infraestructura y Web3. +blog-page-subtitle = Pensamientos, experiencias e insights técnicos del mundo de Rust, infraestructura y Web3. + +# Encabezado de Página de Categoría +blog-category-page-title = Blog e Insights +blog-category-page-description = Artículos filtrados por categoría - profundiza en temas específicos y áreas de experiencia. +blog-category-page-keywords = blog, categoría, artículos, filtrados, temas + +# Contenido del Blog +blog-featured-post = Publicación Destacada +blog-recent-posts = Publicaciones Recientes +blog-read-full-article = Leer artículo completo → +blog-read-more = Leer más → +blog-back-to-all-posts = ← Volver a todas las publicaciones + +# Carga del Blog +blog-error-loading = Error al cargar las publicaciones del blog. Por favor intenta de nuevo más tarde. +blog-loading-content = Cargando contenido del blog... +blog-error-loading-featured-posts = Error al cargar entradas destacadas +blog-error-loading-recent-posts = Error al cargar entradas recientes +blog-error-loading-prescriptions-grid = Error al cargar cuadrícula de prescripciones +blog-stay-updated = Mantente Actualizado +blog-newsletter-description = Recibe notificaciones sobre nuevas publicaciones, insights y desarrollos de la industria. +blog-email-placeholder = Tu dirección de correo electrónico +blog-subscribe = Suscribirse +blog-browse-blog-posts = Explorar Publicaciones del Blog +blog-load-more-posts = Cargar Más Publicaciones + +# Contenido de marcador de posición para cuando se cargan los datos +blog-featured-posts-placeholder = Cargando publicaciones destacadas... +blog-recent-posts-placeholder = Cargando publicaciones recientes... +blog-subscription-form-placeholder = Cargando formulario de suscripción... + +# Sección de navegación por categorías +blog-browse-categories-title = Explorar por Categoría +blog-browse-categories-description = Explora artículos organizados por tecnología y áreas de especialización. + +# Contenido de publicación de muestra +blog-sample-post-date = 15 de diciembre, 2024 +blog-sample-post-title = Construcción de Aplicaciones Rust Escalables +blog-sample-post-excerpt = Aprende a arquitecturar y construir aplicaciones Rust que escalen desde prototipo hasta producción con manejo de errores adecuado y optimización de rendimiento. +blog-sample-post-url = /blog-es/aplicaciones-rust-escalables + +# URLs y nombres de categorías +blog-category-rust-url = /blog-es/rust +blog-category-infrastructure-url = /blog-es/infraestructura +blog-category-web3-url = /blog-es/web3 +blog-category-rust-name = Desarrollo Rust +blog-category-infrastructure-name = Infraestructura +blog-category-web3-name = Web3 y Blockchain + +# Mensajes de la cuadrícula de contenido +blog-no-posts-in-category = No se encontraron publicaciones en esta categoría +blog-view-all-posts = Ver Todas las Publicaciones + +# Títulos y descripciones específicas de categorías +blog-cat-architecture-title = Arquitectura y Diseño +blog-cat-architecture-desc = Patrones de diseño de sistemas, decisiones arquitectónicas y mejores prácticas de arquitectura de software + +blog-cat-devops-title = DevOps e Infraestructura +blog-cat-devops-desc = Prácticas DevOps, pipelines CI/CD, automatización de infraestructura y estrategias de despliegue + +blog-cat-infrastructure-title = Gestión de Infraestructura +blog-cat-infrastructure-desc = Infraestructura de servidores, plataformas en la nube, containerización e infraestructura como código + +blog-cat-rust-title = Desarrollo en Rust +blog-cat-rust-desc = Consejos del lenguaje Rust, patrones, optimización de rendimiento y perspectivas del ecosistema + +blog-cat-web3-title = Web3 y Blockchain +blog-cat-web3-desc = Desarrollo blockchain, contratos inteligentes, protocolos DeFi y tecnologías descentralizadas + +# Mensajes genéricos de contenido (utilizados por UnifiedContentGrid) +# Movidos a content-kinds.ftl para evitar duplicación diff --git a/site/site/i18n/locales/es/pages/contact.ftl b/site/site/i18n/locales/es/pages/contact.ftl new file mode 100644 index 0000000..35e2e93 --- /dev/null +++ b/site/site/i18n/locales/es/pages/contact.ftl @@ -0,0 +1,77 @@ +# Traducciones específicas de la página de contacto - Español + +# Metadatos de Página +contact-page-title = Ponte en Contacto +contact-page-description = Ponte en contacto conmigo +contact-page-keywords = contacto, comunicarse + +# Encabezado de Página +contact-page-subtitle = ¿Listo para discutir tu proyecto o tienes preguntas sobre mis servicios?<br />Me encantaría escucharte y explorar cómo podemos trabajar juntos. + +# Métodos de Contacto +contact-lets-connect = Conectemos +contact-email-contact = Email +contact-telegram-contact = Telegram +contact-schedule-call = Programar una Llamada + +# Opciones de Llamada +contact-consultation-30min = Consulta de 30 minutos +contact-book-time-slot = Reservar un horario → +contact-best-for-detailed = Ideal para discusiones detalladas de proyectos +contact-quick-questions = Preguntas rápidas y discusiones + +# Redes Profesionales +contact-professional-networks = Redes Profesionales +contact-linkedin-profile = 🔗 Perfil de LinkedIn +contact-git-profile = 💻 Perfil de Git +contact-read-my-blog = 📝 Lee Mi Blog +contact-technical-prescriptions = 🔧 Prescripciones Técnicas + +# Información de Respuesta +contact-response-time = Tiempo de Respuesta +contact-response-time-text = Típicamente respondo dentro de 24 horas en días laborables. Para asuntos urgentes, por favor menciónalo en tu mensaje. + +# Formulario de Contacto +contact-send-message = Enviar un Mensaje +contact-contact-form-description = Cuéntame sobre tu proyecto, objetivos, cronograma y cualquier requisito específico. +contact-send-message-btn = Enviar Mensaje + +# Preguntas Frecuentes +contact-frequently-asked-questions = Preguntas Frecuentes +contact-faq-what-projects = ¿En qué tipos de proyectos trabajas? +contact-faq-what-projects-answer = Me especializo en desarrollo en Rust, infraestructura auto-hospedada y proyectos Web3. Esto incluye aplicaciones web, herramientas de sistema, pipelines CI/CD, despliegues en Kubernetes y desarrollo del ecosistema Polkadot. +contact-faq-remote-teams = ¿Trabajas con equipos remotos? +contact-faq-remote-teams-answer = Sí, trabajo exclusivamente con equipos remotos y tengo experiencia colaborando en diferentes zonas horarias. Me siento cómodo con comunicación asíncrona y uso herramientas modernas de colaboración. +contact-faq-pricing-approach = ¿Cuál es tu enfoque para la fijación de precios? +contact-faq-pricing-answer = Los precios dependen del alcance, complejidad y cronograma del proyecto. Ofrezco tanto tarifas por hora para consultoría como cotizaciones de precio fijo para proyectos bien definidos. Hablemos de tus necesidades específicas. +contact-faq-existing-projects = ¿Puedes ayudar con proyectos existentes? +contact-faq-existing-projects-answer = ¡Por supuesto! Puedo ayudar con revisiones de código, mejoras de arquitectura, optimización de rendimiento, agregar nuevas funcionalidades o migrar a soluciones de infraestructura más resilientes. + +# Otras Formas de Conectar +contact-other-ways-connect = Otras Formas de Conectar +contact-project-consultation = Consultoría de Proyectos +contact-consultation-help = ¿Necesitas ayuda definiendo requisitos de proyecto o arquitectura técnica? +contact-request-quote = Solicitar Cotización +contact-mentoring-training = Mentoría y Entrenamiento +contact-mentoring-help = ¿Buscas orientación personalizada o entrenamiento para equipos? +contact-learn-more = Saber Más +contact-speaking-events = Charlas y Eventos +contact-speaking-help = ¿Te interesa que participe como ponente en tu evento o conferencia? +contact-get-in-touch = Ponte en Contacto + +# URLs para navegación +contact-blog-url = /blog +contact-prescriptions-url = /prescripciones +contact-work-request-url = /solicitud-trabajo +contact-services-url = /servicios + +# Etiquetas y marcadores de posición del formulario de contacto +contact-name-label = Nombre +contact-name-placeholder = Tu nombre completo +contact-email-label = Correo electrónico +contact-email-placeholder = tu.correo@ejemplo.com +contact-subject-label = Asunto +contact-subject-placeholder = Breve descripción de tu consulta +contact-message-label = Mensaje +contact-message-placeholder = Cuéntame sobre tu proyecto, objetivos, cronograma, y cualquier requisito específico... +contact-submit-success = ¡Tu mensaje ha sido enviado\! Me pondré en contacto contigo en 24 horas. diff --git a/site/site/i18n/locales/es/pages/content.ftl b/site/site/i18n/locales/es/pages/content.ftl new file mode 100644 index 0000000..e93b851 --- /dev/null +++ b/site/site/i18n/locales/es/pages/content.ftl @@ -0,0 +1,163 @@ +# Traducciones de Tipos de Contenido +# Traducciones dinámicas para diferentes tipos de contenido + +# Nombres de tipos de contenido +content-kind-blog = Blog +content-kind-recetas = Recetas +content-kind-content = Contenido +content-kind-tutoriales = Tutoriales + +# Descripciones de tipos de contenido +content-kind-blog-description = Artículos técnicos e ideas +content-kind-recetas-description = Soluciones y mejores prácticas +content-kind-content-description = Contenido general y documentación +content-kind-tutoriales-description = Tutoriales paso a paso + +# Títulos de página +content-kind-blog-page-title = { $title -> + [blog] Blog - { $site_name } + *[other] { $title } - { $site_name } +} + +content-kind-recetas-page-title = { $title -> + [recetas] Recetas - { $site_name } + *[other] { $title } - { $site_name } +} + +# Fallback de <head> del PostViewer (detalle de contenido). Estático: el lookup del +# head es un get de clave plano, no puede pasar el título del post. El binding real +# per-post del <title> es del framework — ver `ontoref rustelo publish-contract` +# known_gaps (post-detail-head-title-unbound). Quita el bracket roto. +postviewer-page-title = Artículo +postviewer-page-description = Un artículo de este sitio +postviewer-page-keywords = artículo, blog + +# Títulos de <head> de páginas estáticas. Los componentes de ruta buscan claves +# <nombrecomponenteminúsculas>-page-title (p.ej. HomePage -> homepage-page-title); +# las home-page-title / about-page-title preexistentes usaban otra convención y no +# resolvían. Estos alias coinciden con lo que piden los componentes. ContentIndex es +# compartido entre kinds, así que su título es genérico. +homepage-page-title = Ontoref — Estructura que sigue siendo cierta +homepage-page-description = Un proyecto, una infraestructura, una vida: ¿sigues yendo a donde dijiste que ibas? Lo que dejas dicho, alguien podrá comprobarlo. +aboutpage-page-title = Acerca de Jesús Pérez +servicespage-page-title = Servicios Profesionales +contactpage-page-title = Ponte en Contacto +notfound-page-title = Página No Encontrada +contentindex-page-title = Índice + +# Per-kind index titles keyed by the ES URL segment (build_page_seo resolves +# <urlsegment>-page-title for ContentIndex routes). EN segments (blog, projects, +# recipes, activities) already have their own *-page-title keys. +recetas-page-title = Recetas +proyectos-page-title = Proyectos +actividades-page-title = Actividades + +# Etiquetas de navegación +content-nav-back-to = { $content_kind -> + [blog] Volver al Blog + [recetas] Volver a Recetas + [content] Volver a Contenido + [tutoriales] Volver a Tutoriales + *[other] Volver a { $content_kind } +} + +# Soporte de características +content-supports-features = { $content_kind -> + [blog] true + [tutoriales] true + *[other] false +} + +content-supports-emojis = { $content_kind -> + [recetas] true + *[other] false +} + +# Modos de estilo +content-style-mode = { $content_kind -> + [blog] row + [recetas] grid + [content] row + [tutoriales] row + *[other] row +} + +# Vistas CTA +content-cta-view = { $content_kind -> + [blog] BlogCTAView + [recetas] RecetasCTAView + [tutoriales] TutorialesCTAView + *[other] DefaultCTAView +} + +# Mensajes de contenido compartidos (usados por UnifiedContentGrid) +content-no-items-found = No se encontraron items +content-all-categories = Todas las Categorías + +# Etiquetas de la interfaz de filtros (usados por UnifiedCategoryFilter) +content-filter-categories = Categorías +content-filter-tags = Etiquetas +content-filter-all = Todas +content-filter-show-all = Mostrar Todas + +# Interfaz de Gestor de Contenido +content-manager-title = Gestión de Contenido +content-manager-new-content = Nuevo Contenido +content-manager-filter-type = Tipo de Contenido: +content-manager-filter-language = Idioma: +content-manager-all-types = Todos los Tipos +content-manager-all-languages = Todos los Idiomas +content-manager-refresh = Actualizar + +# Encabezados de Tabla del Gestor de Contenido +content-manager-table-title = Título +content-manager-table-type = Tipo +content-manager-table-language = Idioma +content-manager-table-status = Estado +content-manager-table-updated = Actualizado +content-manager-table-actions = Acciones + +# Estado del Gestor de Contenido +content-manager-status-published = Publicado +content-manager-status-draft = Borrador + +# Acciones del Gestor de Contenido +content-manager-action-edit = Editar +content-manager-action-publish = Publicar +content-manager-action-unpublish = Despublicar +content-manager-action-delete = Eliminar + +# Notas del Gestor de Contenido +content-manager-note-label = Nota: +content-manager-note-text = Esta es una maqueta de la interfaz de gestión de contenido. La funcionalidad completa con integración de API se implementará en futuras actualizaciones. + +# Datos de Muestra del Gestor de Contenido +content-manager-sample-category = General +content-manager-sample-blog-title = Publicación de Blog de Ejemplo +content-manager-sample-blog-content = # Publicación de Blog de Ejemplo\n\nEsta es una publicación de blog de ejemplo para fines de demostración. +content-manager-sample-blog-excerpt = Una publicación de blog de ejemplo que demuestra el sistema de gestión de contenido. +content-manager-sample-recipes-title = Receta de Ejemplo +content-manager-sample-recipes-content = # Receta de Ejemplo\n\n## Ingredientes\n- Ingrediente de ejemplo 1\n- Ingrediente de ejemplo 2\n\n## Instrucciones\n1. Paso de ejemplo 1\n2. Paso de ejemplo 2 +content-manager-sample-recipes-excerpt = Una receta de ejemplo que demuestra el sistema de gestión de contenido. + +# Nombres de Idiomas +language-en = English +language-es = Español + +# Nombres de Tipos de Contenido (reservas si no están en content-kinds) +content-kind-recipes = Recetas + +# Contenido del Blog +content-featured-post = Publicación Destacada +content-recent-posts = Publicaciones Recientes +content-read-full-article = Leer artículo completo → +content-read-more = Leer más → +content-back-to-all-posts = ← Volver a todas las publicaciones + +content-failed-to-load = Fallo al cargar el contenido +content-view-all = Ver todo +content-related-view-all = Relacionados: Ver todo + +# Índice del catálogo de verificables (kind `catalog`) +catalog-page-title = Catálogo de Verificables — ontoref +catalog-page-description = Operaciones tipadas de agente y los validadores que las protegen (ADR-024 / ADR-026 / ADR-050). diff --git a/site/site/i18n/locales/es/pages/content_graph.ftl b/site/site/i18n/locales/es/pages/content_graph.ftl new file mode 100644 index 0000000..72cdfb8 --- /dev/null +++ b/site/site/i18n/locales/es/pages/content_graph.ftl @@ -0,0 +1,5 @@ +content-graph-related = Relacionado +content-graph-ontology-context = Contexto ontológico +content-graph-title = Grafo de Conocimiento +content-graph-subtitle = Un mapa interactivo de todo el contenido, nodos ontológicos y decisiones arquitectónicas — y cómo se conectan. +content-graph-hint = Haz clic en un nodo para navegar. Desplázate para hacer zoom. Arrastra para mover. Los nodos de contenido enlazan a su página. diff --git a/site/site/i18n/locales/es/pages/expedientes.ftl b/site/site/i18n/locales/es/pages/expedientes.ftl new file mode 100644 index 0000000..508d6ad --- /dev/null +++ b/site/site/i18n/locales/es/pages/expedientes.ftl @@ -0,0 +1,24 @@ +# Expedientes page translations - Spanish + +# Page header +expedientes-page-title = Expedientes +expedientes-page-description = Biblioteca de expedientes +expedientes-title = Expedientes +expedientes-subtitle = Biblioteca de expedientes + +# Category page header +expedientes-category-page-title = Expedientes +expedientes-category-page-description = Expedientes filtrados por categoría. +expedientes-category-page-keywords = expedientes, casos, archivos, categoría + +# Navigation +nav-expedientes = Expedientes + +# Content grid +expedientes-no-items-in-category = No se encontraron expedientes en esta categoría. +expedientes-view-all = Ver todos los expedientes +expedientes-featured = Destacado +expedientes-load-more = Cargar más + +# Error states +expedientes-error-loading = Error al cargar los expedientes. Por favor, inténtalo de nuevo. diff --git a/site/site/i18n/locales/es/pages/home.ftl b/site/site/i18n/locales/es/pages/home.ftl new file mode 100644 index 0000000..5c53ae0 --- /dev/null +++ b/site/site/i18n/locales/es/pages/home.ftl @@ -0,0 +1,60 @@ +# Home page — Ontoref · Español + +home-page-title = Ontoref — Ontología operacional con reflexión +home-page-description = Un proyecto, una infraestructura, una vida: ¿sigues yendo a donde dijiste que ibas? Lo que dejas dicho, alguien podrá comprobarlo. +home-page-keywords = ontología, reflexión, protocolo, rust, mcp, nickel, grafo-de-conocimiento, arquitectura-software + +# Hero — titular/sub/cierre proyectan desde spine.ncl (home_spine.ftl); aquí solo +# se mantiene el badge a mano. Claves muertas home-hero-* / home-cta-adopt / +# home-tagline-door-{developer,infrastructure,personal} eliminadas 2026-06-20 +# (la plantilla renderiza spine-* y spine-door-*-label). +home-hero-badge = Ontología operacional con reflexión · v0.1.7 + +# Rotador de taglines — aquí solo se mantiene la etiqueta de la puerta 'all'. +home-tagline-door-all = Todo + +home-cta-explore = Explorar el Protocolo +home-cta-github = Código + +# Banda de capacidades bajo el pliegue. El título sostiene ambos polos (lo que un +# proyecto ES y lo que deja verificable); nunca reifica "el grafo" como el que da +# — ver .coder/2026-06-20-home-positioning-projection.plan.md (ondaod). +home-pillars-title = Lo que puede hacer un proyecto que se conoce +home-pillars-subtitle = Tres capacidades desde un único substrato tipado — pregunta qué se rompe, comprueba quién lo cambió, alcánzalo desde cualquier actor. Independiente de cualquier runtime. + +home-pillar-impact-label = PREGUNTA ANTES DE TOCAR +home-pillar-impact-title = El radio de impacto, antes del diff +home-pillar-impact-desc = Pregunta en qué repercute un cambio — qué nodos, qué invariantes, qué ADRs — antes de escribir una línea. El código que acabas de heredar puede decirte qué es lo que sostiene el peso. + +home-pillar-witness-label = COSTURA TESTIGO · Ed25519 +home-pillar-witness-title = Un rastro que cualquiera puede verificar +home-pillar-witness-desc = Cada cambio del estado autoritativo pasa por precondiciones tipadas y deja un testigo Ed25519 en un oplog DAG reproducible y direccionado por contenido. Cualquiera verifica la historia contra las claves del actor — sin fiarse de quien la produjo. + +home-pillar-shared-label = UN GRAFO · CLI · MCP · CI +home-pillar-shared-title = La misma ontología, para cada actor +home-pillar-shared-desc = Personas, agentes y CI leen un único grafo tipado — desde la terminal, desde el IDE, desde el pipeline. Conocimiento que falla en el build cuando se desvía, no en la próxima auditoría. + +# Cómo funciona +home-how-title = Adoptar en Cualquier Proyecto +home-how-subtitle = Tres comandos. Sin lock-in. + +home-how-step1-title = ontoref setup +home-how-step1-desc = Genera el scaffold de .ontology/ y reflection/ con ficheros NCL tipados. Idempotente — seguro de re-ejecutar tras actualizaciones. + +home-how-step2-title = ontoref describe project +home-how-step2-desc = Consulta la identidad, axiomas, tensiones y estado FSM de tu proyecto desde el terminal. Actor-agnostic — misma salida para developer, agente o CI. + +home-how-step3-title = ontoref run start +home-how-step3-desc = Arranca el daemon para UI HTTP y MCP. Configurable via ~/.config/ontoref/config.ncl. El protocolo funciona sin él. + +# Arquitectura +home-arch-title = Arquitectura +home-arch-img-light = /images/ontoref-architecture.svg +home-arch-img-dark = /images/projects/ontoref_architecture-dark.svg +home-arch-caption = Capas del protocolo — entry point → NCL declarativo → Nushell operacional → runtime Rust opcional. Cada capa es testeable de forma independiente. + +# CTA +home-cta-title = Estructura el Conocimiento de Tu Proyecto +home-cta-desc = Instala el CLI, ejecuta ontoref setup y tu proyecto gana invariantes consultables por máquina, ADRs vivos, modos operacionales con actor-awareness y compartición opcional de contexto del daemon. +home-cta-primary = Empezar +home-cta-secondary = Leer el Protocolo diff --git a/site/site/i18n/locales/es/pages/home_spine.ftl b/site/site/i18n/locales/es/pages/home_spine.ftl new file mode 100644 index 0000000..00d9c82 --- /dev/null +++ b/site/site/i18n/locales/es/pages/home_spine.ftl @@ -0,0 +1,30 @@ +# GENERATED from .ontoref/positioning/spine.ncl by gen-spine-pages.nu — do not edit by hand. +spine-hero = Pisa firme y anda ligero. +spine-sub = Tu proyecto, tu infra, tu obra, tu vida: ¿sigues yendo a donde dijiste? +spine-close = Lo que dejas dicho, alguien podrá comprobarlo. +spine-prize = Cuando tu razón de ser es verificable y no solo declarada, mantener el rumbo deja de costar esfuerzo. +spine-doors-heading = Cuatro puertas, el mismo edificio +spine-door-cta = Ver más +spine-door-blog-cta = Posts +spine-door-recipes-cta = Recetas +spine-door-developer-label = Desarrollador +spine-door-developer-line = Pregunta qué se rompe antes de tocar. +spine-door-developer-href = /dominios/developer +spine-door-developer-blog = /blog?tag=developer +spine-door-developer-recipes = /recetas?tag=developer +spine-door-infrastructure-label = Infraestructura +spine-door-infrastructure-line = Hereda el viaje, no solo el estado. +spine-door-infrastructure-href = /dominios/infrastructure +spine-door-infrastructure-blog = /blog?tag=provisioning +spine-door-infrastructure-recipes = /recetas?tag=provisioning +spine-door-personal-label = Persona +spine-door-personal-line = Mira dónde tu esfuerzo se desvía. +spine-door-personal-href = /dominios/personal +spine-door-personal-blog = /blog?tag=personal +spine-door-personal-recipes = /recetas?tag=personal +spine-door-authoring-label = Autoría +spine-door-authoring-line = Difunde la obra entera, no fragmentos sueltos. +spine-door-authoring-href = /dominios/authoring +spine-door-authoring-blog = /blog?tag=knowledge-works +spine-door-authoring-recipes = /recetas?tag=knowledge-works +spine-hook-developer = El AI escribe más rápido y rompe más: +861% de churn, ×3.4 incidentes por PR, 31.3% de PRs fusionados sin una sola revisión. La madurez del equipo no protege. diff --git a/site/site/i18n/locales/es/pages/kogral.ftl b/site/site/i18n/locales/es/pages/kogral.ftl new file mode 100644 index 0000000..97ba72b --- /dev/null +++ b/site/site/i18n/locales/es/pages/kogral.ftl @@ -0,0 +1,56 @@ +# Kogral page — Grafos de Conocimiento Local-First + +# Hero +kogral-badge = v0.1.0 | Fundacion Completa | 97+ tests +kogral-tagline = Gestion de Conocimiento Local-First +kogral-page-title = Grafos de Conocimiento Para Equipos de Desarrollo +kogral-page-subtitle = Gestion estructurada de conocimiento que escala desde proyectos individuales a organizaciones. Arquitectura basada en configuracion para capturar decisiones arquitectonicas, guias de codigo y patrones reutilizables en markdown versionado. 100% Rust. Sin compromisos. + +# Problems +kogral-problems-title = Los 4 Problemas que Resuelve +kogral-problem-1-title = Documentacion Dispersa +kogral-problem-1-desc = Notas en Notion, decisiones en Slack, guias en wikis — todo desconectado. KOGRAL unifica con markdown git-native + MCP. +kogral-problem-2-title = Sin Control de Versiones para Decisiones +kogral-problem-2-desc = Decisiones arquitectonicas perdidas en chat. Sin trazabilidad de por que existe el codigo. KOGRAL proporciona ADRs rastreados en Git con historial completo y referencias @file:line. +kogral-problem-3-title = Contexto Perdido con el Tiempo +kogral-problem-3-desc = Nuevos miembros no encuentran decisiones pasadas. Documentacion desactualizada causa errores repetidos. KOGRAL ofrece busqueda semantica y rastreo de relaciones. +kogral-problem-4-title = Conocimiento Aislado del Equipo +kogral-problem-4-desc = Cada proyecto reinventa la rueda. Los patrones no se comparten entre equipos. La arquitectura multi-grafo de KOGRAL permite conocimiento organizacional compartido con sobrescrituras por proyecto. + +# How It Works +kogral-how-title = Como Funciona +kogral-how-markdown-title = Markdown Nativo +kogral-how-markdown-desc = YAML frontmatter para metadatos. Wikilinks para relaciones. Referencias de codigo @file.rs:42. Formato compatible con Logseq. Legible por humanos, amigable con Git. +kogral-how-search-title = Busqueda Semantica +kogral-how-search-desc = Busqueda de texto en todos los nodos. Embeddings vectoriales para consultas semanticas via stratum-embeddings con fastembed local. APIs en la nube soportadas (OpenAI, Claude, Ollama via rig-core). +kogral-how-config-title = Basado en Configuracion +kogral-how-config-desc = Esquemas Nickel con validacion. 3 modos: dev, prod, test. Storage Factory con dispatch de backend por configuracion. Patron de composicion de fragmentos TypeDialog. Sin rutas o configuraciones hardcodeadas. +kogral-how-mcp-title = Integracion Claude Code +kogral-how-mcp-desc = Servidor MCP (Model Context Protocol). 10 herramientas en namespace kogral/* (buscar, bloques, todos, cards). 6 recursos via esquema URI kogral://. 2 prompts para resumir proyecto y encontrar contenido relacionado. +kogral-how-logseq-title = Bloques Logseq +kogral-how-logseq-desc = Bloques jerarquicos estilo outliner. Estado de tarea: TODO, DOING, DONE, LATER, WAITING. #tags inline y propiedades personalizadas. Referencias de bloque y de pagina. Fidelidad round-trip para import/export Logseq. +kogral-how-events-title = Sistema de Eventos y Orquestacion +kogral-how-events-desc = Publicacion de eventos NATS JetStream. Decorador EventingStorage wraps cualquier backend. Tipos KogralEvent para operaciones de nodo y grafo. Disparo de pipelines stratum-orchestrator. Soporte SurrealDB dual-engine. + +# Components +kogral-components-title = Componentes Principales +kogral-comp-core-role = Biblioteca nucleo +kogral-comp-cli-role = 13 comandos + bloques +kogral-comp-mcp-role = 10 herramientas, kogral:// +kogral-comp-config-role = Esquemas Nickel +kogral-comp-storage-role = Filesystem, SurrealDB, Memoria +kogral-comp-embeddings-role = proveedores fastembed + rig-core +kogral-comp-nodes-role = Nota, Decision, Guia, Patron, Diario, Ejecucion +kogral-comp-relations-role = relates_to, depends_on, implements, extends, supersedes, explains +kogral-comp-multigraph-role = Proyecto + Compartido +kogral-comp-events-role = EventingStorage JetStream +kogral-comp-logseq-role = Tareas, tags, propiedades, refs +kogral-comp-orchestration-role = pipelines stratum-orchestrator + +# Tech stack +kogral-tech-stack-title = Stack Tecnologico + +# CTA +kogral-cta-title = Listo para organizar tu conocimiento? +kogral-cta-subtitle = 3 Crates | 97+ Tests | 11K LOC | 100% Rust +kogral-cta-explore = Comenzar diff --git a/site/site/i18n/locales/es/pages/legal.ftl b/site/site/i18n/locales/es/pages/legal.ftl new file mode 100644 index 0000000..ca2d639 --- /dev/null +++ b/site/site/i18n/locales/es/pages/legal.ftl @@ -0,0 +1,68 @@ +# Traducciones específicas de la página legal - Español + +# Metadatos de Página +legal-page-title = Aviso Legal +legal-page-description = Aviso legal e información legal +legal-page-keywords = aviso legal, términos, información + +# Encabezados de Página +legal-title = Aviso Legal +legal-subtitle = Términos de uso e información legal +legal-effective-date = Fecha de Vigencia: Enero 2025 +legal-intro = Este Aviso Legal regula el uso del sitio web y los servicios proporcionados por Jesús Pérez, Desarrollador Rust y Consultor de Infraestructura. + +# Sección de Información del Sitio Web +legal-website-information = Información del Sitio Web +legal-owner = Propietario +legal-owner-name = Jesús Pérez +legal-owner-value = Jesús Pérez +legal-activity = Actividad +legal-business-activity = Desarrollo Rust y Consultoría de Infraestructura +legal-activity-value = Desarrollo Rust y Consultoría de Infraestructura +legal-location = Ubicación +legal-business-location = Remoto (España) +legal-location-value = Remoto +legal-country-value = España +legal-contact = Contacto +legal-email = Email +legal-contact-email = hello@ontoref.dev +legal-contact-value = hello@ontoref.dev + +# Sección de Términos de Uso +legal-terms-of-use = Términos de Uso +legal-acceptance-of-terms = Aceptación de Términos +legal-acceptance-of-terms-text = Al acceder y utilizar este sitio web, aceptas y te comprometes a cumplir con los términos y disposiciones de este acuerdo. + +# Uso Permitido +legal-permitted-use = Uso Permitido +legal-permitted-use-text = Este sitio web se proporciona con fines informativos sobre nuestros servicios profesionales. Puedes: +legal-browse-and-read = Navegar y leer el contenido +legal-contact-for-business = Contactarnos para consultas comerciales +legal-share-with-attribution = Compartir nuestro contenido con la atribución adecuada + +# Uso Prohibido +legal-prohibited-use = Uso Prohibido +legal-prohibited-use-text = No puedes: +legal-unlawful-purpose = Usar el sitio web para cualquier propósito ilegal +legal-unauthorized-access = Intentar obtener acceso no autorizado a nuestros sistemas +legal-reproduce-without-permission = Reproducir, duplicar o copiar contenido sin permiso + +# Propiedad Intelectual +legal-intellectual-property = Propiedad Intelectual +legal-intellectual-property-text = Todo el contenido de este sitio web, incluyendo texto, imágenes, ejemplos de código y elementos de diseño, es propiedad de Jesús Pérez o está licenciado para su uso. + +# Información Técnica +legal-technical-information = Información Técnica +legal-technical-information-text = Este sitio web está construido con: +legal-rust-and-leptos = Framework Rust y Leptos +legal-server-side-rendering = Renderizado del lado del servidor (SSR) +legal-webassembly-functionality = WebAssembly para funcionalidad del lado del cliente + +# Información de Contacto +legal-contact-information = Información de Contacto +legal-contact-legal-text = Para consultas legales o para reportar violaciones de estos términos: +legal-contact-email-label = Correo Electrónico +legal-email-value = hello@ontoref.dev +legal-address = Dirección +legal-business-address = Remoto (España) +legal-address-value = Remoto (España) diff --git a/site/site/i18n/locales/es/pages/not_found.ftl b/site/site/i18n/locales/es/pages/not_found.ftl new file mode 100644 index 0000000..aa09e61 --- /dev/null +++ b/site/site/i18n/locales/es/pages/not_found.ftl @@ -0,0 +1,28 @@ +# Traducciones de la página 404 No Encontrada + +# Metadatos de Página +not-found-page-title = Página No Encontrada +not-found-page-description = La página solicitada no pudo ser encontrada +not-found-page-keywords = 404, no encontrado, error + +# Título y contenido de la página +not-found-title = Página No Encontrada +not-found-description = Lo sentimos, la página que busca no existe o ha sido movida. + +# Botones de navegación +not-found-go-home = Ir al Inicio +not-found-contact-us = Contáctanos +not-found-back-to-blog = Volver al Blog +not-found-back-to-prescriptions = Volver a Prescripciones +not-found-sign-in = Iniciar Sesión + +# URLs de navegación +not-found-home-url = /inicio +not-found-contact-url = /contacto +not-found-blog-url = /blog +not-found-prescriptions-url = /prescripciones +not-found-login-url = /iniciar-sesion + +# Texto de ayuda adicional (opcional) +not-found-section-title = ¿Qué puedes hacer? +not-found-help-text = Puede intentar volver a la página de inicio o contactarnos si cree que esto es un error. \ No newline at end of file diff --git a/site/site/i18n/locales/es/pages/ontoref.ftl b/site/site/i18n/locales/es/pages/ontoref.ftl new file mode 100644 index 0000000..a3ea8d7 --- /dev/null +++ b/site/site/i18n/locales/es/pages/ontoref.ftl @@ -0,0 +1,132 @@ +ontoref-adoption-subtitle = ontoref setup conecta cualquier proyecto nuevo o existente — scaffold idempotente con bootstrap de auth keys opcional. +ontoref-adoption-title = Adoptar en Cualquier Proyecto +ontoref-architecture-title = Arquitectura +ontoref-badge = Protocolo + Runtime · v0.1.0 +ontoref-crates-title = Crates y Herramientas +ontoref-cta-explore = Explorar el Protocolo +ontoref-cta-subtitle = Empieza con ontoref setup. Tu proyecto gana invariantes consultables por máquina, ADRs vivos, modos operacionales con actor-awareness y un daemon que comparte contexto entre todos los actores en tiempo real. +ontoref-cta-title = Estructura que Recuerda el Porqué +ontoref-duality-title = Ontología y Reflexión — Yin y Yang +ontoref-footer-tagline = Protocolo + Runtime. Sin coacción. Un grafo por proyecto. +ontoref-graph-desc = Grafo dirigido por fuerzas de la ontología en vivo. Los nodos son tipados (Axioma · Tensión · Práctica) y polarizados (Yang · Yin · Espiral). Haz clic en cualquier nodo para abrir su panel de detalles. +ontoref-graph-title = La UI en Acción -> Vista de Grafo +ontoref-hero-coda = Protocolo + Runtime. Sin coacción. +ontoref-hero-desc = — codifica lo que un sistema ES (invariantes, tensiones, constraints) y hacia dónde VA (dimensiones de estado, condiciones de transición, membranas) en grafos acíclicos dirigidos consultables por máquina. Proyectos de software, sistemas operacionales personales, contextos de agente — los mismos tres ficheros, el mismo protocolo. Pregunta el radio de impacto de un cambio antes de hacerlo, verifica cada mutación contra las claves del actor, comparte un único contexto tipado en vivo entre CLI, IDE y CI. Un protocolo para desarrolladores, agentes, CI e individuos. +ontoref-hero-highlight = Ontología + Reflexión + Daemon + MCP +ontoref-layer-adopt-desc = Cada proyecto mantiene sus propios datos de .ontology/. Ontoref provee los schemas, módulos y scripts de migración. Cero vendor lock-in. +ontoref-layer-adopt-label = CAPA DE ADOPCION · Por proyecto +ontoref-layer-decl-desc = Tipos fuertes, contratos, enums. Falla en definición, no en runtime. +ontoref-layer-decl-label = CAPA DECLARATIVA · Nickel +ontoref-layer-entry-desc = Un único entry point por proyecto. Detecta actor (developer/agent/CI), adquiere lock, despacha al módulo Nu correcto. +ontoref-layer-entry-label = PUNTO DE ENTRADA · Bash → Nu +ontoref-layer-graph-desc = El proyecto sabe qué sabe. Actor-agnostic. Consultable por máquina vía nickel export. +ontoref-layer-graph-label = GRAFO DE CONOCIMIENTO · .ontology/ +ontoref-layer-op-desc = Pipelines tipadas sobre datos estructurados. No streams de texto. +ontoref-layer-op-label = CAPA OPERACIONAL · Nushell +ontoref-layer-runtime-desc = Daemon persistente opcional. Caché de exports NCL, UI HTTP, servidor MCP, registro de actores, almacén de notificaciones, motor de búsqueda, persistencia SurrealDB. Nunca un requisito del protocolo. +ontoref-layer-runtime-label = CAPA RUNTIME · Rust + axum +ontoref-mcp-backlog-desc = <li><strong>pre_commit</strong> — el hook pre-commit hace POLL en <code>GET /notifications/pending?token=X&project=Y</code>; bloquea el commit git hasta que todo es reconocido</li><li><strong>drift</strong> — drift de schema detectado entre codebase y ontología</li><li><strong>ontology_drift</strong> — emitido por el observador pasivo con conteos missing/stale/drift/broken tras 15s debounce</li><li>Fail-open: si el daemon no está disponible, el hook pre-commit pasa — los commits nunca son bloqueados por caída del daemon</li><li>Ack vía UI o <code>POST /notifications/ack</code>; notificaciones custom vía <code>POST /{slug}/notifications/emit</code></li><li>Los botones de acción en notificaciones pueden enlazar a cualquier página del dashboard</li> +ontoref-mcp-backlog-title = Barrera de Notificaciones +ontoref-mcp-core-desc = ontoref-daemon es un proceso persistente opcional. Cachea exports NCL, sirve la UI web, expone la superficie de herramientas MCP, mantiene un registro de actores, almacena notificaciones, indexa todo para búsqueda y opcionalmente persiste en SurrealDB. Auth es opt-in: todas las superficies (CLI, UI, MCP) intercambian una project key por un token de sesión UUID v4 via <code>POST /sessions</code>; la CLI inyecta <code>ONTOREF_TOKEN</code> como Bearer automáticamente. Nunca cambia el protocolo — acelera y comparte el acceso a él. Configurado via <code>~/.config/ontoref/config.ncl</code> (Nickel, type-checked); edición interactiva con <code>ontoref config-edit</code>. Iniciado via NCL pipe bootstrap: <code>ontoref-daemon-boot</code>. +ontoref-mcp-knowledge-desc = <li>Habilitado con flag de feature <code>--db</code> y <code>--db-url ws://...</code></li><li>Conecta vía WebSocket al inicio — 5s timeout, <strong>fail-open</strong> (el daemon funciona sin él)</li><li>Siembra tablas de ontología desde archivos NCL locales al inicio y en cambios de fichero</li><li>Persiste: sesiones de actores, tablas de ontología sembradas, índice de búsqueda, historial de notificaciones</li><li>Sin <code>--db</code>: respaldado por DashMap en memoria, solo durante el proceso</li><li>Namespace configurable vía <code>--db-namespace</code>; credenciales vía <code>--db-username/--db-password</code></li> +ontoref-mcp-knowledge-title = Persistencia SurrealDB — Opcional +ontoref-mcp-query-title = El Servidor MCP — Acceso al DAG para Agentes +ontoref-mcp-table-desc-header = Descripción +ontoref-mcp-table-tool-header = Herramienta +ontoref-mcp-title = Daemon & MCP — Capa de Inteligencia en Tiempo de Ejecución +ontoref-mcp-tool-action-add-desc = Crear modo de reflexión + registrar como acción rápida +ontoref-mcp-tool-action-list-desc = Catálogo de acciones rápidas de .ontoref/config.ncl +ontoref-mcp-tool-backlog-desc = Añadir o actualizar estado de elemento del backlog +ontoref-mcp-tool-constraints-desc = Todos los constraints arquitectónicos hard + soft +ontoref-mcp-tool-describe-desc = Resumen de arquitectura y auto-descripción +ontoref-mcp-tool-get-adr-desc = Contenido completo de ADR con constraints +ontoref-mcp-tool-get-backlog-desc = Elementos de backlog filtrados por estado +ontoref-mcp-tool-get-desc = Obtener nodo de ontología por id +ontoref-mcp-tool-get-mode-desc = Contrato DAG del modo — pasos, pre/postcondiciones +ontoref-mcp-tool-get-node-desc = Nodo completo con aristas y constraints +ontoref-mcp-tool-help-desc = Lista herramientas disponibles y uso +ontoref-mcp-tool-list-adrs-desc = Listar ADRs filtrados por estado +ontoref-mcp-tool-list-modes-desc = Listar todos los modos de reflexión +ontoref-mcp-tool-list-projects-desc = Enumerar todos los proyectos registrados +ontoref-mcp-tool-project-status-desc = Dashboard completo del proyecto — salud, drift, actores +ontoref-mcp-tool-qa-add-desc = Persistir nueva entrada Q&A en reflection/qa.ncl +ontoref-mcp-tool-qa-list-desc = Listar almacén Q&A con filtro opcional +ontoref-mcp-tool-search-desc = Búsqueda de texto libre en nodos, ADRs, modos +ontoref-mcp-tool-set-project-desc = Establecer contexto de proyecto por defecto +ontoref-metrics-title = Métricas del Protocolo +ontoref-page-subtitle = Protocolo Auto-Descriptivo para<br>Sistemas Evolutivos +ontoref-page-title = Ontoref — Un Protocolo de Ontología y Reflexión Auto-Descriptivo +ontoref-problem-1-desc = <li>Decisiones arquitectónicas en chat, olvidadas tras rotación</li><li>Sin fuente consultable por máquina de por qué algo existe</li><li>ADRs como Nickel tipado: invariantes, constraints, cadena de supersedencia</li><li>Constraints Hard aplicadas en cada operación</li> +ontoref-problem-1-title = Decisiones Sin Memoria +ontoref-problem-2-desc = <li>Configs cambian fuera de cualquier ciclo de revisión</li><li>Sin trazabilidad que vincule cambio a PR o ADR</li><li>Rollback requiere arqueología manual de ficheros</li><li>Perfiles sellados: hash sha256, historia completa, rollback verificado</li> +ontoref-problem-2-title = Drift de Configuración Invisible +ontoref-problem-3-desc = <li>Los LLMs empiezan cada sesión con cero conocimiento del proyecto</li><li>Mismos errores, mismas preguntas, sin acumulación entre operaciones</li><li>El registro de actores rastrea cada token de sesión, tipo, modo actual, último visto — persistido en disco</li><li>Las herramientas MCP dan a los agentes acceso DAG de lectura/escritura directo: nodos, ADRs, backlog, Q&A</li><li>Tareas compuestas compartidas via daemon — múltiples actores ven el mismo contexto operacional en vivo</li> +ontoref-problem-3-title = Agentes Sin Contexto +ontoref-problem-4-desc = <li>Guías en wikis, patrones en docs, decisiones en Slack</li><li>Sin fuente única consultable por humanos, agentes y CI por igual</li><li><code>.ontology/</code> separa tres concerns ortogonales: <code>core.ncl</code> (lo que ES) · <code>state.ncl</code> (dónde ESTAMOS vs queremos estar) · <code>gate.ncl</code> (cuándo LISTO para cruzar una frontera)</li><li><code>reflection/</code> lee los tres y responde consultas de autoconocimiento — un agente entiende el proyecto sin leer código, solo consultando el grafo declarativo</li> +ontoref-problem-4-title = Conocimiento de Proyecto Disperso +ontoref-problem-5-desc = <li>Cada proyecto reinventa sus propias convenciones</li><li>Sin contrato compartido para cómo se definen y ejecutan las operaciones</li><li>Modos de reflexión: contratos DAG tipados para cualquier flujo</li><li>Un protocolo adoptado por proyecto, sin imponer uniformidad</li> +ontoref-problem-5-title = Fragmentación de Protocolo +ontoref-problem-6-desc = <li>Q&A respondido en una sesión olvidado en la siguiente</li><li>El agente repite preguntas ya respondidas en sesiones anteriores</li><li>Q&A Knowledge Store: NCL tipado, versionado en git, persiste a través de resets del navegador</li><li>La barrera de notificaciones transmite drift a los agentes de forma proactiva — señales pre_commit, drift, ontology_drift bloquean hasta ser reconocidas</li> +ontoref-problem-6-title = Conocimiento Perdido Entre Sesiones +ontoref-problem-7-desc = <li>Decisiones personales y profesionales tomadas contra supuestos implícitos e inverificables</li><li>Sin modelo consultable de lo que nunca comprometes</li><li>Sin forma estructurada de preguntar: ¿viola esta oportunidad quién soy?</li><li>ontoref como ontología operacional personal — los mismos ficheros core/state/gate aplicados a dimensiones de vida, carrera y ecosistema</li><li><code>jpl validate "aceptar oferta"</code> → invariants_at_risk, aristas relevantes, veredicto</li> +ontoref-problem-7-title = Decisiones Sin Mapa +ontoref-problems-title = Los 7 Problemas que Resuelve +ontoref-tagline = Estructura que recuerda el porqué +ontoref-tech-stack-title = Stack Tecnológico +ontoref-tension-1 = Ontología sin Reflexión = correcta pero estática. Invariantes perfectos sin operaciones = documentación muerta. +ontoref-tension-2 = Reflexión sin Ontología = fluida pero sin ancla. Flujos que olvidan lo que protegen. +ontoref-tension-thesis = El protocolo vive en la coexistencia. +ontoref-ui-dashboard-title = La UI Web +ontoref-yang-desc = <li><strong>Modos</strong> — contratos DAG tipados de flujo (precondiciones, pasos, postcondiciones)</li><li><strong>Formularios</strong> — recolección de parámetros que conducen modos</li><li><strong>Ciclo de vida ADR</strong> — Proposed → Accepted → Superseded, con historial de constraints</li><li><strong>Actores</strong> — developer / agent / CI, mismo protocolo, distintas capacidades</li><li><strong>Config seals</strong> — perfiles sellados con sha256, drift detection, rollback</li><li><strong>Quick Actions</strong> — atajos ejecutables sobre modos; configurados en <code>.ontoref/config.ncl</code></li><li><strong>Observador de Drift Pasivo</strong> — observa cambios de código, emite notificaciones <code>ontology_drift</code> con conteos de missing/stale/drift/broken</li> +ontoref-yang-sub = Cómo las cosas se mueven y cambian +ontoref-yang-title = Yang — La Capa de Reflexión +ontoref-yin-desc = <li><strong>Invariantes</strong> — axiomas que no pueden cambiar sin un nuevo ADR</li><li><strong>Tensiones</strong> — conflictos estructurales que el proyecto navega, nunca resuelve</li><li><strong>Prácticas</strong> — patrones confirmados con rutas a archivos reales y validadores ADR declarados</li><li><strong>Gates</strong> — membranas que controlan umbrales de preparación</li><li><strong>Dimensiones</strong> — estado actual vs deseado, con condiciones de transición</li><li><strong>Q&A Knowledge Store</strong> — Q&A acumulado persistido en NCL, versionado en git, consultable por cualquier actor</li> +ontoref-yin-sub = Lo que debe ser verdad +ontoref-yin-title = Yin — La Capa de Ontología +ontoref-layers-title = Stack de Protocolo +ontoref-layer-decl-items = .ontology/ · adrs/ · reflection/schemas/ +ontoref-layer-op-items = adr · register · config · backlog · forms · describe +ontoref-layer-entry-items = ontoref · detección de actor · advisory locking · ONTOREF_IMPORT_PATH +ontoref-layer-graph-items = nodes · invariantes · tensiones · gates · dimensiones · estados +ontoref-layer-runtime-items = ontoref-daemon · ontoref-ontology · ontoref-reflection · motor de búsqueda · barrera de notificaciones · SurrealDB (opcional) +ontoref-layer-adopt-items = .ontoref/config.ncl · ontoref CLI · modo adopt_ontoref +ontoref-ui-title = UI Web — Lee el Grafo +ontoref-ui-graph-title = Grafo +ontoref-ui-graph-desc = Visualización D3 dirigida por fuerzas del DAG de ontología. Diamantes Yang·Axioma, círculos Yin·Tensión, cuadrados Spiral·Práctica. +ontoref-ui-search-title = Búsqueda +ontoref-ui-search-desc = Búsqueda de texto libre en nodos, ADRs y modos de reflexión. +ontoref-ui-sessions-title = Sesiones +ontoref-ui-sessions-desc = Registro de actores con token, tipo, registered_at, last_seen y modo actual por actor. +ontoref-ui-notifications-title = Notificaciones +ontoref-ui-notifications-desc = Feed de notificaciones para señales pre_commit, drift y ontology_drift. Confirma, descarta o emite notificaciones personalizadas con botones de acción. +ontoref-ui-backlog-title = Backlog +ontoref-ui-backlog-desc = Elementos de trabajo filtrables por estado con operaciones de añadir y actualizar. +ontoref-ui-qa-title = Q&A +ontoref-ui-qa-desc = Almacén de conocimiento hidratado por servidor desde reflection/qa.ncl. Añade, edita y elimina entradas — persistidas como NCL tipado. +ontoref-ui-actions-title = Acciones +ontoref-ui-actions-desc = Catálogo de acciones rápidas de .ontoref/config.ncl. Ejecuta atajos sobre modos vía POST /actions/run. +ontoref-ui-modes-title = Modos +ontoref-ui-modes-desc = Lista de modos de reflexión de reflection/modes/ — nombre, descripción y contrato DAG completo por modo. +ontoref-ui-compose-title = Compose +ontoref-ui-compose-desc = Formularios de modos interactivos con compartición de sesión en vivo para flujos multi-actor. +ontoref-ui-dashboard-desc = Salud del proyecto, estado de drift, resumen de actores y notificaciones pendientes de un vistazo. +ontoref-mcp-core-title = Core +ontoref-mcp-query-desc = search, get_node, list_adrs, get_adr, list_modes, get_mode, constraints — recorrido completo del DAG. +ontoref-crate-ontology-title = ontoref-ontology +ontoref-crate-ontology-desc = Carga ficheros .ontology/ NCL como structs Rust tipados. Recorrido de grafo (callers, callees, impact queries). Extracción de invariantes y validación de constraints. Sin dependencias de stratumiops — superficie de adopción mínima. +ontoref-crate-reflection-title = ontoref-reflection +ontoref-crate-reflection-desc = Ejecuta modos de reflexión como contratos DAG NCL tipados. Ejecución de pasos con resolución de dependencias. Ciclo de vida ADR de Proposed a Accepted a Superseded. Operaciones de config seal y rollback. +ontoref-crate-nushell-title = Módulos Nushell +ontoref-crate-nushell-desc = store.nu (caché respaldado por SurrealDB con export NCL) · sync.nu (sincronización de código de ontología) · describe.nu (autoconocimiento de proyecto con actor-awareness) · coder.nu (registros de sesión estructurados). 16 módulos en total — uno por dominio operacional. +ontoref-crate-nickel-title = Schemas Nickel +ontoref-crate-nickel-desc = Tipos de ontología core: Node, Edge, Pole, AbstractionLevel. Tipos de máquina de estados: Dimension, Transition, Gate, Membrane. Schema ADR: Constraint, Severity, Status, supersession. Schema de reflexión: Mode, Step, OnError, Dependency. +ontoref-crate-daemon-title = ontoref-daemon +ontoref-crate-daemon-desc = Registro de actores vía DashMap. Barrera de notificaciones con integración de hook pre-commit. Formularios Compose para despacho de modos compartidos. Persistencia respaldada por SurrealDB como feature opt-in. Feature gate NATS para setups distribuidos. + +ontoref-positioning-title = Posicionamiento en Vivo +ontoref-positioning-subtitle = Diferenciadores y propuestas de valor en tiempo real del daemon de ontoref. +ontoref-positioning-loading = Cargando datos de posicionamiento… +ontoref-positioning-value-prop-label = Propuesta de Valor +ontoref-positioning-differentiators-label = Diferenciadores +ontoref-positioning-live-label = Activo +ontoref-positioning-draft-label = Borrador diff --git a/site/site/i18n/locales/es/pages/post.ftl b/site/site/i18n/locales/es/pages/post.ftl new file mode 100644 index 0000000..840dfdc --- /dev/null +++ b/site/site/i18n/locales/es/pages/post.ftl @@ -0,0 +1,20 @@ +# Traducciones de la página de visualización de artículos + +# Estructura del contenido del artículo +post-author = Autor +post-date = Fecha de Publicación +post-category = Categoría +post-tags = Etiquetas + +# Navegación y acciones +post-back-to-blog = Volver al Blog +post-back-to-recetas = Volver a Recetas +post-back-to-content = Volver a Contenido +post-back-to-tutoriales = Volver a Tutoriales +post-share = Compartir este artículo +post-print = Imprimir artículo + +# Metadatos +post-reading-time = Tiempo de lectura +post-difficulty = Dificultad +post-last-updated = Última actualización \ No newline at end of file diff --git a/site/site/i18n/locales/es/pages/privacy.ftl b/site/site/i18n/locales/es/pages/privacy.ftl new file mode 100644 index 0000000..b546a85 --- /dev/null +++ b/site/site/i18n/locales/es/pages/privacy.ftl @@ -0,0 +1,59 @@ +# Traducciones específicas de la página de privacidad - Español + +# Metadatos de Página +privacy-page-title = Política de Privacidad +privacy-page-description = Nuestra política de privacidad y prácticas de protección de datos +privacy-page-keywords = privacidad, política, protección de datos + +# Encabezados de Página +privacy-title = Política de Privacidad +privacy-subtitle = Cómo recopilamos, utilizamos y protegemos tu información +privacy-effective-date = Última actualización: julio de 2026 +privacy-intro = Esta Política de Privacidad explica cómo Jesús Pérez Lorenzo (responsable del tratamiento) recopila, utiliza y protege tus datos personales cuando visitas este sitio web, te suscribes a nuestras comunicaciones o utilizas nuestros servicios, conforme al RGPD (UE 2016/679) y la LOPDGDD. + +# Responsable del Tratamiento +privacy-controller = Responsable del Tratamiento +privacy-controller-text = Jesús Pérez Lorenzo — Contacto y ejercicio de derechos: ontoref@jesusperez.pro (España). +privacy-legal-basis = Tratamos tus datos con base en tu consentimiento (art. 6.1.a), la ejecución de un contrato o medidas precontractuales (art. 6.1.b) y nuestro interés legítimo en la seguridad del sitio (art. 6.1.f). +privacy-consent-proof = Para la suscripción registramos la IP y la fecha/hora del alta como prueba del consentimiento. Puedes retirar tu consentimiento en cualquier momento desde el enlace de baja o escribiendo a ontoref@jesusperez.pro. + +# Recopilación de Información +privacy-information-we-collect = Información que Recopilamos +privacy-information-you-provide = Información que Proporcionas +privacy-information-you-provide-text = Información de contacto (nombre, correo electrónico, número de teléfono) cuando te pones en contacto con nosotros +privacy-project-details-text = Detalles del proyecto y requisitos al solicitar servicios +privacy-communication-preferences-text = Preferencias de comunicación y comentarios + +# Información Recopilada Automáticamente +privacy-automatically-collected-information = Información Recopilada Automáticamente +privacy-website-usage-data = Datos de uso del sitio web a través de herramientas de análisis +privacy-technical-information-ip = Información técnica (dirección IP, tipo de navegador, información del dispositivo) +privacy-cookies-tracking = Cookies y tecnologías de seguimiento similares para la funcionalidad del sitio web + +# Cómo Utilizamos la Información +privacy-how-we-use-information = Cómo Utilizamos Tu Información +privacy-how-we-use-text = Utilizamos la información recopilada para: +privacy-respond-to-inquiries = Responder a tus consultas y proporcionar los servicios solicitados +privacy-improve-website = Mejorar nuestro sitio web y ofertas de servicios +privacy-send-updates = Enviar actualizaciones relevantes sobre nuestros servicios (con tu consentimiento) +privacy-ensure-security = Garantizar la seguridad del sitio web y prevenir el fraude +privacy-comply-legal = Cumplir con obligaciones legales + +# Tus Derechos +privacy-your-rights = Tus Derechos +privacy-your-rights-text = Puedes ejercer gratuitamente, escribiendo a ontoref@jesusperez.pro, tus derechos de: +privacy-access-review = Acceso a tus datos personales +privacy-request-corrections = Rectificación de datos inexactos +privacy-request-deletion = Supresión de tus datos personales +privacy-right-portability = Portabilidad de tus datos +privacy-right-restriction = Limitación y oposición al tratamiento +privacy-right-withdraw = Retirada del consentimiento en cualquier momento +privacy-opt-out-marketing = Baja de las comunicaciones (enlace en cada envío) +privacy-right-complaint = Reclamación ante la Agencia Española de Protección de Datos (AEPD), www.aepd.es + +# Información de Contacto +privacy-contact-us = Contáctanos +privacy-contact-privacy-text = Si tienes alguna pregunta sobre esta Política de Privacidad o nuestras prácticas de datos, por favor contáctanos: +privacy-email = Correo Electrónico +privacy-address = Dirección +privacy-spain = España diff --git a/site/site/i18n/locales/es/pages/projects.ftl b/site/site/i18n/locales/es/pages/projects.ftl new file mode 100644 index 0000000..31777a7 --- /dev/null +++ b/site/site/i18n/locales/es/pages/projects.ftl @@ -0,0 +1,23 @@ +# Projects page translations - Spanish + +# Page header +projects-page-title = Proyectos +# superficie de dominios ontoref (ADR-065) — <title> del índice, por segmento URL /dominios +dominios-page-title = Dominios +projects-page-description = Proyectos open source y plataformas +projects-title = Proyectos +projects-subtitle = Proyectos open source y plataformas + +# Category page header +projects-category-page-title = Proyectos +projects-category-page-description = Proyectos filtrados por categoría. +projects-category-page-keywords = proyectos, open source, rust, plataformas, categoría + +# Content grid +projects-no-items-in-category = No se encontraron proyectos en esta categoría. +projects-view-all = Ver todos los proyectos +projects-featured = Destacado +projects-load-more = Cargar más + +# Error states +projects-error-loading = Error al cargar los proyectos. Por favor, inténtalo de nuevo. diff --git a/site/site/i18n/locales/es/pages/provisioning.ftl b/site/site/i18n/locales/es/pages/provisioning.ftl new file mode 100644 index 0000000..76fb5bd --- /dev/null +++ b/site/site/i18n/locales/es/pages/provisioning.ftl @@ -0,0 +1,73 @@ +# Provisioning page — Plataforma de Orquestación de Infraestructura + +# Hero +provisioning-badge = v3.0.11 | Listo para Producción +provisioning-tagline = Provision at Scale +provisioning-page-title = Orquestación de Infraestructura Con Configuración como Código +provisioning-page-subtitle = Gestión de infraestructura declarativa con esquemas Nickel, orquestación Nushell y ejecutables Rust. Configuración type-safe, validación automática y despliegue cloud-native en Kubernetes, Docker y plataformas personalizadas. 100% infraestructura como código. + +# Core Capabilities +provisioning-capabilities-title = Capacidades Principales +provisioning-cap-1-title = Configuración Type-Safe +provisioning-cap-1-desc = Nickel proporciona verificación formal de tipos, validación automática y fusión recursiva para definiciones de infraestructura. Elimina desviación de configuración y errores de análisis. +provisioning-cap-2-title = Orquestación Inteligente +provisioning-cap-2-desc = Los scripts Nushell orquestan flujos de despliegue complejos con canales de datos estructurados, gestión de estado y recuperación de errores. Sistema de tipos incorporado previene fallos en tiempo de ejecución. +provisioning-cap-3-title = Soporte Multi-Plataforma +provisioning-cap-3-desc = Despliega en Kubernetes, Docker Compose, máquinas virtuales locales e infraestructura personalizada. Interfaz unificada con sobrescrituras específicas de plataforma y aislamiento de espacios de trabajo. +provisioning-cap-4-title = Listo para Control de Versiones +provisioning-cap-4-desc = Toda la infraestructura vive en Git. Los esquemas de configuración, módulos Nickel y scripts Nushell son versionables, revisables y reversibles. + +# How It Works +provisioning-how-title = Cómo Funciona +provisioning-how-nickel-title = Definir con Nickel +provisioning-how-nickel-desc = Escriba esquemas de infraestructura declarativos con seguridad de tipos completa. La evaluación perezosa de Nickel y la fusión recursiva permiten abstracciones poderosas y composición de configuración. +provisioning-how-nushell-title = Orquestar con Nushell +provisioning-how-nushell-desc = Canales de datos estructurados, operaciones type-safe y flujos de trabajo con estado. Nushell elimina la fragilidad de los scripts de shell manteniendo la simplicidad de los scripts. +provisioning-how-rust-title = Ejecutar con Rust +provisioning-how-rust-desc = Operaciones críticas de rendimiento respaldadas por Rust. Abstracciones de costo cero, seguridad de memoria y concurrencia sin miedo para herramientas de infraestructura. +provisioning-how-nats-title = Coordinar via NATS JetStream +provisioning-how-nats-desc = Bus de eventos persistente para coordinación desacoplada. Los servicios intercambian lease_id, task_id y eventos de estado — las credenciales nunca atraviesan el bus. Auditable, con capacidad de replay y entrega at-least-once. +provisioning-how-security-title = Seguridad extremo a extremo +provisioning-how-security-desc = Cedar como código de autorización, sesiones JWT gestionadas exclusivamente por ControlCenter y criptografía post-cuántica via SecretumVault. Las credenciales nunca salen del vault — los servicios operan solo con referencias de lease. +provisioning-how-oci-title = Extender via OCI Registry +provisioning-how-oci-desc = Distribuye proveedores, task services y definiciones de clúster personalizados como artefactos OCI. El Extension Registry cataloga y versiona capacidades de forma independiente — intercambia o compone proveedores sin tocar el código del núcleo. +provisioning-how-cli-title = CLI de Plataforma y Servicios Externos +provisioning-how-cli-desc = 8 subcomandos de plataforma (list, status, health, check, config, connections, init, start) con gestión declarativa de servicios externos. Valida SurrealDB, registros OCI, fuentes Git y caché antes del arranque. +provisioning-how-versions-title = Gestión Centralizada de Versiones +provisioning-how-versions-desc = Todas las versiones de herramientas y proveedores definidas en esquemas Nickel. Descubrimiento automático de proveedores, integración con scripts shell y fuente única de verdad para Nushell, Nickel, SOPS, Age, AWS CLI y todos los proveedores. + +# SOLID Architecture Boundaries +provisioning-solid-title = Límites de Arquitectura SOLID +provisioning-solid-provider-title = APIs de Proveedor — solo Orchestrator +provisioning-solid-provider-desc = Todas las llamadas a hcloud, AWS y SDKs de proveedor están aisladas en el crate Orchestrator. Todos los demás servicios enrutan a través de la API HTTP del Orchestrator. Las operaciones SSH comparten este mismo límite. +provisioning-solid-auth-title = Decisiones de Auth — solo ControlCenter +provisioning-solid-auth-desc = La validación JWT y la evaluación de políticas Cedar ocurren exclusivamente en ControlCenter. Los demás servicios reciben un UserContext via middleware — ningún servicio re-valida tokens ni evalúa políticas directamente. +provisioning-solid-secrets-title = Secretos — solo API del Vault Service +provisioning-solid-secrets-desc = Las credenciales nunca se almacenan en mensajes NATS ni variables de entorno. Los servicios mantienen un lease_id y recuperan los secretos reales vía HTTPS al Vault Service. El bus transporta referencias, no valores. + +# Architecture diagram +provisioning-architecture-title = Arquitectura del Sistema + +# Tech stack +provisioning-tech-stack-title = Stack Tecnológico + +# Platform Services +provisioning-services-title = Servicios de Plataforma (13) +provisioning-svc-orchestrator-role = APIs de proveedor + límite SSH +provisioning-svc-controlcenter-role = Límite auth Cedar +provisioning-svc-controlcenter-ui-role = Dashboard Leptos WASM +provisioning-svc-mcp-server-role = RAG + Herramientas IA +provisioning-svc-ai-service-role = Capa inferencia LLM +provisioning-svc-extension-registry-role = Catálogo de extensiones +provisioning-svc-secretumvault-role = API de secretos PQC +provisioning-svc-detector-role = Detección de eventos +provisioning-svc-daemon-cli-role = Ciclo de vida de servicios +provisioning-svc-machines-role = Gestión de máquinas +provisioning-svc-observability-role = Métricas Prometheus +provisioning-svc-backup-role = Backup de estado +provisioning-svc-encrypt-role = Operaciones de clave + +# CTA +provisioning-cta-title = ¿Listo para la automatización de infraestructura? +provisioning-cta-subtitle = Construido con Nickel y Nushell | Rust | Type-Safe | Open Source +provisioning-cta-explore = Explorar Repo Git diff --git a/site/site/i18n/locales/es/pages/recipes.ftl b/site/site/i18n/locales/es/pages/recipes.ftl new file mode 100644 index 0000000..b86cc6d --- /dev/null +++ b/site/site/i18n/locales/es/pages/recipes.ftl @@ -0,0 +1,87 @@ +# Traducciones específicas de página de recetas - Español + +# Metadatos de Página +recipes-page-title = Recetas +recipes-page-description = Colección de recetas +recipes-page-keywords = recetas, cocina + +# Encabezado de página +recipes-page-subtitle = Recetas de código prácticas y guías de implementación para desafíos de desarrollo modernos. Enfocado en Rust, DevOps y soluciones cloud-native. + +# Encabezado de página de categoría +recipes-category-page-title = Recetas Técnicas +recipes-category-page-description = Recetas filtradas por categoría - descubre soluciones prácticas para áreas tecnológicas específicas. +recipes-category-page-keywords = recetas, categoría, código, soluciones, filtradas, tecnología + +# Contenido de recetas +recipes-featured = Recetas Destacadas +recipes-featured-description = Soluciones prácticas curadas para los desafíos de desarrollo más comunes +recipes-all = Todas las Recetas +recipes-all-recipes = Todas las Recetas +view-recipe = Ver Receta +view-all-recipes = Ver Todas las Recetas +recipes-difficulty = Dificultad +recipes-view-recipe = Ver Receta → +recipes-need-custom-recipe = ¿Necesitas una Receta Personalizada? +recipes-custom-recipe-description = ¿No encuentras una solución para tu desafío específico? Permíteme crear una receta personalizada para tu caso de uso. +recipes-request-custom-recipe = Solicitar Receta Personalizada +recipes-error-loading-recipes = Error al cargar recetas. Por favor, inténtalo de nuevo más tarde. + +# Acciones de recetas +recipes-need-custom-recipe-title = ¿Necesitas una Receta Personalizada? +recipes-need-custom-recipe-desc = ¿No encuentras una solución para tu desafío específico? Permíteme crear una receta personalizada para tu caso de uso. +recipes-request-custom-recipe-btn = Solicitar Receta Personalizada +recipes-browse-blog-posts-btn = Explorar Artículos del Blog +recipes-view-recipe-btn = Ver Receta +recipes-contact-url = /contactar +recipes-blog-url = /blog-es + +# Categorías de recetas +recipes-categories = Categorías +recipes-rust-programming = Programación Rust +recipes-docker = Docker +recipes-kubernetes = Kubernetes +recipes-devops = DevOps +recipes-microservices = Microservicios +recipes-infrastructure = Infraestructura +recipes-async-programming = Programación Asíncrona +recipes-web-development = Desarrollo Web +recipes-architecture = Arquitectura +recipes-cicd = CI/CD + +# Traducciones adicionales específicas de recetas +recipes-disabled-title = Recetas Temporalmente No Disponibles +recipes-disabled-message = La sección de recetas está actualmente deshabilitada. Por favor, vuelve más tarde o contáctanos para obtener asistencia. +recipes-loading = Cargando recetas... +recipes-error-loading-featured = Error al cargar recetas destacadas +recipes-error-loading-recipes-grid = Error al cargar cuadrícula de recetas + +# Contenido de marcador de posición para cuando se están cargando los datos +recipes-grid-placeholder = Cargando cuadrícula de recetas... + +# Sección de navegación por categorías +recipes-browse-categories-title = Navegar por Categoría +recipes-browse-categories-description = Explora recetas organizadas por categoría y nivel de dificultad. + +# Mensajes de la cuadrícula de contenido +recipes-no-posts-in-category = No se encontraron recetas en esta categoría +recipes-view-all-posts = Ver Todas las Recetas + +# Títulos y descripciones específicas de categorías +recipes-cat-async-programming-title = Programación Asíncrona +recipes-cat-async-programming-desc = Patrones de programación asíncrona, futures, async/await y técnicas de ejecución concurrente + +recipes-cat-cicd-title = CI/CD y Automatización +recipes-cat-cicd-desc = Integración continua, despliegue continuo, pruebas automatizadas y recetas de pipelines de despliegue + +recipes-cat-docker-title = Docker y Containerización +recipes-cat-docker-desc = Recetas de contenedores Docker, builds multi-etapa, técnicas de optimización y mejores prácticas de containerización + +recipes-cat-kubernetes-title = Kubernetes y Orquestación +recipes-cat-kubernetes-desc = Patrones de despliegue de Kubernetes, configuraciones de service mesh y soluciones de orquestación de contenedores + +recipes-cat-rust-programming-title = Programación en Rust +recipes-cat-rust-programming-desc = Recetas del lenguaje Rust, optimización de rendimiento, gestión de memoria e integración con el ecosistema + +# Mensajes genéricos de contenido (utilizados por UnifiedContentGrid) +# Movidos a content-kinds.ftl para evitar duplicación \ No newline at end of file diff --git a/site/site/i18n/locales/es/pages/resources.ftl b/site/site/i18n/locales/es/pages/resources.ftl new file mode 100644 index 0000000..e6f1768 --- /dev/null +++ b/site/site/i18n/locales/es/pages/resources.ftl @@ -0,0 +1,26 @@ +# Resources page translations - Spanish + +# Page header +resources-page-title = Recursos +# título del índice por segmento URL /recursos +recursos-page-title = Recursos +resources-page-description = Decks de releases de Ontoref y diapositivas de charlas +resources-title = Recursos +resources-subtitle = Decks de releases de Ontoref y diapositivas de charlas + +# Category page header +resources-category-page-title = Recursos +resources-category-page-description = Recursos filtrados por categoría. +resources-category-page-keywords = recursos, decks, diapositivas, slides, releases, presentaciones, categoría + +# Navigation +nav-resources = Recursos + +# Content grid +resources-no-items-in-category = No se encontraron recursos en esta categoría. +resources-view-all = Ver todos los recursos +resources-featured = Destacado +resources-load-more = Cargar más + +# Error states +resources-error-loading = Error al cargar los recursos. Por favor, inténtalo de nuevo. diff --git a/site/site/i18n/locales/es/pages/rustelo.ftl b/site/site/i18n/locales/es/pages/rustelo.ftl new file mode 100644 index 0000000..8023083 --- /dev/null +++ b/site/site/i18n/locales/es/pages/rustelo.ftl @@ -0,0 +1,76 @@ +# Rustelo page — Framework Web Rust Full-Stack + +# Hero +rustelo-badge = Framework Web Rust Full-Stack +rustelo-tagline = Modular · Seguro en Tipos · Orientado a Configuración · Seguro en Memoria +rustelo-hero-title = Construye. Despliega. Entrega. +rustelo-hero-subtitle = Plataforma Rust modular y type-safe construida sobre Leptos y Axum. Arquitectura orientada a configuración con enrutamiento agnóstico al idioma, sistema de plugins y compilación dual WASM+nativo. + +# Stats +rustelo-stat-crates = Crates +rustelo-stat-layers = Capas +rustelo-stat-unsafe = bloques unsafe +rustelo-stat-targets = Targets WASM+nativo + +# CTAs +rustelo-cta-github = Git Repo +rustelo-cta-architecture = Ver Arquitectura +rustelo-cta-get-started = Comenzar + +# Architecture section +rustelo-arch-title = Diseño en Tres Capas +rustelo-arch-subtitle = Separación clara entre tu aplicación, los ports del framework y la fundación. Cada capa tiene una única responsabilidad. + +# Layer badges / titles / descriptions +rustelo-layer-app-badge = Tu App +rustelo-layer-app-title = Capa de Implementación +rustelo-layer-app-desc = Los crates de tu aplicación: server (Axum SSR), client (Leptos WASM) y pages (Leptos components). La generación de código en tiempo de build produce routes, pages e i18n desde la configuración. + +rustelo-layer-ports-title = Ports del Framework +rustelo-layer-ports-desc = API pública estable para implementaciones. Los ports envuelven los internos de la fundación y exponen interfaces limpias y versionadas: web stack, autenticación y gestión de contenido. + +rustelo-layer-foundation-title = Fundación +rustelo-layer-foundation-desc = Implementaciones core: config, i18n, RBAC, servidor Axum SSR, cliente Leptos WASM, componentes UI, sistema de generación de páginas y configuración de build basada en Nickel. + +# Features section +rustelo-features-title = Todo Incluido +rustelo-features-subtitle = Una plataforma completa desde autenticación hasta despliegue, composable mediante Cargo features. + +rustelo-feat-wasm-title = Frontend WASM Reactivo +rustelo-feat-wasm-desc = Reactividad de grano fino con Leptos. Signals, memos, effects. Sin virtual DOM. Hidratación SSR con tachys. + +rustelo-feat-auth-title = Autenticación y RBAC +rustelo-feat-auth-desc = JWT, OAuth2, 2FA con TOTP. Control de acceso basado en roles mediante políticas Nickel NCL. Hash Argon2. + +rustelo-feat-routing-title = Enrutamiento Agnóstico +rustelo-feat-routing-desc = Rutas definidas en TOML/NCL. Generación en tiempo de build. Sin rutas hardcodeadas. Descubrimiento automático de idiomas. + +rustelo-feat-plugins-title = Sistema de Plugins +rustelo-feat-plugins-desc = Traits ResourceContributor y PageContributor. Registro al inicio, sin overhead en runtime. include_str!() para embedding en tiempo de compilación. + +rustelo-feat-db-title = Abstracción de BD +rustelo-feat-db-desc = PostgreSQL y SQLite vía SQLx. Verificación de queries en tiempo de compilación. Pool de conexiones async. Migraciones separadas por tipo de base de datos. + +rustelo-feat-email-title = Email y Notificaciones +rustelo-feat-email-desc = Sistema de email multi-proveedor vía Lettre. Emails basados en plantillas con FTL i18n. Notificaciones en tiempo real WebSocket vía tokio-tungstenite. + +rustelo-feat-codegen-title = Generación de Código en Build +rustelo-feat-codegen-desc = Nickel NCL → build.rs → OUT_DIR → include!(). Routes, pages, menus y registro FTL generados en tiempo de compilación. Coste cero en runtime. + +rustelo-feat-features-title = Composición de Features Cargo +rustelo-feat-features-desc = auth, content-db, content-static, email, tls, metrics, crypto. Activa solo lo que necesitas. Compilación por features para targets WASM y nativo. + +# Build pipeline +rustelo-pipeline-title = Generación Orientada a Configuración +rustelo-pipeline-subtitle = Todo generado en tiempo de compilación desde configuración Nickel NCL. Sin overhead en runtime, seguridad de tipos completa. + +# Quick start +rustelo-quickstart-title = En Marcha +rustelo-quickstart-subtitle = De cero a aplicación Rustelo funcionando. + +# Tech stack +rustelo-tech-title = Construido sobre Bases Sólidas + +# Bottom CTA +rustelo-cta-title = ¿Listo para construir con Rust? +rustelo-cta-subtitle = Open source · Self-hosted · Zero unsafe · 100% Rust diff --git a/site/site/i18n/locales/es/pages/secretumvault.ftl b/site/site/i18n/locales/es/pages/secretumvault.ftl new file mode 100644 index 0000000..a5d8070 --- /dev/null +++ b/site/site/i18n/locales/es/pages/secretumvault.ftl @@ -0,0 +1,37 @@ +# SecretumVault page — Boveda de Secretos Post-Cuantica + +# Hero +secretumvault-badge = PQC Listo para Produccion | Rust 1.75+ | Cedar ABAC | 50+ Tests +secretumvault-tagline = Boveda Criptografica Post-Cuantica de Secretos +secretumvault-page-title = Boveda de Secretos Post-Cuantica para Infraestructura Moderna +secretumvault-page-subtitle = ML-KEM-768 + ML-DSA-65 listo para produccion con autorizacion Cedar, backends criptograficos enchufables y almacenamiento multi-cloud. Despliega criptografia post-cuantica hoy. Una linea de config para activar PQC. Sin cambios de codigo. + +# Why +secretumvault-why-title = Por que SecretumVault +secretumvault-problem-title = El Problema +secretumvault-problem-desc = La encriptacion actual sera rota por computadoras cuanticas. La mayoria de bovedas no tienen ruta de migracion PQC. Los KMS cloud te atrapan. Los lenguajes de politicas son propietarios y basados en ACL. +secretumvault-solution-title = La Solucion +secretumvault-solution-desc = Agilidad criptografica mediante backends enchufables. ML-KEM-768 + ML-DSA-65 funcionan hoy via OQS. Las politicas Cedar son portables. El almacenamiento multi-cloud evita dependencia. Cambia backends via config — sin recompilar. + +# Features +secretumvault-features-title = Capacidades Principales +secretumvault-feat-pqc-title = Criptografia Post-Cuantica +secretumvault-feat-pqc-desc = Encapsulacion de claves ML-KEM-768 (FIPS 203) y firmas digitales ML-DSA-65 (FIPS 204) via OQS. Conformidad NIST verificada. Modo hibrido clasico+PQC en desarrollo. +secretumvault-feat-engines-title = Motores de Secretos +secretumvault-feat-engines-desc = KV (versionado), Transit (encriptacion como servicio), PKI (certificados X.509), Database (credenciales dinamicas para PostgreSQL, MySQL, MongoDB). Extensible via trait. +secretumvault-feat-storage-title = Almacenamiento Multi-Backend +secretumvault-feat-storage-desc = etcd (HA distribuido), SurrealDB (documento + grafo con MVCC), PostgreSQL (ACID), filesystem (dev/test). Enchufable via trait StorageBackend. +secretumvault-feat-cedar-title = Autorizacion Cedar +secretumvault-feat-cedar-desc = Lenguaje de politicas Cedar de AWS para control de acceso basado en atributos. Decisiones contextuales: allowlisting IP, acceso temporal, restricciones de entorno. Politicas como codigo. +secretumvault-feat-cloud-title = Cloud-Native +secretumvault-feat-cloud-desc = Preparado para Kubernetes con charts Helm. Builds multi-stage Docker (<50MB). Metricas Prometheus, logging estructurado con IDs de correlacion. Bus de eventos NATS para notificaciones de ciclo de vida. +secretumvault-feat-enterprise-title = Preparado para Empresa +secretumvault-feat-enterprise-desc = TLS/mTLS, Shamir Secret Sharing (2-de-3, 3-de-5), gestion de tokens con TTL/renovacion/revocacion, logging de auditoria completo para cumplimiento SOC2/GDPR/HIPAA. + +# Tech stack +secretumvault-tech-stack-title = Stack Tecnologico + +# CTA +secretumvault-cta-title = Despliega criptografia post-cuantica hoy +secretumvault-cta-subtitle = Nativo Rust | Apache 2.0 | Auto-Alojado | Multi-Cloud +secretumvault-cta-explore = Explorar Repo Git diff --git a/site/site/i18n/locales/es/pages/services.ftl b/site/site/i18n/locales/es/pages/services.ftl new file mode 100644 index 0000000..ad9de83 --- /dev/null +++ b/site/site/i18n/locales/es/pages/services.ftl @@ -0,0 +1,82 @@ +# Página de servicios — ontoref. Estructurada por tiers: operar tier-2, aprender tiers 0–1. +# Anclada en .ontoref/positioning (viability via-001, tier-taglines, value-props). + +# SEO +services-page-title = Adopta el protocolo — o apréndelo +services-page-description = ontoref es un regalo. Lo que se ofrece es ayuda para operar su tier más alto — la superficie de operaciones y gobernanza atestiguada — y formación y coaching para subir los tiers inferiores. +services-page-keywords = ontoref, ontología, gobernanza, traza de auditoría, testigo, formación, coaching, tier-2 + +# Hero +services-page-subtitle = El protocolo nunca se vende — es opt-in, por tier, y nunca una dependencia en tiempo de ejecución. Lo que se ofrece es ayuda para operar su tier más alto y formación para subir los demás. +services-page-note = Lo que dejas escrito, cualquiera lo puede comprobar. + +# ── Tier 2 — Operaciones y gobernanza (la propuesta) ──────────────────────── +services-tier2-eyebrow = TIER 2 · OPERACIONES · SOSTIENE EL RUMBO +services-tier2-title = Operaciones y gobernanza, operadas como servicio +services-tier2-desc = El tier 2 es donde cada mutación pasa por una operación tipada y deposita un testigo Ed25519 — el recorrido de los cambios, no solo el estado, queda sostenido y verificable. El protocolo sigue abierto; la superficie atestiguada es lo que se opera o se da soporte por ti. +services-tier2-ops-title = Adopción de la capa de operaciones +services-tier2-ops-desc = Levantar el tier 2: operaciones de dominio como único camino de mutación del agente, precondiciones tipadas, dispatch y el daemon. El runtime pasa a ser el único sitio donde el estado autoritativo puede cambiar (ADR-024). +services-tier2-audit-title = Traza de auditoría atestiguada +services-tier2-audit-desc = Un DAG de oplog Ed25519, direccionado por contenido y reproducible — un registro a prueba de manipulación que cualquiera verifica contra las claves de actor sin confiar en quien lo produjo (ADR-023/047, vp-004). +services-tier2-validators-title = Validadores y criterios a medida +services-tier2-validators-desc = Codifica tus restricciones duras como validadores atestiguados de primera clase que rechazan estado mal tipado en los tres planos de validación — tus reglas de gobernanza se vuelven comprobables, no consejos (ADR-026/050). +services-tier2-migration-title = Migración de sustrato +services-tier2-migration-desc = Pasar de docs dispersos, scripts ad-hoc y un sistema de ficheros NCL al sustrato verificable — op log, blobs direccionados por contenido, triples bitemporales, compromisos Merkle — sin una reescritura de golpe (ADR-023/029). +services-what-you-get = Qué obtienes +services-get-witnessed-history = ✅ Un historial atestiguado y reproducible de cada cambio autoritativo +services-get-validators = ✅ Tus restricciones como validadores de primera clase, no checklists de revisión +services-get-knowledge-transfer = ✅ Transferencia de conocimiento — tu equipo lo opera sin nosotros +services-get-no-lockin = ✅ Sin lock-in: el protocolo nunca es una dependencia en tiempo de ejecución +services-get-open-protocol = ✅ El protocolo abierto sigue siendo un regalo — solo se mide la superficie atestiguada del tier alto + +# ── Formación y coaching — los tiers inferiores, para usar o aprender ──────── +services-learn-title = Formación y coaching +services-learn-desc = La mayoría de los equipos no deberían empezar en el tier 2. Empieza declarando lo que eres (tier 0) y comprobando tu estado (tier 1) — son aprendibles, y el coaching te encuentra donde estás. +services-coaching-eyebrow = TIER 0 · DECLARATIVO · SOSTIENE SU PORQUÉ +services-coaching-title = Coaching — declara tu porqué +services-coaching-desc = Acompañamiento práctico para llevar tu arquitectura a NCL tipado: axiomas, tensiones, ADR y estado — para que un agente o un recién llegado entienda el proyecto desde el grafo, no desde la memoria tribal. +services-coaching-1to1-title = Sesiones 1:1 +services-coaching-1to1-note = 1–2 h +services-coaching-1to1-desc = Trabaja sobre tu ontología real — nombra los axiomas y las tensiones que de verdad gobiernan tus decisiones. +services-coaching-ondaod-title = Disciplina de tensiones ondaod +services-coaching-ondaod-note = 1–2 h +services-coaching-ondaod-desc = Aprende a mantener un Spiral abierto en vez de colapsarlo — lee primero las tensiones, describe la síntesis, decide sin cerrar la puerta. +services-coaching-adr-title = Modelado de ADR y decisiones +services-coaching-adr-note = 1–2 h +services-coaching-adr-desc = Convierte decisiones en ADR tipados con restricciones y alternativas rechazadas — reversible-o-no, consultable, enlazado al grafo. +services-coaching-ongoing-title = Acompañamiento continuo +services-coaching-ongoing-note = continuo +services-coaching-ongoing-desc = Sesiones recurrentes a medida que crece tu ontología — mantén el porqué declarado coherente con el código según cambia. + +services-training-eyebrow = TIER 1 · SUSTRATO · SE SOSTIENE Y SE COMPRUEBA +services-training-title = Formación — comprueba tu estado +services-training-desc = Cursos y talleres sobre el tier de sustrato: compromete tu estado para que sea comprobable externamente antes de que exista ninguna operación — verificación sin toda la maquinaria de operaciones. +services-training-courses-title = Cursos a medida +services-training-courses-desc = Un temario adaptado a tu stack: la división de la ontología en tres ficheros, los compromisos de sustrato y cómo describe responde a las consultas de autoconocimiento. +services-training-workshops-title = Talleres prácticos +services-training-workshops-desc = Tu equipo lleva un subsistema real de sin documentar a declarado-y-verificado en una sesión — compromiso de sustrato, verificación testigo-no-clon (ADR-028). +services-training-talks-title = Charlas y seminarios +services-training-talks-desc = Sesiones tipo conferencia sobre ontología operacional, la dualidad ontología/reflexión y la verificación suficiente sobre el conocimiento completo. +services-training-content-title = Contenido educativo +services-training-content-desc = Guías escritas y el docsite proyectado (mdBook desde la ontología, ADR-060) adaptados como ruta de aprendizaje para tu equipo. + +# ── Los tres tiers — cuál encaja ──────────────────────────────────────────── +services-tiers-title = Qué tier encaja contigo +services-tiers-desc = La adopción es graduada (ADR-029): entra con un solo fichero NCL y sube solo cuando duela. Nunca eres rehén de un runtime ni de un SaaS. +services-tier0-label = TIER 0 · DECLARATIVO +services-tier0-title = Sostiene su porqué +services-tier0-desc = Una ontología tipada más describe: declaras la razón por la que tu proyecto existe como NCL. El coaching te lleva aquí. +services-tier1-label = TIER 1 · SUSTRATO +services-tier1-title = Se sostiene y se comprueba +services-tier1-desc = Un compromiso de sustrato hace tu estado comprobable externamente antes de cualquier mutación. La formación te lleva aquí. +services-tier2-label = TIER 2 · OPERACIONES +services-tier2-card-title = Sostiene el rumbo +services-tier2-card-desc = Testigos Ed25519 y determinismo de replay: cada cambio queda atestiguado, así que el recorrido es verificable. Lo operamos o damos soporte contigo. + +# ── CTA ───────────────────────────────────────────────────────────────────── +services-cta-title = ¿Por dónde empezar? +services-cta-desc = Dinos tu tier y tu stack — te señalamos el primer paso más pequeño, sea una sesión de coaching o una intervención de tier 2. +services-cta-primary = Empezar una conversación +services-cta-primary-url = /contacto +services-cta-secondary = Ver qué es ontoref +services-cta-secondary-url = /acerca-de diff --git a/site/site/i18n/locales/es/pages/signout.ftl b/site/site/i18n/locales/es/pages/signout.ftl new file mode 100644 index 0000000..a4d6519 --- /dev/null +++ b/site/site/i18n/locales/es/pages/signout.ftl @@ -0,0 +1,11 @@ +# Página de cierre de sesión - Español + +signout-page-title = Sesión cerrada +signout-page-description = Has cerrado sesión correctamente + +signout-title = ¡Hasta pronto! +signout-title-with-name = ¡Hasta pronto, $name! +signout-subtitle = Has cerrado sesión correctamente. Gracias por visitarnos. +signout-home = Volver al inicio +signout-login-again = Iniciar sesión de nuevo +signout-redirect = Redirigiendo al inicio en $seconds segundos… diff --git a/site/site/i18n/locales/es/pages/stratumiops.ftl b/site/site/i18n/locales/es/pages/stratumiops.ftl new file mode 100644 index 0000000..a4211db --- /dev/null +++ b/site/site/i18n/locales/es/pages/stratumiops.ftl @@ -0,0 +1,127 @@ +# StratumIOps page — Ecosistema de Crates Rust para Gobernanza de Proyectos + +# Hero +stratumiops-badge = v0.1.0 | 52+ Crates | 74+ Tests | 100% Rust +stratumiops-tagline = Ecosistema de Crates Rust para Gobernanza de Proyectos +stratumiops-page-title = Librerias para Proyectos Autoconscientes +stratumiops-page-subtitle = 9 crates Rust, 6 modulos Nushell, schemas Nickel — integra gobernanza dirigida por ontologia, ADRs vivos, configuracion sellada, orquestacion de agentes IA, gestion de conocimiento y secretos post-cuanticos en tus proyectos. Un protocolo para humanos, agentes y CI. 100% Rust. Sin compromisos. + +# Problems +stratumiops-problems-title = Los 8 Problemas que Resuelve +stratumiops-problem-1-title = Conocimiento Disperso +stratumiops-problem-1-desc = Decisiones en Slack, guias en wikis, patrones en docs — todo desconectado. Kogral unifica con markdown git-native + MCP. +stratumiops-problem-2-title = Costos LLM sin Control +stratumiops-problem-2-desc = Sin visibilidad en gasto IA por equipo. Sin limites o controles de presupuesto. Vapora provee presupuestos en tiempo real, fallback automatico a proveedores baratos y routing basado en expertise. +stratumiops-problem-3-title = Configuracion YAML Fragil +stratumiops-problem-3-desc = Errores de runtime por config sin tipos. Sin validacion antes del deployment. Provisioning usa Nickel con validacion pre-runtime. TypeDialog provee formularios con validacion de contratos. +stratumiops-problem-4-title = Criptografia Estatica +stratumiops-problem-4-desc = Sin preparacion para amenazas cuanticas. Bloqueado en una biblioteca cripto. SecretumVault entrega cripto post-cuantico en produccion con ML-KEM-768, ML-DSA-65 y 4 backends enchufables. +stratumiops-problem-5-title = Flujos de Proyecto Caoticos +stratumiops-problem-5-desc = TODOs dispersos, sin fases estructuradas. Sin trazabilidad de cambios de estado. Syntaxis provee orquestacion sistematica de proyectos con 4 interfaces y coordinacion de agentes IA lista para VAPORA. +stratumiops-problem-6-title = Decisiones Sin Memoria +stratumiops-problem-6-desc = Decisiones arquitectonicas en verbal o en chat. Constraints olvidadas tras cambios de equipo. Ontologia + ADRs proveen invariantes consultables por maquina con constraints hard aplicadas en cada operacion. +stratumiops-problem-7-title = Drift de Configuracion Invisible +stratumiops-problem-7-desc = Configs modificadas fuera de cualquier ciclo de revision. Sin trazabilidad que vincule cambio a PR o ADR. Reflection provee perfiles de config sellados con sha256, drift detection, rollback verificado e historia completa. +stratumiops-problem-8-title = Documentacion Muerta +stratumiops-problem-8-desc = Docs escritas una vez, nunca actualizadas. Distintos actores generan distintos formatos sin fuente comun. Kogral + Ontologia genera docs vivas desde el mismo grafo — docs para humanos, contexto para agentes, informes de CI. + +# Ontologia, Reflection y ADRs +stratumiops-ontology-title = Ontologia, Reflection y ADRs + +stratumiops-yin-title = Yin — La Capa Formal +stratumiops-yin-subtitle = Lo que debe ser verdad +stratumiops-yin-items = Nickel schemas — correccion estructural en tiempo de definicion + ADR constraints — "esto nunca puede violarse" + Config seals — estados sellados, verificables con sha256 + Ontology invariants — lo que no puede cambiar sin un nuevo ADR + Hashes matematicos — prueba de que fue sellado y cuando + +stratumiops-yang-title = Yang — La Capa Operacional +stratumiops-yang-subtitle = Como las cosas se mueven y cambian +stratumiops-yang-items = Nu commands — transformacion estructurada de datos + Actores (human/agent/CI) — mismo protocolo, distintas capacidades + Register flow — captura cambios, enruta al artefacto correcto + Mode definitions — secuencias de operaciones con verificacion + Pre-commit hooks — sincronizacion forzada en el momento del commit + +stratumiops-tension-1 = Yang sin Yin = fluido pero caotico. Cualquier cosa puede cambiar. Nada es verificable. +stratumiops-tension-2 = Yin sin Yang = correcto pero inutil. Schemas perfectos sin operaciones = documentacion muerta. +stratumiops-tension-thesis = El sistema vive en la coexistencia. + +stratumiops-layer-decl-label = Capa Declarativa — Nickel +stratumiops-layer-decl-items = .ontology/ · adrs/ · reflection/schemas/ · reflection/configs/ +stratumiops-layer-decl-desc = Tipos fuertes, contratos, enums. Falla en definicion, no en runtime. + +stratumiops-layer-op-label = Capa Operacional — Nushell +stratumiops-layer-op-items = adr · register · config · backlog · forms · prereqs +stratumiops-layer-op-desc = Pipelines tipadas sobre datos estructurados. No streams de texto. + +stratumiops-layer-entry-label = Punto de Entrada — Bash a Nu +stratumiops-layer-entry-items = stratum.sh · deteccion de actor · advisory locking · NICKEL_IMPORT_PATH +stratumiops-layer-entry-desc = Un unico entry point. Detecta actor, adquiere lock, despacha al modulo Nu correcto. + +stratumiops-layer-graph-label = Grafo de Conocimiento — Ontologia + ADRs +stratumiops-layer-graph-items = nodos · invariantes · gates · dimensiones · estados +stratumiops-layer-graph-desc = El sistema sabe que sabe. Actor-agnostic. Machine-queryable. + +stratumiops-layer-seal-label = Estados Sellados — Config + Historia +stratumiops-layer-seal-items = perfiles · sellos sha256 · audit trail · rollback +stratumiops-layer-seal-desc = Inmutabilidad verificable. Drift detection. Trazabilidad completa ADR/PR/bug. + +# Diagramas de Arquitectura +stratumiops-arch-title = Arquitectura +stratumiops-arch-orchestrator = Arquitectura del Orquestador +stratumiops-arch-operation-flow = Flujo de Operacion + +# Proyectos del Ecosistema +stratumiops-projects-title = Proyectos del Ecosistema +stratumiops-proj-vapora-title = Vapora +stratumiops-proj-vapora-desc = Orquestacion de agentes IA con aprendizaje. Agentes mejoran con experiencia. Fallback automatico de presupuesto. Coordinacion NATS JetStream. 13 crates, 218 tests, 50K LOC. +stratumiops-proj-kogral-title = Kogral +stratumiops-proj-kogral-desc = Grafo de conocimiento con MCP para Claude Code. 6 tipos de nodos: Notas, ADRs, Guias, Patrones, Diarios, Ejecuciones. Markdown git-native. Busqueda semantica con embeddings. 3 crates, 56 tests, 15K LOC. +stratumiops-proj-typedialog-title = TypeDialog +stratumiops-proj-typedialog-desc = 6 backends: CLI, TUI, Web, IA, Agente, Prov-gen. Formularios NCL-nativos con edicion nickel-roundtrip. Validacion de contratos Nickel. Campos condicionales y grupos repetibles. 8 crates, 3,818 tests, 90K LOC. +stratumiops-proj-provisioning-title = Provisioning +stratumiops-proj-provisioning-desc = IaC declarativa con Nickel + generacion asistida por IA. Multi-cloud: AWS, UpCloud, Local (LXD). RAG con 1,200+ docs de dominio. Servidor MCP para consultas en lenguaje natural. Orquestador con rollback automatico. 15+ crates, 218 tests, 40K LOC. +stratumiops-proj-secretumvault-title = SecretumVault +stratumiops-proj-secretumvault-desc = Cripto post-cuantico: ML-KEM-768, ML-DSA-65 (NIST FIPS 203/204). 4 backends cripto. 4 backends storage. 4 engines de secretos: KV, Transit, PKI, Database. Shamir Secret Sharing. 50+ tests, 11K LOC. +stratumiops-proj-syntaxis-title = Syntaxis +stratumiops-proj-syntaxis-desc = Plataforma de orquestacion sistematica de proyectos. 4 interfaces: CLI, TUI, Dashboard, REST API. Dual DB: SQLite + SurrealDB. Ciclo de vida por fases. Base SST de VAPORA para tareas de agentes IA. 12 crates, 1,030+ tests, 60K LOC. + +stratumiops-more-info = Mas Info + +# Crates Stratum +stratumiops-stratum-title = Crates Stratum +stratumiops-stratum-subtitle = Bibliotecas de infraestructura compartida del ecosistema +stratumiops-stratum-orchestrator-role = Motor de workflows basado en grafos +stratumiops-stratum-graph-role = ActionNode, Capability, GraphRepository +stratumiops-stratum-state-role = PipelineRun, StepRecord, StateTracker +stratumiops-stratum-llm-role = Proveedores LLM unificados + circuit breaker +stratumiops-stratum-embeddings-role = Proveedores de embeddings + VectorStore +stratumiops-stratum-nats-role = Consumidor JetStream + auth NKey +stratumiops-stratum-ncl-role = Resolutor OCI a Nickel local +stratumiops-stratum-ontology-role = Grafo de ontologia, invariantes, gates, dimensiones +stratumiops-stratum-reflection-role = Ciclo de vida ADR, config seals, backlog, modos +stratumiops-stratum-dispatcher-role = Dispatcher modular con actor-awareness +stratumiops-stratum-plugins-role = nickel-export, tera-render, nats pub + +# Metricas del Ecosistema +stratumiops-metrics-title = Metricas del Ecosistema +stratumiops-metric-crates = Crates Rust +stratumiops-metric-tests = Tests +stratumiops-metric-loc = Lineas de Codigo +stratumiops-metric-clippy = Warnings Clippy +stratumiops-metric-unsafe = Bloques Unsafe +stratumiops-metric-docs = Cobertura Doc +stratumiops-metric-crypto = Backends Cripto +stratumiops-metric-storage = Backends Storage +stratumiops-metric-typedialog = Backends TypeDialog +stratumiops-metric-mcp = Herramientas MCP + +# Stack Tecnologico +stratumiops-tech-stack-title = Stack Tecnologico + +# CTA +stratumiops-cta-title = Integra gobernanza en tu proyecto +stratumiops-cta-subtitle = 6 Proyectos | 9 Crates Stratum | 52+ Crates | 74+ Tests | 100% Rust +stratumiops-cta-explore = Explorar Ecosistema diff --git a/site/site/i18n/locales/es/pages/syntaxis.ftl b/site/site/i18n/locales/es/pages/syntaxis.ftl new file mode 100644 index 0000000..2570cce --- /dev/null +++ b/site/site/i18n/locales/es/pages/syntaxis.ftl @@ -0,0 +1,80 @@ +# Syntaxis page — Plataforma de Orquestacion de Proyectos + +# Hero +syntaxis-badge = Beta Produccion | 632+ tests | 8 crates +syntaxis-tagline = Orquestacion Sistematica, Perfectamente Dispuesta +syntaxis-page-title = SYNTAXIS +syntaxis-page-subtitle = Plataforma de orquestacion de proyectos de grado produccion construida en Rust. Cuatro interfaces, soporte dual de base de datos, preparada para VAPORA. Cada tarea tiene su lugar perfecto en la sintaxis de tu flujo de trabajo. + +# Interfaces +syntaxis-interfaces-title = Cuatro Interfaces, Un Nucleo +syntaxis-interfaces-subtitle = Elige la interfaz que se adapte a tu flujo. Todas comparten el mismo motor central. +syntaxis-iface-cli-title = Linea de Comandos +syntaxis-iface-cli-label = syntaxis-cli (clap) +syntaxis-iface-cli-desc = Subcomandos estructurados para automatizacion, pipelines CI/CD y scripting. Auto-descubrimiento de configuracion con wrappers NuShell. +syntaxis-iface-tui-title = UI Terminal +syntaxis-iface-tui-label = syntaxis-tui (ratatui) +syntaxis-iface-tui-desc = Interfaz terminal interactiva con navegacion vim (hjkl). Compatible con SSH, funciona en cualquier maquina remota. +syntaxis-iface-dashboard-title = Dashboard +syntaxis-iface-dashboard-label = Leptos WASM (CSR) +syntaxis-iface-dashboard-desc = Dashboard web moderno compilado a WebAssembly. Actualizaciones en tiempo real, diseno responsivo, servido por la API. +syntaxis-iface-api-title = REST API +syntaxis-iface-api-label = syntaxis-api (axum) +syntaxis-iface-api-desc = API REST completa con middleware Tower, soporte WebSocket, autenticacion, limitacion de tasa y metricas. + +# Quality stats +syntaxis-quality-title = Calidad de Codigo +syntaxis-quality-tests = Tests pasando +syntaxis-quality-lines = Lineas de Rust +syntaxis-quality-unsafe = Bloques unsafe +syntaxis-quality-unwrap = Llamadas unwrap() +syntaxis-quality-docs = APIs publicas documentadas +syntaxis-quality-crates = Crates del workspace + +# Features +syntaxis-features-title = Funcionalidades Principales +syntaxis-features-subtitle = Construido para produccion desde el dia uno. Sin atajos, sin deuda tecnica. +syntaxis-feat-lifecycle-title = Ciclo de Fases +syntaxis-feat-lifecycle-desc = Crear, Desarrollar, Publicar, Archivar. Maquina de estados completa con trail de auditoria en cada transicion. +syntaxis-feat-audit-title = Trail de Auditoria +syntaxis-feat-audit-desc = Historial completo de cambios en cada entidad. Soporte de rollback de estado para cumplimiento empresarial. +syntaxis-feat-dualdb-title = Base de Datos Dual +syntaxis-feat-dualdb-desc = SQLite para local, SurrealDB 2.3 para equipos. Cambia via configuracion, sin cambios en codigo. +syntaxis-feat-config-title = Dirigido por Config +syntaxis-feat-config-desc = Configuracion TOML + Nickel. Sistema de auto-descubrimiento encuentra configs en jerarquias de proyecto. +syntaxis-feat-templates-title = Plantillas de Proyecto +syntaxis-feat-templates-desc = Estructuras de proyecto reutilizables con fases, checklists y plantillas de tareas preconfiguradas. +syntaxis-feat-vapora-title = VAPORA SST +syntaxis-feat-vapora-desc = Base oficial para VAPORA Software Specification & Tasks. Orquestacion de agentes via streaming de eventos NATS. +syntaxis-feat-checklists-title = Checklists de Fase +syntaxis-feat-checklists-desc = Checklists de verificacion atados a transiciones de fase. Control de calidad antes de avanzar. +syntaxis-feat-bootstrap-title = Instalacion Bootstrap +syntaxis-feat-bootstrap-desc = Instalacion de un comando en 50+ arquitecturas. Rust pre-compilado, todo lo demas desde fuente. + +# Database +syntaxis-db-title = Persistencia Flexible +syntaxis-db-subtitle = Un trait, dos backends. Cambia via configuracion en tiempo de ejecucion. +syntaxis-db-sqlite-title = SQLite (Default) +syntaxis-db-sqlite-1 = Cero dependencias externas +syntaxis-db-sqlite-2 = Conexiones async con pool via sqlx +syntaxis-db-sqlite-3 = Modo WAL para lecturas concurrentes +syntaxis-db-sqlite-4 = Sentencias preparadas y operaciones batch +syntaxis-db-sqlite-5 = Devs individuales, equipos pequenos, local +syntaxis-db-surreal-title = SurrealDB 2.3 +syntaxis-db-surreal-1 = 40+ operaciones completamente implementadas +syntaxis-db-surreal-2 = En-Memoria, Archivo, Servidor, Docker, K8s +syntaxis-db-surreal-3 = Async type-safe con JSON binding +syntaxis-db-surreal-4 = Dependencia opcional con feature gates +syntaxis-db-surreal-5 = Equipos, sistemas distribuidos, enterprise + +# Architecture diagram +syntaxis-architecture-title = Arquitectura del Sistema + +# Tech stack +syntaxis-tech-stack-title = Stack Tecnologico + +# CTA +syntaxis-cta-title = Listo para la orquestacion estructurada de proyectos? +syntaxis-cta-subtitle = Construido con Rust | Open Source | Parte del ecosistema VAPORA +syntaxis-cta-explore = Explorar Repo Git +syntaxis-cta-architecture = Ver Arquitectura diff --git a/site/site/i18n/locales/es/pages/typedialog.ftl b/site/site/i18n/locales/es/pages/typedialog.ftl new file mode 100644 index 0000000..57138bd --- /dev/null +++ b/site/site/i18n/locales/es/pages/typedialog.ftl @@ -0,0 +1,53 @@ +# TypeDialog page — Dialogos Interactivos con Type-Safety + +# Hero +typedialog-badge = 3.818 Tests | 6 Backends | 4 Proveedores LLM +typedialog-tagline = Dialogos tipados para inputs, formularios y schemas en los que puedes confiar +typedialog-page-title = Crea Dialogos Interactivos con Type-Safety +typedialog-page-subtitle = Formularios declarativos con definiciones Nickel/TOML, 6 backends (CLI, TUI, Web, AI, Agent, Prov-Gen) y validacion type-safe. Desde prompts interactivos hasta generacion de infraestructura. Un schema. Toda superficie. + +# Problems +typedialog-problems-title = Lo que TypeDialog Resuelve +typedialog-problem-1-title = Codigo Por-Backend +typedialog-problem-1-desc = Una definicion Nickel o TOML controla CLI, TUI, Web y AI. Sin codigo de formularios por backend. El schema es la fuente unica de verdad. +typedialog-problem-2-title = Validacion Fail-Open +typedialog-problem-2-desc = Los contratos Nickel validan cada predicado en tiempo de carga. Predicados desconocidos causan fallos duros, no passes silenciosos. Sin reimplementacion paralela en Rust. +typedialog-problem-3-title = I/O Oculto en Ejecucion +typedialog-problem-3-desc = La carga de fragmentos ocurre una vez en load_form(). El executor de tres fases es puro — sin acceso al filesystem, sin efectos secundarios durante la interaccion. +typedialog-problem-4-title = Ensamblaje Manual de IaC +typedialog-problem-4-desc = Formularios interactivos generan configuraciones de infraestructura validadas para 6 proveedores cloud. Pipeline de validacion de 7 capas desde formularios hasta JSON final. + +# How It Works +typedialog-how-title = Como Funciona +typedialog-how-forms-title = Formularios Declarativos +typedialog-how-forms-desc = Define formularios en Nickel (.ncl) o TOML (.toml). Nickel provee contratos, imports y composicion type-safe. TOML provee simplicidad sin dependencias. Ambos producen structs FormDefinition identicos. +typedialog-how-execution-title = Ejecucion en Tres Fases +typedialog-how-execution-desc = Fase 1: Ejecutar campos selector que controlan condicionales. Fase 2: Construir lista de elementos (puro, sin I/O). Fase 3: Despachar al backend con evaluacion when/when_false. Modos de renderizado completo o campo a campo. +typedialog-how-factory-title = BackendFactory +typedialog-how-factory-desc = Feature gates en tiempo de compilacion eliminan codigo muerto. Match runtime de BackendType despacha al backend seleccionado. Auto-deteccion via env TYPEDIALOG_BACKEND con fallback CLI. +typedialog-how-ai-title = Backends AI y Agent +typedialog-how-ai-desc = Backend AI con RAG, embeddings y busqueda semantica. Backend Agent ejecuta archivos .agent.mdx con soporte multi-LLM (Claude, OpenAI, Gemini, Ollama). Variables de template, imports de archivos, output en streaming. +typedialog-how-infra-title = Generacion de Infraestructura +typedialog-how-infra-desc = Prov-Gen transforma respuestas de formularios en configuraciones IaC. 6 proveedores cloud (AWS, GCP, Azure, Hetzner, UpCloud, LXD). Pipeline de validacion de 7 capas desde formularios hasta JSON final. +typedialog-how-roundtrip-title = Roundtrip Nickel +typedialog-how-roundtrip-desc = Lee schemas .ncl, recolecta input del usuario via cualquier backend, genera output .ncl validado preservando contratos. ContractParser extrae validadores. TemplateRenderer preserva formato. + +# Backends +typedialog-backends-title = Backends +typedialog-backend-cli-role = inquire — prompts interactivos +typedialog-backend-tui-role = ratatui — interfaz de terminal +typedialog-backend-web-role = axum — formularios HTTP +typedialog-backend-ai-role = RAG y embeddings +typedialog-backend-agent-role = Ejecucion multi-LLM +typedialog-backend-provgen-role = Generacion IaC + +# Tech stack +typedialog-tech-stack-title = Stack Tecnologico + +# Architecture +typedialog-architecture-title = Arquitectura + +# CTA +typedialog-cta-title = Dialogos type-safe para toda superficie +typedialog-cta-subtitle = Construido con Rust | Open Source | Licencia MIT +typedialog-cta-explore = Explorar Repo Git diff --git a/site/site/i18n/locales/es/pages/user.ftl b/site/site/i18n/locales/es/pages/user.ftl new file mode 100644 index 0000000..e920aa9 --- /dev/null +++ b/site/site/i18n/locales/es/pages/user.ftl @@ -0,0 +1,115 @@ +# Traducciones específicas de la página de usuario - Español + +# Metadatos de Página +user-page-title = Perfil de Usuario +user-page-description = Ver información del perfil de usuario +user-page-keywords = usuario, perfil + +# Panel de Usuario +user-dashboard = Panel de Usuario +user-welcome = Bienvenido a tu panel de usuario +user-profile = Perfil +user-account = Cuenta +user-settings = Configuración + +# Pestañas del Panel +user-tab-profile = Perfil +user-tab-bookmarks = Marcadores +user-tab-messages = Mensajes +user-tab-notes = Notas +user-tab-resources = Recursos + +# Tarjetas del Panel +user-profile-description = Gestiona la información de tu perfil y configuraciones. +user-settings-description = Configura las preferencias de tu cuenta y ajustes de seguridad. +user-edit-profile = Editar Perfil + +# Sección de Perfil +user-profile-info = Información del Perfil +user-name = Nombre +user-email = Correo Electrónico +user-phone = Teléfono +user-company = Empresa +user-role = Rol +user-location = Ubicación +user-bio = Biografía + +# Gestión de Cuenta +user-account-settings = Configuración de Cuenta +user-change-password = Cambiar Contraseña +user-update-profile = Actualizar Perfil +user-delete-account = Eliminar Cuenta +user-account-security = Seguridad de la Cuenta +user-two-factor-auth = Autenticación de Dos Factores +user-login-history = Historial de Acceso + +# Gestión de Proyectos +user-projects = Mis Proyectos +user-active-projects = Proyectos Activos +user-completed-projects = Proyectos Completados +user-project-requests = Solicitudes de Proyecto +user-new-project = Nueva Solicitud de Proyecto +user-view-project = Ver Proyecto +user-project-status = Estado del Proyecto + +# Notificaciones +user-notifications = Notificaciones +user-unread-messages = Mensajes No Leídos +user-system-alerts = Alertas del Sistema +user-email-preferences = Preferencias de Correo +user-notification-settings = Configuración de Notificaciones + +# Servicios / Marcadores +user-services = Mis Servicios +user-services-empty = Aún no hay elementos guardados +user-bookmarks-empty = No tienes marcadores aún + +# Mensajes +user-messages-empty = No tienes mensajes +user-messages-subject = Asunto +user-messages-from = De +user-messages-unread = sin leer +user-messages-mark-read = Marcar como leído + +# Notas +user-notes-empty = No tienes notas aún +user-notes-create = Nueva nota +user-notes-title-ph = Título... +user-notes-body-ph = Escribe tu nota... +user-notes-save = Guardar +user-notes-delete = Eliminar +user-notes-saved = Nota guardada + +# Recursos +user-resources-empty = No hay recursos disponibles +user-resources-open = Abrir + +# Zona de peligro +user-danger-zone = Zona de peligro +user-service-history = Historial de Servicios +user-invoices = Facturas +user-billing = Facturación +user-payment-methods = Métodos de Pago + +# Soporte +user-support = Soporte +user-help-center = Centro de Ayuda +user-contact-support = Contactar Soporte +user-faq = Preguntas Frecuentes +user-documentation = Documentación + +# Acciones +user-save-changes = Guardar Cambios +user-cancel = Cancelar +user-edit = Editar +user-delete = Eliminar +user-download = Descargar +user-upload = Subir +user-logout = Cerrar Sesión + +# Mensajes de Estado +user-profile-updated = Perfil actualizado exitosamente +user-changes-saved = Cambios guardados +user-error-occurred = Ocurrió un error +user-please-try-again = Por favor intenta de nuevo +user-loading = Cargando... diff --git a/site/site/i18n/locales/es/pages/vapora.ftl b/site/site/i18n/locales/es/pages/vapora.ftl new file mode 100644 index 0000000..72e5a3c --- /dev/null +++ b/site/site/i18n/locales/es/pages/vapora.ftl @@ -0,0 +1,68 @@ +# Vapora page — Plataforma de Orquestación Inteligente de Desarrollo + +# Hero +vapora-badge = ✅ v1.2.0 | 620 Tests | 100% Éxito +vapora-tagline = Evapora la complejidad +vapora-page-title = El Desarrollo Fluye Cuando los Equipos y la IA Orquestan +vapora-page-subtitle = Agentes especializados que orquestan pipelines para diseño, implementación, testing, documentación y deployment. Los agentes aprenden del historial y optimizan costos automáticamente. 100% self-hosted en Kubernetes — sin SaaS, control total. + +# Architecture diagram +vapora-architecture-title = Visión General de la Arquitectura + +# Problems section +vapora-problems-title = Los 4 Problemas que Resuelve +vapora-problem-1-title = Cambio de Contexto +vapora-problem-1-desc = Los developers saltan constantemente entre herramientas. Vapora unifica todo en un sistema inteligente donde el contexto fluye. +vapora-problem-2-title = Fragmentación de Conocimiento +vapora-problem-2-desc = Decisiones perdidas en threads, código disperso, docs desactualizadas. RLM con búsqueda híbrida (BM25 + semántica) y chunking hace el conocimiento visible incluso en documentos de 100k+ tokens. +vapora-problem-3-title = Coordinación Manual +vapora-problem-3-desc = Orquestar manualmente code review, testing, documentación y deployment crea cuellos de botella. Los workflows multi-agente lo resuelven. +vapora-problem-4-title = Fricción Dev-Ops +vapora-problem-4-desc = Los handoffs entre developers y operaciones carecen de visibilidad y contexto. Vapora mantiene unificada la deployment readiness. + +# Features section +vapora-features-title = Cómo Funciona +vapora-feat-agents-title = Agentes Especializados +vapora-feat-agents-desc = 71 tests verifican orquestación de agentes, perfiles de aprendizaje y asignación de tareas. Los agentes rastrean expertise por tipo de tarea con sesgo de recencia de 7 días (peso 3×). Persistencia real SurrealDB + coordinación NATS. +vapora-feat-orchestration-title = Orquestación Inteligente +vapora-feat-orchestration-desc = 53 tests verifican routing multi-proveedor (Claude, OpenAI, Gemini, Ollama), límites de presupuesto por rol, tracking de costos y cadenas automáticas de fallback. Coordinación swarm con asignación balanceada. +vapora-feat-rlm-title = Recursive Language Models (RLM) +vapora-feat-rlm-desc = Procesa documentos de 100k+ tokens sin límites de contexto. Búsqueda híbrida combina BM25 (keywords) + embeddings semánticos via fusión RRF. Chunking inteligente (Fixed/Semantic/Code) con persistencia SurrealDB. +vapora-feat-a2a-title = Protocolo Agent-to-Agent (A2A) +vapora-feat-a2a-desc = Coordinación distribuida de agentes con despacho de tareas, seguimiento de estado y recolección de resultados. Mensajería NATS para completado asíncrono. Reintento con backoff exponencial y circuit breaker. 12 tests de integración. +vapora-feat-knowledge-title = Knowledge Graph +vapora-feat-knowledge-desc = Historial de ejecución temporal con relaciones causales. Curvas de aprendizaje desde agregaciones diarias con ventana. Búsqueda de similitud recomienda soluciones de tareas pasadas. 20 tests verifican persistencia de grafo y perfiles de aprendizaje. +vapora-feat-nats-title = NATS JetStream +vapora-feat-nats-desc = Entrega confiable de mensajes para coordinación de agentes. Streams JetStream para eventos de workflow, completado de tareas y actualizaciones de estado. Fallback graceful cuando NATS no disponible. +vapora-feat-surrealdb-title = SurrealDB +vapora-feat-surrealdb-desc = Base de datos multi-modelo con capacidades de grafo. Scopes multi-tenant para aislamiento de workspace. Relaciones de grafo nativas para Knowledge Graph. Todas las queries usan bindings parametrizados. Tablas SCHEMAFULL con índices explícitos. +vapora-feat-api-title = Backend API y Conectores MCP +vapora-feat-api-desc = 40+ endpoints REST (proyectos, tareas, agentes, workflows, swarm). Actualizaciones en tiempo real vía WebSocket. Gateway MCP para integración de herramientas externas. Métricas Prometheus. 161 tests verifican corrección de API. +vapora-feat-webhooks-title = Notificaciones Webhook +vapora-feat-webhooks-desc = Alertas en tiempo real a Slack, Discord y Telegram — sin SDKs de vendor. Resolución de secretos ${VAR} integrada en ChannelRegistry. Hooks fire-and-forget en completado de tareas, aprobación de propuestas y eventos del ciclo de vida de workflows. +vapora-feat-capabilities-title = Paquetes de Capacidades +vapora-feat-capabilities-desc = Bundles de agentes optimizados por dominio — system prompt, modelo LLM preferido, tipos de tarea y herramientas MCP preconfigurados por rol. Tres built-ins (code-reviewer, doc-generator, pr-monitor) en startup. Overrides TOML para cambiar modelo o prompt sin código. 22 tests. + +# Tech stack +vapora-tech-stack-title = Stack Tecnológico + +# Agents +vapora-agents-title = Agentes Disponibles +vapora-agent-architect-role = Diseño de sistemas +vapora-agent-developer-role = Implementación de código +vapora-agent-reviewer-role = Aseguramiento de calidad +vapora-agent-tester-role = Tests y benchmarks +vapora-agent-documenter-role = Documentación +vapora-agent-marketer-role = Contenido marketing +vapora-agent-presenter-role = Presentaciones +vapora-agent-devops-role = Despliegue CI/CD +vapora-agent-monitor-role = Salud y alerting +vapora-agent-security-role = Auditoría y compliance +vapora-agent-pm-role = Tracking de roadmap +vapora-agent-decisionmaker-role = Resolución de conflictos + +# CTA +vapora-cta-title = ¿Listo para la orquestación inteligente? +vapora-cta-subtitle = Construido con Rust 🦀 | Open Source | Self-Hosted +vapora-cta-explore = Explorar Repo Git +vapora-cta-visit = Visitar vapora.dev diff --git a/site/site/i18n/locales/es/pages/work_request.ftl b/site/site/i18n/locales/es/pages/work_request.ftl new file mode 100644 index 0000000..4805033 --- /dev/null +++ b/site/site/i18n/locales/es/pages/work_request.ftl @@ -0,0 +1,181 @@ +# Traducciones específicas de la página de solicitud de trabajo - Español + +# Metadatos de Página +work-request-page-title = Solicitud de Trabajo +work-request-page-description = Envía una solicitud de trabajo para tu proyecto +work-request-page-keywords = trabajo, solicitud, consulta, proyecto + +# Encabezado de la Página +work-request-title = Solicitar Cotización de Proyecto +work-request-subtitle = Hablemos sobre los requisitos de tu proyecto y cómo puedo ayudarte a alcanzar tus objetivos. +work-request-description = Completa el formulario a continuación con los detalles de tu proyecto, y te responderé en 24 horas con una propuesta personalizada. + +# Sección de Información del Proyecto +work-request-project-info = Información del Proyecto +work-request-project-title = Título del Proyecto +work-request-project-title-placeholder = Título breve para tu proyecto +work-request-project-description = Descripción del Proyecto +work-request-project-description-placeholder = Describe tu proyecto, objetivos y requisitos en detalle... +work-request-project-type = Tipo de Proyecto +work-request-timeline = Cronograma +work-request-budget = Rango de Presupuesto +work-request-priority = Nivel de Prioridad + +# Tipos de Servicio +work-request-service-types = Tipos de Servicio (selecciona todos los que apliquen) +work-request-rust-development = Desarrollo en Rust +work-request-web-applications = Aplicaciones Web +work-request-infrastructure = Infraestructura y DevOps +work-request-web3-polkadot = Web3 y Polkadot +work-request-consulting = Consultoría Técnica +work-request-code-review = Revisión y Auditoría de Código +work-request-training = Entrenamiento y Mentoría +work-request-custom-solution = Solución Personalizada + +# Opciones de Cronograma +work-request-timeline-asap = Lo antes posible (trabajo urgente) +work-request-timeline-1-month = Dentro de 1 mes +work-request-timeline-3-months = 1-3 meses +work-request-timeline-6-months = 3-6 meses +work-request-timeline-flexible = Cronograma flexible + +# Opciones de Presupuesto +work-request-budget-under-5k = Menos de $5,000 +work-request-budget-5k-15k = $5,000 - $15,000 +work-request-budget-15k-50k = $15,000 - $50,000 +work-request-budget-50k-plus = $50,000+ +work-request-budget-discuss = Hablemos + +# Niveles de Prioridad +work-request-priority-low = Baja +work-request-priority-medium = Media +work-request-priority-high = Alta +work-request-priority-urgent = Urgente + +# Información de Contacto +work-request-contact-info = Información de Contacto +work-request-your-name = Tu Nombre +work-request-your-name-placeholder = Nombre completo +work-request-your-email = Dirección de Correo Electrónico +work-request-your-email-placeholder = tu.correo@empresa.com +work-request-company = Empresa/Organización +work-request-company-placeholder = Nombre de la empresa (opcional) +work-request-phone = Número de Teléfono +work-request-phone-placeholder = +1 (555) 123-4567 (opcional) +work-request-preferred-contact = Método de Contacto Preferido +work-request-contact-email = Correo Electrónico +work-request-contact-phone = Teléfono +work-request-contact-telegram = Telegram + +# Detalles Técnicos +work-request-technical-details = Detalles Técnicos (Opcional) +work-request-current-stack = Stack Tecnológico Actual +work-request-current-stack-placeholder = ¿Qué tecnologías estás usando actualmente? +work-request-specific-requirements = Requisitos Específicos +work-request-specific-requirements-placeholder = Cualquier requisito técnico específico, restricciones o preferencias... +work-request-existing-codebase = Base de Código Existente +work-request-codebase-new = Proyecto nuevo desde cero +work-request-codebase-existing = Trabajando con código existente +work-request-codebase-migration = Proyecto de migración/modernización + +# Información Adicional +work-request-additional-info = Información Adicional +work-request-how-did-you-find = ¿Cómo me encontraste? +work-request-referral-source = Fuente de Referencia +work-request-additional-notes = Notas Adicionales +work-request-additional-notes-placeholder = ¿Algo más que te gustaría que sepa sobre tu proyecto? + +# Acciones del Formulario +work-request-submit = Enviar Solicitud +work-request-submitting = Enviando... +work-request-cancel = Cancelar +work-request-reset-form = Reiniciar Formulario + +# Campos Requeridos +work-request-required = Requerido +work-request-required-fields = Los campos marcados con * son requeridos + +# Mensajes de Validación +work-request-name-required = El nombre es requerido +work-request-email-required = La dirección de correo electrónico es requerida +work-request-email-invalid = Por favor ingresa una dirección de correo válida +work-request-project-title-required = El título del proyecto es requerido +work-request-project-description-required = La descripción del proyecto es requerida +work-request-service-type-required = Por favor selecciona al menos un tipo de servicio + +# Mensajes de Éxito/Error +work-request-success-title = ¡Solicitud Enviada Exitosamente! +work-request-success-message = Gracias por tu solicitud de proyecto. Revisaré tus requisitos y te responderé en 24 horas con una propuesta detallada. +work-request-success-next-steps = ¿Qué sigue? +work-request-success-step-1 = Revisaré los requisitos de tu proyecto +work-request-success-step-2 = Prepararé una propuesta detallada y cronograma +work-request-success-step-3 = Programaremos una llamada para discutir detalles +work-request-error-title = Envío Fallido +work-request-error-message = Hubo un error al enviar tu solicitud. Por favor intenta de nuevo o contáctame directamente. +work-request-try-again = Intentar de Nuevo +work-request-contact-direct = Contáctame Directamente + +# Términos y Condiciones +work-request-terms = Términos y Condiciones +work-request-terms-text = Al enviar esta solicitud, aceptas nuestros términos de servicio y política de privacidad. +work-request-terms-checkbox = Acepto los términos y condiciones +work-request-privacy-notice = Tu información se mantendrá confidencial y se usará únicamente para responder a tu solicitud. + +# Llamada a la Acción +work-request-ready-to-start = ¿Listo para Comenzar tu Proyecto? +work-request-cta-description = Me especializo en desarrollo Rust, automatización de infraestructura y soluciones Web3. Construyamos algo increíble juntos. +work-request-alternative-contact = ¿Prefieres hablar primero? Programa una consulta gratuita. +work-request-schedule-call = Programar Llamada + +work-request-form-action = mailto:hello@ontoref.dev + +# Claves faltantes necesarias por el componente +work-request-project-type-label = Tipo de Proyecto +work-request-project-type-placeholder = Seleccionar tipo de proyecto +work-request-project-type-new = Proyecto Nuevo +work-request-project-type-existing = Mejora de Proyecto Existente +work-request-project-type-infrastructure = Infraestructura y DevOps +work-request-project-type-consulting = Consultoría Técnica +work-request-project-type-mentoring = Mentoría y Entrenamiento +work-request-project-type-training = Programas de Entrenamiento +work-request-project-type-audit = Revisión y Auditoría de Código +work-request-project-type-other = Otro + +work-request-project-description-label = Descripción del Proyecto + +work-request-current-situation-label = Situación Actual +work-request-current-situation-placeholder = ¿Cuál es tu situación técnica actual o desafío? + +work-request-goals-label = Objetivos del Proyecto +work-request-goals-placeholder = ¿Qué quieres lograr con este proyecto? + +work-request-technical-requirements-label = Requisitos Técnicos +work-request-technical-requirements-placeholder = Cualquier requisito técnico específico, restricciones o preferencias... + +work-request-timeline-budget = Cronograma y Presupuesto +work-request-timeline-label = Cronograma +work-request-timeline-placeholder = Seleccionar cronograma +work-request-timeline-1-3-months = 1-3 meses +work-request-timeline-3-6-months = 3-6 meses +work-request-timeline-6plus = 6+ meses + +work-request-communication-preferences = Preferencias de Comunicación +work-request-contact-method-label = Método de Contacto Preferido +work-request-contact-method-placeholder = Seleccionar método de contacto +work-request-contact-method-email = Correo Electrónico +work-request-contact-method-telegram = Telegram +work-request-contact-method-video = Videollamada +work-request-contact-method-phone = Llamada Telefónica + +work-request-submit-btn = Enviar Solicitud de Proyecto +work-request-submit-note = Te responderé en 24 horas con una propuesta detallada. + +work-request-what-happens-next = ¿Qué Pasa Después? +work-request-step-1-title = Revisión y Análisis +work-request-step-1-description = Revisaré cuidadosamente los requisitos de tu proyecto y necesidades técnicas. +work-request-step-2-title = Propuesta Detallada +work-request-step-2-description = Recibirás una propuesta completa con cronograma y precios. +work-request-step-3-title = Llamada de Planificación +work-request-step-3-description = Programaremos una llamada para discutir detalles y próximos pasos. + +work-request-submit-success = ¡Tu solicitud ha sido enviada\! Me pondré en contacto contigo en 24 horas. diff --git a/site/site/info/automation_guide.md b/site/site/info/automation_guide.md new file mode 100644 index 0000000..93ccb82 --- /dev/null +++ b/site/site/info/automation_guide.md @@ -0,0 +1,60 @@ +# Route Documentation Automation Guide + +Generated at: 2026-06-03T02:41:52.959774+00:00 + +## Overview + +This documentation is automatically generated during the build process from the actual route definitions and component code. The generated TOML files can be consumed by external tools for automation purposes. + +## Generated Files + +- `server_routes.toml` - API endpoint definitions in TOML format +- `client_routes.toml` - Page route definitions in TOML format +- `components.toml` - Component documentation in TOML format +- `*.md` files - Human-readable documentation + +## TOML Schema + +### Server Routes + +```toml +[[api_routes]] +path = "/api/example" +methods = ["GET", "POST"] +handler = "handlers::example_handler" +module = "crates/server/src/handlers/mod.rs" +response_type = "Json<ExampleResponse>" +requires_auth = false +middleware = ["cors"] +description = "Example API endpoint" +``` + +### Client Routes + +```toml +[[page_routes]] +path = "/example" +component = "Example" +page_component = "ExamplePage" +unified_component = "UnifiedExamplePage" +language = "en" +enabled = true +priority = 0.8 +``` + +## Usage in External Tools + +These TOML files can be consumed by: +- API client generators +- Testing frameworks +- Documentation generators +- CI/CD pipelines +- Monitoring and alerting systems + +## Configuration + +The documentation generation is controlled by: +- `SITE_CONFIG_PATH` environment variable (default: `site/config`) +- Build system integration in `crates/*/build.rs` +- Route configuration files in `site/config/routes/` + diff --git a/site/site/info/data.json b/site/site/info/data.json new file mode 100644 index 0000000..bcee686 --- /dev/null +++ b/site/site/info/data.json @@ -0,0 +1,6 @@ +{ + "api_routes": [], + "components": [], + "generated_at": "2026-06-03T00:34:31.055736+00:00", + "page_routes": [] +} \ No newline at end of file diff --git a/site/site/info/data.toml b/site/site/info/data.toml new file mode 100644 index 0000000..7fb0198 --- /dev/null +++ b/site/site/info/data.toml @@ -0,0 +1,6 @@ +# Route metadata generated at build time + +generated_at = "2026-06-03T00:34:31.055736+00:00" +components = [] + +[[page_routes]] diff --git a/site/site/info/pages_analysis.md b/site/site/info/pages_analysis.md new file mode 100644 index 0000000..fbeaa50 --- /dev/null +++ b/site/site/info/pages_analysis.md @@ -0,0 +1,3 @@ +# Pages Analysis + +Generated pages documentation will be placed here. diff --git a/site/site/info/server_analysis.md b/site/site/info/server_analysis.md new file mode 100644 index 0000000..71ed780 --- /dev/null +++ b/site/site/info/server_analysis.md @@ -0,0 +1,3 @@ +# Server Analysis + +Generated server documentation will be placed here. diff --git a/site/site/models/all_allow_rbac.ncl b/site/site/models/all_allow_rbac.ncl new file mode 100644 index 0000000..92c3956 --- /dev/null +++ b/site/site/models/all_allow_rbac.ncl @@ -0,0 +1,140 @@ +# RBAC Policy Configuration +# +# Defines access rules for the website. Identity (who the user is) +# comes from JWT claims or session; this file defines what each group +# is allowed to access. +# +# Group names must match the role strings derived from JWT: +# Role::Admin → "admin" +# Role::Moderator → "moderator" +# Role::User → "user" +# Role::Guest → "guest" +# Role::Custom(s) → s +# +# Validate: nickel export rbac.ncl + +let C = import "./nickel/rbac/contracts.ncl" in + +# Reusable effect sets — compose with array concatenation (@) +let E = { + redirect_login = [{ type = "redirect", to = "/login?return={request.path}" }], + audit_deny = [{ type = "audit_log", event = "access_denied", level = "warn" }], + audit_allow = [{ type = "audit_log", event = "access_granted", level = "info" }], + api_forbidden = [{ type = "response", status = 403, body.error = "Forbidden" }], + log_warn = [{ type = "log", level = "warn", message = "Denied: {request.path}" }], + security_alert = [{ + type = "notify", + channel = "security", + message = "Admin access: {request.path} by {user.email}", + }], +} in + +{ + groups = [ + { + name = "admin", + parent_groups = [], + permissions = [ + { + resource = "endpoint", + pattern = "/api/*", + outcome = "allow", + methods = [], + effects = E.audit_allow, + }, + { + resource = "page", + pattern = "/*", + outcome = "allow", + methods = [], + effects = [], + }, + ], + }, + { + name = "moderator", + parent_groups = ["user"], + permissions = [ + { + resource = "endpoint", + pattern = "/api/content/*", + outcome = "allow", + methods = ["GET", "POST", "PUT"], + effects = E.audit_allow, + }, + { + resource = "endpoint", + pattern = "/api/admin/*", + outcome = "deny", + methods = [], + effects = E.audit_deny @ E.api_forbidden, + }, + { + resource = "page", + pattern = "/admin/content/*", + outcome = "allow", + methods = [], + effects = [], + }, + ], + }, + { + name = "user", + parent_groups = [], + permissions = [ + { + resource = "page", + pattern = "/*", + outcome = "allow", + methods = [], + effects = [], + }, + { + resource = "endpoint", + pattern = "/api/*", + outcome = "deny", + methods = [], + effects = E.audit_deny @ E.api_forbidden, + }, + ], + }, + ], + + user_overrides = [ + # Example: block a specific user and redirect to login + # { + # id = "blocked@ontoref.dev", + # permissions = [{ + # resource = "page", + # pattern = "/*", + # outcome = "deny", + # methods = [], + # effects = E.audit_deny @ E.security_alert @ E.redirect_login, + # }], + # }, + ], + + defaults = { + unauthenticated_allow = [ + # Protected paths evaluated first — first match wins + { resource = "page", pattern = "/admin/*", outcome = "deny", methods = [], effects = E.audit_deny @ E.redirect_login }, + # Public pages + { resource = "page", pattern = "/login", outcome = "allow", methods = [], effects = [] }, + { resource = "page", pattern = "/register", outcome = "allow", methods = [], effects = [] }, + { resource = "page", pattern = "/*", outcome = "allow", methods = [], effects = [] }, + # Public endpoints and assets + { resource = "endpoint", pattern = "/api/health", outcome = "allow", methods = [], effects = [] }, + { resource = "endpoint", pattern = "/api/ws", outcome = "allow", methods = ["GET"], effects = [] }, + { resource = "endpoint", pattern = "/api/auth/*", outcome = "allow", methods = [], effects = [] }, + { resource = "endpoint", pattern = "/api/server_request_otp*", outcome = "allow", methods = ["POST"], effects = [] }, + { resource = "endpoint", pattern = "/api/server_verify_otp*", outcome = "allow", methods = ["POST"], effects = [] }, + { resource = "endpoint", pattern = "/api/content-render/*", outcome = "allow", methods = ["GET"], effects = [] }, + { resource = "endpoint", pattern = "/api/content-index/*", outcome = "allow", methods = ["GET"], effects = [] }, + { resource = "asset", pattern = "/*", outcome = "allow", methods = [], effects = [] }, + ], + unauthenticated_deny = [], + authenticated_fallback = "deny", + unauthenticated_fallback = "deny", + fallback_effects = E.log_warn, + }, +} | C.FileRbacConfig diff --git a/site/site/models/no_recipes_rbac.ncl b/site/site/models/no_recipes_rbac.ncl new file mode 100644 index 0000000..bc699aa --- /dev/null +++ b/site/site/models/no_recipes_rbac.ncl @@ -0,0 +1,165 @@ +# RBAC Policy Configuration +# +# Defines access rules for the website. Identity (who the user is) +# comes from JWT claims or session; this file defines what each group +# is allowed to access. +# +# Group names must match the role strings derived from JWT: +# Role::Admin → "admin" +# Role::Moderator → "moderator" +# Role::User → "user" +# Role::Guest → "guest" +# Role::Custom(s) → s +# +# Validate: nickel export rbac.ncl + +let C = import "./nickel/rbac/contracts.ncl" in + +# Reusable effect sets — compose with array concatenation (@) +let E = { + redirect_login = [{ type = "redirect", to = "/login?return={request.path}" }], + audit_deny = [{ type = "audit_log", event = "access_denied", level = "warn" }], + audit_allow = [{ type = "audit_log", event = "access_granted", level = "info" }], + api_forbidden = [{ type = "response", status = 403, body.error = "Forbidden" }], + log_warn = [{ type = "log", level = "warn", message = "Denied: {request.path}" }], + security_alert = [{ + type = "notify", + channel = "security", + message = "Admin access: {request.path} by {user.email}", + }], +} in + +{ + groups = [ + { + name = "admin", + parent_groups = [], + permissions = [ + { + resource = "endpoint", + pattern = "/api/*", + outcome = "allow", + methods = [], + effects = E.audit_allow, + }, + { + resource = "page", + pattern = "/*", + outcome = "allow", + methods = [], + effects = [], + }, + ], + }, + { + name = "moderator", + parent_groups = ["user"], + permissions = [ + { + resource = "endpoint", + pattern = "/api/content/*", + outcome = "allow", + methods = ["GET", "POST", "PUT"], + effects = E.audit_allow, + }, + { + resource = "endpoint", + pattern = "/api/admin/*", + outcome = "deny", + methods = [], + effects = E.audit_deny @ E.api_forbidden, + }, + { + resource = "page", + pattern = "/admin/content/*", + outcome = "allow", + methods = [], + effects = [], + }, + ], + }, + { + name = "user", + parent_groups = [], + permissions = [ + { + resource = "page", + pattern = "/*", + outcome = "allow", + methods = [], + effects = [], + }, + # Allow read access to public content endpoints (blog, recipes, etc.) + { + resource = "endpoint", + pattern = "/api/content-render/*", + outcome = "allow", + methods = ["GET"], + effects = [], + }, + { + resource = "endpoint", + pattern = "/api/content-index/*", + outcome = "allow", + methods = ["GET"], + effects = [], + }, + { + resource = "endpoint", + pattern = "/api/*", + outcome = "deny", + methods = [], + effects = E.audit_deny @ E.api_forbidden, + }, + ], + }, + ], + + user_overrides = [ + # Example: block a specific user and redirect to login + # { + # id = "blocked@ontoref.dev", + # permissions = [{ + # resource = "page", + # pattern = "/*", + # outcome = "deny", + # methods = [], + # effects = E.audit_deny @ E.security_alert @ E.redirect_login, + # }], + # }, + ], + + defaults = { + unauthenticated_allow = [ + # Protected paths evaluated first — first match wins + { resource = "page", pattern = "/admin/*", outcome = "deny", methods = [], effects = E.audit_deny @ E.redirect_login }, + # Recipes require authentication (EN + ES routes) + { resource = "page", pattern = "/recipes", outcome = "deny", methods = [], effects = E.audit_deny @ E.redirect_login }, + { resource = "page", pattern = "/recipes/*", outcome = "deny", methods = [], effects = E.audit_deny @ E.redirect_login }, + { resource = "page", pattern = "/recetas", outcome = "deny", methods = [], effects = E.audit_deny @ E.redirect_login }, + { resource = "page", pattern = "/recetas/*", outcome = "deny", methods = [], effects = E.audit_deny @ E.redirect_login }, + # Public pages + { resource = "page", pattern = "/login", outcome = "allow", methods = [], effects = [] }, + { resource = "page", pattern = "/register", outcome = "allow", methods = [], effects = [] }, + { resource = "page", pattern = "/*", outcome = "allow", methods = [], effects = [] }, + # Recipes API endpoints also require authentication + { resource = "endpoint", pattern = "/api/content-render/recipes/*", outcome = "deny", methods = [], effects = E.audit_deny @ E.api_forbidden }, + { resource = "endpoint", pattern = "/api/content-index/recipes/*", outcome = "deny", methods = [], effects = E.audit_deny @ E.api_forbidden }, + # Public endpoints and assets + { resource = "endpoint", pattern = "/api/health", outcome = "allow", methods = [], effects = [] }, + { resource = "endpoint", pattern = "/api/auth/*", outcome = "allow", methods = [], effects = [] }, + { resource = "endpoint", pattern = "/api/ws", outcome = "allow", methods = ["GET"], effects = [] }, + # Leptos server functions for OTP auth flow — hash suffix varies per build, + # glob * matches the numeric hash without requiring a path separator. + { resource = "endpoint", pattern = "/api/server_request_otp*", outcome = "allow", methods = ["POST"], effects = [] }, + { resource = "endpoint", pattern = "/api/server_verify_otp*", outcome = "allow", methods = ["POST"], effects = [] }, + { resource = "endpoint", pattern = "/api/content-render/*", outcome = "allow", methods = ["GET"], effects = [] }, + { resource = "endpoint", pattern = "/api/content-index/*", outcome = "allow", methods = ["GET"], effects = [] }, + { resource = "asset", pattern = "/*", outcome = "allow", methods = [], effects = [] }, + ], + unauthenticated_deny = [], + authenticated_fallback = "deny", + unauthenticated_fallback = "deny", + fallback_effects = E.log_warn, + }, +} | C.FileRbacConfig diff --git a/site/site/models/pre_config.ncl b/site/site/models/pre_config.ncl new file mode 100644 index 0000000..18ac07f --- /dev/null +++ b/site/site/models/pre_config.ncl @@ -0,0 +1,324 @@ +# Unified Site Configuration — Single Source of Truth +# +# Replaces: +# site/config/routes/en.ncl + es.ncl (per-language routes) +# site/ui/menus/menu.ncl (multilingual menus) +# site/content/content-kinds.toml (content type definitions) +# site/ui/footer/footer.ncl (footer metadata) +# +# Consumed by build_config::site_config::load_unified_site_config() in all +# build.rs scripts (shared, client, server). +# +# NICKEL_IMPORT_PATH must point to rustelo/nickel/ (auto-detected by nickel.rs). +# +# Adding a new language: add the lang code to site.languages, add a paths +# entry to every route record, and create the corresponding FTL file. +# No other files need changing. + +let site_d = import "site/defaults.ncl" in +let site_c = import "site/contracts.ncl" in +let { make_route, make_content_route, make_external, priorities, orders, no_menu } + = site_d in + +{ + site = { + name = "Website", + languages = ["en", "es"], + default_language = "en", + }, + + paths = { + i18n = "site/i18n/locales", + content = "site/content", + themes = "site/config/themes", + assets = "site/public", + migrations = "rustelo/migrations", + email_templates = "site/templates/email", + work = "works-pv", + }, + + themes = { + default = "default", + available = ["default", "dark", "corporate"], + }, + + logo = { + light_src = "/logos/site-logo-b.png", + dark_src = "/logos/site-logo-w.png", + alt = "Site logo", + show_in_nav = true, + show_in_footer = true, + width = null, + height = null, + }, + + content_types = [ + { + name = "blog", + directory = "blog", + enabled = true, + features = { + style_mode = "row", + style_css = "blog-content", + use_feature = true, + use_categories = true, + use_tags = true, + use_emojis = true, + cta_view = "BlogCTAView", + default_page_size = 8, + page_size_options = [5, 8, 12, 20], + show_page_info = true, + pagination_style = "numbered", + }, + }, + { + name = "recipes", + directory = "recipes", + enabled = true, + features = { + style_mode = "grid", + style_css = "recipe-content", + use_feature = false, + use_categories = true, + use_tags = true, + use_emojis = true, + cta_view = "RecipesCTAView", + default_page_size = 12, + page_size_options = [6, 12, 18, 24], + show_page_info = true, + pagination_style = "numbered", + }, + }, + ], + + database = { + url = "sqlite:works-pv/data/dev_database.db", + create_if_missing = true, + max_connections = 5, + min_connections = 1, + connect_timeout = 30, + idle_timeout = 600, + max_lifetime = 1800, + }, + + footer = { + title = "Jesús Pérez", + company_desc = { + en = "Rust Developer & Infrastructure Consultant", + es = "Desarrollador Rust y Consultor de Infraestructura", + }, + copyright_text = { + en = "© 2024 Jesús Pérez. All rights reserved.", + es = "© 2024 Jesús Pérez. Todos los derechos reservados.", + }, + social_links = [ + { name = "LinkedIn", url = "https://linkedin.com/in/example-org", icon = "fab fa-linkedin" }, + { name = "GitHub", url = "https://github.com/example-org", icon = "fab fa-github" }, + ], + }, + + routes = [ + # ── Home ──────────────────────────────────────────────────────────────── + make_route "HomePage" { en = "/", es = "/inicio" } + & { + priority = priorities.critical, + menu = { + enabled = true, + order = orders.home, + icon = "home", + label_key = "nav-home", + labels = { en = "Home", es = "Inicio" }, + use_in = ["header"], + }, + }, + + # Alternate Spanish home path (/es) — no menu entry + make_route "HomePage" { es = "/es" } + & { + priority = priorities.critical, + menu = no_menu, + }, + + # ── About ──────────────────────────────────────────────────────────────── + make_route "AboutPage" { en = "/about", es = "/acerca-de" } + & { + priority = priorities.high, + menu = { + enabled = true, + order = orders.about, + icon = "fas fa-user", + label_key = "nav-about", + labels = { en = "About", es = "Acerca de" }, + use_in = ["header"], + }, + }, + + # ── Services ───────────────────────────────────────────────────────────── + make_route "ServicesPage" { en = "/services", es = "/servicios" } + & { + priority = priorities.high, + menu = { + enabled = true, + order = orders.services, + icon = "briefcase", + label_key = "nav-services", + labels = { en = "Services", es = "Servicios" }, + use_in = ["header"], + }, + }, + + # ── Blog article viewer ─────────────────────────────────────────────────── + make_content_route "PostViewer" "blog" { en = "/blog/{category}/{slug}", es = "/blog/{category}/{slug}" } + & { + priority = priorities.minimal, + menu = no_menu, + }, + + # ── Recipes article viewer ──────────────────────────────────────────────── + make_content_route "PostViewer" "recipes" { en = "/recipes/{category}/{slug}", es = "/recetas/{category}/{slug}" } + & { + priority = priorities.minimal, + menu = no_menu, + }, + + # ── Blog category filter ────────────────────────────────────────────────── + make_content_route "ContentIndex" "blog" { en = "/blog/{category}", es = "/blog/{category}" } + & { + priority = priorities.low, + menu = no_menu, + }, + + # ── Recipes category filter ─────────────────────────────────────────────── + make_content_route "ContentIndex" "recipes" { en = "/recipes/{category}", es = "/recetas/{category}" } + & { + priority = priorities.low, + menu = no_menu, + }, + + # ── Blog (content index) ────────────────────────────────────────────────── + make_content_route "ContentIndex" "blog" { en = "/blog", es = "/blog" } + & { + priority = priorities.normal, + menu = { + enabled = true, + order = orders.blog, + icon = "book", + label_key = "nav-blog", + labels = { en = "Blog", es = "Blog" }, + use_in = ["header"], + }, + props = { + content_type = "blog", + cta_view = "BlogCTAView", + style_css = "blog-content", + style_mode = "row", + use_categories = true, + use_emojis = true, + use_feature = true, + use_links = [ + ["archive", "/blog/archive"], + ["rss", "/blog/rss.xml"], + ["categories", "/blog/categories"], + ], + use_tags = true, + }, + }, + + # ── Recipes (content index) ─────────────────────────────────────────────── + make_content_route "ContentIndex" "recipes" { en = "/recipes", es = "/recetas" } + & { + priority = priorities.normal, + menu = { + enabled = true, + order = orders.recipes, + icon = "lightbulb", + label_key = "nav-recipes", + labels = { en = "Recipes", es = "Recetas" }, + use_in = ["header"], + }, + props = { + content_type = "recipes", + cta_view = "RecipesCTAView", + style_css = "recipes-content", + style_mode = "row", + use_categories = true, + use_feature = true, + }, + }, + + # ── Work Request ────────────────────────────────────────────────────────── + make_route "WorkRequestPage" { en = "/work-request", es = "/solicitud-trabajo" } + & { + priority = priorities.normal, + menu = no_menu, + }, + + # ── Contact ─────────────────────────────────────────────────────────────── + make_route "ContactPage" { en = "/contact", es = "/contacto" } + & { + priority = priorities.normal, + menu = { + enabled = true, + order = orders.contact, + icon = "fas fa-envelope", + label_key = "nav-contact", + labels = { en = "Contact", es = "Contacto" }, + use_in = ["header"], + }, + }, + + # ── Legal / footer routes ───────────────────────────────────────────────── + make_route "LegalPage" { en = "/legal", es = "/legal" } + & { + priority = priorities.low, + menu = { + enabled = true, + order = 2, + icon = "fas fa-gavel", + label_key = "nav-legal", + labels = { en = "Legal Notice", es = "Aviso Legal" }, + use_in = ["footer"], + }, + }, + + make_route "PrivacyPage" { en = "/privacy", es = "/privacidad" } + & { + priority = priorities.low, + menu = { + enabled = true, + order = 1, + icon = "fas fa-shield-alt", + label_key = "nav-privacy", + labels = { en = "Privacy Policy", es = "Política de Privacidad" }, + use_in = ["footer"], + }, + }, + + # ── User (not in sitemap) ───────────────────────────────────────────────── + make_route "UserPage" { en = "/user", es = "/usuario" } + & { + priority = priorities.minimal, + menu = no_menu, + }, + + # ── Login (passwordless OTP) ────────────────────────────────────────────── + make_route "LoginPage" { en = "/login", es = "/iniciar-sesion" } + & { + priority = priorities.minimal, + menu = no_menu, + }, + + # ── External links ──────────────────────────────────────────────────────── + make_external "Kit-Digital" "/kitdigital.html" + & { + menu = { + enabled = true, + order = orders.external, + label_key = "nav-kit-digital", + labels = { en = "Kit-Digital", es = "Kit-Digital" }, + visible_in = ["es"], + use_in = ["header"], + }, + }, + ], +} | site_c.SiteConfig diff --git a/site/site/nickel b/site/site/nickel new file mode 120000 index 0000000..4213c30 --- /dev/null +++ b/site/site/nickel @@ -0,0 +1 @@ +/Users/jesusperezlorenzo/.local/share/rustelo/nickel \ No newline at end of file diff --git a/site/site/public/README.md b/site/site/public/README.md new file mode 100644 index 0000000..9b9fb32 --- /dev/null +++ b/site/site/public/README.md @@ -0,0 +1,27 @@ +# website-impl Public Assets + +This directory contains static assets served directly by the web server. + +## Directory Structure + +``` +public/ +├── images/ # Static images (logos, icons, etc.) +├── scripts/ # Client-side JavaScript +├── styles/ # Public CSS files +├── documents/ # Downloadable documents +└── README.md # This file +``` + +## Usage + +Files in this directory are served at the root URL: +- `public/logo.png` → `https://yoursite.com/logo.png` +- `public/styles/custom.css` → `https://yoursite.com/styles/custom.css` + +## Guidelines + +- Keep file sizes optimized for web delivery +- Use descriptive filenames +- Organize by type (images, scripts, styles, documents) +- Consider CDN deployment for production diff --git a/site/site/public/_content/blog/_images/devops/building-self-hosted-ci-cd-with-rust-and-kubernetes/building-self-hosted-ci-cd-with-rust-and-kubernetes_thumbnail.png b/site/site/public/_content/blog/_images/devops/building-self-hosted-ci-cd-with-rust-and-kubernetes/building-self-hosted-ci-cd-with-rust-and-kubernetes_thumbnail.png new file mode 100644 index 0000000..28f02bf Binary files /dev/null and b/site/site/public/_content/blog/_images/devops/building-self-hosted-ci-cd-with-rust-and-kubernetes/building-self-hosted-ci-cd-with-rust-and-kubernetes_thumbnail.png differ diff --git a/site/site/public/_content/blog/_images/ontoref/trust-is-an-output-not-an-input/trust-is-an-output-not-an-input_feature.jpg b/site/site/public/_content/blog/_images/ontoref/trust-is-an-output-not-an-input/trust-is-an-output-not-an-input_feature.jpg new file mode 100644 index 0000000..b689284 Binary files /dev/null and b/site/site/public/_content/blog/_images/ontoref/trust-is-an-output-not-an-input/trust-is-an-output-not-an-input_feature.jpg differ diff --git a/site/site/public/_content/blog/en/devops/building-self-hosted-ci-cd-with-rust-and-kubernetes/images/building-self-hosted-ci-cd-with-rust-and-kubernetes_thumbnail.png b/site/site/public/_content/blog/en/devops/building-self-hosted-ci-cd-with-rust-and-kubernetes/images/building-self-hosted-ci-cd-with-rust-and-kubernetes_thumbnail.png new file mode 100644 index 0000000..28f02bf Binary files /dev/null and b/site/site/public/_content/blog/en/devops/building-self-hosted-ci-cd-with-rust-and-kubernetes/images/building-self-hosted-ci-cd-with-rust-and-kubernetes_thumbnail.png differ diff --git a/site/site/public/_content/projects/en/provisioning-systems/images/provisioning-systems_arch-dark.svg b/site/site/public/_content/projects/en/provisioning-systems/images/provisioning-systems_arch-dark.svg new file mode 100644 index 0000000..4eb7b37 --- /dev/null +++ b/site/site/public/_content/projects/en/provisioning-systems/images/provisioning-systems_arch-dark.svg @@ -0,0 +1,700 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 900"> + <style> + .orbit-flow { animation: orbit-dash 4s linear infinite; } + @keyframes orbit-dash { from { stroke-dashoffset: 40; } to { stroke-dashoffset: 0; } } + + .ring-glow { animation: ring-breathe 3s ease-in-out infinite; } + @keyframes ring-breathe { 0%,100% { opacity: 0.06; } 50% { opacity: 0.18; } } + + .spoke { animation: spoke-dash 2s linear infinite; } + @keyframes spoke-dash { from { stroke-dashoffset: 16; } to { stroke-dashoffset: 0; } } + + .health { animation: pulse 2.2s ease-in-out infinite; } + .health-d1 { animation-delay: 0.4s; } + .health-d2 { animation-delay: 0.8s; } + .health-d3 { animation-delay: 1.2s; } + .health-d4 { animation-delay: 1.6s; } + @keyframes pulse { 0%,100% { opacity: 0.35; } 50% { opacity: 1; } } + + .https-beam { animation: https-p 4s ease-in-out infinite; } + @keyframes https-p { 0%,100% { opacity: 0.15; stroke-width: 1.5; } 50% { opacity: 0.7; stroke-width: 2.5; } } + .https-label { animation: https-lbl 4s ease-in-out infinite; } + @keyframes https-lbl { 0%,100% { opacity: 0.3; } 50% { opacity: 1; } } + + .solid-border { animation: solid-glow 3.5s ease-in-out infinite; } + @keyframes solid-glow { 0%,100% { stroke-opacity: 0.3; } 50% { stroke-opacity: 0.7; } } + + .hub-pulse { animation: hub-beat 3s ease-in-out infinite; } + @keyframes hub-beat { 0%,100% { opacity: 0.3; } 50% { opacity: 0.6; } } + + .cred-dot { opacity: 0; } + .db-flow { animation: db-dash 2.5s linear infinite; } + @keyframes db-dash { from { stroke-dashoffset: 12; } to { stroke-dashoffset: 0; } } + </style> + + <defs> + <filter id="glow-blue" x="-50%" y="-50%" width="200%" height="200%"> + <feGaussianBlur in="SourceGraphic" stdDeviation="6" result="blur"/> + <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge> + </filter> + <filter id="glow-orange" x="-50%" y="-50%" width="200%" height="200%"> + <feGaussianBlur in="SourceGraphic" stdDeviation="5" result="blur"/> + <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge> + </filter> + <filter id="glow-pink" x="-50%" y="-50%" width="200%" height="200%"> + <feGaussianBlur in="SourceGraphic" stdDeviation="5" result="blur"/> + <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge> + </filter> + <filter id="glow-purple" x="-50%" y="-50%" width="200%" height="200%"> + <feGaussianBlur in="SourceGraphic" stdDeviation="5" result="blur"/> + <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge> + </filter> + <filter id="glow-cyan" x="-50%" y="-50%" width="200%" height="200%"> + <feGaussianBlur in="SourceGraphic" stdDeviation="5" result="blur"/> + <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge> + </filter> + <filter id="glow-gold" x="-50%" y="-50%" width="200%" height="200%"> + <feGaussianBlur in="SourceGraphic" stdDeviation="6" result="blur"/> + <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge> + </filter> + <filter id="glow-flash" x="-100%" y="-100%" width="300%" height="300%"> + <feGaussianBlur in="SourceGraphic" stdDeviation="10" result="blur"/> + <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge> + </filter> + <filter id="soft-shadow" x="-20%" y="-20%" width="140%" height="140%"> + <feDropShadow dx="0" dy="2" stdDeviation="8" flood-color="#000" flood-opacity="0.5"/> + </filter> + <filter id="ring-glow-f" x="-20%" y="-20%" width="140%" height="140%"> + <feGaussianBlur in="SourceGraphic" stdDeviation="8" result="blur"/> + <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge> + </filter> + + <radialGradient id="bg-radial" cx="50%" cy="42%" r="55%"> + <stop offset="0%" stop-color="#111520"/> + <stop offset="100%" stop-color="#0a0c10"/> + </radialGradient> + <radialGradient id="hub-glow" cx="50%" cy="50%" r="50%"> + <stop offset="0%" stop-color="#94a3b8" stop-opacity="0.15"/> + <stop offset="100%" stop-color="#94a3b8" stop-opacity="0"/> + </radialGradient> + <linearGradient id="orch-grad" x1="0%" y1="0%" x2="100%" y2="100%"> + <stop offset="0%" stop-color="#2a2016"/><stop offset="100%" stop-color="#1a150f"/> + </linearGradient> + <linearGradient id="ctrl-grad" x1="0%" y1="0%" x2="100%" y2="100%"> + <stop offset="0%" stop-color="#1a1630"/><stop offset="100%" stop-color="#110f1e"/> + </linearGradient> + <linearGradient id="vault-grad" x1="0%" y1="0%" x2="100%" y2="100%"> + <stop offset="0%" stop-color="#2a1630"/><stop offset="100%" stop-color="#1a0f1e"/> + </linearGradient> + <linearGradient id="ext-grad" x1="0%" y1="0%" x2="100%" y2="100%"> + <stop offset="0%" stop-color="#2a1620"/><stop offset="100%" stop-color="#1a0f14"/> + </linearGradient> + <linearGradient id="mcp-grad" x1="0%" y1="0%" x2="100%" y2="100%"> + <stop offset="0%" stop-color="#0f2a2e"/><stop offset="100%" stop-color="#0a1c1e"/> + </linearGradient> + <linearGradient id="surreal-grad" x1="0%" y1="0%" x2="100%" y2="100%"> + <stop offset="0%" stop-color="#3a3050"/><stop offset="100%" stop-color="#2e2640"/> + </linearGradient> + <linearGradient id="cli-grad" x1="0%" y1="0%" x2="100%" y2="100%"> + <stop offset="0%" stop-color="#1a2a42"/><stop offset="100%" stop-color="#0f1a2e"/> + </linearGradient> + + <!-- Animated gradient for infinity engine color cycling --> + <linearGradient id="infinityGradient" x1="0%" y1="0%" x2="100%" y2="0%"> + <stop offset="0%" style="stop-color:#00D4FF;stop-opacity:1"> + <animate attributeName="stop-color" values="#00D4FF;#7B2BFF;#00FFB3;#00D4FF" dur="3s" repeatCount="indefinite"/> + </stop> + <stop offset="50%" style="stop-color:#7B2BFF;stop-opacity:1"> + <animate attributeName="stop-color" values="#7B2BFF;#00FFB3;#00D4FF;#7B2BFF" dur="3s" repeatCount="indefinite"/> + </stop> + <stop offset="100%" style="stop-color:#00FFB3;stop-opacity:1"> + <animate attributeName="stop-color" values="#00FFB3;#00D4FF;#7B2BFF;#00FFB3" dur="3s" repeatCount="indefinite"/> + </stop> + </linearGradient> + + <marker id="arr-blue" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"> + <polygon points="0 0, 8 3, 0 6" fill="#4a9eff"/> + </marker> + <marker id="arr-silver" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"> + <polygon points="0 0, 8 3, 0 6" fill="#94a3b8"/> + </marker> + <marker id="arr-gold" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"> + <polygon points="0 0, 8 3, 0 6" fill="#fbbf24"/> + </marker> + <marker id="arr-green" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"> + <polygon points="0 0, 8 3, 0 6" fill="#10b981"/> + </marker> + <marker id="arr-green-sm" markerWidth="5" markerHeight="4" refX="5" refY="2" orient="auto"> + <polygon points="0 0, 5 2, 0 4" fill="#10b981"/> + </marker> + + <path id="orbit-cw" d="M 640,185 A 135,135 0 1,1 640,455 A 135,135 0 1,1 640,185 Z" fill="none"/> + <path id="orbit-ccw" d="M 640,185 A 135,135 0 1,0 640,455 A 135,135 0 1,0 640,185 Z" fill="none"/> + + <path id="path-orch-vault" d="M 640,185 A 135,135 0 0,0 508,276" fill="none"/> + <path id="path-vault-orch" d="M 508,276 A 135,135 0 0,1 640,185" fill="none"/> + <path id="path-cli-orch" d="M 660,98 L 660,115" fill="none"/> + </defs> + + <!-- ═══ BACKGROUND ═══ --> + <rect width="1280" height="900" fill="url(#bg-radial)"/> + <g opacity="0.03"> + <pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse"> + <path d="M 40 0 L 0 0 0 40" fill="none" stroke="#fff" stroke-width="0.5"/> + </pattern> + <rect width="1280" height="900" fill="url(#grid)"/> + </g> + + <!-- Star field --> + <g opacity="0.35"> + <circle cx="95" cy="145" r="0.8" fill="#fff"/><circle cx="340" cy="78" r="0.6" fill="#fff"/> + <circle cx="890" cy="115" r="1" fill="#fff"/><circle cx="1120" cy="210" r="0.7" fill="#fff"/> + <circle cx="180" cy="460" r="0.5" fill="#fff"/><circle cx="1160" cy="510" r="0.8" fill="#fff"/> + <circle cx="72" cy="660" r="0.6" fill="#fff"/><circle cx="960" cy="710" r="0.9" fill="#fff"/> + </g> + + <!-- ═══ TITLE ═══ --> + <g transform="translate(206, 20) scale(0.35)"> + <g id="logo-content"><g><g><path d="M601.85,109.76c.89,.04,.57-.38,.7-.46-1.49,.09-.67,.57-.7,.46Z" style="fill:#b0dff6;"/><polygon points="587.17 110.72 587.43 110.55 585.26 110.96 587.17 110.72" style="fill:#b0dff6;"/><path d="M573.43,110.71c1.3,.71,4.98-.12,7.42,.14l.4,.16,1.1-.57c-2.97,.09-6.84,.47-8.93,.27Z" style="fill:#b0dff6;"/><path d="M566.11,109.16c.45,.28-1.58,.24,1.71,.09-.17-.03-.21-.05-.31-.07-.3,0-.7,0-1.4-.02Z" style="fill:#b0dff6;"/><path d="M567.51,109.18c1.45-.02-.95-.23,0,0h0Z" style="fill:#b0dff6;"/><path d="M560.83,109.34l1.9-.25c-.4-.16-2.09-.2-1.46-.32-1.95,.14-1.55,.3-.44,.57Z" style="fill:#b0dff6;"/><path d="M559.02,109.86l.99-.1c-.17-.07-.43-.09-.99,.1Z" style="fill:#b0dff6;"/><path d="M562.11,109.53l-2.09,.22c.28,.12,.3,.35,.96-.08-.17,.21,.24,0,1.13-.14Z" style="fill:#b0dff6;"/><path d="M563.29,109.64c1.07,.16,2.45,.25,.54,.5,3.64-.1,3.11-.6-.54-.5Z" style="fill:#b0dff6;"/><path d="M572.73,111.45l1.43,.21,1.24-.24c-.62,.12-2.75-.19-2.66,.03Z" style="fill:#b0dff6;"/><path d="M548.96,108.97c-.12,.15,.64,.4,.78,.6l1.41-.17c-.72-.1-2.09-.2-2.19-.42Z" style="fill:#b0dff6;"/><path d="M549.75,109.57l-2.11,.26c1.88,.03,2.22-.09,2.11-.26Z" style="fill:#b0dff6;"/><polygon points="558.69 110.65 562.14 110.11 558.02 110.66 558.69 110.65" style="fill:#b0dff6;"/><polygon points="540.92 108.95 542.12 108.6 539.99 109.13 540.92 108.95" style="fill:#b0dff6;"/><path d="M535.59,109.01c.88-.29-1.21-.49-.28-.67-1.45,.05-2.69,.3-3.8,.53,1.18-.13,2.44-.09,4.08,.14Z" style="fill:#b0dff6;"/><path d="M529.64,109.2c.59-.07,1.2-.19,1.87-.33-.63,.07-1.25,.16-1.87,.33Z" style="fill:#b0dff6;"/><path d="M544.06,109.76c-.16-.09-.48-.33-1.66-.43l.83,.43c.25,0,.54,0,.83,0Z" style="fill:#b0dff6;"/><path d="M543.26,109.76h-.03c-.9-.02-1.52-.03-1.79-.04,.23,0,.78,.01,1.82,.04Z" style="fill:#b0dff6;"/><path d="M544.26,109.76c-.08,0-.13,0-.21,0,.08,.04,.13,.06,.21,0Z" style="fill:#b0dff6;"/><path d="M553.17,110.27c-1.24,.24-3.82,.5-1.32,.86,.89-.29-.54-.5,1.32-.86Z" style="fill:#b0dff6;"/><path d="M545.29,110.29l2.24,.1c-.04-.11-.45-.28,.17-.4l-2.4,.3Z" style="fill:#b0dff6;"/><polygon points="544.2 110.43 545.29 110.29 544.46 110.26 544.2 110.43" style="fill:#b0dff6;"/><path d="M533.47,109.6c-.97,.19-2.02,.2-3.22,.23,1.28,.08,2.6,.13,3.86,.13,.18-.13,.73-.38-.64-.36Z" style="fill:#b0dff6;"/><path d="M538.23,109.71c-.07,0-.15,.01-.21,.02,.13,.04,.24,.05,.21-.02Z" style="fill:#b0dff6;"/><path d="M518.51,109.82c.07,.02,.12,.05,.19,.08,.01-.02,.02-.05,.04-.07h-.23Z" style="fill:#b0dff6;"/><path d="M534.33,110.04c.96,.08,1.54-.02,2.08-.15-.72,.04-1.48,.07-2.3,.07-.08,.06-.09,.1,.21,.08Z" style="fill:#b0dff6;"/><path d="M526.36,109.4c.5,.09,1.07,.17,1.67,.23-.49-.14-1.49-.35-1.67-.23Z" style="fill:#b0dff6;"/><path d="M537.69,109.62s.09-.01,.14-.02c-.32-.09-.28-.05-.14,.02Z" style="fill:#b0dff6;"/><path d="M537.69,109.62c-.49,.06-.88,.17-1.27,.26,.59-.03,1.13-.08,1.6-.14-.12-.03-.24-.08-.32-.12Z" style="fill:#b0dff6;"/><path d="M514.95,112.57c.17-.4,2.7-.76,4.88-.34,2.15-.3,.74-1.6-1.13-2.33-.07,.17-.07,.33-2.28,.57,.77,.22,4.9,.51,3.04,.86-2.93,.2-2.4-.14-3.82-.35,1.21,.49-3.72,.72-3.09,1.44l2.57-.26c.05,.11-.88,.29-.17,.4Z" style="fill:#b0dff6;"/><path d="M522.91,109.94l.91-.05c-1.04-.07-1.78-.23-3.15-.54-1.51,.19-1.83,.34-1.93,.47l4.48,.06-.31,.06Z" style="fill:#b0dff6;"/><path d="M527.9,109.89c-.71,.43-2.89,.61-1.56,1.08,2.67-.03,1.87-.36,3.46-.55-4.31,.11,1.24-.24-1.87-.48,.85-.07,1.61-.1,2.33-.11-.78-.05-1.52-.12-2.23-.2,.04,0,.07,.02,.1,.03h.02s0,0,0,0c.19,.05,.25,.09,.04,.06-.01,0-.07,0-.09,0-.03,.06-.11,.11-.19,.16-1.25-.09-.2-.11,.19-.16,.01-.02,.05-.04,.05-.06,0,0-.01,0-.02,0l-4.31,.24c.91,.06,2.06,.05,4.08,0Z" style="fill:#b0dff6;"/><path d="M549.54,112.05c-.13-.04-.13-.07-.14-.1-.28,.05-.38,.09,.14,.1Z" style="fill:#b0dff6;"/><path d="M547.77,111.79c.31-.06,.57-.23,1.59-.19,.88,.13,0,.21,.04,.35,.61-.11,2.15-.31,.22-.52l-.31,.06c-1.74-.15-3.97-.69-3.66-.74,.51,.39,.95,.66,2.11,1.04Z" style="fill:#b0dff6;"/><path d="M537.71,110.95l-3.59,.21,1.83,.37-.09-.22c2.04,.09,1.6-.19,1.86-.36Z" style="fill:#b0dff6;"/><polygon points="518.1 109.61 518.57 109.15 516.5 109.8 518.1 109.61" style="fill:#b0dff6;"/><polygon points="513.43 109.66 513.6 109.27 512.37 109.51 513.43 109.66" style="fill:#b0dff6;"/><path d="M250.01,114.57l-1.06,.11c.5-.03,.82-.07,1.06-.11Z" style="fill:#b0dff6;"/><path d="M257.65,114.6l.19-.19c-.35,.11-.5,.19-.19,.19Z" style="fill:#b0dff6;"/><path d="M398.78,109.71c-1.54-.37-3.25-.94-3.07-.5l.47,.43c1.07-.08,1.9-.03,2.6,.06Z" style="fill:#b0dff6;"/><path d="M320.27,112.3c.15,.04,.3,.09,.45,.13,.37-.15,.73-.29,1.04-.42-.45,.09-.96,.18-1.49,.28Z" style="fill:#b0dff6;"/><path d="M319.18,114.09l-2.03-.28c.46,.1,1.11,.19,2.03,.28Z" style="fill:#b0dff6;"/><path d="M208.27,115.03l-1.23,.44c1.93,0,1.08-.23,1.23-.44Z" style="fill:#b0dff6;"/><path d="M465.62,109.82c-1.33-.17-2.38-.24-3.31-.26,1.6,.25,2.89,.62,3.31,.26Z" style="fill:#b0dff6;"/><path d="M253.01,109.97c-.65,.08-.92,.17-.9,.26,.66-.07,1.04-.16,.9-.26Z" style="fill:#b0dff6;"/><path d="M458.48,109.56c.23,.04,.46,.07,.7,.1,.97-.07,1.95-.13,3.13-.1-1.18-.19-2.53-.3-3.83,0Z" style="fill:#b0dff6;"/><path d="M56.92,114.68c-.11-.02-.24-.03-.34-.05-.03,.06,.12,.06,.34,.05Z" style="fill:#b0dff6;"/><path d="M12.82,110.3c.43-.03,1.14-.03,1.81-.03-.33-.03-.87-.03-1.81,.03Z" style="fill:#b0dff6;"/><path d="M494.65,109.47c.99-.03,.16-.05,0,0h0Z" style="fill:#b0dff6;"/><path d="M499.46,111.19c-.21,.05-.37,.11-.4,.21,.08-.1,.22-.17,.4-.21Z" style="fill:#b0dff6;"/><path d="M448.61,112.26s-.05,0-.09,0c.29,.08,.24,.07,.09,0Z" style="fill:#b0dff6;"/><path d="M496.14,109.67c-.37-.05-.8-.1-1.27-.13,.2,.03,.59,.08,1.27,.13Z" style="fill:#b0dff6;"/><path d="M495.93,109.47c.34,.02,.61,.03,.87,.04,.12-.07,.01-.12-.87-.04Z" style="fill:#b0dff6;"/><path d="M493.27,109.5c.57,0,1.09,.01,1.6,.05-.19-.03-.28-.05-.22-.07-.29,0-.7,.02-1.38,.02Z" style="fill:#b0dff6;"/><path d="M138.01,113.51c-.07-.01-.14-.02-.21-.04-1,.18-.56,.15,.21,.04Z" style="fill:#b0dff6;"/><path d="M373.02,114.21s.07-.02,.11-.02c-.05,0-.1-.01-.15-.02l.03,.04Z" style="fill:#b0dff6;"/><path d="M369.46,114.54c1.51-.54,2.38-.47,3.52-.36l-1.18-1.25-2.35,1.61Z" style="fill:#b0dff6;"/><path d="M231.71,114.7l2.5,.39c-3.01,.64-5.19,.42-2.85,.97,.35-1.36,9.71-.14,10.05-1.5l1.65,.29c-.47-.01-.55,.07-1.01,.05,3.2,.65,2.38-.98,6.5-.79,1.44,.1,2.44,.3,1.46,.46l3.2-.34c.93,.03,.7,.26-.22,.24l4.89-.11-.04,.04c.7-.21,2.28-.54,3.05-.68,1.24,.19-.62,.14-.3,.31l2.71-.33c1.1,.35-1.14,.69-3.01,.64,2.53,1.36,2.24-.34,7.7,.45l-2.55,.17c1.65,.77,3.64,.49,6.53,.97-.69-.25-5.78-1.21-2.85-1.55,.83,.1,1.92,.15,3.5-.12,.01,.48,1.56,.36,3.5,.34l-.13,.64c2.09-.18,1.93-.51,3.16-.8,2.33,.06,2.82,.56,3.36,.98,3.73,.1-2.58-.79,1.84-.91,3.11,.24,5.29-.02,9-.4,2.17,.22-.38,.39,.4,.57l1.93-.51c.47,.01,.32,.17,.24,.25,1.62-.2,.13-.64,2.69-.81,.69-.7,5.24,1.03,8.4,.23,.78,.18-.38,.39-.14,.64,4.33-.52,3.72-.38,7.27-1.09l3.03,.42c-2.23-.48,.62-1.05,3.11-1.52-.79-.23-1.47-.43-1.92-.53l3.98-.34c.48,.1,.08,.32-.57,.59,.27-.05,.53-.11,.73-.15,1.42,.24,.69,.61-.33,.96-.48-.13-.97-.26-1.43-.39-.98,.39-1.9,.82-1.7,1.19,.26-.07,.76-.14,1.3-.17-.26,.11-.42,.2-.3,.26,.14,.07,.55-.07,1.2-.24l.9,.71,.22-.28,5.2,.45c3.72-.72,6.96-1.77,12.87-2.07-1.47,.52-.32,.85-.4,1.47-1.61-.66-4.58,.28-7.64,.15,1.6,.07,1.25,.36,.84,.47l5.46-.6c-.12,.51,1.16,.38,2.59,.59-1.08-1,4.07-.67,6.3-.97,.14,.34-.4,.68-3.11,.6,2.99,.75,3.87-1.18,6.8-.54-.66,0-1.02-.04-1.28,.13,1.55-.3,5.33-.06,4.44,.23h-.66c4.4,.13,13.38,.01,14.04-.84,.04,.11-.58,1.03-.84,1.2l7.84-2.86c-.69,.73,1.96,2.47-1.68,2.88,1.05,.1,2.34,.23,4.55-.03-.71-.1-1.78-1.06-.8-1.12,2.68,.81,1.86,.43,5.16,1.13-1.07-.16,.12-1.3,1.81-1.27-.26,.17,.14,1.13-.48,1.25l4.21-1.12c-1.05,.2-.19,.95,.76,1.27-.44-.22,3.64,.02,4.76-.09l-1.78-.26c4.75,.17,4.7-1.58,9.41-1.52-.57,.23-1.41,1.43,.99,1.57,1.1-.58,4.87-2.77,9.04-3.21l.8,.33,3.2-.38c-1.99,.87-7.59,2.69-10.64,3.4,1.78,.26,.5,.39,3.16,.36,.94,.31-1.45,.47-2.24,.49l6.2,.2c-.28-.67,4.74-.67,4.51-1.23l-6.74,.7c-.27-.67,3.9-1.91,8.29-1.79,1.12,.27-.84,1.2-.74,1.42,.57-.23,4.17-.44,4.58-.28l-1.86,.36c2.45,.25,3.99-.89,6.93-.25,1.01,.04,3.03,.87,3.21,.47-1.66-.77-1.98-3.14-.88-3.72,.36,.05,5.33,.73,6.49,1.11,1.67,.6-2.51,1.43-1.09,1.95,.06-.18,.98-.41,1.37-.48,1.07,.16-.44,.57,1.87,.49,.39-.68,4.71,.06,1.09-.58,2.27-.19,2.41,.14,5.07,.11-.85-.44,2.39-1.5,4.66-1.69-.13,.17,.38,.4,.62,.5,4.64,.06,7.14,.37,11.47,.38,.4,.17,2.49,.36,1.6,.66,.62-.12,1.19-.35,2.53-.37,2.58,.59-2.98,.09-1.01,.8,.17-.4,3.45-.55,5.36-.79-.59-.61-4.39-.12-6.3,.13,0-.84,3.31-1.73,7.83-2.12,3.6-.21,1.56,.55,2.18,.43,6.76,.15,5.76-1.47,11.37-.86,2.23,.54,.1,1.07,.59,1.46-2.93,.2-4.5-.34-6.48-.32l2.44,.25c-.93,.18-2.89,.31-3.95,.16,1.87,.48,11.41,.09,16.63,.54,.93-.18,2.52-.37,1.77-.58l-1.65,.08c-1.52-.43,3.1-.6,1.02-.8,2.21-.3,4.69-.78,7.81-.54l.59,.61c-.07-.18-2.04-.37-2.91-.18,1.3-.27,4.78,.83,6.2,.03l-2.18-.42c2.22-.31,4.97-.96,7.73-.77-1.26-.61-.64,.07-2.6-.64,1.7,.88-7.06-.04-4.33,.89-2.01-.82-3.48-.35-6.11-1.05,.08,.2,.93,.39-1.39,.29-.1,.06-.35,.13-.51,.18-.07,0-.1,0-.16-.02,.05,0,.08,.02,.13,.02-.1,.03-.11,.04,.05,0,.55,.09,1,.19,1.18,.31-1.15,.46-4.83,.45-6.08,.69-1.38-.1,.57-.23-.18-.45l-1.86,.36c-.18-.45-3.65-.75-.41-1.01l-2.92-.02c-1.72-.25-4.08-.35-5.53-.16-1.38-.1-3.47,.5-2.28,.15l-4.57,.28-.04-.11c-4.51-.06-9.29,.74-14.44-.07-1.52,.11-3.09,.25-5.63,.12l-.5-.39c-1.95,.14-5.86-.39-6.69,.02-2.1-1.05-9.46-.23-12.89-.41l-.13,.51c-2.36-.03-4.62,.12-7.55,.32l.18,.45c-2.53,.37-6.49-.32-10.49-.27,.27-.17,1.29-.13,2-.02-4.77-1.01-10.07,.74-14.4,0-1.08,.16-1.86,.11-2.62,0,.1,0,.19,0,.27-.02-.11,0-.27,0-.44,0-.48-.08-.97-.17-1.5-.24,.45,.11,.88,.2,1.26,.24-.83,0-2.09,.07-3.33,.18l-.53-.48c-.1,0-.18,0-.29,.01-1.21-.18-.98,.23-.33,.59-.62,.08-1.19,.17-1.64,.29-.99-.78-3.25,.26-4.59-.57-.83,.4-6.22,.3-6.88,1.15-.04-.11-.41-.16,.26-.17-1.38-.1-2.76-.19-3.99,.05l-.59-.61-3.01,.82c-1.83-.37-2.59-.59-.77-1.06-4.39,.72-3.56,.27-7.29,.99l.18-.4c-1.33,.02-3.46,.55-4.27,.22-2.98-.75-14.76-.1-22.41-.8,1.48,1.17-3.56-.52-3.86,.38-.45-.28-1.51-.43-.27-.67-3.91,.27-5.87-.44-8.57,.32-.45-.28,.84-.4,.44-.57-.31,.06-1.24,.24-1.65,.08-.4-.16,.57-.23,1.19-.35-3.99,.05-4.4,.9-4.69,1.69-2.02-.38-3.03-.32-4.49,.2-.7-.26-1.88-.53,.6-.63-1.4-.04-8.52-.38-8.67,.26-.44-.16-1.95-.05-2.32,.03-3.13-.04-3.41-.1-6.48-.04l.85,.1c-1.21,.77-2.56,.17-5.03,.26l.07-.08c-5.68-.56-2.45,.27-8.06-.37l.3,.48c-.52,1.03-4.97-.61-8.22-.21l1.17,.27c-1.78,.35-4.97-1.4-6.75-1.53-.39-.09,.61-.14,1.16-.21-4.67-.61-.61,.63-4.09,.77-.64-.34,.83-.86-1.52-.92-1.17-.27-6.8,1.1-9.99,.45,.7,.26,1.4,.52-.21,.72-2.48,.09-6.7-.99-8.94-.16-.32-.07-.43-.14-.45-.2-1.89,.21-6.16,.35-7.3,.8-1.75-1.66-11.78,.81-11.74-.72l-5.83,.08,.15-.16c-3.73-.1-5.04,.27-6.35,.63-.86-.1-.23-.25-.16-.33-5.44-.31-5.98-.24-10.55,.52l-.47-.5c-1.24,.29-7.87-.78-13.21-.2,.05-.06,.16-.15,.59-.22-6.01,.58-14.38-1.38-16.58,.43l-3.96,.44c5.13,.14-.76,.78,1.35,1.08-2.02,.11-5.21-.54-2.74-.64l.39,.09c.91-.94-6.37-.33-6.23-.97-10.09,.05-19.93-.47-29.38-.51l1.09,.73-3.3-.04c-.87-.17-1.16-.57,1.19-.54-1.24-.41-3.94,.35-4.01,.51-4.61-.3,1.8-.85,.39-.87l-1.92,.05,.43,.09c-1.55,.3-1.69,.62-4.52,.58-1.79-.15-.92-.45-1.62-.48,0-.05-.23-.08-1.08-.02l-4.15-.06,1.66,.58c-1.84,.28-3.91-.03-1.99,.62-2.81-1.01-15.5-.36-17.22-.68-2.58,.42-5.12,.41-8.28,.36,.78,.12,1.25,.77-1.47,.69,.99-1.34-4.98-.58-7.18-1.55,1.96,.64-7.73,.16-4.65,1.07-2.27-.11-.14-.5-1.86-.82-5.4,.52-11.9-.34-18.58-.16-.1,.18,1.01,.44-.42,.71l-3.52-.88c-1.32,.09-1.21,1.02-3.62,.4,.34,.15,.85,.37-.04,.43-13.08-.86-26.73,.7-40.01-.91,1.8,.34,.6,.37-.77,.39,.89,.08-.02,.43,.16,.67l-5.56-.66c-1.01,.66-5.96,.04-6.54,.68l1.76-.11c-2.34,.75,3.28,1.66,2.93,2.62,2.62-.85,6.91,1.26,11.22,.04,.95,.2-.81,.31-.3,.53,1.09-.41,2.41-.5,4.85-.31l-.28,.1c6.86-.1,9.8,.3,17.5,.57l-.57-.47c1.66,.06,2,.21,2.78,.33,1.9-.72-4.65-.04-2.92-.83,2.2,.96,11.67,.01,13.16,1.11,2.55,0-.75-.55,1.8-.54l.68,.3,.64-.38c1.67,.06,2.51,.43,2.57,.68-.41-.03-1.15,.07-1.59,.09,2.16,.35,6.1-.12,6.85,0l-2.45-.18c6.95-.28,15.43,.11,22.15-.49l-.51-.22c5.13-.41,4.11,.25,9.74,.05l-.2,.05c1.13-.22,2.63-.33,4.17-.31-1.42,.26,2.67,.51,.81,.8,5.56-.44,2.75-.35,6.42-1.18l.85,.37c1.25-.34,1.36-.51,4.17-.61-2.31,.32,1.79,.57-.79,.99,5.9,.81,8.76-.82,11.47,.37,2.71-1.03-5.11-.57-3.59-.79-1.27-.34,2.24-.77,4.09-.66,1.88,.02,3.2,1.31,7.94,1.29-.47,0-.5,.07-.97,.07,1.77,.26,3.48-.35,5.65,.07,1.22-.62,2.83,.04,3.18-.76l-4.35,.18c2.47-.21,4.78-1.13,8.84-.68-.32,.16-1.52,.37-2.36,.49,1.19,.23,2.29-.15,3.52,.08-.35,.79-5.28,.17-7.93,.78,1.27,.33,5-.57,3.67,.28,1.91-1.09,4.67,.14,7.88-.7l-.21,.47c.51-.07,1.56-.3,2.5-.29l-1.69,.62c2.6-.52,5.01,.46,7.5,.17-5.22,.01-1.17-.57-3.38-.92,6.44-.64,3.7,1.24,10.91,1.01-.98,.07-3.66-.29-2.14-.5,1.37,.1,3.18,.28,4.03,.53,4.78-.1-.73-.49,1.3-.78,1.63,.58,2.34-.24,4.44-.42v.48c5.46,.31,1.46-1.01,6.36-.63l-2.07,.67,2.55-.17-.53,.55c2.78-.41,3.64-.3,6.37-.15-.63-.34,.36-.88,2.77-.89,1.64,.29-.92,.46,2.5,.39-.77,.3-1.62,.68-3.1,.24-.15,.16-.76,.3-.92,.46,1.77,.33,4.45,.08,5.69,.07-.48-.02-1.05-.04-1.34-.11l4.95-.67c.78,.18,.16,.33-.45,.47,1-.05,1.7-.27,3.17-.32-.38,.39-.29,.8-2.38,.98l4.72-.44c.32,.17,2.42,.47,1.81,.61,2.62,.23,6.42-.76,9.89-.46,.18-.08,.54-.15,1.28-.21,3.26,.09,6.15,.57,10.01,.03l2.19,.7c3.03-.16-2.97-.89,2.07-1.15,3.57-.22,1.33,.6,2.58,.8,1.77-.35,4.86-1.08,8.14-.5-1.08,.13-1.94,.03-2.95,.08Zm37.32-.31c-.72,.1-2.16,.41-2.69,.13,.82-.55,1.46-.29,2.69-.13Zm56.73-1.16c-.54,.16-1.37,.08-2.31-.1,.68-.04,1.45-.02,2.31,.1Zm158.03-3.36s-.05,.01-.08,.02c-1.61-.19-1.04-.11,.08-.02Zm3.35,.21c.34-.07,.31-.13,.17-.2-.23,.12-.99,.13-1.84,.1,.24,.13,.67,.21,1.67,.11Zm-359.7,.19l.65,.13c-1.47,.14-1.06,.01-.65-.13Z" style="fill:#b0dff6;"/></g><g><path d="M6.89,82.3c-1.67-1.33-2.5-3.57-2.5-6.7s1.12-12.62,3.35-28.45c2.23-15.83,3.35-27.13,3.35-33.9s-.47-11.18-1.4-13.25c6.8,.53,11.27,3.2,13.4,8,5.13-2.8,10.78-4.2,16.95-4.2s11.03,1.92,14.6,5.75c3.57,3.83,5.35,8.55,5.35,14.15,0,6.93-2.54,13.2-7.6,18.8-2.47,2.67-5.65,4.83-9.55,6.5-3.9,1.67-8.22,2.5-12.95,2.5-2,0-3.9-.13-5.7-.4l-.3-4.2h.8c7.53,0,13.53-2.8,18-8.4,3.07-4,4.6-8.2,4.6-12.6,0-6.47-2.63-10.73-7.9-12.8-4.47-1.67-9.47-1.33-15,1v.2c0,2.53-.95,10.82-2.85,24.85-1.9,14.03-2.85,25.75-2.85,35.15,0,4.2,.2,7.47,.6,9.8-2.13,.13-3.53,.2-4.2,.2-3.8,0-6.53-.67-8.2-2Z" style="fill:#b0dff6;"/><path d="M80.19,50.1c2.4-8.67,5.28-15.05,8.65-19.15,3.37-4.1,6.5-6.15,9.4-6.15s5.08,1.08,6.55,3.25c1.47,2.17,2.2,4.97,2.2,8.4s-1.02,6.58-3.05,9.45c-2.03,2.87-4.82,4.3-8.35,4.3-.93,0-2.04-.27-3.3-.8,2.07-2.6,3.1-5.73,3.1-9.4,0-2.13-.87-3.2-2.6-3.2-1.27,0-2.63,.88-4.1,2.65-1.47,1.77-2.87,4.2-4.2,7.3-1.33,3.1-2.45,7.07-3.35,11.9-.9,4.83-1.35,9.98-1.35,15.45,0,.67,.17,3.5,.5,8.5-2.13,.13-3.5,.2-4.1,.2-3.8,0-6.53-.67-8.2-2-1.67-1.33-2.5-3.53-2.5-6.6,0-1.33,.7-6.46,2.1-15.4,1.4-8.93,2.1-16.05,2.1-21.35s-.57-10.18-1.7-14.65c4.87,1.53,8.27,3.72,10.2,6.55,1.93,2.83,2.9,6.13,2.9,9.9s-.3,7.38-.9,10.85Z" style="fill:#b0dff6;"/><path d="M132.39,23.5c.73,0,1.42,.22,2.05,.65,.63,.43,.95,.88,.95,1.35-6.73,4.67-10.77,11.17-12.1,19.5,1.4-3.73,3.12-6.97,5.15-9.7,2.03-2.73,4.15-4.8,6.35-6.2,4.2-2.6,8.3-3.9,12.3-3.9s7.4,1.97,10.2,5.9c2.8,3.93,4.2,9.2,4.2,15.8,0,7.4-1.5,14.2-4.5,20.4-3.33,7-8.07,11.83-14.2,14.5-3.47,1.53-7.27,2.3-11.4,2.3-5.8,0-10.85-2.25-15.15-6.75s-6.45-10.97-6.45-19.4,2.07-15.87,6.2-22.3c4.13-6.43,9.6-10.48,16.4-12.15Zm8.5,9c-4.33,0-8.2,2.92-11.6,8.75-3.4,5.83-5.1,11.65-5.1,17.45,0,12.13,3.13,18.2,9.4,18.2,4.47,0,8.1-2.92,10.9-8.75,2.8-5.83,4.2-12.38,4.2-19.65,0-10.67-2.6-16-7.8-16Z" style="fill:#b0dff6;"/><path d="M193.09,82.6c-2.13,.93-4.37,1.4-6.7,1.4s-3.63-.5-3.9-1.5c-1.07-4.27-2.3-10.65-3.7-19.15s-2.82-15.93-4.25-22.3c-1.43-6.37-3.15-11.18-5.15-14.45,3.67-1.73,6.6-2.6,8.8-2.6s3.87,.5,5,1.5c1.13,1,2.08,2.98,2.85,5.95,.77,2.97,1.32,5.42,1.65,7.35,.93,6.4,1.4,9.97,1.4,10.7s.27,3.72,.8,8.95c.53,5.23,.93,8.68,1.2,10.35,11.87-23,17.8-38.37,17.8-46.1,5.47,2.8,8.2,5.83,8.2,9.1,0,2.67-1.38,6.62-4.15,11.85-2.77,5.23-6.23,11.57-10.4,19-4.17,7.43-7.32,14.08-9.45,19.95Z" style="fill:#b0dff6;"/><path d="M241.34,48.65c-1.63,9.5-2.47,17.33-2.5,23.5-.03,6.17,.35,10.15,1.15,11.95-5.2-1.33-8.7-3.35-10.5-6.05s-2.7-6.65-2.7-11.85c0-2.8,.53-7.87,1.6-15.2,1.07-7.33,1.6-13.03,1.6-17.1s-.2-7.2-.6-9.4c1.47-.13,2.83-.2,4.1-.2,3.87,0,6.55,.65,8.05,1.95,1.5,1.3,2.25,3.52,2.25,6.65,0,1-.82,6.25-2.45,15.75Zm3.95-33.15c-1.87,.27-3.5,.4-4.9,.4-6.87,0-10.3-2.4-10.3-7.2,0-1.73,.5-4.1,1.5-7.1,1.87-.27,3.5-.4,4.9-.4,6.87,0,10.3,2.4,10.3,7.2,0,1.73-.5,4.1-1.5,7.1Z" style="fill:#b0dff6;"/><path d="M278.59,30.8c-1.87,0-3.53,.62-5,1.85-1.47,1.23-2.2,2.98-2.2,5.25s.78,4.4,2.35,6.4c1.57,2,3.48,3.75,5.75,5.25,2.27,1.5,4.55,3.03,6.85,4.6,2.3,1.57,4.32,3.5,6.05,5.8,1.73,2.3,2.7,4.85,2.9,7.65,0,4.8-2.07,8.83-6.2,12.1-4.13,3.27-9.27,4.9-15.4,4.9s-10.95-1.23-14.45-3.7-5.25-5.3-5.25-8.5,.98-5.78,2.95-7.75,4.42-2.95,7.35-2.95c1.53,0,2.97,.27,4.3,.8-2.13,2.27-3.2,4.67-3.2,7.2s.7,4.57,2.1,6.1c1.4,1.53,3.3,2.3,5.7,2.3s4.42-.62,6.05-1.85c1.63-1.23,2.45-2.83,2.45-4.8s-.57-3.71-1.7-5.25c-1.13-1.53-2.55-2.88-4.25-4.05-1.7-1.17-3.55-2.48-5.55-3.95-2-1.46-3.85-2.95-5.55-4.45-1.7-1.5-3.12-3.4-4.25-5.7-1.13-2.3-1.7-4.82-1.7-7.55,0-4.73,1.95-8.62,5.85-11.65,3.9-3.03,8.77-4.55,14.6-4.55s10.1,1.13,12.8,3.4c2.7,2.27,4.05,4.92,4.05,7.95s-.95,5.62-2.85,7.75c-1.9,2.13-4.35,3.2-7.35,3.2-1.47,0-3.13-.3-5-.9,1.47-1.27,2.6-2.71,3.4-4.35,.8-1.63,1.2-3.25,1.2-4.85s-.67-2.95-2-4.05c-1.33-1.1-2.93-1.65-4.8-1.65Z" style="fill:#b0dff6;"/><path d="M323.64,48.65c-1.63,9.5-2.47,17.33-2.5,23.5-.03,6.17,.35,10.15,1.15,11.95-5.2-1.33-8.7-3.35-10.5-6.05s-2.7-6.65-2.7-11.85c0-2.8,.53-7.87,1.6-15.2,1.07-7.33,1.6-13.03,1.6-17.1s-.2-7.2-.6-9.4c1.47-.13,2.83-.2,4.1-.2,3.87,0,6.55,.65,8.05,1.95,1.5,1.3,2.25,3.52,2.25,6.65,0,1-.82,6.25-2.45,15.75Zm3.95-33.15c-1.87,.27-3.5,.4-4.9,.4-6.87,0-10.3-2.4-10.3-7.2,0-1.73,.5-4.1,1.5-7.1,1.87-.27,3.5-.4,4.9-.4,6.87,0,10.3,2.4,10.3,7.2,0,1.73-.5,4.1-1.5,7.1Z" style="fill:#b0dff6;"/><path d="M360.09,23.5c.73,0,1.42,.22,2.05,.65,.63,.43,.95,.88,.95,1.35-6.73,4.67-10.77,11.17-12.1,19.5,1.4-3.73,3.12-6.97,5.15-9.7,2.03-2.73,4.15-4.8,6.35-6.2,4.2-2.6,8.3-3.9,12.3-3.9s7.4,1.97,10.2,5.9c2.8,3.93,4.2,9.2,4.2,15.8,0,7.4-1.5,14.2-4.5,20.4-3.34,7-8.07,11.83-14.2,14.5-3.47,1.53-7.27,2.3-11.4,2.3-5.8,0-10.85-2.25-15.15-6.75-4.3-4.5-6.45-10.97-6.45-19.4s2.07-15.87,6.2-22.3c4.13-6.43,9.6-10.48,16.4-12.15Zm8.5,9c-4.33,0-8.2,2.92-11.6,8.75-3.4,5.83-5.1,11.65-5.1,17.45,0,12.13,3.13,18.2,9.4,18.2,4.47,0,8.1-2.92,10.9-8.75,2.8-5.83,4.2-12.38,4.2-19.65,0-10.67-2.6-16-7.8-16Z" style="fill:#b0dff6;"/><path d="M438.99,24.9c3.93,0,6.75,1.22,8.45,3.65,1.7,2.43,2.55,5.73,2.55,9.9s-.65,10.02-1.95,17.55c-1.3,7.53-1.95,13.02-1.95,16.45s.43,5.93,1.3,7.5c.87,1.57,2.33,2.92,4.4,4.05-.87,.07-2.1,.1-3.7,.1-5.6,0-9.58-.87-11.95-2.6-2.37-1.73-3.55-4.77-3.55-9.1,0-2.47,.67-7.28,2-14.45,1.33-7.17,2-12.52,2-16.05,0-4.4-1.33-6.6-4-6.6-1.54,0-3.27,.9-5.2,2.7-1.93,1.8-3.8,4.25-5.6,7.35-1.8,3.1-3.3,7.08-4.5,11.95-1.2,4.87-1.8,9.72-1.8,14.55s.2,8.42,.6,10.75c-2.13,.13-3.54,.2-4.2,.2-3.8,0-6.53-.67-8.2-2-1.67-1.33-2.5-3.53-2.5-6.6,0-1.33,.7-6.46,2.1-15.4,1.4-8.93,2.1-16.05,2.1-21.35s-.57-10.18-1.7-14.65c4.87,1.53,8.27,3.72,10.2,6.55,1.93,2.83,2.9,6.07,2.9,9.7s-.4,7.85-1.2,12.65c2.2-7.46,5.67-13.8,10.4-19,4.73-5.2,9.07-7.8,13-7.8Z" style="fill:#b0dff6;"/><path d="M480.04,48.65c-1.63,9.5-2.47,17.33-2.5,23.5-.03,6.17,.35,10.15,1.15,11.95-5.2-1.33-8.7-3.35-10.5-6.05-1.8-2.7-2.7-6.65-2.7-11.85,0-2.8,.53-7.87,1.6-15.2,1.07-7.33,1.6-13.03,1.6-17.1s-.2-7.2-.6-9.4c1.47-.13,2.83-.2,4.1-.2,3.87,0,6.55,.65,8.05,1.95,1.5,1.3,2.25,3.52,2.25,6.65,0,1-.82,6.25-2.45,15.75Zm3.95-33.15c-1.87,.27-3.5,.4-4.9,.4-6.87,0-10.3-2.4-10.3-7.2,0-1.73,.5-4.1,1.5-7.1,1.87-.27,3.5-.4,4.9-.4,6.87,0,10.3,2.4,10.3,7.2,0,1.73-.5,4.1-1.5,7.1Z" style="fill:#b0dff6;"/><path d="M533.69,24.9c3.93,0,6.75,1.22,8.45,3.65,1.7,2.43,2.55,5.73,2.55,9.9s-.65,10.02-1.95,17.55c-1.3,7.53-1.95,13.02-1.95,16.45s.43,5.93,1.3,7.5c.87,1.57,2.33,2.92,4.4,4.05-.87,.07-2.1,.1-3.7,.1-5.6,0-9.58-.87-11.95-2.6-2.37-1.73-3.55-4.77-3.55-9.1,0-2.47,.67-7.28,2-14.45,1.33-7.17,2-12.52,2-16.05,0-4.4-1.33-6.6-4-6.6-1.54,0-3.27,.9-5.2,2.7-1.93,1.8-3.8,4.25-5.6,7.35-1.8,3.1-3.3,7.08-4.5,11.95-1.2,4.87-1.8,9.72-1.8,14.55s.2,8.42,.6,10.75c-2.13,.13-3.54,.2-4.2,.2-3.8,0-6.53-.67-8.2-2-1.67-1.33-2.5-3.53-2.5-6.6,0-1.33,.7-6.46,2.1-15.4,1.4-8.93,2.1-16.05,2.1-21.35s-.57-10.18-1.7-14.65c4.87,1.53,8.27,3.72,10.2,6.55,1.93,2.83,2.9,6.07,2.9,9.7s-.4,7.85-1.2,12.65c2.2-7.46,5.67-13.8,10.4-19,4.73-5.2,9.07-7.8,13-7.8Z" style="fill:#b0dff6;"/><path d="M588.59,25.9c1.27,0,2.23,.07,2.9,.2,1.73,.33,2.67,1.57,2.8,3.7-7.27,1.53-13.05,5.47-17.35,11.8-4.3,6.33-6.45,12.93-6.45,19.8,0,8,2.43,12,7.3,12,2.93,0,5.57-1.6,7.9-4.8,4.13-5.8,7.03-14.87,8.7-27.2,1.27-9.73,3.63-16,7.1-18.8,1.67-1.4,3.73-2.5,6.2-3.3-.93,7.07-1.4,12.4-1.4,16,0,26.67-2.85,46.7-8.55,60.1-5.7,13.4-14.98,20.1-27.85,20.1-5.2,0-9.27-1.38-12.2-4.15-2.93-2.77-4.4-5.8-4.4-9.1s1.03-6,3.1-8.1c2.07-2.1,4.96-3.15,8.7-3.15,1.53,0,3.1,.33,4.7,1-2.53,1.67-3.8,4.23-3.8,7.7,0,2.33,.7,4.35,2.1,6.05,1.4,1.7,3.12,2.55,5.15,2.55s3.82-.42,5.35-1.25c1.53-.83,3.1-2.32,4.7-4.45,1.6-2.13,3-4.8,4.2-8,2.73-7.4,4.23-17.5,4.5-30.3-1.93,5.73-4.87,10.33-8.8,13.8-3.93,3.47-7.93,5.2-12,5.2-5.13,0-9.05-1.95-11.75-5.85-2.7-3.9-4.05-8.88-4.05-14.95,0-7.07,1.7-13.63,5.1-19.7,3.8-7,9.07-11.87,15.8-14.6,3.73-1.53,7.83-2.3,12.3-2.3Z" style="fill:#b0dff6;"/></g></g></g> + </g> + <text x="1002" y="38" text-anchor="middle" fill="#a0b0c0" font-family="'IBM Plex Mono',monospace" font-size="10" letter-spacing="3">CONTROL PLANE ARCHITECTURE</text> + <line x1="872" y1="46" x2="1132" y2="46" stroke="#888" stroke-width="0.8"/> + + <!-- ═══ CLI ═══ --> + <g> + <rect x="235" y="104" width="140" height="68" rx="8" fill="url(#cli-grad)" filter="url(#soft-shadow)"/> + <rect x="235" y="104" width="140" height="68" rx="8" fill="none" stroke="#1c2a3a" stroke-width="1"/> + <rect x="235" y="107" width="3" height="62" rx="1.5" fill="#4a9eff" opacity="0.6"/> + <circle cx="256" cy="118" r="7" fill="#2d3748" stroke="#4a9eff" stroke-width="1" opacity="0.8"/> + <text x="256" y="121" text-anchor="middle" fill="#4a9eff" font-size="8" font-family="'IBM Plex Mono',monospace">$</text> + <text x="265" y="123" fill="#4a9eff" font-family="'DM Sans',sans-serif" font-weight="600" font-size="11">CLI</text> + <text x="270" y="148" fill="#6b8aaa" font-family="'IBM Plex Mono',monospace" font-size="8">provisioning</text> + </g> + + <!-- Arrow: CLI → Orchestrator --> + <line x1="640" y1="98" x2="640" y2="115" stroke="#4a9eff" stroke-width="1.5" stroke-dasharray="5 3" class="spoke" opacity="0.7" marker-end="url(#arr-blue)"/> + + + <!-- ═══════════════════════════════════════ --> + <!-- NATS ORBITAL RING SYSTEM --> + <!-- ═══════════════════════════════════════ --> + + <!-- Ring outer glow --> + <circle cx="640" cy="320" r="138" fill="none" stroke="#94a3b8" stroke-width="10" opacity="0.04" class="ring-glow" filter="url(#ring-glow-f)"/> + + <!-- Main orbit ring (flowing dashes) --> + <circle cx="640" cy="320" r="135" fill="none" stroke="#94a3b8" stroke-width="2" stroke-dasharray="10 8" class="orbit-flow" opacity="0.45"/> + + <!-- ═══ INFINITY ENGINE (∞ dual-wheel perpetual motion from logo) ═══ --> + <!-- Centered at (640,310), scale 0.55 from original logo coordinates --> + <!-- Left wheel center: (115.86, 113.25) | Right wheel center: (261.91, 113.28) --> + <g transform="translate(640, 310) scale(0.55) translate(-188.88, -113.27)" opacity="0.85"> + + <!-- Background circle with CLI gradient --> + <rect x="100" y="40" width="178" height="146" rx="12" style="fill:url(#cli-grad); opacity:0.35;"/> + <circle cx="188.88" cy="113.27" r="50" style="fill:url(#cli-grad); opacity:0.3;"/> + + <!-- Left wheel — clockwise 8s (outline only) --> + <g> + <path d="M3.04,87.53c-3.66,16.4-3.36,33.43,.85,49.7l18.49-.31c2.12,7.08,5.09,13.89,8.85,20.26l-12.81,13.12c9.22,14.08,21.56,25.9,36.09,34.55l12.81-13.13c6.56,3.51,13.52,6.22,20.74,8.08l.31,18.3c16.56,3.62,33.75,3.33,50.18-.85l-.31-18.3c7.15-2.1,14.02-5.04,20.46-8.77l13.25,12.69c14.22-9.14,26.14-21.36,34.87-35.75l-13.25-12.68c3.54-6.5,6.28-13.4,8.15-20.55l18.49-.31c3.65-16.4,3.36-33.43-.85-49.7l-18.49,.31c-2.12-7.08-5.09-13.88-8.85-20.26l12.8-13.12c-9.22-14.08-21.56-25.9-36.08-34.55l-12.81,13.13c-6.56-3.51-13.53-6.23-20.75-8.08l-.31-18.3c-16.56-3.62-33.75-3.33-50.18,.85l.31,18.3c-7.15,2.1-14.01,5.04-20.45,8.77l-13.25-12.69c-14.22,9.14-26.15,21.36-34.88,35.75l13.25,12.68c-3.54,6.5-6.28,13.4-8.15,20.55l-18.49,.31Z" style="fill:none; fill-rule:evenodd; stroke:#4cc2f1; stroke-miterlimit:10; stroke-width:1.2; opacity:0.85;"> + <animateTransform attributeName="transform" type="rotate" values="0 115.86 113.25;360 115.86 113.25" dur="8s" repeatCount="indefinite"/> + </path> + </g> + + <!-- Right wheel — counter-clockwise 6s (outline only) --> + <g> + <path d="M167.28,92.87c-3.19,14.31-2.94,29.17,.74,43.37l16.13-.27c1.85,6.18,4.44,12.12,7.72,17.68l-11.18,11.45c8.05,12.29,18.82,22.6,31.49,30.15l11.18-11.46c5.72,3.06,11.8,5.43,18.1,7.05l.27,15.97c14.45,3.16,29.45,2.91,43.78-.74l-.27-15.97c6.24-1.83,12.23-4.4,17.85-7.66l11.56,11.08c12.41-7.97,22.81-18.64,30.43-31.19l-11.56-11.06c3.09-5.67,5.48-11.69,7.11-17.93l16.13-.27c3.18-14.31,2.93-29.17-.74-43.37l-16.13,.27c-1.85-6.18-4.44-12.11-7.72-17.68l11.17-11.44c-8.04-12.29-18.81-22.6-31.48-30.15l-11.18,11.46c-5.72-3.07-11.8-5.43-18.1-7.05l-.27-15.97c-14.45-3.16-29.45-2.91-43.78,.74l.27,15.97c-6.24,1.83-12.23,4.4-17.84,7.66l-11.56-11.08c-12.41,7.97-22.81,18.64-30.44,31.19l11.56,11.06c-3.09,5.67-5.48,11.69-7.11,17.93l-16.13,.27Z" style="fill:none; fill-rule:evenodd; stroke:#4cc2f1; stroke-miterlimit:10; stroke-width:1.2; opacity:0.85;"> + <animateTransform attributeName="transform" type="rotate" values="0 261.91 113.28;-360 261.91 113.28" dur="6s" repeatCount="indefinite"/> + </path> + </g> + + <!-- Left wheel golden center (strong original colors) --> + <path d="M115.86,78.19c19.55,0,35.4,15.7,35.4,35.06s-15.85,35.06-35.4,35.06-35.4-15.7-35.4-35.06,15.85-35.06,35.4-35.06" style="fill:#f2b03f; opacity:0.9;"/> + <ellipse cx="116.42" cy="115.68" rx="20.17" ry="20.15" style="fill:none; stroke:#4159a4; stroke-miterlimit:10; stroke-width:1.58px; opacity:0.8;"/> + <path d="M116.42,107.88c4.35-.04,7.91,3.43,7.95,7.73,.04,4.31-3.46,7.83-7.81,7.87-4.35,.04-7.91-3.43-7.95-7.73,0-.02,0-.05,0-.07-.02-4.29,3.48-7.78,7.81-7.8" style="fill:#fff; opacity:0.8;"/> + + <!-- Right wheel golden center (strong original colors) --> + <ellipse cx="261.91" cy="113.28" rx="35.65" ry="35.31" style="fill:#f9b224; opacity:0.9;"/> + + <!-- Right wheel — dashboard display (simplified from logo chronometer) --> + <circle cx="261.91" cy="113.28" r="26" style="fill:#3d5070; opacity:0.85;"/> + <!-- Window controls (3 dots) --> + <circle cx="243" cy="95" r="1.2" style="fill:#d68a87; opacity:0.6;"/> + <circle cx="247" cy="95" r="1.2" style="fill:#c9a558; opacity:0.6;"/> + <circle cx="251" cy="95" r="1.2" style="fill:#5a7093; opacity:0.6;"/> + <!-- Terminal header bar --> + <rect x="243" y="99" width="38" height="3.2" rx="1.5" style="fill:#6a7a98; opacity:0.5;"/> + <!-- Status bars (staggered widths = data readout) --> + <rect x="243" y="105" width="28" height="2.8" rx="1" style="fill:#7a8aaa; opacity:0.5;"/> + <rect x="243" y="110" width="34" height="2.8" rx="1" style="fill:#a89870; opacity:0.5;"/> + <rect x="243" y="115" width="20" height="2.8" rx="1" style="fill:#6a7a8a; opacity:0.4;"/> + <rect x="243" y="120" width="30" height="2.8" rx="1" style="fill:#7a8aaa; opacity:0.4;"/> + <!-- Green health indicator --> + <circle cx="271" cy="127" r="5.5" style="fill:#4a9e8f; opacity:0.7;"> + <animate attributeName="opacity" values="0.7;0.4;0.7" dur="2s" repeatCount="indefinite"/> + </circle> + <!-- Animated checkmark --> + <path d="M268,126.5l2.5,2.5,4.2-4.2" style="fill:none; stroke:#a0c0b0; stroke-width:1.8; stroke-linecap:round; stroke-linejoin:round; opacity:0.8;"> + <animate attributeName="stroke-dasharray" values="0 15;15 15" dur="1.5s" repeatCount="indefinite"/> + </path> + + <!-- ∞ path segments — staggered color cycling --> + + <!-- Top left of ∞ --> + <path d="M225.8,83.92c1.22-1.35,2.46-2.66,3.75-3.94,16.69-16.54,43.22-18.12,61.79-3.68l17.46-2.65,2.57-16.63c-29.5-25.42-73.83-23.89-101.47,3.5-1.24,1.23-2.45,2.49-3.63,3.77l16.89,2.56,2.64,17.07Z" style="stroke:#5a7a90; stroke-miterlimit:10;"> + <animate attributeName="fill" values="#5e74b7;#00D4FF;#7B2BFF;#00FFB3;#5e74b7" dur="2s" repeatCount="indefinite"/> + <animate attributeName="stroke-width" values="0.5;2;0.5" dur="2s" repeatCount="indefinite"/> + </path> + + <!-- Bottom left of ∞ --> + <path d="M78.59,141.18c-14.54-18.4-12.95-44.65,3.71-61.2,1.33-1.31,2.73-2.54,4.2-3.68l-17.46-2.65-2.57-16.63c-1.3,1.13-2.58,2.28-3.82,3.5-27.79,27.53-28.96,71.57-3.53,100.51l16.79-2.55,2.67-17.3Z" style="stroke:#5a7a90; stroke-miterlimit:10;"> + <animate attributeName="fill" values="#5e74b7;#7B2BFF;#00FFB3;#00D4FF;#5e74b7" dur="2s" begin="0.5s" repeatCount="indefinite"/> + <animate attributeName="stroke-width" values="0.5;2;0.5" dur="2s" begin="0.5s" repeatCount="indefinite"/> + </path> + + <!-- Top right of ∞ --> + <path d="M86.5,76.3c18.57-14.44,45.1-12.86,61.79,3.68,1.29,1.27,2.53,2.6,3.75,3.94l2.64-17.07,16.89-2.56c-1.18-1.28-2.39-2.54-3.63-3.77-27.64-27.39-71.97-28.92-101.48-3.5l2.57,16.63,17.46,2.65Z" style="stroke:#5a7a90; stroke-miterlimit:10;"> + <animate attributeName="fill" values="#8d9ccf;#00FFB3;#00D4FF;#7B2BFF;#8d9ccf" dur="2s" begin="1s" repeatCount="indefinite"/> + <animate attributeName="stroke-width" values="0.5;2;0.5" dur="2s" begin="1s" repeatCount="indefinite"/> + </path> + + <!-- Bottom right of ∞ --> + <path d="M299.26,141.18c-15.88,20.08-45.19,23.61-65.46,7.88l-.04-.03-17.46,2.65-2.57,16.63c29.5,25.42,73.83,23.89,101.47-3.5,1.24-1.22,2.4-2.49,3.53-3.78l-16.79-2.55-2.68-17.3Z" style="stroke:#5a7a90; stroke-miterlimit:10;"> + <animate attributeName="fill" values="#8d9ccf;#7B2BFF;#00D4FF;#00FFB3;#8d9ccf" dur="2s" begin="1.5s" repeatCount="indefinite"/> + <animate attributeName="stroke-width" values="0.5;2;0.5" dur="2s" begin="1.5s" repeatCount="indefinite"/> + </path> + + <!-- ∞ connector arcs (static deep blue) --> + <path d="M315.19,60.52c-1.23-1.22-2.51-2.38-3.82-3.5l-2.57,16.63-17.46,2.65c1.47,1.14,2.88,2.37,4.2,3.68,16.66,16.55,18.25,42.8,3.71,61.2l2.68,17.3,16.79,2.55c25.42-28.93,24.26-72.98-3.53-100.5m-171.1,88.5c-20.2,15.76-49.48,12.31-65.39-7.7-.04-.05-.08-.1-.12-.15l-2.68,17.3-16.79,2.55c1.13,1.29,2.3,2.56,3.53,3.78,27.64,27.39,71.97,28.92,101.47,3.5l-2.57-16.63-17.46-2.65Z" style="fill:#455aa5; stroke:#3a5a7a; stroke-miterlimit:10;"/> + + <!-- ∞ transition pieces --> + <path d="M204.38,103.42c1.09,1.79,2.17,3.56,3.25,5.32,5.65-8.96,11.44-17.4,18.17-24.81l-2.64-17.07-16.89-2.56c-5.38,5.87-10.34,12.11-14.83,18.68,4.58,6.77,8.8,13.66,12.94,20.45m-30.92,18.5c-1.09-1.79-2.18-3.56-3.25-5.32-6.68,10.59-13.53,20.44-21.92,28.75-1.33,1.31-2.73,2.54-4.21,3.68l17.46,2.65,2.57,16.63c1.3-1.12,2.58-2.28,3.82-3.5,6.84-6.9,13.02-14.41,18.46-22.43-4.57-6.76-8.78-13.66-12.93-20.46" style="fill:#8d9ccf; stroke:#5a7a90; stroke-miterlimit:10;"/> + + <!-- Central flow — animated infinity gradient --> + <path d="M171.57,64.29c11.77,12.66,20.64,27.17,29.24,41.26,9,14.74,17.5,28.67,28.74,39.8,1.33,1.31,2.73,2.54,4.21,3.68l-17.46,2.65-2.57,16.63c-1.3-1.12-2.58-2.27-3.82-3.5-13.63-13.5-23.41-29.52-32.87-45.02-7.97-13.05-15.55-25.46-24.99-35.86l2.64-17.07,16.89-2.56Z" style="stroke:#5a7a90; stroke-miterlimit:10;"> + <animate attributeName="fill" values="#00D4FF;#7B2BFF;#00FFB3;#00D4FF" dur="3s" repeatCount="indefinite"/> + <animate attributeName="stroke-width" values="0.5;3;0.5" dur="3s" repeatCount="indefinite"/> + <animate attributeName="opacity" values="0.8;1;0.8" dur="3s" repeatCount="indefinite"/> + </path> + + </g> + + <!-- Hub labels (below infinity engine) --> + <text x="640" y="403" text-anchor="middle" fill="#94a3b8" font-family="'DM Sans',sans-serif" font-weight="700" font-size="15" letter-spacing="1.2" opacity="0.7">Events Stream</text> + <text x="640" y="429" text-anchor="middle" fill="#5a6278" font-family="'IBM Plex Mono',monospace" font-size="7">NATS JetStream</text> + <text x="640" y="440" text-anchor="middle" fill="#5a6278" font-family="'IBM Plex Mono',monospace" font-size="7">:4222</text> + + + <!-- ═══════════════════════════════════════ --> + <!-- SATELLITE SERVICES --> + <!-- ═══════════════════════════════════════ --> + + <!-- ─── ORCHESTRATOR (top, 0°) ─── --> + <g> + <rect x="505" y="60" width="270" height="110" rx="14" fill="none" stroke="#f59e0b" stroke-width="1.5" stroke-dasharray="8 4" class="solid-border"/> + <rect x="515" y="70" width="250" height="90" rx="10" fill="url(#orch-grad)" filter="url(#soft-shadow)"/> + <rect x="515" y="70" width="250" height="90" rx="10" fill="none" stroke="#3a2e1c" stroke-width="1"/> + <rect x="515" y="73" width="3" height="84" rx="1.5" fill="#f59e0b" opacity="0.5"/> + <circle cx="535" cy="92" r="8" fill="#2d3748" stroke="#f59e0b" stroke-width="1" opacity="0.8"/> + <text x="535" y="96" text-anchor="middle" fill="#f59e0b" font-size="9">▶</text> + <text x="553" y="95" fill="#f59e0b" font-family="'DM Sans',sans-serif" font-weight="700" font-size="13" letter-spacing="0.5">Orchestrator</text> + <text x="740" y="95" text-anchor="end" fill="#f59e0b" font-family="'IBM Plex Mono',monospace" font-size="8" opacity="0.5">:9011</text> + <circle cx="752" cy="92" r="3.5" fill="#f59e0b" class="health"/> + <g font-family="'IBM Plex Mono',monospace" font-size="8" fill="#a8a8a8"> + <rect x="528" y="113" width="220" height="14" rx="4" fill="#100b05" stroke="#2a2016" stroke-width="0.5"/> + <text x="638" y="123" text-anchor="middle" opacity="0.6">Task State Machine • Provider API • SSH</text> + <rect x="528" y="137" width="220" height="14" rx="4" fill="#100b05" stroke="#2a2016" stroke-width="0.5"/> + <text x="638" y="147" text-anchor="middle" opacity="0.55">Webhooks • Rollback • Audit Collector</text> + </g> + </g> + + <!-- ─── CONFIGURATION (top-right, cylinder) ─── --> + <g> + <ellipse cx="990" cy="152" rx="70" ry="11" fill="#10b981" opacity="0.12" stroke="none"/> + <path d="M 920 152 Q 990 179 1060 152" stroke="#10b981" stroke-width="0.8" fill="none" stroke-dasharray="4 2" opacity="0.4"/> + <rect x="920" y="91" width="140" height="61" rx="3" fill="#10b981" stroke="none" opacity="0.06" filter="url(#soft-shadow)"/> + <ellipse cx="990" cy="91" rx="70" ry="13" fill="#10b981" opacity="0.35" stroke="none"/> + <text x="990" y="126" text-anchor="middle" fill="#10b981" font-family="'DM Sans',sans-serif" font-weight="700" font-size="12" letter-spacing="0.5">Configuration</text> + <text x="990" y="152" text-anchor="middle" fill="#10b981" font-family="'IBM Plex Mono',monospace" font-size="7" opacity="0.6">Nickel • ∞ TypeDialog</text> + </g> + + <!-- ─── CONTROL CENTER (72°, upper-right) ─── --> + <g> + <rect x="241" y="248" width="250" height="110" rx="10" fill="url(#ctrl-grad)" filter="url(#soft-shadow)"/> + <rect x="241" y="248" width="250" height="110" rx="10" fill="none" stroke="#2e2848" stroke-width="1"/> + <rect x="241" y="251" width="3" height="104" rx="1.5" fill="#818cf8" opacity="0.5"/> + <circle cx="263" cy="270" r="8" fill="#2d3748" stroke="#818cf8" stroke-width="1" opacity="0.8"/> + <text x="263" y="274" text-anchor="middle" fill="#818cf8" font-size="9">◯</text> + <text x="279" y="273" fill="#818cf8" font-family="'DM Sans',sans-serif" font-weight="700" font-size="13" letter-spacing="0.5">Control Center</text> + <text x="461" y="273" text-anchor="end" fill="#818cf8" font-family="'IBM Plex Mono',monospace" font-size="8" opacity="0.5">:9012</text> + <circle cx="473" cy="270" r="3.5" fill="#818cf8" class="health health-d1"/> + <g font-family="'IBM Plex Mono',monospace" font-size="8" fill="#9090b8"> + <rect x="254" y="301" width="220" height="14" rx="4" fill="#070514" stroke="#1a1630" stroke-width="0.5"/> + <text x="364" y="311" text-anchor="middle" opacity="0.9">Cedar Policies • JWT • Sessions</text> + <rect x="254" y="325" width="220" height="14" rx="4" fill="#070514" stroke="#1a1630" stroke-width="0.5"/> + <text x="364" y="335" text-anchor="middle" opacity="0.85">WebSocket • RBAC • Solo: auto-session</text> + </g> + </g> + + <!-- ─── EXTENSION REGISTRY (144°, lower-right) ─── --> + <g> + <rect x="732" y="430" width="250" height="110" rx="10" fill="url(#ext-grad)" filter="url(#soft-shadow)"/> + <rect x="732" y="430" width="250" height="110" rx="10" fill="none" stroke="#3a1c28" stroke-width="1"/> + <rect x="978" y="433" width="3" height="104" rx="1.5" fill="#ef4444" opacity="0.5"/> + <circle cx="754" cy="452" r="8" fill="#2d3748" stroke="#ef4444" stroke-width="1" opacity="0.8"/> + <text x="754" y="456" text-anchor="middle" fill="#ef4444" font-size="9">⚙</text> + <text x="770" y="455" fill="#ef4444" font-family="'DM Sans',sans-serif" font-weight="700" font-size="12" letter-spacing="0.5">Extensions Registry</text> + <text x="952" y="455" text-anchor="end" fill="#ef4444" font-family="'IBM Plex Mono',monospace" font-size="8" opacity="0.5">:8084</text> + <circle cx="964" cy="452" r="3.5" fill="#ef4444" class="health health-d3"/> + <g font-family="'IBM Plex Mono',monospace" font-size="8" fill="#9a7878"> + <rect x="745" y="483" width="220" height="14" rx="4" fill="#10050a" stroke="#2a1620" stroke-width="0.5"/> + <text x="855" y="493" text-anchor="middle" opacity="0.9">Git • OCI • LRU</text> + <rect x="745" y="507" width="220" height="14" rx="4" fill="#10050a" stroke="#2a1620" stroke-width="0.5"/> + <text x="855" y="517" text-anchor="middle" opacity="0.85">Providers • Taskservs • vault:// creds</text> + </g> + </g> + + <!-- ─── AI/MCP (216°, lower-left) ─── --> + <g> + <rect x="298" y="430" width="250" height="110" rx="10" fill="url(#mcp-grad)" filter="url(#soft-shadow)"/> + <rect x="298" y="430" width="250" height="110" rx="10" fill="none" stroke="#1c3038" stroke-width="1"/> + <rect x="298" y="433" width="3" height="104" rx="1.5" fill="#22d3ee" opacity="0.5"/> + <circle cx="320" cy="452" r="8" fill="#2d3748" stroke="#22d3ee" stroke-width="1" opacity="0.8"/> + <text x="320" y="456" text-anchor="middle" fill="#22d3ee" font-size="9">🧠</text> + <text x="336" y="455" fill="#22d3ee" font-family="'DM Sans',sans-serif" font-weight="700" font-size="12" letter-spacing="0.5">AI • MCP</text> + <text x="523" y="455" text-anchor="end" fill="#22d3ee" font-family="'IBM Plex Mono',monospace" font-size="8" opacity="0.5">:9082</text> + <circle cx="535" cy="452" r="3.5" fill="#22d3ee" class="health health-d2"/> + <g font-family="'IBM Plex Mono',monospace" font-size="8" fill="#7aa8a8"> + <rect x="311" y="483" width="220" height="14" rx="4" fill="#041618" stroke="#0f2a2e" stroke-width="0.5"/> + <text x="421" y="493" text-anchor="middle" opacity="0.9">RAG Engine • MCP Server • Tools</text> + <rect x="311" y="507" width="220" height="14" rx="4" fill="#041618" stroke="#0f2a2e" stroke-width="0.5"/> + <text x="421" y="517" text-anchor="middle" opacity="0.85">Embeddings • Model Routing • KGraph</text> + </g> + </g> + + <!-- ─── VAULT SERVICE (288°, upper-left) ─── --> + <g> + <rect x="789" y="248" width="250" height="110" rx="10" fill="url(#vault-grad)" filter="url(#soft-shadow)"/> + <rect x="789" y="248" width="250" height="110" rx="10" fill="none" stroke="#3a1c30" stroke-width="1"/> + <rect x="1035" y="251" width="3" height="104" rx="1.5" fill="#ec4899" opacity="0.5"/> + <circle cx="811" cy="270" r="8" fill="#2d3748" stroke="#ec4899" stroke-width="1" opacity="0.8"/> + <text x="811" y="274" text-anchor="middle" fill="#ec4899" font-size="9">🛡</text> + <text x="827" y="273" fill="#ec4899" font-family="'DM Sans',sans-serif" font-weight="700" font-size="13" letter-spacing="0.5">Vault Service</text> + <text x="1009" y="273" text-anchor="end" fill="#ec4899" font-family="'IBM Plex Mono',monospace" font-size="8" opacity="0.5">:9094</text> + <circle cx="1021" cy="270" r="3.5" fill="#ec4899" class="health health-d4"/> + <g font-family="'IBM Plex Mono',monospace" font-size="8" fill="#9a8090"> + <rect x="802" y="301" width="220" height="14" rx="4" fill="#100514" stroke="#2a1630" stroke-width="0.5"/> + <text x="912" y="311" text-anchor="middle" opacity="0.9">Lease Lifecycle • Key Management</text> + <rect x="802" y="325" width="220" height="14" rx="4" fill="#100514" stroke="#2a1630" stroke-width="0.5"/> + <text x="912" y="335" text-anchor="middle" opacity="0.85">SOPS • Age • Secrets never in NATS</text> + </g> + </g> + + + <!-- ═══ VERTICAL ARROW: CONFIGURATION → VAULT ═══ --> + <line x1="995" y1="169" x2="995" y2="248" stroke="#10b981" stroke-width="1.5" stroke-dasharray="4 3" opacity="0.35" marker-end="url(#arr-green-sm)"/> + + <!-- ═══ VERTICAL ARROW: CLI → CONTROL CENTER ═══ --> + <line x1="305" y1="177" x2="305" y2="248" stroke="#10b981" stroke-width="1.5" stroke-dasharray="4 3" opacity="0.35" marker-end="url(#arr-green-sm)"/> + + <!-- ═══ CLI → ORCHESTRATOR CURVE ═══ --> + <path d="M 375 138 C 430 100 480 80 505 120" fill="none" stroke="#fbbf24" stroke-width="1.2" stroke-dasharray="3 2" opacity="0.5"/> + + <!-- ═══ CONFIGURATION → ORCHESTRATOR CURVE ═══ --> + <path d="M 920 128 C 850 90 800 70 775 120" fill="none" stroke="#fbbf24" stroke-width="1.2" stroke-dasharray="3 2" opacity="0.5"/> + + <!-- ═══ CLI → AI CURVE ═══ --> + <path d="M 235 138 C 150 250 200 350 300 444" fill="none" stroke="#fbbf24" stroke-width="1.2" stroke-dasharray="3 2" opacity="0.5"/> + + <!-- ═══ CONFIGURATION → EXT CURVE ═══ --> + <path d="M 1060 128 C 1145 240 1095 340 979 444" fill="none" stroke="#fbbf24" stroke-width="1.2" stroke-dasharray="3 2" opacity="0.5"/> + + <!-- ═══ HTTPS CREDENTIAL BEAM ═══ --> + <path d="M 505 168 C 450 187 420 217 447 245" fill="none" stroke="#4a9eff" stroke-width="2" class="https-beam" filter="url(#glow-blue)"/> + <path d="M 505 168 C 450 187 420 217 447 245" fill="none" stroke="#4a9eff" stroke-width="1" stroke-dasharray="4 3" class="https-beam" opacity="0.4"/> + <path d="M 447 245 C 420 217 450 187 505 168" fill="none" stroke="#4a9eff" stroke-width="2" class="https-beam" filter="url(#glow-blue)"/> + <path d="M 447 245 C 420 217 450 187 505 168" fill="none" stroke="#4a9eff" stroke-width="1" stroke-dasharray="4 3" class="https-beam" opacity="0.4"/> + <text x="475" y="209" fill="#4a9eff" font-family="'IBM Plex Mono',monospace" font-size="7" font-weight="700" class="https-label">HTTPS</text> + <path d="M 775 168 C 830 187 860 217 833 245" fill="none" stroke="#4a9eff" stroke-width="2" class="https-beam" filter="url(#glow-blue)"/> + <path d="M 775 168 C 830 187 860 217 833 245" fill="none" stroke="#4a9eff" stroke-width="1" stroke-dasharray="4 3" class="https-beam" opacity="0.4"/> + <path d="M 833 245 C 860 217 830 187 775 168" fill="none" stroke="#4a9eff" stroke-width="2" class="https-beam" filter="url(#glow-blue)"/> + <path d="M 833 245 C 860 217 830 187 775 168" fill="none" stroke="#4a9eff" stroke-width="1" stroke-dasharray="4 3" class="https-beam" opacity="0.4"/> + <text x="803" y="209" fill="#4a9eff" font-family="'IBM Plex Mono',monospace" font-size="7" font-weight="700" class="https-label">HTTPS</text> + + + <!-- ═══ ORBITAL PARTICLES ═══ --> + + <circle r="3.5" fill="#f59e0b" opacity="0" filter="url(#glow-orange)"> + <animateMotion dur="8s" begin="0s" repeatCount="indefinite"> + <mpath href="#orbit-cw"/> + </animateMotion> + <animate attributeName="opacity" values="0;0.9;0.9;0" keyTimes="0;0.05;0.92;1" dur="8s" repeatCount="indefinite"/> + </circle> + + <circle r="2.5" fill="#ec4899" opacity="0" filter="url(#glow-pink)"> + <animateMotion dur="10s" begin="2s" repeatCount="indefinite"> + <mpath href="#orbit-cw"/> + </animateMotion> + <animate attributeName="opacity" values="0;0.8;0.8;0" keyTimes="0;0.05;0.92;1" dur="10s" begin="2s" repeatCount="indefinite"/> + </circle> + + <circle r="3" fill="#4a9eff" opacity="0" filter="url(#glow-blue)"> + <animateMotion dur="7s" begin="1s" repeatCount="indefinite"> + <mpath href="#orbit-ccw"/> + </animateMotion> + <animate attributeName="opacity" values="0;0.85;0.85;0" keyTimes="0;0.05;0.92;1" dur="7s" begin="1s" repeatCount="indefinite"/> + </circle> + + <circle r="2.5" fill="#818cf8" opacity="0" filter="url(#glow-purple)"> + <animateMotion dur="9s" begin="4s" repeatCount="indefinite"> + <mpath href="#orbit-cw"/> + </animateMotion> + <animate attributeName="opacity" values="0;0.7;0.7;0" keyTimes="0;0.05;0.92;1" dur="9s" begin="4s" repeatCount="indefinite"/> + </circle> + + <circle r="3" fill="#22d3ee" opacity="0" filter="url(#glow-cyan)"> + <animateMotion dur="11s" begin="3s" repeatCount="indefinite"> + <mpath href="#orbit-ccw"/> + </animateMotion> + <animate attributeName="opacity" values="0;0.75;0.75;0" keyTimes="0;0.05;0.92;1" dur="11s" begin="3s" repeatCount="indefinite"/> + </circle> + + <circle r="2" fill="#ef4444" opacity="0" filter="url(#glow-orange)"> + <animateMotion dur="12s" begin="5s" repeatCount="indefinite"> + <mpath href="#orbit-cw"/> + </animateMotion> + <animate attributeName="opacity" values="0;0.7;0.7;0" keyTimes="0;0.05;0.92;1" dur="12s" begin="5s" repeatCount="indefinite"/> + </circle> + + + <!-- ═══ ORBITAL FLASH EFFECTS ═══ --> + <!-- Color-matched bursts: each fires when its orbital particle passes nearest the service box --> + + <!-- Orchestrator burst — amber #f59e0b, CW 8s begin=0s, particle at top at cycle boundary --> + <g> + <path d="M 640 183 L 645 178 L 635 174 L 640 170" fill="none" stroke="#f59e0b" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" filter="url(#glow-flash)"/> + <circle cx="640" cy="183" r="0" fill="#f59e0b" filter="url(#glow-flash)"> + <animate attributeName="r" values="10;0;0;10" keyTimes="0;0.15;0.85;1" dur="8s" begin="0s" repeatCount="indefinite"/> + </circle> + <animate attributeName="opacity" values="1;0;0;1" keyTimes="0;0.15;0.85;1" dur="8s" begin="0s" repeatCount="indefinite"/> + </g> + + <!-- Vault burst — pink #ec4899, CW 10s begin=2s, particle passes Vault at keyTime=0.23 --> + <g opacity="0"> + <path d="M 775 304 L 779 300 L 782 308 L 789 304" fill="none" stroke="#ec4899" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" filter="url(#glow-flash)"/> + <circle cx="775" cy="304" r="0" fill="#ec4899" filter="url(#glow-flash)"> + <animate attributeName="r" values="0;0;10;0;0" keyTimes="0;0.15;0.23;0.38;1" dur="10s" begin="2s" repeatCount="indefinite"/> + </circle> + <animate attributeName="opacity" values="0;0;1;0;0" keyTimes="0;0.15;0.23;0.38;1" dur="10s" begin="2s" repeatCount="indefinite"/> + </g> + + <!-- Extensions burst — red #ef4444, CW 12s begin=5s, particle passes Extensions at keyTime=0.38 --> + <g opacity="0"> + <path d="M 732 420 L 736 424 L 728 427 L 732 430" fill="none" stroke="#ef4444" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" filter="url(#glow-flash)"/> + <circle cx="732" cy="420" r="0" fill="#ef4444" filter="url(#glow-flash)"> + <animate attributeName="r" values="0;0;10;0;0" keyTimes="0;0.30;0.38;0.53;1" dur="12s" begin="5s" repeatCount="indefinite"/> + </circle> + <animate attributeName="opacity" values="0;0;1;0;0" keyTimes="0;0.30;0.38;0.53;1" dur="12s" begin="5s" repeatCount="indefinite"/> + </g> + + <!-- AI•MCP burst — cyan #22d3ee, CCW 11s begin=3s, particle passes AI at keyTime=0.38 --> + <g opacity="0"> + <path d="M 548 420 L 544 424 L 552 427 L 548 430" fill="none" stroke="#22d3ee" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" filter="url(#glow-flash)"/> + <circle cx="548" cy="420" r="0" fill="#22d3ee" filter="url(#glow-flash)"> + <animate attributeName="r" values="0;0;10;0;0" keyTimes="0;0.30;0.38;0.53;1" dur="11s" begin="3s" repeatCount="indefinite"/> + </circle> + <animate attributeName="opacity" values="0;0;1;0;0" keyTimes="0;0.30;0.38;0.53;1" dur="11s" begin="3s" repeatCount="indefinite"/> + </g> + + <!-- Control Center burst — purple #818cf8, CW 9s begin=4s, particle passes CC at keyTime=0.75 --> + <g opacity="0"> + <path d="M 505 320 L 500 315 L 498 325 L 491 320" fill="none" stroke="#818cf8" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" filter="url(#glow-flash)"/> + <circle cx="505" cy="320" r="0" fill="#818cf8" filter="url(#glow-flash)"> + <animate attributeName="r" values="0;0;10;0;0" keyTimes="0;0.67;0.75;0.90;1" dur="9s" begin="4s" repeatCount="indefinite"/> + </circle> + <animate attributeName="opacity" values="0;0;1;0;0" keyTimes="0;0.67;0.75;0.90;1" dur="9s" begin="4s" repeatCount="indefinite"/> + </g> + + + <!-- ═══ CREDENTIAL FLOW ═══ --> + + <circle r="4" fill="#f59e0b" opacity="0" filter="url(#glow-gold)"> + <animateMotion dur="2.5s" begin="0s" repeatCount="indefinite"> + <mpath href="#path-orch-vault"/> + </animateMotion> + <animate attributeName="opacity" values="0;0.95;0.95;0" keyTimes="0;0.1;0.8;1" dur="2.5s" repeatCount="indefinite"/> + </circle> + + <circle r="3.5" fill="#ec4899" opacity="0" filter="url(#glow-pink)"> + <animateMotion dur="2.5s" begin="4s" repeatCount="indefinite"> + <mpath href="#path-vault-orch"/> + </animateMotion> + <animate attributeName="opacity" values="0;0.9;0.9;0" keyTimes="0;0.1;0.8;1" dur="2.5s" begin="4s" repeatCount="indefinite"/> + </circle> + + <circle r="3" fill="#4a9eff" opacity="0" filter="url(#glow-blue)"> + <animateMotion dur="1.2s" begin="0s" repeatCount="indefinite"> + <mpath href="#path-cli-orch"/> + </animateMotion> + <animate attributeName="opacity" values="0;0.85;0.85;0" keyTimes="0;0.15;0.7;1" dur="1.2s" repeatCount="indefinite"/> + </circle> + + + <!-- ═══ CONFIGURATION CYLINDER EDGES ═══ --> + <line x1="920" y1="95" x2="920" y2="156" stroke="#10b981" stroke-width="0.8" stroke-dasharray="4 2" opacity="0.4"/> + <line x1="1060" y1="95" x2="1060" y2="156" stroke="#10b981" stroke-width="0.8" stroke-dasharray="4 2" opacity="0.4"/> + + <!-- ═══ DATA PERSISTENCE CYLINDER EDGES ═══ --> + <line x1="210" y1="582" x2="210" y2="669" stroke="#a78bfa" stroke-width="0.8" stroke-dasharray="4 2" opacity="0.4"/> + <line x1="1070" y1="582" x2="1070" y2="669" stroke="#a78bfa" stroke-width="0.8" stroke-dasharray="4 2" opacity="0.4"/> + + <!-- ═══ CONNECTIONS TO SURREALDB ═══ --> + <g opacity="0.3"> + <line x1="640" y1="460" x2="640" y2="570" stroke="#a78bfa" stroke-width="1" stroke-dasharray="4 3" class="db-flow" marker-end="url(#arr-silver)"/> + <line x1="430" y1="537" x2="430" y2="571" stroke="#a78bfa" stroke-width="0.8" stroke-dasharray="4 3" class="db-flow" marker-end="url(#arr-silver)"/> + <line x1="850" y1="537" x2="850" y2="571" stroke="#a78bfa" stroke-width="0.8" stroke-dasharray="4 3" class="db-flow" marker-end="url(#arr-silver)"/> + <path d="M 377 358 C 280 370 230 480 240 572" fill="none" stroke="#a78bfa" stroke-width="0.8" stroke-dasharray="4 3" class="db-flow" marker-end="url(#arr-silver)"/> + <path d="M 903 358 C 1000 370 1050 480 1040 572" fill="none" stroke="#a78bfa" stroke-width="0.8" stroke-dasharray="4 3" class="db-flow" marker-end="url(#arr-silver)"/> + <path d="M 1060 128 C 1195 270 1145 410 1070 577" fill="none" stroke="#a78bfa" stroke-width="0.8" stroke-dasharray="4 3" class="db-flow" marker-end="url(#arr-silver)"/> + <path d="M 235 138 C 100 280 150 420 210 579" fill="none" stroke="#a78bfa" stroke-width="0.8" stroke-dasharray="4 3" class="db-flow" marker-end="url(#arr-silver)"/> + </g> + + + <!-- ═══ DATA PERSISTENCE ═══ --> + + <g> + <ellipse cx="640" cy="669" rx="430" ry="10" fill="#2e2640" stroke="none" opacity="0.35"/> + <path d="M 210 669 Q 640 686 1070 669" stroke="#a78bfa" stroke-width="1" fill="none" stroke-dasharray="4 2" opacity="0.5"/> + <rect x="210" y="582" width="860" height="87" rx="8" fill="url(#surreal-grad)" stroke="none" opacity="0.3" filter="url(#soft-shadow)"/> + <ellipse cx="640" cy="582" rx="430" ry="12" fill="#a78bfa" opacity="0.45" stroke="none"/> + + <text x="640" y="617" text-anchor="middle" fill="#a78bfa" font-family="'DM Sans',sans-serif" font-weight="700" font-size="11" letter-spacing="1">DATA PERSISTENCE</text> + <text x="1055" y="607" text-anchor="end" fill="#c4a8fc" font-family="'IBM Plex Mono',monospace" font-size="7" opacity="0.7">Solo: RocksDB • Multi: WebSocket</text> + + <g font-family="'IBM Plex Mono',monospace" font-size="7" font-weight="600"> + <rect x="258" y="637" width="130" height="16" rx="3" fill="#f59e0b" stroke="#f59e0b" stroke-width="0.8" opacity="0.25"/> + <text x="323" y="648" text-anchor="middle" fill="#f59e0b">orchestrator</text> + + <rect x="400" y="637" width="110" height="16" rx="3" fill="#ec4899" stroke="#ec4899" stroke-width="0.8" opacity="0.25"/> + <text x="455" y="648" text-anchor="middle" fill="#ec4899">vault</text> + + <rect x="522" y="637" width="140" height="16" rx="3" fill="#818cf8" stroke="#818cf8" stroke-width="0.8" opacity="0.25"/> + <text x="592" y="648" text-anchor="middle" fill="#818cf8">control_center</text> + + <rect x="674" y="637" width="90" height="16" rx="3" fill="#10b981" stroke="#10b981" stroke-width="0.8" opacity="0.25"/> + <text x="719" y="648" text-anchor="middle" fill="#10b981">audit</text> + + <rect x="776" y="637" width="130" height="16" rx="3" fill="#4a9eff" stroke="#4a9eff" stroke-width="0.8" opacity="0.25"/> + <text x="841" y="648" text-anchor="middle" fill="#4a9eff">workspace</text> + + <rect x="918" y="637" width="100" height="16" rx="3" fill="none" stroke="#10b981" stroke-width="0.6" stroke-dasharray="4 2" opacity="0.5"/> + <text x="968" y="648" text-anchor="middle" fill="#10b981">Nickel config</text> + </g> + </g> + + + <!-- ═══ SOLID ENFORCEMENT ═══ --> + + <g transform="translate(120, 759)"> + <text x="0" y="0" fill="#3a3e48" font-family="'IBM Plex Mono',monospace" font-weight="600" font-size="8" letter-spacing="1.5">SOLID ENFORCEMENT LAYERS</text> + <g font-family="'IBM Plex Mono',monospace" font-size="7"> + <rect x="0" y="8" width="80" height="12" rx="3" fill="#f59e0b" opacity="0.18" stroke="#f59e0b" stroke-width="0.5" stroke-opacity="0.5"/> + <text x="40" y="17" text-anchor="middle" fill="#f59e0b" opacity="0.8">Compile-time</text> + <rect x="88" y="8" width="68" height="12" rx="3" fill="#818cf8" opacity="0.18" stroke="#818cf8" stroke-width="0.5" stroke-opacity="0.5"/> + <text x="122" y="17" text-anchor="middle" fill="#818cf8" opacity="0.8">Dev-time</text> + <rect x="164" y="8" width="74" height="12" rx="3" fill="#ec4899" opacity="0.18" stroke="#ec4899" stroke-width="0.5" stroke-opacity="0.5"/> + <text x="201" y="17" text-anchor="middle" fill="#ec4899" opacity="0.8">Pre-commit</text> + <rect x="246" y="8" width="50" height="12" rx="3" fill="#ef4444" opacity="0.18" stroke="#ef4444" stroke-width="0.5" stroke-opacity="0.5"/> + <text x="271" y="17" text-anchor="middle" fill="#ef4444" opacity="0.8">CI/CD</text> + <rect x="304" y="8" width="60" height="12" rx="3" fill="#22d3ee" opacity="0.18" stroke="#22d3ee" stroke-width="0.5" stroke-opacity="0.5"/> + <text x="334" y="17" text-anchor="middle" fill="#22d3ee" opacity="0.8">Runtime</text> + <rect x="372" y="8" width="50" height="12" rx="3" fill="#94a3b8" opacity="0.18" stroke="#94a3b8" stroke-width="0.5" stroke-opacity="0.5"/> + <text x="397" y="17" text-anchor="middle" fill="#94a3b8" opacity="0.6">Audit</text> + </g> + <g font-family="'IBM Plex Mono',monospace" font-size="7" fill="#e2e6ee" opacity="0.6"> + <text x="460" y="16" font-family="'IBM Plex Mono',monospace" font-size="7"><tspan fill="#f59e0b">Providers APIs or CLI</tspan><tspan fill="#e2e6ee">: Orchestrator only | </tspan><tspan fill="#818cf8">SSH</tspan><tspan fill="#e2e6ee">: Orchestrator + Machines | </tspan><tspan fill="#22d3ee">Auth</tspan><tspan fill="#e2e6ee">: Control Center only | </tspan><tspan fill="#ec4899">Secrets</tspan><tspan fill="#e2e6ee">: Vault Service API only</tspan></text> + </g> + </g> + + + <!-- ═══ LEGEND ═══ --> + + <g transform="translate(120, 802)" font-family="'IBM Plex Mono',monospace" font-size="8"> + <text x="0" y="0" fill="#3a3e48" font-weight="600" font-size="8" letter-spacing="1.5">SERVICES</text> + <circle cx="7" cy="12" r="3" fill="#f59e0b"/><text x="15" y="16" fill="#6b6050">Orchestrator</text> + <circle cx="7" cy="24" r="3" fill="#818cf8"/><text x="15" y="28" fill="#9090b8">Control Center</text> + <circle cx="7" cy="36" r="3" fill="#ec4899"/><text x="15" y="40" fill="#5a3850">Vault Service</text> + <circle cx="7" cy="48" r="3" fill="#ef4444"/><text x="15" y="52" fill="#5a3848">Extension Registry</text> + <circle cx="7" cy="60" r="3" fill="#22d3ee"/><text x="15" y="64" fill="#385a58">AI • MCP</text> + + <text x="240" y="0" fill="#3a3e48" font-weight="600" font-size="8" letter-spacing="1.5">INFRASTRUCTURE</text> + <circle cx="248" cy="12" r="5" fill="none" stroke="#94a3b8" stroke-width="1.2" stroke-dasharray="2 2"/> + <text x="260" y="16" fill="#5a6278" opacity="0.5">NATS Orbital Ring</text> + <rect x="243" y="22.5" width="6" height="6" fill="none" stroke="#ef4444" stroke-width="0.8" rx="0.5"/> + <line x1="243" y1="25.5" x2="249" y2="25.5" stroke="#ef4444" stroke-width="0.7"/> + <line x1="246" y1="22.5" x2="246" y2="28.5" stroke="#ef4444" stroke-width="0.7" opacity="0.6"/> + <text x="260" y="28" fill="#ef4444" opacity="0.5">OCI registry</text> + <line x1="240" y1="36" x2="254" y2="36" stroke="#818cf8" stroke-width="1.5"/> + <text x="260" y="40" fill="#818cf8" opacity="0.5">Token and Auth</text> + <path d="M 244 43 L 250 43 L 250 48 Q 247 52 244 48 L 244 43" fill="none" stroke="#ec4899" stroke-width="1.2" stroke-linejoin="round"/> + <text x="260" y="52" fill="#ec4899" opacity="0.5">SecretumVault</text> + <line x1="240" y1="60" x2="254" y2="60" stroke="#10b981" stroke-width="1.5"/> + <text x="260" y="64" fill="#10b981" opacity="0.5">Nickel</text> + <ellipse cx="247" cy="69" rx="5" ry="2" fill="none" stroke="#a78bfa" stroke-width="0.8"/> + <rect x="243" y="69" width="8" height="5" fill="none" stroke="#a78bfa" stroke-width="0.8" opacity="0.5"/> + <ellipse cx="247" cy="74" rx="5" ry="2" fill="#a78bfa" opacity="0.3" stroke="#a78bfa" stroke-width="0.8"/> + <text x="260" y="76" fill="#a78bfa" opacity="0.5">SurrealDB</text> + + <text x="460" y="0" fill="#3a3e48" font-weight="600" font-size="8" letter-spacing="1.5">CONNECTIONS</text> + <line x1="460" y1="12" x2="474" y2="12" stroke="#4a9eff" stroke-width="2" filter="url(#glow-blue)"/> + <line x1="460" y1="12" x2="474" y2="12" stroke="#4a9eff" stroke-width="1" stroke-dasharray="4 3" opacity="0.7"/> + <text x="486" y="16" fill="#4a9eff" opacity="0.5">Encrypted</text> + <line x1="460" y1="24" x2="474" y2="24" stroke="#ef9a3c" stroke-width="1.5" stroke-dasharray="2 2"/> + <text x="486" y="28" fill="#ef9a3c" opacity="0.3">I/O and Defs</text> + <line x1="460" y1="36" x2="474" y2="36" stroke="#a78bfa" stroke-width="1.5" stroke-dasharray="2 2"/> + <text x="486" y="40" fill="#5a4878">DBs data</text> + <line x1="460" y1="48" x2="474" y2="48" stroke="#10b981" stroke-width="1.5" stroke-dasharray="2 2"/> + <text x="486" y="52" fill="#10b981" opacity="0.5">Secure access</text> + <circle cx="468" cy="60" r="5" fill="none" stroke="#94a3b8" stroke-width="1.2" stroke-dasharray="2 2"/> + <text x="486" y="64" fill="#5a6278">Events Stream</text> + + <text x="685" y="0" fill="#3a3e48" font-weight="600" font-size="8" letter-spacing="1.5">MODES</text> + <rect x="685" y="11" width="40" height="10" rx="2" fill="#34d399" opacity="0.1" stroke="#34d399" stroke-width="0.4"/> + <text x="705" y="19" text-anchor="middle" fill="#34d399" font-size="7" font-weight="600">SOLO</text> + <text x="735" y="19" fill="#4a7a5a" font-size="7">RocksDB + local NATS + auto-session</text> + <rect x="685" y="25" width="40" height="10" rx="2" fill="#818cf8" opacity="0.1" stroke="#818cf8" stroke-width="0.4"/> + <text x="705" y="33" text-anchor="middle" fill="#818cf8" font-size="7" font-weight="600">MULTI</text> + <text x="735" y="33" fill="#9090b8" font-size="7">WebSocket DB + NATS cluster + JWT+Cedar</text> + <rect x="685" y="39" width="40" height="10" rx="2" fill="#f59e0b" opacity="0.1" stroke="#f59e0b" stroke-width="0.4"/> + <text x="705" y="47" text-anchor="middle" fill="#f59e0b" font-size="7" font-weight="600">ENT</text> + <text x="735" y="47" fill="#d97706" font-size="7">Enterprise</text> + </g> + + + <!-- ═══ DEPLOYMENT ═══ --> + + <g transform="translate(145, 693)" font-family="'IBM Plex Mono',monospace"> + <rect x="160" y="0" width="120" height="45" rx="6" fill="#2a1515" stroke="#2a1010" stroke-width="0.8"/> + <circle cx="174" cy="15" r="3" fill="#ef4444"/><text x="182" y="18" fill="#d0a0a0" font-size="8" font-weight="500">Production</text> + <text x="209" y="35" fill="#5a3838" font-size="7" text-anchor="middle">Hetzner • AWS</text> + + <rect x="300" y="0" width="120" height="45" rx="6" fill="#2a2010" stroke="#1a1810" stroke-width="0.8"/> + <circle cx="314" cy="15" r="3" fill="#f59e0b"/><text x="322" y="18" fill="#d0c0a0" font-size="8" font-weight="500">Staging</text> + <text x="344" y="35" fill="#5a5038" font-size="7" text-anchor="middle">K8s cluster</text> + + <rect x="440" y="0" width="120" height="45" rx="6" fill="#1a2820" stroke="#0a1810" stroke-width="0.8"/> + <circle cx="454" cy="15" r="3" fill="#34d399"/><text x="462" y="18" fill="#a0d0b8" font-size="8" font-weight="500">Dev</text> + <text x="484" y="35" fill="#385a48" font-size="7" text-anchor="middle">Solo mode</text> + + <rect x="580" y="0" width="120" height="45" rx="6" fill="#1a182a" stroke="#0a0a1a" stroke-width="0.8"/> + <circle cx="594" cy="15" r="3" fill="#818cf8"/><text x="602" y="18" fill="#b0b0d8" font-size="8" font-weight="500">Edge</text> + <text x="624" y="35" fill="#48486a" font-size="7" text-anchor="middle">On-prem • IoT</text> + + <rect x="720" y="0" width="120" height="45" rx="6" fill="#1a2020" stroke="#0a1010" stroke-width="0.8"/> + <circle cx="734" cy="15" r="3" fill="#22d3ee"/><text x="742" y="18" fill="#a0c8c8" font-size="8" font-weight="500">Custom</text> + <text x="774" y="35" fill="#385858" font-size="7" text-anchor="middle">GitOps • Webhook</text> + </g> + + <!-- ═══ VERSION ═══ --> + <text x="1160" y="872" text-anchor="end" fill="#b0dff6" font-family="'IBM Plex Mono',monospace" font-size="9" letter-spacing="0.5">v3.0.11 • ∞ Architecture</text> +</svg> diff --git a/site/site/public/_content/projects/en/provisioning-systems/images/provisioning-systems_arch-light.svg b/site/site/public/_content/projects/en/provisioning-systems/images/provisioning-systems_arch-light.svg new file mode 100644 index 0000000..4291305 --- /dev/null +++ b/site/site/public/_content/projects/en/provisioning-systems/images/provisioning-systems_arch-light.svg @@ -0,0 +1,703 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 900"> + <style> + .orbit-flow { animation: orbit-dash 4s linear infinite; } + @keyframes orbit-dash { from { stroke-dashoffset: 40; } to { stroke-dashoffset: 0; } } + + .ring-glow { animation: ring-breathe 3s ease-in-out infinite; } + @keyframes ring-breathe { 0%,100% { opacity: 0.06; } 50% { opacity: 0.18; } } + + .spoke { animation: spoke-dash 2s linear infinite; } + @keyframes spoke-dash { from { stroke-dashoffset: 16; } to { stroke-dashoffset: 0; } } + + .health { animation: pulse 2.2s ease-in-out infinite; } + .health-d1 { animation-delay: 0.4s; } + .health-d2 { animation-delay: 0.8s; } + .health-d3 { animation-delay: 1.2s; } + .health-d4 { animation-delay: 1.6s; } + @keyframes pulse { 0%,100% { opacity: 0.35; } 50% { opacity: 1; } } + + .https-beam { animation: https-p 4s ease-in-out infinite; } + @keyframes https-p { 0%,100% { opacity: 0.15; stroke-width: 1.5; } 50% { opacity: 0.7; stroke-width: 2.5; } } + .https-label { animation: https-lbl 4s ease-in-out infinite; } + @keyframes https-lbl { 0%,100% { opacity: 0.3; } 50% { opacity: 1; } } + + .solid-border { animation: solid-glow 3.5s ease-in-out infinite; } + @keyframes solid-glow { 0%,100% { stroke-opacity: 0.3; } 50% { stroke-opacity: 0.7; } } + + .hub-pulse { animation: hub-beat 3s ease-in-out infinite; } + @keyframes hub-beat { 0%,100% { opacity: 0.3; } 50% { opacity: 0.6; } } + + .cred-dot { opacity: 0; } + .db-flow { animation: db-dash 2.5s linear infinite; } + @keyframes db-dash { from { stroke-dashoffset: 12; } to { stroke-dashoffset: 0; } } + </style> + + <defs> + <filter id="glow-blue" x="-50%" y="-50%" width="200%" height="200%"> + <feGaussianBlur in="SourceGraphic" stdDeviation="6" result="blur"/> + <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge> + </filter> + <filter id="glow-orange" x="-50%" y="-50%" width="200%" height="200%"> + <feGaussianBlur in="SourceGraphic" stdDeviation="5" result="blur"/> + <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge> + </filter> + <filter id="glow-pink" x="-50%" y="-50%" width="200%" height="200%"> + <feGaussianBlur in="SourceGraphic" stdDeviation="5" result="blur"/> + <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge> + </filter> + <filter id="glow-purple" x="-50%" y="-50%" width="200%" height="200%"> + <feGaussianBlur in="SourceGraphic" stdDeviation="5" result="blur"/> + <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge> + </filter> + <filter id="glow-cyan" x="-50%" y="-50%" width="200%" height="200%"> + <feGaussianBlur in="SourceGraphic" stdDeviation="5" result="blur"/> + <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge> + </filter> + <filter id="glow-gold" x="-50%" y="-50%" width="200%" height="200%"> + <feGaussianBlur in="SourceGraphic" stdDeviation="6" result="blur"/> + <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge> + </filter> + <filter id="glow-flash" x="-50%" y="-50%" width="200%" height="200%"> + <feGaussianBlur in="SourceGraphic" stdDeviation="4" result="blur"/> + <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge> + </filter> + <filter id="soft-shadow" x="-20%" y="-20%" width="140%" height="140%"> + <feDropShadow dx="0" dy="2" stdDeviation="8" flood-color="#000" flood-opacity="0.12"/> + </filter> + <filter id="ring-glow-f" x="-20%" y="-20%" width="140%" height="140%"> + <feGaussianBlur in="SourceGraphic" stdDeviation="8" result="blur"/> + <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge> + </filter> + + <radialGradient id="bg-radial" cx="50%" cy="42%" r="55%"> + <stop offset="0%" stop-color="#ffffff"/> + <stop offset="100%" stop-color="#ffffff"/> + </radialGradient> + <radialGradient id="hub-glow" cx="50%" cy="50%" r="50%"> + <stop offset="0%" stop-color="#94a3b8" stop-opacity="0.15"/> + <stop offset="100%" stop-color="#94a3b8" stop-opacity="0"/> + </radialGradient> + <linearGradient id="orch-grad" x1="0%" y1="0%" x2="100%" y2="100%"> + <stop offset="0%" stop-color="#fff8ed"/><stop offset="100%" stop-color="#fef3e0"/> + </linearGradient> + <linearGradient id="ctrl-grad" x1="0%" y1="0%" x2="100%" y2="100%"> + <stop offset="0%" stop-color="#f0eeff"/><stop offset="100%" stop-color="#e8e5fa"/> + </linearGradient> + <linearGradient id="vault-grad" x1="0%" y1="0%" x2="100%" y2="100%"> + <stop offset="0%" stop-color="#fef0f5"/><stop offset="100%" stop-color="#fce8f0"/> + </linearGradient> + <linearGradient id="ext-grad" x1="0%" y1="0%" x2="100%" y2="100%"> + <stop offset="0%" stop-color="#f7ede7"/><stop offset="100%" stop-color="#f1e4dc"/> + </linearGradient> + <linearGradient id="mcp-grad" x1="0%" y1="0%" x2="100%" y2="100%"> + <stop offset="0%" stop-color="#edf9fc"/><stop offset="100%" stop-color="#e5f5f8"/> + </linearGradient> + <linearGradient id="surreal-grad" x1="0%" y1="0%" x2="100%" y2="100%"> + <stop offset="0%" stop-color="#f3f0fe"/><stop offset="100%" stop-color="#eee8fc"/> + </linearGradient> + <linearGradient id="cli-grad" x1="0%" y1="0%" x2="100%" y2="100%"> + <stop offset="0%" stop-color="#edf2fa"/><stop offset="100%" stop-color="#e5ecf5"/> + </linearGradient> + + <!-- Animated gradient for infinity engine color cycling --> + <linearGradient id="infinityGradient" x1="0%" y1="0%" x2="100%" y2="0%"> + <stop offset="0%" style="stop-color:#00D4FF;stop-opacity:1"> + <animate attributeName="stop-color" values="#00D4FF;#7B2BFF;#00FFB3;#00D4FF" dur="3s" repeatCount="indefinite"/> + </stop> + <stop offset="50%" style="stop-color:#7B2BFF;stop-opacity:1"> + <animate attributeName="stop-color" values="#7B2BFF;#00FFB3;#00D4FF;#7B2BFF" dur="3s" repeatCount="indefinite"/> + </stop> + <stop offset="100%" style="stop-color:#00FFB3;stop-opacity:1"> + <animate attributeName="stop-color" values="#00FFB3;#00D4FF;#7B2BFF;#00FFB3" dur="3s" repeatCount="indefinite"/> + </stop> + </linearGradient> + + <marker id="arr-blue" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"> + <polygon points="0 0, 8 3, 0 6" fill="#4a9eff"/> + </marker> + <marker id="arr-silver" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"> + <polygon points="0 0, 8 3, 0 6" fill="#94a3b8"/> + </marker> + <marker id="arr-gold" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"> + <polygon points="0 0, 8 3, 0 6" fill="#fbbf24"/> + </marker> + <marker id="arr-green" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"> + <polygon points="0 0, 8 3, 0 6" fill="#10b981"/> + </marker> + <marker id="arr-green-sm" markerWidth="5" markerHeight="4" refX="5" refY="2" orient="auto"> + <polygon points="0 0, 5 2, 0 4" fill="#10b981"/> + </marker> + + <path id="orbit-cw" d="M 640,185 A 135,135 0 1,1 640,455 A 135,135 0 1,1 640,185 Z" fill="none"/> + <path id="orbit-ccw" d="M 640,185 A 135,135 0 1,0 640,455 A 135,135 0 1,0 640,185 Z" fill="none"/> + + <path id="path-orch-vault" d="M 640,185 A 135,135 0 0,0 508,276" fill="none"/> + <path id="path-vault-orch" d="M 508,276 A 135,135 0 0,1 640,185" fill="none"/> + <path id="path-cli-orch" d="M 660,98 L 660,115" fill="none"/> + </defs> + + <!-- ═══ BACKGROUND ═══ --> + <rect width="1280" height="900" fill="url(#bg-radial)"/> + <g opacity="0.03"> + <pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse"> + <path d="M 40 0 L 0 0 0 40" fill="none" stroke="#ccc" stroke-width="0.5"/> + </pattern> + <rect width="1280" height="900" fill="url(#grid)"/> + </g> + + <!-- Star field --> + <g opacity="0.35"> + <circle cx="95" cy="145" r="0.8" fill="#fff"/><circle cx="340" cy="78" r="0.6" fill="#fff"/> + <circle cx="890" cy="115" r="1" fill="#fff"/><circle cx="1120" cy="210" r="0.7" fill="#fff"/> + <circle cx="180" cy="460" r="0.5" fill="#fff"/><circle cx="1160" cy="510" r="0.8" fill="#fff"/> + <circle cx="72" cy="660" r="0.6" fill="#fff"/><circle cx="960" cy="710" r="0.9" fill="#fff"/> + </g> + + <!-- ═══ TITLE ═══ --> + <g transform="translate(206, 20) scale(0.35)"> + <g id="logo-content"><g><g><path d="M601.85,109.76c.89,.04,.57-.38,.7-.46-1.49,.09-.67,.57-.7,.46Z" style="fill:#4cc2f1;"/><polygon points="587.17 110.72 587.43 110.55 585.26 110.96 587.17 110.72" style="fill:#4cc2f1;"/><path d="M573.43,110.71c1.3,.71,4.98-.12,7.42,.14l.4,.16,1.1-.57c-2.97,.09-6.84,.47-8.93,.27Z" style="fill:#4cc2f1;"/><path d="M566.11,109.16c.45,.28-1.58,.24,1.71,.09-.17-.03-.21-.05-.31-.07-.3,0-.7,0-1.4-.02Z" style="fill:#4cc2f1;"/><path d="M567.51,109.18c1.45-.02-.95-.23,0,0h0Z" style="fill:#4cc2f1;"/><path d="M560.83,109.34l1.9-.25c-.4-.16-2.09-.2-1.46-.32-1.95,.14-1.55,.3-.44,.57Z" style="fill:#4cc2f1;"/><path d="M559.02,109.86l.99-.1c-.17-.07-.43-.09-.99,.1Z" style="fill:#4cc2f1;"/><path d="M562.11,109.53l-2.09,.22c.28,.12,.3,.35,.96-.08-.17,.21,.24,0,1.13-.14Z" style="fill:#4cc2f1;"/><path d="M563.29,109.64c1.07,.16,2.45,.25,.54,.5,3.64-.1,3.11-.6-.54-.5Z" style="fill:#4cc2f1;"/><path d="M572.73,111.45l1.43,.21,1.24-.24c-.62,.12-2.75-.19-2.66,.03Z" style="fill:#4cc2f1;"/><path d="M548.96,108.97c-.12,.15,.64,.4,.78,.6l1.41-.17c-.72-.1-2.09-.2-2.19-.42Z" style="fill:#4cc2f1;"/><path d="M549.75,109.57l-2.11,.26c1.88,.03,2.22-.09,2.11-.26Z" style="fill:#4cc2f1;"/><polygon points="558.69 110.65 562.14 110.11 558.02 110.66 558.69 110.65" style="fill:#4cc2f1;"/><polygon points="540.92 108.95 542.12 108.6 539.99 109.13 540.92 108.95" style="fill:#4cc2f1;"/><path d="M535.59,109.01c.88-.29-1.21-.49-.28-.67-1.45,.05-2.69,.3-3.8,.53,1.18-.13,2.44-.09,4.08,.14Z" style="fill:#4cc2f1;"/><path d="M529.64,109.2c.59-.07,1.2-.19,1.87-.33-.63,.07-1.25,.16-1.87,.33Z" style="fill:#4cc2f1;"/><path d="M544.06,109.76c-.16-.09-.48-.33-1.66-.43l.83,.43c.25,0,.54,0,.83,0Z" style="fill:#4cc2f1;"/><path d="M543.26,109.76h-.03c-.9-.02-1.52-.03-1.79-.04,.23,0,.78,.01,1.82,.04Z" style="fill:#4cc2f1;"/><path d="M544.26,109.76c-.08,0-.13,0-.21,0,.08,.04,.13,.06,.21,0Z" style="fill:#4cc2f1;"/><path d="M553.17,110.27c-1.24,.24-3.82,.5-1.32,.86,.89-.29-.54-.5,1.32-.86Z" style="fill:#4cc2f1;"/><path d="M545.29,110.29l2.24,.1c-.04-.11-.45-.28,.17-.4l-2.4,.3Z" style="fill:#4cc2f1;"/><polygon points="544.2 110.43 545.29 110.29 544.46 110.26 544.2 110.43" style="fill:#4cc2f1;"/><path d="M533.47,109.6c-.97,.19-2.02,.2-3.22,.23,1.28,.08,2.6,.13,3.86,.13,.18-.13,.73-.38-.64-.36Z" style="fill:#4cc2f1;"/><path d="M538.23,109.71c-.07,0-.15,.01-.21,.02,.13,.04,.24,.05,.21-.02Z" style="fill:#4cc2f1;"/><path d="M518.51,109.82c.07,.02,.12,.05,.19,.08,.01-.02,.02-.05,.04-.07h-.23Z" style="fill:#4cc2f1;"/><path d="M534.33,110.04c.96,.08,1.54-.02,2.08-.15-.72,.04-1.48,.07-2.3,.07-.08,.06-.09,.1,.21,.08Z" style="fill:#4cc2f1;"/><path d="M526.36,109.4c.5,.09,1.07,.17,1.67,.23-.49-.14-1.49-.35-1.67-.23Z" style="fill:#4cc2f1;"/><path d="M537.69,109.62s.09-.01,.14-.02c-.32-.09-.28-.05-.14,.02Z" style="fill:#4cc2f1;"/><path d="M537.69,109.62c-.49,.06-.88,.17-1.27,.26,.59-.03,1.13-.08,1.6-.14-.12-.03-.24-.08-.32-.12Z" style="fill:#4cc2f1;"/><path d="M514.95,112.57c.17-.4,2.7-.76,4.88-.34,2.15-.3,.74-1.6-1.13-2.33-.07,.17-.07,.33-2.28,.57,.77,.22,4.9,.51,3.04,.86-2.93,.2-2.4-.14-3.82-.35,1.21,.49-3.72,.72-3.09,1.44l2.57-.26c.05,.11-.88,.29-.17,.4Z" style="fill:#4cc2f1;"/><path d="M522.91,109.94l.91-.05c-1.04-.07-1.78-.23-3.15-.54-1.51,.19-1.83,.34-1.93,.47l4.48,.06-.31,.06Z" style="fill:#4cc2f1;"/><path d="M527.9,109.89c-.71,.43-2.89,.61-1.56,1.08,2.67-.03,1.87-.36,3.46-.55-4.31,.11,1.24-.24-1.87-.48,.85-.07,1.61-.1,2.33-.11-.78-.05-1.52-.12-2.23-.2,.04,0,.07,.02,.1,.03h.02s0,0,0,0c.19,.05,.25,.09,.04,.06-.01,0-.07,0-.09,0-.03,.06-.11,.11-.19,.16-1.25-.09-.2-.11,.19-.16,.01-.02,.05-.04,.05-.06,0,0-.01,0-.02,0l-4.31,.24c.91,.06,2.06,.05,4.08,0Z" style="fill:#4cc2f1;"/><path d="M549.54,112.05c-.13-.04-.13-.07-.14-.1-.28,.05-.38,.09,.14,.1Z" style="fill:#4cc2f1;"/><path d="M547.77,111.79c.31-.06,.57-.23,1.59-.19,.88,.13,0,.21,.04,.35,.61-.11,2.15-.31,.22-.52l-.31,.06c-1.74-.15-3.97-.69-3.66-.74,.51,.39,.95,.66,2.11,1.04Z" style="fill:#4cc2f1;"/><path d="M537.71,110.95l-3.59,.21,1.83,.37-.09-.22c2.04,.09,1.6-.19,1.86-.36Z" style="fill:#4cc2f1;"/><polygon points="518.1 109.61 518.57 109.15 516.5 109.8 518.1 109.61" style="fill:#4cc2f1;"/><polygon points="513.43 109.66 513.6 109.27 512.37 109.51 513.43 109.66" style="fill:#4cc2f1;"/><path d="M250.01,114.57l-1.06,.11c.5-.03,.82-.07,1.06-.11Z" style="fill:#4cc2f1;"/><path d="M257.65,114.6l.19-.19c-.35,.11-.5,.19-.19,.19Z" style="fill:#4cc2f1;"/><path d="M398.78,109.71c-1.54-.37-3.25-.94-3.07-.5l.47,.43c1.07-.08,1.9-.03,2.6,.06Z" style="fill:#4cc2f1;"/><path d="M320.27,112.3c.15,.04,.3,.09,.45,.13,.37-.15,.73-.29,1.04-.42-.45,.09-.96,.18-1.49,.28Z" style="fill:#4cc2f1;"/><path d="M319.18,114.09l-2.03-.28c.46,.1,1.11,.19,2.03,.28Z" style="fill:#4cc2f1;"/><path d="M208.27,115.03l-1.23,.44c1.93,0,1.08-.23,1.23-.44Z" style="fill:#4cc2f1;"/><path d="M465.62,109.82c-1.33-.17-2.38-.24-3.31-.26,1.6,.25,2.89,.62,3.31,.26Z" style="fill:#4cc2f1;"/><path d="M253.01,109.97c-.65,.08-.92,.17-.9,.26,.66-.07,1.04-.16,.9-.26Z" style="fill:#4cc2f1;"/><path d="M458.48,109.56c.23,.04,.46,.07,.7,.1,.97-.07,1.95-.13,3.13-.1-1.18-.19-2.53-.3-3.83,0Z" style="fill:#4cc2f1;"/><path d="M56.92,114.68c-.11-.02-.24-.03-.34-.05-.03,.06,.12,.06,.34,.05Z" style="fill:#4cc2f1;"/><path d="M12.82,110.3c.43-.03,1.14-.03,1.81-.03-.33-.03-.87-.03-1.81,.03Z" style="fill:#4cc2f1;"/><path d="M494.65,109.47c.99-.03,.16-.05,0,0h0Z" style="fill:#4cc2f1;"/><path d="M499.46,111.19c-.21,.05-.37,.11-.4,.21,.08-.1,.22-.17,.4-.21Z" style="fill:#4cc2f1;"/><path d="M448.61,112.26s-.05,0-.09,0c.29,.08,.24,.07,.09,0Z" style="fill:#4cc2f1;"/><path d="M496.14,109.67c-.37-.05-.8-.1-1.27-.13,.2,.03,.59,.08,1.27,.13Z" style="fill:#4cc2f1;"/><path d="M495.93,109.47c.34,.02,.61,.03,.87,.04,.12-.07,.01-.12-.87-.04Z" style="fill:#4cc2f1;"/><path d="M493.27,109.5c.57,0,1.09,.01,1.6,.05-.19-.03-.28-.05-.22-.07-.29,0-.7,.02-1.38,.02Z" style="fill:#4cc2f1;"/><path d="M138.01,113.51c-.07-.01-.14-.02-.21-.04-1,.18-.56,.15,.21,.04Z" style="fill:#4cc2f1;"/><path d="M373.02,114.21s.07-.02,.11-.02c-.05,0-.1-.01-.15-.02l.03,.04Z" style="fill:#4cc2f1;"/><path d="M369.46,114.54c1.51-.54,2.38-.47,3.52-.36l-1.18-1.25-2.35,1.61Z" style="fill:#4cc2f1;"/><path d="M231.71,114.7l2.5,.39c-3.01,.64-5.19,.42-2.85,.97,.35-1.36,9.71-.14,10.05-1.5l1.65,.29c-.47-.01-.55,.07-1.01,.05,3.2,.65,2.38-.98,6.5-.79,1.44,.1,2.44,.3,1.46,.46l3.2-.34c.93,.03,.7,.26-.22,.24l4.89-.11-.04,.04c.7-.21,2.28-.54,3.05-.68,1.24,.19-.62,.14-.3,.31l2.71-.33c1.1,.35-1.14,.69-3.01,.64,2.53,1.36,2.24-.34,7.7,.45l-2.55,.17c1.65,.77,3.64,.49,6.53,.97-.69-.25-5.78-1.21-2.85-1.55,.83,.1,1.92,.15,3.5-.12,.01,.48,1.56,.36,3.5,.34l-.13,.64c2.09-.18,1.93-.51,3.16-.8,2.33,.06,2.82,.56,3.36,.98,3.73,.1-2.58-.79,1.84-.91,3.11,.24,5.29-.02,9-.4,2.17,.22-.38,.39,.4,.57l1.93-.51c.47,.01,.32,.17,.24,.25,1.62-.2,.13-.64,2.69-.81,.69-.7,5.24,1.03,8.4,.23,.78,.18-.38,.39-.14,.64,4.33-.52,3.72-.38,7.27-1.09l3.03,.42c-2.23-.48,.62-1.05,3.11-1.52-.79-.23-1.47-.43-1.92-.53l3.98-.34c.48,.1,.08,.32-.57,.59,.27-.05,.53-.11,.73-.15,1.42,.24,.69,.61-.33,.96-.48-.13-.97-.26-1.43-.39-.98,.39-1.9,.82-1.7,1.19,.26-.07,.76-.14,1.3-.17-.26,.11-.42,.2-.3,.26,.14,.07,.55-.07,1.2-.24l.9,.71,.22-.28,5.2,.45c3.72-.72,6.96-1.77,12.87-2.07-1.47,.52-.32,.85-.4,1.47-1.61-.66-4.58,.28-7.64,.15,1.6,.07,1.25,.36,.84,.47l5.46-.6c-.12,.51,1.16,.38,2.59,.59-1.08-1,4.07-.67,6.3-.97,.14,.34-.4,.68-3.11,.6,2.99,.75,3.87-1.18,6.8-.54-.66,0-1.02-.04-1.28,.13,1.55-.3,5.33-.06,4.44,.23h-.66c4.4,.13,13.38,.01,14.04-.84,.04,.11-.58,1.03-.84,1.2l7.84-2.86c-.69,.73,1.96,2.47-1.68,2.88,1.05,.1,2.34,.23,4.55-.03-.71-.1-1.78-1.06-.8-1.12,2.68,.81,1.86,.43,5.16,1.13-1.07-.16,.12-1.3,1.81-1.27-.26,.17,.14,1.13-.48,1.25l4.21-1.12c-1.05,.2-.19,.95,.76,1.27-.44-.22,3.64,.02,4.76-.09l-1.78-.26c4.75,.17,4.7-1.58,9.41-1.52-.57,.23-1.41,1.43,.99,1.57,1.1-.58,4.87-2.77,9.04-3.21l.8,.33,3.2-.38c-1.99,.87-7.59,2.69-10.64,3.4,1.78,.26,.5,.39,3.16,.36,.94,.31-1.45,.47-2.24,.49l6.2,.2c-.28-.67,4.74-.67,4.51-1.23l-6.74,.7c-.27-.67,3.9-1.91,8.29-1.79,1.12,.27-.84,1.2-.74,1.42,.57-.23,4.17-.44,4.58-.28l-1.86,.36c2.45,.25,3.99-.89,6.93-.25,1.01,.04,3.03,.87,3.21,.47-1.66-.77-1.98-3.14-.88-3.72,.36,.05,5.33,.73,6.49,1.11,1.67,.6-2.51,1.43-1.09,1.95,.06-.18,.98-.41,1.37-.48,1.07,.16-.44,.57,1.87,.49,.39-.68,4.71,.06,1.09-.58,2.27-.19,2.41,.14,5.07,.11-.85-.44,2.39-1.5,4.66-1.69-.13,.17,.38,.4,.62,.5,4.64,.06,7.14,.37,11.47,.38,.4,.17,2.49,.36,1.6,.66,.62-.12,1.19-.35,2.53-.37,2.58,.59-2.98,.09-1.01,.8,.17-.4,3.45-.55,5.36-.79-.59-.61-4.39-.12-6.3,.13,0-.84,3.31-1.73,7.83-2.12,3.6-.21,1.56,.55,2.18,.43,6.76,.15,5.76-1.47,11.37-.86,2.23,.54,.1,1.07,.59,1.46-2.93,.2-4.5-.34-6.48-.32l2.44,.25c-.93,.18-2.89,.31-3.95,.16,1.87,.48,11.41,.09,16.63,.54,.93-.18,2.52-.37,1.77-.58l-1.65,.08c-1.52-.43,3.1-.6,1.02-.8,2.21-.3,4.69-.78,7.81-.54l.59,.61c-.07-.18-2.04-.37-2.91-.18,1.3-.27,4.78,.83,6.2,.03l-2.18-.42c2.22-.31,4.97-.96,7.73-.77-1.26-.61-.64,.07-2.6-.64,1.7,.88-7.06-.04-4.33,.89-2.01-.82-3.48-.35-6.11-1.05,.08,.2,.93,.39-1.39,.29-.1,.06-.35,.13-.51,.18-.07,0-.1,0-.16-.02,.05,0,.08,.02,.13,.02-.1,.03-.11,.04,.05,0,.55,.09,1,.19,1.18,.31-1.15,.46-4.83,.45-6.08,.69-1.38-.1,.57-.23-.18-.45l-1.86,.36c-.18-.45-3.65-.75-.41-1.01l-2.92-.02c-1.72-.25-4.08-.35-5.53-.16-1.38-.1-3.47,.5-2.28,.15l-4.57,.28-.04-.11c-4.51-.06-9.29,.74-14.44-.07-1.52,.11-3.09,.25-5.63,.12l-.5-.39c-1.95,.14-5.86-.39-6.69,.02-2.1-1.05-9.46-.23-12.89-.41l-.13,.51c-2.36-.03-4.62,.12-7.55,.32l.18,.45c-2.53,.37-6.49-.32-10.49-.27,.27-.17,1.29-.13,2-.02-4.77-1.01-10.07,.74-14.4,0-1.08,.16-1.86,.11-2.62,0,.1,0,.19,0,.27-.02-.11,0-.27,0-.44,0-.48-.08-.97-.17-1.5-.24,.45,.11,.88,.2,1.26,.24-.83,0-2.09,.07-3.33,.18l-.53-.48c-.1,0-.18,0-.29,.01-1.21-.18-.98,.23-.33,.59-.62,.08-1.19,.17-1.64,.29-.99-.78-3.25,.26-4.59-.57-.83,.4-6.22,.3-6.88,1.15-.04-.11-.41-.16,.26-.17-1.38-.1-2.76-.19-3.99,.05l-.59-.61-3.01,.82c-1.83-.37-2.59-.59-.77-1.06-4.39,.72-3.56,.27-7.29,.99l.18-.4c-1.33,.02-3.46,.55-4.27,.22-2.98-.75-14.76-.1-22.41-.8,1.48,1.17-3.56-.52-3.86,.38-.45-.28-1.51-.43-.27-.67-3.91,.27-5.87-.44-8.57,.32-.45-.28,.84-.4,.44-.57-.31,.06-1.24,.24-1.65,.08-.4-.16,.57-.23,1.19-.35-3.99,.05-4.4,.9-4.69,1.69-2.02-.38-3.03-.32-4.49,.2-.7-.26-1.88-.53,.6-.63-1.4-.04-8.52-.38-8.67,.26-.44-.16-1.95-.05-2.32,.03-3.13-.04-3.41-.1-6.48-.04l.85,.1c-1.21,.77-2.56,.17-5.03,.26l.07-.08c-5.68-.56-2.45,.27-8.06-.37l.3,.48c-.52,1.03-4.97-.61-8.22-.21l1.17,.27c-1.78,.35-4.97-1.4-6.75-1.53-.39-.09,.61-.14,1.16-.21-4.67-.61-.61,.63-4.09,.77-.64-.34,.83-.86-1.52-.92-1.17-.27-6.8,1.1-9.99,.45,.7,.26,1.4,.52-.21,.72-2.48,.09-6.7-.99-8.94-.16-.32-.07-.43-.14-.45-.2-1.89,.21-6.16,.35-7.3,.8-1.75-1.66-11.78,.81-11.74-.72l-5.83,.08,.15-.16c-3.73-.1-5.04,.27-6.35,.63-.86-.1-.23-.25-.16-.33-5.44-.31-5.98-.24-10.55,.52l-.47-.5c-1.24,.29-7.87-.78-13.21-.2,.05-.06,.16-.15,.59-.22-6.01,.58-14.38-1.38-16.58,.43l-3.96,.44c5.13,.14-.76,.78,1.35,1.08-2.02,.11-5.21-.54-2.74-.64l.39,.09c.91-.94-6.37-.33-6.23-.97-10.09,.05-19.93-.47-29.38-.51l1.09,.73-3.3-.04c-.87-.17-1.16-.57,1.19-.54-1.24-.41-3.94,.35-4.01,.51-4.61-.3,1.8-.85,.39-.87l-1.92,.05,.43,.09c-1.55,.3-1.69,.62-4.52,.58-1.79-.15-.92-.45-1.62-.48,0-.05-.23-.08-1.08-.02l-4.15-.06,1.66,.58c-1.84,.28-3.91-.03-1.99,.62-2.81-1.01-15.5-.36-17.22-.68-2.58,.42-5.12,.41-8.28,.36,.78,.12,1.25,.77-1.47,.69,.99-1.34-4.98-.58-7.18-1.55,1.96,.64-7.73,.16-4.65,1.07-2.27-.11-.14-.5-1.86-.82-5.4,.52-11.9-.34-18.58-.16-.1,.18,1.01,.44-.42,.71l-3.52-.88c-1.32,.09-1.21,1.02-3.62,.4,.34,.15,.85,.37-.04,.43-13.08-.86-26.73,.7-40.01-.91,1.8,.34,.6,.37-.77,.39,.89,.08-.02,.43,.16,.67l-5.56-.66c-1.01,.66-5.96,.04-6.54,.68l1.76-.11c-2.34,.75,3.28,1.66,2.93,2.62,2.62-.85,6.91,1.26,11.22,.04,.95,.2-.81,.31-.3,.53,1.09-.41,2.41-.5,4.85-.31l-.28,.1c6.86-.1,9.8,.3,17.5,.57l-.57-.47c1.66,.06,2,.21,2.78,.33,1.9-.72-4.65-.04-2.92-.83,2.2,.96,11.67,.01,13.16,1.11,2.55,0-.75-.55,1.8-.54l.68,.3,.64-.38c1.67,.06,2.51,.43,2.57,.68-.41-.03-1.15,.07-1.59,.09,2.16,.35,6.1-.12,6.85,0l-2.45-.18c6.95-.28,15.43,.11,22.15-.49l-.51-.22c5.13-.41,4.11,.25,9.74,.05l-.2,.05c1.13-.22,2.63-.33,4.17-.31-1.42,.26,2.67,.51,.81,.8,5.56-.44,2.75-.35,6.42-1.18l.85,.37c1.25-.34,1.36-.51,4.17-.61-2.31,.32,1.79,.57-.79,.99,5.9,.81,8.76-.82,11.47,.37,2.71-1.03-5.11-.57-3.59-.79-1.27-.34,2.24-.77,4.09-.66,1.88,.02,3.2,1.31,7.94,1.29-.47,0-.5,.07-.97,.07,1.77,.26,3.48-.35,5.65,.07,1.22-.62,2.83,.04,3.18-.76l-4.35,.18c2.47-.21,4.78-1.13,8.84-.68-.32,.16-1.52,.37-2.36,.49,1.19,.23,2.29-.15,3.52,.08-.35,.79-5.28,.17-7.93,.78,1.27,.33,5-.57,3.67,.28,1.91-1.09,4.67,.14,7.88-.7l-.21,.47c.51-.07,1.56-.3,2.5-.29l-1.69,.62c2.6-.52,5.01,.46,7.5,.17-5.22,.01-1.17-.57-3.38-.92,6.44-.64,3.7,1.24,10.91,1.01-.98,.07-3.66-.29-2.14-.5,1.37,.1,3.18,.28,4.03,.53,4.78-.1-.73-.49,1.3-.78,1.63,.58,2.34-.24,4.44-.42v.48c5.46,.31,1.46-1.01,6.36-.63l-2.07,.67,2.55-.17-.53,.55c2.78-.41,3.64-.3,6.37-.15-.63-.34,.36-.88,2.77-.89,1.64,.29-.92,.46,2.5,.39-.77,.3-1.62,.68-3.1,.24-.15,.16-.76,.3-.92,.46,1.77,.33,4.45,.08,5.69,.07-.48-.02-1.05-.04-1.34-.11l4.95-.67c.78,.18,.16,.33-.45,.47,1-.05,1.7-.27,3.17-.32-.38,.39-.29,.8-2.38,.98l4.72-.44c.32,.17,2.42,.47,1.81,.61,2.62,.23,6.42-.76,9.89-.46,.18-.08,.54-.15,1.28-.21,3.26,.09,6.15,.57,10.01,.03l2.19,.7c3.03-.16-2.97-.89,2.07-1.15,3.57-.22,1.33,.6,2.58,.8,1.77-.35,4.86-1.08,8.14-.5-1.08,.13-1.94,.03-2.95,.08Zm37.32-.31c-.72,.1-2.16,.41-2.69,.13,.82-.55,1.46-.29,2.69-.13Zm56.73-1.16c-.54,.16-1.37,.08-2.31-.1,.68-.04,1.45-.02,2.31,.1Zm158.03-3.36s-.05,.01-.08,.02c-1.61-.19-1.04-.11,.08-.02Zm3.35,.21c.34-.07,.31-.13,.17-.2-.23,.12-.99,.13-1.84,.1,.24,.13,.67,.21,1.67,.11Zm-359.7,.19l.65,.13c-1.47,.14-1.06,.01-.65-.13Z" style="fill:#4cc2f1;"/></g><g><path d="M6.89,82.3c-1.67-1.33-2.5-3.57-2.5-6.7s1.12-12.62,3.35-28.45c2.23-15.83,3.35-27.13,3.35-33.9s-.47-11.18-1.4-13.25c6.8,.53,11.27,3.2,13.4,8,5.13-2.8,10.78-4.2,16.95-4.2s11.03,1.92,14.6,5.75c3.57,3.83,5.35,8.55,5.35,14.15,0,6.93-2.54,13.2-7.6,18.8-2.47,2.67-5.65,4.83-9.55,6.5-3.9,1.67-8.22,2.5-12.95,2.5-2,0-3.9-.13-5.7-.4l-.3-4.2h.8c7.53,0,13.53-2.8,18-8.4,3.07-4,4.6-8.2,4.6-12.6,0-6.47-2.63-10.73-7.9-12.8-4.47-1.67-9.47-1.33-15,1v.2c0,2.53-.95,10.82-2.85,24.85-1.9,14.03-2.85,25.75-2.85,35.15,0,4.2,.2,7.47,.6,9.8-2.13,.13-3.53,.2-4.2,.2-3.8,0-6.53-.67-8.2-2Z" style="fill:#4cc2f1;"/><path d="M80.19,50.1c2.4-8.67,5.28-15.05,8.65-19.15,3.37-4.1,6.5-6.15,9.4-6.15s5.08,1.08,6.55,3.25c1.47,2.17,2.2,4.97,2.2,8.4s-1.02,6.58-3.05,9.45c-2.03,2.87-4.82,4.3-8.35,4.3-.93,0-2.04-.27-3.3-.8,2.07-2.6,3.1-5.73,3.1-9.4,0-2.13-.87-3.2-2.6-3.2-1.27,0-2.63,.88-4.1,2.65-1.47,1.77-2.87,4.2-4.2,7.3-1.33,3.1-2.45,7.07-3.35,11.9-.9,4.83-1.35,9.98-1.35,15.45,0,.67,.17,3.5,.5,8.5-2.13,.13-3.5,.2-4.1,.2-3.8,0-6.53-.67-8.2-2-1.67-1.33-2.5-3.53-2.5-6.6,0-1.33,.7-6.46,2.1-15.4,1.4-8.93,2.1-16.05,2.1-21.35s-.57-10.18-1.7-14.65c4.87,1.53,8.27,3.72,10.2,6.55,1.93,2.83,2.9,6.13,2.9,9.9s-.3,7.38-.9,10.85Z" style="fill:#4cc2f1;"/><path d="M132.39,23.5c.73,0,1.42,.22,2.05,.65,.63,.43,.95,.88,.95,1.35-6.73,4.67-10.77,11.17-12.1,19.5,1.4-3.73,3.12-6.97,5.15-9.7,2.03-2.73,4.15-4.8,6.35-6.2,4.2-2.6,8.3-3.9,12.3-3.9s7.4,1.97,10.2,5.9c2.8,3.93,4.2,9.2,4.2,15.8,0,7.4-1.5,14.2-4.5,20.4-3.33,7-8.07,11.83-14.2,14.5-3.47,1.53-7.27,2.3-11.4,2.3-5.8,0-10.85-2.25-15.15-6.75s-6.45-10.97-6.45-19.4,2.07-15.87,6.2-22.3c4.13-6.43,9.6-10.48,16.4-12.15Zm8.5,9c-4.33,0-8.2,2.92-11.6,8.75-3.4,5.83-5.1,11.65-5.1,17.45,0,12.13,3.13,18.2,9.4,18.2,4.47,0,8.1-2.92,10.9-8.75,2.8-5.83,4.2-12.38,4.2-19.65,0-10.67-2.6-16-7.8-16Z" style="fill:#4cc2f1;"/><path d="M193.09,82.6c-2.13,.93-4.37,1.4-6.7,1.4s-3.63-.5-3.9-1.5c-1.07-4.27-2.3-10.65-3.7-19.15s-2.82-15.93-4.25-22.3c-1.43-6.37-3.15-11.18-5.15-14.45,3.67-1.73,6.6-2.6,8.8-2.6s3.87,.5,5,1.5c1.13,1,2.08,2.98,2.85,5.95,.77,2.97,1.32,5.42,1.65,7.35,.93,6.4,1.4,9.97,1.4,10.7s.27,3.72,.8,8.95c.53,5.23,.93,8.68,1.2,10.35,11.87-23,17.8-38.37,17.8-46.1,5.47,2.8,8.2,5.83,8.2,9.1,0,2.67-1.38,6.62-4.15,11.85-2.77,5.23-6.23,11.57-10.4,19-4.17,7.43-7.32,14.08-9.45,19.95Z" style="fill:#4cc2f1;"/><path d="M241.34,48.65c-1.63,9.5-2.47,17.33-2.5,23.5-.03,6.17,.35,10.15,1.15,11.95-5.2-1.33-8.7-3.35-10.5-6.05s-2.7-6.65-2.7-11.85c0-2.8,.53-7.87,1.6-15.2,1.07-7.33,1.6-13.03,1.6-17.1s-.2-7.2-.6-9.4c1.47-.13,2.83-.2,4.1-.2,3.87,0,6.55,.65,8.05,1.95,1.5,1.3,2.25,3.52,2.25,6.65,0,1-.82,6.25-2.45,15.75Zm3.95-33.15c-1.87,.27-3.5,.4-4.9,.4-6.87,0-10.3-2.4-10.3-7.2,0-1.73,.5-4.1,1.5-7.1,1.87-.27,3.5-.4,4.9-.4,6.87,0,10.3,2.4,10.3,7.2,0,1.73-.5,4.1-1.5,7.1Z" style="fill:#4cc2f1;"/><path d="M278.59,30.8c-1.87,0-3.53,.62-5,1.85-1.47,1.23-2.2,2.98-2.2,5.25s.78,4.4,2.35,6.4c1.57,2,3.48,3.75,5.75,5.25,2.27,1.5,4.55,3.03,6.85,4.6,2.3,1.57,4.32,3.5,6.05,5.8,1.73,2.3,2.7,4.85,2.9,7.65,0,4.8-2.07,8.83-6.2,12.1-4.13,3.27-9.27,4.9-15.4,4.9s-10.95-1.23-14.45-3.7-5.25-5.3-5.25-8.5,.98-5.78,2.95-7.75,4.42-2.95,7.35-2.95c1.53,0,2.97,.27,4.3,.8-2.13,2.27-3.2,4.67-3.2,7.2s.7,4.57,2.1,6.1c1.4,1.53,3.3,2.3,5.7,2.3s4.42-.62,6.05-1.85c1.63-1.23,2.45-2.83,2.45-4.8s-.57-3.71-1.7-5.25c-1.13-1.53-2.55-2.88-4.25-4.05-1.7-1.17-3.55-2.48-5.55-3.95-2-1.46-3.85-2.95-5.55-4.45-1.7-1.5-3.12-3.4-4.25-5.7-1.13-2.3-1.7-4.82-1.7-7.55,0-4.73,1.95-8.62,5.85-11.65,3.9-3.03,8.77-4.55,14.6-4.55s10.1,1.13,12.8,3.4c2.7,2.27,4.05,4.92,4.05,7.95s-.95,5.62-2.85,7.75c-1.9,2.13-4.35,3.2-7.35,3.2-1.47,0-3.13-.3-5-.9,1.47-1.27,2.6-2.71,3.4-4.35,.8-1.63,1.2-3.25,1.2-4.85s-.67-2.95-2-4.05c-1.33-1.1-2.93-1.65-4.8-1.65Z" style="fill:#4cc2f1;"/><path d="M323.64,48.65c-1.63,9.5-2.47,17.33-2.5,23.5-.03,6.17,.35,10.15,1.15,11.95-5.2-1.33-8.7-3.35-10.5-6.05s-2.7-6.65-2.7-11.85c0-2.8,.53-7.87,1.6-15.2,1.07-7.33,1.6-13.03,1.6-17.1s-.2-7.2-.6-9.4c1.47-.13,2.83-.2,4.1-.2,3.87,0,6.55,.65,8.05,1.95,1.5,1.3,2.25,3.52,2.25,6.65,0,1-.82,6.25-2.45,15.75Zm3.95-33.15c-1.87,.27-3.5,.4-4.9,.4-6.87,0-10.3-2.4-10.3-7.2,0-1.73,.5-4.1,1.5-7.1,1.87-.27,3.5-.4,4.9-.4,6.87,0,10.3,2.4,10.3,7.2,0,1.73-.5,4.1-1.5,7.1Z" style="fill:#4cc2f1;"/><path d="M360.09,23.5c.73,0,1.42,.22,2.05,.65,.63,.43,.95,.88,.95,1.35-6.73,4.67-10.77,11.17-12.1,19.5,1.4-3.73,3.12-6.97,5.15-9.7,2.03-2.73,4.15-4.8,6.35-6.2,4.2-2.6,8.3-3.9,12.3-3.9s7.4,1.97,10.2,5.9c2.8,3.93,4.2,9.2,4.2,15.8,0,7.4-1.5,14.2-4.5,20.4-3.34,7-8.07,11.83-14.2,14.5-3.47,1.53-7.27,2.3-11.4,2.3-5.8,0-10.85-2.25-15.15-6.75-4.3-4.5-6.45-10.97-6.45-19.4s2.07-15.87,6.2-22.3c4.13-6.43,9.6-10.48,16.4-12.15Zm8.5,9c-4.33,0-8.2,2.92-11.6,8.75-3.4,5.83-5.1,11.65-5.1,17.45,0,12.13,3.13,18.2,9.4,18.2,4.47,0,8.1-2.92,10.9-8.75,2.8-5.83,4.2-12.38,4.2-19.65,0-10.67-2.6-16-7.8-16Z" style="fill:#4cc2f1;"/><path d="M438.99,24.9c3.93,0,6.75,1.22,8.45,3.65,1.7,2.43,2.55,5.73,2.55,9.9s-.65,10.02-1.95,17.55c-1.3,7.53-1.95,13.02-1.95,16.45s.43,5.93,1.3,7.5c.87,1.57,2.33,2.92,4.4,4.05-.87,.07-2.1,.1-3.7,.1-5.6,0-9.58-.87-11.95-2.6-2.37-1.73-3.55-4.77-3.55-9.1,0-2.47,.67-7.28,2-14.45,1.33-7.17,2-12.52,2-16.05,0-4.4-1.33-6.6-4-6.6-1.54,0-3.27,.9-5.2,2.7-1.93,1.8-3.8,4.25-5.6,7.35-1.8,3.1-3.3,7.08-4.5,11.95-1.2,4.87-1.8,9.72-1.8,14.55s.2,8.42,.6,10.75c-2.13,.13-3.54,.2-4.2,.2-3.8,0-6.53-.67-8.2-2-1.67-1.33-2.5-3.53-2.5-6.6,0-1.33,.7-6.46,2.1-15.4,1.4-8.93,2.1-16.05,2.1-21.35s-.57-10.18-1.7-14.65c4.87,1.53,8.27,3.72,10.2,6.55,1.93,2.83,2.9,6.07,2.9,9.7s-.4,7.85-1.2,12.65c2.2-7.46,5.67-13.8,10.4-19,4.73-5.2,9.07-7.8,13-7.8Z" style="fill:#4cc2f1;"/><path d="M480.04,48.65c-1.63,9.5-2.47,17.33-2.5,23.5-.03,6.17,.35,10.15,1.15,11.95-5.2-1.33-8.7-3.35-10.5-6.05-1.8-2.7-2.7-6.65-2.7-11.85,0-2.8,.53-7.87,1.6-15.2,1.07-7.33,1.6-13.03,1.6-17.1s-.2-7.2-.6-9.4c1.47-.13,2.83-.2,4.1-.2,3.87,0,6.55,.65,8.05,1.95,1.5,1.3,2.25,3.52,2.25,6.65,0,1-.82,6.25-2.45,15.75Zm3.95-33.15c-1.87,.27-3.5,.4-4.9,.4-6.87,0-10.3-2.4-10.3-7.2,0-1.73,.5-4.1,1.5-7.1,1.87-.27,3.5-.4,4.9-.4,6.87,0,10.3,2.4,10.3,7.2,0,1.73-.5,4.1-1.5,7.1Z" style="fill:#4cc2f1;"/><path d="M533.69,24.9c3.93,0,6.75,1.22,8.45,3.65,1.7,2.43,2.55,5.73,2.55,9.9s-.65,10.02-1.95,17.55c-1.3,7.53-1.95,13.02-1.95,16.45s.43,5.93,1.3,7.5c.87,1.57,2.33,2.92,4.4,4.05-.87,.07-2.1,.1-3.7,.1-5.6,0-9.58-.87-11.95-2.6-2.37-1.73-3.55-4.77-3.55-9.1,0-2.47,.67-7.28,2-14.45,1.33-7.17,2-12.52,2-16.05,0-4.4-1.33-6.6-4-6.6-1.54,0-3.27,.9-5.2,2.7-1.93,1.8-3.8,4.25-5.6,7.35-1.8,3.1-3.3,7.08-4.5,11.95-1.2,4.87-1.8,9.72-1.8,14.55s.2,8.42,.6,10.75c-2.13,.13-3.54,.2-4.2,.2-3.8,0-6.53-.67-8.2-2-1.67-1.33-2.5-3.53-2.5-6.6,0-1.33,.7-6.46,2.1-15.4,1.4-8.93,2.1-16.05,2.1-21.35s-.57-10.18-1.7-14.65c4.87,1.53,8.27,3.72,10.2,6.55,1.93,2.83,2.9,6.07,2.9,9.7s-.4,7.85-1.2,12.65c2.2-7.46,5.67-13.8,10.4-19,4.73-5.2,9.07-7.8,13-7.8Z" style="fill:#4cc2f1;"/><path d="M588.59,25.9c1.27,0,2.23,.07,2.9,.2,1.73,.33,2.67,1.57,2.8,3.7-7.27,1.53-13.05,5.47-17.35,11.8-4.3,6.33-6.45,12.93-6.45,19.8,0,8,2.43,12,7.3,12,2.93,0,5.57-1.6,7.9-4.8,4.13-5.8,7.03-14.87,8.7-27.2,1.27-9.73,3.63-16,7.1-18.8,1.67-1.4,3.73-2.5,6.2-3.3-.93,7.07-1.4,12.4-1.4,16,0,26.67-2.85,46.7-8.55,60.1-5.7,13.4-14.98,20.1-27.85,20.1-5.2,0-9.27-1.38-12.2-4.15-2.93-2.77-4.4-5.8-4.4-9.1s1.03-6,3.1-8.1c2.07-2.1,4.96-3.15,8.7-3.15,1.53,0,3.1,.33,4.7,1-2.53,1.67-3.8,4.23-3.8,7.7,0,2.33,.7,4.35,2.1,6.05,1.4,1.7,3.12,2.55,5.15,2.55s3.82-.42,5.35-1.25c1.53-.83,3.1-2.32,4.7-4.45,1.6-2.13,3-4.8,4.2-8,2.73-7.4,4.23-17.5,4.5-30.3-1.93,5.73-4.87,10.33-8.8,13.8-3.93,3.47-7.93,5.2-12,5.2-5.13,0-9.05-1.95-11.75-5.85-2.7-3.9-4.05-8.88-4.05-14.95,0-7.07,1.7-13.63,5.1-19.7,3.8-7,9.07-11.87,15.8-14.6,3.73-1.53,7.83-2.3,12.3-2.3Z" style="fill:#4cc2f1;"/></g></g></g> + </g> + <text x="1002" y="38" text-anchor="middle" fill="#666666" font-family="'IBM Plex Mono',monospace" font-size="10" letter-spacing="3">CONTROL PLANE ARCHITECTURE</text> + <line x1="872" y1="46" x2="1132" y2="46" stroke="#ccc" stroke-width="0.8"/> + + <!-- ═══ CLI ═══ --> + <g> + <rect x="235" y="104" width="140" height="68" rx="8" fill="url(#cli-grad)" filter="url(#soft-shadow)"/> + <rect x="235" y="104" width="140" height="68" rx="8" fill="none" stroke="#c5d5e5" stroke-width="1"/> + <rect x="235" y="107" width="3" height="62" rx="1.5" fill="#4a9eff" opacity="0.6"/> + <circle cx="256" cy="118" r="7" fill="#e2e8f0" stroke="#4a9eff" stroke-width="1" opacity="0.8"/> + <text x="256" y="121" text-anchor="middle" fill="#4a9eff" font-size="8" font-family="'IBM Plex Mono',monospace">$</text> + <text x="265" y="123" fill="#4a9eff" font-family="'DM Sans',sans-serif" font-weight="600" font-size="11">CLI</text> + <text x="270" y="148" fill="#6b8aaa" font-family="'IBM Plex Mono',monospace" font-size="8">provisioning</text> + </g> + + <!-- Arrow: CLI → Orchestrator --> + <line x1="640" y1="98" x2="640" y2="115" stroke="#4a9eff" stroke-width="1.5" stroke-dasharray="5 3" class="spoke" opacity="0.7" marker-end="url(#arr-blue)"/> + + + <!-- ═══════════════════════════════════════ --> + <!-- NATS ORBITAL RING SYSTEM --> + <!-- ═══════════════════════════════════════ --> + + <!-- Central area fill --> + <circle cx="640" cy="320" r="133" fill="#e8ecf0" opacity="0.55"/> + + <!-- Ring outer glow --> + <circle cx="640" cy="320" r="138" fill="none" stroke="#94a3b8" stroke-width="10" opacity="0.04" class="ring-glow" filter="url(#ring-glow-f)"/> + + <!-- Main orbit ring (flowing dashes) --> + <circle cx="640" cy="320" r="135" fill="none" stroke="#94a3b8" stroke-width="2" stroke-dasharray="10 8" class="orbit-flow" opacity="0.45"/> + + <!-- ═══ INFINITY ENGINE (∞ dual-wheel perpetual motion from logo) ═══ --> + <!-- Centered at (640,310), scale 0.55 from original logo coordinates --> + <!-- Left wheel center: (115.86, 113.25) | Right wheel center: (261.91, 113.28) --> + <g transform="translate(640, 310) scale(0.55) translate(-188.88, -113.27)" opacity="0.85"> + + <!-- Background circle with CLI gradient --> + <rect x="100" y="40" width="178" height="146" rx="12" style="fill:url(#cli-grad); opacity:0.35;"/> + <circle cx="188.88" cy="113.27" r="50" style="fill:url(#cli-grad); opacity:0.3;"/> + + <!-- Left wheel — clockwise 8s (outline only) --> + <g> + <path d="M3.04,87.53c-3.66,16.4-3.36,33.43,.85,49.7l18.49-.31c2.12,7.08,5.09,13.89,8.85,20.26l-12.81,13.12c9.22,14.08,21.56,25.9,36.09,34.55l12.81-13.13c6.56,3.51,13.52,6.22,20.74,8.08l.31,18.3c16.56,3.62,33.75,3.33,50.18-.85l-.31-18.3c7.15-2.1,14.02-5.04,20.46-8.77l13.25,12.69c14.22-9.14,26.14-21.36,34.87-35.75l-13.25-12.68c3.54-6.5,6.28-13.4,8.15-20.55l18.49-.31c3.65-16.4,3.36-33.43-.85-49.7l-18.49,.31c-2.12-7.08-5.09-13.88-8.85-20.26l12.8-13.12c-9.22-14.08-21.56-25.9-36.08-34.55l-12.81,13.13c-6.56-3.51-13.53-6.23-20.75-8.08l-.31-18.3c-16.56-3.62-33.75-3.33-50.18,.85l.31,18.3c-7.15,2.1-14.01,5.04-20.45,8.77l-13.25-12.69c-14.22,9.14-26.15,21.36-34.88,35.75l13.25,12.68c-3.54,6.5-6.28,13.4-8.15,20.55l-18.49,.31Z" style="fill:#ffffff; fill-rule:evenodd; stroke:#4cc2f1; stroke-miterlimit:10; stroke-width:1.2; opacity:0.85;"> + <animateTransform attributeName="transform" type="rotate" values="0 115.86 113.25;360 115.86 113.25" dur="8s" repeatCount="indefinite"/> + </path> + </g> + + <!-- Right wheel — counter-clockwise 6s (outline only) --> + <g> + <path d="M167.28,92.87c-3.19,14.31-2.94,29.17,.74,43.37l16.13-.27c1.85,6.18,4.44,12.12,7.72,17.68l-11.18,11.45c8.05,12.29,18.82,22.6,31.49,30.15l11.18-11.46c5.72,3.06,11.8,5.43,18.1,7.05l.27,15.97c14.45,3.16,29.45,2.91,43.78-.74l-.27-15.97c6.24-1.83,12.23-4.4,17.85-7.66l11.56,11.08c12.41-7.97,22.81-18.64,30.43-31.19l-11.56-11.06c3.09-5.67,5.48-11.69,7.11-17.93l16.13-.27c3.18-14.31,2.93-29.17-.74-43.37l-16.13,.27c-1.85-6.18-4.44-12.11-7.72-17.68l11.17-11.44c-8.04-12.29-18.81-22.6-31.48-30.15l-11.18,11.46c-5.72-3.07-11.8-5.43-18.1-7.05l-.27-15.97c-14.45-3.16-29.45-2.91-43.78,.74l.27,15.97c-6.24,1.83-12.23,4.4-17.84,7.66l-11.56-11.08c-12.41,7.97-22.81,18.64-30.44,31.19l11.56,11.06c-3.09,5.67-5.48,11.69-7.11,17.93l-16.13,.27Z" style="fill:#ffffff; fill-rule:evenodd; stroke:#4cc2f1; stroke-miterlimit:10; stroke-width:1.2; opacity:0.85;"> + <animateTransform attributeName="transform" type="rotate" values="0 261.91 113.28;-360 261.91 113.28" dur="6s" repeatCount="indefinite"/> + </path> + </g> + + <!-- Left wheel golden center (strong original colors) --> + <path d="M115.86,78.19c19.55,0,35.4,15.7,35.4,35.06s-15.85,35.06-35.4,35.06-35.4-15.7-35.4-35.06,15.85-35.06,35.4-35.06" style="fill:#f2b03f; opacity:0.9;"/> + <ellipse cx="116.42" cy="115.68" rx="20.17" ry="20.15" style="fill:none; stroke:#4159a4; stroke-miterlimit:10; stroke-width:1.58px; opacity:0.8;"/> + <path d="M116.42,107.88c4.35-.04,7.91,3.43,7.95,7.73,.04,4.31-3.46,7.83-7.81,7.87-4.35,.04-7.91-3.43-7.95-7.73,0-.02,0-.05,0-.07-.02-4.29,3.48-7.78,7.81-7.8" style="fill:#fff; opacity:0.8;"/> + + <!-- Right wheel golden center (strong original colors) --> + <ellipse cx="261.91" cy="113.28" rx="35.65" ry="35.31" style="fill:#f9b224; opacity:0.9;"/> + + <!-- Right wheel — dashboard display (simplified from logo chronometer) --> + <circle cx="261.91" cy="113.28" r="26" style="fill:#3d5070; opacity:0.85;"/> + <!-- Window controls (3 dots) --> + <circle cx="243" cy="95" r="1.2" style="fill:#d68a87; opacity:0.6;"/> + <circle cx="247" cy="95" r="1.2" style="fill:#c9a558; opacity:0.6;"/> + <circle cx="251" cy="95" r="1.2" style="fill:#5a7093; opacity:0.6;"/> + <!-- Terminal header bar --> + <rect x="243" y="99" width="38" height="3.2" rx="1.5" style="fill:#6a7a98; opacity:0.5;"/> + <!-- Status bars (staggered widths = data readout) --> + <rect x="243" y="105" width="28" height="2.8" rx="1" style="fill:#7a8aaa; opacity:0.5;"/> + <rect x="243" y="110" width="34" height="2.8" rx="1" style="fill:#a89870; opacity:0.5;"/> + <rect x="243" y="115" width="20" height="2.8" rx="1" style="fill:#6a7a8a; opacity:0.4;"/> + <rect x="243" y="120" width="30" height="2.8" rx="1" style="fill:#7a8aaa; opacity:0.4;"/> + <!-- Green health indicator --> + <circle cx="271" cy="127" r="5.5" style="fill:#4a9e8f; opacity:0.7;"> + <animate attributeName="opacity" values="0.7;0.4;0.7" dur="2s" repeatCount="indefinite"/> + </circle> + <!-- Animated checkmark --> + <path d="M268,126.5l2.5,2.5,4.2-4.2" style="fill:none; stroke:#a0c0b0; stroke-width:1.8; stroke-linecap:round; stroke-linejoin:round; opacity:0.8;"> + <animate attributeName="stroke-dasharray" values="0 15;15 15" dur="1.5s" repeatCount="indefinite"/> + </path> + + <!-- ∞ path segments — staggered color cycling --> + + <!-- Top left of ∞ --> + <path d="M225.8,83.92c1.22-1.35,2.46-2.66,3.75-3.94,16.69-16.54,43.22-18.12,61.79-3.68l17.46-2.65,2.57-16.63c-29.5-25.42-73.83-23.89-101.47,3.5-1.24,1.23-2.45,2.49-3.63,3.77l16.89,2.56,2.64,17.07Z" style="stroke:#5a7a90; stroke-miterlimit:10;"> + <animate attributeName="fill" values="#5e74b7;#00D4FF;#7B2BFF;#00FFB3;#5e74b7" dur="2s" repeatCount="indefinite"/> + <animate attributeName="stroke-width" values="0.5;2;0.5" dur="2s" repeatCount="indefinite"/> + </path> + + <!-- Bottom left of ∞ --> + <path d="M78.59,141.18c-14.54-18.4-12.95-44.65,3.71-61.2,1.33-1.31,2.73-2.54,4.2-3.68l-17.46-2.65-2.57-16.63c-1.3,1.13-2.58,2.28-3.82,3.5-27.79,27.53-28.96,71.57-3.53,100.51l16.79-2.55,2.67-17.3Z" style="stroke:#5a7a90; stroke-miterlimit:10;"> + <animate attributeName="fill" values="#5e74b7;#7B2BFF;#00FFB3;#00D4FF;#5e74b7" dur="2s" begin="0.5s" repeatCount="indefinite"/> + <animate attributeName="stroke-width" values="0.5;2;0.5" dur="2s" begin="0.5s" repeatCount="indefinite"/> + </path> + + <!-- Top right of ∞ --> + <path d="M86.5,76.3c18.57-14.44,45.1-12.86,61.79,3.68,1.29,1.27,2.53,2.6,3.75,3.94l2.64-17.07,16.89-2.56c-1.18-1.28-2.39-2.54-3.63-3.77-27.64-27.39-71.97-28.92-101.48-3.5l2.57,16.63,17.46,2.65Z" style="stroke:#5a7a90; stroke-miterlimit:10;"> + <animate attributeName="fill" values="#8d9ccf;#00FFB3;#00D4FF;#7B2BFF;#8d9ccf" dur="2s" begin="1s" repeatCount="indefinite"/> + <animate attributeName="stroke-width" values="0.5;2;0.5" dur="2s" begin="1s" repeatCount="indefinite"/> + </path> + + <!-- Bottom right of ∞ --> + <path d="M299.26,141.18c-15.88,20.08-45.19,23.61-65.46,7.88l-.04-.03-17.46,2.65-2.57,16.63c29.5,25.42,73.83,23.89,101.47-3.5,1.24-1.22,2.4-2.49,3.53-3.78l-16.79-2.55-2.68-17.3Z" style="stroke:#5a7a90; stroke-miterlimit:10;"> + <animate attributeName="fill" values="#8d9ccf;#7B2BFF;#00D4FF;#00FFB3;#8d9ccf" dur="2s" begin="1.5s" repeatCount="indefinite"/> + <animate attributeName="stroke-width" values="0.5;2;0.5" dur="2s" begin="1.5s" repeatCount="indefinite"/> + </path> + + <!-- ∞ connector arcs (static deep blue) --> + <path d="M315.19,60.52c-1.23-1.22-2.51-2.38-3.82-3.5l-2.57,16.63-17.46,2.65c1.47,1.14,2.88,2.37,4.2,3.68,16.66,16.55,18.25,42.8,3.71,61.2l2.68,17.3,16.79,2.55c25.42-28.93,24.26-72.98-3.53-100.5m-171.1,88.5c-20.2,15.76-49.48,12.31-65.39-7.7-.04-.05-.08-.1-.12-.15l-2.68,17.3-16.79,2.55c1.13,1.29,2.3,2.56,3.53,3.78,27.64,27.39,71.97,28.92,101.47,3.5l-2.57-16.63-17.46-2.65Z" style="fill:#455aa5; stroke:#3a5a7a; stroke-miterlimit:10;"/> + + <!-- ∞ transition pieces --> + <path d="M204.38,103.42c1.09,1.79,2.17,3.56,3.25,5.32,5.65-8.96,11.44-17.4,18.17-24.81l-2.64-17.07-16.89-2.56c-5.38,5.87-10.34,12.11-14.83,18.68,4.58,6.77,8.8,13.66,12.94,20.45m-30.92,18.5c-1.09-1.79-2.18-3.56-3.25-5.32-6.68,10.59-13.53,20.44-21.92,28.75-1.33,1.31-2.73,2.54-4.21,3.68l17.46,2.65,2.57,16.63c1.3-1.12,2.58-2.28,3.82-3.5,6.84-6.9,13.02-14.41,18.46-22.43-4.57-6.76-8.78-13.66-12.93-20.46" style="fill:#8d9ccf; stroke:#5a7a90; stroke-miterlimit:10;"/> + + <!-- Central flow — animated infinity gradient --> + <path d="M171.57,64.29c11.77,12.66,20.64,27.17,29.24,41.26,9,14.74,17.5,28.67,28.74,39.8,1.33,1.31,2.73,2.54,4.21,3.68l-17.46,2.65-2.57,16.63c-1.3-1.12-2.58-2.27-3.82-3.5-13.63-13.5-23.41-29.52-32.87-45.02-7.97-13.05-15.55-25.46-24.99-35.86l2.64-17.07,16.89-2.56Z" style="stroke:#5a7a90; stroke-miterlimit:10;"> + <animate attributeName="fill" values="#00D4FF;#7B2BFF;#00FFB3;#00D4FF" dur="3s" repeatCount="indefinite"/> + <animate attributeName="stroke-width" values="0.5;3;0.5" dur="3s" repeatCount="indefinite"/> + <animate attributeName="opacity" values="0.8;1;0.8" dur="3s" repeatCount="indefinite"/> + </path> + + </g> + + <!-- Hub labels (below infinity engine) --> + <text x="640" y="403" text-anchor="middle" fill="#7a8a9e" font-family="'DM Sans',sans-serif" font-weight="700" font-size="15" letter-spacing="1.2">Events Stream</text> + <text x="640" y="429" text-anchor="middle" fill="#666666" font-family="'IBM Plex Mono',monospace" font-size="7">NATS JetStream</text> + <text x="640" y="440" text-anchor="middle" fill="#666666" font-family="'IBM Plex Mono',monospace" font-size="7">:4222</text> + + + <!-- ═══════════════════════════════════════ --> + <!-- SATELLITE SERVICES --> + <!-- ═══════════════════════════════════════ --> + + <!-- ─── ORCHESTRATOR (top, 0°) ─── --> + <g> + <rect x="505" y="60" width="270" height="110" rx="14" fill="none" stroke="#f59e0b" stroke-width="1.5" stroke-dasharray="8 4" class="solid-border"/> + <rect x="515" y="70" width="250" height="90" rx="10" fill="url(#orch-grad)" filter="url(#soft-shadow)"/> + <rect x="515" y="70" width="250" height="90" rx="10" fill="none" stroke="#e0d5c5" stroke-width="1"/> + <rect x="515" y="73" width="3" height="84" rx="1.5" fill="#f59e0b" opacity="0.5"/> + <circle cx="535" cy="92" r="8" fill="#e2e8f0" stroke="#f59e0b" stroke-width="1" opacity="0.8"/> + <text x="535" y="96" text-anchor="middle" fill="#f59e0b" font-size="9">▶</text> + <text x="553" y="95" fill="#f59e0b" font-family="'DM Sans',sans-serif" font-weight="700" font-size="13" letter-spacing="0.5">Orchestrator</text> + <text x="740" y="95" text-anchor="end" fill="#f59e0b" font-family="'IBM Plex Mono',monospace" font-size="8" opacity="0.5">:9011</text> + <circle cx="752" cy="92" r="3.5" fill="#f59e0b" class="health"/> + <g font-family="'IBM Plex Mono',monospace" font-size="8" fill="#555555"> + <rect x="528" y="113" width="220" height="14" rx="4" fill="#fef3db" stroke="#e8d5a0" stroke-width="0.5"/> + <text x="638" y="123" text-anchor="middle" opacity="0.6">Task State Machine • Provider API • SSH</text> + <rect x="528" y="137" width="220" height="14" rx="4" fill="#fef3db" stroke="#e8d5a0" stroke-width="0.5"/> + <text x="638" y="147" text-anchor="middle" opacity="0.55">Webhooks • Rollback • Audit Collector</text> + </g> + </g> + + <!-- ─── CONFIGURATION (top-right, cylinder) ─── --> + <g> + <ellipse cx="990" cy="152" rx="70" ry="11" fill="#10b981" opacity="0.12" stroke="none"/> + <path d="M 920 152 Q 990 178 1060 152" stroke="#10b981" stroke-width="0.8" fill="none" stroke-dasharray="4 2" opacity="0.3"/> + <rect x="920" y="91" width="140" height="61" rx="3" fill="#10b981" stroke="none" opacity="0.06" filter="url(#soft-shadow)"/> + <ellipse cx="990" cy="91" rx="70" ry="13" fill="#10b981" opacity="0.35" stroke="none"/> + <text x="990" y="126" text-anchor="middle" fill="#10b981" font-family="'DM Sans',sans-serif" font-weight="700" font-size="12" letter-spacing="0.5">Configuration</text> + <text x="990" y="152" text-anchor="middle" fill="#0a7a52" font-family="'IBM Plex Mono',monospace" font-size="7">Nickel • ∞ TypeDialog</text> + </g> + + <!-- ─── CONTROL CENTER (72°, upper-right) ─── --> + <g> + <rect x="241" y="248" width="250" height="110" rx="10" fill="url(#ctrl-grad)" filter="url(#soft-shadow)"/> + <rect x="241" y="248" width="250" height="110" rx="10" fill="none" stroke="#d5d0e8" stroke-width="1"/> + <rect x="241" y="251" width="3" height="104" rx="1.5" fill="#818cf8" opacity="0.5"/> + <circle cx="263" cy="270" r="8" fill="#e2e8f0" stroke="#818cf8" stroke-width="1" opacity="0.8"/> + <text x="263" y="274" text-anchor="middle" fill="#818cf8" font-size="9">◯</text> + <text x="279" y="273" fill="#818cf8" font-family="'DM Sans',sans-serif" font-weight="700" font-size="13" letter-spacing="0.5">Control Center</text> + <text x="461" y="273" text-anchor="end" fill="#818cf8" font-family="'IBM Plex Mono',monospace" font-size="8" opacity="0.5">:9012</text> + <circle cx="473" cy="270" r="3.5" fill="#818cf8" class="health health-d1"/> + <g font-family="'IBM Plex Mono',monospace" font-size="8" fill="#4a4870"> + <rect x="254" y="301" width="220" height="14" rx="4" fill="#eae5f8" stroke="#c8c0e8" stroke-width="0.5"/> + <text x="364" y="311" text-anchor="middle" opacity="0.9">Cedar Policies • JWT • Sessions</text> + <rect x="254" y="325" width="220" height="14" rx="4" fill="#eae5f8" stroke="#c8c0e8" stroke-width="0.5"/> + <text x="364" y="335" text-anchor="middle" opacity="0.85">WebSocket • RBAC • Solo: auto-session</text> + </g> + </g> + + <!-- ─── EXTENSION REGISTRY (144°, lower-right) ─── --> + <g> + <rect x="732" y="430" width="250" height="110" rx="10" fill="url(#ext-grad)" filter="url(#soft-shadow)"/> + <rect x="732" y="430" width="250" height="110" rx="10" fill="none" stroke="#d4bab2" stroke-width="1"/> + <rect x="978" y="433" width="3" height="104" rx="1.5" fill="#ef4444" opacity="0.5"/> + <circle cx="754" cy="452" r="8" fill="#e2e8f0" stroke="#ef4444" stroke-width="1" opacity="0.8"/> + <text x="754" y="456" text-anchor="middle" fill="#ef4444" font-size="9">⚙</text> + <text x="770" y="455" fill="#ef4444" font-family="'DM Sans',sans-serif" font-weight="700" font-size="12" letter-spacing="0.5">Extensions Registry</text> + <text x="952" y="455" text-anchor="end" fill="#ef4444" font-family="'IBM Plex Mono',monospace" font-size="8" opacity="0.5">:8084</text> + <circle cx="964" cy="452" r="3.5" fill="#ef4444" class="health health-d3"/> + <g font-family="'IBM Plex Mono',monospace" font-size="8" fill="#7a4868"> + <rect x="745" y="483" width="220" height="14" rx="4" fill="#eee1db" stroke="#d4bab2" stroke-width="0.5"/> + <text x="855" y="493" text-anchor="middle" opacity="0.9">Git • OCI • LRU</text> + <rect x="745" y="507" width="220" height="14" rx="4" fill="#eee1db" stroke="#d4bab2" stroke-width="0.5"/> + <text x="855" y="517" text-anchor="middle" opacity="0.85">Providers • Taskservs • vault:// creds</text> + </g> + </g> + + <!-- ─── AI/MCP (216°, lower-left) ─── --> + <g> + <rect x="298" y="430" width="250" height="110" rx="10" fill="url(#mcp-grad)" filter="url(#soft-shadow)"/> + <rect x="298" y="430" width="250" height="110" rx="10" fill="none" stroke="#c0e0e8" stroke-width="1"/> + <rect x="298" y="433" width="3" height="104" rx="1.5" fill="#22d3ee" opacity="0.5"/> + <circle cx="320" cy="452" r="8" fill="#e2e8f0" stroke="#22d3ee" stroke-width="1" opacity="0.8"/> + <text x="320" y="456" text-anchor="middle" fill="#22d3ee" font-size="9">🧠</text> + <text x="336" y="455" fill="#22d3ee" font-family="'DM Sans',sans-serif" font-weight="700" font-size="12" letter-spacing="0.5">AI • MCP</text> + <text x="523" y="455" text-anchor="end" fill="#22d3ee" font-family="'IBM Plex Mono',monospace" font-size="8" opacity="0.5">:9082</text> + <circle cx="535" cy="452" r="3.5" fill="#22d3ee" class="health health-d2"/> + <g font-family="'IBM Plex Mono',monospace" font-size="8" fill="#2a5050"> + <rect x="311" y="483" width="220" height="14" rx="4" fill="#ddf2f7" stroke="#b0dce8" stroke-width="0.5"/> + <text x="421" y="493" text-anchor="middle" opacity="0.9">RAG Engine • MCP Server • Tools</text> + <rect x="311" y="507" width="220" height="14" rx="4" fill="#ddf2f7" stroke="#b0dce8" stroke-width="0.5"/> + <text x="421" y="517" text-anchor="middle" opacity="0.85">Embeddings • Model Routing • KGraph</text> + </g> + </g> + + <!-- ─── VAULT SERVICE (288°, upper-left) ─── --> + <g> + <rect x="789" y="248" width="250" height="110" rx="10" fill="url(#vault-grad)" filter="url(#soft-shadow)"/> + <rect x="789" y="248" width="250" height="110" rx="10" fill="none" stroke="#f0c8d8" stroke-width="1"/> + <rect x="1035" y="251" width="3" height="104" rx="1.5" fill="#ec4899" opacity="0.5"/> + <circle cx="811" cy="270" r="8" fill="#e2e8f0" stroke="#ec4899" stroke-width="1" opacity="0.8"/> + <text x="811" y="274" text-anchor="middle" fill="#ec4899" font-size="9">🛡</text> + <text x="827" y="273" fill="#ec4899" font-family="'DM Sans',sans-serif" font-weight="700" font-size="13" letter-spacing="0.5">Vault Service</text> + <text x="1009" y="273" text-anchor="end" fill="#ec4899" font-family="'IBM Plex Mono',monospace" font-size="8" opacity="0.5">:9094</text> + <circle cx="1021" cy="270" r="3.5" fill="#ec4899" class="health health-d4"/> + <g font-family="'IBM Plex Mono',monospace" font-size="8" fill="#7a4870"> + <rect x="802" y="301" width="220" height="14" rx="4" fill="#fef0f5" stroke="#f0c8d8" stroke-width="0.5"/> + <text x="912" y="311" text-anchor="middle" opacity="0.9">Lease Lifecycle • Key Management</text> + <rect x="802" y="325" width="220" height="14" rx="4" fill="#fef0f5" stroke="#f0c8d8" stroke-width="0.5"/> + <text x="912" y="335" text-anchor="middle" opacity="0.85">SOPS • Age • Secrets never in NATS</text> + </g> + </g> + + + <!-- ═══ VERTICAL ARROW: CONFIGURATION → VAULT ═══ --> + <line x1="995" y1="169" x2="995" y2="248" stroke="#10b981" stroke-width="1.5" stroke-dasharray="4 3" opacity="0.35" marker-end="url(#arr-green-sm)"/> + + <!-- ═══ VERTICAL ARROW: CLI → CONTROL CENTER ═══ --> + <line x1="305" y1="177" x2="305" y2="248" stroke="#10b981" stroke-width="1.5" stroke-dasharray="4 3" opacity="0.35" marker-end="url(#arr-green-sm)"/> + + <!-- ═══ CLI → ORCHESTRATOR CURVE ═══ --> + <path d="M 375 138 C 430 100 480 80 505 120" fill="none" stroke="#fbbf24" stroke-width="1.2" stroke-dasharray="3 2" opacity="0.5"/> + + <!-- ═══ CONFIGURATION → ORCHESTRATOR CURVE ═══ --> + <path d="M 920 128 C 850 90 800 70 775 120" fill="none" stroke="#fbbf24" stroke-width="1.2" stroke-dasharray="3 2" opacity="0.5"/> + + <!-- ═══ CLI → AI CURVE ═══ --> + <path d="M 235 138 C 150 250 200 350 300 444" fill="none" stroke="#fbbf24" stroke-width="1.2" stroke-dasharray="3 2" opacity="0.5"/> + + <!-- ═══ CONFIGURATION → EXT CURVE ═══ --> + <path d="M 1060 128 C 1145 240 1095 340 979 444" fill="none" stroke="#fbbf24" stroke-width="1.2" stroke-dasharray="3 2" opacity="0.5"/> + + <!-- ═══ HTTPS CREDENTIAL BEAM ═══ --> + <path d="M 505 168 C 450 187 420 217 447 245" fill="none" stroke="#4a9eff" stroke-width="2" class="https-beam" filter="url(#glow-blue)"/> + <path d="M 505 168 C 450 187 420 217 447 245" fill="none" stroke="#4a9eff" stroke-width="1" stroke-dasharray="4 3" class="https-beam" opacity="0.4"/> + <path d="M 447 245 C 420 217 450 187 505 168" fill="none" stroke="#4a9eff" stroke-width="2" class="https-beam" filter="url(#glow-blue)"/> + <path d="M 447 245 C 420 217 450 187 505 168" fill="none" stroke="#4a9eff" stroke-width="1" stroke-dasharray="4 3" class="https-beam" opacity="0.4"/> + <text x="475" y="209" fill="#4a9eff" font-family="'IBM Plex Mono',monospace" font-size="7" font-weight="700" class="https-label">HTTPS</text> + <path d="M 775 168 C 830 187 860 217 833 245" fill="none" stroke="#4a9eff" stroke-width="2" class="https-beam" filter="url(#glow-blue)"/> + <path d="M 775 168 C 830 187 860 217 833 245" fill="none" stroke="#4a9eff" stroke-width="1" stroke-dasharray="4 3" class="https-beam" opacity="0.4"/> + <path d="M 833 245 C 860 217 830 187 775 168" fill="none" stroke="#4a9eff" stroke-width="2" class="https-beam" filter="url(#glow-blue)"/> + <path d="M 833 245 C 860 217 830 187 775 168" fill="none" stroke="#4a9eff" stroke-width="1" stroke-dasharray="4 3" class="https-beam" opacity="0.4"/> + <text x="803" y="209" fill="#4a9eff" font-family="'IBM Plex Mono',monospace" font-size="7" font-weight="700" class="https-label">HTTPS</text> + + + <!-- ═══ ORBITAL PARTICLES ═══ --> + + <circle r="3.5" fill="#f59e0b" opacity="0" filter="url(#glow-orange)"> + <animateMotion dur="8s" begin="0s" repeatCount="indefinite"> + <mpath href="#orbit-cw"/> + </animateMotion> + <animate attributeName="opacity" values="0;0.9;0.9;0" keyTimes="0;0.05;0.92;1" dur="8s" repeatCount="indefinite"/> + </circle> + + <circle r="2.5" fill="#ec4899" opacity="0" filter="url(#glow-pink)"> + <animateMotion dur="10s" begin="2s" repeatCount="indefinite"> + <mpath href="#orbit-cw"/> + </animateMotion> + <animate attributeName="opacity" values="0;0.8;0.8;0" keyTimes="0;0.05;0.92;1" dur="10s" begin="2s" repeatCount="indefinite"/> + </circle> + + <circle r="3" fill="#4a9eff" opacity="0" filter="url(#glow-blue)"> + <animateMotion dur="7s" begin="1s" repeatCount="indefinite"> + <mpath href="#orbit-ccw"/> + </animateMotion> + <animate attributeName="opacity" values="0;0.85;0.85;0" keyTimes="0;0.05;0.92;1" dur="7s" begin="1s" repeatCount="indefinite"/> + </circle> + + <circle r="2.5" fill="#818cf8" opacity="0" filter="url(#glow-purple)"> + <animateMotion dur="9s" begin="4s" repeatCount="indefinite"> + <mpath href="#orbit-cw"/> + </animateMotion> + <animate attributeName="opacity" values="0;0.7;0.7;0" keyTimes="0;0.05;0.92;1" dur="9s" begin="4s" repeatCount="indefinite"/> + </circle> + + <circle r="3" fill="#22d3ee" opacity="0" filter="url(#glow-cyan)"> + <animateMotion dur="11s" begin="3s" repeatCount="indefinite"> + <mpath href="#orbit-ccw"/> + </animateMotion> + <animate attributeName="opacity" values="0;0.75;0.75;0" keyTimes="0;0.05;0.92;1" dur="11s" begin="3s" repeatCount="indefinite"/> + </circle> + + <circle r="2" fill="#ef4444" opacity="0" filter="url(#glow-orange)"> + <animateMotion dur="12s" begin="5s" repeatCount="indefinite"> + <mpath href="#orbit-cw"/> + </animateMotion> + <animate attributeName="opacity" values="0;0.7;0.7;0" keyTimes="0;0.05;0.92;1" dur="12s" begin="5s" repeatCount="indefinite"/> + </circle> + + + <!-- ═══ ORBITAL FLASH EFFECTS ═══ --> + <!-- Color-matched bursts: each fires when its orbital particle passes nearest the service box --> + + <!-- Orchestrator burst — amber #f59e0b, CW 8s begin=0s, particle at top at cycle boundary --> + <g> + <path d="M 640 183 L 645 178 L 635 174 L 640 170" fill="none" stroke="#f59e0b" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round" filter="url(#glow-flash)"/> + <circle cx="640" cy="183" r="0" fill="#f59e0b" filter="url(#glow-flash)"> + <animate attributeName="r" values="10;0;0;10" keyTimes="0;0.15;0.85;1" dur="8s" begin="0s" repeatCount="indefinite"/> + </circle> + <animate attributeName="opacity" values="1;0;0;1" keyTimes="0;0.15;0.85;1" dur="8s" begin="0s" repeatCount="indefinite"/> + </g> + + <!-- Vault burst — pink #ec4899, CW 10s begin=2s, particle passes Vault at keyTime=0.23 --> + <g opacity="0"> + <path d="M 775 304 L 779 300 L 782 308 L 789 304" fill="none" stroke="#ec4899" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round" filter="url(#glow-flash)"/> + <circle cx="775" cy="304" r="0" fill="#ec4899" filter="url(#glow-flash)"> + <animate attributeName="r" values="0;0;10;0;0" keyTimes="0;0.15;0.23;0.38;1" dur="10s" begin="2s" repeatCount="indefinite"/> + </circle> + <animate attributeName="opacity" values="0;0;1;0;0" keyTimes="0;0.15;0.23;0.38;1" dur="10s" begin="2s" repeatCount="indefinite"/> + </g> + + <!-- Extensions burst — red #ef4444, CW 12s begin=5s, particle passes Extensions at keyTime=0.38 --> + <g opacity="0"> + <path d="M 732 420 L 736 424 L 728 427 L 732 430" fill="none" stroke="#ef4444" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round" filter="url(#glow-flash)"/> + <circle cx="732" cy="420" r="0" fill="#ef4444" filter="url(#glow-flash)"> + <animate attributeName="r" values="0;0;10;0;0" keyTimes="0;0.30;0.38;0.53;1" dur="12s" begin="5s" repeatCount="indefinite"/> + </circle> + <animate attributeName="opacity" values="0;0;1;0;0" keyTimes="0;0.30;0.38;0.53;1" dur="12s" begin="5s" repeatCount="indefinite"/> + </g> + + <!-- AI•MCP burst — cyan #22d3ee, CCW 11s begin=3s, particle passes AI at keyTime=0.38 --> + <g opacity="0"> + <path d="M 548 420 L 544 424 L 552 427 L 548 430" fill="none" stroke="#22d3ee" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round" filter="url(#glow-flash)"/> + <circle cx="548" cy="420" r="0" fill="#22d3ee" filter="url(#glow-flash)"> + <animate attributeName="r" values="0;0;10;0;0" keyTimes="0;0.30;0.38;0.53;1" dur="11s" begin="3s" repeatCount="indefinite"/> + </circle> + <animate attributeName="opacity" values="0;0;1;0;0" keyTimes="0;0.30;0.38;0.53;1" dur="11s" begin="3s" repeatCount="indefinite"/> + </g> + + <!-- Control Center burst — purple #818cf8, CW 9s begin=4s, particle passes CC at keyTime=0.75 --> + <g opacity="0"> + <path d="M 505 320 L 500 315 L 498 325 L 491 320" fill="none" stroke="#818cf8" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round" filter="url(#glow-flash)"/> + <circle cx="505" cy="320" r="0" fill="#818cf8" filter="url(#glow-flash)"> + <animate attributeName="r" values="0;0;10;0;0" keyTimes="0;0.67;0.75;0.90;1" dur="9s" begin="4s" repeatCount="indefinite"/> + </circle> + <animate attributeName="opacity" values="0;0;1;0;0" keyTimes="0;0.67;0.75;0.90;1" dur="9s" begin="4s" repeatCount="indefinite"/> + </g> + + + <!-- ═══ CREDENTIAL FLOW ═══ --> + + <circle r="4" fill="#f59e0b" opacity="0" filter="url(#glow-gold)"> + <animateMotion dur="2.5s" begin="0s" repeatCount="indefinite"> + <mpath href="#path-orch-vault"/> + </animateMotion> + <animate attributeName="opacity" values="0;0.95;0.95;0" keyTimes="0;0.1;0.8;1" dur="2.5s" repeatCount="indefinite"/> + </circle> + + <circle r="3.5" fill="#ec4899" opacity="0" filter="url(#glow-pink)"> + <animateMotion dur="2.5s" begin="4s" repeatCount="indefinite"> + <mpath href="#path-vault-orch"/> + </animateMotion> + <animate attributeName="opacity" values="0;0.9;0.9;0" keyTimes="0;0.1;0.8;1" dur="2.5s" begin="4s" repeatCount="indefinite"/> + </circle> + + <circle r="3" fill="#4a9eff" opacity="0" filter="url(#glow-blue)"> + <animateMotion dur="1.2s" begin="0s" repeatCount="indefinite"> + <mpath href="#path-cli-orch"/> + </animateMotion> + <animate attributeName="opacity" values="0;0.85;0.85;0" keyTimes="0;0.15;0.7;1" dur="1.2s" repeatCount="indefinite"/> + </circle> + + + <!-- ═══ CONFIGURATION CYLINDER EDGES ═══ --> + <line x1="920" y1="95" x2="920" y2="156" stroke="#10b981" stroke-width="0.8" stroke-dasharray="4 2" opacity="0.4"/> + <line x1="1060" y1="95" x2="1060" y2="156" stroke="#10b981" stroke-width="0.8" stroke-dasharray="4 2" opacity="0.4"/> + + <!-- ═══ DATA PERSISTENCE CYLINDER EDGES ═══ --> + <line x1="210" y1="582" x2="210" y2="669" stroke="#a78bfa" stroke-width="0.8" stroke-dasharray="4 2" opacity="0.4"/> + <line x1="1070" y1="582" x2="1070" y2="669" stroke="#a78bfa" stroke-width="0.8" stroke-dasharray="4 2" opacity="0.4"/> + + <!-- ═══ CONNECTIONS TO SURREALDB ═══ --> + <g opacity="0.3"> + <line x1="640" y1="460" x2="640" y2="570" stroke="#a78bfa" stroke-width="1" stroke-dasharray="4 3" class="db-flow" marker-end="url(#arr-silver)"/> + <line x1="430" y1="537" x2="430" y2="571" stroke="#a78bfa" stroke-width="0.8" stroke-dasharray="4 3" class="db-flow" marker-end="url(#arr-silver)"/> + <line x1="850" y1="537" x2="850" y2="571" stroke="#a78bfa" stroke-width="0.8" stroke-dasharray="4 3" class="db-flow" marker-end="url(#arr-silver)"/> + <path d="M 377 358 C 280 370 230 480 240 572" fill="none" stroke="#a78bfa" stroke-width="0.8" stroke-dasharray="4 3" class="db-flow" marker-end="url(#arr-silver)"/> + <path d="M 903 358 C 1000 370 1050 480 1040 572" fill="none" stroke="#a78bfa" stroke-width="0.8" stroke-dasharray="4 3" class="db-flow" marker-end="url(#arr-silver)"/> + <path d="M 1060 128 C 1195 270 1145 410 1070 577" fill="none" stroke="#a78bfa" stroke-width="0.8" stroke-dasharray="4 3" class="db-flow" marker-end="url(#arr-silver)"/> + <path d="M 235 138 C 100 280 150 420 210 579" fill="none" stroke="#a78bfa" stroke-width="0.8" stroke-dasharray="4 3" class="db-flow" marker-end="url(#arr-silver)"/> + </g> + + + <!-- ═══ DATA PERSISTENCE ═══ --> + + <g> + <ellipse cx="640" cy="669" rx="430" ry="10" fill="none" stroke="#a78bfa" stroke-width="1" opacity="0.1"/> + <path d="M 210 669 Q 640 687 1070 669" stroke="#c4a8f8" stroke-width="1" fill="none" stroke-dasharray="4 2" opacity="0.4"/> + <rect x="210" y="582" width="860" height="87" rx="8" fill="url(#surreal-grad)" stroke="none" opacity="0.6" filter="url(#soft-shadow)"/> + <ellipse cx="640" cy="582" rx="430" ry="12" fill="#a78bfa" opacity="0.45" stroke="none"/> + + <text x="640" y="620" text-anchor="middle" fill="#a78bfa" font-family="'DM Sans',sans-serif" font-weight="700" font-size="11" letter-spacing="1">DATA PERSISTENCE</text> + <text x="1055" y="607" text-anchor="end" fill="#6a58b8" font-family="'IBM Plex Mono',monospace" font-size="7" opacity="0.75">Solo: RocksDB • Multi: WebSocket</text> + + <g font-family="'IBM Plex Mono',monospace" font-size="7" font-weight="600"> + <rect x="258" y="640" width="130" height="16" rx="3" fill="#f59e0b" stroke="#f59e0b" stroke-width="0.8" opacity="0.25"/> + <text x="323" y="651" text-anchor="middle" fill="#8a5800">orchestrator</text> + + <rect x="400" y="640" width="110" height="16" rx="3" fill="#ec4899" stroke="#ec4899" stroke-width="0.8" opacity="0.25"/> + <text x="455" y="651" text-anchor="middle" fill="#900050">vault</text> + + <rect x="522" y="640" width="140" height="16" rx="3" fill="#818cf8" stroke="#818cf8" stroke-width="0.8" opacity="0.25"/> + <text x="592" y="651" text-anchor="middle" fill="#2830a0">control_center</text> + + <rect x="674" y="640" width="90" height="16" rx="3" fill="#10b981" stroke="#10b981" stroke-width="0.8" opacity="0.25"/> + <text x="719" y="651" text-anchor="middle" fill="#065c3a">audit</text> + + <rect x="776" y="640" width="130" height="16" rx="3" fill="#4a9eff" stroke="#4a9eff" stroke-width="0.8" opacity="0.25"/> + <text x="841" y="651" text-anchor="middle" fill="#0040a0">workspace</text> + + <rect x="918" y="640" width="100" height="16" rx="3" fill="none" stroke="#10b981" stroke-width="0.6" stroke-dasharray="4 2" opacity="0.5"/> + <text x="968" y="651" text-anchor="middle" fill="#065c3a">Nickel config</text> + </g> + </g> + + + <!-- ═══ SOLID ENFORCEMENT ═══ --> + + <g transform="translate(120, 759)"> + <text x="0" y="0" fill="#444444" font-family="'IBM Plex Mono',monospace" font-weight="600" font-size="8" letter-spacing="1.5">SOLID ENFORCEMENT LAYERS</text> + <g font-family="'IBM Plex Mono',monospace" font-size="7"> + <rect x="0" y="8" width="80" height="12" rx="3" fill="#f59e0b" opacity="0.25" stroke="#f59e0b" stroke-width="0.5" stroke-opacity="0.7"/> + <text x="40" y="17" text-anchor="middle" fill="#c07800">Compile-time</text> + <rect x="88" y="8" width="68" height="12" rx="3" fill="#818cf8" opacity="0.25" stroke="#818cf8" stroke-width="0.5" stroke-opacity="0.7"/> + <text x="122" y="17" text-anchor="middle" fill="#4850c0">Dev-time</text> + <rect x="164" y="8" width="74" height="12" rx="3" fill="#ec4899" opacity="0.25" stroke="#ec4899" stroke-width="0.5" stroke-opacity="0.7"/> + <text x="201" y="17" text-anchor="middle" fill="#b8206a">Pre-commit</text> + <rect x="246" y="8" width="50" height="12" rx="3" fill="#ef4444" opacity="0.25" stroke="#ef4444" stroke-width="0.5" stroke-opacity="0.7"/> + <text x="271" y="17" text-anchor="middle" fill="#b82020">CI/CD</text> + <rect x="304" y="8" width="60" height="12" rx="3" fill="#22d3ee" opacity="0.25" stroke="#22d3ee" stroke-width="0.5" stroke-opacity="0.7"/> + <text x="334" y="17" text-anchor="middle" fill="#0890a8">Runtime</text> + <rect x="372" y="8" width="50" height="12" rx="3" fill="#94a3b8" opacity="0.25" stroke="#94a3b8" stroke-width="0.5" stroke-opacity="0.7"/> + <text x="397" y="17" text-anchor="middle" fill="#4a5870">Audit</text> + </g> + <g font-family="'IBM Plex Mono',monospace" font-size="7" fill="#2a2e38" opacity="0.6"> + <text x="460" y="16" font-family="'IBM Plex Mono',monospace" font-size="7"><tspan fill="#f59e0b">Providers APIs or CLI</tspan><tspan fill="#2a2e38">: Orchestrator only | </tspan><tspan fill="#818cf8">SSH</tspan><tspan fill="#2a2e38">: Orchestrator + Machines | </tspan><tspan fill="#22d3ee">Auth</tspan><tspan fill="#2a2e38">: Control Center only | </tspan><tspan fill="#ec4899">Secrets</tspan><tspan fill="#2a2e38">: Vault Service API only</tspan></text> + </g> + </g> + + + <!-- ═══ LEGEND ═══ --> + + <g transform="translate(120, 802)" font-family="'IBM Plex Mono',monospace" font-size="8"> + <text x="0" y="0" fill="#444444" font-weight="600" font-size="8" letter-spacing="1.5">SERVICES</text> + <circle cx="7" cy="12" r="3" fill="#f59e0b"/><text x="15" y="16" fill="#8a7a60">Orchestrator</text> + <circle cx="7" cy="24" r="3" fill="#818cf8"/><text x="15" y="28" fill="#4a4870">Control Center</text> + <circle cx="7" cy="36" r="3" fill="#ec4899"/><text x="15" y="40" fill="#7a4870">Vault Service</text> + <circle cx="7" cy="48" r="3" fill="#ef4444"/><text x="15" y="52" fill="#7a4868">Extension Registry</text> + <circle cx="7" cy="60" r="3" fill="#22d3ee"/><text x="15" y="64" fill="#487a78">AI • MCP</text> + + <text x="240" y="0" fill="#444444" font-weight="600" font-size="8" letter-spacing="1.5">INFRASTRUCTURE</text> + <circle cx="248" cy="12" r="5" fill="none" stroke="#94a3b8" stroke-width="1.2" stroke-dasharray="2 2"/> + <text x="260" y="16" fill="#666666" opacity="0.5">NATS Orbital Ring</text> + <rect x="243" y="22.5" width="6" height="6" fill="none" stroke="#ef4444" stroke-width="0.8" rx="0.5"/> + <line x1="243" y1="25.5" x2="249" y2="25.5" stroke="#ef4444" stroke-width="0.7"/> + <line x1="246" y1="22.5" x2="246" y2="28.5" stroke="#ef4444" stroke-width="0.7" opacity="0.6"/> + <text x="260" y="28" fill="#ef4444" opacity="0.5">OCI registry</text> + <line x1="240" y1="36" x2="254" y2="36" stroke="#818cf8" stroke-width="1.5"/> + <text x="260" y="40" fill="#818cf8" opacity="0.5">Token and Auth</text> + <path d="M 244 43 L 250 43 L 250 48 Q 247 52 244 48 L 244 43" fill="none" stroke="#ec4899" stroke-width="1.2" stroke-linejoin="round"/> + <text x="260" y="52" fill="#ec4899" opacity="0.5">SecretumVault</text> + <line x1="240" y1="60" x2="254" y2="60" stroke="#10b981" stroke-width="1.5"/> + <text x="260" y="64" fill="#10b981" opacity="0.5">Nickel</text> + <ellipse cx="247" cy="69" rx="5" ry="2" fill="none" stroke="#a78bfa" stroke-width="0.8"/> + <rect x="243" y="69" width="8" height="5" fill="none" stroke="#a78bfa" stroke-width="0.8" opacity="0.5"/> + <ellipse cx="247" cy="74" rx="5" ry="2" fill="#a78bfa" opacity="0.3" stroke="#a78bfa" stroke-width="0.8"/> + <text x="260" y="76" fill="#a78bfa" opacity="0.5">SurrealDB</text> + + <text x="460" y="0" fill="#444444" font-weight="600" font-size="8" letter-spacing="1.5">CONNECTIONS</text> + <line x1="460" y1="12" x2="474" y2="12" stroke="#4a9eff" stroke-width="2" filter="url(#glow-blue)"/> + <line x1="460" y1="12" x2="474" y2="12" stroke="#4a9eff" stroke-width="1" stroke-dasharray="4 3" opacity="0.7"/> + <text x="486" y="16" fill="#4a9eff" opacity="0.5">Encrypted</text> + <line x1="460" y1="24" x2="474" y2="24" stroke="#ef9a3c" stroke-width="1.5" stroke-dasharray="2 2"/> + <text x="486" y="28" fill="#ef9a3c" opacity="0.3">I/O and Defs</text> + <line x1="460" y1="36" x2="474" y2="36" stroke="#a78bfa" stroke-width="1.5" stroke-dasharray="2 2"/> + <text x="486" y="40" fill="#6a5888">DBs data</text> + <line x1="460" y1="48" x2="474" y2="48" stroke="#10b981" stroke-width="1.5" stroke-dasharray="2 2"/> + <text x="486" y="52" fill="#10b981" opacity="0.5">Secure access</text> + <circle cx="468" cy="60" r="5" fill="none" stroke="#94a3b8" stroke-width="1.2" stroke-dasharray="2 2"/> + <text x="486" y="64" fill="#666666">Events Stream</text> + + <text x="685" y="0" fill="#444444" font-weight="600" font-size="8" letter-spacing="1.5">MODES</text> + <rect x="685" y="11" width="40" height="10" rx="2" fill="#34d399" opacity="0.1" stroke="#34d399" stroke-width="0.4"/> + <text x="705" y="19" text-anchor="middle" fill="#34d399" font-size="7" font-weight="600">SOLO</text> + <text x="735" y="19" fill="#5a8a6a" font-size="7">RocksDB + local NATS + auto-session</text> + <rect x="685" y="25" width="40" height="10" rx="2" fill="#818cf8" opacity="0.1" stroke="#818cf8" stroke-width="0.4"/> + <text x="705" y="33" text-anchor="middle" fill="#818cf8" font-size="7" font-weight="600">MULTI</text> + <text x="735" y="33" fill="#4a4870" font-size="7">WebSocket DB + NATS cluster + JWT+Cedar</text> + <rect x="685" y="39" width="40" height="10" rx="2" fill="#f59e0b" opacity="0.1" stroke="#f59e0b" stroke-width="0.4"/> + <text x="705" y="47" text-anchor="middle" fill="#f59e0b" font-size="7" font-weight="600">ENT</text> + <text x="735" y="47" fill="#d97706" font-size="7">Enterprise</text> + </g> + + + <!-- ═══ DEPLOYMENT ═══ --> + + <g transform="translate(145, 693)" font-family="'IBM Plex Mono',monospace"> + <rect x="160" y="0" width="120" height="45" rx="6" fill="#f4ebe4" stroke="#d4bab2" stroke-width="0.8"/> + <circle cx="174" cy="15" r="3" fill="#ef4444"/><text x="182" y="18" fill="#c04040" font-size="8" font-weight="500">Production</text> + <text x="209" y="35" fill="#8a5858" font-size="7" text-anchor="middle">Hetzner • AWS</text> + + <rect x="300" y="0" width="120" height="45" rx="6" fill="#fff8ed" stroke="#f0e0c0" stroke-width="0.8"/> + <circle cx="314" cy="15" r="3" fill="#f59e0b"/><text x="322" y="18" fill="#b07808" font-size="8" font-weight="500">Staging</text> + <text x="344" y="35" fill="#8a7050" font-size="7" text-anchor="middle">K8s cluster</text> + + <rect x="440" y="0" width="120" height="45" rx="6" fill="#edf8f2" stroke="#c0e8d0" stroke-width="0.8"/> + <circle cx="454" cy="15" r="3" fill="#34d399"/><text x="462" y="18" fill="#208060" font-size="8" font-weight="500">Dev</text> + <text x="484" y="35" fill="#508a68" font-size="7" text-anchor="middle">Solo mode</text> + + <rect x="580" y="0" width="120" height="45" rx="6" fill="#f0eeff" stroke="#d0d0f0" stroke-width="0.8"/> + <circle cx="594" cy="15" r="3" fill="#818cf8"/><text x="602" y="18" fill="#5060c0" font-size="8" font-weight="500">Edge</text> + <text x="624" y="35" fill="#68688a" font-size="7" text-anchor="middle">On-prem • IoT</text> + + <rect x="720" y="0" width="120" height="45" rx="6" fill="#edf9fc" stroke="#c0e8f0" stroke-width="0.8"/> + <circle cx="734" cy="15" r="3" fill="#22d3ee"/><text x="742" y="18" fill="#1898a8" font-size="8" font-weight="500">Custom</text> + <text x="774" y="35" fill="#508888" font-size="7" text-anchor="middle">GitOps • Webhook</text> + </g> + + <!-- ═══ VERSION ═══ --> + <text x="1160" y="872" text-anchor="end" fill="#4cc2f1" font-family="'IBM Plex Mono',monospace" font-size="9" letter-spacing="0.5">v3.0.11 • ∞ Architecture</text> +</svg> diff --git a/site/site/public/_content/projects/es/provisioning-systems/images/provisioning-systems_arch-dark.svg b/site/site/public/_content/projects/es/provisioning-systems/images/provisioning-systems_arch-dark.svg new file mode 100644 index 0000000..4eb7b37 --- /dev/null +++ b/site/site/public/_content/projects/es/provisioning-systems/images/provisioning-systems_arch-dark.svg @@ -0,0 +1,700 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 900"> + <style> + .orbit-flow { animation: orbit-dash 4s linear infinite; } + @keyframes orbit-dash { from { stroke-dashoffset: 40; } to { stroke-dashoffset: 0; } } + + .ring-glow { animation: ring-breathe 3s ease-in-out infinite; } + @keyframes ring-breathe { 0%,100% { opacity: 0.06; } 50% { opacity: 0.18; } } + + .spoke { animation: spoke-dash 2s linear infinite; } + @keyframes spoke-dash { from { stroke-dashoffset: 16; } to { stroke-dashoffset: 0; } } + + .health { animation: pulse 2.2s ease-in-out infinite; } + .health-d1 { animation-delay: 0.4s; } + .health-d2 { animation-delay: 0.8s; } + .health-d3 { animation-delay: 1.2s; } + .health-d4 { animation-delay: 1.6s; } + @keyframes pulse { 0%,100% { opacity: 0.35; } 50% { opacity: 1; } } + + .https-beam { animation: https-p 4s ease-in-out infinite; } + @keyframes https-p { 0%,100% { opacity: 0.15; stroke-width: 1.5; } 50% { opacity: 0.7; stroke-width: 2.5; } } + .https-label { animation: https-lbl 4s ease-in-out infinite; } + @keyframes https-lbl { 0%,100% { opacity: 0.3; } 50% { opacity: 1; } } + + .solid-border { animation: solid-glow 3.5s ease-in-out infinite; } + @keyframes solid-glow { 0%,100% { stroke-opacity: 0.3; } 50% { stroke-opacity: 0.7; } } + + .hub-pulse { animation: hub-beat 3s ease-in-out infinite; } + @keyframes hub-beat { 0%,100% { opacity: 0.3; } 50% { opacity: 0.6; } } + + .cred-dot { opacity: 0; } + .db-flow { animation: db-dash 2.5s linear infinite; } + @keyframes db-dash { from { stroke-dashoffset: 12; } to { stroke-dashoffset: 0; } } + </style> + + <defs> + <filter id="glow-blue" x="-50%" y="-50%" width="200%" height="200%"> + <feGaussianBlur in="SourceGraphic" stdDeviation="6" result="blur"/> + <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge> + </filter> + <filter id="glow-orange" x="-50%" y="-50%" width="200%" height="200%"> + <feGaussianBlur in="SourceGraphic" stdDeviation="5" result="blur"/> + <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge> + </filter> + <filter id="glow-pink" x="-50%" y="-50%" width="200%" height="200%"> + <feGaussianBlur in="SourceGraphic" stdDeviation="5" result="blur"/> + <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge> + </filter> + <filter id="glow-purple" x="-50%" y="-50%" width="200%" height="200%"> + <feGaussianBlur in="SourceGraphic" stdDeviation="5" result="blur"/> + <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge> + </filter> + <filter id="glow-cyan" x="-50%" y="-50%" width="200%" height="200%"> + <feGaussianBlur in="SourceGraphic" stdDeviation="5" result="blur"/> + <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge> + </filter> + <filter id="glow-gold" x="-50%" y="-50%" width="200%" height="200%"> + <feGaussianBlur in="SourceGraphic" stdDeviation="6" result="blur"/> + <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge> + </filter> + <filter id="glow-flash" x="-100%" y="-100%" width="300%" height="300%"> + <feGaussianBlur in="SourceGraphic" stdDeviation="10" result="blur"/> + <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge> + </filter> + <filter id="soft-shadow" x="-20%" y="-20%" width="140%" height="140%"> + <feDropShadow dx="0" dy="2" stdDeviation="8" flood-color="#000" flood-opacity="0.5"/> + </filter> + <filter id="ring-glow-f" x="-20%" y="-20%" width="140%" height="140%"> + <feGaussianBlur in="SourceGraphic" stdDeviation="8" result="blur"/> + <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge> + </filter> + + <radialGradient id="bg-radial" cx="50%" cy="42%" r="55%"> + <stop offset="0%" stop-color="#111520"/> + <stop offset="100%" stop-color="#0a0c10"/> + </radialGradient> + <radialGradient id="hub-glow" cx="50%" cy="50%" r="50%"> + <stop offset="0%" stop-color="#94a3b8" stop-opacity="0.15"/> + <stop offset="100%" stop-color="#94a3b8" stop-opacity="0"/> + </radialGradient> + <linearGradient id="orch-grad" x1="0%" y1="0%" x2="100%" y2="100%"> + <stop offset="0%" stop-color="#2a2016"/><stop offset="100%" stop-color="#1a150f"/> + </linearGradient> + <linearGradient id="ctrl-grad" x1="0%" y1="0%" x2="100%" y2="100%"> + <stop offset="0%" stop-color="#1a1630"/><stop offset="100%" stop-color="#110f1e"/> + </linearGradient> + <linearGradient id="vault-grad" x1="0%" y1="0%" x2="100%" y2="100%"> + <stop offset="0%" stop-color="#2a1630"/><stop offset="100%" stop-color="#1a0f1e"/> + </linearGradient> + <linearGradient id="ext-grad" x1="0%" y1="0%" x2="100%" y2="100%"> + <stop offset="0%" stop-color="#2a1620"/><stop offset="100%" stop-color="#1a0f14"/> + </linearGradient> + <linearGradient id="mcp-grad" x1="0%" y1="0%" x2="100%" y2="100%"> + <stop offset="0%" stop-color="#0f2a2e"/><stop offset="100%" stop-color="#0a1c1e"/> + </linearGradient> + <linearGradient id="surreal-grad" x1="0%" y1="0%" x2="100%" y2="100%"> + <stop offset="0%" stop-color="#3a3050"/><stop offset="100%" stop-color="#2e2640"/> + </linearGradient> + <linearGradient id="cli-grad" x1="0%" y1="0%" x2="100%" y2="100%"> + <stop offset="0%" stop-color="#1a2a42"/><stop offset="100%" stop-color="#0f1a2e"/> + </linearGradient> + + <!-- Animated gradient for infinity engine color cycling --> + <linearGradient id="infinityGradient" x1="0%" y1="0%" x2="100%" y2="0%"> + <stop offset="0%" style="stop-color:#00D4FF;stop-opacity:1"> + <animate attributeName="stop-color" values="#00D4FF;#7B2BFF;#00FFB3;#00D4FF" dur="3s" repeatCount="indefinite"/> + </stop> + <stop offset="50%" style="stop-color:#7B2BFF;stop-opacity:1"> + <animate attributeName="stop-color" values="#7B2BFF;#00FFB3;#00D4FF;#7B2BFF" dur="3s" repeatCount="indefinite"/> + </stop> + <stop offset="100%" style="stop-color:#00FFB3;stop-opacity:1"> + <animate attributeName="stop-color" values="#00FFB3;#00D4FF;#7B2BFF;#00FFB3" dur="3s" repeatCount="indefinite"/> + </stop> + </linearGradient> + + <marker id="arr-blue" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"> + <polygon points="0 0, 8 3, 0 6" fill="#4a9eff"/> + </marker> + <marker id="arr-silver" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"> + <polygon points="0 0, 8 3, 0 6" fill="#94a3b8"/> + </marker> + <marker id="arr-gold" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"> + <polygon points="0 0, 8 3, 0 6" fill="#fbbf24"/> + </marker> + <marker id="arr-green" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"> + <polygon points="0 0, 8 3, 0 6" fill="#10b981"/> + </marker> + <marker id="arr-green-sm" markerWidth="5" markerHeight="4" refX="5" refY="2" orient="auto"> + <polygon points="0 0, 5 2, 0 4" fill="#10b981"/> + </marker> + + <path id="orbit-cw" d="M 640,185 A 135,135 0 1,1 640,455 A 135,135 0 1,1 640,185 Z" fill="none"/> + <path id="orbit-ccw" d="M 640,185 A 135,135 0 1,0 640,455 A 135,135 0 1,0 640,185 Z" fill="none"/> + + <path id="path-orch-vault" d="M 640,185 A 135,135 0 0,0 508,276" fill="none"/> + <path id="path-vault-orch" d="M 508,276 A 135,135 0 0,1 640,185" fill="none"/> + <path id="path-cli-orch" d="M 660,98 L 660,115" fill="none"/> + </defs> + + <!-- ═══ BACKGROUND ═══ --> + <rect width="1280" height="900" fill="url(#bg-radial)"/> + <g opacity="0.03"> + <pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse"> + <path d="M 40 0 L 0 0 0 40" fill="none" stroke="#fff" stroke-width="0.5"/> + </pattern> + <rect width="1280" height="900" fill="url(#grid)"/> + </g> + + <!-- Star field --> + <g opacity="0.35"> + <circle cx="95" cy="145" r="0.8" fill="#fff"/><circle cx="340" cy="78" r="0.6" fill="#fff"/> + <circle cx="890" cy="115" r="1" fill="#fff"/><circle cx="1120" cy="210" r="0.7" fill="#fff"/> + <circle cx="180" cy="460" r="0.5" fill="#fff"/><circle cx="1160" cy="510" r="0.8" fill="#fff"/> + <circle cx="72" cy="660" r="0.6" fill="#fff"/><circle cx="960" cy="710" r="0.9" fill="#fff"/> + </g> + + <!-- ═══ TITLE ═══ --> + <g transform="translate(206, 20) scale(0.35)"> + <g id="logo-content"><g><g><path d="M601.85,109.76c.89,.04,.57-.38,.7-.46-1.49,.09-.67,.57-.7,.46Z" style="fill:#b0dff6;"/><polygon points="587.17 110.72 587.43 110.55 585.26 110.96 587.17 110.72" style="fill:#b0dff6;"/><path d="M573.43,110.71c1.3,.71,4.98-.12,7.42,.14l.4,.16,1.1-.57c-2.97,.09-6.84,.47-8.93,.27Z" style="fill:#b0dff6;"/><path d="M566.11,109.16c.45,.28-1.58,.24,1.71,.09-.17-.03-.21-.05-.31-.07-.3,0-.7,0-1.4-.02Z" style="fill:#b0dff6;"/><path d="M567.51,109.18c1.45-.02-.95-.23,0,0h0Z" style="fill:#b0dff6;"/><path d="M560.83,109.34l1.9-.25c-.4-.16-2.09-.2-1.46-.32-1.95,.14-1.55,.3-.44,.57Z" style="fill:#b0dff6;"/><path d="M559.02,109.86l.99-.1c-.17-.07-.43-.09-.99,.1Z" style="fill:#b0dff6;"/><path d="M562.11,109.53l-2.09,.22c.28,.12,.3,.35,.96-.08-.17,.21,.24,0,1.13-.14Z" style="fill:#b0dff6;"/><path d="M563.29,109.64c1.07,.16,2.45,.25,.54,.5,3.64-.1,3.11-.6-.54-.5Z" style="fill:#b0dff6;"/><path d="M572.73,111.45l1.43,.21,1.24-.24c-.62,.12-2.75-.19-2.66,.03Z" style="fill:#b0dff6;"/><path d="M548.96,108.97c-.12,.15,.64,.4,.78,.6l1.41-.17c-.72-.1-2.09-.2-2.19-.42Z" style="fill:#b0dff6;"/><path d="M549.75,109.57l-2.11,.26c1.88,.03,2.22-.09,2.11-.26Z" style="fill:#b0dff6;"/><polygon points="558.69 110.65 562.14 110.11 558.02 110.66 558.69 110.65" style="fill:#b0dff6;"/><polygon points="540.92 108.95 542.12 108.6 539.99 109.13 540.92 108.95" style="fill:#b0dff6;"/><path d="M535.59,109.01c.88-.29-1.21-.49-.28-.67-1.45,.05-2.69,.3-3.8,.53,1.18-.13,2.44-.09,4.08,.14Z" style="fill:#b0dff6;"/><path d="M529.64,109.2c.59-.07,1.2-.19,1.87-.33-.63,.07-1.25,.16-1.87,.33Z" style="fill:#b0dff6;"/><path d="M544.06,109.76c-.16-.09-.48-.33-1.66-.43l.83,.43c.25,0,.54,0,.83,0Z" style="fill:#b0dff6;"/><path d="M543.26,109.76h-.03c-.9-.02-1.52-.03-1.79-.04,.23,0,.78,.01,1.82,.04Z" style="fill:#b0dff6;"/><path d="M544.26,109.76c-.08,0-.13,0-.21,0,.08,.04,.13,.06,.21,0Z" style="fill:#b0dff6;"/><path d="M553.17,110.27c-1.24,.24-3.82,.5-1.32,.86,.89-.29-.54-.5,1.32-.86Z" style="fill:#b0dff6;"/><path d="M545.29,110.29l2.24,.1c-.04-.11-.45-.28,.17-.4l-2.4,.3Z" style="fill:#b0dff6;"/><polygon points="544.2 110.43 545.29 110.29 544.46 110.26 544.2 110.43" style="fill:#b0dff6;"/><path d="M533.47,109.6c-.97,.19-2.02,.2-3.22,.23,1.28,.08,2.6,.13,3.86,.13,.18-.13,.73-.38-.64-.36Z" style="fill:#b0dff6;"/><path d="M538.23,109.71c-.07,0-.15,.01-.21,.02,.13,.04,.24,.05,.21-.02Z" style="fill:#b0dff6;"/><path d="M518.51,109.82c.07,.02,.12,.05,.19,.08,.01-.02,.02-.05,.04-.07h-.23Z" style="fill:#b0dff6;"/><path d="M534.33,110.04c.96,.08,1.54-.02,2.08-.15-.72,.04-1.48,.07-2.3,.07-.08,.06-.09,.1,.21,.08Z" style="fill:#b0dff6;"/><path d="M526.36,109.4c.5,.09,1.07,.17,1.67,.23-.49-.14-1.49-.35-1.67-.23Z" style="fill:#b0dff6;"/><path d="M537.69,109.62s.09-.01,.14-.02c-.32-.09-.28-.05-.14,.02Z" style="fill:#b0dff6;"/><path d="M537.69,109.62c-.49,.06-.88,.17-1.27,.26,.59-.03,1.13-.08,1.6-.14-.12-.03-.24-.08-.32-.12Z" style="fill:#b0dff6;"/><path d="M514.95,112.57c.17-.4,2.7-.76,4.88-.34,2.15-.3,.74-1.6-1.13-2.33-.07,.17-.07,.33-2.28,.57,.77,.22,4.9,.51,3.04,.86-2.93,.2-2.4-.14-3.82-.35,1.21,.49-3.72,.72-3.09,1.44l2.57-.26c.05,.11-.88,.29-.17,.4Z" style="fill:#b0dff6;"/><path d="M522.91,109.94l.91-.05c-1.04-.07-1.78-.23-3.15-.54-1.51,.19-1.83,.34-1.93,.47l4.48,.06-.31,.06Z" style="fill:#b0dff6;"/><path d="M527.9,109.89c-.71,.43-2.89,.61-1.56,1.08,2.67-.03,1.87-.36,3.46-.55-4.31,.11,1.24-.24-1.87-.48,.85-.07,1.61-.1,2.33-.11-.78-.05-1.52-.12-2.23-.2,.04,0,.07,.02,.1,.03h.02s0,0,0,0c.19,.05,.25,.09,.04,.06-.01,0-.07,0-.09,0-.03,.06-.11,.11-.19,.16-1.25-.09-.2-.11,.19-.16,.01-.02,.05-.04,.05-.06,0,0-.01,0-.02,0l-4.31,.24c.91,.06,2.06,.05,4.08,0Z" style="fill:#b0dff6;"/><path d="M549.54,112.05c-.13-.04-.13-.07-.14-.1-.28,.05-.38,.09,.14,.1Z" style="fill:#b0dff6;"/><path d="M547.77,111.79c.31-.06,.57-.23,1.59-.19,.88,.13,0,.21,.04,.35,.61-.11,2.15-.31,.22-.52l-.31,.06c-1.74-.15-3.97-.69-3.66-.74,.51,.39,.95,.66,2.11,1.04Z" style="fill:#b0dff6;"/><path d="M537.71,110.95l-3.59,.21,1.83,.37-.09-.22c2.04,.09,1.6-.19,1.86-.36Z" style="fill:#b0dff6;"/><polygon points="518.1 109.61 518.57 109.15 516.5 109.8 518.1 109.61" style="fill:#b0dff6;"/><polygon points="513.43 109.66 513.6 109.27 512.37 109.51 513.43 109.66" style="fill:#b0dff6;"/><path d="M250.01,114.57l-1.06,.11c.5-.03,.82-.07,1.06-.11Z" style="fill:#b0dff6;"/><path d="M257.65,114.6l.19-.19c-.35,.11-.5,.19-.19,.19Z" style="fill:#b0dff6;"/><path d="M398.78,109.71c-1.54-.37-3.25-.94-3.07-.5l.47,.43c1.07-.08,1.9-.03,2.6,.06Z" style="fill:#b0dff6;"/><path d="M320.27,112.3c.15,.04,.3,.09,.45,.13,.37-.15,.73-.29,1.04-.42-.45,.09-.96,.18-1.49,.28Z" style="fill:#b0dff6;"/><path d="M319.18,114.09l-2.03-.28c.46,.1,1.11,.19,2.03,.28Z" style="fill:#b0dff6;"/><path d="M208.27,115.03l-1.23,.44c1.93,0,1.08-.23,1.23-.44Z" style="fill:#b0dff6;"/><path d="M465.62,109.82c-1.33-.17-2.38-.24-3.31-.26,1.6,.25,2.89,.62,3.31,.26Z" style="fill:#b0dff6;"/><path d="M253.01,109.97c-.65,.08-.92,.17-.9,.26,.66-.07,1.04-.16,.9-.26Z" style="fill:#b0dff6;"/><path d="M458.48,109.56c.23,.04,.46,.07,.7,.1,.97-.07,1.95-.13,3.13-.1-1.18-.19-2.53-.3-3.83,0Z" style="fill:#b0dff6;"/><path d="M56.92,114.68c-.11-.02-.24-.03-.34-.05-.03,.06,.12,.06,.34,.05Z" style="fill:#b0dff6;"/><path d="M12.82,110.3c.43-.03,1.14-.03,1.81-.03-.33-.03-.87-.03-1.81,.03Z" style="fill:#b0dff6;"/><path d="M494.65,109.47c.99-.03,.16-.05,0,0h0Z" style="fill:#b0dff6;"/><path d="M499.46,111.19c-.21,.05-.37,.11-.4,.21,.08-.1,.22-.17,.4-.21Z" style="fill:#b0dff6;"/><path d="M448.61,112.26s-.05,0-.09,0c.29,.08,.24,.07,.09,0Z" style="fill:#b0dff6;"/><path d="M496.14,109.67c-.37-.05-.8-.1-1.27-.13,.2,.03,.59,.08,1.27,.13Z" style="fill:#b0dff6;"/><path d="M495.93,109.47c.34,.02,.61,.03,.87,.04,.12-.07,.01-.12-.87-.04Z" style="fill:#b0dff6;"/><path d="M493.27,109.5c.57,0,1.09,.01,1.6,.05-.19-.03-.28-.05-.22-.07-.29,0-.7,.02-1.38,.02Z" style="fill:#b0dff6;"/><path d="M138.01,113.51c-.07-.01-.14-.02-.21-.04-1,.18-.56,.15,.21,.04Z" style="fill:#b0dff6;"/><path d="M373.02,114.21s.07-.02,.11-.02c-.05,0-.1-.01-.15-.02l.03,.04Z" style="fill:#b0dff6;"/><path d="M369.46,114.54c1.51-.54,2.38-.47,3.52-.36l-1.18-1.25-2.35,1.61Z" style="fill:#b0dff6;"/><path d="M231.71,114.7l2.5,.39c-3.01,.64-5.19,.42-2.85,.97,.35-1.36,9.71-.14,10.05-1.5l1.65,.29c-.47-.01-.55,.07-1.01,.05,3.2,.65,2.38-.98,6.5-.79,1.44,.1,2.44,.3,1.46,.46l3.2-.34c.93,.03,.7,.26-.22,.24l4.89-.11-.04,.04c.7-.21,2.28-.54,3.05-.68,1.24,.19-.62,.14-.3,.31l2.71-.33c1.1,.35-1.14,.69-3.01,.64,2.53,1.36,2.24-.34,7.7,.45l-2.55,.17c1.65,.77,3.64,.49,6.53,.97-.69-.25-5.78-1.21-2.85-1.55,.83,.1,1.92,.15,3.5-.12,.01,.48,1.56,.36,3.5,.34l-.13,.64c2.09-.18,1.93-.51,3.16-.8,2.33,.06,2.82,.56,3.36,.98,3.73,.1-2.58-.79,1.84-.91,3.11,.24,5.29-.02,9-.4,2.17,.22-.38,.39,.4,.57l1.93-.51c.47,.01,.32,.17,.24,.25,1.62-.2,.13-.64,2.69-.81,.69-.7,5.24,1.03,8.4,.23,.78,.18-.38,.39-.14,.64,4.33-.52,3.72-.38,7.27-1.09l3.03,.42c-2.23-.48,.62-1.05,3.11-1.52-.79-.23-1.47-.43-1.92-.53l3.98-.34c.48,.1,.08,.32-.57,.59,.27-.05,.53-.11,.73-.15,1.42,.24,.69,.61-.33,.96-.48-.13-.97-.26-1.43-.39-.98,.39-1.9,.82-1.7,1.19,.26-.07,.76-.14,1.3-.17-.26,.11-.42,.2-.3,.26,.14,.07,.55-.07,1.2-.24l.9,.71,.22-.28,5.2,.45c3.72-.72,6.96-1.77,12.87-2.07-1.47,.52-.32,.85-.4,1.47-1.61-.66-4.58,.28-7.64,.15,1.6,.07,1.25,.36,.84,.47l5.46-.6c-.12,.51,1.16,.38,2.59,.59-1.08-1,4.07-.67,6.3-.97,.14,.34-.4,.68-3.11,.6,2.99,.75,3.87-1.18,6.8-.54-.66,0-1.02-.04-1.28,.13,1.55-.3,5.33-.06,4.44,.23h-.66c4.4,.13,13.38,.01,14.04-.84,.04,.11-.58,1.03-.84,1.2l7.84-2.86c-.69,.73,1.96,2.47-1.68,2.88,1.05,.1,2.34,.23,4.55-.03-.71-.1-1.78-1.06-.8-1.12,2.68,.81,1.86,.43,5.16,1.13-1.07-.16,.12-1.3,1.81-1.27-.26,.17,.14,1.13-.48,1.25l4.21-1.12c-1.05,.2-.19,.95,.76,1.27-.44-.22,3.64,.02,4.76-.09l-1.78-.26c4.75,.17,4.7-1.58,9.41-1.52-.57,.23-1.41,1.43,.99,1.57,1.1-.58,4.87-2.77,9.04-3.21l.8,.33,3.2-.38c-1.99,.87-7.59,2.69-10.64,3.4,1.78,.26,.5,.39,3.16,.36,.94,.31-1.45,.47-2.24,.49l6.2,.2c-.28-.67,4.74-.67,4.51-1.23l-6.74,.7c-.27-.67,3.9-1.91,8.29-1.79,1.12,.27-.84,1.2-.74,1.42,.57-.23,4.17-.44,4.58-.28l-1.86,.36c2.45,.25,3.99-.89,6.93-.25,1.01,.04,3.03,.87,3.21,.47-1.66-.77-1.98-3.14-.88-3.72,.36,.05,5.33,.73,6.49,1.11,1.67,.6-2.51,1.43-1.09,1.95,.06-.18,.98-.41,1.37-.48,1.07,.16-.44,.57,1.87,.49,.39-.68,4.71,.06,1.09-.58,2.27-.19,2.41,.14,5.07,.11-.85-.44,2.39-1.5,4.66-1.69-.13,.17,.38,.4,.62,.5,4.64,.06,7.14,.37,11.47,.38,.4,.17,2.49,.36,1.6,.66,.62-.12,1.19-.35,2.53-.37,2.58,.59-2.98,.09-1.01,.8,.17-.4,3.45-.55,5.36-.79-.59-.61-4.39-.12-6.3,.13,0-.84,3.31-1.73,7.83-2.12,3.6-.21,1.56,.55,2.18,.43,6.76,.15,5.76-1.47,11.37-.86,2.23,.54,.1,1.07,.59,1.46-2.93,.2-4.5-.34-6.48-.32l2.44,.25c-.93,.18-2.89,.31-3.95,.16,1.87,.48,11.41,.09,16.63,.54,.93-.18,2.52-.37,1.77-.58l-1.65,.08c-1.52-.43,3.1-.6,1.02-.8,2.21-.3,4.69-.78,7.81-.54l.59,.61c-.07-.18-2.04-.37-2.91-.18,1.3-.27,4.78,.83,6.2,.03l-2.18-.42c2.22-.31,4.97-.96,7.73-.77-1.26-.61-.64,.07-2.6-.64,1.7,.88-7.06-.04-4.33,.89-2.01-.82-3.48-.35-6.11-1.05,.08,.2,.93,.39-1.39,.29-.1,.06-.35,.13-.51,.18-.07,0-.1,0-.16-.02,.05,0,.08,.02,.13,.02-.1,.03-.11,.04,.05,0,.55,.09,1,.19,1.18,.31-1.15,.46-4.83,.45-6.08,.69-1.38-.1,.57-.23-.18-.45l-1.86,.36c-.18-.45-3.65-.75-.41-1.01l-2.92-.02c-1.72-.25-4.08-.35-5.53-.16-1.38-.1-3.47,.5-2.28,.15l-4.57,.28-.04-.11c-4.51-.06-9.29,.74-14.44-.07-1.52,.11-3.09,.25-5.63,.12l-.5-.39c-1.95,.14-5.86-.39-6.69,.02-2.1-1.05-9.46-.23-12.89-.41l-.13,.51c-2.36-.03-4.62,.12-7.55,.32l.18,.45c-2.53,.37-6.49-.32-10.49-.27,.27-.17,1.29-.13,2-.02-4.77-1.01-10.07,.74-14.4,0-1.08,.16-1.86,.11-2.62,0,.1,0,.19,0,.27-.02-.11,0-.27,0-.44,0-.48-.08-.97-.17-1.5-.24,.45,.11,.88,.2,1.26,.24-.83,0-2.09,.07-3.33,.18l-.53-.48c-.1,0-.18,0-.29,.01-1.21-.18-.98,.23-.33,.59-.62,.08-1.19,.17-1.64,.29-.99-.78-3.25,.26-4.59-.57-.83,.4-6.22,.3-6.88,1.15-.04-.11-.41-.16,.26-.17-1.38-.1-2.76-.19-3.99,.05l-.59-.61-3.01,.82c-1.83-.37-2.59-.59-.77-1.06-4.39,.72-3.56,.27-7.29,.99l.18-.4c-1.33,.02-3.46,.55-4.27,.22-2.98-.75-14.76-.1-22.41-.8,1.48,1.17-3.56-.52-3.86,.38-.45-.28-1.51-.43-.27-.67-3.91,.27-5.87-.44-8.57,.32-.45-.28,.84-.4,.44-.57-.31,.06-1.24,.24-1.65,.08-.4-.16,.57-.23,1.19-.35-3.99,.05-4.4,.9-4.69,1.69-2.02-.38-3.03-.32-4.49,.2-.7-.26-1.88-.53,.6-.63-1.4-.04-8.52-.38-8.67,.26-.44-.16-1.95-.05-2.32,.03-3.13-.04-3.41-.1-6.48-.04l.85,.1c-1.21,.77-2.56,.17-5.03,.26l.07-.08c-5.68-.56-2.45,.27-8.06-.37l.3,.48c-.52,1.03-4.97-.61-8.22-.21l1.17,.27c-1.78,.35-4.97-1.4-6.75-1.53-.39-.09,.61-.14,1.16-.21-4.67-.61-.61,.63-4.09,.77-.64-.34,.83-.86-1.52-.92-1.17-.27-6.8,1.1-9.99,.45,.7,.26,1.4,.52-.21,.72-2.48,.09-6.7-.99-8.94-.16-.32-.07-.43-.14-.45-.2-1.89,.21-6.16,.35-7.3,.8-1.75-1.66-11.78,.81-11.74-.72l-5.83,.08,.15-.16c-3.73-.1-5.04,.27-6.35,.63-.86-.1-.23-.25-.16-.33-5.44-.31-5.98-.24-10.55,.52l-.47-.5c-1.24,.29-7.87-.78-13.21-.2,.05-.06,.16-.15,.59-.22-6.01,.58-14.38-1.38-16.58,.43l-3.96,.44c5.13,.14-.76,.78,1.35,1.08-2.02,.11-5.21-.54-2.74-.64l.39,.09c.91-.94-6.37-.33-6.23-.97-10.09,.05-19.93-.47-29.38-.51l1.09,.73-3.3-.04c-.87-.17-1.16-.57,1.19-.54-1.24-.41-3.94,.35-4.01,.51-4.61-.3,1.8-.85,.39-.87l-1.92,.05,.43,.09c-1.55,.3-1.69,.62-4.52,.58-1.79-.15-.92-.45-1.62-.48,0-.05-.23-.08-1.08-.02l-4.15-.06,1.66,.58c-1.84,.28-3.91-.03-1.99,.62-2.81-1.01-15.5-.36-17.22-.68-2.58,.42-5.12,.41-8.28,.36,.78,.12,1.25,.77-1.47,.69,.99-1.34-4.98-.58-7.18-1.55,1.96,.64-7.73,.16-4.65,1.07-2.27-.11-.14-.5-1.86-.82-5.4,.52-11.9-.34-18.58-.16-.1,.18,1.01,.44-.42,.71l-3.52-.88c-1.32,.09-1.21,1.02-3.62,.4,.34,.15,.85,.37-.04,.43-13.08-.86-26.73,.7-40.01-.91,1.8,.34,.6,.37-.77,.39,.89,.08-.02,.43,.16,.67l-5.56-.66c-1.01,.66-5.96,.04-6.54,.68l1.76-.11c-2.34,.75,3.28,1.66,2.93,2.62,2.62-.85,6.91,1.26,11.22,.04,.95,.2-.81,.31-.3,.53,1.09-.41,2.41-.5,4.85-.31l-.28,.1c6.86-.1,9.8,.3,17.5,.57l-.57-.47c1.66,.06,2,.21,2.78,.33,1.9-.72-4.65-.04-2.92-.83,2.2,.96,11.67,.01,13.16,1.11,2.55,0-.75-.55,1.8-.54l.68,.3,.64-.38c1.67,.06,2.51,.43,2.57,.68-.41-.03-1.15,.07-1.59,.09,2.16,.35,6.1-.12,6.85,0l-2.45-.18c6.95-.28,15.43,.11,22.15-.49l-.51-.22c5.13-.41,4.11,.25,9.74,.05l-.2,.05c1.13-.22,2.63-.33,4.17-.31-1.42,.26,2.67,.51,.81,.8,5.56-.44,2.75-.35,6.42-1.18l.85,.37c1.25-.34,1.36-.51,4.17-.61-2.31,.32,1.79,.57-.79,.99,5.9,.81,8.76-.82,11.47,.37,2.71-1.03-5.11-.57-3.59-.79-1.27-.34,2.24-.77,4.09-.66,1.88,.02,3.2,1.31,7.94,1.29-.47,0-.5,.07-.97,.07,1.77,.26,3.48-.35,5.65,.07,1.22-.62,2.83,.04,3.18-.76l-4.35,.18c2.47-.21,4.78-1.13,8.84-.68-.32,.16-1.52,.37-2.36,.49,1.19,.23,2.29-.15,3.52,.08-.35,.79-5.28,.17-7.93,.78,1.27,.33,5-.57,3.67,.28,1.91-1.09,4.67,.14,7.88-.7l-.21,.47c.51-.07,1.56-.3,2.5-.29l-1.69,.62c2.6-.52,5.01,.46,7.5,.17-5.22,.01-1.17-.57-3.38-.92,6.44-.64,3.7,1.24,10.91,1.01-.98,.07-3.66-.29-2.14-.5,1.37,.1,3.18,.28,4.03,.53,4.78-.1-.73-.49,1.3-.78,1.63,.58,2.34-.24,4.44-.42v.48c5.46,.31,1.46-1.01,6.36-.63l-2.07,.67,2.55-.17-.53,.55c2.78-.41,3.64-.3,6.37-.15-.63-.34,.36-.88,2.77-.89,1.64,.29-.92,.46,2.5,.39-.77,.3-1.62,.68-3.1,.24-.15,.16-.76,.3-.92,.46,1.77,.33,4.45,.08,5.69,.07-.48-.02-1.05-.04-1.34-.11l4.95-.67c.78,.18,.16,.33-.45,.47,1-.05,1.7-.27,3.17-.32-.38,.39-.29,.8-2.38,.98l4.72-.44c.32,.17,2.42,.47,1.81,.61,2.62,.23,6.42-.76,9.89-.46,.18-.08,.54-.15,1.28-.21,3.26,.09,6.15,.57,10.01,.03l2.19,.7c3.03-.16-2.97-.89,2.07-1.15,3.57-.22,1.33,.6,2.58,.8,1.77-.35,4.86-1.08,8.14-.5-1.08,.13-1.94,.03-2.95,.08Zm37.32-.31c-.72,.1-2.16,.41-2.69,.13,.82-.55,1.46-.29,2.69-.13Zm56.73-1.16c-.54,.16-1.37,.08-2.31-.1,.68-.04,1.45-.02,2.31,.1Zm158.03-3.36s-.05,.01-.08,.02c-1.61-.19-1.04-.11,.08-.02Zm3.35,.21c.34-.07,.31-.13,.17-.2-.23,.12-.99,.13-1.84,.1,.24,.13,.67,.21,1.67,.11Zm-359.7,.19l.65,.13c-1.47,.14-1.06,.01-.65-.13Z" style="fill:#b0dff6;"/></g><g><path d="M6.89,82.3c-1.67-1.33-2.5-3.57-2.5-6.7s1.12-12.62,3.35-28.45c2.23-15.83,3.35-27.13,3.35-33.9s-.47-11.18-1.4-13.25c6.8,.53,11.27,3.2,13.4,8,5.13-2.8,10.78-4.2,16.95-4.2s11.03,1.92,14.6,5.75c3.57,3.83,5.35,8.55,5.35,14.15,0,6.93-2.54,13.2-7.6,18.8-2.47,2.67-5.65,4.83-9.55,6.5-3.9,1.67-8.22,2.5-12.95,2.5-2,0-3.9-.13-5.7-.4l-.3-4.2h.8c7.53,0,13.53-2.8,18-8.4,3.07-4,4.6-8.2,4.6-12.6,0-6.47-2.63-10.73-7.9-12.8-4.47-1.67-9.47-1.33-15,1v.2c0,2.53-.95,10.82-2.85,24.85-1.9,14.03-2.85,25.75-2.85,35.15,0,4.2,.2,7.47,.6,9.8-2.13,.13-3.53,.2-4.2,.2-3.8,0-6.53-.67-8.2-2Z" style="fill:#b0dff6;"/><path d="M80.19,50.1c2.4-8.67,5.28-15.05,8.65-19.15,3.37-4.1,6.5-6.15,9.4-6.15s5.08,1.08,6.55,3.25c1.47,2.17,2.2,4.97,2.2,8.4s-1.02,6.58-3.05,9.45c-2.03,2.87-4.82,4.3-8.35,4.3-.93,0-2.04-.27-3.3-.8,2.07-2.6,3.1-5.73,3.1-9.4,0-2.13-.87-3.2-2.6-3.2-1.27,0-2.63,.88-4.1,2.65-1.47,1.77-2.87,4.2-4.2,7.3-1.33,3.1-2.45,7.07-3.35,11.9-.9,4.83-1.35,9.98-1.35,15.45,0,.67,.17,3.5,.5,8.5-2.13,.13-3.5,.2-4.1,.2-3.8,0-6.53-.67-8.2-2-1.67-1.33-2.5-3.53-2.5-6.6,0-1.33,.7-6.46,2.1-15.4,1.4-8.93,2.1-16.05,2.1-21.35s-.57-10.18-1.7-14.65c4.87,1.53,8.27,3.72,10.2,6.55,1.93,2.83,2.9,6.13,2.9,9.9s-.3,7.38-.9,10.85Z" style="fill:#b0dff6;"/><path d="M132.39,23.5c.73,0,1.42,.22,2.05,.65,.63,.43,.95,.88,.95,1.35-6.73,4.67-10.77,11.17-12.1,19.5,1.4-3.73,3.12-6.97,5.15-9.7,2.03-2.73,4.15-4.8,6.35-6.2,4.2-2.6,8.3-3.9,12.3-3.9s7.4,1.97,10.2,5.9c2.8,3.93,4.2,9.2,4.2,15.8,0,7.4-1.5,14.2-4.5,20.4-3.33,7-8.07,11.83-14.2,14.5-3.47,1.53-7.27,2.3-11.4,2.3-5.8,0-10.85-2.25-15.15-6.75s-6.45-10.97-6.45-19.4,2.07-15.87,6.2-22.3c4.13-6.43,9.6-10.48,16.4-12.15Zm8.5,9c-4.33,0-8.2,2.92-11.6,8.75-3.4,5.83-5.1,11.65-5.1,17.45,0,12.13,3.13,18.2,9.4,18.2,4.47,0,8.1-2.92,10.9-8.75,2.8-5.83,4.2-12.38,4.2-19.65,0-10.67-2.6-16-7.8-16Z" style="fill:#b0dff6;"/><path d="M193.09,82.6c-2.13,.93-4.37,1.4-6.7,1.4s-3.63-.5-3.9-1.5c-1.07-4.27-2.3-10.65-3.7-19.15s-2.82-15.93-4.25-22.3c-1.43-6.37-3.15-11.18-5.15-14.45,3.67-1.73,6.6-2.6,8.8-2.6s3.87,.5,5,1.5c1.13,1,2.08,2.98,2.85,5.95,.77,2.97,1.32,5.42,1.65,7.35,.93,6.4,1.4,9.97,1.4,10.7s.27,3.72,.8,8.95c.53,5.23,.93,8.68,1.2,10.35,11.87-23,17.8-38.37,17.8-46.1,5.47,2.8,8.2,5.83,8.2,9.1,0,2.67-1.38,6.62-4.15,11.85-2.77,5.23-6.23,11.57-10.4,19-4.17,7.43-7.32,14.08-9.45,19.95Z" style="fill:#b0dff6;"/><path d="M241.34,48.65c-1.63,9.5-2.47,17.33-2.5,23.5-.03,6.17,.35,10.15,1.15,11.95-5.2-1.33-8.7-3.35-10.5-6.05s-2.7-6.65-2.7-11.85c0-2.8,.53-7.87,1.6-15.2,1.07-7.33,1.6-13.03,1.6-17.1s-.2-7.2-.6-9.4c1.47-.13,2.83-.2,4.1-.2,3.87,0,6.55,.65,8.05,1.95,1.5,1.3,2.25,3.52,2.25,6.65,0,1-.82,6.25-2.45,15.75Zm3.95-33.15c-1.87,.27-3.5,.4-4.9,.4-6.87,0-10.3-2.4-10.3-7.2,0-1.73,.5-4.1,1.5-7.1,1.87-.27,3.5-.4,4.9-.4,6.87,0,10.3,2.4,10.3,7.2,0,1.73-.5,4.1-1.5,7.1Z" style="fill:#b0dff6;"/><path d="M278.59,30.8c-1.87,0-3.53,.62-5,1.85-1.47,1.23-2.2,2.98-2.2,5.25s.78,4.4,2.35,6.4c1.57,2,3.48,3.75,5.75,5.25,2.27,1.5,4.55,3.03,6.85,4.6,2.3,1.57,4.32,3.5,6.05,5.8,1.73,2.3,2.7,4.85,2.9,7.65,0,4.8-2.07,8.83-6.2,12.1-4.13,3.27-9.27,4.9-15.4,4.9s-10.95-1.23-14.45-3.7-5.25-5.3-5.25-8.5,.98-5.78,2.95-7.75,4.42-2.95,7.35-2.95c1.53,0,2.97,.27,4.3,.8-2.13,2.27-3.2,4.67-3.2,7.2s.7,4.57,2.1,6.1c1.4,1.53,3.3,2.3,5.7,2.3s4.42-.62,6.05-1.85c1.63-1.23,2.45-2.83,2.45-4.8s-.57-3.71-1.7-5.25c-1.13-1.53-2.55-2.88-4.25-4.05-1.7-1.17-3.55-2.48-5.55-3.95-2-1.46-3.85-2.95-5.55-4.45-1.7-1.5-3.12-3.4-4.25-5.7-1.13-2.3-1.7-4.82-1.7-7.55,0-4.73,1.95-8.62,5.85-11.65,3.9-3.03,8.77-4.55,14.6-4.55s10.1,1.13,12.8,3.4c2.7,2.27,4.05,4.92,4.05,7.95s-.95,5.62-2.85,7.75c-1.9,2.13-4.35,3.2-7.35,3.2-1.47,0-3.13-.3-5-.9,1.47-1.27,2.6-2.71,3.4-4.35,.8-1.63,1.2-3.25,1.2-4.85s-.67-2.95-2-4.05c-1.33-1.1-2.93-1.65-4.8-1.65Z" style="fill:#b0dff6;"/><path d="M323.64,48.65c-1.63,9.5-2.47,17.33-2.5,23.5-.03,6.17,.35,10.15,1.15,11.95-5.2-1.33-8.7-3.35-10.5-6.05s-2.7-6.65-2.7-11.85c0-2.8,.53-7.87,1.6-15.2,1.07-7.33,1.6-13.03,1.6-17.1s-.2-7.2-.6-9.4c1.47-.13,2.83-.2,4.1-.2,3.87,0,6.55,.65,8.05,1.95,1.5,1.3,2.25,3.52,2.25,6.65,0,1-.82,6.25-2.45,15.75Zm3.95-33.15c-1.87,.27-3.5,.4-4.9,.4-6.87,0-10.3-2.4-10.3-7.2,0-1.73,.5-4.1,1.5-7.1,1.87-.27,3.5-.4,4.9-.4,6.87,0,10.3,2.4,10.3,7.2,0,1.73-.5,4.1-1.5,7.1Z" style="fill:#b0dff6;"/><path d="M360.09,23.5c.73,0,1.42,.22,2.05,.65,.63,.43,.95,.88,.95,1.35-6.73,4.67-10.77,11.17-12.1,19.5,1.4-3.73,3.12-6.97,5.15-9.7,2.03-2.73,4.15-4.8,6.35-6.2,4.2-2.6,8.3-3.9,12.3-3.9s7.4,1.97,10.2,5.9c2.8,3.93,4.2,9.2,4.2,15.8,0,7.4-1.5,14.2-4.5,20.4-3.34,7-8.07,11.83-14.2,14.5-3.47,1.53-7.27,2.3-11.4,2.3-5.8,0-10.85-2.25-15.15-6.75-4.3-4.5-6.45-10.97-6.45-19.4s2.07-15.87,6.2-22.3c4.13-6.43,9.6-10.48,16.4-12.15Zm8.5,9c-4.33,0-8.2,2.92-11.6,8.75-3.4,5.83-5.1,11.65-5.1,17.45,0,12.13,3.13,18.2,9.4,18.2,4.47,0,8.1-2.92,10.9-8.75,2.8-5.83,4.2-12.38,4.2-19.65,0-10.67-2.6-16-7.8-16Z" style="fill:#b0dff6;"/><path d="M438.99,24.9c3.93,0,6.75,1.22,8.45,3.65,1.7,2.43,2.55,5.73,2.55,9.9s-.65,10.02-1.95,17.55c-1.3,7.53-1.95,13.02-1.95,16.45s.43,5.93,1.3,7.5c.87,1.57,2.33,2.92,4.4,4.05-.87,.07-2.1,.1-3.7,.1-5.6,0-9.58-.87-11.95-2.6-2.37-1.73-3.55-4.77-3.55-9.1,0-2.47,.67-7.28,2-14.45,1.33-7.17,2-12.52,2-16.05,0-4.4-1.33-6.6-4-6.6-1.54,0-3.27,.9-5.2,2.7-1.93,1.8-3.8,4.25-5.6,7.35-1.8,3.1-3.3,7.08-4.5,11.95-1.2,4.87-1.8,9.72-1.8,14.55s.2,8.42,.6,10.75c-2.13,.13-3.54,.2-4.2,.2-3.8,0-6.53-.67-8.2-2-1.67-1.33-2.5-3.53-2.5-6.6,0-1.33,.7-6.46,2.1-15.4,1.4-8.93,2.1-16.05,2.1-21.35s-.57-10.18-1.7-14.65c4.87,1.53,8.27,3.72,10.2,6.55,1.93,2.83,2.9,6.07,2.9,9.7s-.4,7.85-1.2,12.65c2.2-7.46,5.67-13.8,10.4-19,4.73-5.2,9.07-7.8,13-7.8Z" style="fill:#b0dff6;"/><path d="M480.04,48.65c-1.63,9.5-2.47,17.33-2.5,23.5-.03,6.17,.35,10.15,1.15,11.95-5.2-1.33-8.7-3.35-10.5-6.05-1.8-2.7-2.7-6.65-2.7-11.85,0-2.8,.53-7.87,1.6-15.2,1.07-7.33,1.6-13.03,1.6-17.1s-.2-7.2-.6-9.4c1.47-.13,2.83-.2,4.1-.2,3.87,0,6.55,.65,8.05,1.95,1.5,1.3,2.25,3.52,2.25,6.65,0,1-.82,6.25-2.45,15.75Zm3.95-33.15c-1.87,.27-3.5,.4-4.9,.4-6.87,0-10.3-2.4-10.3-7.2,0-1.73,.5-4.1,1.5-7.1,1.87-.27,3.5-.4,4.9-.4,6.87,0,10.3,2.4,10.3,7.2,0,1.73-.5,4.1-1.5,7.1Z" style="fill:#b0dff6;"/><path d="M533.69,24.9c3.93,0,6.75,1.22,8.45,3.65,1.7,2.43,2.55,5.73,2.55,9.9s-.65,10.02-1.95,17.55c-1.3,7.53-1.95,13.02-1.95,16.45s.43,5.93,1.3,7.5c.87,1.57,2.33,2.92,4.4,4.05-.87,.07-2.1,.1-3.7,.1-5.6,0-9.58-.87-11.95-2.6-2.37-1.73-3.55-4.77-3.55-9.1,0-2.47,.67-7.28,2-14.45,1.33-7.17,2-12.52,2-16.05,0-4.4-1.33-6.6-4-6.6-1.54,0-3.27,.9-5.2,2.7-1.93,1.8-3.8,4.25-5.6,7.35-1.8,3.1-3.3,7.08-4.5,11.95-1.2,4.87-1.8,9.72-1.8,14.55s.2,8.42,.6,10.75c-2.13,.13-3.54,.2-4.2,.2-3.8,0-6.53-.67-8.2-2-1.67-1.33-2.5-3.53-2.5-6.6,0-1.33,.7-6.46,2.1-15.4,1.4-8.93,2.1-16.05,2.1-21.35s-.57-10.18-1.7-14.65c4.87,1.53,8.27,3.72,10.2,6.55,1.93,2.83,2.9,6.07,2.9,9.7s-.4,7.85-1.2,12.65c2.2-7.46,5.67-13.8,10.4-19,4.73-5.2,9.07-7.8,13-7.8Z" style="fill:#b0dff6;"/><path d="M588.59,25.9c1.27,0,2.23,.07,2.9,.2,1.73,.33,2.67,1.57,2.8,3.7-7.27,1.53-13.05,5.47-17.35,11.8-4.3,6.33-6.45,12.93-6.45,19.8,0,8,2.43,12,7.3,12,2.93,0,5.57-1.6,7.9-4.8,4.13-5.8,7.03-14.87,8.7-27.2,1.27-9.73,3.63-16,7.1-18.8,1.67-1.4,3.73-2.5,6.2-3.3-.93,7.07-1.4,12.4-1.4,16,0,26.67-2.85,46.7-8.55,60.1-5.7,13.4-14.98,20.1-27.85,20.1-5.2,0-9.27-1.38-12.2-4.15-2.93-2.77-4.4-5.8-4.4-9.1s1.03-6,3.1-8.1c2.07-2.1,4.96-3.15,8.7-3.15,1.53,0,3.1,.33,4.7,1-2.53,1.67-3.8,4.23-3.8,7.7,0,2.33,.7,4.35,2.1,6.05,1.4,1.7,3.12,2.55,5.15,2.55s3.82-.42,5.35-1.25c1.53-.83,3.1-2.32,4.7-4.45,1.6-2.13,3-4.8,4.2-8,2.73-7.4,4.23-17.5,4.5-30.3-1.93,5.73-4.87,10.33-8.8,13.8-3.93,3.47-7.93,5.2-12,5.2-5.13,0-9.05-1.95-11.75-5.85-2.7-3.9-4.05-8.88-4.05-14.95,0-7.07,1.7-13.63,5.1-19.7,3.8-7,9.07-11.87,15.8-14.6,3.73-1.53,7.83-2.3,12.3-2.3Z" style="fill:#b0dff6;"/></g></g></g> + </g> + <text x="1002" y="38" text-anchor="middle" fill="#a0b0c0" font-family="'IBM Plex Mono',monospace" font-size="10" letter-spacing="3">CONTROL PLANE ARCHITECTURE</text> + <line x1="872" y1="46" x2="1132" y2="46" stroke="#888" stroke-width="0.8"/> + + <!-- ═══ CLI ═══ --> + <g> + <rect x="235" y="104" width="140" height="68" rx="8" fill="url(#cli-grad)" filter="url(#soft-shadow)"/> + <rect x="235" y="104" width="140" height="68" rx="8" fill="none" stroke="#1c2a3a" stroke-width="1"/> + <rect x="235" y="107" width="3" height="62" rx="1.5" fill="#4a9eff" opacity="0.6"/> + <circle cx="256" cy="118" r="7" fill="#2d3748" stroke="#4a9eff" stroke-width="1" opacity="0.8"/> + <text x="256" y="121" text-anchor="middle" fill="#4a9eff" font-size="8" font-family="'IBM Plex Mono',monospace">$</text> + <text x="265" y="123" fill="#4a9eff" font-family="'DM Sans',sans-serif" font-weight="600" font-size="11">CLI</text> + <text x="270" y="148" fill="#6b8aaa" font-family="'IBM Plex Mono',monospace" font-size="8">provisioning</text> + </g> + + <!-- Arrow: CLI → Orchestrator --> + <line x1="640" y1="98" x2="640" y2="115" stroke="#4a9eff" stroke-width="1.5" stroke-dasharray="5 3" class="spoke" opacity="0.7" marker-end="url(#arr-blue)"/> + + + <!-- ═══════════════════════════════════════ --> + <!-- NATS ORBITAL RING SYSTEM --> + <!-- ═══════════════════════════════════════ --> + + <!-- Ring outer glow --> + <circle cx="640" cy="320" r="138" fill="none" stroke="#94a3b8" stroke-width="10" opacity="0.04" class="ring-glow" filter="url(#ring-glow-f)"/> + + <!-- Main orbit ring (flowing dashes) --> + <circle cx="640" cy="320" r="135" fill="none" stroke="#94a3b8" stroke-width="2" stroke-dasharray="10 8" class="orbit-flow" opacity="0.45"/> + + <!-- ═══ INFINITY ENGINE (∞ dual-wheel perpetual motion from logo) ═══ --> + <!-- Centered at (640,310), scale 0.55 from original logo coordinates --> + <!-- Left wheel center: (115.86, 113.25) | Right wheel center: (261.91, 113.28) --> + <g transform="translate(640, 310) scale(0.55) translate(-188.88, -113.27)" opacity="0.85"> + + <!-- Background circle with CLI gradient --> + <rect x="100" y="40" width="178" height="146" rx="12" style="fill:url(#cli-grad); opacity:0.35;"/> + <circle cx="188.88" cy="113.27" r="50" style="fill:url(#cli-grad); opacity:0.3;"/> + + <!-- Left wheel — clockwise 8s (outline only) --> + <g> + <path d="M3.04,87.53c-3.66,16.4-3.36,33.43,.85,49.7l18.49-.31c2.12,7.08,5.09,13.89,8.85,20.26l-12.81,13.12c9.22,14.08,21.56,25.9,36.09,34.55l12.81-13.13c6.56,3.51,13.52,6.22,20.74,8.08l.31,18.3c16.56,3.62,33.75,3.33,50.18-.85l-.31-18.3c7.15-2.1,14.02-5.04,20.46-8.77l13.25,12.69c14.22-9.14,26.14-21.36,34.87-35.75l-13.25-12.68c3.54-6.5,6.28-13.4,8.15-20.55l18.49-.31c3.65-16.4,3.36-33.43-.85-49.7l-18.49,.31c-2.12-7.08-5.09-13.88-8.85-20.26l12.8-13.12c-9.22-14.08-21.56-25.9-36.08-34.55l-12.81,13.13c-6.56-3.51-13.53-6.23-20.75-8.08l-.31-18.3c-16.56-3.62-33.75-3.33-50.18,.85l.31,18.3c-7.15,2.1-14.01,5.04-20.45,8.77l-13.25-12.69c-14.22,9.14-26.15,21.36-34.88,35.75l13.25,12.68c-3.54,6.5-6.28,13.4-8.15,20.55l-18.49,.31Z" style="fill:none; fill-rule:evenodd; stroke:#4cc2f1; stroke-miterlimit:10; stroke-width:1.2; opacity:0.85;"> + <animateTransform attributeName="transform" type="rotate" values="0 115.86 113.25;360 115.86 113.25" dur="8s" repeatCount="indefinite"/> + </path> + </g> + + <!-- Right wheel — counter-clockwise 6s (outline only) --> + <g> + <path d="M167.28,92.87c-3.19,14.31-2.94,29.17,.74,43.37l16.13-.27c1.85,6.18,4.44,12.12,7.72,17.68l-11.18,11.45c8.05,12.29,18.82,22.6,31.49,30.15l11.18-11.46c5.72,3.06,11.8,5.43,18.1,7.05l.27,15.97c14.45,3.16,29.45,2.91,43.78-.74l-.27-15.97c6.24-1.83,12.23-4.4,17.85-7.66l11.56,11.08c12.41-7.97,22.81-18.64,30.43-31.19l-11.56-11.06c3.09-5.67,5.48-11.69,7.11-17.93l16.13-.27c3.18-14.31,2.93-29.17-.74-43.37l-16.13,.27c-1.85-6.18-4.44-12.11-7.72-17.68l11.17-11.44c-8.04-12.29-18.81-22.6-31.48-30.15l-11.18,11.46c-5.72-3.07-11.8-5.43-18.1-7.05l-.27-15.97c-14.45-3.16-29.45-2.91-43.78,.74l.27,15.97c-6.24,1.83-12.23,4.4-17.84,7.66l-11.56-11.08c-12.41,7.97-22.81,18.64-30.44,31.19l11.56,11.06c-3.09,5.67-5.48,11.69-7.11,17.93l-16.13,.27Z" style="fill:none; fill-rule:evenodd; stroke:#4cc2f1; stroke-miterlimit:10; stroke-width:1.2; opacity:0.85;"> + <animateTransform attributeName="transform" type="rotate" values="0 261.91 113.28;-360 261.91 113.28" dur="6s" repeatCount="indefinite"/> + </path> + </g> + + <!-- Left wheel golden center (strong original colors) --> + <path d="M115.86,78.19c19.55,0,35.4,15.7,35.4,35.06s-15.85,35.06-35.4,35.06-35.4-15.7-35.4-35.06,15.85-35.06,35.4-35.06" style="fill:#f2b03f; opacity:0.9;"/> + <ellipse cx="116.42" cy="115.68" rx="20.17" ry="20.15" style="fill:none; stroke:#4159a4; stroke-miterlimit:10; stroke-width:1.58px; opacity:0.8;"/> + <path d="M116.42,107.88c4.35-.04,7.91,3.43,7.95,7.73,.04,4.31-3.46,7.83-7.81,7.87-4.35,.04-7.91-3.43-7.95-7.73,0-.02,0-.05,0-.07-.02-4.29,3.48-7.78,7.81-7.8" style="fill:#fff; opacity:0.8;"/> + + <!-- Right wheel golden center (strong original colors) --> + <ellipse cx="261.91" cy="113.28" rx="35.65" ry="35.31" style="fill:#f9b224; opacity:0.9;"/> + + <!-- Right wheel — dashboard display (simplified from logo chronometer) --> + <circle cx="261.91" cy="113.28" r="26" style="fill:#3d5070; opacity:0.85;"/> + <!-- Window controls (3 dots) --> + <circle cx="243" cy="95" r="1.2" style="fill:#d68a87; opacity:0.6;"/> + <circle cx="247" cy="95" r="1.2" style="fill:#c9a558; opacity:0.6;"/> + <circle cx="251" cy="95" r="1.2" style="fill:#5a7093; opacity:0.6;"/> + <!-- Terminal header bar --> + <rect x="243" y="99" width="38" height="3.2" rx="1.5" style="fill:#6a7a98; opacity:0.5;"/> + <!-- Status bars (staggered widths = data readout) --> + <rect x="243" y="105" width="28" height="2.8" rx="1" style="fill:#7a8aaa; opacity:0.5;"/> + <rect x="243" y="110" width="34" height="2.8" rx="1" style="fill:#a89870; opacity:0.5;"/> + <rect x="243" y="115" width="20" height="2.8" rx="1" style="fill:#6a7a8a; opacity:0.4;"/> + <rect x="243" y="120" width="30" height="2.8" rx="1" style="fill:#7a8aaa; opacity:0.4;"/> + <!-- Green health indicator --> + <circle cx="271" cy="127" r="5.5" style="fill:#4a9e8f; opacity:0.7;"> + <animate attributeName="opacity" values="0.7;0.4;0.7" dur="2s" repeatCount="indefinite"/> + </circle> + <!-- Animated checkmark --> + <path d="M268,126.5l2.5,2.5,4.2-4.2" style="fill:none; stroke:#a0c0b0; stroke-width:1.8; stroke-linecap:round; stroke-linejoin:round; opacity:0.8;"> + <animate attributeName="stroke-dasharray" values="0 15;15 15" dur="1.5s" repeatCount="indefinite"/> + </path> + + <!-- ∞ path segments — staggered color cycling --> + + <!-- Top left of ∞ --> + <path d="M225.8,83.92c1.22-1.35,2.46-2.66,3.75-3.94,16.69-16.54,43.22-18.12,61.79-3.68l17.46-2.65,2.57-16.63c-29.5-25.42-73.83-23.89-101.47,3.5-1.24,1.23-2.45,2.49-3.63,3.77l16.89,2.56,2.64,17.07Z" style="stroke:#5a7a90; stroke-miterlimit:10;"> + <animate attributeName="fill" values="#5e74b7;#00D4FF;#7B2BFF;#00FFB3;#5e74b7" dur="2s" repeatCount="indefinite"/> + <animate attributeName="stroke-width" values="0.5;2;0.5" dur="2s" repeatCount="indefinite"/> + </path> + + <!-- Bottom left of ∞ --> + <path d="M78.59,141.18c-14.54-18.4-12.95-44.65,3.71-61.2,1.33-1.31,2.73-2.54,4.2-3.68l-17.46-2.65-2.57-16.63c-1.3,1.13-2.58,2.28-3.82,3.5-27.79,27.53-28.96,71.57-3.53,100.51l16.79-2.55,2.67-17.3Z" style="stroke:#5a7a90; stroke-miterlimit:10;"> + <animate attributeName="fill" values="#5e74b7;#7B2BFF;#00FFB3;#00D4FF;#5e74b7" dur="2s" begin="0.5s" repeatCount="indefinite"/> + <animate attributeName="stroke-width" values="0.5;2;0.5" dur="2s" begin="0.5s" repeatCount="indefinite"/> + </path> + + <!-- Top right of ∞ --> + <path d="M86.5,76.3c18.57-14.44,45.1-12.86,61.79,3.68,1.29,1.27,2.53,2.6,3.75,3.94l2.64-17.07,16.89-2.56c-1.18-1.28-2.39-2.54-3.63-3.77-27.64-27.39-71.97-28.92-101.48-3.5l2.57,16.63,17.46,2.65Z" style="stroke:#5a7a90; stroke-miterlimit:10;"> + <animate attributeName="fill" values="#8d9ccf;#00FFB3;#00D4FF;#7B2BFF;#8d9ccf" dur="2s" begin="1s" repeatCount="indefinite"/> + <animate attributeName="stroke-width" values="0.5;2;0.5" dur="2s" begin="1s" repeatCount="indefinite"/> + </path> + + <!-- Bottom right of ∞ --> + <path d="M299.26,141.18c-15.88,20.08-45.19,23.61-65.46,7.88l-.04-.03-17.46,2.65-2.57,16.63c29.5,25.42,73.83,23.89,101.47-3.5,1.24-1.22,2.4-2.49,3.53-3.78l-16.79-2.55-2.68-17.3Z" style="stroke:#5a7a90; stroke-miterlimit:10;"> + <animate attributeName="fill" values="#8d9ccf;#7B2BFF;#00D4FF;#00FFB3;#8d9ccf" dur="2s" begin="1.5s" repeatCount="indefinite"/> + <animate attributeName="stroke-width" values="0.5;2;0.5" dur="2s" begin="1.5s" repeatCount="indefinite"/> + </path> + + <!-- ∞ connector arcs (static deep blue) --> + <path d="M315.19,60.52c-1.23-1.22-2.51-2.38-3.82-3.5l-2.57,16.63-17.46,2.65c1.47,1.14,2.88,2.37,4.2,3.68,16.66,16.55,18.25,42.8,3.71,61.2l2.68,17.3,16.79,2.55c25.42-28.93,24.26-72.98-3.53-100.5m-171.1,88.5c-20.2,15.76-49.48,12.31-65.39-7.7-.04-.05-.08-.1-.12-.15l-2.68,17.3-16.79,2.55c1.13,1.29,2.3,2.56,3.53,3.78,27.64,27.39,71.97,28.92,101.47,3.5l-2.57-16.63-17.46-2.65Z" style="fill:#455aa5; stroke:#3a5a7a; stroke-miterlimit:10;"/> + + <!-- ∞ transition pieces --> + <path d="M204.38,103.42c1.09,1.79,2.17,3.56,3.25,5.32,5.65-8.96,11.44-17.4,18.17-24.81l-2.64-17.07-16.89-2.56c-5.38,5.87-10.34,12.11-14.83,18.68,4.58,6.77,8.8,13.66,12.94,20.45m-30.92,18.5c-1.09-1.79-2.18-3.56-3.25-5.32-6.68,10.59-13.53,20.44-21.92,28.75-1.33,1.31-2.73,2.54-4.21,3.68l17.46,2.65,2.57,16.63c1.3-1.12,2.58-2.28,3.82-3.5,6.84-6.9,13.02-14.41,18.46-22.43-4.57-6.76-8.78-13.66-12.93-20.46" style="fill:#8d9ccf; stroke:#5a7a90; stroke-miterlimit:10;"/> + + <!-- Central flow — animated infinity gradient --> + <path d="M171.57,64.29c11.77,12.66,20.64,27.17,29.24,41.26,9,14.74,17.5,28.67,28.74,39.8,1.33,1.31,2.73,2.54,4.21,3.68l-17.46,2.65-2.57,16.63c-1.3-1.12-2.58-2.27-3.82-3.5-13.63-13.5-23.41-29.52-32.87-45.02-7.97-13.05-15.55-25.46-24.99-35.86l2.64-17.07,16.89-2.56Z" style="stroke:#5a7a90; stroke-miterlimit:10;"> + <animate attributeName="fill" values="#00D4FF;#7B2BFF;#00FFB3;#00D4FF" dur="3s" repeatCount="indefinite"/> + <animate attributeName="stroke-width" values="0.5;3;0.5" dur="3s" repeatCount="indefinite"/> + <animate attributeName="opacity" values="0.8;1;0.8" dur="3s" repeatCount="indefinite"/> + </path> + + </g> + + <!-- Hub labels (below infinity engine) --> + <text x="640" y="403" text-anchor="middle" fill="#94a3b8" font-family="'DM Sans',sans-serif" font-weight="700" font-size="15" letter-spacing="1.2" opacity="0.7">Events Stream</text> + <text x="640" y="429" text-anchor="middle" fill="#5a6278" font-family="'IBM Plex Mono',monospace" font-size="7">NATS JetStream</text> + <text x="640" y="440" text-anchor="middle" fill="#5a6278" font-family="'IBM Plex Mono',monospace" font-size="7">:4222</text> + + + <!-- ═══════════════════════════════════════ --> + <!-- SATELLITE SERVICES --> + <!-- ═══════════════════════════════════════ --> + + <!-- ─── ORCHESTRATOR (top, 0°) ─── --> + <g> + <rect x="505" y="60" width="270" height="110" rx="14" fill="none" stroke="#f59e0b" stroke-width="1.5" stroke-dasharray="8 4" class="solid-border"/> + <rect x="515" y="70" width="250" height="90" rx="10" fill="url(#orch-grad)" filter="url(#soft-shadow)"/> + <rect x="515" y="70" width="250" height="90" rx="10" fill="none" stroke="#3a2e1c" stroke-width="1"/> + <rect x="515" y="73" width="3" height="84" rx="1.5" fill="#f59e0b" opacity="0.5"/> + <circle cx="535" cy="92" r="8" fill="#2d3748" stroke="#f59e0b" stroke-width="1" opacity="0.8"/> + <text x="535" y="96" text-anchor="middle" fill="#f59e0b" font-size="9">▶</text> + <text x="553" y="95" fill="#f59e0b" font-family="'DM Sans',sans-serif" font-weight="700" font-size="13" letter-spacing="0.5">Orchestrator</text> + <text x="740" y="95" text-anchor="end" fill="#f59e0b" font-family="'IBM Plex Mono',monospace" font-size="8" opacity="0.5">:9011</text> + <circle cx="752" cy="92" r="3.5" fill="#f59e0b" class="health"/> + <g font-family="'IBM Plex Mono',monospace" font-size="8" fill="#a8a8a8"> + <rect x="528" y="113" width="220" height="14" rx="4" fill="#100b05" stroke="#2a2016" stroke-width="0.5"/> + <text x="638" y="123" text-anchor="middle" opacity="0.6">Task State Machine • Provider API • SSH</text> + <rect x="528" y="137" width="220" height="14" rx="4" fill="#100b05" stroke="#2a2016" stroke-width="0.5"/> + <text x="638" y="147" text-anchor="middle" opacity="0.55">Webhooks • Rollback • Audit Collector</text> + </g> + </g> + + <!-- ─── CONFIGURATION (top-right, cylinder) ─── --> + <g> + <ellipse cx="990" cy="152" rx="70" ry="11" fill="#10b981" opacity="0.12" stroke="none"/> + <path d="M 920 152 Q 990 179 1060 152" stroke="#10b981" stroke-width="0.8" fill="none" stroke-dasharray="4 2" opacity="0.4"/> + <rect x="920" y="91" width="140" height="61" rx="3" fill="#10b981" stroke="none" opacity="0.06" filter="url(#soft-shadow)"/> + <ellipse cx="990" cy="91" rx="70" ry="13" fill="#10b981" opacity="0.35" stroke="none"/> + <text x="990" y="126" text-anchor="middle" fill="#10b981" font-family="'DM Sans',sans-serif" font-weight="700" font-size="12" letter-spacing="0.5">Configuration</text> + <text x="990" y="152" text-anchor="middle" fill="#10b981" font-family="'IBM Plex Mono',monospace" font-size="7" opacity="0.6">Nickel • ∞ TypeDialog</text> + </g> + + <!-- ─── CONTROL CENTER (72°, upper-right) ─── --> + <g> + <rect x="241" y="248" width="250" height="110" rx="10" fill="url(#ctrl-grad)" filter="url(#soft-shadow)"/> + <rect x="241" y="248" width="250" height="110" rx="10" fill="none" stroke="#2e2848" stroke-width="1"/> + <rect x="241" y="251" width="3" height="104" rx="1.5" fill="#818cf8" opacity="0.5"/> + <circle cx="263" cy="270" r="8" fill="#2d3748" stroke="#818cf8" stroke-width="1" opacity="0.8"/> + <text x="263" y="274" text-anchor="middle" fill="#818cf8" font-size="9">◯</text> + <text x="279" y="273" fill="#818cf8" font-family="'DM Sans',sans-serif" font-weight="700" font-size="13" letter-spacing="0.5">Control Center</text> + <text x="461" y="273" text-anchor="end" fill="#818cf8" font-family="'IBM Plex Mono',monospace" font-size="8" opacity="0.5">:9012</text> + <circle cx="473" cy="270" r="3.5" fill="#818cf8" class="health health-d1"/> + <g font-family="'IBM Plex Mono',monospace" font-size="8" fill="#9090b8"> + <rect x="254" y="301" width="220" height="14" rx="4" fill="#070514" stroke="#1a1630" stroke-width="0.5"/> + <text x="364" y="311" text-anchor="middle" opacity="0.9">Cedar Policies • JWT • Sessions</text> + <rect x="254" y="325" width="220" height="14" rx="4" fill="#070514" stroke="#1a1630" stroke-width="0.5"/> + <text x="364" y="335" text-anchor="middle" opacity="0.85">WebSocket • RBAC • Solo: auto-session</text> + </g> + </g> + + <!-- ─── EXTENSION REGISTRY (144°, lower-right) ─── --> + <g> + <rect x="732" y="430" width="250" height="110" rx="10" fill="url(#ext-grad)" filter="url(#soft-shadow)"/> + <rect x="732" y="430" width="250" height="110" rx="10" fill="none" stroke="#3a1c28" stroke-width="1"/> + <rect x="978" y="433" width="3" height="104" rx="1.5" fill="#ef4444" opacity="0.5"/> + <circle cx="754" cy="452" r="8" fill="#2d3748" stroke="#ef4444" stroke-width="1" opacity="0.8"/> + <text x="754" y="456" text-anchor="middle" fill="#ef4444" font-size="9">⚙</text> + <text x="770" y="455" fill="#ef4444" font-family="'DM Sans',sans-serif" font-weight="700" font-size="12" letter-spacing="0.5">Extensions Registry</text> + <text x="952" y="455" text-anchor="end" fill="#ef4444" font-family="'IBM Plex Mono',monospace" font-size="8" opacity="0.5">:8084</text> + <circle cx="964" cy="452" r="3.5" fill="#ef4444" class="health health-d3"/> + <g font-family="'IBM Plex Mono',monospace" font-size="8" fill="#9a7878"> + <rect x="745" y="483" width="220" height="14" rx="4" fill="#10050a" stroke="#2a1620" stroke-width="0.5"/> + <text x="855" y="493" text-anchor="middle" opacity="0.9">Git • OCI • LRU</text> + <rect x="745" y="507" width="220" height="14" rx="4" fill="#10050a" stroke="#2a1620" stroke-width="0.5"/> + <text x="855" y="517" text-anchor="middle" opacity="0.85">Providers • Taskservs • vault:// creds</text> + </g> + </g> + + <!-- ─── AI/MCP (216°, lower-left) ─── --> + <g> + <rect x="298" y="430" width="250" height="110" rx="10" fill="url(#mcp-grad)" filter="url(#soft-shadow)"/> + <rect x="298" y="430" width="250" height="110" rx="10" fill="none" stroke="#1c3038" stroke-width="1"/> + <rect x="298" y="433" width="3" height="104" rx="1.5" fill="#22d3ee" opacity="0.5"/> + <circle cx="320" cy="452" r="8" fill="#2d3748" stroke="#22d3ee" stroke-width="1" opacity="0.8"/> + <text x="320" y="456" text-anchor="middle" fill="#22d3ee" font-size="9">🧠</text> + <text x="336" y="455" fill="#22d3ee" font-family="'DM Sans',sans-serif" font-weight="700" font-size="12" letter-spacing="0.5">AI • MCP</text> + <text x="523" y="455" text-anchor="end" fill="#22d3ee" font-family="'IBM Plex Mono',monospace" font-size="8" opacity="0.5">:9082</text> + <circle cx="535" cy="452" r="3.5" fill="#22d3ee" class="health health-d2"/> + <g font-family="'IBM Plex Mono',monospace" font-size="8" fill="#7aa8a8"> + <rect x="311" y="483" width="220" height="14" rx="4" fill="#041618" stroke="#0f2a2e" stroke-width="0.5"/> + <text x="421" y="493" text-anchor="middle" opacity="0.9">RAG Engine • MCP Server • Tools</text> + <rect x="311" y="507" width="220" height="14" rx="4" fill="#041618" stroke="#0f2a2e" stroke-width="0.5"/> + <text x="421" y="517" text-anchor="middle" opacity="0.85">Embeddings • Model Routing • KGraph</text> + </g> + </g> + + <!-- ─── VAULT SERVICE (288°, upper-left) ─── --> + <g> + <rect x="789" y="248" width="250" height="110" rx="10" fill="url(#vault-grad)" filter="url(#soft-shadow)"/> + <rect x="789" y="248" width="250" height="110" rx="10" fill="none" stroke="#3a1c30" stroke-width="1"/> + <rect x="1035" y="251" width="3" height="104" rx="1.5" fill="#ec4899" opacity="0.5"/> + <circle cx="811" cy="270" r="8" fill="#2d3748" stroke="#ec4899" stroke-width="1" opacity="0.8"/> + <text x="811" y="274" text-anchor="middle" fill="#ec4899" font-size="9">🛡</text> + <text x="827" y="273" fill="#ec4899" font-family="'DM Sans',sans-serif" font-weight="700" font-size="13" letter-spacing="0.5">Vault Service</text> + <text x="1009" y="273" text-anchor="end" fill="#ec4899" font-family="'IBM Plex Mono',monospace" font-size="8" opacity="0.5">:9094</text> + <circle cx="1021" cy="270" r="3.5" fill="#ec4899" class="health health-d4"/> + <g font-family="'IBM Plex Mono',monospace" font-size="8" fill="#9a8090"> + <rect x="802" y="301" width="220" height="14" rx="4" fill="#100514" stroke="#2a1630" stroke-width="0.5"/> + <text x="912" y="311" text-anchor="middle" opacity="0.9">Lease Lifecycle • Key Management</text> + <rect x="802" y="325" width="220" height="14" rx="4" fill="#100514" stroke="#2a1630" stroke-width="0.5"/> + <text x="912" y="335" text-anchor="middle" opacity="0.85">SOPS • Age • Secrets never in NATS</text> + </g> + </g> + + + <!-- ═══ VERTICAL ARROW: CONFIGURATION → VAULT ═══ --> + <line x1="995" y1="169" x2="995" y2="248" stroke="#10b981" stroke-width="1.5" stroke-dasharray="4 3" opacity="0.35" marker-end="url(#arr-green-sm)"/> + + <!-- ═══ VERTICAL ARROW: CLI → CONTROL CENTER ═══ --> + <line x1="305" y1="177" x2="305" y2="248" stroke="#10b981" stroke-width="1.5" stroke-dasharray="4 3" opacity="0.35" marker-end="url(#arr-green-sm)"/> + + <!-- ═══ CLI → ORCHESTRATOR CURVE ═══ --> + <path d="M 375 138 C 430 100 480 80 505 120" fill="none" stroke="#fbbf24" stroke-width="1.2" stroke-dasharray="3 2" opacity="0.5"/> + + <!-- ═══ CONFIGURATION → ORCHESTRATOR CURVE ═══ --> + <path d="M 920 128 C 850 90 800 70 775 120" fill="none" stroke="#fbbf24" stroke-width="1.2" stroke-dasharray="3 2" opacity="0.5"/> + + <!-- ═══ CLI → AI CURVE ═══ --> + <path d="M 235 138 C 150 250 200 350 300 444" fill="none" stroke="#fbbf24" stroke-width="1.2" stroke-dasharray="3 2" opacity="0.5"/> + + <!-- ═══ CONFIGURATION → EXT CURVE ═══ --> + <path d="M 1060 128 C 1145 240 1095 340 979 444" fill="none" stroke="#fbbf24" stroke-width="1.2" stroke-dasharray="3 2" opacity="0.5"/> + + <!-- ═══ HTTPS CREDENTIAL BEAM ═══ --> + <path d="M 505 168 C 450 187 420 217 447 245" fill="none" stroke="#4a9eff" stroke-width="2" class="https-beam" filter="url(#glow-blue)"/> + <path d="M 505 168 C 450 187 420 217 447 245" fill="none" stroke="#4a9eff" stroke-width="1" stroke-dasharray="4 3" class="https-beam" opacity="0.4"/> + <path d="M 447 245 C 420 217 450 187 505 168" fill="none" stroke="#4a9eff" stroke-width="2" class="https-beam" filter="url(#glow-blue)"/> + <path d="M 447 245 C 420 217 450 187 505 168" fill="none" stroke="#4a9eff" stroke-width="1" stroke-dasharray="4 3" class="https-beam" opacity="0.4"/> + <text x="475" y="209" fill="#4a9eff" font-family="'IBM Plex Mono',monospace" font-size="7" font-weight="700" class="https-label">HTTPS</text> + <path d="M 775 168 C 830 187 860 217 833 245" fill="none" stroke="#4a9eff" stroke-width="2" class="https-beam" filter="url(#glow-blue)"/> + <path d="M 775 168 C 830 187 860 217 833 245" fill="none" stroke="#4a9eff" stroke-width="1" stroke-dasharray="4 3" class="https-beam" opacity="0.4"/> + <path d="M 833 245 C 860 217 830 187 775 168" fill="none" stroke="#4a9eff" stroke-width="2" class="https-beam" filter="url(#glow-blue)"/> + <path d="M 833 245 C 860 217 830 187 775 168" fill="none" stroke="#4a9eff" stroke-width="1" stroke-dasharray="4 3" class="https-beam" opacity="0.4"/> + <text x="803" y="209" fill="#4a9eff" font-family="'IBM Plex Mono',monospace" font-size="7" font-weight="700" class="https-label">HTTPS</text> + + + <!-- ═══ ORBITAL PARTICLES ═══ --> + + <circle r="3.5" fill="#f59e0b" opacity="0" filter="url(#glow-orange)"> + <animateMotion dur="8s" begin="0s" repeatCount="indefinite"> + <mpath href="#orbit-cw"/> + </animateMotion> + <animate attributeName="opacity" values="0;0.9;0.9;0" keyTimes="0;0.05;0.92;1" dur="8s" repeatCount="indefinite"/> + </circle> + + <circle r="2.5" fill="#ec4899" opacity="0" filter="url(#glow-pink)"> + <animateMotion dur="10s" begin="2s" repeatCount="indefinite"> + <mpath href="#orbit-cw"/> + </animateMotion> + <animate attributeName="opacity" values="0;0.8;0.8;0" keyTimes="0;0.05;0.92;1" dur="10s" begin="2s" repeatCount="indefinite"/> + </circle> + + <circle r="3" fill="#4a9eff" opacity="0" filter="url(#glow-blue)"> + <animateMotion dur="7s" begin="1s" repeatCount="indefinite"> + <mpath href="#orbit-ccw"/> + </animateMotion> + <animate attributeName="opacity" values="0;0.85;0.85;0" keyTimes="0;0.05;0.92;1" dur="7s" begin="1s" repeatCount="indefinite"/> + </circle> + + <circle r="2.5" fill="#818cf8" opacity="0" filter="url(#glow-purple)"> + <animateMotion dur="9s" begin="4s" repeatCount="indefinite"> + <mpath href="#orbit-cw"/> + </animateMotion> + <animate attributeName="opacity" values="0;0.7;0.7;0" keyTimes="0;0.05;0.92;1" dur="9s" begin="4s" repeatCount="indefinite"/> + </circle> + + <circle r="3" fill="#22d3ee" opacity="0" filter="url(#glow-cyan)"> + <animateMotion dur="11s" begin="3s" repeatCount="indefinite"> + <mpath href="#orbit-ccw"/> + </animateMotion> + <animate attributeName="opacity" values="0;0.75;0.75;0" keyTimes="0;0.05;0.92;1" dur="11s" begin="3s" repeatCount="indefinite"/> + </circle> + + <circle r="2" fill="#ef4444" opacity="0" filter="url(#glow-orange)"> + <animateMotion dur="12s" begin="5s" repeatCount="indefinite"> + <mpath href="#orbit-cw"/> + </animateMotion> + <animate attributeName="opacity" values="0;0.7;0.7;0" keyTimes="0;0.05;0.92;1" dur="12s" begin="5s" repeatCount="indefinite"/> + </circle> + + + <!-- ═══ ORBITAL FLASH EFFECTS ═══ --> + <!-- Color-matched bursts: each fires when its orbital particle passes nearest the service box --> + + <!-- Orchestrator burst — amber #f59e0b, CW 8s begin=0s, particle at top at cycle boundary --> + <g> + <path d="M 640 183 L 645 178 L 635 174 L 640 170" fill="none" stroke="#f59e0b" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" filter="url(#glow-flash)"/> + <circle cx="640" cy="183" r="0" fill="#f59e0b" filter="url(#glow-flash)"> + <animate attributeName="r" values="10;0;0;10" keyTimes="0;0.15;0.85;1" dur="8s" begin="0s" repeatCount="indefinite"/> + </circle> + <animate attributeName="opacity" values="1;0;0;1" keyTimes="0;0.15;0.85;1" dur="8s" begin="0s" repeatCount="indefinite"/> + </g> + + <!-- Vault burst — pink #ec4899, CW 10s begin=2s, particle passes Vault at keyTime=0.23 --> + <g opacity="0"> + <path d="M 775 304 L 779 300 L 782 308 L 789 304" fill="none" stroke="#ec4899" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" filter="url(#glow-flash)"/> + <circle cx="775" cy="304" r="0" fill="#ec4899" filter="url(#glow-flash)"> + <animate attributeName="r" values="0;0;10;0;0" keyTimes="0;0.15;0.23;0.38;1" dur="10s" begin="2s" repeatCount="indefinite"/> + </circle> + <animate attributeName="opacity" values="0;0;1;0;0" keyTimes="0;0.15;0.23;0.38;1" dur="10s" begin="2s" repeatCount="indefinite"/> + </g> + + <!-- Extensions burst — red #ef4444, CW 12s begin=5s, particle passes Extensions at keyTime=0.38 --> + <g opacity="0"> + <path d="M 732 420 L 736 424 L 728 427 L 732 430" fill="none" stroke="#ef4444" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" filter="url(#glow-flash)"/> + <circle cx="732" cy="420" r="0" fill="#ef4444" filter="url(#glow-flash)"> + <animate attributeName="r" values="0;0;10;0;0" keyTimes="0;0.30;0.38;0.53;1" dur="12s" begin="5s" repeatCount="indefinite"/> + </circle> + <animate attributeName="opacity" values="0;0;1;0;0" keyTimes="0;0.30;0.38;0.53;1" dur="12s" begin="5s" repeatCount="indefinite"/> + </g> + + <!-- AI•MCP burst — cyan #22d3ee, CCW 11s begin=3s, particle passes AI at keyTime=0.38 --> + <g opacity="0"> + <path d="M 548 420 L 544 424 L 552 427 L 548 430" fill="none" stroke="#22d3ee" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" filter="url(#glow-flash)"/> + <circle cx="548" cy="420" r="0" fill="#22d3ee" filter="url(#glow-flash)"> + <animate attributeName="r" values="0;0;10;0;0" keyTimes="0;0.30;0.38;0.53;1" dur="11s" begin="3s" repeatCount="indefinite"/> + </circle> + <animate attributeName="opacity" values="0;0;1;0;0" keyTimes="0;0.30;0.38;0.53;1" dur="11s" begin="3s" repeatCount="indefinite"/> + </g> + + <!-- Control Center burst — purple #818cf8, CW 9s begin=4s, particle passes CC at keyTime=0.75 --> + <g opacity="0"> + <path d="M 505 320 L 500 315 L 498 325 L 491 320" fill="none" stroke="#818cf8" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" filter="url(#glow-flash)"/> + <circle cx="505" cy="320" r="0" fill="#818cf8" filter="url(#glow-flash)"> + <animate attributeName="r" values="0;0;10;0;0" keyTimes="0;0.67;0.75;0.90;1" dur="9s" begin="4s" repeatCount="indefinite"/> + </circle> + <animate attributeName="opacity" values="0;0;1;0;0" keyTimes="0;0.67;0.75;0.90;1" dur="9s" begin="4s" repeatCount="indefinite"/> + </g> + + + <!-- ═══ CREDENTIAL FLOW ═══ --> + + <circle r="4" fill="#f59e0b" opacity="0" filter="url(#glow-gold)"> + <animateMotion dur="2.5s" begin="0s" repeatCount="indefinite"> + <mpath href="#path-orch-vault"/> + </animateMotion> + <animate attributeName="opacity" values="0;0.95;0.95;0" keyTimes="0;0.1;0.8;1" dur="2.5s" repeatCount="indefinite"/> + </circle> + + <circle r="3.5" fill="#ec4899" opacity="0" filter="url(#glow-pink)"> + <animateMotion dur="2.5s" begin="4s" repeatCount="indefinite"> + <mpath href="#path-vault-orch"/> + </animateMotion> + <animate attributeName="opacity" values="0;0.9;0.9;0" keyTimes="0;0.1;0.8;1" dur="2.5s" begin="4s" repeatCount="indefinite"/> + </circle> + + <circle r="3" fill="#4a9eff" opacity="0" filter="url(#glow-blue)"> + <animateMotion dur="1.2s" begin="0s" repeatCount="indefinite"> + <mpath href="#path-cli-orch"/> + </animateMotion> + <animate attributeName="opacity" values="0;0.85;0.85;0" keyTimes="0;0.15;0.7;1" dur="1.2s" repeatCount="indefinite"/> + </circle> + + + <!-- ═══ CONFIGURATION CYLINDER EDGES ═══ --> + <line x1="920" y1="95" x2="920" y2="156" stroke="#10b981" stroke-width="0.8" stroke-dasharray="4 2" opacity="0.4"/> + <line x1="1060" y1="95" x2="1060" y2="156" stroke="#10b981" stroke-width="0.8" stroke-dasharray="4 2" opacity="0.4"/> + + <!-- ═══ DATA PERSISTENCE CYLINDER EDGES ═══ --> + <line x1="210" y1="582" x2="210" y2="669" stroke="#a78bfa" stroke-width="0.8" stroke-dasharray="4 2" opacity="0.4"/> + <line x1="1070" y1="582" x2="1070" y2="669" stroke="#a78bfa" stroke-width="0.8" stroke-dasharray="4 2" opacity="0.4"/> + + <!-- ═══ CONNECTIONS TO SURREALDB ═══ --> + <g opacity="0.3"> + <line x1="640" y1="460" x2="640" y2="570" stroke="#a78bfa" stroke-width="1" stroke-dasharray="4 3" class="db-flow" marker-end="url(#arr-silver)"/> + <line x1="430" y1="537" x2="430" y2="571" stroke="#a78bfa" stroke-width="0.8" stroke-dasharray="4 3" class="db-flow" marker-end="url(#arr-silver)"/> + <line x1="850" y1="537" x2="850" y2="571" stroke="#a78bfa" stroke-width="0.8" stroke-dasharray="4 3" class="db-flow" marker-end="url(#arr-silver)"/> + <path d="M 377 358 C 280 370 230 480 240 572" fill="none" stroke="#a78bfa" stroke-width="0.8" stroke-dasharray="4 3" class="db-flow" marker-end="url(#arr-silver)"/> + <path d="M 903 358 C 1000 370 1050 480 1040 572" fill="none" stroke="#a78bfa" stroke-width="0.8" stroke-dasharray="4 3" class="db-flow" marker-end="url(#arr-silver)"/> + <path d="M 1060 128 C 1195 270 1145 410 1070 577" fill="none" stroke="#a78bfa" stroke-width="0.8" stroke-dasharray="4 3" class="db-flow" marker-end="url(#arr-silver)"/> + <path d="M 235 138 C 100 280 150 420 210 579" fill="none" stroke="#a78bfa" stroke-width="0.8" stroke-dasharray="4 3" class="db-flow" marker-end="url(#arr-silver)"/> + </g> + + + <!-- ═══ DATA PERSISTENCE ═══ --> + + <g> + <ellipse cx="640" cy="669" rx="430" ry="10" fill="#2e2640" stroke="none" opacity="0.35"/> + <path d="M 210 669 Q 640 686 1070 669" stroke="#a78bfa" stroke-width="1" fill="none" stroke-dasharray="4 2" opacity="0.5"/> + <rect x="210" y="582" width="860" height="87" rx="8" fill="url(#surreal-grad)" stroke="none" opacity="0.3" filter="url(#soft-shadow)"/> + <ellipse cx="640" cy="582" rx="430" ry="12" fill="#a78bfa" opacity="0.45" stroke="none"/> + + <text x="640" y="617" text-anchor="middle" fill="#a78bfa" font-family="'DM Sans',sans-serif" font-weight="700" font-size="11" letter-spacing="1">DATA PERSISTENCE</text> + <text x="1055" y="607" text-anchor="end" fill="#c4a8fc" font-family="'IBM Plex Mono',monospace" font-size="7" opacity="0.7">Solo: RocksDB • Multi: WebSocket</text> + + <g font-family="'IBM Plex Mono',monospace" font-size="7" font-weight="600"> + <rect x="258" y="637" width="130" height="16" rx="3" fill="#f59e0b" stroke="#f59e0b" stroke-width="0.8" opacity="0.25"/> + <text x="323" y="648" text-anchor="middle" fill="#f59e0b">orchestrator</text> + + <rect x="400" y="637" width="110" height="16" rx="3" fill="#ec4899" stroke="#ec4899" stroke-width="0.8" opacity="0.25"/> + <text x="455" y="648" text-anchor="middle" fill="#ec4899">vault</text> + + <rect x="522" y="637" width="140" height="16" rx="3" fill="#818cf8" stroke="#818cf8" stroke-width="0.8" opacity="0.25"/> + <text x="592" y="648" text-anchor="middle" fill="#818cf8">control_center</text> + + <rect x="674" y="637" width="90" height="16" rx="3" fill="#10b981" stroke="#10b981" stroke-width="0.8" opacity="0.25"/> + <text x="719" y="648" text-anchor="middle" fill="#10b981">audit</text> + + <rect x="776" y="637" width="130" height="16" rx="3" fill="#4a9eff" stroke="#4a9eff" stroke-width="0.8" opacity="0.25"/> + <text x="841" y="648" text-anchor="middle" fill="#4a9eff">workspace</text> + + <rect x="918" y="637" width="100" height="16" rx="3" fill="none" stroke="#10b981" stroke-width="0.6" stroke-dasharray="4 2" opacity="0.5"/> + <text x="968" y="648" text-anchor="middle" fill="#10b981">Nickel config</text> + </g> + </g> + + + <!-- ═══ SOLID ENFORCEMENT ═══ --> + + <g transform="translate(120, 759)"> + <text x="0" y="0" fill="#3a3e48" font-family="'IBM Plex Mono',monospace" font-weight="600" font-size="8" letter-spacing="1.5">SOLID ENFORCEMENT LAYERS</text> + <g font-family="'IBM Plex Mono',monospace" font-size="7"> + <rect x="0" y="8" width="80" height="12" rx="3" fill="#f59e0b" opacity="0.18" stroke="#f59e0b" stroke-width="0.5" stroke-opacity="0.5"/> + <text x="40" y="17" text-anchor="middle" fill="#f59e0b" opacity="0.8">Compile-time</text> + <rect x="88" y="8" width="68" height="12" rx="3" fill="#818cf8" opacity="0.18" stroke="#818cf8" stroke-width="0.5" stroke-opacity="0.5"/> + <text x="122" y="17" text-anchor="middle" fill="#818cf8" opacity="0.8">Dev-time</text> + <rect x="164" y="8" width="74" height="12" rx="3" fill="#ec4899" opacity="0.18" stroke="#ec4899" stroke-width="0.5" stroke-opacity="0.5"/> + <text x="201" y="17" text-anchor="middle" fill="#ec4899" opacity="0.8">Pre-commit</text> + <rect x="246" y="8" width="50" height="12" rx="3" fill="#ef4444" opacity="0.18" stroke="#ef4444" stroke-width="0.5" stroke-opacity="0.5"/> + <text x="271" y="17" text-anchor="middle" fill="#ef4444" opacity="0.8">CI/CD</text> + <rect x="304" y="8" width="60" height="12" rx="3" fill="#22d3ee" opacity="0.18" stroke="#22d3ee" stroke-width="0.5" stroke-opacity="0.5"/> + <text x="334" y="17" text-anchor="middle" fill="#22d3ee" opacity="0.8">Runtime</text> + <rect x="372" y="8" width="50" height="12" rx="3" fill="#94a3b8" opacity="0.18" stroke="#94a3b8" stroke-width="0.5" stroke-opacity="0.5"/> + <text x="397" y="17" text-anchor="middle" fill="#94a3b8" opacity="0.6">Audit</text> + </g> + <g font-family="'IBM Plex Mono',monospace" font-size="7" fill="#e2e6ee" opacity="0.6"> + <text x="460" y="16" font-family="'IBM Plex Mono',monospace" font-size="7"><tspan fill="#f59e0b">Providers APIs or CLI</tspan><tspan fill="#e2e6ee">: Orchestrator only | </tspan><tspan fill="#818cf8">SSH</tspan><tspan fill="#e2e6ee">: Orchestrator + Machines | </tspan><tspan fill="#22d3ee">Auth</tspan><tspan fill="#e2e6ee">: Control Center only | </tspan><tspan fill="#ec4899">Secrets</tspan><tspan fill="#e2e6ee">: Vault Service API only</tspan></text> + </g> + </g> + + + <!-- ═══ LEGEND ═══ --> + + <g transform="translate(120, 802)" font-family="'IBM Plex Mono',monospace" font-size="8"> + <text x="0" y="0" fill="#3a3e48" font-weight="600" font-size="8" letter-spacing="1.5">SERVICES</text> + <circle cx="7" cy="12" r="3" fill="#f59e0b"/><text x="15" y="16" fill="#6b6050">Orchestrator</text> + <circle cx="7" cy="24" r="3" fill="#818cf8"/><text x="15" y="28" fill="#9090b8">Control Center</text> + <circle cx="7" cy="36" r="3" fill="#ec4899"/><text x="15" y="40" fill="#5a3850">Vault Service</text> + <circle cx="7" cy="48" r="3" fill="#ef4444"/><text x="15" y="52" fill="#5a3848">Extension Registry</text> + <circle cx="7" cy="60" r="3" fill="#22d3ee"/><text x="15" y="64" fill="#385a58">AI • MCP</text> + + <text x="240" y="0" fill="#3a3e48" font-weight="600" font-size="8" letter-spacing="1.5">INFRASTRUCTURE</text> + <circle cx="248" cy="12" r="5" fill="none" stroke="#94a3b8" stroke-width="1.2" stroke-dasharray="2 2"/> + <text x="260" y="16" fill="#5a6278" opacity="0.5">NATS Orbital Ring</text> + <rect x="243" y="22.5" width="6" height="6" fill="none" stroke="#ef4444" stroke-width="0.8" rx="0.5"/> + <line x1="243" y1="25.5" x2="249" y2="25.5" stroke="#ef4444" stroke-width="0.7"/> + <line x1="246" y1="22.5" x2="246" y2="28.5" stroke="#ef4444" stroke-width="0.7" opacity="0.6"/> + <text x="260" y="28" fill="#ef4444" opacity="0.5">OCI registry</text> + <line x1="240" y1="36" x2="254" y2="36" stroke="#818cf8" stroke-width="1.5"/> + <text x="260" y="40" fill="#818cf8" opacity="0.5">Token and Auth</text> + <path d="M 244 43 L 250 43 L 250 48 Q 247 52 244 48 L 244 43" fill="none" stroke="#ec4899" stroke-width="1.2" stroke-linejoin="round"/> + <text x="260" y="52" fill="#ec4899" opacity="0.5">SecretumVault</text> + <line x1="240" y1="60" x2="254" y2="60" stroke="#10b981" stroke-width="1.5"/> + <text x="260" y="64" fill="#10b981" opacity="0.5">Nickel</text> + <ellipse cx="247" cy="69" rx="5" ry="2" fill="none" stroke="#a78bfa" stroke-width="0.8"/> + <rect x="243" y="69" width="8" height="5" fill="none" stroke="#a78bfa" stroke-width="0.8" opacity="0.5"/> + <ellipse cx="247" cy="74" rx="5" ry="2" fill="#a78bfa" opacity="0.3" stroke="#a78bfa" stroke-width="0.8"/> + <text x="260" y="76" fill="#a78bfa" opacity="0.5">SurrealDB</text> + + <text x="460" y="0" fill="#3a3e48" font-weight="600" font-size="8" letter-spacing="1.5">CONNECTIONS</text> + <line x1="460" y1="12" x2="474" y2="12" stroke="#4a9eff" stroke-width="2" filter="url(#glow-blue)"/> + <line x1="460" y1="12" x2="474" y2="12" stroke="#4a9eff" stroke-width="1" stroke-dasharray="4 3" opacity="0.7"/> + <text x="486" y="16" fill="#4a9eff" opacity="0.5">Encrypted</text> + <line x1="460" y1="24" x2="474" y2="24" stroke="#ef9a3c" stroke-width="1.5" stroke-dasharray="2 2"/> + <text x="486" y="28" fill="#ef9a3c" opacity="0.3">I/O and Defs</text> + <line x1="460" y1="36" x2="474" y2="36" stroke="#a78bfa" stroke-width="1.5" stroke-dasharray="2 2"/> + <text x="486" y="40" fill="#5a4878">DBs data</text> + <line x1="460" y1="48" x2="474" y2="48" stroke="#10b981" stroke-width="1.5" stroke-dasharray="2 2"/> + <text x="486" y="52" fill="#10b981" opacity="0.5">Secure access</text> + <circle cx="468" cy="60" r="5" fill="none" stroke="#94a3b8" stroke-width="1.2" stroke-dasharray="2 2"/> + <text x="486" y="64" fill="#5a6278">Events Stream</text> + + <text x="685" y="0" fill="#3a3e48" font-weight="600" font-size="8" letter-spacing="1.5">MODES</text> + <rect x="685" y="11" width="40" height="10" rx="2" fill="#34d399" opacity="0.1" stroke="#34d399" stroke-width="0.4"/> + <text x="705" y="19" text-anchor="middle" fill="#34d399" font-size="7" font-weight="600">SOLO</text> + <text x="735" y="19" fill="#4a7a5a" font-size="7">RocksDB + local NATS + auto-session</text> + <rect x="685" y="25" width="40" height="10" rx="2" fill="#818cf8" opacity="0.1" stroke="#818cf8" stroke-width="0.4"/> + <text x="705" y="33" text-anchor="middle" fill="#818cf8" font-size="7" font-weight="600">MULTI</text> + <text x="735" y="33" fill="#9090b8" font-size="7">WebSocket DB + NATS cluster + JWT+Cedar</text> + <rect x="685" y="39" width="40" height="10" rx="2" fill="#f59e0b" opacity="0.1" stroke="#f59e0b" stroke-width="0.4"/> + <text x="705" y="47" text-anchor="middle" fill="#f59e0b" font-size="7" font-weight="600">ENT</text> + <text x="735" y="47" fill="#d97706" font-size="7">Enterprise</text> + </g> + + + <!-- ═══ DEPLOYMENT ═══ --> + + <g transform="translate(145, 693)" font-family="'IBM Plex Mono',monospace"> + <rect x="160" y="0" width="120" height="45" rx="6" fill="#2a1515" stroke="#2a1010" stroke-width="0.8"/> + <circle cx="174" cy="15" r="3" fill="#ef4444"/><text x="182" y="18" fill="#d0a0a0" font-size="8" font-weight="500">Production</text> + <text x="209" y="35" fill="#5a3838" font-size="7" text-anchor="middle">Hetzner • AWS</text> + + <rect x="300" y="0" width="120" height="45" rx="6" fill="#2a2010" stroke="#1a1810" stroke-width="0.8"/> + <circle cx="314" cy="15" r="3" fill="#f59e0b"/><text x="322" y="18" fill="#d0c0a0" font-size="8" font-weight="500">Staging</text> + <text x="344" y="35" fill="#5a5038" font-size="7" text-anchor="middle">K8s cluster</text> + + <rect x="440" y="0" width="120" height="45" rx="6" fill="#1a2820" stroke="#0a1810" stroke-width="0.8"/> + <circle cx="454" cy="15" r="3" fill="#34d399"/><text x="462" y="18" fill="#a0d0b8" font-size="8" font-weight="500">Dev</text> + <text x="484" y="35" fill="#385a48" font-size="7" text-anchor="middle">Solo mode</text> + + <rect x="580" y="0" width="120" height="45" rx="6" fill="#1a182a" stroke="#0a0a1a" stroke-width="0.8"/> + <circle cx="594" cy="15" r="3" fill="#818cf8"/><text x="602" y="18" fill="#b0b0d8" font-size="8" font-weight="500">Edge</text> + <text x="624" y="35" fill="#48486a" font-size="7" text-anchor="middle">On-prem • IoT</text> + + <rect x="720" y="0" width="120" height="45" rx="6" fill="#1a2020" stroke="#0a1010" stroke-width="0.8"/> + <circle cx="734" cy="15" r="3" fill="#22d3ee"/><text x="742" y="18" fill="#a0c8c8" font-size="8" font-weight="500">Custom</text> + <text x="774" y="35" fill="#385858" font-size="7" text-anchor="middle">GitOps • Webhook</text> + </g> + + <!-- ═══ VERSION ═══ --> + <text x="1160" y="872" text-anchor="end" fill="#b0dff6" font-family="'IBM Plex Mono',monospace" font-size="9" letter-spacing="0.5">v3.0.11 • ∞ Architecture</text> +</svg> diff --git a/site/site/public/_content/projects/es/provisioning-systems/images/provisioning-systems_arch-light.svg b/site/site/public/_content/projects/es/provisioning-systems/images/provisioning-systems_arch-light.svg new file mode 100644 index 0000000..4291305 --- /dev/null +++ b/site/site/public/_content/projects/es/provisioning-systems/images/provisioning-systems_arch-light.svg @@ -0,0 +1,703 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 900"> + <style> + .orbit-flow { animation: orbit-dash 4s linear infinite; } + @keyframes orbit-dash { from { stroke-dashoffset: 40; } to { stroke-dashoffset: 0; } } + + .ring-glow { animation: ring-breathe 3s ease-in-out infinite; } + @keyframes ring-breathe { 0%,100% { opacity: 0.06; } 50% { opacity: 0.18; } } + + .spoke { animation: spoke-dash 2s linear infinite; } + @keyframes spoke-dash { from { stroke-dashoffset: 16; } to { stroke-dashoffset: 0; } } + + .health { animation: pulse 2.2s ease-in-out infinite; } + .health-d1 { animation-delay: 0.4s; } + .health-d2 { animation-delay: 0.8s; } + .health-d3 { animation-delay: 1.2s; } + .health-d4 { animation-delay: 1.6s; } + @keyframes pulse { 0%,100% { opacity: 0.35; } 50% { opacity: 1; } } + + .https-beam { animation: https-p 4s ease-in-out infinite; } + @keyframes https-p { 0%,100% { opacity: 0.15; stroke-width: 1.5; } 50% { opacity: 0.7; stroke-width: 2.5; } } + .https-label { animation: https-lbl 4s ease-in-out infinite; } + @keyframes https-lbl { 0%,100% { opacity: 0.3; } 50% { opacity: 1; } } + + .solid-border { animation: solid-glow 3.5s ease-in-out infinite; } + @keyframes solid-glow { 0%,100% { stroke-opacity: 0.3; } 50% { stroke-opacity: 0.7; } } + + .hub-pulse { animation: hub-beat 3s ease-in-out infinite; } + @keyframes hub-beat { 0%,100% { opacity: 0.3; } 50% { opacity: 0.6; } } + + .cred-dot { opacity: 0; } + .db-flow { animation: db-dash 2.5s linear infinite; } + @keyframes db-dash { from { stroke-dashoffset: 12; } to { stroke-dashoffset: 0; } } + </style> + + <defs> + <filter id="glow-blue" x="-50%" y="-50%" width="200%" height="200%"> + <feGaussianBlur in="SourceGraphic" stdDeviation="6" result="blur"/> + <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge> + </filter> + <filter id="glow-orange" x="-50%" y="-50%" width="200%" height="200%"> + <feGaussianBlur in="SourceGraphic" stdDeviation="5" result="blur"/> + <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge> + </filter> + <filter id="glow-pink" x="-50%" y="-50%" width="200%" height="200%"> + <feGaussianBlur in="SourceGraphic" stdDeviation="5" result="blur"/> + <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge> + </filter> + <filter id="glow-purple" x="-50%" y="-50%" width="200%" height="200%"> + <feGaussianBlur in="SourceGraphic" stdDeviation="5" result="blur"/> + <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge> + </filter> + <filter id="glow-cyan" x="-50%" y="-50%" width="200%" height="200%"> + <feGaussianBlur in="SourceGraphic" stdDeviation="5" result="blur"/> + <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge> + </filter> + <filter id="glow-gold" x="-50%" y="-50%" width="200%" height="200%"> + <feGaussianBlur in="SourceGraphic" stdDeviation="6" result="blur"/> + <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge> + </filter> + <filter id="glow-flash" x="-50%" y="-50%" width="200%" height="200%"> + <feGaussianBlur in="SourceGraphic" stdDeviation="4" result="blur"/> + <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge> + </filter> + <filter id="soft-shadow" x="-20%" y="-20%" width="140%" height="140%"> + <feDropShadow dx="0" dy="2" stdDeviation="8" flood-color="#000" flood-opacity="0.12"/> + </filter> + <filter id="ring-glow-f" x="-20%" y="-20%" width="140%" height="140%"> + <feGaussianBlur in="SourceGraphic" stdDeviation="8" result="blur"/> + <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge> + </filter> + + <radialGradient id="bg-radial" cx="50%" cy="42%" r="55%"> + <stop offset="0%" stop-color="#ffffff"/> + <stop offset="100%" stop-color="#ffffff"/> + </radialGradient> + <radialGradient id="hub-glow" cx="50%" cy="50%" r="50%"> + <stop offset="0%" stop-color="#94a3b8" stop-opacity="0.15"/> + <stop offset="100%" stop-color="#94a3b8" stop-opacity="0"/> + </radialGradient> + <linearGradient id="orch-grad" x1="0%" y1="0%" x2="100%" y2="100%"> + <stop offset="0%" stop-color="#fff8ed"/><stop offset="100%" stop-color="#fef3e0"/> + </linearGradient> + <linearGradient id="ctrl-grad" x1="0%" y1="0%" x2="100%" y2="100%"> + <stop offset="0%" stop-color="#f0eeff"/><stop offset="100%" stop-color="#e8e5fa"/> + </linearGradient> + <linearGradient id="vault-grad" x1="0%" y1="0%" x2="100%" y2="100%"> + <stop offset="0%" stop-color="#fef0f5"/><stop offset="100%" stop-color="#fce8f0"/> + </linearGradient> + <linearGradient id="ext-grad" x1="0%" y1="0%" x2="100%" y2="100%"> + <stop offset="0%" stop-color="#f7ede7"/><stop offset="100%" stop-color="#f1e4dc"/> + </linearGradient> + <linearGradient id="mcp-grad" x1="0%" y1="0%" x2="100%" y2="100%"> + <stop offset="0%" stop-color="#edf9fc"/><stop offset="100%" stop-color="#e5f5f8"/> + </linearGradient> + <linearGradient id="surreal-grad" x1="0%" y1="0%" x2="100%" y2="100%"> + <stop offset="0%" stop-color="#f3f0fe"/><stop offset="100%" stop-color="#eee8fc"/> + </linearGradient> + <linearGradient id="cli-grad" x1="0%" y1="0%" x2="100%" y2="100%"> + <stop offset="0%" stop-color="#edf2fa"/><stop offset="100%" stop-color="#e5ecf5"/> + </linearGradient> + + <!-- Animated gradient for infinity engine color cycling --> + <linearGradient id="infinityGradient" x1="0%" y1="0%" x2="100%" y2="0%"> + <stop offset="0%" style="stop-color:#00D4FF;stop-opacity:1"> + <animate attributeName="stop-color" values="#00D4FF;#7B2BFF;#00FFB3;#00D4FF" dur="3s" repeatCount="indefinite"/> + </stop> + <stop offset="50%" style="stop-color:#7B2BFF;stop-opacity:1"> + <animate attributeName="stop-color" values="#7B2BFF;#00FFB3;#00D4FF;#7B2BFF" dur="3s" repeatCount="indefinite"/> + </stop> + <stop offset="100%" style="stop-color:#00FFB3;stop-opacity:1"> + <animate attributeName="stop-color" values="#00FFB3;#00D4FF;#7B2BFF;#00FFB3" dur="3s" repeatCount="indefinite"/> + </stop> + </linearGradient> + + <marker id="arr-blue" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"> + <polygon points="0 0, 8 3, 0 6" fill="#4a9eff"/> + </marker> + <marker id="arr-silver" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"> + <polygon points="0 0, 8 3, 0 6" fill="#94a3b8"/> + </marker> + <marker id="arr-gold" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"> + <polygon points="0 0, 8 3, 0 6" fill="#fbbf24"/> + </marker> + <marker id="arr-green" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"> + <polygon points="0 0, 8 3, 0 6" fill="#10b981"/> + </marker> + <marker id="arr-green-sm" markerWidth="5" markerHeight="4" refX="5" refY="2" orient="auto"> + <polygon points="0 0, 5 2, 0 4" fill="#10b981"/> + </marker> + + <path id="orbit-cw" d="M 640,185 A 135,135 0 1,1 640,455 A 135,135 0 1,1 640,185 Z" fill="none"/> + <path id="orbit-ccw" d="M 640,185 A 135,135 0 1,0 640,455 A 135,135 0 1,0 640,185 Z" fill="none"/> + + <path id="path-orch-vault" d="M 640,185 A 135,135 0 0,0 508,276" fill="none"/> + <path id="path-vault-orch" d="M 508,276 A 135,135 0 0,1 640,185" fill="none"/> + <path id="path-cli-orch" d="M 660,98 L 660,115" fill="none"/> + </defs> + + <!-- ═══ BACKGROUND ═══ --> + <rect width="1280" height="900" fill="url(#bg-radial)"/> + <g opacity="0.03"> + <pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse"> + <path d="M 40 0 L 0 0 0 40" fill="none" stroke="#ccc" stroke-width="0.5"/> + </pattern> + <rect width="1280" height="900" fill="url(#grid)"/> + </g> + + <!-- Star field --> + <g opacity="0.35"> + <circle cx="95" cy="145" r="0.8" fill="#fff"/><circle cx="340" cy="78" r="0.6" fill="#fff"/> + <circle cx="890" cy="115" r="1" fill="#fff"/><circle cx="1120" cy="210" r="0.7" fill="#fff"/> + <circle cx="180" cy="460" r="0.5" fill="#fff"/><circle cx="1160" cy="510" r="0.8" fill="#fff"/> + <circle cx="72" cy="660" r="0.6" fill="#fff"/><circle cx="960" cy="710" r="0.9" fill="#fff"/> + </g> + + <!-- ═══ TITLE ═══ --> + <g transform="translate(206, 20) scale(0.35)"> + <g id="logo-content"><g><g><path d="M601.85,109.76c.89,.04,.57-.38,.7-.46-1.49,.09-.67,.57-.7,.46Z" style="fill:#4cc2f1;"/><polygon points="587.17 110.72 587.43 110.55 585.26 110.96 587.17 110.72" style="fill:#4cc2f1;"/><path d="M573.43,110.71c1.3,.71,4.98-.12,7.42,.14l.4,.16,1.1-.57c-2.97,.09-6.84,.47-8.93,.27Z" style="fill:#4cc2f1;"/><path d="M566.11,109.16c.45,.28-1.58,.24,1.71,.09-.17-.03-.21-.05-.31-.07-.3,0-.7,0-1.4-.02Z" style="fill:#4cc2f1;"/><path d="M567.51,109.18c1.45-.02-.95-.23,0,0h0Z" style="fill:#4cc2f1;"/><path d="M560.83,109.34l1.9-.25c-.4-.16-2.09-.2-1.46-.32-1.95,.14-1.55,.3-.44,.57Z" style="fill:#4cc2f1;"/><path d="M559.02,109.86l.99-.1c-.17-.07-.43-.09-.99,.1Z" style="fill:#4cc2f1;"/><path d="M562.11,109.53l-2.09,.22c.28,.12,.3,.35,.96-.08-.17,.21,.24,0,1.13-.14Z" style="fill:#4cc2f1;"/><path d="M563.29,109.64c1.07,.16,2.45,.25,.54,.5,3.64-.1,3.11-.6-.54-.5Z" style="fill:#4cc2f1;"/><path d="M572.73,111.45l1.43,.21,1.24-.24c-.62,.12-2.75-.19-2.66,.03Z" style="fill:#4cc2f1;"/><path d="M548.96,108.97c-.12,.15,.64,.4,.78,.6l1.41-.17c-.72-.1-2.09-.2-2.19-.42Z" style="fill:#4cc2f1;"/><path d="M549.75,109.57l-2.11,.26c1.88,.03,2.22-.09,2.11-.26Z" style="fill:#4cc2f1;"/><polygon points="558.69 110.65 562.14 110.11 558.02 110.66 558.69 110.65" style="fill:#4cc2f1;"/><polygon points="540.92 108.95 542.12 108.6 539.99 109.13 540.92 108.95" style="fill:#4cc2f1;"/><path d="M535.59,109.01c.88-.29-1.21-.49-.28-.67-1.45,.05-2.69,.3-3.8,.53,1.18-.13,2.44-.09,4.08,.14Z" style="fill:#4cc2f1;"/><path d="M529.64,109.2c.59-.07,1.2-.19,1.87-.33-.63,.07-1.25,.16-1.87,.33Z" style="fill:#4cc2f1;"/><path d="M544.06,109.76c-.16-.09-.48-.33-1.66-.43l.83,.43c.25,0,.54,0,.83,0Z" style="fill:#4cc2f1;"/><path d="M543.26,109.76h-.03c-.9-.02-1.52-.03-1.79-.04,.23,0,.78,.01,1.82,.04Z" style="fill:#4cc2f1;"/><path d="M544.26,109.76c-.08,0-.13,0-.21,0,.08,.04,.13,.06,.21,0Z" style="fill:#4cc2f1;"/><path d="M553.17,110.27c-1.24,.24-3.82,.5-1.32,.86,.89-.29-.54-.5,1.32-.86Z" style="fill:#4cc2f1;"/><path d="M545.29,110.29l2.24,.1c-.04-.11-.45-.28,.17-.4l-2.4,.3Z" style="fill:#4cc2f1;"/><polygon points="544.2 110.43 545.29 110.29 544.46 110.26 544.2 110.43" style="fill:#4cc2f1;"/><path d="M533.47,109.6c-.97,.19-2.02,.2-3.22,.23,1.28,.08,2.6,.13,3.86,.13,.18-.13,.73-.38-.64-.36Z" style="fill:#4cc2f1;"/><path d="M538.23,109.71c-.07,0-.15,.01-.21,.02,.13,.04,.24,.05,.21-.02Z" style="fill:#4cc2f1;"/><path d="M518.51,109.82c.07,.02,.12,.05,.19,.08,.01-.02,.02-.05,.04-.07h-.23Z" style="fill:#4cc2f1;"/><path d="M534.33,110.04c.96,.08,1.54-.02,2.08-.15-.72,.04-1.48,.07-2.3,.07-.08,.06-.09,.1,.21,.08Z" style="fill:#4cc2f1;"/><path d="M526.36,109.4c.5,.09,1.07,.17,1.67,.23-.49-.14-1.49-.35-1.67-.23Z" style="fill:#4cc2f1;"/><path d="M537.69,109.62s.09-.01,.14-.02c-.32-.09-.28-.05-.14,.02Z" style="fill:#4cc2f1;"/><path d="M537.69,109.62c-.49,.06-.88,.17-1.27,.26,.59-.03,1.13-.08,1.6-.14-.12-.03-.24-.08-.32-.12Z" style="fill:#4cc2f1;"/><path d="M514.95,112.57c.17-.4,2.7-.76,4.88-.34,2.15-.3,.74-1.6-1.13-2.33-.07,.17-.07,.33-2.28,.57,.77,.22,4.9,.51,3.04,.86-2.93,.2-2.4-.14-3.82-.35,1.21,.49-3.72,.72-3.09,1.44l2.57-.26c.05,.11-.88,.29-.17,.4Z" style="fill:#4cc2f1;"/><path d="M522.91,109.94l.91-.05c-1.04-.07-1.78-.23-3.15-.54-1.51,.19-1.83,.34-1.93,.47l4.48,.06-.31,.06Z" style="fill:#4cc2f1;"/><path d="M527.9,109.89c-.71,.43-2.89,.61-1.56,1.08,2.67-.03,1.87-.36,3.46-.55-4.31,.11,1.24-.24-1.87-.48,.85-.07,1.61-.1,2.33-.11-.78-.05-1.52-.12-2.23-.2,.04,0,.07,.02,.1,.03h.02s0,0,0,0c.19,.05,.25,.09,.04,.06-.01,0-.07,0-.09,0-.03,.06-.11,.11-.19,.16-1.25-.09-.2-.11,.19-.16,.01-.02,.05-.04,.05-.06,0,0-.01,0-.02,0l-4.31,.24c.91,.06,2.06,.05,4.08,0Z" style="fill:#4cc2f1;"/><path d="M549.54,112.05c-.13-.04-.13-.07-.14-.1-.28,.05-.38,.09,.14,.1Z" style="fill:#4cc2f1;"/><path d="M547.77,111.79c.31-.06,.57-.23,1.59-.19,.88,.13,0,.21,.04,.35,.61-.11,2.15-.31,.22-.52l-.31,.06c-1.74-.15-3.97-.69-3.66-.74,.51,.39,.95,.66,2.11,1.04Z" style="fill:#4cc2f1;"/><path d="M537.71,110.95l-3.59,.21,1.83,.37-.09-.22c2.04,.09,1.6-.19,1.86-.36Z" style="fill:#4cc2f1;"/><polygon points="518.1 109.61 518.57 109.15 516.5 109.8 518.1 109.61" style="fill:#4cc2f1;"/><polygon points="513.43 109.66 513.6 109.27 512.37 109.51 513.43 109.66" style="fill:#4cc2f1;"/><path d="M250.01,114.57l-1.06,.11c.5-.03,.82-.07,1.06-.11Z" style="fill:#4cc2f1;"/><path d="M257.65,114.6l.19-.19c-.35,.11-.5,.19-.19,.19Z" style="fill:#4cc2f1;"/><path d="M398.78,109.71c-1.54-.37-3.25-.94-3.07-.5l.47,.43c1.07-.08,1.9-.03,2.6,.06Z" style="fill:#4cc2f1;"/><path d="M320.27,112.3c.15,.04,.3,.09,.45,.13,.37-.15,.73-.29,1.04-.42-.45,.09-.96,.18-1.49,.28Z" style="fill:#4cc2f1;"/><path d="M319.18,114.09l-2.03-.28c.46,.1,1.11,.19,2.03,.28Z" style="fill:#4cc2f1;"/><path d="M208.27,115.03l-1.23,.44c1.93,0,1.08-.23,1.23-.44Z" style="fill:#4cc2f1;"/><path d="M465.62,109.82c-1.33-.17-2.38-.24-3.31-.26,1.6,.25,2.89,.62,3.31,.26Z" style="fill:#4cc2f1;"/><path d="M253.01,109.97c-.65,.08-.92,.17-.9,.26,.66-.07,1.04-.16,.9-.26Z" style="fill:#4cc2f1;"/><path d="M458.48,109.56c.23,.04,.46,.07,.7,.1,.97-.07,1.95-.13,3.13-.1-1.18-.19-2.53-.3-3.83,0Z" style="fill:#4cc2f1;"/><path d="M56.92,114.68c-.11-.02-.24-.03-.34-.05-.03,.06,.12,.06,.34,.05Z" style="fill:#4cc2f1;"/><path d="M12.82,110.3c.43-.03,1.14-.03,1.81-.03-.33-.03-.87-.03-1.81,.03Z" style="fill:#4cc2f1;"/><path d="M494.65,109.47c.99-.03,.16-.05,0,0h0Z" style="fill:#4cc2f1;"/><path d="M499.46,111.19c-.21,.05-.37,.11-.4,.21,.08-.1,.22-.17,.4-.21Z" style="fill:#4cc2f1;"/><path d="M448.61,112.26s-.05,0-.09,0c.29,.08,.24,.07,.09,0Z" style="fill:#4cc2f1;"/><path d="M496.14,109.67c-.37-.05-.8-.1-1.27-.13,.2,.03,.59,.08,1.27,.13Z" style="fill:#4cc2f1;"/><path d="M495.93,109.47c.34,.02,.61,.03,.87,.04,.12-.07,.01-.12-.87-.04Z" style="fill:#4cc2f1;"/><path d="M493.27,109.5c.57,0,1.09,.01,1.6,.05-.19-.03-.28-.05-.22-.07-.29,0-.7,.02-1.38,.02Z" style="fill:#4cc2f1;"/><path d="M138.01,113.51c-.07-.01-.14-.02-.21-.04-1,.18-.56,.15,.21,.04Z" style="fill:#4cc2f1;"/><path d="M373.02,114.21s.07-.02,.11-.02c-.05,0-.1-.01-.15-.02l.03,.04Z" style="fill:#4cc2f1;"/><path d="M369.46,114.54c1.51-.54,2.38-.47,3.52-.36l-1.18-1.25-2.35,1.61Z" style="fill:#4cc2f1;"/><path d="M231.71,114.7l2.5,.39c-3.01,.64-5.19,.42-2.85,.97,.35-1.36,9.71-.14,10.05-1.5l1.65,.29c-.47-.01-.55,.07-1.01,.05,3.2,.65,2.38-.98,6.5-.79,1.44,.1,2.44,.3,1.46,.46l3.2-.34c.93,.03,.7,.26-.22,.24l4.89-.11-.04,.04c.7-.21,2.28-.54,3.05-.68,1.24,.19-.62,.14-.3,.31l2.71-.33c1.1,.35-1.14,.69-3.01,.64,2.53,1.36,2.24-.34,7.7,.45l-2.55,.17c1.65,.77,3.64,.49,6.53,.97-.69-.25-5.78-1.21-2.85-1.55,.83,.1,1.92,.15,3.5-.12,.01,.48,1.56,.36,3.5,.34l-.13,.64c2.09-.18,1.93-.51,3.16-.8,2.33,.06,2.82,.56,3.36,.98,3.73,.1-2.58-.79,1.84-.91,3.11,.24,5.29-.02,9-.4,2.17,.22-.38,.39,.4,.57l1.93-.51c.47,.01,.32,.17,.24,.25,1.62-.2,.13-.64,2.69-.81,.69-.7,5.24,1.03,8.4,.23,.78,.18-.38,.39-.14,.64,4.33-.52,3.72-.38,7.27-1.09l3.03,.42c-2.23-.48,.62-1.05,3.11-1.52-.79-.23-1.47-.43-1.92-.53l3.98-.34c.48,.1,.08,.32-.57,.59,.27-.05,.53-.11,.73-.15,1.42,.24,.69,.61-.33,.96-.48-.13-.97-.26-1.43-.39-.98,.39-1.9,.82-1.7,1.19,.26-.07,.76-.14,1.3-.17-.26,.11-.42,.2-.3,.26,.14,.07,.55-.07,1.2-.24l.9,.71,.22-.28,5.2,.45c3.72-.72,6.96-1.77,12.87-2.07-1.47,.52-.32,.85-.4,1.47-1.61-.66-4.58,.28-7.64,.15,1.6,.07,1.25,.36,.84,.47l5.46-.6c-.12,.51,1.16,.38,2.59,.59-1.08-1,4.07-.67,6.3-.97,.14,.34-.4,.68-3.11,.6,2.99,.75,3.87-1.18,6.8-.54-.66,0-1.02-.04-1.28,.13,1.55-.3,5.33-.06,4.44,.23h-.66c4.4,.13,13.38,.01,14.04-.84,.04,.11-.58,1.03-.84,1.2l7.84-2.86c-.69,.73,1.96,2.47-1.68,2.88,1.05,.1,2.34,.23,4.55-.03-.71-.1-1.78-1.06-.8-1.12,2.68,.81,1.86,.43,5.16,1.13-1.07-.16,.12-1.3,1.81-1.27-.26,.17,.14,1.13-.48,1.25l4.21-1.12c-1.05,.2-.19,.95,.76,1.27-.44-.22,3.64,.02,4.76-.09l-1.78-.26c4.75,.17,4.7-1.58,9.41-1.52-.57,.23-1.41,1.43,.99,1.57,1.1-.58,4.87-2.77,9.04-3.21l.8,.33,3.2-.38c-1.99,.87-7.59,2.69-10.64,3.4,1.78,.26,.5,.39,3.16,.36,.94,.31-1.45,.47-2.24,.49l6.2,.2c-.28-.67,4.74-.67,4.51-1.23l-6.74,.7c-.27-.67,3.9-1.91,8.29-1.79,1.12,.27-.84,1.2-.74,1.42,.57-.23,4.17-.44,4.58-.28l-1.86,.36c2.45,.25,3.99-.89,6.93-.25,1.01,.04,3.03,.87,3.21,.47-1.66-.77-1.98-3.14-.88-3.72,.36,.05,5.33,.73,6.49,1.11,1.67,.6-2.51,1.43-1.09,1.95,.06-.18,.98-.41,1.37-.48,1.07,.16-.44,.57,1.87,.49,.39-.68,4.71,.06,1.09-.58,2.27-.19,2.41,.14,5.07,.11-.85-.44,2.39-1.5,4.66-1.69-.13,.17,.38,.4,.62,.5,4.64,.06,7.14,.37,11.47,.38,.4,.17,2.49,.36,1.6,.66,.62-.12,1.19-.35,2.53-.37,2.58,.59-2.98,.09-1.01,.8,.17-.4,3.45-.55,5.36-.79-.59-.61-4.39-.12-6.3,.13,0-.84,3.31-1.73,7.83-2.12,3.6-.21,1.56,.55,2.18,.43,6.76,.15,5.76-1.47,11.37-.86,2.23,.54,.1,1.07,.59,1.46-2.93,.2-4.5-.34-6.48-.32l2.44,.25c-.93,.18-2.89,.31-3.95,.16,1.87,.48,11.41,.09,16.63,.54,.93-.18,2.52-.37,1.77-.58l-1.65,.08c-1.52-.43,3.1-.6,1.02-.8,2.21-.3,4.69-.78,7.81-.54l.59,.61c-.07-.18-2.04-.37-2.91-.18,1.3-.27,4.78,.83,6.2,.03l-2.18-.42c2.22-.31,4.97-.96,7.73-.77-1.26-.61-.64,.07-2.6-.64,1.7,.88-7.06-.04-4.33,.89-2.01-.82-3.48-.35-6.11-1.05,.08,.2,.93,.39-1.39,.29-.1,.06-.35,.13-.51,.18-.07,0-.1,0-.16-.02,.05,0,.08,.02,.13,.02-.1,.03-.11,.04,.05,0,.55,.09,1,.19,1.18,.31-1.15,.46-4.83,.45-6.08,.69-1.38-.1,.57-.23-.18-.45l-1.86,.36c-.18-.45-3.65-.75-.41-1.01l-2.92-.02c-1.72-.25-4.08-.35-5.53-.16-1.38-.1-3.47,.5-2.28,.15l-4.57,.28-.04-.11c-4.51-.06-9.29,.74-14.44-.07-1.52,.11-3.09,.25-5.63,.12l-.5-.39c-1.95,.14-5.86-.39-6.69,.02-2.1-1.05-9.46-.23-12.89-.41l-.13,.51c-2.36-.03-4.62,.12-7.55,.32l.18,.45c-2.53,.37-6.49-.32-10.49-.27,.27-.17,1.29-.13,2-.02-4.77-1.01-10.07,.74-14.4,0-1.08,.16-1.86,.11-2.62,0,.1,0,.19,0,.27-.02-.11,0-.27,0-.44,0-.48-.08-.97-.17-1.5-.24,.45,.11,.88,.2,1.26,.24-.83,0-2.09,.07-3.33,.18l-.53-.48c-.1,0-.18,0-.29,.01-1.21-.18-.98,.23-.33,.59-.62,.08-1.19,.17-1.64,.29-.99-.78-3.25,.26-4.59-.57-.83,.4-6.22,.3-6.88,1.15-.04-.11-.41-.16,.26-.17-1.38-.1-2.76-.19-3.99,.05l-.59-.61-3.01,.82c-1.83-.37-2.59-.59-.77-1.06-4.39,.72-3.56,.27-7.29,.99l.18-.4c-1.33,.02-3.46,.55-4.27,.22-2.98-.75-14.76-.1-22.41-.8,1.48,1.17-3.56-.52-3.86,.38-.45-.28-1.51-.43-.27-.67-3.91,.27-5.87-.44-8.57,.32-.45-.28,.84-.4,.44-.57-.31,.06-1.24,.24-1.65,.08-.4-.16,.57-.23,1.19-.35-3.99,.05-4.4,.9-4.69,1.69-2.02-.38-3.03-.32-4.49,.2-.7-.26-1.88-.53,.6-.63-1.4-.04-8.52-.38-8.67,.26-.44-.16-1.95-.05-2.32,.03-3.13-.04-3.41-.1-6.48-.04l.85,.1c-1.21,.77-2.56,.17-5.03,.26l.07-.08c-5.68-.56-2.45,.27-8.06-.37l.3,.48c-.52,1.03-4.97-.61-8.22-.21l1.17,.27c-1.78,.35-4.97-1.4-6.75-1.53-.39-.09,.61-.14,1.16-.21-4.67-.61-.61,.63-4.09,.77-.64-.34,.83-.86-1.52-.92-1.17-.27-6.8,1.1-9.99,.45,.7,.26,1.4,.52-.21,.72-2.48,.09-6.7-.99-8.94-.16-.32-.07-.43-.14-.45-.2-1.89,.21-6.16,.35-7.3,.8-1.75-1.66-11.78,.81-11.74-.72l-5.83,.08,.15-.16c-3.73-.1-5.04,.27-6.35,.63-.86-.1-.23-.25-.16-.33-5.44-.31-5.98-.24-10.55,.52l-.47-.5c-1.24,.29-7.87-.78-13.21-.2,.05-.06,.16-.15,.59-.22-6.01,.58-14.38-1.38-16.58,.43l-3.96,.44c5.13,.14-.76,.78,1.35,1.08-2.02,.11-5.21-.54-2.74-.64l.39,.09c.91-.94-6.37-.33-6.23-.97-10.09,.05-19.93-.47-29.38-.51l1.09,.73-3.3-.04c-.87-.17-1.16-.57,1.19-.54-1.24-.41-3.94,.35-4.01,.51-4.61-.3,1.8-.85,.39-.87l-1.92,.05,.43,.09c-1.55,.3-1.69,.62-4.52,.58-1.79-.15-.92-.45-1.62-.48,0-.05-.23-.08-1.08-.02l-4.15-.06,1.66,.58c-1.84,.28-3.91-.03-1.99,.62-2.81-1.01-15.5-.36-17.22-.68-2.58,.42-5.12,.41-8.28,.36,.78,.12,1.25,.77-1.47,.69,.99-1.34-4.98-.58-7.18-1.55,1.96,.64-7.73,.16-4.65,1.07-2.27-.11-.14-.5-1.86-.82-5.4,.52-11.9-.34-18.58-.16-.1,.18,1.01,.44-.42,.71l-3.52-.88c-1.32,.09-1.21,1.02-3.62,.4,.34,.15,.85,.37-.04,.43-13.08-.86-26.73,.7-40.01-.91,1.8,.34,.6,.37-.77,.39,.89,.08-.02,.43,.16,.67l-5.56-.66c-1.01,.66-5.96,.04-6.54,.68l1.76-.11c-2.34,.75,3.28,1.66,2.93,2.62,2.62-.85,6.91,1.26,11.22,.04,.95,.2-.81,.31-.3,.53,1.09-.41,2.41-.5,4.85-.31l-.28,.1c6.86-.1,9.8,.3,17.5,.57l-.57-.47c1.66,.06,2,.21,2.78,.33,1.9-.72-4.65-.04-2.92-.83,2.2,.96,11.67,.01,13.16,1.11,2.55,0-.75-.55,1.8-.54l.68,.3,.64-.38c1.67,.06,2.51,.43,2.57,.68-.41-.03-1.15,.07-1.59,.09,2.16,.35,6.1-.12,6.85,0l-2.45-.18c6.95-.28,15.43,.11,22.15-.49l-.51-.22c5.13-.41,4.11,.25,9.74,.05l-.2,.05c1.13-.22,2.63-.33,4.17-.31-1.42,.26,2.67,.51,.81,.8,5.56-.44,2.75-.35,6.42-1.18l.85,.37c1.25-.34,1.36-.51,4.17-.61-2.31,.32,1.79,.57-.79,.99,5.9,.81,8.76-.82,11.47,.37,2.71-1.03-5.11-.57-3.59-.79-1.27-.34,2.24-.77,4.09-.66,1.88,.02,3.2,1.31,7.94,1.29-.47,0-.5,.07-.97,.07,1.77,.26,3.48-.35,5.65,.07,1.22-.62,2.83,.04,3.18-.76l-4.35,.18c2.47-.21,4.78-1.13,8.84-.68-.32,.16-1.52,.37-2.36,.49,1.19,.23,2.29-.15,3.52,.08-.35,.79-5.28,.17-7.93,.78,1.27,.33,5-.57,3.67,.28,1.91-1.09,4.67,.14,7.88-.7l-.21,.47c.51-.07,1.56-.3,2.5-.29l-1.69,.62c2.6-.52,5.01,.46,7.5,.17-5.22,.01-1.17-.57-3.38-.92,6.44-.64,3.7,1.24,10.91,1.01-.98,.07-3.66-.29-2.14-.5,1.37,.1,3.18,.28,4.03,.53,4.78-.1-.73-.49,1.3-.78,1.63,.58,2.34-.24,4.44-.42v.48c5.46,.31,1.46-1.01,6.36-.63l-2.07,.67,2.55-.17-.53,.55c2.78-.41,3.64-.3,6.37-.15-.63-.34,.36-.88,2.77-.89,1.64,.29-.92,.46,2.5,.39-.77,.3-1.62,.68-3.1,.24-.15,.16-.76,.3-.92,.46,1.77,.33,4.45,.08,5.69,.07-.48-.02-1.05-.04-1.34-.11l4.95-.67c.78,.18,.16,.33-.45,.47,1-.05,1.7-.27,3.17-.32-.38,.39-.29,.8-2.38,.98l4.72-.44c.32,.17,2.42,.47,1.81,.61,2.62,.23,6.42-.76,9.89-.46,.18-.08,.54-.15,1.28-.21,3.26,.09,6.15,.57,10.01,.03l2.19,.7c3.03-.16-2.97-.89,2.07-1.15,3.57-.22,1.33,.6,2.58,.8,1.77-.35,4.86-1.08,8.14-.5-1.08,.13-1.94,.03-2.95,.08Zm37.32-.31c-.72,.1-2.16,.41-2.69,.13,.82-.55,1.46-.29,2.69-.13Zm56.73-1.16c-.54,.16-1.37,.08-2.31-.1,.68-.04,1.45-.02,2.31,.1Zm158.03-3.36s-.05,.01-.08,.02c-1.61-.19-1.04-.11,.08-.02Zm3.35,.21c.34-.07,.31-.13,.17-.2-.23,.12-.99,.13-1.84,.1,.24,.13,.67,.21,1.67,.11Zm-359.7,.19l.65,.13c-1.47,.14-1.06,.01-.65-.13Z" style="fill:#4cc2f1;"/></g><g><path d="M6.89,82.3c-1.67-1.33-2.5-3.57-2.5-6.7s1.12-12.62,3.35-28.45c2.23-15.83,3.35-27.13,3.35-33.9s-.47-11.18-1.4-13.25c6.8,.53,11.27,3.2,13.4,8,5.13-2.8,10.78-4.2,16.95-4.2s11.03,1.92,14.6,5.75c3.57,3.83,5.35,8.55,5.35,14.15,0,6.93-2.54,13.2-7.6,18.8-2.47,2.67-5.65,4.83-9.55,6.5-3.9,1.67-8.22,2.5-12.95,2.5-2,0-3.9-.13-5.7-.4l-.3-4.2h.8c7.53,0,13.53-2.8,18-8.4,3.07-4,4.6-8.2,4.6-12.6,0-6.47-2.63-10.73-7.9-12.8-4.47-1.67-9.47-1.33-15,1v.2c0,2.53-.95,10.82-2.85,24.85-1.9,14.03-2.85,25.75-2.85,35.15,0,4.2,.2,7.47,.6,9.8-2.13,.13-3.53,.2-4.2,.2-3.8,0-6.53-.67-8.2-2Z" style="fill:#4cc2f1;"/><path d="M80.19,50.1c2.4-8.67,5.28-15.05,8.65-19.15,3.37-4.1,6.5-6.15,9.4-6.15s5.08,1.08,6.55,3.25c1.47,2.17,2.2,4.97,2.2,8.4s-1.02,6.58-3.05,9.45c-2.03,2.87-4.82,4.3-8.35,4.3-.93,0-2.04-.27-3.3-.8,2.07-2.6,3.1-5.73,3.1-9.4,0-2.13-.87-3.2-2.6-3.2-1.27,0-2.63,.88-4.1,2.65-1.47,1.77-2.87,4.2-4.2,7.3-1.33,3.1-2.45,7.07-3.35,11.9-.9,4.83-1.35,9.98-1.35,15.45,0,.67,.17,3.5,.5,8.5-2.13,.13-3.5,.2-4.1,.2-3.8,0-6.53-.67-8.2-2-1.67-1.33-2.5-3.53-2.5-6.6,0-1.33,.7-6.46,2.1-15.4,1.4-8.93,2.1-16.05,2.1-21.35s-.57-10.18-1.7-14.65c4.87,1.53,8.27,3.72,10.2,6.55,1.93,2.83,2.9,6.13,2.9,9.9s-.3,7.38-.9,10.85Z" style="fill:#4cc2f1;"/><path d="M132.39,23.5c.73,0,1.42,.22,2.05,.65,.63,.43,.95,.88,.95,1.35-6.73,4.67-10.77,11.17-12.1,19.5,1.4-3.73,3.12-6.97,5.15-9.7,2.03-2.73,4.15-4.8,6.35-6.2,4.2-2.6,8.3-3.9,12.3-3.9s7.4,1.97,10.2,5.9c2.8,3.93,4.2,9.2,4.2,15.8,0,7.4-1.5,14.2-4.5,20.4-3.33,7-8.07,11.83-14.2,14.5-3.47,1.53-7.27,2.3-11.4,2.3-5.8,0-10.85-2.25-15.15-6.75s-6.45-10.97-6.45-19.4,2.07-15.87,6.2-22.3c4.13-6.43,9.6-10.48,16.4-12.15Zm8.5,9c-4.33,0-8.2,2.92-11.6,8.75-3.4,5.83-5.1,11.65-5.1,17.45,0,12.13,3.13,18.2,9.4,18.2,4.47,0,8.1-2.92,10.9-8.75,2.8-5.83,4.2-12.38,4.2-19.65,0-10.67-2.6-16-7.8-16Z" style="fill:#4cc2f1;"/><path d="M193.09,82.6c-2.13,.93-4.37,1.4-6.7,1.4s-3.63-.5-3.9-1.5c-1.07-4.27-2.3-10.65-3.7-19.15s-2.82-15.93-4.25-22.3c-1.43-6.37-3.15-11.18-5.15-14.45,3.67-1.73,6.6-2.6,8.8-2.6s3.87,.5,5,1.5c1.13,1,2.08,2.98,2.85,5.95,.77,2.97,1.32,5.42,1.65,7.35,.93,6.4,1.4,9.97,1.4,10.7s.27,3.72,.8,8.95c.53,5.23,.93,8.68,1.2,10.35,11.87-23,17.8-38.37,17.8-46.1,5.47,2.8,8.2,5.83,8.2,9.1,0,2.67-1.38,6.62-4.15,11.85-2.77,5.23-6.23,11.57-10.4,19-4.17,7.43-7.32,14.08-9.45,19.95Z" style="fill:#4cc2f1;"/><path d="M241.34,48.65c-1.63,9.5-2.47,17.33-2.5,23.5-.03,6.17,.35,10.15,1.15,11.95-5.2-1.33-8.7-3.35-10.5-6.05s-2.7-6.65-2.7-11.85c0-2.8,.53-7.87,1.6-15.2,1.07-7.33,1.6-13.03,1.6-17.1s-.2-7.2-.6-9.4c1.47-.13,2.83-.2,4.1-.2,3.87,0,6.55,.65,8.05,1.95,1.5,1.3,2.25,3.52,2.25,6.65,0,1-.82,6.25-2.45,15.75Zm3.95-33.15c-1.87,.27-3.5,.4-4.9,.4-6.87,0-10.3-2.4-10.3-7.2,0-1.73,.5-4.1,1.5-7.1,1.87-.27,3.5-.4,4.9-.4,6.87,0,10.3,2.4,10.3,7.2,0,1.73-.5,4.1-1.5,7.1Z" style="fill:#4cc2f1;"/><path d="M278.59,30.8c-1.87,0-3.53,.62-5,1.85-1.47,1.23-2.2,2.98-2.2,5.25s.78,4.4,2.35,6.4c1.57,2,3.48,3.75,5.75,5.25,2.27,1.5,4.55,3.03,6.85,4.6,2.3,1.57,4.32,3.5,6.05,5.8,1.73,2.3,2.7,4.85,2.9,7.65,0,4.8-2.07,8.83-6.2,12.1-4.13,3.27-9.27,4.9-15.4,4.9s-10.95-1.23-14.45-3.7-5.25-5.3-5.25-8.5,.98-5.78,2.95-7.75,4.42-2.95,7.35-2.95c1.53,0,2.97,.27,4.3,.8-2.13,2.27-3.2,4.67-3.2,7.2s.7,4.57,2.1,6.1c1.4,1.53,3.3,2.3,5.7,2.3s4.42-.62,6.05-1.85c1.63-1.23,2.45-2.83,2.45-4.8s-.57-3.71-1.7-5.25c-1.13-1.53-2.55-2.88-4.25-4.05-1.7-1.17-3.55-2.48-5.55-3.95-2-1.46-3.85-2.95-5.55-4.45-1.7-1.5-3.12-3.4-4.25-5.7-1.13-2.3-1.7-4.82-1.7-7.55,0-4.73,1.95-8.62,5.85-11.65,3.9-3.03,8.77-4.55,14.6-4.55s10.1,1.13,12.8,3.4c2.7,2.27,4.05,4.92,4.05,7.95s-.95,5.62-2.85,7.75c-1.9,2.13-4.35,3.2-7.35,3.2-1.47,0-3.13-.3-5-.9,1.47-1.27,2.6-2.71,3.4-4.35,.8-1.63,1.2-3.25,1.2-4.85s-.67-2.95-2-4.05c-1.33-1.1-2.93-1.65-4.8-1.65Z" style="fill:#4cc2f1;"/><path d="M323.64,48.65c-1.63,9.5-2.47,17.33-2.5,23.5-.03,6.17,.35,10.15,1.15,11.95-5.2-1.33-8.7-3.35-10.5-6.05s-2.7-6.65-2.7-11.85c0-2.8,.53-7.87,1.6-15.2,1.07-7.33,1.6-13.03,1.6-17.1s-.2-7.2-.6-9.4c1.47-.13,2.83-.2,4.1-.2,3.87,0,6.55,.65,8.05,1.95,1.5,1.3,2.25,3.52,2.25,6.65,0,1-.82,6.25-2.45,15.75Zm3.95-33.15c-1.87,.27-3.5,.4-4.9,.4-6.87,0-10.3-2.4-10.3-7.2,0-1.73,.5-4.1,1.5-7.1,1.87-.27,3.5-.4,4.9-.4,6.87,0,10.3,2.4,10.3,7.2,0,1.73-.5,4.1-1.5,7.1Z" style="fill:#4cc2f1;"/><path d="M360.09,23.5c.73,0,1.42,.22,2.05,.65,.63,.43,.95,.88,.95,1.35-6.73,4.67-10.77,11.17-12.1,19.5,1.4-3.73,3.12-6.97,5.15-9.7,2.03-2.73,4.15-4.8,6.35-6.2,4.2-2.6,8.3-3.9,12.3-3.9s7.4,1.97,10.2,5.9c2.8,3.93,4.2,9.2,4.2,15.8,0,7.4-1.5,14.2-4.5,20.4-3.34,7-8.07,11.83-14.2,14.5-3.47,1.53-7.27,2.3-11.4,2.3-5.8,0-10.85-2.25-15.15-6.75-4.3-4.5-6.45-10.97-6.45-19.4s2.07-15.87,6.2-22.3c4.13-6.43,9.6-10.48,16.4-12.15Zm8.5,9c-4.33,0-8.2,2.92-11.6,8.75-3.4,5.83-5.1,11.65-5.1,17.45,0,12.13,3.13,18.2,9.4,18.2,4.47,0,8.1-2.92,10.9-8.75,2.8-5.83,4.2-12.38,4.2-19.65,0-10.67-2.6-16-7.8-16Z" style="fill:#4cc2f1;"/><path d="M438.99,24.9c3.93,0,6.75,1.22,8.45,3.65,1.7,2.43,2.55,5.73,2.55,9.9s-.65,10.02-1.95,17.55c-1.3,7.53-1.95,13.02-1.95,16.45s.43,5.93,1.3,7.5c.87,1.57,2.33,2.92,4.4,4.05-.87,.07-2.1,.1-3.7,.1-5.6,0-9.58-.87-11.95-2.6-2.37-1.73-3.55-4.77-3.55-9.1,0-2.47,.67-7.28,2-14.45,1.33-7.17,2-12.52,2-16.05,0-4.4-1.33-6.6-4-6.6-1.54,0-3.27,.9-5.2,2.7-1.93,1.8-3.8,4.25-5.6,7.35-1.8,3.1-3.3,7.08-4.5,11.95-1.2,4.87-1.8,9.72-1.8,14.55s.2,8.42,.6,10.75c-2.13,.13-3.54,.2-4.2,.2-3.8,0-6.53-.67-8.2-2-1.67-1.33-2.5-3.53-2.5-6.6,0-1.33,.7-6.46,2.1-15.4,1.4-8.93,2.1-16.05,2.1-21.35s-.57-10.18-1.7-14.65c4.87,1.53,8.27,3.72,10.2,6.55,1.93,2.83,2.9,6.07,2.9,9.7s-.4,7.85-1.2,12.65c2.2-7.46,5.67-13.8,10.4-19,4.73-5.2,9.07-7.8,13-7.8Z" style="fill:#4cc2f1;"/><path d="M480.04,48.65c-1.63,9.5-2.47,17.33-2.5,23.5-.03,6.17,.35,10.15,1.15,11.95-5.2-1.33-8.7-3.35-10.5-6.05-1.8-2.7-2.7-6.65-2.7-11.85,0-2.8,.53-7.87,1.6-15.2,1.07-7.33,1.6-13.03,1.6-17.1s-.2-7.2-.6-9.4c1.47-.13,2.83-.2,4.1-.2,3.87,0,6.55,.65,8.05,1.95,1.5,1.3,2.25,3.52,2.25,6.65,0,1-.82,6.25-2.45,15.75Zm3.95-33.15c-1.87,.27-3.5,.4-4.9,.4-6.87,0-10.3-2.4-10.3-7.2,0-1.73,.5-4.1,1.5-7.1,1.87-.27,3.5-.4,4.9-.4,6.87,0,10.3,2.4,10.3,7.2,0,1.73-.5,4.1-1.5,7.1Z" style="fill:#4cc2f1;"/><path d="M533.69,24.9c3.93,0,6.75,1.22,8.45,3.65,1.7,2.43,2.55,5.73,2.55,9.9s-.65,10.02-1.95,17.55c-1.3,7.53-1.95,13.02-1.95,16.45s.43,5.93,1.3,7.5c.87,1.57,2.33,2.92,4.4,4.05-.87,.07-2.1,.1-3.7,.1-5.6,0-9.58-.87-11.95-2.6-2.37-1.73-3.55-4.77-3.55-9.1,0-2.47,.67-7.28,2-14.45,1.33-7.17,2-12.52,2-16.05,0-4.4-1.33-6.6-4-6.6-1.54,0-3.27,.9-5.2,2.7-1.93,1.8-3.8,4.25-5.6,7.35-1.8,3.1-3.3,7.08-4.5,11.95-1.2,4.87-1.8,9.72-1.8,14.55s.2,8.42,.6,10.75c-2.13,.13-3.54,.2-4.2,.2-3.8,0-6.53-.67-8.2-2-1.67-1.33-2.5-3.53-2.5-6.6,0-1.33,.7-6.46,2.1-15.4,1.4-8.93,2.1-16.05,2.1-21.35s-.57-10.18-1.7-14.65c4.87,1.53,8.27,3.72,10.2,6.55,1.93,2.83,2.9,6.07,2.9,9.7s-.4,7.85-1.2,12.65c2.2-7.46,5.67-13.8,10.4-19,4.73-5.2,9.07-7.8,13-7.8Z" style="fill:#4cc2f1;"/><path d="M588.59,25.9c1.27,0,2.23,.07,2.9,.2,1.73,.33,2.67,1.57,2.8,3.7-7.27,1.53-13.05,5.47-17.35,11.8-4.3,6.33-6.45,12.93-6.45,19.8,0,8,2.43,12,7.3,12,2.93,0,5.57-1.6,7.9-4.8,4.13-5.8,7.03-14.87,8.7-27.2,1.27-9.73,3.63-16,7.1-18.8,1.67-1.4,3.73-2.5,6.2-3.3-.93,7.07-1.4,12.4-1.4,16,0,26.67-2.85,46.7-8.55,60.1-5.7,13.4-14.98,20.1-27.85,20.1-5.2,0-9.27-1.38-12.2-4.15-2.93-2.77-4.4-5.8-4.4-9.1s1.03-6,3.1-8.1c2.07-2.1,4.96-3.15,8.7-3.15,1.53,0,3.1,.33,4.7,1-2.53,1.67-3.8,4.23-3.8,7.7,0,2.33,.7,4.35,2.1,6.05,1.4,1.7,3.12,2.55,5.15,2.55s3.82-.42,5.35-1.25c1.53-.83,3.1-2.32,4.7-4.45,1.6-2.13,3-4.8,4.2-8,2.73-7.4,4.23-17.5,4.5-30.3-1.93,5.73-4.87,10.33-8.8,13.8-3.93,3.47-7.93,5.2-12,5.2-5.13,0-9.05-1.95-11.75-5.85-2.7-3.9-4.05-8.88-4.05-14.95,0-7.07,1.7-13.63,5.1-19.7,3.8-7,9.07-11.87,15.8-14.6,3.73-1.53,7.83-2.3,12.3-2.3Z" style="fill:#4cc2f1;"/></g></g></g> + </g> + <text x="1002" y="38" text-anchor="middle" fill="#666666" font-family="'IBM Plex Mono',monospace" font-size="10" letter-spacing="3">CONTROL PLANE ARCHITECTURE</text> + <line x1="872" y1="46" x2="1132" y2="46" stroke="#ccc" stroke-width="0.8"/> + + <!-- ═══ CLI ═══ --> + <g> + <rect x="235" y="104" width="140" height="68" rx="8" fill="url(#cli-grad)" filter="url(#soft-shadow)"/> + <rect x="235" y="104" width="140" height="68" rx="8" fill="none" stroke="#c5d5e5" stroke-width="1"/> + <rect x="235" y="107" width="3" height="62" rx="1.5" fill="#4a9eff" opacity="0.6"/> + <circle cx="256" cy="118" r="7" fill="#e2e8f0" stroke="#4a9eff" stroke-width="1" opacity="0.8"/> + <text x="256" y="121" text-anchor="middle" fill="#4a9eff" font-size="8" font-family="'IBM Plex Mono',monospace">$</text> + <text x="265" y="123" fill="#4a9eff" font-family="'DM Sans',sans-serif" font-weight="600" font-size="11">CLI</text> + <text x="270" y="148" fill="#6b8aaa" font-family="'IBM Plex Mono',monospace" font-size="8">provisioning</text> + </g> + + <!-- Arrow: CLI → Orchestrator --> + <line x1="640" y1="98" x2="640" y2="115" stroke="#4a9eff" stroke-width="1.5" stroke-dasharray="5 3" class="spoke" opacity="0.7" marker-end="url(#arr-blue)"/> + + + <!-- ═══════════════════════════════════════ --> + <!-- NATS ORBITAL RING SYSTEM --> + <!-- ═══════════════════════════════════════ --> + + <!-- Central area fill --> + <circle cx="640" cy="320" r="133" fill="#e8ecf0" opacity="0.55"/> + + <!-- Ring outer glow --> + <circle cx="640" cy="320" r="138" fill="none" stroke="#94a3b8" stroke-width="10" opacity="0.04" class="ring-glow" filter="url(#ring-glow-f)"/> + + <!-- Main orbit ring (flowing dashes) --> + <circle cx="640" cy="320" r="135" fill="none" stroke="#94a3b8" stroke-width="2" stroke-dasharray="10 8" class="orbit-flow" opacity="0.45"/> + + <!-- ═══ INFINITY ENGINE (∞ dual-wheel perpetual motion from logo) ═══ --> + <!-- Centered at (640,310), scale 0.55 from original logo coordinates --> + <!-- Left wheel center: (115.86, 113.25) | Right wheel center: (261.91, 113.28) --> + <g transform="translate(640, 310) scale(0.55) translate(-188.88, -113.27)" opacity="0.85"> + + <!-- Background circle with CLI gradient --> + <rect x="100" y="40" width="178" height="146" rx="12" style="fill:url(#cli-grad); opacity:0.35;"/> + <circle cx="188.88" cy="113.27" r="50" style="fill:url(#cli-grad); opacity:0.3;"/> + + <!-- Left wheel — clockwise 8s (outline only) --> + <g> + <path d="M3.04,87.53c-3.66,16.4-3.36,33.43,.85,49.7l18.49-.31c2.12,7.08,5.09,13.89,8.85,20.26l-12.81,13.12c9.22,14.08,21.56,25.9,36.09,34.55l12.81-13.13c6.56,3.51,13.52,6.22,20.74,8.08l.31,18.3c16.56,3.62,33.75,3.33,50.18-.85l-.31-18.3c7.15-2.1,14.02-5.04,20.46-8.77l13.25,12.69c14.22-9.14,26.14-21.36,34.87-35.75l-13.25-12.68c3.54-6.5,6.28-13.4,8.15-20.55l18.49-.31c3.65-16.4,3.36-33.43-.85-49.7l-18.49,.31c-2.12-7.08-5.09-13.88-8.85-20.26l12.8-13.12c-9.22-14.08-21.56-25.9-36.08-34.55l-12.81,13.13c-6.56-3.51-13.53-6.23-20.75-8.08l-.31-18.3c-16.56-3.62-33.75-3.33-50.18,.85l.31,18.3c-7.15,2.1-14.01,5.04-20.45,8.77l-13.25-12.69c-14.22,9.14-26.15,21.36-34.88,35.75l13.25,12.68c-3.54,6.5-6.28,13.4-8.15,20.55l-18.49,.31Z" style="fill:#ffffff; fill-rule:evenodd; stroke:#4cc2f1; stroke-miterlimit:10; stroke-width:1.2; opacity:0.85;"> + <animateTransform attributeName="transform" type="rotate" values="0 115.86 113.25;360 115.86 113.25" dur="8s" repeatCount="indefinite"/> + </path> + </g> + + <!-- Right wheel — counter-clockwise 6s (outline only) --> + <g> + <path d="M167.28,92.87c-3.19,14.31-2.94,29.17,.74,43.37l16.13-.27c1.85,6.18,4.44,12.12,7.72,17.68l-11.18,11.45c8.05,12.29,18.82,22.6,31.49,30.15l11.18-11.46c5.72,3.06,11.8,5.43,18.1,7.05l.27,15.97c14.45,3.16,29.45,2.91,43.78-.74l-.27-15.97c6.24-1.83,12.23-4.4,17.85-7.66l11.56,11.08c12.41-7.97,22.81-18.64,30.43-31.19l-11.56-11.06c3.09-5.67,5.48-11.69,7.11-17.93l16.13-.27c3.18-14.31,2.93-29.17-.74-43.37l-16.13,.27c-1.85-6.18-4.44-12.11-7.72-17.68l11.17-11.44c-8.04-12.29-18.81-22.6-31.48-30.15l-11.18,11.46c-5.72-3.07-11.8-5.43-18.1-7.05l-.27-15.97c-14.45-3.16-29.45-2.91-43.78,.74l.27,15.97c-6.24,1.83-12.23,4.4-17.84,7.66l-11.56-11.08c-12.41,7.97-22.81,18.64-30.44,31.19l11.56,11.06c-3.09,5.67-5.48,11.69-7.11,17.93l-16.13,.27Z" style="fill:#ffffff; fill-rule:evenodd; stroke:#4cc2f1; stroke-miterlimit:10; stroke-width:1.2; opacity:0.85;"> + <animateTransform attributeName="transform" type="rotate" values="0 261.91 113.28;-360 261.91 113.28" dur="6s" repeatCount="indefinite"/> + </path> + </g> + + <!-- Left wheel golden center (strong original colors) --> + <path d="M115.86,78.19c19.55,0,35.4,15.7,35.4,35.06s-15.85,35.06-35.4,35.06-35.4-15.7-35.4-35.06,15.85-35.06,35.4-35.06" style="fill:#f2b03f; opacity:0.9;"/> + <ellipse cx="116.42" cy="115.68" rx="20.17" ry="20.15" style="fill:none; stroke:#4159a4; stroke-miterlimit:10; stroke-width:1.58px; opacity:0.8;"/> + <path d="M116.42,107.88c4.35-.04,7.91,3.43,7.95,7.73,.04,4.31-3.46,7.83-7.81,7.87-4.35,.04-7.91-3.43-7.95-7.73,0-.02,0-.05,0-.07-.02-4.29,3.48-7.78,7.81-7.8" style="fill:#fff; opacity:0.8;"/> + + <!-- Right wheel golden center (strong original colors) --> + <ellipse cx="261.91" cy="113.28" rx="35.65" ry="35.31" style="fill:#f9b224; opacity:0.9;"/> + + <!-- Right wheel — dashboard display (simplified from logo chronometer) --> + <circle cx="261.91" cy="113.28" r="26" style="fill:#3d5070; opacity:0.85;"/> + <!-- Window controls (3 dots) --> + <circle cx="243" cy="95" r="1.2" style="fill:#d68a87; opacity:0.6;"/> + <circle cx="247" cy="95" r="1.2" style="fill:#c9a558; opacity:0.6;"/> + <circle cx="251" cy="95" r="1.2" style="fill:#5a7093; opacity:0.6;"/> + <!-- Terminal header bar --> + <rect x="243" y="99" width="38" height="3.2" rx="1.5" style="fill:#6a7a98; opacity:0.5;"/> + <!-- Status bars (staggered widths = data readout) --> + <rect x="243" y="105" width="28" height="2.8" rx="1" style="fill:#7a8aaa; opacity:0.5;"/> + <rect x="243" y="110" width="34" height="2.8" rx="1" style="fill:#a89870; opacity:0.5;"/> + <rect x="243" y="115" width="20" height="2.8" rx="1" style="fill:#6a7a8a; opacity:0.4;"/> + <rect x="243" y="120" width="30" height="2.8" rx="1" style="fill:#7a8aaa; opacity:0.4;"/> + <!-- Green health indicator --> + <circle cx="271" cy="127" r="5.5" style="fill:#4a9e8f; opacity:0.7;"> + <animate attributeName="opacity" values="0.7;0.4;0.7" dur="2s" repeatCount="indefinite"/> + </circle> + <!-- Animated checkmark --> + <path d="M268,126.5l2.5,2.5,4.2-4.2" style="fill:none; stroke:#a0c0b0; stroke-width:1.8; stroke-linecap:round; stroke-linejoin:round; opacity:0.8;"> + <animate attributeName="stroke-dasharray" values="0 15;15 15" dur="1.5s" repeatCount="indefinite"/> + </path> + + <!-- ∞ path segments — staggered color cycling --> + + <!-- Top left of ∞ --> + <path d="M225.8,83.92c1.22-1.35,2.46-2.66,3.75-3.94,16.69-16.54,43.22-18.12,61.79-3.68l17.46-2.65,2.57-16.63c-29.5-25.42-73.83-23.89-101.47,3.5-1.24,1.23-2.45,2.49-3.63,3.77l16.89,2.56,2.64,17.07Z" style="stroke:#5a7a90; stroke-miterlimit:10;"> + <animate attributeName="fill" values="#5e74b7;#00D4FF;#7B2BFF;#00FFB3;#5e74b7" dur="2s" repeatCount="indefinite"/> + <animate attributeName="stroke-width" values="0.5;2;0.5" dur="2s" repeatCount="indefinite"/> + </path> + + <!-- Bottom left of ∞ --> + <path d="M78.59,141.18c-14.54-18.4-12.95-44.65,3.71-61.2,1.33-1.31,2.73-2.54,4.2-3.68l-17.46-2.65-2.57-16.63c-1.3,1.13-2.58,2.28-3.82,3.5-27.79,27.53-28.96,71.57-3.53,100.51l16.79-2.55,2.67-17.3Z" style="stroke:#5a7a90; stroke-miterlimit:10;"> + <animate attributeName="fill" values="#5e74b7;#7B2BFF;#00FFB3;#00D4FF;#5e74b7" dur="2s" begin="0.5s" repeatCount="indefinite"/> + <animate attributeName="stroke-width" values="0.5;2;0.5" dur="2s" begin="0.5s" repeatCount="indefinite"/> + </path> + + <!-- Top right of ∞ --> + <path d="M86.5,76.3c18.57-14.44,45.1-12.86,61.79,3.68,1.29,1.27,2.53,2.6,3.75,3.94l2.64-17.07,16.89-2.56c-1.18-1.28-2.39-2.54-3.63-3.77-27.64-27.39-71.97-28.92-101.48-3.5l2.57,16.63,17.46,2.65Z" style="stroke:#5a7a90; stroke-miterlimit:10;"> + <animate attributeName="fill" values="#8d9ccf;#00FFB3;#00D4FF;#7B2BFF;#8d9ccf" dur="2s" begin="1s" repeatCount="indefinite"/> + <animate attributeName="stroke-width" values="0.5;2;0.5" dur="2s" begin="1s" repeatCount="indefinite"/> + </path> + + <!-- Bottom right of ∞ --> + <path d="M299.26,141.18c-15.88,20.08-45.19,23.61-65.46,7.88l-.04-.03-17.46,2.65-2.57,16.63c29.5,25.42,73.83,23.89,101.47-3.5,1.24-1.22,2.4-2.49,3.53-3.78l-16.79-2.55-2.68-17.3Z" style="stroke:#5a7a90; stroke-miterlimit:10;"> + <animate attributeName="fill" values="#8d9ccf;#7B2BFF;#00D4FF;#00FFB3;#8d9ccf" dur="2s" begin="1.5s" repeatCount="indefinite"/> + <animate attributeName="stroke-width" values="0.5;2;0.5" dur="2s" begin="1.5s" repeatCount="indefinite"/> + </path> + + <!-- ∞ connector arcs (static deep blue) --> + <path d="M315.19,60.52c-1.23-1.22-2.51-2.38-3.82-3.5l-2.57,16.63-17.46,2.65c1.47,1.14,2.88,2.37,4.2,3.68,16.66,16.55,18.25,42.8,3.71,61.2l2.68,17.3,16.79,2.55c25.42-28.93,24.26-72.98-3.53-100.5m-171.1,88.5c-20.2,15.76-49.48,12.31-65.39-7.7-.04-.05-.08-.1-.12-.15l-2.68,17.3-16.79,2.55c1.13,1.29,2.3,2.56,3.53,3.78,27.64,27.39,71.97,28.92,101.47,3.5l-2.57-16.63-17.46-2.65Z" style="fill:#455aa5; stroke:#3a5a7a; stroke-miterlimit:10;"/> + + <!-- ∞ transition pieces --> + <path d="M204.38,103.42c1.09,1.79,2.17,3.56,3.25,5.32,5.65-8.96,11.44-17.4,18.17-24.81l-2.64-17.07-16.89-2.56c-5.38,5.87-10.34,12.11-14.83,18.68,4.58,6.77,8.8,13.66,12.94,20.45m-30.92,18.5c-1.09-1.79-2.18-3.56-3.25-5.32-6.68,10.59-13.53,20.44-21.92,28.75-1.33,1.31-2.73,2.54-4.21,3.68l17.46,2.65,2.57,16.63c1.3-1.12,2.58-2.28,3.82-3.5,6.84-6.9,13.02-14.41,18.46-22.43-4.57-6.76-8.78-13.66-12.93-20.46" style="fill:#8d9ccf; stroke:#5a7a90; stroke-miterlimit:10;"/> + + <!-- Central flow — animated infinity gradient --> + <path d="M171.57,64.29c11.77,12.66,20.64,27.17,29.24,41.26,9,14.74,17.5,28.67,28.74,39.8,1.33,1.31,2.73,2.54,4.21,3.68l-17.46,2.65-2.57,16.63c-1.3-1.12-2.58-2.27-3.82-3.5-13.63-13.5-23.41-29.52-32.87-45.02-7.97-13.05-15.55-25.46-24.99-35.86l2.64-17.07,16.89-2.56Z" style="stroke:#5a7a90; stroke-miterlimit:10;"> + <animate attributeName="fill" values="#00D4FF;#7B2BFF;#00FFB3;#00D4FF" dur="3s" repeatCount="indefinite"/> + <animate attributeName="stroke-width" values="0.5;3;0.5" dur="3s" repeatCount="indefinite"/> + <animate attributeName="opacity" values="0.8;1;0.8" dur="3s" repeatCount="indefinite"/> + </path> + + </g> + + <!-- Hub labels (below infinity engine) --> + <text x="640" y="403" text-anchor="middle" fill="#7a8a9e" font-family="'DM Sans',sans-serif" font-weight="700" font-size="15" letter-spacing="1.2">Events Stream</text> + <text x="640" y="429" text-anchor="middle" fill="#666666" font-family="'IBM Plex Mono',monospace" font-size="7">NATS JetStream</text> + <text x="640" y="440" text-anchor="middle" fill="#666666" font-family="'IBM Plex Mono',monospace" font-size="7">:4222</text> + + + <!-- ═══════════════════════════════════════ --> + <!-- SATELLITE SERVICES --> + <!-- ═══════════════════════════════════════ --> + + <!-- ─── ORCHESTRATOR (top, 0°) ─── --> + <g> + <rect x="505" y="60" width="270" height="110" rx="14" fill="none" stroke="#f59e0b" stroke-width="1.5" stroke-dasharray="8 4" class="solid-border"/> + <rect x="515" y="70" width="250" height="90" rx="10" fill="url(#orch-grad)" filter="url(#soft-shadow)"/> + <rect x="515" y="70" width="250" height="90" rx="10" fill="none" stroke="#e0d5c5" stroke-width="1"/> + <rect x="515" y="73" width="3" height="84" rx="1.5" fill="#f59e0b" opacity="0.5"/> + <circle cx="535" cy="92" r="8" fill="#e2e8f0" stroke="#f59e0b" stroke-width="1" opacity="0.8"/> + <text x="535" y="96" text-anchor="middle" fill="#f59e0b" font-size="9">▶</text> + <text x="553" y="95" fill="#f59e0b" font-family="'DM Sans',sans-serif" font-weight="700" font-size="13" letter-spacing="0.5">Orchestrator</text> + <text x="740" y="95" text-anchor="end" fill="#f59e0b" font-family="'IBM Plex Mono',monospace" font-size="8" opacity="0.5">:9011</text> + <circle cx="752" cy="92" r="3.5" fill="#f59e0b" class="health"/> + <g font-family="'IBM Plex Mono',monospace" font-size="8" fill="#555555"> + <rect x="528" y="113" width="220" height="14" rx="4" fill="#fef3db" stroke="#e8d5a0" stroke-width="0.5"/> + <text x="638" y="123" text-anchor="middle" opacity="0.6">Task State Machine • Provider API • SSH</text> + <rect x="528" y="137" width="220" height="14" rx="4" fill="#fef3db" stroke="#e8d5a0" stroke-width="0.5"/> + <text x="638" y="147" text-anchor="middle" opacity="0.55">Webhooks • Rollback • Audit Collector</text> + </g> + </g> + + <!-- ─── CONFIGURATION (top-right, cylinder) ─── --> + <g> + <ellipse cx="990" cy="152" rx="70" ry="11" fill="#10b981" opacity="0.12" stroke="none"/> + <path d="M 920 152 Q 990 178 1060 152" stroke="#10b981" stroke-width="0.8" fill="none" stroke-dasharray="4 2" opacity="0.3"/> + <rect x="920" y="91" width="140" height="61" rx="3" fill="#10b981" stroke="none" opacity="0.06" filter="url(#soft-shadow)"/> + <ellipse cx="990" cy="91" rx="70" ry="13" fill="#10b981" opacity="0.35" stroke="none"/> + <text x="990" y="126" text-anchor="middle" fill="#10b981" font-family="'DM Sans',sans-serif" font-weight="700" font-size="12" letter-spacing="0.5">Configuration</text> + <text x="990" y="152" text-anchor="middle" fill="#0a7a52" font-family="'IBM Plex Mono',monospace" font-size="7">Nickel • ∞ TypeDialog</text> + </g> + + <!-- ─── CONTROL CENTER (72°, upper-right) ─── --> + <g> + <rect x="241" y="248" width="250" height="110" rx="10" fill="url(#ctrl-grad)" filter="url(#soft-shadow)"/> + <rect x="241" y="248" width="250" height="110" rx="10" fill="none" stroke="#d5d0e8" stroke-width="1"/> + <rect x="241" y="251" width="3" height="104" rx="1.5" fill="#818cf8" opacity="0.5"/> + <circle cx="263" cy="270" r="8" fill="#e2e8f0" stroke="#818cf8" stroke-width="1" opacity="0.8"/> + <text x="263" y="274" text-anchor="middle" fill="#818cf8" font-size="9">◯</text> + <text x="279" y="273" fill="#818cf8" font-family="'DM Sans',sans-serif" font-weight="700" font-size="13" letter-spacing="0.5">Control Center</text> + <text x="461" y="273" text-anchor="end" fill="#818cf8" font-family="'IBM Plex Mono',monospace" font-size="8" opacity="0.5">:9012</text> + <circle cx="473" cy="270" r="3.5" fill="#818cf8" class="health health-d1"/> + <g font-family="'IBM Plex Mono',monospace" font-size="8" fill="#4a4870"> + <rect x="254" y="301" width="220" height="14" rx="4" fill="#eae5f8" stroke="#c8c0e8" stroke-width="0.5"/> + <text x="364" y="311" text-anchor="middle" opacity="0.9">Cedar Policies • JWT • Sessions</text> + <rect x="254" y="325" width="220" height="14" rx="4" fill="#eae5f8" stroke="#c8c0e8" stroke-width="0.5"/> + <text x="364" y="335" text-anchor="middle" opacity="0.85">WebSocket • RBAC • Solo: auto-session</text> + </g> + </g> + + <!-- ─── EXTENSION REGISTRY (144°, lower-right) ─── --> + <g> + <rect x="732" y="430" width="250" height="110" rx="10" fill="url(#ext-grad)" filter="url(#soft-shadow)"/> + <rect x="732" y="430" width="250" height="110" rx="10" fill="none" stroke="#d4bab2" stroke-width="1"/> + <rect x="978" y="433" width="3" height="104" rx="1.5" fill="#ef4444" opacity="0.5"/> + <circle cx="754" cy="452" r="8" fill="#e2e8f0" stroke="#ef4444" stroke-width="1" opacity="0.8"/> + <text x="754" y="456" text-anchor="middle" fill="#ef4444" font-size="9">⚙</text> + <text x="770" y="455" fill="#ef4444" font-family="'DM Sans',sans-serif" font-weight="700" font-size="12" letter-spacing="0.5">Extensions Registry</text> + <text x="952" y="455" text-anchor="end" fill="#ef4444" font-family="'IBM Plex Mono',monospace" font-size="8" opacity="0.5">:8084</text> + <circle cx="964" cy="452" r="3.5" fill="#ef4444" class="health health-d3"/> + <g font-family="'IBM Plex Mono',monospace" font-size="8" fill="#7a4868"> + <rect x="745" y="483" width="220" height="14" rx="4" fill="#eee1db" stroke="#d4bab2" stroke-width="0.5"/> + <text x="855" y="493" text-anchor="middle" opacity="0.9">Git • OCI • LRU</text> + <rect x="745" y="507" width="220" height="14" rx="4" fill="#eee1db" stroke="#d4bab2" stroke-width="0.5"/> + <text x="855" y="517" text-anchor="middle" opacity="0.85">Providers • Taskservs • vault:// creds</text> + </g> + </g> + + <!-- ─── AI/MCP (216°, lower-left) ─── --> + <g> + <rect x="298" y="430" width="250" height="110" rx="10" fill="url(#mcp-grad)" filter="url(#soft-shadow)"/> + <rect x="298" y="430" width="250" height="110" rx="10" fill="none" stroke="#c0e0e8" stroke-width="1"/> + <rect x="298" y="433" width="3" height="104" rx="1.5" fill="#22d3ee" opacity="0.5"/> + <circle cx="320" cy="452" r="8" fill="#e2e8f0" stroke="#22d3ee" stroke-width="1" opacity="0.8"/> + <text x="320" y="456" text-anchor="middle" fill="#22d3ee" font-size="9">🧠</text> + <text x="336" y="455" fill="#22d3ee" font-family="'DM Sans',sans-serif" font-weight="700" font-size="12" letter-spacing="0.5">AI • MCP</text> + <text x="523" y="455" text-anchor="end" fill="#22d3ee" font-family="'IBM Plex Mono',monospace" font-size="8" opacity="0.5">:9082</text> + <circle cx="535" cy="452" r="3.5" fill="#22d3ee" class="health health-d2"/> + <g font-family="'IBM Plex Mono',monospace" font-size="8" fill="#2a5050"> + <rect x="311" y="483" width="220" height="14" rx="4" fill="#ddf2f7" stroke="#b0dce8" stroke-width="0.5"/> + <text x="421" y="493" text-anchor="middle" opacity="0.9">RAG Engine • MCP Server • Tools</text> + <rect x="311" y="507" width="220" height="14" rx="4" fill="#ddf2f7" stroke="#b0dce8" stroke-width="0.5"/> + <text x="421" y="517" text-anchor="middle" opacity="0.85">Embeddings • Model Routing • KGraph</text> + </g> + </g> + + <!-- ─── VAULT SERVICE (288°, upper-left) ─── --> + <g> + <rect x="789" y="248" width="250" height="110" rx="10" fill="url(#vault-grad)" filter="url(#soft-shadow)"/> + <rect x="789" y="248" width="250" height="110" rx="10" fill="none" stroke="#f0c8d8" stroke-width="1"/> + <rect x="1035" y="251" width="3" height="104" rx="1.5" fill="#ec4899" opacity="0.5"/> + <circle cx="811" cy="270" r="8" fill="#e2e8f0" stroke="#ec4899" stroke-width="1" opacity="0.8"/> + <text x="811" y="274" text-anchor="middle" fill="#ec4899" font-size="9">🛡</text> + <text x="827" y="273" fill="#ec4899" font-family="'DM Sans',sans-serif" font-weight="700" font-size="13" letter-spacing="0.5">Vault Service</text> + <text x="1009" y="273" text-anchor="end" fill="#ec4899" font-family="'IBM Plex Mono',monospace" font-size="8" opacity="0.5">:9094</text> + <circle cx="1021" cy="270" r="3.5" fill="#ec4899" class="health health-d4"/> + <g font-family="'IBM Plex Mono',monospace" font-size="8" fill="#7a4870"> + <rect x="802" y="301" width="220" height="14" rx="4" fill="#fef0f5" stroke="#f0c8d8" stroke-width="0.5"/> + <text x="912" y="311" text-anchor="middle" opacity="0.9">Lease Lifecycle • Key Management</text> + <rect x="802" y="325" width="220" height="14" rx="4" fill="#fef0f5" stroke="#f0c8d8" stroke-width="0.5"/> + <text x="912" y="335" text-anchor="middle" opacity="0.85">SOPS • Age • Secrets never in NATS</text> + </g> + </g> + + + <!-- ═══ VERTICAL ARROW: CONFIGURATION → VAULT ═══ --> + <line x1="995" y1="169" x2="995" y2="248" stroke="#10b981" stroke-width="1.5" stroke-dasharray="4 3" opacity="0.35" marker-end="url(#arr-green-sm)"/> + + <!-- ═══ VERTICAL ARROW: CLI → CONTROL CENTER ═══ --> + <line x1="305" y1="177" x2="305" y2="248" stroke="#10b981" stroke-width="1.5" stroke-dasharray="4 3" opacity="0.35" marker-end="url(#arr-green-sm)"/> + + <!-- ═══ CLI → ORCHESTRATOR CURVE ═══ --> + <path d="M 375 138 C 430 100 480 80 505 120" fill="none" stroke="#fbbf24" stroke-width="1.2" stroke-dasharray="3 2" opacity="0.5"/> + + <!-- ═══ CONFIGURATION → ORCHESTRATOR CURVE ═══ --> + <path d="M 920 128 C 850 90 800 70 775 120" fill="none" stroke="#fbbf24" stroke-width="1.2" stroke-dasharray="3 2" opacity="0.5"/> + + <!-- ═══ CLI → AI CURVE ═══ --> + <path d="M 235 138 C 150 250 200 350 300 444" fill="none" stroke="#fbbf24" stroke-width="1.2" stroke-dasharray="3 2" opacity="0.5"/> + + <!-- ═══ CONFIGURATION → EXT CURVE ═══ --> + <path d="M 1060 128 C 1145 240 1095 340 979 444" fill="none" stroke="#fbbf24" stroke-width="1.2" stroke-dasharray="3 2" opacity="0.5"/> + + <!-- ═══ HTTPS CREDENTIAL BEAM ═══ --> + <path d="M 505 168 C 450 187 420 217 447 245" fill="none" stroke="#4a9eff" stroke-width="2" class="https-beam" filter="url(#glow-blue)"/> + <path d="M 505 168 C 450 187 420 217 447 245" fill="none" stroke="#4a9eff" stroke-width="1" stroke-dasharray="4 3" class="https-beam" opacity="0.4"/> + <path d="M 447 245 C 420 217 450 187 505 168" fill="none" stroke="#4a9eff" stroke-width="2" class="https-beam" filter="url(#glow-blue)"/> + <path d="M 447 245 C 420 217 450 187 505 168" fill="none" stroke="#4a9eff" stroke-width="1" stroke-dasharray="4 3" class="https-beam" opacity="0.4"/> + <text x="475" y="209" fill="#4a9eff" font-family="'IBM Plex Mono',monospace" font-size="7" font-weight="700" class="https-label">HTTPS</text> + <path d="M 775 168 C 830 187 860 217 833 245" fill="none" stroke="#4a9eff" stroke-width="2" class="https-beam" filter="url(#glow-blue)"/> + <path d="M 775 168 C 830 187 860 217 833 245" fill="none" stroke="#4a9eff" stroke-width="1" stroke-dasharray="4 3" class="https-beam" opacity="0.4"/> + <path d="M 833 245 C 860 217 830 187 775 168" fill="none" stroke="#4a9eff" stroke-width="2" class="https-beam" filter="url(#glow-blue)"/> + <path d="M 833 245 C 860 217 830 187 775 168" fill="none" stroke="#4a9eff" stroke-width="1" stroke-dasharray="4 3" class="https-beam" opacity="0.4"/> + <text x="803" y="209" fill="#4a9eff" font-family="'IBM Plex Mono',monospace" font-size="7" font-weight="700" class="https-label">HTTPS</text> + + + <!-- ═══ ORBITAL PARTICLES ═══ --> + + <circle r="3.5" fill="#f59e0b" opacity="0" filter="url(#glow-orange)"> + <animateMotion dur="8s" begin="0s" repeatCount="indefinite"> + <mpath href="#orbit-cw"/> + </animateMotion> + <animate attributeName="opacity" values="0;0.9;0.9;0" keyTimes="0;0.05;0.92;1" dur="8s" repeatCount="indefinite"/> + </circle> + + <circle r="2.5" fill="#ec4899" opacity="0" filter="url(#glow-pink)"> + <animateMotion dur="10s" begin="2s" repeatCount="indefinite"> + <mpath href="#orbit-cw"/> + </animateMotion> + <animate attributeName="opacity" values="0;0.8;0.8;0" keyTimes="0;0.05;0.92;1" dur="10s" begin="2s" repeatCount="indefinite"/> + </circle> + + <circle r="3" fill="#4a9eff" opacity="0" filter="url(#glow-blue)"> + <animateMotion dur="7s" begin="1s" repeatCount="indefinite"> + <mpath href="#orbit-ccw"/> + </animateMotion> + <animate attributeName="opacity" values="0;0.85;0.85;0" keyTimes="0;0.05;0.92;1" dur="7s" begin="1s" repeatCount="indefinite"/> + </circle> + + <circle r="2.5" fill="#818cf8" opacity="0" filter="url(#glow-purple)"> + <animateMotion dur="9s" begin="4s" repeatCount="indefinite"> + <mpath href="#orbit-cw"/> + </animateMotion> + <animate attributeName="opacity" values="0;0.7;0.7;0" keyTimes="0;0.05;0.92;1" dur="9s" begin="4s" repeatCount="indefinite"/> + </circle> + + <circle r="3" fill="#22d3ee" opacity="0" filter="url(#glow-cyan)"> + <animateMotion dur="11s" begin="3s" repeatCount="indefinite"> + <mpath href="#orbit-ccw"/> + </animateMotion> + <animate attributeName="opacity" values="0;0.75;0.75;0" keyTimes="0;0.05;0.92;1" dur="11s" begin="3s" repeatCount="indefinite"/> + </circle> + + <circle r="2" fill="#ef4444" opacity="0" filter="url(#glow-orange)"> + <animateMotion dur="12s" begin="5s" repeatCount="indefinite"> + <mpath href="#orbit-cw"/> + </animateMotion> + <animate attributeName="opacity" values="0;0.7;0.7;0" keyTimes="0;0.05;0.92;1" dur="12s" begin="5s" repeatCount="indefinite"/> + </circle> + + + <!-- ═══ ORBITAL FLASH EFFECTS ═══ --> + <!-- Color-matched bursts: each fires when its orbital particle passes nearest the service box --> + + <!-- Orchestrator burst — amber #f59e0b, CW 8s begin=0s, particle at top at cycle boundary --> + <g> + <path d="M 640 183 L 645 178 L 635 174 L 640 170" fill="none" stroke="#f59e0b" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round" filter="url(#glow-flash)"/> + <circle cx="640" cy="183" r="0" fill="#f59e0b" filter="url(#glow-flash)"> + <animate attributeName="r" values="10;0;0;10" keyTimes="0;0.15;0.85;1" dur="8s" begin="0s" repeatCount="indefinite"/> + </circle> + <animate attributeName="opacity" values="1;0;0;1" keyTimes="0;0.15;0.85;1" dur="8s" begin="0s" repeatCount="indefinite"/> + </g> + + <!-- Vault burst — pink #ec4899, CW 10s begin=2s, particle passes Vault at keyTime=0.23 --> + <g opacity="0"> + <path d="M 775 304 L 779 300 L 782 308 L 789 304" fill="none" stroke="#ec4899" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round" filter="url(#glow-flash)"/> + <circle cx="775" cy="304" r="0" fill="#ec4899" filter="url(#glow-flash)"> + <animate attributeName="r" values="0;0;10;0;0" keyTimes="0;0.15;0.23;0.38;1" dur="10s" begin="2s" repeatCount="indefinite"/> + </circle> + <animate attributeName="opacity" values="0;0;1;0;0" keyTimes="0;0.15;0.23;0.38;1" dur="10s" begin="2s" repeatCount="indefinite"/> + </g> + + <!-- Extensions burst — red #ef4444, CW 12s begin=5s, particle passes Extensions at keyTime=0.38 --> + <g opacity="0"> + <path d="M 732 420 L 736 424 L 728 427 L 732 430" fill="none" stroke="#ef4444" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round" filter="url(#glow-flash)"/> + <circle cx="732" cy="420" r="0" fill="#ef4444" filter="url(#glow-flash)"> + <animate attributeName="r" values="0;0;10;0;0" keyTimes="0;0.30;0.38;0.53;1" dur="12s" begin="5s" repeatCount="indefinite"/> + </circle> + <animate attributeName="opacity" values="0;0;1;0;0" keyTimes="0;0.30;0.38;0.53;1" dur="12s" begin="5s" repeatCount="indefinite"/> + </g> + + <!-- AI•MCP burst — cyan #22d3ee, CCW 11s begin=3s, particle passes AI at keyTime=0.38 --> + <g opacity="0"> + <path d="M 548 420 L 544 424 L 552 427 L 548 430" fill="none" stroke="#22d3ee" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round" filter="url(#glow-flash)"/> + <circle cx="548" cy="420" r="0" fill="#22d3ee" filter="url(#glow-flash)"> + <animate attributeName="r" values="0;0;10;0;0" keyTimes="0;0.30;0.38;0.53;1" dur="11s" begin="3s" repeatCount="indefinite"/> + </circle> + <animate attributeName="opacity" values="0;0;1;0;0" keyTimes="0;0.30;0.38;0.53;1" dur="11s" begin="3s" repeatCount="indefinite"/> + </g> + + <!-- Control Center burst — purple #818cf8, CW 9s begin=4s, particle passes CC at keyTime=0.75 --> + <g opacity="0"> + <path d="M 505 320 L 500 315 L 498 325 L 491 320" fill="none" stroke="#818cf8" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round" filter="url(#glow-flash)"/> + <circle cx="505" cy="320" r="0" fill="#818cf8" filter="url(#glow-flash)"> + <animate attributeName="r" values="0;0;10;0;0" keyTimes="0;0.67;0.75;0.90;1" dur="9s" begin="4s" repeatCount="indefinite"/> + </circle> + <animate attributeName="opacity" values="0;0;1;0;0" keyTimes="0;0.67;0.75;0.90;1" dur="9s" begin="4s" repeatCount="indefinite"/> + </g> + + + <!-- ═══ CREDENTIAL FLOW ═══ --> + + <circle r="4" fill="#f59e0b" opacity="0" filter="url(#glow-gold)"> + <animateMotion dur="2.5s" begin="0s" repeatCount="indefinite"> + <mpath href="#path-orch-vault"/> + </animateMotion> + <animate attributeName="opacity" values="0;0.95;0.95;0" keyTimes="0;0.1;0.8;1" dur="2.5s" repeatCount="indefinite"/> + </circle> + + <circle r="3.5" fill="#ec4899" opacity="0" filter="url(#glow-pink)"> + <animateMotion dur="2.5s" begin="4s" repeatCount="indefinite"> + <mpath href="#path-vault-orch"/> + </animateMotion> + <animate attributeName="opacity" values="0;0.9;0.9;0" keyTimes="0;0.1;0.8;1" dur="2.5s" begin="4s" repeatCount="indefinite"/> + </circle> + + <circle r="3" fill="#4a9eff" opacity="0" filter="url(#glow-blue)"> + <animateMotion dur="1.2s" begin="0s" repeatCount="indefinite"> + <mpath href="#path-cli-orch"/> + </animateMotion> + <animate attributeName="opacity" values="0;0.85;0.85;0" keyTimes="0;0.15;0.7;1" dur="1.2s" repeatCount="indefinite"/> + </circle> + + + <!-- ═══ CONFIGURATION CYLINDER EDGES ═══ --> + <line x1="920" y1="95" x2="920" y2="156" stroke="#10b981" stroke-width="0.8" stroke-dasharray="4 2" opacity="0.4"/> + <line x1="1060" y1="95" x2="1060" y2="156" stroke="#10b981" stroke-width="0.8" stroke-dasharray="4 2" opacity="0.4"/> + + <!-- ═══ DATA PERSISTENCE CYLINDER EDGES ═══ --> + <line x1="210" y1="582" x2="210" y2="669" stroke="#a78bfa" stroke-width="0.8" stroke-dasharray="4 2" opacity="0.4"/> + <line x1="1070" y1="582" x2="1070" y2="669" stroke="#a78bfa" stroke-width="0.8" stroke-dasharray="4 2" opacity="0.4"/> + + <!-- ═══ CONNECTIONS TO SURREALDB ═══ --> + <g opacity="0.3"> + <line x1="640" y1="460" x2="640" y2="570" stroke="#a78bfa" stroke-width="1" stroke-dasharray="4 3" class="db-flow" marker-end="url(#arr-silver)"/> + <line x1="430" y1="537" x2="430" y2="571" stroke="#a78bfa" stroke-width="0.8" stroke-dasharray="4 3" class="db-flow" marker-end="url(#arr-silver)"/> + <line x1="850" y1="537" x2="850" y2="571" stroke="#a78bfa" stroke-width="0.8" stroke-dasharray="4 3" class="db-flow" marker-end="url(#arr-silver)"/> + <path d="M 377 358 C 280 370 230 480 240 572" fill="none" stroke="#a78bfa" stroke-width="0.8" stroke-dasharray="4 3" class="db-flow" marker-end="url(#arr-silver)"/> + <path d="M 903 358 C 1000 370 1050 480 1040 572" fill="none" stroke="#a78bfa" stroke-width="0.8" stroke-dasharray="4 3" class="db-flow" marker-end="url(#arr-silver)"/> + <path d="M 1060 128 C 1195 270 1145 410 1070 577" fill="none" stroke="#a78bfa" stroke-width="0.8" stroke-dasharray="4 3" class="db-flow" marker-end="url(#arr-silver)"/> + <path d="M 235 138 C 100 280 150 420 210 579" fill="none" stroke="#a78bfa" stroke-width="0.8" stroke-dasharray="4 3" class="db-flow" marker-end="url(#arr-silver)"/> + </g> + + + <!-- ═══ DATA PERSISTENCE ═══ --> + + <g> + <ellipse cx="640" cy="669" rx="430" ry="10" fill="none" stroke="#a78bfa" stroke-width="1" opacity="0.1"/> + <path d="M 210 669 Q 640 687 1070 669" stroke="#c4a8f8" stroke-width="1" fill="none" stroke-dasharray="4 2" opacity="0.4"/> + <rect x="210" y="582" width="860" height="87" rx="8" fill="url(#surreal-grad)" stroke="none" opacity="0.6" filter="url(#soft-shadow)"/> + <ellipse cx="640" cy="582" rx="430" ry="12" fill="#a78bfa" opacity="0.45" stroke="none"/> + + <text x="640" y="620" text-anchor="middle" fill="#a78bfa" font-family="'DM Sans',sans-serif" font-weight="700" font-size="11" letter-spacing="1">DATA PERSISTENCE</text> + <text x="1055" y="607" text-anchor="end" fill="#6a58b8" font-family="'IBM Plex Mono',monospace" font-size="7" opacity="0.75">Solo: RocksDB • Multi: WebSocket</text> + + <g font-family="'IBM Plex Mono',monospace" font-size="7" font-weight="600"> + <rect x="258" y="640" width="130" height="16" rx="3" fill="#f59e0b" stroke="#f59e0b" stroke-width="0.8" opacity="0.25"/> + <text x="323" y="651" text-anchor="middle" fill="#8a5800">orchestrator</text> + + <rect x="400" y="640" width="110" height="16" rx="3" fill="#ec4899" stroke="#ec4899" stroke-width="0.8" opacity="0.25"/> + <text x="455" y="651" text-anchor="middle" fill="#900050">vault</text> + + <rect x="522" y="640" width="140" height="16" rx="3" fill="#818cf8" stroke="#818cf8" stroke-width="0.8" opacity="0.25"/> + <text x="592" y="651" text-anchor="middle" fill="#2830a0">control_center</text> + + <rect x="674" y="640" width="90" height="16" rx="3" fill="#10b981" stroke="#10b981" stroke-width="0.8" opacity="0.25"/> + <text x="719" y="651" text-anchor="middle" fill="#065c3a">audit</text> + + <rect x="776" y="640" width="130" height="16" rx="3" fill="#4a9eff" stroke="#4a9eff" stroke-width="0.8" opacity="0.25"/> + <text x="841" y="651" text-anchor="middle" fill="#0040a0">workspace</text> + + <rect x="918" y="640" width="100" height="16" rx="3" fill="none" stroke="#10b981" stroke-width="0.6" stroke-dasharray="4 2" opacity="0.5"/> + <text x="968" y="651" text-anchor="middle" fill="#065c3a">Nickel config</text> + </g> + </g> + + + <!-- ═══ SOLID ENFORCEMENT ═══ --> + + <g transform="translate(120, 759)"> + <text x="0" y="0" fill="#444444" font-family="'IBM Plex Mono',monospace" font-weight="600" font-size="8" letter-spacing="1.5">SOLID ENFORCEMENT LAYERS</text> + <g font-family="'IBM Plex Mono',monospace" font-size="7"> + <rect x="0" y="8" width="80" height="12" rx="3" fill="#f59e0b" opacity="0.25" stroke="#f59e0b" stroke-width="0.5" stroke-opacity="0.7"/> + <text x="40" y="17" text-anchor="middle" fill="#c07800">Compile-time</text> + <rect x="88" y="8" width="68" height="12" rx="3" fill="#818cf8" opacity="0.25" stroke="#818cf8" stroke-width="0.5" stroke-opacity="0.7"/> + <text x="122" y="17" text-anchor="middle" fill="#4850c0">Dev-time</text> + <rect x="164" y="8" width="74" height="12" rx="3" fill="#ec4899" opacity="0.25" stroke="#ec4899" stroke-width="0.5" stroke-opacity="0.7"/> + <text x="201" y="17" text-anchor="middle" fill="#b8206a">Pre-commit</text> + <rect x="246" y="8" width="50" height="12" rx="3" fill="#ef4444" opacity="0.25" stroke="#ef4444" stroke-width="0.5" stroke-opacity="0.7"/> + <text x="271" y="17" text-anchor="middle" fill="#b82020">CI/CD</text> + <rect x="304" y="8" width="60" height="12" rx="3" fill="#22d3ee" opacity="0.25" stroke="#22d3ee" stroke-width="0.5" stroke-opacity="0.7"/> + <text x="334" y="17" text-anchor="middle" fill="#0890a8">Runtime</text> + <rect x="372" y="8" width="50" height="12" rx="3" fill="#94a3b8" opacity="0.25" stroke="#94a3b8" stroke-width="0.5" stroke-opacity="0.7"/> + <text x="397" y="17" text-anchor="middle" fill="#4a5870">Audit</text> + </g> + <g font-family="'IBM Plex Mono',monospace" font-size="7" fill="#2a2e38" opacity="0.6"> + <text x="460" y="16" font-family="'IBM Plex Mono',monospace" font-size="7"><tspan fill="#f59e0b">Providers APIs or CLI</tspan><tspan fill="#2a2e38">: Orchestrator only | </tspan><tspan fill="#818cf8">SSH</tspan><tspan fill="#2a2e38">: Orchestrator + Machines | </tspan><tspan fill="#22d3ee">Auth</tspan><tspan fill="#2a2e38">: Control Center only | </tspan><tspan fill="#ec4899">Secrets</tspan><tspan fill="#2a2e38">: Vault Service API only</tspan></text> + </g> + </g> + + + <!-- ═══ LEGEND ═══ --> + + <g transform="translate(120, 802)" font-family="'IBM Plex Mono',monospace" font-size="8"> + <text x="0" y="0" fill="#444444" font-weight="600" font-size="8" letter-spacing="1.5">SERVICES</text> + <circle cx="7" cy="12" r="3" fill="#f59e0b"/><text x="15" y="16" fill="#8a7a60">Orchestrator</text> + <circle cx="7" cy="24" r="3" fill="#818cf8"/><text x="15" y="28" fill="#4a4870">Control Center</text> + <circle cx="7" cy="36" r="3" fill="#ec4899"/><text x="15" y="40" fill="#7a4870">Vault Service</text> + <circle cx="7" cy="48" r="3" fill="#ef4444"/><text x="15" y="52" fill="#7a4868">Extension Registry</text> + <circle cx="7" cy="60" r="3" fill="#22d3ee"/><text x="15" y="64" fill="#487a78">AI • MCP</text> + + <text x="240" y="0" fill="#444444" font-weight="600" font-size="8" letter-spacing="1.5">INFRASTRUCTURE</text> + <circle cx="248" cy="12" r="5" fill="none" stroke="#94a3b8" stroke-width="1.2" stroke-dasharray="2 2"/> + <text x="260" y="16" fill="#666666" opacity="0.5">NATS Orbital Ring</text> + <rect x="243" y="22.5" width="6" height="6" fill="none" stroke="#ef4444" stroke-width="0.8" rx="0.5"/> + <line x1="243" y1="25.5" x2="249" y2="25.5" stroke="#ef4444" stroke-width="0.7"/> + <line x1="246" y1="22.5" x2="246" y2="28.5" stroke="#ef4444" stroke-width="0.7" opacity="0.6"/> + <text x="260" y="28" fill="#ef4444" opacity="0.5">OCI registry</text> + <line x1="240" y1="36" x2="254" y2="36" stroke="#818cf8" stroke-width="1.5"/> + <text x="260" y="40" fill="#818cf8" opacity="0.5">Token and Auth</text> + <path d="M 244 43 L 250 43 L 250 48 Q 247 52 244 48 L 244 43" fill="none" stroke="#ec4899" stroke-width="1.2" stroke-linejoin="round"/> + <text x="260" y="52" fill="#ec4899" opacity="0.5">SecretumVault</text> + <line x1="240" y1="60" x2="254" y2="60" stroke="#10b981" stroke-width="1.5"/> + <text x="260" y="64" fill="#10b981" opacity="0.5">Nickel</text> + <ellipse cx="247" cy="69" rx="5" ry="2" fill="none" stroke="#a78bfa" stroke-width="0.8"/> + <rect x="243" y="69" width="8" height="5" fill="none" stroke="#a78bfa" stroke-width="0.8" opacity="0.5"/> + <ellipse cx="247" cy="74" rx="5" ry="2" fill="#a78bfa" opacity="0.3" stroke="#a78bfa" stroke-width="0.8"/> + <text x="260" y="76" fill="#a78bfa" opacity="0.5">SurrealDB</text> + + <text x="460" y="0" fill="#444444" font-weight="600" font-size="8" letter-spacing="1.5">CONNECTIONS</text> + <line x1="460" y1="12" x2="474" y2="12" stroke="#4a9eff" stroke-width="2" filter="url(#glow-blue)"/> + <line x1="460" y1="12" x2="474" y2="12" stroke="#4a9eff" stroke-width="1" stroke-dasharray="4 3" opacity="0.7"/> + <text x="486" y="16" fill="#4a9eff" opacity="0.5">Encrypted</text> + <line x1="460" y1="24" x2="474" y2="24" stroke="#ef9a3c" stroke-width="1.5" stroke-dasharray="2 2"/> + <text x="486" y="28" fill="#ef9a3c" opacity="0.3">I/O and Defs</text> + <line x1="460" y1="36" x2="474" y2="36" stroke="#a78bfa" stroke-width="1.5" stroke-dasharray="2 2"/> + <text x="486" y="40" fill="#6a5888">DBs data</text> + <line x1="460" y1="48" x2="474" y2="48" stroke="#10b981" stroke-width="1.5" stroke-dasharray="2 2"/> + <text x="486" y="52" fill="#10b981" opacity="0.5">Secure access</text> + <circle cx="468" cy="60" r="5" fill="none" stroke="#94a3b8" stroke-width="1.2" stroke-dasharray="2 2"/> + <text x="486" y="64" fill="#666666">Events Stream</text> + + <text x="685" y="0" fill="#444444" font-weight="600" font-size="8" letter-spacing="1.5">MODES</text> + <rect x="685" y="11" width="40" height="10" rx="2" fill="#34d399" opacity="0.1" stroke="#34d399" stroke-width="0.4"/> + <text x="705" y="19" text-anchor="middle" fill="#34d399" font-size="7" font-weight="600">SOLO</text> + <text x="735" y="19" fill="#5a8a6a" font-size="7">RocksDB + local NATS + auto-session</text> + <rect x="685" y="25" width="40" height="10" rx="2" fill="#818cf8" opacity="0.1" stroke="#818cf8" stroke-width="0.4"/> + <text x="705" y="33" text-anchor="middle" fill="#818cf8" font-size="7" font-weight="600">MULTI</text> + <text x="735" y="33" fill="#4a4870" font-size="7">WebSocket DB + NATS cluster + JWT+Cedar</text> + <rect x="685" y="39" width="40" height="10" rx="2" fill="#f59e0b" opacity="0.1" stroke="#f59e0b" stroke-width="0.4"/> + <text x="705" y="47" text-anchor="middle" fill="#f59e0b" font-size="7" font-weight="600">ENT</text> + <text x="735" y="47" fill="#d97706" font-size="7">Enterprise</text> + </g> + + + <!-- ═══ DEPLOYMENT ═══ --> + + <g transform="translate(145, 693)" font-family="'IBM Plex Mono',monospace"> + <rect x="160" y="0" width="120" height="45" rx="6" fill="#f4ebe4" stroke="#d4bab2" stroke-width="0.8"/> + <circle cx="174" cy="15" r="3" fill="#ef4444"/><text x="182" y="18" fill="#c04040" font-size="8" font-weight="500">Production</text> + <text x="209" y="35" fill="#8a5858" font-size="7" text-anchor="middle">Hetzner • AWS</text> + + <rect x="300" y="0" width="120" height="45" rx="6" fill="#fff8ed" stroke="#f0e0c0" stroke-width="0.8"/> + <circle cx="314" cy="15" r="3" fill="#f59e0b"/><text x="322" y="18" fill="#b07808" font-size="8" font-weight="500">Staging</text> + <text x="344" y="35" fill="#8a7050" font-size="7" text-anchor="middle">K8s cluster</text> + + <rect x="440" y="0" width="120" height="45" rx="6" fill="#edf8f2" stroke="#c0e8d0" stroke-width="0.8"/> + <circle cx="454" cy="15" r="3" fill="#34d399"/><text x="462" y="18" fill="#208060" font-size="8" font-weight="500">Dev</text> + <text x="484" y="35" fill="#508a68" font-size="7" text-anchor="middle">Solo mode</text> + + <rect x="580" y="0" width="120" height="45" rx="6" fill="#f0eeff" stroke="#d0d0f0" stroke-width="0.8"/> + <circle cx="594" cy="15" r="3" fill="#818cf8"/><text x="602" y="18" fill="#5060c0" font-size="8" font-weight="500">Edge</text> + <text x="624" y="35" fill="#68688a" font-size="7" text-anchor="middle">On-prem • IoT</text> + + <rect x="720" y="0" width="120" height="45" rx="6" fill="#edf9fc" stroke="#c0e8f0" stroke-width="0.8"/> + <circle cx="734" cy="15" r="3" fill="#22d3ee"/><text x="742" y="18" fill="#1898a8" font-size="8" font-weight="500">Custom</text> + <text x="774" y="35" fill="#508888" font-size="7" text-anchor="middle">GitOps • Webhook</text> + </g> + + <!-- ═══ VERSION ═══ --> + <text x="1160" y="872" text-anchor="end" fill="#4cc2f1" font-family="'IBM Plex Mono',monospace" font-size="9" letter-spacing="0.5">v3.0.11 • ∞ Architecture</text> +</svg> diff --git a/site/site/public/adr/adr-001/index.html b/site/site/public/adr/adr-001/index.html new file mode 100644 index 0000000..04c101e --- /dev/null +++ b/site/site/public/adr/adr-001/index.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<html lang="en"><head><meta charset="utf-8"> +<meta name="viewport" content="width=device-width, initial-scale=1"> +<title>Ontoref is a Standalone Protocol Project, Not Part of Stratumiops · Ontoref ADR + + +
    +
    + Accepted + adr-001 · 2026-03-12 +

    Ontoref is a Standalone Protocol Project, Not Part of Stratumiops

    +
    + +

    Context

    The ontology/reflection patterns originated inside stratumiops as self-description tooling (stratum-ontology-core, stratum-reflection-core, stratum-daemon). Consumer projects (typedialog, vapora, kogral) needed the same patterns but had no path to adoption that did not entangle them with stratumiops' pipeline-specific crates (stratum-graph, stratum-state, stratum-orchestrator). Protocol evolution (schema changes, ADR lifecycle, daemon features) was blocked behind stratumiops release cycles. The three crates were logically a specification layer that happened to live in the wrong repo.

    Decision

    Ontoref is extracted as a standalone protocol project with independent versioning, CI, and crates: ontoref-ontology (Rust types for the ontology graph), ontoref-reflection (mode runner and schema validation), ontoref-daemon (NCL export cache, file watcher, actor registry). Consumer projects adopt the protocol via a thin `scripts/ontoref` bash wrapper and a `.ontoref/config.ncl` declaration. The ontoref project itself is allowed to use stratum-db and platform-nats as optional peer dependencies via workspace path references, since those crates are infrastructural rather than domain-specific.

    Constraints

    • Hard

      ontoref crates must not import stratumiops domain crates: stratum-graph, stratum-state, stratum-orchestrator, stratum-llm, stratum-embeddings

    • Hard

      The ontoref entry point must not unconditionally overwrite ONTOREF_PROJECT_ROOT — it must default only when unset

    • Soft

      A consumer project must only need .ontoref/config.ncl and scripts/ontoref to adopt the protocol — no other files copied into the consumer

    Alternatives considered

    • Keep all tooling in stratumiops, consumers depend on it as a git subtree or submodule

      Rejected: Submodule/subtree patterns create update friction and do not solve the versioning coupling. Every consumer needs stratumiops' full dependency tree even for protocol-only use.

    • Publish ontoref crates to crates.io and consume via version pins

      Rejected: Rapid iteration on protocol schemas makes published crate semantics too rigid at this stage. Path dependencies allow simultaneous development of protocol and consumers without publication ceremony.

    • Inline protocol tooling into each consumer project separately

      Rejected: Schema drift across projects would immediately arise. The protocol's value is precisely its shared contract — decentralizing it defeats the purpose.

    +
    diff --git a/site/site/public/adr/adr-002/index.html b/site/site/public/adr/adr-002/index.html new file mode 100644 index 0000000..f02fbca --- /dev/null +++ b/site/site/public/adr/adr-002/index.html @@ -0,0 +1,33 @@ + + + +Ontoref Daemon for NCL Caching, File Watching, and Actor Notification Barrier · Ontoref ADR + + +
    +
    + Accepted + adr-002 · 2026-03-12 +

    Ontoref Daemon for NCL Caching, File Watching, and Actor Notification Barrier

    +
    + +

    Context

    Nushell reflection modules invoke `nickel export` as a subprocess ~39 times per full sync scan, taking 2m42s. Each invocation forks a new process (~100ms). There is no shared state between developers and agents working on the same project, no notification when a peer changes an ontology or ADR file mid-session, and no persistent store for scan results. The protocol-not-runtime axiom forbids required runtime services — any daemon must be optional with full subprocess fallback. This ADR supersedes stratumiops adr-007, which designed this system inside stratumiops before the protocol was extracted to ontoref.

    Decision

    ontoref-daemon is an optional persistent daemon providing: (1) NCL export caching — results keyed by (path, mtime, import_path) served via HTTP, file watcher invalidates on change; (2) actor registry — developers, agents, CI register on startup with deterministic tokens (type:hostname:pid), sweep reaps stale sessions every 30s; (3) notification barrier — file changes in .ontology/, adrs/, reflection/ generate typed notifications stored in a per-project ring buffer; pre-commit hook queries pending notifications and blocks commits until acknowledged (fail-open: daemon down = commit allowed). Consumer projects configure the daemon via `.ontoref/config.ncl` (daemon.enabled, daemon.port, db.enabled, db.url). All Nushell modules fall back to direct `nickel export` subprocess when the daemon is unavailable. stratum-db (optional feature, path dep on stratumiops) handles SurrealDB persistence. platform-nats (optional feature, path dep on stratumiops) handles NATS event publishing.

    Constraints

    • Hard

      No Nushell module or bash script may fail when ontoref-daemon is unavailable

    • Hard

      ontoref-daemon must bind to 127.0.0.1, never to 0.0.0.0 or a public interface

    • Hard

      The pre-commit hook must allow commits when ontoref-daemon is unreachable, printing a warning but not blocking

    • Soft

      All daemon HTTP requests from consumer wrappers must include X-Ontoref-Project header or equivalent project scoping

    Alternatives considered

    • In-process Nickel evaluation via nickel-lang-core library

      Rejected: nickel-lang-core has an unstable Rust API and resolves import paths differently from the CLI. The subprocess approach with caching is simpler, more stable, and already <1ms cached.

    • Required daemon (always running, no fallback)

      Rejected: Violates the protocol-not-runtime axiom. Consumer projects must function without any ontoref process running. The daemon is an optimization and awareness layer, not infrastructure.

    • Filesystem-based JSON cache without a daemon process

      Rejected: File-based caching requires lock management, cannot serve concurrent requests from multiple actors, and does not provide file watching or the actor registry. A daemon centralizes these concerns cleanly.

    • Bundle SurrealDB and NATS clients directly in ontoref, not as path deps

      Rejected: Duplicating stratum-db and platform-nats creates divergence. Both crates are general-purpose infrastructure; ontoref consumers already have stratumiops checked out for other reasons. Path deps preserve the single canonical implementation.

    +
    diff --git a/site/site/public/adr/adr-003/index.html b/site/site/public/adr/adr-003/index.html new file mode 100644 index 0000000..05e97a4 --- /dev/null +++ b/site/site/public/adr/adr-003/index.html @@ -0,0 +1,33 @@ + + + +Q&A and Accumulated Knowledge Persist to NCL, Not Browser Storage · Ontoref ADR + + +
    +
    + Accepted + adr-003 · 2026-03-12 +

    Q&A and Accumulated Knowledge Persist to NCL, Not Browser Storage

    +
    + +

    Context

    The initial Q&A bookmarks feature stored entries in browser localStorage keyed by project. This is convenient but violates the dag-formalized axiom: knowledge that lives only in a browser session is invisible to agents, not git-versioned, not queryable via MCP, and is lost on browser data reset. The same problem applies to quick actions and any other accumulated operational knowledge. The system accumulates knowledge during development sessions (AI interactions, architectural reviews, debugging) that should be first-class artifacts — not ephemeral browser state.

    Decision

    All Q&A entries persist to `reflection/qa.ncl` — a typed NCL record file governed by `reflection/schemas/qa.ncl`. Mutations happen via `crates/ontoref-daemon/src/ui/qa_ncl.rs` (line-level surgery, same pattern as backlog_ncl.rs). Four MCP tools expose the store to AI agents: `ontoref_qa_list` (read, with filter), `ontoref_qa_add` (append), `ontoref_qa_delete` (remove block), `ontoref_qa_update` (field mutation). HTTP endpoints `/qa-json` (GET), `/qa/add`, `/qa/delete`, `/qa/update` (POST) serve the UI. The UI renders server-side entries via Tera template injection (SERVER_ENTRIES JSON blob), eliminating the need for a separate fetch on load. The same principle is applied to quick actions: they are declared in `.ontoref/config.ncl` (quick_actions array) and new modes are created as `reflection/modes/<id>.ncl` files via `ontoref_action_add`.

    Constraints

    • Hard

      All mutations to reflection/qa.ncl must go through crates/ontoref-daemon/src/ui/qa_ncl.rs — no direct file writes from other call sites

    • Hard

      reflection/qa.ncl must conform to the QaStore contract from reflection/schemas/qa.ncl — nickel typecheck must pass

    • Hard

      MCP tools ontoref_qa_list and ontoref_qa_add must never trigger sync apply steps or modify .ontology/ files

    Alternatives considered

    • Keep localStorage as the storage backend, add optional sync to NCL on explicit user action

      Rejected: Two sources of truth creates sync complexity and divergence. AI agents would still not see localStorage entries. The sync step would be frequently skipped. NCL as primary is simpler.

    • Store Q&A in SurrealDB via stratum-db (existing optional dependency)

      Rejected: Requires the db feature and a running SurrealDB instance. NCL files are always present, git-versioned, and work without any database. The protocol-not-runtime axiom argues for file-first. SurrealDB can be added as a secondary index later if full-text search is needed.

    • Full AST parse of qa.ncl via nickel-lang-core for mutations

      Rejected: nickel-lang-core has an unstable Rust API. The file structure is predictable enough for line-level surgery. backlog_ncl.rs has been operating safely with this pattern. Adding a hard dependency on nickel-lang-core for a file that is always written by the daemon is unnecessary complexity.

    +
    diff --git a/site/site/public/adr/adr-004/index.html b/site/site/public/adr/adr-004/index.html new file mode 100644 index 0000000..7d60c41 --- /dev/null +++ b/site/site/public/adr/adr-004/index.html @@ -0,0 +1,33 @@ + + + +NCL Pipe Bootstrap — Config Validation and Secret Injection via Unix Pipeline · Ontoref ADR + + +
    +
    + Accepted + adr-004 · 2026-03-13 +

    NCL Pipe Bootstrap — Config Validation and Secret Injection via Unix Pipeline

    +
    + +

    Context

    Ontoref-daemon and any process that receives structured config faces two problems: (1) Nickel NCL requires a subprocess to evaluate, introducing a system-call injection surface if the daemon itself calls `nickel export` at runtime; (2) credentials and secrets embedded in config files (TOML, JSON) persist on disk after the process starts, creating a forensic artifact. The existing registry.toml approach (NCL → TOML file → daemon reads file) partially addresses the first problem but not the second — the TOML file remains on disk with hashed credentials. SOPS and Vault are standard secret management tools that produce decrypted output on stdout.

    Decision

    All config delivery to long-running processes follows a three-stage Unix pipeline: Stage 1 — structural validation: `nickel export --format json config.ncl` produces JSON with schema-validated structure but no secret values; Stage 2 — secret injection (optional): SOPS decrypt or Vault lookup merges credentials into the JSON stream; Stage 3 — process bootstrap: the target process reads the composed JSON from stdin via `--config-stdin`. No intermediate file is written to disk. If any stage fails, the pipeline breaks and the process does not start. A bash wrapper script (not Nu — Nu may not be available at service boot time) orchestrates the pipeline. A Nu helper `ncl-bootstrap` provides the same interface for interactive/development use.

    Constraints

    • Hard

      The bootstrap pipeline must not write an intermediate config file to disk at any stage

    • Hard

      The bash wrapper must depend only on bash, nickel, and the target binary — no Nu, no jq unless SOPS/Vault stage is active

    • Hard

      The target process must redirect stdin to /dev/null after reading the config JSON

    • Hard

      NCL config files used with ncl-bootstrap must not contain plaintext secret values — only SecretRef placeholders or empty fields

    Alternatives considered

    • TOML file on disk (current registry.toml approach)

      Rejected: File persists on disk with credentials. Stale file may be read after config changes. Requires explicit cleanup logic. Forensic artifact risk.

    • Environment variables for secrets

      Rejected: Environment variables are visible in /proc/PID/environ on Linux and via `ps eww` on some systems. They persist for the lifetime of the process and are inherited by child processes. Worse attack surface than stdin pipe.

    • Encrypted TOML file (AES256 at rest)

      Rejected: Decryption key must be available at runtime — the problem is deferred, not solved. The decrypted form still passes through disk (tmpfs or swap). Adds a custom encryption layer instead of using standard tools (SOPS, Vault) that the ecosystem already supports.

    • Daemon reads NCL directly at runtime via nickel-lang-core

      Rejected: nickel-lang-core has an unstable Rust API. More critically, it means the daemon can evaluate arbitrary Nickel — including NCL files with system calls via builtins. The pipeline approach ensures the daemon only ever sees validated JSON, never executable Nickel.

    +
    diff --git a/site/site/public/adr/adr-005/index.html b/site/site/public/adr/adr-005/index.html new file mode 100644 index 0000000..e70f59e --- /dev/null +++ b/site/site/public/adr/adr-005/index.html @@ -0,0 +1,33 @@ + + + +Unified Key-to-Session Auth Model Across CLI, UI, and MCP · Ontoref ADR + + +
    +
    + Accepted + adr-005 · 2026-03-13 +

    Unified Key-to-Session Auth Model Across CLI, UI, and MCP

    +
    + +

    Context

    ontoref-daemon exposes project knowledge and mutations over HTTP, a browser UI, and an MCP server. Projects can define argon2id-hashed keys in project.ncl (with role admin|viewer and an audit label). Prior to this ADR, the UI login flow set a session cookie but the REST API accepted raw passwords as Bearer tokens on every request — each call paying ~100ms for argon2 verification. The CLI had no Bearer support at all; project-add and project-remove called the daemon without credentials. The daemon manage page had no admin identity concept. There was no way to enumerate or revoke active sessions.

    Decision

    All surfaces exchange a raw key once via POST /sessions for a UUID v4 bearer token (30-day lifetime, in-memory SessionStore). The session token is used for all subsequent calls — O(1) DashMap lookup. Sessions carry a stable public id (distinct from the bearer token) for safe list/revoke operations without leaking credentials. Project keys have a label field for audit trail. Daemon-level admin uses a separate argon2id hash (ONTOREF_ADMIN_TOKEN_FILE preferred over ONTOREF_ADMIN_TOKEN) and creates sessions under virtual slug '_daemon'. The CLI injects ONTOREF_TOKEN as Authorization: Bearer automatically via bearer-args in store.nu. Key rotation (PUT /projects/{slug}/keys) revokes all active sessions for the rotated project. GET /sessions and DELETE /sessions/{id} implement two-tier visibility: project admin sees own project sessions; daemon admin sees all.

    Constraints

    • Hard

      GET /sessions responses must never include the bearer token, only the public session id

    • Hard

      POST /sessions must not require authentication — it is the credential exchange endpoint

    • Hard

      PUT /projects/{slug}/keys must call revoke_all_for_slug before persisting new keys

    • Soft

      All CLI HTTP calls to the daemon must use bearer-args from store.nu — no hardcoded curl without auth args

    Alternatives considered

    • Verify argon2 on every Bearer request (no session concept)

      Rejected: ~100ms per request is unacceptable for CLI invocations (each command is a new HTTP call) and MCP tool sequences (multiple calls per agent turn). Session token lookup is sub-microsecond.

    • Axum middleware for auth instead of per-handler check_primary_auth

      Rejected: Middleware applies uniformly to all routes. ontoref-daemon coexists auth-enabled and open projects on the same router — some endpoints are always public (health, POST /sessions itself), some require project-scoped auth, some require daemon admin. Per-handler checks encode these rules explicitly; middleware would require complex route exemption logic.

    • Expose bearer token in session list responses

      Rejected: GET /sessions is accessible to project admins. Exposing the bearer would allow any project admin to impersonate any other session holder. The public session.id is a safe substitute for revocation targeting.

    • Separate token store per project

      Rejected: A single DashMap keyed by token with a slug field in SessionEntry is sufficient. A secondary id_index DashMap gives O(1) revoke-by-id. Per-project sharding would add complexity without benefit given the expected session count (tens, not millions).

    • JWT instead of opaque UUID v4 tokens

      Rejected: JWTs are self-contained and cannot be revoked without a denylist. Opaque tokens enable instant revocation (key rotation, logout, admin force-revoke) with O(1) lookup. The daemon is local-only — there is no distributed verification scenario that would justify JWT complexity.

    +
    diff --git a/site/site/public/adr/adr-006/index.html b/site/site/public/adr/adr-006/index.html new file mode 100644 index 0000000..c1bf760 --- /dev/null +++ b/site/site/public/adr/adr-006/index.html @@ -0,0 +1,33 @@ + + + +Nushell 0.111 String Interpolation Compatibility Fix · Ontoref ADR + + +
    +
    + Accepted + adr-006 · 2026-03-14 +

    Nushell 0.111 String Interpolation Compatibility Fix

    +
    + +

    Context

    Nushell 0.111 introduced a breaking change in string interpolation parsing: expressions inside `$"..."` that match the pattern `(identifier: expr)` are now parsed as command calls rather than as record literals or literal text. This broke four print statements in reflection/bin/ontoref.nu that used patterns like `(kind: ($kind))`, `(logo: ($logo_file))`, `(parents: ($parent_slugs))`, and `(POST /actors/register)`. The bug manifested when running `ontoref setup` and `ontoref hooks-install` on any consumer project using Nu 0.111+. The minimum Nu version gate (>= 0.110.0) did not catch 0.111 regressions since it only guards the lower bound.

    Decision

    Fix all four affected print statements by removing the outer parentheses from label-value pairs inside string interpolations, or by removing the `$` prefix from strings that contain no variable interpolation. The fix is minimal and non-semantic: `(kind: ($kind))` becomes `kind: ($kind)` (literal label + variable), and `$"(POST /actors/register)"` becomes `"(POST /actors/register)"` (plain string). The fix is applied to both the dev repo (reflection/bin/ontoref.nu) and the installed copy (~/.local/bin/ontoref via just install-daemon). The minimum version gate remains >= 0.110.0 but 0.111 is now the tested floor.

    Constraints

    • Hard

      String interpolations in ontoref.nu must not use `(identifier: expr)` patterns — use bare `identifier: (expr)` instead

    • Soft

      Print statements with no variable interpolation must use plain strings, not `$"..."`

    Alternatives considered

    • Raise minimum Nu version to 0.111 and document the breaking change

      Rejected: Does not fix the broken syntax — just makes the breakage explicit. Consumer projects already on 0.111 would still fail until the print statements are fixed.

    • Use escape sequences or string concatenation to embed literal parens

      Rejected: Nushell has no escape for parens in string interpolation. String concatenation (e.g. `'(kind: ' + $kind + ')'`) works but is significantly less readable than bare `kind: ($kind)`.

    +
    diff --git a/site/site/public/adr/adr-007/index.html b/site/site/public/adr/adr-007/index.html new file mode 100644 index 0000000..0241797 --- /dev/null +++ b/site/site/public/adr/adr-007/index.html @@ -0,0 +1,33 @@ + + + +API Surface Discoverability via #[onto_api] Proc-Macro · Ontoref ADR + + +
    +
    + Accepted + adr-007 · 2026-03-23 +

    API Surface Discoverability via #[onto_api] Proc-Macro

    +
    + +

    Context

    ontoref-daemon exposes ~28 HTTP routes across api.rs, sync.rs, and other handler modules. Before this decision, the authoritative route list existed only in the axum Router definition — undiscoverable without reading source. MCP agents, CLI users, and the web UI had no machine-readable way to enumerate routes, their auth requirements, parameter shapes, or actor restrictions. OpenAPI was considered but rejected as a runtime dependency that would require schema maintenance separate from the handler code. The `#[onto_api]` proc-macro in `ontoref-derive` addresses this by making the handler annotation the single source of truth: the macro emits `inventory::submit!(ApiRouteEntry{...})` at link time, and `api_catalog::catalog()` collects them via `inventory::collect!`. No runtime registry, no startup allocation, no separate schema file.

    Decision

    Every HTTP handler in ontoref-daemon must carry `#[onto_api(method, path, description, auth, actors, params, tags)]`. The proc-macro (in `crates/ontoref-derive`) emits `inventory::submit!(ApiRouteEntry{...})` at link time. `GET /api/catalog` calls `api_catalog::catalog()` — a pure function over `inventory::iter::<ApiRouteEntry>()` — and returns the annotated surface as JSON. The web UI at `/ui/{slug}/api` renders it with client-side filtering. `describe api [--actor] [--tag] [--auth] [--fmt]` queries this endpoint from the CLI. The MCP tool `ontoref_api_catalog` calls `catalog()` directly without HTTP. This surfaces the complete API to three actors (browser, CLI, MCP agent) from one annotation site per handler.

    Constraints

    • Hard

      Every public HTTP handler in ontoref-daemon must carry #[onto_api(...)]

    • Hard

      inventory must remain a workspace dependency gated behind the 'catalog' feature of ontoref-derive; ontoref-ontology must not depend on inventory

    Alternatives considered

    • OpenAPI / utoipa with generated JSON schema

      Rejected: Requires maintaining a separate schema artifact (openapi.json) and a runtime schema struct tree. The schema can drift from actual handler signatures. utoipa adds ~15 transitive deps including serde_yaml. Violates 'Protocol, Not Runtime' — the schema becomes a runtime artifact rather than a compile-time invariant.

    • Manual route registry (Vec<RouteInfo> in main.rs)

      Rejected: A manually maintained Vec has guaranteed drift: handlers are added, routes change, and the Vec is updated inconsistently. Proven failure mode in the previous session where insert_mcp_ctx listed 15 tools while the router had 27.

    • Runtime reflection via axum Router introspection

      Rejected: axum does not expose a stable introspection API for registered routes. Workarounds (tower_http trace layer capture, method_router hacks) are brittle across axum versions and cannot surface handler metadata (auth, actors, params).

    +
    diff --git a/site/site/public/adr/adr-008/index.html b/site/site/public/adr/adr-008/index.html new file mode 100644 index 0000000..8b5cff2 --- /dev/null +++ b/site/site/public/adr/adr-008/index.html @@ -0,0 +1,33 @@ + + + +NCL-First Config Validation and Override-Layer Mutation · Ontoref ADR + + +
    +
    + Accepted + adr-008 · 2026-03-26 +

    NCL-First Config Validation and Override-Layer Mutation

    +
    + +

    Context

    The config surface feature adds per-project config introspection and mutation to ontoref-daemon. Two design questions arise: (1) Where does config field validation live — in NCL contracts, in Rust struct validation, or both? (2) How does a PUT /config/{section} request mutate a project's config without corrupting the source NCL files? Direct mutation via nickel export → JSON → write-back destroys NCL comments, contract annotations, and section merge structure. Duplicating validation in both NCL and Rust creates two sources of truth with guaranteed divergence. The config surface spans ontoref's own .ontoref/config.ncl and all consumer-project configs, making the choice of validation ownership a protocol-level constraint.

    Decision

    NCL contracts (std.contract.from_validator) are the single validation layer for all config fields. Rust serde structs are contract-trusted readers: they carry #[serde(default)] and consume pre-validated JSON from nickel export — no validator(), no custom Deserialize, no duplicate field constraints. Config mutation via PUT /projects/{slug}/config/{section} never modifies the original NCL source files. Instead it writes a {section}.overrides.ncl file containing only the changed fields plus a _overrides_meta audit record (actor, reason, timestamp, previous values), then appends a single idempotent import line to the entry-point NCL (using NCL's & merge operator so the override wins). nickel export validates the merged result against the section's declared contract before the mutation is committed; validation failure reverts the override file and returns the nickel error verbatim. Ontoref demonstrates this pattern on itself: .ontoref/contracts.ncl declares LogConfig and DaemonConfig contracts applied in .ontoref/config.ncl.

    Constraints

    • Hard

      The daemon must never write to original NCL config source files during a PUT /config/{section} mutation; only {section}.overrides.ncl may be created or modified

    • Hard

      Rust config structs must not implement custom field validation; all field constraints live in NCL contracts applied before JSON reaches Rust

    • Soft

      Every {section}.overrides.ncl file written by the daemon must contain a top-level _overrides_meta record with managed_by, created_at, and entries fields

    Alternatives considered

    • Duplicate validation in Rust (validator crate or custom Deserialize)

      Rejected: Two validators for the same field inevitably diverge. The NCL contract is already the authoritative schema for documentation, MCP export, and quickref generation — adding a Rust duplicate makes it decorative. validator crate adds 8+ transitive dependencies and requires annotation churn across every config struct.

    • Direct NCL file mutation (read → merge JSON → overwrite)

      Rejected: nickel export → JSON write-back destroys comments, contract annotations (| C.LogConfig), section merge structure, and in-file rationale. The resulting file is syntactically valid but semantically impoverished. Once a file is overwritten this way, the original structure cannot be recovered from git history if the file was also changed manually between sessions.

    • Separate config store (JSON or TOML side-file)

      Rejected: A side-file in a different format bypasses NCL type safety entirely — the merge operator and contract validation no longer apply. The daemon would need a custom merge algorithm to reconcile the side-file with the source NCL, and agents would need to understand two config representations. NCL's & merge operator is purpose-built for this use case.

    +
    diff --git a/site/site/public/adr/adr-009/index.html b/site/site/public/adr/adr-009/index.html new file mode 100644 index 0000000..55ec35d --- /dev/null +++ b/site/site/public/adr/adr-009/index.html @@ -0,0 +1,33 @@ + + + +Manifest Self-Interrogation Layer — Three Semantic Axes · Ontoref ADR + + +
    +
    + Accepted + adr-009 · 2026-03-26 +

    Manifest Self-Interrogation Layer — Three Semantic Axes

    +
    + +

    Context

    The manifest.ncl schema described structural facts (layers, modes, consumption modes, tools, config surface) but had no typed layer for self-interrogation: agents and operators could not query why a capability exists, what it needs to run, or what external dependencies have a documented blast radius. The existing tools[] field covered only dev tooling (install_method, version) — no prod/dev classification, no services, no environment variables, no infrastructure dependencies. Practice/node descriptions in core.ncl carry architectural meaning (invariant=true, ADR-backed) but are not the right home for operational, audience-facing descriptions of what a project offers. Capabilities, requirements, and dependency blast-radius analysis are three orthogonal concerns that needed typed, queryable homes in the manifest schema.

    Decision

    Three new typed arrays are added to manifest_type: capabilities[] (capability_type), requirements[] (requirement_type), and critical_deps[] (critical_dep_type). These are semantically distinct layers: capabilities answer 'what does this project do, why does it exist, and how does it work' with explicit cross-references to ontology node IDs and ADR IDs; requirements classify prerequisites by env_target_type ('Production | 'Development | 'Both) and requirement_kind_type ('Tool | 'Service | 'EnvVar | 'Infrastructure); critical_deps document blast radius — what breaks when an external dependency disappears or breaks its contract — distinct from requirements because the concern is runtime failure impact, not startup prerequisites. A description | String | default = '' field is also added to manifest_type, fixing a pre-existing bug where collect-identity in describe.nu read manifest.description? (always null) instead of a field that existed. describe requirements is added as a new subcommand; describe capabilities is extended to render manifest capabilities; describe guides output gains capabilities/requirements/critical_deps keys so agents on cold start receive full self-interrogation context.

    Constraints

    • Soft

      capability_type.nodes[] entries must reference valid node IDs declared in .ontology/core.ncl

    • Hard

      critical_dep_type.failure_impact must be a non-empty string — undocumented blast radius defeats the purpose of the type

    Alternatives considered

    • Extend tools[] with env and kind fields

      Rejected: tools[] is semantically 'dev tooling required to build'. Extending it to cover SurrealDB (a production service) or ONTOREF_ADMIN_TOKEN_FILE (an env var) would violate the field's implied meaning. A type named tool_requirement_type that has kind = 'Service is confusing. Separate types with clear names are preferable to an overloaded catch-all.

    • Add capability descriptions to Practice nodes in core.ncl

      Rejected: Practice nodes are architectural: invariant=true nodes are ADR-protected and represent constraints future contributors must follow. Adding 'what does this offer to end users' to the invariant graph would force every capability description through the ADR lifecycle. capabilities[] is per-project, evolves freely, and belongs to the manifest (the operational layer), not the ontology (the architectural layer).

    • Merge critical_deps into requirements with an is_critical flag

      Rejected: The concern is different: requirements are prerequisites (the system cannot start without them). critical_deps are runtime load-bearing with a documented blast radius. A requirement that is optional (required = false) can still be a critical dep. The is_critical flag on requirement_type would blur this distinction and make failure_impact logically optional (not required for non-critical items) — creating a type that is partially applicable based on a flag, which is a code smell in typed schemas.

    +
    diff --git a/site/site/public/adr/adr-010/index.html b/site/site/public/adr/adr-010/index.html new file mode 100644 index 0000000..246755d --- /dev/null +++ b/site/site/public/adr/adr-010/index.html @@ -0,0 +1,33 @@ + + + +Protocol Migration System — Progressive NCL Checks for Consumer Project Upgrades · Ontoref ADR + + +
    +
    + Accepted + adr-010 · 2026-03-28 +

    Protocol Migration System — Progressive NCL Checks for Consumer Project Upgrades

    +
    + +

    Context

    As the ontoref protocol evolved (manifest.ncl self-interrogation, typed ADR checks, CLAUDE.md agent entry-point, justfile convention), the adoption tooling relied on static prompt templates with manual {placeholder} substitution. An agent or developer adopting ontoref had no machine-queryable way to know which protocol features were missing from their project, nor how to apply them in a safe, ordered sequence. The template approach produced four separate documents that drifted out of sync with the actual protocol state and required human judgement to determine which ones applied. There was no idempotency guarantee and no check mechanism — a project that had already applied a change would re-read instructions that no longer applied.

    Decision

    Protocol upgrades for consumer projects are expressed as ordered NCL migration files in reflection/migrations/NNN-slug.ncl. Each migration declares: id (zero-padded 4-digit string), slug, description, a typed check record (FileExists | Grep | NuCmd), and an instructions string interpolated at runtime with project_root and project_name. Applied state is determined solely by whether the check passes — there is no state file. This makes migrations fully idempotent: running `migrate list` on an already-compliant project shows all applied with no side effects. NuCmd checks must be valid Nushell (no bash &&, $env.VAR not $VAR, no bash redirects). Grep checks targeting ADR files must use the glob pattern adrs/adr-[0-9][0-9][0-9]-*.ncl to exclude infrastructure files (adr-schema.ncl, adr-constraints.ncl, _template.ncl) that legitimately contain deprecated field names as schema definitions. The system is exposed via `ontoref migrate list`, `migrate pending`, and `migrate show <id>` — wired into the interactive group dispatch and help system. Migrations are advisory: the system reports state, never applies changes automatically.

    Constraints

    • Hard

      Any change to templates/, reflection/schemas/*.ncl, .claude/CLAUDE.md, or consumer-facing reflection/modes/ that consumer projects need to adopt must be accompanied by a new migration in reflection/migrations/

    • Hard

      NuCmd check cmd fields must be valid Nushell — no bash operators (&&, ||, 2>/dev/null), no $VARNAME (must be $env.VARNAME)

    • Soft

      Grep checks targeting ADR files must scope to adrs/adr-[0-9][0-9][0-9]-*.ncl, not adrs/ or adrs/adr-*.ncl

    Alternatives considered

    • Single monolithic adoption prompt template with {placeholder} substitution

      Rejected: Produced four separate documents (project-full-adoption-prompt.md, update-ontology-prompt.md, manifest-self-interrogation-prompt.md, vendor-frontend-assets-prompt.md) that drifted out of sync. Required manual judgement to determine which applied to a given project. No idempotency, no machine-queryable state, no ordered application guarantee. Each new protocol feature required updating multiple templates.

    • State file recording applied migration IDs

      Rejected: State files become stale on branch switches, cherry-picks, and fresh clones. They require commit discipline to keep in sync. A project where someone manually applied the changes without running the migration tool would show the migration as pending despite being satisfied — false negatives. The check-as-truth model has no false negatives by construction.

    • Jinja2/j2 templating for instruction rendering

      Rejected: The ontoref runtime already runs Nushell for all automation. Adding a j2 dependency for template rendering introduces a new tool to install, configure, and maintain. Runtime string interpolation in Nushell (str replace --all) is sufficient for the two substitution values needed (project_root, project_name) and keeps the migration runner dependency-free.

    +
    diff --git a/site/site/public/adr/adr-011/index.html b/site/site/public/adr/adr-011/index.html new file mode 100644 index 0000000..82737b5 --- /dev/null +++ b/site/site/public/adr/adr-011/index.html @@ -0,0 +1,33 @@ + + + +Mode Guards and Convergence — Active Partner and Refinement Loop in the Mode Schema · Ontoref ADR + + +
    +
    + Accepted + adr-011 · 2026-03-30 +

    Mode Guards and Convergence — Active Partner and Refinement Loop in the Mode Schema

    +
    + +

    Context

    Reflection modes executed procedures as typed DAGs but lacked two capabilities that caused real failures: (1) modes could run against projects in invalid states — missing ontology files, unavailable tools, incomplete manifests — because preconditions were informational text, not executable checks. An agent following the protocol would execute sync-ontology on a project without core.ncl and get an opaque nickel error instead of a clear block. (2) Modes like sync-ontology require iteration — scan, diff, propose, apply, then verify that drift is zero. If drift remained after one pass, the mode reported success and the agent moved on. There was no mechanism for the protocol to say 'keep going until this condition is met'. Both gaps were identified during a systematic comparison against 45 augmented coding patterns (lexler.github.io/augmented-coding-patterns): Active Partner (#1) requires the system to push back on invalid actions, and Refinement Loop (#36) requires iteration until convergence. A separate action subsystem (ext/action) was considered and rejected in favor of extending the existing mode schema.

    Decision

    Extend reflection/schema.ncl with two new optional fields on ModeBase: guards (Array Guard) for pre-flight executable checks, and converge (Converge) for post-execution convergence loops. Guards run before any step and can Block (abort) or Warn (continue with message). Converge evaluates a condition command after all steps complete and re-executes failed or all steps up to max_iterations times. Both are backward-compatible — all existing modes export unchanged with guards defaulting to [] and converge being optional.

    Constraints

    • Hard

      reflection/schema.ncl exports a Guard type with id, cmd, reason, and severity fields

    • Hard

      reflection/schema.ncl exports a Converge type with condition, max_iterations, and strategy fields

    • Hard

      The mode executor in reflection/nulib/modes.nu evaluates guards before executing steps

    Alternatives considered

    • ext/action — separate action contract schema with gates, events, and convergence

      Rejected: Creates a parallel execution abstraction competing with modes. Consumer projects would need to learn both modes and actions, and the boundary between them would be ambiguous. The mode schema already has steps, dependencies, and error strategies — guards and converge are natural extensions of the same concept.

    • Executable preconditions — make existing preconditions[] run commands instead of being text

      Rejected: Preconditions serve a different purpose: they document what the human should verify. Making them executable would lose the documentation function. Guards are a separate concept: machine-checked pre-flight blocks. Both can coexist — preconditions for humans, guards for machines.

    • External convergence via Vapora workflows — let the orchestrator handle iteration

      Rejected: Convergence is a property of the mode itself, not of the orchestrator. sync-ontology should declare that it iterates until zero drift regardless of whether it runs via CLI, Vapora, or CI. Pushing this to the orchestrator means every orchestrator must know which modes need iteration and under what conditions — that knowledge belongs in the mode contract.

    +
    diff --git a/site/site/public/adr/adr-012/index.html b/site/site/public/adr/adr-012/index.html new file mode 100644 index 0000000..04278e4 --- /dev/null +++ b/site/site/public/adr/adr-012/index.html @@ -0,0 +1,33 @@ + + + +Domain Extension System — Bash-Layer Dispatch for repo_kind-Conditional CLI Domains · Ontoref ADR + + +
    +
    + Accepted + adr-012 · 2026-04-05 +

    Domain Extension System — Bash-Layer Dispatch for repo_kind-Conditional CLI Domains

    +
    + +

    Context

    Consumer projects have project-type-specific CLI needs that ontoref's generic command set cannot satisfy. A PersonalOntology project has CFP pipelines, career schemas, and opportunity tracking that no other repo_kind needs. A DevWorkspace project has workspace cards, cluster state dimensions, and production-readiness gates. These capabilities lived as local scripts (scripts/jpl.nu) — invisible to ontoref's help system, absent from describe capabilities, and not portable to other PersonalOntology projects. The problem had two layers: (1) how to conditionally load domain-specific Nu commands without breaking the existing dispatcher, and (2) how to make domain commands discoverable (help, describe capabilities) and aliasable (ore prov, personal state). Nu's module system (`use`, `source`, `overlay use`) is compile-time: paths must be known at parse time and `overlay use` creates an isolated namespace that cannot extend `def main` in the existing dispatcher. Runtime loading of arbitrary Nu modules is architecturally impossible in the current Nu model.

    Decision

    Implement a bash-layer domain dispatch system. The ontoref bash wrapper (install/ontoref-global) resolves the first CLI argument against $ONTOREF_ROOT/domains/{arg}/repo_kinds.txt before delegating to the Nu dispatcher. If the argument matches a domain directory name (or a registered alias from domains/aliases.txt) AND the current project's repo_kind appears in that domain's repo_kinds.txt, dispatch directly to nu domains/{id}/commands.nu passing remaining args. Each domain ships three files: domain.ncl (NCL contract declaring commands, pages, repo_kinds, short_alias), commands.nu (Nu script with def main [...args] entry point), and repo_kinds.txt (plain-text list of matching repo_kind values, grep-readable by the bash wrapper without running nickel). install.nu copies the entire domains/ tree to $data_dir/domains/ and generates domains/aliases.txt mapping short_alias → domain_id. Short aliases also create standalone bin wrappers at $bin_dir/alias.

    Constraints

    • Hard

      Every domain directory under $ONTOREF_ROOT/domains/{id}/ must contain domain.ncl, commands.nu, and repo_kinds.txt

    • Hard

      commands.nu must declare def main [...args: string] as its entry point — no dynamic use/source calls inside Nu scripts

    Alternatives considered

    • Nu overlay use for runtime domain loading

      Rejected: overlay use creates an isolated namespace — commands defined in an overlayed module cannot be called by name in the parent scope. It is also parse-time in module context. Confirmed broken: nu -c 'use FILE *; command args' causes infinite recursion when called from def main.

    • Add domain commands directly to reflection/bin/ontoref.nu

      Rejected: Would require hardcoding every domain's commands in the main dispatcher, or using dynamic path strings in `use` which Nu forbids. Also violates the no-enforcement axiom — the main dispatcher should not know about PersonalOntology specifics.

    • Project-local scripts/ (scripts/jpl.nu approach)

      Rejected: Invisible to ore help and ore describe capabilities. Not portable across PersonalOntology projects. Namespace requires prefix (jpl cfp vs cfp). Dispatch requires knowing the script path.

    +
    diff --git a/site/site/public/adr/adr-013/index.html b/site/site/public/adr/adr-013/index.html new file mode 100644 index 0000000..8358ad1 --- /dev/null +++ b/site/site/public/adr/adr-013/index.html @@ -0,0 +1,33 @@ + + + +VCS Abstraction Layer — Uniform jj/git API via vcs.nu · Ontoref ADR + + +
    +
    + Accepted + adr-013 · 2026-04-07 +

    VCS Abstraction Layer — Uniform jj/git API via vcs.nu

    +
    + +

    Context

    ontoref modules that interact with version control (opmode.nu, git-event.nu, jjw.nu, init-repo.nu) historically hardcoded ^git subcommands. As jj adoption grows among contributors and orchestration projects (vapora), hardcoded git calls produce silent failures: jj repos have .jj/ but may lack a .git/ HEAD in expected locations, and jj semantics differ (the working copy is always a commit, @- is the parent, `jj file show` replaces `git show HEAD:path`). The dual-VCS problem had two layers: (1) detection — which VCS is active in a given project root, and (2) semantics — same logical operation (show last committed state, restore a file, get remote URL) expressed differently per VCS. Spreading detection logic across modules produces duplication and makes future VCS additions (e.g. Pijul, Sapling) a multi-file change.

    Decision

    Introduce reflection/modules/vcs.nu as the single VCS abstraction layer. It exports: detect (returns 'jj' | 'git' | 'none' via filesystem check — .jj/ presence), is-repo, show-committed (jj: `jj file show -r @-`; git: `git show HEAD:path`), restore-file (jj: `jj restore --from @-`; git: `git checkout --`), remote-url (jj: `jj git remote list`; git: `git remote get-url origin`), current-branch (jj: `jj log -r @ --no-graph -T bookmarks`; git: `git branch --show-current`), uncommitted-files (jj: `jj diff --summary -r @`; git: `git status --porcelain`), commit-count (jj: `jj log --no-graph -T '' | lines | length`; git: `git rev-list --count HEAD`). All ontoref modules must import vcs.nu and call these exports — direct ^git or ^jj subprocess calls inside modules are prohibited. jj and rad are not listed as requirements in ontoref's manifest: they are opt-in tools whose requirements belong in orchestration projects that depend on them.

    Constraints

    • Hard

      All VCS subprocess calls in reflection/modules/ and reflection/bin/ must go through vcs.nu exports — no direct ^git or ^jj calls outside vcs.nu itself

    • Hard

      jj and rad must not appear as required = true entries in .ontology/manifest.ncl requirements[]

    Alternatives considered

    • Env var ONTOREF_VCS to select backend

      Rejected: Creates mutable state that can desync from the actual repo state. A repo cloned fresh has no env var set; a contributor switching between git and jj repos would need to update the env var manually. Filesystem detection is always correct without configuration.

    • Per-module inline detection (duplicate detect logic in each file)

      Rejected: Already the de-facto state before vcs.nu. Duplicated detection means any change to jj semantics (e.g. a jj CLI flag change) requires hunting every module. The abstraction cost is one import line per module.

    • Wrap the entire CLI in a shim that translates git commands to jj

      Rejected: Shim-layer translation is fragile — git and jj command surfaces are not isomorphic (jj has no git stash equivalent; jj describe vs git commit -m). The operations ontoref needs are a small, well-defined set; a typed Nu module is a cleaner contract than a command-translation shim.

    +
    diff --git a/site/site/public/adr/adr-014/index.html b/site/site/public/adr/adr-014/index.html new file mode 100644 index 0000000..ee98131 --- /dev/null +++ b/site/site/public/adr/adr-014/index.html @@ -0,0 +1,33 @@ + + + +Runtime Service Toggles — AtomicBool Flags for MCP and GraphQL · Ontoref ADR + + +
    +
    + Accepted + adr-014 · 2026-04-26 +

    Runtime Service Toggles — AtomicBool Flags for MCP and GraphQL

    +
    + +

    Context

    ontoref-daemon exposes optional services (MCP, GraphQL) compiled in via Cargo feature flags. Once compiled, these services were always active for the lifetime of the process. Two scenarios require disabling them at runtime without restart: (1) security incident — temporarily disable a surface while investigating a compromise; (2) operator choice — enable graphql during a debug session, disable it in production. The alternative of restarting the daemon is disruptive because it drops all in-memory sessions, actors, and notification queues. Compile-time feature flags solve the binary presence problem but cannot address runtime availability.

    Decision

    Introduce ServiceFlags (pub struct in api.rs) holding one AtomicBool per toggleable service, gated by the corresponding feature flag. ServiceFlags::new() initialises all flags to true (enabled). AppState holds Arc<ServiceFlags> shared across all clones. The toggle check lives in a route_layer middleware on the sub-router for each service — not inside the service handlers themselves — so the check is enforced regardless of which handler a request reaches. Two toggle surfaces are provided: (1) REST API: PUT /api/services/:service {"enabled": bool} — daemon admin Bearer required; (2) UI: POST /ui/manage/services/:service/toggle — AdminGuard (cookie session). Both surfaces return the new state. The manage page shows each compiled-in service with an HTMX toggle button; the navbar badges reflect runtime state on every page render.

    Constraints

    • Hard

      ServiceFlags::new() must initialise all AtomicBool flags to true — a compiled-in service is always enabled at startup

    • Hard

      Any new optional service added to the daemon must add an AtomicBool to ServiceFlags and a route_layer toggle middleware on its sub-router

    • Soft

      Service toggle endpoints require daemon admin credentials — project-level auth is insufficient

    Alternatives considered

    • Restart daemon with different feature flags or config

      Rejected: Restart drops SessionStore (all active logins), actor registry, notification queue, and NATS subscriptions. A 1-second outage is acceptable for planned maintenance but not for rapid incident response.

    • RwLock<bool> per service in AppState

      Rejected: RwLock introduces lock contention on every request. The toggle check does not need mutual exclusion with writes — a store and a load never run concurrently in a way that would corrupt state. Relaxed AtomicBool is sufficient and faster.

    • Dynamic axum Router rebuild — swap out the sub-router entirely

      Rejected: axum Router is not live-rebuildable without replacing the entire tower Service. This would require Arc<RwLock<Router>>, a custom Service wrapper, and would still incur a lock per request. The middleware approach achieves the same result with orders of magnitude less complexity.

    +
    diff --git a/site/site/public/adr/adr-015/index.html b/site/site/public/adr/adr-015/index.html new file mode 100644 index 0000000..6735ac3 --- /dev/null +++ b/site/site/public/adr/adr-015/index.html @@ -0,0 +1,33 @@ + + + +MCP Tool Catalog via #[onto_mcp_tool] Proc-Macro + Inventory · Ontoref ADR + + +
    +
    + Accepted + adr-015 · 2026-04-26 +

    MCP Tool Catalog via #[onto_mcp_tool] Proc-Macro + Inventory

    +
    + +

    Context

    ontoref-daemon exposes 33 MCP tools via the rmcp ToolRouter in crates/ontoref-daemon/src/mcp/mod.rs. The authoritative tool implementation lives in each tool struct's `ToolBase`/`AsyncTool` impls (name, description, schema). However, the agent-facing tool catalog returned by `ontoref_help` was a hand-typed JSON literal (~100 lines, mod.rs:1129-1226) duplicating every tool's name, description, and parameter shape. This is the same failure mode ADR-007 documents as the original motivation for `#[onto_api]`: the previous-session bug where `insert_mcp_ctx` listed 15 tools while the router had 27. ADR-007 fixed that drift for the HTTP API surface (inventory-collected `ApiRouteEntry`) but did not extend the pattern to MCP. As of 2026-04-26 the manifest claim 'daemon exposes 33 MCP tools' is also a hand-maintained string in `.ontology/manifest.ncl`. With the rate of MCP tool additions through 2026-Q1 (qa, bookmarks, actions, config, ontology extensions all added in separate sessions), drift was becoming inevitable.

    Decision

    Every MCP tool struct in ontoref-daemon must carry `#[onto_mcp_tool(name, description, category, params)]`. The proc-macro (in `crates/ontoref-derive`) emits `inventory::submit!(ontoref_ontology::McpToolEntry{...})` at link time and leaves the annotated struct unchanged — the existing `ToolBase` and `AsyncTool` impls are untouched. A new pure function `ontoref_daemon::mcp::catalog()` walks `inventory::iter::<McpToolEntry>()`, sorts by name, and returns `Vec<&'static McpToolEntry>`. `HelpTool::invoke` now serializes `catalog()` instead of holding a hand-typed JSON literal. `McpToolEntry` lives in `ontoref-ontology` next to `ApiRouteEntry` and reuses `ApiParam` for parameter metadata — both are protocol surfaces, the type is generic. `tool_router()`'s compile-time `with_async_tool::<T>()` list is left as-is; the Rust type system requires the type list at compile time and a separate macro architecture would be needed to derive it from inventory (out of scope for this ADR).

    Constraints

    • Hard

      Every MCP tool struct in ontoref-daemon must carry #[onto_mcp_tool(...)]

    • Soft

      The number of #[onto_mcp_tool] annotations must equal the number of with_async_tool::<T>() calls in tool_router()

    Alternatives considered

    • Generate the help JSON from ToolBase::name() + description() directly via reflection

      Rejected: ToolBase exposes name and description but not parameter metadata in a structured form (input_schema returns a JsonObject blob, not the per-param required/values/note shape that ontoref_help emits). Walking the JSON schema and reverse-engineering the per-param hints is brittle. The inventory entry lets us declare the agent-facing param documentation in a stable, typed shape next to the ToolBase impl.

    • Replace tool_router()'s with_async_tool::<T>() list with macro-driven iteration over inventory

      Rejected: rmcp's ToolRouter requires the tool type at compile time (generic associated types over each tool's Parameter/Output types). Driving registration from inventory entries — which are runtime values of `&'static McpToolEntry` — would require either a build.rs that emits the registration list or a macro that takes the full type list as input. Either is feasible but is a larger architectural change with no immediate reliability win beyond what the inventory already provides for the help/catalog surface. Deferred.

    • Skip the macro and write an `inventory::submit!` block manually next to each tool

      Rejected: Eliminates the macro infrastructure but loses the validation that #[onto_mcp_tool] applies (key spelling check, param string parsing reuse). Manual blocks also bypass the file!() capture for source-file traceability that ApiRouteEntry already uses.

    +
    diff --git a/site/site/public/adr/adr-016/index.html b/site/site/public/adr/adr-016/index.html new file mode 100644 index 0000000..34390a1 --- /dev/null +++ b/site/site/public/adr/adr-016/index.html @@ -0,0 +1,33 @@ + + + +Component Lift-Out Pattern: Four-Criterion Gate for Standalone Extraction · Ontoref ADR + + +
    +
    + Accepted + adr-016 · 2026-05-01 +

    Component Lift-Out Pattern: Four-Criterion Gate for Standalone Extraction

    +
    + +

    Context

    Ontoref itself was extracted from stratumiops (ADR-001). The same pattern recurred in the provisioning project: buildkit-launcher (build substrate) and backup-manager (backup orchestration) grew inside provisioning but serve a broader class of consumers — vapora, workspace infras, CI pipelines, and third-party projects. No formal criterion existed for deciding when a component is ready to become a standalone peer project. Without a criterion, the decision is arbitrary and either premature (decomposition overhead before consumer plurality) or delayed (host coupling accretes, extraction becomes expensive).

    Decision

    A component is extracted as a standalone peer project when it passes all four criteria: (1) Orthogonal concern — the component's core domain is not the host project's core domain; (2) Consumer plurality — at least two distinct callers exist or are immediately planned; (3) Release cadence divergence — the component's evolution is not gated by the host project's release cycle; (4) Config path-agnostic — the component can receive its config from any caller without importing host infrastructure (workspace crates, host config loaders, host schema registries). The extracted project registers in ontoref before any code moves. The host project retains extension-side artifacts (schemas, defaults, component declarations) that allow workspace infras to declare directives for the extracted tool.

    Constraints

    • Hard

      An extracted project must have .ontology/core.ncl, .ontology/state.ncl, and adrs/adr-001 committed before any code from the host is moved

    • Hard

      The extracted project must not import workspace crates, config loaders, or schema registries from the host project

    Alternatives considered

    • Extract only when a second consumer exists and requests it

      Rejected: Reactive extraction means coupling has already accreted. By the time a second consumer requests the component, host infrastructure imports are deep. Proactive evaluation at the four-criterion gate is cheaper than reactive untangling.

    • Keep all tools inside provisioning as internal workspace crates, expose via provisioning CLI

      Rejected: This routes all callers through provisioning's release cycle and binary. vapora and workspace CI pipelines cannot use the tool without depending on provisioning. The four-criterion test exists precisely to identify when this constraint becomes incorrect.

    • Publish extracted crates to crates.io immediately

      Rejected: Published crates require stable API contracts before the consumer relationship is proven. Path-based standalone project references allow simultaneous development of the extracted project and its consumers without publication ceremony.

    +
    diff --git a/site/site/public/adr/adr-017/index.html b/site/site/public/adr/adr-017/index.html new file mode 100644 index 0000000..0ebbc53 --- /dev/null +++ b/site/site/public/adr/adr-017/index.html @@ -0,0 +1,33 @@ + + + +Registry Credential Vault Model: src-vault, Multi-Recipient sops, and Actor-Scoped Access · Ontoref ADR + + +
    +
    + Accepted + adr-017 · 2026-05-01 +

    Registry Credential Vault Model: src-vault, Multi-Recipient sops, and Actor-Scoped Access

    +
    + +

    Context

    The provisioning registry (zot OCI) became the primary coordination hub for domain and mode artifacts (ADR-016 consequences, migration 0015). This introduced registry credentials as a first-class concern: every project that pushes or pulls artifacts needs credentials, and those credentials must be distributed across actors (developers, CI pipelines, the ontoref daemon, AI agents, ops tooling) with different access levels. The naive model — environment variables or ambient ~/.docker/config.json — has three compounding failure modes: (1) credentials are ambient and unscoped, so an operation against project A can use credentials that belong to project B; (2) there is no distribution mechanism — adding a new team member or CI key requires manual redistribution of a shared secret; (3) there is no audit trail for credential access or changes. A supply-chain attack via misconfigured registry namespace endpoints is the concrete risk: if the daemon proxied registry calls, any actor with MCP access could redirect them to an arbitrary endpoint using ambient credentials.

    Decision

    Registry credentials are managed through a per-project src-vault stored as an OCI artifact in the same ZOT registry it protects. The src-vault uses sops with age multi-recipient encryption: each actor role has its own age keypair, and credential files are encrypted for the exact set of recipients that need access. The vault backend (restic or kopia) handles versioning and local copies; the ZOT registry handles distribution. Each project has a local access.sops.yaml in ~/.config/ontoref/vaults/<project-slug>/ containing three fields: zot_username, zot_password, and vault_key. All three are encrypted by the actor's master age private key (.kage), which lives at an external path the actor controls (hardware key, encrypted disk, or declared path in config.ncl) — never inside the vault directory. At operation time, sops decrypts access.sops.yaml in memory: zot_username and zot_password are used to pull the src-vault OCI artifact via a DOCKER_CONFIG tmpdir that is deleted immediately after; vault_key is passed as RESTIC_PASSWORD or KOPIA_PASSWORD env var for the duration of the vault operation and never written to disk. No plaintext credential of any kind persists beyond the operation scope. Credential resolution runs exclusively in the ontoref CLI — the daemon is structurally excluded. All oras invocations use an isolated DOCKER_CONFIG tmpdir; no ambient ~/.docker/config.json is consulted. Access logs are appended to a jsonl file stored as a layer in the same src-vault OCI artifact and mirrored locally in ~/.config/ontoref/vaults/<project-slug>/logs/access.jsonl.

    Constraints

    • Hard

      The credentials required to pull the src-vault OCI artifact from ZOT must be stored outside the vault itself — each src-vault has its own access credentials, held in the actor's local .kage, not inside any vault it protects

    • Hard

      RegistryEntry must not use credential_env — only credential_sops or credential_oidc are valid credential reference fields

    • Hard

      Every *.sops.yaml credential file must include at minimum the admin and the bound_actor recipients for its access class

    • Hard

      Any domain or mode that pushes or pulls from a registry must declare uses_registry referencing the RegistryEntry id in manifest.ncl

    • Soft

      All sops operations on registry credential files must go through ore secrets — direct sops invocations bypass lock state and audit log

    • Hard

      Every oras invocation must use DOCKER_CONFIG pointing to a tmpdir containing only the credential for the target registry endpoint — no ambient ~/.docker/config.json

    • Hard

      vault_key must never be written to disk in plaintext — it is decrypted from access.sops.yaml into RESTIC_PASSWORD or KOPIA_PASSWORD for the duration of the operation only

    • Soft

      All vault snapshot operations must use vault-backend.nu — direct restic or kopia invocations are not permitted in recipes or modules

    • Hard

      Every push of src-vault/<project>:latest to ZOT must produce a cosign signature; every pull must verify the signature before the artifact is trusted

    Alternatives considered

    • Environment variables (REGISTRY_TOKEN, DOCKER_CONFIG) per CI job

      Rejected: Visible in process list, inherited by subprocesses, logged by CI systems, cannot be scoped to a specific registry endpoint. No revocation without rotating all consumers. The primary attack surface for supply-chain credential leakage.

    • Shared age keyring — one private key distributed to all developers

      Rejected: Revocation requires rotating the shared key and redistributing to all remaining members — coordination cost scales with team size. No per-actor audit trail. A leaked key compromises all actors simultaneously.

    • HashiCorp Vault or similar external secrets manager

      Rejected: Introduces a network dependency for every credential resolution. Requires operating a separate service with its own HA, backup, and auth model. The OCI registry is already the coordination hub — using it as the vault distribution backend reuses existing infrastructure and auth.

    • Daemon resolves credentials on behalf of CLI actors

      Rejected: The daemon is a long-lived process accessible to multiple actors (developer, agent, CI via MCP). Giving it credential resolution capability means any actor with daemon access can trigger registry operations using credentials they do not personally hold. The structural exclusion of the daemon from credential resolution is a load-bearing architectural property.

    • Single credential file per registry (no RO/RW split)

      Rejected: A single credential with RW access distributed to read-only actors (cdci, ontoref, agent) violates least-privilege. A compromised CI pipeline with RW credentials can push malicious artifacts. The RO/RW split means a compromised read-only credential cannot alter the artifact namespace.

    +
    diff --git a/site/site/public/adr/adr-018/index.html b/site/site/public/adr/adr-018/index.html new file mode 100644 index 0000000..913d56e --- /dev/null +++ b/site/site/public/adr/adr-018/index.html @@ -0,0 +1,33 @@ + + + +Level Hierarchy and Mode Resolution Strategy — Observable Boundary Traversal · Ontoref ADR + + +
    +
    + Accepted + adr-018 · 2026-05-01 +

    Level Hierarchy and Mode Resolution Strategy — Observable Boundary Traversal

    +
    + +

    Context

    ADR-012 introduced a domain extension system that enables project-specific specialization of ontoref. In practice this created a three-level hierarchy: (1) ontoref base — generic protocol and reflection operations; (2) project domain — specialization of ontoref for a specific project type (provisioning, personal, ...); (3) domain instance — a concrete project derived from a domain (a workspace, an infra, a team ontoref). The hierarchy was not formalized: no level declares its identity, no mode declares whether its implementation is complete or delegates to the level above, and no mechanism exists to determine which level answered a given operation. The result is implicit traversal — a caller invoking 'build-docs' has no way to know whether the operation ran at level 1, 2, or 3, whether it merged contributions from multiple levels, or whether it silently fell through to the base because the domain had not implemented it yet. The discussion that produced this ADR also identified that resolution strategies cannot be uniform: the correct strategy depends on the mode, the project context, and the adoption phase. A domain in early adoption may Delegate all documentation modes to the base; the same domain at maturity Overrides them with project-specific implementations. The transition between strategies must itself be observable — crossing a level boundary is an architectural event, not an implementation detail.

    Decision

    Formalize the three-level hierarchy with four mechanisms: (1) Level identity declaration — each ontoref instance declares level.index (1=base, 2=domain, 3=instance), level.name, and level.parent (name of the level above; absent at level 1) in manifest.ncl. This makes level identity explicit and queryable. (2) Per-mode resolution strategy — each reflection mode declares a strategy field using one of four values: 'Override (implementation is complete at this level, traversal stops), 'Delegate (no implementation here, traverse to parent), 'Merge (accumulate fields bottom-up across all levels; lower level wins on conflicts), 'Compose (declare explicit partial inheritance via an extends field naming specific steps or fields to inherit from the parent). Strategy is required at level 2+; absent at level 1 (base is always Override by definition). Implicit absence at level 2+ is treated as 'Delegate with a Soft validation warning. (3) FSM-bound strategy transitions — when a mode's strategy is expected to change as the project matures, declare a state dimension in state.ncl whose current_state tracks the strategy value. The transition from 'Delegate to 'Override (or any other pair) is then an observable FSM event: tracked in state.ncl, visible in ore describe state, and triggerable by the same transition conditions as any other dimension. Strategy changes are architectural events, not silent refactors. (4) Observable traversal via ore mode resolve — the command 'ore mode resolve <id>' reports which level answered the operation, the strategy applied, the source file, and the reason (declared strategy or FSM state). No mode resolution is ever silent.

    Constraints

    • Hard

      Any ontoref instance at level 2 or 3 must declare level.index, level.name, and level.parent in manifest.ncl

    • Soft

      Every reflection mode at level 2+ must declare strategy explicitly; implicit absence is a Soft violation

    • Hard

      No Delegate chain may terminate without an Override at some level — every mode invocation must resolve to a concrete implementation

    • Soft

      If state.ncl declares a dimension for a mode's strategy, its current_state must match the strategy field declared in the mode NCL

    • Hard

      A mode with strategy = 'Compose must declare an extends field; every step or field reference in extends must exist in the named parent mode

    Alternatives considered

    • Implicit inheritance — current state, no declaration required

      Rejected: Makes level traversal invisible. Coupling is undiagnosable. Adoption phase is unknown. Boundary crossing is unobservable. This is the state that produced the architectural confusion this ADR resolves.

    • Global strategy per level — one strategy applies to all modes at a given level

      Rejected: Domains specialize incrementally. A global Override for level 2 blocks early adoption (all modes must be implemented before any work). A global Delegate for level 2 makes domains invisible (nothing is ever implemented). Per-mode strategy reflects the real adoption curve.

    • Override-only — levels either implement fully or do not appear

      Rejected: Eliminates phased adoption. A domain cannot exist in the system until it has implemented every mode — a steep entry cost that contradicts ADR-001 (voluntary coherence, no enforcement). Delegate and Compose are specifically needed for incremental adoption.

    • Separate level.ncl file instead of level field in manifest.ncl

      Rejected: manifest.ncl already holds structural self-description (requirements, capabilities, registry topology, layers). Level identity is structural metadata of the same kind. A separate file adds coordination overhead (two files to keep in sync, two files for describe to read) for no additional expressiveness.

    +
    diff --git a/site/site/public/adr/adr-019/index.html b/site/site/public/adr/adr-019/index.html new file mode 100644 index 0000000..3b481ce --- /dev/null +++ b/site/site/public/adr/adr-019/index.html @@ -0,0 +1,33 @@ + + + +Per-File Recipient Routing for Tenant Isolation in lieu of Multi-Vault · Ontoref ADR + + +
    +
    + Accepted + adr-019 · 2026-05-03 +

    Per-File Recipient Routing for Tenant Isolation in lieu of Multi-Vault

    +
    + +

    Context

    ADR-017 established per-project credential vaults as OCI artifacts in ZOT, encrypted with sops + age multi-recipient. The model held one recipient set per vault: every actor who could decrypt access.sops.yaml could decrypt every credential file inside the vault. Real projects (libre-wuji, the canonical multi-tenant example) need stronger separation — a single project hosts multiple clients with distinct services, AI agents that operate with restricted access, and developer/admin roles that occasionally overlap. Three concrete failure modes appear: (1) credential-of-clientB is visible to anyone who can open the vault even when only working on clientA — recipient lists are coarse and indiscriminate; (2) AI agents with read-only intent receive credentials whose blast radius exceeds their declared scope; (3) blast-radius limitation requires per-file recipient sets, which the original single-set model does not express. A naive answer is multi-vault — one vault_id per tenant or per environment — but it cascades through every layer (schema, helpers, recipes, dispatcher, migration) and creates an architectural debt without justifying a real isolation requirement (separate master keys, separate restic repos) for the typical case.

    Decision

    Tenant isolation within a single project is expressed via sops creation_rules, declared in project.ncl::sops as recipient_groups (named lists of age public keys) and recipient_rules (path_regex to group-union mappings). The bootstrap recipe generates <vault_dir>/.sops.yaml from these declarations and sops natively encrypts each file with the union of declared groups. One vault_id per project remains the unit. Multi-vault is explicitly NOT implemented and remains out of scope until a project requires HARD isolation (separate master keys, separate restic repos, compliance-grade separation surviving accidental cross-decryption); such a case requires a future ADR. When recipient_rules are declared, project.ncl is the single source of truth: secrets-add-key and secrets-remove-key error out and direct the operator to edit project.ncl plus run secrets-rekey, which regenerates .sops.yaml and re-encrypts every *.sops.yaml file. Three adoption templates (single-team, multi-tenant, agent-first) ship in install/resources/templates/sops/ as copy-paste starting points; a project may adopt any pattern or none.

    Constraints

    • Hard

      Every group referenced in recipient_rules must be declared in recipient_groups

    • Hard

      A rule whose group union resolves to zero recipients is rejected at bootstrap and rekey

    • Hard

      secrets-add-key and secrets-remove-key recipes must error when project declares recipient_rules; canonical workflow is edit project.ncl + secrets-rekey

    • Hard

      Every *.sops.yaml under <vault_dir>/ must match at least one declared rule when recipient_rules is non-empty

    • Hard

      Projects must not declare a multi-vault structure (e.g. sops.vaults map). Multi-vault adoption requires a new ADR superseding or extending this one

    • Soft

      Three adoption templates (single-team, multi-tenant, agent-first) live under install/resources/templates/sops/ and are referenced by qa.ncl::credential-vault-templates

    Alternatives considered

    • Multi-vault: project.ncl::sops.vault_id (single string) becomes sops.vaults (record of named SopsConfigs)

      Rejected: Cascades through every layer (schema, helpers, recipes, dispatcher), forcing a 12-component refactor and a migration for every existing project to express what one optional schema field accomplishes via sops creation_rules. The HARD isolation it provides (separate master keys, separate restic repos) is rarely required; per-file routing covers the common case while leaving the door open for a future multi-vault ADR if a project genuinely needs filesystem-level separation.

    • Single recipient set + role-based decryption gating in helper code

      Rejected: Would require a custom layer over sops and reinvent recipient routing. sops already does it natively via creation_rules. Inventing a parallel mechanism doubles the surface and breaks composition with sops tooling (sops --decrypt, updatekeys).

    • Externalize tenant credentials to an external secret manager (e.g. HashiCorp Vault)

      Rejected: Adds an external runtime dependency that contradicts the ADR-017 invariant of self-contained, distribution-via-OCI credentials. Reasonable for projects that already operate such a system, but inappropriate as the default ontoref pattern.

    +
    diff --git a/site/site/public/adr/adr-020/index.html b/site/site/public/adr/adr-020/index.html new file mode 100644 index 0000000..fc3af46 --- /dev/null +++ b/site/site/public/adr/adr-020/index.html @@ -0,0 +1,86 @@ + + + +Three-Layer Model for Project Ontoref Instances · Ontoref ADR + + +
    +
    + Proposed + adr-020 · 2026-05-03 +

    Three-Layer Model for Project Ontoref Instances

    +
    + +

    Context

    Multiple projects now adopt the ontoref protocol (lian-build is the explicit +external case under bl-002 / bl-008). Field experience across ontoref + +lian-build + provisioning shows that an ontoref-onboarded project's +repository carries TWO distinct ontoref-shaped layers, while a third layer +exists outside the project (in caller repositories). Without codification, +adopters re-derive the model per project, mix layers accidentally, and lose +boundaries silently:

    +

    - Layer 3 content (caller-side cabling) drifts into a project's qa.ncl, + making the FAQ a how-to-deploy guide for one specific caller. + - Layer 2 schemas live under reflection/ as 'project notes', drifting + from the binary because they're not on the contract path. + - Layer 1 architectural rationale ends up in catalog/domains/<id>/ + contract.ncl, where consumers expecting a typed shape get prose.

    +

    The model has been observed (lian-build/reflection/qa.ncl::lian-build-what- +and-why), described (ontoref/reflection/qa.ncl::ontoref-three-layer-model), +and queued for codification (bl-009). This ADR commits the model with +machine-checkable constraints. Acceptance is gated on the constraints +running clean across the existing ontoref-onboarded projects (ontoref +itself, lian-build, provisioning).

    Decision

    A project's ontoref instance has THREE distinct layers — but only the first +two live in the project's own repository:

    +

    LAYER 1 — Self-management ontoref (about the project itself) + paths .ontology/ reflection/ adrs/ + audience this project's developers and maintainers + purpose describe the project to itself — axioms, FSM dimensions, + binding decisions, open questions, accepted knowledge + presence MANDATORY on every ontoref-onboarded project

    +

    LAYER 2 — Specialized domain/mode ontoref (the integration surface) + paths schemas/ catalog/{domains,modes}/ + manifest.ncl::registry_provides + audience OTHER projects that want to integrate this project + purpose the contract surface other projects bind to — typed domain + artifacts, orchestration mode artifacts, registry-namespace + claim + presence OPTIONAL but BICONDITIONAL — a project either has all of + {schemas/, catalog/, registry_provides} or none. Half-Layer-2 + is a contract violation.

    +

    LAYER 3 — Caller-side implementations (NOT in this project) + paths <caller>/extensions/<this-project>/ + <caller>/catalog/components/<...>/ (when consuming) + <workspace>/infra/<ws>/integrations/ + audience operators and CI of caller projects + presence PER CALLER, NEVER in this project's repo. Cross-references + from this project to Layer 3 are explicit pointers, never + copy-paste.

    +

    The three-layer axis is ORTHOGONAL to ADR-018's level hierarchy +(Base/Domain/Instance). A project at any level may have any combination of +Layer 1 (always) and Layer 2 (sometimes). Layer 3 is the boundary outward, +not a property of the project. The 3-layer × 3-level matrix is navigable in +both axes: 'where in the protocol hierarchy' (level) is independent of +'where in the project's repo' (layer).

    +

    Cross-layer references inside a project carry an explicit layer-N tag on +qa entries. A qa entry whose primary topic is a Layer N concern wears +the layer-N tag; satellite entries (operational how-to, troubleshooting) +inherit the layer of their anchor without re-tagging.

    Constraints

    • Hard

      Every ontoref-onboarded project has .ontology/core.ncl

    • Hard

      Every ontoref-onboarded project has reflection/qa.ncl

    • Hard

      Every ontoref-onboarded project has reflection/backlog.ncl

    • Hard

      Every ontoref-onboarded project has an adrs/ directory

    • Hard

      A federated-peer catalog (catalog/domains/ or catalog/modes/) exists if and only if manifest.ncl declares registry_provides

    • Soft

      qa entries whose primary topic is a Layer N concern carry the layer-N tag (anchors only; satellites inherit by association)

    Alternatives considered

    • Single 'ontoref content' namespace, no layering

      Rejected: Observed drift outcomes (Layer 3 in qa, Layer 2 schemas treated as notes, etc.) are caused precisely by absence of layering. The single-namespace alternative is what we're correcting; rejecting it is the entire decision.

    • Two-layer model collapsing self-management with integration-surface

      Rejected: lian-build's adoption explicitly experienced the schema-vs-rationale split: schemas/build_directives.ncl (Layer 2 contract) and adrs/adr-001-lian-build-as-standalone.ncl (Layer 1 rationale) have different audiences, different change cadences, different validation rules. Collapsing them produced the FAQ-vs-contract confusion that retiring lian-build/.ontology/FAQ.md addressed. The two-layer alternative re-creates that confusion.

    • Make Layer 3 (caller-side) optionally co-resident with Layer 2 in the producer's repo

      Rejected: Creates ambiguity about who owns the cabling. If a producer's repo carries `extensions/<self>/`, who maintains it when a caller's workspace evolves? The producer doesn't know about the caller's infrastructure; the caller can't depend on the producer to update the cabling on its schedule. Co-residence inverts the integration arrow.

    • Numbered layers (Layer 1 / 2 / 3) vs named layers ('self' / 'integration-surface' / 'caller-side')

      Rejected: Named layers are more descriptive but verbose; numbered layers are more precise but flat. The compromise (this ADR): use 'Layer N — descriptive name' in prose, 'layer-N' in tags. Tag economy wins; prose retains the descriptive name.

    +
    diff --git a/site/site/public/adr/adr-021/index.html b/site/site/public/adr/adr-021/index.html new file mode 100644 index 0000000..4519f7a --- /dev/null +++ b/site/site/public/adr/adr-021/index.html @@ -0,0 +1,33 @@ + + + +Auth and UI Lift-Out: ontoref-daemon consumes ontoref-auth + ontoref-ui; JWKS and Introspect Exposed for SSO · Ontoref ADR + + +
    +
    + Accepted + adr-021 · 2026-05-12 +

    Auth and UI Lift-Out: ontoref-daemon consumes ontoref-auth + ontoref-ui; JWKS and Introspect Exposed for SSO

    +
    + +

    Context

    The daemon historically owned its own session store, Argon2id password hashing, Tera template engine init, and cookie/Bearer extraction logic. Lian-build re-implemented the same surface independently. With ontoref-auth and ontoref-ui shipping as standalone foundation siblings (ADR-016 lift-out pattern), every daemon-internal copy is duplicate code maintained out of band: bug fixes, hardening, and Ed25519 token issuance for SSO had to be re-applied to each consumer. Federation across the family also required a common token vocabulary (issuer, kid, audience) that no daemon had so far. Without consolidation, the SSO mode that lets multiple panels share authentication remains unimplementable.

    Decision

    Migrate ontoref-daemon to depend on `ontoref-auth` (path dep, axum+persist features) and `ontoref-ui` (path dep, gated behind the existing `ui` feature). Re-export `Role` and `KeyEntry` from `ontoref_auth` through `crate::registry`; rewrite `crate::session` as a thin facade that preserves the historic daemon API surface (`SessionEntry`, `SessionStore`, `SessionView`, `RevokeResult`, `COOKIE_NAME`, `extract_cookie`) while delegating storage and lifecycle to `ontoref_auth::SessionStore`. Replace `tera::Tera::new(glob)` initialization with `ontoref_ui::init_tera(TeraOptions)`; `AppState.tera` now holds `Option<Arc<RwLock<ontoref_ui::TeraEnv>>>`. Daemon-specific page templates (manage.html, project_picker.html, dashboard.html, etc.) remain in `crates/ontoref-daemon/templates/` and continue to be loaded via the `template_dirs` consumer-override path. Add `TokenIssuer` (Ed25519, seed loaded from `ONTOREF_SIGNING_KEY_FILE` when set, ephemeral otherwise with WARNING) and `SignedTokenProvider` to `AppState`. Publish JWKS at `GET /.well-known/ontoref-keys.json` and introspection at `POST /.session/introspect` — both unauthenticated by SSO contract.

    Constraints

    • Hard

      ontoref-daemon must declare ontoref-auth as a path dependency with axum and persist features enabled

    • Hard

      ontoref-daemon must declare ontoref-ui as an optional path dependency, gated behind the ui feature

    • Hard

      ontoref-daemon source must not call tera::Tera::new directly — Tera initialization flows through ontoref_ui::init_tera

    • Hard

      The daemon must publish its JWKS at /.well-known/ontoref-keys.json

    • Hard

      The daemon must publish RFC 7662-shaped introspection at /.session/introspect

    Alternatives considered

    • Wholesale call-site migration to ontoref_auth::AuthUser extractor and ontoref_auth::Session

      Rejected: The daemon's AuthUser is multi-project: it extracts a Path<String> slug and validates that the session's scope matches the requested project. ontoref_auth::AuthUser is single-tenant — it resolves a credential to an Identity without slug-awareness. Replacing the daemon's extractor would require either (a) duplicating slug validation in every handler, or (b) wrapping ontoref-auth's extractor with a daemon-specific layer that re-does the slug check. Both are strictly more code than keeping the daemon's slug-aware extractor and routing it to the shared SessionStore.

    • Bridge ontoref_auth::Role and registry::Role with a daemon-local enum + From impls

      Rejected: The variants and serde representations are identical. The two-enum bridge would force every keys-overlay.json reader, every projects.ncl serializer, and every UI template that reads the role string to perform conversions. The re-export keeps the wire format identical and the type stable.

    • Embed ontoref-ui's full base.html and override only nav fragments

      Rejected: ontoref-ui's base.html does not have block hooks for the daemon's domain ontology widget, registry topology bar, or vault state row. Either ontoref-ui grows daemon-specific blocks (which violates the harmonization-first lift-out principle — A.2 explicitly keeps generic core/), or the daemon defines its own base.html that includes ontoref-ui's partials piece by piece. The full-override path the daemon already takes is simpler and preserves the exact pre-migration rendering.

    • Defer JWKS until the SignedTokenProvider has a key rotation policy implemented in code

      Rejected: TokenIssuer::rotate() is already implemented in ontoref-auth and exposes JwksSnapshot containing both current and previous public keys. The policy decision (when to rotate, when to forget_previous) is operational, not structural — it lives in the rotation playbook, not in the JWKS endpoint code. Shipping JWKS now with an ephemeral default unblocks SSO; the rotation policy ships as runbook content alongside the panel daemon's adoption of SSO mode.

    +
    diff --git a/site/site/public/adr/adr-022/index.html b/site/site/public/adr/adr-022/index.html new file mode 100644 index 0000000..60b7fc0 --- /dev/null +++ b/site/site/public/adr/adr-022/index.html @@ -0,0 +1,33 @@ + + + +Secret Management Operations on the MCP Surface — Observation, Orchestration, and Permanent Exclusions · Ontoref ADR + + +
    +
    + Proposed + adr-022 · 2026-05-16 +

    Secret Management Operations on the MCP Surface — Observation, Orchestration, and Permanent Exclusions

    +
    + +

    Context

    ADR-017 established that the daemon is structurally excluded from credential resolution: it has no age private key and cannot decrypt sops files. This is load-bearing — any actor with MCP access cannot use the daemon as a credential amplifier. The existing MCP surface has one secret-related tool: ontoref_vault_status (read-only metadata: vault_id, recipient_count, access_sops_present, recipient_groups). The workspace toolchain (secrets.just + provisioning/workspace/tools/secrets.nu) exposes a full secret management lifecycle: gen, new, make, list, pending, show, edit, rekey. The question arose when staging plaintext credential files (*.sops.yaml.template) was added to the workspace workflow, and when CI/CD and agent automation scenarios began requiring machine-readable discovery of pending and existing secrets. Two failure modes emerge if the wrong boundary is drawn: (1) exposing decrypt operations via MCP turns the daemon into a plaintext exfiltration channel — any actor with daemon access and the right MCP token can trigger decryption of any credential file; (2) exposing no operations at all forces agents and CI to shell out to just directly, bypassing the actor authorization model entirely and producing invisible, unaudited secret access.

    Decision

    Secret management operations on the MCP surface are partitioned into three permanent tiers. Tier 1 — Permanent Exclusions (never on MCP surface): any operation that produces plaintext credential values in a response payload (secret-show, decrypt-to-stdout, raw sops --decrypt), age private key material (.kage content or derived values), and staged plaintext template content (*.sops.yaml.template). These exclusions are structural and cannot be lifted by actor configuration or policy override. Tier 2 — Read-Only Observation (permitted for any authorized actor): ontoref_vault_status (already exists), secret names list (encrypted filenames, no values), pending secret discovery (*.sops.yaml.template filenames without corresponding *.sops.yaml). Tier 2 tools invoke the secrets engine as a read-only subprocess and return structured metadata only. Tier 3 — Orchestrated Mutating Operations (permitted only for actor=admin, requires subprocess isolation, mandatory audit log): secret-gen, secret-make, secret-new, secret-rekey. These invoke the secrets.nu subprocess with the workspace environment variables; the daemon never holds key material — the subprocess reads .kage from the filesystem path configured in the project. Every Tier 3 invocation appends an entry to the vault access log (logs/access.jsonl) recording: actor, operation, secret name, timestamp, exit code. The daemon never buffers subprocess output that contains plaintext — stdout of any secret subprocess is streamed to the caller's MCP response only after the daemon confirms the response payload type is metadata (exit code, names, status), never raw subprocess stdout when the underlying command is decrypt-capable.

    Constraints

    • Hard

      No MCP tool may return plaintext credential values in its response payload — secret-show and any decrypt-capable operation are permanently excluded from the MCP surface

    • Hard

      No MCP tool may return age private key content, sops DEK material, or staged template plaintext in its response payload

    • Hard

      Tier 3 mutating tools (secret-gen, secret-make, secret-new, secret-rekey) must require actor=admin; agent and developer actors must receive Unauthorized before the subprocess is spawned

    • Hard

      Every Tier 3 subprocess invocation must append an entry to logs/access.jsonl before returning — a missing log entry for a completed operation is a constraint violation

    • Hard

      The daemon must not forward raw subprocess stdout to the MCP response caller when the invoked secrets.nu subcommand is decrypt-capable (show, edit); for Tier 3 commands, only structured metadata (exit code, names changed, audit entry written) may be returned

    • Hard

      Every MCP tool in category credentials must declare its tier (1|2|3) as a metadata field at registration time; tools without a declared tier fail daemon startup

    Alternatives considered

    • No expansion of MCP surface (Tier 1 only, current state)

      Rejected: Forces agents and CI pipelines to shell out to just secret-* outside the actor authorization model. Pending secret discovery, vault compliance checks, and automated secret rotation all become invisible to the audit trail. The automation use case is real and growing; refusing it produces shadow workflows rather than preventing them.

    • Full exposure including decrypt-to-response (no Tier 1 exclusions)

      Rejected: secret-show and any decrypt-capable operation as MCP tools turns the daemon into a plaintext credential proxy. Any actor with admin token and network access to the loopback daemon can extract credential values without a trace in the vault's own audit log (daemon access log is separate). The structural exclusion of ADR-017 would be effectively nullified — the daemon would hold no key but could produce plaintext on demand.

    • Daemon-as-proxy: daemon acquires .kage path, resolves credentials in-process

      Rejected: Breaks the load-bearing invariant of ADR-017: 'the daemon cannot be used as a credential amplifier regardless of MCP actor permissions'. In-process resolution means the daemon holds plaintext during the operation lifecycle. Any memory disclosure (core dump, debug interface, language runtime reflection) exposes key material. The subprocess model keeps key material in a child process with a narrower attack surface and no persistence in daemon memory.

    • Separate secret-management daemon with its own MCP server

      Rejected: Adds operational complexity (two daemons to manage, two auth models to align) without meaningful security gain over subprocess isolation. The tier model achieves the same separation of concerns within the existing daemon's actor authorization infrastructure. A separate daemon would need its own loopback trust model — re-solving a solved problem.

    +
    diff --git a/site/site/public/adr/adr-023/index.html b/site/site/public/adr/adr-023/index.html new file mode 100644 index 0000000..23b3a8d --- /dev/null +++ b/site/site/public/adr/adr-023/index.html @@ -0,0 +1,33 @@ + + + +Adoption of the Verifiable Substrate: Op Log + Content-Addressed Blobs + Bitemporal Triples + Merkle Commitments + JetStream Fabric, Replacing the NCL/Filesystem/Git Substrate · Ontoref ADR + + +
    +
    + Accepted + adr-023 · 2026-05-26 +

    Adoption of the Verifiable Substrate: Op Log + Content-Addressed Blobs + Bitemporal Triples + Merkle Commitments + JetStream Fabric, Replacing the NCL/Filesystem/Git Substrate

    +
    + +

    Context

    Ontoref's current substrate is plana: NCL files on disk (.ontology/, adrs/, reflection/) + git history + filesystem watching + Nushell automation + Rust crates loading NCL into typed structs. Verification of any claim about a project requires running ontoref against the full SST: there is no way to attest 'this project's ontology has property X at time T' without re-evaluating the whole tree. The Self-Describing and DAG-Formalized Knowledge axioms are therefore aspirational — they describe what the protocol promises, not what an external verifier can confirm. Multi-actor concurrency is unaddressed: who wrote which assertion, when, with what authority, is implicit in git history rather than in the protocol. The recent commits 82a358f (#[onto_mcp_tool] catalog, OCI vault, ADR-018 mode hierarchy validation), 6721daf (NCL-first workflow generator), and ADRs 021 and 022 each surfaced the same structural deficit: every layer that needs typed contracts, signed operations, or external attestations reimplements them ad hoc. The verifiable substrate, prototyped in .coder/2026-05-24-verifiable-substrate-prototype.plan.md and consolidated in .coder/2026-05-25-implementation-final.plan.md, addresses this by replacing the substrate itself: every assertion about a project becomes a typed, signed, content-addressed operation in an append-only log; the materialized state (triples) and the cryptographic commitment (Merkle state root) are derivable from the log; JetStream carries operations to validators, materializers, and agents as a fabric, not as a runtime dependency. The change is structural, not incremental.

    Decision

    Adopt the verifiable substrate as the canonical substrate for ontoref, following the gates G1 through G12 of .coder/2026-05-25-implementation-final.plan.md. Construct the substrate in an isolated branch (ontoref-kernel) on top of the existing main. The substrate consists of seven layered crates (ontoref-types, ontoref-blobs, ontoref-oplog, ontoref-triples, ontoref-commit, ontoref-query, ontoref-fabric) composed by ontoref-core, plus ontoref-modes, ontoref-cli, ontoref-secrets (extracted pre-G1 from the daemon), and an internally rewritten ontoref-daemon that preserves auth and operator surface (ADR-021 carried verbatim). NCL/Nushell layers (.ontology/, adrs/*.ncl, reflection/) are removed at G12 after the kernel demo passes; their content is optionally migrated via tools/ncl-import. Two named tensions in .ontology/core.ncl are explicitly engaged by this decision (ondaod): ontology-vs-reflection and formalization-vs-adoption. The synthesis state and direction of motion for each are recorded in .coder/2026-05-25-implementation-final.plan.md §1.bis and MUST be read in conjunction with this ADR; collapsing either Spiral (e.g. by treating the substrate as 'reflection only' or by enforcing universal formalization without tier-1 fallback) constitutes a forbidden pattern. Activation tier for the prototype is Tier 2 (kernel + fabric); Wasm components, ZK proving, and Radicle p2p replication are deferred to post-G12 work and do not block this decision.

    Constraints

    • Hard

      New kernel crates (ontoref-types, ontoref-blobs, ontoref-oplog, ontoref-triples, ontoref-commit, ontoref-query, ontoref-fabric, ontoref-core, ontoref-modes, ontoref-cli) MUST NOT depend on stratumiops crates (stratum-graph, stratum-state, stratum-db, platform-nats)

    • Hard

      Every operation MUST round-trip through canonical CBOR encoding with byte-stable output: encode(op) == encode(op) for all op; OpId(op) == blake3(encode(op))

    • Hard

      Substrate crates MUST NOT contain todo!(), unimplemented!(), or panic!() with not-implemented messages

    • Hard

      Plan §1.bis MUST exist and name both engaged tensions (ontology-vs-reflection, formalization-vs-adoption) with direction-of-motion and synthesis state; this ADR cites it

    • Hard

      At G12 completion, crates/legacy/ MUST NOT exist; crates/ontoref-ontology/, crates/ontoref-reflection/ MUST NOT exist; reflection/, .ontology/, adrs/*.ncl, templates/ MUST NOT exist

    Alternatives considered

    • Continue evolving on the NCL+filesystem+git substrate; incrementally add typed contracts where pain emerges

      Rejected: Each new typed surface (#[onto_mcp_tool] catalog, OCI vault, ADR-021 TokenIssuer) has been bolted onto a substrate that does not natively support typed signed operations. The result is increasing surface area implementing the same primitives in isolation. The decision is not 'should we add typed signed operations', it is 'should one substrate-level layer own them'. Incrementalism preserves the substrate but pays the integration cost N times.

    • Adopt an off-the-shelf knowledge graph database (Datomic, XTDB, TerminusDB, Neo4j)

      Rejected: These systems target operational knowledge bases, not protocols that projects implement. Bitemporality (Datomic, XTDB) covers a subset of the substrate's properties but not multi-actor signed operations or external attestation via state-root commitments. Wrapping one of them would make ontoref a runtime dependency (violating Invariant #1: Protocol Not Runtime). The substrate is a protocol specification with reference implementation; an off-the-shelf KG DB is a runtime product.

    • Implement only the op-log layer (L0–L2) and skip triples/commit/query/fabric

      Rejected: Without the commitment layer (L4), external verification is impossible. Without the fabric (Tier 2), multi-actor demonstrations cannot run. Without the query layer (L5), the substrate's discoverability is worse than the current describe.nu surface. The substrate's value is the combination; partial adoption defers the verification property to indefinite future work.

    • Build the substrate as a new project (substrate-* crates), keep ontoref consuming it

      Rejected: This was the predecessor plan's framing (.coder/2026-05-24-verifiable-substrate-implementation.plan.md naming). D9 reverses it: ontoref-* prefix preserved because the substrate is ontoref realising its own axioms, not a new product line. A separate substrate-* project would create two protocols where one suffices, splitting the contributor community and forcing every consumer to track two projects.

    +
    diff --git a/site/site/public/adr/adr-024/index.html b/site/site/public/adr/adr-024/index.html new file mode 100644 index 0000000..d468932 --- /dev/null +++ b/site/site/public/adr/adr-024/index.html @@ -0,0 +1,113 @@ + + + +Operations Layer — Domain Operations as the Agent's Only Project-Touching Path · Ontoref ADR + + +
    +
    + Accepted + adr-024 · 2026-05-26 +

    Operations Layer — Domain Operations as the Agent's Only Project-Touching Path

    +
    + +

    Context

    After extensive interactive diagnosis across the session of 2026-05-25 (see +.coder/2026-05-25-substrate-utility-audit.info.md and the dialogue that produced +it .coder/2026-05-25-implementation-final-stop-review_done.md), +the recurring pain experienced across all 19 ontoref-onboarded projects +(provisioning, mina, lian-build, forge-fleet, libre-{daoshi,wuji,forge}, +stratumiops, rustelo, vapora, kogral, secretumvault, typedialog, jpl-website, +personal-ontoref, jpl-ontoref, cloudatasave, build-in-layers, mirador, +DD7pasos, librosys, ontoref itself) is located at a layer that is neither the +ontology layer (rich, declarative, mature) nor the verifiable substrate (G1-G10 +of the ontoref-kernel work is green and works).

    +

    The pain lives at the acoplamiento entre acción y consumo — agents have generic +primitives (Bash, Edit, Read, Write) plus optional ontology access (34 query-only +MCP tools listed in crates/ontoref-daemon/src/mcp/mod.rs), and the ontology +consumption is never required before action. Evidence from this repository:

    +

    - MCP surface is 34 tools, all queries: list_projects, search, describe, + list_adrs, get_adr, get_node, validate, impact, status, guides, etc. + Mutations are limited to qa_add, action_add, bookmark_add, set_project, + config_update — META operations on ontoref itself, not DOMAIN operations + on the project being worked on. + - crates/ontoref-daemon/src/federation.rs (419 lines) implements cross-project + BFS impact graph with depth 5 and HTTP for push-only remotes. It is unused: + install/resources/remote-projects.ncl is the empty list. The infrastructure + exists; the use case for it has not been activated. + - ADR-012 explicitly bypasses the NCL canon (repo_kinds.txt as grep-readable + side channel) because nickel export was too slow for hot-path dispatch + (200-400ms per invocation). The NCL canon is aspirational; the operative + path goes around it whenever performance demands it. + - The three-layer model of ADR-020 declares an integration surface (Layer 2) + but its Layer-2 contract is type-shape (schemas, catalog data), not + operation-shape — consumers bind to data structures, not to verbs.

    +

    The consequence: agents consume the ontology only when nudged; otherwise they +operate through Bash/Edit on raw filesystem, producing outcomes that may or may +not respect declared constraints. Validation runs post-mortem (pre-commit hooks, +CI checks) rather than constitutively. Cross-project coordination requires the +human to remember what lives where. The interactive session that produced this +ADR consumed many hours of dialogue precisely because the human had to refill +context the agent failed to fetch — and this dialogue cannot be relied on to +persist across sessions or to survive context compaction.

    +

    The verifiable substrate (ADR-023, Proposed) provides storage and witness +mechanisms (canonical encoding, bitemporal triples, content-addressed blobs, +state-root commitments) but does not on its own resolve why agents bypass the +declared content. The substrate is necessary infrastructure for receipts and +witnesses; it is not the mechanism that forces consumption.

    Decision

    Every ontoref-onboarded project carries — or progressively gains — a catalog of +domain operations exposed exclusively to agents as their tool surface. The +agent's configuration on a project DOES NOT include generic primitives (Bash, +Edit, Read, Write) over project paths. The only path by which an agent may +query, mutate, or affect the project is through operations in the catalog.

    +

    Each domain operation declares:

    +

    1. Required ontological precondition slice — which nodes, ADRs, state + dimensions, constraints, or external project entities must be loaded + and validated for this operation to proceed. + 2. Input contract — typed inputs, satisfied or rejected at invocation time. + 3. Effects — what the operation may change (file paths, state dimensions, + downstream entities, cross-project signals). + 4. Witness shape — what receipt is emitted upon successful completion.

    +

    The operation runtime:

    +

    1. Loads the declared precondition slice before invoking the operation body. + Loading is constitutive of the invocation, not a courtesy of the agent. + 2. Validates the precondition slice against the operation's declared + constraints. Failure produces a structured block message naming the + unmet precondition, not a generic error. + 3. Executes the operation body in a confined context that has access only + to the loaded slice and the declared effect scope. + 4. Emits a signed receipt: operation_id, actor, timestamp, state_root, + effect summary, downstream project signals if any.

    +

    Cross-project operations are routed through the existing federation.rs +infrastructure: an operation in project A that declares a dependency on or +effect in project B resolves the B-side via the federated query mechanism. +Operation receipts are first-class entities in the substrate (operations as +entities, per the audit's H7 finding), queryable cross-project.

    +

    Runtime placement is shared: the operations runtime is built once and lives in +ontoref-daemon (or a new ontoref-ops crate, decided at implementation time). +The operation catalog is per-project: each project owns its domain verbs under +a new declared path (catalog/operations/), with each operation contract written +in NCL using the ontoref-derive macros to surface to the MCP/API layer.

    +

    Agent configuration discipline: agents launched into an ontoref-onboarded +project receive an MCP server whose tool list is exactly the project's +operations catalog (plus the existing META operations like describe). Raw +filesystem tools over the project root are disabled at the agent-launch +boundary. The launcher is responsible for enforcing this; ontoref provides the +catalog and the runtime.

    Constraints

    • Hard

      Agent-facing mutations on a project's authoritative state must go through catalogued operations (dispatch_op), not raw filesystem primitives (Bash/Edit). Direct file edits bypass typed preconditions and the witness-seam.

    • Hard

      Every catalogued operation must declare at least one typed precondition. Ops with no preconditions provide no validation plane and defeat the purpose of the operations layer.

    Alternatives considered

    • Keep generic agent tools, improve ontology documentation and the MCP query surface

      Rejected: Years of evidence across 19 projects demonstrate that better declarative content does not lead to consumption when consumption is optional. The optionality IS the failure mode; adding more documentation reinforces the same pattern at greater cost.

    • Build the full G1-G12 substrate plan as written, defer operations layer indefinitely

      Rejected: The substrate without an operations layer is verifiable storage that no agent uses operationally. G1-G10 is already green; G11-G12 (multi-actor daemon, demo) would extend storage without addressing why agents bypass the storage. The recurring pain persists across all of G11-G12's work.

    • Per-project ad-hoc solutions (custom CLI wrappers, project-specific guardrails, per-project skills)

      Rejected: Combinatorial cost across 19 projects. No shared infrastructure means each project re-derives the constrained-action pattern. Cross-project federation impossible by construction. This is the current state and the source of the recurring pain.

    • Revert ontoref to pre-substrate state and restart the design

      Rejected: The substrate G1-G10 provides exactly the receipt/witness mechanism this ADR's operations layer requires. Reverting throws away material that becomes load-bearing under this decision. The substrate was not wrong; it was incomplete on its own. ADR-023 and ADR-024 together close the gap.

    • Enforce ontology consumption via stricter pre-action hooks instead of constraining the tool surface

      Rejected: Hooks fire on commit, not on action. An agent can perform many actions before commit; by then the cost of bad action is already paid. Constraining the tool surface is the only mechanism that intervenes before the action exists.

    +
    diff --git a/site/site/public/adr/adr-025/index.html b/site/site/public/adr/adr-025/index.html new file mode 100644 index 0000000..dd72b20 --- /dev/null +++ b/site/site/public/adr/adr-025/index.html @@ -0,0 +1,142 @@ + + + +Ontology Core as Authoritative State, NCL as Manifestation — Two-Phase Adoption · Ontoref ADR + + +
    +
    + Accepted + adr-025 · 2026-05-26 +

    Ontology Core as Authoritative State, NCL as Manifestation — Two-Phase Adoption

    +
    + +

    Context

    ADR-024 establishes the operations layer as the agent's only project-touching +path. This ADR closes the remaining ambiguity ADR-024 left implicit: where does +the authoritative project state actually live, and what is the role of the +existing NCL filesystem (.ontology/, adrs/, reflection/).

    +

    Two competing readings have coexisted through the project's evolution:

    +

    Reading 1 (current de facto): the NCL filesystem IS the state. Rust code + (ontoref-ontology, ontoref-reflection crates) parses NCL into typed structs + for query. Mutations happen by editing files; git is the history.

    +

    Reading 2 (substrate plan original direction): NCL is intended to disappear + (ADR-023 § decision, constraint legacy-crates-removed-at-g12). State lives in + triples (G4) + commitments (G5); operations are recorded in oplog (G3). NCL + was destined to become migration tool input only.

    +

    Neither reading is currently coherent with the project's lived axioms and the +direction set by ADR-024. Reading 1 contradicts ADR-024 (an agent who edits +.ontology/*.ncl with Edit bypasses operations entirely). Reading 2 dissolves +.ontology/ which collides with the Self-Describing axiom and removes the +human-legible substrate the project has invested years in.

    +

    The session of 2026-05-25 (see .coder/2026-05-25-implementation-final-stop-review_done.md) +closed Reading 2 by adopting ADR-024: operations layer becomes the fin, substrate +becomes the medio. This ADR closes the matching question on state location: the +authoritative state lives in Rust (loaded into typed structs, committed via +the substrate), and NCL is one of several manifestations rendered from that +state. The plain-text legibility of .ontology/ is preserved; its semantic +authority is moved to the Rust-side state.

    +

    The transition has a load-bearing constraint: it must be progressive. A +big-bang switch to "NCL is read-only output" would block all legitimate human +edits during the period when the operations catalog is incomplete (which is +inherent to first adoption — the catalog grows dialectically with use). The +decision adopts a two-phase model: Phase 1 NCL editable with parse-on-save +reconciliation; Phase 2 NCL render-only when the catalog is mature.

    +

    The decision also collides with the existing axiom no-enforcement +(.ontology/core.ncl, invariant=true): "There is no enforcement mechanism. +Coherence is voluntary." Once operations are the only mutation path and +parse-on-save can reject inconsistent edits, there IS enforcement — internal, +structural, post-adoption. The axiom must evolve to reflect this distinction +between voluntary adoption (preserved) and enforced internal coherence (new).

    Decision

    The authoritative project state lives in Rust, materialized over the +verifiable substrate (ontoref-types + ontoref-triples + ontoref-oplog + +ontoref-commit, G1-G10 verde). NCL files in .ontology/, adrs/, and reflection/ +become one manifestation of that state — human-legible, git-versioned, but no +longer the seat of semantic authority.

    +

    The transition is two-phase:

    +

    Phase 1 — C-progressive (during catalog construction)

    +

    NCL files remain manually editable. Each save to .ontology/*.ncl, + adrs/*.ncl, or reflection/*.ncl triggers a reconciliation:

    +

    parse(file) → diff vs current state → emit ops to align → apply

    +

    If parse fails or diff requires an op not in the catalog, the save fails + with a structured message naming the missing operation. Each failure + teaches the dialectical step needed to extend the catalog.

    +

    Render from state to NCL happens after any state mutation (whether + triggered by a save or by a direct ops invocation), so the NCL files + always reflect the post-mutation state.

    +

    Phase 1 is the operating mode during the build-out of ADR-024's + operations catalog. It coexists with the existing CLI surface; ops + invocations via MCP, ops invocations via save-triggered reconciliation, + and (during this phase) legacy CLI mutations via the existing ontoref CLI + commands all converge on the same state.

    +

    Phase 2 — C-pure (post-catalog-maturity)

    +

    NCL files become render-only. Direct edits are silently overwritten by the + next render. All mutations flow through declared operations.

    +

    Transition from Phase 1 to Phase 2 is a deliberate decision recorded as a + configuration flag (.ontoref/config.ncl::ops.phase = 'Pure) plus a new + ADR (likely ADR-026) documenting the maturity assessment.

    +

    Reconciliation between this decision and the existing no-enforcement axiom +requires the axiom to evolve. no-enforcement is split into two axioms that +preserve voluntary adoption while making explicit the enforcement that lives +inside the post-adoption operation runtime:

    +

    voluntary-adoption (invariant=true) — Projects adopt ontoref by choice; the + protocol never imposes itself. Adoption is opt-in per tier (tier-0 + minimal NCL, tier-1 substrate, tier-2 operations).

    +

    internal-coherence-enforced (invariant=true) — Once a project adopts the + operations tier, mutation of authoritative state happens exclusively + through declared domain operations. The agent (human or AI) cannot + bypass.

    +

    Both new axioms are added to .ontology/core.ncl as part of the pre-flight to +the operations-layer implementation plan; the existing no-enforcement node is +removed. This is enforced as a Hard constraint of this ADR.

    +

    Invocation surfaces (the API answer):

    +

    Operations are invoked exclusively through typed API surfaces. NCL files are + NEVER the invocation channel; they are the catalog (declaration of what + operations exist) and the manifestation (render of resulting state). Three + API surfaces, all built on the existing ontoref-daemon authentication + (ADR-005):

    +

    1. MCP (Model Context Protocol) — agents call ops via tools/call <op_id>; + the tool list is dynamically populated from ontoref-ops::registry + (inventory::collect of OperationEntry). Auth via Bearer session token. + Existing 34 query tools coexist.

    +

    2. HTTP/REST — POST /ops/{id} with JSON body matching the op's declared + inputs; response includes the witness. Auth via Bearer. Suitable for + scripts, CI/CD, GraphQL bridge, and remote clients.

    +

    3. CLI — ./ontoref ops <id> --json '{...}' (alternatively positional args + resolved by the op's input schema). Same dispatch path as MCP and + HTTP under the hood.

    +

    All three surfaces converge on ontoref-ops::dispatch(op_id, inputs) which + loads the precondition slice, validates, executes, and emits the witness. + In Phase 1, save-to-NCL is a fourth implicit invocation channel that goes + through parse → diff → emit_ops → dispatch. In Phase 2, save-to-NCL has no + effect on state.

    +

    Crate consequences:

    +

    - ontoref-ontology stays — its role shifts from "NCL parser" to "NCL ↔ state + bridge" (parse-to-ops in Phase 1; render-from-state in both phases). + - ontoref-reflection stays — modes execute against state queries (via + ontoref-query L5) rather than parsing reflection/modes/*.ncl on each call. + - ontoref-ops (new crate, plan's D2) is the operation runtime; it composes + ontoref-ontology (for render/parse), ontoref-triples (for state), and + ontoref-commit (for witnesses). + - ontoref-daemon adapts to expose POST /ops/{id} routes and integrates + ops into the MCP tool list; no rewrite required. + - The legacy-crates-removed-at-g12 constraint in ADR-023 is invalidated by + this ADR and is to be removed when ADR-023 transitions to Accepted (post + O3 of the operations-layer plan).

    Constraints

    • Hard

      .ontology/core.ncl MUST replace the no-enforcement node by two new axiom nodes: voluntary-adoption and internal-coherence-enforced (both invariant=true). The no-enforcement id MUST NOT remain in the nodes array.

    • Hard

      Rendering state → NCL MUST be byte-stable: render(state, file_path) produces identical bytes on repeated invocations for the same state, and parsing the rendered file MUST yield a state equal to the source state.

    • Hard

      .ontoref/config.ncl MUST declare an ops.phase field with value 'Progressive | 'Pure once ADR-025 is Accepted. Default value 'Progressive applies until a follow-up ADR records the Phase 2 transition.

    • Hard

      This ADR engages and synthesizes the named tensions ontology-vs-reflection, formalization-vs-adoption, and the to-be-split no-enforcement axiom; the synthesis state and direction of motion MUST be discoverable in the decision and rationale of this ADR.

    Alternatives considered

    • Reading 1: keep NCL as authoritative state, ADR-024 enforcement applied only when operations exist

      Rejected: This is the current state. The H7 diagnosis demonstrates it does not work: agents bypass NCL because direct file edit is structurally cheaper than consulting via MCP. The ADR-024 commitment to operations-as-only-path is incoherent if the underlying state is still file-edit-accessible. The reading collapses ADR-024 into a recommendation rather than a constitutive constraint.

    • Reading 2: dissolve NCL completely per the original substrate plan

      Rejected: Closing the human-legible substrate breaks Self-Describing's lived form (the directories ARE the description, not an optional rendering). It also abandons years of investment in NCL schemas, contracts, and onboarding tooling. The H7 dialectic showed the problem was not NCL-the-substrate but the missing acting-binding to NCL — the substrate is fine; what was missing was the mutation discipline. Dissolving NCL is over-reach.

    • C-pure from day one (state in Rust, NCL render-only immediately)

      Rejected: Big-bang transition during catalog incompleteness blocks legitimate work. Every mutation that has not yet been catalog-modeled is a hard block on the editor. Reasonable extensions (a new tension name in a new ontology node, a new ADR with a yet-unmodeled constraint type) become catalog-extension dialectics before they can be expressed. The friction kills adoption velocity precisely when adoption velocity is needed to mature the catalog.

    • C-progressive permanent (parse-on-save without ever reaching pure render-only)

      Rejected: Stops at the half-way point. The internal-coherence-enforced axiom is only partially satisfied — humans can still mutate via save, ops are not the unique path. The cryptographic verifiability that ADR-024 promised (witness-of-mutation cannot be fabricated) is undermined by the existence of an alternative mutation channel. The model is functional but incomplete; Phase 2 must be the eventual target even if its trigger is far in the future.

    • Keep no-enforcement axiom unchanged; treat ADR-024 + ADR-025 as conscious axiom violations documented per case

      Rejected: Encourages the protocol to drift from its declared axioms while the axioms remain frozen as aspiration. Future readers of core.ncl would see no-enforcement as truth while the runtime structurally enforces. The protocol's self-description must remain accurate; an axiom that no longer describes the runtime is not an axiom, it is a wish. The split makes the lived position legible.

    +
    diff --git a/site/site/public/adr/adr-026/index.html b/site/site/public/adr/adr-026/index.html new file mode 100644 index 0000000..c2064e1 --- /dev/null +++ b/site/site/public/adr/adr-026/index.html @@ -0,0 +1,151 @@ + + + +Validation Architecture — Three Planes, First-Class Validators, SLA per Operation, Pluggable Commitment Backend · Ontoref ADR + + +
    +
    + Accepted + adr-026 · 2026-05-26 +

    Validation Architecture — Three Planes, First-Class Validators, SLA per Operation, Pluggable Commitment Backend

    +
    + +

    Context

    ADR-024 establishes the operations layer as the agent's only project-touching +path. ADR-025 establishes that the authoritative state lives in the +verifiable substrate and NCL is its manifestation. Both decisions leave one +question deliberately open: how is the coherence of a mutation actually +verified? The plan that implements ADR-024 + ADR-025 mentions "validate" as a +step inside the operation runtime but does not articulate where validators +live, what kinds of validation apply, or what verifiability guarantees are +emitted alongside the mutation.

    +

    Without this articulation, the operations layer collapses to a thin wrapper: +the runtime loads a precondition slice, runs an opaque validate() body +inside the operation, and emits a witness that no external party can +meaningfully check. This is the failure pattern of the current pre-commit +hooks at scale — validators that run inside the producer's process, +producing pass/fail signals that consumer projects must trust on faith. The +H7 diagnosis is repeated at the validator layer: validators that consume the +full state saturate, drift, and lose locality.

    +

    Three structural deficits the operations layer alone does not resolve:

    +

    1. Validators that require complete knowledge of project state cannot run + locally without paying the saturation cost. The substrate's witness + mechanism (G5) provides local verifiability for cells, but the + operations layer does not exploit it — validators read the live state + instead of reading a witnessed slice.

    +

    2. The same validator predicate runs on every invocation regardless of + contextual relevance. An IPv4-shape validator runs identically for + every IP; a "this IP is in this pool given the FSM state" validator + needs slice + context. The current pre-commit pattern conflates the + two — both run as NuCmd processes loading whatever they want.

    +

    3. Validation latency and concurrency requirements are not expressible per + operation. A move_fsm_state must validate synchronously (the state + transition is the contract); a cross-project impact assertion may + legitimately validate eventually (consumer in B asserts the + consequence of A's op asynchronously). The current model imposes one + mode (sync, pre-commit) on all validations.

    +

    The substrate G1-G10 was designed precisely to support locally-verifiable +witnesses — bitemporal triples (G4), state root commitments (G5), proof +paths for cells (witness API). These primitives have been built and tested +but not consumed by an articulated validation layer. This ADR closes that +gap.

    Decision

    Validation is articulated as a first-class architecture, parallel to the +operation catalog. The architecture has three load-bearing properties:

    +

    Three planes of validation:

    +

    Pre-validation — runs after precondition slice load and before the + operation body executes. If any pre-validator returns Verdict::Reject, + the operation does not execute; the runtime returns a structured block + message naming the validator and the unmet predicate.

    +

    Inline validation — runs during the operation body. The body may invoke + additional validators that depend on values computed mid-execution + (e.g. a cross-project query whose result is itself validated). Failures + abort the body with rollback at the substrate level (the op is not + appended to the oplog).

    +

    Post-validation — runs after the operation body has applied to the state. + Two modes: + - Synchronous: the runtime invokes registered validators inline; the + op is committed only if all return Accept. + - Asynchronous: validators are external processes consuming the oplog + via the existing subscription channel (ontoref-query G6 + subscribe()); they emit AttestationOp entries materialized in the + same substrate. The state can carry not-yet-attested ops; consumer + queries can filter by attestation status.

    +

    Two categories of validators:

    +

    Structural validators — predicates over input or local data with no + dependency on project state or context. Examples: IPv4 shape, CBOR + canonical, Ed25519 signature valid, NCL schema contract held. + Universally applicable, cheap, deterministic.

    +

    Contextual validators — predicates over a slice of project state, possibly + parameterized by actor, dimension, or domain. Examples: "this entity is + in this pool given the current FSM state", "this actor may invoke this + op given their role", "cross-project op respects constraint declared in + project B". The slice query is part of the validator declaration; the + runtime loads exactly that slice — no more — before running the + predicate.

    +

    Validators are first-class entities, declared in two combinable forms:

    +

    catalog/validators/*.ncl — NCL declaration: id, description, category + (Structural | Contextual), slice_query (for contextual), predicate_ref, + witness_shape, actor_scope. Reusable across operations.

    +

    #[onto_validator(id, kind, slice, ...)] — Rust macro on the validator + implementation function. Emits inventory::submit!(ValidatorEntry { ... }) + at link time. The validator function signature is + fn validate(slice: &Slice, ctx: &ValidationCtx) -> Verdict.

    +

    An operation references validators by id in its constraints field; + alternatively, an operation may declare inline constraints when the + validator is single-use and reuse is not anticipated. Both forms coexist.

    +

    SLA per operation:

    +

    Each operation declares a validation_sla field: + 'Synchronous — all validators (pre, inline, post) run sync; + op only commits on full Accept + 'EventualWithin(Duration) — pre/inline sync; post validators run async + with deadline; consumers query attestation + status with as_of clauses + 'Background — pre/inline sync; post validators run background; + no deadline; ops are eventually-attested

    +

    The runtime applies the declared mode; mixing modes within a single op is + not permitted (to keep dispatch unambiguous).

    +

    Pluggable commitment backend:

    +

    The substrate's commitment layer (G5, currently hand-rolled binary Merkle) + is abstracted behind a CommitmentBackend trait with methods apply, root, + witness, verify_witness. The G5 hand-rolled implementation becomes the + BinaryMerkle impl. The trait permits alternative impls (NOMT, jmt, + sparse-merkle-tree) without modifying call sites.

    +

    The choice of backend is deferred: the BinaryMerkle impl is the default + and proven (10 tests, clippy clean). A trigger-based spike runs only when + empirical signals from the pilot indicate a backend with different + trade-offs may be beneficial:

    +

    - validate_latency_p99 > 500ms (sustained) + - state_cells_count > 100k + - commit_root_time_p99 > 100ms (sustained) + - daemon memory pressure > 1GB on substrate alone

    +

    A one-off type-fit assessment (1-2 hours, no benchmark) checks whether + candidate backends (NOMT 1.0.4, jmt latest, sparse-merkle-tree latest) + adapt to the CommitmentBackend trait without invasive wrappers. Backends + that do not fit are eliminated without benchmark cost. The assessment is + optional and may be deferred indefinitely; absence of assessment leaves + BinaryMerkle as the conscious choice.

    +

    Local verifiability:

    +

    Every operation emits a witness containing op_id, post-apply state_root, + signed by the actor (ADR-005 keypair model), plus Merkle proof paths for + cells the operation's effects touched. An external verifier holding only + the state_root + witness can check predicate(slice, witness, state_root) + for any contextual validator without needing access to the full state. + This is the Sigstore-like property the substrate was built to enable.

    Constraints

    • Hard

      crates/ontoref-ops MUST declare the trait Validator with method fn validate(&self, slice: &Slice, ctx: &ValidationCtx) -> Verdict; crates/ontoref-commit MUST refactor the existing commit module to extract trait CommitmentBackend with methods apply, root, witness, verify_witness. BinaryMerkle (the existing hand-rolled impl) MUST be the default impl.

    • Hard

      Every operation in catalog/operations/*.ncl MUST declare a validation_sla field with one of the values 'Synchronous, 'EventualWithin(duration), or 'Background. Operations that lack the field are rejected by the catalog schema at parse time.

    • Hard

      .ontoref/config.ncl MUST declare an ops.commitment_backend field with enum value 'BinaryMerkle | 'Nomt | 'Jmt | 'SparseMerkleTree. Default value is 'BinaryMerkle. The runtime selects the impl based on this field.

    • Soft

      docs/validation/spike-triggers.md MUST exist documenting the empirical thresholds that activate the commitment-backend spike (validate_latency_p99, state_cells_count, commit_root_time_p99, daemon memory pressure). The thresholds MUST be revisable via PR; the document records the current values plus rationale.

    • Hard

      This ADR engages and synthesizes the named tensions ontology-vs-reflection, formalization-vs-adoption, and the internal-coherence-enforced axiom; the synthesis state and direction of motion MUST be discoverable in the decision and rationale.

    Alternatives considered

    • Single-plane validation: pre-only (as in current pre-commit hooks)

      Rejected: Pre-only cannot handle validators that depend on values computed during op execution (inline) and cannot handle expensive validators that would block throughput unacceptably (async post). The current model fails precisely because it has only this one plane — the H7 diagnosis traces some of its dysfunction to validators that try to do everything pre.

    • Validators as Rust traits only, no NCL declaration

      Rejected: Loses the Self-Describing property: validators become invisible to non-Rust consumers (NCL readers, GraphQL clients, MCP agents querying catalog). Also loses the ability for validators to be cross-project queryable — a project that wants to know what validators apply to its mutations would need to read source code rather than declarative artifacts.

    • Validators as NCL only, no Rust impl bridge

      Rejected: NCL is declarative; predicate evaluation logic must run somewhere. Forcing all predicates into Nickel contract syntax limits expressiveness severely (no graph traversal, no FFI, no I/O). The dual-write (NCL declares, Rust implements) is the only way to satisfy both Self-Describing (NCL is the catalog) and effective implementation (Rust runs the code).

    • Force synchronous validation for all ops (no SLA per op)

      Rejected: Equates correctness with synchronous correctness. Many legitimate validators (cross-project, statistical, graph-traversal) cannot reasonably run synchronously without throttling the runtime. The sync-only model would either reject these validators (impoverishing the architecture) or require their bodies to be approximations of the real predicate (sacrificing correctness for latency).

    • Commit immediately to NOMT or jmt without spike or trait abstraction

      Rejected: No prior implementation experience exists. Either choice without data is gambling. The trait abstraction costs minimal effort (the G5 implementation needs trait extraction; the other backends, if ever evaluated, just implement the trait). Deferring the decision preserves optionality at near-zero cost; committing now sacrifices optionality for no benefit.

    • Defer all of validation architecture to a later ADR, ship operations layer with thin validate()

      Rejected: The operations layer without articulated validators is the failure pattern this entire trajectory is trying to escape. Shipping ADR-024 + ADR-025 + the operations-layer plan without ADR-026 would produce a runtime that has the surface of validation (a validate step in the dispatch pipeline) but the substance of the current pre-commit model (opaque body running ad-hoc checks). Validation architecture is co-constitutive with operations architecture; they share the load-bearing.

    +
    diff --git a/site/site/public/adr/adr-027/index.html b/site/site/public/adr/adr-027/index.html new file mode 100644 index 0000000..77a021c --- /dev/null +++ b/site/site/public/adr/adr-027/index.html @@ -0,0 +1,145 @@ + + + +Decentralization Architecture — P2P Pure, Pluggable SyncBackend, CRDT per Domain · Ontoref ADR + + +
    +
    + Accepted + adr-027 · 2026-05-26 +

    Decentralization Architecture — P2P Pure, Pluggable SyncBackend, CRDT per Domain

    +
    + +

    Context

    ADR-024 establishes operations as the agent's only project-touching path. +ADR-025 establishes that authoritative state lives in Rust on the substrate +(G1-G10) with NCL as manifestation. ADR-026 establishes the validation +architecture with witness-based local verification. All three were written +assuming a single-actor, single-node operational model — the substrate runs +locally, the operation dispatches locally, the validators verify locally.

    +

    The current production reality contradicts this assumption: ontoref-daemon +ships with stratum-db (feature-gated) that connects to a remote SurrealDB +server for the query cache. The 19 ontoref-onboarded projects share tables +in this remote DB via {slug}--{id} prefixes. This is a centralized model: a +remote authority holds the materialized state; outage of the DB outage +breaks discovery and cross-project query for every consumer.

    +

    A deeper structural deficit was identified during the session of 2026-05-26 +(this ADR's session): when the ontology lives inside the project's filesystem, +cross-project operations or verifications require dragging the entire other +project into local scope. This repeats the H7 saturation pattern at a +different layer — instead of agent-saturated-with-context, we have +verifier-saturated-with-other-project. The substrate's witness mechanism +(G5 Merkle proofs) was designed precisely to escape this saturation — a +verifier holding only state_root and witness can validate a claim without +holding the source. But the witness mechanism is useless if there is no +decentralized exchange channel and no separation of ontology from project.

    +

    This ADR closes the decentralization question. ADR-028 closes the matching +ontology-from-project separation question. Together they reorganize the +operational model from "centralized substrate + remote DB" to "P2P pure + +per-actor substrate + content-addressed ontology layer + witness-based +cross-actor verification".

    +

    The decision is irreversible-but-incremental: P2P pure is the architectural +commitment; the sync protocol implementation is deferred via a trait +abstraction that lets candidates (Iroh, Hypercore, Radicle, NATS) be +evaluated when empirical signals demand. The reversibility lives in the +backend choice, not in the model.

    Decision

    Adopt P2P-pure decentralization as the operational model: no central +authority node; each actor maintains its own substrate locally; sync is +peer-to-peer between actors who explicitly follow each other.

    +

    Four load-bearing capabilities that the architecture must support:

    +

    1. Multi-actor concurrent with sovereignty per actor — multiple actors + (humans + AI agents) operate simultaneously on the same logical + project; each holds its own copy; decides whom to follow; no + coordination authority.

    +

    2. Verifiability without complete state access — any actor can verify a + claim by another with only state_root + witness; the substrate's G5 + Merkle proofs are the mechanism; ADR-026's validators rely on this.

    +

    3. Decentralized discovery — actors find each other via DHT, gossip, or + social protocol (Radicle); no central registry. The current + ~/.config/ontoref/projects.ncl becomes "peers I follow", not + "registry I depend on".

    +

    4. Resilience to partition / offline-first — an actor offline retains + full local operation; reconnection syncs lazily; no synchronous + coordination required for correctness.

    +

    SyncBackend trait (pluggable backend, decision deferred):

    +

    trait SyncBackend { + async fn announce(&self, oplog_head: OpId, public_key: PublicKey); + async fn discover_peers(&self) -> Vec<Peer>; + async fn fetch_op(&self, peer: &Peer, op_id: OpId) -> Result<Op>; + async fn subscribe_peer(&self, peer: &Peer) -> Stream<Op>; + }

    +

    Candidate impls behind trigger-based assessment (same pattern as ADR-026 + CommitmentBackend): + - FilesystemSync — default, trivial impl (peers share a filesystem + directory; useful for development and testing; not really + decentralized but zero-dependency) + - IrohSync — n0-iroh; Rust-native; gossip + content-addressed blobs + + DHT; aligned with Ed25519 identity (ADR-005) + - HypercoreSync — Hypercore via Rust bindings if mature; append-only + log primitive aligned with oplog + - RadicleSync — git-protocol-over-Radicle for repos; potentially + different layer from oplog sync + - NatsSync — NATS JetStream as fabric (G7 of the substrate plan, + previously skipped)

    +

    The decision among these is deferred. The trigger conditions: + - cross_actor_sync_required > 0 (i.e., the pilot has reached the point + where actual P2P sync is needed; until then, FilesystemSync suffices) + - actor_count > 1 (a real second actor exists, not a self-loop) + - peer_discovery_latency or sync_throughput become measurable concerns

    +

    The fit-assessment (type-fit, not benchmark) for each candidate runs as + ADR-026 spike: does the candidate adapt to SyncBackend trait without + invasive wrappers. Candidates that do not fit are eliminated.

    +

    CRDT per-domain (selective CRDT, not global):

    +

    D6 of the old plan ("HLC without CRDT") was a sound default for the + substrate's structural correctness but is insufficient when multi-actor + concurrent edits target the same cell. This ADR modifies D6 selectively: + domains where concurrent edits over the same cell are plausible declare a + CRDT merge strategy; other domains retain HLC last-writer-wins.

    +

    Identified domains needing CRDT (this session's identification): + - Backlog (BacklogItem): multiple actors add/status/done concurrently; + MergeStrategy::OrSet for items; MergeStrategy::Lww per status with + HLC tiebreak + - Ontology nodes (description, edges): collaborative description + enrichment + edge addition by multiple contributors; + MergeStrategy::TextMerge for description (LSEQ or similar); + MergeStrategy::OrSet for edges + - QA entries + search bookmarks: append-only accumulators; + MergeStrategy::GSet trivially

    +

    CrdtMergeStrategy trait (pluggable, similar to SyncBackend and + CommitmentBackend):

    +

    trait CrdtMergeStrategy { + type Item; + fn merge(&self, a: Self::Item, b: Self::Item) -> Self::Item; + fn merge_witness(&self, ...) -> MergeWitness; + }

    +

    Default impl: HlcLastWriterWins (preserves current D6 behavior). + Per-domain impls declared in the domain's NCL schema.

    +

    SurrealDB position under P2P pure:

    +

    SurrealDB becomes opt-in via the existing feature flag (cargo build + -p ontoref-daemon --features db). The runtime does not depend on + SurrealDB; consumers that want SurrealDB's query expressiveness configure + it locally. The stratum-db dependency moves from "feature-gated default" + to "feature-gated optional with explicit choice". Existing 19 onboarded + projects continue to work unchanged; the migration to non-SurrealDB + query layer (datalog G6 + ontoref-query) is opt-in per project.

    +

    Crucially: SurrealDB is local-only under P2P pure. The connect_remote + path becomes connect_local (embedded mode) or is deprecated when the + project chooses opt-out.

    Constraints

    • Hard

      crates/ontoref-ops or new crate ontoref-sync MUST declare trait SyncBackend with methods announce, discover_peers, fetch_op, subscribe_peer. A default impl FilesystemSync MUST exist that satisfies the trait without external dependencies (suitable for development and single-actor pilot phase).

    • Hard

      crates/ontoref-ops MUST declare trait CrdtMergeStrategy with method merge(a, b) -> Item + merge_witness(...). A default impl HlcLastWriterWins MUST exist. Domain-specific impls (OrSet for backlog, TextMerge for ontology descriptions, GSet for QA accumulators) MUST be declared when the corresponding domain catalog operations are implemented.

    • Hard

      ontoref-daemon MUST build successfully with --no-default-features --features mcp (or equivalent) — i.e. without the db feature that pulls stratum-db. The runtime MUST function for query, mutation, validation, and witness emission without SurrealDB present.

    • Hard

      This ADR engages and synthesizes the voluntary-adoption + internal-coherence-enforced axioms (ADR-025) and the formalization-vs-adoption tension. The synthesis is the per-actor sovereignty model: adoption of P2P is voluntary per project (opt-in tier), but a project that adopts P2P operates within enforced internal coherence (witness-verified ops, signed identity, CRDT per declared domain).

    Alternatives considered

    • Keep centralized SurrealDB as authoritative query layer; add P2P as optional layer above

      Rejected: Reproduces the H7 saturation pattern at the storage layer: actors depend on a central authority for queries, defeating the substrate's local-verifiability purpose. The 'optional P2P' layer becomes a courtesy feature rather than the operational backbone; usage in practice falls back to the centralized path. The decision to decentralize must be in the model, not an additional layer.

    • Federation model (ActivityPub / Matrix / NATS cluster) — nodes federate but each is a hub

      Rejected: Federation introduces 'nodes' as a category distinct from 'actors', creating a power asymmetry: node operators control sync topology. This contradicts the per-actor sovereignty capacity. Federation is the right model for some systems (Mastodon, Matrix), but not for ontoref's protocol-of-self-knowledge use case where every actor is logically equivalent.

    • CRDT globally for all domains

      Rejected: Forces CRDT complexity on domains that do not need it. ADR transitions are sequential by their definition (Proposed → Accepted is causally ordered; there is no semantic merge of 'two simultaneous Accepts'). Forcing CRDT here creates a richer data model without operational benefit. The per-domain selective model is the right grain.

    • Pick a sync protocol now (e.g. Iroh) and commit before having multi-actor scenarios

      Rejected: Same mistake as ADR-026 commitment-backend would be without trigger-based deferral. Sync protocol choice without operational scenarios is a guess; the cost of remaking the choice later (after seeing actual sync patterns) outweighs the benefit of committing now. The trait abstraction is cheap; the deferral is honest.

    • Remove SurrealDB unilaterally; force all consumers to datalog G6

      Rejected: Breaks 19 onboarded projects without consultation. The capacity to use SurrealDB for SurrealQL expressiveness is legitimate for projects that have invested in it. The opt-in demotion lets each project decide; coexistence is sustainable under P2P pure because SurrealDB is local-only.

    +
    diff --git a/site/site/public/adr/adr-028/index.html b/site/site/public/adr/adr-028/index.html new file mode 100644 index 0000000..0cff114 --- /dev/null +++ b/site/site/public/adr/adr-028/index.html @@ -0,0 +1,177 @@ + + + +Ontology Layer Separation from Project Layer — Content-Addressed Decentralized Ontology with Classification · Ontoref ADR + + +
    +
    + Accepted + adr-028 · 2026-05-26 +

    Ontology Layer Separation from Project Layer — Content-Addressed Decentralized Ontology with Classification

    +
    + +

    Context

    Through this session's evolution, the project's ontology lived inside the +project's filesystem (.ontology/, adrs/, reflection/), git-versioned +alongside the project's code. This pattern made sense when the ontology was +specific to a single project; it became structurally problematic as the +ecosystem grew to 19 onboarded projects sharing the same ontoref protocol, +the same axioms, the same Practice nodes for common patterns, and +cross-project federation became a recurring need.

    +

    The session's load-bearing insight (your formulation): when the ontology is +coupled to the project's filesystem, cross-project operations or +verifications require dragging the entire other project into local scope. +A project A trying to validate against a constraint declared in project B +has no choice but to clone B, parse B's NCL, materialize B's state — and +then perform a single check that touches one node of B. This is the H7 +saturation pattern at the storage layer: as agents saturate when loaded +with full project context, verifiers saturate when loaded with full +foreign-project context.

    +

    The substrate G1-G10 (G5 in particular: Merkle commitments + witness API) +was designed to escape this — a verifier holding only state_root + witness +can validate a claim without holding the source. But the witness mechanism +is useless when the verifier still must clone the source to know what +state_root applies, what schema is in force, what validators bind to which +ops. The asymmetry between (small witness) and (huge cloned context) breaks +the local-verifiability promise.

    +

    This ADR closes the asymmetry by separating two layers that have been +conflated:

    +

    Project layer — the consumer codebase. Rust crates, Nushell scripts, + config files, project-specific NCL extensions. Lives + in Radicle / jj repositories. Managed by the existing + VCS abstraction (ADR-013).

    +

    Ontology layer — protocol-level and shared-domain ontologies. Lives + as content-addressed decentralized streams: each + ontology is an append-only oplog (G3 primitive), with + Merkle commitments (G5) over its current state. + Reachable by content-address, not by filesystem + location. Multiple projects reference the same + ontology by its content-address; none of them owns it.

    +

    Cross-project verification under this separation: project A's operation +that requires validating against an ontological entity declared in shared +ontology X resolves the reference by content-address, fetches only the +witness for the specific cell, verifies against the ontology's state_root. +No project is dragged; only the witness travels.

    Decision

    Separate the operational world into two layers with distinct sovereignty, +sync mechanisms, and discovery patterns:

    +

    Ontology layer (decentralized, content-addressed):

    +

    An ontology is an append-only oplog (G3 primitive from substrate G1-G10) + containing operations that build the ontological knowledge: axioms, + tensions, practices, ADR entries, validators, schemas. Each ontology + has: + - An identity (Ed25519 keypair for its maintainers; multi-maintainer + via CRDT per-domain from ADR-027) + - A content-address: blake3 hash of the head OpId chain root + - A current state_root (G5 Merkle commitment) + - A discovery surface: pubkey + recent head OpId, broadcast via + SyncBackend (ADR-027) — peer projects subscribe to follow updates

    +

    Ontologies are independent of any project. They exist as their own data + structures with their own sync, their own maintainers, their own + evolution. A project consumes ontologies by reference; cannot mutate + them locally; cannot fork them without explicit forking semantics + (which is itself an ontology operation — fork is a recorded act).

    +

    Project layer (Radicle + jj, existing infrastructure):

    +

    Project codebases continue to live in Radicle repos managed via jj + (ADR-013 VCS abstraction + the existing agent-workspace-orchestration + pattern with jjw). What changes is the content: + - Source code (Rust, Nushell, etc.) — unchanged + - Project-specific NCL extensions — minimized; most ontology content + moves to the ontology layer + - Local oplog of the project — records ops the project emits + (proposed ADRs, FSM state moves, backlog ops); references the + ontology layer for the schema and validators that apply + - Witnesses + state_root snapshots — for cross-project verification

    +

    Ontology classification with granularity:

    +

    Type 1 — Protocol ontology (mandatorily decentralized): + The ontoref-core itself — axioms (voluntary-adoption, + internal-coherence-enforced, protocol-not-runtime, self-describing, + dag-formalized), named tensions, base practices, base validators. + Every adopting project references this by content-address; no project + embeds it. The protocol ontology's maintainers are the ontoref + project's contributors (this repo's current authors); its + content-address is the protocol's published identity.

    +

    Type 2 — Shared domain ontology (granular: federable to mandatorily + decentralized): + Domain ontologies used by multiple projects: provisioning (cluster + ops, component catalog), persona (career, opportunities), lian-build + (image layers), forge-fleet (fleet management). The granularity: + - Type 2a — Federable: shared between this author's projects; + decentralization recommended but not mandatory; can live as a + single oplog accessible to a known peer set + - Type 2b — Mandatorily decentralized: shared with external + consumers (any third party may consume); MUST be content- + addressed and discoverable via SyncBackend; maintainers may be + broader than the originating author

    +

    Type 3 — Project-specific ontology (optional decentralization): + Extensions unique to a single project that have not (yet) graduated + to shared-domain status. Lives as project-local oplog initially; + promotable to Type 2 when consumption emerges. The current + .ontology/ content of the 19 onboarded projects predominantly falls + here.

    +

    Type 4 — Private ontology (local, tier-0): + Projects that have NCL files but no substrate, no oplog, no + operations layer. The pre-ADR-024 baseline. Untouched by this ADR.

    +

    The classification is metadata on every ontology declaration. The + ontoref-ontology-content crate (new, see below) reads the classification + and applies the corresponding sync / sovereignty rules.

    +

    New crate: ontoref-ontology-content

    +

    Hosts the per-ontology oplog primitives, content-addressing, and + fetch-by-witness logic. Distinct from ontoref-ontology (which becomes + the bridge between state and NCL manifestation, per ADR-025). Roles: + - OntologyId(blake3) — content-address + - fetch_ontology(id, sync_backend) — pull ops from peers + - fetch_cell_witness(id, cell_query) — fetch only the witness needed + to verify a specific cell, not the whole state + - verify_cell(ontology_id, cell, witness, state_root) — local check + - publish_op(ontology_id, op) — append to the ontology's oplog if + authorized; broadcast via SyncBackend

    +

    Cross-project verification via witness (the asymmetry-closing property):

    +

    Project A invokes an operation whose precondition includes "this entity + is in the pool declared by shared ontology X". The runtime: + 1. Resolves X by content-address (ontology_id) + 2. Determines the cell-of-X the precondition reads (e.g. the pool + entity) + 3. Calls fetch_cell_witness(X, pool_query) — receives only the + witness for that cell + the current state_root of X + 4. Verifies the cell content + Merkle proof against state_root + 5. If valid, the precondition is satisfied; runtime proceeds

    +

    No clone of X. No materialization of X's state. Only the witness + travels. The asymmetry is closed.

    +

    Migration path:

    +

    The existing 19 onboarded projects do not migrate immediately. ADR-028 + is a structural commitment with progressive realization: + - The ontoref-core ontology (Type 1) is extracted first into its own + content-addressed oplog; the existing .ontology/core.ncl content + becomes the bootstrap content of that oplog + - Each shared-domain ontology (Type 2) is extracted when its + multi-project usage is operationally evident (provisioning is the + most concrete candidate — used by provisioning + forge-fleet + + libre-* projects) + - Project-specific (Type 3) extensions stay in their projects until + they need cross-project consumption + - Private (Type 4) projects remain unaffected

    Constraints

    • Hard

      A new crate ontoref-ontology-content MUST exist with the following surface: OntologyId, fetch_ontology, fetch_cell_witness, verify_cell, publish_op. The crate depends on ontoref-types (for OpBody), ontoref-oplog (for log primitives), ontoref-commit (for state_root and witnesses), and optionally an SyncBackend impl.

    • Hard

      Every ontology (in this project: ontoref-core, the existing .ontology/) MUST declare its classification (Type 1 | Type 2a | Type 2b | Type 3 | Type 4) in its metadata. The classification field is part of the OntologyMetadata struct surfaced via ontoref-ontology-content::metadata(ontology_id).

    • Hard

      When ADR-028 transitions to Accepted (post-implementation), the ontoref-core ontology (current .ontology/core.ncl content) MUST be addressable via its content-address (blake3 of its head OpId chain root) and discoverable via at least one SyncBackend (FilesystemSync acceptable initially). The transition records the content-address as the canonical reference in CHANGELOG.md.

    • Hard

      An integration test in crates/ontoref-ontology-content MUST demonstrate: project A invokes an operation whose precondition references a cell in ontology X, the runtime fetches only the cell witness (not the full ontology), verifies via state_root, and the operation proceeds without materializing X locally. The test fails if X's full state is loaded.

    • Hard

      This ADR engages and synthesizes the protocol-not-runtime + self-describing axioms and the formalization-vs-adoption tension. The synthesis preserves all three: the ontology layer is reference content addressable by content-hash (not a runtime); self-description moves to its own layer (preserved structurally); classification with granularity absorbs the adoption shock per ontology.

    Alternatives considered

    • Keep ontology inside each project; build cross-project federation that smart-loads partial ontology data

      Rejected: This was the current federation.rs design (BFS depth 5 over HTTP). It has been unused for 419 lines of code precisely because the cost of operating it (every project must expose endpoints, every cross-project query is a network call to a project's filesystem) exceeds the benefit. ADR-028's content-addressing inverts the model: the ontology IS the addressable unit, not 'a slice of a project'. The cost of operation drops because the sync target is purpose-built.

    • Single global ontology repo (one Radicle repo for all shared-domain content)

      Rejected: Reproduces the centralization anti-pattern at the content-management layer. A single repo for all shared ontologies creates a coordination point (who can merge to it?) and a single failure mode. The per-ontology oplog model gives each ontology its own sovereignty — provisioning ontology and persona ontology have different maintainers, different update cadences, different consumers; collapsing them into one repo would force false coordination.

    • Move all ontology to a decentralized layer immediately; no Type 3 (project-specific) or Type 4 (private) tier

      Rejected: Crushes adoption. A new project trying ontoref for the first time would be forced to publish its ontology to a decentralized channel as a precondition of using the protocol at all. The classification with optional decentralization for Types 3 and 4 lets projects start where they are; the decentralization happens when consumption justifies it, not as an entry tax.

    • Keep ontology in project but use IPFS-style content addressing for cross-project refs

      Rejected: Hybrid that does not resolve the core asymmetry. The ontology still lives in project filesystem; the content-address is a thin convenience layer. Cross-project queries still require the responding project to materialize its full ontology (because the content-address points to a file the consumer might not have, hence needs full materialization to serve the content). The asymmetry is unresolved.

    • Use Radicle for ontology layer too (one Radicle repo per ontology)

      Rejected: Radicle is a git-protocol-based system; its strengths are code review, branching, social collaboration. Ontology evolution is operationally distinct — append-only oplog with CRDT merge, not git-style branching. Forcing ontology into Radicle would impose the wrong semantics. The ontology layer needs its own primitive (oplog from G3); Radicle stays for project code.

    Anti-patterns

    • Drag Whole Project For Cross-Project Verification

      Verifying a single ontological assertion declared in another project by +cloning that project and materializing its entire NCL state. Inverts the +substrate's G5 witness asymmetry: a Merkle witness is hundreds of bytes, +a project clone is megabytes-to-gigabytes plus parse cost. Saturates +verifiers the way unbounded context saturates agents (H7 pattern).

    • Centralized Shared-Ontology Repo

      Collapsing every shared-domain ontology (provisioning, persona, lian-build, +forge-fleet) into a single global repository. Creates one merge gatekeeper, +one failure mode, and forces false coordination between ontologies whose +maintainers, update cadences, and consumer sets are independent.

    +
    diff --git a/site/site/public/adr/adr-029/index.html b/site/site/public/adr/adr-029/index.html new file mode 100644 index 0000000..57edc58 --- /dev/null +++ b/site/site/public/adr/adr-029/index.html @@ -0,0 +1,139 @@ + + + +Tier Coexistence as Permanent Design — Strictly Additive Stack, Offline-First, No Forced Migration · Ontoref ADR + + +
    +
    + Accepted + adr-029 · 2026-05-26 +

    Tier Coexistence as Permanent Design — Strictly Additive Stack, Offline-First, No Forced Migration

    +
    + +

    Context

    ADR-024 (operations as the agent's only project-touching path) and ADR-025 +(ontology core as authoritative state, NCL as manifestation) introduce a +new mutation discipline grounded in the verifiable substrate (ADR-023). +ADR-025 also performs the axiom split that replaces `no-enforcement` with +`voluntary-adoption` (invariant=true) and `internal-coherence-enforced` +(invariant=true). The split is precise: external adoption remains opt-in; +internal coherence is enforced only AFTER adoption of the operations tier.

    +

    Across the implementation of the operations-layer plan (gates O-pre → O7, +session 2026-05-26), an observed ambiguity arose in operational decisions: +the ontoref piloto self-host has migrated to operations as authoritative +(tier-2, Phase 1), but every other onboarded project (mirador, provisioning, +lian-build, vapora, stratumiops, kogral, libre-{daoshi,wuji,forge}, +secretumvault, typedialog, jpl-website, personal-ontoref, jpl-ontoref, +cloudatasave, build-in-layers, DD7pasos, librosys, forge-fleet, mina) still +relies on NCL-authoritative state with file-edit mutation. Questions +recurred during the session — "are we still going to support NCL?", "did +we migrate to Rust?", "do the tiers have the same platform requirements?" +— that surfaced an unstated design property: the protocol supports +multiple authoritative-state modes simultaneously, by construction, and +indefinitely.

    +

    The plan itself states this in three places without naming it as an +architectural property worth its own ADR:

    +

    - §1: "Piloto: ontoref mismo (self-hosted). Phase 2 queda fuera de + scope." + - §7 R11: "Phase 1 → Phase 2 nunca llega: Aceptable resultado + intermedio. No-transición es estado válido." + - §8: "Expansión a proyectos no-ontoref … requieren cada uno su sesión + de catálogo." (per-project decision, no global migration)

    +

    Without an ADR naming the property, future sessions risk interpreting +the migration as transitional and removing the tier-0/1 paths once the +piloto stabilises. Doing so would silently violate the +`voluntary-adoption` axiom and break the 18 onboarded projects that +chose minimal NCL as their adoption surface. The session that produced +this ADR included a sub-bug — the daemon's `global_schemas_path()` did +not expose the data-dir schemas, so consumer projects in tier-0/1 got +WARN-logged `config_surface not loaded` errors. The fix +(`global_schema_dirs()` returning multiple paths) was structurally +correct because it preserved tier-0/1 operability — but it would have +been incorrect (silent migration coercion) if the design were +transitional. This ADR records the property so the fix-or-not decision +is explicit at every future point.

    Decision

    The protocol supports three authoritative-state modes simultaneously and +indefinitely, as the canonical operational surface:

    +

    tier-0 — minimal NCL adoption. + Authoritative state: `.ontology/*.ncl`, `adrs/`, `reflection/` as files. + Mutation: file edit (any editor). + Stack: `nickel` binary + optional `ontoref` Nushell CLI. + No daemon required for the project's own correctness; the daemon may + serve the UI and describe queries if installed.

    +

    tier-1 — substrate adopted. + Authoritative state: NCL files PLUS the substrate's commit layer + oplog. + Mutation: file edit + reconcile (substrate ingests NCL on read). + Stack: tier-0 + `ontoref-daemon` binary + G1-G10 substrate crates. + Cryptographic: blake3 (commitment root). + Cross-instance verifiability via state_root + witness, but no domain + operations.

    +

    tier-2 — operations adopted. + Authoritative state: Rust substrate (commit_layer + state_root). + NCL is manifestation rendered from state. + Mutation: dispatch_op exclusively (MCP, HTTP, CLI, or save-on-NCL + reconciler in Phase 1). + Stack: tier-1 + `ontoref-ops` + `ontoref-derive` + actor Ed25519 keypair. + Cryptographic: blake3 + Ed25519 (witness signing). + `internal-coherence-enforced` axiom applies HERE — operations are the + only mutation path. Below tier-2 the axiom does not apply (the + project hasn't adopted the operations tier; coherence remains + voluntary).

    +

    The three modes coexist permanently. There is no scheduled transition. +A project may stay at tier-0 indefinitely. A project may climb to +tier-1 or tier-2 voluntarily, on its own schedule, project by project. +The ontoref piloto runs at tier-2 / Phase 1; every other onboarded +project chooses its own tier independently.

    +

    Within tier-2, the Phase 1 → Phase 2 transition (NCL render-only, +save-reconciler off) is also opt-in per project and recorded in +`.ontoref/config.ncl::ops.phase`. The plan documents that Phase 2 may +never be reached — that outcome is valid by design.

    +

    Stack requirements are strictly additive: tier-N includes everything +tier-(N-1) needs plus its own delta. No tier requires network access or +external services for correctness. Optional features (`db`, `nats`, +`ui`, `mcp`, `graphql`, P2P sync backends, S3/OCI blob backends) are +orthogonal to tier — any project at any tier can opt into any feature +independently. Network-using features (real sync, remote blobs) are +trigger-based per ADR-027 / ADR-026 / D23: activated only when +documented thresholds are exceeded.

    +

    The daemon binary serves all tiers from a single build. Per-project +configuration declares the tier; the daemon dispatches against the +declared mode. Migration of an individual project is a per-project +deliberate act, not a protocol upgrade.

    Constraints

    • Hard

      The protocol MUST NOT include any mechanism that automatically migrates a project from one tier to another without an explicit per-project decision recorded in `.ontoref/config.ncl`. Migration aids (templates, documentation, CLI helpers) are permitted; automatic mutation of a project's tier is not.

    • Hard

      The daemon's NICKEL_IMPORT_PATH MUST resolve canonical schema imports (`manifest`, `core`, `state`, `gate`, `ontoref-project`) for tier-0/1 projects. The resolution MUST include `$data_dir/ontology/schemas/` and `$config_dir/schemas/`.

    • Hard

      The default build of `ontoref-daemon` (any features active) MUST operate correctness-wise without network access. Optional sync backends (SyncBackend impls beyond FilesystemSync) and blob backends (BlobBackend impls beyond LocalFilesystemBlobs) MAY require network when activated, but the default substrate MUST NOT.

    • Hard

      Changes to ontoref-ops, ontoref-derive, ontoref-ontology-content, or the substrate crates MUST NOT remove a public API that tier-0/1 consumers rely on. Schema additions are permitted; schema removals require a new ADR documenting the deprecation path.

    • Hard

      Tier transitions (upgrade tier-N → tier-N+1 OR downgrade tier-N → tier-N-1) +MUST refuse to execute when any protocol migration is pending. Specifically: +`ontoref migrate pending` MUST return an empty list before any change to +`.ontoref/config.ncl::ops.tier` is accepted by the daemon. + +Enforcement layers: + 1. The future `transition_tier` operation (to be defined alongside + ADR-033) declares this constraint as a synchronous pre-validator. + 2. Until `transition_tier` is implemented, the daemon's ConfigWatcher + enforces the same precondition: any mutation of `ops.tier` in + `.ontoref/config.ncl` triggers a guard that consults the project's + migration store. When pending migrations are detected, the daemon + emits a Block-severity notification, refuses to swap the config in + its registry, and the project remains at its current tier value. + +This is a forward declaration: the constraint exists at the +protocol-semantic level (ADR-029) before its operational +materialisation (ADR-033). The daemon-side enforcement closes the +operational gap immediately so manual `.ontoref/config.ncl` edits +cannot bypass the rule during the period between this amendment and +ADR-033's authorship.

    Alternatives considered

    • Schedule a transition: all projects migrate to tier-2 by date X

      Rejected: Violates voluntary-adoption (invariant=true) by construction. Forcing migration would require superseding ADR-025 with a new axiom — and no empirical evidence motivates that change. The 18 onboarded projects chose tier-0/1 deliberately; revoking that choice is hostile to ecosystem participants. Also: there is no engineering need — the substrate G1-G10 already coexists with NCL ingestion and the cost of supporting both is dwarfed by the cost of forcing migration on consumer teams.

    • Pick one mode (NCL-authoritative OR Rust-authoritative) and deprecate the other

      Rejected: Both modes have first-class users. Documentation-heavy projects need NCL editability with low ceremony. Multi-actor projects need cryptographic witnesses. Pruning either side narrows the protocol's value. The plan itself preserved this — Phase 2 is opt-in not mandatory, tier-2 is opt-in not mandatory. Codifying that choice as an ADR makes it stable.

    • Leave the property implicit in plan §7 R11 and §8 without a dedicated ADR

      Rejected: The implicit-in-plan path produced confusion during the implementation session itself ('did we migrate?', 'why are we fixing NCL paths?'). If the design intent was unclear at the moment of implementation, it will be unclear at every future session. The cost of one ADR is small; the cost of repeated rediscovery is large.

    • Define an even finer tier ladder (tier-0.5, tier-1.5, etc.) for fractional adoption

      Rejected: The three-tier model already maps to the three distinct value propositions (typed records / cryptographic state / verifiable ops). Sub-tiers would multiply the support matrix without adding qualitatively new modes. If a use case emerges that genuinely needs an intermediate tier, a future ADR can extend the ladder; pre-emptively splitting it is over-design.

    • Force tier-2 only for new projects, leave existing 18 on tier-0/1

      Rejected: Splits the ecosystem into 'old protocol' and 'new protocol' permanently — every cross-project feature has to handle the discontinuity. Permanent coexistence within a single protocol is cleaner: every project chooses its tier, every tier interoperates with the others (within voluntary-adoption limits), and the design admits no temporal cohort.

    Anti-patterns

    • Formalizing during the fire (or under perpetual firefighting)

      Treating ontoref as something to build or update mid-incident, when latency-to-action dominates — or adopting it at all when the work is perpetual firefighting or throwaway (a spike you will delete). The types, flows, and definitions become pure tax: they stop you to understand something you will not revisit. The resolution is not 'never stop' but the distinction between stopping-to-build and stopping-to-read: introspection is time-shifted, not eliminated. You pay in the calm and spend the dividend in the fire — a `describe impact` / `describe constraints` read costs seconds and tells you which pipe not to cut. Harm occurs only when you try to formalize during the fire, or when no calm window ever exists.

    • Tier-0 false certainty — form outruns truth, no witness to anchor

      A well-typed NCL graph looks authoritative even when it is stale or aspirational; the crispness of the form lends unearned credibility to the content. A desired_state can read as a real rudder when it was only a wish. Below tier-2 this harm is undefended: a pretty stale graph misleads exactly like a stale README — worse, because the README does not feign rigor. The witness (tier-2) is the only thing that anchors certainty to something independently verifiable; without it, ontoref can manufacture false certainty.

    • Ceremony capture — feeding the protocol instead of the work

      Adoption degrades into satisfying the protocol — updating nodes, writing ADRs, contenting validators — rather than doing the work the protocol was meant to serve. The tail wags the dog. This is the Yang collapse of formalization-vs-adoption: maximal formalization imposed where it does not pay back.

    • Premature formalization — freezing an unstable identity

      Typing something whose identity is not yet stable freezes a guess. Declaring invariants over what is not yet invariant — a pre-product-market-fit product, a person in genuine transition — ossifies what should stay fluid and makes the inevitable change expensive. Form applied before the substance has settled is rigidity, not coherence.

    +
    diff --git a/site/site/public/adr/adr-030/index.html b/site/site/public/adr/adr-030/index.html new file mode 100644 index 0000000..8c93971 --- /dev/null +++ b/site/site/public/adr/adr-030/index.html @@ -0,0 +1,276 @@ + + + +Catalog Discovery Cross-Project — Tier-2 Ops Beyond the Piloto Self-Host · Ontoref ADR + + +
    +
    + Proposed + adr-030 · 2026-05-26 +

    Catalog Discovery Cross-Project — Tier-2 Ops Beyond the Piloto Self-Host

    +
    + +

    Context

    ADR-029 commits the protocol to permanent tier coexistence: any project +may climb to tier-2 (operations adopted) voluntarily. The piloto self-host +(ontoref itself) demonstrated tier-2 functionally during the session of +2026-05-26 — 10 domain operations registered, dispatched, witnessed, +replay-deterministic. The piloto works because its operations are +compiled into the `ontoref-daemon` binary via `inventory::collect!` at +link time: `crates/ontoref-ops/src/ops/*.rs` annotated `#[onto_operation]` +emit static `OperationEntry` records; `dispatch_op(id, …)` resolves +against `inventory::iter::<OperationEntry>()`.

    +

    This works because the piloto and the daemon are co-developed in the same +repository — the daemon binary that serves the piloto IS the binary +compiled with the piloto's catalog baked in. The 18 other onboarded +projects (mirador, build-in-layers, lian-build, vapora, stratumiops, +kogral, libre-{daoshi,wuji,forge}, secretumvault, typedialog, jpl-website, +personal-ontoref, jpl-ontoref, cloudatasave, DD7pasos, librosys, +forge-fleet, mina) sit at tier-0 or tier-1; their adoption of tier-2 +would surface a structural gap that the current architecture does not +address:

    +

    When project P climbs to tier-2, where do P's domain operations live, + and how does the daemon discover and invoke them?

    +

    Today's only answer — "compile P's ops into the daemon binary and ship +that binary to P" — does not scale. It implies a per-project daemon +build, breaks the "one daemon, mixed-tier projects" model that +ADR-029's `ontoref-multi-tier-management` QA describes, and ties the +project's release cadence to the daemon's. For the piloto self-host this +is acceptable (ontoref's release IS the daemon's release); for any other +project it is not.

    +

    The catalog has two sides per ADR-029:

    +

    - DECLARATIVE side (NCL): `catalog/operations/<id>.ncl` and + `catalog/validators/<id>.ncl`. Per-project, in the project's own repo. + Typed against `catalog/schema.ncl` (the protocol-wide contract). + - EXECUTIVE side (Rust): the `#[onto_operation]`-annotated functions + + `#[onto_validator]`-annotated functions. Today baked into the + daemon binary via inventory.

    +

    The declarative side composes cleanly across projects — ADR-028 already +content-addresses ontologies; the catalog NCL files could be published +the same way (as Type-2 or Type-3 ontologies in `_ontology_refs.ncl`), +discovered via `fetch_cell_witness`, type-checked locally. The +executive side is the harder problem: how does the daemon, running once +per host and serving N projects of mixed tiers, obtain and invoke the +Rust handlers for project P's ops?

    +

    The session that produced ADR-029 surfaced this open question +explicitly: "the daemon binary that hosts ontoref-piloto has the 10 +piloto ops linked at compile time. When lian-build, provisioning, or any +other project climbs to tier-2 and authors its own catalog, the inventory +mechanism does not extend — those projects' ops are not in the daemon's +binary." The piloto demonstrates that tier-2 works; this ADR +acknowledges that scaling tier-2 across the ecosystem needs a discovery +mechanism beyond link-time inventory.

    +

    No external pressure yet justifies picking a mechanism. Only ontoref +itself is at tier-2. The remaining 18 projects have not signalled tier-2 +ambitions. This ADR records the problem, the design space, and the +trigger-based decision pattern (consistent with ADR-026 / D15 commitment +backend deferral, ADR-027 / D18 sync backend deferral, ADR-029 / D23 blob +backend deferral) — IMPLEMENT WHEN TRIGGERED, not before.

    Decision

    The catalog-discovery problem is real, named, and deferred behind +explicit trigger thresholds. The architecture reserves the abstraction +point and documents the design space so a future spike can land an +impl with minimum protocol disruption.

    +

    ABSTRACTION POINT — `CatalogBackend` trait (future)

    +

    A future trait in ontoref-ops or a new crate ontoref-catalog:

    +

    pub trait CatalogBackend { + fn discover_ops(&self, project: &ProjectContext) -> Vec<OperationEntry>; + fn discover_validators(&self, project: &ProjectContext) -> Vec<ValidatorEntry>; + }

    +

    The default impl `InventoryCatalogBackend` returns only the + link-time-registered entries (what the daemon does today). Future + impls extend the surface:

    +

    - DynLibCatalogBackend — loads .so/.dylib per project + - WasmCatalogBackend — loads WASM modules per project + - SidecarCatalogBackend — proxies dispatch to a per-project process + - InterpretedNclBackend — executes NCL-declared ops in-process + - CompositeCatalogBackend — combines the above

    +

    Each impl pairs with documented spike triggers (see below).

    +

    DESIGN SPACE — five candidate mechanisms

    +

    1. CompiledIntoDaemon (current state) + The project's ops are compiled into the daemon binary. The project + ships its own daemon build, OR the project's ops live in a workspace + crate that the daemon includes as a dep. + Pros: full Rust type safety; zero runtime overhead; replay + determinism preserved trivially. + Cons: does NOT scale beyond 1-2 projects co-developed with the + daemon; binary distribution per project; impossible cross-version. + Status: status quo for the ontoref piloto; not a general solution.

    +

    2. DynamicLibraryLoading (libloading / dlopen) + Project compiles `.so/.dylib`; daemon loads it at startup via + `libloading`. The library exposes a C-ABI surface that returns + `OperationEntry` records. + Pros: native Rust speed; the entries integrate naturally with + inventory after a one-time registration call. + Cons: ABI brittleness across Rust compiler versions; platform- + specific paths; security boundary is thin; symbol versioning is a + runtime hazard. + Trigger: 2+ projects at tier-2 + they're willing to compile against + a pinned Rust toolchain matching the daemon's.

    +

    3. WasmComponents (component model) + Project compiles ops to WASM components; daemon loads them via + wasmtime/wasmer with the WIT interface. + Pros: portable across platforms; security sandbox; project's choice + of source language (Rust, AssemblyScript, others); ABI is the + component model, not Rust's. + Cons: WASM build pipeline overhead; need a WIT surface for + OperationEntry + Slice + ValidationCtx + Verdict + OpOutput + (significant interface design); slight runtime overhead. + Trigger: 2+ projects + diverse host platforms + security + sandboxing required (untrusted ops authors). + Note: ADR-026 mentions WASM as candidate for tier-0 Structural + validators. The same infra could host operations.

    +

    4. SidecarProcess (per-project op process) + Each tier-2 project runs its own ops process (binary or + interpreter); daemon proxies `dispatch_op` calls via IPC (Unix + socket, gRPC, named pipe). The sidecar handles its own catalog. + Pros: process isolation; per-project deploy cycle independent of + daemon; language-agnostic (sidecar can be Rust, Go, Python, …). + Cons: extra process management; IPC latency on hot path; witness + signing crosses a process boundary (security review needed); + multiple processes per host complicates packaging. + Trigger: projects need independent op deploy cadence + IPC overhead + is acceptable per dispatch.

    +

    5. InterpretedNclOps (NCL bodies executed in-process) + The op body is declared as an NCL expression (or small DSL) in the + catalog/operations/<id>.ncl file. The daemon interprets it against + `ctx` and `inputs`. No Rust handler required. + Pros: zero deploy overhead; ops author entirely in NCL; cross- + project ops trivially shareable as content-addressed ontologies. + Cons: loses Rust type safety; need an expression evaluator + safe + sandbox; performance lower than native; complex ops (signature + schemes, blob hashing) hard to express. + Trigger: most tier-2 ops in the ecosystem turn out to be small + state-mutation verbs that need no native code, and the cost of + expressing them in NCL is less than the cost of compiled-per- + project handlers. + Note: this is the most aligned with "voluntary-adoption, minimal + friction" if the expressive power suffices.

    +

    6. SidecarProcessWithSharedDomainLibrary + Specialisation of #4 (SidecarProcess) aligned with ADR-018 level + hierarchy. The domain (Level 2, e.g. provisioning) ships a Rust + LIBRARY crate `<domain>-ops` containing the generic + `#[onto_operation]` handlers. Each instance (Level 3, e.g. + libre-daoshi / libre-wuji / libre-forge) compiles its OWN sidecar + BINARY that depends on the domain library AND adds + instance-specific ops. The ontoref main daemon proxies dispatch + to each instance's sidecar via HTTP.

    +

    Convergence/divergence split: + CONVERGE (single source of truth) + - Catalog NCL declarations (shared at domain level via ADR-028) + - Rust handler code (one `<domain>-ops` library crate) + - catalog/schema.ncl validations (protocol-wide) + DIVERGE (per-instance) + - Sidecar binaries (each instance compiles its own) + - Deploy cadence (independent per instance) + - Signing keys per actor per instance + - Instance-specific ops (overrides + extensions) + - Sidecar process per host per instance

    +

    Maps onto ADR-018 mode resolution semantics: + Delegate → instance uses the domain handler unchanged + Override → instance provides its own handler for the same op id + Compose → instance wraps the domain handler with extra logic

    +

    Pros: ZERO new code in the daemon beyond a ~80-LOC proxy handler; + full Rust type safety for handlers (lib path/git dep); + per-instance deploy independence; blast radius isolated per + instance; instances can climb to tier-2 voluntarily without + forcing siblings; signing keys stay per-instance per-actor + (no shared cryptographic boundary); replay determinism preserved. + Cons: N processes per host where N = number of tier-2 instances; + Rust toolchain coupling between domain library and instance + sidecars; HTTP IPC overhead per dispatch (~50-500µs measured); + cross-instance ops require IPC chain (instance A → main daemon + → instance B's sidecar). + Trigger: domain with multiple Level-3 instances reaches tier-2 + AND deploy cadences are independent AND HTTP IPC overhead is + acceptable. provisioning (with libre-daoshi, libre-wuji, + libre-forge as instances) is the canonical example. + Note: this is the RECOMMENDED near-term path for shared-domain + tier-2 adoption — implementable today, no WASM spike required.

    +

    7. CompositeBackend (mix of the above) + Daemon loads multiple backends in priority order; an op lookup + consults each backend until one returns a match. Lets ontoref-piloto + keep its CompiledIntoDaemon backend AND add a second backend for + external tier-2 projects. + Pros: incremental migration; the piloto doesn't have to change to + accommodate new projects; the abstraction holds even with one + backend. + Cons: dispatch lookup is O(N backends); priority semantics need + clear documentation.

    +

    CHOSEN MECHANISM — tiered selection by trigger

    +

    The 7 mechanisms split into two phases of the design space:

    +

    Phase A — implementable today, no spike required: + 1. CompiledIntoDaemon — current piloto self-host + 6. SidecarProcessWithSharedDomainLibrary + — RECOMMENDED for shared-domain + tier-2 (provisioning, etc.) + 7. CompositeBackend (combines 1 + 6)

    +

    Phase B — requires a spike with documented trigger conditions: + 2. DynamicLibraryLoading — Rust ABI brittleness + 3. WasmComponents — WIT interface design + runtime cost + 4. SidecarProcess (raw) — base case of #6 without domain lib + 5. InterpretedNclOps — NCL expressive power proof needed

    +

    For Phase A mechanisms, the protocol does NOT defer — implementation + may proceed when a project commits to the pattern. Mechanism #1 is + already in production (the piloto). Mechanism #6 unblocks any shared + domain (provisioning + its Level-3 instances) at the cost of ~80 LOC + proxy handler in ontoref-daemon + path/git dep for the domain library.

    +

    For Phase B mechanisms, the protocol defers behind explicit triggers:

    +

    T1. THREE OR MORE projects (besides ontoref) author tier-2 catalogs + AND find Phase A mechanisms operationally insufficient. + T2. The ontoref piloto's catalog grows beyond ~30 ops to a size + where the inventory pattern shows cracks (compile-time startup, + debug builds, hot-reload friction). + T3. A consumer project requests cross-project ops dispatch (op A in + project P invokes op B in project Q) — IPC chain in mechanism #6 + becomes the bottleneck. + T4. Toolchain divergence — an instance wants ops in a non-Rust + language; dynlib / WASM is the only way. + T5. Untrusted ops authors — the deployment must sandbox ops from + outside the instance's control; WASM is the natural fit.

    +

    When ANY trigger fires for Phase B, a session is opened to: + 1. Audit which of the Phase B mechanisms (2, 3, 4 raw, 5) fits the + empirical constraints (host platforms, security model, deploy + cadence, language requirements). + 2. Define the WIT / C ABI / NCL DSL surface as appropriate. + 3. Implement the chosen backend behind the `CatalogBackend` trait. + 4. Document the new backend's spike triggers (mirroring the + ADR-026 / D15 spike-triggers pattern).

    +

    This ADR commits the abstraction point and the design space. It does + not commit the implementation. The same trigger-based discipline that + applies to CommitmentBackend (D15), SyncBackend (D18), CrdtMergeStrategy + (D19), and BlobBackend (D23) extends to CatalogBackend (D30).

    +

    DECLARATIVE SIDE — content-addressed catalogs as ontologies

    +

    The declarative side (`catalog/operations/<id>.ncl`) composes + trivially via ADR-028's content-addressed ontology layer. A project's + catalog NCL files can be published as a Type-2 or Type-3 ontology; + consumer projects subscribe to that ontology and gain typed knowledge + of "ops project P exposes". This works TODAY at any tier — the + catalog declarations are inert at tier-0/1 (declarative documentation + only) but compose cross-project. The executive-side gap is what this + ADR defers; the declarative-side composition is already operational.

    +

    CROSS-TIER COMPOSITION (preserved by design)

    +

    Tier-0 / tier-1 projects can declare their own `catalog/operations/` + as forward-looking documentation without paying the executive cost. + Tier-2 projects can reference the declarative ontology of any other + project (via OntologyRef in OperationDecl). The discovery of a + project's executive handlers is the only gap this ADR names; the + declarative half of the catalog already supports the full + cross-project composition envisioned by ADR-028.

    Constraints

    • Hard

      The default `CatalogBackend` impl MUST continue to support the ontoref piloto self-host without changes to the piloto's catalog format. Any future backend that supersedes CompiledIntoDaemon as default MUST be a strict superset of its capabilities for the piloto's 10 ops.

    • Hard

      The declarative side of `catalog/operations/<id>.ncl` and `catalog/validators/<id>.ncl` MUST remain composable cross-project via ADR-028 content-addressing at ANY tier — independent of which `CatalogBackend` impl is in use.

    • Hard

      When more than one `CatalogBackend` impl exists, the daemon MUST select impls based on explicit configuration in `.ontoref/config.ncl::ops.catalog_backend` (or equivalent). No automatic selection by environmental detection.

    Alternatives considered

    • Pick CompiledIntoDaemon as the only mechanism, declare cross-project ops out-of-scope permanently

      Rejected: Forces every tier-2 project to either fork the daemon or upstream their ops into ontoref's workspace. Neither is acceptable for projects with independent release cycles, private domains, or non-Rust catalog authors. Violates the spirit of voluntary-adoption — climbing to tier-2 should be a per-project act, not gated on the protocol's deploy cadence.

    • Pick WASM components now and commit to it

      Rejected: WASM is plausible but the design surface (WIT for OperationEntry + Slice + ValidationCtx + Verdict + OpOutput) is substantial, the runtime overhead unknown, and there is no second tier-2 project signalling demand. Premature commitment without empirical evidence repeats the patterns the trigger-based discipline (ADR-026/D15, ADR-027/D18, ADR-029/D23) was designed to prevent.

    • Pick DynamicLibraryLoading now

      Rejected: Rust ABI is unstable across compiler versions, making dynlib coupling brittle. Without a pinned Rust toolchain shared between the daemon and the project's ops crate, the ABI breaks silently. This is a real-world maintenance burden that surfaces only after the second project ships. Defer until at least one project commits to the toolchain discipline.

    • Defer the abstraction point itself — don't add `CatalogBackend` trait until impl is ready

      Rejected: Without naming the abstraction, future sessions re-rediscover the problem and may pick conflicting solutions. Naming the trait + listing the 5 candidates with trade-offs is cheap (one ADR) and load-bearing for the next session that engages with cross-project tier-2. The design exploration is the deliverable; the impl is the follow-up.

    • Push everyone toward InterpretedNclOps and declare native handlers a legacy path

      Rejected: NCL/expression languages have not been proven expressive enough to handle the full range of ops (Ed25519 signing, blake3 hashing, oplog append, render pipelines). The piloto's 10 ops use native Rust for non-trivial logic. Committing to NCL-only would either restrict ops to the trivial subset OR force a complex NCL expression engine with its own safety story. Defer until evidence shows the trivial subset covers most real ops.

    +
    diff --git a/site/site/public/adr/adr-031/index.html b/site/site/public/adr/adr-031/index.html new file mode 100644 index 0000000..033b26e --- /dev/null +++ b/site/site/public/adr/adr-031/index.html @@ -0,0 +1,242 @@ + + + +Ontology and Reflection as Constitutive Duality — Co-Equal Axes Sealed by Witness · Ontoref ADR + + +
    +
    + Accepted + adr-031 · 2026-05-26 +

    Ontology and Reflection as Constitutive Duality — Co-Equal Axes Sealed by Witness

    +
    + +

    Context

    The named tension `ontology-vs-reflection` has lived in `.ontology/core.ncl` +since the project's first ondaod pass with `pole = 'Spiral` and the gloss +"This tension is onref's core identity." The tension is the protocol's name +for itself: **on+re**. Ontology captures what IS (invariants, structure, +being); Reflection captures what BECOMES (operations, drift, memory). The +Spiral pole records that neither half is reducible to the other.

    +

    The session arc 2026-05-20 → 2026-05-26 (ADR-023 verifiable substrate, ADR-024 +operations layer, ADR-025 ontology core as state and NCL as manifestation, +ADR-026 three-plane validation, ADR-027 P2P pluggable sync, ADR-028 +ontology layer separation from project layer, ADR-029 tier coexistence, +ADR-030 catalog discovery cross-project) produced a precise, lived +articulation of one half of the tension — the **ontology axis**. Substrate +(G1-G10), commit roots, state authority, content-addressed ontologies, +schema discovery: all of these structurally name the substance pole. The +**reflection axis** received concrete commitments too (operations as the +only mutation path, witnesses as cryptographic acts, reflection modes as +NCL DAGs, describe queries as self-knowledge), but its axiom-level status +was never asserted on equal footing.

    +

    Two observations made the gap visible:

    +

    (1) A reading of the eight 2026-05-20s ADRs against the external "modern + ontology stack" vocabulary (Cagle / Shannon 2026-05-19, *What a Modern + Ontology Stack Actually Looks Like*) revealed that the article's five + layers (annotational, schema, graph, projection, inference) cover the + ontology axis cleanly and have no vocabulary for the reflection axis. + The article's "inference layer" is passive derivation (SPARQL, + SHACL rules, LLM resonance over a static substrate); ontoref's + reflection layer is enactive (DAG modes execute work, forms mutate + lifecycle, run protocols are acts the system performs on itself). + Forcing reflection into a "layer" of an ontology-axis stack imports + the substance-only metaphysics the external article carries.

    +

    (2) ADR-030 surfaces the duality operationally without naming it: the + catalog has a "DECLARATIVE side (NCL)" — ontology pole — and an + "EXECUTIVE side (Rust)" — reflection pole. ADR-030 § decision + observes that the declarative side composes cross-project today via + ADR-028 content-addressing while the executive side awaits triggers. + The two sides progress at different rates because they live on + different axes. ADR-030 names this asymmetry empirically; ADR-031 + names it architecturally.

    +

    Without an axiom-level recognition, the same drift will recur: future +sessions may interpret reflection components as "tooling around the +ontology" or treat state as primary rather than as a manifestation that +acts deposit on substance. ADR-025 already moved state from primary to +"render of authority"; that move is half of what this ADR generalises. The +generalisation: the protocol is **constitutively dual**. Every component +sits on the ontology axis, the reflection axis, or the seam where the two +are bound. The seam has a name in the existing architecture — the +**witness** (ADR-023 § decision, ADR-024 § acting-binding). Every reflective +act emits a witness; every witness deposits manifestation into the +ontological substrate. The seam is the formalism by which the two axes +remain bound without one absorbing the other.

    +

    The risk of NOT recording this duality at axiom level is concrete: the +external "modern ontology stack" vocabulary is mature, legible, and +adoption-ready, but it carries a substance-only frame that — uncritically +imported — would collapse the protocol's core identity. The recent ADRs +023-030 stand within the protocol's own discipline (ondaod, Spiral +preservation, voluntary-adoption). An ADR that elevates the duality +itself becomes the constitutional anchor against which any future external +vocabulary import is checked.

    Decision

    Elevate the existing `ontology-vs-reflection` Spiral tension to an axiom- +level constitutive duality of the protocol. Two new axiom nodes are added +to `.ontology/core.ncl`; the existing tension node remains in place as the +ondaod-procedural anchor (tensions are where the Spiral is named; axioms +are where the structural commitments live). The duality has three +formal commitments:

    +

    1. ONTOLOGY AXIS — `ontology-axis-substance` (axiom, invariant=true)

    +

    The protocol's substance pole. Names: axioms, tensions, practices, + schemas, ADR declarations, content-addressed ontologies, state as + manifestation (ADR-025), substrate commitments (ADR-023), the + declarative side of catalogs (ADR-030). Maps onto the existing + `pole = 'Yin` convention for nodes whose primary character is + being/structure.

    +

    Operational reading: every artifact that answers "what IS" — a + schema declaration, an ADR constraint, an ontology node, a render + from state, a commit root — lives on this axis.

    +

    2. REFLECTION AXIS — `reflection-axis-act` (axiom, invariant=true)

    +

    The protocol's act pole. Names: operations, dispatch, reflection + modes (DAG enactment), forms (lifecycle mutation), describe queries + (self-observation), run protocols (acts the system performs on + itself), the executive side of catalogs (ADR-030), validator + execution. Maps onto the existing `pole = 'Yang` convention for + nodes whose primary character is becoming/operation.

    +

    Operational reading: every artifact that answers "what DOES" or + "what KNOWS itself" — a domain operation, a mode step, a form + submission, a `describe` invocation, a validator firing, a run + transition — lives on this axis.

    +

    3. WITNESS AS SEAM — `witness-as-axis-seam` (axiom, invariant=true)

    +

    The witness (ADR-023 G5 commitment + signature, ADR-024 acting- + binding) is the formal seam where the two axes meet. Every act on + the reflection axis that mutates substance emits a witness; every + mutation of the ontology axis state is the deposit of a witnessed + act. The witness is neither pure substance nor pure act — it is the + point at which the two are bound. Maps onto the existing + `pole = 'Spiral` convention for nodes that sustain both poles + without collapse.

    +

    Operational reading: any component on the seam (witness chain, + commit emission, dispatch protocol, state reconciliation in + Phase 1 of ADR-025) carries the load of preserving the duality + across the act-to-substance transition.

    +

    AXIS DECLARATION REQUIREMENT (new structural property):

    +

    Every node in `.ontology/core.ncl` already carries a `pole` field + ('Yin, 'Yang, 'Spiral) and a `level` field ('Axiom for invariant-level + nodes among others). ADR-031 fixes the operational semantics of the + pole field as the axis-declaration mechanism:

    +

    'Yin → ontology axis (substance side) + 'Yang → reflection axis (act side) + 'Spiral → seam (sustains both poles; not a derived synthesis)

    +

    The pole field of `Axiom`-level nodes that ALSO commit to an axis MUST + carry a secondary `axis` field with value 'Substance | 'Act | 'Seam. + This is additive and zero-migration for the existing nodes whose pole + already encodes their axis cleanly.

    +

    GLOSSARY AXIS FIELD (forward integration):

    +

    The forthcoming `.ontology/glossary.ncl` schema (anticipated by the + ontoterm-* skills and the present session's draft state) MUST include + an `axis: 'Substance | 'Act | 'Seam` field on every canonical term. + Terms that name substance ("axiom", "schema", "manifestation", + "commit-root") declare 'Substance. Terms that name acts ("operation", + "dispatch", "describe", "mode", "form") declare 'Act. Terms that name + seams ("witness", "on+re", "ondaod") declare 'Seam.

    +

    FORBIDDEN PATTERNS (consequence of the duality):

    +

    The following architectural moves become explicit anti-patterns under + ADR-031:

    +

    (a) Treating reflection components as "tooling" or "infrastructure + around the ontology". Reflection is axis-level; not subordinate. + (b) Treating state (ADR-025 manifestation) as the primary seat of + authority. State is derived from witnessed acts; it is substance + sediment, not source. + (c) Importing external vocabulary that names only one axis (e.g. the + RDF / SHACL semantic-stack vocabulary that has no analogue for + enactive reflection) AS IF it covered the protocol. Such + vocabulary may be used for projection (per ADR-028 / ADR-030), + but never as primary terminology for ontoref components. + (d) Synthesizing the two axes into a single "stack" or "layered + architecture". The duality is constitutive: there is no layer + in which it dissolves. + (e) Designing components that operate on one axis without declaring + their seam interaction. A new reflection-axis component (e.g. a + new mode kind, a new form variant) MUST declare how its acts + emit witnesses; a new ontology-axis component (e.g. a new schema + type, a new ADR family) MUST declare how it receives manifestation + from witnessed acts.

    +

    OPERATIONAL CONSEQUENCES (downstream ADRs reread under ADR-031):

    +

    - ADR-023 verifiable substrate: G1-G5 are substance-axis (types, + triples, commitments); G6 (oplog) is act-axis (recording acts); G5 + witness is seam. + - ADR-024 operations layer: acting-binding declares the act-axis as + the only path to substance mutation. ADR-031 generalises: + "structurally enforced internal coherence" (the existing axiom) + operates by routing all acts through the seam. + - ADR-025 state as manifestation: substance is rendered from + witnessed act log. The axiom split (voluntary-adoption + + internal-coherence-enforced) ADR-025 introduced is now expressible: + voluntary-adoption is a property of the BINDING between act and + substance (adoption is itself an act); internal-coherence-enforced + is a property of the SEAM (witness chain rejects ill-formed acts). + - ADR-026 three-plane validation: the three planes (Structural / + Semantic / Dialectical) operate at the seam. They are not + substance-axis checks; they are conditions on what acts may pass + through the seam. + - ADR-027 P2P pluggable sync + ADR-028 ontology layer separation: + cross-project content-addressing is substance-axis (the ontology + travels as a substance artifact). Cross-project operation invocation + (ADR-030) is act-axis (the act travels). The seam-axis question is + cross-project witness verification (how does project B verify a + witness signed by project A) — recorded as an open seam-axis + question for future ADR. + - ADR-029 tier coexistence: the three tiers correspond to three + points of duality engagement. tier-0 engages only the substance + axis (NCL files). tier-1 adds substrate (substance-axis with + cryptographic state, but no acting-binding). tier-2 fully engages + the seam (acts emit witnesses; substance is sediment of witnessed + acts). The tiers are progressive realisations of the same duality, + not separate architectures. + - ADR-030 catalog discovery: the declarative/executive split is the + ontology/reflection split. Phase A mechanisms preserve the duality + (declarative composes via substance-axis content addressing; + executive composes via act-axis dispatch). The CatalogBackend + abstraction sits on the seam.

    +

    NON-DECISIONS (deliberately left open):

    +

    - Terminology for the two poles in user-facing documentation + (substance/act, being/becoming, yin/yang, 体/用, ontology/reflection). + The axiom IDs `ontology-axis-substance` and `reflection-axis-act` + fix the canonical English handle; vocabulary choice in docs, + glossary, and CLI output is open for an annotational-layer ADR. + - Whether the witness seam needs a finer decomposition (witness emission + vs witness verification vs witness chain integrity) at axiom level. + Deferred until cross-project witness verification (per ADR-028) + surfaces empirical pressure. + - Whether reflection-axis components require their own typing + discipline parallel to ontology-axis schemas. The current operation + catalog (ADR-030 declarative side) is the existing answer; whether + a richer "operation ontology" is needed is deferred.

    Constraints

    • Hard

      .ontology/core.ncl MUST contain three new Axiom-level nodes with ids ontology-axis-substance, reflection-axis-act, and witness-as-axis-seam. All three MUST have invariant=true. The existing ontology-vs-reflection Tension node MUST remain present (axiom elevation is additive, not replacing).

    • Hard

      Each of the three new Axiom nodes MUST declare an axis field with value 'Substance (for ontology-axis-substance), 'Act (for reflection-axis-act), or 'Seam (for witness-as-axis-seam). The field is additive; pre-existing axioms are unaffected.

    • Hard

      Documentation and ADRs MUST NOT adopt the Cagle/Shannon modern-ontology-stack five-layer model (annotational / schema / graph / projection / inference) as primary self-description of ontoref. The model MAY be used at the projection layer (docs/correspondences/) for external-audience translation, but MUST NOT replace the on+re duality vocabulary in core protocol description.

    • Hard

      This ADR engages and synthesizes the named tension ontology-vs-reflection AND records the synthesis as axiom-level elevation rather than collapse. The synthesis state and direction of motion MUST be discoverable in the decision and rationale of this ADR.

    • Hard

      A migration MUST be added to reflection/migrations/ that instructs consumer projects to add the three new Axiom nodes (ontology-axis-substance, reflection-axis-act, witness-as-axis-seam) to their own core.ncl. The migration MUST be idempotent and MUST NOT remove the existing ontology-vs-reflection Tension node.

    Alternatives considered

    • Leave ontology-vs-reflection as a Tension only, without elevating to axiom

      Rejected: The current Tension-only position is what allowed the asymmetric articulation observed in ADRs 023-030 — substance pole received structural commitments (substrate, state authority, content-addressing) while act pole received only operational commitments (operations exist, modes exist) without axiom-level recognition. Without axiom elevation, the same drift will recur on every future external-vocabulary import. The Tension records the polarity; only an axiom records the structural commitment to preserve it.

    • Adopt the Cagle/Shannon modern-ontology-stack five-layer model as ontoref's primary self-description

      Rejected: The five-layer model (annotational, schema, graph, projection, inference) covers the substance axis cleanly and has NO vocabulary for the act axis beyond passive derivation in the inference layer. Adopting it primarily would erase the reflection axis from ontoref's self-description, collapsing the duality the existing Tension explicitly preserves. The model is useful at the projection layer (per ADR-028) as a correspondence for external RDF/SHACL audiences — but not as primary terminology.

    • Introduce three new axes (substance, act, seam) without preserving the existing ontology-vs-reflection Tension node

      Rejected: Removing the Tension node would lose the ondaod-procedural anchor: tensions are where the Spiral is named for review by future sessions. Replacing the Tension with three Axioms collapses the dialectical record into a structural commitment. ADR-031 keeps both: the Tension preserves the Spiral-naming discipline; the three new Axioms commit to the structural property. Both are non-redundant.

    • Use only the existing pole field (Yin/Yang/Spiral) without adding an explicit 'axis' field on the new axiom nodes

      Rejected: The pole field carries informal convention; axiom nodes deserve an explicit field for axis declaration because (a) axioms set the structural commitment that the convention must henceforth follow, and (b) the explicit field protects against future pole-field semantics drift. The 'axis' field is additive and zero-migration; the cost is small, the explicitness benefit is real.

    • Defer axiom elevation until cross-project witness verification surfaces empirical pressure

      Rejected: The pressure has already surfaced — twice. First operationally (ADR-030's declarative/executive split). Second meta-architecturally (the present session's analysis of external semantic-stack vocabulary). Deferring would let future sessions silently adopt vocabulary that erodes the duality. The cost of an early axiom is small; the cost of late axiom recovery (retroactively rejecting an adopted vocabulary) is large. Trigger-based deferral (ADR-026/D15 pattern) applies to abstraction-point IMPL choices, not to constitutional commitments.

    Anti-patterns

    • Reflection-as-Tooling

      Treating reflection components — modes, forms, describe, dispatch, +validators — as "tooling" or "infrastructure around the ontology". +Demotes the act axis to subordinate plumbing. Reflection is axis-level, +co-equal with ontology; calling it tooling erases the constitutive +duality at the vocabulary boundary.

    • State-as-Primary-Authority

      Treating state (the manifestation rendered from witnessed acts) as the +primary seat of authority. Reverses the ADR-025 direction: state is +substance sediment, not source. Acting on state directly bypasses the +witness chain that gives state its integrity.

    • Single-Axis Vocabulary Import

      Adopting an external vocabulary that names only one axis (e.g. the +RDF/SHACL/OWL semantic-stack: annotational, schema, graph, projection, +inference) as ontoref's primary terminology. Imports a substance-only +metaphysics that has no analogue for enactive reflection — the act axis +becomes invisible in the imported lexicon.

    • Duality-as-Layered-Stack

      Flattening the on+re duality into a single layered architecture (e.g. +"ontology layer → operations layer → UI layer"). Layers imply +subordination; the substance and act axes are co-equal and the seam is +not a layer between them but the formalism that binds them. There is no +layer in which the duality dissolves.

    • Axis Component Without Seam Declaration

      Designing a new component that operates on one axis without declaring its +seam interaction. A reflection-axis component (new mode kind, new form +variant) that does not declare how its acts emit witnesses. An ontology- +axis component (new schema family, new ADR genre) that does not declare +how it receives manifestation from witnessed acts. Either omission lets +the two axes drift toward disconnection one component at a time.

    +
    diff --git a/site/site/public/adr/adr-032/index.html b/site/site/public/adr/adr-032/index.html new file mode 100644 index 0000000..65009f9 --- /dev/null +++ b/site/site/public/adr/adr-032/index.html @@ -0,0 +1,161 @@ + + + +Layout Consolidation Under .ontoref/ Root — Single Hidden Hierarchy for the Protocol's Consumer Footprint · Ontoref ADR + + +
    +
    + Accepted + adr-032 · 2026-05-26 +

    Layout Consolidation Under .ontoref/ Root — Single Hidden Hierarchy for the Protocol's Consumer Footprint

    +
    + +

    Context

    Every project that adopts ontoref currently inherits a sprawl of root +directories: `.ontoref/` (hidden — config and runtime state), `.ontology/` +(hidden — declarative ontology), `adrs/` (visible — Architecture Decision +Records), `reflection/` (visible — qa, backlog, modes, schemas, modules, +migrations, nulib), and most recently `catalog/` (visible — operations and +validators per ADR-024, ADR-026). For consumer projects this means:

    +

    - Five root directories owned by ontoref alone, consuming 33-40% of the + `L-ROOT-LIMIT` (target 12-15 total root dirs) defined in + `.claude/layout_conventions.md`. + - Inconsistent hidden/visible split: `.ontoref/` and `.ontology/` are + dot-prefixed (following `L-NAMING-HIDDEN` for infrastructure dirs), + while `adrs/`, `reflection/`, `catalog/` are visible without + architectural justification for the inconsistency. + - Mixed identity signal: the visible directories present as + "first-class project content" alongside `crates/`, `src/`, `docs/`, + when in fact they are infrastructure of the protocol supporting the + project — not the project's substantive content. + - Operational friction: copying ontoref state across projects requires + enumerating five paths; deleting cleanly requires five `rm -rf` + invocations; CI exclusion requires five glob patterns; backup/restore + means five tar entries; auditing "is ontoref clean here?" means five + independent checks.

    +

    The current layout grew organically across ontoref's evolution. Each +section (`.ontology/` from the earliest ondaod pass; `adrs/` from +adr-001's adoption of ADR-as-data; `reflection/` from the +adopt-ontoref-tooling onboarding template; `catalog/` from ADR-024's +operations layer) arrived as a section of the protocol's design and was +placed where it made narrative sense AT THAT MOMENT. Nothing ever +collected them under a common root because the protocol grew as a tree, +not as a module. The session 2026-05-26 (ADR-029 tier coexistence +articulation, then ADR-030 catalog discovery, then ADR-031 on+re duality +elevation) produced both more sub-directories AND increased visibility +of the cost of the layout sprawl. The user's observation:

    +

    "ontoref debería aparecer en los proyectos como una estructura en un sólo + folder ... esto convierte al layout de proyectos en un lío además de no + cumplir con `.claude/layout_conventions.md`. Facilitaría mucho que todo + estuviera dentro de una misma jerarquía."

    +

    This ADR records that observation as architectural decision and consolidates +ontoref's consumer footprint into a single hidden hierarchy under +`.ontoref/`, propagated per-project via migration 0023 (opt-in, consistent +with `voluntary-adoption`). The change is a protocol version increment to +**0.1.1** — breaking layout change with migration assistance and dual-path +resolver back-compat during the transition window.

    Decision

    All ontoref consumer-facing artefacts MUST consolidate under a single +hidden root directory `.ontoref/` per consumer project. The canonical +layout becomes:

    +

    my-project/ + └── .ontoref/ ← single hidden ontoref subsystem root + ├── config.ncl ← daemon config (unchanged location) + ├── project.ncl ← project identity (unchanged location) + ├── actors.ncl ← actor keypairs (unchanged location) + ├── ontology/ ← MOVED FROM .ontology/ + │ ├── core.ncl + │ ├── state.ncl + │ ├── gate.ncl + │ ├── manifest.ncl + │ └── _refs.ncl ← was _ontology_refs.ncl + ├── adrs/ ← MOVED FROM adrs/ + │ ├── adr-*.ncl + │ └── _template.ncl + ├── reflection/ ← MOVED FROM reflection/ + │ ├── qa.ncl + │ ├── backlog.ncl + │ ├── search_bookmarks.ncl + │ ├── modes/ + │ ├── forms/ + │ ├── schemas/ + │ ├── modules/ + │ ├── migrations/ + │ └── nulib/ + ├── catalog/ ← MOVED FROM catalog/ + │ ├── schema.ncl + │ ├── operations/ + │ └── validators/ + ├── store/ ← runtime state (oplogs, witness chain) + │ └── ontologies/ ← was .ontoref/ontologies/ (content-addressed oplogs) + └── locks/ ← runtime advisory locks (unchanged)

    +

    The consumer project's own content (`crates/`, `src/`, `docs/`, `assets/`, +etc.) retains its existing layout. Only ontoref's footprint moves.

    +

    DUAL-PATH RESOLVER (transition period)

    +

    During the version 0.1.0 → 0.1.1 transition, the daemon implements a + dual-path resolver in `crates/ontoref-daemon/src/registry.rs` and + `crates/ontoref-ontology/src/ontology.rs` that:

    +

    1. Prefers the new path (`.ontoref/<section>/`) when it exists. + 2. Falls back to the legacy path (root-level `.ontology/`, `adrs/`, + `reflection/`, `catalog/`) when the new path is absent. + 3. Emits an `info`-level log when the legacy path is used, noting + the project has not yet applied migration 0023.

    +

    The dual-path resolver remains in the codebase until ontoref version + 0.2.0; at that point a future ADR may declare the legacy paths + removed (advisory, not forced).

    +

    ONTOREF-EL-PROYECTO MIGRATES FIRST (canonical demonstration)

    +

    Per the `self-describing` axiom (invariant=true), ontoref-el-proyecto + applies migration 0023 before any consumer project. This produces:

    +

    - The canonical layout demonstration that other projects can follow + - First-class evidence that the migration script works on real + content (ontoref's own ontology has 40 nodes, 91+ edges, 32 ADRs) + - The reference state for the dual-path resolver tests

    +

    Consumer projects (mirador, libre-{daoshi,wuji,forge}, lian-build, the + other 14 currently onboarded) apply migration 0023 voluntarily on + their own schedules. Until they do, the daemon serves them via the + legacy paths through the dual-path resolver.

    +

    SCOPE — WHAT MOVES, WHAT DOES NOT

    +

    MOVES into .ontoref/: + .ontology/ → .ontoref/ontology/ + adrs/ → .ontoref/adrs/ + reflection/ → .ontoref/reflection/ + catalog/ → .ontoref/catalog/

    +

    STAYS at root (for ontoref-el-proyecto only, not for consumers): + crates/ (Rust implementation crates — not consumer-relevant) + install/ (install scripts — not in consumer projects) + justfiles/ (build automation) + scripts/ (support scripts — co-existing with the consumer's own) + templates/ (consumer onboarding templates) + ontology/ (defaults + schemas installed to consumer data dir) + assets/ (web + presentation)

    +

    REMAINS unchanged (already under .ontoref/): + .ontoref/config.ncl + .ontoref/project.ncl + .ontoref/actors.ncl + .ontoref/locks/

    +

    NCL IMPORT PATH UPDATES

    +

    Inside the moved files, relative `import "..."` paths are updated to + reflect the new depth:

    +

    OLD: `.ontology/core.ncl` with `import "../ontology/defaults/core.ncl"` + NEW: `.ontoref/ontology/core.ncl` with `import "../../ontology/defaults/core.ncl"`

    +

    The daemon's `NICKEL_IMPORT_PATH` (managed by `global_schema_dirs()`) + continues to include the data-dir schemas at `$data_dir/ontology/schemas/` + and `$data_dir/ontology/`. Absolute schema imports (`import "manifest"`, + `import "core"`) keep working unchanged.

    Constraints

    • Hard

      All ontoref consumer-facing artefacts (ontology declarations, ADRs, reflection store, catalog declarations, runtime state, locks) MUST live under `.ontoref/` in a consumer project. The legacy root-level directories `.ontology/`, `adrs/`, `reflection/`, `catalog/` are deprecated by migration 0023 and removed by the migration's mv operations.

    • Hard

      The daemon's `registry.rs` and `ontology.rs` MUST implement a dual-path resolver that prefers the new path (`.ontoref/<section>/`) and falls back to the legacy path (root-level `.ontology/`, `adrs/`, `reflection/`, `catalog/`) when the new path is absent. The fallback MUST emit an info-level log noting the project has not yet applied migration 0023.

    • Hard

      Migration 0023 MUST be applied per-project via the standard `ontoref migrate` workflow. No automatic / silent / boot-time migration of consumer projects is permitted. The daemon detects un-migrated consumers via the legacy-path fallback and surfaces the recommendation via `ontoref migrate pending`, but does not act on it.

    • Hard

      ontoref-el-proyecto (this repository) MUST apply migration 0023 to its own consumer-facing artefacts before any consumer project is recommended to apply it. The canonical layout under `.ontoref/` is demonstrated in ontoref's own repo first; consumer documentation references the ontoref repo as the worked example.

    • Hard

      The project beacon `card.ncl` and the generated `artifacts/api-catalog-*.ncl` files MUST live under `.ontoref/` after migration 0023, NOT at project root. `card.ncl` is pure ontoref metadata (not an OS-level discovery convention like `Cargo.toml` or `package.json`) and `artifacts/` is generated ontoref output — both belong under the consolidated root.

    • Hard

      Section paths (ontology_dir, adrs_dir, reflection_dir, catalog_dir, artifacts_dir, card_path) MUST be addressable through `LayoutConfig` rather than hard-coded literals in production code. Defaults match the consolidated layout (`.ontoref/<section>/`), but projects MAY override any field via their `.ontoref/config.ncl` `[layout]` section. All call sites that read or write ontoref artefacts MUST route through `ontoref_ontology::layout::resolve_section` (reads, with legacy fallback) or `canonical_section` (writes, no fallback) or an explicit `LayoutConfig::resolve` / `canonical` method.

    Alternatives considered

    • Keep the current layout — accept the L-ROOT-LIMIT violation as a cost of organic growth

      Rejected: The user's observation made the cost concrete and named the protocol's own convention (`.claude/layout_conventions.md`) as the rule being violated. The protocol cannot ignore its own conventions without losing credibility as a self-describing system. Five root directories for one subsystem is not organic growth; it is unsynchronised growth that the consolidation corrects.

    • Move ALL of ontoref under a visible `ontoref/` directory (not hidden)

      Rejected: Visible directories signal 'first-class project content' alongside crates/, src/, docs/. ontoref is infrastructure-of-the-project, not the project's value content. L-NAMING-HIDDEN explicitly prescribes dot-prefix for process and configuration directories. Visible `ontoref/` would re-introduce the same identity-signal confusion the consolidation is meant to eliminate.

    • Hybrid layout — runtime state hidden, declarative content visible (`.ontoref/store/` + `ontoref/ontology/`, etc.)

      Rejected: Re-introduces the inconsistency this ADR was opened to resolve. Two root dirs instead of five is an improvement but stops short of the single-point operational manipulation that the configuration-cube of ADR-029 requires. Half-measures cost the same migration effort as the full move with less of the benefit.

    • Per-tier layouts (tier-0 → minimal under .ontoref/; tier-2 → richer hierarchy)

      Rejected: Multiplies the support matrix without qualitative gain. ADR-029 / `tier-coexistence-permanent-design` says tiers are about authoritative-state regimes, not about disk layout. Having one canonical layout that scales to all tiers (some directories empty at tier-0, populated at tier-2) is cleaner than three layouts.

    • Move incrementally — only `catalog/` to `.ontoref/catalog/` first (it is the newest, with no legacy users), defer `adrs/` / `reflection/` / `.ontology/` for later versions

      Rejected: Three partial migrations cost more than one comprehensive migration. Consumer maintainers would face N rounds of layout disruption instead of one. The dual-path resolver pattern works equally well for one or four moved directories. Doing it all at version 0.1.1 lets the protocol stabilise at the new layout sooner.

    +
    diff --git a/site/site/public/adr/adr-033/index.html b/site/site/public/adr/adr-033/index.html new file mode 100644 index 0000000..d1a6af2 --- /dev/null +++ b/site/site/public/adr/adr-033/index.html @@ -0,0 +1,206 @@ + + + +Tier Transition Mechanism — Dual-Surface `transition_tier` with Transactional Effects and Witnessed Substrate Dormancy · Ontoref ADR + + +
    +
    + Accepted + adr-033 · 2026-05-27 +

    Tier Transition Mechanism — Dual-Surface `transition_tier` with Transactional Effects and Witnessed Substrate Dormancy

    +
    + +

    Context

    ADR-029 (Tier Coexistence as Permanent Design) declared that the protocol +admits tier-0, tier-1, and tier-2 simultaneously and indefinitely, with +per-project tier choice recorded in `.ontoref/config.ncl::ops.tier`. +Five Hard constraints anchor the property:

    +

    - `no-forced-tier-migration` — silent/automatic tier change forbidden + - `tier-0-must-resolve-schemas` — lower-tier operability preserved + - `offline-first-default-stack` — no network requirement at any tier + - `additive-stack-no-breaking-tier-changes` — no API removal across tiers + - `tier-transition-requires-clean-migration-state` — guard against + transition with pending migrations

    +

    The fifth constraint's enforcement primitive — `enforce_tier_transition_guard(old, new, pending_migrations)` — is implemented in +`crates/ontoref-daemon/src/tier_guard.rs` (175 LOC, 8 passing unit tests). +It is a pure function that returns `Ok(())` or +`Err(TierGuardError::MigrationsPending)`. It is **not yet wired into any +caller** because no caller exists: ADR-029 committed the constraint at +the protocol-semantic level, deferring the operational materialisation +to a follow-up ADR.

    +

    This ADR is that follow-up. It defines the operational primitive that +moves a project between tiers — what it is called, where it lives, what +it does, and what guarantees it offers — without re-opening ADR-029's +already-settled questions (the three tiers are permanent; transition is +voluntary; migrations-clean is a precondition).

    +

    CURRENT STATE OF THE SYSTEM ENTERING THIS DECISION

    +

    - ontoref-el-proyecto (the piloto) runs at tier-2 / Phase Progressive + *empirically* — 10 catalogued ops, dispatch via inventory, witnesses + emitted, replay-deterministic — but does NOT declare + `ops.tier = 'Tier2` in its `.ontoref/config.ncl`. The tier is + inferred from artefact presence (`catalog/operations/*.ncl`, + `crates/ontoref-ops/`), not from a deliberate config statement. + - The 18 other onboarded projects (mirador, lian-build, vapora, …) + run at tier-0 by config-default (`#[serde(default)]` on `OpsConfig` + yields `Tier::Tier0`). + - No project today exercises the tier_guard's failure path because + no project has crossed a tier boundary since ADR-029 was accepted.

    +

    OPEN QUESTIONS ENTERING THIS SESSION

    +

    1. Op vs CLI vs both — where does `transition_tier` live? + 2. Direction asymmetry — what does downgrade actually do? + 3. TierGuard wiring point — at config-reload, at op pre-validation, + or at both? + 4. Pending-migrations definition — strict or scoped? + 5. Effect catalog — concrete list of substrate effects per direction. + 6. Atomicity — transactional or compensating?

    +

    The decisions below resolve each open question through the ondaod- +synthesis lens (formalization-vs-adoption, ontology-vs-reflection) and +preserve the voluntary-adoption axiom invariant=true.

    Decision

    ONE PRIMITIVE, TWO SURFACES, ONE LOGIC

    +

    The operational primitive is named `transition_tier`. It exists at two +operational surfaces backed by a single Rust implementation:

    +

    SURFACE A — CLI command (every tier) + `ontoref tier set <Tier0|Tier1|Tier2>` + Available at every tier including tier-0 (where the op dispatcher + is not present). Internally calls + `ontoref_daemon::tier_transition::execute(target_tier, ctx)`.

    +

    SURFACE B — tier-2 catalogued operation + `catalog/operations/transition_tier.ncl` + Rust handler annotated + `#[onto_operation(id = "transition_tier", validation_sla = "Synchronous")]`. + Materialises automatically once a project reaches tier-2. Emits a + signed witness (Ed25519) covering the tier change. Calls the same + `ontoref_daemon::tier_transition::execute` function.

    +

    The CLI is the universally-available path; the catalogued op is the +witnessable path. Below tier-2 the CLI is the only surface; at tier-2 +both surfaces are available and BOTH route through the same Rust +function. This preserves ondaod (formalization-vs-adoption Spiral stays +open) by NOT forcing tier-0/1 projects to bootstrap a dispatcher to +change tier, AND by NOT letting tier-2 projects bypass witness emission +when they have the substrate to produce one.

    +

    GUARD WIRING — single entry-point, two reachability paths

    +

    `enforce_tier_transition_guard` fires at exactly one logical entry-point +— inside `ontoref_daemon::tier_transition::execute` — regardless of +surface. Both CLI and op converge on this function as the first +validator. Additionally, the daemon's existing `ConfigWatcher` (which +observes `.ontoref/config.ncl` changes) consults the same guard before +swapping the config in its registry. The three reachability paths:

    +

    a. CLI invocation → execute() → guard + b. Op dispatch → execute() → guard + c. Manual NCL edit + daemon reload → ConfigWatcher → guard

    +

    If the guard returns `Err(MigrationsPending)`, the function returns +without applying any effect; the daemon emits a `Block`-severity +notification; the registry retains the old tier value; manual NCL +edits are NOT swapped in (the daemon does not unilaterally overwrite +the file — it logs the block and lets the developer revert their edit +or `ontoref migrate` first).

    +

    PENDING-MIGRATIONS DEFINITION — strict

    +

    The guard treats `ontoref migrate pending` as authoritative. Any pending +migration blocks any tier change. This matches the current +`tier_guard.rs` implementation exactly and avoids introducing a new +optional schema field (`relevant_to_tier_transition`) whose semantics +would themselves be a Spiral collapse: the protocol cannot reliably +classify which migrations affect tier mechanics without empirically +testing every migration against every tier transition, and the cost of +that classification exceeds the cost of running pending migrations +first.

    +

    DOWNGRADE — mark dormant, never delete

    +

    `transition_tier` lowering `ops.tier` (e.g. Tier2 → Tier0) does NOT +delete substrate artefacts. The behaviour matrix:

    +

    | direction | NCL files | oplog | commit_layer | state_root | signing keys | + | ---------------- | --------- | ----- | ------------ | ---------- | ------------ | + | upgrade N→N+1 | preserved | created if absent | created if absent | initialised | regenerated if absent | + | downgrade N→N-1 | preserved | preserved (read-only) | preserved (read-only) | frozen (last value retained) | preserved (read-only) | + | downgrade N→N-2 | preserved | preserved (read-only) | preserved (read-only) | frozen (last value retained) | preserved (read-only) |

    +

    This is consistent with `ontoref-tier-downgrade-asymmetry` QA: past +cryptographic guarantees survive immutable, future guarantees pause, and +re-upgrade introduces an externally-observable epistemic gap in the +witness chain (not a protocol bug — an inherent property of cryptographic +chains).

    +

    EFFECT CATALOG

    +

    The function applies effects per direction in a fixed order, with each +effect carrying a compensating rollback action (see ATOMICITY below):

    +

    Tier0 → Tier1 + e1. Create `.ontoref/store/oplog/` if absent + e2. Bootstrap commit_layer (write empty root commit if oplog empty) + e3. Compute initial state_root from NCL ingestion + e4. Register the project's substrate state in daemon registry + e5. Mutate `.ontoref/config.ncl::ops.tier = 'Tier1` (atomic + file replace via tempfile + rename)

    +

    Tier1 → Tier2 + e1. Verify or create per-actor Ed25519 keypair + (`~/.config/ontoref/keys/<actor>.key`, 0600) + e2. Register the project's catalog by reading + `.ontoref/catalog/operations/*.ncl` (declarative side) + e3. Verify each declared op has a corresponding registered + OperationEntry (via `inventory::iter`) OR a declared non-Rust + kind per ADR-034 + e4. Mutate `.ontoref/config.ncl::ops.tier = 'Tier2`, + `ops.phase = 'Progressive` (atomic)

    +

    Tier2 → Tier1 + e1. Emit a final tier-descent witness (Ed25519-signed envelope + describing the descent, written to oplog as the last + signed entry — the oplog remains valid for verifiers) + e2. Mutate `.ontoref/config.ncl::ops.tier = 'Tier1` + e3. Update daemon registry to stop dispatching ops for this project + (return 404 from `/ops/{id}` while preserving witness verifiability)

    +

    Tier1 → Tier0 + e1. Update daemon registry to stop maintaining the substrate + (commit_layer becomes read-only archive; new NCL edits are NOT + ingested into the commit_layer) + e2. Mutate `.ontoref/config.ncl::ops.tier = 'Tier0`

    +

    Tier2 → Tier0 (compound descent) + Decomposed into (Tier2 → Tier1) followed by (Tier1 → Tier0), + applied atomically as one transaction. If e1 of the inner descent + succeeds but e2 of the outer descent fails, the WHOLE compound + rolls back. The function never leaves the project at an + intermediate tier value than the one declared.

    +

    ATOMICITY — transactional, all-or-nothing

    +

    The function applies all effects within a single transactional scope. +Each effect has a recorded compensating action. On any failure:

    +

    - The compensations execute in reverse order + - `.ontoref/config.ncl::ops.tier` is restored to its pre-transition + value (the file rename is the last effect; if it succeeds, the + transition committed) + - The daemon's in-memory registry is restored to its pre-transition + snapshot + - The failed transition is recorded in + `.ontoref/artifacts/transition-log/<timestamp>-failed.ncl` for + operator review (logged, never silently discarded)

    +

    The transactional model means the user sees one of exactly two outcomes: +the project is at the requested tier OR the project is at its original +tier; never an intermediate state. Synchronous SLA on the catalogued +op enforces this externally; the CLI surface has the same guarantee +because it calls the same Rust function.

    +

    ONTOREF-EL-PROYECTO DECLARES `ops.tier = 'Tier2`

    +

    As part of this ADR — not a separate housekeeping step — ontoref's own +`.ontoref/config.ncl` gains an explicit `ops = { tier = 'Tier2, phase += 'Progressive }` block. This makes the piloto self-host the FIRST +project to formally declare its tier and the canonical example for +every future declaration. The Hard constraint `piloto-declares-its-tier` +(below) pins the declaration so future sessions cannot silently remove +it.

    +

    NEW SCHEMA FIELDS — none in this ADR

    +

    `transition_tier` operates on the existing `OpsConfig` struct (tier + +phase). It does not require new schema fields. Migration 0024 +introduces no breaking change — it (a) documents the new CLI command +and op, (b) instructs ontoref-el-proyecto to add the explicit tier +declaration, and (c) recommends consumer projects add their own +declaration when they choose a tier.

    Constraints

    • Hard

      The `transition_tier` operational primitive (CLI surface, catalogued op, and ConfigWatcher hook) MUST invoke `enforce_tier_transition_guard` from `crates/ontoref-daemon/src/tier_guard.rs` as its first validator. No alternative implementation of the precondition check is permitted; the guard is single-source.

    • Hard

      The CLI surface (`ontoref tier set <T>`) and the tier-2 catalogued op (`transition_tier`) MUST both route through a single Rust function `ontoref_daemon::tier_transition::execute` (or its successor module). Neither surface may implement the transition logic independently.

    • Hard

      Every invocation of `transition_tier` MUST produce exactly one of two observable outcomes: (a) the project is at the requested tier with all effects applied OR (b) the project is at the original tier with no effect persisted. The function MUST NOT leave the project at any intermediate tier value, and MUST NOT leave any partially-applied substrate state visible to subsequent reads.

    • Hard

      Downgrade transitions (any direction where `new_tier < old_tier`) MUST NOT delete, truncate, or otherwise mutate the existing oplog, commit_layer, state_root snapshot, or actor signing key files. The substrate becomes read-only archive; new mutations follow the new tier's rules.

    • Hard

      When `transition_tier` is invoked with `old_tier = 'Tier2` AND `new_tier != 'Tier2`, the function MUST emit a final Ed25519-signed witness covering the descent before mutating `.ontoref/config.ncl::ops.tier`. The witness payload kind is `tier_descent`; the proof path references the pre-descent state_root.

    • Hard

      The ontoref repository's own `.ontoref/config.ncl` MUST contain an explicit `ops` section declaring `tier = 'Tier2`. The declaration MUST be a deliberate config statement, not inferred from artefact presence.

    • Hard

      The daemon's ConfigWatcher MUST consult `enforce_tier_transition_guard` before swapping a project's `OpsConfig` in the registry when the watcher detects a change to `.ontoref/config.ncl::ops.tier`. When the guard returns `Err`, the watcher MUST log a `Block`-severity notification and retain the previous config; it MUST NOT mutate the user's NCL file to revert the change (only refuses to honour it).

    • Hard

      The guard MUST treat any non-empty result from `ontoref migrate pending` as blocking. The function MUST NOT introduce per-migration opt-out fields (e.g. `relevant_to_tier_transition`) that would relax the constraint.

    • Soft

      Every failed transition (whether blocked by the guard or by a downstream effect failure) MUST produce a record in `.ontoref/artifacts/transition-log/<timestamp>-failed.ncl` containing: the requested target tier, the original tier, the failure source, and the timestamp. The record MUST NOT contain sensitive substrate state (no private key material, no secret values).

    Alternatives considered

    • CLI-only — `transition_tier` lives in `domains/framework/commands.nu` as a Nushell command calling daemon HTTP routes

      Rejected: Tier-2 projects need the act to be witnessable per ADR-024 ('operations are the agent's only path to mutate state, with declared preconditions, validators, and witness emission'). A CLI-only implementation either bypasses the catalog (anti-ADR-024) or duplicates witness emission logic outside the catalog (state inconsistency risk). Dual-surface with one-logic is the synthesis that preserves both surfaces' guarantees.

    • Tier-2-op-only — only a catalogued op, no CLI surface

      Rejected: Tier-0/1 projects don't have the op dispatcher (the op runtime is part of the tier-2 substrate). They cannot climb without first bootstrapping the dispatcher — chicken-and-egg. CLI is the bootstrap surface that breaks this circularity. Removing it would make tier transitions impossible for the lowest tier, violating voluntary-adoption (a tier-0 project must be able to climb when it chooses to).

    • Separate per-direction primitives — `upgrade_tier`, `downgrade_tier`, `set_phase`

      Rejected: Three primitives where one suffices. The function signature `execute(target_tier, ctx)` covers all directions; the effect catalog branches per direction internally. Splitting introduces three places to apply the guard, three CLI commands, three op declarations — without adding semantic clarity. The brief's `ontoref-tier-downgrade-asymmetry` QA already explained that downgrade has different effects than upgrade; that asymmetry lives inside the effect catalog, not in three separate primitives.

    • Pending-migrations scoped by `relevant_to_tier_transition: true` field

      Rejected: Adding a per-migration boolean would require the protocol to pre-classify every future migration. The classification is itself a Spiral-collapse decision (which migrations 'matter' for tier mechanics is precisely what ADR-029's constraint refuses to pre-judge). Strict-any matches the current `tier_guard.rs` exactly, requires no schema change, and the operational cost (run migrations before transitioning) is the obvious workaround.

    • Best-effort with rollback log — apply effects, log completions, manual cleanup on failure

      Rejected: ADR-024 makes operations the agent's only action path with witness emission per op. An op that partially applies state breaks the witness/state correspondence — the Ed25519 signature covers a state the verifier cannot reconstruct from the oplog. Transactional all-or-nothing is the only model compatible with witness-honesty. The cost (compensations per effect) is bounded and one-time.

    • Delete substrate on downgrade — clean slate

      Rejected: Anti-ADR-029. Past witnesses are immutable per voluntary-adoption; deleting the oplog / signing keys would invalidate them from external verifiers' perspective. The `ontoref-tier-downgrade-asymmetry` QA explicitly states 'past witnesses verify forever against actors.ncl public keys' — that property requires keeping the keys and the oplog. Dormancy preserves the property at the cost of disk space; deletion breaks it.

    +
    diff --git a/site/site/public/adr/adr-034/index.html b/site/site/public/adr/adr-034/index.html new file mode 100644 index 0000000..213872f --- /dev/null +++ b/site/site/public/adr/adr-034/index.html @@ -0,0 +1,221 @@ + + + +Catalog Extensibility Beyond Rust — `kind` Discriminator on `OperationDecl` with ondaod-Pre Required for Non-Rust Kinds · Ontoref ADR + + +
    +
    + Proposed + adr-034 · 2026-05-26 +

    Catalog Extensibility Beyond Rust — `kind` Discriminator on `OperationDecl` with ondaod-Pre Required for Non-Rust Kinds

    +
    + +

    Context

    ADR-024 (operations as the agent's only project-touching path) and +ADR-026 (three-planes validation + SLA) commit the protocol to a typed +catalog of domain operations, each declared in +`.ontoref/catalog/operations/<id>.ncl` and paired with a Rust handler +annotated `#[onto_operation]`. The Rust pairing is enforced by +`inventory::collect!(OperationEntry)` at link time — there is no +protocol surface today for a catalog declaration without a matching +Rust function.

    +

    ADR-030 (catalog discovery cross-project) named the broader problem of +how non-piloto projects expose tier-2 ops and enumerated seven candidate +mechanisms:

    +

    1. CompiledIntoDaemon (current — Rust + inventory at link time) + 2. DynamicLibraryLoading (.so/.dylib via libloading) + 3. WasmComponents (WIT-typed WASM modules via wasmtime) + 4. SidecarProcess (HTTP/IPC to per-project process) + 5. InterpretedNclOps (declarative NCL data-transform) + 6. SidecarProcessWithSharedDomainLibrary (refinement of #4) + 7. CompositeBackend (mix of the above)

    +

    ADR-030 split these into Phase A (implementable today: #1, #6, #7) and +Phase B (spike-required behind explicit triggers: #2, #3, #4-raw, #5) +and committed the `CatalogBackend` trait surface without committing +any non-default impl.

    +

    WHAT THIS ADR DOES vs DOES NOT DO

    +

    ADR-034 does NOT re-open ADR-030's deferral. The trigger-based discipline +for choosing WHICH executive backend lands first remains intact: WASM is +deferred behind T3/T4/T5, dynlib behind toolchain-coupling pressure, +NCL-interpreted behind expressive-power proof.

    +

    ADR-034 commits a different layer: the catalog's DECLARATIVE schema. +Today an `OperationDecl` in `catalog/schema.ncl` implicitly assumes the +op is implemented in Rust (no `kind` field, the runtime looks up the +inventory entry by id). For tier-2 to scale across the ecosystem — even +under Phase A mechanisms only — the catalog declaration must be honest +about what mechanism a particular op uses. The piloto's `move_fsm_state` +is Rust; a future provisioning op in libre-daoshi's sidecar is +Sidecar-mechanism; a future trivial CRUD op in a tier-2 mirador clone +might be NclTransform. Each kind has different witness emission rules, +different ondaod-discipline requirements, and different cross-project +composability properties.

    +

    ADR-034 adds a `kind` field to `OperationDecl` with the canonical +enumeration: `'Rust` (default), `'NclTransform`, `'Wasm`, `'Sidecar`. +The schema admits all four kinds. The Rust runtime implements `'Rust` +today (inventory dispatch); the other three kinds are declarable but +unexecutable until an executive backend lands per ADR-030's trigger +discipline. The schema decoupling is the deliverable; the executive +landings remain trigger-deferred.

    +

    WHY DECLARE BEFORE EXECUTE

    +

    Declaring the four kinds in the schema does three things that +executive-only would not:

    +

    1. Consumer projects can author non-Rust ops as documentation today + (declarative side of catalog composes cross-project via ADR-028 + content-addressing per ADR-030's confirmation). A tier-0 project + can declare its future tier-2 catalog including non-Rust kinds + without paying the executive cost. + 2. Cross-project ontology consumers can read the declared `kind` and + understand what dispatch path the foreign project's op takes — + useful for trust analysis (a Sidecar op carries a process boundary; + a Wasm op carries sandbox guarantees). + 3. ondaod-discipline can be enforced structurally at the catalog + level: non-Rust kinds cannot inline-execute the ondaod-evaluation + op (NCL is declarative, WASM is sandboxed from the host runtime, + Sidecar crosses a process boundary). The schema requires + `evaluate_ondaod` in `constraints.pre` for non-Rust kinds, making + the host runtime perform the discipline check before dispatch.

    +

    WHAT VALIDATORS DO

    +

    Validators are pure (no state mutation). The kind field applies +symmetrically to `ValidatorDecl` — `predicate_ref` becomes +mechanism-dependent. Because validators don't mutate state, the +ondaod-pre requirement does not apply to them; only operations are +gated. The schema admits the same four kinds for validators with the +same trigger-deferred executive impls.

    Decision

    SCHEMA FIELD — `kind` discriminator on `OperationDecl` and `ValidatorDecl`

    +

    The `OperationDecl` contract in `.ontoref/catalog/schema.ncl` gains a +new field `kind` typed as:

    +

    let t_OpKind = [| + 'Rust, # #[onto_operation] + inventory (DEFAULT) + 'NclTransform, # declarative NCL data-transform spec + 'Wasm, # WIT-typed WASM module path + 'Sidecar, # HTTP endpoint on a per-project process + |] in

    +

    The field is optional with `default = 'Rust`, so every existing +operation declaration continues to typecheck unchanged. Adding a new +op requires no `kind` declaration unless the author chooses a non-Rust +mechanism.

    +

    PER-KIND BODY FIELD — `body | optional` (open record)

    +

    Each non-Rust kind requires an additional declaration of WHERE the +mechanism's runtime locates the implementation. The `body` field is an +optional open record whose shape is per-kind:

    +

    'Rust → body absent (impl resolved by id via inventory)

    +

    'NclTransform → body = { + target | String, # NCL file path relative to project root + selector | String, # JSONPath into the exported value + operation | [| 'Set, 'Append, 'Remove, 'Merge |], + value_expr | String, # NCL expression evaluated in op context + }

    +

    'Wasm → body = { + module_path | String, # path under .ontoref/catalog/wasm/<id>.wasm + export_name | String, # name of the WIT-exported function + capabilities | Array String, # explicit capability grants (file:read, net:none, ...) + }

    +

    'Sidecar → body = { + endpoint | String, # HTTP URL (host-local default) + auth_token | String | default = "<from .ontoref/config.ncl::ops.sidecar_token>", + timeout_ms | Number | default = 5000, + }

    +

    The `body` field is validated structurally by the schema (Nickel +contracts enforce per-kind shape); executive backends interpret it at +dispatch time. The protocol does NOT validate `body` semantically until +an executive backend exists — declaration without execution is honest +documentation per ADR-030.

    +

    EXECUTIVE IMPL DEFERRAL — unchanged from ADR-030

    +

    The `'Rust` kind is the only kind the daemon dispatches today. The +other three kinds remain trigger-deferred per ADR-030:

    +

    'NclTransform → trigger: most tier-2 ops in the ecosystem turn out + to be small state-mutation verbs that need no native + code, AND the expressive power of the declarative + spec is proven sufficient for ≥3 real ops. + 'Wasm → trigger: ADR-030 T3 / T4 / T5 — cross-instance + dispatch becomes the bottleneck, toolchain divergence, + or untrusted ops authors require sandboxing. + 'Sidecar → already Phase A per ADR-030 mechanism #6 + (SidecarProcessWithSharedDomainLibrary). The ~80-LOC + proxy handler is implementable today; provisioning + + Level-3 instances is the canonical first consumer.

    +

    This ADR commits the schema discriminator. It does NOT commit the +non-`'Rust` executive impls beyond what ADR-030 already commits. +Sidecar may land near-term (Phase A); Wasm and NclTransform stay +deferred.

    +

    ONDAOD-PRE REQUIREMENT FOR NON-Rust KINDS

    +

    ADR-024 places `evaluate_ondaod` as a structural ondaod-discipline +check that runs before architectural-mutation operations. The Rust +runtime can invoke this directly from inside an op body (the op has +access to the dispatch context). Non-Rust kinds cannot:

    +

    - `'NclTransform` ops are declarative — no body to run ondaod inside + - `'Wasm` ops are sandboxed — the host's ondaod context is not + automatically reachable; passing it across the boundary requires + interface design + - `'Sidecar` ops cross a process boundary — invoking ondaod from + the sidecar would require the sidecar to call back into the daemon

    +

    The protocol resolves this by requiring `evaluate_ondaod` (or a +declared specialised ondaod validator) to appear in the +`constraints.pre` array of every non-`'Rust` `OperationDecl`. The +host runtime invokes the pre-validator before dispatching to the +non-Rust kind. This makes ondaod-discipline a structural property +of the catalog declaration, not a behavioural property of the op body — +which is exactly what the declarative side of the catalog is for.

    +

    The constraint is enforced as a Hard schema-level check at typecheck +time:

    +

    let t_OperationDecl = ... in + let t_OndaodPreRequired = std.contract.from_predicate + (fun decl => + decl.kind == 'Rust + || std.array.any (fun v => v == "evaluate_ondaod") decl.constraints.pre) + in

    +

    Rust-kind ops MAY still declare `evaluate_ondaod` in their +`constraints.pre` (and ondaod-touching ops SHOULD); the constraint only +*requires* it for non-Rust kinds. The asymmetry matches the asymmetry +of executive access: Rust ops can synthesise ondaod context internally, +non-Rust ops cannot.

    +

    VALIDATOR EXTENSIBILITY — same field, no ondaod-pre requirement

    +

    `ValidatorDecl` in `.ontoref/catalog/schema.ncl` also gains the `kind` +field with the same enumeration. The ondaod-pre requirement does NOT +apply because validators are pure (no state mutation). Otherwise the +treatment is symmetric: `'Rust` is default and dispatches today; +`'NclTransform` / `'Wasm` / `'Sidecar` are declarable but unexecutable +until the corresponding executive backend lands.

    +

    WITNESS SHAPE HONESTY

    +

    ADR-024 requires every operation to emit a witness covering the act. +The witness shape declared in `OperationDecl.witness_shape` must reflect +what the executive backend can attest:

    +

    'Rust → witness covers the post-state of the host's in-process + application; the signature is over the canonical + serialisation of the state delta (current behaviour). + 'NclTransform → witness covers the same shape — the host runtime + applies the transform in-process and signs the + delta. No additional attestation needed. + 'Wasm → witness covers the host's view of inputs + outputs of + the WASM call; the signature does NOT attest WASM- + internal computation (the sandbox is honest about being + a black box). + 'Sidecar → witness covers the sidecar's response payload as + received by the daemon; the signature is the daemon's + signature, not the sidecar's. The protocol does NOT + claim the sidecar's computation is honest — only that + the daemon faithfully forwarded the sidecar's reported + effect.

    +

    The witness-shape distinction is documented in the schema's comments; +it is NOT enforced as a contract because the protocol cannot statically +verify which witness mode the executive backend will use. It IS +documented in the constraint rationale below so that future executive +backend implementations honour the declared boundaries.

    Constraints

    • Hard

      The `OperationDecl` contract in `.ontoref/catalog/schema.ncl` MUST declare a `kind` field typed as an enum admitting at minimum `'Rust`, `'NclTransform`, `'Wasm`, `'Sidecar`. The field MUST default to `'Rust` so existing declarations remain valid without modification. The `ValidatorDecl` contract MUST declare the same field with the same default.

    • Hard

      Every `OperationDecl` with `kind != 'Rust` MUST include `"evaluate_ondaod"` (or a documented specialised ondaod validator id) in its `constraints.pre` array. The schema MUST enforce this with a contract that fails typecheck when the requirement is unmet.

    • Hard

      The `'Rust` kind MUST remain the default and MUST remain executable by the daemon under every set of feature flags including `--no-default-features`. Adding new kinds MUST NOT regress the Rust-kind dispatch path.

    • Hard

      When a project declares an `OperationDecl` with `kind = 'Wasm`, `'NclTransform`, or `'Sidecar` and the daemon was built without the corresponding executive feature flag (`wasm`, `ncl-transform`, `sidecar` respectively when those features exist), the daemon's `/ops/{id}` endpoint MUST return a 501 Not Implemented response with a structured error explaining the missing feature. Silent fall-through to 404 is forbidden.

    • Hard

      For each non-Rust `kind`, the schema MUST validate the `body` field's required keys at typecheck time. `kind = 'NclTransform` requires `body.target`, `body.selector`, `body.operation`, `body.value_expr`. `kind = 'Wasm` requires `body.module_path`, `body.export_name`, `body.capabilities`. `kind = 'Sidecar` requires `body.endpoint` (other fields default).

    • Hard

      Adding the `'Wasm` kind to the schema MUST NOT introduce a `wasmtime` (or `wasmer`) dependency in the default `ontoref-daemon` build. Any WASM runtime dependency MUST be feature-gated behind an opt-in flag (e.g. `--features wasm`).

    • Hard

      The daemon MUST NOT infer an op's `kind` from artefact presence (e.g. existence of a `.wasm` file, a sidecar URL in config, an NCL body in the declaration). The `kind` field is the authoritative source. When the field is absent, the kind is `'Rust` by default — never any other value.

    • Soft

      For non-Rust kinds (`'Wasm`, `'Sidecar`), the witness emitted on dispatch MUST attest only the host-observable inputs and outputs of the foreign execution. The witness MUST NOT claim to attest the foreign computation's internal correctness, and the `payload_kind` MUST encode this honestly (e.g. `wasm_call_envelope`, `sidecar_response_envelope`).

    Alternatives considered

    • Add only `'Rust` and `'Sidecar` to the kind enum; defer `'Wasm` and `'NclTransform` declarations entirely

      Rejected: Forces a schema migration when `'Wasm` or `'NclTransform` later become declarable. The cost of declaring all four kinds today is one Nickel enum line per kind; the cost of migrating the schema later (with all consumer projects' content needing re-checking) is higher. ADR-030 already enumerated all the mechanisms; adding them to the schema follows the same enumeration.

    • Declare `kind` but make `body` field free-form `Dyn` for all non-Rust kinds

      Rejected: Loses the typecheck protection that the per-kind shape gives. Authors of `kind = 'Sidecar` ops would get no help from the schema confirming they declared `endpoint`, `auth_token`, `timeout_ms`. The open-record-per-kind approach offers minimal typecheck for the canonical fields while leaving room for executive-backend-specific extensions.

    • Require ondaod-pre for ALL ops (Rust and non-Rust)

      Rejected: Over-constrains Rust ops. Rust-kind ops have inline access to dispatch::call("evaluate_ondaod", …) and currently invoke it from inside the body when needed (operationally — see crates/ontoref-ops/src/ops/transition_adr.rs). Forcing every Rust op to also declare it in constraints.pre would duplicate enforcement and create false-positive failures for ops that don't touch architectural state. The asymmetry matches the executive-access asymmetry: structural where structural is needed.

    • Add Nushell as a fifth kind (`'NuScript`)

      Rejected: Nushell is the existing automation surface (reflection/modules/) and is already declarable via `'Sidecar` with the sidecar being a Nushell-based HTTP server. Adding `'NuScript` as a separate kind would create overlap with `'Sidecar` and require duplicate documentation. If empirical pressure later shows that Nushell ops are common enough to justify a dedicated mechanism, a future ADR can add the kind — the four-kind enum is the minimum coherent set today.

    • Defer the schema discriminator entirely — wait until an executive backend lands and add the field then

      Rejected: Anti-pattern from ADR-030's perspective: schema decoupling is cheap; executive backend landings are expensive and require triggers. Adding the schema field now means the FIRST executive backend doesn't have to bump the schema — it implements its kind, and consumer projects that declared it speculatively start working without re-publication. The schema field also unblocks ADR-030 #6 (Sidecar Phase A) by giving it a place to declare without retro-fitting later.

    • Use a tag union without per-kind body field — embed mechanism-specific fields directly in OperationDecl

      Rejected: Pollutes OperationDecl with N×M fields (each kind's fields × every declaration). The per-kind body is the canonical encoding pattern (matches `t_ConstraintCheck` in adr-schema.ncl — tag + per-tag required fields). Authors only see fields relevant to their chosen kind.

    +
    diff --git a/site/site/public/adr/adr-035/index.html b/site/site/public/adr/adr-035/index.html new file mode 100644 index 0000000..6eaf602 --- /dev/null +++ b/site/site/public/adr/adr-035/index.html @@ -0,0 +1,173 @@ + + + +Positioning Layer — Marketing as Queryable Protocol Surface · Ontoref ADR + + +
    +
    + Accepted + adr-035 · 2026-05-26 +

    Positioning Layer — Marketing as Queryable Protocol Surface

    +
    + +

    Context

    Ontoref's thesis is that everything project-defining is queryable NCL: +axioms, tensions, practices, ADRs, reflection modes, manifest capabilities, +config surface, backlog, Q&A, search bookmarks, even the API catalogue +(via the #[onto_api] proc-macro + inventory). The protocol pushes this +discipline aggressively into every layer it touches.

    +

    One surface escapes the discipline: the *outward-facing* artefacts of +the project — the things that explain to a potential adopter, sponsor, +or contributor *why they should care*. Today these live as:

    +

    - card.ncl — a tagline + features array attached to ProjectCard. + Identity-shaped, audience-flat. There is no model of *whom* the + project speaks to, *what claim* is being made to each audience, + or *which architectural decisions back the claim*.

    +

    - assets/web/ — index.html, personal.html, provisioning.html, + architecture-diagram.html. These are the public face of ontoref. + They are **hand-edited markdown-and-HTML**, disconnected from + the NCL substrate. Every other artefact in ontoref derives from + a typed source (ADRs from forms via Jinja, API pages from inventory + catalogue, describe outputs from ontology/manifest/qa). Public + messaging does not.

    +

    - domains/{personal, framework, provisioning}/ — these are *implicit* + audience segments, modelled as technical extension verticals (per + ADR-012) rather than as go-to-market audiences. The mapping + "platform engineer ↔ provisioning domain" exists in the maintainer's + head, nowhere in NCL.

    +

    The gap is real and visible. The user observation that opened this ADR: +when ontoref reasons about *what to build next* (Wish/Idea backlog, +ADR drafting, tension synthesis) it has rich machinery; when ontoref +reasons about *who cares and why* it has card.ncl's tagline string. +Marketing claims drift from architectural decisions; messaging copy +ages independently of the substrate that justifies it; the protocol's +own "everything is NCL" axiom is silently violated at exactly the +surface where adopters meet the project.

    +

    Two prior sessions have orbited this gap without closing it:

    +

    - The PRD-vs-ADR question (this session, prior turn) — initially + framed as "should backlog Wish items carry a PRD block?". That + sketch (extend Item with prd field) addresses *internal* product + intent. It does not address the outward-facing positioning surface.

    +

    - ADR-031's on+re duality — established that ontology (substance, + "what IS") and reflection (act, "what DOES") are constitutive, + not optional. Positioning artefacts live primarily on the reflection + axis (claims are acts of communication toward an audience) but are + *anchored* to substance via evidence_adrs (each claim cites the + ADRs that materially back it). Without this anchoring, claims drift + into pure narrative and become un-auditable.

    +

    The positioning layer formalises the outward-facing surface as queryable +NCL — completing the protocol's coverage of the project's externalised +self — while keeping prose-as-prose via a narrative_path that points to +markdown bodies. The skeleton is verifiable (audience present, evidence +ADRs cited when Live, launch criteria executable); the flesh stays human.

    Decision

    A new layer `positioning` is added to the protocol surface, with three +artefact kinds — **Audience**, **ValueProp**, **Campaign** — typed by +schemas installed to the user data dir, and instantiated per-project +under `.ontoref/positioning/`.

    +

    SCHEMAS (installed to data dir, same install path as project-card.ncl)

    +

    ontology/schemas/positioning.ncl + PositioningStatus | [| 'Draft, 'Validated, 'Live, 'Retired |] + LaunchCheck | tagged record (Grep | NuCmd | ApiCall | FileExists) + Audience | { id, name, description, primary_pain, context, + channels[], evidence[], linked_nodes[] } + ValueProp | { id, claim, audience_ids[], problem_solved, + alternatives[], unique_angle, evidence_adrs[], + proof_points[], narrative_path?, launch_criteria[], + status, retired_at?, superseded_by? } + Campaign | { id, title, audience_ids[], value_prop_ids[], goal, + channels[], launch_criteria[], narrative_path?, + status, starts_at?, ends_at? }

    +

    ontology/defaults/positioning.ncl + make_audience, make_value_prop, make_campaign — apply cross-field + contracts: + - non_empty_audience_ids (value-prop must target someone) + - live_requires_evidence_adrs (Live claims must cite ≥1 ADR) + - retired_has_date (Retired requires retired_at) + - superseded_implies_retired (cannot supersede while Live) + - each_launch_check_well_formed (per-tag field requirements)

    +

    CONSUMER LAYOUT (per-project, opt-in)

    +

    my-project/ + └── .ontoref/ + └── positioning/ ← new layer root + ├── audiences/ + │ ├── audience-platform-engineer.ncl + │ ├── audience-ai-agent-runtime.ncl + │ └── audience-architecture-team.ncl + ├── value-props/ + │ ├── vp-001-structure-that-remembers.ncl + │ ├── vp-002-mcp-agent-discoverability.ncl + │ └── vp-003-decision-replay.ncl + ├── campaigns/ + │ └── campaign-NNNN-slug.ncl + └── narratives/ + ├── vp-001-self-knowledge.md ← prose for the claim + └── audience-platform-engineer.md

    +

    NCL holds the verifiable skeleton (audience, evidence_adrs, launch_criteria, +status). Markdown narratives hold the messaging body. Generated HTML in +`assets/web/` derives from the pair via the same Jinja pattern used for +ADRs.

    +

    CARD.NCL INTEGRATION

    +

    ProjectCard schema gains one optional field: + primary_value_prop_id | String | optional

    +

    When present, it MUST resolve to a file at + .ontoref/positioning/value-props/<id>.ncl

    +

    card.tagline is preserved (zero migration cost). When primary_value_prop_id + is set, downstream renderers (portfolio publication, web assets) MAY + prefer the canonical value-prop's claim over the static tagline.

    +

    CHECK TYPE REUSE — ACKNOWLEDGED DUPLICATION

    +

    positioning.LaunchCheck is structurally identical to the ConstraintCheck + type in `.ontoref/adrs/adr-schema.ncl` (tags: 'Grep, 'NuCmd, 'ApiCall, + 'FileExists; per-tag well-formedness). The duplication is intentional + in this ADR: lifting ConstraintCheck into a shared + `ontology/schemas/check.ncl` requires editing the ADR schema's import + graph (changing what every existing ADR resolves against), which is + out of scope for introducing a new optional layer.

    +

    Lift-out is recorded as a soft constraint in this ADR + (shared-check-lift-out-pending) and deferred to a follow-up migration. + Until then, validators in both layers reference the same tags by name + and the test suite checks shape-equivalence on every CI run.

    +

    ASSETS/WEB/ DERIVATION (out of scope but enabled)

    +

    This ADR does not specify the asset generator. It establishes the + queryable source from which assets/web/index.html, personal.html, etc. + CAN be generated. A follow-up ADR (or migration) replaces hand-edited + HTML with Jinja-templated output reading the positioning tree + linked + narratives, the same way ADRs are generated from forms today. Until + that follow-up lands, assets/web/ remains hand-edited and the + positioning layer informs it via reference, not derivation.

    +

    OPT-IN PER PROJECT

    +

    positioning is a tier-orthogonal optional layer (consistent with + ADR-029's tier-coexistence design). A project at tier-0 with no + positioning/ directory operates exactly as before. Adoption is + declared by creating `.ontoref/positioning/` and ≥1 audience + ≥1 + value-prop; the daemon's describe surface exposes positioning as a + category iff the directory is non-empty. No migration is required + for existing consumer projects; the layer is purely additive.

    +

    CLI / DESCRIBE SURFACE

    +

    ontoref describe positioning [--audience <id>] [--status Live] + → lists value-props (filtered) with their audience, evidence_adrs, + and narrative_path

    +

    ontoref positioning validate + → runs launch_criteria checks across all value-props and campaigns; + reports pass/fail per artefact

    +

    ontoref positioning audit + → cross-references evidence_adrs against the ADRs directory; + flags claims whose cited ADRs are 'Deprecated, 'Superseded, or + missing entirely (drift detection between marketing and architecture)

    Constraints

    • Hard

      The positioning schema (ontology/schemas/positioning.ncl) and its defaults (ontology/defaults/positioning.ncl) MUST be installed to the user data dir as part of every ontoref install, alongside project-card.ncl and the other layer schemas. Without these files, consumer projects cannot resolve `import "positioning"` and the layer is structurally unavailable.

    • Hard

      The positioning defaults file (ontology/defaults/positioning.ncl) MUST be installed alongside the schema. Cross-field contracts (live_requires_evidence_adrs, retired_has_date, etc.) are applied via the make_* helpers in this file — without it, value-prop files importing `defaults/positioning.ncl` fail to parse and the structural invariants the layer promises are unenforceable.

    • Hard

      Audience records MUST live in their own files under `.ontoref/positioning/audiences/<id>.ncl`. Value-prop files MUST reference audiences by id (audience_ids: Array String), NOT inline the audience record. The `primary_pain` field is the canonical signature of an audience record — its presence inside a value-prop file is the violation.

    • Hard

      When `card.ncl` declares `primary_value_prop_id = "vp-XXX-..."`, the referenced value-prop file MUST exist at `.ontoref/positioning/value-props/vp-XXX-....ncl`. Dangling references between identity (card) and positioning (value-props) silently break downstream renderers (portfolio, web pages, MCP surfaces).

    • Hard

      Every value-prop with status='Live MUST declare a non-empty evidence_adrs array. This is enforced at nickel-export time by the live_requires_evidence_adrs contract in `ontology/defaults/positioning.ncl`. The Hard runtime check confirms the contract has not been bypassed (e.g. by editing exported JSON directly).

    • Hard

      When a value-prop or campaign declares `narrative_path = "..."`, the path MUST resolve to an existing file. Markdown narratives outside the NCL ecosystem are referenced, not validated for content, but their existence is the minimum enforceable invariant.

    • Soft

      The LaunchCheck type in `ontology/schemas/positioning.ncl` is structurally identical to ConstraintCheck in `.ontoref/adrs/adr-schema.ncl`. Both MUST be lifted into a shared `ontology/schemas/check.ncl` in a follow-up migration. Until that migration lands, the duplicated definitions MUST stay synchronised: any new tag added to one MUST be added to the other in the same commit.

    Alternatives considered

    • Extend card.ncl monolithically with positioning fields (audiences, value_props, campaigns inline)

      Rejected: Mixes identity (what the project is — stable across years) with positioning (claims aimed at specific audiences — evolves per campaign). The schema becomes ambiguous: is ProjectCard.tagline the project's identity statement or its current value-prop claim? Inline arrays also lose the per-artefact reviewability that separate files give (each value-prop change is its own git diff, its own narrative file, its own launch_criteria). Card-as-monolith fails as soon as a project has more than one audience to address.

    • Address only the internal PRD gap — extend backlog Item with a prd block (route A from the prior session turn)

      Rejected: Solves a different problem (internal product intent for the engineering team) and leaves the outward-facing surface — the one that adopters actually see — untouched. The prior session sketch is complementary, not substitutive: an engineering PRD captured in backlog and a marketing value-prop captured in positioning serve disjoint audiences (team vs. world). Choosing only the PRD route leaves assets/web/ disconnected from the protocol forever.

    • Keep positioning as pure markdown under .ontoref/positioning/*.md — no NCL skeleton

      Rejected: Loses every property that justifies adding the layer: no evidence_adrs anchor (claims drift from architecture invisibly), no launch_criteria (no executable readiness check), no audience targeting (claims float free of who they address), no MCP queryability (markdown is opaque to structured tooling), no audit surface (drift between marketing and architecture is undetectable). Markdown-only positioning is what every project already has via README and blog posts — adding it to the protocol contributes nothing structural.

    • Per-domain positioning — let each domains/{personal,framework,provisioning}/ carry its own positioning files

      Rejected: Couples positioning to the technical extension mechanism (domains) when in fact value-props frequently cross domains (a single claim like 'structure that remembers' targets multiple audiences across multiple domains). Per-domain positioning would either duplicate value-props across domain directories (drift risk) or require a cross-domain index that re-introduces the centralised positioning/ directory by another name. The centralised layer with audience_ids as the cross-cutting key is simpler and supports the cross-domain claim case natively.

    • Defer the entire question — write hand-edited HTML now and revisit positioning later when adoption volume justifies it

      Rejected: The protocol's coherence with its own axiom (everything is NCL) is the value proposition — deferring its application to the outward-facing surface is precisely the pattern (substance-vs-act collapse) that ADR-031 was opened to prevent. Also: the longer the surface stays hand-edited, the more drift accumulates between claims and architecture, and the higher the cost of eventually adopting positioning. The opt-in design means deferral by individual consumers is already supported — what is rejected is the protocol itself deferring the schema.

    Anti-patterns

    • Live Value-Prop Without Evidence ADRs

      A value-prop is set to status='Live but evidence_adrs is empty — marketing is published without architectural backing. The live_requires_evidence_adrs contract catches this at nickel-export, but the anti-pattern is the maintainer reasoning that 'we can fill in evidence later'.

    • Audience Record Inlined in Value-Prop

      A value-prop file declares the audience inline (name, primary_pain, channels) instead of referencing it by id. Multiple value-props targeting the same audience drift independently.

    • Marketing-Architecture Drift

      A value-prop's evidence_adrs cites an ADR that has been superseded or deprecated — the claim is still Live, but its backing is no longer the active architectural decision. The marketing surface and the architecture diverge silently.

    +
    diff --git a/site/site/public/adr/adr-036/index.html b/site/site/public/adr/adr-036/index.html new file mode 100644 index 0000000..89b868a --- /dev/null +++ b/site/site/public/adr/adr-036/index.html @@ -0,0 +1,84 @@ + + + +Tier Transition Substrate Effects — Deferred Scope from ADR-033 · Ontoref ADR + + +
    +
    + Accepted + adr-036 · 2026-05-29 +

    Tier Transition Substrate Effects — Deferred Scope from ADR-033

    +
    + +

    Context

    ADR-033 (Tier Transition Mechanism) enumerated a six-effect catalog per +transition direction. ADR-033 §FULL EFFECTS implemented exactly one of +those effects: the atomic config file replace (the last step of every +direction). The other fifteen effects — oplog bootstrap, commit_layer +initialization, state_root computation, daemon registry mutation, +Ed25519 keypair generation, catalog verification, daemon registry +de-registration, and tier-descent witness emission — were deferred +because they require coordinated implementation across the workspace +substrate crates (`ontoref-oplog`, `ontoref-commit`, `ontoref-ops`), +which were still being built out — not because of any external +dependency. The oplog is redb-backed and lives entirely in the +workspace; no stratumiops crate is required to persist the witness +chain.

    +

    The Hard constraints `downgrade-preserves-substrate-immutable` and +`tier-2-descent-emits-final-witness` from ADR-033 are documented as +DEFERRED in the tier_transition module preamble. This ADR is that +follow-up.

    +

    CURRENT STATE ENTERING THIS DECISION

    +

    Tier transition declaratively works via three reachability paths + (CLI, ConfigWatcher, catalogued op) that all converge on + `ontoref_ops::tier_transition::execute`. The execute function mutates + ONLY the config.ncl file atomically. No substrate is bootstrapped on + upgrade; no witness is emitted on descent; no oplog is created.

    +

    Tier-2 today is therefore declarative-only — projects can declare + `ops.tier = 'Tier2` and the protocol honours the declaration, but + the ADR-024 promises of cryptographic provenance and replay- + deterministic operation logs remain unfulfilled at runtime.

    Decision

    ACCEPTED. The substrate effects are implemented behind a `substrate` +feature flag on ontoref-ops (default: off), coordinated across +ontoref-ops (effect handlers), ontoref-oplog (redb-backed oplog +persistence), and ontoref-commit (commit_layer / state_root). The +oplog persistence backend is redb, in-workspace — NOT stratumiops +stratum-db. This ADR fixes the scope and the binding constraints; the +implementation is tracked as backlog item bl-014.

    +

    SHAPE OF THE NEXT SCOPE

    +

    Substrate effects will be implemented behind a `substrate` feature + flag on ontoref-ops (default: off). When enabled, execute runs the + full effect catalog; when disabled, only the atomic config rewrite + runs — preserving ADR-029 offline-first-default-stack for tier-0 + consumers.

    +

    Per-actor Ed25519 keys: generated on first Tier1→Tier2 transition, + stored at ~/.config/ontoref/keys/<actor>.key (mode 0600), public + key published in .ontoref/actors.ncl.

    +

    Oplog: append-only via ontoref-oplog (redb-backed, in-workspace), + mirrored as NCL render output for human inspection.

    +

    Tier-descent witness: payload_kind = 'tier_descent', written as + the last signed entry of the project's oplog before the config + rewrite to a lower tier.

    +

    Compensating rollback: each substrate effect registers its undo + into the transaction scope established by tier_transition::execute. + On any failure, compensations run in reverse order. Failures are + recorded in .ontoref/artifacts/transition-log/<ts>-failed.ncl + (already implemented in ADR-033 §FULL EFFECTS).

    Constraints

    • Hard

      Substrate effects (oplog bootstrap, Ed25519 keypair, witness emission) MUST be gated behind a `substrate` feature flag on ontoref-ops. Builds without the feature MUST still produce a working `tier_transition::execute` that performs the atomic config rewrite. Preserves ADR-029 offline-first-default-stack.

    • Hard

      When substrate is enabled and `tier_transition::execute` is invoked with old_tier=Tier2 AND new_tier!=Tier2, the function MUST emit a witness with payload_kind='tier_descent' to the project oplog before applying the config rewrite. Closes the deferred half of ADR-033 `tier-2-descent-emits-final-witness`.

    Alternatives considered

    • Leave the gap as implicit Rust source TODOs

      Rejected: Implicit TODOs are not queryable. ADR-031 on+re duality requires protocol gaps to live on the ontology axis. Cost of this ADR is small; cost of every consumer rediscovering the gap grows linearly with the consumer count.

    • Implement substrate effects directly in ADR-033 §FULL EFFECTS instead of splitting

      Rejected: ADR-033 §FULL EFFECTS was a self-contained PR-sized scope: tier_transition module + tests + CLI surface + watcher + holder + handler. Including substrate would have ballooned the scope across multiple substrate crates and pushed the ship date indefinitely. Splitting lets ADR-033 ship its value (working ore tier set + guard + tests) immediately.

    • Persist the oplog via stratumiops stratum-db

      Rejected: An earlier draft of this ADR assumed stratum-db for oplog persistence. The implemented `ontoref-oplog` is redb-backed and self-contained in the workspace, enforcing signature + parent-existence + monotonic-HLC checks on every append. Using stratum-db would reintroduce an external path dependency for tier-2, contradicting ADR-001's minimal-adoption posture and ADR-029's offline-first default. redb keeps the witness chain dependency-free.

    +
    diff --git a/site/site/public/adr/adr-037/index.html b/site/site/public/adr/adr-037/index.html new file mode 100644 index 0000000..8989167 --- /dev/null +++ b/site/site/public/adr/adr-037/index.html @@ -0,0 +1,103 @@ + + + +Interaction Trace — Parse-First Session Records with Structural-Only Validation and Optional Witness Binding · Ontoref ADR + + +
    +
    + Accepted + adr-037 · 2026-05-28 +

    Interaction Trace — Parse-First Session Records with Structural-Only Validation and Optional Witness Binding

    +
    + +

    Context

    An agent or human session produces a stream of reasoning, decisions, file +touches, and outcomes. Today that stream survives only as prose: the +transcript, or at best a `.coder/` markdown entry whose substance lives in a +free-text `content` field (coder.ncl::Record). Extracting "what decisions did +we make across the last ten sessions", "which sessions touched the validator +layer", or "which decisions are ADR candidates" means mining prose by hand — +exactly the cost the rest of the protocol was built to eliminate. Every other +project-defining artefact in ontoref is queryable NCL; the record of what an +agent actually DID in a session is not.

    +

    The opening framing of this decision was stronger than what was adopted: make +the agent emit its interaction in a *typed and validated* language — a contract +over the reasoning itself. That framing was consciously narrowed. The goal is +not to validate reasoning (undecidable, and an invitation to schema theatre +where agents fill fields to satisfy a contract rather than to record signal); +the goal is for the output to be *parseable and filterable* so a review need +not escarbar through prose. The distinction is load-bearing and drives every +constraint below.

    +

    Three adjacent decisions touch this surface but none owns it:

    +

    - ADR-024 (operations layer — agent action boundary) governs the *act*: the + operation an agent invokes and the witness it emits. It says nothing about + the agent's narration of the act. + - ADR-026 (validation architecture — three planes) defines structural vs + contextual validators. It is the plane this decision lives in, but it does + not define an artefact for agent interaction. + - ADR-031 (on+re constitutive duality) establishes the witness as the seam + where reflection-axis acts bind to ontology-axis substance. This decision + asks whether the agent's *interaction record* sits on that seam — and the + answer is: optionally.

    +

    The user's reframe added a second property: the record need not relate to the +witness at all. Absent a witness it still serves a review without deep digging. +That makes the witness binding optional, which in turn makes the layer usable +at every tier with different uses, rather than gated behind operations (tier-2).

    Decision

    Introduce the InteractionRecord — a parse-first trace of an agent/human +session segment, stored as JSONL (one record per line), shipped as the +reflection schema `reflection/schemas/interaction.ncl` (migration 0028).

    +

    DESIGN PROPERTIES (each is a constraint below):

    +

    1. Parse-first, not prose-analytic. Every dimension a review filters on is + an enum or a typed array (events[], decisions[], actions[], insights[], + files_touched[], tensions_engaged[], outcome). Free prose is confined to + three fields — `summary`, `Decision.rationale`, `Event.note` — and those + fields are NEVER a filtering axis. `jq` and `nu where` answer review + questions without reading the prose.

    +

    2. Structural-only validation. The validator (ADR-026 structural plane) + checks shape: required fields present, enum members in their allowed set, + arrays shaped correctly. It MUST NOT inspect the truthfulness of the free + prose. Asking a validator to certify that a rationale is honest or a + summary is faithful is a Hard biconditional on a question core.ncl names + as a Spiral tension — an ondaod forbidden pattern. Validation of prose + content is therefore explicitly out of scope, permanently.

    +

    3. Optional witness binding — the layer is tier-orthogonal. The `witness` + field (op_id, state_root, signature) is optional. Absent → tier-0 review + aid (no substrate required). Present → tier-1/2, where op_ref on events + and actions links the record to witnessed substrate acts; the record + becomes the reflection-side companion of those acts. This is the ADR-031 + seam, made optional so the layer is not gated behind operations adoption.

    +

    4. Vocabulary reuse, not forking. The schema reuses coder.ncl's canonical + enums (ActorKind, Kind, Domain, optional Category) via `import "coder.ncl"` + rather than redeclaring parallel vocabularies that would drift. The + runtime validator parses enum members FROM the schema files at runtime, so + the allowlist can never diverge from the contract.

    +

    5. Two emission paths, both supported. `ore interaction record` validates a + record structurally before appending (the forced path; malformed records + are rejected). Direct JSONL append by the agent is also permitted (the + free path), audited post-hoc by `ore interaction validate` (pre-commit / + CI gate). The `--no-validate` flag bridges the two on the same command.

    +

    6. Decisions materialise the adr? criteria as data. `Decision` carries + `alternatives_rejected`, `reversible`, `adr_candidate`, and + `tensions_engaged` (core.ncl node ids). `ore interaction decisions + --adr-candidates` surfaces ADR candidates across sessions in one query, + feeding the ADR lifecycle from observed decisions rather than recollection.

    +

    The layer is opt-in per ADR-029: a project that never emits interaction JSONL +remains valid and pays zero adoption cost.

    Constraints

    • Soft

      Validation of InteractionRecord MUST remain structural (shape, enum membership, array shape) and MUST NOT inspect the truthfulness or quality of the free-prose fields (`summary`, `Decision.rationale`, `Event.note`). Adding a Hard check that gates on prose content is forbidden — it is a biconditional on a Spiral tension (reality-vs-intent) per ondaod.

    • Hard

      The `witness` field on InteractionRecord MUST remain optional. Making it required gates the layer behind tier-2 (operations) adoption and discards the tier-0 review-aid use. The optional binding is what makes the layer tier-orthogonal per ADR-029.

    • Hard

      The InteractionRecord schema MUST reuse coder.ncl's canonical vocabularies (ActorKind, Kind, Domain, Category) via `import "coder.ncl"` rather than redeclaring parallel enums. The runtime validator MUST derive enum membership from the schema files, not hardcode it.

    • Hard

      The interaction schema (reflection/schemas/interaction.ncl) MUST be shipped to consumer data dirs as part of the reflection/ tree by install.nu, so adopting projects can resolve it. Migration 0028 is its propagation mechanism.

    • Soft

      Free prose in InteractionRecord MUST stay confined to `summary`, `Decision.rationale`, and `Event.note`. New filterable facets MUST be added as enums or typed arrays, never as free-text fields that a review would have to parse. The schema marks `summary` as the only free-prose top-level field.

    Alternatives considered

    • Typed AND validated contract over the agent's reasoning (the opening framing)

      Rejected: Validating reasoning is undecidable and invites schema theatre: agents fill fields to satisfy the contract, not to record signal — the exact H7 dysfunction ADR-024 names. A Hard contract over prose truthfulness is also a biconditional on a Spiral question (reality-vs-intent), an ondaod forbidden pattern. The adopted design keeps the typing for parseability and drops the validation of content.

    • Extend coder.ncl::Record instead of a new schema

      Rejected: Record's substance is a free-text `content` field — it is the prose artefact this layer is meant to complement, not the parse-first one. Bolting events/decisions/actions arrays onto Record would overload one type with two opposed purposes (prose memory vs structured trace) and muddy `coder triage`/`coder record` semantics. A sibling schema that REUSES Record's enums keeps both coherent and distinct.

    • Require the witness — bind every interaction record to the substrate

      Rejected: Gates the layer behind operations adoption (tier-2) and discards the user's explicit requirement that the record serve a review even with no witness present. Optional binding delivers the tier-0 review-aid use AND the tier-2 witnessed-companion use from one schema; mandatory binding delivers only the latter.

    • Store instances as per-record NCL files validated by nickel-export

      Rejected: NCL enums are tags ('AgentClaude), JSONL stores them as strings — applying the NCL contract to imported JSON fails on every enum field, and per-file NCL is the wrong shape for streaming filters at scale. JSONL + a structural Nushell validator (that reads enum members from the NCL schema) is the correct split: NCL is the contract, JSONL is the queryable instance store.

    • Validate the free-prose fields with heuristics (length, keyword presence, sentiment)

      Rejected: Any check that gates on prose content is a proxy for truthfulness that the layer explicitly disclaims. Heuristic prose checks produce false confidence (a record passes because it is verbose, not because it is honest) and re-open the Spiral collapse this ADR closes. Prose quality stays a human review concern.

    Anti-patterns

    • Validating the Content of Free Reflection

      A contributor adds a check that gates on the content of `summary`, `Decision.rationale`, or `Event.note` — length thresholds, required keywords, sentiment, or a claim that the rationale 'must be honest'. This is a biconditional on the reality-vs-intent Spiral and produces false confidence: a record passes because it is verbose, not because it is truthful.

    • New Filterable Facet Added as Free Text

      A new dimension a review wants to filter on (e.g. 'severity', 'subsystem') is added as a free-text field instead of an enum or typed array. Reviews must then string-match prose, re-introducing the prose-mining cost the layer exists to eliminate.

    • Parallel Enum Vocabulary for Sessions

      InteractionRecord redeclares its own ActorKind/Kind/Domain enums instead of importing coder.ncl, or the runtime validator hardcodes the allowlist. The two vocabularies drift the first time one is extended, and the structured trace silently disagrees with the prose Record about what an actor or kind is.

    +
    diff --git a/site/site/public/adr/adr-038/index.html b/site/site/public/adr/adr-038/index.html new file mode 100644 index 0000000..0c21d1b --- /dev/null +++ b/site/site/public/adr/adr-038/index.html @@ -0,0 +1,99 @@ + + + +OCI Distribution and Installer — Multi-Arch Runnable Image plus curl|sh Bundles, Modeled in the Workflow Layer · Ontoref ADR + + +
    +
    + Accepted + adr-038 · 2026-05-29 +

    OCI Distribution and Installer — Multi-Arch Runnable Image plus curl|sh Bundles, Modeled in the Workflow Layer

    +
    + +

    Context

    Until now ontoref could only be installed from a source checkout: `just +install-daemon` runs `cargo build --release` then `install/install.nu`, which +lays out the binary, the bootstrapper, the CLI wrapper (with ONTOREF_ROOT +baked), and the data layer (reflection/, ontology/, domains/, templates/) across +the platform bin/data/config dirs. There was no way to install on a machine that +does not have the source, and no way to run the daemon as a container.

    +

    Two install paths are wanted, sharing ONE artifact origin in the OCI registry +(`reg.librecloud.online`, already declared in manifest.ncl registry_provides):

    +

    1. A runnable multi-arch image — `docker run …/ontoref/ontoref-daemon:VERSION` + — for deploying the daemon as a service/cache. + 2. `curl … | sh` — an installer that detects OS/arch, pulls the matching OCI + bundle (binary + data layer + wrappers), extracts it, and installs into a + chosen prefix, reproducing install.nu's layout.

    +

    The mechanism is NOT a pile of standalone scripts. The workflow layer (the +NCL-first CI/build/distribution model) already had the exact seam: `build_kind` +carried 'Container, `distribution_kind` carried 'ContainerRegistry | 'Package | +'Artifact, and the `distributions` catalog was empty with no generator. The +infrastructure was also already declared: `oras`, `cosign`, `sops`, `age` are +Hard dependencies in manifest.ncl, and ADR-017 models the registry credential +vault. So the coherent move was to populate the workflow layer's distribution +catalog and add the missing distribution generator, not to invent a parallel +packaging path.

    +

    The load-bearing risk is the "Protocol, Not Runtime" axiom: shipping a runnable +daemon image could be read as making ontoref a runtime dependency. The framing +that resolves it is ADR-029 (tier coexistence): the daemon is a cache, not a +hard dependency. The installer therefore installs a CLI + data layer that work +WITHOUT the daemon; the image is an optional accelerator for those who want the +service.

    Decision

    Distribute ontoref through the OCI registry along two paths from one build, +modeled entirely in the workflow layer (catalog in reflection/defaults/workflow.ncl, +a `release` layer triggered OnTag in ontology/workflow.ncl, and a distribution +generator in reflection/modules/workflow.nu). Migration 0029 propagates the +schema/catalog additions to consumers.

    +

    DESIGN PROPERTIES (each is a constraint below):

    +

    1. Modeled in the workflow layer, not hand-maintained scripts. The 'Bundle + build kind, the build/distribution catalog entries, and the generator that + emits .woodpecker/release.yml + install/install.sh + justfiles/release.just + all live in the NCL workflow model. Distribution is therefore self-describing + and queryable via `ore workflow`, like every other build/CI artifact.

    +

    2. Build-once; the image is assembled FROM the binary, never recompiled. The + musl binary is cross-compiled once per arch (`cross`). Both the install + bundle AND the runnable image consume that same binary. The image is + assembled with buildah from the cross output + the data layer — no + `docker buildx`, no QEMU, no docker-in-docker on the runner.

    +

    3. The daemon image is an OPTIONAL accelerator (ADR-029). The curl|sh installer + installs a CLI + data layer that operate without the daemon. The runnable + image serves container deployments only. Adopting ontoref never requires + running the daemon — the "Protocol, Not Runtime" axiom is preserved.

    +

    4. The bundle reproduces install.nu's layout. assemble-bundle.nu packages the + binary, bootstrapper, CLI wrapper, a target-arch nickel, the data layer, and + a config skeleton; install.sh places them exactly as install.nu does, + including the baked ONTOREF_ROOT in the wrapper. The bundle's config skeleton + is taken ONLY from install/resources/ — never from .ontoref/config.ncl.

    +

    5. curl|sh needs nothing but POSIX tools. Bundles are .tar.gz (gzip is + universal; zstd is not assumed). install.sh prefers `oras` and falls back to + plain `curl` against the OCI Distribution API; it verifies the .sha256 + sidecar and, if present, a cosign signature. The bundle namespace + (ontoref/dist) is published for anonymous pull, so no credentials are needed. + The installer itself is served at a plain HTTPS URL — never as an oras + artifact, which a bare `curl | sh` could not fetch.

    +

    6. A publish gate. The `smoke-bundle` validation extracts the freshly-assembled + native bundle and runs the daemon binary before any distribution step + publishes. A broken artifact fails the pipeline rather than reaching users.

    +

    7. Supply chain. The image is cosign-signed and the SBOM cosign-attested. Since + the self-hosted Woodpecker has no Fulcio, signing is key-based; the cosign + key and the registry RW credential come from the src-vault (ADR-017).

    +

    Platform scope is Linux amd64 + arm64 (static musl). macOS has no published +build — install.sh detects Darwin and prints source-build / container guidance.

    Constraints

    • Hard

      OCI distribution MUST be declared in the workflow layer — build/distribution catalog entries in reflection/defaults/workflow.ncl and the generator in reflection/modules/workflow.nu — and the published artifacts (.woodpecker/release.yml, install/install.sh, justfiles/release.just) MUST be generated from it, never hand-maintained. The `distributions` catalog MUST be non-empty.

    • Hard

      The installer MUST install a CLI + data layer that operate WITHOUT the daemon. The runnable daemon image MUST remain an optional accelerator. Making the daemon a required runtime for adoption is forbidden — it would violate the Protocol-Not-Runtime axiom (ADR-029: the daemon is a cache, not a hard dependency).

    • Hard

      The bundle and installer MUST reproduce install.nu's layout: the binary, bootstrapper, CLI wrapper with a baked ONTOREF_ROOT, a bundled nickel, the data layer, and a config skeleton, placed in the same bin/data/config locations. Divergence silently breaks the installed CLI.

    • Hard

      The published runnable image MUST be assembled from the already cross-compiled musl binary (buildah copy), NOT recompiled in CI and NOT built with docker buildx/QEMU. The bundle and the image MUST consume the same per-arch binary.

    • Hard

      The bundle's config skeleton MUST be taken only from install/resources/ — never from the live .ontoref/config.ncl. A corrupt or environment-specific project config must never leak into a published bundle.

    • Soft

      The bundle namespace (ontoref/dist) SHOULD be published for anonymous pull so the curl|sh installer needs no registry credentials. The installer script itself MUST be served at a plain HTTPS URL, not as an oras artifact.

    Alternatives considered

    • Plain GitHub/Gitea-release tarballs, no OCI

      Rejected: ontoref already standardizes on OCI: oras/cosign/sops are Hard deps and the registry topology is declared in manifest.ncl (ADR-017). Plain tarballs would need a separate hosting and integrity story and would not reuse the existing registry credentials or signing. The installer keeps a curl fallback for hosts without oras, but the artifact origin stays OCI.

    • Extract the binary + data from the runnable image's layers

      Rejected: Couples the installer to the image's internal layer layout — a fragile contract that breaks whenever the image build changes. Dedicated per-arch bundle artifacts are arch-addressable (:VERSION-linux-amd64), robust to image changes, and let the bundle carry exactly the install layout without the image's runtime concerns.

    • docker buildx multi-arch (recompile or QEMU emulation)

      Rejected: Duplicates the cross build and requires QEMU/binfmt and docker-in-docker on the runner. Since the musl binaries are already cross-compiled, assembling the image from them with buildah is single-origin, faster, and daemonless on the runner. buildx would rebuild what cross already produced.

    • Standalone install.sh + Dockerfile, not modeled in the workflow layer

      Rejected: Distribution would not be self-describing or queryable and would diverge from the PAP — every other build/CI artifact is generated from the NCL workflow model. The DistributionSpec/'Container kinds exist precisely so distribution is declared, not scripted. Hand-maintained scripts drift from the model.

    • Bundle nushell into the artifact too

      Rejected: Nushell is large and is the CLI's runtime; bundling it bloats every download. nickel (small, static musl, needed by the bootstrapper) IS bundled; nushell is detected at install time and the user is given install guidance. The daemon image needs neither — only the daemon binary + nickel + data.

    Anti-patterns

    • Treating the Daemon Image as a Required Runtime

      A change makes the CLI or adoption depend on a running daemon — e.g. the installer starts/expects the daemon, or commands fail without it. This turns the optional accelerator into a runtime dependency and breaks Protocol-Not-Runtime.

    • Editing the Generated Distribution Artifacts Directly

      A contributor edits .woodpecker/release.yml, install/install.sh, or justfiles/release.just by hand instead of changing the workflow catalog and regenerating. The artifacts drift from the NCL model that is supposed to be their source of truth.

    • Recompiling to Build the Multi-Arch Image

      The image is built by recompiling per arch (docker buildx + QEMU, or cargo inside the Dockerfile) instead of assembling from the already cross-compiled binary. This duplicates the build, reintroduces emulation/docker-in-docker, and can make the image binary differ from the bundle binary.

    • Shipping the Live Project Config in the Bundle

      The assembler copies .ontoref/config.ncl (environment-specific, possibly corrupt) into the bundle instead of the install/resources skeleton, leaking local state into a published artifact.

    +
    diff --git a/site/site/public/adr/adr-039/index.html b/site/site/public/adr/adr-039/index.html new file mode 100644 index 0000000..8c064f3 --- /dev/null +++ b/site/site/public/adr/adr-039/index.html @@ -0,0 +1,113 @@ + + + +Typed Recipe Annotations and the Transversal +/- Mutation Verb — Self-Describing Justfiles, Queryable Like the API Catalog · Ontoref ADR + + +
    +
    + Accepted + adr-039 · 2026-05-29 +

    Typed Recipe Annotations and the Transversal +/- Mutation Verb — Self-Describing Justfiles, Queryable Like the API Catalog

    +
    + +

    Context

    Projects accumulate many just recipes (and loose scripts). ontoref already +INVENTORIES recipes — describe.nu::scan-just-recipes runs `just --list` and +categorizes each recipe by a name-prefix heuristic (categorize-recipe), and +`ore describe tools` lists them. But the inventory is shallow: it knows a +recipe's name, its trailing `#` comment, and a guessed bucket. It cannot answer +"which is the canonical way to run all checks", "show me the deploy tasks", or +"what does build-standalone do and why" — because the recipe carries no declared +INTENT and no real categorization, only a prefix guess.

    +

    Two adjacent layers already exist and must not be duplicated:

    +

    - Recipes (justfiles) are the MECHANISM layer — the concrete command. Single + source of truth for HOW. + - Modes (reflection/modes/*.ncl) are the PROCEDURE layer — multi-step gated + DAGs with actors/steps/depends_on, executed by `ore run`.

    +

    What is missing is not a third store ("devtask" is a target-side abstraction, +not ontoref vocabulary). What is missing is for a recipe to SELF-DESCRIBE — to +declare its intent and categorization in place — exactly as Rust handlers do +with #[onto_api(method, path, description, tags)]: the annotation sits next to +the code, a proc-macro parses it, and inventory aggregates a zero-cost catalog +that `ore describe api` / GET /api/catalog / MCP all read. The same shape is +wanted for recipes: an annotation next to the recipe, parsed by ontoref, that +makes justfiles queryable.

    +

    The qa routing fix in the same session (ore qa show/list/search/add) added the +seeding primitive — a tag-categorized, nickel-validated write path. The question +this ADR settles is the GRAMMAR and the BOUNDARIES of seeding/enriching the tool +surface: how an annotation is written, how it is mutated, and how a project that +already has its own justfiles adopts compliance without losing its recipes.

    +

    The load-bearing tension is the named Spiral `formalization-vs-adoption`: richer +formalization (typed annotations on every recipe) buys ecosystem visibility but +raises adoption cost. The tension's own resolution in core.ncl is "schemas are +optional layers, not mandatory gates" — which constrains this design directly.

    Decision

    Make justfiles self-describing through a typed comment annotation, expose a +transversal `+`/`-` mutation verb to write/remove annotations and other store +entries, and ship a NON-DESTRUCTIVE compliance/migration mechanism so projects +adopt the surface while keeping their own recipes. Migration 0030 propagates the +grammar, the recommended mode taxonomy, and the new surface to consumers.

    +

    DESIGN PROPERTIES (each is a constraint below):

    +

    1. Annotation grammar — `# @onto key=val ...`. A single comment line directly + above the recipe carries typed metadata:

    +

    # Run all CI checks locally + # @onto mode=ci labels=ci,gate intent="all checks before push" + ci-full: + ...

    +

    Keys: `mode` (one token — the domain category: tools|dev|build|dev-build| + dply-build|test|docs|dist|secrets|…), `labels` (csv — free tags), `intent` + (quoted free text — the "best way to X" sentence the inventory cannot + derive). The recipe name and its plain `#` doc comment remain unchanged; + `@onto` enriches, it does not replace.

    +

    2. The recipe is the single source of truth. Annotations live IN the justfile, + above the recipe — never in a parallel devtasks store. ontoref parses them + (scan-just-recipes gains an @onto parser) and DERIVES a catalog, exactly as + #[onto_api] derives artifacts/api-catalog-*.ncl. `ore tool export` writes the + derived catalog; `ore describe tools --mode <m> --label <l>` reads it.

    +

    3. Mutation via a transversal verb. `ore + <noun> <args>` upserts and + `ore - <noun> <args>` removes, across stores:

    +

    ore + tool ci-full --mode ci --labels ci,gate --intent "all checks" + ore - tool ci-full # removes the @onto line only + ore + qa "<q>" "<a>" --tags devtool,ci # the qa add path + ore - qa <id>

    +

    `+ tool` / `- tool` edit the `@onto` comment ABOVE the named recipe in + place, validated against `just --list`; they NEVER touch the recipe body. + `+`/`-` are first-level dispatcher tokens taking `<noun> <args>`, so new + stores plug in without new top-level verbs.

    +

    4. Annotations are OPTIONAL enrichment, never a gate. An un-annotated recipe + stays valid, runnable, and still inventoried by name+category (today's + behavior). Annotation raises a recipe from guessed to declared; it is never + required to run a recipe. This is the `formalization-vs-adoption` balance + made literal — "optional layers, not mandatory gates".

    +

    5. Non-destructive compliance/migration. A `compliant` mode (and `ore tool + migrate`) scans a project's existing justfiles, proposes `@onto` annotations + (seeding `mode` from categorize-recipe + name, leaving `intent` for the + author), and — opt-in — reorganizes recipes into justfiles/<mode>.just. It + MUST preserve every project-owned recipe and its semantics: no recipe is + deleted or renamed, reorg is opt-in and reversible, and the project's own + recipes are first-class (the mechanism annotates them, it does not supplant + them with ontoref-blessed ones).

    +

    6. Drift-checkable round-trip. `ore tool audit` reports orphan annotations + (an `@onto` whose recipe is gone from `just --list`) and, at compliance + level, un-annotated recipes. Mirrors docs-drift: the annotation and the + recipe must stay in sync because they are co-located.

    +

    A multi-step task that deserves gating/steps graduates to a MODE, not an +annotation; `@onto` is for single-recipe intent+categorization. Modes and +annotated recipes are complementary, not competing.

    Constraints

    • Hard

      A recipe in a justfile is the single source of truth for its command. @onto metadata MUST live in a comment co-located with the recipe; there MUST NOT be a parallel store of recipe command strings. Any tool catalog is DERIVED from the annotations and MUST be regenerated, not hand-edited.

    • Hard

      The annotation MUST be a single comment line `# @onto key=val ...` immediately above the recipe. Recognized keys: `mode` (one token), `labels` (comma-separated), `intent` (double-quoted free text). Unknown keys MUST be ignored, not error. The recipe name and its plain `#` doc comment MUST be left intact.

    • Hard

      Annotation/entry mutation MUST go through `ore + <noun> <args>` (upsert) and `ore - <noun> <args>` (remove). For noun `tool`, the verb edits/removes ONLY the @onto comment above the named recipe, validated against `just --list`, and MUST NOT modify the recipe body. New stores MUST be added as nouns, not as new top-level verbs.

    • Hard

      An un-annotated recipe MUST remain valid, runnable, and inventoried by name+category. Annotation MUST NOT be a precondition for running or listing a recipe. Compliance is reported by audit, never enforced as a gate.

    • Hard

      The compliance/migration mechanism MUST NOT delete or rename any project-owned recipe. It annotates in place and may propose reorganization into justfiles/<mode>.just only as an opt-in, semantics-preserving, reversible step. A project's own recipes are first-class and MUST survive migration unchanged in behavior.

    • Soft

      `ore tool audit` MUST report orphan annotations (an @onto whose recipe is absent from `just --list`) and, at compliance level, un-annotated recipes. Every @onto MUST reference an existing recipe.

    • Soft

      The recommended `mode` taxonomy (tools/dev/build/test/ci/deploy/docs/dist/secrets/…) is advisory guidance, not an enforced enum; `labels` are free-form. Validation MUST NOT reject an unrecognized mode or label.

    Alternatives considered

    • A standalone devtasks.ncl store holding command strings + labels

      Rejected: Third source of truth alongside justfiles and modes; the stored command drifts from the real recipe, and it re-imports the loose-script problem. The recipe must stay the single source; the annotation co-locates metadata with it.

    • Promote every dev/deploy recipe into a full NCL mode

      Rejected: Modes are the procedure layer for multi-step gated DAGs. Forcing single-command recipes through mode authoring is heavy and collapses the mechanism/procedure distinction. Multi-step tasks DO graduate to modes; single-recipe intent belongs in an annotation.

    • Make annotations mandatory (a compliance gate that fails un-annotated recipes)

      Rejected: Collapses the named `formalization-vs-adoption` Spiral toward the Yang pole, contradicting its own resolution ('optional layers, not mandatory gates'). Annotations must enrich an always-valid baseline; audit reports compliance, it does not block.

    • Glued verb `+tool` / per-store `tool add` instead of a transversal `+ <noun>`

      Rejected: Per-store verbs do not project to new stores without new top-level commands, and `+tool` glued is a one-off. A transversal `+ <noun> <args>` is the uniform, extensible idiom the user asked for.

    • Destructive reorg — ontoref owns justfiles/ and rewrites them to a canonical layout

      Rejected: Adoption-hostile: projects have their own recipes and conventions. Compliance must be non-destructive — annotate in place, propose reorg opt-in, never delete or rename a project's recipes.

    Anti-patterns

    • A Standalone Devtasks Store Duplicating Recipe Commands

      A parallel devtasks.ncl stores copied command strings + labels alongside the justfiles. It becomes a third source of truth that drifts from the real recipe and re-imports the loose-script problem.

    • Making Annotations Mandatory / Gating on Them

      Compliance fails un-annotated recipes or blocks running them. This collapses the formalization-vs-adoption Spiral toward Yang, contradicting its own 'optional layers, not mandatory gates' resolution.

    • +/- Editing the Recipe Body or Destroying Project Recipes

      `+ tool` rewrites the recipe command, or migration deletes/renames a project-owned recipe. Adoption-hostile; violates recipe-as-single-source and non-destructive-migration.

    • Hand-Editing the Derived Tool Catalog

      Someone edits the generated tool catalog artifact directly. It is derived from the annotations and overwritten on regeneration, so the edit drifts from source.

    • Forcing Single-Command Recipes Through Mode Authoring

      Every dev/deploy recipe is promoted to a full NCL mode. Modes are for multi-step gated DAGs; over-applying them collapses the mechanism/procedure distinction and burdens adoption.

    +
    diff --git a/site/site/public/adr/adr-040/index.html b/site/site/public/adr/adr-040/index.html new file mode 100644 index 0000000..8719cbd --- /dev/null +++ b/site/site/public/adr/adr-040/index.html @@ -0,0 +1,136 @@ + + + +Convention Bindings and Shared-Source Propagation — Declared Guidelines/CI/Config, Synced From a Shared Origin Through the Update Mechanism · Ontoref ADR + + +
    +
    + Accepted + adr-040 · 2026-05-29 +

    Convention Bindings and Shared-Source Propagation — Declared Guidelines/CI/Config, Synced From a Shared Origin Through the Update Mechanism

    +
    + +

    Context

    A project's working conventions are NOT captured by ontoref today, even though +they are load-bearing for every contributor and agent:

    +

    - GUIDELINES — per-language coding rules. In this ecosystem they are SHARED: + .claude/guidelines/<lang>/ is a real directory whose individual *.md files are + PER-FILE symlinks to /Users/Akasha/Tools/dev-system/languages/<lang>/guidelines/*.md + (verified: e.g. .claude/guidelines/rust/ACTIX.md → dev-system/.../rust/guidelines/ACTIX.md). + dev-system is the single origin; many projects symlink the same source. The same + origin also feeds NON-Claude consumers (just-modules/, ci/, workflow.md), so the + guidelines are project conventions of which Claude Code is only one consumer — + not Claude-specific config. A project's OWN guideline is simply a real .md file + sitting beside the symlinked ones in the same directory. + - CI — already modeled: the workflow layer (ADR-038 et al.) declares + validations/builds/distributions and generates .woodpecker/*, pre-commit, + justfiles. CI is declared; it is just not cross-referenced from a single + "what conventions does this project use" surface. + - CONFIG conventions — how a project's config is structured (config.ncl shape, + .cargo layout, env wiring). Partly surfaced by `describe config`.

    +

    ontoref already has adjacent surface: `describe guides` reads `language_guides` +from the .claude/guidelines/*/ glob, `card.ncl` is the project beacon ("what IS +this project"), and `describe config` reports the runtime config. What is missing +is (a) a DECLARATION of which guideline/convention sources a project binds and at +what version, and (b) a propagation/SYNC mechanism so a shared change — e.g. a +new Rust version bumping the guideline — reaches every project, and a project's +own refinement can flow back, without hand-managing symlinks per project.

    +

    The propagation need is the heart of this ADR. Symlinks propagate transparently +but only on one machine, are invisible to ontoref's queryable surface, break on +clone/CI/containers, and cannot be versioned or audited. The protocol already +owns the right mechanism for "a change consumers must adopt": the migration +system (ADR-010) plus the `update_ontoref` mode. Convention bindings should ride +that mechanism, not a second ad-hoc symlink convention.

    +

    Two named Spiral Tensions from `.ontoref/ontology/core.ncl` are engaged:

    +

    - formalization-vs-adoption — declaring conventions raises formalization; + forcing every project to vendor/copy them raises adoption cost. Its + resolution ("optional layers, not mandatory gates") constrains the design. + - ontology-vs-reflection — the binding declaration is Yin (what conventions + the project IS bound to); the sync/update action is Yang (what BECOMES when + the shared origin moves). Both must coexist: a declaration with no sync drifts + silently; a sync with no declaration has nothing to reconcile against.

    +

    This ADR is the meta-convention sibling of ADR-039 (which made the project's OWN +recipes self-describing). ADR-039 is about intent the project authors; ADR-040 is +about conventions the project BINDS from a shared origin and keeps in sync.

    Decision

    Make a project DECLARE its convention bindings, hold the convention CONTENT +authoritatively under `.ontoref/` (protocol-first, harness-independent), expose +both as a queryable surface, and SYNC from the shared origin through the existing +update/migration mechanism — never through hand-managed symlinks as the source of +truth.

    +

    DESIGN PROPERTIES (each is a constraint below):

    +

    1. Declaration in card.ncl. The beacon gains a `conventions` block:

    +

    conventions = { + guidelines = [ + { lang = "rust", source = "dev-system", version = "2024" }, + { lang = "nushell", source = "dev-system", version = "0.111" }, + ], + ci = { workflow_layer = "ci" }, # reference, not a copy (ADR-038) + config = { schema = ".ontoref/config.ncl", profile = "default" }, + }

    +

    card.ncl is chosen over a new catalog because conventions are part of "what + IS this project" (the beacon already answers that and is migration-versioned), + and `describe guides`/`describe config` already read this neighborhood. The + declaration NAMES sources and versions; it does NOT inline guideline content.

    +

    2. Content authoritative under .ontoref/, harness as materialized view + (protocol-first). The guideline CONTENT lives at .ontoref/conventions/<lang>/ + — the protocol surface, addressable independently of any harness (cf. + ontology-decoupled-from-project). .claude/guidelines/<lang>/ becomes a + MATERIALIZED VIEW: a symlink pointing INTO .ontoref/conventions/<lang>/, so + Claude Code keeps reading its expected path while ontoref owns the content. + The conventions survive with no .claude/ at all (CI, other agents, plain + tooling read .ontoref/ directly). Within .ontoref/conventions/<lang>/, each + file is either a symlink to the shared origin (dev-system) or a real + project-OWN file beside them — the per-file model the current layout already + uses, just relocated to the protocol root.

    +

    3. CI is referenced, never re-declared. The `ci` binding points at a workflow + layer id (ADR-038's model). ontoref does not duplicate CI definition into the + conventions surface; it cross-links so `describe conventions` can show "CI = + workflow layer 'ci'" and jump to the workflow model.

    +

    4. Shared origin, resolvable PER-FILE mode (symlink | vendored | local-own). + Inside .ontoref/conventions/<lang>/ each file resolves explicitly: `symlink` + to the shared origin (transparent dev-loop), `vendored` (a copied snapshot, + clone/CI/container-safe), or `local-own` (a real project-authored file with no + origin). The per-file granularity is what lets a project mix shared guidelines + with its own in one directory. The DECLARATION (card.ncl) plus the .ontoref/ + content are the source of truth; the .claude/ symlink is a derived view.

    +

    5. Propagation rides the update/migration mechanism, not bespoke symlink edits. + `ore conventions sync` (and the `update_ontoref` mode) reconcile each declared + binding against the shared origin: detect version drift (origin moved, e.g. + new Rust guideline), report it, and — opt-in — re-link or re-vendor to the + declared version. A shared change reaches every binding project through this + one path. Project-local refinement flows back by updating the origin and + bumping the declared version, never by editing a vendored copy in place and + losing the link to origin.

    +

    6. Bidirectional but origin-authoritative. Sync is two-directional in intent + (origin → projects for shared bumps; project → origin for refinements) but + the ORIGIN is authoritative: a project does not silently fork shared + conventions. A divergence is reported by audit; promoting a local change + means writing it to the origin and re-syncing, so all binding projects can + adopt it.

    +

    7. Optional, audit-not-gate. A project may declare zero conventions and remain + valid. `ore conventions audit` reports: bindings whose origin is unreachable, + version drift (declared != origin), and resolution-mode mismatches (declared + symlink but found vendored, or vice versa). It REPORTS; it does not block — + the formalization-vs-adoption resolution.

    +

    These are MODES/utilities, not new stores: per the user's framing, guidelines/CI/ +config bindings are implementation-framework utilities (templates, examples, +sync). `ore conventions sync|audit|describe` and the `update_ontoref` extension +are the operational surface; card.ncl carries the declaration.

    Constraints

    • Hard

      A project's convention bindings (guidelines per language, CI reference, config conventions) MUST be declared in .ontoref/card.ncl as a `conventions` block naming source + version. Guideline CONTENT MUST NOT be inlined; the declaration names sources, content resolves from the shared origin.

    • Hard

      Convention CONTENT MUST live authoritatively under .ontoref/conventions/<lang>/ (protocol-first). .claude/guidelines/<lang>/ MUST be a materialized view (a symlink into .ontoref/conventions/), never the authoritative location. The conventions MUST resolve with no .claude/ present. The card.ncl declaration plus the .ontoref/ content are the source of truth; per-file resolution mode (symlink to origin | vendored | local-own) is recorded, and a project's own guideline is a real file beside the shared ones.

    • Hard

      Convention propagation/sync MUST be implemented as an extension of the existing migration system (ADR-010) and the update_ontoref mode — e.g. `ore conventions sync` and an update_ontoref step — NOT as a separate symlink-specific propagation path. A shared-origin change reaching consumers MUST be expressible as / accompanied by a migration.

    • Hard

      The shared origin MUST be authoritative for shared conventions. A project MUST NOT silently fork a bound convention by editing a vendored copy in place. Local refinements are promoted by writing to the origin and bumping the declared version; divergence MUST be reported by audit, not auto-merged.

    • Soft

      Convention bindings MUST be optional: a project may declare none and remain valid. `ore conventions audit` MUST report unreachable origins, version drift, and resolution-mode mismatches, but MUST NOT block builds or commands.

    • Soft

      The `ci` convention binding MUST reference a workflow layer id (ADR-038 model), not re-declare CI steps in the conventions surface. `describe conventions` cross-links to the workflow model rather than duplicating it.

    Alternatives considered

    • Keep symlinks as the mechanism, add nothing

      Rejected: Symlinks are invisible to ontoref, unversioned, and break on clone/CI/container. They cannot answer 'what version' or 'is this in sync', and a shared bump requires manual per-project symlink work. The whole point is to make bindings declared, queryable, and synced.

    • Declare conventions in a new .ontoref/catalog/conventions.ncl

      Rejected: Fragments 'what the project is bound to' away from the card.ncl beacon and duplicates the read surface that describe guides/config already provide. A catalog suits kind-extensible operation inventories (ADR-034), not the small, identity-level binding declaration. Chosen against, but the catalog remains the right home if bindings later grow rich enough to need their own kind system.

    • A bespoke convention-sync tool independent of the migration system

      Rejected: Duplicates ADR-010's propagation mechanism and update_ontoref, and stays outside `ore migrate`/`describe`. Convention propagation is the same event shape as protocol propagation; it must reuse that one path.

    • Vendor (copy) conventions only, drop symlinks entirely

      Rejected: Loses the transparent dev-loop where editing the origin is instantly reflected. Both modes have a place: symlink for local dev, vendored for clone/CI/container. The declaration records which mode applies; forcing one collapses a useful choice.

    • Inline guideline content into card.ncl / the project

      Rejected: Bloats the beacon, defeats sharing, and guarantees drift from the origin. Bindings must name sources+versions and resolve content from the shared origin, never inline it.

    Anti-patterns

    • Treating .claude Symlinks as the Authoritative Content

      Convention content lives authoritatively under .claude/guidelines/* (harness-specific) with no card.ncl declaration and nothing under .ontoref/. The binding is invisible to ontoref, unversioned, harness-coupled, and vanishes on clone/CI/container or when there is no Claude Code.

    • A Separate Sync Path Outside the Migration System

      Convention propagation is built as its own tool/script independent of the migration system and update_ontoref, so it is invisible to `ore migrate` and duplicates the protocol's propagation mechanism.

    • Editing a Vendored Convention In Place

      A project edits its vendored copy of a shared guideline directly, forking the shared origin silently. Other binding projects never see the change and the origin fractures.

    • Failing Builds on Convention Drift

      `ore conventions audit` (or a hook) blocks commands/builds when a binding is drifted or undeclared, turning an optional layer into a mandatory gate.

    • Re-Declaring CI in the Conventions Surface

      CI steps are copied into card.ncl's conventions block instead of referencing the workflow layer, creating a second drifting CI source.

    +
    diff --git a/site/site/public/adr/adr-041/index.html b/site/site/public/adr/adr-041/index.html new file mode 100644 index 0000000..64660b6 --- /dev/null +++ b/site/site/public/adr/adr-041/index.html @@ -0,0 +1,69 @@ + + + +Richer Op Model — Retract Operations and Per-Cell Typed CRDT Merge · Ontoref ADR + + +
    +
    + Accepted + adr-041 · 2026-05-30 +

    Richer Op Model — Retract Operations and Per-Cell Typed CRDT Merge

    +
    + +

    Context

    ADR-027 declares per-domain CRDT merge strategies (HlcLastWriterWins, +OrSet, TextMerge, GSet) as load-bearing, and ontoref-sync ships them as +tested `CrdtMergeStrategy` impls. bl-018 wired multi-actor convergence, +but only at the commit-layer cell level via HlcLastWriterWins: the +highest-HLC assertion to a cell wins. Investigation while attempting +bl-029 found the substrate cannot express the other three strategies:

    +

    - `OpPayload` has only `EntityAssert` (and the state-neutral marker + variants). There is NO retract/remove — so OR-Set add/remove cannot + be represented at all. + - The commit layer stores opaque value bytes per cell and resolves + concurrent writes by last-writer-wins. There is no place for a cell + to declare a richer merge (set union, text merge), so OrSet/TextMerge + have nothing to bind to. + - Grow-only/append-only domains (GSet-like) are already covered by the + distinct-entity cell model and need no new machinery.

    +

    Without a richer op model the four CRDT strategies remain library code +no domain can use; convergence is correct only where last-writer-wins is +the right semantics.

    Decision

    ACCEPTED. Extend the substrate op model along two axes, both behind the +existing tier-2/`substrate` posture (tier-0/1 unaffected):

    +

    1. RETRACT OP. Add `OpPayload::Retract { attrs, entity }` — the inverse + of `EntityAssert`, removing the named (entity, attribute) cells. A + retract is a witnessed, signed, DAG-anchored op like any mutation; + it is the precondition for remove-based domains (OR-Set).

    +

    2. PER-CELL TYPED MERGE. A cell resolves its value through a CRDT type + selected by the cell's DOMAIN (derived from the attribute's declared + domain in the catalog), not by unconditional last-writer-wins. The + commit layer's replay FOLDS every op touching a cell through that + domain's `CrdtMergeStrategy` rather than taking the last applied + value. `HlcLastWriterWins` remains the DEFAULT for any cell whose + domain declares no strategy, so the current behaviour and all + existing state roots are preserved exactly.

    +

    The fold preserves determinism: CRDT merges are commutative, associative, +and idempotent, and ties resolve by the same HLC+payload order the oplog +already imposes — so the rebuilt state root is independent of arrival +order, which bl-016's replay determinism and bl-018's convergence both +require. Witnesses are unchanged: a Retract is witnessed exactly as an +EntityAssert is, keeping every mutation on the witness-as-axis-seam.

    Constraints

    • Hard

      A Retract operation MUST be a signed, DAG-anchored oplog entry subject to the same append-time validation (signature, parents, monotonic HLC) as EntityAssert. Deletion MUST NOT have an unwitnessed side channel.

    • Hard

      The per-cell merge MUST default to HlcLastWriterWins for any cell whose domain declares no CRDT strategy, so state roots computed before this ADR are reproduced exactly. The fold MUST be commutative, associative, and idempotent for every strategy.

    Alternatives considered

    • Keep cell-LWW only; never wire the richer strategies

      Rejected: Leaves OrSet/TextMerge as dead library code and convergence correct only for LWW domains — contradicting ADR-027, which makes per-domain CRDT load-bearing.

    • Model deletion as an EntityAssert of a tombstone sentinel value

      Rejected: Overloads the value space (a real value could collide with the sentinel) and still gives LWW semantics, not add-wins. A typed Retract is unambiguous and composes with the fold.

    • Per-domain merge in a layer above the commit layer (post-process the root)

      Rejected: The state root must be the merge result; post-processing it would make the witnessed root and the merged state diverge — exactly the seam tear ADR-044 and bl-018 guard against. The merge must be the commit-layer reduce itself.

    +
    diff --git a/site/site/public/adr/adr-042/index.html b/site/site/public/adr/adr-042/index.html new file mode 100644 index 0000000..6777a74 --- /dev/null +++ b/site/site/public/adr/adr-042/index.html @@ -0,0 +1,67 @@ + + + +SyncBackend Sync/Async Reconciliation — Synchronous Trait, Backend-Owned Runtime for Network Transports · Ontoref ADR + + +
    +
    + Accepted + adr-042 · 2026-05-30 +

    SyncBackend Sync/Async Reconciliation — Synchronous Trait, Backend-Owned Runtime for Network Transports

    +
    + +

    Context

    ADR-027 defines `SyncBackend` as a SYNCHRONOUS trait (`announce`, +`fetch_op`, `announced_ops`, `subscribe_peer` return `Result` directly). +The default `FilesystemSync` is naturally synchronous, and bl-018's +`Substrate::ingest` drives it from synchronous code. Implementing the +first real transport, NatsSync over JetStream (bl-031), surfaced an +impedance mismatch: `platform-nats`/JetStream is async (tokio), so an +async client cannot satisfy a synchronous trait method without bridging, +and calling `block_on` from within a tokio worker thread panics. The +trait shape must be settled before NatsSync can be written.

    +

    A second, related point: the trait's verbs (`announce(op_id)` + +`fetch_op(op_id)` + `announced_ops`) are filesystem-shaped (announce an +id, fetch a body by id). A pull-consumer transport like JetStream more +naturally publishes and pulls whole op messages. The contract must stay +expressible by both without forcing one model onto the other.

    Decision

    ACCEPTED. `SyncBackend` STAYS synchronous — the synchronous trait is the +contract, and synchrony is preserved at the call site (`Substrate::ingest` +and the embedded/filesystem path stay runtime-free, honouring +protocol-not-runtime and offline-first).

    +

    Async network transports bridge INTERNALLY: a backend such as NatsSync +owns its own current-thread tokio runtime and `block_on`s its async +client inside each trait method. The contract is that synchronous +`SyncBackend` methods are invoked from a BLOCKING context — never from a +tokio worker — so the daemon drives a sync round via `spawn_blocking` (or +a dedicated sync thread). The backend's runtime is an implementation +detail invisible to the trait and to `ontoref-core`.

    +

    The verb mapping for pull-consumer transports is fixed as: `announce` +publishes the op id (and, for transports without a separate body store, +the canonical op bytes) to the project subject; `fetch_op` retrieves the +op bytes by id; `announced_ops` drains/pulls the durable consumer to list +ids seen. A backend MAY publish body-with-announcement in one message and +serve `fetch_op` from a local index of pulled messages — the trait does +not mandate a separate body channel.

    +

    Backend selection stays trigger-deferred and feature-gated: NatsSync is +compiled only under the daemon `nats` feature and is an OPTIONAL +accelerator that degrades to absent; FilesystemSync remains the +zero-dependency default.

    Constraints

    • Hard

      The `SyncBackend` trait MUST remain synchronous; async transports MUST bridge internally (backend-owned runtime) and MUST NOT require ontoref-core or FilesystemSync to depend on an async runtime. Sync rounds invoking a network backend MUST run on a blocking context, never a tokio worker.

    • Hard

      A network SyncBackend (e.g. NatsSync) MUST be feature-gated and degrade to absent when its service is unavailable; the zero-dependency FilesystemSync MUST remain the default. No tier may be forced to require a broker.

    Alternatives considered

    • Make SyncBackend async (async fn in trait / async-trait)

      Rejected: Forces an async runtime onto ontoref-core and every substrate consumer, including offline/embedded uses. Contradicts protocol-not-runtime and ADR-029 offline-first for the sake of one network backend.

    • Add a parallel async trait and a bridge layer

      Rejected: Doubles the contract surface and the maintenance burden before any second async backend exists. The backend-owned-runtime bridge achieves the same with no new public trait.

    • Spawn the async client on the daemon's shared runtime and block_on there

      Rejected: block_on inside a tokio worker thread panics. The backend must own a separate runtime (or run on spawn_blocking) — a shared-runtime block_on is unsound.

    +
    diff --git a/site/site/public/adr/adr-043/index.html b/site/site/public/adr/adr-043/index.html new file mode 100644 index 0000000..7b837fa --- /dev/null +++ b/site/site/public/adr/adr-043/index.html @@ -0,0 +1,80 @@ + + + +Positioning as a Four-Element Framework — Orthogonal Axes (Qué / Para-Quién / Cómo) Bound by a Proof Seam with Self-Applied Coherence Rules · Ontoref ADR + + +
    +
    + Accepted + adr-043 · 2026-06-02 +

    Positioning as a Four-Element Framework — Orthogonal Axes (Qué / Para-Quién / Cómo) Bound by a Proof Seam with Self-Applied Coherence Rules

    +
    + +

    Context

    ADR-035 established the positioning layer as a queryable protocol surface, but +its schema entangled the axes of positioning: `ValueProp` carried the value +difference inline (`unique_angle`), the target (`audience_ids`) and the backing +(`evidence_adrs`) all in one record, and `Campaign` was the only model of +outward action. There was no first-class home for "what is our differential +value", competitors lived only as prose strings inside `ValueProp.alternatives`, +difusión collapsed onto "campaign" (a website is difusión but not a campaign), +and there was no model of validation — the success cases where all of it +converges and becomes credible.

    +

    Working through the positioning of ontoref against Knowledge-Graph competitors +surfaced that positioning has three orthogonal concerns — the same orthogonality +ADR-009 already established for manifest self-interrogation (capabilities answer +"what / why / how" as three orthogonal axes): QUÉ (what differential value), +PARA QUIÉN (which audience), CÓMO (how we difundir). They are orthogonal much of +the time and cross only at named seams. A fourth element — VALIDACIÓN — is not a +fourth axis but the costura (seam) that binds the other three and keeps them +credible: a success case is the convergence of a differentiator, an audience and +a difusión mechanism, signed by a real outcome.

    +

    The load-bearing risk is formalization-vs-adoption: adding four typed kinds and +a coherence audit to the marketing surface could impose ceremony before value. +The framing that resolves it is ADR-029 (tier-orthogonal, opt-in) plus +all-defaulted fields: a project that writes zero differentiators pays zero +adoption cost, and existing positioning files validate unchanged.

    Decision

    Model the positioning layer as four elements. Three orthogonal axes, each a +first-class typed kind, bound by a Proof seam:

    +

    QUÉ — `Differentiator` (one file per area of differential value) + + `Competitor` (what they do / how ontoref differs). + PARA QUIÉN — `Audience` (gains `status`: Hypothesis until a Proof references it). + CÓMO — `DifusionMechanism` (kind: Web | DocsSite | McpServer | Repo | + Content | Talk | Event; persistent vs event). Difusión is the + category; `Campaign` is ONE time-boxed coordination within it. + VALIDACIÓN — `Proof`: the seam. `converges = { differentiator_ids, audience_ids, + mechanism_ids }` — references the other three; nothing references + Proof. A Proof is the triple convergence signed by a real outcome.

    +

    The crossings are explicit: `ValueProp` is the QUÉ×QUIÉN seam (gains +`differentiator_ids`); `Campaign` and `Proof` bind CÓMO and VALIDACIÓN.

    +

    The marketing surface is governed by the same mechanism it sells. Five coherence +rules run in `ore positioning audit` (local-first, daemon-independent):

    +

    1. AnchorDrift — a differentiator/proof evidence_adr must be Accepted. + 2. UnprovenValidatedAudience — an Audience marked 'Validated needs >=1 Proof. + 3. Live value-prop — must converge four elements (differentiator + + audience + a difundiendo mechanism + a proven diff). + 4. DanglingRef — a Proof's converges ids must all resolve. + 5. CoverageGap (warn) — a differentiator with no Proof is an explicit + reminder, never a silent gap.

    +

    ADR-drift severity is gated by value-prop status: a Draft claim citing a +Proposed/Missing ADR is work-in-progress (informational, non-blocking); only +Validated/Live claims must cite Accepted ADRs — mirroring the +`_live_requires_evidence_adrs` contract. Coverage warnings never fail CI.

    Constraints

    • Hard

      The positioning schema MUST keep QUÉ (Differentiator), PARA QUIÉN (Audience) and CÓMO (DifusionMechanism) as separate typed kinds. Cross-axis binding MUST happen only through the seam kinds (ValueProp for qué×quién; Campaign and Proof). Collapsing an axis back into another kind is forbidden.

    • Hard

      Proof MUST reference the other three axes (converges.differentiator_ids, audience_ids, mechanism_ids each non-empty) and MUST NOT be referenced by any other kind. The make_proof contract (`_proof_converges_all`) enforces the triple convergence.

    • Hard

      Every Differentiator MUST cite >=1 ontology node and >=1 evidence ADR, and name >=1 competitor category it beats (`vs`). The make_differentiator contracts (`_diff_requires_anchor`, `_diff_requires_vs`) enforce this.

    • Hard

      `ore positioning audit` MUST run the five coherence rules over the positioning layer and gate CI by value-prop status: hard ADR-drift on Validated/Live value-props and any coherence violation fail; Draft drift and coverage gaps are informational.

    • Soft

      A value-prop with status 'Live MUST bind >=1 differentiator (the QUÉ axis); the `_live_requires_differentiator` contract enforces the local half, and the audit enforces the rest (a difundiendo mechanism and a proven differentiator).

    Alternatives considered

    • Keep the entangled ValueProp model — value difference stays inline in unique_angle

      Rejected: Entangling qué (value), para-quién (audience) and backing in one record means any change to the differentiation story churns every claim, and competitors/differentiators are never queryable on their own. Orthogonal axes with a ValueProp seam keep each concern independently evolvable.

    • Competitors and validation as Markdown narratives, no typed kinds

      Rejected: Markdown is not queryable and cannot be drift-checked. ADR-035's whole thesis is marketing-as-typed-protocol-surface; competitors and proofs belong as typed NCL with cross-references, not as prose the audit cannot see.

    • Validación as a fourth orthogonal axis (a fourth folder co-equal to the three)

      Rejected: Validation is not parallel to qué/para-quién/cómo — it is where they converge and get signed. Modeling it as a fourth axis implies a false fourth pole; modeling it as the Proof seam (references the three, referenced by none) is faithful to witness-as-axis-seam (ADR-031).

    • Fail CI on any ADR-drift regardless of value-prop status

      Rejected: A Draft claim citing a Proposed ADR is legitimate work-in-progress; hard-failing it forces deleting true citations or fabricating ratification. Gating severity by status keeps the hard gate on Live/Validated claims where _live_requires_evidence_adrs already places it.

    Anti-patterns

    • Collapsing an Axis Back into Another Kind

      A contributor inlines the value difference back into ValueProp.unique_angle, or folds competitors into alternatives[] strings, instead of using the Differentiator/Competitor kinds. The axes re-entangle and stop being independently queryable.

    • A Proof That Does Not Converge Three Axes

      A success case is written as a testimonial that names an outcome but does not reference a differentiator, an audience and a mechanism. It is no longer the seam — it is unanchored marketing the audit cannot trace to the model.

    • Asserting a Validated Audience Without a Proof

      An Audience is marked status='Validated to look credible, but no Proof references it. The validation is asserted, not earned — the exact failure mode the framework exists to prevent.

    • Routing Marketing Through Untyped Prose to Skip the Audit

      A claim is published on the website or docs without a corresponding typed Live value-prop, so it escapes the coherence audit and can drift freely — re-introducing the prose-that-rots failure ADR-035 closed.

    +
    diff --git a/site/site/public/adr/adr-044/index.html b/site/site/public/adr/adr-044/index.html new file mode 100644 index 0000000..81abef5 --- /dev/null +++ b/site/site/public/adr/adr-044/index.html @@ -0,0 +1,67 @@ + + + +Actor Key Succession — Unforgeable Old→New Rotation Records · Ontoref ADR + + +
    +
    + Accepted + adr-044 · 2026-05-30 +

    Actor Key Succession — Unforgeable Old→New Rotation Records

    +
    + +

    Context

    ADR-023/024 make the witness the unit of provenance: every authoritative +mutation is signed by an actor's Ed25519 key, and external verifiers +check the signature against the actor's published public key +(`.ontoref/actors.ncl`). Until now actor keys were static — there was no +way to rotate a key without stranding every witness it had ever signed.

    +

    Two failure modes follow from static keys:

    +

    - A compromised or lost key has no recovery path. Abandoning it + abandons the verifiability of all prior witnesses signed under it. + - Routine hygiene (periodic re-keying) is impossible without breaking + the audit chain.

    +

    The seam at risk is `witness-as-axis-seam` (Axiom, invariant): the witness +binds the ontology axis (what IS — the signed state) to the reflection +axis (the act). A key change that orphans prior witnesses tears that seam +for every deposit made under the old key.

    Decision

    ACCEPTED. Key rotation is recorded as an unforgeable succession entry in +the oplog, modeled as `OpPayload::KeySuccession { actor_id, new_key, +old_key }`. The record MUST be signed by the OLD key: the enclosing +`OpBody.actor` equals `old_key`, and the operation signature verifies +under it. Only the holder of the current key can therefore authorize its +successor. The record is anchored in the oplog DAG (parents = current +heads, monotonic HLC) and is state-neutral — the commit, triple, and +substrate reducers assert nothing from it.

    +

    Verification chains trust backwards from the actor's current trusted key +(`actors.ncl`) through the succession records: a key is valid for an +actor if it is the current key or a verified predecessor of a valid key. +A record only extends the chain when its declared `old_key` equals its +signer AND its signature verifies — a forged record (signed by anyone but +the declared predecessor) is silently ignored. A witness signed by a +superseded key therefore still verifies: the chain proves that key was +once authoritative for the actor.

    +

    The trust-chain resolver (`ontoref_types::succession::valid_keys_for_actor`) +is a pure function over `&[Operation]` — no IO, no oplog handle — so any +verifier (daemon, CLI, remote peer) applies it offline. Emission (generate +new key, append the succession, republish the public key) is a tier-2 +substrate act, gated behind the `substrate` feature; tier-0/1 projects pay +no cost.

    Constraints

    • Hard

      A KeySuccession record MUST be signed by its declared old_key (OpBody.actor == old_key) and that signature MUST verify; chain resolution MUST reject any record where the signer is not the declared predecessor. Only the holder of the current key may authorize its successor.

    • Hard

      Verification MUST treat a witness signed by a superseded key as valid when a chain of verified succession records links that key to the actor's current key. Reversing this (invalidating prior witnesses on rotation) breaks the witness-as-axis-seam.

    Alternatives considered

    • Static keys (status quo)

      Rejected: No recovery path; a compromised key strands every witness it signed. Rejected because it leaves the witness-as-axis-seam brittle across the inevitable key lifecycle.

    • Sign the succession with the NEW key

      Rejected: Anyone can mint a new key and claim to be an actor's successor. Authorization must come from the party being superseded, so the record is signed by the old key.

    • A central key-authority service that issues/revokes actor keys

      Rejected: Reintroduces a trusted third party and a runtime dependency, contradicting ADR-001 minimal adoption and ADR-027 P2P-pure direction. The oplog-anchored, self-authorizing chain needs no authority.

    +
    diff --git a/site/site/public/adr/adr-045/index.html b/site/site/public/adr/adr-045/index.html new file mode 100644 index 0000000..e26e8b5 --- /dev/null +++ b/site/site/public/adr/adr-045/index.html @@ -0,0 +1,33 @@ + + + +Recursive Level Chains and Domain Co-Tenancy — Relative Levels and Multi-Chain Hosting · Ontoref ADR + + +
    +
    + Accepted + adr-045 · 2026-06-02 +

    Recursive Level Chains and Domain Co-Tenancy — Relative Levels and Multi-Chain Hosting

    +
    + +

    Context

    ADR-012 introduced a domain extension system; ADR-018 formalized a three-level hierarchy (base, domain, instance) with an absolute level.index in {1,2,3} and a single-valued level.parent declared in manifest.ncl. ADR-020 plus the qa entry ontoref-three-layer-model describe three CONTENT layers (Layer 1 self-management, Layer 2 integration surface, Layer 3 caller-side wiring) and flag (bl-009) the unresolved interaction with ADR-018's levels as a '3-layer x 3-level matrix, likely orthogonal but unresolved until the ADR drafts'. A third field case forces resolution. Rustelo builds a domain over a Torred domain over ontoref-base, and Rustelo's implementation mode produces website instances; those website instances are then hosted inside a Provisioning workspace, itself the terminal instance of Provisioning's own provisioning-domain chain. Two facts break ADR-018's absolute triad. (1) DEPTH > 3: the specialization chain base -> Torred -> Rustelo -> website-mode -> website-instance is depth 4-5, but level.index is capped at 3 with fixed semantic names. (2) DOMAIN CO-TENANCY: a single hosting repo (Provisioning) participates in two distinct chains from two distinct projects at once, and one chain's terminal instance re-hosts another chain's instance (instance-as-host). ADR-018's level.parent is single-valued and manifest.ncl declares exactly one level — neither expresses depth > 3, co-residency of chains, nor instance-as-host. The genuinely hard part — cross-project reference — is already designed: ADR-018 makes level.parent a name reference (not a path), ADR-028 makes the ontology addressable independently of the project filesystem (verify by witness, not clone), and ADR-030 provides catalog discovery across projects. What is missing is the generalization of the level axis itself and an explicit declaration for hosting.

    Decision

    Generalize ADR-018's absolute triad into a relative parent chain and add a co-tenancy declaration, both opt-in and backward-compatible, via four mechanisms. (1) RELATIVE LEVEL CHAIN — level identity is the parent chain, not an absolute index. A manifest declares level.parent (a name reference, exactly as ADR-018) and an optional level.role label; level.index becomes a DERIVED depth (walk parents to a base) that is no longer capped at 3 and no longer authoritative. The names base/domain/instance are reinterpreted as ROLES in a sliding window of three relative to an observation point, not global coordinates. Existing index 1/2/3 declarations remain valid as the depth<=3 case; deeper chains simply keep walking. The terminal node of one chain may itself be the parent-root of another (instance-as-host is a well-formed shape, not an error). (2) DOMAIN CO-TENANCY — a repo may declare manifest.ncl::hosts, an array of { chain (name reference to a foreign domain artifact), mount (where in this repo the hosted chain's Layer-3 wiring lives), digest (optional witness pin) }. The foreign chain is resolved by name through catalog discovery (ADR-030) and verified by witness (ADR-028), NEVER cloned. The host carries only Layer-3 wiring for the hosted chain (ADR-020), tagged layer-3-boundary. (3) MUTATION SOVEREIGNTY UNDER CO-TENANCY — a host's runtime MUST NOT mutate the authoritative state of any hosted chain. Hosting is placement plus wiring; each chain's authoritative state mutates only through its own declared operations with its own witness (ADR-024). Co-tenancy is co-residency of wiring, not transfer of state ownership. (4) OBSERVABLE CROSS-CHAIN RESOLUTION — ore mode resolve extends to report the full traversal PATH (every level visited, not a single hop) and the CHAIN SCOPE (which chain answered) when multiple chains are co-resident; ore describe state disambiguates FSM dimensions by chain scope. No cross-chain resolution is silent.

    Constraints

    • Hard

      Every manifest.ncl::hosts entry must name a foreign chain resolvable by catalog discovery (name, optional digest) — resolution is by witness, never by clone

    • Hard

      A host must not declare operations that mutate a hosted chain's authoritative state; hosted state mutates only through the hosted chain's own declared operations

    • Hard

      Every level.parent chain must terminate at a base (a node with no parent); an unresolvable or cyclic parent reference is an error

    • Soft

      Wiring under a hosts mount should be tagged layer-3-boundary; untagged hosted wiring is a Soft warning

    • Soft

      When level.index is declared explicitly it must equal the derived parent-walk depth; a mismatch is a Soft warning during the transition to derived indices

    Alternatives considered

    • Keep ADR-018's absolute index and cap depth at 3

      Rejected: The Rustelo chain is already depth 4-5. Capping forces artificial flattening that hides real specialization boundaries — the exact invisibility ADR-018 set out to eliminate. A depth cap turns a true parent/child relation into a lossy projection.

    • Allow depth > 3 but forbid co-tenancy (one chain per repo)

      Rejected: Provisioning hosting a website inside a workspace is an actual deployment shape, not a hypothetical. Forbidding co-residency pushes the hosting wiring into untyped, untagged territory, producing exactly the Layer-3-in-Layer-1 contamination ADR-020 warns against. The relationship exists whether or not the model can name it; refusing to name it loses the boundary check.

    • Let the host own the hosted chain's state (single runtime per repo)

      Rejected: Collapses ADR-024 internal-coherence-enforced. The hosted chain's validators and witness would be bypassed by the host runtime, making cross-domain mutation unverifiable and reintroducing the unwitnessed-mutation hazard the operations layer exists to close.

    • Model co-tenancy as ordinary Layer-3 with no manifest declaration

      Rejected: Layer-3 wiring does live in the caller, but the hosting RELATIONSHIP (which foreign chain, pinned to which digest) is then implicit. The boundary cannot be validated, and the witness pin (ADR-028) has nowhere to live. An explicit hosts array is the minimal addition that makes the relationship checkable.

    Anti-patterns

    • Host Owns Hosted State

      A hosting repo's runtime mutates the authoritative state of a chain it merely hosts, treating co-residency as ownership. The hosted chain's validators and witness are bypassed; cross-domain mutation becomes unverifiable.

    • Absolute Level Flattening

      Forcing a chain deeper than three into the absolute base/domain/instance triad by collapsing real specialization steps, so distinct boundaries become invisible.

    • Implicit Hosting

      A repo hosts a foreign chain's Layer-3 wiring without declaring manifest.ncl::hosts, so the hosting relationship, the foreign chain identity, and the witness pin are invisible and unverifiable.

    +
    diff --git a/site/site/public/adr/adr-046/index.html b/site/site/public/adr/adr-046/index.html new file mode 100644 index 0000000..805e90d --- /dev/null +++ b/site/site/public/adr/adr-046/index.html @@ -0,0 +1,33 @@ + + + +Substrate Views — Dynamic Context Provisioning by Composing Levels, Verified Under Partial Knowledge, Tuned From the Interaction Trace · Ontoref ADR + + +
    +
    + Accepted + adr-046 · 2026-06-02 +

    Substrate Views — Dynamic Context Provisioning by Composing Levels, Verified Under Partial Knowledge, Tuned From the Interaction Trace

    +
    + +

    Context

    ADR-032 consolidated the project layout under .ontoref/ and ADR-035 added the positioning layer, so a project's knowledge now spans many heterogeneous levels (ontology, adrs, reflection, positioning, catalog, assets, private strategy, diffusion). Holding all of it at once is not workable for a working session — 'todo junto no es gestionable'. Three prior facts shape the resolution. (1) Cross-project work has repeatedly failed in agent sessions: the failure mode was the agent NAVIGATING into a foreign repository, losing its thread and mutating where it should not. (2) The project chose persona/discipline as a first-class axis ORTHOGONAL to the trust actor of api-catalog.ncl — the work AREA a session operates in. (3) ADR-045 generalized levels to relative parent chains and added domain co-tenancy resolved BY WITNESS, never by clone (ADR-028), reusing host_entry_type (chain, mount, digest). What was missing is a single primitive that, given a discipline, provisions only the slice of levels that area needs — intra-project AND cross-project — presents it as one coherent working context, routes every mutation back to the authoritative home of the level it belongs to, and is consumed identically by CLI sessions, agent sessions, and the daemon interfaces (UI / MCP / API / GraphQL). The governing analogy is jj over git: a content-addressed truth store (the levels — ADR-023/025) with an operation-centric working layer above it (the view + engine) that records every operation in an op log (the interaction trace — ADR-037, and the oplog — ADR-036), composes and recomposes on demand, and colocates without forcing migration (ADR-029 tier coexistence). Two further requirements were stated explicitly. First, a mounted level must provide usable knowledge AND a means of verification that, in many cases, works WITHOUT complete knowledge of the level's content — the cross-project case cannot hold the whole foreign substance. Second, the engine must learn from the generated interaction — use the loop FROM THE START and adjust as it consumes real data, not prepare a seam for later.

    Decision

    Establish the substrate view (schema .ontoref/reflection/schemas/substrate-view.ncl) as the unit of dynamic context provisioning, via five mechanisms. (1) DISCIPLINE-SCOPED COMPOSITION — a view is f(discipline, context, actor): the discipline (the work AREA; area equals discipline) selects which levels mount, the actor filters what is visible and mutable. A view composes only the slice the area needs, never the whole project, and is decomposed on exit. (2) PERSISTENCE ROUTING — each mounted level declares persistence ('Inline / 'LiftedOut / 'Remote) and the engine routes every mutation back to that level's authoritative home. 'Inline composes from local files with no daemon (ADR-029 local fallback); 'LiftedOut and 'Remote resolve by witness through catalog discovery (ADR-028/030/045), NEVER cloned. Cross-project knowledge enters as a 'Remote level — the agent never leaves its repo. (3) VERIFICATION UNDER PARTIAL KNOWLEDGE — each level declares a verify means ('Local / 'Digest / 'Validator / 'Commitment). An out-of-repo level MUST use a partial-knowledge means: a content-addressed digest (ADR-045 source.digest), a declared validator answering consistency queries without exposing full content (ADR-026), or a cryptographic commitment proving a claim against committed-but-unrevealed content (ADR-023 G5 witness). Verification by holding the complete foreign substance is forbidden. (4) LEARNED TUNING FROM THE INTERACTION TRACE — a view's declared levels and actions are its AUTHORITY ENVELOPE. Elements marked 'Learned are tuned LIVE (selection, order, salience) by the engine as it consumes the interaction-trace stream named in learns_from (ADR-037), starting from the declared prior. The active recipe is the declared prior composed with a chain of witnessed adjustments — prior plus ops, exactly as a VCS working state is base plus operations. (5) INTERFACE PARITY — the same primitive backs CLI sessions, agent sessions, and the daemon interfaces: a UI panel or an MCP tool IS a mounted substrate view, declared by the surfaces field, not bespoke code. The learning loop is constitutive and used from the start; a view that declares no learns_from is its declared prior verbatim, so the adoption floor pays nothing.

    Constraints

    • Hard

      The engine composes only levels and authorizes only mutations the substrate view declared; 'Learned tunes selection, order and salience strictly within the declared envelope and never adds a level or a mutation route

    • Hard

      Every learned adjustment to a view's active recipe is recorded as a witnessed operation in the interaction trace and is reversible; no silent mutation of the recipe in effect

    • Hard

      The active recipe at any time equals the declared prior composed with the witnessed adjustment chain, and must be reconstructable and replayable from the declaration plus the interaction trace; declared salience is never overwritten by learned deltas

    • Soft

      With no interaction data or no daemon, a view mounts its declared prior verbatim; learning is the accelerated path, not a precondition for composition

    • Hard

      Every level with persistence 'LiftedOut or 'Remote must declare a witness source and a partial-knowledge verify means ('Digest, 'Validator or 'Commitment); it is never verified by holding complete content

    Alternatives considered

    • Prepare the seam for learning but defer the engine (D2 deferred)

      Rejected: The decision is to use the loop from the start and adjust in consumption of real data. Deferring freezes the reflection axis and discards the prior-to-posterior loop, collapsing ontology-vs-reflection toward static ontology — the very Yang freeze ondaod exists to prevent. The prior-as-declared-view framing makes 'from the start' safe, so there is no risk-based reason to defer.

    • Let the engine learn freely, composing levels beyond the declared envelope

      Rejected: Collapses ontology-vs-reflection toward reflection-dominates: the engine deciding authority bypasses ADR-024 internal-coherence-enforced, because a level or mutation route the view never declared would enter without a declared, witnessed path. C1 forbids it — authority is declared, only sintonia is learned.

    • Require human acceptance of every learned adjustment

      Rejected: Kills 'adjust in consumption' with prohibitive friction. Safety does not require pre-acceptance: the jj op-log property — every adjustment is a witnessed, reversible operation (C2) and the recipe is reconstructable from prior plus ops (C3) — gives auditability and undo without a human in every loop.

    • Verify remote levels by cloning the foreign project

      Rejected: Reintroduces the cross-project-in-sessions failure and does not scale across the ecosystem. ADR-028 verify-by-witness-not-clone exists precisely to avoid this; C5 makes partial-knowledge verification mandatory for out-of-repo levels.

    • A flat view recipe with no envelope/tuning distinction

      Rejected: Makes C1 inexpressible: if declaration and learning are indistinguishable, the authority boundary cannot be protected and the engine can silently widen it. The eligibility ('Always/'Learned) split is the minimal addition that makes the authority line a typed, checkable fact.

    Anti-patterns

    • Engine expands authority

      The engine composes a level or authorizes a mutation the substrate view never declared, on the grounds that the interaction data suggested it. Learning is allowed to widen what is authoritative, not merely to tune within a declared envelope.

    • Silent recipe drift

      The engine mutates the active recipe without recording a witnessed, reversible operation, so the recipe in effect cannot be reconstructed or undone and diverges from the declared prior without a trace.

    • Engine decides truth

      The learned recipe is treated as authoritative ontology and the declared prior is discarded as stale, inverting the prior-to-posterior relationship so that reflection becomes the source of truth.

    • Clone to verify

      A remote or lifted-out level is verified by materializing its complete content rather than by digest, validator or commitment — reintroducing the navigational cross-project failure and forfeiting partial-knowledge verification.

    • Learning as a gate

      Composition is blocked until enough interaction data exists or the daemon is running; cold start fails instead of falling back to the declared prior, turning the engine into a hard dependency at the adoption floor.

    +
    diff --git a/site/site/public/adr/adr-047/index.html b/site/site/public/adr/adr-047/index.html new file mode 100644 index 0000000..f38d6e0 --- /dev/null +++ b/site/site/public/adr/adr-047/index.html @@ -0,0 +1,33 @@ + + + +Cryptographic Agility — Scheme-Tagged Witnesses with Ed25519 Default and ML-DSA-65 FIPS Plug-In · Ontoref ADR + + +
    +
    + Accepted + adr-047 · 2026-06-02 +

    Cryptographic Agility — Scheme-Tagged Witnesses with Ed25519 Default and ML-DSA-65 FIPS Plug-In

    +
    + +

    Context

    The protocol signs witnesses (ADR-024 acting-binding, ADR-023 G5 commitment) and, since ADR-046, substrate-view learning adjustments, with Ed25519. Ed25519 is a classical scheme whose security rests on the elliptic-curve discrete-log problem, which Shor's algorithm breaks on a cryptographically-relevant quantum computer. Unlike encryption, signatures have no harvest-now-decrypt-later exposure — but they have a worse one for an append-only record: the day Ed25519 falls, every historical witness in the oplog (ADR-036) becomes FORGEABLE, collapsing the non-repudiation of the entire record retroactively. The witness is precisely the project's long-lived integrity substrate, so it is exactly where algorithm longevity matters. NIST standardised post-quantum signatures in 2024 — FIPS 204 (ML-DSA, derived from CRYSTALS-Dilithium), FIPS 205 (SLH-DSA), FIPS 206 (FN-DSA). Some consumer deployments will additionally face a FIPS mandate independent of the quantum timeline. Hardcoding Ed25519 into the witness format would force a breaking, record-invalidating migration the moment either pressure arrives. ADR-044 already gives key succession (rotating an actor's KEY); what is missing is agility in the ALGORITHM itself. ML-DSA-65 (NIST level 3, ~AES-192) is the natural PQC target, but its artefacts are ~50x larger (public key 1952 B, signature 3309 B vs Ed25519 32/64) and signing is materially slower, so it cannot be a blanket replacement — it must be a plug-in for the deployments that need it.

    Decision

    Make the signature scheme a first-class, tagged dimension of every witness, via five mechanisms. (1) SCHEME TAG — every signed witness and every signed adjustment carries a `scheme` field; an unsigned digest commitment is the tier-0/1 fallback (ADR-029), a signed witness names the algorithm that produced it. (2) DISPATCH, FAIL CLOSED — verification dispatches on the declared scheme. A recognised-but-unbuilt scheme and an unknown scheme both FAIL (distinct non-zero exits); verification never silently accepts a signature whose scheme it cannot evaluate, and never coerces an unknown tag to Ed25519. (3) ED25519 DEFAULT, PQC OPT-IN — Ed25519 is the default for everyone (fast, 32/64-byte artefacts); a PQC scheme is selected per deployment or per actor, never mandated globally. (4) VARIABLE-LENGTH ENCODING — signatures and public keys are stored as variable-length hex, so a 3309-byte ML-DSA signature needs no format change; the witness record must not assume fixed artefact sizes. (5) ML-DSA-65 AS THE FIPS PLUG-IN — `ml-dsa-65` is the recognised PQC scheme slot, built behind a `pqc` Cargo feature backed by a CMVP-validated module (aws-lc-rs in FIPS mode), not an unvalidated pure-Rust implementation; a deployment may claim FIPS only when bound to the validated module. The seam ships now (scheme tag + dispatch); the ML-DSA backend is the deferred plug-in.

    Constraints

    • Hard

      Every signed witness and signed adjustment carries a `scheme` tag identifying the algorithm that produced the signature

    • Hard

      Verification dispatches on the declared scheme and fails (non-zero exit) on a recognised-but-unbuilt or unknown scheme; it never silently accepts or downgrades to Ed25519

    • Soft

      Ed25519 is the default scheme; a PQC scheme is selected per deployment or actor and is never mandated globally

    • Hard

      Signatures and public keys are stored as variable-length hex; the witness record must not assume fixed artefact sizes

    • Soft

      A deployment may claim FIPS only when the PQC scheme is bound to a CMVP-validated crypto module, not an unvalidated pure-Rust implementation

    Alternatives considered

    • Keep Ed25519 hardcoded with no scheme tag

      Rejected: Forces a breaking, record-invalidating migration the moment quantum or FIPS pressure arrives, and offers no way to keep historical witnesses verifiable across the transition. The witness format would have to change under the whole oplog at once.

    • Adopt ML-DSA globally now, replacing Ed25519

      Rejected: 50x larger artefacts, materially slower signing, and a validated-module dependency imposed on every adopter — including those with no quantum or FIPS requirement. Collapses the formalization-vs-adoption Spiral toward mandatory heavyweight crypto.

    • Separate witness formats per scheme

      Rejected: N formats means N verify paths and no uniform record. A single format with a scheme tag and variable-length artefacts expresses the same agility with one code path and one stored shape.

    • Treat an unknown or absent scheme as Ed25519 (lenient verify)

      Rejected: A forgery surface: a signature under a scheme the verifier silently downgrades would be accepted. Verification must fail closed on anything it cannot evaluate.

    Anti-patterns

    • Hardcoded signature scheme

      Signing and verifying with a compile-time-fixed algorithm and no scheme tag on the witness, so migrating the algorithm requires rewriting every historical record.

    • Silent scheme fallback

      A verifier that accepts a signature whose scheme it does not recognise, or coerces an unknown/missing tag to Ed25519, creating a downgrade forgery surface.

    • PQC mandated globally

      Forcing a heavyweight PQC scheme on every deployment regardless of quantum or FIPS pressure, imposing 50x artefacts and slower signing where they buy nothing.

    • Unvalidated FIPS claim

      Claiming FIPS compliance while bound to an unvalidated implementation of ML-DSA — satisfying the algorithm name but not the mandate.

    +
    diff --git a/site/site/public/adr/adr-048/index.html b/site/site/public/adr/adr-048/index.html new file mode 100644 index 0000000..9dcbfd3 --- /dev/null +++ b/site/site/public/adr/adr-048/index.html @@ -0,0 +1,33 @@ + + + +Constellation Layout — Spine at Parent, Code as Sub-Repo · Ontoref ADR + + +
    +
    + Accepted + adr-048 · 2026-06-02 +

    Constellation Layout — Spine at Parent, Code as Sub-Repo

    +
    + +

    Context

    As ontoref grew it accumulated a second git-tracked surface beyond the Rust implementation: brand assets, web site source, outreach presentations, private strategy notes, and the protocol spine itself (.ontoref/). All lived inside one flat git repo. The problems: (1) publication boundaries — code has one remote/visibility, the web site another, private strategy a third; forcing all three through one repo required either over-exposing private content or fragmenting git history with submodules. (2) spine ownership — with .ontoref/ inside code/, every change to the protocol spine became a Rust-repo commit, coupling protocol evolution to the implementation release cadence. (3) cross-area navigation — an agent scoped to code/ could not reach outreach/ or vault/ without leaving the repo; paths like '../outreach' were outside git scope. Three layout options were evaluated: (A) monorepo with subdirectories (code/, outreach/, vault/ all under one git root), (B) constellation (non-versioned parent holding independent git sub-repos), (C) external paths (each sub-repo anywhere, cross-references via absolute paths). Option B was chosen because it provides clean per-area publication boundaries — each folder is its own repo with its own remote and visibility — while keeping the spine at a level that governs all sub-repos without fragmenting it across satellites.

    Decision

    Adopt a constellation layout: a non-versioned parent directory (no .git/) holds three to five independent git sub-repos — code/, outreach/, vault/, and an assets/ directory that is not itself a git repo. The .ontoref/ protocol spine lives at the parent level, not inside code/. The bash wrapper (code/ontoref) derives SPINE_DIR from SCRIPT_DIR/../.ontoref/; env.nu guards the ONTOREF_ROOT assignment with an is-empty check so the bash wrapper's value is not overridden at module load time; nickel_import_paths in config.ncl uses ../.ontoref/... (relative to ONTOREF_ROOT=code/); NCL files in .ontoref/ that import the data-dir companion (code/ontology/) use ../../code/ontology/... relative to the spine location. Cross-project Rust path dependencies that previously resolved via ../../../ now resolve via ../../../../ (one extra hop through code/). install.nu derives repo_root from $env.CURRENT_FILE rather than $env.PWD so it is robust to invocation from an arbitrary CWD.

    Constraints

    • Hard

      The .ontoref/ spine lives at the constellation parent, not inside code/; code/ontoref derives SPINE_DIR as SCRIPT_DIR/../.ontoref/

    • Hard

      env.nu assigns ONTOREF_ROOT only when it is not already set, preserving the bash wrapper's value

    • Hard

      nickel_import_paths entries for .ontoref/ content use ../.ontoref/... (relative to ONTOREF_ROOT=code/), not .ontoref/...

    • Hard

      NCL files in .ontoref/ that import the data-dir companion use ../../code/ontology/... not ../../ontology/...

    Alternatives considered

    • Monorepo subdirectories (option A): one git repo, code/ outreach/ vault/ as subdirs

      Rejected: Cannot provide independent git remotes with different visibility per area without submodules or sparse-checkout. All areas share the same git history and commit graph, which pollutes code/ history with brand/outreach changes and prevents per-area release tagging.

    • External paths (option C): each sub-repo anywhere on the filesystem, cross-references via absolute paths

      Rejected: Absolute paths are machine-specific and cannot be committed to the repo. Cross-repo navigation from an agent scoped to one repo requires the user to set environment variables per machine. The spine would have to live inside one of the repos (satellite problem: others reference it via absolute path and drift when it moves).

    • Keep spine inside code/, model outreach/vault as satellite repos referencing it

      Rejected: Forces outreach/ and vault/ to carry a reference to code/'s absolute path for spine resolution. Moving code/ to a new machine or renaming it breaks the satellites. The spine's authority extends beyond code/; its location should reflect that.

    Anti-patterns

    • Spine as satellite

      Keeping .ontoref/ inside one sub-repo (code/) and referencing it from sibling repos (outreach/, vault/) via absolute or relative cross-repo paths. The spine's authority extends to all sub-repos; scoping it to one creates a satellite dependency where moving or renaming that repo breaks all siblings.

    • Unconstrained env.nu ONTOREF_ROOT override

      env.nu unconditionally sets ONTOREF_ROOT from the file-derived path at module load time, silently overriding the bash wrapper's correctly computed value. In constellation layout this produces a wrong ONTOREF_ROOT (.ontoref/ instead of code/) that causes sync, install, and asset commands to target the wrong directory.

    • Three-hop cross-project Cargo path dependencies

      Using ../../../<peer> in Cargo.toml path deps when the crate is inside code/crates/<crate>/. With constellation, three hops reach the constellation parent (ontoref/), not the development root (Development/); peer projects are siblings of ontoref/, so four hops are required.

    +
    diff --git a/site/site/public/adr/adr-049/index.html b/site/site/public/adr/adr-049/index.html new file mode 100644 index 0000000..fa72302 --- /dev/null +++ b/site/site/public/adr/adr-049/index.html @@ -0,0 +1,33 @@ + + + +Criteria as a Substrate-View Describe Level — Normative and Discovered as Typed Provenance, Promotion Gated Not Automatic · Ontoref ADR + + +
    +
    + Accepted + adr-049 · 2026-06-08 +

    Criteria as a Substrate-View Describe Level — Normative and Discovered as Typed Provenance, Promotion Gated Not Automatic

    +
    + +

    Context

    The constellation-memory work introduces criteria: the conditions a project holds itself to — release gates, quality bars, coherence rules. They arrive from two directions. Some are NORMATIVE: declared requirements an author states up front ('every Accepted ADR cites a Proof'). Others are DISCOVERED: they emerge from harvested insights (the coder harvest, plan component A) and the interaction trace (ADR-037) as patterns that turned out to matter in practice. ADR-046 established the substrate view as the unit of dynamic context provisioning — a discipline mounts only the slice of levels it needs, and a level is consumed identically by CLI, agent, and daemon interfaces via the surfaces field, with eligibility 'Always or 'Learned. ADR-018 fixed level-hierarchy and describe-projection mechanics. What is missing is where criteria sit in that structure. Two errors are available. If criteria are a flat undifferentiated list, a pattern someone NOTICED (discovered, reflection axis — what BECAME salient) is indistinguishable from a requirement someone DECLARED (normative, ontology axis — what IS required), and the first silently acquires the authority of the second. If, conversely, discovered criteria can auto-promote to normative once they recur, the reflection axis starts minting requirements without a declared, witnessed act — the engine deciding what is authoritative. The project's core identity tension, ontology-vs-reflection, is engaged directly, and formalization-vs-adoption is engaged because adding a criteria level is more schema that a minimal adopter must not be forced to carry.

    Decision

    Establish criteria as a first-class describe level mounted through the substrate-view engine (ADR-046), distinguished by a typed provenance taxonomy and a gated promotion path. Four mechanisms. (1) TYPED PROVENANCE TAXONOMY — every criterion carries kind : [| 'Normative, 'Discovered |] as a typed tag, not free text. 'Normative criteria are declared requirements (ontology axis); 'Discovered criteria are harvested from insights/interaction (reflection axis) and MUST record provenance: the source insight or interaction id they were graduated from. The tag is the legibility seam: a reader always knows whether a criterion is asserted or observed. (2) CRITERIA AS A 'Describe LEVEL — register criteria in levels.ncl with kind = 'Describe and eligibility = 'Learned, added to the relevant disciplines[].levels (coding, positioning, difusion); `describe criteria --discipline <D>` projects the discipline's slice, mirroring the existing constraints projection (ADR-018). (3) PROMOTION IS GATED, NEVER AUTOMATIC — a discovered criterion may be PROPOSED for promotion to normative, but promotion is gated behind novelty-check --threshold plus human approval (the backlog propose-status/approve path, ADR-046 witnessed-reversible); recurrence alone never promotes. The reverse — a normative criterion being demoted because reality drifted — is likewise a proposal, never silent. (4) OPT-IN ABSENCE — eligibility 'Learned and an opt-in migration mean a project that declares no criteria mounts no criteria level and pays nothing; the level is absent, not empty-and-mandatory.

    Constraints

    • Hard

      Every criterion carries kind : [| 'Normative, 'Discovered |] as a typed tag; a 'Discovered criterion additionally records the source insight or interaction id it was graduated from

    • Hard

      A 'Discovered criterion becomes 'Normative only through novelty-check plus human approval via propose-status/approve; recurrence alone never promotes, and demotion of a 'Normative criterion is likewise a proposal, never silent

    • Soft

      The criteria level is registered with kind 'Describe and eligibility 'Learned; a project that declares no criteria mounts no criteria level and carries no mandatory criteria schema

    • Hard

      criteria are projected through `describe criteria` and consumed identically across CLI/agent/UI/MCP/GraphQL via the surfaces field, with no bespoke per-surface code

    Alternatives considered

    • A flat untyped criteria list

      Rejected: Collapses ontology-vs-reflection: a discovered pattern becomes indistinguishable from a declared requirement and silently acquires its authority. The typed normative/discovered tag is the minimal addition that keeps both poles legible.

    • Auto-promote discovered criteria to normative once they recur N times

      Rejected: Lets reflection mint requirements without a declared, witnessed act — reflection-dominates, the collapse ADR-046 C1 forbids. Recurrence is evidence; only a gated, approved act confers normative authority.

    • A standalone criteria store outside the substrate-view engine

      Rejected: Reinvents discipline-scoped composition, persistence routing, partial-knowledge verification, and interface parity that ADR-046 already provides, and would need its own cross-project story. Criteria are a level; mounting them through the view engine inherits all of it.

    • A new typed criterion_kind on qa_entry_type instead of tags, requiring a schema-field migration immediately

      Rejected: Premature: tagging normative/discovered on the existing qa entry type needs no required-field migration and keeps the adoption floor flat. A typed criterion_kind is a later move only if a Spiral-poled criterion field is needed (bl-009 graduation), and that is itself an ondaod-gated decision.

    • Make criteria mandatory for all adopters to maximize ecosystem visibility

      Rejected: Collapses formalization-vs-adoption toward mandatory gates, violating the core.ncl node invariant 'schemas are optional layers, not mandatory gates'. eligibility 'Learned plus opt-in migration keep absence free.

    Anti-patterns

    • Discovered criterion wears normative authority

      A criterion harvested from insights or interaction is recorded without a provenance tag and is read as a declared requirement, silently acquiring authority it was never granted.

    • Auto-promotion by recurrence

      A discovered criterion is promoted to normative automatically once it recurs enough times, letting reflection mint authoritative requirements with no declared, witnessed act.

    • Criteria mandatory at the adoption floor

      The criteria level is made mandatory or empty-but-required for every adopter, putting new schema at the adoption floor instead of the top of the curve.

    +
    diff --git a/site/site/public/adr/adr-050/index.html b/site/site/public/adr/adr-050/index.html new file mode 100644 index 0000000..ed977a3 --- /dev/null +++ b/site/site/public/adr/adr-050/index.html @@ -0,0 +1,33 @@ + + + +Witnessed Validators for Hard Criteria — Structural Decidability and Bounded Slices, Never Truthfulness · Ontoref ADR + + +
    +
    + Accepted + adr-050 · 2026-06-08 +

    Witnessed Validators for Hard Criteria — Structural Decidability and Bounded Slices, Never Truthfulness

    +
    + +

    Context

    The constellation-memory work (plan 2026-06-08-constellation-memory-chronicle-criteria) introduces criteria — release/quality/coherence conditions a project holds itself to — harvested from insights and the interaction trace (ADR-037) or declared as requirements. ADR-026 already established validators as first-class, three-plane, SLA-bound, with a pluggable commitment backend, and ADR-024 fixed the agent-action boundary: authoritative state mutates only through declared operations carrying a witness. What is unresolved is the boundary question that decides whether criteria-backed validation stays coherent with the protocol's identity: WHICH criteria may become executable validators, and what they are allowed to read. Two failure modes are in scope. First, a criterion that asserts TRUTHFULNESS ('the positioning is honest', 'the architecture is sound') is not structurally decidable — turning it into a validator makes the reflection axis (judgment, an act) masquerade as the ontology axis (an invariant, what IS), and a passing/failing verdict on such a claim is the engine deciding truth. Second, a validator that reads unbounded state (the whole graph, every level) is an adoption cost: it is slow, it couples validation to the daemon, and it breaks the ADR-029 local-fallback guarantee that every read has a no-daemon Nushell path. The witness seam (ADR-031/023) names the exact point where an act that mutates substance is bound to a commitment; a validator over a criterion must live on the ontology side of that seam (verify structure) while being invoked by a reflection-side act (run + witness).

    Decision

    A criterion is eligible to become an executable validator ONLY if it reduces to a bounded, structurally-decidable slice query; truthfulness criteria are rejected at the eligibility gate and remain QA entries (normative/discovered, ADR-049), never validators. Establish this via four mechanisms. (1) DECIDABILITY ELIGIBILITY GATE — a ValidatorDecl is well-formed only if its predicate is a pure function of a declared, bounded slice plus the state_root commitment; a criterion whose satisfaction depends on a judgment not reducible to structure (honesty, soundness, quality-in-the-abstract) cannot declare a ValidatorDecl and is recorded as a discovered/normative criterion only. (2) BOUNDED SLICE — every ValidatorDecl declares a slice_query naming exactly the nodes/edges/fields it reads; the Rust #[onto_validator] predicate reads ONLY that slice plus state_root and is forbidden from widening its read at runtime. The slice is the validator's read envelope, the structural analogue of ADR-046's authority envelope. (3) STRUCTURE-NOT-TRUTHFULNESS SEVERITY RULE — a validator over a criterion that core.ncl does NOT name as a Spiral tension may carry severity 'Hard (structure is decidable, so pass/fail is meaningful); a validator whose criterion touches a 'Spiral-poled question MUST be 'Soft and report direction of motion, never a 'Hard biconditional — the ondaod prohibition on hard-biconditionals on Spiral questions, applied to validators. (4) NCL-RUST COHERENCE WITNESS — the ValidatorDecl (NCL, the declared structural claim) and the #[onto_validator] predicate (Rust, the executable check) must agree; a coherence check (runtime O6) fails the inventory if a declared validator has no registered predicate or a registered predicate reads beyond its declared slice, and each validator run emits a witness binding the verdict to the state_root it was computed against (ADR-024).

    Constraints

    • Hard

      A criterion may declare a ValidatorDecl ONLY if its predicate is a pure function of a declared bounded slice plus state_root; criteria asserting truthfulness (not reducible to structure) are rejected and remain QA entries, never validators

    • Hard

      Every ValidatorDecl declares a slice_query naming the nodes/edges/fields it reads; the #[onto_validator] predicate reads only that slice plus state_root and never widens its read at runtime

    • Hard

      A validator whose underlying criterion touches a question core.ncl names as a 'Spiral tension must be severity 'Soft and report direction of motion; 'Hard biconditional pass/fail is forbidden for Spiral-touching criteria

    • Hard

      The inventory fails if a declared ValidatorDecl has no registered #[onto_validator] predicate, or a registered predicate reads beyond its declared slice; every validator run emits a witness binding the verdict to the state_root it was computed against

    Alternatives considered

    • Allow any criterion to become a validator, let authors choose severity freely

      Rejected: Collapses ontology-vs-reflection toward reflection-dominates: a truthfulness criterion with a Hard pass/fail makes the engine adjudicate truth, and a verdict on an undecidable claim circulates as if it were an invariant. The decidability gate exists precisely to keep validators on the ontology axis.

    • Let validators read whole state for simplicity, optimize later

      Rejected: Breaks ADR-029 local-fallback and Protocol-not-Runtime: an unbounded validator couples validation to the daemon and to full-graph materialization, putting infrastructure at the adoption floor. Bounded slices are the same authority-envelope discipline ADR-046 established for reads.

    • Use a check_hint string per criterion (the deprecated constraint pattern)

      Rejected: check_hint is untyped and unexecutable — it cannot enforce the slice bound, cannot be coherence-checked against a Rust predicate, and cannot emit a witness. The typed ValidatorDecl is what makes the structural claim and its execution bindable at the witness seam.

    • Make every criterion-validator Soft to be safe

      Rejected: Over-collapses toward never-blocking: a structurally-decidable criterion (no over-read, NCL↔Rust coherent, bounded) is genuinely Hard-checkable, and forcing it Soft discards a real guarantee. The severity rule draws the line by whether the criterion touches a Spiral question, not by blanket caution.

    • Adjudicate truthfulness via an LLM-judge validator

      Rejected: Maximally collapses ontology-vs-reflection: a model verdict on honesty/soundness is pure reflection issuing an ontology-grade pass/fail, unreproducible and unwitnessable against a state_root. Truthfulness criteria stay human-facing QA entries; no mechanical adjudication.

    Anti-patterns

    • Validator adjudicates truth

      A criterion asserting truthfulness (honesty, soundness, quality-in-the-abstract) is promoted to an executable validator that issues a pass/fail verdict, making reflection's judgment circulate as an ontology-grade invariant.

    • Unbounded validator read

      A validator predicate reads the whole graph or arbitrary levels rather than a declared bounded slice, coupling validation to full-graph materialization and the daemon and breaking local fallback.

    • Hard biconditional on a Spiral criterion

      A validator over a criterion that touches a named Spiral tension is declared 'Hard with an A ⟺ B pass/fail, collapsing the tension by fiat instead of reporting direction of motion.

    • Declared but unwitnessed verdict

      A ValidatorDecl exists with no registered predicate, or a predicate over-reads its slice, or a run produces a verdict not bound to a state_root — a verdict that cannot be reproduced or audited.

    +
    diff --git a/site/site/public/adr/adr-051/index.html b/site/site/public/adr/adr-051/index.html new file mode 100644 index 0000000..b8ee71e --- /dev/null +++ b/site/site/public/adr/adr-051/index.html @@ -0,0 +1,33 @@ + + + +Chronicle-to-Proof-Candidate Seam — History Proposes Positioning Proofs, the Audit Verifies, Acceptance Stays Human · Ontoref ADR + + +
    +
    + Accepted + adr-051 · 2026-06-08 +

    Chronicle-to-Proof-Candidate Seam — History Proposes Positioning Proofs, the Audit Verifies, Acceptance Stays Human

    +
    + +

    Context

    The constellation-memory work folds a chronicle (plan component B): companion-NCL milestones, harvested insights, the interaction trace (ADR-037), ADRs-by-date, and migrations-by-number woven into a retrospective timeline of what the project has actually DONE. Independently, ADR-043 established positioning as a four-element framework — orthogonal axes (Qué / Para-Quién / Cómo) bound by a Proof seam — and ADR-035 made positioning a queryable protocol surface; positioning.nu already runs five coherence rules, including that a differentiator or value-prop marked proven must converge on a Proof whose evidence_adr is Accepted. There is an obvious and dangerous adjacency: the chronicle KNOWS what shipped (accepted ADRs, completed milestones), and positioning NEEDS proofs that what it claims is real. The temptation is to let the chronicle auto-populate positioning Proofs — every accepted ADR becomes a proof of the value-prop it touches. That collapses ontology-vs-reflection: the chronicle is reflection (what BECAME, a record of history) and a positioning Proof is an ontology-side claim about what the project IS and delivers; auto-asserting proofs from history makes reflection decide the truth of outward claims. The reality-collapses-intent hazard is also present in mirror form — a milestone that shipped is evidence FOR a proof, but a milestone with no positioning home is not automatically a defect to be erased; it may simply be internal work the positioning layer never intended to claim. What is missing is a seam that lets history INFORM positioning without history ASSERTING positioning.

    Decision

    Establish a one-directional propose-only seam from the chronicle to the positioning Proof layer, verified by the existing positioning audit and accepted only by a human. Three mechanisms. (1) PROOF CANDIDATES, NOT PROOFS — `coder chronicle --as-proof-candidates` emits accepted ADRs and completed milestones as CANDIDATE proofs wired against the ADR-043 four-element structure (which differentiator/value-prop each could support); a candidate is a proposal, never an accepted Proof, and is materialized only through the positioning layer's own acceptance path, never written directly by the chronicle. (2) THE AUDIT VERIFIES, THE CHRONICLE DOES NOT — a proof candidate is checked by positioning audit's existing five rules (ADR-043): a candidate that would create a DanglingRef, an UnprovenValidatedAudience, or an AnchorDrift is surfaced as such and cannot be accepted until the inconsistency is resolved. The chronicle proposes; the audit is the verifier; structural verification is positioning's job, not the chronicle's. (3) ORPHAN MILESTONES WARN, NEVER ERASE — a `positioning roadmap` (sibling to backlog roadmap) lays mechanisms against dated milestones, and an orphan-milestone rule in positioning audit WARNS (warn-only severity) when a shipped milestone has no positioning home — it never deletes the milestone nor auto-creates a claim, because internal work the positioning layer never intended to claim is a legitimate half-state, not a defect. G reads outreach/.coder only and writes nothing into outreach (no spine there).

    Constraints

    • Hard

      The chronicle emits proof CANDIDATES only; a candidate is never written as an accepted Proof and is materialized solely through the positioning layer's own human acceptance path

    • Hard

      Proof candidates are verified by positioning audit's existing five coherence rules (ADR-043); the chronicle does not run its own proof-coherence checks

    • Soft

      A shipped milestone with no positioning home is surfaced as a warning by positioning audit; it is never deleted and never auto-claimed

    • Hard

      The chronicle-to-positioning seam is one-directional and confined to the spine; G reads outreach/.coder and writes nothing into outreach

    Alternatives considered

    • Auto-create positioning Proofs from every accepted ADR / completed milestone

      Rejected: Collapses ontology-vs-reflection toward reflection-dominates: history would assert the truth of outward claims with no human acceptance. Proofs are ontology-side claims; only a declared act on the positioning side confers proof status.

    • Let the chronicle run its own proof-coherence checks

      Rejected: Forks verification away from positioning audit (ADR-043), risking divergent rules and two sources of positioning truth. The audit already encodes the five coherence rules; candidates must be verified there.

    • Treat orphan milestones as defects and auto-remove or auto-claim them

      Rejected: The reality-collapses-intent violation: a shipped milestone with no positioning home is a legitimate half-state (internal work), not a defect. Warn-only describes the gap and leaves the judgement to a human.

    • Write proof candidates directly into outreach/ where the difusion site lives

      Rejected: outreach has no spine and is a separate repo; writing into it reintroduces the cross-project mutation hazard ADR-046 resolves. The seam reads outreach as evidence and writes only within the spine.

    Anti-patterns

    • History asserts a proof

      An accepted ADR or completed milestone is auto-written as an accepted positioning Proof, so the chronicle (reflection) decides the truth of an outward claim (ontology) with no human acceptance.

    • Chronicle self-verifies claims

      The chronicle runs its own proof-coherence logic instead of routing candidates through positioning audit, creating a second, divergent source of positioning truth.

    • Orphan milestone erased or force-claimed

      A shipped milestone with no positioning home is treated as a defect and deleted or auto-claimed, erasing legitimate internal-work intent because it lacks an outward claim.

    • Write into a spineless repo

      The seam writes proof candidates or claims into outreach/, a separate repo with no spine, reintroducing the cross-project mutation hazard.

    +
    diff --git a/site/site/public/adr/adr-052/index.html b/site/site/public/adr/adr-052/index.html new file mode 100644 index 0000000..ec81727 --- /dev/null +++ b/site/site/public/adr/adr-052/index.html @@ -0,0 +1,33 @@ + + + +Memory Feedback Loop — On Mode Completion the Loop Proposes State, Backlog and Proof Deltas, Never Applies Them · Ontoref ADR + + +
    +
    + Accepted + adr-052 · 2026-06-08 +

    Memory Feedback Loop — On Mode Completion the Loop Proposes State, Backlog and Proof Deltas, Never Applies Them

    +
    + +

    Context

    The constellation-memory work closes a loop: harvest insights (component A), fold them with the interaction trace into a chronicle (B), graduate them into criteria (C/ADR-049), and surface proof candidates (G/ADR-051). The question this ADR answers is what happens at the END of a working session — at `mode complete` of the coder-workflow (ADR-011 mode guards/convergence). The accumulated reflection (chronicle, criteria, trace) now contains signals that bear on authoritative ONTOLOGY state: an FSM dimension in state.ncl may have met its transition condition; a backlog item may be completable; a proof candidate may be citable. The reflexive temptation — and the thing that would make the loop feel 'intelligent' — is to have the loop APPLY these: advance the FSM dimension, close the backlog item, attach the proof. That is the precise collapse the project's identity tension forbids. The whole memory loop is reflection (what BECAME — memory, drift); state.ncl, the backlog status, and accepted proofs are ontology (what IS — authoritative state). ADR-024 fixed that authoritative state mutates ONLY through declared operations carrying a witness; ADR-046 fixed authority as declared and learning as tuning within a declared envelope (C1) recorded as witnessed, reversible operations (C2). backlog.nu already ships propose-status and approve — a propose/approve seam built for exactly this. What is missing is the rule binding the loop to that seam: the loop is allowed to NOTICE and PROPOSE, and forbidden to APPLY.

    Decision

    At `mode complete` the memory feedback loop emits PROPOSED deltas and never applies them; authoritative state changes only through the existing witnessed approve path. Four mechanisms. (1) PROPOSE-ONLY HOOK — a `mode complete` proposal hook in run.nu surfaces candidate deltas of three kinds — FSM-transition proposals against state.ncl dimensions, backlog-close proposals, and proof-citation proposals (ADR-051) — each as a proposal record via backlog propose-status, NEVER as a direct mutation of state.ncl, the backlog status, or the proof set. (2) APPROVE IS THE ONLY MUTATION PATH — a proposal becomes an authoritative state change only through approve, which is a declared operation carrying a witness (ADR-024); the loop has no write path to authoritative state that bypasses approve. (3) PARTIAL REALIZATION IS DESCRIBED, NOT COLLAPSED — when harvested reality diverges from a declared state or claim (a dimension's blocker is gone but its catalyst is unmet; a milestone shipped that no proof claims), the loop DESCRIBES the half-state and proposes the delta that would move it; it never drops the declared intent because reality differs, and it never fabricates a transition to make reality and intent agree. (4) IGNORING THE LOOP IS ALSO A FAILURE MODE — the hook always RUNS at mode complete and always SURFACES its proposals (even if none are accepted), so the opposite collapse — Yang decide-and-commit blind to drift — is structurally prevented: the proposals are presented, the human disposes.

    Constraints

    • Hard

      The mode-complete hook emits FSM-transition, backlog-close and proof-citation deltas only as proposals via propose-status; it has no path to mutate state.ncl, backlog status, or the proof set directly

    • Hard

      An FSM transition, backlog close, or proof citation proposed by the loop becomes authoritative only through approve, a declared operation carrying a witness (ADR-024); no silent or timeout-based application

    • Hard

      When harvested reality diverges from a declared state or claim, the loop describes the half-state and proposes the delta; it never drops declared intent because reality differs nor fabricates a transition to reconcile them

    • Soft

      The mode-complete hook always runs and always surfaces its proposals (even when none are accepted); proposals cannot be silently skipped

    Alternatives considered

    • Auto-apply deltas the loop is confident about (advance FSM, close backlog, attach proof)

      Rejected: The defining collapse: reflection (the loop) mutating authoritative ontology state directly, bypassing the ADR-024 witnessed-operation boundary. Confidence is not authority; only approve confers it.

    • Drop or rewrite a declared state/claim when harvested reality contradicts it

      Rejected: The reality-collapses-intent ondaod violation: the half-state is a location on the flow, not a defect. The loop describes partial realization and proposes; it never erases declared intent to match reality.

    • Let the loop's proposals be optional / skippable at mode complete

      Rejected: Permits the mirror collapse toward Yang freeze — the project silently ignoring real drift. The hook always runs and always surfaces proposals so the reflection signal is never invisible, even when declined.

    • Build a dedicated proposal store and approval UI outside backlog

      Rejected: Duplicates the propose-status/approve seam backlog.nu already ships and the ADR-024 witnessed-operation path. Reusing them keeps a single audited mutation channel rather than a second one to secure.

    • Require human acceptance but apply silently if not declined within a window

      Rejected: A timeout-to-apply is auto-apply with a delay — it still lets reflection mutate authoritative state without a positive witnessed act. Approve must be an explicit declared operation, never a default-on-silence.

    Anti-patterns

    • Loop applies a delta

      The mode-complete loop advances an FSM dimension, closes a backlog item, or attaches a proof directly, mutating authoritative ontology state without a declared, witnessed approve.

    • Intent erased to match reality

      A declared state or claim is dropped or rewritten because harvested reality contradicts it, collapsing a legitimate half-state into a fabricated agreement between intent and reality.

    • Drift silently ignored

      The loop's proposals can be skipped without ever being surfaced, so real divergence between reflection and ontology is invisible and the project commits blind to drift.

    • Timeout-to-apply

      A proposal is applied automatically if not declined within a window, making silence a positive authorization and reintroducing unwitnessed mutation through the back door.

    +
    diff --git a/site/site/public/adr/adr-053/index.html b/site/site/public/adr/adr-053/index.html new file mode 100644 index 0000000..f9070b1 --- /dev/null +++ b/site/site/public/adr/adr-053/index.html @@ -0,0 +1,33 @@ + + + +Presentation-Spec as the Config-Driven Rendering Surface — One Declared Panel Model, Many Surfaces, No Engine Fork to Extend · Ontoref ADR + + +
    +
    + Accepted + adr-053 · 2026-06-08 +

    Presentation-Spec as the Config-Driven Rendering Surface — One Declared Panel Model, Many Surfaces, No Engine Fork to Extend

    +
    + +

    Context

    The constellation-memory work produces several things a project will want to SEE: a chronicle/timeline (component B), a roadmap (backlog roadmap + positioning roadmap, ADR-051), a criteria view (ADR-049), history and dependency graphs. The naive path is to write a bespoke Tera page, handler, and route per view, cloning graph.html each time and hard-coding the data source and the link wiring in Rust. That path has two costs the protocol has repeatedly refused. First, it puts presentation logic in Rust, so a DOMAIN project that wants its own panel (a difusion roadmap, a custom board) must fork the engine — violating Protocol-not-Runtime and the voluntary, additive adoption model (ADR-029). Second, it makes the rendered surface drift from the declared knowledge: the panel becomes a second source of truth in Rust rather than a projection of NCL. ADR-046 established the precedent that resolves this — a level, a UI panel, and an MCP tool are all the same declared substrate-view consumed via the surfaces field, not bespoke code — and the existing graph.html already renders an NCL-derived graph generically through the NclCache pipeline (NCL → JSON → {{json|safe}} → Cytoscape). ADR-034 (Proposed) generalizes the catalog with a kind discriminator so non-Rust operation kinds are declarable. What is missing is the analogous declarative model for PRESENTATION: a spec the engine reads generically so chronicle/roadmap/criteria/graph panels render across CLI/UI/GraphQL/md without per-panel Rust, and a domain project extends by shipping its own panel NCL file.

    Decision

    Establish a presentation-spec (schema .ontoref/reflection/schemas/presentation.ncl) as the single declarative panel model the engine reads generically, so adding a panel is dropping an NCL file, never editing Rust. Four mechanisms. (1) ONE PANEL MODEL — a Panel declares { id, title, kind ([| 'Timeline, 'Gantt, 'Graph, 'Table, 'Board |]), data_source ({op, params} naming a catalog operation, ADR-024/030), surfaces (which interfaces expose it), reference_links ([{from_field, to_kind, resolver}] for click-through wiring), filters, actors }; the engine renders any panel from this declaration through the existing NclCache → JSON pipeline (graph.html precedent) with no panel-specific code. (2) GENERIC RENDER, NO PER-PANEL RUST — the render path branches on kind, not on panel identity; the day a specific panel needs bespoke Rust is the day the model has failed, so a new panel-*.ncl appears in CLI, UI, and GraphQL with zero Rust changes (the keystone verification). (3) DUAL-PATH BRIDGE, ONE LIST — panels may be declared either as dedicated .ontoref/presentation/panel-*.ncl files OR referenced from config.ncl quick_actions via a panel_ref, OR surfaced from a substrate-view surface; all three NORMALIZE to one panel list, so there is exactly one resolved set of panels regardless of declaration site. (4) DOMAIN EXTENSION WITHOUT FORK — a consumer project adds panels by shipping its own .ontoref/presentation/*.ncl; the protocol's engine renders them unchanged, and a project that ships none gets the base panels and pays nothing. The data_source MUST name a declared catalog operation, so a panel is a projection of declared knowledge, never an independent Rust query.

    Constraints

    • Hard

      The engine renders any panel generically from its presentation-spec declaration through the NclCache→JSON pipeline; the render path branches on kind, never on panel identity, so a new panel-*.ncl requires zero Rust changes

    • Hard

      Every Panel.data_source names a declared catalog operation (ADR-024/030); a panel is a projection of declared knowledge and never an independent Rust query

    • Hard

      Panels declared as dedicated panel-*.ncl, referenced via quick_actions.panel_ref, or surfaced from a substrate-view surface all normalize to exactly one resolved panel list; no declaration site forms a separate registry

    • Soft

      A consumer project adds panels solely by shipping .ontoref/presentation/*.ncl rendered by the unchanged engine; a project that ships none gets the base panels and carries no mandatory presentation schema

    Alternatives considered

    • A bespoke Tera page + handler + route per view, cloning graph.html each time

      Rejected: Puts presentation logic in Rust, forcing a domain project to fork the engine to add a panel (violating Protocol-not-Runtime and additive adoption) and letting the rendered surface drift from declared NCL. The spec keeps panels declarative and generically rendered.

    • Let each panel's data_source be an arbitrary Rust query rather than a declared catalog operation

      Rejected: Makes the panel an independent source of truth in Rust, drifting from the declared knowledge graph. Requiring a declared catalog operation (ADR-024/030) keeps every panel a projection of ontology.

    • Allow only dedicated panel-*.ncl files; drop the quick_actions / view-surface paths

      Rejected: Ignores that panels are legitimately referenced from quick_actions and substrate-view surfaces today; forbidding those paths fragments the surface. The dual-path bridge normalizes all three into one list instead of forbidding two.

    • Branch the render path on panel id with per-panel special cases

      Rejected: Reintroduces per-panel Rust by the back door and guarantees drift as panels accumulate. Branching only on kind keeps the engine generic; a new panel needs no engine change, a new visualization needs only a new kind.

    • Make the presentation-spec mandatory for all adopters

      Rejected: Raises the adoption floor, collapsing formalization-vs-adoption toward mandatory gates. A project that ships no panels gets the base set and pays nothing; the spec is opt-in expressiveness.

    Anti-patterns

    • Per-panel Rust

      A panel's data or click-through links are hard-coded in Rust, or the render path branches on panel identity, so adding or changing a panel requires editing the engine and the surface drifts from declared NCL.

    • Panel as an independent query

      A panel's data_source is an arbitrary Rust query rather than a declared catalog operation, making the panel a second source of truth that can diverge from the knowledge graph.

    • Divergent panel registries

      Panels declared via dedicated files, quick_actions, and view surfaces are resolved by separate code paths into separate lists, so the set of panels depends on where you look.

    • Presentation mandatory at the floor

      The presentation-spec is required of every adopter, raising the adoption floor instead of adding opt-in expressiveness at the top of the curve.

    +
    diff --git a/site/site/public/adr/adr-054/index.html b/site/site/public/adr/adr-054/index.html new file mode 100644 index 0000000..7240bd6 --- /dev/null +++ b/site/site/public/adr/adr-054/index.html @@ -0,0 +1,33 @@ + + + +Desktop/Mobile Native Shell as a Consumer Surface — Tauri Host over the Daemon, Never a Protocol Component · Ontoref ADR + + +
    +
    + Accepted + adr-054 · 2026-06-09 +

    Desktop/Mobile Native Shell as a Consumer Surface — Tauri Host over the Daemon, Never a Protocol Component

    +
    + +

    Context

    The ontoref-daemon already exposes the full surface a native host would wrap: an axum HTTP /ui/ with rendered pages (graph, api_catalog, actions, login), a unified key-to-session auth model (ADR-005, ADR-021 UI lift-out into ontoref-ui), a NATS event fabric (platform-nats, feature-gated), an actor notification barrier (ADR-002), a passive drift watcher that emits exactly the events worth surfacing to a user (doc drift vs ontology node descriptions, gate-state changes), a multi-project daemon (ONTOREF_PROJECT_ROOT + remote-projects.ncl), and a Quick Actions catalog. The sibling project provisioning ships provisioning-desktop: ~3500 LOC of thin Tauri 2 host that wraps its daemon's /ui/ in a webview and adds the four things a browser cannot — system tray, native OS notifications, supervision of the local daemon child process (graceful SIGTERM), and native keyring — while the same lib compiles for iOS/Android via tauri::mobile_entry_point. The question is whether ontoref should grow an equivalent ontoref-desktop. The prerequisites already exist, so the decision is not feasibility but placement and identity: where such a crate lives, what it may depend on, and which named tension it occupies. Three options were evaluated: (A) no native host — users open the daemon UI in a browser; (B) a native host placed inside the protocol adoption surface, linking protocol internals or bundled into the daemon; (C) a from-scratch native UI (egui/iced) reimplementing the views in Rust. Option D — a thin Tauri shell as a separate consumer that wraps the daemon /ui/ and surfaces drift-watcher events as native notifications — is chosen.

    Decision

    Permit a native shell (ontoref-desktop, with the same Tauri lib targeting desktop and mobile) as a strictly separate consumer of the daemon, modeled on provisioning-desktop. It MUST consume the daemon over HTTP and NATS only — wrapping the existing /ui/ in a webview, bridging NATS/drift-watcher events to native OS notifications and a system tray, supervising the local daemon as a child process, and managing one local-spawn plus N remote-connect connection profiles against the multi-project daemon. It MUST NOT be a dependency of ontoref-ontology, ontoref-reflection, or the daemon: the dependency arrow points shell -> daemon, never the reverse, and never into the protocol-minimal adoption surface (ADR-001). It MAY depend on ontoref-ui for shared presentation. It lives either as its own constellation sub-repo (sibling to code/, per ADR-048) or as a workspace crate explicitly excluded from the installed/adopted set — in both cases outside the protocol footprint a consumer project receives. Within positioning (ADR-035/043) the desktop/mobile surface is declared as a consumer audience and the candidate occupant of the openness-vs-sustainability capture seam, while the protocol itself stays unmetered.

    Constraints

    • Hard

      ontoref-ontology must never list a desktop/native-shell crate as a dependency

    • Hard

      ontoref-reflection must never list a desktop/native-shell crate as a dependency

    • Hard

      ontoref-daemon must never depend on the desktop/native-shell crate; the dependency arrow points shell -> daemon only

    • Hard

      The native shell lives as its own constellation sub-repo or as a workspace crate explicitly excluded from the installed/adopted protocol footprint, consuming the daemon over HTTP/NATS only

    • Soft

      When the native surface occupies the openness-vs-sustainability seam, it does so as a declared consumer audience in positioning; the protocol surface itself is never metered

    Alternatives considered

    • No native host — users open the daemon /ui/ in a browser (option A)

      Rejected: Forfeits the four browser-impossible capabilities, chief among them native notifications for the drift watcher and ADR-002 barrier. The daemon already produces the events; leaving them trapped behind a manually-refreshed page wastes the notification-barrier design.

    • Native host inside the protocol adoption surface — linking protocol internals or bundled into the daemon (option B)

      Rejected: Violates Protocol-Not-Runtime and ADR-001: it would make a GUI consumer part of the surface a consumer project adopts, coupling the minimal protocol crates to a heavy platform toolchain. The dependency arrow would point the wrong way (protocol -> host).

    • From-scratch native UI in egui/iced reimplementing the views (option C)

      Rejected: Duplicates the daemon /ui/ and ontoref-ui, creating two view surfaces that drift apart. High maintenance, no single source of truth for the graph/catalog/actions pages, and it discards the reason a thin Tauri host stays small.

    Anti-patterns

    • Native host inside the adoption surface

      Placing the desktop/mobile crate beside the protocol-minimal crates, or making ontoref-ontology/-reflection/the daemon depend on it, or bundling it into the daemon. This inverts the shell -> daemon arrow and drags a platform/webview toolchain into the surface a consumer project adopts, breaking Protocol-Not-Runtime.

    • Native view-surface fork

      Reimplementing the daemon /ui/ views (graph, api_catalog, actions, qa) natively in egui/iced instead of wrapping the existing HTML in a webview. Creates two view surfaces that drift apart and discards the single-source-of-truth that keeps the host thin.

    • Metering the protocol to capture value

      Putting the openness-vs-sustainability capture on the protocol surface itself — gating tier-0 adoption, schemas, or describe queries behind payment — instead of on a consumer surface. Collapses the Spiral toward Yang by tolling the gift that produces the visibility funnel.

    +
    diff --git a/site/site/public/adr/adr-055/index.html b/site/site/public/adr/adr-055/index.html new file mode 100644 index 0000000..a94be48 --- /dev/null +++ b/site/site/public/adr/adr-055/index.html @@ -0,0 +1,82 @@ + + + +Positioning Viability Seam — Compensation as a Second Costura Parallel to Proof, Not a Fourth Axis · Ontoref ADR + + +
    +
    + Accepted + adr-055 · 2026-06-09 +

    Positioning Viability Seam — Compensation as a Second Costura Parallel to Proof, Not a Fourth Axis

    +
    + +

    Context

    ADR-043 modelled positioning as three orthogonal axes (QUÉ / PARA QUIÉN / CÓMO) +bound by a single Proof seam. That framework has a structural silence: it can +say what the project claims, to whom, how it spreads, and whether the claim is +real — but it has no place to say whether any of it SUSTAINS the maker. There +was no model of compensation, direct or indirect. Decisions about pricing, +metered surfaces, visibility-as-funnel, or a commercial consumer were therefore +reasoned entirely outside the graph and improvised.

    +

    This silence was named in `.ontoref/ontology/core.ncl` as the Spiral tension +`openness-vs-sustainability`: the protocol gives value away to maximise reach +(voluntary-adoption, the personal-ontology audience, difusión as visibility) +while the project that maintains it must capture enough value to sustain itself. +The Yin pole (openness) is the engine of visibility; the Yang pole +(sustainability) is the engine of compensation; they feed one loop. With the +tension named but no positioning element to attach it to, the tension stayed +claim-only.

    +

    The load-bearing risk is identical to ADR-043's and one step sharper: a money +dimension on the marketing surface invites classifying every claim by its +business model — the Yang pole-collapse the named tension exists to prevent. The +framing that resolves it is geometric: compensation is not a facet of a claim +(an axis) but an outcome of convergence (a seam).

    Decision

    Model viability as a SECOND seam, `ViabilityPath`, parallel to `Proof` — not a +fourth orthogonal axis. Where Proof answers "is the claim real?" (validation seam +over the three axes), a ViabilityPath answers "does the claim sustain its maker?" +(sustainability seam over the same three axes). Both reference the axes via +`converges` and are referenced by nothing — self-similar to witness-as-axis-seam +(ADR-031) and to Proof (ADR-043).

    +

    Kind — `ViabilityPath` (one file per declared sustaining flow), under + `.ontoref/positioning/viability/`. + converges — { audience_ids, mechanism_ids, value_prop_ids } each non-empty: + PARA QUIÉN × CÓMO × (QUÉ×QUIÉN). A sustaining flow is an audience + reached by a difusión mechanism monetising a value-prop, or it is + not a path. + directness — 'Direct | 'Indirect, mapping to the two poles of + openness-vs-sustainability. 'Indirect is the Yin/open pole + (visibility, funnel, reputation); 'Direct is the Yang/governance + pole (metered payment for the witness/audit surface). + flow_kind — HostedService | Support | Consulting | DualLicense | Sponsorship + | Funnel | Reputation. Descriptive vocabulary of compensation + forms, NOT a per-claim business-model decision. + status — 'Draft (a hypothesis about how positioning could sustain the + project) until a real, measured `realized_outcome` promotes it — + exactly as an Audience is 'Hypothesis until a Proof validates it.

    +

    The realized-status gate is one-directional: a 'Validated|'Live path MUST carry +`realized_outcome`; a 'Draft path may omit it. It is deliberately NOT a Hard +biconditional (`outcome ⟺ realized`) on the Spiral question — that would be an +ondaod forbidden pattern. Capture lands on a commercial consumer or the +witness/audit tier, never on the open protocol surface: protocol-not-runtime and +voluntary-adoption are preserved by construction.

    +

    The kind is additive and opt-in (ADR-029): a project with no `viability/` +directory pays zero adoption cost, and every existing positioning file validates +unchanged. No migration is required (same precedent as ADR-043's additive kinds).

    Constraints

    • Hard

      Viability MUST be modelled as a seam: `ViabilityPath` references the three axes via `converges` and MUST NOT be referenced by any other kind, and MUST NOT be promoted into a fourth orthogonal axis co-equal to QUÉ / PARA QUIÉN / CÓMO. The schema keeps it parallel to Proof.

    • Hard

      Every ViabilityPath MUST converge >=1 audience, >=1 mechanism and >=1 value-prop. The make_viability_path contract (`_viability_converges_all`) enforces the triple convergence.

    • Hard

      Every ViabilityPath MUST cite >=1 linked_node — typically `openness-vs-sustainability`. The make_viability_path contract (`_viability_anchored`) enforces this.

    • Soft

      The realized-status gate MUST stay one-directional: a 'Validated|'Live ViabilityPath requires `realized_outcome`, but the presence of an outcome MUST NOT be tightened into a Hard biconditional (`outcome ⟺ realized`) on the Spiral question. The `_realized_requires_outcome` contract enforces the implication only.

    • Soft

      A 'Direct ViabilityPath MUST land compensation on a commercial consumer or the witness/audit tier, never on the open protocol surface itself. Metering the protocol would break protocol-not-runtime and voluntary-adoption.

    Alternatives considered

    • Viability as a fourth orthogonal axis (a fourth folder co-equal to QUÉ / PARA QUIÉN / CÓMO)

      Rejected: Compensation is not parallel to the three axes — it is where they converge and return a flow. A fourth axis implies a false fourth pole and invites classifying every claim by its business model, the exact Yang pole-collapse openness-vs-sustainability exists to prevent. The Proof-parallel seam is faithful to witness-as-axis-seam.

    • Viability as an attribute on existing kinds (a compensation_kind field on Audience, or an outcome variant on Proof)

      Rejected: Scatters the sustainability question across kinds and couples it to validation. The seam property — references the axes, referenced by none, with its own Draft→realized lifecycle — is lost. A path that monetises an audience×mechanism×value-prop convergence needs to be a first-class artefact, not a field bolted onto one axis.

    • Reuse Proof with a new proof_kind = 'Revenue

      Rejected: Proof answers 'is the claim real?'; viability answers 'does the claim sustain its maker?'. Overloading Proof conflates validation with viability and breaks the clean question each seam owns. Two questions, two costuras.

    • Make the realized_outcome gate a Hard biconditional (outcome present ⟺ status realized)

      Rejected: openness-vs-sustainability is a Spiral tension; a Hard biconditional on it is an ondaod forbidden pattern. The one-directional gate (realized ⇒ outcome) reports direction of motion without collapsing the Spiral — a Draft path is a legitimate hypothesis that may carry no outcome yet.

    Anti-patterns

    • Modelling Compensation as a Fourth Orthogonal Axis

      A contributor adds a fourth positioning folder co-equal to QUÉ / PARA QUIÉN / CÓMO and starts tagging each claim with a business model. The axis count silently becomes four, the seam geometry is lost, and money becomes a per-claim classification — the Yang pole-collapse openness-vs-sustainability exists to prevent.

    • A Realized Viability Path Without a Measured Outcome

      A ViabilityPath is promoted to 'Validated|'Live to look like the project is sustained, but no real measured `realized_outcome` backs it. Sustenance is asserted, not earned — the same failure mode as a Validated Audience with no Proof.

    • Putting Direct Compensation on the Open Protocol Surface

      A 'Direct viability path meters the protocol itself (the schemas, the CLI, the adoption surface) rather than a commercial consumer or the witness/audit tier. This collapses openness-vs-sustainability onto the Yang pole and voids protocol-not-runtime and voluntary-adoption.

    • Tightening the Realized Gate into a Hard Biconditional

      A contributor 'strengthens' the realized gate to outcome ⟺ realized, so that any path carrying an outcome string is forced to a realized status (and vice versa). This is a Hard biconditional on a Spiral question — it collapses the continuous flow and forbids the legitimate Draft-with-early-evidence half-state.

    +
    diff --git a/site/site/public/adr/adr-056/index.html b/site/site/public/adr/adr-056/index.html new file mode 100644 index 0000000..7cc886a --- /dev/null +++ b/site/site/public/adr/adr-056/index.html @@ -0,0 +1,81 @@ + + + +Sufficient Verification over Complete Knowledge — Accredited, Not Generated, as the Basis of Trust · Ontoref ADR + + +
    +
    + Accepted + adr-056 · 2026-06-10 +

    Sufficient Verification over Complete Knowledge — Accredited, Not Generated, as the Basis of Trust

    +
    + +

    Context

    The protocol's trust model has, until now, been distributed across four +decisions without ever being named as one: the verifiable substrate and its +Merkle/witness commitments (ADR-023), the operations layer that binds every +authoritative mutation to a signed acting-binding (ADR-024), the three +validation planes (ADR-026), and the ontology/project layer separation that +lets one project verify another by witness rather than by cloning it (ADR-028). +Each of these embodies the same underlying stance, but the stance itself was +implicit, citable only by reading four ADRs and inferring the common thread.

    +

    The thread is this: a project's knowledge can never be complete, and even if it +could, total context overflows — recording everything produces more confusion +than clarity, and no single actor (human or AI) ever holds the whole. A trust +model that depends on completeness therefore fails twice: it is unreachable, and +it is self-defeating. The only viable basis is local, partial verification of +sufficient knowledge — but "sufficient" is dangerous if it means "approximately +enough", because a well-typed-but-stale graph manufactures false certainty worse +than stale prose (the tier-0-false-certainty anti-pattern, ADR-029).

    +

    The sharpening that makes sufficiency safe is accreditation. What makes a partial +slice usable is not that it roughly suffices but that it is signed and checkable. +This becomes load-bearing in the AI moment: an agent's GENERATED output, and any +DEDUCED conclusion, is not usable knowledge until it is accredited. Hallucination +is exactly generated content entering the authoritative set unsigned. The stance +needs to be explicit and citable because the difusión surface already sells it +(differentiator diff-accredited-knowledge) and the agent-integration surface +depends on it.

    Decision

    Name the stance as a constitutive axiom — `sufficient-verification`, +"Sufficient Verification over Complete Knowledge" (pole 'Spiral, invariant=true +in .ontoref/ontology/core.ncl) — and fix its operative consequences as typed +constraints.

    +

    1. Verification is always LOCAL and PARTIAL. Any actor can verify that a slice + (an op, a node, a change) is coherent with what is declared WITHOUT holding + or loading global knowledge of the whole. Cross-project verification is by + witness, not by clone (ADR-028).

    +

    2. The protocol NEVER requires complete or global knowledge as a precondition + for a valid act. Any gate of the form "you may not change X until you + understand the whole system" is forbidden by design.

    +

    3. Knowledge is ACCREDITED, not deduced or generated. Deduced or generated + output — explicitly including an AI agent's — is NOT usable authoritative + knowledge until it is signed/witnessed. At tier-2 this is enforced: the + witness is the only mutation path and validators reject ill-typed state + (internal-coherence-enforced). At tier-0, where no witness exists, such + knowledge is unaccredited by construction and MUST NOT be treated as + authoritative — it is a draft to be accredited, not a fact.

    +

    4. "Sufficient" means accredited-and-checkable, never "approximately enough". + The floor is the signature, not a tolerance band.

    +

    This is a Spiral commitment, not a Yin concession: it does not give up certainty, +it fixes the third thing between impossible-complete-certainty and dangerous-blind- +trust — verification of the sufficient — as invariant law. The axiom is the +substance-axis statement; the witness/validators (ADR-023/024/026) are the +reflection-axis enforcement; tier coexistence (ADR-029) is what lets a project +choose how much it commits to accredit, never how much it must know.

    Constraints

    • Hard

      Verification MUST operate on a slice (an op, a node, a change) without requiring the whole graph; cross-project verification MUST be by witness, never by clone (ADR-028). No verification path may demand global load.

    • Hard

      The protocol MUST NOT introduce any gate that requires complete or global knowledge as a precondition for a valid act ('you may not change X until you understand the whole system' is forbidden).

    • Hard

      Deduced or generated output — explicitly including an AI agent's — MUST NOT enter the authoritative set until signed/witnessed. At tier-2 the witness path enforces this (ADR-024); at tier-0, where no witness exists, such output MUST be treated as an unaccredited draft, never as authoritative fact.

    • Soft

      'Sufficient' MUST mean accredited-and-checkable (the floor is a signature), never 'approximately enough' (a tolerance band). No surface may relax sufficiency into laxity.

    Alternatives considered

    • Leave the stance implicit across ADR-023/024/026/028

      Rejected: The stance is load-bearing for difusión and agent integration and was already being sold (diff-accredited-knowledge) without a citable decision behind it. Implicit-across-four-ADRs means no single thing to cite, constrain against, or defend — and the global-knowledge gate could creep in unchallenged.

    • Model it as a differentiator only (diff-accredited-knowledge), not an axiom

      Rejected: A differentiator positions outward; it does not constrain inward. The principle is a constitutive renunciation that must bind all future design, not a competitive talking point. The repo precedent is both: witness-as-axis-seam is an axiom AND diff-witness-seam cites it. Accreditation gets the same two-layer treatment.

    • Model it as a Yin axiom (a concession: we accept knowing less)

      Rejected: Yin frames incompleteness as the price of ontoref — an apology. The honest framing is Spiral: holding the third thing between impossible certainty and blind trust, fixed as law. Incompleteness is the FORM, not the price; that distinction sets the tone of the whole difusión surface (affirmation, not apology).

    Anti-patterns

    • Treating Generated or Deduced Output as Authoritative Without a Signature

      An agent's generated output, or a deduced conclusion, is written into authoritative state (or trusted as fact) without being witnessed. Hallucination enters the set unmarked; the accredited/generated line is erased. This is the precise failure the axiom exists to forbid.

    • Requiring Whole-System Understanding Before a Valid Act

      A mode, validator, or adoption step demands that an actor understand or load the whole project before changing a part. This reinstates the unreachable, self-defeating completeness trust model and blocks the local partial action the axiom guarantees.

    • Resting Trust on Completeness Instead of Verification

      A project tries to earn trust by documenting/knowing everything and keeping it all current — the model that overflows context and drifts, producing more confusion than clarity. Trust should rest on accredited sufficient verification, not on an unreachable whole.

    +
    diff --git a/site/site/public/adr/adr-057/index.html b/site/site/public/adr/adr-057/index.html new file mode 100644 index 0000000..5287edd --- /dev/null +++ b/site/site/public/adr/adr-057/index.html @@ -0,0 +1,86 @@ + + + +Difusión Reveal Architecture — Route Each Door to a Live Graph, Never Argue the Core · Ontoref ADR + + +
    +
    + Accepted + adr-057 · 2026-06-10 +

    Difusión Reveal Architecture — Route Each Door to a Live Graph, Never Argue the Core

    +
    + +

    Context

    The positioning layer (ADR-035) made marketing a queryable protocol surface: +audiences, value-props, differentiators, proofs, viability paths, all typed NCL. +But the outward-facing SITE (outreach/site) did not consume that graph — it +rendered a generic content template (blog/recipes/projects). The originating +complaint that opened the 2026-06-10 ontology-completion interview was exactly +this: "the site content does not correspond to the project."

    +

    The interview established that the gap is not missing ontology (the layer is +rich) but a missing PROJECTION discipline, and — more importantly — that the +naive fix (a site that explains ontoref's core: the two axes, the witness-seam, +the spiral, the wu-wei) is actively wrong. Exposition of the core repels by both +ends, the failure mode named in the Spiral tension `reach-vs-qualification`: it +bores the visitor who came for a concrete utility, and it reads as esoteric to +the visitor who has not yet earned the frame. A site that argues the worldview +either converts no one or attracts the wrong adopter who bails at first friction.

    +

    The interview also established HOW the worldview is actually transmitted — a +three-rung reveal ladder with increasing mediation-dependence: (1) a self-produced +flip when a developer runs a live `describe impact` and sees the tool answer +intent, not just structure; (2) a seeded flip, landing only in real use, when +changing something forces touching the maintained identity (drift-check / ondaod +/ witness); (3) a worldview flip — "this is for anything, not just projects" — +produced ONLY by encounter with a real subject-graph the visitor recognizes as +their own, never by argument. The proofs layer already names this: +proof-personal-ontoref records that the latent audience "self-discovers when they +see a personal graph, not in a pitch", and mech-repo's stance is "show, don't +tell". The decision below makes that discipline architectural.

    Decision

    The site is a vestibule of three concrete doors (developer / infrastructure / +personal), each routing to a live, navigable, witnessed graph. It is NOT an +exposition of the core. Concretely:

    +

    Projection, not authoring — door landings are PROJECTED from the positioning + graph through a structured contract (.ontoref/positioning/spine.ncl), not + hand-written. The contract carries the chosen spine copy (hero A, sub-hero B, + door lines C, prize D, close E) and references real audience/value-prop ids per + door; a generator emits the pages. Change a value-prop, regenerate, the door + updates. No duplicated copy that can drift.

    +

    Hook static, prize live — the HOOK (hero, sub-hero, door landings) is static + NCL→build, so the site is usable with no daemon (local-fallback, ADR-029). The + REVEAL rungs that need a live graph — rung 1 (live `describe impact` demo) and + rung 3 (browsable real graph) — link to the running daemon. The daemon is the + prize surface, never a hard dependency of the hook.

    +

    Route, do not argue — the site MUST route each door to a live graph or describe + surface, and MUST NOT try to produce the worldview flip by prose. rung 1 is + produced by a live demo; rung 2 is seeded (shown, lands in use); rung 3 is + triggered only by serving a real navigable subject-graph (ontoref's own, the + provisioning graph, a published personal graph). Prose alone never produces the + flip — this is the declared structural limit.

    +

    Mediation by witnessed artifact — the worldview flip historically needed a + person to mediate. The decision converts that mediation into a reproducible + witnessed artifact: a browsable real graph (the proofs, made navigable). This + is what lets ontoref.dev produce reveal 1 and 3 without a human bottleneck. + Artifact-mediation serves reach (self-selection at scale); human mediation + remains only for the qualified-but-stuck — the reach-vs-qualification split.

    +

    Honesty as filter — door copy is second person and names the pain (and the + exhaustion of facing it without a substrate); it does not soften the truth that + there is no deterministic recipe. The repulsion of the recipe-seeker is the + qualifier, not funnel leakage (reach-vs-qualification).

    Constraints

    • Soft

      The site MUST route each door to a live graph or describe surface and MUST NOT attempt to produce the worldview flip by prose. The core (two axes, witness-seam, spiral, wu-wei) is the prize revealed after entry, never the landing-page argument.

    • Hard

      Door landing pages MUST be projected from the structured contract (.ontoref/positioning/spine.ncl) referencing real audience/value-prop ids, not hand-authored. A value-prop change MUST be reflected by regeneration, never by editing copy in two places.

    • Hard

      The hook (hero, sub-hero, door landings) MUST be static NCL→build and usable with no daemon (local-fallback). Only the reveal rungs that need a live graph (rung 1 live describe-impact, rung 3 browsable graph) may depend on the running daemon.

    • Soft

      The rung-3 worldview flip MUST be mediated by a reproducible witnessed artifact (a browsable real graph), not by a required human. Publishing a real personal graph as that artifact is a per-artifact privacy decision and MUST be explicitly cleared before exposure.

    Alternatives considered

    • An exposition site that explains the core (two axes, witness-seam, spiral, wu-wei) on the landing page

      Rejected: This is the naive fix the interview rejected. Exposition of the core repels by both ends (reach-vs-qualification): it bores the utility-seeker and reads esoteric to the unready, converting no one or attracting the wrong adopter. The worldview is not transmissible by prose — only by encounter with a real graph.

    • Hand-authored door landing pages (marketing copy written directly as content)

      Rejected: Hand-authored copy drifts from the value-props it sells — the exact failure (content maintained by hand) that produced the generic template complaint. Projection from spine.ncl + the value-props keeps the site a derived view that regenerates on change.

    • Site consumes the daemon API at runtime for everything, including the hook

      Rejected: Couples the whole site to a running daemon, contradicting local-fallback (ADR-029): the site goes dark when the daemon is down. The hook must be static so first contact never depends on the daemon; only the live-graph reveal rungs may.

    • Rely on a human to mediate the worldview flip (talks, calls, demos by a person)

      Rejected: Human mediation does not scale and bottlenecks reach. The flip's mediation is convertible into a reproducible witnessed artifact — a browsable real graph (proof-personal-ontoref, mech-repo). Human mediation is kept only for the qualified-but-stuck, per reach-vs-qualification.

    Anti-patterns

    • Arguing the Core on the Landing Page

      A contributor writes a landing page that explains the two axes, the witness-seam, the spiral, or the wu-wei as the first thing a visitor reads. Exposition repels by both ends (reach-vs-qualification) and cannot produce the worldview flip, which only encounter with a real graph produces.

    • Hand-Authoring Door Copy That Drifts From the Value-Props

      Door landing text is written directly as site content rather than projected from spine.ncl + the value-props. The copy drifts from the claims it sells — the exact failure that produced the generic-template complaint.

    • Gating First Contact on a Running Daemon

      The hook (hero, sub-hero, door landings) is made to require the daemon (e.g. fetched live at runtime), so the site goes dark when the daemon is down. This breaks local-fallback and gates first contact on the prize surface.

    +
    diff --git a/site/site/public/adr/index.html b/site/site/public/adr/index.html new file mode 100644 index 0000000..a8dfc93 --- /dev/null +++ b/site/site/public/adr/index.html @@ -0,0 +1,33 @@ + + + +Architecture Decision Records · Ontoref ADR + + +
    +
    + Index + ADRs · +

    Architecture Decision Records

    +
    +
    +

    Accepted (53)

    Proposed (4)

    +
    diff --git a/site/site/public/console-logger.js b/site/site/public/console-logger.js new file mode 100644 index 0000000..3244b32 --- /dev/null +++ b/site/site/public/console-logger.js @@ -0,0 +1,95 @@ +/** + * Console Logger - Captures all browser console output to localStorage + * This allows viewing logs from /public/logs.html across all pages + */ + +(function() { + // Capture all console methods + const originalLog = console.log; + const originalError = console.error; + const originalWarn = console.warn; + const originalInfo = console.info; + const originalDebug = console.debug; + + function formatTimestamp() { + return new Date().toISOString(); + } + + function captureLog(level, args) { + const message = args.map(arg => { + if (typeof arg === 'object') { + try { + return JSON.stringify(arg, null, 2); + } catch (e) { + return String(arg); + } + } + return String(arg); + }).join(' '); + + const logEntry = { + timestamp: formatTimestamp(), + level, + message + }; + + // Save to localStorage for logs viewer + try { + const stored = localStorage.getItem('console_logs'); + let logs = stored ? JSON.parse(stored) : []; + logs.push(logEntry); + // Keep last 100 logs in localStorage + if (logs.length > 100) { + logs = logs.slice(-100); + } + localStorage.setItem('console_logs', JSON.stringify(logs)); + } catch (e) { + // localStorage might be unavailable or full - silently fail + } + } + + // Override console methods + console.log = function(...args) { + originalLog(...args); + captureLog('LOG', args); + }; + + console.error = function(...args) { + originalError(...args); + captureLog('ERROR', args); + }; + + console.warn = function(...args) { + originalWarn(...args); + captureLog('WARN', args); + }; + + console.info = function(...args) { + originalInfo(...args); + captureLog('INFO', args); + }; + + console.debug = function(...args) { + originalDebug(...args); + captureLog('DEBUG', args); + }; + + // Capture uncaught errors and unhandled promise rejections (module load failures go here) + window.addEventListener('error', function(e) { + const msg = e.message || 'Unknown error'; + const src = e.filename ? ` @ ${e.filename}:${e.lineno}` : ''; + captureLog('ERROR', [`[WINDOW-ERROR] ${msg}${src}`]); + originalError('[WINDOW-ERROR]', msg, src, e.error); + }); + + window.addEventListener('unhandledrejection', function(e) { + const reason = e.reason instanceof Error + ? `${e.reason.message}\n${e.reason.stack}` + : String(e.reason); + captureLog('ERROR', [`[UNHANDLED-REJECTION] ${reason}`]); + originalError('[UNHANDLED-REJECTION]', e.reason); + }); + + // Initial message + originalLog('🔍 Console Logger initialized - capturing to localStorage'); +})(); diff --git a/site/site/public/content/blog/_images/ontoref/trust-is-an-output-not-an-input/trust-is-an-output-not-an-input_feature.jpg b/site/site/public/content/blog/_images/ontoref/trust-is-an-output-not-an-input/trust-is-an-output-not-an-input_feature.jpg new file mode 100644 index 0000000..b689284 Binary files /dev/null and b/site/site/public/content/blog/_images/ontoref/trust-is-an-output-not-an-input/trust-is-an-output-not-an-input_feature.jpg differ diff --git a/site/site/public/docs/index.html b/site/site/public/docs/index.html new file mode 100644 index 0000000..e719621 --- /dev/null +++ b/site/site/public/docs/index.html @@ -0,0 +1 @@ +Silent is gold diff --git a/site/site/public/images/JesusPerez_f.jpg b/site/site/public/images/JesusPerez_f.jpg new file mode 100644 index 0000000..f0bbc3c Binary files /dev/null and b/site/site/public/images/JesusPerez_f.jpg differ diff --git a/site/site/public/images/ai-knowledge-post.avif b/site/site/public/images/ai-knowledge-post.avif new file mode 100644 index 0000000..3980d58 Binary files /dev/null and b/site/site/public/images/ai-knowledge-post.avif differ diff --git a/site/site/public/images/ai-knowledge-post.webp b/site/site/public/images/ai-knowledge-post.webp new file mode 100644 index 0000000..64ef831 Binary files /dev/null and b/site/site/public/images/ai-knowledge-post.webp differ diff --git a/site/site/public/images/dags-everywhere_post.avif b/site/site/public/images/dags-everywhere_post.avif new file mode 100644 index 0000000..5e2429b Binary files /dev/null and b/site/site/public/images/dags-everywhere_post.avif differ diff --git a/site/site/public/images/dags-everywhere_post.webp b/site/site/public/images/dags-everywhere_post.webp new file mode 100644 index 0000000..1172337 Binary files /dev/null and b/site/site/public/images/dags-everywhere_post.webp differ diff --git a/site/site/public/images/decks/ontoref-ontologia-reflexion/0.webp b/site/site/public/images/decks/ontoref-ontologia-reflexion/0.webp new file mode 100644 index 0000000..afaaf89 Binary files /dev/null and b/site/site/public/images/decks/ontoref-ontologia-reflexion/0.webp differ diff --git a/site/site/public/images/decks/ontoref-ontologia-reflexion/1.webp b/site/site/public/images/decks/ontoref-ontologia-reflexion/1.webp new file mode 100644 index 0000000..df132fc Binary files /dev/null and b/site/site/public/images/decks/ontoref-ontologia-reflexion/1.webp differ diff --git a/site/site/public/images/decks/ontoref-ontologia-reflexion/10.webp b/site/site/public/images/decks/ontoref-ontologia-reflexion/10.webp new file mode 100644 index 0000000..1358993 Binary files /dev/null and b/site/site/public/images/decks/ontoref-ontologia-reflexion/10.webp differ diff --git a/site/site/public/images/decks/ontoref-ontologia-reflexion/11.webp b/site/site/public/images/decks/ontoref-ontologia-reflexion/11.webp new file mode 100644 index 0000000..7bbe968 Binary files /dev/null and b/site/site/public/images/decks/ontoref-ontologia-reflexion/11.webp differ diff --git a/site/site/public/images/decks/ontoref-ontologia-reflexion/12.webp b/site/site/public/images/decks/ontoref-ontologia-reflexion/12.webp new file mode 100644 index 0000000..072b6e6 Binary files /dev/null and b/site/site/public/images/decks/ontoref-ontologia-reflexion/12.webp differ diff --git a/site/site/public/images/decks/ontoref-ontologia-reflexion/13.webp b/site/site/public/images/decks/ontoref-ontologia-reflexion/13.webp new file mode 100644 index 0000000..1d46a7b Binary files /dev/null and b/site/site/public/images/decks/ontoref-ontologia-reflexion/13.webp differ diff --git a/site/site/public/images/decks/ontoref-ontologia-reflexion/14.webp b/site/site/public/images/decks/ontoref-ontologia-reflexion/14.webp new file mode 100644 index 0000000..5f50fc5 Binary files /dev/null and b/site/site/public/images/decks/ontoref-ontologia-reflexion/14.webp differ diff --git a/site/site/public/images/decks/ontoref-ontologia-reflexion/15.webp b/site/site/public/images/decks/ontoref-ontologia-reflexion/15.webp new file mode 100644 index 0000000..3455381 Binary files /dev/null and b/site/site/public/images/decks/ontoref-ontologia-reflexion/15.webp differ diff --git a/site/site/public/images/decks/ontoref-ontologia-reflexion/2.webp b/site/site/public/images/decks/ontoref-ontologia-reflexion/2.webp new file mode 100644 index 0000000..cb5ddf1 Binary files /dev/null and b/site/site/public/images/decks/ontoref-ontologia-reflexion/2.webp differ diff --git a/site/site/public/images/decks/ontoref-ontologia-reflexion/3.webp b/site/site/public/images/decks/ontoref-ontologia-reflexion/3.webp new file mode 100644 index 0000000..86d3050 Binary files /dev/null and b/site/site/public/images/decks/ontoref-ontologia-reflexion/3.webp differ diff --git a/site/site/public/images/decks/ontoref-ontologia-reflexion/4.webp b/site/site/public/images/decks/ontoref-ontologia-reflexion/4.webp new file mode 100644 index 0000000..6344621 Binary files /dev/null and b/site/site/public/images/decks/ontoref-ontologia-reflexion/4.webp differ diff --git a/site/site/public/images/decks/ontoref-ontologia-reflexion/5.webp b/site/site/public/images/decks/ontoref-ontologia-reflexion/5.webp new file mode 100644 index 0000000..dd60b6e Binary files /dev/null and b/site/site/public/images/decks/ontoref-ontologia-reflexion/5.webp differ diff --git a/site/site/public/images/decks/ontoref-ontologia-reflexion/6.webp b/site/site/public/images/decks/ontoref-ontologia-reflexion/6.webp new file mode 100644 index 0000000..2c99ddf Binary files /dev/null and b/site/site/public/images/decks/ontoref-ontologia-reflexion/6.webp differ diff --git a/site/site/public/images/decks/ontoref-ontologia-reflexion/7.webp b/site/site/public/images/decks/ontoref-ontologia-reflexion/7.webp new file mode 100644 index 0000000..1c3d977 Binary files /dev/null and b/site/site/public/images/decks/ontoref-ontologia-reflexion/7.webp differ diff --git a/site/site/public/images/decks/ontoref-ontologia-reflexion/8.webp b/site/site/public/images/decks/ontoref-ontologia-reflexion/8.webp new file mode 100644 index 0000000..7d62791 Binary files /dev/null and b/site/site/public/images/decks/ontoref-ontologia-reflexion/8.webp differ diff --git a/site/site/public/images/decks/ontoref-ontologia-reflexion/9.webp b/site/site/public/images/decks/ontoref-ontologia-reflexion/9.webp new file mode 100644 index 0000000..adedc8d Binary files /dev/null and b/site/site/public/images/decks/ontoref-ontologia-reflexion/9.webp differ diff --git a/site/site/public/images/decks/ontoref-ontologia-reflexion/deck.pdf b/site/site/public/images/decks/ontoref-ontologia-reflexion/deck.pdf new file mode 100644 index 0000000..72adbfe Binary files /dev/null and b/site/site/public/images/decks/ontoref-ontologia-reflexion/deck.pdf differ diff --git a/site/site/public/images/expedientes/deriva-header.svg b/site/site/public/images/expedientes/deriva-header.svg new file mode 100644 index 0000000..34efd4f --- /dev/null +++ b/site/site/public/images/expedientes/deriva-header.svg @@ -0,0 +1,19 @@ + + + + + EXPEDIENTE · HOMICIDIOS DE CÓDIGO + + LA DERIVA + HARDCODED + Una tarde perdida. Un match hardcoded. Ocho líneas. + + CASO Nº 404-PAP + CLASIF: ANTI-PAP + + + + CASO CERRADO + 8 LÍNEAS + + diff --git a/site/site/public/images/expedientes/deriva-header.webp b/site/site/public/images/expedientes/deriva-header.webp new file mode 100644 index 0000000..586de9d Binary files /dev/null and b/site/site/public/images/expedientes/deriva-header.webp differ diff --git a/site/site/public/images/expedientes/en/expedientes.html b/site/site/public/images/expedientes/en/expedientes.html new file mode 100644 index 0000000..e994c2d --- /dev/null +++ b/site/site/public/images/expedientes/en/expedientes.html @@ -0,0 +1,468 @@ + + +Case files · with ontoref it wouldn't have gone like this + + + + +
    + + + +
    + + + +
    + +
    + +
    + +
    + + + + diff --git a/site/site/public/images/expedientes/es/deriva.html b/site/site/public/images/expedientes/es/deriva.html new file mode 100644 index 0000000..29c3756 --- /dev/null +++ b/site/site/public/images/expedientes/es/deriva.html @@ -0,0 +1,265 @@ + +Expediente — la deriva hardcoded + + +
    + + + +
    + +
    + + + + diff --git a/site/site/public/images/expedientes/es/expedientes.html b/site/site/public/images/expedientes/es/expedientes.html new file mode 100644 index 0000000..6b67f17 --- /dev/null +++ b/site/site/public/images/expedientes/es/expedientes.html @@ -0,0 +1,474 @@ + + +Expedientes · con ontoref no hubiera sido así + + + + +
    + + + +
    + + + +
    + +
    + +
    + +
    + + + + diff --git a/site/site/public/images/expedientes/es/glosario.html b/site/site/public/images/expedientes/es/glosario.html new file mode 100644 index 0000000..f183bfe --- /dev/null +++ b/site/site/public/images/expedientes/es/glosario.html @@ -0,0 +1,363 @@ + +Glosario · Expedientes
    + +
    +
    +

    Glosario

    + +
    + +
    + +
    + + + + + +
    diff --git a/site/site/public/images/expedientes/es/recaida.html b/site/site/public/images/expedientes/es/recaida.html new file mode 100644 index 0000000..b9f216c --- /dev/null +++ b/site/site/public/images/expedientes/es/recaida.html @@ -0,0 +1,92 @@ + + + + + +Expediente 404-PAP-bis — la recaída + + + +
    +

    Historia clínica · Servicio de Patología del Código

    +

    La recaída

    +

    Creíamos estar de alta. El hub /expedientes en verde, el caso cerrado, la gloria. Y entonces /nosotros dio 404. Al instante. Del logro a la miseria sin transición — un espejismo, una montaña rusa que solo baja.

    +
    Historia Nº 404-PAP-bisDiagnóstico: ANTI-PAP (recaída)Estado: ALTA
    + +
    Motivo de consulta. Paciente que, creyéndose curado, sufre recaída súbita del mismo cuadro. Refiere del susto-sorpresa al pánico y a la incertidumbre en un instante; el ánimo cae del triunfo a la depresión sin escalón intermedio. Verbaliza: "después de esto voy a necesitar una visita a la clínica". Y aquí está.
    + +
    +
    PAP
    Los principios, reglas y patrones de arquitectura del proyecto — la fuente única de la verdad.
    +
    anti-PAP
    Código escrito en contra de esas reglas; se rechaza aunque "funcione".
    +
    + +

    Etiología — el mismo patógeno, otro órgano

    +

    El Expediente 404-PAP mató un match hardcoded en render_content_or_grid (páginas de contenido). El gemelo vivía intacto en el órgano de al lado — las páginas estáticas, pages_htmx::dispatch:

    +
    match path {
    +    "/services" | "/servicios" => Some(static_page::render(env, "services", lang)),
    +    // … cada página a mano … menos /nosotros …
    +    _ => None,   // ← /nosotros caía aquí → 404
    +}
    +

    Ruta declarada, FTL presente, componente horneado — y aun así 404. El mismo anti-PAP: un match que duplica lo que routes.ncl ya sabe.

    + +

    Tratamiento — el mismo antibiótico

    +
    _ => resolve_static_page(env, path, lang),
    +// … lee del registry: cualquier ruta cuyo componente
    +// acabe en "Page" → static_page(page_id kebab). Cero arms nuevos.
    + +

    Pronóstico — y por qué esta recaída no fue como la primera

    +

    Lo revelador no es que recayera. Es cuánto duró. El primer caso, sin historia previa, costó una tarde. El gemelo, idéntico, costó minutos — porque el primero ya estaba declarado como historia clínica (content-kind-howto): fui directo al órgano, reconocí el patógeno, apliqué el mismo antibiótico.

    +
    +

    Caso 404-PAP · sin historia

    • Diagnóstico ~88 min
    • Builds 8
    • Pistas falsas 8
    +

    Caso bis · con historia clínica

    • Diagnóstico ~15 min
    • Builds 1
    • Pistas falsas 0
    +
    +

    Mismo bug. ~30× más rápido. No porque el segundo fuera más fácil — era idéntico — sino porque el primero se declaró. Eso es ontoref trabajando en tiempo real: el dao te sostiene en la recaída, el modo te da el paso, y el howto previo convierte "otra tarde perdida" en "quince minutos y un antibiótico conocido".

    + +

    Alta y profilaxis

    +

    El patógeno tiene familia: cualquier match hardcoded que sombree el registry. La vacuna es la misma para todos — leer de la fuente única. El caso se cierra registrado (static-page-howto) para que el tercer gemelo, si aparece, dure segundos.

    + +
    Servicio de Patología del Código · Hist. 404-PAP-bisSerie: con ontoref no hubiera sido así
    +
    + + diff --git a/site/site/public/images/expedientes/recaida-header.svg b/site/site/public/images/expedientes/recaida-header.svg new file mode 100644 index 0000000..a16090e --- /dev/null +++ b/site/site/public/images/expedientes/recaida-header.svg @@ -0,0 +1,18 @@ + + + + + HISTORIA CLÍNICA · PATOLOGÍA DEL CÓDIGO + + LA RECAÍDA + El mismo patógeno, otro órgano. Esta vez, minutos. + + Nº 404-PAP-bis + DIAG: ANTI-PAP · recaída + + + + ALTA MÉDICA + ~15 MIN + + diff --git a/site/site/public/images/expedientes/recaida-header.webp b/site/site/public/images/expedientes/recaida-header.webp new file mode 100644 index 0000000..cab43ff Binary files /dev/null and b/site/site/public/images/expedientes/recaida-header.webp differ diff --git a/site/site/public/images/favicon.ico b/site/site/public/images/favicon.ico new file mode 100644 index 0000000..2ba8527 Binary files /dev/null and b/site/site/public/images/favicon.ico differ diff --git a/site/site/public/images/home-hero.avif b/site/site/public/images/home-hero.avif new file mode 100644 index 0000000..405fdb9 Binary files /dev/null and b/site/site/public/images/home-hero.avif differ diff --git a/site/site/public/images/home-hero.webp b/site/site/public/images/home-hero.webp new file mode 100644 index 0000000..ae61f8a Binary files /dev/null and b/site/site/public/images/home-hero.webp differ diff --git a/site/site/public/images/jpl-imago-160.png b/site/site/public/images/jpl-imago-160.png new file mode 100644 index 0000000..a0a350d Binary files /dev/null and b/site/site/public/images/jpl-imago-160.png differ diff --git a/site/site/public/images/jpl-imago-320.png b/site/site/public/images/jpl-imago-320.png new file mode 100644 index 0000000..3a89175 Binary files /dev/null and b/site/site/public/images/jpl-imago-320.png differ diff --git a/site/site/public/images/jpl-imago-static-sig.png.svg b/site/site/public/images/jpl-imago-static-sig.png.svg new file mode 100644 index 0000000..86163d3 --- /dev/null +++ b/site/site/public/images/jpl-imago-static-sig.png.svg @@ -0,0 +1,173 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Jesús rez + \ No newline at end of file diff --git a/site/site/public/images/jpl-imago-static-sig.svg b/site/site/public/images/jpl-imago-static-sig.svg new file mode 100644 index 0000000..b3a6fde --- /dev/null +++ b/site/site/public/images/jpl-imago-static-sig.svg @@ -0,0 +1,180 @@ + + + + JPL Imago Static Signature + Jesús Pérez + https://rustelo.dev + Yin-Yang symbolic figure with signature — coexistence principle + © 2026 Jesús Pérez. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Jesús rez + \ No newline at end of file diff --git a/site/site/public/images/jpl-imago-static.svg b/site/site/public/images/jpl-imago-static.svg new file mode 100644 index 0000000..a28f32b --- /dev/null +++ b/site/site/public/images/jpl-imago-static.svg @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/site/site/public/images/logo-dark.png b/site/site/public/images/logo-dark.png new file mode 100644 index 0000000..ecf47c9 Binary files /dev/null and b/site/site/public/images/logo-dark.png differ diff --git a/site/site/public/images/logo-light.png b/site/site/public/images/logo-light.png new file mode 100644 index 0000000..1217712 Binary files /dev/null and b/site/site/public/images/logo-light.png differ diff --git a/site/site/public/images/logos/jesusperez-logo-b.png b/site/site/public/images/logos/jesusperez-logo-b.png new file mode 100644 index 0000000..ecf47c9 Binary files /dev/null and b/site/site/public/images/logos/jesusperez-logo-b.png differ diff --git a/site/site/public/images/logos/jesusperez-logo-w.png b/site/site/public/images/logos/jesusperez-logo-w.png new file mode 100644 index 0000000..1217712 Binary files /dev/null and b/site/site/public/images/logos/jesusperez-logo-w.png differ diff --git a/site/site/public/images/logos/jesusperez.png b/site/site/public/images/logos/jesusperez.png new file mode 100644 index 0000000..f4b775d Binary files /dev/null and b/site/site/public/images/logos/jesusperez.png differ diff --git a/site/site/public/images/logos/jesusperez_w.png b/site/site/public/images/logos/jesusperez_w.png new file mode 100644 index 0000000..f4b775d Binary files /dev/null and b/site/site/public/images/logos/jesusperez_w.png differ diff --git a/site/site/public/images/logos/jesusperez_w.svg b/site/site/public/images/logos/jesusperez_w.svg new file mode 100644 index 0000000..c6021de --- /dev/null +++ b/site/site/public/images/logos/jesusperez_w.svg @@ -0,0 +1,35 @@ + + + + + + + Jesús rez + + \ No newline at end of file diff --git a/site/site/public/images/logos/projects/ailinkra_v.svg b/site/site/public/images/logos/projects/ailinkra_v.svg new file mode 100644 index 0000000..020cd0d --- /dev/null +++ b/site/site/public/images/logos/projects/ailinkra_v.svg @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AI LINKRA + + \ No newline at end of file diff --git a/site/site/public/images/logos/projects/kogral.svg b/site/site/public/images/logos/projects/kogral.svg new file mode 100644 index 0000000..a2161ff --- /dev/null +++ b/site/site/public/images/logos/projects/kogral.svg @@ -0,0 +1,354 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/site/site/public/images/logos/projects/kogral_v.svg b/site/site/public/images/logos/projects/kogral_v.svg new file mode 100644 index 0000000..0cd20f9 --- /dev/null +++ b/site/site/public/images/logos/projects/kogral_v.svg @@ -0,0 +1,309 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/site/site/public/images/logos/projects/lamina_v.svg b/site/site/public/images/logos/projects/lamina_v.svg new file mode 100644 index 0000000..bc63782 --- /dev/null +++ b/site/site/public/images/logos/projects/lamina_v.svg @@ -0,0 +1,39 @@ + + + Lamina · build-layer catalog + + + + + + + + + + + + + lamina + layers + diff --git a/site/site/public/images/logos/projects/lian-build_v.svg b/site/site/public/images/logos/projects/lian-build_v.svg new file mode 100644 index 0000000..35f026c --- /dev/null +++ b/site/site/public/images/logos/projects/lian-build_v.svg @@ -0,0 +1,70 @@ + + + Lian Build + + + + + + + + + + + + + + + + + + + + + lian + build + diff --git a/site/site/public/images/logos/projects/ontoref-v.svg b/site/site/public/images/logos/projects/ontoref-v.svg new file mode 100644 index 0000000..95d7a8a --- /dev/null +++ b/site/site/public/images/logos/projects/ontoref-v.svg @@ -0,0 +1,193 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Ontoref + + + Structure that remembers why + + + diff --git a/site/site/public/images/logos/projects/ontoref.svg b/site/site/public/images/logos/projects/ontoref.svg new file mode 100644 index 0000000..180b071 --- /dev/null +++ b/site/site/public/images/logos/projects/ontoref.svg @@ -0,0 +1,193 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Ontoref + + + Structure that remembers why + + + diff --git a/site/site/public/images/logos/projects/provisioning.svg b/site/site/public/images/logos/projects/provisioning.svg new file mode 100644 index 0000000..0323a9b --- /dev/null +++ b/site/site/public/images/logos/projects/provisioning.svg @@ -0,0 +1,241 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/site/site/public/images/logos/projects/provisioning_s_b_v.svg b/site/site/public/images/logos/projects/provisioning_s_b_v.svg new file mode 100644 index 0000000..b60650e --- /dev/null +++ b/site/site/public/images/logos/projects/provisioning_s_b_v.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/site/site/public/images/logos/projects/provisioning_s_img.svg b/site/site/public/images/logos/projects/provisioning_s_img.svg new file mode 100644 index 0000000..6c3fbf4 --- /dev/null +++ b/site/site/public/images/logos/projects/provisioning_s_img.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/site/site/public/images/logos/projects/provisioning_s_v.svg b/site/site/public/images/logos/projects/provisioning_s_v.svg new file mode 100644 index 0000000..7476e07 --- /dev/null +++ b/site/site/public/images/logos/projects/provisioning_s_v.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/site/site/public/images/logos/projects/provisioning_v.svg b/site/site/public/images/logos/projects/provisioning_v.svg new file mode 100644 index 0000000..f701b3e --- /dev/null +++ b/site/site/public/images/logos/projects/provisioning_v.svg @@ -0,0 +1,243 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/site/site/public/images/logos/projects/secretumvault.svg b/site/site/public/images/logos/projects/secretumvault.svg new file mode 100644 index 0000000..90d5e0e --- /dev/null +++ b/site/site/public/images/logos/projects/secretumvault.svg @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SecretumVault + + + + diff --git a/site/site/public/images/logos/projects/secretumvault_v.svg b/site/site/public/images/logos/projects/secretumvault_v.svg new file mode 100644 index 0000000..589c58a --- /dev/null +++ b/site/site/public/images/logos/projects/secretumvault_v.svg @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SecretumVault + diff --git a/site/site/public/images/logos/projects/solera_v.svg b/site/site/public/images/logos/projects/solera_v.svg new file mode 100644 index 0000000..62a2bea --- /dev/null +++ b/site/site/public/images/logos/projects/solera_v.svg @@ -0,0 +1,39 @@ + + + Solera · runtime-image catalog + + + + + + + + + + + + + solera + runtime + diff --git a/site/site/public/images/logos/projects/stratumiops.svg b/site/site/public/images/logos/projects/stratumiops.svg new file mode 100644 index 0000000..44e711e --- /dev/null +++ b/site/site/public/images/logos/projects/stratumiops.svg @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + StratumIOps + + + + + + + + + + + + + + + diff --git a/site/site/public/images/logos/projects/stratumiops_v.svg b/site/site/public/images/logos/projects/stratumiops_v.svg new file mode 100644 index 0000000..b2407df --- /dev/null +++ b/site/site/public/images/logos/projects/stratumiops_v.svg @@ -0,0 +1,180 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + StratumIOps + + + + + + + + + diff --git a/site/site/public/images/logos/projects/syntaxis.svg b/site/site/public/images/logos/projects/syntaxis.svg new file mode 100644 index 0000000..8ac1e03 --- /dev/null +++ b/site/site/public/images/logos/projects/syntaxis.svg @@ -0,0 +1,214 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SYNTAXIS + + + Systematic Orchestration + diff --git a/site/site/public/images/logos/projects/syntaxis_v.svg b/site/site/public/images/logos/projects/syntaxis_v.svg new file mode 100644 index 0000000..1ad2928 --- /dev/null +++ b/site/site/public/images/logos/projects/syntaxis_v.svg @@ -0,0 +1,213 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SYNTAXIS + + + Systematic Orchestration + diff --git a/site/site/public/images/logos/projects/typedialog.svg b/site/site/public/images/logos/projects/typedialog.svg new file mode 100644 index 0000000..b6e35a3 --- /dev/null +++ b/site/site/public/images/logos/projects/typedialog.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + TypeDialog + diff --git a/site/site/public/images/logos/projects/typedialog_v.svg b/site/site/public/images/logos/projects/typedialog_v.svg new file mode 100644 index 0000000..c1bab71 --- /dev/null +++ b/site/site/public/images/logos/projects/typedialog_v.svg @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + TypeDialog + diff --git a/site/site/public/images/logos/projects/typedialog_w.svg b/site/site/public/images/logos/projects/typedialog_w.svg new file mode 100644 index 0000000..b6e35a3 --- /dev/null +++ b/site/site/public/images/logos/projects/typedialog_w.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + TypeDialog + diff --git a/site/site/public/images/logos/projects/vapora.svg b/site/site/public/images/logos/projects/vapora.svg new file mode 100644 index 0000000..2245824 --- /dev/null +++ b/site/site/public/images/logos/projects/vapora.svg @@ -0,0 +1,197 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + VAPORA + + + + VAPORA + + + + + + VAPORA + + + + + Evaporate complexity + + + + + + + + + + + + + + + + diff --git a/site/site/public/images/logos/projects/vapora_v.svg b/site/site/public/images/logos/projects/vapora_v.svg new file mode 100644 index 0000000..a02f317 --- /dev/null +++ b/site/site/public/images/logos/projects/vapora_v.svg @@ -0,0 +1,194 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + VAPORA + + + + VAPORA + + + + + + VAPORA + + + + + Evaporate complexity + + + + + + + + + + + + + + + + diff --git a/site/site/public/images/logos/projects/vapora_w.svg b/site/site/public/images/logos/projects/vapora_w.svg new file mode 100644 index 0000000..bee977f --- /dev/null +++ b/site/site/public/images/logos/projects/vapora_w.svg @@ -0,0 +1,197 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + VAPORA + + + + VAPORA + + + + + + VAPORA + + + + + Evaporate complexity + + + + + + + + + + + + + + + + diff --git a/site/site/public/images/logos/projects/vapora_w_v.svg b/site/site/public/images/logos/projects/vapora_w_v.svg new file mode 100644 index 0000000..fa16c94 --- /dev/null +++ b/site/site/public/images/logos/projects/vapora_w_v.svg @@ -0,0 +1,194 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + VAPORA + + + + VAPORA + + + + + + VAPORA + + + + + Evaporate complexity + + + + + + + + + + + + + + + + diff --git a/site/site/public/images/logos/rustelo-imag.svg b/site/site/public/images/logos/rustelo-imag.svg new file mode 100644 index 0000000..c22850d --- /dev/null +++ b/site/site/public/images/logos/rustelo-imag.svg @@ -0,0 +1,289 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/site/site/public/images/logos/rustelo-image.ascii b/site/site/public/images/logos/rustelo-image.ascii new file mode 100644 index 0000000..e69de29 diff --git a/site/site/public/images/logos/rustelo-img.svg b/site/site/public/images/logos/rustelo-img.svg new file mode 100644 index 0000000..c22850d --- /dev/null +++ b/site/site/public/images/logos/rustelo-img.svg @@ -0,0 +1,289 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/site/site/public/images/logos/rustelo.svg b/site/site/public/images/logos/rustelo.svg new file mode 100644 index 0000000..13dc0d5 --- /dev/null +++ b/site/site/public/images/logos/rustelo.svg @@ -0,0 +1,307 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Rustelo + dev + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/site/site/public/images/logos/rustelo_dev-logo-b-h.svg b/site/site/public/images/logos/rustelo_dev-logo-b-h.svg new file mode 100644 index 0000000..13dc0d5 --- /dev/null +++ b/site/site/public/images/logos/rustelo_dev-logo-b-h.svg @@ -0,0 +1,307 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Rustelo + dev + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/site/site/public/images/logos/rustelo_dev-logo-b-v.svg b/site/site/public/images/logos/rustelo_dev-logo-b-v.svg new file mode 100644 index 0000000..070bca2 --- /dev/null +++ b/site/site/public/images/logos/rustelo_dev-logo-b-v.svg @@ -0,0 +1,307 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dev + Rustelo + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/site/site/public/images/logos/rustelo_dev-logo-h.svg b/site/site/public/images/logos/rustelo_dev-logo-h.svg new file mode 100644 index 0000000..5576400 --- /dev/null +++ b/site/site/public/images/logos/rustelo_dev-logo-h.svg @@ -0,0 +1,307 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Rustelo + dev + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/site/site/public/images/logos/rustelo_dev-logo-v.svg b/site/site/public/images/logos/rustelo_dev-logo-v.svg new file mode 100644 index 0000000..65c3822 --- /dev/null +++ b/site/site/public/images/logos/rustelo_dev-logo-v.svg @@ -0,0 +1,307 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dev + Rustelo + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/site/site/public/images/logos/rustelo_v.svg b/site/site/public/images/logos/rustelo_v.svg new file mode 100644 index 0000000..070bca2 --- /dev/null +++ b/site/site/public/images/logos/rustelo_v.svg @@ -0,0 +1,307 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dev + Rustelo + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/site/site/public/images/logos/rustelo_w.svg b/site/site/public/images/logos/rustelo_w.svg new file mode 100644 index 0000000..5576400 --- /dev/null +++ b/site/site/public/images/logos/rustelo_w.svg @@ -0,0 +1,307 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Rustelo + dev + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/site/site/public/images/logos/rustelo_w_v.svg b/site/site/public/images/logos/rustelo_w_v.svg new file mode 100644 index 0000000..65c3822 --- /dev/null +++ b/site/site/public/images/logos/rustelo_w_v.svg @@ -0,0 +1,307 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dev + Rustelo + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/site/site/public/images/logos/site-logo-b.png b/site/site/public/images/logos/site-logo-b.png new file mode 100644 index 0000000..ce385b8 Binary files /dev/null and b/site/site/public/images/logos/site-logo-b.png differ diff --git a/site/site/public/images/logos/site-logo-w.png b/site/site/public/images/logos/site-logo-w.png new file mode 100644 index 0000000..93920de Binary files /dev/null and b/site/site/public/images/logos/site-logo-w.png differ diff --git a/site/site/public/images/ontology-live-with-code-post.avif b/site/site/public/images/ontology-live-with-code-post.avif new file mode 100644 index 0000000..5dfa203 Binary files /dev/null and b/site/site/public/images/ontology-live-with-code-post.avif differ diff --git a/site/site/public/images/ontology-live-with-code-post.webp b/site/site/public/images/ontology-live-with-code-post.webp new file mode 100644 index 0000000..3a81e99 Binary files /dev/null and b/site/site/public/images/ontology-live-with-code-post.webp differ diff --git a/site/site/public/images/ontology-reflection-post.avif b/site/site/public/images/ontology-reflection-post.avif new file mode 100644 index 0000000..ab60840 Binary files /dev/null and b/site/site/public/images/ontology-reflection-post.avif differ diff --git a/site/site/public/images/ontology-reflection-post.webp b/site/site/public/images/ontology-reflection-post.webp new file mode 100644 index 0000000..7106c98 Binary files /dev/null and b/site/site/public/images/ontology-reflection-post.webp differ diff --git a/site/site/public/images/ontoref-about-en.avif b/site/site/public/images/ontoref-about-en.avif new file mode 100644 index 0000000..d6daa7c Binary files /dev/null and b/site/site/public/images/ontoref-about-en.avif differ diff --git a/site/site/public/images/ontoref-about-en.webp b/site/site/public/images/ontoref-about-en.webp new file mode 100644 index 0000000..381d8c6 Binary files /dev/null and b/site/site/public/images/ontoref-about-en.webp differ diff --git a/site/site/public/images/ontoref-about-es.avif b/site/site/public/images/ontoref-about-es.avif new file mode 100644 index 0000000..4fe6e89 Binary files /dev/null and b/site/site/public/images/ontoref-about-es.avif differ diff --git a/site/site/public/images/ontoref-about-es.webp b/site/site/public/images/ontoref-about-es.webp new file mode 100644 index 0000000..deb2a80 Binary files /dev/null and b/site/site/public/images/ontoref-about-es.webp differ diff --git a/site/site/public/images/ontoref-accion.avif b/site/site/public/images/ontoref-accion.avif new file mode 100644 index 0000000..5890f76 Binary files /dev/null and b/site/site/public/images/ontoref-accion.avif differ diff --git a/site/site/public/images/ontoref-accion.webp b/site/site/public/images/ontoref-accion.webp new file mode 100644 index 0000000..e92799e Binary files /dev/null and b/site/site/public/images/ontoref-accion.webp differ diff --git a/site/site/public/images/ontoref-action.avif b/site/site/public/images/ontoref-action.avif new file mode 100644 index 0000000..db27c21 Binary files /dev/null and b/site/site/public/images/ontoref-action.avif differ diff --git a/site/site/public/images/ontoref-action.webp b/site/site/public/images/ontoref-action.webp new file mode 100644 index 0000000..7591789 Binary files /dev/null and b/site/site/public/images/ontoref-action.webp differ diff --git a/site/site/public/images/ontoref-ai-knowledge-post.avif b/site/site/public/images/ontoref-ai-knowledge-post.avif new file mode 100644 index 0000000..3980d58 Binary files /dev/null and b/site/site/public/images/ontoref-ai-knowledge-post.avif differ diff --git a/site/site/public/images/ontoref-ai-knowledge-post.webp b/site/site/public/images/ontoref-ai-knowledge-post.webp new file mode 100644 index 0000000..64ef831 Binary files /dev/null and b/site/site/public/images/ontoref-ai-knowledge-post.webp differ diff --git a/site/site/public/images/ontoref-architecture.svg b/site/site/public/images/ontoref-architecture.svg new file mode 100644 index 0000000..e21fb49 --- /dev/null +++ b/site/site/public/images/ontoref-architecture.svg @@ -0,0 +1,114 @@ + + + + + + + + + + + + ontoref + architecture · self-describing protocol + v0.1.7 + + + + + + CONSTELLATION · ADR-062 + ONTOREF_PROJECT_ROOT = ontoref/ · spine .ontoref/ · ONTOREF_ROOT = code/ + members: code Primary · outreach · vault · desktop Addon · assets · examples Projection + + + + + PROTOCOL · DECLARATIVE · NICKEL + .ontoref/ — type-safe contracts, fail at definition time + + + ontology/ + 9 axioms · 6 tensions + 41 practices · 163 edges + + + adrs/ + 65 records · 58 accepted + typed constraints · ondaod + + + reflection/schemas/ + 26 schemas + 8 forms · migrations + + + positioning/ + audiences · value-props + ADR-035 · ADR-057 reveal + + + + + TOOLING · OPERATIONAL · NUSHELL + actor-aware dispatch · advisory lock · NICKEL_IMPORT_PATH + + + code/ontoref + bash · actor detect + + + + + ontoref.nu + Nu dispatcher + + + + + reflection/modules/ · 35 Nushell modules + 23 DAG modes · describe · adr · backlog · sync · graph · view · qa · store · … + + + + + CRATES · RUST + daemon optional — CLI never requires it (ADR-029 tier coexistence) + + + ontoref-ontology + .ontoref/*.ncl → + typed Rust structs · axum-free + + + ontoref-reflection + load · validate · + execute NCL DAG modes + + + ontoref-daemon · axum HTTP (optional) + 49 MCP tools · 21 UI routes · DashMap cache · file watcher · actor registry + stratum-db substrate · substrate views (ADR-046/047) · Ed25519 witness + + + + + SELF-DESCRIPTION · ontoref consumes ontoref + FSM 4 state dimensions · 21 capabilities · 56-node knowledge graph · 5 declared layers + protocol-maturity · self-description-coverage · ecosystem-integration · operational-mode — ADR-001 self-describing axiom in practice + + + + + + nickel export + reads · executes + describes + + + generated from .ontoref/ontology + adrs — counts are a live projection, not hand-maintained + forgejo canonical · github mirror · rad peer + diff --git a/site/site/public/images/ontoref-authoring-en.avif b/site/site/public/images/ontoref-authoring-en.avif new file mode 100644 index 0000000..0e547ad Binary files /dev/null and b/site/site/public/images/ontoref-authoring-en.avif differ diff --git a/site/site/public/images/ontoref-authoring-en.webp b/site/site/public/images/ontoref-authoring-en.webp new file mode 100644 index 0000000..02fe05e Binary files /dev/null and b/site/site/public/images/ontoref-authoring-en.webp differ diff --git a/site/site/public/images/ontoref-authoring-es.avif b/site/site/public/images/ontoref-authoring-es.avif new file mode 100644 index 0000000..9711df8 Binary files /dev/null and b/site/site/public/images/ontoref-authoring-es.avif differ diff --git a/site/site/public/images/ontoref-authoring-es.webp b/site/site/public/images/ontoref-authoring-es.webp new file mode 100644 index 0000000..3aa6256 Binary files /dev/null and b/site/site/public/images/ontoref-authoring-es.webp differ diff --git a/site/site/public/images/ontoref-coaching-en.avif b/site/site/public/images/ontoref-coaching-en.avif new file mode 100644 index 0000000..4716c70 Binary files /dev/null and b/site/site/public/images/ontoref-coaching-en.avif differ diff --git a/site/site/public/images/ontoref-coaching-en.webp b/site/site/public/images/ontoref-coaching-en.webp new file mode 100644 index 0000000..50ee506 Binary files /dev/null and b/site/site/public/images/ontoref-coaching-en.webp differ diff --git a/site/site/public/images/ontoref-coaching-es.avif b/site/site/public/images/ontoref-coaching-es.avif new file mode 100644 index 0000000..23be340 Binary files /dev/null and b/site/site/public/images/ontoref-coaching-es.avif differ diff --git a/site/site/public/images/ontoref-coaching-es.webp b/site/site/public/images/ontoref-coaching-es.webp new file mode 100644 index 0000000..90ac2a7 Binary files /dev/null and b/site/site/public/images/ontoref-coaching-es.webp differ diff --git a/site/site/public/images/ontoref-coaching-training-action.avif b/site/site/public/images/ontoref-coaching-training-action.avif new file mode 100644 index 0000000..e46fc0b Binary files /dev/null and b/site/site/public/images/ontoref-coaching-training-action.avif differ diff --git a/site/site/public/images/ontoref-coaching-training-action.webp b/site/site/public/images/ontoref-coaching-training-action.webp new file mode 100644 index 0000000..99a35ba Binary files /dev/null and b/site/site/public/images/ontoref-coaching-training-action.webp differ diff --git a/site/site/public/images/ontoref-confianza-sello-post.avif b/site/site/public/images/ontoref-confianza-sello-post.avif new file mode 100644 index 0000000..7fed966 Binary files /dev/null and b/site/site/public/images/ontoref-confianza-sello-post.avif differ diff --git a/site/site/public/images/ontoref-confianza-sello-post.webp b/site/site/public/images/ontoref-confianza-sello-post.webp new file mode 100644 index 0000000..430452f Binary files /dev/null and b/site/site/public/images/ontoref-confianza-sello-post.webp differ diff --git a/site/site/public/images/ontoref-contact-en.avif b/site/site/public/images/ontoref-contact-en.avif new file mode 100644 index 0000000..58a1a2e Binary files /dev/null and b/site/site/public/images/ontoref-contact-en.avif differ diff --git a/site/site/public/images/ontoref-contact-en.webp b/site/site/public/images/ontoref-contact-en.webp new file mode 100644 index 0000000..b71c661 Binary files /dev/null and b/site/site/public/images/ontoref-contact-en.webp differ diff --git a/site/site/public/images/ontoref-contactar-v1.avif b/site/site/public/images/ontoref-contactar-v1.avif new file mode 100644 index 0000000..a80e8b0 Binary files /dev/null and b/site/site/public/images/ontoref-contactar-v1.avif differ diff --git a/site/site/public/images/ontoref-contactar-v1.webp b/site/site/public/images/ontoref-contactar-v1.webp new file mode 100644 index 0000000..9fa6345 Binary files /dev/null and b/site/site/public/images/ontoref-contactar-v1.webp differ diff --git a/site/site/public/images/ontoref-contactar.avif b/site/site/public/images/ontoref-contactar.avif new file mode 100644 index 0000000..4c3a796 Binary files /dev/null and b/site/site/public/images/ontoref-contactar.avif differ diff --git a/site/site/public/images/ontoref-contactar.webp b/site/site/public/images/ontoref-contactar.webp new file mode 100644 index 0000000..6ec3c96 Binary files /dev/null and b/site/site/public/images/ontoref-contactar.webp differ diff --git a/site/site/public/images/ontoref-context-declared-not-filled-es.avif b/site/site/public/images/ontoref-context-declared-not-filled-es.avif new file mode 100644 index 0000000..3b52ae1 Binary files /dev/null and b/site/site/public/images/ontoref-context-declared-not-filled-es.avif differ diff --git a/site/site/public/images/ontoref-context-declared-not-filled-es.png b/site/site/public/images/ontoref-context-declared-not-filled-es.png new file mode 100644 index 0000000..40b600f Binary files /dev/null and b/site/site/public/images/ontoref-context-declared-not-filled-es.png differ diff --git a/site/site/public/images/ontoref-context-declared-not-filled-es.webp b/site/site/public/images/ontoref-context-declared-not-filled-es.webp new file mode 100644 index 0000000..94ec947 Binary files /dev/null and b/site/site/public/images/ontoref-context-declared-not-filled-es.webp differ diff --git a/site/site/public/images/ontoref-context-declared-not-filled.avif b/site/site/public/images/ontoref-context-declared-not-filled.avif new file mode 100644 index 0000000..708ce37 Binary files /dev/null and b/site/site/public/images/ontoref-context-declared-not-filled.avif differ diff --git a/site/site/public/images/ontoref-context-declared-not-filled.png b/site/site/public/images/ontoref-context-declared-not-filled.png new file mode 100644 index 0000000..253a45e Binary files /dev/null and b/site/site/public/images/ontoref-context-declared-not-filled.png differ diff --git a/site/site/public/images/ontoref-context-declared-not-filled.webp b/site/site/public/images/ontoref-context-declared-not-filled.webp new file mode 100644 index 0000000..6c431c5 Binary files /dev/null and b/site/site/public/images/ontoref-context-declared-not-filled.webp differ diff --git a/site/site/public/images/ontoref-context-declared-post.avif b/site/site/public/images/ontoref-context-declared-post.avif new file mode 100644 index 0000000..708ce37 Binary files /dev/null and b/site/site/public/images/ontoref-context-declared-post.avif differ diff --git a/site/site/public/images/ontoref-context-declared-post.png b/site/site/public/images/ontoref-context-declared-post.png new file mode 100644 index 0000000..253a45e Binary files /dev/null and b/site/site/public/images/ontoref-context-declared-post.png differ diff --git a/site/site/public/images/ontoref-context-declared-post.webp b/site/site/public/images/ontoref-context-declared-post.webp new file mode 100644 index 0000000..6c431c5 Binary files /dev/null and b/site/site/public/images/ontoref-context-declared-post.webp differ diff --git a/site/site/public/images/ontoref-dags-everywhere_post.avif b/site/site/public/images/ontoref-dags-everywhere_post.avif new file mode 100644 index 0000000..5e2429b Binary files /dev/null and b/site/site/public/images/ontoref-dags-everywhere_post.avif differ diff --git a/site/site/public/images/ontoref-dags-everywhere_post.webp b/site/site/public/images/ontoref-dags-everywhere_post.webp new file mode 100644 index 0000000..1172337 Binary files /dev/null and b/site/site/public/images/ontoref-dags-everywhere_post.webp differ diff --git a/site/site/public/images/ontoref-developer-en.avif b/site/site/public/images/ontoref-developer-en.avif new file mode 100644 index 0000000..e13f05b Binary files /dev/null and b/site/site/public/images/ontoref-developer-en.avif differ diff --git a/site/site/public/images/ontoref-developer-en.webp b/site/site/public/images/ontoref-developer-en.webp new file mode 100644 index 0000000..ea13541 Binary files /dev/null and b/site/site/public/images/ontoref-developer-en.webp differ diff --git a/site/site/public/images/ontoref-developer-es.avif b/site/site/public/images/ontoref-developer-es.avif new file mode 100644 index 0000000..d4e52ff Binary files /dev/null and b/site/site/public/images/ontoref-developer-es.avif differ diff --git a/site/site/public/images/ontoref-developer-es.webp b/site/site/public/images/ontoref-developer-es.webp new file mode 100644 index 0000000..854724d Binary files /dev/null and b/site/site/public/images/ontoref-developer-es.webp differ diff --git a/site/site/public/images/ontoref-diagram.svg b/site/site/public/images/ontoref-diagram.svg new file mode 100644 index 0000000..2bd724e --- /dev/null +++ b/site/site/public/images/ontoref-diagram.svg @@ -0,0 +1,298 @@ + + + ontorefontology graph · 60 nodes · 171 edges + Axioms · 9 + Tensions · 6 + Practices · 45 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Protocol, Not Runtime + + Self-Describing + + Voluntary Adoption + + Internal Coherence Enforce… + + DAG-Formalized Knowledge + + Ontology Axis — Substanc… + + Reflection Axis — Act + + Witness as Axis Seam + + Sufficient Verification ov… + + Formalization vs Adoption… + + Ontology vs Reflection + + Openness vs Sustainability + + Plane Habitability — Coe… + + Enforcement vs Emergence �… + + Reach vs Qualification —… + + Governed Delivery — Witn… + + ADR Lifecycle + + Reflection Modes + + Coder Process Memory + + Describe Query Layer + + Ontoref Ontology Crate + + Ontoref Reflection Crate + + Adopt Ontoref Tooling + + Protocol Migration System + + Ontology Three-File Split + + ADR–Node Declared Linkag… + + Web Presence + + Level-Aware About Projecti… + + Ontoref Daemon + + API Catalog Surface + + Unified Auth Model + + Project Onboarding + + Daemon Config Management + + Q&A Knowledge Store + + Quick Actions Catalog + + Personal Ontology Schemas + + Content & Career Reflectio… + + Search Bookmarks + + Passive Drift Observation + + Manifest Self-Interrogatio… + + Config Surface + + Domain Extension System + + VCS Abstraction Layer + + Constellation Member Taxon… + + Constellation Reconciliati… + + Agent Workspace Orchestrat… + + MCP Server Surface + + GraphQL API Surface + + Registry Credential Vault + + Level Hierarchy and Mode R… + + CI/CD Pipelines + + Tier Coexistence as Perman… + + Read Tensions First (ondao… + + Positioning Layer + + Protocol Anti-Pattern Cata… + + Interaction Trace + + OCI Distribution + + Docsite Projection — mdB… + + Audit Harness — Converge… + + Graph-Integrity Validation… + \ No newline at end of file diff --git a/site/site/public/images/ontoref-formacion-accion.avif b/site/site/public/images/ontoref-formacion-accion.avif new file mode 100644 index 0000000..8456ed9 Binary files /dev/null and b/site/site/public/images/ontoref-formacion-accion.avif differ diff --git a/site/site/public/images/ontoref-formacion-accion.webp b/site/site/public/images/ontoref-formacion-accion.webp new file mode 100644 index 0000000..d5afbd8 Binary files /dev/null and b/site/site/public/images/ontoref-formacion-accion.webp differ diff --git a/site/site/public/images/ontoref-formacion.avif b/site/site/public/images/ontoref-formacion.avif new file mode 100644 index 0000000..1d5b1b3 Binary files /dev/null and b/site/site/public/images/ontoref-formacion.avif differ diff --git a/site/site/public/images/ontoref-formacion.webp b/site/site/public/images/ontoref-formacion.webp new file mode 100644 index 0000000..ab4acc7 Binary files /dev/null and b/site/site/public/images/ontoref-formacion.webp differ diff --git a/site/site/public/images/ontoref-graph.html b/site/site/public/images/ontoref-graph.html new file mode 100644 index 0000000..f98e8bd --- /dev/null +++ b/site/site/public/images/ontoref-graph.html @@ -0,0 +1,91 @@ + + + + + +ontoref · ontology graph + + + +
    + ontoref + ontology graph · 60 nodes · 171 edges + ← About +
    +
    +
    +
    Axioms · 9
    +
    Tensions · 6
    +
    Practices · 45
    +
    +
    drag to pan · scroll to zoom · click a node to focus · click empty space to reset
    + + + + + + + + diff --git a/site/site/public/images/ontoref-home-seguro.avif b/site/site/public/images/ontoref-home-seguro.avif new file mode 100644 index 0000000..c138a54 Binary files /dev/null and b/site/site/public/images/ontoref-home-seguro.avif differ diff --git a/site/site/public/images/ontoref-home-seguro.webp b/site/site/public/images/ontoref-home-seguro.webp new file mode 100644 index 0000000..a460e42 Binary files /dev/null and b/site/site/public/images/ontoref-home-seguro.webp differ diff --git a/site/site/public/images/ontoref-home-sure-footed.avif b/site/site/public/images/ontoref-home-sure-footed.avif new file mode 100644 index 0000000..ef840c9 Binary files /dev/null and b/site/site/public/images/ontoref-home-sure-footed.avif differ diff --git a/site/site/public/images/ontoref-home-sure-footed.webp b/site/site/public/images/ontoref-home-sure-footed.webp new file mode 100644 index 0000000..c5cab63 Binary files /dev/null and b/site/site/public/images/ontoref-home-sure-footed.webp differ diff --git a/site/site/public/images/ontoref-infra-en.avif b/site/site/public/images/ontoref-infra-en.avif new file mode 100644 index 0000000..84d1f83 Binary files /dev/null and b/site/site/public/images/ontoref-infra-en.avif differ diff --git a/site/site/public/images/ontoref-infra-en.webp b/site/site/public/images/ontoref-infra-en.webp new file mode 100644 index 0000000..d8a2ba2 Binary files /dev/null and b/site/site/public/images/ontoref-infra-en.webp differ diff --git a/site/site/public/images/ontoref-infra-es.avif b/site/site/public/images/ontoref-infra-es.avif new file mode 100644 index 0000000..31b7b75 Binary files /dev/null and b/site/site/public/images/ontoref-infra-es.avif differ diff --git a/site/site/public/images/ontoref-infra-es.webp b/site/site/public/images/ontoref-infra-es.webp new file mode 100644 index 0000000..199e6c4 Binary files /dev/null and b/site/site/public/images/ontoref-infra-es.webp differ diff --git a/site/site/public/images/ontoref-logo.svg b/site/site/public/images/ontoref-logo.svg new file mode 100644 index 0000000..e49870f --- /dev/null +++ b/site/site/public/images/ontoref-logo.svg @@ -0,0 +1,190 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Ontoref + + + diff --git a/site/site/public/images/ontoref-one-protocol-multiple-subjects-es.avif b/site/site/public/images/ontoref-one-protocol-multiple-subjects-es.avif new file mode 100644 index 0000000..6b0ab78 Binary files /dev/null and b/site/site/public/images/ontoref-one-protocol-multiple-subjects-es.avif differ diff --git a/site/site/public/images/ontoref-one-protocol-multiple-subjects-es.png b/site/site/public/images/ontoref-one-protocol-multiple-subjects-es.png new file mode 100644 index 0000000..e8b751e Binary files /dev/null and b/site/site/public/images/ontoref-one-protocol-multiple-subjects-es.png differ diff --git a/site/site/public/images/ontoref-one-protocol-multiple-subjects-es.webp b/site/site/public/images/ontoref-one-protocol-multiple-subjects-es.webp new file mode 100644 index 0000000..66eaf58 Binary files /dev/null and b/site/site/public/images/ontoref-one-protocol-multiple-subjects-es.webp differ diff --git a/site/site/public/images/ontoref-one-protocol-multiple-subjects.avif b/site/site/public/images/ontoref-one-protocol-multiple-subjects.avif new file mode 100644 index 0000000..ebd4c6b Binary files /dev/null and b/site/site/public/images/ontoref-one-protocol-multiple-subjects.avif differ diff --git a/site/site/public/images/ontoref-one-protocol-multiple-subjects.png b/site/site/public/images/ontoref-one-protocol-multiple-subjects.png new file mode 100644 index 0000000..a53d636 Binary files /dev/null and b/site/site/public/images/ontoref-one-protocol-multiple-subjects.png differ diff --git a/site/site/public/images/ontoref-one-protocol-multiple-subjects.webp b/site/site/public/images/ontoref-one-protocol-multiple-subjects.webp new file mode 100644 index 0000000..96033f0 Binary files /dev/null and b/site/site/public/images/ontoref-one-protocol-multiple-subjects.webp differ diff --git a/site/site/public/images/ontoref-personal-en.avif b/site/site/public/images/ontoref-personal-en.avif new file mode 100644 index 0000000..b7a5117 Binary files /dev/null and b/site/site/public/images/ontoref-personal-en.avif differ diff --git a/site/site/public/images/ontoref-personal-en.webp b/site/site/public/images/ontoref-personal-en.webp new file mode 100644 index 0000000..3596510 Binary files /dev/null and b/site/site/public/images/ontoref-personal-en.webp differ diff --git a/site/site/public/images/ontoref-personal-es.avif b/site/site/public/images/ontoref-personal-es.avif new file mode 100644 index 0000000..ec0ad73 Binary files /dev/null and b/site/site/public/images/ontoref-personal-es.avif differ diff --git a/site/site/public/images/ontoref-personal-es.webp b/site/site/public/images/ontoref-personal-es.webp new file mode 100644 index 0000000..8d33154 Binary files /dev/null and b/site/site/public/images/ontoref-personal-es.webp differ diff --git a/site/site/public/images/ontoref-release-from-model.avif b/site/site/public/images/ontoref-release-from-model.avif new file mode 100644 index 0000000..3033f15 Binary files /dev/null and b/site/site/public/images/ontoref-release-from-model.avif differ diff --git a/site/site/public/images/ontoref-release-from-model.webp b/site/site/public/images/ontoref-release-from-model.webp new file mode 100644 index 0000000..c4bcde4 Binary files /dev/null and b/site/site/public/images/ontoref-release-from-model.webp differ diff --git a/site/site/public/images/ontoref-release-one-model-post.avif b/site/site/public/images/ontoref-release-one-model-post.avif new file mode 100644 index 0000000..9d1465f Binary files /dev/null and b/site/site/public/images/ontoref-release-one-model-post.avif differ diff --git a/site/site/public/images/ontoref-release-one-model-post.webp b/site/site/public/images/ontoref-release-one-model-post.webp new file mode 100644 index 0000000..92cf0c1 Binary files /dev/null and b/site/site/public/images/ontoref-release-one-model-post.webp differ diff --git a/site/site/public/images/ontoref-seven-sins-post-es.avif b/site/site/public/images/ontoref-seven-sins-post-es.avif new file mode 100644 index 0000000..4bcdb5b Binary files /dev/null and b/site/site/public/images/ontoref-seven-sins-post-es.avif differ diff --git a/site/site/public/images/ontoref-seven-sins-post-es.png b/site/site/public/images/ontoref-seven-sins-post-es.png new file mode 100644 index 0000000..e4e134d Binary files /dev/null and b/site/site/public/images/ontoref-seven-sins-post-es.png differ diff --git a/site/site/public/images/ontoref-seven-sins-post-es.webp b/site/site/public/images/ontoref-seven-sins-post-es.webp new file mode 100644 index 0000000..6b4bea8 Binary files /dev/null and b/site/site/public/images/ontoref-seven-sins-post-es.webp differ diff --git a/site/site/public/images/ontoref-seven-sins-post.avif b/site/site/public/images/ontoref-seven-sins-post.avif new file mode 100644 index 0000000..deffef5 Binary files /dev/null and b/site/site/public/images/ontoref-seven-sins-post.avif differ diff --git a/site/site/public/images/ontoref-seven-sins-post.png b/site/site/public/images/ontoref-seven-sins-post.png new file mode 100644 index 0000000..ed45e3f Binary files /dev/null and b/site/site/public/images/ontoref-seven-sins-post.png differ diff --git a/site/site/public/images/ontoref-seven-sins-post.webp b/site/site/public/images/ontoref-seven-sins-post.webp new file mode 100644 index 0000000..3eede94 Binary files /dev/null and b/site/site/public/images/ontoref-seven-sins-post.webp differ diff --git a/site/site/public/images/ontoref-taller-actividad.avif b/site/site/public/images/ontoref-taller-actividad.avif new file mode 100644 index 0000000..05f821e Binary files /dev/null and b/site/site/public/images/ontoref-taller-actividad.avif differ diff --git a/site/site/public/images/ontoref-taller-actividad.webp b/site/site/public/images/ontoref-taller-actividad.webp new file mode 100644 index 0000000..aeb9236 Binary files /dev/null and b/site/site/public/images/ontoref-taller-actividad.webp differ diff --git a/site/site/public/images/ontoref-training-action-en.avif b/site/site/public/images/ontoref-training-action-en.avif new file mode 100644 index 0000000..4574749 Binary files /dev/null and b/site/site/public/images/ontoref-training-action-en.avif differ diff --git a/site/site/public/images/ontoref-training-action-en.webp b/site/site/public/images/ontoref-training-action-en.webp new file mode 100644 index 0000000..82aaac7 Binary files /dev/null and b/site/site/public/images/ontoref-training-action-en.webp differ diff --git a/site/site/public/images/ontoref-training.avif b/site/site/public/images/ontoref-training.avif new file mode 100644 index 0000000..3aa2d87 Binary files /dev/null and b/site/site/public/images/ontoref-training.avif differ diff --git a/site/site/public/images/ontoref-training.webp b/site/site/public/images/ontoref-training.webp new file mode 100644 index 0000000..3e2ea12 Binary files /dev/null and b/site/site/public/images/ontoref-training.webp differ diff --git a/site/site/public/images/ontoref-trust-output-post.avif b/site/site/public/images/ontoref-trust-output-post.avif new file mode 100644 index 0000000..d7e0e8e Binary files /dev/null and b/site/site/public/images/ontoref-trust-output-post.avif differ diff --git a/site/site/public/images/ontoref-trust-output-post.webp b/site/site/public/images/ontoref-trust-output-post.webp new file mode 100644 index 0000000..543f83b Binary files /dev/null and b/site/site/public/images/ontoref-trust-output-post.webp differ diff --git a/site/site/public/images/ontoref-trust-seal-post.avif b/site/site/public/images/ontoref-trust-seal-post.avif new file mode 100644 index 0000000..f128703 Binary files /dev/null and b/site/site/public/images/ontoref-trust-seal-post.avif differ diff --git a/site/site/public/images/ontoref-trust-seal-post.webp b/site/site/public/images/ontoref-trust-seal-post.webp new file mode 100644 index 0000000..48408df Binary files /dev/null and b/site/site/public/images/ontoref-trust-seal-post.webp differ diff --git a/site/site/public/images/ontoref-workshop-talk.avif b/site/site/public/images/ontoref-workshop-talk.avif new file mode 100644 index 0000000..726af04 Binary files /dev/null and b/site/site/public/images/ontoref-workshop-talk.avif differ diff --git a/site/site/public/images/ontoref-workshop-talk.webp b/site/site/public/images/ontoref-workshop-talk.webp new file mode 100644 index 0000000..0992bb2 Binary files /dev/null and b/site/site/public/images/ontoref-workshop-talk.webp differ diff --git a/site/site/public/images/ontoref_home_hero_en.avif b/site/site/public/images/ontoref_home_hero_en.avif new file mode 100644 index 0000000..b0462fe Binary files /dev/null and b/site/site/public/images/ontoref_home_hero_en.avif differ diff --git a/site/site/public/images/ontoref_home_hero_en.webp b/site/site/public/images/ontoref_home_hero_en.webp new file mode 100644 index 0000000..bba62ee Binary files /dev/null and b/site/site/public/images/ontoref_home_hero_en.webp differ diff --git a/site/site/public/images/ontoref_home_hero_es.avif b/site/site/public/images/ontoref_home_hero_es.avif new file mode 100644 index 0000000..144a116 Binary files /dev/null and b/site/site/public/images/ontoref_home_hero_es.avif differ diff --git a/site/site/public/images/ontoref_home_hero_es.webp b/site/site/public/images/ontoref_home_hero_es.webp new file mode 100644 index 0000000..595e781 Binary files /dev/null and b/site/site/public/images/ontoref_home_hero_es.webp differ diff --git a/site/site/public/images/ontoref_v.svg b/site/site/public/images/ontoref_v.svg new file mode 100644 index 0000000..65c6c7a --- /dev/null +++ b/site/site/public/images/ontoref_v.svg @@ -0,0 +1,190 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Ontoref + + + diff --git a/site/site/public/images/projects/ontoref_architecture-dark.svg b/site/site/public/images/projects/ontoref_architecture-dark.svg new file mode 100644 index 0000000..3f2b5f9 --- /dev/null +++ b/site/site/public/images/projects/ontoref_architecture-dark.svg @@ -0,0 +1,273 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + PROTOCOL LAYERS + + + + + + + + DECLARATIVE · NICKEL + type-safe contracts · fails at definition time + + + .ontology/ + + adrs/*.ncl + + modes/*.ncl + + schemas/*.ncl + + qa.ncl + + config.ncl + + + nickel export → JSON + + + + + KNOWLEDGE GRAPH · .ontology/ + self-describing · actor-agnostic · machine-queryable + + + axioms + + tensions + + practices + + nodes + edges + + invariants + + gates + + + reads KG via nickel export + + + + + OPERATIONAL · NUSHELL + 16 modules · DAG-validated modes · typed pipelines + + + adr.nu + + backlog.nu + + describe.nu + + sync.nu + + coder.nu + + +11 modules + + + dispatches command + actor + + + + + ENTRY POINT · BASH → NU + actor detect · advisory lock · NICKEL_IMPORT_PATH + + + ./ontoref + + actor detect + + advisory lock + + → ontoref.nu + + + optional · caches · serves · never required + + + + + RUNTIME · RUST + axum (optional daemon) + 10 UI pages · 19 MCP tools · actor registry · search · SurrealDB? + + + ontoref-daemon + + axum HTTP + + MCP ×19 + + DashMap + + search + + SurrealDB? + + + configures · ONTOREF_PROJECT_ROOT + + + + + ADOPTION · PER-PROJECT + templates/ · one ontoref checkout · zero lock-in + + + templates/ + + adopt_ontoref + + ONTOREF_PROJECT_ROOT + + zero lock-in + + plain NCL + + + + + + REQUEST FLOWS + + + CLI path — developer · agent · CI + + + + + Actor + dev · agent · CI + + + + + ./ontoref + bash wrapper + + + lock + + + ontoref.nu + Nu dispatcher + + + + + module.nu + adr · backlog · etc + + + export + + + .ontology/ + nickel export + + + + + reflection/ + qa.ncl · backlog + + + + daemon? (optional) + + + MCP path — AI agent · Claude Code · any MCP client + + + AI Agent + Claude · any MCP + + + + + MCP server + stdio · HTTP /mcp + + + + + 19 tools + read · write · query + + + + + NCL files + git-versioned + + + Pre-commit barrier — notification-gated commit path + + + git commit + pre-commit fires + + + + + GET /notify + /pending?token=X + + + + + pending? + + + YES + + + BLOCK + until acked in UI + + + NO — daemon unreachable → fail-open + + + acked + + + PASS ✓ + commit proceeds + + + ontoref v0.1.0 + diff --git a/site/site/public/images/projects/ontoref_architecture-light.svg b/site/site/public/images/projects/ontoref_architecture-light.svg new file mode 100644 index 0000000..bff30ef --- /dev/null +++ b/site/site/public/images/projects/ontoref_architecture-light.svg @@ -0,0 +1,273 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + PROTOCOL LAYERS + + + + + + + + DECLARATIVE · NICKEL + type-safe contracts · fails at definition time + + + .ontology/ + + adrs/*.ncl + + modes/*.ncl + + schemas/*.ncl + + qa.ncl + + config.ncl + + + nickel export → JSON + + + + + KNOWLEDGE GRAPH · .ontology/ + self-describing · actor-agnostic · machine-queryable + + + axioms + + tensions + + practices + + nodes + edges + + invariants + + gates + + + reads KG via nickel export + + + + + OPERATIONAL · NUSHELL + 16 modules · DAG-validated modes · typed pipelines + + + adr.nu + + backlog.nu + + describe.nu + + sync.nu + + coder.nu + + +11 modules + + + dispatches command + actor + + + + + ENTRY POINT · BASH → NU + actor detect · advisory lock · NICKEL_IMPORT_PATH + + + ./ontoref + + actor detect + + advisory lock + + → ontoref.nu + + + optional · caches · serves · never required + + + + + RUNTIME · RUST + axum (optional daemon) + 10 UI pages · 19 MCP tools · actor registry · search · SurrealDB? + + + ontoref-daemon + + axum HTTP + + MCP ×19 + + DashMap + + search + + SurrealDB? + + + configures · ONTOREF_PROJECT_ROOT + + + + + ADOPTION · PER-PROJECT + templates/ · one ontoref checkout · zero lock-in + + + templates/ + + adopt_ontoref + + ONTOREF_PROJECT_ROOT + + zero lock-in + + plain NCL + + + + + + REQUEST FLOWS + + + CLI path — developer · agent · CI + + + + + Actor + dev · agent · CI + + + + + ./ontoref + bash wrapper + + + lock + + + ontoref.nu + Nu dispatcher + + + + + module.nu + adr · backlog · etc + + + export + + + .ontology/ + nickel export + + + + + reflection/ + qa.ncl · backlog + + + + daemon? (optional) + + + MCP path — AI agent · Claude Code · any MCP client + + + AI Agent + Claude · any MCP + + + + + MCP server + stdio · HTTP /mcp + + + + + 19 tools + read · write · query + + + + + NCL files + git-versioned + + + Pre-commit barrier — notification-gated commit path + + + git commit + pre-commit fires + + + + + GET /notify + /pending?token=X + + + + + pending? + + + YES + + + BLOCK + until acked in UI + + + NO — daemon unreachable → fail-open + + + acked + + + PASS ✓ + commit proceeds + + + ontoref v0.1.0 + diff --git a/site/site/public/images/projects/ontoref_graph_view-dark.png b/site/site/public/images/projects/ontoref_graph_view-dark.png new file mode 100644 index 0000000..81dbee3 Binary files /dev/null and b/site/site/public/images/projects/ontoref_graph_view-dark.png differ diff --git a/site/site/public/images/projects/ontoref_graph_view-light.png b/site/site/public/images/projects/ontoref_graph_view-light.png new file mode 100644 index 0000000..6eb5d3b Binary files /dev/null and b/site/site/public/images/projects/ontoref_graph_view-light.png differ diff --git a/site/site/public/images/projects/provisioning_architecture-dark.svg b/site/site/public/images/projects/provisioning_architecture-dark.svg new file mode 100644 index 0000000..4eb7b37 --- /dev/null +++ b/site/site/public/images/projects/provisioning_architecture-dark.svg @@ -0,0 +1,700 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CONTROL PLANE ARCHITECTURE + + + + + + + + + $ + CLI + provisioning + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Events Stream + NATS JetStream + :4222 + + + + + + + + + + + + + + + Orchestrator + :9011 + + + + Task State Machine • Provider API • SSH + + Webhooks • Rollback • Audit Collector + + + + + + + + + + Configuration + Nickel • ∞ TypeDialog + + + + + + + + + + Control Center + :9012 + + + + Cedar Policies • JWT • Sessions + + WebSocket • RBAC • Solo: auto-session + + + + + + + + + + + Extensions Registry + :8084 + + + + Git • OCI • LRU + + Providers • Taskservs • vault:// creds + + + + + + + + + + 🧠 + AI • MCP + :9082 + + + + RAG Engine • MCP Server • Tools + + Embeddings • Model Routing • KGraph + + + + + + + + + + 🛡 + Vault Service + :9094 + + + + Lease Lifecycle • Key Management + + SOPS • Age • Secrets never in NATS + + + + + + + + + + + + + + + + + + + + + + + + + + + + HTTPS + + + + + HTTPS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DATA PERSISTENCE + Solo: RocksDB • Multi: WebSocket + + + + orchestrator + + + vault + + + control_center + + + audit + + + workspace + + + Nickel config + + + + + + + + SOLID ENFORCEMENT LAYERS + + + Compile-time + + Dev-time + + Pre-commit + + CI/CD + + Runtime + + Audit + + + Providers APIs or CLI: Orchestrator only | SSH: Orchestrator + Machines | Auth: Control Center only | Secrets: Vault Service API only + + + + + + + + SERVICES + Orchestrator + Control Center + Vault Service + Extension Registry + AI • MCP + + INFRASTRUCTURE + + NATS Orbital Ring + + + + OCI registry + + Token and Auth + + SecretumVault + + Nickel + + + + SurrealDB + + CONNECTIONS + + + Encrypted + + I/O and Defs + + DBs data + + Secure access + + Events Stream + + MODES + + SOLO + RocksDB + local NATS + auto-session + + MULTI + WebSocket DB + NATS cluster + JWT+Cedar + + ENT + Enterprise + + + + + + + + Production + Hetzner • AWS + + + Staging + K8s cluster + + + Dev + Solo mode + + + Edge + On-prem • IoT + + + Custom + GitOps • Webhook + + + + v3.0.11 • ∞ Architecture + diff --git a/site/site/public/images/projects/provisioning_architecture-light.svg b/site/site/public/images/projects/provisioning_architecture-light.svg new file mode 100644 index 0000000..4291305 --- /dev/null +++ b/site/site/public/images/projects/provisioning_architecture-light.svg @@ -0,0 +1,703 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CONTROL PLANE ARCHITECTURE + + + + + + + + + $ + CLI + provisioning + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Events Stream + NATS JetStream + :4222 + + + + + + + + + + + + + + + Orchestrator + :9011 + + + + Task State Machine • Provider API • SSH + + Webhooks • Rollback • Audit Collector + + + + + + + + + + Configuration + Nickel • ∞ TypeDialog + + + + + + + + + + Control Center + :9012 + + + + Cedar Policies • JWT • Sessions + + WebSocket • RBAC • Solo: auto-session + + + + + + + + + + + Extensions Registry + :8084 + + + + Git • OCI • LRU + + Providers • Taskservs • vault:// creds + + + + + + + + + + 🧠 + AI • MCP + :9082 + + + + RAG Engine • MCP Server • Tools + + Embeddings • Model Routing • KGraph + + + + + + + + + + 🛡 + Vault Service + :9094 + + + + Lease Lifecycle • Key Management + + SOPS • Age • Secrets never in NATS + + + + + + + + + + + + + + + + + + + + + + + + + + + + HTTPS + + + + + HTTPS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DATA PERSISTENCE + Solo: RocksDB • Multi: WebSocket + + + + orchestrator + + + vault + + + control_center + + + audit + + + workspace + + + Nickel config + + + + + + + + SOLID ENFORCEMENT LAYERS + + + Compile-time + + Dev-time + + Pre-commit + + CI/CD + + Runtime + + Audit + + + Providers APIs or CLI: Orchestrator only | SSH: Orchestrator + Machines | Auth: Control Center only | Secrets: Vault Service API only + + + + + + + + SERVICES + Orchestrator + Control Center + Vault Service + Extension Registry + AI • MCP + + INFRASTRUCTURE + + NATS Orbital Ring + + + + OCI registry + + Token and Auth + + SecretumVault + + Nickel + + + + SurrealDB + + CONNECTIONS + + + Encrypted + + I/O and Defs + + DBs data + + Secure access + + Events Stream + + MODES + + SOLO + RocksDB + local NATS + auto-session + + MULTI + WebSocket DB + NATS cluster + JWT+Cedar + + ENT + Enterprise + + + + + + + + Production + Hetzner • AWS + + + Staging + K8s cluster + + + Dev + Solo mode + + + Edge + On-prem • IoT + + + Custom + GitOps • Webhook + + + + v3.0.11 • ∞ Architecture + diff --git a/site/site/public/images/projects/stratumiops_operation-flow-dark.svg b/site/site/public/images/projects/stratumiops_operation-flow-dark.svg new file mode 100644 index 0000000..c4f18b2 --- /dev/null +++ b/site/site/public/images/projects/stratumiops_operation-flow-dark.svg @@ -0,0 +1,253 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + stratumiops — Operation Flow + two main flows: knowledge registration · config sealed state — actor-aware, advisory locking + + + + + FLOW A — KNOWLEDGE REGISTRATION (stratum register) + + + + FLOW B — CONFIG SEALED STATE (stratum config apply) + + + + + + ACTOR + developer (TTY) + agent (piped) + ci / admin + + + + + + LOCK ACQUIRE + mkdir .stratum/locks/changelog + write PID to lock dir + stale? kill -0 PID → remove + + + + + + FORM (actor-aware) + developer → typedialog UI + agent/ci → nickel-export + tera + forms/*.ncl drives field layout + + + filled + + + + REGISTER OUTPUTS + CHANGELOG append (locked) + ADR hint → adrs/adr-NNN.ncl + ontology patch → .ontology/ + mode stub → reflection/modes/ + optional: trigger config apply + + + + LOCK RELEASE + rm -rf .stratum/locks/changelog + triggered by EXIT trap + + + + + + + + + + ADR CONSTRAINT VALIDATION (stratum adr validate) + nickel export adrs/*.ncl | where status == Accepted | get constraints | where severity == Hard + for each: nu -c check_hint → exit 0 + no stdout = pass + ^nickel export (not plugin cache) — always fresh · Superseded ADRs excluded + + + + + + + + + ACTOR + developer (TTY) + agent (piped) + ci / admin + + + + + + LOCK ACQUIRE + mkdir .stratum/locks/manifest + write PID to lock dir + stale? kill -0 PID → remove + + + + + + EXPORT + HASH + nickel export <profile>.ncl + sha256(export) → seal.hash + sha256(snapshot) → snapshot_hash + + + + + + HISTORY ENTRY + cfg-<YYYYMMDDTHHmmSS>-<actor>.ncl + ConfigState record (immutable) + seal.hash + snapshot_hash + supersedes: prev entry | null + + + + + + PATCH MANIFEST + manifest.ncl → active[<profile>] + = <new cfg entry ref> + under manifest lock + + + + LOCK RELEASE + rm -rf .stratum/locks/manifest + triggered by EXIT trap + + + + + + + + + + DRIFT AUDIT (stratum config audit) + for each profile in manifest: sha256(live profile.ncl) == manifest.active[p].seal.hash + match → clean · mismatch → drift (warn actor, optionally block) + read-only; no lock acquired · rollback: verify snapshot_hash → restore → new apply (forward operation) + + + + actor flows through both paths automatically — locking is per-resource, not global + + + stratum.sh register + STRATUM_ACTOR=developer (TTY) + typedialog form → CHANGELOG locked + + + stratum.sh config apply prod + STRATUM_ACTOR=ci + manifest lock → cfg-ts-ci.ncl written + + + stratum.sh config audit + any actor, no lock needed + sha256 compare live vs seal.hash + + + stratum.sh config rollback <ts> + verify snapshot_hash → restore → new apply + rollback is always a forward operation + + + + Legend: + + main flow + + pass / ok + + error / lock release + + form complete + + config flow (hash/write) + + output / audit + + Lock resources: + changelog (register) · manifest (config apply/rollback) · backlog (backlog done/cancel) + History IDs: + cfg-<YYYYMMDDTHHmmSS>-<actor>.ncl — timestamp collision-free, no lock needed for history writes + + + + FLOW C — BACKLOG (stratum backlog …) + + + backlog list|show + read backlog.ncl (no lock) + nickel export → filter/sort + + + + + backlog add + no lock (append, unique ID) + typed item: Todo|Bug|Wish|Debt + + + + + backlog done|cancel + mkdir lock on backlog resource + mutates status field in backlog.ncl + + + + + backlog promote + graduates_to: Adr|Mode|StateTransition + triggers adr or mode stub creation + + + + + backlog roadmap + state.ncl dimensions not-yet-reached + open items by priority (read-only) + + 2026-03 + diff --git a/site/site/public/images/projects/stratumiops_operation-flow-light.svg b/site/site/public/images/projects/stratumiops_operation-flow-light.svg new file mode 100644 index 0000000..1db8b3d --- /dev/null +++ b/site/site/public/images/projects/stratumiops_operation-flow-light.svg @@ -0,0 +1,252 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + stratumiops — Operation Flow + two main flows: knowledge registration · config sealed state — actor-aware, advisory locking + + + + + FLOW A — KNOWLEDGE REGISTRATION (stratum register) + + + + FLOW B — CONFIG SEALED STATE (stratum config apply) + + + + + + ACTOR + developer (TTY) + agent (piped) + ci / admin + + + + + + LOCK ACQUIRE + mkdir .stratum/locks/changelog + write PID to lock dir + stale? kill -0 PID → remove + + + + + + FORM (actor-aware) + developer → typedialog UI + agent/ci → nickel-export + tera + forms/*.ncl drives field layout + + + filled + + + + REGISTER OUTPUTS + CHANGELOG append (locked) + ADR hint → adrs/adr-NNN.ncl + ontology patch → .ontology/ + mode stub → reflection/modes/ + optional: trigger config apply + + + + LOCK RELEASE + rm -rf .stratum/locks/changelog + triggered by EXIT trap + + + + + + + + + + ADR CONSTRAINT VALIDATION (stratum adr validate) + nickel export adrs/*.ncl | where status == Accepted | get constraints | where severity == Hard + for each: nu -c check_hint → exit 0 + no stdout = pass + ^nickel export (not plugin cache) — always fresh · Superseded ADRs excluded + + + + + + + + + ACTOR + developer (TTY) + agent (piped) + ci / admin + + + + + + LOCK ACQUIRE + mkdir .stratum/locks/manifest + write PID to lock dir + stale? kill -0 PID → remove + + + + + + EXPORT + HASH + nickel export <profile>.ncl + sha256(export) → seal.hash + sha256(snapshot) → snapshot_hash + + + + + + HISTORY ENTRY + cfg-<YYYYMMDDTHHmmSS>-<actor>.ncl + ConfigState record (immutable) + seal.hash + snapshot_hash + supersedes: prev entry | null + + + + + + PATCH MANIFEST + manifest.ncl → active[<profile>] + = <new cfg entry ref> + under manifest lock + + + + LOCK RELEASE + rm -rf .stratum/locks/manifest + triggered by EXIT trap + + + + + + + + + + DRIFT AUDIT (stratum config audit) + for each profile in manifest: sha256(live profile.ncl) == manifest.active[p].seal.hash + match → clean · mismatch → drift (warn actor, optionally block) + read-only; no lock acquired · rollback: verify snapshot_hash → restore → new apply (forward operation) + + + + actor flows through both paths automatically — locking is per-resource, not global + + + stratum.sh register + STRATUM_ACTOR=developer (TTY) + typedialog form → CHANGELOG locked + + + stratum.sh config apply prod + STRATUM_ACTOR=ci + manifest lock → cfg-ts-ci.ncl written + + + stratum.sh config audit + any actor, no lock needed + sha256 compare live vs seal.hash + + + stratum.sh config rollback <ts> + verify snapshot_hash → restore → new apply + rollback is always a forward operation + + + + Legend: + + main flow + + pass / ok + + error / lock release + + form complete + + config flow (hash/write) + + output / audit + + Lock resources: + changelog (register) · manifest (config apply/rollback) · backlog (backlog done/cancel) + History IDs: + cfg-<YYYYMMDDTHHmmSS>-<actor>.ncl — timestamp collision-free, no lock needed for history writes + + + + FLOW C — BACKLOG (stratum backlog …) + + + backlog list|show + read backlog.ncl (no lock) + nickel export → filter/sort + + + + + backlog add + no lock (append, unique ID) + typed item: Todo|Bug|Wish|Debt + + + + + backlog done|cancel + mkdir lock on backlog resource + mutates status field in backlog.ncl + + + + + backlog promote + graduates_to: Adr|Mode|StateTransition + triggers adr or mode stub creation + + + + + backlog roadmap + state.ncl dimensions not-yet-reached + open items by priority (read-only) + + 2026-03 + diff --git a/site/site/public/images/projects/stratumiops_orchestrator-dark.svg b/site/site/public/images/projects/stratumiops_orchestrator-dark.svg new file mode 100644 index 0000000..0685f35 --- /dev/null +++ b/site/site/public/images/projects/stratumiops_orchestrator-dark.svg @@ -0,0 +1,477 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +STRATUM ORCHESTRATOR +Event-Driven · Graph-Guided · Atomic Execution · Stateless · OCI-Native + + +EVENT SOURCES + + + + +provisioning +emits dev.crate.> +crate-modified · deploy + + + + +kogral +emits dev.knowledge.> +node-updated · indexed + + + + +syntaxis +emits dev.project.> +phase · task-completed + + + + +stratumiops +emits dev.model.> +llm-call · embed-request + + + + +typedialog +emits dev.form.> +submitted · validated + + ++ more ... + + + + + + + + + + + + + + + + + + + + +NATS +JetStream +dev.> + + + + + + + + + + + + + + + + + +NKey verify +ed25519 JWT + + + + + + +STRATUM ORCHESTRATOR +Agnostic · Stateless · Graph-Guided + + + + +◈ ActionGraph +in-memory · Nickel nodes +topo-sort · cycle-detect + + + +⬡ PipelineCtx +DB-first · typed caps +schema-validated + + + +▶ StageRunner +JoinSet · parallel stages +CancellationToken +retry + backoff on failure +saga compensate.nu + + + +⚙ RuleEngine + +Cedar + +◈ Cedar +permit · forbid · conditions +per-node authz policies + +◈ NKey +ed25519 asymmetric keys +JWT per-process · verify + +↺ Saga rollback on failure · compensate.nu in reverse + + + + + + + + + + + +SurrealDB +Pipeline state · Step results +orchestrator_state ns +crash recovery + + + + + +SecretumVault +Credentials · TTL leases +vault:/secret/... +never in NATS payload + + + + + +Zot OCI Registry +Node defs · Nickel libs +oci://registry/nodes/ +content-addressed · signed + + +OPTIONAL + + + + + + + + +Kogral +Knowledge graph +node-updated triggers + +optional + + + +Syntaxis +Project orchestration +phase-transition events + +optional + + + +TypeDialog +Service config UI +startup config NCL only + + + + + + + + + + +Git repo +Git events → NATS +webhook → dev.crate.> +push · tag · pr + + + + + + + + + + +Nu Executor +Atomic steps · Pure functions +scripts/nu/*.nu +stdout=output · exit-code=status + + + + +AI Agent +stratum-llm · NATS protocol +dev.agent.*.requested/responded +oneshot correlation · timeout + + + + + +Nickel Base Library +OCI-published · content-addressed · build-verified · typecheck-gated +orchestrator-types.ncl · capability-schemas.ncl · defaults.ncl + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + StratumIOps + + + + + + + +LEGEND + +event flow (NATS) + +auth / credentials + +execution + +state (DB) + +OCI / registry + +optional + + +stratumiops · v0.1 + diff --git a/site/site/public/images/projects/stratumiops_orchestrator-light.svg b/site/site/public/images/projects/stratumiops_orchestrator-light.svg new file mode 100644 index 0000000..59e2814 --- /dev/null +++ b/site/site/public/images/projects/stratumiops_orchestrator-light.svg @@ -0,0 +1,469 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +STRATUM ORCHESTRATOR +Event-Driven · Graph-Guided · Atomic Execution · Stateless · OCI-Native + + +EVENT SOURCES + + + + +provisioning +emits dev.crate.> +crate-modified · deploy + + + + +kogral +emits dev.knowledge.> +node-updated · indexed + + + + +syntaxis +emits dev.project.> +phase · task-completed + + + + +stratumiops +emits dev.model.> +llm-call · embed-request + + + + +typedialog +emits dev.form.> +submitted · validated + + ++ more ... + + + + + + + + + + + + + + + + + + +NATS +JetStream +dev.> + + + + + + + + + + + + + + + + + +NKey verify +ed25519 JWT + + + + + + +STRATUM ORCHESTRATOR +Agnostic · Stateless · Graph-Guided + + + + +◈ ActionGraph +in-memory · Nickel nodes +topo-sort · cycle-detect + + + +⬡ PipelineCtx +DB-first · typed caps +schema-validated + + + +▶ StageRunner +JoinSet · parallel stages +CancellationToken +retry + backoff on failure +saga compensate.nu + + + +⚙ RuleEngine + +Cedar + +◈ Cedar +permit · forbid · conditions +per-node authz policies + +◈ NKey +ed25519 asymmetric keys +JWT per-process · verify + +↺ Saga rollback on failure · compensate.nu in reverse + + + + + + + + + + +SurrealDB +Pipeline state · Step results +orchestrator_state ns +crash recovery + + + + + +SecretumVault +Credentials · TTL leases +vault:/secret/... +never in NATS payload + + + + + +Zot OCI Registry +Node defs · Nickel libs +oci://registry/nodes/ +content-addressed · signed + + +OPTIONAL + + + + + + + +Kogral +Knowledge graph +node-updated triggers + +optional + + + +Syntaxis +Project orchestration +phase-transition events + +optional + + + +TypeDialog +Service config UI +startup config NCL only + + + + + + + + +Git repo +Git events → NATS +webhook → dev.crate.> +push · tag · pr + + + + + + + + +Nu Executor +Atomic steps · Pure functions +scripts/nu/*.nu +stdout=output · exit-code=status + + + + +AI Agent +stratum-llm · NATS protocol +dev.agent.*.requested/responded +oneshot correlation · timeout + + + + + +Nickel Base Library +OCI-published · content-addressed · build-verified · typecheck-gated +orchestrator-types.ncl · capability-schemas.ncl · defaults.ncl + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + StratumIOps + + + + + + + +LEGEND + +event flow (NATS) + +auth / credentials + +execution + +state (DB) + +OCI / registry + +optional + + +stratumiops · v0.1 + diff --git a/site/site/public/images/projects/syntaxis_architecture-dark.svg b/site/site/public/images/projects/syntaxis_architecture-dark.svg new file mode 100644 index 0000000..5537d7f --- /dev/null +++ b/site/site/public/images/projects/syntaxis_architecture-dark.svg @@ -0,0 +1,372 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SYNTAXIS Architecture + Systematic Orchestration, Perfectly Arranged + + + + + + + + User Interfaces + + 4 + + + + + CLI + syntaxis-cli + clap structured commands + Automation & CI/CD + + ACTIVE + + + + + + TUI + syntaxis-tui + ratatui + vim keys (hjkl) + Interactive & SSH-friendly + + ACTIVE + + + + + + Dashboard + Leptos WASM (CSR) + Trunk + UnoCSS build + Real-time & Responsive + + ACTIVE + + + + + + REST API + syntaxis-api (axum) + Tower middleware + WebSocket + Auth, Rate Limit, Metrics + + ACTIVE + + + + + + + + + + + + + + + + + Core Engine + syntaxis-core + + + + + Domain Models + + Project + id, name, metadata + Phase + create/devel/pub/arch + Task + state, priority, audit + Template + reusable structures + + + + + + Phase Lifecycle + + + + CREATE + + + + DEVEL + + + + PUBLISH + + + + ARCHIVE + State transitions with audit trail + + + + + + Business Logic + + Audit + Full change history & rollback + Checklist + Phase-based verification + Security + Auth & access control + Config + TOML + Nickel driven + + + + + + Quality + + 632+ TESTS + + ZERO UNSAFE + + NO UNWRAP() + + 100% DOCS + + CLIPPY 0W + thiserror | Result<T> | #![forbid(unsafe_code)] | cargo fmt | cargo audit + + + + + Test distribution: + core:173 + tui-lib:262 + api-lib:93 + vapora:57 + dashboard:52 + shared:33 + tui:10 + dash-shared:4 + + + + + + + + + VAPORA SST + syntaxis-vapora + + + + Orchestration Adapter + + Agent triggering on task state changes + Phase-based workflow orchestration + Enterprise audit & rollback support + + + + + Event Streaming + + NATS + KOGRAL & SYNTAXIS_SOURCE streams + Real-time federation between services + + + + 57 TESTS | ACTIVE + + + + + + + + Persistence Layer + Database trait abstraction + + + + + SQLite (Default) + + sqlx async + Connection pooling + WAL mode + Prepared statements | Indexed queries | Batch ops + Best for: Solo dev, small teams, local + + + + + + SurrealDB 2.3 + + 40+ operations + Type-safe async | JSON binding + In-Memory | File (RocksDB) | Server | Docker | K8s + Best for: Teams, distributed, enterprise + + + + + + Configuration + + TOML + Nickel + Runtime backend selection + database-default.toml | database-surrealdb.toml + Switch via: cp configs/database-*.toml configs/database.toml + + + + + + + + + + + + + + + + Shared Libraries + + + + shared-api-lib + REST utilities, request/response helpers (93 tests) + + + + + rust-tui + TUI utilities, ratatui helpers (262 tests) + + + + + tools-shared + Configuration, utilities, helpers (33 tests) + + + + + + + + + Infra + + + tokio + + + + axum + + + + sqlx + + + + serde + + + + clap + + + + ratatui + + + + leptos + + + + tower + + + + nats + + + + nickel + + + + + SYNTAXIS v0.x | Rust 2021 (MSRV 1.75+) | 10K+ LOC | 632+ tests | Production Beta + diff --git a/site/site/public/images/projects/syntaxis_architecture-light.svg b/site/site/public/images/projects/syntaxis_architecture-light.svg new file mode 100644 index 0000000..b93709b --- /dev/null +++ b/site/site/public/images/projects/syntaxis_architecture-light.svg @@ -0,0 +1,316 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SYNTAXIS Architecture + Systematic Orchestration, Perfectly Arranged + + + + + + User Interfaces + + 4 + + + + CLI + syntaxis-cli + clap structured commands + Automation & CI/CD + + ACTIVE + + + + + TUI + syntaxis-tui + ratatui + vim keys (hjkl) + Interactive & SSH-friendly + + ACTIVE + + + + + Dashboard + Leptos WASM (CSR) + Trunk + UnoCSS build + Real-time & Responsive + + ACTIVE + + + + + REST API + syntaxis-api (axum) + Tower middleware + WebSocket + Auth, Rate Limit, Metrics + + ACTIVE + + + + + + + + + + + + + + + Core Engine + syntaxis-core + + + + Domain Models + + Projectid, name, metadata + Phasecreate/devel/pub/arch + Taskstate, priority, audit + Templatereusable structures + + + + + Phase Lifecycle + + + CREATE + + + DEVEL + + + PUBLISH + + ARCHIVE + State transitions with audit trail + + + + + Business Logic + + AuditFull change history & rollback + ChecklistPhase-based verification + SecurityAuth & access control + ConfigTOML + Nickel driven + + + + + Quality + + 632+ TESTS + + ZERO UNSAFE + + NO UNWRAP() + + 100% DOCS + + CLIPPY 0W + thiserror | Result<T> | #![forbid(unsafe_code)] | cargo fmt | cargo audit + + + + Test distribution: + core:173 + tui-lib:262 + api-lib:93 + vapora:57 + dashboard:52 + shared:33 + tui:10 + dash-shared:4 + + + + + + + VAPORA SST + syntaxis-vapora + + + + Orchestration Adapter + + Agent triggering on task state changes + Phase-based workflow orchestration + Enterprise audit & rollback support + + + + + Event Streaming + + NATS + KOGRAL & SYNTAXIS_SOURCE streams + Real-time federation between services + + + + 57 TESTS | ACTIVE + + + + + + Persistence Layer + Database trait abstraction + + + + SQLite (Default) + + sqlx asyncConnection pooling + WAL mode + Prepared statements | Indexed queries | Batch ops + Best for: Solo dev, small teams, local + + + + + SurrealDB 2.3 + + 40+ operationsType-safe async | JSON binding + In-Memory | File (RocksDB) | Server | Docker | K8s + Best for: Teams, distributed, enterprise + + + + + Configuration + + TOML + NickelRuntime backend selection + database-default.toml | database-surrealdb.toml + Switch via: cp configs/database-*.toml configs/database.toml + + + + + + + + + + + + + + Shared Libraries + + + + shared-api-lib + REST utilities, request/response helpers (93 tests) + + + + + rust-tui + TUI utilities, ratatui helpers (262 tests) + + + + + tools-shared + Configuration, utilities, helpers (33 tests) + + + + + + + Infra + + tokio + + + axum + + + sqlx + + + serde + + + clap + + + ratatui + + + leptos + + + tower + + + nats + + + nickel + + + + + SYNTAXIS v0.x | Rust 2021 (MSRV 1.75+) | 10K+ LOC | 632+ tests | Production Beta + diff --git a/site/site/public/images/projects/typedialog_architecture-dark.svg b/site/site/public/images/projects/typedialog_architecture-dark.svg new file mode 100644 index 0000000..e4c8908 --- /dev/null +++ b/site/site/public/images/projects/typedialog_architecture-dark.svg @@ -0,0 +1,253 @@ + + + + + + + + + + + + + + + + + + + + + + + + + TypeDialog Architecture + Multi-Backend Form Orchestration Layer + + + FORM DEFINITIONS + + + + + Nickel (.ncl) + nickel export --format json + + + + TOML (.toml) + serde direct deserialization + + + + load_form() — Unified Entry Point + Extension dispatch: .ncl → subprocess | .toml → serde | Fail-fast on errors + + + + + FormDefinition + + + TYPEDIALOG-CORE + + + + THREE-PHASE EXECUTION + + + + Phase 1: Selectors + Identify & execute + + + + Phase 2: Element List + Pure, no I/O + + + + Phase 3: Dispatch + when/when_false eval + + + + + + + + + CORE MODULES + + + + form_parser + TOML/Nickel parsing + Field definitions + + + + validation + Nickel contracts + Pre/post conditions + + + + i18n (Fluent) + Locale detection + .ftl translations + + + + encryption + Field-level encrypt + External services + + + + templates (Tera) + Jinja2-compatible + Variable rendering + + + + RenderContext + results: HashMap + locale + + + + BackendFactory + #[cfg(feature)] compile-time + + runtime BackendType match + + + + + Box<dyn FormBackend> + + trait FormBackend: Send + Sync { execute_field, execute_form_complete } + + + BACKENDS (6 CRATES) + + + + + CLI + inquire 0.9 + Interactive prompts + Scripts, CI/CD + + + + TUI + ratatui + Terminal UI + Keyboard + Mouse + + + + Web + axum + HTTP server + Browser forms + + + + AI + RAG + embeddings + Semantic search + Knowledge graph + + + + Agent + Multi-LLM execution + .agent.mdx files + Streaming, templates + + + + Prov-Gen + IaC generation + AWS, GCP, Azure + Hetzner, UpCloud, LXD + + + + + HashMap<String, Value> + + + OUTPUT FORMATS + + + + + JSON + + + YAML + + + TOML + + + Nickel Roundtrip + + + LLM PROVIDERS + + + + Claude + + + OpenAI + + + Gemini + + + Ollama (local) + + + INTEGRATIONS + + + + Nushell Plugin + + + Nickel Contracts + + + Template Engine (Tera) + + + Multi-Cloud APIs + + + CI/CD (GitHub + Woodpecker) + + + 8 crates | 6 backends | 3,818 tests | 4 output formats | 4 LLM providers | 6 cloud targets + + + diff --git a/site/site/public/images/projects/typedialog_architecture-light.svg b/site/site/public/images/projects/typedialog_architecture-light.svg new file mode 100644 index 0000000..63f7daf --- /dev/null +++ b/site/site/public/images/projects/typedialog_architecture-light.svg @@ -0,0 +1,229 @@ + + + + + + + + + + + + + + + + + TypeDialog Architecture + Multi-Backend Form Orchestration Layer + + + FORM DEFINITIONS + + + + + Nickel (.ncl) + nickel export --format json + + + + TOML (.toml) + serde direct deserialization + + + + load_form() — Unified Entry Point + Extension dispatch: .ncl → subprocess | .toml → serde | Fail-fast on errors + + + + + FormDefinition + + + TYPEDIALOG-CORE + + + + THREE-PHASE EXECUTION + + + Phase 1: Selectors + Identify & execute + + + Phase 2: Element List + Pure, no I/O + + + Phase 3: Dispatch + when/when_false eval + + + + + + + + CORE MODULES + + + form_parser + TOML/Nickel parsing + Field definitions + + + validation + Nickel contracts + Pre/post conditions + + + i18n (Fluent) + Locale detection + .ftl translations + + + encryption + Field-level encrypt + External services + + + templates (Tera) + Jinja2-compatible + Variable rendering + + + + RenderContext + results: HashMap + locale + + + + BackendFactory + #[cfg(feature)] compile-time + + runtime BackendType match + + + + + Box<dyn FormBackend> + + trait FormBackend: Send + Sync { execute_field, execute_form_complete } + + + BACKENDS (6 CRATES) + + + + CLI + inquire 0.9 + Interactive prompts + Scripts, CI/CD + + + TUI + ratatui + Terminal UI + Keyboard + Mouse + + + Web + axum + HTTP server + Browser forms + + + AI + RAG + embeddings + Semantic search + Knowledge graph + + + Agent + Multi-LLM execution + .agent.mdx files + Streaming, templates + + + Prov-Gen + IaC generation + AWS, GCP, Azure + Hetzner, UpCloud, LXD + + + + + HashMap<String, Value> + + + OUTPUT FORMATS + + + + JSON + + + YAML + + + TOML + + + Nickel Roundtrip + + + LLM PROVIDERS + + + + Claude + + + OpenAI + + + Gemini + + + Ollama (local) + + + INTEGRATIONS + + + + Nushell Plugin + + + Nickel Contracts + + + Template Engine (Tera) + + + Multi-Cloud APIs + + + CI/CD (GitHub + Woodpecker) + + + 8 crates | 6 backends | 3,818 tests | 4 output formats | 4 LLM providers | 6 cloud targets + + + diff --git a/site/site/public/images/projects/vapora_architecture-dark.svg b/site/site/public/images/projects/vapora_architecture-dark.svg new file mode 100644 index 0000000..024d66f --- /dev/null +++ b/site/site/public/images/projects/vapora_architecture-dark.svg @@ -0,0 +1,576 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + VAPORA ARCHITECTURE + 18 CRATES · 354 TESTS · 100% RUST + + + PRESENTATION + SERVICES + INTELLIGENCE + DATA + PROVIDERS + + + + + + + + + + + Leptos WASM Frontend + Kanban Board · Glassmorphism UI · UnoCSS · Reactive Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Axum Backend API + 40+ REST Endpoints · 161 Tests + + + Projects + Tasks + Agents + Workflows + Proposals + Swarm + Metrics + RLM API + WebSocket + + + + + + + + Agent Runtime + Orchestration · Learning Profiles · 71 Tests + + Registry + Coordinator + Scoring + Profiles + 12 Roles + Health Checks + Recency Bias + + + + + + + + + + + + + MCP Gateway + Model Context Protocol · Plugin System + + Stdio Transport + SSE Transport + JSON-RPC + 6 Tools + Tool Registry + Schema Validation + + + + + + + A2A Protocol + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RLM Engine + Recursive Language Models · 38 Tests · 17k+ LOC + + + + + + Chunking + + + Hybrid Search + + + Dispatcher + + + + + BM25 + + + Semantic + + + RRF + + + Sandbox + + + + + + + + + + + + + + + + + + + Multi-IA LLM Router + Budget Enforcement · Cost Tracking · 53 Tests + + + + + Rule Router + + Budget Manager + + Cost Tracker + + + + Fallback Chain + + Cost Ranker + + Providers + + + + + + + + + + + + + + + + + + Swarm Coordinator + Load Balancing · Prometheus · 6 Tests + + + + Assignment + + Filtering + + Metrics + + + + success_rate / (1+load) + + Capabilities + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Knowledge Graph + Temporal History · Learning Curves · 20 Tests + + + Executions + + Similarity + + Causal + + + + + + + + SurrealDB + Multi-Model Database · Multi-Tenant Scopes + + + + Projects + + Tasks + + Agents + + Chunks + + A2A + + + + + + + + + + + + + + + + + + + + + + + + + NATS JetStream + Message Queue · Async Coordination · Pub/Sub + + + + Agent Jobs + + Workflows + + Proposals + + A2A + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Anthropic Claude + Opus · Sonnet · Haiku + + + + + + OpenAI + GPT-4 · GPT-4o · GPT-3.5 + + + + + + Google Gemini + 2.0 Pro · Flash · 1.5 Pro + + + + + + Ollama (Local) + Llama · Mistral · CodeLlama + + + + + + + + + SUPPORTING CRATES + + vapora-shared + vapora-tracking + vapora-telemetry + vapora-analytics + vapora-worktree + vapora-doc-lifecycle + vapora-workflow-engine + vapora-cli + vapora-leptos-ui + + + + + + + + + + + + PROMETHEUS · GRAFANA · OPENTELEMETRY + + + + + + + VAPORA v1.2.0 + Evaporate complexity · Full-Stack Rust · Production Ready + + + + + diff --git a/site/site/public/images/projects/vapora_architecture-light.svg b/site/site/public/images/projects/vapora_architecture-light.svg new file mode 100644 index 0000000..2dd47d8 --- /dev/null +++ b/site/site/public/images/projects/vapora_architecture-light.svg @@ -0,0 +1,576 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + VAPORA ARCHITECTURE + 18 CRATES · 354 TESTS · 100% RUST + + + PRESENTATION + SERVICES + INTELLIGENCE + DATA + PROVIDERS + + + + + + + + + + + Leptos WASM Frontend + Kanban Board · Glassmorphism UI · UnoCSS · Reactive Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Axum Backend API + 40+ REST Endpoints · 161 Tests + + + Projects + Tasks + Agents + Workflows + Proposals + Swarm + Metrics + RLM API + WebSocket + + + + + + + + Agent Runtime + Orchestration · Learning Profiles · 71 Tests + + Registry + Coordinator + Scoring + Profiles + 12 Roles + Health Checks + Recency Bias + + + + + + + + + + + + + MCP Gateway + Model Context Protocol · Plugin System + + Stdio Transport + SSE Transport + JSON-RPC + 6 Tools + Tool Registry + Schema Validation + + + + + + + A2A Protocol + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RLM Engine + Recursive Language Models · 38 Tests · 17k+ LOC + + + + + + Chunking + + + Hybrid Search + + + Dispatcher + + + + + BM25 + + + Semantic + + + RRF + + + Sandbox + + + + + + + + + + + + + + + + + + + Multi-IA LLM Router + Budget Enforcement · Cost Tracking · 53 Tests + + + + + Rule Router + + Budget Manager + + Cost Tracker + + + + Fallback Chain + + Cost Ranker + + Providers + + + + + + + + + + + + + + + + + + Swarm Coordinator + Load Balancing · Prometheus · 6 Tests + + + + Assignment + + Filtering + + Metrics + + + + success_rate / (1+load) + + Capabilities + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Knowledge Graph + Temporal History · Learning Curves · 20 Tests + + + Executions + + Similarity + + Causal + + + + + + + + SurrealDB + Multi-Model Database · Multi-Tenant Scopes + + + + Projects + + Tasks + + Agents + + Chunks + + A2A + + + + + + + + + + + + + + + + + + + + + + + + + NATS JetStream + Message Queue · Async Coordination · Pub/Sub + + + + Agent Jobs + + Workflows + + Proposals + + A2A + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Anthropic Claude + Opus · Sonnet · Haiku + + + + + + OpenAI + GPT-4 · GPT-4o · GPT-3.5 + + + + + + Google Gemini + 2.0 Pro · Flash · 1.5 Pro + + + + + + Ollama (Local) + Llama · Mistral · CodeLlama + + + + + + + + + SUPPORTING CRATES + + vapora-shared + vapora-tracking + vapora-telemetry + vapora-analytics + vapora-worktree + vapora-doc-lifecycle + vapora-workflow-engine + vapora-cli + vapora-leptos-ui + + + + + + + + + + + + PROMETHEUS · GRAFANA · OPENTELEMETRY + + + + + + + VAPORA v1.2.0 + Evaporate complexity · Full-Stack Rust · Production Ready + + + + + diff --git a/site/site/public/images/rustelo/architecture-dark.svg b/site/site/public/images/rustelo/architecture-dark.svg new file mode 100644 index 0000000..35aaf98 --- /dev/null +++ b/site/site/public/images/rustelo/architecture-dark.svg @@ -0,0 +1,659 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RUSTELO ARCHITECTURE + 21 CRATES · TYPE-SAFE · CONFIGURATION-DRIVEN · DUAL-TARGET WASM+NATIVE + + + IMPLEMENTATION + PORTS + FOUNDATION + RESOURCES + + + + + + + + + YOUR APPLICATION + + + + + + website-server + Axum · SSR · 40+ endpoints · RBAC · WebSocket + + + Auth + + Content + + Email + + Analytics + + i18n SSR + + + + build.rs gen + + RBAC NCL + + Route table + + + + + + + + website-client + Leptos WASM · Reactive · Hydration · FTL registry + + + Signals + + Memos + + Effects + + SPA Router + + + + wasm-bindgen + + FTL registry + + Menu gen + + + + + + + + website-pages + Leptos components · build-time gen · multi-lang + + + Config-driven + + i18n + + SSR + WASM + + ImageModal + + + + UnifiedXxxPage + + PageProvider + + + + + + + + + + + + + + + uses + + + + + + + + FRAMEWORK PORTS + + + + + + rustelo_web + Leptos + Axum umbrella · shell renderer + + + ssr + + hydrate + + Router + + AppState + + Shell SSR + + + + run_server() + + register_shell_renderer() + + + + + + + + rustelo_auth + JWT · OAuth2 · 2FA · Sessions · RBAC + + + JwtService + + PasswordService + + TOTP + + RBAC + + + + AuthFtlContrib. + + WebSocket + + Argon2 + + + + + + + + rustelo_content + Markdown · FTL embed · SsrTranslator · Assets + + + FTL Embed + + Markdown + + Syntect HL + + Search + + + + ContentFtlContrib. + + AssetManager + + + + + + + + + + + + + + + wraps + + + + + + + + FOUNDATION + + + + + + + rustelo_core_lib + config · i18n · auth · RBAC · state · registration + + + FTL/Fluent + + Registry + + OTP / TOTP + + RBAC engine + + + + + + + + rustelo_server + Axum SSR · middleware · email · DB · WebSocket + + + SQLx + + tower-http + + Lettre + + tokio-tung. + + + + + + + + rustelo_client + Leptos WASM · UnoCSS · components · FTL client + + + wasm32 + + Hydration + + UnoCSS + + + + + + + + + rustelo_components + NavMenu · Footer · Auth UI · ImageModal · Toast + + + NavMenu + + AuthUI + + Themes + + PostViewer + + + + + + + + rustelo_pages + build-time page generation · SSR + WASM templates + + + build_generator + + PageProvider + + SsrTransltr + + + + + + + + build-config + Nickel NCL parser · site_config.rs · code-gen helpers + + + NCL parse + + Route gen + + Menu gen + + + + + + + + + + + + RESOURCES + + site/nickel/ + site/i18n/ + site/config/ + site/templates/ + rustelo/resources/ + + + — 14 NCL schema domains + — FTL translations (en, es) + — routes · menus · themes · RBAC + — email · build-tools + — nickel/ · i18n/ · templates/ + + + + + routes + + menus + + themes + + rbac + + content + + + + + + + + PLUGIN SYSTEM + + + + + + + ResourceContributor + contribute_themes() contribute_menus() contribute_ftl() + PageContributor + contribute_pages() — UnifiedXxxPage components + register_contributor() + startup registration → global registries + + + + + Trait-based + + Zero overhead + + Send + Sync + + include_str!() + + + + + + + + CARGO FEATURES + + + + auth + + content-db + + content-static + + email + + + tls + + metrics + + crypto + + markdown + + + default = [ ] + + full = [auth, content-db, …] + + + + + PostgreSQL + + SQLite + + SQLx + + + + + + + + + + BUILD-TIME CODE GENERATION + + + + + + site/config/*.ncl + Nickel schema config + + + + + + + + + build.rs + Cargo build script + + + + + + + + + OUT_DIR + routes.rs · pages.rs · menus.rs + + + + + + + + + include!() + Macro inclusion + + + + + + + + + Compiled Binary + Zero-cost · Type-safe · Inlined + + + + + + Routes → ROUTE_TABLE + generated_routes.rs + Pages → page_*.rs (SSR + WASM templates) + FTL → ftl_registry_fn.rs (46+ entries) + + + + + + + + ALL CRATES + + website-server + website-client + website-pages + rustelo_web + rustelo_auth + rustelo_content + rustelo_core_lib + rustelo_server + rustelo_client + rustelo_components + build-config + + + + + + + + + + + LEPTOS · AXUM · SQLX · TOKIO · UNO-CSS · FLUENT · NICKEL + + + + + + RUSTELO v0.1.0 + Modular · Type-Safe · Configuration-Driven · Memory-Safe · Dual-Target WASM+Native + + + + + + + + + + + + + + + diff --git a/site/site/public/images/rustelo/architecture-light.svg b/site/site/public/images/rustelo/architecture-light.svg new file mode 100644 index 0000000..3f00ba8 --- /dev/null +++ b/site/site/public/images/rustelo/architecture-light.svg @@ -0,0 +1,594 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RUSTELO ARCHITECTURE + 21 CRATES · TYPE-SAFE · CONFIGURATION-DRIVEN · DUAL-TARGET WASM+NATIVE + + + IMPLEMENTATION + PORTS + FOUNDATION + RESOURCES + + + + + + + + YOUR APPLICATION + + + + + + website-server + Axum · SSR · 40+ endpoints · RBAC · WebSocket + + + Auth + + Content + + Email + + Analytics + + i18n SSR + + + + build.rs gen + + RBAC NCL + + Route table + + + + + + + + website-client + Leptos WASM · Reactive · Hydration · FTL registry + + + Signals + + Memos + + Effects + + SPA Router + + + + wasm-bindgen + + FTL registry + + + + + + + + website-pages + Leptos components · build-time gen · multi-lang + + + Config-driven + + i18n + + SSR + WASM + + + + UnifiedXxxPage + + PageProvider + + + + + + + + + + + uses + + + + + + + + FRAMEWORK PORTS + + + + + + rustelo_web + Leptos + Axum umbrella · shell renderer + + + ssr + + hydrate + + Router + + AppState + + + + run_server() + + register_shell_renderer() + + + + + + + + rustelo_auth + JWT · OAuth2 · 2FA · Sessions · RBAC + + + JwtService + + PasswordService + + TOTP + + RBAC + + + + AuthFtlContrib. + + WebSocket + + Argon2 + + + + + + + + rustelo_content + Markdown · FTL embed · SsrTranslator · Assets + + + FTL Embed + + Markdown + + Syntect HL + + + + ContentFtlContrib. + + AssetManager + + + + + + + + + + + wraps + + + + + + + + FOUNDATION + + + + + + rustelo_core_lib + config · i18n · auth · RBAC · state · registration + + + FTL/Fluent + + Registry + + OTP / TOTP + + RBAC engine + + + + + + + + rustelo_server + Axum SSR · middleware · email · DB · WebSocket + + + SQLx + + tower-http + + Lettre + + tokio-tung. + + + + + + + + rustelo_client + Leptos WASM · UnoCSS · components · FTL client + + + wasm32 + + Hydration + + UnoCSS + + + + + + + + rustelo_components + NavMenu · Footer · Auth UI · ImageModal · Toast + + + NavMenu + + AuthUI + + Themes + + PostViewer + + + + + + + + rustelo_pages + build-time page generation · SSR + WASM templates + + + build_generator + + PageProvider + + SsrTransltr + + + + + + + + build-config + Nickel NCL parser · site_config.rs · code-gen helpers + + + NCL parse + + Route gen + + Menu gen + + + + + + + + + + + + RESOURCES + + site/nickel/ + site/i18n/ + site/config/ + site/templates/ + rustelo/resources/ + + + — 14 NCL schema domains + — FTL translations (en, es) + — routes · menus · themes · RBAC + — email · build-tools + — nickel/ · i18n/ · templates/ + + + + routes + + menus + + themes + + rbac + + content + + + + + + + + PLUGIN SYSTEM + + ResourceContributor + contribute_themes() contribute_menus() contribute_ftl() + PageContributor + contribute_pages() — UnifiedXxxPage components + register_contributor() + startup registration → global registries + + + + Trait-based + + Zero overhead + + Send + Sync + + + + + + + + CARGO FEATURES + + + auth + + content-db + + content-static + + email + + + tls + + metrics + + crypto + + markdown + + + default = [ ] + + full = [auth, content-db, …] + + + + PostgreSQL + + SQLite + + SQLx + + + + + + + + + + BUILD-TIME CODE GENERATION + + + + site/config/*.ncl + Nickel schema config + + + + + + + build.rs + Cargo build script + + + + + + + OUT_DIR + routes.rs · pages.rs · menus.rs + + + + + + + include!() + Macro inclusion + + + + + + + Compiled Binary + Zero-cost · Type-safe · Inlined + + + + Routes → ROUTE_TABLE + generated_routes.rs + Pages → page_*.rs (SSR + WASM templates) + FTL → ftl_registry_fn.rs (46+ entries) + + + + + + + + ALL CRATES + + website-server + website-client + website-pages + rustelo_web + rustelo_auth + rustelo_content + rustelo_core_lib + rustelo_server + rustelo_client + rustelo_components + build-config + + + + + + + LEPTOS · AXUM · SQLX · TOKIO · UNO-CSS · FLUENT · NICKEL + + + + + + RUSTELO v0.1.0 + Modular · Type-Safe · Configuration-Driven · Memory-Safe · Dual-Target WASM+Native + + + + + + + + + + + + + + diff --git a/site/site/public/images/rustikon-2026-poster.avif b/site/site/public/images/rustikon-2026-poster.avif new file mode 100644 index 0000000..d677883 Binary files /dev/null and b/site/site/public/images/rustikon-2026-poster.avif differ diff --git a/site/site/public/images/rustikon-2026-poster.webp b/site/site/public/images/rustikon-2026-poster.webp new file mode 100644 index 0000000..6a7a681 Binary files /dev/null and b/site/site/public/images/rustikon-2026-poster.webp differ diff --git a/site/site/public/images/rustikon-2026-stage.avif b/site/site/public/images/rustikon-2026-stage.avif new file mode 100644 index 0000000..c1e1e0b Binary files /dev/null and b/site/site/public/images/rustikon-2026-stage.avif differ diff --git a/site/site/public/images/rustikon-2026-stage.webp b/site/site/public/images/rustikon-2026-stage.webp new file mode 100644 index 0000000..84f927b Binary files /dev/null and b/site/site/public/images/rustikon-2026-stage.webp differ diff --git a/site/site/public/images/trust-output-post.avif b/site/site/public/images/trust-output-post.avif new file mode 100644 index 0000000..d7e0e8e Binary files /dev/null and b/site/site/public/images/trust-output-post.avif differ diff --git a/site/site/public/images/trust-output-post.webp b/site/site/public/images/trust-output-post.webp new file mode 100644 index 0000000..543f83b Binary files /dev/null and b/site/site/public/images/trust-output-post.webp differ diff --git a/site/site/public/js/content-graph-shim.js b/site/site/public/js/content-graph-shim.js new file mode 100644 index 0000000..6573c2f --- /dev/null +++ b/site/site/public/js/content-graph-shim.js @@ -0,0 +1,262 @@ +/** + * content-graph-shim.js + * + * Thin bridge between Leptos/WASM and Cytoscape.js. + * Exposes window.ContentGraph with a single render() entry point. + * + * Called from GraphView component after mount: + * window.ContentGraph.render(containerId, focusNodeId, theme) + * + * Reads graph data from: + * + * + * Requires (loaded before this script via assets pipeline): + * - cytoscape.min.js + * - cytoscape-cose-bilkent.min.js + */ + +(function () { + 'use strict'; + + // ── Theme tokens ──────────────────────────────────────────────────────────── + + function buildPalette(isDark) { + return { + // Content node variants + contentFill: isDark ? '#3b82f6' : '#2563eb', + // Ontology node variants by level + axiomFill: isDark ? '#8b5cf6' : '#7c3aed', + tensionFill: isDark ? '#f59e0b' : '#d97706', + practiceFill: isDark ? '#10b981' : '#059669', + projectFill: isDark ? '#06b6d4' : '#0891b2', + // ADR + adrFill: isDark ? '#f97316' : '#ea580c', + // Ego node ring + egoStroke: isDark ? '#facc15' : '#ca8a04', + // Edge colours by kind group + declaredEdge: isDark ? '#6b7280' : '#9ca3af', + autoEdge: isDark ? '#4b5563' : '#d1d5db', + implicitEdge: isDark ? '#374151' : '#e5e7eb', + // Labels + labelColor: isDark ? '#f9fafb' : '#111827', + labelBg: isDark ? 'rgba(17,24,39,0.75)' : 'rgba(255,255,255,0.75)', + // Background + bg: isDark ? '#111827' : '#ffffff', + }; + } + + // ── Cytoscape stylesheet ───────────────────────────────────────────────────── + + function buildStylesheet(palette, egoId) { + const label = { + label: 'data(title)', + color: palette.labelColor, + 'background-color':palette.labelBg, + 'background-opacity': 0.75, + 'background-padding': '3px', + 'font-size': '11px', + 'text-valign': 'bottom', + 'text-halign': 'center', + 'text-margin-y': '4px', + }; + + return [ + // Default node + { + selector: 'node', + style: { + ...label, + shape: 'ellipse', + width: '36px', + height: '36px', + 'background-color': palette.contentFill, + 'border-width': '0px', + 'cursor': 'pointer', + }, + }, + // Ego node (focused) + { + selector: `node[id="${egoId}"]`, + style: { + width: '48px', + height: '48px', + 'border-width': '3px', + 'border-color': palette.egoStroke, + 'font-size': '13px', + 'font-weight': 'bold', + }, + }, + // Ontology variants + { selector: 'node[level="Axiom"]', style: { 'background-color': palette.axiomFill, shape: 'diamond' } }, + { selector: 'node[level="Tension"]', style: { 'background-color': palette.tensionFill, shape: 'triangle' } }, + { selector: 'node[level="Practice"]', style: { 'background-color': palette.practiceFill, shape: 'rectangle' } }, + { selector: 'node[level="Project"]', style: { 'background-color': palette.projectFill, shape: 'rectangle' } }, + // ADR + { selector: 'node[type="adr"]', style: { 'background-color': palette.adrFill, shape: 'pentagon' } }, + // Hover + { + selector: 'node:selected', + style: { 'border-width': '3px', 'border-color': palette.egoStroke }, + }, + // Default edge + { + selector: 'edge', + style: { + width: '1.5px', + 'line-color': palette.declaredEdge, + 'target-arrow-color':palette.declaredEdge, + 'target-arrow-shape':'triangle', + 'curve-style': 'bezier', + opacity: 0.7, + }, + }, + // Auto-backlink edges (lighter) + { + selector: 'edge[source="auto_backlink"]', + style: { 'line-color': palette.autoEdge, 'target-arrow-color': palette.autoEdge, opacity: 0.5 }, + }, + // Implicit / SharedTag edges (dashed, very light) + { + selector: 'edge[kind="shared_tag"]', + style: { + 'line-style': 'dashed', + 'line-color': palette.implicitEdge, + 'target-arrow-color':palette.implicitEdge, + opacity: 0.35, + }, + }, + ]; + } + + // ── Element conversion ─────────────────────────────────────────────────────── + + function toCytoscapeElements(graphData) { + const nodes = (graphData.nodes || []).map(n => ({ + data: { + id: n.id, + title: n.title || n.name || n.id, + type: n.type, + level: n.level || null, + url: n.url || null, + instance: n.instance || null, + }, + })); + + const edges = (graphData.edges || []).map((e, i) => ({ + data: { + id: `e-${i}`, + source: e.from, + target: e.to, + kind: e.kind, + source_type: e.source, // "declared" | "auto_backlink" | "implicit" + }, + })); + + return [...nodes, ...edges]; + } + + // ── Layout config ──────────────────────────────────────────────────────────── + + function buildLayout(nodeCount) { + // cose-bilkent for larger graphs; grid fallback for tiny graphs + if (nodeCount <= 4) { + return { name: 'circle', animate: false, padding: 40 }; + } + return { + name: 'cose-bilkent', + animate: false, + nodeDimensionsIncludeLabels: true, + idealEdgeLength: 100, + nodeRepulsion: 8000, + padding: 30, + }; + } + + // ── Instances registry ─────────────────────────────────────────────────────── + // Keeps track of active cy instances so they can be destroyed on re-render. + + const _instances = new Map(); + + // ── Public API ─────────────────────────────────────────────────────────────── + + window.ContentGraph = { + /** + * Render the knowledge graph into `containerId`. + * + * @param {string} containerId - DOM id of the container element + * @param {string} focusNodeId - node id to highlight/center; may be empty + * @param {string} theme - "dark" | "light" + */ + render(containerId, focusNodeId, theme) { + const container = document.getElementById(containerId); + if (!container) { + console.warn('[ContentGraph] container not found:', containerId); + return; + } + + // Destroy previous instance if re-rendering + if (_instances.has(containerId)) { + _instances.get(containerId).destroy(); + _instances.delete(containerId); + } + + // Read graph data embedded by SSR + const dataEl = document.getElementById('content-graph-data'); + if (!dataEl) { + console.warn('[ContentGraph] #content-graph-data not found'); + return; + } + + let graphData; + try { + graphData = JSON.parse(dataEl.textContent); + } catch (e) { + console.error('[ContentGraph] failed to parse graph data:', e); + return; + } + + const isDark = theme === 'dark'; + const palette = buildPalette(isDark); + const egoId = focusNodeId || ''; + + const cy = window.cytoscape({ + container, + elements: toCytoscapeElements(graphData), + style: buildStylesheet(palette, egoId), + layout: buildLayout((graphData.nodes || []).length), + minZoom: 0.3, + maxZoom: 4, + }); + + _instances.set(containerId, cy); + + // Navigate to content URL on node tap + cy.on('tap', 'node', evt => { + const url = evt.target.data('url'); + if (url) window.location.href = url; + }); + + // Pointer cursor on hover + cy.on('mouseover', 'node[?url]', () => { container.style.cursor = 'pointer'; }); + cy.on('mouseout', 'node', () => { container.style.cursor = 'default'; }); + + // Focus and center on ego node + if (egoId) { + const egoNode = cy.$(`#${CSS.escape(egoId)}`); + if (egoNode.length) { + cy.animate({ fit: { eles: egoNode.neighborhood().add(egoNode), padding: 60 } }, { duration: 400 }); + } + } + }, + + /** + * Destroy the Cytoscape instance for a given container (cleanup on unmount). + */ + destroy(containerId) { + if (_instances.has(containerId)) { + _instances.get(containerId).destroy(); + _instances.delete(containerId); + } + }, + }; +})(); diff --git a/site/site/public/js/cose-base.js b/site/site/public/js/cose-base.js new file mode 100644 index 0000000..49ccc66 --- /dev/null +++ b/site/site/public/js/cose-base.js @@ -0,0 +1,3214 @@ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(require("layout-base")); + else if(typeof define === 'function' && define.amd) + define(["layout-base"], factory); + else if(typeof exports === 'object') + exports["coseBase"] = factory(require("layout-base")); + else + root["coseBase"] = factory(root["layoutBase"]); +})(this, function(__WEBPACK_EXTERNAL_MODULE__551__) { +return /******/ (() => { // webpackBootstrap +/******/ "use strict"; +/******/ var __webpack_modules__ = ({ + +/***/ 45: +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + + + +var coseBase = {}; + +coseBase.layoutBase = __webpack_require__(551); +coseBase.CoSEConstants = __webpack_require__(806); +coseBase.CoSEEdge = __webpack_require__(767); +coseBase.CoSEGraph = __webpack_require__(880); +coseBase.CoSEGraphManager = __webpack_require__(578); +coseBase.CoSELayout = __webpack_require__(765); +coseBase.CoSENode = __webpack_require__(991); +coseBase.ConstraintHandler = __webpack_require__(902); + +module.exports = coseBase; + +/***/ }), + +/***/ 806: +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + + + +var FDLayoutConstants = __webpack_require__(551).FDLayoutConstants; + +function CoSEConstants() {} + +//CoSEConstants inherits static props in FDLayoutConstants +for (var prop in FDLayoutConstants) { + CoSEConstants[prop] = FDLayoutConstants[prop]; +} + +CoSEConstants.DEFAULT_USE_MULTI_LEVEL_SCALING = false; +CoSEConstants.DEFAULT_RADIAL_SEPARATION = FDLayoutConstants.DEFAULT_EDGE_LENGTH; +CoSEConstants.DEFAULT_COMPONENT_SEPERATION = 60; +CoSEConstants.TILE = true; +CoSEConstants.TILING_PADDING_VERTICAL = 10; +CoSEConstants.TILING_PADDING_HORIZONTAL = 10; +CoSEConstants.TRANSFORM_ON_CONSTRAINT_HANDLING = true; +CoSEConstants.ENFORCE_CONSTRAINTS = true; +CoSEConstants.APPLY_LAYOUT = true; +CoSEConstants.RELAX_MOVEMENT_ON_CONSTRAINTS = true; +CoSEConstants.TREE_REDUCTION_ON_INCREMENTAL = true; // this should be set to false if there will be a constraint +// This constant is for differentiating whether actual layout algorithm that uses cose-base wants to apply only incremental layout or +// an incremental layout on top of a randomized layout. If it is only incremental layout, then this constant should be true. +CoSEConstants.PURE_INCREMENTAL = CoSEConstants.DEFAULT_INCREMENTAL; + +module.exports = CoSEConstants; + +/***/ }), + +/***/ 767: +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + + + +var FDLayoutEdge = __webpack_require__(551).FDLayoutEdge; + +function CoSEEdge(source, target, vEdge) { + FDLayoutEdge.call(this, source, target, vEdge); +} + +CoSEEdge.prototype = Object.create(FDLayoutEdge.prototype); +for (var prop in FDLayoutEdge) { + CoSEEdge[prop] = FDLayoutEdge[prop]; +} + +module.exports = CoSEEdge; + +/***/ }), + +/***/ 880: +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + + + +var LGraph = __webpack_require__(551).LGraph; + +function CoSEGraph(parent, graphMgr, vGraph) { + LGraph.call(this, parent, graphMgr, vGraph); +} + +CoSEGraph.prototype = Object.create(LGraph.prototype); +for (var prop in LGraph) { + CoSEGraph[prop] = LGraph[prop]; +} + +module.exports = CoSEGraph; + +/***/ }), + +/***/ 578: +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + + + +var LGraphManager = __webpack_require__(551).LGraphManager; + +function CoSEGraphManager(layout) { + LGraphManager.call(this, layout); +} + +CoSEGraphManager.prototype = Object.create(LGraphManager.prototype); +for (var prop in LGraphManager) { + CoSEGraphManager[prop] = LGraphManager[prop]; +} + +module.exports = CoSEGraphManager; + +/***/ }), + +/***/ 765: +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + + + +var FDLayout = __webpack_require__(551).FDLayout; +var CoSEGraphManager = __webpack_require__(578); +var CoSEGraph = __webpack_require__(880); +var CoSENode = __webpack_require__(991); +var CoSEEdge = __webpack_require__(767); +var CoSEConstants = __webpack_require__(806); +var ConstraintHandler = __webpack_require__(902); +var FDLayoutConstants = __webpack_require__(551).FDLayoutConstants; +var LayoutConstants = __webpack_require__(551).LayoutConstants; +var Point = __webpack_require__(551).Point; +var PointD = __webpack_require__(551).PointD; +var DimensionD = __webpack_require__(551).DimensionD; +var Layout = __webpack_require__(551).Layout; +var Integer = __webpack_require__(551).Integer; +var IGeometry = __webpack_require__(551).IGeometry; +var LGraph = __webpack_require__(551).LGraph; +var Transform = __webpack_require__(551).Transform; +var LinkedList = __webpack_require__(551).LinkedList; + +function CoSELayout() { + FDLayout.call(this); + + this.toBeTiled = {}; // Memorize if a node is to be tiled or is tiled + this.constraints = {}; // keep layout constraints +} + +CoSELayout.prototype = Object.create(FDLayout.prototype); + +for (var prop in FDLayout) { + CoSELayout[prop] = FDLayout[prop]; +} + +CoSELayout.prototype.newGraphManager = function () { + var gm = new CoSEGraphManager(this); + this.graphManager = gm; + return gm; +}; + +CoSELayout.prototype.newGraph = function (vGraph) { + return new CoSEGraph(null, this.graphManager, vGraph); +}; + +CoSELayout.prototype.newNode = function (vNode) { + return new CoSENode(this.graphManager, vNode); +}; + +CoSELayout.prototype.newEdge = function (vEdge) { + return new CoSEEdge(null, null, vEdge); +}; + +CoSELayout.prototype.initParameters = function () { + FDLayout.prototype.initParameters.call(this, arguments); + if (!this.isSubLayout) { + if (CoSEConstants.DEFAULT_EDGE_LENGTH < 10) { + this.idealEdgeLength = 10; + } else { + this.idealEdgeLength = CoSEConstants.DEFAULT_EDGE_LENGTH; + } + + this.useSmartIdealEdgeLengthCalculation = CoSEConstants.DEFAULT_USE_SMART_IDEAL_EDGE_LENGTH_CALCULATION; + this.gravityConstant = FDLayoutConstants.DEFAULT_GRAVITY_STRENGTH; + this.compoundGravityConstant = FDLayoutConstants.DEFAULT_COMPOUND_GRAVITY_STRENGTH; + this.gravityRangeFactor = FDLayoutConstants.DEFAULT_GRAVITY_RANGE_FACTOR; + this.compoundGravityRangeFactor = FDLayoutConstants.DEFAULT_COMPOUND_GRAVITY_RANGE_FACTOR; + + // variables for tree reduction support + this.prunedNodesAll = []; + this.growTreeIterations = 0; + this.afterGrowthIterations = 0; + this.isTreeGrowing = false; + this.isGrowthFinished = false; + } +}; + +// This method is used to set CoSE related parameters used by spring embedder. +CoSELayout.prototype.initSpringEmbedder = function () { + FDLayout.prototype.initSpringEmbedder.call(this); + + // variables for cooling + this.coolingCycle = 0; + this.maxCoolingCycle = this.maxIterations / FDLayoutConstants.CONVERGENCE_CHECK_PERIOD; + this.finalTemperature = 0.04; + this.coolingAdjuster = 1; +}; + +CoSELayout.prototype.layout = function () { + var createBendsAsNeeded = LayoutConstants.DEFAULT_CREATE_BENDS_AS_NEEDED; + if (createBendsAsNeeded) { + this.createBendpoints(); + this.graphManager.resetAllEdges(); + } + + this.level = 0; + return this.classicLayout(); +}; + +CoSELayout.prototype.classicLayout = function () { + this.nodesWithGravity = this.calculateNodesToApplyGravitationTo(); + this.graphManager.setAllNodesToApplyGravitation(this.nodesWithGravity); + this.calcNoOfChildrenForAllNodes(); + this.graphManager.calcLowestCommonAncestors(); + this.graphManager.calcInclusionTreeDepths(); + this.graphManager.getRoot().calcEstimatedSize(); + this.calcIdealEdgeLengths(); + + if (!this.incremental) { + var forest = this.getFlatForest(); + + // The graph associated with this layout is flat and a forest + if (forest.length > 0) { + this.positionNodesRadially(forest); + } + // The graph associated with this layout is not flat or a forest + else { + // Reduce the trees when incremental mode is not enabled and graph is not a forest + this.reduceTrees(); + // Update nodes that gravity will be applied + this.graphManager.resetAllNodesToApplyGravitation(); + var allNodes = new Set(this.getAllNodes()); + var intersection = this.nodesWithGravity.filter(function (x) { + return allNodes.has(x); + }); + this.graphManager.setAllNodesToApplyGravitation(intersection); + + this.positionNodesRandomly(); + } + } else { + if (CoSEConstants.TREE_REDUCTION_ON_INCREMENTAL) { + // Reduce the trees in incremental mode if only this constant is set to true + this.reduceTrees(); + // Update nodes that gravity will be applied + this.graphManager.resetAllNodesToApplyGravitation(); + var allNodes = new Set(this.getAllNodes()); + var intersection = this.nodesWithGravity.filter(function (x) { + return allNodes.has(x); + }); + this.graphManager.setAllNodesToApplyGravitation(intersection); + } + } + + if (Object.keys(this.constraints).length > 0) { + ConstraintHandler.handleConstraints(this); + this.initConstraintVariables(); + } + + this.initSpringEmbedder(); + if (CoSEConstants.APPLY_LAYOUT) { + this.runSpringEmbedder(); + } + + return true; +}; + +CoSELayout.prototype.tick = function () { + this.totalIterations++; + + if (this.totalIterations === this.maxIterations && !this.isTreeGrowing && !this.isGrowthFinished) { + if (this.prunedNodesAll.length > 0) { + this.isTreeGrowing = true; + } else { + return true; + } + } + + if (this.totalIterations % FDLayoutConstants.CONVERGENCE_CHECK_PERIOD == 0 && !this.isTreeGrowing && !this.isGrowthFinished) { + if (this.isConverged()) { + if (this.prunedNodesAll.length > 0) { + this.isTreeGrowing = true; + } else { + return true; + } + } + + this.coolingCycle++; + + if (this.layoutQuality == 0) { + // quality - "draft" + this.coolingAdjuster = this.coolingCycle; + } else if (this.layoutQuality == 1) { + // quality - "default" + this.coolingAdjuster = this.coolingCycle / 3; + } + + // cooling schedule is based on http://www.btluke.com/simanf1.html -> cooling schedule 3 + this.coolingFactor = Math.max(this.initialCoolingFactor - Math.pow(this.coolingCycle, Math.log(100 * (this.initialCoolingFactor - this.finalTemperature)) / Math.log(this.maxCoolingCycle)) / 100 * this.coolingAdjuster, this.finalTemperature); + this.animationPeriod = Math.ceil(this.initialAnimationPeriod * Math.sqrt(this.coolingFactor)); + } + // Operations while tree is growing again + if (this.isTreeGrowing) { + if (this.growTreeIterations % 10 == 0) { + if (this.prunedNodesAll.length > 0) { + this.graphManager.updateBounds(); + this.updateGrid(); + this.growTree(this.prunedNodesAll); + // Update nodes that gravity will be applied + this.graphManager.resetAllNodesToApplyGravitation(); + var allNodes = new Set(this.getAllNodes()); + var intersection = this.nodesWithGravity.filter(function (x) { + return allNodes.has(x); + }); + this.graphManager.setAllNodesToApplyGravitation(intersection); + + this.graphManager.updateBounds(); + this.updateGrid(); + if (CoSEConstants.PURE_INCREMENTAL) this.coolingFactor = FDLayoutConstants.DEFAULT_COOLING_FACTOR_INCREMENTAL / 2;else this.coolingFactor = FDLayoutConstants.DEFAULT_COOLING_FACTOR_INCREMENTAL; + } else { + this.isTreeGrowing = false; + this.isGrowthFinished = true; + } + } + this.growTreeIterations++; + } + // Operations after growth is finished + if (this.isGrowthFinished) { + if (this.isConverged()) { + return true; + } + if (this.afterGrowthIterations % 10 == 0) { + this.graphManager.updateBounds(); + this.updateGrid(); + } + if (CoSEConstants.PURE_INCREMENTAL) this.coolingFactor = FDLayoutConstants.DEFAULT_COOLING_FACTOR_INCREMENTAL / 2 * ((100 - this.afterGrowthIterations) / 100);else this.coolingFactor = FDLayoutConstants.DEFAULT_COOLING_FACTOR_INCREMENTAL * ((100 - this.afterGrowthIterations) / 100); + this.afterGrowthIterations++; + } + + var gridUpdateAllowed = !this.isTreeGrowing && !this.isGrowthFinished; + var forceToNodeSurroundingUpdate = this.growTreeIterations % 10 == 1 && this.isTreeGrowing || this.afterGrowthIterations % 10 == 1 && this.isGrowthFinished; + + this.totalDisplacement = 0; + this.graphManager.updateBounds(); + this.calcSpringForces(); + this.calcRepulsionForces(gridUpdateAllowed, forceToNodeSurroundingUpdate); + this.calcGravitationalForces(); + this.moveNodes(); + this.animate(); + + return false; // Layout is not ended yet return false +}; + +CoSELayout.prototype.getPositionsData = function () { + var allNodes = this.graphManager.getAllNodes(); + var pData = {}; + for (var i = 0; i < allNodes.length; i++) { + var rect = allNodes[i].rect; + var id = allNodes[i].id; + pData[id] = { + id: id, + x: rect.getCenterX(), + y: rect.getCenterY(), + w: rect.width, + h: rect.height + }; + } + + return pData; +}; + +CoSELayout.prototype.runSpringEmbedder = function () { + this.initialAnimationPeriod = 25; + this.animationPeriod = this.initialAnimationPeriod; + var layoutEnded = false; + + // If aminate option is 'during' signal that layout is supposed to start iterating + if (FDLayoutConstants.ANIMATE === 'during') { + this.emit('layoutstarted'); + } else { + // If aminate option is 'during' tick() function will be called on index.js + while (!layoutEnded) { + layoutEnded = this.tick(); + } + + this.graphManager.updateBounds(); + } +}; + +// overrides moveNodes method in FDLayout +CoSELayout.prototype.moveNodes = function () { + var lNodes = this.getAllNodes(); + var node; + + // calculate displacement for each node + for (var i = 0; i < lNodes.length; i++) { + node = lNodes[i]; + node.calculateDisplacement(); + } + + if (Object.keys(this.constraints).length > 0) { + this.updateDisplacements(); + } + + // move each node + for (var i = 0; i < lNodes.length; i++) { + node = lNodes[i]; + node.move(); + } +}; + +// constraint related methods: initConstraintVariables and updateDisplacements + +// initialize constraint related variables +CoSELayout.prototype.initConstraintVariables = function () { + var self = this; + this.idToNodeMap = new Map(); + this.fixedNodeSet = new Set(); + + var allNodes = this.graphManager.getAllNodes(); + + // fill idToNodeMap + for (var i = 0; i < allNodes.length; i++) { + var node = allNodes[i]; + this.idToNodeMap.set(node.id, node); + } + + // calculate fixed node weight for given compound node + var calculateCompoundWeight = function calculateCompoundWeight(compoundNode) { + var nodes = compoundNode.getChild().getNodes(); + var node; + var fixedNodeWeight = 0; + for (var i = 0; i < nodes.length; i++) { + node = nodes[i]; + if (node.getChild() == null) { + if (self.fixedNodeSet.has(node.id)) { + fixedNodeWeight += 100; + } + } else { + fixedNodeWeight += calculateCompoundWeight(node); + } + } + return fixedNodeWeight; + }; + + if (this.constraints.fixedNodeConstraint) { + // fill fixedNodeSet + this.constraints.fixedNodeConstraint.forEach(function (nodeData) { + self.fixedNodeSet.add(nodeData.nodeId); + }); + + // assign fixed node weights to compounds if they contain fixed nodes + var allNodes = this.graphManager.getAllNodes(); + var node; + + for (var i = 0; i < allNodes.length; i++) { + node = allNodes[i]; + if (node.getChild() != null) { + var fixedNodeWeight = calculateCompoundWeight(node); + if (fixedNodeWeight > 0) { + node.fixedNodeWeight = fixedNodeWeight; + } + } + } + } + + if (this.constraints.relativePlacementConstraint) { + var nodeToDummyForVerticalAlignment = new Map(); + var nodeToDummyForHorizontalAlignment = new Map(); + this.dummyToNodeForVerticalAlignment = new Map(); + this.dummyToNodeForHorizontalAlignment = new Map(); + this.fixedNodesOnHorizontal = new Set(); + this.fixedNodesOnVertical = new Set(); + + // fill maps and sets + this.fixedNodeSet.forEach(function (nodeId) { + self.fixedNodesOnHorizontal.add(nodeId); + self.fixedNodesOnVertical.add(nodeId); + }); + + if (this.constraints.alignmentConstraint) { + if (this.constraints.alignmentConstraint.vertical) { + var verticalAlignment = this.constraints.alignmentConstraint.vertical; + for (var i = 0; i < verticalAlignment.length; i++) { + this.dummyToNodeForVerticalAlignment.set("dummy" + i, []); + verticalAlignment[i].forEach(function (nodeId) { + nodeToDummyForVerticalAlignment.set(nodeId, "dummy" + i); + self.dummyToNodeForVerticalAlignment.get("dummy" + i).push(nodeId); + if (self.fixedNodeSet.has(nodeId)) { + self.fixedNodesOnHorizontal.add("dummy" + i); + } + }); + } + } + if (this.constraints.alignmentConstraint.horizontal) { + var horizontalAlignment = this.constraints.alignmentConstraint.horizontal; + for (var i = 0; i < horizontalAlignment.length; i++) { + this.dummyToNodeForHorizontalAlignment.set("dummy" + i, []); + horizontalAlignment[i].forEach(function (nodeId) { + nodeToDummyForHorizontalAlignment.set(nodeId, "dummy" + i); + self.dummyToNodeForHorizontalAlignment.get("dummy" + i).push(nodeId); + if (self.fixedNodeSet.has(nodeId)) { + self.fixedNodesOnVertical.add("dummy" + i); + } + }); + } + } + } + + if (CoSEConstants.RELAX_MOVEMENT_ON_CONSTRAINTS) { + + this.shuffle = function (array) { + var j, x, i; + for (i = array.length - 1; i >= 2 * array.length / 3; i--) { + j = Math.floor(Math.random() * (i + 1)); + x = array[i]; + array[i] = array[j]; + array[j] = x; + } + return array; + }; + + this.nodesInRelativeHorizontal = []; + this.nodesInRelativeVertical = []; + this.nodeToRelativeConstraintMapHorizontal = new Map(); + this.nodeToRelativeConstraintMapVertical = new Map(); + this.nodeToTempPositionMapHorizontal = new Map(); + this.nodeToTempPositionMapVertical = new Map(); + + // fill arrays and maps + this.constraints.relativePlacementConstraint.forEach(function (constraint) { + if (constraint.left) { + var nodeIdLeft = nodeToDummyForVerticalAlignment.has(constraint.left) ? nodeToDummyForVerticalAlignment.get(constraint.left) : constraint.left; + var nodeIdRight = nodeToDummyForVerticalAlignment.has(constraint.right) ? nodeToDummyForVerticalAlignment.get(constraint.right) : constraint.right; + + if (!self.nodesInRelativeHorizontal.includes(nodeIdLeft)) { + self.nodesInRelativeHorizontal.push(nodeIdLeft); + self.nodeToRelativeConstraintMapHorizontal.set(nodeIdLeft, []); + if (self.dummyToNodeForVerticalAlignment.has(nodeIdLeft)) { + self.nodeToTempPositionMapHorizontal.set(nodeIdLeft, self.idToNodeMap.get(self.dummyToNodeForVerticalAlignment.get(nodeIdLeft)[0]).getCenterX()); + } else { + self.nodeToTempPositionMapHorizontal.set(nodeIdLeft, self.idToNodeMap.get(nodeIdLeft).getCenterX()); + } + } + if (!self.nodesInRelativeHorizontal.includes(nodeIdRight)) { + self.nodesInRelativeHorizontal.push(nodeIdRight); + self.nodeToRelativeConstraintMapHorizontal.set(nodeIdRight, []); + if (self.dummyToNodeForVerticalAlignment.has(nodeIdRight)) { + self.nodeToTempPositionMapHorizontal.set(nodeIdRight, self.idToNodeMap.get(self.dummyToNodeForVerticalAlignment.get(nodeIdRight)[0]).getCenterX()); + } else { + self.nodeToTempPositionMapHorizontal.set(nodeIdRight, self.idToNodeMap.get(nodeIdRight).getCenterX()); + } + } + + self.nodeToRelativeConstraintMapHorizontal.get(nodeIdLeft).push({ right: nodeIdRight, gap: constraint.gap }); + self.nodeToRelativeConstraintMapHorizontal.get(nodeIdRight).push({ left: nodeIdLeft, gap: constraint.gap }); + } else { + var nodeIdTop = nodeToDummyForHorizontalAlignment.has(constraint.top) ? nodeToDummyForHorizontalAlignment.get(constraint.top) : constraint.top; + var nodeIdBottom = nodeToDummyForHorizontalAlignment.has(constraint.bottom) ? nodeToDummyForHorizontalAlignment.get(constraint.bottom) : constraint.bottom; + + if (!self.nodesInRelativeVertical.includes(nodeIdTop)) { + self.nodesInRelativeVertical.push(nodeIdTop); + self.nodeToRelativeConstraintMapVertical.set(nodeIdTop, []); + if (self.dummyToNodeForHorizontalAlignment.has(nodeIdTop)) { + self.nodeToTempPositionMapVertical.set(nodeIdTop, self.idToNodeMap.get(self.dummyToNodeForHorizontalAlignment.get(nodeIdTop)[0]).getCenterY()); + } else { + self.nodeToTempPositionMapVertical.set(nodeIdTop, self.idToNodeMap.get(nodeIdTop).getCenterY()); + } + } + if (!self.nodesInRelativeVertical.includes(nodeIdBottom)) { + self.nodesInRelativeVertical.push(nodeIdBottom); + self.nodeToRelativeConstraintMapVertical.set(nodeIdBottom, []); + if (self.dummyToNodeForHorizontalAlignment.has(nodeIdBottom)) { + self.nodeToTempPositionMapVertical.set(nodeIdBottom, self.idToNodeMap.get(self.dummyToNodeForHorizontalAlignment.get(nodeIdBottom)[0]).getCenterY()); + } else { + self.nodeToTempPositionMapVertical.set(nodeIdBottom, self.idToNodeMap.get(nodeIdBottom).getCenterY()); + } + } + self.nodeToRelativeConstraintMapVertical.get(nodeIdTop).push({ bottom: nodeIdBottom, gap: constraint.gap }); + self.nodeToRelativeConstraintMapVertical.get(nodeIdBottom).push({ top: nodeIdTop, gap: constraint.gap }); + } + }); + } else { + var subGraphOnHorizontal = new Map(); // subgraph from vertical RP constraints + var subGraphOnVertical = new Map(); // subgraph from vertical RP constraints + + // construct subgraphs from relative placement constraints + this.constraints.relativePlacementConstraint.forEach(function (constraint) { + if (constraint.left) { + var left = nodeToDummyForVerticalAlignment.has(constraint.left) ? nodeToDummyForVerticalAlignment.get(constraint.left) : constraint.left; + var right = nodeToDummyForVerticalAlignment.has(constraint.right) ? nodeToDummyForVerticalAlignment.get(constraint.right) : constraint.right; + if (subGraphOnHorizontal.has(left)) { + subGraphOnHorizontal.get(left).push(right); + } else { + subGraphOnHorizontal.set(left, [right]); + } + if (subGraphOnHorizontal.has(right)) { + subGraphOnHorizontal.get(right).push(left); + } else { + subGraphOnHorizontal.set(right, [left]); + } + } else { + var top = nodeToDummyForHorizontalAlignment.has(constraint.top) ? nodeToDummyForHorizontalAlignment.get(constraint.top) : constraint.top; + var bottom = nodeToDummyForHorizontalAlignment.has(constraint.bottom) ? nodeToDummyForHorizontalAlignment.get(constraint.bottom) : constraint.bottom; + if (subGraphOnVertical.has(top)) { + subGraphOnVertical.get(top).push(bottom); + } else { + subGraphOnVertical.set(top, [bottom]); + } + if (subGraphOnVertical.has(bottom)) { + subGraphOnVertical.get(bottom).push(top); + } else { + subGraphOnVertical.set(bottom, [top]); + } + } + }); + + // function to construct components from a given graph + // also returns an array that keeps whether each component contains fixed node + var constructComponents = function constructComponents(graph, fixedNodes) { + var components = []; + var isFixed = []; + var queue = new LinkedList(); + var visited = new Set(); + var count = 0; + + graph.forEach(function (value, key) { + if (!visited.has(key)) { + components[count] = []; + isFixed[count] = false; + var currentNode = key; + queue.push(currentNode); + visited.add(currentNode); + components[count].push(currentNode); + + while (queue.length != 0) { + currentNode = queue.shift(); + if (fixedNodes.has(currentNode)) { + isFixed[count] = true; + } + var neighbors = graph.get(currentNode); + neighbors.forEach(function (neighbor) { + if (!visited.has(neighbor)) { + queue.push(neighbor); + visited.add(neighbor); + components[count].push(neighbor); + } + }); + } + count++; + } + }); + + return { components: components, isFixed: isFixed }; + }; + + var resultOnHorizontal = constructComponents(subGraphOnHorizontal, self.fixedNodesOnHorizontal); + this.componentsOnHorizontal = resultOnHorizontal.components; + this.fixedComponentsOnHorizontal = resultOnHorizontal.isFixed; + var resultOnVertical = constructComponents(subGraphOnVertical, self.fixedNodesOnVertical); + this.componentsOnVertical = resultOnVertical.components; + this.fixedComponentsOnVertical = resultOnVertical.isFixed; + } + } +}; + +// updates node displacements based on constraints +CoSELayout.prototype.updateDisplacements = function () { + var self = this; + if (this.constraints.fixedNodeConstraint) { + this.constraints.fixedNodeConstraint.forEach(function (nodeData) { + var fixedNode = self.idToNodeMap.get(nodeData.nodeId); + fixedNode.displacementX = 0; + fixedNode.displacementY = 0; + }); + } + + if (this.constraints.alignmentConstraint) { + if (this.constraints.alignmentConstraint.vertical) { + var allVerticalAlignments = this.constraints.alignmentConstraint.vertical; + for (var i = 0; i < allVerticalAlignments.length; i++) { + var totalDisplacementX = 0; + for (var j = 0; j < allVerticalAlignments[i].length; j++) { + if (this.fixedNodeSet.has(allVerticalAlignments[i][j])) { + totalDisplacementX = 0; + break; + } + totalDisplacementX += this.idToNodeMap.get(allVerticalAlignments[i][j]).displacementX; + } + var averageDisplacementX = totalDisplacementX / allVerticalAlignments[i].length; + for (var j = 0; j < allVerticalAlignments[i].length; j++) { + this.idToNodeMap.get(allVerticalAlignments[i][j]).displacementX = averageDisplacementX; + } + } + } + if (this.constraints.alignmentConstraint.horizontal) { + var allHorizontalAlignments = this.constraints.alignmentConstraint.horizontal; + for (var i = 0; i < allHorizontalAlignments.length; i++) { + var totalDisplacementY = 0; + for (var j = 0; j < allHorizontalAlignments[i].length; j++) { + if (this.fixedNodeSet.has(allHorizontalAlignments[i][j])) { + totalDisplacementY = 0; + break; + } + totalDisplacementY += this.idToNodeMap.get(allHorizontalAlignments[i][j]).displacementY; + } + var averageDisplacementY = totalDisplacementY / allHorizontalAlignments[i].length; + for (var j = 0; j < allHorizontalAlignments[i].length; j++) { + this.idToNodeMap.get(allHorizontalAlignments[i][j]).displacementY = averageDisplacementY; + } + } + } + } + + if (this.constraints.relativePlacementConstraint) { + + if (CoSEConstants.RELAX_MOVEMENT_ON_CONSTRAINTS) { + // shuffle array to randomize node processing order + if (this.totalIterations % 10 == 0) { + this.shuffle(this.nodesInRelativeHorizontal); + this.shuffle(this.nodesInRelativeVertical); + } + + this.nodesInRelativeHorizontal.forEach(function (nodeId) { + if (!self.fixedNodesOnHorizontal.has(nodeId)) { + var displacement = 0; + if (self.dummyToNodeForVerticalAlignment.has(nodeId)) { + displacement = self.idToNodeMap.get(self.dummyToNodeForVerticalAlignment.get(nodeId)[0]).displacementX; + } else { + displacement = self.idToNodeMap.get(nodeId).displacementX; + } + self.nodeToRelativeConstraintMapHorizontal.get(nodeId).forEach(function (constraint) { + if (constraint.right) { + var diff = self.nodeToTempPositionMapHorizontal.get(constraint.right) - self.nodeToTempPositionMapHorizontal.get(nodeId) - displacement; + if (diff < constraint.gap) { + displacement -= constraint.gap - diff; + } + } else { + var diff = self.nodeToTempPositionMapHorizontal.get(nodeId) - self.nodeToTempPositionMapHorizontal.get(constraint.left) + displacement; + if (diff < constraint.gap) { + displacement += constraint.gap - diff; + } + } + }); + self.nodeToTempPositionMapHorizontal.set(nodeId, self.nodeToTempPositionMapHorizontal.get(nodeId) + displacement); + if (self.dummyToNodeForVerticalAlignment.has(nodeId)) { + self.dummyToNodeForVerticalAlignment.get(nodeId).forEach(function (nodeId) { + self.idToNodeMap.get(nodeId).displacementX = displacement; + }); + } else { + self.idToNodeMap.get(nodeId).displacementX = displacement; + } + } + }); + + this.nodesInRelativeVertical.forEach(function (nodeId) { + if (!self.fixedNodesOnHorizontal.has(nodeId)) { + var displacement = 0; + if (self.dummyToNodeForHorizontalAlignment.has(nodeId)) { + displacement = self.idToNodeMap.get(self.dummyToNodeForHorizontalAlignment.get(nodeId)[0]).displacementY; + } else { + displacement = self.idToNodeMap.get(nodeId).displacementY; + } + self.nodeToRelativeConstraintMapVertical.get(nodeId).forEach(function (constraint) { + if (constraint.bottom) { + var diff = self.nodeToTempPositionMapVertical.get(constraint.bottom) - self.nodeToTempPositionMapVertical.get(nodeId) - displacement; + if (diff < constraint.gap) { + displacement -= constraint.gap - diff; + } + } else { + var diff = self.nodeToTempPositionMapVertical.get(nodeId) - self.nodeToTempPositionMapVertical.get(constraint.top) + displacement; + if (diff < constraint.gap) { + displacement += constraint.gap - diff; + } + } + }); + self.nodeToTempPositionMapVertical.set(nodeId, self.nodeToTempPositionMapVertical.get(nodeId) + displacement); + if (self.dummyToNodeForHorizontalAlignment.has(nodeId)) { + self.dummyToNodeForHorizontalAlignment.get(nodeId).forEach(function (nodeId) { + self.idToNodeMap.get(nodeId).displacementY = displacement; + }); + } else { + self.idToNodeMap.get(nodeId).displacementY = displacement; + } + } + }); + } else { + for (var i = 0; i < this.componentsOnHorizontal.length; i++) { + var component = this.componentsOnHorizontal[i]; + if (this.fixedComponentsOnHorizontal[i]) { + for (var j = 0; j < component.length; j++) { + if (this.dummyToNodeForVerticalAlignment.has(component[j])) { + this.dummyToNodeForVerticalAlignment.get(component[j]).forEach(function (nodeId) { + self.idToNodeMap.get(nodeId).displacementX = 0; + }); + } else { + this.idToNodeMap.get(component[j]).displacementX = 0; + } + } + } else { + var sum = 0; + var count = 0; + for (var j = 0; j < component.length; j++) { + if (this.dummyToNodeForVerticalAlignment.has(component[j])) { + var actualNodes = this.dummyToNodeForVerticalAlignment.get(component[j]); + sum += actualNodes.length * this.idToNodeMap.get(actualNodes[0]).displacementX; + count += actualNodes.length; + } else { + sum += this.idToNodeMap.get(component[j]).displacementX; + count++; + } + } + var averageDisplacement = sum / count; + for (var j = 0; j < component.length; j++) { + if (this.dummyToNodeForVerticalAlignment.has(component[j])) { + this.dummyToNodeForVerticalAlignment.get(component[j]).forEach(function (nodeId) { + self.idToNodeMap.get(nodeId).displacementX = averageDisplacement; + }); + } else { + this.idToNodeMap.get(component[j]).displacementX = averageDisplacement; + } + } + } + } + + for (var i = 0; i < this.componentsOnVertical.length; i++) { + var component = this.componentsOnVertical[i]; + if (this.fixedComponentsOnVertical[i]) { + for (var j = 0; j < component.length; j++) { + if (this.dummyToNodeForHorizontalAlignment.has(component[j])) { + this.dummyToNodeForHorizontalAlignment.get(component[j]).forEach(function (nodeId) { + self.idToNodeMap.get(nodeId).displacementY = 0; + }); + } else { + this.idToNodeMap.get(component[j]).displacementY = 0; + } + } + } else { + var sum = 0; + var count = 0; + for (var j = 0; j < component.length; j++) { + if (this.dummyToNodeForHorizontalAlignment.has(component[j])) { + var actualNodes = this.dummyToNodeForHorizontalAlignment.get(component[j]); + sum += actualNodes.length * this.idToNodeMap.get(actualNodes[0]).displacementY; + count += actualNodes.length; + } else { + sum += this.idToNodeMap.get(component[j]).displacementY; + count++; + } + } + var averageDisplacement = sum / count; + for (var j = 0; j < component.length; j++) { + if (this.dummyToNodeForHorizontalAlignment.has(component[j])) { + this.dummyToNodeForHorizontalAlignment.get(component[j]).forEach(function (nodeId) { + self.idToNodeMap.get(nodeId).displacementY = averageDisplacement; + }); + } else { + this.idToNodeMap.get(component[j]).displacementY = averageDisplacement; + } + } + } + } + } + } +}; + +CoSELayout.prototype.calculateNodesToApplyGravitationTo = function () { + var nodeList = []; + var graph; + + var graphs = this.graphManager.getGraphs(); + var size = graphs.length; + var i; + for (i = 0; i < size; i++) { + graph = graphs[i]; + + graph.updateConnected(); + + if (!graph.isConnected) { + nodeList = nodeList.concat(graph.getNodes()); + } + } + + return nodeList; +}; + +CoSELayout.prototype.createBendpoints = function () { + var edges = []; + edges = edges.concat(this.graphManager.getAllEdges()); + var visited = new Set(); + var i; + for (i = 0; i < edges.length; i++) { + var edge = edges[i]; + + if (!visited.has(edge)) { + var source = edge.getSource(); + var target = edge.getTarget(); + + if (source == target) { + edge.getBendpoints().push(new PointD()); + edge.getBendpoints().push(new PointD()); + this.createDummyNodesForBendpoints(edge); + visited.add(edge); + } else { + var edgeList = []; + + edgeList = edgeList.concat(source.getEdgeListToNode(target)); + edgeList = edgeList.concat(target.getEdgeListToNode(source)); + + if (!visited.has(edgeList[0])) { + if (edgeList.length > 1) { + var k; + for (k = 0; k < edgeList.length; k++) { + var multiEdge = edgeList[k]; + multiEdge.getBendpoints().push(new PointD()); + this.createDummyNodesForBendpoints(multiEdge); + } + } + edgeList.forEach(function (edge) { + visited.add(edge); + }); + } + } + } + + if (visited.size == edges.length) { + break; + } + } +}; + +CoSELayout.prototype.positionNodesRadially = function (forest) { + // We tile the trees to a grid row by row; first tree starts at (0,0) + var currentStartingPoint = new Point(0, 0); + var numberOfColumns = Math.ceil(Math.sqrt(forest.length)); + var height = 0; + var currentY = 0; + var currentX = 0; + var point = new PointD(0, 0); + + for (var i = 0; i < forest.length; i++) { + if (i % numberOfColumns == 0) { + // Start of a new row, make the x coordinate 0, increment the + // y coordinate with the max height of the previous row + currentX = 0; + currentY = height; + + if (i != 0) { + currentY += CoSEConstants.DEFAULT_COMPONENT_SEPERATION; + } + + height = 0; + } + + var tree = forest[i]; + + // Find the center of the tree + var centerNode = Layout.findCenterOfTree(tree); + + // Set the staring point of the next tree + currentStartingPoint.x = currentX; + currentStartingPoint.y = currentY; + + // Do a radial layout starting with the center + point = CoSELayout.radialLayout(tree, centerNode, currentStartingPoint); + + if (point.y > height) { + height = Math.floor(point.y); + } + + currentX = Math.floor(point.x + CoSEConstants.DEFAULT_COMPONENT_SEPERATION); + } + + this.transform(new PointD(LayoutConstants.WORLD_CENTER_X - point.x / 2, LayoutConstants.WORLD_CENTER_Y - point.y / 2)); +}; + +CoSELayout.radialLayout = function (tree, centerNode, startingPoint) { + var radialSep = Math.max(this.maxDiagonalInTree(tree), CoSEConstants.DEFAULT_RADIAL_SEPARATION); + CoSELayout.branchRadialLayout(centerNode, null, 0, 359, 0, radialSep); + var bounds = LGraph.calculateBounds(tree); + + var transform = new Transform(); + transform.setDeviceOrgX(bounds.getMinX()); + transform.setDeviceOrgY(bounds.getMinY()); + transform.setWorldOrgX(startingPoint.x); + transform.setWorldOrgY(startingPoint.y); + + for (var i = 0; i < tree.length; i++) { + var node = tree[i]; + node.transform(transform); + } + + var bottomRight = new PointD(bounds.getMaxX(), bounds.getMaxY()); + + return transform.inverseTransformPoint(bottomRight); +}; + +CoSELayout.branchRadialLayout = function (node, parentOfNode, startAngle, endAngle, distance, radialSeparation) { + // First, position this node by finding its angle. + var halfInterval = (endAngle - startAngle + 1) / 2; + + if (halfInterval < 0) { + halfInterval += 180; + } + + var nodeAngle = (halfInterval + startAngle) % 360; + var teta = nodeAngle * IGeometry.TWO_PI / 360; + + // Make polar to java cordinate conversion. + var cos_teta = Math.cos(teta); + var x_ = distance * Math.cos(teta); + var y_ = distance * Math.sin(teta); + + node.setCenter(x_, y_); + + // Traverse all neighbors of this node and recursively call this + // function. + var neighborEdges = []; + neighborEdges = neighborEdges.concat(node.getEdges()); + var childCount = neighborEdges.length; + + if (parentOfNode != null) { + childCount--; + } + + var branchCount = 0; + + var incEdgesCount = neighborEdges.length; + var startIndex; + + var edges = node.getEdgesBetween(parentOfNode); + + // If there are multiple edges, prune them until there remains only one + // edge. + while (edges.length > 1) { + //neighborEdges.remove(edges.remove(0)); + var temp = edges[0]; + edges.splice(0, 1); + var index = neighborEdges.indexOf(temp); + if (index >= 0) { + neighborEdges.splice(index, 1); + } + incEdgesCount--; + childCount--; + } + + if (parentOfNode != null) { + //assert edges.length == 1; + startIndex = (neighborEdges.indexOf(edges[0]) + 1) % incEdgesCount; + } else { + startIndex = 0; + } + + var stepAngle = Math.abs(endAngle - startAngle) / childCount; + + for (var i = startIndex; branchCount != childCount; i = ++i % incEdgesCount) { + var currentNeighbor = neighborEdges[i].getOtherEnd(node); + + // Don't back traverse to root node in current tree. + if (currentNeighbor == parentOfNode) { + continue; + } + + var childStartAngle = (startAngle + branchCount * stepAngle) % 360; + var childEndAngle = (childStartAngle + stepAngle) % 360; + + CoSELayout.branchRadialLayout(currentNeighbor, node, childStartAngle, childEndAngle, distance + radialSeparation, radialSeparation); + + branchCount++; + } +}; + +CoSELayout.maxDiagonalInTree = function (tree) { + var maxDiagonal = Integer.MIN_VALUE; + + for (var i = 0; i < tree.length; i++) { + var node = tree[i]; + var diagonal = node.getDiagonal(); + + if (diagonal > maxDiagonal) { + maxDiagonal = diagonal; + } + } + + return maxDiagonal; +}; + +CoSELayout.prototype.calcRepulsionRange = function () { + // formula is 2 x (level + 1) x idealEdgeLength + return 2 * (this.level + 1) * this.idealEdgeLength; +}; + +// Tiling methods + +// Group zero degree members whose parents are not to be tiled, create dummy parents where needed and fill memberGroups by their dummp parent id's +CoSELayout.prototype.groupZeroDegreeMembers = function () { + var self = this; + // array of [parent_id x oneDegreeNode_id] + var tempMemberGroups = {}; // A temporary map of parent node and its zero degree members + this.memberGroups = {}; // A map of dummy parent node and its zero degree members whose parents are not to be tiled + this.idToDummyNode = {}; // A map of id to dummy node + + var zeroDegree = []; // List of zero degree nodes whose parents are not to be tiled + var allNodes = this.graphManager.getAllNodes(); + + // Fill zero degree list + for (var i = 0; i < allNodes.length; i++) { + var node = allNodes[i]; + var parent = node.getParent(); + // If a node has zero degree and its parent is not to be tiled if exists add that node to zeroDegres list + if (this.getNodeDegreeWithChildren(node) === 0 && (parent.id == undefined || !this.getToBeTiled(parent))) { + zeroDegree.push(node); + } + } + + // Create a map of parent node and its zero degree members + for (var i = 0; i < zeroDegree.length; i++) { + var node = zeroDegree[i]; // Zero degree node itself + var p_id = node.getParent().id; // Parent id + + if (typeof tempMemberGroups[p_id] === "undefined") tempMemberGroups[p_id] = []; + + tempMemberGroups[p_id] = tempMemberGroups[p_id].concat(node); // Push node to the list belongs to its parent in tempMemberGroups + } + + // If there are at least two nodes at a level, create a dummy compound for them + Object.keys(tempMemberGroups).forEach(function (p_id) { + if (tempMemberGroups[p_id].length > 1) { + var dummyCompoundId = "DummyCompound_" + p_id; // The id of dummy compound which will be created soon + self.memberGroups[dummyCompoundId] = tempMemberGroups[p_id]; // Add dummy compound to memberGroups + + var parent = tempMemberGroups[p_id][0].getParent(); // The parent of zero degree nodes will be the parent of new dummy compound + + // Create a dummy compound with calculated id + var dummyCompound = new CoSENode(self.graphManager); + dummyCompound.id = dummyCompoundId; + dummyCompound.paddingLeft = parent.paddingLeft || 0; + dummyCompound.paddingRight = parent.paddingRight || 0; + dummyCompound.paddingBottom = parent.paddingBottom || 0; + dummyCompound.paddingTop = parent.paddingTop || 0; + + self.idToDummyNode[dummyCompoundId] = dummyCompound; + + var dummyParentGraph = self.getGraphManager().add(self.newGraph(), dummyCompound); + var parentGraph = parent.getChild(); + + // Add dummy compound to parent the graph + parentGraph.add(dummyCompound); + + // For each zero degree node in this level remove it from its parent graph and add it to the graph of dummy parent + for (var i = 0; i < tempMemberGroups[p_id].length; i++) { + var node = tempMemberGroups[p_id][i]; + + parentGraph.remove(node); + dummyParentGraph.add(node); + } + } + }); +}; + +CoSELayout.prototype.clearCompounds = function () { + var childGraphMap = {}; + var idToNode = {}; + + // Get compound ordering by finding the inner one first + this.performDFSOnCompounds(); + + for (var i = 0; i < this.compoundOrder.length; i++) { + + idToNode[this.compoundOrder[i].id] = this.compoundOrder[i]; + childGraphMap[this.compoundOrder[i].id] = [].concat(this.compoundOrder[i].getChild().getNodes()); + + // Remove children of compounds + this.graphManager.remove(this.compoundOrder[i].getChild()); + this.compoundOrder[i].child = null; + } + + this.graphManager.resetAllNodes(); + + // Tile the removed children + this.tileCompoundMembers(childGraphMap, idToNode); +}; + +CoSELayout.prototype.clearZeroDegreeMembers = function () { + var self = this; + var tiledZeroDegreePack = this.tiledZeroDegreePack = []; + + Object.keys(this.memberGroups).forEach(function (id) { + var compoundNode = self.idToDummyNode[id]; // Get the dummy compound + + tiledZeroDegreePack[id] = self.tileNodes(self.memberGroups[id], compoundNode.paddingLeft + compoundNode.paddingRight); + + // Set the width and height of the dummy compound as calculated + compoundNode.rect.width = tiledZeroDegreePack[id].width; + compoundNode.rect.height = tiledZeroDegreePack[id].height; + compoundNode.setCenter(tiledZeroDegreePack[id].centerX, tiledZeroDegreePack[id].centerY); + + // compound left and top margings for labels + // when node labels are included, these values may be set to different values below and are used in tilingPostLayout, + // otherwise they stay as zero + compoundNode.labelMarginLeft = 0; + compoundNode.labelMarginTop = 0; + + // Update compound bounds considering its label properties and set label margins for left and top + if (CoSEConstants.NODE_DIMENSIONS_INCLUDE_LABELS) { + + var width = compoundNode.rect.width; + var height = compoundNode.rect.height; + + if (compoundNode.labelWidth) { + if (compoundNode.labelPosHorizontal == "left") { + compoundNode.rect.x -= compoundNode.labelWidth; + compoundNode.setWidth(width + compoundNode.labelWidth); + compoundNode.labelMarginLeft = compoundNode.labelWidth; + } else if (compoundNode.labelPosHorizontal == "center" && compoundNode.labelWidth > width) { + compoundNode.rect.x -= (compoundNode.labelWidth - width) / 2; + compoundNode.setWidth(compoundNode.labelWidth); + compoundNode.labelMarginLeft = (compoundNode.labelWidth - width) / 2; + } else if (compoundNode.labelPosHorizontal == "right") { + compoundNode.setWidth(width + compoundNode.labelWidth); + } + } + + if (compoundNode.labelHeight) { + if (compoundNode.labelPosVertical == "top") { + compoundNode.rect.y -= compoundNode.labelHeight; + compoundNode.setHeight(height + compoundNode.labelHeight); + compoundNode.labelMarginTop = compoundNode.labelHeight; + } else if (compoundNode.labelPosVertical == "center" && compoundNode.labelHeight > height) { + compoundNode.rect.y -= (compoundNode.labelHeight - height) / 2; + compoundNode.setHeight(compoundNode.labelHeight); + compoundNode.labelMarginTop = (compoundNode.labelHeight - height) / 2; + } else if (compoundNode.labelPosVertical == "bottom") { + compoundNode.setHeight(height + compoundNode.labelHeight); + } + } + } + }); +}; + +CoSELayout.prototype.repopulateCompounds = function () { + for (var i = this.compoundOrder.length - 1; i >= 0; i--) { + var lCompoundNode = this.compoundOrder[i]; + var id = lCompoundNode.id; + var horizontalMargin = lCompoundNode.paddingLeft; + var verticalMargin = lCompoundNode.paddingTop; + var labelMarginLeft = lCompoundNode.labelMarginLeft; + var labelMarginTop = lCompoundNode.labelMarginTop; + + this.adjustLocations(this.tiledMemberPack[id], lCompoundNode.rect.x, lCompoundNode.rect.y, horizontalMargin, verticalMargin, labelMarginLeft, labelMarginTop); + } +}; + +CoSELayout.prototype.repopulateZeroDegreeMembers = function () { + var self = this; + var tiledPack = this.tiledZeroDegreePack; + + Object.keys(tiledPack).forEach(function (id) { + var compoundNode = self.idToDummyNode[id]; // Get the dummy compound by its id + var horizontalMargin = compoundNode.paddingLeft; + var verticalMargin = compoundNode.paddingTop; + var labelMarginLeft = compoundNode.labelMarginLeft; + var labelMarginTop = compoundNode.labelMarginTop; + + // Adjust the positions of nodes wrt its compound + self.adjustLocations(tiledPack[id], compoundNode.rect.x, compoundNode.rect.y, horizontalMargin, verticalMargin, labelMarginLeft, labelMarginTop); + }); +}; + +CoSELayout.prototype.getToBeTiled = function (node) { + var id = node.id; + //firstly check the previous results + if (this.toBeTiled[id] != null) { + return this.toBeTiled[id]; + } + + //only compound nodes are to be tiled + var childGraph = node.getChild(); + if (childGraph == null) { + this.toBeTiled[id] = false; + return false; + } + + var children = childGraph.getNodes(); // Get the children nodes + + //a compound node is not to be tiled if all of its compound children are not to be tiled + for (var i = 0; i < children.length; i++) { + var theChild = children[i]; + + if (this.getNodeDegree(theChild) > 0) { + this.toBeTiled[id] = false; + return false; + } + + //pass the children not having the compound structure + if (theChild.getChild() == null) { + this.toBeTiled[theChild.id] = false; + continue; + } + + if (!this.getToBeTiled(theChild)) { + this.toBeTiled[id] = false; + return false; + } + } + this.toBeTiled[id] = true; + return true; +}; + +// Get degree of a node depending of its edges and independent of its children +CoSELayout.prototype.getNodeDegree = function (node) { + var id = node.id; + var edges = node.getEdges(); + var degree = 0; + + // For the edges connected + for (var i = 0; i < edges.length; i++) { + var edge = edges[i]; + if (edge.getSource().id !== edge.getTarget().id) { + degree = degree + 1; + } + } + return degree; +}; + +// Get degree of a node with its children +CoSELayout.prototype.getNodeDegreeWithChildren = function (node) { + var degree = this.getNodeDegree(node); + if (node.getChild() == null) { + return degree; + } + var children = node.getChild().getNodes(); + for (var i = 0; i < children.length; i++) { + var child = children[i]; + degree += this.getNodeDegreeWithChildren(child); + } + return degree; +}; + +CoSELayout.prototype.performDFSOnCompounds = function () { + this.compoundOrder = []; + this.fillCompexOrderByDFS(this.graphManager.getRoot().getNodes()); +}; + +CoSELayout.prototype.fillCompexOrderByDFS = function (children) { + for (var i = 0; i < children.length; i++) { + var child = children[i]; + if (child.getChild() != null) { + this.fillCompexOrderByDFS(child.getChild().getNodes()); + } + if (this.getToBeTiled(child)) { + this.compoundOrder.push(child); + } + } +}; + +/** +* This method places each zero degree member wrt given (x,y) coordinates (top left). +*/ +CoSELayout.prototype.adjustLocations = function (organization, x, y, compoundHorizontalMargin, compoundVerticalMargin, compoundLabelMarginLeft, compoundLabelMarginTop) { + x += compoundHorizontalMargin + compoundLabelMarginLeft; + y += compoundVerticalMargin + compoundLabelMarginTop; + + var left = x; + + for (var i = 0; i < organization.rows.length; i++) { + var row = organization.rows[i]; + x = left; + var maxHeight = 0; + + for (var j = 0; j < row.length; j++) { + var lnode = row[j]; + + lnode.rect.x = x; // + lnode.rect.width / 2; + lnode.rect.y = y; // + lnode.rect.height / 2; + + x += lnode.rect.width + organization.horizontalPadding; + + if (lnode.rect.height > maxHeight) maxHeight = lnode.rect.height; + } + + y += maxHeight + organization.verticalPadding; + } +}; + +CoSELayout.prototype.tileCompoundMembers = function (childGraphMap, idToNode) { + var self = this; + this.tiledMemberPack = []; + + Object.keys(childGraphMap).forEach(function (id) { + // Get the compound node + var compoundNode = idToNode[id]; + + self.tiledMemberPack[id] = self.tileNodes(childGraphMap[id], compoundNode.paddingLeft + compoundNode.paddingRight); + + compoundNode.rect.width = self.tiledMemberPack[id].width; + compoundNode.rect.height = self.tiledMemberPack[id].height; + compoundNode.setCenter(self.tiledMemberPack[id].centerX, self.tiledMemberPack[id].centerY); + + // compound left and top margings for labels + // when node labels are included, these values may be set to different values below and are used in tilingPostLayout, + // otherwise they stay as zero + compoundNode.labelMarginLeft = 0; + compoundNode.labelMarginTop = 0; + + // Update compound bounds considering its label properties and set label margins for left and top + if (CoSEConstants.NODE_DIMENSIONS_INCLUDE_LABELS) { + + var width = compoundNode.rect.width; + var height = compoundNode.rect.height; + + if (compoundNode.labelWidth) { + if (compoundNode.labelPosHorizontal == "left") { + compoundNode.rect.x -= compoundNode.labelWidth; + compoundNode.setWidth(width + compoundNode.labelWidth); + compoundNode.labelMarginLeft = compoundNode.labelWidth; + } else if (compoundNode.labelPosHorizontal == "center" && compoundNode.labelWidth > width) { + compoundNode.rect.x -= (compoundNode.labelWidth - width) / 2; + compoundNode.setWidth(compoundNode.labelWidth); + compoundNode.labelMarginLeft = (compoundNode.labelWidth - width) / 2; + } else if (compoundNode.labelPosHorizontal == "right") { + compoundNode.setWidth(width + compoundNode.labelWidth); + } + } + + if (compoundNode.labelHeight) { + if (compoundNode.labelPosVertical == "top") { + compoundNode.rect.y -= compoundNode.labelHeight; + compoundNode.setHeight(height + compoundNode.labelHeight); + compoundNode.labelMarginTop = compoundNode.labelHeight; + } else if (compoundNode.labelPosVertical == "center" && compoundNode.labelHeight > height) { + compoundNode.rect.y -= (compoundNode.labelHeight - height) / 2; + compoundNode.setHeight(compoundNode.labelHeight); + compoundNode.labelMarginTop = (compoundNode.labelHeight - height) / 2; + } else if (compoundNode.labelPosVertical == "bottom") { + compoundNode.setHeight(height + compoundNode.labelHeight); + } + } + } + }); +}; + +CoSELayout.prototype.tileNodes = function (nodes, minWidth) { + var horizontalOrg = this.tileNodesByFavoringDim(nodes, minWidth, true); + var verticalOrg = this.tileNodesByFavoringDim(nodes, minWidth, false); + + var horizontalRatio = this.getOrgRatio(horizontalOrg); + var verticalRatio = this.getOrgRatio(verticalOrg); + var bestOrg; + + // the best ratio is the one that is closer to 1 since the ratios are already normalized + // and the best organization is the one that has the best ratio + if (verticalRatio < horizontalRatio) { + bestOrg = verticalOrg; + } else { + bestOrg = horizontalOrg; + } + + return bestOrg; +}; + +// get the width/height ratio of the organization that is normalized so that it will not be less than 1 +CoSELayout.prototype.getOrgRatio = function (organization) { + // get dimensions and calculate the initial ratio + var width = organization.width; + var height = organization.height; + var ratio = width / height; + + // if the initial ratio is less then 1 then inverse it + if (ratio < 1) { + ratio = 1 / ratio; + } + + // return the normalized ratio + return ratio; +}; + +/* + * Calculates the ideal width for the rows. This method assumes that + * each node has the same sizes and calculates the ideal row width that + * approximates a square shaped complex accordingly. However, since nodes would + * have different sizes some rows would have different sizes and the resulting + * shape would not be an exact square. + */ +CoSELayout.prototype.calcIdealRowWidth = function (members, favorHorizontalDim) { + // To approximate a square shaped complex we need to make complex width equal to complex height. + // To achieve this we need to solve the following equation system for hc: + // (x + bx) * hc - bx = (y + by) * vc - by, hc * vc = n + // where x is the avarage width of the nodes, y is the avarage height of nodes + // bx and by are the buffer sizes in horizontal and vertical dimensions accordingly, + // hc and vc are the number of rows in horizontal and vertical dimensions + // n is number of members. + + var verticalPadding = CoSEConstants.TILING_PADDING_VERTICAL; + var horizontalPadding = CoSEConstants.TILING_PADDING_HORIZONTAL; + + // number of members + var membersSize = members.length; + + // sum of the width of all members + var totalWidth = 0; + + // sum of the height of all members + var totalHeight = 0; + + var maxWidth = 0; + + // traverse all members to calculate total width and total height and get the maximum members width + members.forEach(function (node) { + totalWidth += node.getWidth(); + totalHeight += node.getHeight(); + + if (node.getWidth() > maxWidth) { + maxWidth = node.getWidth(); + } + }); + + // average width of the members + var averageWidth = totalWidth / membersSize; + + // average height of the members + var averageHeight = totalHeight / membersSize; + + // solving the initial equation system for the hc yields the following second degree equation: + // hc^2 * (x+bx) + hc * (by - bx) - n * (y + by) = 0 + + // the delta value to solve the equation above for hc + var delta = Math.pow(verticalPadding - horizontalPadding, 2) + 4 * (averageWidth + horizontalPadding) * (averageHeight + verticalPadding) * membersSize; + + // solve the equation using delta value to calculate the horizontal count + // that represents the number of nodes in an ideal row + var horizontalCountDouble = (horizontalPadding - verticalPadding + Math.sqrt(delta)) / (2 * (averageWidth + horizontalPadding)); + // round the calculated horizontal count up or down according to the favored dimension + var horizontalCount; + + if (favorHorizontalDim) { + horizontalCount = Math.ceil(horizontalCountDouble); + // if horizontalCount count is not a float value then both of rounding to floor and ceil + // will yield the same values. Instead of repeating the same calculation try going up + // while favoring horizontal dimension in such cases + if (horizontalCount == horizontalCountDouble) { + horizontalCount++; + } + } else { + horizontalCount = Math.floor(horizontalCountDouble); + } + + // ideal width to be calculated + var idealWidth = horizontalCount * (averageWidth + horizontalPadding) - horizontalPadding; + + // if max width is bigger than calculated ideal width reset ideal width to it + if (maxWidth > idealWidth) { + idealWidth = maxWidth; + } + + // add the left-right margins to the ideal row width + idealWidth += horizontalPadding * 2; + + // return the ideal row width1 + return idealWidth; +}; + +CoSELayout.prototype.tileNodesByFavoringDim = function (nodes, minWidth, favorHorizontalDim) { + var verticalPadding = CoSEConstants.TILING_PADDING_VERTICAL; + var horizontalPadding = CoSEConstants.TILING_PADDING_HORIZONTAL; + var tilingCompareBy = CoSEConstants.TILING_COMPARE_BY; + var organization = { + rows: [], + rowWidth: [], + rowHeight: [], + width: 0, + height: minWidth, // assume minHeight equals to minWidth + verticalPadding: verticalPadding, + horizontalPadding: horizontalPadding, + centerX: 0, + centerY: 0 + }; + + if (tilingCompareBy) { + organization.idealRowWidth = this.calcIdealRowWidth(nodes, favorHorizontalDim); + } + + var getNodeArea = function getNodeArea(n) { + return n.rect.width * n.rect.height; + }; + + var areaCompareFcn = function areaCompareFcn(n1, n2) { + return getNodeArea(n2) - getNodeArea(n1); + }; + + // Sort the nodes in descending order of their areas + nodes.sort(function (n1, n2) { + var cmpBy = areaCompareFcn; + if (organization.idealRowWidth) { + cmpBy = tilingCompareBy; + return cmpBy(n1.id, n2.id); + } + return cmpBy(n1, n2); + }); + + // Create the organization -> calculate compound center + var sumCenterX = 0; + var sumCenterY = 0; + for (var i = 0; i < nodes.length; i++) { + var lNode = nodes[i]; + + sumCenterX += lNode.getCenterX(); + sumCenterY += lNode.getCenterY(); + } + + organization.centerX = sumCenterX / nodes.length; + organization.centerY = sumCenterY / nodes.length; + + // Create the organization -> tile members + for (var i = 0; i < nodes.length; i++) { + var lNode = nodes[i]; + + if (organization.rows.length == 0) { + this.insertNodeToRow(organization, lNode, 0, minWidth); + } else if (this.canAddHorizontal(organization, lNode.rect.width, lNode.rect.height)) { + var rowIndex = organization.rows.length - 1; + if (!organization.idealRowWidth) { + rowIndex = this.getShortestRowIndex(organization); + } + this.insertNodeToRow(organization, lNode, rowIndex, minWidth); + } else { + this.insertNodeToRow(organization, lNode, organization.rows.length, minWidth); + } + + this.shiftToLastRow(organization); + } + + return organization; +}; + +CoSELayout.prototype.insertNodeToRow = function (organization, node, rowIndex, minWidth) { + var minCompoundSize = minWidth; + + // Add new row if needed + if (rowIndex == organization.rows.length) { + var secondDimension = []; + + organization.rows.push(secondDimension); + organization.rowWidth.push(minCompoundSize); + organization.rowHeight.push(0); + } + + // Update row width + var w = organization.rowWidth[rowIndex] + node.rect.width; + + if (organization.rows[rowIndex].length > 0) { + w += organization.horizontalPadding; + } + + organization.rowWidth[rowIndex] = w; + // Update compound width + if (organization.width < w) { + organization.width = w; + } + + // Update height + var h = node.rect.height; + if (rowIndex > 0) h += organization.verticalPadding; + + var extraHeight = 0; + if (h > organization.rowHeight[rowIndex]) { + extraHeight = organization.rowHeight[rowIndex]; + organization.rowHeight[rowIndex] = h; + extraHeight = organization.rowHeight[rowIndex] - extraHeight; + } + + organization.height += extraHeight; + + // Insert node + organization.rows[rowIndex].push(node); +}; + +//Scans the rows of an organization and returns the one with the min width +CoSELayout.prototype.getShortestRowIndex = function (organization) { + var r = -1; + var min = Number.MAX_VALUE; + + for (var i = 0; i < organization.rows.length; i++) { + if (organization.rowWidth[i] < min) { + r = i; + min = organization.rowWidth[i]; + } + } + return r; +}; + +//Scans the rows of an organization and returns the one with the max width +CoSELayout.prototype.getLongestRowIndex = function (organization) { + var r = -1; + var max = Number.MIN_VALUE; + + for (var i = 0; i < organization.rows.length; i++) { + + if (organization.rowWidth[i] > max) { + r = i; + max = organization.rowWidth[i]; + } + } + + return r; +}; + +/** +* This method checks whether adding extra width to the organization violates +* the aspect ratio(1) or not. +*/ +CoSELayout.prototype.canAddHorizontal = function (organization, extraWidth, extraHeight) { + + // if there is an ideal row width specified use it instead of checking the aspect ratio + if (organization.idealRowWidth) { + var lastRowIndex = organization.rows.length - 1; + var lastRowWidth = organization.rowWidth[lastRowIndex]; + + // check and return if ideal row width will be exceed if the node is added to the row + return lastRowWidth + extraWidth + organization.horizontalPadding <= organization.idealRowWidth; + } + + var sri = this.getShortestRowIndex(organization); + + if (sri < 0) { + return true; + } + + var min = organization.rowWidth[sri]; + + if (min + organization.horizontalPadding + extraWidth <= organization.width) return true; + + var hDiff = 0; + + // Adding to an existing row + if (organization.rowHeight[sri] < extraHeight) { + if (sri > 0) hDiff = extraHeight + organization.verticalPadding - organization.rowHeight[sri]; + } + + var add_to_row_ratio; + if (organization.width - min >= extraWidth + organization.horizontalPadding) { + add_to_row_ratio = (organization.height + hDiff) / (min + extraWidth + organization.horizontalPadding); + } else { + add_to_row_ratio = (organization.height + hDiff) / organization.width; + } + + // Adding a new row for this node + hDiff = extraHeight + organization.verticalPadding; + var add_new_row_ratio; + if (organization.width < extraWidth) { + add_new_row_ratio = (organization.height + hDiff) / extraWidth; + } else { + add_new_row_ratio = (organization.height + hDiff) / organization.width; + } + + if (add_new_row_ratio < 1) add_new_row_ratio = 1 / add_new_row_ratio; + + if (add_to_row_ratio < 1) add_to_row_ratio = 1 / add_to_row_ratio; + + return add_to_row_ratio < add_new_row_ratio; +}; + +//If moving the last node from the longest row and adding it to the last +//row makes the bounding box smaller, do it. +CoSELayout.prototype.shiftToLastRow = function (organization) { + var longest = this.getLongestRowIndex(organization); + var last = organization.rowWidth.length - 1; + var row = organization.rows[longest]; + var node = row[row.length - 1]; + + var diff = node.width + organization.horizontalPadding; + + // Check if there is enough space on the last row + if (organization.width - organization.rowWidth[last] > diff && longest != last) { + // Remove the last element of the longest row + row.splice(-1, 1); + + // Push it to the last row + organization.rows[last].push(node); + + organization.rowWidth[longest] = organization.rowWidth[longest] - diff; + organization.rowWidth[last] = organization.rowWidth[last] + diff; + organization.width = organization.rowWidth[instance.getLongestRowIndex(organization)]; + + // Update heights of the organization + var maxHeight = Number.MIN_VALUE; + for (var i = 0; i < row.length; i++) { + if (row[i].height > maxHeight) maxHeight = row[i].height; + } + if (longest > 0) maxHeight += organization.verticalPadding; + + var prevTotal = organization.rowHeight[longest] + organization.rowHeight[last]; + + organization.rowHeight[longest] = maxHeight; + if (organization.rowHeight[last] < node.height + organization.verticalPadding) organization.rowHeight[last] = node.height + organization.verticalPadding; + + var finalTotal = organization.rowHeight[longest] + organization.rowHeight[last]; + organization.height += finalTotal - prevTotal; + + this.shiftToLastRow(organization); + } +}; + +CoSELayout.prototype.tilingPreLayout = function () { + if (CoSEConstants.TILE) { + // Find zero degree nodes and create a compound for each level + this.groupZeroDegreeMembers(); + // Tile and clear children of each compound + this.clearCompounds(); + // Separately tile and clear zero degree nodes for each level + this.clearZeroDegreeMembers(); + } +}; + +CoSELayout.prototype.tilingPostLayout = function () { + if (CoSEConstants.TILE) { + this.repopulateZeroDegreeMembers(); + this.repopulateCompounds(); + } +}; + +// ----------------------------------------------------------------------------- +// Section: Tree Reduction methods +// ----------------------------------------------------------------------------- +// Reduce trees +CoSELayout.prototype.reduceTrees = function () { + var prunedNodesAll = []; + var containsLeaf = true; + var node; + + while (containsLeaf) { + var allNodes = this.graphManager.getAllNodes(); + var prunedNodesInStepTemp = []; + containsLeaf = false; + + for (var i = 0; i < allNodes.length; i++) { + node = allNodes[i]; + if (node.getEdges().length == 1 && !node.getEdges()[0].isInterGraph && node.getChild() == null) { + if (CoSEConstants.PURE_INCREMENTAL) { + var otherEnd = node.getEdges()[0].getOtherEnd(node); + var relativePosition = new DimensionD(node.getCenterX() - otherEnd.getCenterX(), node.getCenterY() - otherEnd.getCenterY()); + prunedNodesInStepTemp.push([node, node.getEdges()[0], node.getOwner(), relativePosition]); + } else { + prunedNodesInStepTemp.push([node, node.getEdges()[0], node.getOwner()]); + } + containsLeaf = true; + } + } + if (containsLeaf == true) { + var prunedNodesInStep = []; + for (var j = 0; j < prunedNodesInStepTemp.length; j++) { + if (prunedNodesInStepTemp[j][0].getEdges().length == 1) { + prunedNodesInStep.push(prunedNodesInStepTemp[j]); + prunedNodesInStepTemp[j][0].getOwner().remove(prunedNodesInStepTemp[j][0]); + } + } + prunedNodesAll.push(prunedNodesInStep); + this.graphManager.resetAllNodes(); + this.graphManager.resetAllEdges(); + } + } + this.prunedNodesAll = prunedNodesAll; +}; + +// Grow tree one step +CoSELayout.prototype.growTree = function (prunedNodesAll) { + var lengthOfPrunedNodesInStep = prunedNodesAll.length; + var prunedNodesInStep = prunedNodesAll[lengthOfPrunedNodesInStep - 1]; + + var nodeData; + for (var i = 0; i < prunedNodesInStep.length; i++) { + nodeData = prunedNodesInStep[i]; + + this.findPlaceforPrunedNode(nodeData); + + nodeData[2].add(nodeData[0]); + nodeData[2].add(nodeData[1], nodeData[1].source, nodeData[1].target); + } + + prunedNodesAll.splice(prunedNodesAll.length - 1, 1); + this.graphManager.resetAllNodes(); + this.graphManager.resetAllEdges(); +}; + +// Find an appropriate position to replace pruned node, this method can be improved +CoSELayout.prototype.findPlaceforPrunedNode = function (nodeData) { + + var gridForPrunedNode; + var nodeToConnect; + var prunedNode = nodeData[0]; + if (prunedNode == nodeData[1].source) { + nodeToConnect = nodeData[1].target; + } else { + nodeToConnect = nodeData[1].source; + } + + if (CoSEConstants.PURE_INCREMENTAL) { + prunedNode.setCenter(nodeToConnect.getCenterX() + nodeData[3].getWidth(), nodeToConnect.getCenterY() + nodeData[3].getHeight()); + } else { + var startGridX = nodeToConnect.startX; + var finishGridX = nodeToConnect.finishX; + var startGridY = nodeToConnect.startY; + var finishGridY = nodeToConnect.finishY; + + var upNodeCount = 0; + var downNodeCount = 0; + var rightNodeCount = 0; + var leftNodeCount = 0; + var controlRegions = [upNodeCount, rightNodeCount, downNodeCount, leftNodeCount]; + + if (startGridY > 0) { + for (var i = startGridX; i <= finishGridX; i++) { + controlRegions[0] += this.grid[i][startGridY - 1].length + this.grid[i][startGridY].length - 1; + } + } + if (finishGridX < this.grid.length - 1) { + for (var i = startGridY; i <= finishGridY; i++) { + controlRegions[1] += this.grid[finishGridX + 1][i].length + this.grid[finishGridX][i].length - 1; + } + } + if (finishGridY < this.grid[0].length - 1) { + for (var i = startGridX; i <= finishGridX; i++) { + controlRegions[2] += this.grid[i][finishGridY + 1].length + this.grid[i][finishGridY].length - 1; + } + } + if (startGridX > 0) { + for (var i = startGridY; i <= finishGridY; i++) { + controlRegions[3] += this.grid[startGridX - 1][i].length + this.grid[startGridX][i].length - 1; + } + } + var min = Integer.MAX_VALUE; + var minCount; + var minIndex; + for (var j = 0; j < controlRegions.length; j++) { + if (controlRegions[j] < min) { + min = controlRegions[j]; + minCount = 1; + minIndex = j; + } else if (controlRegions[j] == min) { + minCount++; + } + } + + if (minCount == 3 && min == 0) { + if (controlRegions[0] == 0 && controlRegions[1] == 0 && controlRegions[2] == 0) { + gridForPrunedNode = 1; + } else if (controlRegions[0] == 0 && controlRegions[1] == 0 && controlRegions[3] == 0) { + gridForPrunedNode = 0; + } else if (controlRegions[0] == 0 && controlRegions[2] == 0 && controlRegions[3] == 0) { + gridForPrunedNode = 3; + } else if (controlRegions[1] == 0 && controlRegions[2] == 0 && controlRegions[3] == 0) { + gridForPrunedNode = 2; + } + } else if (minCount == 2 && min == 0) { + var random = Math.floor(Math.random() * 2); + if (controlRegions[0] == 0 && controlRegions[1] == 0) { + ; + if (random == 0) { + gridForPrunedNode = 0; + } else { + gridForPrunedNode = 1; + } + } else if (controlRegions[0] == 0 && controlRegions[2] == 0) { + if (random == 0) { + gridForPrunedNode = 0; + } else { + gridForPrunedNode = 2; + } + } else if (controlRegions[0] == 0 && controlRegions[3] == 0) { + if (random == 0) { + gridForPrunedNode = 0; + } else { + gridForPrunedNode = 3; + } + } else if (controlRegions[1] == 0 && controlRegions[2] == 0) { + if (random == 0) { + gridForPrunedNode = 1; + } else { + gridForPrunedNode = 2; + } + } else if (controlRegions[1] == 0 && controlRegions[3] == 0) { + if (random == 0) { + gridForPrunedNode = 1; + } else { + gridForPrunedNode = 3; + } + } else { + if (random == 0) { + gridForPrunedNode = 2; + } else { + gridForPrunedNode = 3; + } + } + } else if (minCount == 4 && min == 0) { + var random = Math.floor(Math.random() * 4); + gridForPrunedNode = random; + } else { + gridForPrunedNode = minIndex; + } + + if (gridForPrunedNode == 0) { + prunedNode.setCenter(nodeToConnect.getCenterX(), nodeToConnect.getCenterY() - nodeToConnect.getHeight() / 2 - FDLayoutConstants.DEFAULT_EDGE_LENGTH - prunedNode.getHeight() / 2); + } else if (gridForPrunedNode == 1) { + prunedNode.setCenter(nodeToConnect.getCenterX() + nodeToConnect.getWidth() / 2 + FDLayoutConstants.DEFAULT_EDGE_LENGTH + prunedNode.getWidth() / 2, nodeToConnect.getCenterY()); + } else if (gridForPrunedNode == 2) { + prunedNode.setCenter(nodeToConnect.getCenterX(), nodeToConnect.getCenterY() + nodeToConnect.getHeight() / 2 + FDLayoutConstants.DEFAULT_EDGE_LENGTH + prunedNode.getHeight() / 2); + } else { + prunedNode.setCenter(nodeToConnect.getCenterX() - nodeToConnect.getWidth() / 2 - FDLayoutConstants.DEFAULT_EDGE_LENGTH - prunedNode.getWidth() / 2, nodeToConnect.getCenterY()); + } + } +}; + +module.exports = CoSELayout; + +/***/ }), + +/***/ 991: +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + + + +var FDLayoutNode = __webpack_require__(551).FDLayoutNode; +var IMath = __webpack_require__(551).IMath; + +function CoSENode(gm, loc, size, vNode) { + FDLayoutNode.call(this, gm, loc, size, vNode); +} + +CoSENode.prototype = Object.create(FDLayoutNode.prototype); +for (var prop in FDLayoutNode) { + CoSENode[prop] = FDLayoutNode[prop]; +} + +CoSENode.prototype.calculateDisplacement = function () { + var layout = this.graphManager.getLayout(); + // this check is for compound nodes that contain fixed nodes + if (this.getChild() != null && this.fixedNodeWeight) { + this.displacementX += layout.coolingFactor * (this.springForceX + this.repulsionForceX + this.gravitationForceX) / this.fixedNodeWeight; + this.displacementY += layout.coolingFactor * (this.springForceY + this.repulsionForceY + this.gravitationForceY) / this.fixedNodeWeight; + } else { + this.displacementX += layout.coolingFactor * (this.springForceX + this.repulsionForceX + this.gravitationForceX) / this.noOfChildren; + this.displacementY += layout.coolingFactor * (this.springForceY + this.repulsionForceY + this.gravitationForceY) / this.noOfChildren; + } + + if (Math.abs(this.displacementX) > layout.coolingFactor * layout.maxNodeDisplacement) { + this.displacementX = layout.coolingFactor * layout.maxNodeDisplacement * IMath.sign(this.displacementX); + } + + if (Math.abs(this.displacementY) > layout.coolingFactor * layout.maxNodeDisplacement) { + this.displacementY = layout.coolingFactor * layout.maxNodeDisplacement * IMath.sign(this.displacementY); + } + + // non-empty compound node, propogate movement to children as well + if (this.child && this.child.getNodes().length > 0) { + this.propogateDisplacementToChildren(this.displacementX, this.displacementY); + } +}; + +CoSENode.prototype.propogateDisplacementToChildren = function (dX, dY) { + var nodes = this.getChild().getNodes(); + var node; + for (var i = 0; i < nodes.length; i++) { + node = nodes[i]; + if (node.getChild() == null) { + node.displacementX += dX; + node.displacementY += dY; + } else { + node.propogateDisplacementToChildren(dX, dY); + } + } +}; + +CoSENode.prototype.move = function () { + var layout = this.graphManager.getLayout(); + + // a simple node or an empty compound node, move it + if (this.child == null || this.child.getNodes().length == 0) { + this.moveBy(this.displacementX, this.displacementY); + + layout.totalDisplacement += Math.abs(this.displacementX) + Math.abs(this.displacementY); + } + + this.springForceX = 0; + this.springForceY = 0; + this.repulsionForceX = 0; + this.repulsionForceY = 0; + this.gravitationForceX = 0; + this.gravitationForceY = 0; + this.displacementX = 0; + this.displacementY = 0; +}; + +CoSENode.prototype.setPred1 = function (pred1) { + this.pred1 = pred1; +}; + +CoSENode.prototype.getPred1 = function () { + return pred1; +}; + +CoSENode.prototype.getPred2 = function () { + return pred2; +}; + +CoSENode.prototype.setNext = function (next) { + this.next = next; +}; + +CoSENode.prototype.getNext = function () { + return next; +}; + +CoSENode.prototype.setProcessed = function (processed) { + this.processed = processed; +}; + +CoSENode.prototype.isProcessed = function () { + return processed; +}; + +module.exports = CoSENode; + +/***/ }), + +/***/ 902: +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + + + +function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } + +var CoSEConstants = __webpack_require__(806); +var LinkedList = __webpack_require__(551).LinkedList; +var Matrix = __webpack_require__(551).Matrix; +var SVD = __webpack_require__(551).SVD; + +function ConstraintHandler() {} + +ConstraintHandler.handleConstraints = function (layout) { + // let layout = this.graphManager.getLayout(); + + // get constraints from layout + var constraints = {}; + constraints.fixedNodeConstraint = layout.constraints.fixedNodeConstraint; + constraints.alignmentConstraint = layout.constraints.alignmentConstraint; + constraints.relativePlacementConstraint = layout.constraints.relativePlacementConstraint; + + var idToNodeMap = new Map(); + var nodeIndexes = new Map(); + var xCoords = []; + var yCoords = []; + + var allNodes = layout.getAllNodes(); + var index = 0; + // fill index map and coordinates + for (var i = 0; i < allNodes.length; i++) { + var node = allNodes[i]; + if (node.getChild() == null) { + nodeIndexes.set(node.id, index++); + xCoords.push(node.getCenterX()); + yCoords.push(node.getCenterY()); + idToNodeMap.set(node.id, node); + } + } + + // if there exists relative placement constraint without gap value, set it to default + if (constraints.relativePlacementConstraint) { + constraints.relativePlacementConstraint.forEach(function (constraint) { + if (!constraint.gap && constraint.gap != 0) { + if (constraint.left) { + constraint.gap = CoSEConstants.DEFAULT_EDGE_LENGTH + idToNodeMap.get(constraint.left).getWidth() / 2 + idToNodeMap.get(constraint.right).getWidth() / 2; + } else { + constraint.gap = CoSEConstants.DEFAULT_EDGE_LENGTH + idToNodeMap.get(constraint.top).getHeight() / 2 + idToNodeMap.get(constraint.bottom).getHeight() / 2; + } + } + }); + } + + /* auxiliary functions */ + + // calculate difference between two position objects + var calculatePositionDiff = function calculatePositionDiff(pos1, pos2) { + return { x: pos1.x - pos2.x, y: pos1.y - pos2.y }; + }; + + // calculate average position of the nodes + var calculateAvgPosition = function calculateAvgPosition(nodeIdSet) { + var xPosSum = 0; + var yPosSum = 0; + nodeIdSet.forEach(function (nodeId) { + xPosSum += xCoords[nodeIndexes.get(nodeId)]; + yPosSum += yCoords[nodeIndexes.get(nodeId)]; + }); + + return { x: xPosSum / nodeIdSet.size, y: yPosSum / nodeIdSet.size }; + }; + + // find an appropriate positioning for the nodes in a given graph according to relative placement constraints + // this function also takes the fixed nodes and alignment constraints into account + // graph: dag to be evaluated, direction: "horizontal" or "vertical", + // fixedNodes: set of fixed nodes to consider during evaluation, dummyPositions: appropriate coordinates of the dummy nodes + var findAppropriatePositionForRelativePlacement = function findAppropriatePositionForRelativePlacement(graph, direction, fixedNodes, dummyPositions, componentSources) { + + // find union of two sets + function setUnion(setA, setB) { + var union = new Set(setA); + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = setB[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var elem = _step.value; + + union.add(elem); + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } + } + + return union; + } + + // find indegree count for each node + var inDegrees = new Map(); + + graph.forEach(function (value, key) { + inDegrees.set(key, 0); + }); + graph.forEach(function (value, key) { + value.forEach(function (adjacent) { + inDegrees.set(adjacent.id, inDegrees.get(adjacent.id) + 1); + }); + }); + + var positionMap = new Map(); // keeps the position for each node + var pastMap = new Map(); // keeps the predecessors(past) of a node + var queue = new LinkedList(); + inDegrees.forEach(function (value, key) { + if (value == 0) { + queue.push(key); + if (!fixedNodes) { + if (direction == "horizontal") { + positionMap.set(key, nodeIndexes.has(key) ? xCoords[nodeIndexes.get(key)] : dummyPositions.get(key)); + } else { + positionMap.set(key, nodeIndexes.has(key) ? yCoords[nodeIndexes.get(key)] : dummyPositions.get(key)); + } + } + } else { + positionMap.set(key, Number.NEGATIVE_INFINITY); + } + if (fixedNodes) { + pastMap.set(key, new Set([key])); + } + }); + + // align sources of each component in enforcement phase + if (fixedNodes) { + componentSources.forEach(function (component) { + var fixedIds = []; + component.forEach(function (nodeId) { + if (fixedNodes.has(nodeId)) { + fixedIds.push(nodeId); + } + }); + if (fixedIds.length > 0) { + var position = 0; + fixedIds.forEach(function (fixedId) { + if (direction == "horizontal") { + positionMap.set(fixedId, nodeIndexes.has(fixedId) ? xCoords[nodeIndexes.get(fixedId)] : dummyPositions.get(fixedId)); + position += positionMap.get(fixedId); + } else { + positionMap.set(fixedId, nodeIndexes.has(fixedId) ? yCoords[nodeIndexes.get(fixedId)] : dummyPositions.get(fixedId)); + position += positionMap.get(fixedId); + } + }); + position = position / fixedIds.length; + component.forEach(function (nodeId) { + if (!fixedNodes.has(nodeId)) { + positionMap.set(nodeId, position); + } + }); + } else { + var _position = 0; + component.forEach(function (nodeId) { + if (direction == "horizontal") { + _position += nodeIndexes.has(nodeId) ? xCoords[nodeIndexes.get(nodeId)] : dummyPositions.get(nodeId); + } else { + _position += nodeIndexes.has(nodeId) ? yCoords[nodeIndexes.get(nodeId)] : dummyPositions.get(nodeId); + } + }); + _position = _position / component.length; + component.forEach(function (nodeId) { + positionMap.set(nodeId, _position); + }); + } + }); + } + + // calculate positions of the nodes + + var _loop = function _loop() { + var currentNode = queue.shift(); + var neighbors = graph.get(currentNode); + neighbors.forEach(function (neighbor) { + if (positionMap.get(neighbor.id) < positionMap.get(currentNode) + neighbor.gap) { + if (fixedNodes && fixedNodes.has(neighbor.id)) { + var fixedPosition = void 0; + if (direction == "horizontal") { + fixedPosition = nodeIndexes.has(neighbor.id) ? xCoords[nodeIndexes.get(neighbor.id)] : dummyPositions.get(neighbor.id); + } else { + fixedPosition = nodeIndexes.has(neighbor.id) ? yCoords[nodeIndexes.get(neighbor.id)] : dummyPositions.get(neighbor.id); + } + positionMap.set(neighbor.id, fixedPosition); // TODO: may do unnecessary work + if (fixedPosition < positionMap.get(currentNode) + neighbor.gap) { + var diff = positionMap.get(currentNode) + neighbor.gap - fixedPosition; + pastMap.get(currentNode).forEach(function (nodeId) { + positionMap.set(nodeId, positionMap.get(nodeId) - diff); + }); + } + } else { + positionMap.set(neighbor.id, positionMap.get(currentNode) + neighbor.gap); + } + } + inDegrees.set(neighbor.id, inDegrees.get(neighbor.id) - 1); + if (inDegrees.get(neighbor.id) == 0) { + queue.push(neighbor.id); + } + if (fixedNodes) { + pastMap.set(neighbor.id, setUnion(pastMap.get(currentNode), pastMap.get(neighbor.id))); + } + }); + }; + + while (queue.length != 0) { + _loop(); + } + + // readjust position of the nodes after enforcement + if (fixedNodes) { + // find indegree count for each node + var sinkNodes = new Set(); + + graph.forEach(function (value, key) { + if (value.length == 0) { + sinkNodes.add(key); + } + }); + + var _components = []; + pastMap.forEach(function (value, key) { + if (sinkNodes.has(key)) { + var isFixedComponent = false; + var _iteratorNormalCompletion2 = true; + var _didIteratorError2 = false; + var _iteratorError2 = undefined; + + try { + for (var _iterator2 = value[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { + var nodeId = _step2.value; + + if (fixedNodes.has(nodeId)) { + isFixedComponent = true; + } + } + } catch (err) { + _didIteratorError2 = true; + _iteratorError2 = err; + } finally { + try { + if (!_iteratorNormalCompletion2 && _iterator2.return) { + _iterator2.return(); + } + } finally { + if (_didIteratorError2) { + throw _iteratorError2; + } + } + } + + if (!isFixedComponent) { + var isExist = false; + var existAt = void 0; + _components.forEach(function (component, index) { + if (component.has([].concat(_toConsumableArray(value))[0])) { + isExist = true; + existAt = index; + } + }); + if (!isExist) { + _components.push(new Set(value)); + } else { + value.forEach(function (ele) { + _components[existAt].add(ele); + }); + } + } + } + }); + + _components.forEach(function (component, index) { + var minBefore = Number.POSITIVE_INFINITY; + var minAfter = Number.POSITIVE_INFINITY; + var maxBefore = Number.NEGATIVE_INFINITY; + var maxAfter = Number.NEGATIVE_INFINITY; + + var _iteratorNormalCompletion3 = true; + var _didIteratorError3 = false; + var _iteratorError3 = undefined; + + try { + for (var _iterator3 = component[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { + var nodeId = _step3.value; + + var posBefore = void 0; + if (direction == "horizontal") { + posBefore = nodeIndexes.has(nodeId) ? xCoords[nodeIndexes.get(nodeId)] : dummyPositions.get(nodeId); + } else { + posBefore = nodeIndexes.has(nodeId) ? yCoords[nodeIndexes.get(nodeId)] : dummyPositions.get(nodeId); + } + var posAfter = positionMap.get(nodeId); + if (posBefore < minBefore) { + minBefore = posBefore; + } + if (posBefore > maxBefore) { + maxBefore = posBefore; + } + if (posAfter < minAfter) { + minAfter = posAfter; + } + if (posAfter > maxAfter) { + maxAfter = posAfter; + } + } + } catch (err) { + _didIteratorError3 = true; + _iteratorError3 = err; + } finally { + try { + if (!_iteratorNormalCompletion3 && _iterator3.return) { + _iterator3.return(); + } + } finally { + if (_didIteratorError3) { + throw _iteratorError3; + } + } + } + + var diff = (minBefore + maxBefore) / 2 - (minAfter + maxAfter) / 2; + + var _iteratorNormalCompletion4 = true; + var _didIteratorError4 = false; + var _iteratorError4 = undefined; + + try { + for (var _iterator4 = component[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { + var _nodeId = _step4.value; + + positionMap.set(_nodeId, positionMap.get(_nodeId) + diff); + } + } catch (err) { + _didIteratorError4 = true; + _iteratorError4 = err; + } finally { + try { + if (!_iteratorNormalCompletion4 && _iterator4.return) { + _iterator4.return(); + } + } finally { + if (_didIteratorError4) { + throw _iteratorError4; + } + } + } + }); + } + + return positionMap; + }; + + // find transformation based on rel. placement constraints if there are both alignment and rel. placement constraints + // or if there are only rel. placement contraints where the largest component isn't sufficiently large + var applyReflectionForRelativePlacement = function applyReflectionForRelativePlacement(relativePlacementConstraints) { + // variables to count votes + var reflectOnY = 0, + notReflectOnY = 0; + var reflectOnX = 0, + notReflectOnX = 0; + + relativePlacementConstraints.forEach(function (constraint) { + if (constraint.left) { + xCoords[nodeIndexes.get(constraint.left)] - xCoords[nodeIndexes.get(constraint.right)] >= 0 ? reflectOnY++ : notReflectOnY++; + } else { + yCoords[nodeIndexes.get(constraint.top)] - yCoords[nodeIndexes.get(constraint.bottom)] >= 0 ? reflectOnX++ : notReflectOnX++; + } + }); + + if (reflectOnY > notReflectOnY && reflectOnX > notReflectOnX) { + for (var _i = 0; _i < nodeIndexes.size; _i++) { + xCoords[_i] = -1 * xCoords[_i]; + yCoords[_i] = -1 * yCoords[_i]; + } + } else if (reflectOnY > notReflectOnY) { + for (var _i2 = 0; _i2 < nodeIndexes.size; _i2++) { + xCoords[_i2] = -1 * xCoords[_i2]; + } + } else if (reflectOnX > notReflectOnX) { + for (var _i3 = 0; _i3 < nodeIndexes.size; _i3++) { + yCoords[_i3] = -1 * yCoords[_i3]; + } + } + }; + + // find weakly connected components in undirected graph + var findComponents = function findComponents(graph) { + // find weakly connected components in dag + var components = []; + var queue = new LinkedList(); + var visited = new Set(); + var count = 0; + + graph.forEach(function (value, key) { + if (!visited.has(key)) { + components[count] = []; + var _currentNode = key; + queue.push(_currentNode); + visited.add(_currentNode); + components[count].push(_currentNode); + + while (queue.length != 0) { + _currentNode = queue.shift(); + var neighbors = graph.get(_currentNode); + neighbors.forEach(function (neighbor) { + if (!visited.has(neighbor.id)) { + queue.push(neighbor.id); + visited.add(neighbor.id); + components[count].push(neighbor.id); + } + }); + } + count++; + } + }); + return components; + }; + + // return undirected version of given dag + var dagToUndirected = function dagToUndirected(dag) { + var undirected = new Map(); + + dag.forEach(function (value, key) { + undirected.set(key, []); + }); + + dag.forEach(function (value, key) { + value.forEach(function (adjacent) { + undirected.get(key).push(adjacent); + undirected.get(adjacent.id).push({ id: key, gap: adjacent.gap, direction: adjacent.direction }); + }); + }); + + return undirected; + }; + + // return reversed (directions inverted) version of given dag + var dagToReversed = function dagToReversed(dag) { + var reversed = new Map(); + + dag.forEach(function (value, key) { + reversed.set(key, []); + }); + + dag.forEach(function (value, key) { + value.forEach(function (adjacent) { + reversed.get(adjacent.id).push({ id: key, gap: adjacent.gap, direction: adjacent.direction }); + }); + }); + + return reversed; + }; + + /**** apply transformation to the initial draft layout to better align with constrained nodes ****/ + // solve the Orthogonal Procrustean Problem to rotate and/or reflect initial draft layout + // here we follow the solution in Chapter 20.2 of Borg, I. & Groenen, P. (2005) Modern Multidimensional Scaling: Theory and Applications + + /* construct source and target configurations */ + + var targetMatrix = []; // A - target configuration + var sourceMatrix = []; // B - source configuration + var standardTransformation = false; // false for no transformation, true for standart (Procrustes) transformation (rotation and/or reflection) + var reflectionType = false; // false/true for reflection check, 'reflectOnX', 'reflectOnY' or 'reflectOnBoth' for reflection type if necessary + var fixedNodes = new Set(); + var dag = new Map(); // adjacency list to keep directed acyclic graph (dag) that consists of relative placement constraints + var dagUndirected = new Map(); // undirected version of the dag + var components = []; // weakly connected components + + // fill fixedNodes collection to use later + if (constraints.fixedNodeConstraint) { + constraints.fixedNodeConstraint.forEach(function (nodeData) { + fixedNodes.add(nodeData.nodeId); + }); + } + + // construct dag from relative placement constraints + if (constraints.relativePlacementConstraint) { + // construct both directed and undirected version of the dag + constraints.relativePlacementConstraint.forEach(function (constraint) { + if (constraint.left) { + if (dag.has(constraint.left)) { + dag.get(constraint.left).push({ id: constraint.right, gap: constraint.gap, direction: "horizontal" }); + } else { + dag.set(constraint.left, [{ id: constraint.right, gap: constraint.gap, direction: "horizontal" }]); + } + if (!dag.has(constraint.right)) { + dag.set(constraint.right, []); + } + } else { + if (dag.has(constraint.top)) { + dag.get(constraint.top).push({ id: constraint.bottom, gap: constraint.gap, direction: "vertical" }); + } else { + dag.set(constraint.top, [{ id: constraint.bottom, gap: constraint.gap, direction: "vertical" }]); + } + if (!dag.has(constraint.bottom)) { + dag.set(constraint.bottom, []); + } + } + }); + + dagUndirected = dagToUndirected(dag); + components = findComponents(dagUndirected); + } + + if (CoSEConstants.TRANSFORM_ON_CONSTRAINT_HANDLING) { + // first check fixed node constraint + if (constraints.fixedNodeConstraint && constraints.fixedNodeConstraint.length > 1) { + constraints.fixedNodeConstraint.forEach(function (nodeData, i) { + targetMatrix[i] = [nodeData.position.x, nodeData.position.y]; + sourceMatrix[i] = [xCoords[nodeIndexes.get(nodeData.nodeId)], yCoords[nodeIndexes.get(nodeData.nodeId)]]; + }); + standardTransformation = true; + } else if (constraints.alignmentConstraint) { + (function () { + // then check alignment constraint + var count = 0; + if (constraints.alignmentConstraint.vertical) { + var verticalAlign = constraints.alignmentConstraint.vertical; + + var _loop2 = function _loop2(_i4) { + var alignmentSet = new Set(); + verticalAlign[_i4].forEach(function (nodeId) { + alignmentSet.add(nodeId); + }); + var intersection = new Set([].concat(_toConsumableArray(alignmentSet)).filter(function (x) { + return fixedNodes.has(x); + })); + var xPos = void 0; + if (intersection.size > 0) xPos = xCoords[nodeIndexes.get(intersection.values().next().value)];else xPos = calculateAvgPosition(alignmentSet).x; + + verticalAlign[_i4].forEach(function (nodeId) { + targetMatrix[count] = [xPos, yCoords[nodeIndexes.get(nodeId)]]; + sourceMatrix[count] = [xCoords[nodeIndexes.get(nodeId)], yCoords[nodeIndexes.get(nodeId)]]; + count++; + }); + }; + + for (var _i4 = 0; _i4 < verticalAlign.length; _i4++) { + _loop2(_i4); + } + standardTransformation = true; + } + if (constraints.alignmentConstraint.horizontal) { + var horizontalAlign = constraints.alignmentConstraint.horizontal; + + var _loop3 = function _loop3(_i5) { + var alignmentSet = new Set(); + horizontalAlign[_i5].forEach(function (nodeId) { + alignmentSet.add(nodeId); + }); + var intersection = new Set([].concat(_toConsumableArray(alignmentSet)).filter(function (x) { + return fixedNodes.has(x); + })); + var yPos = void 0; + if (intersection.size > 0) yPos = xCoords[nodeIndexes.get(intersection.values().next().value)];else yPos = calculateAvgPosition(alignmentSet).y; + + horizontalAlign[_i5].forEach(function (nodeId) { + targetMatrix[count] = [xCoords[nodeIndexes.get(nodeId)], yPos]; + sourceMatrix[count] = [xCoords[nodeIndexes.get(nodeId)], yCoords[nodeIndexes.get(nodeId)]]; + count++; + }); + }; + + for (var _i5 = 0; _i5 < horizontalAlign.length; _i5++) { + _loop3(_i5); + } + standardTransformation = true; + } + if (constraints.relativePlacementConstraint) { + reflectionType = true; + } + })(); + } else if (constraints.relativePlacementConstraint) { + // finally check relative placement constraint + // find largest component in dag + var largestComponentSize = 0; + var largestComponentIndex = 0; + for (var _i6 = 0; _i6 < components.length; _i6++) { + if (components[_i6].length > largestComponentSize) { + largestComponentSize = components[_i6].length; + largestComponentIndex = _i6; + } + } + // if largest component isn't dominant, then take the votes for reflection + if (largestComponentSize < dagUndirected.size / 2) { + applyReflectionForRelativePlacement(constraints.relativePlacementConstraint); + standardTransformation = false; + reflectionType = false; + } else { + // use largest component for transformation + // construct horizontal and vertical subgraphs in the largest component + var subGraphOnHorizontal = new Map(); + var subGraphOnVertical = new Map(); + var constraintsInlargestComponent = []; + + components[largestComponentIndex].forEach(function (nodeId) { + dag.get(nodeId).forEach(function (adjacent) { + if (adjacent.direction == "horizontal") { + if (subGraphOnHorizontal.has(nodeId)) { + subGraphOnHorizontal.get(nodeId).push(adjacent); + } else { + subGraphOnHorizontal.set(nodeId, [adjacent]); + } + if (!subGraphOnHorizontal.has(adjacent.id)) { + subGraphOnHorizontal.set(adjacent.id, []); + } + constraintsInlargestComponent.push({ left: nodeId, right: adjacent.id }); + } else { + if (subGraphOnVertical.has(nodeId)) { + subGraphOnVertical.get(nodeId).push(adjacent); + } else { + subGraphOnVertical.set(nodeId, [adjacent]); + } + if (!subGraphOnVertical.has(adjacent.id)) { + subGraphOnVertical.set(adjacent.id, []); + } + constraintsInlargestComponent.push({ top: nodeId, bottom: adjacent.id }); + } + }); + }); + + applyReflectionForRelativePlacement(constraintsInlargestComponent); + reflectionType = false; + + // calculate appropriate positioning for subgraphs + var positionMapHorizontal = findAppropriatePositionForRelativePlacement(subGraphOnHorizontal, "horizontal"); + var positionMapVertical = findAppropriatePositionForRelativePlacement(subGraphOnVertical, "vertical"); + + // construct source and target configuration + components[largestComponentIndex].forEach(function (nodeId, i) { + sourceMatrix[i] = [xCoords[nodeIndexes.get(nodeId)], yCoords[nodeIndexes.get(nodeId)]]; + targetMatrix[i] = []; + if (positionMapHorizontal.has(nodeId)) { + targetMatrix[i][0] = positionMapHorizontal.get(nodeId); + } else { + targetMatrix[i][0] = xCoords[nodeIndexes.get(nodeId)]; + } + if (positionMapVertical.has(nodeId)) { + targetMatrix[i][1] = positionMapVertical.get(nodeId); + } else { + targetMatrix[i][1] = yCoords[nodeIndexes.get(nodeId)]; + } + }); + + standardTransformation = true; + } + } + + // if transformation is required, then calculate and apply transformation matrix + if (standardTransformation) { + /* calculate transformation matrix */ + var transformationMatrix = void 0; + var targetMatrixTranspose = Matrix.transpose(targetMatrix); // A' + var sourceMatrixTranspose = Matrix.transpose(sourceMatrix); // B' + + // centralize transpose matrices + for (var _i7 = 0; _i7 < targetMatrixTranspose.length; _i7++) { + targetMatrixTranspose[_i7] = Matrix.multGamma(targetMatrixTranspose[_i7]); + sourceMatrixTranspose[_i7] = Matrix.multGamma(sourceMatrixTranspose[_i7]); + } + + // do actual calculation for transformation matrix + var tempMatrix = Matrix.multMat(targetMatrixTranspose, Matrix.transpose(sourceMatrixTranspose)); // tempMatrix = A'B + var SVDResult = SVD.svd(tempMatrix); // SVD(A'B) = USV', svd function returns U, S and V + transformationMatrix = Matrix.multMat(SVDResult.V, Matrix.transpose(SVDResult.U)); // transformationMatrix = T = VU' + + /* apply found transformation matrix to obtain final draft layout */ + for (var _i8 = 0; _i8 < nodeIndexes.size; _i8++) { + var temp1 = [xCoords[_i8], yCoords[_i8]]; + var temp2 = [transformationMatrix[0][0], transformationMatrix[1][0]]; + var temp3 = [transformationMatrix[0][1], transformationMatrix[1][1]]; + xCoords[_i8] = Matrix.dotProduct(temp1, temp2); + yCoords[_i8] = Matrix.dotProduct(temp1, temp3); + } + + // applied only both alignment and rel. placement constraints exist + if (reflectionType) { + applyReflectionForRelativePlacement(constraints.relativePlacementConstraint); + } + } + } + + if (CoSEConstants.ENFORCE_CONSTRAINTS) { + /**** enforce constraints on the transformed draft layout ****/ + + /* first enforce fixed node constraint */ + + if (constraints.fixedNodeConstraint && constraints.fixedNodeConstraint.length > 0) { + var translationAmount = { x: 0, y: 0 }; + constraints.fixedNodeConstraint.forEach(function (nodeData, i) { + var posInTheory = { x: xCoords[nodeIndexes.get(nodeData.nodeId)], y: yCoords[nodeIndexes.get(nodeData.nodeId)] }; + var posDesired = nodeData.position; + var posDiff = calculatePositionDiff(posDesired, posInTheory); + translationAmount.x += posDiff.x; + translationAmount.y += posDiff.y; + }); + translationAmount.x /= constraints.fixedNodeConstraint.length; + translationAmount.y /= constraints.fixedNodeConstraint.length; + + xCoords.forEach(function (value, i) { + xCoords[i] += translationAmount.x; + }); + + yCoords.forEach(function (value, i) { + yCoords[i] += translationAmount.y; + }); + + constraints.fixedNodeConstraint.forEach(function (nodeData) { + xCoords[nodeIndexes.get(nodeData.nodeId)] = nodeData.position.x; + yCoords[nodeIndexes.get(nodeData.nodeId)] = nodeData.position.y; + }); + } + + /* then enforce alignment constraint */ + + if (constraints.alignmentConstraint) { + if (constraints.alignmentConstraint.vertical) { + var xAlign = constraints.alignmentConstraint.vertical; + + var _loop4 = function _loop4(_i9) { + var alignmentSet = new Set(); + xAlign[_i9].forEach(function (nodeId) { + alignmentSet.add(nodeId); + }); + var intersection = new Set([].concat(_toConsumableArray(alignmentSet)).filter(function (x) { + return fixedNodes.has(x); + })); + var xPos = void 0; + if (intersection.size > 0) xPos = xCoords[nodeIndexes.get(intersection.values().next().value)];else xPos = calculateAvgPosition(alignmentSet).x; + + alignmentSet.forEach(function (nodeId) { + if (!fixedNodes.has(nodeId)) xCoords[nodeIndexes.get(nodeId)] = xPos; + }); + }; + + for (var _i9 = 0; _i9 < xAlign.length; _i9++) { + _loop4(_i9); + } + } + if (constraints.alignmentConstraint.horizontal) { + var yAlign = constraints.alignmentConstraint.horizontal; + + var _loop5 = function _loop5(_i10) { + var alignmentSet = new Set(); + yAlign[_i10].forEach(function (nodeId) { + alignmentSet.add(nodeId); + }); + var intersection = new Set([].concat(_toConsumableArray(alignmentSet)).filter(function (x) { + return fixedNodes.has(x); + })); + var yPos = void 0; + if (intersection.size > 0) yPos = yCoords[nodeIndexes.get(intersection.values().next().value)];else yPos = calculateAvgPosition(alignmentSet).y; + + alignmentSet.forEach(function (nodeId) { + if (!fixedNodes.has(nodeId)) yCoords[nodeIndexes.get(nodeId)] = yPos; + }); + }; + + for (var _i10 = 0; _i10 < yAlign.length; _i10++) { + _loop5(_i10); + } + } + } + + /* finally enforce relative placement constraint */ + + if (constraints.relativePlacementConstraint) { + (function () { + var nodeToDummyForVerticalAlignment = new Map(); + var nodeToDummyForHorizontalAlignment = new Map(); + var dummyToNodeForVerticalAlignment = new Map(); + var dummyToNodeForHorizontalAlignment = new Map(); + var dummyPositionsForVerticalAlignment = new Map(); + var dummyPositionsForHorizontalAlignment = new Map(); + var fixedNodesOnHorizontal = new Set(); + var fixedNodesOnVertical = new Set(); + + // fill maps and sets + fixedNodes.forEach(function (nodeId) { + fixedNodesOnHorizontal.add(nodeId); + fixedNodesOnVertical.add(nodeId); + }); + + if (constraints.alignmentConstraint) { + if (constraints.alignmentConstraint.vertical) { + var verticalAlignment = constraints.alignmentConstraint.vertical; + + var _loop6 = function _loop6(_i11) { + dummyToNodeForVerticalAlignment.set("dummy" + _i11, []); + verticalAlignment[_i11].forEach(function (nodeId) { + nodeToDummyForVerticalAlignment.set(nodeId, "dummy" + _i11); + dummyToNodeForVerticalAlignment.get("dummy" + _i11).push(nodeId); + if (fixedNodes.has(nodeId)) { + fixedNodesOnHorizontal.add("dummy" + _i11); + } + }); + dummyPositionsForVerticalAlignment.set("dummy" + _i11, xCoords[nodeIndexes.get(verticalAlignment[_i11][0])]); + }; + + for (var _i11 = 0; _i11 < verticalAlignment.length; _i11++) { + _loop6(_i11); + } + } + if (constraints.alignmentConstraint.horizontal) { + var horizontalAlignment = constraints.alignmentConstraint.horizontal; + + var _loop7 = function _loop7(_i12) { + dummyToNodeForHorizontalAlignment.set("dummy" + _i12, []); + horizontalAlignment[_i12].forEach(function (nodeId) { + nodeToDummyForHorizontalAlignment.set(nodeId, "dummy" + _i12); + dummyToNodeForHorizontalAlignment.get("dummy" + _i12).push(nodeId); + if (fixedNodes.has(nodeId)) { + fixedNodesOnVertical.add("dummy" + _i12); + } + }); + dummyPositionsForHorizontalAlignment.set("dummy" + _i12, yCoords[nodeIndexes.get(horizontalAlignment[_i12][0])]); + }; + + for (var _i12 = 0; _i12 < horizontalAlignment.length; _i12++) { + _loop7(_i12); + } + } + } + + // construct horizontal and vertical dags (subgraphs) from overall dag + var dagOnHorizontal = new Map(); + var dagOnVertical = new Map(); + + var _loop8 = function _loop8(nodeId) { + dag.get(nodeId).forEach(function (adjacent) { + var sourceId = void 0; + var targetNode = void 0; + if (adjacent["direction"] == "horizontal") { + sourceId = nodeToDummyForVerticalAlignment.get(nodeId) ? nodeToDummyForVerticalAlignment.get(nodeId) : nodeId; + if (nodeToDummyForVerticalAlignment.get(adjacent.id)) { + targetNode = { id: nodeToDummyForVerticalAlignment.get(adjacent.id), gap: adjacent.gap, direction: adjacent.direction }; + } else { + targetNode = adjacent; + } + if (dagOnHorizontal.has(sourceId)) { + dagOnHorizontal.get(sourceId).push(targetNode); + } else { + dagOnHorizontal.set(sourceId, [targetNode]); + } + if (!dagOnHorizontal.has(targetNode.id)) { + dagOnHorizontal.set(targetNode.id, []); + } + } else { + sourceId = nodeToDummyForHorizontalAlignment.get(nodeId) ? nodeToDummyForHorizontalAlignment.get(nodeId) : nodeId; + if (nodeToDummyForHorizontalAlignment.get(adjacent.id)) { + targetNode = { id: nodeToDummyForHorizontalAlignment.get(adjacent.id), gap: adjacent.gap, direction: adjacent.direction }; + } else { + targetNode = adjacent; + } + if (dagOnVertical.has(sourceId)) { + dagOnVertical.get(sourceId).push(targetNode); + } else { + dagOnVertical.set(sourceId, [targetNode]); + } + if (!dagOnVertical.has(targetNode.id)) { + dagOnVertical.set(targetNode.id, []); + } + } + }); + }; + + var _iteratorNormalCompletion5 = true; + var _didIteratorError5 = false; + var _iteratorError5 = undefined; + + try { + for (var _iterator5 = dag.keys()[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) { + var nodeId = _step5.value; + + _loop8(nodeId); + } + + // find source nodes of each component in horizontal and vertical dags + } catch (err) { + _didIteratorError5 = true; + _iteratorError5 = err; + } finally { + try { + if (!_iteratorNormalCompletion5 && _iterator5.return) { + _iterator5.return(); + } + } finally { + if (_didIteratorError5) { + throw _iteratorError5; + } + } + } + + var undirectedOnHorizontal = dagToUndirected(dagOnHorizontal); + var undirectedOnVertical = dagToUndirected(dagOnVertical); + var componentsOnHorizontal = findComponents(undirectedOnHorizontal); + var componentsOnVertical = findComponents(undirectedOnVertical); + var reversedDagOnHorizontal = dagToReversed(dagOnHorizontal); + var reversedDagOnVertical = dagToReversed(dagOnVertical); + var componentSourcesOnHorizontal = []; + var componentSourcesOnVertical = []; + + componentsOnHorizontal.forEach(function (component, index) { + componentSourcesOnHorizontal[index] = []; + component.forEach(function (nodeId) { + if (reversedDagOnHorizontal.get(nodeId).length == 0) { + componentSourcesOnHorizontal[index].push(nodeId); + } + }); + }); + + componentsOnVertical.forEach(function (component, index) { + componentSourcesOnVertical[index] = []; + component.forEach(function (nodeId) { + if (reversedDagOnVertical.get(nodeId).length == 0) { + componentSourcesOnVertical[index].push(nodeId); + } + }); + }); + + // calculate appropriate positioning for subgraphs + var positionMapHorizontal = findAppropriatePositionForRelativePlacement(dagOnHorizontal, "horizontal", fixedNodesOnHorizontal, dummyPositionsForVerticalAlignment, componentSourcesOnHorizontal); + var positionMapVertical = findAppropriatePositionForRelativePlacement(dagOnVertical, "vertical", fixedNodesOnVertical, dummyPositionsForHorizontalAlignment, componentSourcesOnVertical); + + // update positions of the nodes based on relative placement constraints + + var _loop9 = function _loop9(key) { + if (dummyToNodeForVerticalAlignment.get(key)) { + dummyToNodeForVerticalAlignment.get(key).forEach(function (nodeId) { + xCoords[nodeIndexes.get(nodeId)] = positionMapHorizontal.get(key); + }); + } else { + xCoords[nodeIndexes.get(key)] = positionMapHorizontal.get(key); + } + }; + + var _iteratorNormalCompletion6 = true; + var _didIteratorError6 = false; + var _iteratorError6 = undefined; + + try { + for (var _iterator6 = positionMapHorizontal.keys()[Symbol.iterator](), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) { + var key = _step6.value; + + _loop9(key); + } + } catch (err) { + _didIteratorError6 = true; + _iteratorError6 = err; + } finally { + try { + if (!_iteratorNormalCompletion6 && _iterator6.return) { + _iterator6.return(); + } + } finally { + if (_didIteratorError6) { + throw _iteratorError6; + } + } + } + + var _loop10 = function _loop10(key) { + if (dummyToNodeForHorizontalAlignment.get(key)) { + dummyToNodeForHorizontalAlignment.get(key).forEach(function (nodeId) { + yCoords[nodeIndexes.get(nodeId)] = positionMapVertical.get(key); + }); + } else { + yCoords[nodeIndexes.get(key)] = positionMapVertical.get(key); + } + }; + + var _iteratorNormalCompletion7 = true; + var _didIteratorError7 = false; + var _iteratorError7 = undefined; + + try { + for (var _iterator7 = positionMapVertical.keys()[Symbol.iterator](), _step7; !(_iteratorNormalCompletion7 = (_step7 = _iterator7.next()).done); _iteratorNormalCompletion7 = true) { + var key = _step7.value; + + _loop10(key); + } + } catch (err) { + _didIteratorError7 = true; + _iteratorError7 = err; + } finally { + try { + if (!_iteratorNormalCompletion7 && _iterator7.return) { + _iterator7.return(); + } + } finally { + if (_didIteratorError7) { + throw _iteratorError7; + } + } + } + })(); + } + } + + // assign new coordinates to nodes after constraint handling + for (var _i13 = 0; _i13 < allNodes.length; _i13++) { + var _node = allNodes[_i13]; + if (_node.getChild() == null) { + _node.setCenter(xCoords[nodeIndexes.get(_node.id)], yCoords[nodeIndexes.get(_node.id)]); + } + } +}; + +module.exports = ConstraintHandler; + +/***/ }), + +/***/ 551: +/***/ ((module) => { + +module.exports = __WEBPACK_EXTERNAL_MODULE__551__; + +/***/ }) + +/******/ }); +/************************************************************************/ +/******/ // The module cache +/******/ var __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ var cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/************************************************************************/ +/******/ +/******/ // startup +/******/ // Load entry module and return exports +/******/ // This entry module is referenced by other modules so it can't be inlined +/******/ var __webpack_exports__ = __webpack_require__(45); +/******/ +/******/ return __webpack_exports__; +/******/ })() +; +}); \ No newline at end of file diff --git a/site/site/public/js/cytoscape-cose-bilkent.js b/site/site/public/js/cytoscape-cose-bilkent.js new file mode 100644 index 0000000..f8847be --- /dev/null +++ b/site/site/public/js/cytoscape-cose-bilkent.js @@ -0,0 +1,5296 @@ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.cytoscapeCoseBilkent = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0) + + { + this.positionNodesRadially(forest); + } + // The graph associated with this layout is not flat or a forest + else + { + this.positionNodesRandomly(); + } + } + + this.initSpringEmbedder(); + this.runSpringEmbedder(); + + return true; +}; + +CoSELayout.prototype.tick = function() { + this.totalIterations++; + + if (this.totalIterations === this.maxIterations) { + return true; // Layout is not ended return true + } + + if (this.totalIterations % FDLayoutConstants.CONVERGENCE_CHECK_PERIOD == 0) + { + if (this.isConverged()) + { + return true; // Layout is not ended return true + } + + this.coolingFactor = this.initialCoolingFactor * + ((this.maxIterations - this.totalIterations) / this.maxIterations); + this.animationPeriod = Math.ceil(this.initialAnimationPeriod * Math.sqrt(this.coolingFactor)); + + } + this.totalDisplacement = 0; + this.graphManager.updateBounds(); + this.calcSpringForces(); + this.calcRepulsionForces(); + this.calcGravitationalForces(); + this.moveNodes(); + this.animate(); + + return false; // Layout is not ended yet return false +}; + +CoSELayout.prototype.getPositionsData = function() { + var allNodes = this.graphManager.getAllNodes(); + var pData = {}; + for (var i = 0; i < allNodes.length; i++) { + var rect = allNodes[i].rect; + var id = allNodes[i].id; + pData[id] = { + id: id, + x: rect.getCenterX(), + y: rect.getCenterY(), + w: rect.width, + h: rect.height + }; + } + + return pData; +}; + +CoSELayout.prototype.runSpringEmbedder = function () { + this.initialAnimationPeriod = 25; + this.animationPeriod = this.initialAnimationPeriod; + var layoutEnded = false; + + // If aminate option is 'during' signal that layout is supposed to start iterating + if ( FDLayoutConstants.ANIMATE === 'during' ) { + this.emit('layoutstarted'); + } + else { + // If aminate option is 'during' tick() function will be called on index.js + while (!layoutEnded) { + layoutEnded = this.tick(); + } + + this.graphManager.updateBounds(); + } +}; + +CoSELayout.prototype.calculateNodesToApplyGravitationTo = function () { + var nodeList = []; + var graph; + + var graphs = this.graphManager.getGraphs(); + var size = graphs.length; + var i; + for (i = 0; i < size; i++) + { + graph = graphs[i]; + + graph.updateConnected(); + + if (!graph.isConnected) + { + nodeList = nodeList.concat(graph.getNodes()); + } + } + + this.graphManager.setAllNodesToApplyGravitation(nodeList); +}; + +CoSELayout.prototype.createBendpoints = function () { + var edges = []; + edges = edges.concat(this.graphManager.getAllEdges()); + var visited = new HashSet(); + var i; + for (i = 0; i < edges.length; i++) + { + var edge = edges[i]; + + if (!visited.contains(edge)) + { + var source = edge.getSource(); + var target = edge.getTarget(); + + if (source == target) + { + edge.getBendpoints().push(new PointD()); + edge.getBendpoints().push(new PointD()); + this.createDummyNodesForBendpoints(edge); + visited.add(edge); + } + else + { + var edgeList = []; + + edgeList = edgeList.concat(source.getEdgeListToNode(target)); + edgeList = edgeList.concat(target.getEdgeListToNode(source)); + + if (!visited.contains(edgeList[0])) + { + if (edgeList.length > 1) + { + var k; + for (k = 0; k < edgeList.length; k++) + { + var multiEdge = edgeList[k]; + multiEdge.getBendpoints().push(new PointD()); + this.createDummyNodesForBendpoints(multiEdge); + } + } + visited.addAll(list); + } + } + } + + if (visited.size() == edges.length) + { + break; + } + } +}; + +CoSELayout.prototype.positionNodesRadially = function (forest) { + // We tile the trees to a grid row by row; first tree starts at (0,0) + var currentStartingPoint = new Point(0, 0); + var numberOfColumns = Math.ceil(Math.sqrt(forest.length)); + var height = 0; + var currentY = 0; + var currentX = 0; + var point = new PointD(0, 0); + + for (var i = 0; i < forest.length; i++) + { + if (i % numberOfColumns == 0) + { + // Start of a new row, make the x coordinate 0, increment the + // y coordinate with the max height of the previous row + currentX = 0; + currentY = height; + + if (i != 0) + { + currentY += CoSEConstants.DEFAULT_COMPONENT_SEPERATION; + } + + height = 0; + } + + var tree = forest[i]; + + // Find the center of the tree + var centerNode = Layout.findCenterOfTree(tree); + + // Set the staring point of the next tree + currentStartingPoint.x = currentX; + currentStartingPoint.y = currentY; + + // Do a radial layout starting with the center + point = + CoSELayout.radialLayout(tree, centerNode, currentStartingPoint); + + if (point.y > height) + { + height = Math.floor(point.y); + } + + currentX = Math.floor(point.x + CoSEConstants.DEFAULT_COMPONENT_SEPERATION); + } + + this.transform( + new PointD(LayoutConstants.WORLD_CENTER_X - point.x / 2, + LayoutConstants.WORLD_CENTER_Y - point.y / 2)); +}; + +CoSELayout.radialLayout = function (tree, centerNode, startingPoint) { + var radialSep = Math.max(this.maxDiagonalInTree(tree), + CoSEConstants.DEFAULT_RADIAL_SEPARATION); + CoSELayout.branchRadialLayout(centerNode, null, 0, 359, 0, radialSep); + var bounds = LGraph.calculateBounds(tree); + + var transform = new Transform(); + transform.setDeviceOrgX(bounds.getMinX()); + transform.setDeviceOrgY(bounds.getMinY()); + transform.setWorldOrgX(startingPoint.x); + transform.setWorldOrgY(startingPoint.y); + + for (var i = 0; i < tree.length; i++) + { + var node = tree[i]; + node.transform(transform); + } + + var bottomRight = + new PointD(bounds.getMaxX(), bounds.getMaxY()); + + return transform.inverseTransformPoint(bottomRight); +}; + +CoSELayout.branchRadialLayout = function (node, parentOfNode, startAngle, endAngle, distance, radialSeparation) { + // First, position this node by finding its angle. + var halfInterval = ((endAngle - startAngle) + 1) / 2; + + if (halfInterval < 0) + { + halfInterval += 180; + } + + var nodeAngle = (halfInterval + startAngle) % 360; + var teta = (nodeAngle * IGeometry.TWO_PI) / 360; + + // Make polar to java cordinate conversion. + var cos_teta = Math.cos(teta); + var x_ = distance * Math.cos(teta); + var y_ = distance * Math.sin(teta); + + node.setCenter(x_, y_); + + // Traverse all neighbors of this node and recursively call this + // function. + var neighborEdges = []; + neighborEdges = neighborEdges.concat(node.getEdges()); + var childCount = neighborEdges.length; + + if (parentOfNode != null) + { + childCount--; + } + + var branchCount = 0; + + var incEdgesCount = neighborEdges.length; + var startIndex; + + var edges = node.getEdgesBetween(parentOfNode); + + // If there are multiple edges, prune them until there remains only one + // edge. + while (edges.length > 1) + { + //neighborEdges.remove(edges.remove(0)); + var temp = edges[0]; + edges.splice(0, 1); + var index = neighborEdges.indexOf(temp); + if (index >= 0) { + neighborEdges.splice(index, 1); + } + incEdgesCount--; + childCount--; + } + + if (parentOfNode != null) + { + //assert edges.length == 1; + startIndex = (neighborEdges.indexOf(edges[0]) + 1) % incEdgesCount; + } + else + { + startIndex = 0; + } + + var stepAngle = Math.abs(endAngle - startAngle) / childCount; + + for (var i = startIndex; + branchCount != childCount; + i = (++i) % incEdgesCount) + { + var currentNeighbor = + neighborEdges[i].getOtherEnd(node); + + // Don't back traverse to root node in current tree. + if (currentNeighbor == parentOfNode) + { + continue; + } + + var childStartAngle = + (startAngle + branchCount * stepAngle) % 360; + var childEndAngle = (childStartAngle + stepAngle) % 360; + + CoSELayout.branchRadialLayout(currentNeighbor, + node, + childStartAngle, childEndAngle, + distance + radialSeparation, radialSeparation); + + branchCount++; + } +}; + +CoSELayout.maxDiagonalInTree = function (tree) { + var maxDiagonal = Integer.MIN_VALUE; + + for (var i = 0; i < tree.length; i++) + { + var node = tree[i]; + var diagonal = node.getDiagonal(); + + if (diagonal > maxDiagonal) + { + maxDiagonal = diagonal; + } + } + + return maxDiagonal; +}; + +CoSELayout.prototype.calcRepulsionRange = function () { + // formula is 2 x (level + 1) x idealEdgeLength + return (2 * (this.level + 1) * this.idealEdgeLength); +}; + +module.exports = CoSELayout; + +},{"./CoSEConstants":1,"./CoSEEdge":2,"./CoSEGraph":3,"./CoSEGraphManager":4,"./CoSENode":6,"./FDLayout":9,"./FDLayoutConstants":10,"./IGeometry":15,"./Integer":17,"./LGraph":19,"./Layout":23,"./LayoutConstants":24,"./Point":25,"./PointD":26,"./Transform":30}],6:[function(require,module,exports){ +var FDLayoutNode = require('./FDLayoutNode'); +var IMath = require('./IMath'); + +function CoSENode(gm, loc, size, vNode) { + FDLayoutNode.call(this, gm, loc, size, vNode); +} + + +CoSENode.prototype = Object.create(FDLayoutNode.prototype); +for (var prop in FDLayoutNode) { + CoSENode[prop] = FDLayoutNode[prop]; +} + +CoSENode.prototype.move = function () +{ + var layout = this.graphManager.getLayout(); + this.displacementX = layout.coolingFactor * + (this.springForceX + this.repulsionForceX + this.gravitationForceX); + this.displacementY = layout.coolingFactor * + (this.springForceY + this.repulsionForceY + this.gravitationForceY); + + + if (Math.abs(this.displacementX) > layout.coolingFactor * layout.maxNodeDisplacement) + { + this.displacementX = layout.coolingFactor * layout.maxNodeDisplacement * + IMath.sign(this.displacementX); + } + + if (Math.abs(this.displacementY) > layout.coolingFactor * layout.maxNodeDisplacement) + { + this.displacementY = layout.coolingFactor * layout.maxNodeDisplacement * + IMath.sign(this.displacementY); + } + + // a simple node, just move it + if (this.child == null) + { + this.moveBy(this.displacementX, this.displacementY); + } + // an empty compound node, again just move it + else if (this.child.getNodes().length == 0) + { + this.moveBy(this.displacementX, this.displacementY); + } + // non-empty compound node, propogate movement to children as well + else + { + this.propogateDisplacementToChildren(this.displacementX, + this.displacementY); + } + + layout.totalDisplacement += + Math.abs(this.displacementX) + Math.abs(this.displacementY); + + this.springForceX = 0; + this.springForceY = 0; + this.repulsionForceX = 0; + this.repulsionForceY = 0; + this.gravitationForceX = 0; + this.gravitationForceY = 0; + this.displacementX = 0; + this.displacementY = 0; +}; + +CoSENode.prototype.propogateDisplacementToChildren = function (dX, dY) +{ + var nodes = this.getChild().getNodes(); + var node; + for (var i = 0; i < nodes.length; i++) + { + node = nodes[i]; + if (node.getChild() == null) + { + node.moveBy(dX, dY); + node.displacementX += dX; + node.displacementY += dY; + } + else + { + node.propogateDisplacementToChildren(dX, dY); + } + } +}; + +CoSENode.prototype.setPred1 = function (pred1) +{ + this.pred1 = pred1; +}; + +CoSENode.prototype.getPred1 = function () +{ + return pred1; +}; + +CoSENode.prototype.getPred2 = function () +{ + return pred2; +}; + +CoSENode.prototype.setNext = function (next) +{ + this.next = next; +}; + +CoSENode.prototype.getNext = function () +{ + return next; +}; + +CoSENode.prototype.setProcessed = function (processed) +{ + this.processed = processed; +}; + +CoSENode.prototype.isProcessed = function () +{ + return processed; +}; + +module.exports = CoSENode; + +},{"./FDLayoutNode":12,"./IMath":16}],7:[function(require,module,exports){ +function DimensionD(width, height) { + this.width = 0; + this.height = 0; + if (width !== null && height !== null) { + this.height = height; + this.width = width; + } +} + +DimensionD.prototype.getWidth = function () +{ + return this.width; +}; + +DimensionD.prototype.setWidth = function (width) +{ + this.width = width; +}; + +DimensionD.prototype.getHeight = function () +{ + return this.height; +}; + +DimensionD.prototype.setHeight = function (height) +{ + this.height = height; +}; + +module.exports = DimensionD; + +},{}],8:[function(require,module,exports){ +function Emitter(){ + this.listeners = []; +} + +var p = Emitter.prototype; + +p.addListener = function( event, callback ){ + this.listeners.push({ + event: event, + callback: callback + }); +}; + +p.removeListener = function( event, callback ){ + for( var i = this.listeners.length; i >= 0; i-- ){ + var l = this.listeners[i]; + + if( l.event === event && l.callback === callback ){ + this.listeners.splice( i, 1 ); + } + } +}; + +p.emit = function( event, data ){ + for( var i = 0; i < this.listeners.length; i++ ){ + var l = this.listeners[i]; + + if( event === l.event ){ + l.callback( data ); + } + } +}; + +module.exports = Emitter; + +},{}],9:[function(require,module,exports){ +var Layout = require('./Layout'); +var FDLayoutConstants = require('./FDLayoutConstants'); +var LayoutConstants = require('./LayoutConstants'); +var IGeometry = require('./IGeometry'); +var IMath = require('./IMath'); + +function FDLayout() { + Layout.call(this); + + this.useSmartIdealEdgeLengthCalculation = FDLayoutConstants.DEFAULT_USE_SMART_IDEAL_EDGE_LENGTH_CALCULATION; + this.idealEdgeLength = FDLayoutConstants.DEFAULT_EDGE_LENGTH; + this.springConstant = FDLayoutConstants.DEFAULT_SPRING_STRENGTH; + this.repulsionConstant = FDLayoutConstants.DEFAULT_REPULSION_STRENGTH; + this.gravityConstant = FDLayoutConstants.DEFAULT_GRAVITY_STRENGTH; + this.compoundGravityConstant = FDLayoutConstants.DEFAULT_COMPOUND_GRAVITY_STRENGTH; + this.gravityRangeFactor = FDLayoutConstants.DEFAULT_GRAVITY_RANGE_FACTOR; + this.compoundGravityRangeFactor = FDLayoutConstants.DEFAULT_COMPOUND_GRAVITY_RANGE_FACTOR; + this.displacementThresholdPerNode = (3.0 * FDLayoutConstants.DEFAULT_EDGE_LENGTH) / 100; + this.coolingFactor = 1.0; + this.initialCoolingFactor = 1.0; + this.totalDisplacement = 0.0; + this.oldTotalDisplacement = 0.0; + this.maxIterations = FDLayoutConstants.MAX_ITERATIONS; +} + +FDLayout.prototype = Object.create(Layout.prototype); + +for (var prop in Layout) { + FDLayout[prop] = Layout[prop]; +} + +FDLayout.prototype.initParameters = function () { + Layout.prototype.initParameters.call(this, arguments); + + if (this.layoutQuality == LayoutConstants.DRAFT_QUALITY) + { + this.displacementThresholdPerNode += 0.30; + this.maxIterations *= 0.8; + } + else if (this.layoutQuality == LayoutConstants.PROOF_QUALITY) + { + this.displacementThresholdPerNode -= 0.30; + this.maxIterations *= 1.2; + } + + this.totalIterations = 0; + this.notAnimatedIterations = 0; + +// this.useFRGridVariant = layoutOptionsPack.smartRepulsionRangeCalc; +}; + +FDLayout.prototype.calcIdealEdgeLengths = function () { + var edge; + var lcaDepth; + var source; + var target; + var sizeOfSourceInLca; + var sizeOfTargetInLca; + + var allEdges = this.getGraphManager().getAllEdges(); + for (var i = 0; i < allEdges.length; i++) + { + edge = allEdges[i]; + + edge.idealLength = this.idealEdgeLength; + + if (edge.isInterGraph) + { + source = edge.getSource(); + target = edge.getTarget(); + + sizeOfSourceInLca = edge.getSourceInLca().getEstimatedSize(); + sizeOfTargetInLca = edge.getTargetInLca().getEstimatedSize(); + + if (this.useSmartIdealEdgeLengthCalculation) + { + edge.idealLength += sizeOfSourceInLca + sizeOfTargetInLca - + 2 * LayoutConstants.SIMPLE_NODE_SIZE; + } + + lcaDepth = edge.getLca().getInclusionTreeDepth(); + + edge.idealLength += FDLayoutConstants.DEFAULT_EDGE_LENGTH * + FDLayoutConstants.PER_LEVEL_IDEAL_EDGE_LENGTH_FACTOR * + (source.getInclusionTreeDepth() + + target.getInclusionTreeDepth() - 2 * lcaDepth); + } + } +}; + +FDLayout.prototype.initSpringEmbedder = function () { + + if (this.incremental) + { + this.coolingFactor = 0.8; + this.initialCoolingFactor = 0.8; + this.maxNodeDisplacement = + FDLayoutConstants.MAX_NODE_DISPLACEMENT_INCREMENTAL; + } + else + { + this.coolingFactor = 1.0; + this.initialCoolingFactor = 1.0; + this.maxNodeDisplacement = + FDLayoutConstants.MAX_NODE_DISPLACEMENT; + } + + this.maxIterations = + Math.max(this.getAllNodes().length * 5, this.maxIterations); + + this.totalDisplacementThreshold = + this.displacementThresholdPerNode * this.getAllNodes().length; + + this.repulsionRange = this.calcRepulsionRange(); +}; + +FDLayout.prototype.calcSpringForces = function () { + var lEdges = this.getAllEdges(); + var edge; + + for (var i = 0; i < lEdges.length; i++) + { + edge = lEdges[i]; + + this.calcSpringForce(edge, edge.idealLength); + } +}; + +FDLayout.prototype.calcRepulsionForces = function () { + var i, j; + var nodeA, nodeB; + var lNodes = this.getAllNodes(); + + for (i = 0; i < lNodes.length; i++) + { + nodeA = lNodes[i]; + + for (j = i + 1; j < lNodes.length; j++) + { + nodeB = lNodes[j]; + + // If both nodes are not members of the same graph, skip. + if (nodeA.getOwner() != nodeB.getOwner()) + { + continue; + } + + this.calcRepulsionForce(nodeA, nodeB); + } + } +}; + +FDLayout.prototype.calcGravitationalForces = function () { + var node; + var lNodes = this.getAllNodesToApplyGravitation(); + + for (var i = 0; i < lNodes.length; i++) + { + node = lNodes[i]; + this.calcGravitationalForce(node); + } +}; + +FDLayout.prototype.moveNodes = function () { + var lNodes = this.getAllNodes(); + var node; + + for (var i = 0; i < lNodes.length; i++) + { + node = lNodes[i]; + node.move(); + } +} + +FDLayout.prototype.calcSpringForce = function (edge, idealLength) { + var sourceNode = edge.getSource(); + var targetNode = edge.getTarget(); + + var length; + var springForce; + var springForceX; + var springForceY; + + // Update edge length + if (this.uniformLeafNodeSizes && + sourceNode.getChild() == null && targetNode.getChild() == null) + { + edge.updateLengthSimple(); + } + else + { + edge.updateLength(); + + if (edge.isOverlapingSourceAndTarget) + { + return; + } + } + + length = edge.getLength(); + + // Calculate spring forces + springForce = this.springConstant * (length - idealLength); + + // Project force onto x and y axes + springForceX = springForce * (edge.lengthX / length); + springForceY = springForce * (edge.lengthY / length); + + // Apply forces on the end nodes + sourceNode.springForceX += springForceX; + sourceNode.springForceY += springForceY; + targetNode.springForceX -= springForceX; + targetNode.springForceY -= springForceY; +}; + +FDLayout.prototype.calcRepulsionForce = function (nodeA, nodeB) { + var rectA = nodeA.getRect(); + var rectB = nodeB.getRect(); + var overlapAmount = new Array(2); + var clipPoints = new Array(4); + var distanceX; + var distanceY; + var distanceSquared; + var distance; + var repulsionForce; + var repulsionForceX; + var repulsionForceY; + + if (rectA.intersects(rectB))// two nodes overlap + { + // calculate separation amount in x and y directions + IGeometry.calcSeparationAmount(rectA, + rectB, + overlapAmount, + FDLayoutConstants.DEFAULT_EDGE_LENGTH / 2.0); + + repulsionForceX = overlapAmount[0]; + repulsionForceY = overlapAmount[1]; + } + else// no overlap + { + // calculate distance + + if (this.uniformLeafNodeSizes && + nodeA.getChild() == null && nodeB.getChild() == null)// simply base repulsion on distance of node centers + { + distanceX = rectB.getCenterX() - rectA.getCenterX(); + distanceY = rectB.getCenterY() - rectA.getCenterY(); + } + else// use clipping points + { + IGeometry.getIntersection(rectA, rectB, clipPoints); + + distanceX = clipPoints[2] - clipPoints[0]; + distanceY = clipPoints[3] - clipPoints[1]; + } + + // No repulsion range. FR grid variant should take care of this. + if (Math.abs(distanceX) < FDLayoutConstants.MIN_REPULSION_DIST) + { + distanceX = IMath.sign(distanceX) * + FDLayoutConstants.MIN_REPULSION_DIST; + } + + if (Math.abs(distanceY) < FDLayoutConstants.MIN_REPULSION_DIST) + { + distanceY = IMath.sign(distanceY) * + FDLayoutConstants.MIN_REPULSION_DIST; + } + + distanceSquared = distanceX * distanceX + distanceY * distanceY; + distance = Math.sqrt(distanceSquared); + + repulsionForce = this.repulsionConstant / distanceSquared; + + // Project force onto x and y axes + repulsionForceX = repulsionForce * distanceX / distance; + repulsionForceY = repulsionForce * distanceY / distance; + } + + // Apply forces on the two nodes + nodeA.repulsionForceX -= repulsionForceX; + nodeA.repulsionForceY -= repulsionForceY; + nodeB.repulsionForceX += repulsionForceX; + nodeB.repulsionForceY += repulsionForceY; +}; + +FDLayout.prototype.calcGravitationalForce = function (node) { + var ownerGraph; + var ownerCenterX; + var ownerCenterY; + var distanceX; + var distanceY; + var absDistanceX; + var absDistanceY; + var estimatedSize; + ownerGraph = node.getOwner(); + + ownerCenterX = (ownerGraph.getRight() + ownerGraph.getLeft()) / 2; + ownerCenterY = (ownerGraph.getTop() + ownerGraph.getBottom()) / 2; + distanceX = node.getCenterX() - ownerCenterX; + distanceY = node.getCenterY() - ownerCenterY; + absDistanceX = Math.abs(distanceX); + absDistanceY = Math.abs(distanceY); + + if (node.getOwner() == this.graphManager.getRoot())// in the root graph + { + Math.floor(80); + estimatedSize = Math.floor(ownerGraph.getEstimatedSize() * + this.gravityRangeFactor); + + if (absDistanceX > estimatedSize || absDistanceY > estimatedSize) + { + node.gravitationForceX = -this.gravityConstant * distanceX; + node.gravitationForceY = -this.gravityConstant * distanceY; + } + } + else// inside a compound + { + estimatedSize = Math.floor((ownerGraph.getEstimatedSize() * + this.compoundGravityRangeFactor)); + + if (absDistanceX > estimatedSize || absDistanceY > estimatedSize) + { + node.gravitationForceX = -this.gravityConstant * distanceX * + this.compoundGravityConstant; + node.gravitationForceY = -this.gravityConstant * distanceY * + this.compoundGravityConstant; + } + } +}; + +FDLayout.prototype.isConverged = function () { + var converged; + var oscilating = false; + + if (this.totalIterations > this.maxIterations / 3) + { + oscilating = + Math.abs(this.totalDisplacement - this.oldTotalDisplacement) < 2; + } + + converged = this.totalDisplacement < this.totalDisplacementThreshold; + + this.oldTotalDisplacement = this.totalDisplacement; + + return converged || oscilating; +}; + +FDLayout.prototype.animate = function () { + if (this.animationDuringLayout && !this.isSubLayout) + { + if (this.notAnimatedIterations == this.animationPeriod) + { + this.update(); + this.notAnimatedIterations = 0; + } + else + { + this.notAnimatedIterations++; + } + } +}; + +FDLayout.prototype.calcRepulsionRange = function () { + return 0.0; +}; + +module.exports = FDLayout; + +},{"./FDLayoutConstants":10,"./IGeometry":15,"./IMath":16,"./Layout":23,"./LayoutConstants":24}],10:[function(require,module,exports){ +var LayoutConstants = require('./LayoutConstants'); + +function FDLayoutConstants() { +} + +//FDLayoutConstants inherits static props in LayoutConstants +for (var prop in LayoutConstants) { + FDLayoutConstants[prop] = LayoutConstants[prop]; +} + +FDLayoutConstants.MAX_ITERATIONS = 2500; + +FDLayoutConstants.DEFAULT_EDGE_LENGTH = 50; +FDLayoutConstants.DEFAULT_SPRING_STRENGTH = 0.45; +FDLayoutConstants.DEFAULT_REPULSION_STRENGTH = 4500.0; +FDLayoutConstants.DEFAULT_GRAVITY_STRENGTH = 0.4; +FDLayoutConstants.DEFAULT_COMPOUND_GRAVITY_STRENGTH = 1.0; +FDLayoutConstants.DEFAULT_GRAVITY_RANGE_FACTOR = 3.8; +FDLayoutConstants.DEFAULT_COMPOUND_GRAVITY_RANGE_FACTOR = 1.5; +FDLayoutConstants.DEFAULT_USE_SMART_IDEAL_EDGE_LENGTH_CALCULATION = true; +FDLayoutConstants.DEFAULT_USE_SMART_REPULSION_RANGE_CALCULATION = true; +FDLayoutConstants.MAX_NODE_DISPLACEMENT_INCREMENTAL = 100.0; +FDLayoutConstants.MAX_NODE_DISPLACEMENT = FDLayoutConstants.MAX_NODE_DISPLACEMENT_INCREMENTAL * 3; +FDLayoutConstants.MIN_REPULSION_DIST = FDLayoutConstants.DEFAULT_EDGE_LENGTH / 10.0; +FDLayoutConstants.CONVERGENCE_CHECK_PERIOD = 100; +FDLayoutConstants.PER_LEVEL_IDEAL_EDGE_LENGTH_FACTOR = 0.1; +FDLayoutConstants.MIN_EDGE_LENGTH = 1; +FDLayoutConstants.GRID_CALCULATION_CHECK_PERIOD = 10; + +module.exports = FDLayoutConstants; + +},{"./LayoutConstants":24}],11:[function(require,module,exports){ +var LEdge = require('./LEdge'); +var FDLayoutConstants = require('./FDLayoutConstants'); + +function FDLayoutEdge(source, target, vEdge) { + LEdge.call(this, source, target, vEdge); + this.idealLength = FDLayoutConstants.DEFAULT_EDGE_LENGTH; +} + +FDLayoutEdge.prototype = Object.create(LEdge.prototype); + +for (var prop in LEdge) { + FDLayoutEdge[prop] = LEdge[prop]; +} + +module.exports = FDLayoutEdge; + +},{"./FDLayoutConstants":10,"./LEdge":18}],12:[function(require,module,exports){ +var LNode = require('./LNode'); + +function FDLayoutNode(gm, loc, size, vNode) { + // alternative constructor is handled inside LNode + LNode.call(this, gm, loc, size, vNode); + //Spring, repulsion and gravitational forces acting on this node + this.springForceX = 0; + this.springForceY = 0; + this.repulsionForceX = 0; + this.repulsionForceY = 0; + this.gravitationForceX = 0; + this.gravitationForceY = 0; + //Amount by which this node is to be moved in this iteration + this.displacementX = 0; + this.displacementY = 0; + + //Start and finish grid coordinates that this node is fallen into + this.startX = 0; + this.finishX = 0; + this.startY = 0; + this.finishY = 0; + + //Geometric neighbors of this node + this.surrounding = []; +} + +FDLayoutNode.prototype = Object.create(LNode.prototype); + +for (var prop in LNode) { + FDLayoutNode[prop] = LNode[prop]; +} + +FDLayoutNode.prototype.setGridCoordinates = function (_startX, _finishX, _startY, _finishY) +{ + this.startX = _startX; + this.finishX = _finishX; + this.startY = _startY; + this.finishY = _finishY; + +}; + +module.exports = FDLayoutNode; + +},{"./LNode":22}],13:[function(require,module,exports){ +var UniqueIDGeneretor = require('./UniqueIDGeneretor'); + +function HashMap() { + this.map = {}; + this.keys = []; +} + +HashMap.prototype.put = function (key, value) { + var theId = UniqueIDGeneretor.createID(key); + if (!this.contains(theId)) { + this.map[theId] = value; + this.keys.push(key); + } +}; + +HashMap.prototype.contains = function (key) { + var theId = UniqueIDGeneretor.createID(key); + return this.map[key] != null; +}; + +HashMap.prototype.get = function (key) { + var theId = UniqueIDGeneretor.createID(key); + return this.map[theId]; +}; + +HashMap.prototype.keySet = function () { + return this.keys; +}; + +module.exports = HashMap; + +},{"./UniqueIDGeneretor":31}],14:[function(require,module,exports){ +var UniqueIDGeneretor = require('./UniqueIDGeneretor'); + +function HashSet() { + this.set = {}; +} +; + +HashSet.prototype.add = function (obj) { + var theId = UniqueIDGeneretor.createID(obj); + if (!this.contains(theId)) + this.set[theId] = obj; +}; + +HashSet.prototype.remove = function (obj) { + delete this.set[UniqueIDGeneretor.createID(obj)]; +}; + +HashSet.prototype.clear = function () { + this.set = {}; +}; + +HashSet.prototype.contains = function (obj) { + return this.set[UniqueIDGeneretor.createID(obj)] == obj; +}; + +HashSet.prototype.isEmpty = function () { + return this.size() === 0; +}; + +HashSet.prototype.size = function () { + return Object.keys(this.set).length; +}; + +//concats this.set to the given list +HashSet.prototype.addAllTo = function (list) { + var keys = Object.keys(this.set); + var length = keys.length; + for (var i = 0; i < length; i++) { + list.push(this.set[keys[i]]); + } +}; + +HashSet.prototype.size = function () { + return Object.keys(this.set).length; +}; + +HashSet.prototype.addAll = function (list) { + var s = list.length; + for (var i = 0; i < s; i++) { + var v = list[i]; + this.add(v); + } +}; + +module.exports = HashSet; + +},{"./UniqueIDGeneretor":31}],15:[function(require,module,exports){ +function IGeometry() { +} + +IGeometry.calcSeparationAmount = function (rectA, rectB, overlapAmount, separationBuffer) +{ + if (!rectA.intersects(rectB)) { + throw "assert failed"; + } + var directions = new Array(2); + IGeometry.decideDirectionsForOverlappingNodes(rectA, rectB, directions); + overlapAmount[0] = Math.min(rectA.getRight(), rectB.getRight()) - + Math.max(rectA.x, rectB.x); + overlapAmount[1] = Math.min(rectA.getBottom(), rectB.getBottom()) - + Math.max(rectA.y, rectB.y); + // update the overlapping amounts for the following cases: + if ((rectA.getX() <= rectB.getX()) && (rectA.getRight() >= rectB.getRight())) + { + overlapAmount[0] += Math.min((rectB.getX() - rectA.getX()), + (rectA.getRight() - rectB.getRight())); + } + else if ((rectB.getX() <= rectA.getX()) && (rectB.getRight() >= rectA.getRight())) + { + overlapAmount[0] += Math.min((rectA.getX() - rectB.getX()), + (rectB.getRight() - rectA.getRight())); + } + if ((rectA.getY() <= rectB.getY()) && (rectA.getBottom() >= rectB.getBottom())) + { + overlapAmount[1] += Math.min((rectB.getY() - rectA.getY()), + (rectA.getBottom() - rectB.getBottom())); + } + else if ((rectB.getY() <= rectA.getY()) && (rectB.getBottom() >= rectA.getBottom())) + { + overlapAmount[1] += Math.min((rectA.getY() - rectB.getY()), + (rectB.getBottom() - rectA.getBottom())); + } + + // find slope of the line passes two centers + var slope = Math.abs((rectB.getCenterY() - rectA.getCenterY()) / + (rectB.getCenterX() - rectA.getCenterX())); + // if centers are overlapped + if ((rectB.getCenterY() == rectA.getCenterY()) && + (rectB.getCenterX() == rectA.getCenterX())) + { + // assume the slope is 1 (45 degree) + slope = 1.0; + } + + var moveByY = slope * overlapAmount[0]; + var moveByX = overlapAmount[1] / slope; + if (overlapAmount[0] < moveByX) + { + moveByX = overlapAmount[0]; + } + else + { + moveByY = overlapAmount[1]; + } + // return half the amount so that if each rectangle is moved by these + // amounts in opposite directions, overlap will be resolved + overlapAmount[0] = -1 * directions[0] * ((moveByX / 2) + separationBuffer); + overlapAmount[1] = -1 * directions[1] * ((moveByY / 2) + separationBuffer); +} + +IGeometry.decideDirectionsForOverlappingNodes = function (rectA, rectB, directions) +{ + if (rectA.getCenterX() < rectB.getCenterX()) + { + directions[0] = -1; + } + else + { + directions[0] = 1; + } + + if (rectA.getCenterY() < rectB.getCenterY()) + { + directions[1] = -1; + } + else + { + directions[1] = 1; + } +} + +IGeometry.getIntersection2 = function (rectA, rectB, result) +{ + //result[0-1] will contain clipPoint of rectA, result[2-3] will contain clipPoint of rectB + var p1x = rectA.getCenterX(); + var p1y = rectA.getCenterY(); + var p2x = rectB.getCenterX(); + var p2y = rectB.getCenterY(); + + //if two rectangles intersect, then clipping points are centers + if (rectA.intersects(rectB)) + { + result[0] = p1x; + result[1] = p1y; + result[2] = p2x; + result[3] = p2y; + return true; + } + //variables for rectA + var topLeftAx = rectA.getX(); + var topLeftAy = rectA.getY(); + var topRightAx = rectA.getRight(); + var bottomLeftAx = rectA.getX(); + var bottomLeftAy = rectA.getBottom(); + var bottomRightAx = rectA.getRight(); + var halfWidthA = rectA.getWidthHalf(); + var halfHeightA = rectA.getHeightHalf(); + //variables for rectB + var topLeftBx = rectB.getX(); + var topLeftBy = rectB.getY(); + var topRightBx = rectB.getRight(); + var bottomLeftBx = rectB.getX(); + var bottomLeftBy = rectB.getBottom(); + var bottomRightBx = rectB.getRight(); + var halfWidthB = rectB.getWidthHalf(); + var halfHeightB = rectB.getHeightHalf(); + //flag whether clipping points are found + var clipPointAFound = false; + var clipPointBFound = false; + + // line is vertical + if (p1x == p2x) + { + if (p1y > p2y) + { + result[0] = p1x; + result[1] = topLeftAy; + result[2] = p2x; + result[3] = bottomLeftBy; + return false; + } + else if (p1y < p2y) + { + result[0] = p1x; + result[1] = bottomLeftAy; + result[2] = p2x; + result[3] = topLeftBy; + return false; + } + else + { + //not line, return null; + } + } + // line is horizontal + else if (p1y == p2y) + { + if (p1x > p2x) + { + result[0] = topLeftAx; + result[1] = p1y; + result[2] = topRightBx; + result[3] = p2y; + return false; + } + else if (p1x < p2x) + { + result[0] = topRightAx; + result[1] = p1y; + result[2] = topLeftBx; + result[3] = p2y; + return false; + } + else + { + //not valid line, return null; + } + } + else + { + //slopes of rectA's and rectB's diagonals + var slopeA = rectA.height / rectA.width; + var slopeB = rectB.height / rectB.width; + + //slope of line between center of rectA and center of rectB + var slopePrime = (p2y - p1y) / (p2x - p1x); + var cardinalDirectionA; + var cardinalDirectionB; + var tempPointAx; + var tempPointAy; + var tempPointBx; + var tempPointBy; + + //determine whether clipping point is the corner of nodeA + if ((-slopeA) == slopePrime) + { + if (p1x > p2x) + { + result[0] = bottomLeftAx; + result[1] = bottomLeftAy; + clipPointAFound = true; + } + else + { + result[0] = topRightAx; + result[1] = topLeftAy; + clipPointAFound = true; + } + } + else if (slopeA == slopePrime) + { + if (p1x > p2x) + { + result[0] = topLeftAx; + result[1] = topLeftAy; + clipPointAFound = true; + } + else + { + result[0] = bottomRightAx; + result[1] = bottomLeftAy; + clipPointAFound = true; + } + } + + //determine whether clipping point is the corner of nodeB + if ((-slopeB) == slopePrime) + { + if (p2x > p1x) + { + result[2] = bottomLeftBx; + result[3] = bottomLeftBy; + clipPointBFound = true; + } + else + { + result[2] = topRightBx; + result[3] = topLeftBy; + clipPointBFound = true; + } + } + else if (slopeB == slopePrime) + { + if (p2x > p1x) + { + result[2] = topLeftBx; + result[3] = topLeftBy; + clipPointBFound = true; + } + else + { + result[2] = bottomRightBx; + result[3] = bottomLeftBy; + clipPointBFound = true; + } + } + + //if both clipping points are corners + if (clipPointAFound && clipPointBFound) + { + return false; + } + + //determine Cardinal Direction of rectangles + if (p1x > p2x) + { + if (p1y > p2y) + { + cardinalDirectionA = IGeometry.getCardinalDirection(slopeA, slopePrime, 4); + cardinalDirectionB = IGeometry.getCardinalDirection(slopeB, slopePrime, 2); + } + else + { + cardinalDirectionA = IGeometry.getCardinalDirection(-slopeA, slopePrime, 3); + cardinalDirectionB = IGeometry.getCardinalDirection(-slopeB, slopePrime, 1); + } + } + else + { + if (p1y > p2y) + { + cardinalDirectionA = IGeometry.getCardinalDirection(-slopeA, slopePrime, 1); + cardinalDirectionB = IGeometry.getCardinalDirection(-slopeB, slopePrime, 3); + } + else + { + cardinalDirectionA = IGeometry.getCardinalDirection(slopeA, slopePrime, 2); + cardinalDirectionB = IGeometry.getCardinalDirection(slopeB, slopePrime, 4); + } + } + //calculate clipping Point if it is not found before + if (!clipPointAFound) + { + switch (cardinalDirectionA) + { + case 1: + tempPointAy = topLeftAy; + tempPointAx = p1x + (-halfHeightA) / slopePrime; + result[0] = tempPointAx; + result[1] = tempPointAy; + break; + case 2: + tempPointAx = bottomRightAx; + tempPointAy = p1y + halfWidthA * slopePrime; + result[0] = tempPointAx; + result[1] = tempPointAy; + break; + case 3: + tempPointAy = bottomLeftAy; + tempPointAx = p1x + halfHeightA / slopePrime; + result[0] = tempPointAx; + result[1] = tempPointAy; + break; + case 4: + tempPointAx = bottomLeftAx; + tempPointAy = p1y + (-halfWidthA) * slopePrime; + result[0] = tempPointAx; + result[1] = tempPointAy; + break; + } + } + if (!clipPointBFound) + { + switch (cardinalDirectionB) + { + case 1: + tempPointBy = topLeftBy; + tempPointBx = p2x + (-halfHeightB) / slopePrime; + result[2] = tempPointBx; + result[3] = tempPointBy; + break; + case 2: + tempPointBx = bottomRightBx; + tempPointBy = p2y + halfWidthB * slopePrime; + result[2] = tempPointBx; + result[3] = tempPointBy; + break; + case 3: + tempPointBy = bottomLeftBy; + tempPointBx = p2x + halfHeightB / slopePrime; + result[2] = tempPointBx; + result[3] = tempPointBy; + break; + case 4: + tempPointBx = bottomLeftBx; + tempPointBy = p2y + (-halfWidthB) * slopePrime; + result[2] = tempPointBx; + result[3] = tempPointBy; + break; + } + } + } + return false; +} + +IGeometry.getCardinalDirection = function (slope, slopePrime, line) +{ + if (slope > slopePrime) + { + return line; + } + else + { + return 1 + line % 4; + } +} + +IGeometry.getIntersection = function (s1, s2, f1, f2) +{ + if (f2 == null) { + return IGeometry.getIntersection2(s1, s2, f1); + } + var x1 = s1.x; + var y1 = s1.y; + var x2 = s2.x; + var y2 = s2.y; + var x3 = f1.x; + var y3 = f1.y; + var x4 = f2.x; + var y4 = f2.y; + var x, y; // intersection point + var a1, a2, b1, b2, c1, c2; // coefficients of line eqns. + var denom; + + a1 = y2 - y1; + b1 = x1 - x2; + c1 = x2 * y1 - x1 * y2; // { a1*x + b1*y + c1 = 0 is line 1 } + + a2 = y4 - y3; + b2 = x3 - x4; + c2 = x4 * y3 - x3 * y4; // { a2*x + b2*y + c2 = 0 is line 2 } + + denom = a1 * b2 - a2 * b1; + + if (denom == 0) + { + return null; + } + + x = (b1 * c2 - b2 * c1) / denom; + y = (a2 * c1 - a1 * c2) / denom; + + return new Point(x, y); +} + +// ----------------------------------------------------------------------------- +// Section: Class Constants +// ----------------------------------------------------------------------------- +/** + * Some useful pre-calculated constants + */ +IGeometry.HALF_PI = 0.5 * Math.PI; +IGeometry.ONE_AND_HALF_PI = 1.5 * Math.PI; +IGeometry.TWO_PI = 2.0 * Math.PI; +IGeometry.THREE_PI = 3.0 * Math.PI; + +module.exports = IGeometry; + +},{}],16:[function(require,module,exports){ +function IMath() { +} + +/** + * This method returns the sign of the input value. + */ +IMath.sign = function (value) { + if (value > 0) + { + return 1; + } + else if (value < 0) + { + return -1; + } + else + { + return 0; + } +} + +IMath.floor = function (value) { + return value < 0 ? Math.ceil(value) : Math.floor(value); +} + +IMath.ceil = function (value) { + return value < 0 ? Math.floor(value) : Math.ceil(value); +} + +module.exports = IMath; + +},{}],17:[function(require,module,exports){ +function Integer() { +} + +Integer.MAX_VALUE = 2147483647; +Integer.MIN_VALUE = -2147483648; + +module.exports = Integer; + +},{}],18:[function(require,module,exports){ +var LGraphObject = require('./LGraphObject'); +var IGeometry = require('./IGeometry'); +var IMath = require('./IMath'); + +function LEdge(source, target, vEdge) { + LGraphObject.call(this, vEdge); + + this.isOverlapingSourceAndTarget = false; + this.vGraphObject = vEdge; + this.bendpoints = []; + this.source = source; + this.target = target; +} + +LEdge.prototype = Object.create(LGraphObject.prototype); + +for (var prop in LGraphObject) { + LEdge[prop] = LGraphObject[prop]; +} + +LEdge.prototype.getSource = function () +{ + return this.source; +}; + +LEdge.prototype.getTarget = function () +{ + return this.target; +}; + +LEdge.prototype.isInterGraph = function () +{ + return this.isInterGraph; +}; + +LEdge.prototype.getLength = function () +{ + return this.length; +}; + +LEdge.prototype.isOverlapingSourceAndTarget = function () +{ + return this.isOverlapingSourceAndTarget; +}; + +LEdge.prototype.getBendpoints = function () +{ + return this.bendpoints; +}; + +LEdge.prototype.getLca = function () +{ + return this.lca; +}; + +LEdge.prototype.getSourceInLca = function () +{ + return this.sourceInLca; +}; + +LEdge.prototype.getTargetInLca = function () +{ + return this.targetInLca; +}; + +LEdge.prototype.getOtherEnd = function (node) +{ + if (this.source === node) + { + return this.target; + } + else if (this.target === node) + { + return this.source; + } + else + { + throw "Node is not incident with this edge"; + } +} + +LEdge.prototype.getOtherEndInGraph = function (node, graph) +{ + var otherEnd = this.getOtherEnd(node); + var root = graph.getGraphManager().getRoot(); + + while (true) + { + if (otherEnd.getOwner() == graph) + { + return otherEnd; + } + + if (otherEnd.getOwner() == root) + { + break; + } + + otherEnd = otherEnd.getOwner().getParent(); + } + + return null; +}; + +LEdge.prototype.updateLength = function () +{ + var clipPointCoordinates = new Array(4); + + this.isOverlapingSourceAndTarget = + IGeometry.getIntersection(this.target.getRect(), + this.source.getRect(), + clipPointCoordinates); + + if (!this.isOverlapingSourceAndTarget) + { + this.lengthX = clipPointCoordinates[0] - clipPointCoordinates[2]; + this.lengthY = clipPointCoordinates[1] - clipPointCoordinates[3]; + + if (Math.abs(this.lengthX) < 1.0) + { + this.lengthX = IMath.sign(this.lengthX); + } + + if (Math.abs(this.lengthY) < 1.0) + { + this.lengthY = IMath.sign(this.lengthY); + } + + this.length = Math.sqrt( + this.lengthX * this.lengthX + this.lengthY * this.lengthY); + } +}; + +LEdge.prototype.updateLengthSimple = function () +{ + this.lengthX = this.target.getCenterX() - this.source.getCenterX(); + this.lengthY = this.target.getCenterY() - this.source.getCenterY(); + + if (Math.abs(this.lengthX) < 1.0) + { + this.lengthX = IMath.sign(this.lengthX); + } + + if (Math.abs(this.lengthY) < 1.0) + { + this.lengthY = IMath.sign(this.lengthY); + } + + this.length = Math.sqrt( + this.lengthX * this.lengthX + this.lengthY * this.lengthY); +} + +module.exports = LEdge; + +},{"./IGeometry":15,"./IMath":16,"./LGraphObject":21}],19:[function(require,module,exports){ +var LGraphObject = require('./LGraphObject'); +var Integer = require('./Integer'); +var LayoutConstants = require('./LayoutConstants'); +var LGraphManager = require('./LGraphManager'); +var LNode = require('./LNode'); +var HashSet = require('./HashSet'); +var RectangleD = require('./RectangleD'); +var Point = require('./Point'); + +function LGraph(parent, obj2, vGraph) { + LGraphObject.call(this, vGraph); + this.estimatedSize = Integer.MIN_VALUE; + this.margin = LayoutConstants.DEFAULT_GRAPH_MARGIN; + this.edges = []; + this.nodes = []; + this.isConnected = false; + this.parent = parent; + + if (obj2 != null && obj2 instanceof LGraphManager) { + this.graphManager = obj2; + } + else if (obj2 != null && obj2 instanceof Layout) { + this.graphManager = obj2.graphManager; + } +} + +LGraph.prototype = Object.create(LGraphObject.prototype); +for (var prop in LGraphObject) { + LGraph[prop] = LGraphObject[prop]; +} + +LGraph.prototype.getNodes = function () { + return this.nodes; +}; + +LGraph.prototype.getEdges = function () { + return this.edges; +}; + +LGraph.prototype.getGraphManager = function () +{ + return this.graphManager; +}; + +LGraph.prototype.getParent = function () +{ + return this.parent; +}; + +LGraph.prototype.getLeft = function () +{ + return this.left; +}; + +LGraph.prototype.getRight = function () +{ + return this.right; +}; + +LGraph.prototype.getTop = function () +{ + return this.top; +}; + +LGraph.prototype.getBottom = function () +{ + return this.bottom; +}; + +LGraph.prototype.isConnected = function () +{ + return this.isConnected; +}; + +LGraph.prototype.add = function (obj1, sourceNode, targetNode) { + if (sourceNode == null && targetNode == null) { + var newNode = obj1; + if (this.graphManager == null) { + throw "Graph has no graph mgr!"; + } + if (this.getNodes().indexOf(newNode) > -1) { + throw "Node already in graph!"; + } + newNode.owner = this; + this.getNodes().push(newNode); + + return newNode; + } + else { + var newEdge = obj1; + if (!(this.getNodes().indexOf(sourceNode) > -1 && (this.getNodes().indexOf(targetNode)) > -1)) { + throw "Source or target not in graph!"; + } + + if (!(sourceNode.owner == targetNode.owner && sourceNode.owner == this)) { + throw "Both owners must be this graph!"; + } + + if (sourceNode.owner != targetNode.owner) + { + return null; + } + + // set source and target + newEdge.source = sourceNode; + newEdge.target = targetNode; + + // set as intra-graph edge + newEdge.isInterGraph = false; + + // add to graph edge list + this.getEdges().push(newEdge); + + // add to incidency lists + sourceNode.edges.push(newEdge); + + if (targetNode != sourceNode) + { + targetNode.edges.push(newEdge); + } + + return newEdge; + } +}; + +LGraph.prototype.remove = function (obj) { + var node = obj; + if (obj instanceof LNode) { + if (node == null) { + throw "Node is null!"; + } + if (!(node.owner != null && node.owner == this)) { + throw "Owner graph is invalid!"; + } + if (this.graphManager == null) { + throw "Owner graph manager is invalid!"; + } + // remove incident edges first (make a copy to do it safely) + var edgesToBeRemoved = node.edges.slice(); + var edge; + var s = edgesToBeRemoved.length; + for (var i = 0; i < s; i++) + { + edge = edgesToBeRemoved[i]; + + if (edge.isInterGraph) + { + this.graphManager.remove(edge); + } + else + { + edge.source.owner.remove(edge); + } + } + + // now the node itself + var index = this.nodes.indexOf(node); + if (index == -1) { + throw "Node not in owner node list!"; + } + + this.nodes.splice(index, 1); + } + else if (obj instanceof LEdge) { + var edge = obj; + if (edge == null) { + throw "Edge is null!"; + } + if (!(edge.source != null && edge.target != null)) { + throw "Source and/or target is null!"; + } + if (!(edge.source.owner != null && edge.target.owner != null && + edge.source.owner == this && edge.target.owner == this)) { + throw "Source and/or target owner is invalid!"; + } + + var sourceIndex = edge.source.edges.indexOf(edge); + var targetIndex = edge.target.edges.indexOf(edge); + if (!(sourceIndex > -1 && targetIndex > -1)) { + throw "Source and/or target doesn't know this edge!"; + } + + edge.source.edges.splice(sourceIndex, 1); + + if (edge.target != edge.source) + { + edge.target.edges.splice(targetIndex, 1); + } + + var index = edge.source.owner.getEdges().indexOf(edge); + if (index == -1) { + throw "Not in owner's edge list!"; + } + + edge.source.owner.getEdges().splice(index, 1); + } +}; + +LGraph.prototype.updateLeftTop = function () +{ + var top = Integer.MAX_VALUE; + var left = Integer.MAX_VALUE; + var nodeTop; + var nodeLeft; + + var nodes = this.getNodes(); + var s = nodes.length; + + for (var i = 0; i < s; i++) + { + var lNode = nodes[i]; + nodeTop = Math.floor(lNode.getTop()); + nodeLeft = Math.floor(lNode.getLeft()); + + if (top > nodeTop) + { + top = nodeTop; + } + + if (left > nodeLeft) + { + left = nodeLeft; + } + } + + // Do we have any nodes in this graph? + if (top == Integer.MAX_VALUE) + { + return null; + } + + this.left = left - this.margin; + this.top = top - this.margin; + + // Apply the margins and return the result + return new Point(this.left, this.top); +}; + +LGraph.prototype.updateBounds = function (recursive) +{ + // calculate bounds + var left = Integer.MAX_VALUE; + var right = -Integer.MAX_VALUE; + var top = Integer.MAX_VALUE; + var bottom = -Integer.MAX_VALUE; + var nodeLeft; + var nodeRight; + var nodeTop; + var nodeBottom; + + var nodes = this.nodes; + var s = nodes.length; + for (var i = 0; i < s; i++) + { + var lNode = nodes[i]; + + if (recursive && lNode.child != null) + { + lNode.updateBounds(); + } + nodeLeft = Math.floor(lNode.getLeft()); + nodeRight = Math.floor(lNode.getRight()); + nodeTop = Math.floor(lNode.getTop()); + nodeBottom = Math.floor(lNode.getBottom()); + + if (left > nodeLeft) + { + left = nodeLeft; + } + + if (right < nodeRight) + { + right = nodeRight; + } + + if (top > nodeTop) + { + top = nodeTop; + } + + if (bottom < nodeBottom) + { + bottom = nodeBottom; + } + } + + var boundingRect = new RectangleD(left, top, right - left, bottom - top); + if (left == Integer.MAX_VALUE) + { + this.left = Math.floor(this.parent.getLeft()); + this.right = Math.floor(this.parent.getRight()); + this.top = Math.floor(this.parent.getTop()); + this.bottom = Math.floor(this.parent.getBottom()); + } + + this.left = boundingRect.x - this.margin; + this.right = boundingRect.x + boundingRect.width + this.margin; + this.top = boundingRect.y - this.margin; + this.bottom = boundingRect.y + boundingRect.height + this.margin; +}; + +LGraph.calculateBounds = function (nodes) +{ + var left = Integer.MAX_VALUE; + var right = -Integer.MAX_VALUE; + var top = Integer.MAX_VALUE; + var bottom = -Integer.MAX_VALUE; + var nodeLeft; + var nodeRight; + var nodeTop; + var nodeBottom; + + var s = nodes.length; + + for (var i = 0; i < s; i++) + { + var lNode = nodes[i]; + nodeLeft = Math.floor(lNode.getLeft()); + nodeRight = Math.floor(lNode.getRight()); + nodeTop = Math.floor(lNode.getTop()); + nodeBottom = Math.floor(lNode.getBottom()); + + if (left > nodeLeft) + { + left = nodeLeft; + } + + if (right < nodeRight) + { + right = nodeRight; + } + + if (top > nodeTop) + { + top = nodeTop; + } + + if (bottom < nodeBottom) + { + bottom = nodeBottom; + } + } + + var boundingRect = new RectangleD(left, top, right - left, bottom - top); + + return boundingRect; +}; + +LGraph.prototype.getInclusionTreeDepth = function () +{ + if (this == this.graphManager.getRoot()) + { + return 1; + } + else + { + return this.parent.getInclusionTreeDepth(); + } +}; + +LGraph.prototype.getEstimatedSize = function () +{ + if (this.estimatedSize == Integer.MIN_VALUE) { + throw "assert failed"; + } + return this.estimatedSize; +}; + +LGraph.prototype.calcEstimatedSize = function () +{ + var size = 0; + var nodes = this.nodes; + var s = nodes.length; + + for (var i = 0; i < s; i++) + { + var lNode = nodes[i]; + size += lNode.calcEstimatedSize(); + } + + if (size == 0) + { + this.estimatedSize = LayoutConstants.EMPTY_COMPOUND_NODE_SIZE; + } + else + { + this.estimatedSize = Math.floor(size / Math.sqrt(this.nodes.length)); + } + + return Math.floor(this.estimatedSize); +}; + +LGraph.prototype.updateConnected = function () +{ + if (this.nodes.length == 0) + { + this.isConnected = true; + return; + } + + var toBeVisited = []; + var visited = new HashSet(); + var currentNode = this.nodes[0]; + var neighborEdges; + var currentNeighbor; + toBeVisited = toBeVisited.concat(currentNode.withChildren()); + + while (toBeVisited.length > 0) + { + currentNode = toBeVisited.shift(); + visited.add(currentNode); + + // Traverse all neighbors of this node + neighborEdges = currentNode.getEdges(); + var s = neighborEdges.length; + for (var i = 0; i < s; i++) + { + var neighborEdge = neighborEdges[i]; + currentNeighbor = + neighborEdge.getOtherEndInGraph(currentNode, this); + + // Add unvisited neighbors to the list to visit + if (currentNeighbor != null && + !visited.contains(currentNeighbor)) + { + toBeVisited = toBeVisited.concat(currentNeighbor.withChildren()); + } + } + } + + this.isConnected = false; + + if (visited.size() >= this.nodes.length) + { + var noOfVisitedInThisGraph = 0; + + var s = visited.size(); + for (var visitedId in visited.set) + { + var visitedNode = visited.set[visitedId]; + if (visitedNode.owner == this) + { + noOfVisitedInThisGraph++; + } + } + + if (noOfVisitedInThisGraph == this.nodes.length) + { + this.isConnected = true; + } + } +}; + +module.exports = LGraph; + +},{"./HashSet":14,"./Integer":17,"./LGraphManager":20,"./LGraphObject":21,"./LNode":22,"./LayoutConstants":24,"./Point":25,"./RectangleD":28}],20:[function(require,module,exports){ +function LGraphManager(layout) { + this.layout = layout; + + this.graphs = []; + this.edges = []; +} + +LGraphManager.prototype.addRoot = function () +{ + var ngraph = this.layout.newGraph(); + var nnode = this.layout.newNode(null); + var root = this.add(ngraph, nnode); + this.setRootGraph(root); + return this.rootGraph; +}; + +LGraphManager.prototype.add = function (newGraph, parentNode, newEdge, sourceNode, targetNode) +{ + //there are just 2 parameters are passed then it adds an LGraph else it adds an LEdge + if (newEdge == null && sourceNode == null && targetNode == null) { + if (newGraph == null) { + throw "Graph is null!"; + } + if (parentNode == null) { + throw "Parent node is null!"; + } + if (this.graphs.indexOf(newGraph) > -1) { + throw "Graph already in this graph mgr!"; + } + + this.graphs.push(newGraph); + + if (newGraph.parent != null) { + throw "Already has a parent!"; + } + if (parentNode.child != null) { + throw "Already has a child!"; + } + + newGraph.parent = parentNode; + parentNode.child = newGraph; + + return newGraph; + } + else { + //change the order of the parameters + targetNode = newEdge; + sourceNode = parentNode; + newEdge = newGraph; + var sourceGraph = sourceNode.getOwner(); + var targetGraph = targetNode.getOwner(); + + if (!(sourceGraph != null && sourceGraph.getGraphManager() == this)) { + throw "Source not in this graph mgr!"; + } + if (!(targetGraph != null && targetGraph.getGraphManager() == this)) { + throw "Target not in this graph mgr!"; + } + + if (sourceGraph == targetGraph) + { + newEdge.isInterGraph = false; + return sourceGraph.add(newEdge, sourceNode, targetNode); + } + else + { + newEdge.isInterGraph = true; + + // set source and target + newEdge.source = sourceNode; + newEdge.target = targetNode; + + // add edge to inter-graph edge list + if (this.edges.indexOf(newEdge) > -1) { + throw "Edge already in inter-graph edge list!"; + } + + this.edges.push(newEdge); + + // add edge to source and target incidency lists + if (!(newEdge.source != null && newEdge.target != null)) { + throw "Edge source and/or target is null!"; + } + + if (!(newEdge.source.edges.indexOf(newEdge) == -1 && newEdge.target.edges.indexOf(newEdge) == -1)) { + throw "Edge already in source and/or target incidency list!"; + } + + newEdge.source.edges.push(newEdge); + newEdge.target.edges.push(newEdge); + + return newEdge; + } + } +}; + +LGraphManager.prototype.remove = function (lObj) { + if (lObj instanceof LGraph) { + var graph = lObj; + if (graph.getGraphManager() != this) { + throw "Graph not in this graph mgr"; + } + if (!(graph == this.rootGraph || (graph.parent != null && graph.parent.graphManager == this))) { + throw "Invalid parent node!"; + } + + // first the edges (make a copy to do it safely) + var edgesToBeRemoved = []; + + edgesToBeRemoved = edgesToBeRemoved.concat(graph.getEdges()); + + var edge; + var s = edgesToBeRemoved.length; + for (var i = 0; i < s; i++) + { + edge = edgesToBeRemoved[i]; + graph.remove(edge); + } + + // then the nodes (make a copy to do it safely) + var nodesToBeRemoved = []; + + nodesToBeRemoved = nodesToBeRemoved.concat(graph.getNodes()); + + var node; + s = nodesToBeRemoved.length; + for (var i = 0; i < s; i++) + { + node = nodesToBeRemoved[i]; + graph.remove(node); + } + + // check if graph is the root + if (graph == this.rootGraph) + { + this.setRootGraph(null); + } + + // now remove the graph itself + var index = this.graphs.indexOf(graph); + this.graphs.splice(index, 1); + + // also reset the parent of the graph + graph.parent = null; + } + else if (lObj instanceof LEdge) { + edge = lObj; + if (edge == null) { + throw "Edge is null!"; + } + if (!edge.isInterGraph) { + throw "Not an inter-graph edge!"; + } + if (!(edge.source != null && edge.target != null)) { + throw "Source and/or target is null!"; + } + + // remove edge from source and target nodes' incidency lists + + if (!(edge.source.edges.indexOf(edge) != -1 && edge.target.edges.indexOf(edge) != -1)) { + throw "Source and/or target doesn't know this edge!"; + } + + var index = edge.source.edges.indexOf(edge); + edge.source.edges.splice(index, 1); + index = edge.target.edges.indexOf(edge); + edge.target.edges.splice(index, 1); + + // remove edge from owner graph manager's inter-graph edge list + + if (!(edge.source.owner != null && edge.source.owner.getGraphManager() != null)) { + throw "Edge owner graph or owner graph manager is null!"; + } + if (edge.source.owner.getGraphManager().edges.indexOf(edge) == -1) { + throw "Not in owner graph manager's edge list!"; + } + + var index = edge.source.owner.getGraphManager().edges.indexOf(edge); + edge.source.owner.getGraphManager().edges.splice(index, 1); + } +}; + +LGraphManager.prototype.updateBounds = function () +{ + this.rootGraph.updateBounds(true); +}; + +LGraphManager.prototype.getGraphs = function () +{ + return this.graphs; +}; + +LGraphManager.prototype.getAllNodes = function () +{ + if (this.allNodes == null) + { + var nodeList = []; + var graphs = this.getGraphs(); + var s = graphs.length; + for (var i = 0; i < s; i++) + { + nodeList = nodeList.concat(graphs[i].getNodes()); + } + this.allNodes = nodeList; + } + return this.allNodes; +}; + +LGraphManager.prototype.resetAllNodes = function () +{ + this.allNodes = null; +}; + +LGraphManager.prototype.resetAllEdges = function () +{ + this.allEdges = null; +}; + +LGraphManager.prototype.resetAllNodesToApplyGravitation = function () +{ + this.allNodesToApplyGravitation = null; +}; + +LGraphManager.prototype.getAllEdges = function () +{ + if (this.allEdges == null) + { + var edgeList = []; + var graphs = this.getGraphs(); + var s = graphs.length; + for (var i = 0; i < graphs.length; i++) + { + edgeList = edgeList.concat(graphs[i].getEdges()); + } + + edgeList = edgeList.concat(this.edges); + + this.allEdges = edgeList; + } + return this.allEdges; +}; + +LGraphManager.prototype.getAllNodesToApplyGravitation = function () +{ + return this.allNodesToApplyGravitation; +}; + +LGraphManager.prototype.setAllNodesToApplyGravitation = function (nodeList) +{ + if (this.allNodesToApplyGravitation != null) { + throw "assert failed"; + } + + this.allNodesToApplyGravitation = nodeList; +}; + +LGraphManager.prototype.getRoot = function () +{ + return this.rootGraph; +}; + +LGraphManager.prototype.setRootGraph = function (graph) +{ + if (graph.getGraphManager() != this) { + throw "Root not in this graph mgr!"; + } + + this.rootGraph = graph; + // root graph must have a root node associated with it for convenience + if (graph.parent == null) + { + graph.parent = this.layout.newNode("Root node"); + } +}; + +LGraphManager.prototype.getLayout = function () +{ + return this.layout; +}; + +LGraphManager.prototype.isOneAncestorOfOther = function (firstNode, secondNode) +{ + if (!(firstNode != null && secondNode != null)) { + throw "assert failed"; + } + + if (firstNode == secondNode) + { + return true; + } + // Is second node an ancestor of the first one? + var ownerGraph = firstNode.getOwner(); + var parentNode; + + do + { + parentNode = ownerGraph.getParent(); + + if (parentNode == null) + { + break; + } + + if (parentNode == secondNode) + { + return true; + } + + ownerGraph = parentNode.getOwner(); + if (ownerGraph == null) + { + break; + } + } while (true); + // Is first node an ancestor of the second one? + ownerGraph = secondNode.getOwner(); + + do + { + parentNode = ownerGraph.getParent(); + + if (parentNode == null) + { + break; + } + + if (parentNode == firstNode) + { + return true; + } + + ownerGraph = parentNode.getOwner(); + if (ownerGraph == null) + { + break; + } + } while (true); + + return false; +}; + +LGraphManager.prototype.calcLowestCommonAncestors = function () +{ + var edge; + var sourceNode; + var targetNode; + var sourceAncestorGraph; + var targetAncestorGraph; + + var edges = this.getAllEdges(); + var s = edges.length; + for (var i = 0; i < s; i++) + { + edge = edges[i]; + + sourceNode = edge.source; + targetNode = edge.target; + edge.lca = null; + edge.sourceInLca = sourceNode; + edge.targetInLca = targetNode; + + if (sourceNode == targetNode) + { + edge.lca = sourceNode.getOwner(); + continue; + } + + sourceAncestorGraph = sourceNode.getOwner(); + + while (edge.lca == null) + { + targetAncestorGraph = targetNode.getOwner(); + + while (edge.lca == null) + { + if (targetAncestorGraph == sourceAncestorGraph) + { + edge.lca = targetAncestorGraph; + break; + } + + if (targetAncestorGraph == this.rootGraph) + { + break; + } + + if (edge.lca != null) { + throw "assert failed"; + } + edge.targetInLca = targetAncestorGraph.getParent(); + targetAncestorGraph = edge.targetInLca.getOwner(); + } + + if (sourceAncestorGraph == this.rootGraph) + { + break; + } + + if (edge.lca == null) + { + edge.sourceInLca = sourceAncestorGraph.getParent(); + sourceAncestorGraph = edge.sourceInLca.getOwner(); + } + } + + if (edge.lca == null) { + throw "assert failed"; + } + } +}; + +LGraphManager.prototype.calcLowestCommonAncestor = function (firstNode, secondNode) +{ + if (firstNode == secondNode) + { + return firstNode.getOwner(); + } + var firstOwnerGraph = firstNode.getOwner(); + + do + { + if (firstOwnerGraph == null) + { + break; + } + var secondOwnerGraph = secondNode.getOwner(); + + do + { + if (secondOwnerGraph == null) + { + break; + } + + if (secondOwnerGraph == firstOwnerGraph) + { + return secondOwnerGraph; + } + secondOwnerGraph = secondOwnerGraph.getParent().getOwner(); + } while (true); + + firstOwnerGraph = firstOwnerGraph.getParent().getOwner(); + } while (true); + + return firstOwnerGraph; +}; + +LGraphManager.prototype.calcInclusionTreeDepths = function (graph, depth) { + if (graph == null && depth == null) { + graph = this.rootGraph; + depth = 1; + } + var node; + + var nodes = graph.getNodes(); + var s = nodes.length; + for (var i = 0; i < s; i++) + { + node = nodes[i]; + node.inclusionTreeDepth = depth; + + if (node.child != null) + { + this.calcInclusionTreeDepths(node.child, depth + 1); + } + } +}; + +LGraphManager.prototype.includesInvalidEdge = function () +{ + var edge; + + var s = this.edges.length; + for (var i = 0; i < s; i++) + { + edge = this.edges[i]; + + if (this.isOneAncestorOfOther(edge.source, edge.target)) + { + return true; + } + } + return false; +}; + +module.exports = LGraphManager; + +},{}],21:[function(require,module,exports){ +function LGraphObject(vGraphObject) { + this.vGraphObject = vGraphObject; +} + +module.exports = LGraphObject; + +},{}],22:[function(require,module,exports){ +var LGraphObject = require('./LGraphObject'); +var Integer = require('./Integer'); +var RectangleD = require('./RectangleD'); +var LayoutConstants = require('./LayoutConstants'); +var RandomSeed = require('./RandomSeed'); +var PointD = require('./PointD'); +var HashSet = require('./HashSet'); + +function LNode(gm, loc, size, vNode) { + //Alternative constructor 1 : LNode(LGraphManager gm, Point loc, Dimension size, Object vNode) + if (size == null && vNode == null) { + vNode = loc; + } + + LGraphObject.call(this, vNode); + + //Alternative constructor 2 : LNode(Layout layout, Object vNode) + if (gm.graphManager != null) + gm = gm.graphManager; + + this.estimatedSize = Integer.MIN_VALUE; + this.inclusionTreeDepth = Integer.MAX_VALUE; + this.vGraphObject = vNode; + this.edges = []; + this.graphManager = gm; + + if (size != null && loc != null) + this.rect = new RectangleD(loc.x, loc.y, size.width, size.height); + else + this.rect = new RectangleD(); +} + +LNode.prototype = Object.create(LGraphObject.prototype); +for (var prop in LGraphObject) { + LNode[prop] = LGraphObject[prop]; +} + +LNode.prototype.getEdges = function () +{ + return this.edges; +}; + +LNode.prototype.getChild = function () +{ + return this.child; +}; + +LNode.prototype.getOwner = function () +{ + if (this.owner != null) { + if (!(this.owner == null || this.owner.getNodes().indexOf(this) > -1)) { + throw "assert failed"; + } + } + + return this.owner; +}; + +LNode.prototype.getWidth = function () +{ + return this.rect.width; +}; + +LNode.prototype.setWidth = function (width) +{ + this.rect.width = width; +}; + +LNode.prototype.getHeight = function () +{ + return this.rect.height; +}; + +LNode.prototype.setHeight = function (height) +{ + this.rect.height = height; +}; + +LNode.prototype.getCenterX = function () +{ + return this.rect.x + this.rect.width / 2; +}; + +LNode.prototype.getCenterY = function () +{ + return this.rect.y + this.rect.height / 2; +}; + +LNode.prototype.getCenter = function () +{ + return new PointD(this.rect.x + this.rect.width / 2, + this.rect.y + this.rect.height / 2); +}; + +LNode.prototype.getLocation = function () +{ + return new PointD(this.rect.x, this.rect.y); +}; + +LNode.prototype.getRect = function () +{ + return this.rect; +}; + +LNode.prototype.getDiagonal = function () +{ + return Math.sqrt(this.rect.width * this.rect.width + + this.rect.height * this.rect.height); +}; + +LNode.prototype.setRect = function (upperLeft, dimension) +{ + this.rect.x = upperLeft.x; + this.rect.y = upperLeft.y; + this.rect.width = dimension.width; + this.rect.height = dimension.height; +}; + +LNode.prototype.setCenter = function (cx, cy) +{ + this.rect.x = cx - this.rect.width / 2; + this.rect.y = cy - this.rect.height / 2; +}; + +LNode.prototype.setLocation = function (x, y) +{ + this.rect.x = x; + this.rect.y = y; +}; + +LNode.prototype.moveBy = function (dx, dy) +{ + this.rect.x += dx; + this.rect.y += dy; +}; + +LNode.prototype.getEdgeListToNode = function (to) +{ + var edgeList = []; + var edge; + + for (var obj in this.edges) + { + edge = obj; + + if (edge.target == to) + { + if (edge.source != this) + throw "Incorrect edge source!"; + + edgeList.push(edge); + } + } + + return edgeList; +}; + +LNode.prototype.getEdgesBetween = function (other) +{ + var edgeList = []; + var edge; + + for (var obj in this.edges) + { + edge = this.edges[obj]; + + if (!(edge.source == this || edge.target == this)) + throw "Incorrect edge source and/or target"; + + if ((edge.target == other) || (edge.source == other)) + { + edgeList.push(edge); + } + } + + return edgeList; +}; + +LNode.prototype.getNeighborsList = function () +{ + var neighbors = new HashSet(); + var edge; + + for (var obj in this.edges) + { + edge = this.edges[obj]; + + if (edge.source == this) + { + neighbors.add(edge.target); + } + else + { + if (!edge.target == this) + throw "Incorrect incidency!"; + neighbors.add(edge.source); + } + } + + return neighbors; +}; + +LNode.prototype.withChildren = function () +{ + var withNeighborsList = []; + var childNode; + + withNeighborsList.push(this); + + if (this.child != null) + { + var nodes = this.child.getNodes(); + for (var i = 0; i < nodes.length; i++) + { + childNode = nodes[i]; + + withNeighborsList = withNeighborsList.concat(childNode.withChildren()); + } + } + + return withNeighborsList; +}; + +LNode.prototype.getEstimatedSize = function () { + if (this.estimatedSize == Integer.MIN_VALUE) { + throw "assert failed"; + } + return this.estimatedSize; +}; + +LNode.prototype.calcEstimatedSize = function () { + if (this.child == null) + { + return this.estimatedSize = Math.floor((this.rect.width + this.rect.height) / 2); + } + else + { + this.estimatedSize = this.child.calcEstimatedSize(); + this.rect.width = this.estimatedSize; + this.rect.height = this.estimatedSize; + + return this.estimatedSize; + } +}; + +LNode.prototype.scatter = function () { + var randomCenterX; + var randomCenterY; + + var minX = -LayoutConstants.INITIAL_WORLD_BOUNDARY; + var maxX = LayoutConstants.INITIAL_WORLD_BOUNDARY; + randomCenterX = LayoutConstants.WORLD_CENTER_X + + (RandomSeed.nextDouble() * (maxX - minX)) + minX; + + var minY = -LayoutConstants.INITIAL_WORLD_BOUNDARY; + var maxY = LayoutConstants.INITIAL_WORLD_BOUNDARY; + randomCenterY = LayoutConstants.WORLD_CENTER_Y + + (RandomSeed.nextDouble() * (maxY - minY)) + minY; + + this.rect.x = randomCenterX; + this.rect.y = randomCenterY +}; + +LNode.prototype.updateBounds = function () { + if (this.getChild() == null) { + throw "assert failed"; + } + if (this.getChild().getNodes().length != 0) + { + // wrap the children nodes by re-arranging the boundaries + var childGraph = this.getChild(); + childGraph.updateBounds(true); + + this.rect.x = childGraph.getLeft(); + this.rect.y = childGraph.getTop(); + + this.setWidth(childGraph.getRight() - childGraph.getLeft()); + this.setHeight(childGraph.getBottom() - childGraph.getTop()); + } +}; + +LNode.prototype.getInclusionTreeDepth = function () +{ + if (this.inclusionTreeDepth == Integer.MAX_VALUE) { + throw "assert failed"; + } + return this.inclusionTreeDepth; +}; + +LNode.prototype.transform = function (trans) +{ + var left = this.rect.x; + + if (left > LayoutConstants.WORLD_BOUNDARY) + { + left = LayoutConstants.WORLD_BOUNDARY; + } + else if (left < -LayoutConstants.WORLD_BOUNDARY) + { + left = -LayoutConstants.WORLD_BOUNDARY; + } + + var top = this.rect.y; + + if (top > LayoutConstants.WORLD_BOUNDARY) + { + top = LayoutConstants.WORLD_BOUNDARY; + } + else if (top < -LayoutConstants.WORLD_BOUNDARY) + { + top = -LayoutConstants.WORLD_BOUNDARY; + } + + var leftTop = new PointD(left, top); + var vLeftTop = trans.inverseTransformPoint(leftTop); + + this.setLocation(vLeftTop.x, vLeftTop.y); +}; + +LNode.prototype.getLeft = function () +{ + return this.rect.x; +}; + +LNode.prototype.getRight = function () +{ + return this.rect.x + this.rect.width; +}; + +LNode.prototype.getTop = function () +{ + return this.rect.y; +}; + +LNode.prototype.getBottom = function () +{ + return this.rect.y + this.rect.height; +}; + +LNode.prototype.getParent = function () +{ + if (this.owner == null) + { + return null; + } + + return this.owner.getParent(); +}; + +module.exports = LNode; + +},{"./HashSet":14,"./Integer":17,"./LGraphObject":21,"./LayoutConstants":24,"./PointD":26,"./RandomSeed":27,"./RectangleD":28}],23:[function(require,module,exports){ +var LayoutConstants = require('./LayoutConstants'); +var HashMap = require('./HashMap'); +var LGraphManager = require('./LGraphManager'); +var LNode = require('./LNode'); +var LEdge = require('./LEdge'); +var LGraph = require('./LGraph'); +var PointD = require('./PointD'); +var Transform = require('./Transform'); +var Emitter = require('./Emitter'); +var HashSet = require('./HashSet'); + +function Layout(isRemoteUse) { + Emitter.call( this ); + + //Layout Quality: 0:proof, 1:default, 2:draft + this.layoutQuality = LayoutConstants.DEFAULT_QUALITY; + //Whether layout should create bendpoints as needed or not + this.createBendsAsNeeded = + LayoutConstants.DEFAULT_CREATE_BENDS_AS_NEEDED; + //Whether layout should be incremental or not + this.incremental = LayoutConstants.DEFAULT_INCREMENTAL; + //Whether we animate from before to after layout node positions + this.animationOnLayout = + LayoutConstants.DEFAULT_ANIMATION_ON_LAYOUT; + //Whether we animate the layout process or not + this.animationDuringLayout = LayoutConstants.DEFAULT_ANIMATION_DURING_LAYOUT; + //Number iterations that should be done between two successive animations + this.animationPeriod = LayoutConstants.DEFAULT_ANIMATION_PERIOD; + /** + * Whether or not leaf nodes (non-compound nodes) are of uniform sizes. When + * they are, both spring and repulsion forces between two leaf nodes can be + * calculated without the expensive clipping point calculations, resulting + * in major speed-up. + */ + this.uniformLeafNodeSizes = + LayoutConstants.DEFAULT_UNIFORM_LEAF_NODE_SIZES; + /** + * This is used for creation of bendpoints by using dummy nodes and edges. + * Maps an LEdge to its dummy bendpoint path. + */ + this.edgeToDummyNodes = new HashMap(); + this.graphManager = new LGraphManager(this); + this.isLayoutFinished = false; + this.isSubLayout = false; + this.isRemoteUse = false; + + if (isRemoteUse != null) { + this.isRemoteUse = isRemoteUse; + } +} + +Layout.RANDOM_SEED = 1; + +Layout.prototype = Object.create( Emitter.prototype ); + +Layout.prototype.getGraphManager = function () { + return this.graphManager; +}; + +Layout.prototype.getAllNodes = function () { + return this.graphManager.getAllNodes(); +}; + +Layout.prototype.getAllEdges = function () { + return this.graphManager.getAllEdges(); +}; + +Layout.prototype.getAllNodesToApplyGravitation = function () { + return this.graphManager.getAllNodesToApplyGravitation(); +}; + +Layout.prototype.newGraphManager = function () { + var gm = new LGraphManager(this); + this.graphManager = gm; + return gm; +}; + +Layout.prototype.newGraph = function (vGraph) +{ + return new LGraph(null, this.graphManager, vGraph); +}; + +Layout.prototype.newNode = function (vNode) +{ + return new LNode(this.graphManager, vNode); +}; + +Layout.prototype.newEdge = function (vEdge) +{ + return new LEdge(null, null, vEdge); +}; + +Layout.prototype.runLayout = function () +{ + this.isLayoutFinished = false; + + this.initParameters(); + var isLayoutSuccessfull; + + if ((this.graphManager.getRoot() == null) + || this.graphManager.getRoot().getNodes().length == 0 + || this.graphManager.includesInvalidEdge()) + { + isLayoutSuccessfull = false; + } + else + { + // calculate execution time + var startTime = 0; + + if (!this.isSubLayout) + { + startTime = new Date().getTime() + } + + isLayoutSuccessfull = this.layout(); + + if (!this.isSubLayout) + { + var endTime = new Date().getTime(); + var excTime = endTime - startTime; + } + } + + if (isLayoutSuccessfull) + { + if (!this.isSubLayout) + { + this.doPostLayout(); + } + } + + this.isLayoutFinished = true; + + return isLayoutSuccessfull; +}; + +/** + * This method performs the operations required after layout. + */ +Layout.prototype.doPostLayout = function () +{ + //assert !isSubLayout : "Should not be called on sub-layout!"; + // Propagate geometric changes to v-level objects + this.transform(); + this.update(); +}; + +/** + * This method updates the geometry of the target graph according to + * calculated layout. + */ +Layout.prototype.update2 = function () { + // update bend points + if (this.createBendsAsNeeded) + { + this.createBendpointsFromDummyNodes(); + + // reset all edges, since the topology has changed + this.graphManager.resetAllEdges(); + } + + // perform edge, node and root updates if layout is not called + // remotely + if (!this.isRemoteUse) + { + // update all edges + var edge; + var allEdges = this.graphManager.getAllEdges(); + for (var i = 0; i < allEdges.length; i++) + { + edge = allEdges[i]; +// this.update(edge); + } + + // recursively update nodes + var node; + var nodes = this.graphManager.getRoot().getNodes(); + for (var i = 0; i < nodes.length; i++) + { + node = nodes[i]; +// this.update(node); + } + + // update root graph + this.update(this.graphManager.getRoot()); + } +}; + +Layout.prototype.update = function (obj) { + if (obj == null) { + this.update2(); + } + else if (obj instanceof LNode) { + var node = obj; + if (node.getChild() != null) + { + // since node is compound, recursively update child nodes + var nodes = node.getChild().getNodes(); + for (var i = 0; i < nodes.length; i++) + { + update(nodes[i]); + } + } + + // if the l-level node is associated with a v-level graph object, + // then it is assumed that the v-level node implements the + // interface Updatable. + if (node.vGraphObject != null) + { + // cast to Updatable without any type check + var vNode = node.vGraphObject; + + // call the update method of the interface + vNode.update(node); + } + } + else if (obj instanceof LEdge) { + var edge = obj; + // if the l-level edge is associated with a v-level graph object, + // then it is assumed that the v-level edge implements the + // interface Updatable. + + if (edge.vGraphObject != null) + { + // cast to Updatable without any type check + var vEdge = edge.vGraphObject; + + // call the update method of the interface + vEdge.update(edge); + } + } + else if (obj instanceof LGraph) { + var graph = obj; + // if the l-level graph is associated with a v-level graph object, + // then it is assumed that the v-level object implements the + // interface Updatable. + + if (graph.vGraphObject != null) + { + // cast to Updatable without any type check + var vGraph = graph.vGraphObject; + + // call the update method of the interface + vGraph.update(graph); + } + } +}; + +/** + * This method is used to set all layout parameters to default values + * determined at compile time. + */ +Layout.prototype.initParameters = function () { + if (!this.isSubLayout) + { + this.layoutQuality = LayoutConstants.DEFAULT_QUALITY; + this.animationDuringLayout = LayoutConstants.DEFAULT_ANIMATION_ON_LAYOUT; + this.animationPeriod = LayoutConstants.DEFAULT_ANIMATION_PERIOD; + this.animationOnLayout = LayoutConstants.DEFAULT_ANIMATION_DURING_LAYOUT; + this.incremental = LayoutConstants.DEFAULT_INCREMENTAL; + this.createBendsAsNeeded = LayoutConstants.DEFAULT_CREATE_BENDS_AS_NEEDED; + this.uniformLeafNodeSizes = LayoutConstants.DEFAULT_UNIFORM_LEAF_NODE_SIZES; + } + + if (this.animationDuringLayout) + { + animationOnLayout = false; + } +}; + +Layout.prototype.transform = function (newLeftTop) { + if (newLeftTop == undefined) { + this.transform(new PointD(0, 0)); + } + else { + // create a transformation object (from Eclipse to layout). When an + // inverse transform is applied, we get upper-left coordinate of the + // drawing or the root graph at given input coordinate (some margins + // already included in calculation of left-top). + + var trans = new Transform(); + var leftTop = this.graphManager.getRoot().updateLeftTop(); + + if (leftTop != null) + { + trans.setWorldOrgX(newLeftTop.x); + trans.setWorldOrgY(newLeftTop.y); + + trans.setDeviceOrgX(leftTop.x); + trans.setDeviceOrgY(leftTop.y); + + var nodes = this.getAllNodes(); + var node; + + for (var i = 0; i < nodes.length; i++) + { + node = nodes[i]; + node.transform(trans); + } + } + } +}; + +Layout.prototype.positionNodesRandomly = function (graph) { + + if (graph == undefined) { + //assert !this.incremental; + this.positionNodesRandomly(this.getGraphManager().getRoot()); + this.getGraphManager().getRoot().updateBounds(true); + } + else { + var lNode; + var childGraph; + + var nodes = graph.getNodes(); + for (var i = 0; i < nodes.length; i++) + { + lNode = nodes[i]; + childGraph = lNode.getChild(); + + if (childGraph == null) + { + lNode.scatter(); + } + else if (childGraph.getNodes().length == 0) + { + lNode.scatter(); + } + else + { + this.positionNodesRandomly(childGraph); + lNode.updateBounds(); + } + } + } +}; + +/** + * This method returns a list of trees where each tree is represented as a + * list of l-nodes. The method returns a list of size 0 when: + * - The graph is not flat or + * - One of the component(s) of the graph is not a tree. + */ +Layout.prototype.getFlatForest = function () +{ + var flatForest = []; + var isForest = true; + + // Quick reference for all nodes in the graph manager associated with + // this layout. The list should not be changed. + var allNodes = this.graphManager.getRoot().getNodes(); + + // First be sure that the graph is flat + var isFlat = true; + + for (var i = 0; i < allNodes.length; i++) + { + if (allNodes[i].getChild() != null) + { + isFlat = false; + } + } + + // Return empty forest if the graph is not flat. + if (!isFlat) + { + return flatForest; + } + + // Run BFS for each component of the graph. + + var visited = new HashSet(); + var toBeVisited = []; + var parents = new HashMap(); + var unProcessedNodes = []; + + unProcessedNodes = unProcessedNodes.concat(allNodes); + + // Each iteration of this loop finds a component of the graph and + // decides whether it is a tree or not. If it is a tree, adds it to the + // forest and continued with the next component. + + while (unProcessedNodes.length > 0 && isForest) + { + toBeVisited.push(unProcessedNodes[0]); + + // Start the BFS. Each iteration of this loop visits a node in a + // BFS manner. + while (toBeVisited.length > 0 && isForest) + { + //pool operation + var currentNode = toBeVisited[0]; + toBeVisited.splice(0, 1); + visited.add(currentNode); + + // Traverse all neighbors of this node + var neighborEdges = currentNode.getEdges(); + + for (var i = 0; i < neighborEdges.length; i++) + { + var currentNeighbor = + neighborEdges[i].getOtherEnd(currentNode); + + // If BFS is not growing from this neighbor. + if (parents.get(currentNode) != currentNeighbor) + { + // We haven't previously visited this neighbor. + if (!visited.contains(currentNeighbor)) + { + toBeVisited.push(currentNeighbor); + parents.put(currentNeighbor, currentNode); + } + // Since we have previously visited this neighbor and + // this neighbor is not parent of currentNode, given + // graph contains a component that is not tree, hence + // it is not a forest. + else + { + isForest = false; + break; + } + } + } + } + + // The graph contains a component that is not a tree. Empty + // previously found trees. The method will end. + if (!isForest) + { + flatForest = []; + } + // Save currently visited nodes as a tree in our forest. Reset + // visited and parents lists. Continue with the next component of + // the graph, if any. + else + { + var temp = []; + visited.addAllTo(temp); + flatForest.push(temp); + //flatForest = flatForest.concat(temp); + //unProcessedNodes.removeAll(visited); + for (var i = 0; i < temp.length; i++) { + var value = temp[i]; + var index = unProcessedNodes.indexOf(value); + if (index > -1) { + unProcessedNodes.splice(index, 1); + } + } + visited = new HashSet(); + parents = new HashMap(); + } + } + + return flatForest; +}; + +/** + * This method creates dummy nodes (an l-level node with minimal dimensions) + * for the given edge (one per bendpoint). The existing l-level structure + * is updated accordingly. + */ +Layout.prototype.createDummyNodesForBendpoints = function (edge) +{ + var dummyNodes = []; + var prev = edge.source; + + var graph = this.graphManager.calcLowestCommonAncestor(edge.source, edge.target); + + for (var i = 0; i < edge.bendpoints.length; i++) + { + // create new dummy node + var dummyNode = this.newNode(null); + dummyNode.setRect(new Point(0, 0), new Dimension(1, 1)); + + graph.add(dummyNode); + + // create new dummy edge between prev and dummy node + var dummyEdge = this.newEdge(null); + this.graphManager.add(dummyEdge, prev, dummyNode); + + dummyNodes.add(dummyNode); + prev = dummyNode; + } + + var dummyEdge = this.newEdge(null); + this.graphManager.add(dummyEdge, prev, edge.target); + + this.edgeToDummyNodes.put(edge, dummyNodes); + + // remove real edge from graph manager if it is inter-graph + if (edge.isInterGraph()) + { + this.graphManager.remove(edge); + } + // else, remove the edge from the current graph + else + { + graph.remove(edge); + } + + return dummyNodes; +}; + +/** + * This method creates bendpoints for edges from the dummy nodes + * at l-level. + */ +Layout.prototype.createBendpointsFromDummyNodes = function () +{ + var edges = []; + edges = edges.concat(this.graphManager.getAllEdges()); + edges = this.edgeToDummyNodes.keySet().concat(edges); + + for (var k = 0; k < edges.length; k++) + { + var lEdge = edges[k]; + + if (lEdge.bendpoints.length > 0) + { + var path = this.edgeToDummyNodes.get(lEdge); + + for (var i = 0; i < path.length; i++) + { + var dummyNode = path[i]; + var p = new PointD(dummyNode.getCenterX(), + dummyNode.getCenterY()); + + // update bendpoint's location according to dummy node + var ebp = lEdge.bendpoints.get(i); + ebp.x = p.x; + ebp.y = p.y; + + // remove the dummy node, dummy edges incident with this + // dummy node is also removed (within the remove method) + dummyNode.getOwner().remove(dummyNode); + } + + // add the real edge to graph + this.graphManager.add(lEdge, lEdge.source, lEdge.target); + } + } +}; + +Layout.transform = function (sliderValue, defaultValue, minDiv, maxMul) { + if (minDiv != undefined && maxMul != undefined) { + var value = defaultValue; + + if (sliderValue <= 50) + { + var minValue = defaultValue / minDiv; + value -= ((defaultValue - minValue) / 50) * (50 - sliderValue); + } + else + { + var maxValue = defaultValue * maxMul; + value += ((maxValue - defaultValue) / 50) * (sliderValue - 50); + } + + return value; + } + else { + var a, b; + + if (sliderValue <= 50) + { + a = 9.0 * defaultValue / 500.0; + b = defaultValue / 10.0; + } + else + { + a = 9.0 * defaultValue / 50.0; + b = -8 * defaultValue; + } + + return (a * sliderValue + b); + } +}; + +/** + * This method finds and returns the center of the given nodes, assuming + * that the given nodes form a tree in themselves. + */ +Layout.findCenterOfTree = function (nodes) +{ + var list = []; + list = list.concat(nodes); + + var removedNodes = []; + var remainingDegrees = new HashMap(); + var foundCenter = false; + var centerNode = null; + + if (list.length == 1 || list.length == 2) + { + foundCenter = true; + centerNode = list[0]; + } + + for (var i = 0; i < list.length; i++) + { + var node = list[i]; + var degree = node.getNeighborsList().size(); + remainingDegrees.put(node, node.getNeighborsList().size()); + + if (degree == 1) + { + removedNodes.push(node); + } + } + + var tempList = []; + tempList = tempList.concat(removedNodes); + + while (!foundCenter) + { + var tempList2 = []; + tempList2 = tempList2.concat(tempList); + tempList = []; + + for (var i = 0; i < list.length; i++) + { + var node = list[i]; + + var index = list.indexOf(node); + if (index >= 0) { + list.splice(index, 1); + } + + var neighbours = node.getNeighborsList(); + + for (var j in neighbours.set) + { + var neighbour = neighbours.set[j]; + if (removedNodes.indexOf(neighbour) < 0) + { + var otherDegree = remainingDegrees.get(neighbour); + var newDegree = otherDegree - 1; + + if (newDegree == 1) + { + tempList.push(neighbour); + } + + remainingDegrees.put(neighbour, newDegree); + } + } + } + + removedNodes = removedNodes.concat(tempList); + + if (list.length == 1 || list.length == 2) + { + foundCenter = true; + centerNode = list[0]; + } + } + + return centerNode; +}; + +/** + * During the coarsening process, this layout may be referenced by two graph managers + * this setter function grants access to change the currently being used graph manager + */ +Layout.prototype.setGraphManager = function (gm) +{ + this.graphManager = gm; +}; + +module.exports = Layout; + +},{"./Emitter":8,"./HashMap":13,"./HashSet":14,"./LEdge":18,"./LGraph":19,"./LGraphManager":20,"./LNode":22,"./LayoutConstants":24,"./PointD":26,"./Transform":30}],24:[function(require,module,exports){ +function LayoutConstants() { +} + +/** + * Layout Quality + */ +LayoutConstants.PROOF_QUALITY = 0; +LayoutConstants.DEFAULT_QUALITY = 1; +LayoutConstants.DRAFT_QUALITY = 2; + +/** + * Default parameters + */ +LayoutConstants.DEFAULT_CREATE_BENDS_AS_NEEDED = false; +//LayoutConstants.DEFAULT_INCREMENTAL = true; +LayoutConstants.DEFAULT_INCREMENTAL = false; +LayoutConstants.DEFAULT_ANIMATION_ON_LAYOUT = true; +LayoutConstants.DEFAULT_ANIMATION_DURING_LAYOUT = false; +LayoutConstants.DEFAULT_ANIMATION_PERIOD = 50; +LayoutConstants.DEFAULT_UNIFORM_LEAF_NODE_SIZES = false; + +// ----------------------------------------------------------------------------- +// Section: General other constants +// ----------------------------------------------------------------------------- +/* + * Margins of a graph to be applied on bouding rectangle of its contents. We + * assume margins on all four sides to be uniform. + */ +LayoutConstants.DEFAULT_GRAPH_MARGIN = 15; + +/* + * Default dimension of a non-compound node. + */ +LayoutConstants.SIMPLE_NODE_SIZE = 40; + +/* + * Default dimension of a non-compound node. + */ +LayoutConstants.SIMPLE_NODE_HALF_SIZE = LayoutConstants.SIMPLE_NODE_SIZE / 2; + +/* + * Empty compound node size. When a compound node is empty, its both + * dimensions should be of this value. + */ +LayoutConstants.EMPTY_COMPOUND_NODE_SIZE = 40; + +/* + * Minimum length that an edge should take during layout + */ +LayoutConstants.MIN_EDGE_LENGTH = 1; + +/* + * World boundaries that layout operates on + */ +LayoutConstants.WORLD_BOUNDARY = 1000000; + +/* + * World boundaries that random positioning can be performed with + */ +LayoutConstants.INITIAL_WORLD_BOUNDARY = LayoutConstants.WORLD_BOUNDARY / 1000; + +/* + * Coordinates of the world center + */ +LayoutConstants.WORLD_CENTER_X = 1200; +LayoutConstants.WORLD_CENTER_Y = 900; + +module.exports = LayoutConstants; + +},{}],25:[function(require,module,exports){ +/* + *This class is the javascript implementation of the Point.java class in jdk + */ +function Point(x, y, p) { + this.x = null; + this.y = null; + if (x == null && y == null && p == null) { + this.x = 0; + this.y = 0; + } + else if (typeof x == 'number' && typeof y == 'number' && p == null) { + this.x = x; + this.y = y; + } + else if (x.constructor.name == 'Point' && y == null && p == null) { + p = x; + this.x = p.x; + this.y = p.y; + } +} + +Point.prototype.getX = function () { + return this.x; +} + +Point.prototype.getY = function () { + return this.y; +} + +Point.prototype.getLocation = function () { + return new Point(this.x, this.y); +} + +Point.prototype.setLocation = function (x, y, p) { + if (x.constructor.name == 'Point' && y == null && p == null) { + p = x; + this.setLocation(p.x, p.y); + } + else if (typeof x == 'number' && typeof y == 'number' && p == null) { + //if both parameters are integer just move (x,y) location + if (parseInt(x) == x && parseInt(y) == y) { + this.move(x, y); + } + else { + this.x = Math.floor(x + 0.5); + this.y = Math.floor(y + 0.5); + } + } +} + +Point.prototype.move = function (x, y) { + this.x = x; + this.y = y; +} + +Point.prototype.translate = function (dx, dy) { + this.x += dx; + this.y += dy; +} + +Point.prototype.equals = function (obj) { + if (obj.constructor.name == "Point") { + var pt = obj; + return (this.x == pt.x) && (this.y == pt.y); + } + return this == obj; +} + +Point.prototype.toString = function () { + return new Point().constructor.name + "[x=" + this.x + ",y=" + this.y + "]"; +} + +module.exports = Point; + +},{}],26:[function(require,module,exports){ +function PointD(x, y) { + if (x == null && y == null) { + this.x = 0; + this.y = 0; + } else { + this.x = x; + this.y = y; + } +} + +PointD.prototype.getX = function () +{ + return this.x; +}; + +PointD.prototype.getY = function () +{ + return this.y; +}; + +PointD.prototype.setX = function (x) +{ + this.x = x; +}; + +PointD.prototype.setY = function (y) +{ + this.y = y; +}; + +PointD.prototype.getDifference = function (pt) +{ + return new DimensionD(this.x - pt.x, this.y - pt.y); +}; + +PointD.prototype.getCopy = function () +{ + return new PointD(this.x, this.y); +}; + +PointD.prototype.translate = function (dim) +{ + this.x += dim.width; + this.y += dim.height; + return this; +}; + +module.exports = PointD; + +},{}],27:[function(require,module,exports){ +function RandomSeed() { +} +RandomSeed.seed = 1; +RandomSeed.x = 0; + +RandomSeed.nextDouble = function () { + RandomSeed.x = Math.sin(RandomSeed.seed++) * 10000; + return RandomSeed.x - Math.floor(RandomSeed.x); +}; + +module.exports = RandomSeed; + +},{}],28:[function(require,module,exports){ +function RectangleD(x, y, width, height) { + this.x = 0; + this.y = 0; + this.width = 0; + this.height = 0; + + if (x != null && y != null && width != null && height != null) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } +} + +RectangleD.prototype.getX = function () +{ + return this.x; +}; + +RectangleD.prototype.setX = function (x) +{ + this.x = x; +}; + +RectangleD.prototype.getY = function () +{ + return this.y; +}; + +RectangleD.prototype.setY = function (y) +{ + this.y = y; +}; + +RectangleD.prototype.getWidth = function () +{ + return this.width; +}; + +RectangleD.prototype.setWidth = function (width) +{ + this.width = width; +}; + +RectangleD.prototype.getHeight = function () +{ + return this.height; +}; + +RectangleD.prototype.setHeight = function (height) +{ + this.height = height; +}; + +RectangleD.prototype.getRight = function () +{ + return this.x + this.width; +}; + +RectangleD.prototype.getBottom = function () +{ + return this.y + this.height; +}; + +RectangleD.prototype.intersects = function (a) +{ + if (this.getRight() < a.x) + { + return false; + } + + if (this.getBottom() < a.y) + { + return false; + } + + if (a.getRight() < this.x) + { + return false; + } + + if (a.getBottom() < this.y) + { + return false; + } + + return true; +}; + +RectangleD.prototype.getCenterX = function () +{ + return this.x + this.width / 2; +}; + +RectangleD.prototype.getMinX = function () +{ + return this.getX(); +}; + +RectangleD.prototype.getMaxX = function () +{ + return this.getX() + this.width; +}; + +RectangleD.prototype.getCenterY = function () +{ + return this.y + this.height / 2; +}; + +RectangleD.prototype.getMinY = function () +{ + return this.getY(); +}; + +RectangleD.prototype.getMaxY = function () +{ + return this.getY() + this.height; +}; + +RectangleD.prototype.getWidthHalf = function () +{ + return this.width / 2; +}; + +RectangleD.prototype.getHeightHalf = function () +{ + return this.height / 2; +}; + +module.exports = RectangleD; + +},{}],29:[function(require,module,exports){ +module.exports = function (instance) { + instance.toBeTiled = {}; + + instance.getToBeTiled = function (node) { + var id = node.data("id"); + //firstly check the previous results + if (instance.toBeTiled[id] != null) { + return instance.toBeTiled[id]; + } + + //only compound nodes are to be tiled + var children = node.children(); + if (children == null || children.length == 0) { + instance.toBeTiled[id] = false; + return false; + } + + //a compound node is not to be tiled if all of its compound children are not to be tiled + for (var i = 0; i < children.length; i++) { + var theChild = children[i]; + + if (instance.getNodeDegree(theChild) > 0) { + instance.toBeTiled[id] = false; + return false; + } + + //pass the children not having the compound structure + if (theChild.children() == null || theChild.children().length == 0) { + instance.toBeTiled[theChild.data("id")] = false; + continue; + } + + if (!instance.getToBeTiled(theChild)) { + instance.toBeTiled[id] = false; + return false; + } + } + instance.toBeTiled[id] = true; + return true; + }; + + instance.getNodeDegree = function (node) { + var id = node.id(); + var edges = instance.options.eles.edges().filter(function (ele, i) { + if (typeof ele === "number") { + ele = i; + } + var source = ele.data('source'); + var target = ele.data('target'); + if (source != target && (source == id || target == id)) { + return true; + } + }); + return edges.length; + }; + + instance.getNodeDegreeWithChildren = function (node) { + var degree = instance.getNodeDegree(node); + var children = node.children(); + for (var i = 0; i < children.length; i++) { + var child = children[i]; + degree += instance.getNodeDegreeWithChildren(child); + } + return degree; + }; + + instance.groupZeroDegreeMembers = function () { + // array of [parent_id x oneDegreeNode_id] + var tempMemberGroups = []; + var memberGroups = []; + var self = this; + var parentMap = {}; + + for (var i = 0; i < instance.options.eles.nodes().length; i++) { + parentMap[instance.options.eles.nodes()[i].id()] = true; + } + + // Find all zero degree nodes which aren't covered by a compound + var zeroDegree = instance.options.eles.nodes().filter(function (ele, i) { + if (typeof ele === "number") { + ele = i; + } + var pid = ele.data('parent'); + if (pid != undefined && !parentMap[pid]) { + pid = undefined; + } + + if (self.getNodeDegreeWithChildren(ele) == 0 && (pid == undefined || (pid != undefined && !self.getToBeTiled(ele.parent()[0])))) + return true; + else + return false; + }); + + // Create a map of parent node and its zero degree members + for (var i = 0; i < zeroDegree.length; i++) + { + var node = zeroDegree[i]; + var p_id = node.parent().id(); + + if (p_id != undefined && !parentMap[p_id]) { + p_id = undefined; + } + + if (typeof tempMemberGroups[p_id] === "undefined") + tempMemberGroups[p_id] = []; + + tempMemberGroups[p_id] = tempMemberGroups[p_id].concat(node); + } + + // If there are at least two nodes at a level, create a dummy compound for them + for (var p_id in tempMemberGroups) { + if (tempMemberGroups[p_id].length > 1) { + var dummyCompoundId = "DummyCompound_" + p_id; + memberGroups[dummyCompoundId] = tempMemberGroups[p_id]; + + // Create a dummy compound + if (instance.options.cy.getElementById(dummyCompoundId).empty()) { + instance.options.cy.add({ + group: "nodes", + data: {id: dummyCompoundId, parent: p_id + } + }); + + var dummy = instance.options.cy.nodes()[instance.options.cy.nodes().length - 1]; + instance.options.eles = instance.options.eles.union(dummy); + dummy.hide(); + + for (var i = 0; i < tempMemberGroups[p_id].length; i++) { + if (i == 0) { + dummy.scratch('coseBilkent', {tempchildren: []}); + } + var node = tempMemberGroups[p_id][i]; + var scratchObj = node.scratch('coseBilkent'); + if (!scratchObj) { + scratchObj = {}; + node.scratch('coseBilkent', scratchObj); + } + scratchObj['dummy_parent_id'] = dummyCompoundId; + instance.options.cy.add({ + group: "nodes", + data: {parent: dummyCompoundId, width: node.width(), height: node.height() + } + }); + var tempchild = instance.options.cy.nodes()[instance.options.cy.nodes().length - 1]; + tempchild.hide(); + tempchild.css('width', tempchild.data('width')); + tempchild.css('height', tempchild.data('height')); + tempchild.width(); + dummy.scratch('coseBilkent').tempchildren.push(tempchild); + } + } + } + } + + return memberGroups; + }; + + instance.performDFSOnCompounds = function (options) { + var compoundOrder = []; + + var roots = instance.getTopMostNodes(instance.options.eles.nodes()); + instance.fillCompexOrderByDFS(compoundOrder, roots); + + return compoundOrder; + }; + + instance.fillCompexOrderByDFS = function (compoundOrder, children) { + for (var i = 0; i < children.length; i++) { + var child = children[i]; + instance.fillCompexOrderByDFS(compoundOrder, child.children()); + if (instance.getToBeTiled(child)) { + compoundOrder.push(child); + } + } + }; + + instance.clearCompounds = function () { + var childGraphMap = []; + + // Get compound ordering by finding the inner one first + var compoundOrder = instance.performDFSOnCompounds(instance.options); + instance.compoundOrder = compoundOrder; + instance.processChildrenList(instance.root, instance.getTopMostNodes(instance.options.eles.nodes()), instance.layout); + + for (var i = 0; i < compoundOrder.length; i++) { + // find the corresponding layout node + var lCompoundNode = instance.idToLNode[compoundOrder[i].id()]; + + childGraphMap[compoundOrder[i].id()] = compoundOrder[i].children(); + + // Remove children of compounds + lCompoundNode.child = null; + } + + // Tile the removed children + var tiledMemberPack = instance.tileCompoundMembers(childGraphMap); + + return tiledMemberPack; + }; + + instance.clearZeroDegreeMembers = function (memberGroups) { + var tiledZeroDegreePack = []; + + for (var id in memberGroups) { + var compoundNode = instance.idToLNode[id]; + + tiledZeroDegreePack[id] = instance.tileNodes(memberGroups[id]); + + // Set the width and height of the dummy compound as calculated + compoundNode.rect.width = tiledZeroDegreePack[id].width; + compoundNode.rect.height = tiledZeroDegreePack[id].height; + } + return tiledZeroDegreePack; + }; + + instance.repopulateCompounds = function (tiledMemberPack) { + for (var i = instance.compoundOrder.length - 1; i >= 0; i--) { + var id = instance.compoundOrder[i].id(); + var lCompoundNode = instance.idToLNode[id]; + var horizontalMargin = parseInt(instance.compoundOrder[i].css('padding-left')); + var verticalMargin = parseInt(instance.compoundOrder[i].css('padding-top')); + + instance.adjustLocations(tiledMemberPack[id], lCompoundNode.rect.x, lCompoundNode.rect.y, horizontalMargin, verticalMargin); + } + }; + + instance.repopulateZeroDegreeMembers = function (tiledPack) { + for (var i in tiledPack) { + var compound = instance.cy.getElementById(i); + var compoundNode = instance.idToLNode[i]; + var horizontalMargin = parseInt(compound.css('padding-left')); + var verticalMargin = parseInt(compound.css('padding-top')); + + // Adjust the positions of nodes wrt its compound + instance.adjustLocations(tiledPack[i], compoundNode.rect.x, compoundNode.rect.y, horizontalMargin, verticalMargin); + + var tempchildren = compound.scratch('coseBilkent').tempchildren; + for (var i = 0; i < tempchildren.length; i++) { + tempchildren[i].remove(); + } + + // Remove the dummy compound + compound.remove(); + } + }; + + /** + * This method places each zero degree member wrt given (x,y) coordinates (top left). + */ + instance.adjustLocations = function (organization, x, y, compoundHorizontalMargin, compoundVerticalMargin) { + x += compoundHorizontalMargin; + y += compoundVerticalMargin; + + var left = x; + + for (var i = 0; i < organization.rows.length; i++) { + var row = organization.rows[i]; + x = left; + var maxHeight = 0; + + for (var j = 0; j < row.length; j++) { + var lnode = row[j]; + var node = instance.cy.getElementById(lnode.id); + + lnode.rect.x = x;// + lnode.rect.width / 2; + lnode.rect.y = y;// + lnode.rect.height / 2; + + x += lnode.rect.width + organization.horizontalPadding; + + if (lnode.rect.height > maxHeight) + maxHeight = lnode.rect.height; + } + + y += maxHeight + organization.verticalPadding; + } + }; + + instance.tileCompoundMembers = function (childGraphMap) { + var tiledMemberPack = []; + + for (var id in childGraphMap) { + // Access layoutInfo nodes to set the width and height of compounds + var compoundNode = instance.idToLNode[id]; + + tiledMemberPack[id] = instance.tileNodes(childGraphMap[id]); + + compoundNode.rect.width = tiledMemberPack[id].width + 20; + compoundNode.rect.height = tiledMemberPack[id].height + 20; + } + + return tiledMemberPack; + }; + + instance.tileNodes = function (nodes) { + var self = this; + var verticalPadding = typeof self.options.tilingPaddingVertical === 'function' ? self.options.tilingPaddingVertical.call() : self.options.tilingPaddingVertical; + var horizontalPadding = typeof self.options.tilingPaddingHorizontal === 'function' ? self.options.tilingPaddingHorizontal.call() : self.options.tilingPaddingHorizontal; + var organization = { + rows: [], + rowWidth: [], + rowHeight: [], + width: 20, + height: 20, + verticalPadding: verticalPadding, + horizontalPadding: horizontalPadding + }; + + var layoutNodes = []; + + // Get layout nodes + for (var i = 0; i < nodes.length; i++) { + var node = nodes[i]; + var lNode = instance.idToLNode[node.id()]; + + if (!node.scratch('coseBilkent') || !node.scratch('coseBilkent').dummy_parent_id) { + var owner = lNode.owner; + owner.remove(lNode); + + instance.gm.resetAllNodes(); + instance.gm.getAllNodes(); + } + + layoutNodes.push(lNode); + } + + // Sort the nodes in ascending order of their areas + layoutNodes.sort(function (n1, n2) { + if (n1.rect.width * n1.rect.height > n2.rect.width * n2.rect.height) + return -1; + if (n1.rect.width * n1.rect.height < n2.rect.width * n2.rect.height) + return 1; + return 0; + }); + + // Create the organization -> tile members + for (var i = 0; i < layoutNodes.length; i++) { + var lNode = layoutNodes[i]; + + var cyNode = instance.cy.getElementById(lNode.id).parent()[0]; + var minWidth = 0; + if (cyNode) { + minWidth = parseInt(cyNode.css('padding-left')) + parseInt(cyNode.css('padding-right')); + } + + if (organization.rows.length == 0) { + instance.insertNodeToRow(organization, lNode, 0, minWidth); + } + else if (instance.canAddHorizontal(organization, lNode.rect.width, lNode.rect.height)) { + instance.insertNodeToRow(organization, lNode, instance.getShortestRowIndex(organization), minWidth); + } + else { + instance.insertNodeToRow(organization, lNode, organization.rows.length, minWidth); + } + + instance.shiftToLastRow(organization); + } + + return organization; + }; + + instance.insertNodeToRow = function (organization, node, rowIndex, minWidth) { + var minCompoundSize = minWidth; + + // Add new row if needed + if (rowIndex == organization.rows.length) { + var secondDimension = []; + + organization.rows.push(secondDimension); + organization.rowWidth.push(minCompoundSize); + organization.rowHeight.push(0); + } + + // Update row width + var w = organization.rowWidth[rowIndex] + node.rect.width; + + if (organization.rows[rowIndex].length > 0) { + w += organization.horizontalPadding; + } + + organization.rowWidth[rowIndex] = w; + // Update compound width + if (organization.width < w) { + organization.width = w; + } + + // Update height + var h = node.rect.height; + if (rowIndex > 0) + h += organization.verticalPadding; + + var extraHeight = 0; + if (h > organization.rowHeight[rowIndex]) { + extraHeight = organization.rowHeight[rowIndex]; + organization.rowHeight[rowIndex] = h; + extraHeight = organization.rowHeight[rowIndex] - extraHeight; + } + + organization.height += extraHeight; + + // Insert node + organization.rows[rowIndex].push(node); + }; + +//Scans the rows of an organization and returns the one with the min width + instance.getShortestRowIndex = function (organization) { + var r = -1; + var min = Number.MAX_VALUE; + + for (var i = 0; i < organization.rows.length; i++) { + if (organization.rowWidth[i] < min) { + r = i; + min = organization.rowWidth[i]; + } + } + return r; + }; + +//Scans the rows of an organization and returns the one with the max width + instance.getLongestRowIndex = function (organization) { + var r = -1; + var max = Number.MIN_VALUE; + + for (var i = 0; i < organization.rows.length; i++) { + + if (organization.rowWidth[i] > max) { + r = i; + max = organization.rowWidth[i]; + } + } + + return r; + }; + + /** + * This method checks whether adding extra width to the organization violates + * the aspect ratio(1) or not. + */ + instance.canAddHorizontal = function (organization, extraWidth, extraHeight) { + + var sri = instance.getShortestRowIndex(organization); + + if (sri < 0) { + return true; + } + + var min = organization.rowWidth[sri]; + + if (min + organization.horizontalPadding + extraWidth <= organization.width) + return true; + + var hDiff = 0; + + // Adding to an existing row + if (organization.rowHeight[sri] < extraHeight) { + if (sri > 0) + hDiff = extraHeight + organization.verticalPadding - organization.rowHeight[sri]; + } + + var add_to_row_ratio; + if (organization.width - min >= extraWidth + organization.horizontalPadding) { + add_to_row_ratio = (organization.height + hDiff) / (min + extraWidth + organization.horizontalPadding); + } else { + add_to_row_ratio = (organization.height + hDiff) / organization.width; + } + + // Adding a new row for this node + hDiff = extraHeight + organization.verticalPadding; + var add_new_row_ratio; + if (organization.width < extraWidth) { + add_new_row_ratio = (organization.height + hDiff) / extraWidth; + } else { + add_new_row_ratio = (organization.height + hDiff) / organization.width; + } + + if (add_new_row_ratio < 1) + add_new_row_ratio = 1 / add_new_row_ratio; + + if (add_to_row_ratio < 1) + add_to_row_ratio = 1 / add_to_row_ratio; + + return add_to_row_ratio < add_new_row_ratio; + }; + + +//If moving the last node from the longest row and adding it to the last +//row makes the bounding box smaller, do it. + instance.shiftToLastRow = function (organization) { + var longest = instance.getLongestRowIndex(organization); + var last = organization.rowWidth.length - 1; + var row = organization.rows[longest]; + var node = row[row.length - 1]; + + var diff = node.width + organization.horizontalPadding; + + // Check if there is enough space on the last row + if (organization.width - organization.rowWidth[last] > diff && longest != last) { + // Remove the last element of the longest row + row.splice(-1, 1); + + // Push it to the last row + organization.rows[last].push(node); + + organization.rowWidth[longest] = organization.rowWidth[longest] - diff; + organization.rowWidth[last] = organization.rowWidth[last] + diff; + organization.width = organization.rowWidth[instance.getLongestRowIndex(organization)]; + + // Update heights of the organization + var maxHeight = Number.MIN_VALUE; + for (var i = 0; i < row.length; i++) { + if (row[i].height > maxHeight) + maxHeight = row[i].height; + } + if (longest > 0) + maxHeight += organization.verticalPadding; + + var prevTotal = organization.rowHeight[longest] + organization.rowHeight[last]; + + organization.rowHeight[longest] = maxHeight; + if (organization.rowHeight[last] < node.height + organization.verticalPadding) + organization.rowHeight[last] = node.height + organization.verticalPadding; + + var finalTotal = organization.rowHeight[longest] + organization.rowHeight[last]; + organization.height += (finalTotal - prevTotal); + + instance.shiftToLastRow(organization); + } + }; + + instance.preLayout = function() { + // Find zero degree nodes and create a compound for each level + var memberGroups = instance.groupZeroDegreeMembers(); + // Tile and clear children of each compound + instance.tiledMemberPack = instance.clearCompounds(); + // Separately tile and clear zero degree nodes for each level + instance.tiledZeroDegreeNodes = instance.clearZeroDegreeMembers(memberGroups); + }; + + instance.postLayout = function() { + var nodes = instance.options.eles.nodes(); + //fill the toBeTiled map + for (var i = 0; i < nodes.length; i++) { + instance.getToBeTiled(nodes[i]); + } + + // Repopulate members + instance.repopulateZeroDegreeMembers(instance.tiledZeroDegreeNodes); + + instance.repopulateCompounds(instance.tiledMemberPack); + + instance.options.cy.nodes().updateCompoundBounds(); + }; +}; +},{}],30:[function(require,module,exports){ +var PointD = require('./PointD'); + +function Transform(x, y) { + this.lworldOrgX = 0.0; + this.lworldOrgY = 0.0; + this.ldeviceOrgX = 0.0; + this.ldeviceOrgY = 0.0; + this.lworldExtX = 1.0; + this.lworldExtY = 1.0; + this.ldeviceExtX = 1.0; + this.ldeviceExtY = 1.0; +} + +Transform.prototype.getWorldOrgX = function () +{ + return this.lworldOrgX; +} + +Transform.prototype.setWorldOrgX = function (wox) +{ + this.lworldOrgX = wox; +} + +Transform.prototype.getWorldOrgY = function () +{ + return this.lworldOrgY; +} + +Transform.prototype.setWorldOrgY = function (woy) +{ + this.lworldOrgY = woy; +} + +Transform.prototype.getWorldExtX = function () +{ + return this.lworldExtX; +} + +Transform.prototype.setWorldExtX = function (wex) +{ + this.lworldExtX = wex; +} + +Transform.prototype.getWorldExtY = function () +{ + return this.lworldExtY; +} + +Transform.prototype.setWorldExtY = function (wey) +{ + this.lworldExtY = wey; +} + +/* Device related */ + +Transform.prototype.getDeviceOrgX = function () +{ + return this.ldeviceOrgX; +} + +Transform.prototype.setDeviceOrgX = function (dox) +{ + this.ldeviceOrgX = dox; +} + +Transform.prototype.getDeviceOrgY = function () +{ + return this.ldeviceOrgY; +} + +Transform.prototype.setDeviceOrgY = function (doy) +{ + this.ldeviceOrgY = doy; +} + +Transform.prototype.getDeviceExtX = function () +{ + return this.ldeviceExtX; +} + +Transform.prototype.setDeviceExtX = function (dex) +{ + this.ldeviceExtX = dex; +} + +Transform.prototype.getDeviceExtY = function () +{ + return this.ldeviceExtY; +} + +Transform.prototype.setDeviceExtY = function (dey) +{ + this.ldeviceExtY = dey; +} + +Transform.prototype.transformX = function (x) +{ + var xDevice = 0.0; + var worldExtX = this.lworldExtX; + if (worldExtX != 0.0) + { + xDevice = this.ldeviceOrgX + + ((x - this.lworldOrgX) * this.ldeviceExtX / worldExtX); + } + + return xDevice; +} + +Transform.prototype.transformY = function (y) +{ + var yDevice = 0.0; + var worldExtY = this.lworldExtY; + if (worldExtY != 0.0) + { + yDevice = this.ldeviceOrgY + + ((y - this.lworldOrgY) * this.ldeviceExtY / worldExtY); + } + + + return yDevice; +} + +Transform.prototype.inverseTransformX = function (x) +{ + var xWorld = 0.0; + var deviceExtX = this.ldeviceExtX; + if (deviceExtX != 0.0) + { + xWorld = this.lworldOrgX + + ((x - this.ldeviceOrgX) * this.lworldExtX / deviceExtX); + } + + + return xWorld; +} + +Transform.prototype.inverseTransformY = function (y) +{ + var yWorld = 0.0; + var deviceExtY = this.ldeviceExtY; + if (deviceExtY != 0.0) + { + yWorld = this.lworldOrgY + + ((y - this.ldeviceOrgY) * this.lworldExtY / deviceExtY); + } + return yWorld; +} + +Transform.prototype.inverseTransformPoint = function (inPoint) +{ + var outPoint = + new PointD(this.inverseTransformX(inPoint.x), + this.inverseTransformY(inPoint.y)); + return outPoint; +} + +module.exports = Transform; + +},{"./PointD":26}],31:[function(require,module,exports){ +function UniqueIDGeneretor() { +} + +UniqueIDGeneretor.lastID = 0; + +UniqueIDGeneretor.createID = function (obj) { + if (UniqueIDGeneretor.isPrimitive(obj)) { + return obj; + } + if (obj.uniqueID != null) { + return obj.uniqueID; + } + obj.uniqueID = UniqueIDGeneretor.getString(); + UniqueIDGeneretor.lastID++; + return obj.uniqueID; +} + +UniqueIDGeneretor.getString = function (id) { + if (id == null) + id = UniqueIDGeneretor.lastID; + return "Object#" + id + ""; +} + +UniqueIDGeneretor.isPrimitive = function (arg) { + var type = typeof arg; + return arg == null || (type != "object" && type != "function"); +} + +module.exports = UniqueIDGeneretor; + +},{}],32:[function(require,module,exports){ +'use strict'; + +var DimensionD = require('./DimensionD'); +var HashMap = require('./HashMap'); +var HashSet = require('./HashSet'); +var IGeometry = require('./IGeometry'); +var IMath = require('./IMath'); +var Integer = require('./Integer'); +var Point = require('./Point'); +var PointD = require('./PointD'); +var RandomSeed = require('./RandomSeed'); +var RectangleD = require('./RectangleD'); +var Transform = require('./Transform'); +var UniqueIDGeneretor = require('./UniqueIDGeneretor'); +var LGraphObject = require('./LGraphObject'); +var LGraph = require('./LGraph'); +var LEdge = require('./LEdge'); +var LGraphManager = require('./LGraphManager'); +var LNode = require('./LNode'); +var Layout = require('./Layout'); +var LayoutConstants = require('./LayoutConstants'); +var FDLayout = require('./FDLayout'); +var FDLayoutConstants = require('./FDLayoutConstants'); +var FDLayoutEdge = require('./FDLayoutEdge'); +var FDLayoutNode = require('./FDLayoutNode'); +var CoSEConstants = require('./CoSEConstants'); +var CoSEEdge = require('./CoSEEdge'); +var CoSEGraph = require('./CoSEGraph'); +var CoSEGraphManager = require('./CoSEGraphManager'); +var CoSELayout = require('./CoSELayout'); +var CoSENode = require('./CoSENode'); +var TilingExtension = require('./TilingExtension'); + +var defaults = { + // Called on `layoutready` + ready: function () { + }, + // Called on `layoutstop` + stop: function () { + }, + // number of ticks per frame; higher is faster but more jerky + refresh: 30, + // Whether to fit the network view after when done + fit: true, + // Padding on fit + padding: 10, + // Padding for compounds + paddingCompound: 15, + // Whether to enable incremental mode + randomize: true, + // Node repulsion (non overlapping) multiplier + nodeRepulsion: 4500, + // Ideal edge (non nested) length + idealEdgeLength: 50, + // Divisor to compute edge forces + edgeElasticity: 0.45, + // Nesting factor (multiplier) to compute ideal edge length for nested edges + nestingFactor: 0.1, + // Gravity force (constant) + gravity: 0.25, + // Maximum number of iterations to perform + numIter: 2500, + // For enabling tiling + tile: true, + // Type of layout animation. The option set is {'during', 'end', false} + animate: 'end', + // Duration for animate:end + animationDuration: 500, + // Represents the amount of the vertical space to put between the zero degree members during the tiling operation(can also be a function) + tilingPaddingVertical: 10, + // Represents the amount of the horizontal space to put between the zero degree members during the tiling operation(can also be a function) + tilingPaddingHorizontal: 10, + // Gravity range (constant) for compounds + gravityRangeCompound: 1.5, + // Gravity force (constant) for compounds + gravityCompound: 1.0, + // Gravity range (constant) + gravityRange: 3.8 +}; + +function extend(defaults, options) { + var obj = {}; + + for (var i in defaults) { + obj[i] = defaults[i]; + } + + for (var i in options) { + obj[i] = options[i]; + } + + return obj; +}; + +function _CoSELayout(_options) { + TilingExtension(this); // Extend this instance with tiling functions + this.options = extend(defaults, _options); + getUserOptions(this.options); +} + +var getUserOptions = function (options) { + if (options.nodeRepulsion != null) + CoSEConstants.DEFAULT_REPULSION_STRENGTH = FDLayoutConstants.DEFAULT_REPULSION_STRENGTH = options.nodeRepulsion; + if (options.idealEdgeLength != null) + CoSEConstants.DEFAULT_EDGE_LENGTH = FDLayoutConstants.DEFAULT_EDGE_LENGTH = options.idealEdgeLength; + if (options.edgeElasticity != null) + CoSEConstants.DEFAULT_SPRING_STRENGTH = FDLayoutConstants.DEFAULT_SPRING_STRENGTH = options.edgeElasticity; + if (options.nestingFactor != null) + CoSEConstants.PER_LEVEL_IDEAL_EDGE_LENGTH_FACTOR = FDLayoutConstants.PER_LEVEL_IDEAL_EDGE_LENGTH_FACTOR = options.nestingFactor; + if (options.gravity != null) + CoSEConstants.DEFAULT_GRAVITY_STRENGTH = FDLayoutConstants.DEFAULT_GRAVITY_STRENGTH = options.gravity; + if (options.numIter != null) + CoSEConstants.MAX_ITERATIONS = FDLayoutConstants.MAX_ITERATIONS = options.numIter; + if (options.paddingCompound != null) + CoSEConstants.DEFAULT_GRAPH_MARGIN = FDLayoutConstants.DEFAULT_GRAPH_MARGIN = LayoutConstants.DEFAULT_GRAPH_MARGIN = options.paddingCompound; + if (options.gravityRange != null) + CoSEConstants.DEFAULT_GRAVITY_RANGE_FACTOR = FDLayoutConstants.DEFAULT_GRAVITY_RANGE_FACTOR = options.gravityRange; + if(options.gravityCompound != null) + CoSEConstants.DEFAULT_COMPOUND_GRAVITY_STRENGTH = FDLayoutConstants.DEFAULT_COMPOUND_GRAVITY_STRENGTH = options.gravityCompound; + if(options.gravityRangeCompound != null) + CoSEConstants.DEFAULT_COMPOUND_GRAVITY_RANGE_FACTOR = FDLayoutConstants.DEFAULT_COMPOUND_GRAVITY_RANGE_FACTOR = options.gravityRangeCompound; + + CoSEConstants.DEFAULT_INCREMENTAL = FDLayoutConstants.DEFAULT_INCREMENTAL = LayoutConstants.DEFAULT_INCREMENTAL = + !(options.randomize); + CoSEConstants.ANIMATE = FDLayoutConstants.ANIMATE = options.animate; +}; + +_CoSELayout.prototype.run = function () { + var ready; + var frameId; + var options = this.options; + var idToLNode = this.idToLNode = {}; + var layout = this.layout = new CoSELayout(); + var self = this; + + this.cy = this.options.cy; + + this.cy.trigger('layoutstart'); + + var gm = layout.newGraphManager(); + this.gm = gm; + + var nodes = this.options.eles.nodes(); + var edges = this.options.eles.edges(); + + this.root = gm.addRoot(); + + if (!this.options.tile) { + this.processChildrenList(this.root, this.getTopMostNodes(nodes), layout); + } + else { + this.preLayout(); + } + + + for (var i = 0; i < edges.length; i++) { + var edge = edges[i]; + var sourceNode = this.idToLNode[edge.data("source")]; + var targetNode = this.idToLNode[edge.data("target")]; + var e1 = gm.add(layout.newEdge(), sourceNode, targetNode); + e1.id = edge.id(); + } + + var getPositions = function(ele, i){ + if(typeof ele === "number") { + ele = i; + } + var theId = ele.data('id'); + var lNode = self.idToLNode[theId]; + + return { + x: lNode.getRect().getCenterX(), + y: lNode.getRect().getCenterY() + }; + }; + + /* + * Reposition nodes in iterations animatedly + */ + var iterateAnimated = function () { + // Thigs to perform after nodes are repositioned on screen + var afterReposition = function() { + if (options.fit) { + options.cy.fit(options.eles.nodes(), options.padding); + } + + if (!ready) { + ready = true; + self.cy.one('layoutready', options.ready); + self.cy.trigger({type: 'layoutready', layout: self}); + } + }; + + var ticksPerFrame = self.options.refresh; + var isDone; + + for( var i = 0; i < ticksPerFrame && !isDone; i++ ){ + isDone = self.layout.tick(); + } + + // If layout is done + if (isDone) { + if (self.options.tile) { + self.postLayout(); + } + self.options.eles.nodes().positions(getPositions); + + afterReposition(); + + // trigger layoutstop when the layout stops (e.g. finishes) + self.cy.one('layoutstop', self.options.stop); + self.cy.trigger('layoutstop'); + + if (frameId) { + cancelAnimationFrame(frameId); + } + + self.options.eles.nodes().removeScratch('coseBilkent'); + ready = false; + return; + } + + var animationData = self.layout.getPositionsData(); // Get positions of layout nodes note that all nodes may not be layout nodes because of tiling + // Position nodes, for the nodes who are not passed to layout because of tiling return the position of their dummy compound + options.eles.nodes().positions(function (ele, i) { + if (typeof ele === "number") { + ele = i; + } + if (ele.scratch('coseBilkent') && ele.scratch('coseBilkent').dummy_parent_id) { + var dummyParent = ele.scratch('coseBilkent').dummy_parent_id; + return { + x: dummyParent.x, + y: dummyParent.y + }; + } + var theId = ele.data('id'); + var pNode = animationData[theId]; + var temp = ele; + while (pNode == null) { + temp = temp.parent()[0]; + pNode = animationData[temp.id()]; + animationData[theId] = pNode; + } + return { + x: pNode.x, + y: pNode.y + }; + }); + + afterReposition(); + + frameId = requestAnimationFrame(iterateAnimated); + }; + + /* + * Listen 'layoutstarted' event and start animated iteration if animate option is 'during' + */ + layout.addListener('layoutstarted', function () { + if (self.options.animate === 'during') { + frameId = requestAnimationFrame(iterateAnimated); + } + }); + + layout.runLayout(); // Run cose layout + + /* + * If animate option is not 'during' ('end' or false) perform these here (If it is 'during' similar things are already performed) + */ + if(this.options.animate !== 'during'){ + setTimeout(function() { + if (self.options.tile) { + self.postLayout(); + } + self.options.eles.nodes().not(":parent").layoutPositions(self, self.options, getPositions); // Use layout positions to reposition the nodes it considers the options parameter + self.options.eles.nodes().removeScratch('coseBilkent'); + ready = false; + }, 0); + + } + + return this; // chaining +}; + +//Get the top most ones of a list of nodes +_CoSELayout.prototype.getTopMostNodes = function(nodes) { + var nodesMap = {}; + for (var i = 0; i < nodes.length; i++) { + nodesMap[nodes[i].id()] = true; + } + var roots = nodes.filter(function (ele, i) { + if(typeof ele === "number") { + ele = i; + } + var parent = ele.parent()[0]; + while(parent != null){ + if(nodesMap[parent.id()]){ + return false; + } + parent = parent.parent()[0]; + } + return true; + }); + + return roots; +}; + +_CoSELayout.prototype.processChildrenList = function (parent, children, layout) { + var size = children.length; + for (var i = 0; i < size; i++) { + var theChild = children[i]; + this.options.eles.nodes().length; + var children_of_children = theChild.children(); + var theNode; + + if (theChild.width() != null + && theChild.height() != null) { + theNode = parent.add(new CoSENode(layout.graphManager, + new PointD(theChild.position('x'), theChild.position('y')), + new DimensionD(parseFloat(theChild.width()), + parseFloat(theChild.height())))); + } + else { + theNode = parent.add(new CoSENode(this.graphManager)); + } + theNode.id = theChild.data("id"); + this.idToLNode[theChild.data("id")] = theNode; + + if (isNaN(theNode.rect.x)) { + theNode.rect.x = 0; + } + + if (isNaN(theNode.rect.y)) { + theNode.rect.y = 0; + } + + if (children_of_children != null && children_of_children.length > 0) { + var theNewGraph; + theNewGraph = layout.getGraphManager().add(layout.newGraph(), theNode); + this.processChildrenList(theNewGraph, children_of_children, layout); + } + } +}; + +/** + * @brief : called on continuous layouts to stop them before they finish + */ +_CoSELayout.prototype.stop = function () { + this.stopped = true; + + this.trigger('layoutstop'); + + return this; // chaining +}; + +module.exports = function get(cytoscape) { + return _CoSELayout; +}; + +},{"./CoSEConstants":1,"./CoSEEdge":2,"./CoSEGraph":3,"./CoSEGraphManager":4,"./CoSELayout":5,"./CoSENode":6,"./DimensionD":7,"./FDLayout":9,"./FDLayoutConstants":10,"./FDLayoutEdge":11,"./FDLayoutNode":12,"./HashMap":13,"./HashSet":14,"./IGeometry":15,"./IMath":16,"./Integer":17,"./LEdge":18,"./LGraph":19,"./LGraphManager":20,"./LGraphObject":21,"./LNode":22,"./Layout":23,"./LayoutConstants":24,"./Point":25,"./PointD":26,"./RandomSeed":27,"./RectangleD":28,"./TilingExtension":29,"./Transform":30,"./UniqueIDGeneretor":31}],33:[function(require,module,exports){ +'use strict'; + +// registers the extension on a cytoscape lib ref +var getLayout = require('./Layout'); + +var register = function( cytoscape ){ + var Layout = getLayout( cytoscape ); + + cytoscape('layout', 'cose-bilkent', Layout); +}; + +// auto reg for globals +if( typeof cytoscape !== 'undefined' ){ + register( cytoscape ); +} + +module.exports = register; + +},{"./Layout":32}]},{},[33])(33) +}); +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy9icm93c2VyLXBhY2svX3ByZWx1ZGUuanMiLCJzcmMvTGF5b3V0L0NvU0VDb25zdGFudHMuanMiLCJzcmMvTGF5b3V0L0NvU0VFZGdlLmpzIiwic3JjL0xheW91dC9Db1NFR3JhcGguanMiLCJzcmMvTGF5b3V0L0NvU0VHcmFwaE1hbmFnZXIuanMiLCJzcmMvTGF5b3V0L0NvU0VMYXlvdXQuanMiLCJzcmMvTGF5b3V0L0NvU0VOb2RlLmpzIiwic3JjL0xheW91dC9EaW1lbnNpb25ELmpzIiwic3JjL0xheW91dC9FbWl0dGVyLmpzIiwic3JjL0xheW91dC9GRExheW91dC5qcyIsInNyYy9MYXlvdXQvRkRMYXlvdXRDb25zdGFudHMuanMiLCJzcmMvTGF5b3V0L0ZETGF5b3V0RWRnZS5qcyIsInNyYy9MYXlvdXQvRkRMYXlvdXROb2RlLmpzIiwic3JjL0xheW91dC9IYXNoTWFwLmpzIiwic3JjL0xheW91dC9IYXNoU2V0LmpzIiwic3JjL0xheW91dC9JR2VvbWV0cnkuanMiLCJzcmMvTGF5b3V0L0lNYXRoLmpzIiwic3JjL0xheW91dC9JbnRlZ2VyLmpzIiwic3JjL0xheW91dC9MRWRnZS5qcyIsInNyYy9MYXlvdXQvTEdyYXBoLmpzIiwic3JjL0xheW91dC9MR3JhcGhNYW5hZ2VyLmpzIiwic3JjL0xheW91dC9MR3JhcGhPYmplY3QuanMiLCJzcmMvTGF5b3V0L0xOb2RlLmpzIiwic3JjL0xheW91dC9MYXlvdXQuanMiLCJzcmMvTGF5b3V0L0xheW91dENvbnN0YW50cy5qcyIsInNyYy9MYXlvdXQvUG9pbnQuanMiLCJzcmMvTGF5b3V0L1BvaW50RC5qcyIsInNyYy9MYXlvdXQvUmFuZG9tU2VlZC5qcyIsInNyYy9MYXlvdXQvUmVjdGFuZ2xlRC5qcyIsInNyYy9MYXlvdXQvVGlsaW5nRXh0ZW5zaW9uLmpzIiwic3JjL0xheW91dC9UcmFuc2Zvcm0uanMiLCJzcmMvTGF5b3V0L1VuaXF1ZUlER2VuZXJldG9yLmpzIiwic3JjL0xheW91dC9pbmRleC5qcyIsInNyYyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTtBQ0FBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ2ZBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ1pBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ1pBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ1pBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQzViQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUN4SEE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDOUJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDbENBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ2pYQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUM5QkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDZkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDMUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQzlCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3ZEQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDMVpBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQzlCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ1BBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3pKQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3RjQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUN0ZUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ0xBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUM5VkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQy9wQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3BFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3pFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNoREE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ1hBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDbElBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUN2aUJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDN0pBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUM3QkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDcldBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSIsImZpbGUiOiJnZW5lcmF0ZWQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlc0NvbnRlbnQiOlsiKGZ1bmN0aW9uIGUodCxuLHIpe2Z1bmN0aW9uIHMobyx1KXtpZighbltvXSl7aWYoIXRbb10pe3ZhciBhPXR5cGVvZiByZXF1aXJlPT1cImZ1bmN0aW9uXCImJnJlcXVpcmU7aWYoIXUmJmEpcmV0dXJuIGEobywhMCk7aWYoaSlyZXR1cm4gaShvLCEwKTt2YXIgZj1uZXcgRXJyb3IoXCJDYW5ub3QgZmluZCBtb2R1bGUgJ1wiK28rXCInXCIpO3Rocm93IGYuY29kZT1cIk1PRFVMRV9OT1RfRk9VTkRcIixmfXZhciBsPW5bb109e2V4cG9ydHM6e319O3Rbb11bMF0uY2FsbChsLmV4cG9ydHMsZnVuY3Rpb24oZSl7dmFyIG49dFtvXVsxXVtlXTtyZXR1cm4gcyhuP246ZSl9LGwsbC5leHBvcnRzLGUsdCxuLHIpfXJldHVybiBuW29dLmV4cG9ydHN9dmFyIGk9dHlwZW9mIHJlcXVpcmU9PVwiZnVuY3Rpb25cIiYmcmVxdWlyZTtmb3IodmFyIG89MDtvPHIubGVuZ3RoO28rKylzKHJbb10pO3JldHVybiBzfSkiLCJ2YXIgRkRMYXlvdXRDb25zdGFudHMgPSByZXF1aXJlKCcuL0ZETGF5b3V0Q29uc3RhbnRzJyk7XG5cbmZ1bmN0aW9uIENvU0VDb25zdGFudHMoKSB7XG59XG5cbi8vQ29TRUNvbnN0YW50cyBpbmhlcml0cyBzdGF0aWMgcHJvcHMgaW4gRkRMYXlvdXRDb25zdGFudHNcbmZvciAodmFyIHByb3AgaW4gRkRMYXlvdXRDb25zdGFudHMpIHtcbiAgQ29TRUNvbnN0YW50c1twcm9wXSA9IEZETGF5b3V0Q29uc3RhbnRzW3Byb3BdO1xufVxuXG5Db1NFQ29uc3RhbnRzLkRFRkFVTFRfVVNFX01VTFRJX0xFVkVMX1NDQUxJTkcgPSBmYWxzZTtcbkNvU0VDb25zdGFudHMuREVGQVVMVF9SQURJQUxfU0VQQVJBVElPTiA9IEZETGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfRURHRV9MRU5HVEg7XG5Db1NFQ29uc3RhbnRzLkRFRkFVTFRfQ09NUE9ORU5UX1NFUEVSQVRJT04gPSA2MDtcblxubW9kdWxlLmV4cG9ydHMgPSBDb1NFQ29uc3RhbnRzO1xuIiwidmFyIEZETGF5b3V0RWRnZSA9IHJlcXVpcmUoJy4vRkRMYXlvdXRFZGdlJyk7XG5cbmZ1bmN0aW9uIENvU0VFZGdlKHNvdXJjZSwgdGFyZ2V0LCB2RWRnZSkge1xuICBGRExheW91dEVkZ2UuY2FsbCh0aGlzLCBzb3VyY2UsIHRhcmdldCwgdkVkZ2UpO1xufVxuXG5Db1NFRWRnZS5wcm90b3R5cGUgPSBPYmplY3QuY3JlYXRlKEZETGF5b3V0RWRnZS5wcm90b3R5cGUpO1xuZm9yICh2YXIgcHJvcCBpbiBGRExheW91dEVkZ2UpIHtcbiAgQ29TRUVkZ2VbcHJvcF0gPSBGRExheW91dEVkZ2VbcHJvcF07XG59XG5cbm1vZHVsZS5leHBvcnRzID0gQ29TRUVkZ2VcbiIsInZhciBMR3JhcGggPSByZXF1aXJlKCcuL0xHcmFwaCcpO1xuXG5mdW5jdGlvbiBDb1NFR3JhcGgocGFyZW50LCBncmFwaE1nciwgdkdyYXBoKSB7XG4gIExHcmFwaC5jYWxsKHRoaXMsIHBhcmVudCwgZ3JhcGhNZ3IsIHZHcmFwaCk7XG59XG5cbkNvU0VHcmFwaC5wcm90b3R5cGUgPSBPYmplY3QuY3JlYXRlKExHcmFwaC5wcm90b3R5cGUpO1xuZm9yICh2YXIgcHJvcCBpbiBMR3JhcGgpIHtcbiAgQ29TRUdyYXBoW3Byb3BdID0gTEdyYXBoW3Byb3BdO1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IENvU0VHcmFwaDtcbiIsInZhciBMR3JhcGhNYW5hZ2VyID0gcmVxdWlyZSgnLi9MR3JhcGhNYW5hZ2VyJyk7XG5cbmZ1bmN0aW9uIENvU0VHcmFwaE1hbmFnZXIobGF5b3V0KSB7XG4gIExHcmFwaE1hbmFnZXIuY2FsbCh0aGlzLCBsYXlvdXQpO1xufVxuXG5Db1NFR3JhcGhNYW5hZ2VyLnByb3RvdHlwZSA9IE9iamVjdC5jcmVhdGUoTEdyYXBoTWFuYWdlci5wcm90b3R5cGUpO1xuZm9yICh2YXIgcHJvcCBpbiBMR3JhcGhNYW5hZ2VyKSB7XG4gIENvU0VHcmFwaE1hbmFnZXJbcHJvcF0gPSBMR3JhcGhNYW5hZ2VyW3Byb3BdO1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IENvU0VHcmFwaE1hbmFnZXI7XG4iLCJ2YXIgRkRMYXlvdXQgPSByZXF1aXJlKCcuL0ZETGF5b3V0Jyk7XG52YXIgQ29TRUdyYXBoTWFuYWdlciA9IHJlcXVpcmUoJy4vQ29TRUdyYXBoTWFuYWdlcicpO1xudmFyIENvU0VHcmFwaCA9IHJlcXVpcmUoJy4vQ29TRUdyYXBoJyk7XG52YXIgQ29TRU5vZGUgPSByZXF1aXJlKCcuL0NvU0VOb2RlJyk7XG52YXIgQ29TRUVkZ2UgPSByZXF1aXJlKCcuL0NvU0VFZGdlJyk7XG52YXIgQ29TRUNvbnN0YW50cyA9IHJlcXVpcmUoJy4vQ29TRUNvbnN0YW50cycpO1xudmFyIEZETGF5b3V0Q29uc3RhbnRzID0gcmVxdWlyZSgnLi9GRExheW91dENvbnN0YW50cycpO1xudmFyIExheW91dENvbnN0YW50cyA9IHJlcXVpcmUoJy4vTGF5b3V0Q29uc3RhbnRzJyk7XG52YXIgUG9pbnQgPSByZXF1aXJlKCcuL1BvaW50Jyk7XG52YXIgUG9pbnREID0gcmVxdWlyZSgnLi9Qb2ludEQnKTtcbnZhciBMYXlvdXQgPSByZXF1aXJlKCcuL0xheW91dCcpO1xudmFyIEludGVnZXIgPSByZXF1aXJlKCcuL0ludGVnZXInKTtcbnZhciBJR2VvbWV0cnkgPSByZXF1aXJlKCcuL0lHZW9tZXRyeScpO1xudmFyIExHcmFwaCA9IHJlcXVpcmUoJy4vTEdyYXBoJyk7XG52YXIgVHJhbnNmb3JtID0gcmVxdWlyZSgnLi9UcmFuc2Zvcm0nKTtcblxuZnVuY3Rpb24gQ29TRUxheW91dCgpIHtcbiAgRkRMYXlvdXQuY2FsbCh0aGlzKTtcbn1cblxuQ29TRUxheW91dC5wcm90b3R5cGUgPSBPYmplY3QuY3JlYXRlKEZETGF5b3V0LnByb3RvdHlwZSk7XG5cbmZvciAodmFyIHByb3AgaW4gRkRMYXlvdXQpIHtcbiAgQ29TRUxheW91dFtwcm9wXSA9IEZETGF5b3V0W3Byb3BdO1xufVxuXG5Db1NFTGF5b3V0LnByb3RvdHlwZS5uZXdHcmFwaE1hbmFnZXIgPSBmdW5jdGlvbiAoKSB7XG4gIHZhciBnbSA9IG5ldyBDb1NFR3JhcGhNYW5hZ2VyKHRoaXMpO1xuICB0aGlzLmdyYXBoTWFuYWdlciA9IGdtO1xuICByZXR1cm4gZ207XG59O1xuXG5Db1NFTGF5b3V0LnByb3RvdHlwZS5uZXdHcmFwaCA9IGZ1bmN0aW9uICh2R3JhcGgpIHtcbiAgcmV0dXJuIG5ldyBDb1NFR3JhcGgobnVsbCwgdGhpcy5ncmFwaE1hbmFnZXIsIHZHcmFwaCk7XG59O1xuXG5Db1NFTGF5b3V0LnByb3RvdHlwZS5uZXdOb2RlID0gZnVuY3Rpb24gKHZOb2RlKSB7XG4gIHJldHVybiBuZXcgQ29TRU5vZGUodGhpcy5ncmFwaE1hbmFnZXIsIHZOb2RlKTtcbn07XG5cbkNvU0VMYXlvdXQucHJvdG90eXBlLm5ld0VkZ2UgPSBmdW5jdGlvbiAodkVkZ2UpIHtcbiAgcmV0dXJuIG5ldyBDb1NFRWRnZShudWxsLCBudWxsLCB2RWRnZSk7XG59O1xuXG5Db1NFTGF5b3V0LnByb3RvdHlwZS5pbml0UGFyYW1ldGVycyA9IGZ1bmN0aW9uICgpIHtcbiAgRkRMYXlvdXQucHJvdG90eXBlLmluaXRQYXJhbWV0ZXJzLmNhbGwodGhpcywgYXJndW1lbnRzKTtcbiAgaWYgKCF0aGlzLmlzU3ViTGF5b3V0KSB7XG4gICAgaWYgKENvU0VDb25zdGFudHMuREVGQVVMVF9FREdFX0xFTkdUSCA8IDEwKVxuICAgIHtcbiAgICAgIHRoaXMuaWRlYWxFZGdlTGVuZ3RoID0gMTA7XG4gICAgfVxuICAgIGVsc2VcbiAgICB7XG4gICAgICB0aGlzLmlkZWFsRWRnZUxlbmd0aCA9IENvU0VDb25zdGFudHMuREVGQVVMVF9FREdFX0xFTkdUSDtcbiAgICB9XG5cbiAgICB0aGlzLnVzZVNtYXJ0SWRlYWxFZGdlTGVuZ3RoQ2FsY3VsYXRpb24gPVxuICAgICAgICAgICAgQ29TRUNvbnN0YW50cy5ERUZBVUxUX1VTRV9TTUFSVF9JREVBTF9FREdFX0xFTkdUSF9DQUxDVUxBVElPTjtcbiAgICB0aGlzLnNwcmluZ0NvbnN0YW50ID1cbiAgICAgICAgICAgIEZETGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfU1BSSU5HX1NUUkVOR1RIO1xuICAgIHRoaXMucmVwdWxzaW9uQ29uc3RhbnQgPVxuICAgICAgICAgICAgRkRMYXlvdXRDb25zdGFudHMuREVGQVVMVF9SRVBVTFNJT05fU1RSRU5HVEg7XG4gICAgdGhpcy5ncmF2aXR5Q29uc3RhbnQgPVxuICAgICAgICAgICAgRkRMYXlvdXRDb25zdGFudHMuREVGQVVMVF9HUkFWSVRZX1NUUkVOR1RIO1xuICAgIHRoaXMuY29tcG91bmRHcmF2aXR5Q29uc3RhbnQgPVxuICAgICAgICAgICAgRkRMYXlvdXRDb25zdGFudHMuREVGQVVMVF9DT01QT1VORF9HUkFWSVRZX1NUUkVOR1RIO1xuICAgIHRoaXMuZ3Jhdml0eVJhbmdlRmFjdG9yID1cbiAgICAgICAgICAgIEZETGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfR1JBVklUWV9SQU5HRV9GQUNUT1I7XG4gICAgdGhpcy5jb21wb3VuZEdyYXZpdHlSYW5nZUZhY3RvciA9XG4gICAgICAgICAgICBGRExheW91dENvbnN0YW50cy5ERUZBVUxUX0NPTVBPVU5EX0dSQVZJVFlfUkFOR0VfRkFDVE9SO1xuICB9XG59O1xuXG5Db1NFTGF5b3V0LnByb3RvdHlwZS5sYXlvdXQgPSBmdW5jdGlvbiAoKSB7XG4gIHZhciBjcmVhdGVCZW5kc0FzTmVlZGVkID0gTGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfQ1JFQVRFX0JFTkRTX0FTX05FRURFRDtcbiAgaWYgKGNyZWF0ZUJlbmRzQXNOZWVkZWQpXG4gIHtcbiAgICB0aGlzLmNyZWF0ZUJlbmRwb2ludHMoKTtcbiAgICB0aGlzLmdyYXBoTWFuYWdlci5yZXNldEFsbEVkZ2VzKCk7XG4gIH1cblxuICB0aGlzLmxldmVsID0gMDtcbiAgcmV0dXJuIHRoaXMuY2xhc3NpY0xheW91dCgpO1xufTtcblxuQ29TRUxheW91dC5wcm90b3R5cGUuY2xhc3NpY0xheW91dCA9IGZ1bmN0aW9uICgpIHtcbiAgdGhpcy5jYWxjdWxhdGVOb2Rlc1RvQXBwbHlHcmF2aXRhdGlvblRvKCk7XG4gIHRoaXMuZ3JhcGhNYW5hZ2VyLmNhbGNMb3dlc3RDb21tb25BbmNlc3RvcnMoKTtcbiAgdGhpcy5ncmFwaE1hbmFnZXIuY2FsY0luY2x1c2lvblRyZWVEZXB0aHMoKTtcbiAgdGhpcy5ncmFwaE1hbmFnZXIuZ2V0Um9vdCgpLmNhbGNFc3RpbWF0ZWRTaXplKCk7XG4gIHRoaXMuY2FsY0lkZWFsRWRnZUxlbmd0aHMoKTtcbiAgaWYgKCF0aGlzLmluY3JlbWVudGFsKVxuICB7XG4gICAgdmFyIGZvcmVzdCA9IHRoaXMuZ2V0RmxhdEZvcmVzdCgpO1xuXG4gICAgLy8gVGhlIGdyYXBoIGFzc29jaWF0ZWQgd2l0aCB0aGlzIGxheW91dCBpcyBmbGF0IGFuZCBhIGZvcmVzdFxuICAgIGlmIChmb3Jlc3QubGVuZ3RoID4gMClcblxuICAgIHtcbiAgICAgIHRoaXMucG9zaXRpb25Ob2Rlc1JhZGlhbGx5KGZvcmVzdCk7XG4gICAgfVxuICAgIC8vIFRoZSBncmFwaCBhc3NvY2lhdGVkIHdpdGggdGhpcyBsYXlvdXQgaXMgbm90IGZsYXQgb3IgYSBmb3Jlc3RcbiAgICBlbHNlXG4gICAge1xuICAgICAgdGhpcy5wb3NpdGlvbk5vZGVzUmFuZG9tbHkoKTtcbiAgICB9XG4gIH1cblxuICB0aGlzLmluaXRTcHJpbmdFbWJlZGRlcigpO1xuICB0aGlzLnJ1blNwcmluZ0VtYmVkZGVyKCk7XG5cbiAgcmV0dXJuIHRydWU7XG59O1xuXG5Db1NFTGF5b3V0LnByb3RvdHlwZS50aWNrID0gZnVuY3Rpb24oKSB7XG4gIHRoaXMudG90YWxJdGVyYXRpb25zKys7XG4gIFxuICBpZiAodGhpcy50b3RhbEl0ZXJhdGlvbnMgPT09IHRoaXMubWF4SXRlcmF0aW9ucykge1xuICAgIHJldHVybiB0cnVlOyAvLyBMYXlvdXQgaXMgbm90IGVuZGVkIHJldHVybiB0cnVlXG4gIH1cbiAgXG4gIGlmICh0aGlzLnRvdGFsSXRlcmF0aW9ucyAlIEZETGF5b3V0Q29uc3RhbnRzLkNPTlZFUkdFTkNFX0NIRUNLX1BFUklPRCA9PSAwKVxuICB7XG4gICAgaWYgKHRoaXMuaXNDb252ZXJnZWQoKSlcbiAgICB7XG4gICAgICByZXR1cm4gdHJ1ZTsgLy8gTGF5b3V0IGlzIG5vdCBlbmRlZCByZXR1cm4gdHJ1ZVxuICAgIH1cblxuICAgIHRoaXMuY29vbGluZ0ZhY3RvciA9IHRoaXMuaW5pdGlhbENvb2xpbmdGYWN0b3IgKlxuICAgICAgICAgICAgKCh0aGlzLm1heEl0ZXJhdGlvbnMgLSB0aGlzLnRvdGFsSXRlcmF0aW9ucykgLyB0aGlzLm1heEl0ZXJhdGlvbnMpO1xuICAgIHRoaXMuYW5pbWF0aW9uUGVyaW9kID0gTWF0aC5jZWlsKHRoaXMuaW5pdGlhbEFuaW1hdGlvblBlcmlvZCAqIE1hdGguc3FydCh0aGlzLmNvb2xpbmdGYWN0b3IpKTtcblxuICB9XG4gIHRoaXMudG90YWxEaXNwbGFjZW1lbnQgPSAwO1xuICB0aGlzLmdyYXBoTWFuYWdlci51cGRhdGVCb3VuZHMoKTtcbiAgdGhpcy5jYWxjU3ByaW5nRm9yY2VzKCk7XG4gIHRoaXMuY2FsY1JlcHVsc2lvbkZvcmNlcygpO1xuICB0aGlzLmNhbGNHcmF2aXRhdGlvbmFsRm9yY2VzKCk7XG4gIHRoaXMubW92ZU5vZGVzKCk7XG4gIHRoaXMuYW5pbWF0ZSgpO1xuICBcbiAgcmV0dXJuIGZhbHNlOyAvLyBMYXlvdXQgaXMgbm90IGVuZGVkIHlldCByZXR1cm4gZmFsc2Vcbn07XG5cbkNvU0VMYXlvdXQucHJvdG90eXBlLmdldFBvc2l0aW9uc0RhdGEgPSBmdW5jdGlvbigpIHtcbiAgdmFyIGFsbE5vZGVzID0gdGhpcy5ncmFwaE1hbmFnZXIuZ2V0QWxsTm9kZXMoKTtcbiAgdmFyIHBEYXRhID0ge307XG4gIGZvciAodmFyIGkgPSAwOyBpIDwgYWxsTm9kZXMubGVuZ3RoOyBpKyspIHtcbiAgICB2YXIgcmVjdCA9IGFsbE5vZGVzW2ldLnJlY3Q7XG4gICAgdmFyIGlkID0gYWxsTm9kZXNbaV0uaWQ7XG4gICAgcERhdGFbaWRdID0ge1xuICAgICAgaWQ6IGlkLFxuICAgICAgeDogcmVjdC5nZXRDZW50ZXJYKCksXG4gICAgICB5OiByZWN0LmdldENlbnRlclkoKSxcbiAgICAgIHc6IHJlY3Qud2lkdGgsXG4gICAgICBoOiByZWN0LmhlaWdodFxuICAgIH07XG4gIH1cbiAgXG4gIHJldHVybiBwRGF0YTtcbn07XG5cbkNvU0VMYXlvdXQucHJvdG90eXBlLnJ1blNwcmluZ0VtYmVkZGVyID0gZnVuY3Rpb24gKCkge1xuICB0aGlzLmluaXRpYWxBbmltYXRpb25QZXJpb2QgPSAyNTtcbiAgdGhpcy5hbmltYXRpb25QZXJpb2QgPSB0aGlzLmluaXRpYWxBbmltYXRpb25QZXJpb2Q7XG4gIHZhciBsYXlvdXRFbmRlZCA9IGZhbHNlO1xuICBcbiAgLy8gSWYgYW1pbmF0ZSBvcHRpb24gaXMgJ2R1cmluZycgc2lnbmFsIHRoYXQgbGF5b3V0IGlzIHN1cHBvc2VkIHRvIHN0YXJ0IGl0ZXJhdGluZ1xuICBpZiAoIEZETGF5b3V0Q29uc3RhbnRzLkFOSU1BVEUgPT09ICdkdXJpbmcnICkge1xuICAgIHRoaXMuZW1pdCgnbGF5b3V0c3RhcnRlZCcpO1xuICB9XG4gIGVsc2Uge1xuICAgIC8vIElmIGFtaW5hdGUgb3B0aW9uIGlzICdkdXJpbmcnIHRpY2soKSBmdW5jdGlvbiB3aWxsIGJlIGNhbGxlZCBvbiBpbmRleC5qc1xuICAgIHdoaWxlICghbGF5b3V0RW5kZWQpIHtcbiAgICAgIGxheW91dEVuZGVkID0gdGhpcy50aWNrKCk7XG4gICAgfVxuXG4gICAgdGhpcy5ncmFwaE1hbmFnZXIudXBkYXRlQm91bmRzKCk7XG4gIH1cbn07XG5cbkNvU0VMYXlvdXQucHJvdG90eXBlLmNhbGN1bGF0ZU5vZGVzVG9BcHBseUdyYXZpdGF0aW9uVG8gPSBmdW5jdGlvbiAoKSB7XG4gIHZhciBub2RlTGlzdCA9IFtdO1xuICB2YXIgZ3JhcGg7XG5cbiAgdmFyIGdyYXBocyA9IHRoaXMuZ3JhcGhNYW5hZ2VyLmdldEdyYXBocygpO1xuICB2YXIgc2l6ZSA9IGdyYXBocy5sZW5ndGg7XG4gIHZhciBpO1xuICBmb3IgKGkgPSAwOyBpIDwgc2l6ZTsgaSsrKVxuICB7XG4gICAgZ3JhcGggPSBncmFwaHNbaV07XG5cbiAgICBncmFwaC51cGRhdGVDb25uZWN0ZWQoKTtcblxuICAgIGlmICghZ3JhcGguaXNDb25uZWN0ZWQpXG4gICAge1xuICAgICAgbm9kZUxpc3QgPSBub2RlTGlzdC5jb25jYXQoZ3JhcGguZ2V0Tm9kZXMoKSk7XG4gICAgfVxuICB9XG5cbiAgdGhpcy5ncmFwaE1hbmFnZXIuc2V0QWxsTm9kZXNUb0FwcGx5R3Jhdml0YXRpb24obm9kZUxpc3QpO1xufTtcblxuQ29TRUxheW91dC5wcm90b3R5cGUuY3JlYXRlQmVuZHBvaW50cyA9IGZ1bmN0aW9uICgpIHtcbiAgdmFyIGVkZ2VzID0gW107XG4gIGVkZ2VzID0gZWRnZXMuY29uY2F0KHRoaXMuZ3JhcGhNYW5hZ2VyLmdldEFsbEVkZ2VzKCkpO1xuICB2YXIgdmlzaXRlZCA9IG5ldyBIYXNoU2V0KCk7XG4gIHZhciBpO1xuICBmb3IgKGkgPSAwOyBpIDwgZWRnZXMubGVuZ3RoOyBpKyspXG4gIHtcbiAgICB2YXIgZWRnZSA9IGVkZ2VzW2ldO1xuXG4gICAgaWYgKCF2aXNpdGVkLmNvbnRhaW5zKGVkZ2UpKVxuICAgIHtcbiAgICAgIHZhciBzb3VyY2UgPSBlZGdlLmdldFNvdXJjZSgpO1xuICAgICAgdmFyIHRhcmdldCA9IGVkZ2UuZ2V0VGFyZ2V0KCk7XG5cbiAgICAgIGlmIChzb3VyY2UgPT0gdGFyZ2V0KVxuICAgICAge1xuICAgICAgICBlZGdlLmdldEJlbmRwb2ludHMoKS5wdXNoKG5ldyBQb2ludEQoKSk7XG4gICAgICAgIGVkZ2UuZ2V0QmVuZHBvaW50cygpLnB1c2gobmV3IFBvaW50RCgpKTtcbiAgICAgICAgdGhpcy5jcmVhdGVEdW1teU5vZGVzRm9yQmVuZHBvaW50cyhlZGdlKTtcbiAgICAgICAgdmlzaXRlZC5hZGQoZWRnZSk7XG4gICAgICB9XG4gICAgICBlbHNlXG4gICAgICB7XG4gICAgICAgIHZhciBlZGdlTGlzdCA9IFtdO1xuXG4gICAgICAgIGVkZ2VMaXN0ID0gZWRnZUxpc3QuY29uY2F0KHNvdXJjZS5nZXRFZGdlTGlzdFRvTm9kZSh0YXJnZXQpKTtcbiAgICAgICAgZWRnZUxpc3QgPSBlZGdlTGlzdC5jb25jYXQodGFyZ2V0LmdldEVkZ2VMaXN0VG9Ob2RlKHNvdXJjZSkpO1xuXG4gICAgICAgIGlmICghdmlzaXRlZC5jb250YWlucyhlZGdlTGlzdFswXSkpXG4gICAgICAgIHtcbiAgICAgICAgICBpZiAoZWRnZUxpc3QubGVuZ3RoID4gMSlcbiAgICAgICAgICB7XG4gICAgICAgICAgICB2YXIgaztcbiAgICAgICAgICAgIGZvciAoayA9IDA7IGsgPCBlZGdlTGlzdC5sZW5ndGg7IGsrKylcbiAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgdmFyIG11bHRpRWRnZSA9IGVkZ2VMaXN0W2tdO1xuICAgICAgICAgICAgICBtdWx0aUVkZ2UuZ2V0QmVuZHBvaW50cygpLnB1c2gobmV3IFBvaW50RCgpKTtcbiAgICAgICAgICAgICAgdGhpcy5jcmVhdGVEdW1teU5vZGVzRm9yQmVuZHBvaW50cyhtdWx0aUVkZ2UpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgICB2aXNpdGVkLmFkZEFsbChsaXN0KTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cblxuICAgIGlmICh2aXNpdGVkLnNpemUoKSA9PSBlZGdlcy5sZW5ndGgpXG4gICAge1xuICAgICAgYnJlYWs7XG4gICAgfVxuICB9XG59O1xuXG5Db1NFTGF5b3V0LnByb3RvdHlwZS5wb3NpdGlvbk5vZGVzUmFkaWFsbHkgPSBmdW5jdGlvbiAoZm9yZXN0KSB7XG4gIC8vIFdlIHRpbGUgdGhlIHRyZWVzIHRvIGEgZ3JpZCByb3cgYnkgcm93OyBmaXJzdCB0cmVlIHN0YXJ0cyBhdCAoMCwwKVxuICB2YXIgY3VycmVudFN0YXJ0aW5nUG9pbnQgPSBuZXcgUG9pbnQoMCwgMCk7XG4gIHZhciBudW1iZXJPZkNvbHVtbnMgPSBNYXRoLmNlaWwoTWF0aC5zcXJ0KGZvcmVzdC5sZW5ndGgpKTtcbiAgdmFyIGhlaWdodCA9IDA7XG4gIHZhciBjdXJyZW50WSA9IDA7XG4gIHZhciBjdXJyZW50WCA9IDA7XG4gIHZhciBwb2ludCA9IG5ldyBQb2ludEQoMCwgMCk7XG5cbiAgZm9yICh2YXIgaSA9IDA7IGkgPCBmb3Jlc3QubGVuZ3RoOyBpKyspXG4gIHtcbiAgICBpZiAoaSAlIG51bWJlck9mQ29sdW1ucyA9PSAwKVxuICAgIHtcbiAgICAgIC8vIFN0YXJ0IG9mIGEgbmV3IHJvdywgbWFrZSB0aGUgeCBjb29yZGluYXRlIDAsIGluY3JlbWVudCB0aGVcbiAgICAgIC8vIHkgY29vcmRpbmF0ZSB3aXRoIHRoZSBtYXggaGVpZ2h0IG9mIHRoZSBwcmV2aW91cyByb3dcbiAgICAgIGN1cnJlbnRYID0gMDtcbiAgICAgIGN1cnJlbnRZID0gaGVpZ2h0O1xuXG4gICAgICBpZiAoaSAhPSAwKVxuICAgICAge1xuICAgICAgICBjdXJyZW50WSArPSBDb1NFQ29uc3RhbnRzLkRFRkFVTFRfQ09NUE9ORU5UX1NFUEVSQVRJT047XG4gICAgICB9XG5cbiAgICAgIGhlaWdodCA9IDA7XG4gICAgfVxuXG4gICAgdmFyIHRyZWUgPSBmb3Jlc3RbaV07XG5cbiAgICAvLyBGaW5kIHRoZSBjZW50ZXIgb2YgdGhlIHRyZWVcbiAgICB2YXIgY2VudGVyTm9kZSA9IExheW91dC5maW5kQ2VudGVyT2ZUcmVlKHRyZWUpO1xuXG4gICAgLy8gU2V0IHRoZSBzdGFyaW5nIHBvaW50IG9mIHRoZSBuZXh0IHRyZWVcbiAgICBjdXJyZW50U3RhcnRpbmdQb2ludC54ID0gY3VycmVudFg7XG4gICAgY3VycmVudFN0YXJ0aW5nUG9pbnQueSA9IGN1cnJlbnRZO1xuXG4gICAgLy8gRG8gYSByYWRpYWwgbGF5b3V0IHN0YXJ0aW5nIHdpdGggdGhlIGNlbnRlclxuICAgIHBvaW50ID1cbiAgICAgICAgICAgIENvU0VMYXlvdXQucmFkaWFsTGF5b3V0KHRyZWUsIGNlbnRlck5vZGUsIGN1cnJlbnRTdGFydGluZ1BvaW50KTtcblxuICAgIGlmIChwb2ludC55ID4gaGVpZ2h0KVxuICAgIHtcbiAgICAgIGhlaWdodCA9IE1hdGguZmxvb3IocG9pbnQueSk7XG4gICAgfVxuXG4gICAgY3VycmVudFggPSBNYXRoLmZsb29yKHBvaW50LnggKyBDb1NFQ29uc3RhbnRzLkRFRkFVTFRfQ09NUE9ORU5UX1NFUEVSQVRJT04pO1xuICB9XG5cbiAgdGhpcy50cmFuc2Zvcm0oXG4gICAgICAgICAgbmV3IFBvaW50RChMYXlvdXRDb25zdGFudHMuV09STERfQ0VOVEVSX1ggLSBwb2ludC54IC8gMixcbiAgICAgICAgICAgICAgICAgIExheW91dENvbnN0YW50cy5XT1JMRF9DRU5URVJfWSAtIHBvaW50LnkgLyAyKSk7XG59O1xuXG5Db1NFTGF5b3V0LnJhZGlhbExheW91dCA9IGZ1bmN0aW9uICh0cmVlLCBjZW50ZXJOb2RlLCBzdGFydGluZ1BvaW50KSB7XG4gIHZhciByYWRpYWxTZXAgPSBNYXRoLm1heCh0aGlzLm1heERpYWdvbmFsSW5UcmVlKHRyZWUpLFxuICAgICAgICAgIENvU0VDb25zdGFudHMuREVGQVVMVF9SQURJQUxfU0VQQVJBVElPTik7XG4gIENvU0VMYXlvdXQuYnJhbmNoUmFkaWFsTGF5b3V0KGNlbnRlck5vZGUsIG51bGwsIDAsIDM1OSwgMCwgcmFkaWFsU2VwKTtcbiAgdmFyIGJvdW5kcyA9IExHcmFwaC5jYWxjdWxhdGVCb3VuZHModHJlZSk7XG5cbiAgdmFyIHRyYW5zZm9ybSA9IG5ldyBUcmFuc2Zvcm0oKTtcbiAgdHJhbnNmb3JtLnNldERldmljZU9yZ1goYm91bmRzLmdldE1pblgoKSk7XG4gIHRyYW5zZm9ybS5zZXREZXZpY2VPcmdZKGJvdW5kcy5nZXRNaW5ZKCkpO1xuICB0cmFuc2Zvcm0uc2V0V29ybGRPcmdYKHN0YXJ0aW5nUG9pbnQueCk7XG4gIHRyYW5zZm9ybS5zZXRXb3JsZE9yZ1koc3RhcnRpbmdQb2ludC55KTtcblxuICBmb3IgKHZhciBpID0gMDsgaSA8IHRyZWUubGVuZ3RoOyBpKyspXG4gIHtcbiAgICB2YXIgbm9kZSA9IHRyZWVbaV07XG4gICAgbm9kZS50cmFuc2Zvcm0odHJhbnNmb3JtKTtcbiAgfVxuXG4gIHZhciBib3R0b21SaWdodCA9XG4gICAgICAgICAgbmV3IFBvaW50RChib3VuZHMuZ2V0TWF4WCgpLCBib3VuZHMuZ2V0TWF4WSgpKTtcblxuICByZXR1cm4gdHJhbnNmb3JtLmludmVyc2VUcmFuc2Zvcm1Qb2ludChib3R0b21SaWdodCk7XG59O1xuXG5Db1NFTGF5b3V0LmJyYW5jaFJhZGlhbExheW91dCA9IGZ1bmN0aW9uIChub2RlLCBwYXJlbnRPZk5vZGUsIHN0YXJ0QW5nbGUsIGVuZEFuZ2xlLCBkaXN0YW5jZSwgcmFkaWFsU2VwYXJhdGlvbikge1xuICAvLyBGaXJzdCwgcG9zaXRpb24gdGhpcyBub2RlIGJ5IGZpbmRpbmcgaXRzIGFuZ2xlLlxuICB2YXIgaGFsZkludGVydmFsID0gKChlbmRBbmdsZSAtIHN0YXJ0QW5nbGUpICsgMSkgLyAyO1xuXG4gIGlmIChoYWxmSW50ZXJ2YWwgPCAwKVxuICB7XG4gICAgaGFsZkludGVydmFsICs9IDE4MDtcbiAgfVxuXG4gIHZhciBub2RlQW5nbGUgPSAoaGFsZkludGVydmFsICsgc3RhcnRBbmdsZSkgJSAzNjA7XG4gIHZhciB0ZXRhID0gKG5vZGVBbmdsZSAqIElHZW9tZXRyeS5UV09fUEkpIC8gMzYwO1xuXG4gIC8vIE1ha2UgcG9sYXIgdG8gamF2YSBjb3JkaW5hdGUgY29udmVyc2lvbi5cbiAgdmFyIGNvc190ZXRhID0gTWF0aC5jb3ModGV0YSk7XG4gIHZhciB4XyA9IGRpc3RhbmNlICogTWF0aC5jb3ModGV0YSk7XG4gIHZhciB5XyA9IGRpc3RhbmNlICogTWF0aC5zaW4odGV0YSk7XG5cbiAgbm9kZS5zZXRDZW50ZXIoeF8sIHlfKTtcblxuICAvLyBUcmF2ZXJzZSBhbGwgbmVpZ2hib3JzIG9mIHRoaXMgbm9kZSBhbmQgcmVjdXJzaXZlbHkgY2FsbCB0aGlzXG4gIC8vIGZ1bmN0aW9uLlxuICB2YXIgbmVpZ2hib3JFZGdlcyA9IFtdO1xuICBuZWlnaGJvckVkZ2VzID0gbmVpZ2hib3JFZGdlcy5jb25jYXQobm9kZS5nZXRFZGdlcygpKTtcbiAgdmFyIGNoaWxkQ291bnQgPSBuZWlnaGJvckVkZ2VzLmxlbmd0aDtcblxuICBpZiAocGFyZW50T2ZOb2RlICE9IG51bGwpXG4gIHtcbiAgICBjaGlsZENvdW50LS07XG4gIH1cblxuICB2YXIgYnJhbmNoQ291bnQgPSAwO1xuXG4gIHZhciBpbmNFZGdlc0NvdW50ID0gbmVpZ2hib3JFZGdlcy5sZW5ndGg7XG4gIHZhciBzdGFydEluZGV4O1xuXG4gIHZhciBlZGdlcyA9IG5vZGUuZ2V0RWRnZXNCZXR3ZWVuKHBhcmVudE9mTm9kZSk7XG5cbiAgLy8gSWYgdGhlcmUgYXJlIG11bHRpcGxlIGVkZ2VzLCBwcnVuZSB0aGVtIHVudGlsIHRoZXJlIHJlbWFpbnMgb25seSBvbmVcbiAgLy8gZWRnZS5cbiAgd2hpbGUgKGVkZ2VzLmxlbmd0aCA+IDEpXG4gIHtcbiAgICAvL25laWdoYm9yRWRnZXMucmVtb3ZlKGVkZ2VzLnJlbW92ZSgwKSk7XG4gICAgdmFyIHRlbXAgPSBlZGdlc1swXTtcbiAgICBlZGdlcy5zcGxpY2UoMCwgMSk7XG4gICAgdmFyIGluZGV4ID0gbmVpZ2hib3JFZGdlcy5pbmRleE9mKHRlbXApO1xuICAgIGlmIChpbmRleCA+PSAwKSB7XG4gICAgICBuZWlnaGJvckVkZ2VzLnNwbGljZShpbmRleCwgMSk7XG4gICAgfVxuICAgIGluY0VkZ2VzQ291bnQtLTtcbiAgICBjaGlsZENvdW50LS07XG4gIH1cblxuICBpZiAocGFyZW50T2ZOb2RlICE9IG51bGwpXG4gIHtcbiAgICAvL2Fzc2VydCBlZGdlcy5sZW5ndGggPT0gMTtcbiAgICBzdGFydEluZGV4ID0gKG5laWdoYm9yRWRnZXMuaW5kZXhPZihlZGdlc1swXSkgKyAxKSAlIGluY0VkZ2VzQ291bnQ7XG4gIH1cbiAgZWxzZVxuICB7XG4gICAgc3RhcnRJbmRleCA9IDA7XG4gIH1cblxuICB2YXIgc3RlcEFuZ2xlID0gTWF0aC5hYnMoZW5kQW5nbGUgLSBzdGFydEFuZ2xlKSAvIGNoaWxkQ291bnQ7XG5cbiAgZm9yICh2YXIgaSA9IHN0YXJ0SW5kZXg7XG4gICAgICAgICAgYnJhbmNoQ291bnQgIT0gY2hpbGRDb3VudDtcbiAgICAgICAgICBpID0gKCsraSkgJSBpbmNFZGdlc0NvdW50KVxuICB7XG4gICAgdmFyIGN1cnJlbnROZWlnaGJvciA9XG4gICAgICAgICAgICBuZWlnaGJvckVkZ2VzW2ldLmdldE90aGVyRW5kKG5vZGUpO1xuXG4gICAgLy8gRG9uJ3QgYmFjayB0cmF2ZXJzZSB0byByb290IG5vZGUgaW4gY3VycmVudCB0cmVlLlxuICAgIGlmIChjdXJyZW50TmVpZ2hib3IgPT0gcGFyZW50T2ZOb2RlKVxuICAgIHtcbiAgICAgIGNvbnRpbnVlO1xuICAgIH1cblxuICAgIHZhciBjaGlsZFN0YXJ0QW5nbGUgPVxuICAgICAgICAgICAgKHN0YXJ0QW5nbGUgKyBicmFuY2hDb3VudCAqIHN0ZXBBbmdsZSkgJSAzNjA7XG4gICAgdmFyIGNoaWxkRW5kQW5nbGUgPSAoY2hpbGRTdGFydEFuZ2xlICsgc3RlcEFuZ2xlKSAlIDM2MDtcblxuICAgIENvU0VMYXlvdXQuYnJhbmNoUmFkaWFsTGF5b3V0KGN1cnJlbnROZWlnaGJvcixcbiAgICAgICAgICAgIG5vZGUsXG4gICAgICAgICAgICBjaGlsZFN0YXJ0QW5nbGUsIGNoaWxkRW5kQW5nbGUsXG4gICAgICAgICAgICBkaXN0YW5jZSArIHJhZGlhbFNlcGFyYXRpb24sIHJhZGlhbFNlcGFyYXRpb24pO1xuXG4gICAgYnJhbmNoQ291bnQrKztcbiAgfVxufTtcblxuQ29TRUxheW91dC5tYXhEaWFnb25hbEluVHJlZSA9IGZ1bmN0aW9uICh0cmVlKSB7XG4gIHZhciBtYXhEaWFnb25hbCA9IEludGVnZXIuTUlOX1ZBTFVFO1xuXG4gIGZvciAodmFyIGkgPSAwOyBpIDwgdHJlZS5sZW5ndGg7IGkrKylcbiAge1xuICAgIHZhciBub2RlID0gdHJlZVtpXTtcbiAgICB2YXIgZGlhZ29uYWwgPSBub2RlLmdldERpYWdvbmFsKCk7XG5cbiAgICBpZiAoZGlhZ29uYWwgPiBtYXhEaWFnb25hbClcbiAgICB7XG4gICAgICBtYXhEaWFnb25hbCA9IGRpYWdvbmFsO1xuICAgIH1cbiAgfVxuXG4gIHJldHVybiBtYXhEaWFnb25hbDtcbn07XG5cbkNvU0VMYXlvdXQucHJvdG90eXBlLmNhbGNSZXB1bHNpb25SYW5nZSA9IGZ1bmN0aW9uICgpIHtcbiAgLy8gZm9ybXVsYSBpcyAyIHggKGxldmVsICsgMSkgeCBpZGVhbEVkZ2VMZW5ndGhcbiAgcmV0dXJuICgyICogKHRoaXMubGV2ZWwgKyAxKSAqIHRoaXMuaWRlYWxFZGdlTGVuZ3RoKTtcbn07XG5cbm1vZHVsZS5leHBvcnRzID0gQ29TRUxheW91dDtcbiIsInZhciBGRExheW91dE5vZGUgPSByZXF1aXJlKCcuL0ZETGF5b3V0Tm9kZScpO1xudmFyIElNYXRoID0gcmVxdWlyZSgnLi9JTWF0aCcpO1xuXG5mdW5jdGlvbiBDb1NFTm9kZShnbSwgbG9jLCBzaXplLCB2Tm9kZSkge1xuICBGRExheW91dE5vZGUuY2FsbCh0aGlzLCBnbSwgbG9jLCBzaXplLCB2Tm9kZSk7XG59XG5cblxuQ29TRU5vZGUucHJvdG90eXBlID0gT2JqZWN0LmNyZWF0ZShGRExheW91dE5vZGUucHJvdG90eXBlKTtcbmZvciAodmFyIHByb3AgaW4gRkRMYXlvdXROb2RlKSB7XG4gIENvU0VOb2RlW3Byb3BdID0gRkRMYXlvdXROb2RlW3Byb3BdO1xufVxuXG5Db1NFTm9kZS5wcm90b3R5cGUubW92ZSA9IGZ1bmN0aW9uICgpXG57XG4gIHZhciBsYXlvdXQgPSB0aGlzLmdyYXBoTWFuYWdlci5nZXRMYXlvdXQoKTtcbiAgdGhpcy5kaXNwbGFjZW1lbnRYID0gbGF5b3V0LmNvb2xpbmdGYWN0b3IgKlxuICAgICAgICAgICh0aGlzLnNwcmluZ0ZvcmNlWCArIHRoaXMucmVwdWxzaW9uRm9yY2VYICsgdGhpcy5ncmF2aXRhdGlvbkZvcmNlWCk7XG4gIHRoaXMuZGlzcGxhY2VtZW50WSA9IGxheW91dC5jb29saW5nRmFjdG9yICpcbiAgICAgICAgICAodGhpcy5zcHJpbmdGb3JjZVkgKyB0aGlzLnJlcHVsc2lvbkZvcmNlWSArIHRoaXMuZ3Jhdml0YXRpb25Gb3JjZVkpO1xuXG5cbiAgaWYgKE1hdGguYWJzKHRoaXMuZGlzcGxhY2VtZW50WCkgPiBsYXlvdXQuY29vbGluZ0ZhY3RvciAqIGxheW91dC5tYXhOb2RlRGlzcGxhY2VtZW50KVxuICB7XG4gICAgdGhpcy5kaXNwbGFjZW1lbnRYID0gbGF5b3V0LmNvb2xpbmdGYWN0b3IgKiBsYXlvdXQubWF4Tm9kZURpc3BsYWNlbWVudCAqXG4gICAgICAgICAgICBJTWF0aC5zaWduKHRoaXMuZGlzcGxhY2VtZW50WCk7XG4gIH1cblxuICBpZiAoTWF0aC5hYnModGhpcy5kaXNwbGFjZW1lbnRZKSA+IGxheW91dC5jb29saW5nRmFjdG9yICogbGF5b3V0Lm1heE5vZGVEaXNwbGFjZW1lbnQpXG4gIHtcbiAgICB0aGlzLmRpc3BsYWNlbWVudFkgPSBsYXlvdXQuY29vbGluZ0ZhY3RvciAqIGxheW91dC5tYXhOb2RlRGlzcGxhY2VtZW50ICpcbiAgICAgICAgICAgIElNYXRoLnNpZ24odGhpcy5kaXNwbGFjZW1lbnRZKTtcbiAgfVxuXG4gIC8vIGEgc2ltcGxlIG5vZGUsIGp1c3QgbW92ZSBpdFxuICBpZiAodGhpcy5jaGlsZCA9PSBudWxsKVxuICB7XG4gICAgdGhpcy5tb3ZlQnkodGhpcy5kaXNwbGFjZW1lbnRYLCB0aGlzLmRpc3BsYWNlbWVudFkpO1xuICB9XG4gIC8vIGFuIGVtcHR5IGNvbXBvdW5kIG5vZGUsIGFnYWluIGp1c3QgbW92ZSBpdFxuICBlbHNlIGlmICh0aGlzLmNoaWxkLmdldE5vZGVzKCkubGVuZ3RoID09IDApXG4gIHtcbiAgICB0aGlzLm1vdmVCeSh0aGlzLmRpc3BsYWNlbWVudFgsIHRoaXMuZGlzcGxhY2VtZW50WSk7XG4gIH1cbiAgLy8gbm9uLWVtcHR5IGNvbXBvdW5kIG5vZGUsIHByb3BvZ2F0ZSBtb3ZlbWVudCB0byBjaGlsZHJlbiBhcyB3ZWxsXG4gIGVsc2VcbiAge1xuICAgIHRoaXMucHJvcG9nYXRlRGlzcGxhY2VtZW50VG9DaGlsZHJlbih0aGlzLmRpc3BsYWNlbWVudFgsXG4gICAgICAgICAgICB0aGlzLmRpc3BsYWNlbWVudFkpO1xuICB9XG5cbiAgbGF5b3V0LnRvdGFsRGlzcGxhY2VtZW50ICs9XG4gICAgICAgICAgTWF0aC5hYnModGhpcy5kaXNwbGFjZW1lbnRYKSArIE1hdGguYWJzKHRoaXMuZGlzcGxhY2VtZW50WSk7XG5cbiAgdGhpcy5zcHJpbmdGb3JjZVggPSAwO1xuICB0aGlzLnNwcmluZ0ZvcmNlWSA9IDA7XG4gIHRoaXMucmVwdWxzaW9uRm9yY2VYID0gMDtcbiAgdGhpcy5yZXB1bHNpb25Gb3JjZVkgPSAwO1xuICB0aGlzLmdyYXZpdGF0aW9uRm9yY2VYID0gMDtcbiAgdGhpcy5ncmF2aXRhdGlvbkZvcmNlWSA9IDA7XG4gIHRoaXMuZGlzcGxhY2VtZW50WCA9IDA7XG4gIHRoaXMuZGlzcGxhY2VtZW50WSA9IDA7XG59O1xuXG5Db1NFTm9kZS5wcm90b3R5cGUucHJvcG9nYXRlRGlzcGxhY2VtZW50VG9DaGlsZHJlbiA9IGZ1bmN0aW9uIChkWCwgZFkpXG57XG4gIHZhciBub2RlcyA9IHRoaXMuZ2V0Q2hpbGQoKS5nZXROb2RlcygpO1xuICB2YXIgbm9kZTtcbiAgZm9yICh2YXIgaSA9IDA7IGkgPCBub2Rlcy5sZW5ndGg7IGkrKylcbiAge1xuICAgIG5vZGUgPSBub2Rlc1tpXTtcbiAgICBpZiAobm9kZS5nZXRDaGlsZCgpID09IG51bGwpXG4gICAge1xuICAgICAgbm9kZS5tb3ZlQnkoZFgsIGRZKTtcbiAgICAgIG5vZGUuZGlzcGxhY2VtZW50WCArPSBkWDtcbiAgICAgIG5vZGUuZGlzcGxhY2VtZW50WSArPSBkWTtcbiAgICB9XG4gICAgZWxzZVxuICAgIHtcbiAgICAgIG5vZGUucHJvcG9nYXRlRGlzcGxhY2VtZW50VG9DaGlsZHJlbihkWCwgZFkpO1xuICAgIH1cbiAgfVxufTtcblxuQ29TRU5vZGUucHJvdG90eXBlLnNldFByZWQxID0gZnVuY3Rpb24gKHByZWQxKVxue1xuICB0aGlzLnByZWQxID0gcHJlZDE7XG59O1xuXG5Db1NFTm9kZS5wcm90b3R5cGUuZ2V0UHJlZDEgPSBmdW5jdGlvbiAoKVxue1xuICByZXR1cm4gcHJlZDE7XG59O1xuXG5Db1NFTm9kZS5wcm90b3R5cGUuZ2V0UHJlZDIgPSBmdW5jdGlvbiAoKVxue1xuICByZXR1cm4gcHJlZDI7XG59O1xuXG5Db1NFTm9kZS5wcm90b3R5cGUuc2V0TmV4dCA9IGZ1bmN0aW9uIChuZXh0KVxue1xuICB0aGlzLm5leHQgPSBuZXh0O1xufTtcblxuQ29TRU5vZGUucHJvdG90eXBlLmdldE5leHQgPSBmdW5jdGlvbiAoKVxue1xuICByZXR1cm4gbmV4dDtcbn07XG5cbkNvU0VOb2RlLnByb3RvdHlwZS5zZXRQcm9jZXNzZWQgPSBmdW5jdGlvbiAocHJvY2Vzc2VkKVxue1xuICB0aGlzLnByb2Nlc3NlZCA9IHByb2Nlc3NlZDtcbn07XG5cbkNvU0VOb2RlLnByb3RvdHlwZS5pc1Byb2Nlc3NlZCA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiBwcm9jZXNzZWQ7XG59O1xuXG5tb2R1bGUuZXhwb3J0cyA9IENvU0VOb2RlO1xuIiwiZnVuY3Rpb24gRGltZW5zaW9uRCh3aWR0aCwgaGVpZ2h0KSB7XG4gIHRoaXMud2lkdGggPSAwO1xuICB0aGlzLmhlaWdodCA9IDA7XG4gIGlmICh3aWR0aCAhPT0gbnVsbCAmJiBoZWlnaHQgIT09IG51bGwpIHtcbiAgICB0aGlzLmhlaWdodCA9IGhlaWdodDtcbiAgICB0aGlzLndpZHRoID0gd2lkdGg7XG4gIH1cbn1cblxuRGltZW5zaW9uRC5wcm90b3R5cGUuZ2V0V2lkdGggPSBmdW5jdGlvbiAoKVxue1xuICByZXR1cm4gdGhpcy53aWR0aDtcbn07XG5cbkRpbWVuc2lvbkQucHJvdG90eXBlLnNldFdpZHRoID0gZnVuY3Rpb24gKHdpZHRoKVxue1xuICB0aGlzLndpZHRoID0gd2lkdGg7XG59O1xuXG5EaW1lbnNpb25ELnByb3RvdHlwZS5nZXRIZWlnaHQgPSBmdW5jdGlvbiAoKVxue1xuICByZXR1cm4gdGhpcy5oZWlnaHQ7XG59O1xuXG5EaW1lbnNpb25ELnByb3RvdHlwZS5zZXRIZWlnaHQgPSBmdW5jdGlvbiAoaGVpZ2h0KVxue1xuICB0aGlzLmhlaWdodCA9IGhlaWdodDtcbn07XG5cbm1vZHVsZS5leHBvcnRzID0gRGltZW5zaW9uRDtcbiIsImZ1bmN0aW9uIEVtaXR0ZXIoKXtcbiAgdGhpcy5saXN0ZW5lcnMgPSBbXTtcbn1cblxudmFyIHAgPSBFbWl0dGVyLnByb3RvdHlwZTtcblxucC5hZGRMaXN0ZW5lciA9IGZ1bmN0aW9uKCBldmVudCwgY2FsbGJhY2sgKXtcbiAgdGhpcy5saXN0ZW5lcnMucHVzaCh7XG4gICAgZXZlbnQ6IGV2ZW50LFxuICAgIGNhbGxiYWNrOiBjYWxsYmFja1xuICB9KTtcbn07XG5cbnAucmVtb3ZlTGlzdGVuZXIgPSBmdW5jdGlvbiggZXZlbnQsIGNhbGxiYWNrICl7XG4gIGZvciggdmFyIGkgPSB0aGlzLmxpc3RlbmVycy5sZW5ndGg7IGkgPj0gMDsgaS0tICl7XG4gICAgdmFyIGwgPSB0aGlzLmxpc3RlbmVyc1tpXTtcblxuICAgIGlmKCBsLmV2ZW50ID09PSBldmVudCAmJiBsLmNhbGxiYWNrID09PSBjYWxsYmFjayApe1xuICAgICAgdGhpcy5saXN0ZW5lcnMuc3BsaWNlKCBpLCAxICk7XG4gICAgfVxuICB9XG59O1xuXG5wLmVtaXQgPSBmdW5jdGlvbiggZXZlbnQsIGRhdGEgKXtcbiAgZm9yKCB2YXIgaSA9IDA7IGkgPCB0aGlzLmxpc3RlbmVycy5sZW5ndGg7IGkrKyApe1xuICAgIHZhciBsID0gdGhpcy5saXN0ZW5lcnNbaV07XG5cbiAgICBpZiggZXZlbnQgPT09IGwuZXZlbnQgKXtcbiAgICAgIGwuY2FsbGJhY2soIGRhdGEgKTtcbiAgICB9XG4gIH1cbn07XG5cbm1vZHVsZS5leHBvcnRzID0gRW1pdHRlcjtcbiIsInZhciBMYXlvdXQgPSByZXF1aXJlKCcuL0xheW91dCcpO1xudmFyIEZETGF5b3V0Q29uc3RhbnRzID0gcmVxdWlyZSgnLi9GRExheW91dENvbnN0YW50cycpO1xudmFyIExheW91dENvbnN0YW50cyA9IHJlcXVpcmUoJy4vTGF5b3V0Q29uc3RhbnRzJyk7XG52YXIgSUdlb21ldHJ5ID0gcmVxdWlyZSgnLi9JR2VvbWV0cnknKTtcbnZhciBJTWF0aCA9IHJlcXVpcmUoJy4vSU1hdGgnKTtcblxuZnVuY3Rpb24gRkRMYXlvdXQoKSB7XG4gIExheW91dC5jYWxsKHRoaXMpO1xuXG4gIHRoaXMudXNlU21hcnRJZGVhbEVkZ2VMZW5ndGhDYWxjdWxhdGlvbiA9IEZETGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfVVNFX1NNQVJUX0lERUFMX0VER0VfTEVOR1RIX0NBTENVTEFUSU9OO1xuICB0aGlzLmlkZWFsRWRnZUxlbmd0aCA9IEZETGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfRURHRV9MRU5HVEg7XG4gIHRoaXMuc3ByaW5nQ29uc3RhbnQgPSBGRExheW91dENvbnN0YW50cy5ERUZBVUxUX1NQUklOR19TVFJFTkdUSDtcbiAgdGhpcy5yZXB1bHNpb25Db25zdGFudCA9IEZETGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfUkVQVUxTSU9OX1NUUkVOR1RIO1xuICB0aGlzLmdyYXZpdHlDb25zdGFudCA9IEZETGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfR1JBVklUWV9TVFJFTkdUSDtcbiAgdGhpcy5jb21wb3VuZEdyYXZpdHlDb25zdGFudCA9IEZETGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfQ09NUE9VTkRfR1JBVklUWV9TVFJFTkdUSDtcbiAgdGhpcy5ncmF2aXR5UmFuZ2VGYWN0b3IgPSBGRExheW91dENvbnN0YW50cy5ERUZBVUxUX0dSQVZJVFlfUkFOR0VfRkFDVE9SO1xuICB0aGlzLmNvbXBvdW5kR3Jhdml0eVJhbmdlRmFjdG9yID0gRkRMYXlvdXRDb25zdGFudHMuREVGQVVMVF9DT01QT1VORF9HUkFWSVRZX1JBTkdFX0ZBQ1RPUjtcbiAgdGhpcy5kaXNwbGFjZW1lbnRUaHJlc2hvbGRQZXJOb2RlID0gKDMuMCAqIEZETGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfRURHRV9MRU5HVEgpIC8gMTAwO1xuICB0aGlzLmNvb2xpbmdGYWN0b3IgPSAxLjA7XG4gIHRoaXMuaW5pdGlhbENvb2xpbmdGYWN0b3IgPSAxLjA7XG4gIHRoaXMudG90YWxEaXNwbGFjZW1lbnQgPSAwLjA7XG4gIHRoaXMub2xkVG90YWxEaXNwbGFjZW1lbnQgPSAwLjA7XG4gIHRoaXMubWF4SXRlcmF0aW9ucyA9IEZETGF5b3V0Q29uc3RhbnRzLk1BWF9JVEVSQVRJT05TO1xufVxuXG5GRExheW91dC5wcm90b3R5cGUgPSBPYmplY3QuY3JlYXRlKExheW91dC5wcm90b3R5cGUpO1xuXG5mb3IgKHZhciBwcm9wIGluIExheW91dCkge1xuICBGRExheW91dFtwcm9wXSA9IExheW91dFtwcm9wXTtcbn1cblxuRkRMYXlvdXQucHJvdG90eXBlLmluaXRQYXJhbWV0ZXJzID0gZnVuY3Rpb24gKCkge1xuICBMYXlvdXQucHJvdG90eXBlLmluaXRQYXJhbWV0ZXJzLmNhbGwodGhpcywgYXJndW1lbnRzKTtcblxuICBpZiAodGhpcy5sYXlvdXRRdWFsaXR5ID09IExheW91dENvbnN0YW50cy5EUkFGVF9RVUFMSVRZKVxuICB7XG4gICAgdGhpcy5kaXNwbGFjZW1lbnRUaHJlc2hvbGRQZXJOb2RlICs9IDAuMzA7XG4gICAgdGhpcy5tYXhJdGVyYXRpb25zICo9IDAuODtcbiAgfVxuICBlbHNlIGlmICh0aGlzLmxheW91dFF1YWxpdHkgPT0gTGF5b3V0Q29uc3RhbnRzLlBST09GX1FVQUxJVFkpXG4gIHtcbiAgICB0aGlzLmRpc3BsYWNlbWVudFRocmVzaG9sZFBlck5vZGUgLT0gMC4zMDtcbiAgICB0aGlzLm1heEl0ZXJhdGlvbnMgKj0gMS4yO1xuICB9XG5cbiAgdGhpcy50b3RhbEl0ZXJhdGlvbnMgPSAwO1xuICB0aGlzLm5vdEFuaW1hdGVkSXRlcmF0aW9ucyA9IDA7XG5cbi8vICAgIHRoaXMudXNlRlJHcmlkVmFyaWFudCA9IGxheW91dE9wdGlvbnNQYWNrLnNtYXJ0UmVwdWxzaW9uUmFuZ2VDYWxjO1xufTtcblxuRkRMYXlvdXQucHJvdG90eXBlLmNhbGNJZGVhbEVkZ2VMZW5ndGhzID0gZnVuY3Rpb24gKCkge1xuICB2YXIgZWRnZTtcbiAgdmFyIGxjYURlcHRoO1xuICB2YXIgc291cmNlO1xuICB2YXIgdGFyZ2V0O1xuICB2YXIgc2l6ZU9mU291cmNlSW5MY2E7XG4gIHZhciBzaXplT2ZUYXJnZXRJbkxjYTtcblxuICB2YXIgYWxsRWRnZXMgPSB0aGlzLmdldEdyYXBoTWFuYWdlcigpLmdldEFsbEVkZ2VzKCk7XG4gIGZvciAodmFyIGkgPSAwOyBpIDwgYWxsRWRnZXMubGVuZ3RoOyBpKyspXG4gIHtcbiAgICBlZGdlID0gYWxsRWRnZXNbaV07XG5cbiAgICBlZGdlLmlkZWFsTGVuZ3RoID0gdGhpcy5pZGVhbEVkZ2VMZW5ndGg7XG5cbiAgICBpZiAoZWRnZS5pc0ludGVyR3JhcGgpXG4gICAge1xuICAgICAgc291cmNlID0gZWRnZS5nZXRTb3VyY2UoKTtcbiAgICAgIHRhcmdldCA9IGVkZ2UuZ2V0VGFyZ2V0KCk7XG5cbiAgICAgIHNpemVPZlNvdXJjZUluTGNhID0gZWRnZS5nZXRTb3VyY2VJbkxjYSgpLmdldEVzdGltYXRlZFNpemUoKTtcbiAgICAgIHNpemVPZlRhcmdldEluTGNhID0gZWRnZS5nZXRUYXJnZXRJbkxjYSgpLmdldEVzdGltYXRlZFNpemUoKTtcblxuICAgICAgaWYgKHRoaXMudXNlU21hcnRJZGVhbEVkZ2VMZW5ndGhDYWxjdWxhdGlvbilcbiAgICAgIHtcbiAgICAgICAgZWRnZS5pZGVhbExlbmd0aCArPSBzaXplT2ZTb3VyY2VJbkxjYSArIHNpemVPZlRhcmdldEluTGNhIC1cbiAgICAgICAgICAgICAgICAyICogTGF5b3V0Q29uc3RhbnRzLlNJTVBMRV9OT0RFX1NJWkU7XG4gICAgICB9XG5cbiAgICAgIGxjYURlcHRoID0gZWRnZS5nZXRMY2EoKS5nZXRJbmNsdXNpb25UcmVlRGVwdGgoKTtcblxuICAgICAgZWRnZS5pZGVhbExlbmd0aCArPSBGRExheW91dENvbnN0YW50cy5ERUZBVUxUX0VER0VfTEVOR1RIICpcbiAgICAgICAgICAgICAgRkRMYXlvdXRDb25zdGFudHMuUEVSX0xFVkVMX0lERUFMX0VER0VfTEVOR1RIX0ZBQ1RPUiAqXG4gICAgICAgICAgICAgIChzb3VyY2UuZ2V0SW5jbHVzaW9uVHJlZURlcHRoKCkgK1xuICAgICAgICAgICAgICAgICAgICAgIHRhcmdldC5nZXRJbmNsdXNpb25UcmVlRGVwdGgoKSAtIDIgKiBsY2FEZXB0aCk7XG4gICAgfVxuICB9XG59O1xuXG5GRExheW91dC5wcm90b3R5cGUuaW5pdFNwcmluZ0VtYmVkZGVyID0gZnVuY3Rpb24gKCkge1xuXG4gIGlmICh0aGlzLmluY3JlbWVudGFsKVxuICB7XG4gICAgdGhpcy5jb29saW5nRmFjdG9yID0gMC44O1xuICAgIHRoaXMuaW5pdGlhbENvb2xpbmdGYWN0b3IgPSAwLjg7XG4gICAgdGhpcy5tYXhOb2RlRGlzcGxhY2VtZW50ID1cbiAgICAgICAgICAgIEZETGF5b3V0Q29uc3RhbnRzLk1BWF9OT0RFX0RJU1BMQUNFTUVOVF9JTkNSRU1FTlRBTDtcbiAgfVxuICBlbHNlXG4gIHtcbiAgICB0aGlzLmNvb2xpbmdGYWN0b3IgPSAxLjA7XG4gICAgdGhpcy5pbml0aWFsQ29vbGluZ0ZhY3RvciA9IDEuMDtcbiAgICB0aGlzLm1heE5vZGVEaXNwbGFjZW1lbnQgPVxuICAgICAgICAgICAgRkRMYXlvdXRDb25zdGFudHMuTUFYX05PREVfRElTUExBQ0VNRU5UO1xuICB9XG5cbiAgdGhpcy5tYXhJdGVyYXRpb25zID1cbiAgICAgICAgICBNYXRoLm1heCh0aGlzLmdldEFsbE5vZGVzKCkubGVuZ3RoICogNSwgdGhpcy5tYXhJdGVyYXRpb25zKTtcblxuICB0aGlzLnRvdGFsRGlzcGxhY2VtZW50VGhyZXNob2xkID1cbiAgICAgICAgICB0aGlzLmRpc3BsYWNlbWVudFRocmVzaG9sZFBlck5vZGUgKiB0aGlzLmdldEFsbE5vZGVzKCkubGVuZ3RoO1xuXG4gIHRoaXMucmVwdWxzaW9uUmFuZ2UgPSB0aGlzLmNhbGNSZXB1bHNpb25SYW5nZSgpO1xufTtcblxuRkRMYXlvdXQucHJvdG90eXBlLmNhbGNTcHJpbmdGb3JjZXMgPSBmdW5jdGlvbiAoKSB7XG4gIHZhciBsRWRnZXMgPSB0aGlzLmdldEFsbEVkZ2VzKCk7XG4gIHZhciBlZGdlO1xuXG4gIGZvciAodmFyIGkgPSAwOyBpIDwgbEVkZ2VzLmxlbmd0aDsgaSsrKVxuICB7XG4gICAgZWRnZSA9IGxFZGdlc1tpXTtcblxuICAgIHRoaXMuY2FsY1NwcmluZ0ZvcmNlKGVkZ2UsIGVkZ2UuaWRlYWxMZW5ndGgpO1xuICB9XG59O1xuXG5GRExheW91dC5wcm90b3R5cGUuY2FsY1JlcHVsc2lvbkZvcmNlcyA9IGZ1bmN0aW9uICgpIHtcbiAgdmFyIGksIGo7XG4gIHZhciBub2RlQSwgbm9kZUI7XG4gIHZhciBsTm9kZXMgPSB0aGlzLmdldEFsbE5vZGVzKCk7XG5cbiAgZm9yIChpID0gMDsgaSA8IGxOb2Rlcy5sZW5ndGg7IGkrKylcbiAge1xuICAgIG5vZGVBID0gbE5vZGVzW2ldO1xuXG4gICAgZm9yIChqID0gaSArIDE7IGogPCBsTm9kZXMubGVuZ3RoOyBqKyspXG4gICAge1xuICAgICAgbm9kZUIgPSBsTm9kZXNbal07XG5cbiAgICAgIC8vIElmIGJvdGggbm9kZXMgYXJlIG5vdCBtZW1iZXJzIG9mIHRoZSBzYW1lIGdyYXBoLCBza2lwLlxuICAgICAgaWYgKG5vZGVBLmdldE93bmVyKCkgIT0gbm9kZUIuZ2V0T3duZXIoKSlcbiAgICAgIHtcbiAgICAgICAgY29udGludWU7XG4gICAgICB9XG5cbiAgICAgIHRoaXMuY2FsY1JlcHVsc2lvbkZvcmNlKG5vZGVBLCBub2RlQik7XG4gICAgfVxuICB9XG59O1xuXG5GRExheW91dC5wcm90b3R5cGUuY2FsY0dyYXZpdGF0aW9uYWxGb3JjZXMgPSBmdW5jdGlvbiAoKSB7XG4gIHZhciBub2RlO1xuICB2YXIgbE5vZGVzID0gdGhpcy5nZXRBbGxOb2Rlc1RvQXBwbHlHcmF2aXRhdGlvbigpO1xuXG4gIGZvciAodmFyIGkgPSAwOyBpIDwgbE5vZGVzLmxlbmd0aDsgaSsrKVxuICB7XG4gICAgbm9kZSA9IGxOb2Rlc1tpXTtcbiAgICB0aGlzLmNhbGNHcmF2aXRhdGlvbmFsRm9yY2Uobm9kZSk7XG4gIH1cbn07XG5cbkZETGF5b3V0LnByb3RvdHlwZS5tb3ZlTm9kZXMgPSBmdW5jdGlvbiAoKSB7XG4gIHZhciBsTm9kZXMgPSB0aGlzLmdldEFsbE5vZGVzKCk7XG4gIHZhciBub2RlO1xuXG4gIGZvciAodmFyIGkgPSAwOyBpIDwgbE5vZGVzLmxlbmd0aDsgaSsrKVxuICB7XG4gICAgbm9kZSA9IGxOb2Rlc1tpXTtcbiAgICBub2RlLm1vdmUoKTtcbiAgfVxufVxuXG5GRExheW91dC5wcm90b3R5cGUuY2FsY1NwcmluZ0ZvcmNlID0gZnVuY3Rpb24gKGVkZ2UsIGlkZWFsTGVuZ3RoKSB7XG4gIHZhciBzb3VyY2VOb2RlID0gZWRnZS5nZXRTb3VyY2UoKTtcbiAgdmFyIHRhcmdldE5vZGUgPSBlZGdlLmdldFRhcmdldCgpO1xuXG4gIHZhciBsZW5ndGg7XG4gIHZhciBzcHJpbmdGb3JjZTtcbiAgdmFyIHNwcmluZ0ZvcmNlWDtcbiAgdmFyIHNwcmluZ0ZvcmNlWTtcblxuICAvLyBVcGRhdGUgZWRnZSBsZW5ndGhcbiAgaWYgKHRoaXMudW5pZm9ybUxlYWZOb2RlU2l6ZXMgJiZcbiAgICAgICAgICBzb3VyY2VOb2RlLmdldENoaWxkKCkgPT0gbnVsbCAmJiB0YXJnZXROb2RlLmdldENoaWxkKCkgPT0gbnVsbClcbiAge1xuICAgIGVkZ2UudXBkYXRlTGVuZ3RoU2ltcGxlKCk7XG4gIH1cbiAgZWxzZVxuICB7XG4gICAgZWRnZS51cGRhdGVMZW5ndGgoKTtcblxuICAgIGlmIChlZGdlLmlzT3ZlcmxhcGluZ1NvdXJjZUFuZFRhcmdldClcbiAgICB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICB9XG5cbiAgbGVuZ3RoID0gZWRnZS5nZXRMZW5ndGgoKTtcblxuICAvLyBDYWxjdWxhdGUgc3ByaW5nIGZvcmNlc1xuICBzcHJpbmdGb3JjZSA9IHRoaXMuc3ByaW5nQ29uc3RhbnQgKiAobGVuZ3RoIC0gaWRlYWxMZW5ndGgpO1xuXG4gIC8vIFByb2plY3QgZm9yY2Ugb250byB4IGFuZCB5IGF4ZXNcbiAgc3ByaW5nRm9yY2VYID0gc3ByaW5nRm9yY2UgKiAoZWRnZS5sZW5ndGhYIC8gbGVuZ3RoKTtcbiAgc3ByaW5nRm9yY2VZID0gc3ByaW5nRm9yY2UgKiAoZWRnZS5sZW5ndGhZIC8gbGVuZ3RoKTtcblxuICAvLyBBcHBseSBmb3JjZXMgb24gdGhlIGVuZCBub2Rlc1xuICBzb3VyY2VOb2RlLnNwcmluZ0ZvcmNlWCArPSBzcHJpbmdGb3JjZVg7XG4gIHNvdXJjZU5vZGUuc3ByaW5nRm9yY2VZICs9IHNwcmluZ0ZvcmNlWTtcbiAgdGFyZ2V0Tm9kZS5zcHJpbmdGb3JjZVggLT0gc3ByaW5nRm9yY2VYO1xuICB0YXJnZXROb2RlLnNwcmluZ0ZvcmNlWSAtPSBzcHJpbmdGb3JjZVk7XG59O1xuXG5GRExheW91dC5wcm90b3R5cGUuY2FsY1JlcHVsc2lvbkZvcmNlID0gZnVuY3Rpb24gKG5vZGVBLCBub2RlQikge1xuICB2YXIgcmVjdEEgPSBub2RlQS5nZXRSZWN0KCk7XG4gIHZhciByZWN0QiA9IG5vZGVCLmdldFJlY3QoKTtcbiAgdmFyIG92ZXJsYXBBbW91bnQgPSBuZXcgQXJyYXkoMik7XG4gIHZhciBjbGlwUG9pbnRzID0gbmV3IEFycmF5KDQpO1xuICB2YXIgZGlzdGFuY2VYO1xuICB2YXIgZGlzdGFuY2VZO1xuICB2YXIgZGlzdGFuY2VTcXVhcmVkO1xuICB2YXIgZGlzdGFuY2U7XG4gIHZhciByZXB1bHNpb25Gb3JjZTtcbiAgdmFyIHJlcHVsc2lvbkZvcmNlWDtcbiAgdmFyIHJlcHVsc2lvbkZvcmNlWTtcblxuICBpZiAocmVjdEEuaW50ZXJzZWN0cyhyZWN0QikpLy8gdHdvIG5vZGVzIG92ZXJsYXBcbiAge1xuICAgIC8vIGNhbGN1bGF0ZSBzZXBhcmF0aW9uIGFtb3VudCBpbiB4IGFuZCB5IGRpcmVjdGlvbnNcbiAgICBJR2VvbWV0cnkuY2FsY1NlcGFyYXRpb25BbW91bnQocmVjdEEsXG4gICAgICAgICAgICByZWN0QixcbiAgICAgICAgICAgIG92ZXJsYXBBbW91bnQsXG4gICAgICAgICAgICBGRExheW91dENvbnN0YW50cy5ERUZBVUxUX0VER0VfTEVOR1RIIC8gMi4wKTtcblxuICAgIHJlcHVsc2lvbkZvcmNlWCA9IG92ZXJsYXBBbW91bnRbMF07XG4gICAgcmVwdWxzaW9uRm9yY2VZID0gb3ZlcmxhcEFtb3VudFsxXTtcbiAgfVxuICBlbHNlLy8gbm8gb3ZlcmxhcFxuICB7XG4gICAgLy8gY2FsY3VsYXRlIGRpc3RhbmNlXG5cbiAgICBpZiAodGhpcy51bmlmb3JtTGVhZk5vZGVTaXplcyAmJlxuICAgICAgICAgICAgbm9kZUEuZ2V0Q2hpbGQoKSA9PSBudWxsICYmIG5vZGVCLmdldENoaWxkKCkgPT0gbnVsbCkvLyBzaW1wbHkgYmFzZSByZXB1bHNpb24gb24gZGlzdGFuY2Ugb2Ygbm9kZSBjZW50ZXJzXG4gICAge1xuICAgICAgZGlzdGFuY2VYID0gcmVjdEIuZ2V0Q2VudGVyWCgpIC0gcmVjdEEuZ2V0Q2VudGVyWCgpO1xuICAgICAgZGlzdGFuY2VZID0gcmVjdEIuZ2V0Q2VudGVyWSgpIC0gcmVjdEEuZ2V0Q2VudGVyWSgpO1xuICAgIH1cbiAgICBlbHNlLy8gdXNlIGNsaXBwaW5nIHBvaW50c1xuICAgIHtcbiAgICAgIElHZW9tZXRyeS5nZXRJbnRlcnNlY3Rpb24ocmVjdEEsIHJlY3RCLCBjbGlwUG9pbnRzKTtcblxuICAgICAgZGlzdGFuY2VYID0gY2xpcFBvaW50c1syXSAtIGNsaXBQb2ludHNbMF07XG4gICAgICBkaXN0YW5jZVkgPSBjbGlwUG9pbnRzWzNdIC0gY2xpcFBvaW50c1sxXTtcbiAgICB9XG5cbiAgICAvLyBObyByZXB1bHNpb24gcmFuZ2UuIEZSIGdyaWQgdmFyaWFudCBzaG91bGQgdGFrZSBjYXJlIG9mIHRoaXMuXG4gICAgaWYgKE1hdGguYWJzKGRpc3RhbmNlWCkgPCBGRExheW91dENvbnN0YW50cy5NSU5fUkVQVUxTSU9OX0RJU1QpXG4gICAge1xuICAgICAgZGlzdGFuY2VYID0gSU1hdGguc2lnbihkaXN0YW5jZVgpICpcbiAgICAgICAgICAgICAgRkRMYXlvdXRDb25zdGFudHMuTUlOX1JFUFVMU0lPTl9ESVNUO1xuICAgIH1cblxuICAgIGlmIChNYXRoLmFicyhkaXN0YW5jZVkpIDwgRkRMYXlvdXRDb25zdGFudHMuTUlOX1JFUFVMU0lPTl9ESVNUKVxuICAgIHtcbiAgICAgIGRpc3RhbmNlWSA9IElNYXRoLnNpZ24oZGlzdGFuY2VZKSAqXG4gICAgICAgICAgICAgIEZETGF5b3V0Q29uc3RhbnRzLk1JTl9SRVBVTFNJT05fRElTVDtcbiAgICB9XG5cbiAgICBkaXN0YW5jZVNxdWFyZWQgPSBkaXN0YW5jZVggKiBkaXN0YW5jZVggKyBkaXN0YW5jZVkgKiBkaXN0YW5jZVk7XG4gICAgZGlzdGFuY2UgPSBNYXRoLnNxcnQoZGlzdGFuY2VTcXVhcmVkKTtcblxuICAgIHJlcHVsc2lvbkZvcmNlID0gdGhpcy5yZXB1bHNpb25Db25zdGFudCAvIGRpc3RhbmNlU3F1YXJlZDtcblxuICAgIC8vIFByb2plY3QgZm9yY2Ugb250byB4IGFuZCB5IGF4ZXNcbiAgICByZXB1bHNpb25Gb3JjZVggPSByZXB1bHNpb25Gb3JjZSAqIGRpc3RhbmNlWCAvIGRpc3RhbmNlO1xuICAgIHJlcHVsc2lvbkZvcmNlWSA9IHJlcHVsc2lvbkZvcmNlICogZGlzdGFuY2VZIC8gZGlzdGFuY2U7XG4gIH1cblxuICAvLyBBcHBseSBmb3JjZXMgb24gdGhlIHR3byBub2Rlc1xuICBub2RlQS5yZXB1bHNpb25Gb3JjZVggLT0gcmVwdWxzaW9uRm9yY2VYO1xuICBub2RlQS5yZXB1bHNpb25Gb3JjZVkgLT0gcmVwdWxzaW9uRm9yY2VZO1xuICBub2RlQi5yZXB1bHNpb25Gb3JjZVggKz0gcmVwdWxzaW9uRm9yY2VYO1xuICBub2RlQi5yZXB1bHNpb25Gb3JjZVkgKz0gcmVwdWxzaW9uRm9yY2VZO1xufTtcblxuRkRMYXlvdXQucHJvdG90eXBlLmNhbGNHcmF2aXRhdGlvbmFsRm9yY2UgPSBmdW5jdGlvbiAobm9kZSkge1xuICB2YXIgb3duZXJHcmFwaDtcbiAgdmFyIG93bmVyQ2VudGVyWDtcbiAgdmFyIG93bmVyQ2VudGVyWTtcbiAgdmFyIGRpc3RhbmNlWDtcbiAgdmFyIGRpc3RhbmNlWTtcbiAgdmFyIGFic0Rpc3RhbmNlWDtcbiAgdmFyIGFic0Rpc3RhbmNlWTtcbiAgdmFyIGVzdGltYXRlZFNpemU7XG4gIG93bmVyR3JhcGggPSBub2RlLmdldE93bmVyKCk7XG5cbiAgb3duZXJDZW50ZXJYID0gKG93bmVyR3JhcGguZ2V0UmlnaHQoKSArIG93bmVyR3JhcGguZ2V0TGVmdCgpKSAvIDI7XG4gIG93bmVyQ2VudGVyWSA9IChvd25lckdyYXBoLmdldFRvcCgpICsgb3duZXJHcmFwaC5nZXRCb3R0b20oKSkgLyAyO1xuICBkaXN0YW5jZVggPSBub2RlLmdldENlbnRlclgoKSAtIG93bmVyQ2VudGVyWDtcbiAgZGlzdGFuY2VZID0gbm9kZS5nZXRDZW50ZXJZKCkgLSBvd25lckNlbnRlclk7XG4gIGFic0Rpc3RhbmNlWCA9IE1hdGguYWJzKGRpc3RhbmNlWCk7XG4gIGFic0Rpc3RhbmNlWSA9IE1hdGguYWJzKGRpc3RhbmNlWSk7XG5cbiAgaWYgKG5vZGUuZ2V0T3duZXIoKSA9PSB0aGlzLmdyYXBoTWFuYWdlci5nZXRSb290KCkpLy8gaW4gdGhlIHJvb3QgZ3JhcGhcbiAge1xuICAgIE1hdGguZmxvb3IoODApO1xuICAgIGVzdGltYXRlZFNpemUgPSBNYXRoLmZsb29yKG93bmVyR3JhcGguZ2V0RXN0aW1hdGVkU2l6ZSgpICpcbiAgICAgICAgICAgIHRoaXMuZ3Jhdml0eVJhbmdlRmFjdG9yKTtcblxuICAgIGlmIChhYnNEaXN0YW5jZVggPiBlc3RpbWF0ZWRTaXplIHx8IGFic0Rpc3RhbmNlWSA+IGVzdGltYXRlZFNpemUpXG4gICAge1xuICAgICAgbm9kZS5ncmF2aXRhdGlvbkZvcmNlWCA9IC10aGlzLmdyYXZpdHlDb25zdGFudCAqIGRpc3RhbmNlWDtcbiAgICAgIG5vZGUuZ3Jhdml0YXRpb25Gb3JjZVkgPSAtdGhpcy5ncmF2aXR5Q29uc3RhbnQgKiBkaXN0YW5jZVk7XG4gICAgfVxuICB9XG4gIGVsc2UvLyBpbnNpZGUgYSBjb21wb3VuZFxuICB7XG4gICAgZXN0aW1hdGVkU2l6ZSA9IE1hdGguZmxvb3IoKG93bmVyR3JhcGguZ2V0RXN0aW1hdGVkU2l6ZSgpICpcbiAgICAgICAgICAgIHRoaXMuY29tcG91bmRHcmF2aXR5UmFuZ2VGYWN0b3IpKTtcblxuICAgIGlmIChhYnNEaXN0YW5jZVggPiBlc3RpbWF0ZWRTaXplIHx8IGFic0Rpc3RhbmNlWSA+IGVzdGltYXRlZFNpemUpXG4gICAge1xuICAgICAgbm9kZS5ncmF2aXRhdGlvbkZvcmNlWCA9IC10aGlzLmdyYXZpdHlDb25zdGFudCAqIGRpc3RhbmNlWCAqXG4gICAgICAgICAgICAgIHRoaXMuY29tcG91bmRHcmF2aXR5Q29uc3RhbnQ7XG4gICAgICBub2RlLmdyYXZpdGF0aW9uRm9yY2VZID0gLXRoaXMuZ3Jhdml0eUNvbnN0YW50ICogZGlzdGFuY2VZICpcbiAgICAgICAgICAgICAgdGhpcy5jb21wb3VuZEdyYXZpdHlDb25zdGFudDtcbiAgICB9XG4gIH1cbn07XG5cbkZETGF5b3V0LnByb3RvdHlwZS5pc0NvbnZlcmdlZCA9IGZ1bmN0aW9uICgpIHtcbiAgdmFyIGNvbnZlcmdlZDtcbiAgdmFyIG9zY2lsYXRpbmcgPSBmYWxzZTtcblxuICBpZiAodGhpcy50b3RhbEl0ZXJhdGlvbnMgPiB0aGlzLm1heEl0ZXJhdGlvbnMgLyAzKVxuICB7XG4gICAgb3NjaWxhdGluZyA9XG4gICAgICAgICAgICBNYXRoLmFicyh0aGlzLnRvdGFsRGlzcGxhY2VtZW50IC0gdGhpcy5vbGRUb3RhbERpc3BsYWNlbWVudCkgPCAyO1xuICB9XG5cbiAgY29udmVyZ2VkID0gdGhpcy50b3RhbERpc3BsYWNlbWVudCA8IHRoaXMudG90YWxEaXNwbGFjZW1lbnRUaHJlc2hvbGQ7XG5cbiAgdGhpcy5vbGRUb3RhbERpc3BsYWNlbWVudCA9IHRoaXMudG90YWxEaXNwbGFjZW1lbnQ7XG5cbiAgcmV0dXJuIGNvbnZlcmdlZCB8fCBvc2NpbGF0aW5nO1xufTtcblxuRkRMYXlvdXQucHJvdG90eXBlLmFuaW1hdGUgPSBmdW5jdGlvbiAoKSB7XG4gIGlmICh0aGlzLmFuaW1hdGlvbkR1cmluZ0xheW91dCAmJiAhdGhpcy5pc1N1YkxheW91dClcbiAge1xuICAgIGlmICh0aGlzLm5vdEFuaW1hdGVkSXRlcmF0aW9ucyA9PSB0aGlzLmFuaW1hdGlvblBlcmlvZClcbiAgICB7XG4gICAgICB0aGlzLnVwZGF0ZSgpO1xuICAgICAgdGhpcy5ub3RBbmltYXRlZEl0ZXJhdGlvbnMgPSAwO1xuICAgIH1cbiAgICBlbHNlXG4gICAge1xuICAgICAgdGhpcy5ub3RBbmltYXRlZEl0ZXJhdGlvbnMrKztcbiAgICB9XG4gIH1cbn07XG5cbkZETGF5b3V0LnByb3RvdHlwZS5jYWxjUmVwdWxzaW9uUmFuZ2UgPSBmdW5jdGlvbiAoKSB7XG4gIHJldHVybiAwLjA7XG59O1xuXG5tb2R1bGUuZXhwb3J0cyA9IEZETGF5b3V0O1xuIiwidmFyIExheW91dENvbnN0YW50cyA9IHJlcXVpcmUoJy4vTGF5b3V0Q29uc3RhbnRzJyk7XG5cbmZ1bmN0aW9uIEZETGF5b3V0Q29uc3RhbnRzKCkge1xufVxuXG4vL0ZETGF5b3V0Q29uc3RhbnRzIGluaGVyaXRzIHN0YXRpYyBwcm9wcyBpbiBMYXlvdXRDb25zdGFudHNcbmZvciAodmFyIHByb3AgaW4gTGF5b3V0Q29uc3RhbnRzKSB7XG4gIEZETGF5b3V0Q29uc3RhbnRzW3Byb3BdID0gTGF5b3V0Q29uc3RhbnRzW3Byb3BdO1xufVxuXG5GRExheW91dENvbnN0YW50cy5NQVhfSVRFUkFUSU9OUyA9IDI1MDA7XG5cbkZETGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfRURHRV9MRU5HVEggPSA1MDtcbkZETGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfU1BSSU5HX1NUUkVOR1RIID0gMC40NTtcbkZETGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfUkVQVUxTSU9OX1NUUkVOR1RIID0gNDUwMC4wO1xuRkRMYXlvdXRDb25zdGFudHMuREVGQVVMVF9HUkFWSVRZX1NUUkVOR1RIID0gMC40O1xuRkRMYXlvdXRDb25zdGFudHMuREVGQVVMVF9DT01QT1VORF9HUkFWSVRZX1NUUkVOR1RIID0gMS4wO1xuRkRMYXlvdXRDb25zdGFudHMuREVGQVVMVF9HUkFWSVRZX1JBTkdFX0ZBQ1RPUiA9IDMuODtcbkZETGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfQ09NUE9VTkRfR1JBVklUWV9SQU5HRV9GQUNUT1IgPSAxLjU7XG5GRExheW91dENvbnN0YW50cy5ERUZBVUxUX1VTRV9TTUFSVF9JREVBTF9FREdFX0xFTkdUSF9DQUxDVUxBVElPTiA9IHRydWU7XG5GRExheW91dENvbnN0YW50cy5ERUZBVUxUX1VTRV9TTUFSVF9SRVBVTFNJT05fUkFOR0VfQ0FMQ1VMQVRJT04gPSB0cnVlO1xuRkRMYXlvdXRDb25zdGFudHMuTUFYX05PREVfRElTUExBQ0VNRU5UX0lOQ1JFTUVOVEFMID0gMTAwLjA7XG5GRExheW91dENvbnN0YW50cy5NQVhfTk9ERV9ESVNQTEFDRU1FTlQgPSBGRExheW91dENvbnN0YW50cy5NQVhfTk9ERV9ESVNQTEFDRU1FTlRfSU5DUkVNRU5UQUwgKiAzO1xuRkRMYXlvdXRDb25zdGFudHMuTUlOX1JFUFVMU0lPTl9ESVNUID0gRkRMYXlvdXRDb25zdGFudHMuREVGQVVMVF9FREdFX0xFTkdUSCAvIDEwLjA7XG5GRExheW91dENvbnN0YW50cy5DT05WRVJHRU5DRV9DSEVDS19QRVJJT0QgPSAxMDA7XG5GRExheW91dENvbnN0YW50cy5QRVJfTEVWRUxfSURFQUxfRURHRV9MRU5HVEhfRkFDVE9SID0gMC4xO1xuRkRMYXlvdXRDb25zdGFudHMuTUlOX0VER0VfTEVOR1RIID0gMTtcbkZETGF5b3V0Q29uc3RhbnRzLkdSSURfQ0FMQ1VMQVRJT05fQ0hFQ0tfUEVSSU9EID0gMTA7XG5cbm1vZHVsZS5leHBvcnRzID0gRkRMYXlvdXRDb25zdGFudHM7XG4iLCJ2YXIgTEVkZ2UgPSByZXF1aXJlKCcuL0xFZGdlJyk7XG52YXIgRkRMYXlvdXRDb25zdGFudHMgPSByZXF1aXJlKCcuL0ZETGF5b3V0Q29uc3RhbnRzJyk7XG5cbmZ1bmN0aW9uIEZETGF5b3V0RWRnZShzb3VyY2UsIHRhcmdldCwgdkVkZ2UpIHtcbiAgTEVkZ2UuY2FsbCh0aGlzLCBzb3VyY2UsIHRhcmdldCwgdkVkZ2UpO1xuICB0aGlzLmlkZWFsTGVuZ3RoID0gRkRMYXlvdXRDb25zdGFudHMuREVGQVVMVF9FREdFX0xFTkdUSDtcbn1cblxuRkRMYXlvdXRFZGdlLnByb3RvdHlwZSA9IE9iamVjdC5jcmVhdGUoTEVkZ2UucHJvdG90eXBlKTtcblxuZm9yICh2YXIgcHJvcCBpbiBMRWRnZSkge1xuICBGRExheW91dEVkZ2VbcHJvcF0gPSBMRWRnZVtwcm9wXTtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBGRExheW91dEVkZ2U7XG4iLCJ2YXIgTE5vZGUgPSByZXF1aXJlKCcuL0xOb2RlJyk7XG5cbmZ1bmN0aW9uIEZETGF5b3V0Tm9kZShnbSwgbG9jLCBzaXplLCB2Tm9kZSkge1xuICAvLyBhbHRlcm5hdGl2ZSBjb25zdHJ1Y3RvciBpcyBoYW5kbGVkIGluc2lkZSBMTm9kZVxuICBMTm9kZS5jYWxsKHRoaXMsIGdtLCBsb2MsIHNpemUsIHZOb2RlKTtcbiAgLy9TcHJpbmcsIHJlcHVsc2lvbiBhbmQgZ3Jhdml0YXRpb25hbCBmb3JjZXMgYWN0aW5nIG9uIHRoaXMgbm9kZVxuICB0aGlzLnNwcmluZ0ZvcmNlWCA9IDA7XG4gIHRoaXMuc3ByaW5nRm9yY2VZID0gMDtcbiAgdGhpcy5yZXB1bHNpb25Gb3JjZVggPSAwO1xuICB0aGlzLnJlcHVsc2lvbkZvcmNlWSA9IDA7XG4gIHRoaXMuZ3Jhdml0YXRpb25Gb3JjZVggPSAwO1xuICB0aGlzLmdyYXZpdGF0aW9uRm9yY2VZID0gMDtcbiAgLy9BbW91bnQgYnkgd2hpY2ggdGhpcyBub2RlIGlzIHRvIGJlIG1vdmVkIGluIHRoaXMgaXRlcmF0aW9uXG4gIHRoaXMuZGlzcGxhY2VtZW50WCA9IDA7XG4gIHRoaXMuZGlzcGxhY2VtZW50WSA9IDA7XG5cbiAgLy9TdGFydCBhbmQgZmluaXNoIGdyaWQgY29vcmRpbmF0ZXMgdGhhdCB0aGlzIG5vZGUgaXMgZmFsbGVuIGludG9cbiAgdGhpcy5zdGFydFggPSAwO1xuICB0aGlzLmZpbmlzaFggPSAwO1xuICB0aGlzLnN0YXJ0WSA9IDA7XG4gIHRoaXMuZmluaXNoWSA9IDA7XG5cbiAgLy9HZW9tZXRyaWMgbmVpZ2hib3JzIG9mIHRoaXMgbm9kZVxuICB0aGlzLnN1cnJvdW5kaW5nID0gW107XG59XG5cbkZETGF5b3V0Tm9kZS5wcm90b3R5cGUgPSBPYmplY3QuY3JlYXRlKExOb2RlLnByb3RvdHlwZSk7XG5cbmZvciAodmFyIHByb3AgaW4gTE5vZGUpIHtcbiAgRkRMYXlvdXROb2RlW3Byb3BdID0gTE5vZGVbcHJvcF07XG59XG5cbkZETGF5b3V0Tm9kZS5wcm90b3R5cGUuc2V0R3JpZENvb3JkaW5hdGVzID0gZnVuY3Rpb24gKF9zdGFydFgsIF9maW5pc2hYLCBfc3RhcnRZLCBfZmluaXNoWSlcbntcbiAgdGhpcy5zdGFydFggPSBfc3RhcnRYO1xuICB0aGlzLmZpbmlzaFggPSBfZmluaXNoWDtcbiAgdGhpcy5zdGFydFkgPSBfc3RhcnRZO1xuICB0aGlzLmZpbmlzaFkgPSBfZmluaXNoWTtcblxufTtcblxubW9kdWxlLmV4cG9ydHMgPSBGRExheW91dE5vZGU7XG4iLCJ2YXIgVW5pcXVlSURHZW5lcmV0b3IgPSByZXF1aXJlKCcuL1VuaXF1ZUlER2VuZXJldG9yJyk7XG5cbmZ1bmN0aW9uIEhhc2hNYXAoKSB7XG4gIHRoaXMubWFwID0ge307XG4gIHRoaXMua2V5cyA9IFtdO1xufVxuXG5IYXNoTWFwLnByb3RvdHlwZS5wdXQgPSBmdW5jdGlvbiAoa2V5LCB2YWx1ZSkge1xuICB2YXIgdGhlSWQgPSBVbmlxdWVJREdlbmVyZXRvci5jcmVhdGVJRChrZXkpO1xuICBpZiAoIXRoaXMuY29udGFpbnModGhlSWQpKSB7XG4gICAgdGhpcy5tYXBbdGhlSWRdID0gdmFsdWU7XG4gICAgdGhpcy5rZXlzLnB1c2goa2V5KTtcbiAgfVxufTtcblxuSGFzaE1hcC5wcm90b3R5cGUuY29udGFpbnMgPSBmdW5jdGlvbiAoa2V5KSB7XG4gIHZhciB0aGVJZCA9IFVuaXF1ZUlER2VuZXJldG9yLmNyZWF0ZUlEKGtleSk7XG4gIHJldHVybiB0aGlzLm1hcFtrZXldICE9IG51bGw7XG59O1xuXG5IYXNoTWFwLnByb3RvdHlwZS5nZXQgPSBmdW5jdGlvbiAoa2V5KSB7XG4gIHZhciB0aGVJZCA9IFVuaXF1ZUlER2VuZXJldG9yLmNyZWF0ZUlEKGtleSk7XG4gIHJldHVybiB0aGlzLm1hcFt0aGVJZF07XG59O1xuXG5IYXNoTWFwLnByb3RvdHlwZS5rZXlTZXQgPSBmdW5jdGlvbiAoKSB7XG4gIHJldHVybiB0aGlzLmtleXM7XG59O1xuXG5tb2R1bGUuZXhwb3J0cyA9IEhhc2hNYXA7XG4iLCJ2YXIgVW5pcXVlSURHZW5lcmV0b3IgPSByZXF1aXJlKCcuL1VuaXF1ZUlER2VuZXJldG9yJyk7XG5cbmZ1bmN0aW9uIEhhc2hTZXQoKSB7XG4gIHRoaXMuc2V0ID0ge307XG59XG47XG5cbkhhc2hTZXQucHJvdG90eXBlLmFkZCA9IGZ1bmN0aW9uIChvYmopIHtcbiAgdmFyIHRoZUlkID0gVW5pcXVlSURHZW5lcmV0b3IuY3JlYXRlSUQob2JqKTtcbiAgaWYgKCF0aGlzLmNvbnRhaW5zKHRoZUlkKSlcbiAgICB0aGlzLnNldFt0aGVJZF0gPSBvYmo7XG59O1xuXG5IYXNoU2V0LnByb3RvdHlwZS5yZW1vdmUgPSBmdW5jdGlvbiAob2JqKSB7XG4gIGRlbGV0ZSB0aGlzLnNldFtVbmlxdWVJREdlbmVyZXRvci5jcmVhdGVJRChvYmopXTtcbn07XG5cbkhhc2hTZXQucHJvdG90eXBlLmNsZWFyID0gZnVuY3Rpb24gKCkge1xuICB0aGlzLnNldCA9IHt9O1xufTtcblxuSGFzaFNldC5wcm90b3R5cGUuY29udGFpbnMgPSBmdW5jdGlvbiAob2JqKSB7XG4gIHJldHVybiB0aGlzLnNldFtVbmlxdWVJREdlbmVyZXRvci5jcmVhdGVJRChvYmopXSA9PSBvYmo7XG59O1xuXG5IYXNoU2V0LnByb3RvdHlwZS5pc0VtcHR5ID0gZnVuY3Rpb24gKCkge1xuICByZXR1cm4gdGhpcy5zaXplKCkgPT09IDA7XG59O1xuXG5IYXNoU2V0LnByb3RvdHlwZS5zaXplID0gZnVuY3Rpb24gKCkge1xuICByZXR1cm4gT2JqZWN0LmtleXModGhpcy5zZXQpLmxlbmd0aDtcbn07XG5cbi8vY29uY2F0cyB0aGlzLnNldCB0byB0aGUgZ2l2ZW4gbGlzdFxuSGFzaFNldC5wcm90b3R5cGUuYWRkQWxsVG8gPSBmdW5jdGlvbiAobGlzdCkge1xuICB2YXIga2V5cyA9IE9iamVjdC5rZXlzKHRoaXMuc2V0KTtcbiAgdmFyIGxlbmd0aCA9IGtleXMubGVuZ3RoO1xuICBmb3IgKHZhciBpID0gMDsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgbGlzdC5wdXNoKHRoaXMuc2V0W2tleXNbaV1dKTtcbiAgfVxufTtcblxuSGFzaFNldC5wcm90b3R5cGUuc2l6ZSA9IGZ1bmN0aW9uICgpIHtcbiAgcmV0dXJuIE9iamVjdC5rZXlzKHRoaXMuc2V0KS5sZW5ndGg7XG59O1xuXG5IYXNoU2V0LnByb3RvdHlwZS5hZGRBbGwgPSBmdW5jdGlvbiAobGlzdCkge1xuICB2YXIgcyA9IGxpc3QubGVuZ3RoO1xuICBmb3IgKHZhciBpID0gMDsgaSA8IHM7IGkrKykge1xuICAgIHZhciB2ID0gbGlzdFtpXTtcbiAgICB0aGlzLmFkZCh2KTtcbiAgfVxufTtcblxubW9kdWxlLmV4cG9ydHMgPSBIYXNoU2V0O1xuIiwiZnVuY3Rpb24gSUdlb21ldHJ5KCkge1xufVxuXG5JR2VvbWV0cnkuY2FsY1NlcGFyYXRpb25BbW91bnQgPSBmdW5jdGlvbiAocmVjdEEsIHJlY3RCLCBvdmVybGFwQW1vdW50LCBzZXBhcmF0aW9uQnVmZmVyKVxue1xuICBpZiAoIXJlY3RBLmludGVyc2VjdHMocmVjdEIpKSB7XG4gICAgdGhyb3cgXCJhc3NlcnQgZmFpbGVkXCI7XG4gIH1cbiAgdmFyIGRpcmVjdGlvbnMgPSBuZXcgQXJyYXkoMik7XG4gIElHZW9tZXRyeS5kZWNpZGVEaXJlY3Rpb25zRm9yT3ZlcmxhcHBpbmdOb2RlcyhyZWN0QSwgcmVjdEIsIGRpcmVjdGlvbnMpO1xuICBvdmVybGFwQW1vdW50WzBdID0gTWF0aC5taW4ocmVjdEEuZ2V0UmlnaHQoKSwgcmVjdEIuZ2V0UmlnaHQoKSkgLVxuICAgICAgICAgIE1hdGgubWF4KHJlY3RBLngsIHJlY3RCLngpO1xuICBvdmVybGFwQW1vdW50WzFdID0gTWF0aC5taW4ocmVjdEEuZ2V0Qm90dG9tKCksIHJlY3RCLmdldEJvdHRvbSgpKSAtXG4gICAgICAgICAgTWF0aC5tYXgocmVjdEEueSwgcmVjdEIueSk7XG4gIC8vIHVwZGF0ZSB0aGUgb3ZlcmxhcHBpbmcgYW1vdW50cyBmb3IgdGhlIGZvbGxvd2luZyBjYXNlczpcbiAgaWYgKChyZWN0QS5nZXRYKCkgPD0gcmVjdEIuZ2V0WCgpKSAmJiAocmVjdEEuZ2V0UmlnaHQoKSA+PSByZWN0Qi5nZXRSaWdodCgpKSlcbiAge1xuICAgIG92ZXJsYXBBbW91bnRbMF0gKz0gTWF0aC5taW4oKHJlY3RCLmdldFgoKSAtIHJlY3RBLmdldFgoKSksXG4gICAgICAgICAgICAocmVjdEEuZ2V0UmlnaHQoKSAtIHJlY3RCLmdldFJpZ2h0KCkpKTtcbiAgfVxuICBlbHNlIGlmICgocmVjdEIuZ2V0WCgpIDw9IHJlY3RBLmdldFgoKSkgJiYgKHJlY3RCLmdldFJpZ2h0KCkgPj0gcmVjdEEuZ2V0UmlnaHQoKSkpXG4gIHtcbiAgICBvdmVybGFwQW1vdW50WzBdICs9IE1hdGgubWluKChyZWN0QS5nZXRYKCkgLSByZWN0Qi5nZXRYKCkpLFxuICAgICAgICAgICAgKHJlY3RCLmdldFJpZ2h0KCkgLSByZWN0QS5nZXRSaWdodCgpKSk7XG4gIH1cbiAgaWYgKChyZWN0QS5nZXRZKCkgPD0gcmVjdEIuZ2V0WSgpKSAmJiAocmVjdEEuZ2V0Qm90dG9tKCkgPj0gcmVjdEIuZ2V0Qm90dG9tKCkpKVxuICB7XG4gICAgb3ZlcmxhcEFtb3VudFsxXSArPSBNYXRoLm1pbigocmVjdEIuZ2V0WSgpIC0gcmVjdEEuZ2V0WSgpKSxcbiAgICAgICAgICAgIChyZWN0QS5nZXRCb3R0b20oKSAtIHJlY3RCLmdldEJvdHRvbSgpKSk7XG4gIH1cbiAgZWxzZSBpZiAoKHJlY3RCLmdldFkoKSA8PSByZWN0QS5nZXRZKCkpICYmIChyZWN0Qi5nZXRCb3R0b20oKSA+PSByZWN0QS5nZXRCb3R0b20oKSkpXG4gIHtcbiAgICBvdmVybGFwQW1vdW50WzFdICs9IE1hdGgubWluKChyZWN0QS5nZXRZKCkgLSByZWN0Qi5nZXRZKCkpLFxuICAgICAgICAgICAgKHJlY3RCLmdldEJvdHRvbSgpIC0gcmVjdEEuZ2V0Qm90dG9tKCkpKTtcbiAgfVxuXG4gIC8vIGZpbmQgc2xvcGUgb2YgdGhlIGxpbmUgcGFzc2VzIHR3byBjZW50ZXJzXG4gIHZhciBzbG9wZSA9IE1hdGguYWJzKChyZWN0Qi5nZXRDZW50ZXJZKCkgLSByZWN0QS5nZXRDZW50ZXJZKCkpIC9cbiAgICAgICAgICAocmVjdEIuZ2V0Q2VudGVyWCgpIC0gcmVjdEEuZ2V0Q2VudGVyWCgpKSk7XG4gIC8vIGlmIGNlbnRlcnMgYXJlIG92ZXJsYXBwZWRcbiAgaWYgKChyZWN0Qi5nZXRDZW50ZXJZKCkgPT0gcmVjdEEuZ2V0Q2VudGVyWSgpKSAmJlxuICAgICAgICAgIChyZWN0Qi5nZXRDZW50ZXJYKCkgPT0gcmVjdEEuZ2V0Q2VudGVyWCgpKSlcbiAge1xuICAgIC8vIGFzc3VtZSB0aGUgc2xvcGUgaXMgMSAoNDUgZGVncmVlKVxuICAgIHNsb3BlID0gMS4wO1xuICB9XG5cbiAgdmFyIG1vdmVCeVkgPSBzbG9wZSAqIG92ZXJsYXBBbW91bnRbMF07XG4gIHZhciBtb3ZlQnlYID0gb3ZlcmxhcEFtb3VudFsxXSAvIHNsb3BlO1xuICBpZiAob3ZlcmxhcEFtb3VudFswXSA8IG1vdmVCeVgpXG4gIHtcbiAgICBtb3ZlQnlYID0gb3ZlcmxhcEFtb3VudFswXTtcbiAgfVxuICBlbHNlXG4gIHtcbiAgICBtb3ZlQnlZID0gb3ZlcmxhcEFtb3VudFsxXTtcbiAgfVxuICAvLyByZXR1cm4gaGFsZiB0aGUgYW1vdW50IHNvIHRoYXQgaWYgZWFjaCByZWN0YW5nbGUgaXMgbW92ZWQgYnkgdGhlc2VcbiAgLy8gYW1vdW50cyBpbiBvcHBvc2l0ZSBkaXJlY3Rpb25zLCBvdmVybGFwIHdpbGwgYmUgcmVzb2x2ZWRcbiAgb3ZlcmxhcEFtb3VudFswXSA9IC0xICogZGlyZWN0aW9uc1swXSAqICgobW92ZUJ5WCAvIDIpICsgc2VwYXJhdGlvbkJ1ZmZlcik7XG4gIG92ZXJsYXBBbW91bnRbMV0gPSAtMSAqIGRpcmVjdGlvbnNbMV0gKiAoKG1vdmVCeVkgLyAyKSArIHNlcGFyYXRpb25CdWZmZXIpO1xufVxuXG5JR2VvbWV0cnkuZGVjaWRlRGlyZWN0aW9uc0Zvck92ZXJsYXBwaW5nTm9kZXMgPSBmdW5jdGlvbiAocmVjdEEsIHJlY3RCLCBkaXJlY3Rpb25zKVxue1xuICBpZiAocmVjdEEuZ2V0Q2VudGVyWCgpIDwgcmVjdEIuZ2V0Q2VudGVyWCgpKVxuICB7XG4gICAgZGlyZWN0aW9uc1swXSA9IC0xO1xuICB9XG4gIGVsc2VcbiAge1xuICAgIGRpcmVjdGlvbnNbMF0gPSAxO1xuICB9XG5cbiAgaWYgKHJlY3RBLmdldENlbnRlclkoKSA8IHJlY3RCLmdldENlbnRlclkoKSlcbiAge1xuICAgIGRpcmVjdGlvbnNbMV0gPSAtMTtcbiAgfVxuICBlbHNlXG4gIHtcbiAgICBkaXJlY3Rpb25zWzFdID0gMTtcbiAgfVxufVxuXG5JR2VvbWV0cnkuZ2V0SW50ZXJzZWN0aW9uMiA9IGZ1bmN0aW9uIChyZWN0QSwgcmVjdEIsIHJlc3VsdClcbntcbiAgLy9yZXN1bHRbMC0xXSB3aWxsIGNvbnRhaW4gY2xpcFBvaW50IG9mIHJlY3RBLCByZXN1bHRbMi0zXSB3aWxsIGNvbnRhaW4gY2xpcFBvaW50IG9mIHJlY3RCXG4gIHZhciBwMXggPSByZWN0QS5nZXRDZW50ZXJYKCk7XG4gIHZhciBwMXkgPSByZWN0QS5nZXRDZW50ZXJZKCk7XG4gIHZhciBwMnggPSByZWN0Qi5nZXRDZW50ZXJYKCk7XG4gIHZhciBwMnkgPSByZWN0Qi5nZXRDZW50ZXJZKCk7XG5cbiAgLy9pZiB0d28gcmVjdGFuZ2xlcyBpbnRlcnNlY3QsIHRoZW4gY2xpcHBpbmcgcG9pbnRzIGFyZSBjZW50ZXJzXG4gIGlmIChyZWN0QS5pbnRlcnNlY3RzKHJlY3RCKSlcbiAge1xuICAgIHJlc3VsdFswXSA9IHAxeDtcbiAgICByZXN1bHRbMV0gPSBwMXk7XG4gICAgcmVzdWx0WzJdID0gcDJ4O1xuICAgIHJlc3VsdFszXSA9IHAyeTtcbiAgICByZXR1cm4gdHJ1ZTtcbiAgfVxuICAvL3ZhcmlhYmxlcyBmb3IgcmVjdEFcbiAgdmFyIHRvcExlZnRBeCA9IHJlY3RBLmdldFgoKTtcbiAgdmFyIHRvcExlZnRBeSA9IHJlY3RBLmdldFkoKTtcbiAgdmFyIHRvcFJpZ2h0QXggPSByZWN0QS5nZXRSaWdodCgpO1xuICB2YXIgYm90dG9tTGVmdEF4ID0gcmVjdEEuZ2V0WCgpO1xuICB2YXIgYm90dG9tTGVmdEF5ID0gcmVjdEEuZ2V0Qm90dG9tKCk7XG4gIHZhciBib3R0b21SaWdodEF4ID0gcmVjdEEuZ2V0UmlnaHQoKTtcbiAgdmFyIGhhbGZXaWR0aEEgPSByZWN0QS5nZXRXaWR0aEhhbGYoKTtcbiAgdmFyIGhhbGZIZWlnaHRBID0gcmVjdEEuZ2V0SGVpZ2h0SGFsZigpO1xuICAvL3ZhcmlhYmxlcyBmb3IgcmVjdEJcbiAgdmFyIHRvcExlZnRCeCA9IHJlY3RCLmdldFgoKTtcbiAgdmFyIHRvcExlZnRCeSA9IHJlY3RCLmdldFkoKTtcbiAgdmFyIHRvcFJpZ2h0QnggPSByZWN0Qi5nZXRSaWdodCgpO1xuICB2YXIgYm90dG9tTGVmdEJ4ID0gcmVjdEIuZ2V0WCgpO1xuICB2YXIgYm90dG9tTGVmdEJ5ID0gcmVjdEIuZ2V0Qm90dG9tKCk7XG4gIHZhciBib3R0b21SaWdodEJ4ID0gcmVjdEIuZ2V0UmlnaHQoKTtcbiAgdmFyIGhhbGZXaWR0aEIgPSByZWN0Qi5nZXRXaWR0aEhhbGYoKTtcbiAgdmFyIGhhbGZIZWlnaHRCID0gcmVjdEIuZ2V0SGVpZ2h0SGFsZigpO1xuICAvL2ZsYWcgd2hldGhlciBjbGlwcGluZyBwb2ludHMgYXJlIGZvdW5kXG4gIHZhciBjbGlwUG9pbnRBRm91bmQgPSBmYWxzZTtcbiAgdmFyIGNsaXBQb2ludEJGb3VuZCA9IGZhbHNlO1xuXG4gIC8vIGxpbmUgaXMgdmVydGljYWxcbiAgaWYgKHAxeCA9PSBwMngpXG4gIHtcbiAgICBpZiAocDF5ID4gcDJ5KVxuICAgIHtcbiAgICAgIHJlc3VsdFswXSA9IHAxeDtcbiAgICAgIHJlc3VsdFsxXSA9IHRvcExlZnRBeTtcbiAgICAgIHJlc3VsdFsyXSA9IHAyeDtcbiAgICAgIHJlc3VsdFszXSA9IGJvdHRvbUxlZnRCeTtcbiAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG4gICAgZWxzZSBpZiAocDF5IDwgcDJ5KVxuICAgIHtcbiAgICAgIHJlc3VsdFswXSA9IHAxeDtcbiAgICAgIHJlc3VsdFsxXSA9IGJvdHRvbUxlZnRBeTtcbiAgICAgIHJlc3VsdFsyXSA9IHAyeDtcbiAgICAgIHJlc3VsdFszXSA9IHRvcExlZnRCeTtcbiAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG4gICAgZWxzZVxuICAgIHtcbiAgICAgIC8vbm90IGxpbmUsIHJldHVybiBudWxsO1xuICAgIH1cbiAgfVxuICAvLyBsaW5lIGlzIGhvcml6b250YWxcbiAgZWxzZSBpZiAocDF5ID09IHAyeSlcbiAge1xuICAgIGlmIChwMXggPiBwMngpXG4gICAge1xuICAgICAgcmVzdWx0WzBdID0gdG9wTGVmdEF4O1xuICAgICAgcmVzdWx0WzFdID0gcDF5O1xuICAgICAgcmVzdWx0WzJdID0gdG9wUmlnaHRCeDtcbiAgICAgIHJlc3VsdFszXSA9IHAyeTtcbiAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG4gICAgZWxzZSBpZiAocDF4IDwgcDJ4KVxuICAgIHtcbiAgICAgIHJlc3VsdFswXSA9IHRvcFJpZ2h0QXg7XG4gICAgICByZXN1bHRbMV0gPSBwMXk7XG4gICAgICByZXN1bHRbMl0gPSB0b3BMZWZ0Qng7XG4gICAgICByZXN1bHRbM10gPSBwMnk7XG4gICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICAgIGVsc2VcbiAgICB7XG4gICAgICAvL25vdCB2YWxpZCBsaW5lLCByZXR1cm4gbnVsbDtcbiAgICB9XG4gIH1cbiAgZWxzZVxuICB7XG4gICAgLy9zbG9wZXMgb2YgcmVjdEEncyBhbmQgcmVjdEIncyBkaWFnb25hbHNcbiAgICB2YXIgc2xvcGVBID0gcmVjdEEuaGVpZ2h0IC8gcmVjdEEud2lkdGg7XG4gICAgdmFyIHNsb3BlQiA9IHJlY3RCLmhlaWdodCAvIHJlY3RCLndpZHRoO1xuXG4gICAgLy9zbG9wZSBvZiBsaW5lIGJldHdlZW4gY2VudGVyIG9mIHJlY3RBIGFuZCBjZW50ZXIgb2YgcmVjdEJcbiAgICB2YXIgc2xvcGVQcmltZSA9IChwMnkgLSBwMXkpIC8gKHAyeCAtIHAxeCk7XG4gICAgdmFyIGNhcmRpbmFsRGlyZWN0aW9uQTtcbiAgICB2YXIgY2FyZGluYWxEaXJlY3Rpb25CO1xuICAgIHZhciB0ZW1wUG9pbnRBeDtcbiAgICB2YXIgdGVtcFBvaW50QXk7XG4gICAgdmFyIHRlbXBQb2ludEJ4O1xuICAgIHZhciB0ZW1wUG9pbnRCeTtcblxuICAgIC8vZGV0ZXJtaW5lIHdoZXRoZXIgY2xpcHBpbmcgcG9pbnQgaXMgdGhlIGNvcm5lciBvZiBub2RlQVxuICAgIGlmICgoLXNsb3BlQSkgPT0gc2xvcGVQcmltZSlcbiAgICB7XG4gICAgICBpZiAocDF4ID4gcDJ4KVxuICAgICAge1xuICAgICAgICByZXN1bHRbMF0gPSBib3R0b21MZWZ0QXg7XG4gICAgICAgIHJlc3VsdFsxXSA9IGJvdHRvbUxlZnRBeTtcbiAgICAgICAgY2xpcFBvaW50QUZvdW5kID0gdHJ1ZTtcbiAgICAgIH1cbiAgICAgIGVsc2VcbiAgICAgIHtcbiAgICAgICAgcmVzdWx0WzBdID0gdG9wUmlnaHRBeDtcbiAgICAgICAgcmVzdWx0WzFdID0gdG9wTGVmdEF5O1xuICAgICAgICBjbGlwUG9pbnRBRm91bmQgPSB0cnVlO1xuICAgICAgfVxuICAgIH1cbiAgICBlbHNlIGlmIChzbG9wZUEgPT0gc2xvcGVQcmltZSlcbiAgICB7XG4gICAgICBpZiAocDF4ID4gcDJ4KVxuICAgICAge1xuICAgICAgICByZXN1bHRbMF0gPSB0b3BMZWZ0QXg7XG4gICAgICAgIHJlc3VsdFsxXSA9IHRvcExlZnRBeTtcbiAgICAgICAgY2xpcFBvaW50QUZvdW5kID0gdHJ1ZTtcbiAgICAgIH1cbiAgICAgIGVsc2VcbiAgICAgIHtcbiAgICAgICAgcmVzdWx0WzBdID0gYm90dG9tUmlnaHRBeDtcbiAgICAgICAgcmVzdWx0WzFdID0gYm90dG9tTGVmdEF5O1xuICAgICAgICBjbGlwUG9pbnRBRm91bmQgPSB0cnVlO1xuICAgICAgfVxuICAgIH1cblxuICAgIC8vZGV0ZXJtaW5lIHdoZXRoZXIgY2xpcHBpbmcgcG9pbnQgaXMgdGhlIGNvcm5lciBvZiBub2RlQlxuICAgIGlmICgoLXNsb3BlQikgPT0gc2xvcGVQcmltZSlcbiAgICB7XG4gICAgICBpZiAocDJ4ID4gcDF4KVxuICAgICAge1xuICAgICAgICByZXN1bHRbMl0gPSBib3R0b21MZWZ0Qng7XG4gICAgICAgIHJlc3VsdFszXSA9IGJvdHRvbUxlZnRCeTtcbiAgICAgICAgY2xpcFBvaW50QkZvdW5kID0gdHJ1ZTtcbiAgICAgIH1cbiAgICAgIGVsc2VcbiAgICAgIHtcbiAgICAgICAgcmVzdWx0WzJdID0gdG9wUmlnaHRCeDtcbiAgICAgICAgcmVzdWx0WzNdID0gdG9wTGVmdEJ5O1xuICAgICAgICBjbGlwUG9pbnRCRm91bmQgPSB0cnVlO1xuICAgICAgfVxuICAgIH1cbiAgICBlbHNlIGlmIChzbG9wZUIgPT0gc2xvcGVQcmltZSlcbiAgICB7XG4gICAgICBpZiAocDJ4ID4gcDF4KVxuICAgICAge1xuICAgICAgICByZXN1bHRbMl0gPSB0b3BMZWZ0Qng7XG4gICAgICAgIHJlc3VsdFszXSA9IHRvcExlZnRCeTtcbiAgICAgICAgY2xpcFBvaW50QkZvdW5kID0gdHJ1ZTtcbiAgICAgIH1cbiAgICAgIGVsc2VcbiAgICAgIHtcbiAgICAgICAgcmVzdWx0WzJdID0gYm90dG9tUmlnaHRCeDtcbiAgICAgICAgcmVzdWx0WzNdID0gYm90dG9tTGVmdEJ5O1xuICAgICAgICBjbGlwUG9pbnRCRm91bmQgPSB0cnVlO1xuICAgICAgfVxuICAgIH1cblxuICAgIC8vaWYgYm90aCBjbGlwcGluZyBwb2ludHMgYXJlIGNvcm5lcnNcbiAgICBpZiAoY2xpcFBvaW50QUZvdW5kICYmIGNsaXBQb2ludEJGb3VuZClcbiAgICB7XG4gICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuXG4gICAgLy9kZXRlcm1pbmUgQ2FyZGluYWwgRGlyZWN0aW9uIG9mIHJlY3RhbmdsZXNcbiAgICBpZiAocDF4ID4gcDJ4KVxuICAgIHtcbiAgICAgIGlmIChwMXkgPiBwMnkpXG4gICAgICB7XG4gICAgICAgIGNhcmRpbmFsRGlyZWN0aW9uQSA9IElHZW9tZXRyeS5nZXRDYXJkaW5hbERpcmVjdGlvbihzbG9wZUEsIHNsb3BlUHJpbWUsIDQpO1xuICAgICAgICBjYXJkaW5hbERpcmVjdGlvbkIgPSBJR2VvbWV0cnkuZ2V0Q2FyZGluYWxEaXJlY3Rpb24oc2xvcGVCLCBzbG9wZVByaW1lLCAyKTtcbiAgICAgIH1cbiAgICAgIGVsc2VcbiAgICAgIHtcbiAgICAgICAgY2FyZGluYWxEaXJlY3Rpb25BID0gSUdlb21ldHJ5LmdldENhcmRpbmFsRGlyZWN0aW9uKC1zbG9wZUEsIHNsb3BlUHJpbWUsIDMpO1xuICAgICAgICBjYXJkaW5hbERpcmVjdGlvbkIgPSBJR2VvbWV0cnkuZ2V0Q2FyZGluYWxEaXJlY3Rpb24oLXNsb3BlQiwgc2xvcGVQcmltZSwgMSk7XG4gICAgICB9XG4gICAgfVxuICAgIGVsc2VcbiAgICB7XG4gICAgICBpZiAocDF5ID4gcDJ5KVxuICAgICAge1xuICAgICAgICBjYXJkaW5hbERpcmVjdGlvbkEgPSBJR2VvbWV0cnkuZ2V0Q2FyZGluYWxEaXJlY3Rpb24oLXNsb3BlQSwgc2xvcGVQcmltZSwgMSk7XG4gICAgICAgIGNhcmRpbmFsRGlyZWN0aW9uQiA9IElHZW9tZXRyeS5nZXRDYXJkaW5hbERpcmVjdGlvbigtc2xvcGVCLCBzbG9wZVByaW1lLCAzKTtcbiAgICAgIH1cbiAgICAgIGVsc2VcbiAgICAgIHtcbiAgICAgICAgY2FyZGluYWxEaXJlY3Rpb25BID0gSUdlb21ldHJ5LmdldENhcmRpbmFsRGlyZWN0aW9uKHNsb3BlQSwgc2xvcGVQcmltZSwgMik7XG4gICAgICAgIGNhcmRpbmFsRGlyZWN0aW9uQiA9IElHZW9tZXRyeS5nZXRDYXJkaW5hbERpcmVjdGlvbihzbG9wZUIsIHNsb3BlUHJpbWUsIDQpO1xuICAgICAgfVxuICAgIH1cbiAgICAvL2NhbGN1bGF0ZSBjbGlwcGluZyBQb2ludCBpZiBpdCBpcyBub3QgZm91bmQgYmVmb3JlXG4gICAgaWYgKCFjbGlwUG9pbnRBRm91bmQpXG4gICAge1xuICAgICAgc3dpdGNoIChjYXJkaW5hbERpcmVjdGlvbkEpXG4gICAgICB7XG4gICAgICAgIGNhc2UgMTpcbiAgICAgICAgICB0ZW1wUG9pbnRBeSA9IHRvcExlZnRBeTtcbiAgICAgICAgICB0ZW1wUG9pbnRBeCA9IHAxeCArICgtaGFsZkhlaWdodEEpIC8gc2xvcGVQcmltZTtcbiAgICAgICAgICByZXN1bHRbMF0gPSB0ZW1wUG9pbnRBeDtcbiAgICAgICAgICByZXN1bHRbMV0gPSB0ZW1wUG9pbnRBeTtcbiAgICAgICAgICBicmVhaztcbiAgICAgICAgY2FzZSAyOlxuICAgICAgICAgIHRlbXBQb2ludEF4ID0gYm90dG9tUmlnaHRBeDtcbiAgICAgICAgICB0ZW1wUG9pbnRBeSA9IHAxeSArIGhhbGZXaWR0aEEgKiBzbG9wZVByaW1lO1xuICAgICAgICAgIHJlc3VsdFswXSA9IHRlbXBQb2ludEF4O1xuICAgICAgICAgIHJlc3VsdFsxXSA9IHRlbXBQb2ludEF5O1xuICAgICAgICAgIGJyZWFrO1xuICAgICAgICBjYXNlIDM6XG4gICAgICAgICAgdGVtcFBvaW50QXkgPSBib3R0b21MZWZ0QXk7XG4gICAgICAgICAgdGVtcFBvaW50QXggPSBwMXggKyBoYWxmSGVpZ2h0QSAvIHNsb3BlUHJpbWU7XG4gICAgICAgICAgcmVzdWx0WzBdID0gdGVtcFBvaW50QXg7XG4gICAgICAgICAgcmVzdWx0WzFdID0gdGVtcFBvaW50QXk7XG4gICAgICAgICAgYnJlYWs7XG4gICAgICAgIGNhc2UgNDpcbiAgICAgICAgICB0ZW1wUG9pbnRBeCA9IGJvdHRvbUxlZnRBeDtcbiAgICAgICAgICB0ZW1wUG9pbnRBeSA9IHAxeSArICgtaGFsZldpZHRoQSkgKiBzbG9wZVByaW1lO1xuICAgICAgICAgIHJlc3VsdFswXSA9IHRlbXBQb2ludEF4O1xuICAgICAgICAgIHJlc3VsdFsxXSA9IHRlbXBQb2ludEF5O1xuICAgICAgICAgIGJyZWFrO1xuICAgICAgfVxuICAgIH1cbiAgICBpZiAoIWNsaXBQb2ludEJGb3VuZClcbiAgICB7XG4gICAgICBzd2l0Y2ggKGNhcmRpbmFsRGlyZWN0aW9uQilcbiAgICAgIHtcbiAgICAgICAgY2FzZSAxOlxuICAgICAgICAgIHRlbXBQb2ludEJ5ID0gdG9wTGVmdEJ5O1xuICAgICAgICAgIHRlbXBQb2ludEJ4ID0gcDJ4ICsgKC1oYWxmSGVpZ2h0QikgLyBzbG9wZVByaW1lO1xuICAgICAgICAgIHJlc3VsdFsyXSA9IHRlbXBQb2ludEJ4O1xuICAgICAgICAgIHJlc3VsdFszXSA9IHRlbXBQb2ludEJ5O1xuICAgICAgICAgIGJyZWFrO1xuICAgICAgICBjYXNlIDI6XG4gICAgICAgICAgdGVtcFBvaW50QnggPSBib3R0b21SaWdodEJ4O1xuICAgICAgICAgIHRlbXBQb2ludEJ5ID0gcDJ5ICsgaGFsZldpZHRoQiAqIHNsb3BlUHJpbWU7XG4gICAgICAgICAgcmVzdWx0WzJdID0gdGVtcFBvaW50Qng7XG4gICAgICAgICAgcmVzdWx0WzNdID0gdGVtcFBvaW50Qnk7XG4gICAgICAgICAgYnJlYWs7XG4gICAgICAgIGNhc2UgMzpcbiAgICAgICAgICB0ZW1wUG9pbnRCeSA9IGJvdHRvbUxlZnRCeTtcbiAgICAgICAgICB0ZW1wUG9pbnRCeCA9IHAyeCArIGhhbGZIZWlnaHRCIC8gc2xvcGVQcmltZTtcbiAgICAgICAgICByZXN1bHRbMl0gPSB0ZW1wUG9pbnRCeDtcbiAgICAgICAgICByZXN1bHRbM10gPSB0ZW1wUG9pbnRCeTtcbiAgICAgICAgICBicmVhaztcbiAgICAgICAgY2FzZSA0OlxuICAgICAgICAgIHRlbXBQb2ludEJ4ID0gYm90dG9tTGVmdEJ4O1xuICAgICAgICAgIHRlbXBQb2ludEJ5ID0gcDJ5ICsgKC1oYWxmV2lkdGhCKSAqIHNsb3BlUHJpbWU7XG4gICAgICAgICAgcmVzdWx0WzJdID0gdGVtcFBvaW50Qng7XG4gICAgICAgICAgcmVzdWx0WzNdID0gdGVtcFBvaW50Qnk7XG4gICAgICAgICAgYnJlYWs7XG4gICAgICB9XG4gICAgfVxuICB9XG4gIHJldHVybiBmYWxzZTtcbn1cblxuSUdlb21ldHJ5LmdldENhcmRpbmFsRGlyZWN0aW9uID0gZnVuY3Rpb24gKHNsb3BlLCBzbG9wZVByaW1lLCBsaW5lKVxue1xuICBpZiAoc2xvcGUgPiBzbG9wZVByaW1lKVxuICB7XG4gICAgcmV0dXJuIGxpbmU7XG4gIH1cbiAgZWxzZVxuICB7XG4gICAgcmV0dXJuIDEgKyBsaW5lICUgNDtcbiAgfVxufVxuXG5JR2VvbWV0cnkuZ2V0SW50ZXJzZWN0aW9uID0gZnVuY3Rpb24gKHMxLCBzMiwgZjEsIGYyKVxue1xuICBpZiAoZjIgPT0gbnVsbCkge1xuICAgIHJldHVybiBJR2VvbWV0cnkuZ2V0SW50ZXJzZWN0aW9uMihzMSwgczIsIGYxKTtcbiAgfVxuICB2YXIgeDEgPSBzMS54O1xuICB2YXIgeTEgPSBzMS55O1xuICB2YXIgeDIgPSBzMi54O1xuICB2YXIgeTIgPSBzMi55O1xuICB2YXIgeDMgPSBmMS54O1xuICB2YXIgeTMgPSBmMS55O1xuICB2YXIgeDQgPSBmMi54O1xuICB2YXIgeTQgPSBmMi55O1xuICB2YXIgeCwgeTsgLy8gaW50ZXJzZWN0aW9uIHBvaW50XG4gIHZhciBhMSwgYTIsIGIxLCBiMiwgYzEsIGMyOyAvLyBjb2VmZmljaWVudHMgb2YgbGluZSBlcW5zLlxuICB2YXIgZGVub207XG5cbiAgYTEgPSB5MiAtIHkxO1xuICBiMSA9IHgxIC0geDI7XG4gIGMxID0geDIgKiB5MSAtIHgxICogeTI7ICAvLyB7IGExKnggKyBiMSp5ICsgYzEgPSAwIGlzIGxpbmUgMSB9XG5cbiAgYTIgPSB5NCAtIHkzO1xuICBiMiA9IHgzIC0geDQ7XG4gIGMyID0geDQgKiB5MyAtIHgzICogeTQ7ICAvLyB7IGEyKnggKyBiMip5ICsgYzIgPSAwIGlzIGxpbmUgMiB9XG5cbiAgZGVub20gPSBhMSAqIGIyIC0gYTIgKiBiMTtcblxuICBpZiAoZGVub20gPT0gMClcbiAge1xuICAgIHJldHVybiBudWxsO1xuICB9XG5cbiAgeCA9IChiMSAqIGMyIC0gYjIgKiBjMSkgLyBkZW5vbTtcbiAgeSA9IChhMiAqIGMxIC0gYTEgKiBjMikgLyBkZW5vbTtcblxuICByZXR1cm4gbmV3IFBvaW50KHgsIHkpO1xufVxuXG4vLyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuLy8gU2VjdGlvbjogQ2xhc3MgQ29uc3RhbnRzXG4vLyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuLyoqXG4gKiBTb21lIHVzZWZ1bCBwcmUtY2FsY3VsYXRlZCBjb25zdGFudHNcbiAqL1xuSUdlb21ldHJ5LkhBTEZfUEkgPSAwLjUgKiBNYXRoLlBJO1xuSUdlb21ldHJ5Lk9ORV9BTkRfSEFMRl9QSSA9IDEuNSAqIE1hdGguUEk7XG5JR2VvbWV0cnkuVFdPX1BJID0gMi4wICogTWF0aC5QSTtcbklHZW9tZXRyeS5USFJFRV9QSSA9IDMuMCAqIE1hdGguUEk7XG5cbm1vZHVsZS5leHBvcnRzID0gSUdlb21ldHJ5O1xuIiwiZnVuY3Rpb24gSU1hdGgoKSB7XG59XG5cbi8qKlxuICogVGhpcyBtZXRob2QgcmV0dXJucyB0aGUgc2lnbiBvZiB0aGUgaW5wdXQgdmFsdWUuXG4gKi9cbklNYXRoLnNpZ24gPSBmdW5jdGlvbiAodmFsdWUpIHtcbiAgaWYgKHZhbHVlID4gMClcbiAge1xuICAgIHJldHVybiAxO1xuICB9XG4gIGVsc2UgaWYgKHZhbHVlIDwgMClcbiAge1xuICAgIHJldHVybiAtMTtcbiAgfVxuICBlbHNlXG4gIHtcbiAgICByZXR1cm4gMDtcbiAgfVxufVxuXG5JTWF0aC5mbG9vciA9IGZ1bmN0aW9uICh2YWx1ZSkge1xuICByZXR1cm4gdmFsdWUgPCAwID8gTWF0aC5jZWlsKHZhbHVlKSA6IE1hdGguZmxvb3IodmFsdWUpO1xufVxuXG5JTWF0aC5jZWlsID0gZnVuY3Rpb24gKHZhbHVlKSB7XG4gIHJldHVybiB2YWx1ZSA8IDAgPyBNYXRoLmZsb29yKHZhbHVlKSA6IE1hdGguY2VpbCh2YWx1ZSk7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gSU1hdGg7XG4iLCJmdW5jdGlvbiBJbnRlZ2VyKCkge1xufVxuXG5JbnRlZ2VyLk1BWF9WQUxVRSA9IDIxNDc0ODM2NDc7XG5JbnRlZ2VyLk1JTl9WQUxVRSA9IC0yMTQ3NDgzNjQ4O1xuXG5tb2R1bGUuZXhwb3J0cyA9IEludGVnZXI7XG4iLCJ2YXIgTEdyYXBoT2JqZWN0ID0gcmVxdWlyZSgnLi9MR3JhcGhPYmplY3QnKTtcbnZhciBJR2VvbWV0cnkgPSByZXF1aXJlKCcuL0lHZW9tZXRyeScpO1xudmFyIElNYXRoID0gcmVxdWlyZSgnLi9JTWF0aCcpO1xuXG5mdW5jdGlvbiBMRWRnZShzb3VyY2UsIHRhcmdldCwgdkVkZ2UpIHtcbiAgTEdyYXBoT2JqZWN0LmNhbGwodGhpcywgdkVkZ2UpO1xuXG4gIHRoaXMuaXNPdmVybGFwaW5nU291cmNlQW5kVGFyZ2V0ID0gZmFsc2U7XG4gIHRoaXMudkdyYXBoT2JqZWN0ID0gdkVkZ2U7XG4gIHRoaXMuYmVuZHBvaW50cyA9IFtdO1xuICB0aGlzLnNvdXJjZSA9IHNvdXJjZTtcbiAgdGhpcy50YXJnZXQgPSB0YXJnZXQ7XG59XG5cbkxFZGdlLnByb3RvdHlwZSA9IE9iamVjdC5jcmVhdGUoTEdyYXBoT2JqZWN0LnByb3RvdHlwZSk7XG5cbmZvciAodmFyIHByb3AgaW4gTEdyYXBoT2JqZWN0KSB7XG4gIExFZGdlW3Byb3BdID0gTEdyYXBoT2JqZWN0W3Byb3BdO1xufVxuXG5MRWRnZS5wcm90b3R5cGUuZ2V0U291cmNlID0gZnVuY3Rpb24gKClcbntcbiAgcmV0dXJuIHRoaXMuc291cmNlO1xufTtcblxuTEVkZ2UucHJvdG90eXBlLmdldFRhcmdldCA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLnRhcmdldDtcbn07XG5cbkxFZGdlLnByb3RvdHlwZS5pc0ludGVyR3JhcGggPSBmdW5jdGlvbiAoKVxue1xuICByZXR1cm4gdGhpcy5pc0ludGVyR3JhcGg7XG59O1xuXG5MRWRnZS5wcm90b3R5cGUuZ2V0TGVuZ3RoID0gZnVuY3Rpb24gKClcbntcbiAgcmV0dXJuIHRoaXMubGVuZ3RoO1xufTtcblxuTEVkZ2UucHJvdG90eXBlLmlzT3ZlcmxhcGluZ1NvdXJjZUFuZFRhcmdldCA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLmlzT3ZlcmxhcGluZ1NvdXJjZUFuZFRhcmdldDtcbn07XG5cbkxFZGdlLnByb3RvdHlwZS5nZXRCZW5kcG9pbnRzID0gZnVuY3Rpb24gKClcbntcbiAgcmV0dXJuIHRoaXMuYmVuZHBvaW50cztcbn07XG5cbkxFZGdlLnByb3RvdHlwZS5nZXRMY2EgPSBmdW5jdGlvbiAoKVxue1xuICByZXR1cm4gdGhpcy5sY2E7XG59O1xuXG5MRWRnZS5wcm90b3R5cGUuZ2V0U291cmNlSW5MY2EgPSBmdW5jdGlvbiAoKVxue1xuICByZXR1cm4gdGhpcy5zb3VyY2VJbkxjYTtcbn07XG5cbkxFZGdlLnByb3RvdHlwZS5nZXRUYXJnZXRJbkxjYSA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLnRhcmdldEluTGNhO1xufTtcblxuTEVkZ2UucHJvdG90eXBlLmdldE90aGVyRW5kID0gZnVuY3Rpb24gKG5vZGUpXG57XG4gIGlmICh0aGlzLnNvdXJjZSA9PT0gbm9kZSlcbiAge1xuICAgIHJldHVybiB0aGlzLnRhcmdldDtcbiAgfVxuICBlbHNlIGlmICh0aGlzLnRhcmdldCA9PT0gbm9kZSlcbiAge1xuICAgIHJldHVybiB0aGlzLnNvdXJjZTtcbiAgfVxuICBlbHNlXG4gIHtcbiAgICB0aHJvdyBcIk5vZGUgaXMgbm90IGluY2lkZW50IHdpdGggdGhpcyBlZGdlXCI7XG4gIH1cbn1cblxuTEVkZ2UucHJvdG90eXBlLmdldE90aGVyRW5kSW5HcmFwaCA9IGZ1bmN0aW9uIChub2RlLCBncmFwaClcbntcbiAgdmFyIG90aGVyRW5kID0gdGhpcy5nZXRPdGhlckVuZChub2RlKTtcbiAgdmFyIHJvb3QgPSBncmFwaC5nZXRHcmFwaE1hbmFnZXIoKS5nZXRSb290KCk7XG5cbiAgd2hpbGUgKHRydWUpXG4gIHtcbiAgICBpZiAob3RoZXJFbmQuZ2V0T3duZXIoKSA9PSBncmFwaClcbiAgICB7XG4gICAgICByZXR1cm4gb3RoZXJFbmQ7XG4gICAgfVxuXG4gICAgaWYgKG90aGVyRW5kLmdldE93bmVyKCkgPT0gcm9vdClcbiAgICB7XG4gICAgICBicmVhaztcbiAgICB9XG5cbiAgICBvdGhlckVuZCA9IG90aGVyRW5kLmdldE93bmVyKCkuZ2V0UGFyZW50KCk7XG4gIH1cblxuICByZXR1cm4gbnVsbDtcbn07XG5cbkxFZGdlLnByb3RvdHlwZS51cGRhdGVMZW5ndGggPSBmdW5jdGlvbiAoKVxue1xuICB2YXIgY2xpcFBvaW50Q29vcmRpbmF0ZXMgPSBuZXcgQXJyYXkoNCk7XG5cbiAgdGhpcy5pc092ZXJsYXBpbmdTb3VyY2VBbmRUYXJnZXQgPVxuICAgICAgICAgIElHZW9tZXRyeS5nZXRJbnRlcnNlY3Rpb24odGhpcy50YXJnZXQuZ2V0UmVjdCgpLFxuICAgICAgICAgICAgICAgICAgdGhpcy5zb3VyY2UuZ2V0UmVjdCgpLFxuICAgICAgICAgICAgICAgICAgY2xpcFBvaW50Q29vcmRpbmF0ZXMpO1xuXG4gIGlmICghdGhpcy5pc092ZXJsYXBpbmdTb3VyY2VBbmRUYXJnZXQpXG4gIHtcbiAgICB0aGlzLmxlbmd0aFggPSBjbGlwUG9pbnRDb29yZGluYXRlc1swXSAtIGNsaXBQb2ludENvb3JkaW5hdGVzWzJdO1xuICAgIHRoaXMubGVuZ3RoWSA9IGNsaXBQb2ludENvb3JkaW5hdGVzWzFdIC0gY2xpcFBvaW50Q29vcmRpbmF0ZXNbM107XG5cbiAgICBpZiAoTWF0aC5hYnModGhpcy5sZW5ndGhYKSA8IDEuMClcbiAgICB7XG4gICAgICB0aGlzLmxlbmd0aFggPSBJTWF0aC5zaWduKHRoaXMubGVuZ3RoWCk7XG4gICAgfVxuXG4gICAgaWYgKE1hdGguYWJzKHRoaXMubGVuZ3RoWSkgPCAxLjApXG4gICAge1xuICAgICAgdGhpcy5sZW5ndGhZID0gSU1hdGguc2lnbih0aGlzLmxlbmd0aFkpO1xuICAgIH1cblxuICAgIHRoaXMubGVuZ3RoID0gTWF0aC5zcXJ0KFxuICAgICAgICAgICAgdGhpcy5sZW5ndGhYICogdGhpcy5sZW5ndGhYICsgdGhpcy5sZW5ndGhZICogdGhpcy5sZW5ndGhZKTtcbiAgfVxufTtcblxuTEVkZ2UucHJvdG90eXBlLnVwZGF0ZUxlbmd0aFNpbXBsZSA9IGZ1bmN0aW9uICgpXG57XG4gIHRoaXMubGVuZ3RoWCA9IHRoaXMudGFyZ2V0LmdldENlbnRlclgoKSAtIHRoaXMuc291cmNlLmdldENlbnRlclgoKTtcbiAgdGhpcy5sZW5ndGhZID0gdGhpcy50YXJnZXQuZ2V0Q2VudGVyWSgpIC0gdGhpcy5zb3VyY2UuZ2V0Q2VudGVyWSgpO1xuXG4gIGlmIChNYXRoLmFicyh0aGlzLmxlbmd0aFgpIDwgMS4wKVxuICB7XG4gICAgdGhpcy5sZW5ndGhYID0gSU1hdGguc2lnbih0aGlzLmxlbmd0aFgpO1xuICB9XG5cbiAgaWYgKE1hdGguYWJzKHRoaXMubGVuZ3RoWSkgPCAxLjApXG4gIHtcbiAgICB0aGlzLmxlbmd0aFkgPSBJTWF0aC5zaWduKHRoaXMubGVuZ3RoWSk7XG4gIH1cblxuICB0aGlzLmxlbmd0aCA9IE1hdGguc3FydChcbiAgICAgICAgICB0aGlzLmxlbmd0aFggKiB0aGlzLmxlbmd0aFggKyB0aGlzLmxlbmd0aFkgKiB0aGlzLmxlbmd0aFkpO1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IExFZGdlO1xuIiwidmFyIExHcmFwaE9iamVjdCA9IHJlcXVpcmUoJy4vTEdyYXBoT2JqZWN0Jyk7XG52YXIgSW50ZWdlciA9IHJlcXVpcmUoJy4vSW50ZWdlcicpO1xudmFyIExheW91dENvbnN0YW50cyA9IHJlcXVpcmUoJy4vTGF5b3V0Q29uc3RhbnRzJyk7XG52YXIgTEdyYXBoTWFuYWdlciA9IHJlcXVpcmUoJy4vTEdyYXBoTWFuYWdlcicpO1xudmFyIExOb2RlID0gcmVxdWlyZSgnLi9MTm9kZScpO1xudmFyIEhhc2hTZXQgPSByZXF1aXJlKCcuL0hhc2hTZXQnKTtcbnZhciBSZWN0YW5nbGVEID0gcmVxdWlyZSgnLi9SZWN0YW5nbGVEJyk7XG52YXIgUG9pbnQgPSByZXF1aXJlKCcuL1BvaW50Jyk7XG5cbmZ1bmN0aW9uIExHcmFwaChwYXJlbnQsIG9iajIsIHZHcmFwaCkge1xuICBMR3JhcGhPYmplY3QuY2FsbCh0aGlzLCB2R3JhcGgpO1xuICB0aGlzLmVzdGltYXRlZFNpemUgPSBJbnRlZ2VyLk1JTl9WQUxVRTtcbiAgdGhpcy5tYXJnaW4gPSBMYXlvdXRDb25zdGFudHMuREVGQVVMVF9HUkFQSF9NQVJHSU47XG4gIHRoaXMuZWRnZXMgPSBbXTtcbiAgdGhpcy5ub2RlcyA9IFtdO1xuICB0aGlzLmlzQ29ubmVjdGVkID0gZmFsc2U7XG4gIHRoaXMucGFyZW50ID0gcGFyZW50O1xuXG4gIGlmIChvYmoyICE9IG51bGwgJiYgb2JqMiBpbnN0YW5jZW9mIExHcmFwaE1hbmFnZXIpIHtcbiAgICB0aGlzLmdyYXBoTWFuYWdlciA9IG9iajI7XG4gIH1cbiAgZWxzZSBpZiAob2JqMiAhPSBudWxsICYmIG9iajIgaW5zdGFuY2VvZiBMYXlvdXQpIHtcbiAgICB0aGlzLmdyYXBoTWFuYWdlciA9IG9iajIuZ3JhcGhNYW5hZ2VyO1xuICB9XG59XG5cbkxHcmFwaC5wcm90b3R5cGUgPSBPYmplY3QuY3JlYXRlKExHcmFwaE9iamVjdC5wcm90b3R5cGUpO1xuZm9yICh2YXIgcHJvcCBpbiBMR3JhcGhPYmplY3QpIHtcbiAgTEdyYXBoW3Byb3BdID0gTEdyYXBoT2JqZWN0W3Byb3BdO1xufVxuXG5MR3JhcGgucHJvdG90eXBlLmdldE5vZGVzID0gZnVuY3Rpb24gKCkge1xuICByZXR1cm4gdGhpcy5ub2Rlcztcbn07XG5cbkxHcmFwaC5wcm90b3R5cGUuZ2V0RWRnZXMgPSBmdW5jdGlvbiAoKSB7XG4gIHJldHVybiB0aGlzLmVkZ2VzO1xufTtcblxuTEdyYXBoLnByb3RvdHlwZS5nZXRHcmFwaE1hbmFnZXIgPSBmdW5jdGlvbiAoKVxue1xuICByZXR1cm4gdGhpcy5ncmFwaE1hbmFnZXI7XG59O1xuXG5MR3JhcGgucHJvdG90eXBlLmdldFBhcmVudCA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLnBhcmVudDtcbn07XG5cbkxHcmFwaC5wcm90b3R5cGUuZ2V0TGVmdCA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLmxlZnQ7XG59O1xuXG5MR3JhcGgucHJvdG90eXBlLmdldFJpZ2h0ID0gZnVuY3Rpb24gKClcbntcbiAgcmV0dXJuIHRoaXMucmlnaHQ7XG59O1xuXG5MR3JhcGgucHJvdG90eXBlLmdldFRvcCA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLnRvcDtcbn07XG5cbkxHcmFwaC5wcm90b3R5cGUuZ2V0Qm90dG9tID0gZnVuY3Rpb24gKClcbntcbiAgcmV0dXJuIHRoaXMuYm90dG9tO1xufTtcblxuTEdyYXBoLnByb3RvdHlwZS5pc0Nvbm5lY3RlZCA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLmlzQ29ubmVjdGVkO1xufTtcblxuTEdyYXBoLnByb3RvdHlwZS5hZGQgPSBmdW5jdGlvbiAob2JqMSwgc291cmNlTm9kZSwgdGFyZ2V0Tm9kZSkge1xuICBpZiAoc291cmNlTm9kZSA9PSBudWxsICYmIHRhcmdldE5vZGUgPT0gbnVsbCkge1xuICAgIHZhciBuZXdOb2RlID0gb2JqMTtcbiAgICBpZiAodGhpcy5ncmFwaE1hbmFnZXIgPT0gbnVsbCkge1xuICAgICAgdGhyb3cgXCJHcmFwaCBoYXMgbm8gZ3JhcGggbWdyIVwiO1xuICAgIH1cbiAgICBpZiAodGhpcy5nZXROb2RlcygpLmluZGV4T2YobmV3Tm9kZSkgPiAtMSkge1xuICAgICAgdGhyb3cgXCJOb2RlIGFscmVhZHkgaW4gZ3JhcGghXCI7XG4gICAgfVxuICAgIG5ld05vZGUub3duZXIgPSB0aGlzO1xuICAgIHRoaXMuZ2V0Tm9kZXMoKS5wdXNoKG5ld05vZGUpO1xuXG4gICAgcmV0dXJuIG5ld05vZGU7XG4gIH1cbiAgZWxzZSB7XG4gICAgdmFyIG5ld0VkZ2UgPSBvYmoxO1xuICAgIGlmICghKHRoaXMuZ2V0Tm9kZXMoKS5pbmRleE9mKHNvdXJjZU5vZGUpID4gLTEgJiYgKHRoaXMuZ2V0Tm9kZXMoKS5pbmRleE9mKHRhcmdldE5vZGUpKSA+IC0xKSkge1xuICAgICAgdGhyb3cgXCJTb3VyY2Ugb3IgdGFyZ2V0IG5vdCBpbiBncmFwaCFcIjtcbiAgICB9XG5cbiAgICBpZiAoIShzb3VyY2VOb2RlLm93bmVyID09IHRhcmdldE5vZGUub3duZXIgJiYgc291cmNlTm9kZS5vd25lciA9PSB0aGlzKSkge1xuICAgICAgdGhyb3cgXCJCb3RoIG93bmVycyBtdXN0IGJlIHRoaXMgZ3JhcGghXCI7XG4gICAgfVxuXG4gICAgaWYgKHNvdXJjZU5vZGUub3duZXIgIT0gdGFyZ2V0Tm9kZS5vd25lcilcbiAgICB7XG4gICAgICByZXR1cm4gbnVsbDtcbiAgICB9XG5cbiAgICAvLyBzZXQgc291cmNlIGFuZCB0YXJnZXRcbiAgICBuZXdFZGdlLnNvdXJjZSA9IHNvdXJjZU5vZGU7XG4gICAgbmV3RWRnZS50YXJnZXQgPSB0YXJnZXROb2RlO1xuXG4gICAgLy8gc2V0IGFzIGludHJhLWdyYXBoIGVkZ2VcbiAgICBuZXdFZGdlLmlzSW50ZXJHcmFwaCA9IGZhbHNlO1xuXG4gICAgLy8gYWRkIHRvIGdyYXBoIGVkZ2UgbGlzdFxuICAgIHRoaXMuZ2V0RWRnZXMoKS5wdXNoKG5ld0VkZ2UpO1xuXG4gICAgLy8gYWRkIHRvIGluY2lkZW5jeSBsaXN0c1xuICAgIHNvdXJjZU5vZGUuZWRnZXMucHVzaChuZXdFZGdlKTtcblxuICAgIGlmICh0YXJnZXROb2RlICE9IHNvdXJjZU5vZGUpXG4gICAge1xuICAgICAgdGFyZ2V0Tm9kZS5lZGdlcy5wdXNoKG5ld0VkZ2UpO1xuICAgIH1cblxuICAgIHJldHVybiBuZXdFZGdlO1xuICB9XG59O1xuXG5MR3JhcGgucHJvdG90eXBlLnJlbW92ZSA9IGZ1bmN0aW9uIChvYmopIHtcbiAgdmFyIG5vZGUgPSBvYmo7XG4gIGlmIChvYmogaW5zdGFuY2VvZiBMTm9kZSkge1xuICAgIGlmIChub2RlID09IG51bGwpIHtcbiAgICAgIHRocm93IFwiTm9kZSBpcyBudWxsIVwiO1xuICAgIH1cbiAgICBpZiAoIShub2RlLm93bmVyICE9IG51bGwgJiYgbm9kZS5vd25lciA9PSB0aGlzKSkge1xuICAgICAgdGhyb3cgXCJPd25lciBncmFwaCBpcyBpbnZhbGlkIVwiO1xuICAgIH1cbiAgICBpZiAodGhpcy5ncmFwaE1hbmFnZXIgPT0gbnVsbCkge1xuICAgICAgdGhyb3cgXCJPd25lciBncmFwaCBtYW5hZ2VyIGlzIGludmFsaWQhXCI7XG4gICAgfVxuICAgIC8vIHJlbW92ZSBpbmNpZGVudCBlZGdlcyBmaXJzdCAobWFrZSBhIGNvcHkgdG8gZG8gaXQgc2FmZWx5KVxuICAgIHZhciBlZGdlc1RvQmVSZW1vdmVkID0gbm9kZS5lZGdlcy5zbGljZSgpO1xuICAgIHZhciBlZGdlO1xuICAgIHZhciBzID0gZWRnZXNUb0JlUmVtb3ZlZC5sZW5ndGg7XG4gICAgZm9yICh2YXIgaSA9IDA7IGkgPCBzOyBpKyspXG4gICAge1xuICAgICAgZWRnZSA9IGVkZ2VzVG9CZVJlbW92ZWRbaV07XG5cbiAgICAgIGlmIChlZGdlLmlzSW50ZXJHcmFwaClcbiAgICAgIHtcbiAgICAgICAgdGhpcy5ncmFwaE1hbmFnZXIucmVtb3ZlKGVkZ2UpO1xuICAgICAgfVxuICAgICAgZWxzZVxuICAgICAge1xuICAgICAgICBlZGdlLnNvdXJjZS5vd25lci5yZW1vdmUoZWRnZSk7XG4gICAgICB9XG4gICAgfVxuXG4gICAgLy8gbm93IHRoZSBub2RlIGl0c2VsZlxuICAgIHZhciBpbmRleCA9IHRoaXMubm9kZXMuaW5kZXhPZihub2RlKTtcbiAgICBpZiAoaW5kZXggPT0gLTEpIHtcbiAgICAgIHRocm93IFwiTm9kZSBub3QgaW4gb3duZXIgbm9kZSBsaXN0IVwiO1xuICAgIH1cblxuICAgIHRoaXMubm9kZXMuc3BsaWNlKGluZGV4LCAxKTtcbiAgfVxuICBlbHNlIGlmIChvYmogaW5zdGFuY2VvZiBMRWRnZSkge1xuICAgIHZhciBlZGdlID0gb2JqO1xuICAgIGlmIChlZGdlID09IG51bGwpIHtcbiAgICAgIHRocm93IFwiRWRnZSBpcyBudWxsIVwiO1xuICAgIH1cbiAgICBpZiAoIShlZGdlLnNvdXJjZSAhPSBudWxsICYmIGVkZ2UudGFyZ2V0ICE9IG51bGwpKSB7XG4gICAgICB0aHJvdyBcIlNvdXJjZSBhbmQvb3IgdGFyZ2V0IGlzIG51bGwhXCI7XG4gICAgfVxuICAgIGlmICghKGVkZ2Uuc291cmNlLm93bmVyICE9IG51bGwgJiYgZWRnZS50YXJnZXQub3duZXIgIT0gbnVsbCAmJlxuICAgICAgICAgICAgZWRnZS5zb3VyY2Uub3duZXIgPT0gdGhpcyAmJiBlZGdlLnRhcmdldC5vd25lciA9PSB0aGlzKSkge1xuICAgICAgdGhyb3cgXCJTb3VyY2UgYW5kL29yIHRhcmdldCBvd25lciBpcyBpbnZhbGlkIVwiO1xuICAgIH1cblxuICAgIHZhciBzb3VyY2VJbmRleCA9IGVkZ2Uuc291cmNlLmVkZ2VzLmluZGV4T2YoZWRnZSk7XG4gICAgdmFyIHRhcmdldEluZGV4ID0gZWRnZS50YXJnZXQuZWRnZXMuaW5kZXhPZihlZGdlKTtcbiAgICBpZiAoIShzb3VyY2VJbmRleCA+IC0xICYmIHRhcmdldEluZGV4ID4gLTEpKSB7XG4gICAgICB0aHJvdyBcIlNvdXJjZSBhbmQvb3IgdGFyZ2V0IGRvZXNuJ3Qga25vdyB0aGlzIGVkZ2UhXCI7XG4gICAgfVxuXG4gICAgZWRnZS5zb3VyY2UuZWRnZXMuc3BsaWNlKHNvdXJjZUluZGV4LCAxKTtcblxuICAgIGlmIChlZGdlLnRhcmdldCAhPSBlZGdlLnNvdXJjZSlcbiAgICB7XG4gICAgICBlZGdlLnRhcmdldC5lZGdlcy5zcGxpY2UodGFyZ2V0SW5kZXgsIDEpO1xuICAgIH1cblxuICAgIHZhciBpbmRleCA9IGVkZ2Uuc291cmNlLm93bmVyLmdldEVkZ2VzKCkuaW5kZXhPZihlZGdlKTtcbiAgICBpZiAoaW5kZXggPT0gLTEpIHtcbiAgICAgIHRocm93IFwiTm90IGluIG93bmVyJ3MgZWRnZSBsaXN0IVwiO1xuICAgIH1cblxuICAgIGVkZ2Uuc291cmNlLm93bmVyLmdldEVkZ2VzKCkuc3BsaWNlKGluZGV4LCAxKTtcbiAgfVxufTtcblxuTEdyYXBoLnByb3RvdHlwZS51cGRhdGVMZWZ0VG9wID0gZnVuY3Rpb24gKClcbntcbiAgdmFyIHRvcCA9IEludGVnZXIuTUFYX1ZBTFVFO1xuICB2YXIgbGVmdCA9IEludGVnZXIuTUFYX1ZBTFVFO1xuICB2YXIgbm9kZVRvcDtcbiAgdmFyIG5vZGVMZWZ0O1xuXG4gIHZhciBub2RlcyA9IHRoaXMuZ2V0Tm9kZXMoKTtcbiAgdmFyIHMgPSBub2Rlcy5sZW5ndGg7XG5cbiAgZm9yICh2YXIgaSA9IDA7IGkgPCBzOyBpKyspXG4gIHtcbiAgICB2YXIgbE5vZGUgPSBub2Rlc1tpXTtcbiAgICBub2RlVG9wID0gTWF0aC5mbG9vcihsTm9kZS5nZXRUb3AoKSk7XG4gICAgbm9kZUxlZnQgPSBNYXRoLmZsb29yKGxOb2RlLmdldExlZnQoKSk7XG5cbiAgICBpZiAodG9wID4gbm9kZVRvcClcbiAgICB7XG4gICAgICB0b3AgPSBub2RlVG9wO1xuICAgIH1cblxuICAgIGlmIChsZWZ0ID4gbm9kZUxlZnQpXG4gICAge1xuICAgICAgbGVmdCA9IG5vZGVMZWZ0O1xuICAgIH1cbiAgfVxuXG4gIC8vIERvIHdlIGhhdmUgYW55IG5vZGVzIGluIHRoaXMgZ3JhcGg/XG4gIGlmICh0b3AgPT0gSW50ZWdlci5NQVhfVkFMVUUpXG4gIHtcbiAgICByZXR1cm4gbnVsbDtcbiAgfVxuXG4gIHRoaXMubGVmdCA9IGxlZnQgLSB0aGlzLm1hcmdpbjtcbiAgdGhpcy50b3AgPSB0b3AgLSB0aGlzLm1hcmdpbjtcblxuICAvLyBBcHBseSB0aGUgbWFyZ2lucyBhbmQgcmV0dXJuIHRoZSByZXN1bHRcbiAgcmV0dXJuIG5ldyBQb2ludCh0aGlzLmxlZnQsIHRoaXMudG9wKTtcbn07XG5cbkxHcmFwaC5wcm90b3R5cGUudXBkYXRlQm91bmRzID0gZnVuY3Rpb24gKHJlY3Vyc2l2ZSlcbntcbiAgLy8gY2FsY3VsYXRlIGJvdW5kc1xuICB2YXIgbGVmdCA9IEludGVnZXIuTUFYX1ZBTFVFO1xuICB2YXIgcmlnaHQgPSAtSW50ZWdlci5NQVhfVkFMVUU7XG4gIHZhciB0b3AgPSBJbnRlZ2VyLk1BWF9WQUxVRTtcbiAgdmFyIGJvdHRvbSA9IC1JbnRlZ2VyLk1BWF9WQUxVRTtcbiAgdmFyIG5vZGVMZWZ0O1xuICB2YXIgbm9kZVJpZ2h0O1xuICB2YXIgbm9kZVRvcDtcbiAgdmFyIG5vZGVCb3R0b207XG5cbiAgdmFyIG5vZGVzID0gdGhpcy5ub2RlcztcbiAgdmFyIHMgPSBub2Rlcy5sZW5ndGg7XG4gIGZvciAodmFyIGkgPSAwOyBpIDwgczsgaSsrKVxuICB7XG4gICAgdmFyIGxOb2RlID0gbm9kZXNbaV07XG5cbiAgICBpZiAocmVjdXJzaXZlICYmIGxOb2RlLmNoaWxkICE9IG51bGwpXG4gICAge1xuICAgICAgbE5vZGUudXBkYXRlQm91bmRzKCk7XG4gICAgfVxuICAgIG5vZGVMZWZ0ID0gTWF0aC5mbG9vcihsTm9kZS5nZXRMZWZ0KCkpO1xuICAgIG5vZGVSaWdodCA9IE1hdGguZmxvb3IobE5vZGUuZ2V0UmlnaHQoKSk7XG4gICAgbm9kZVRvcCA9IE1hdGguZmxvb3IobE5vZGUuZ2V0VG9wKCkpO1xuICAgIG5vZGVCb3R0b20gPSBNYXRoLmZsb29yKGxOb2RlLmdldEJvdHRvbSgpKTtcblxuICAgIGlmIChsZWZ0ID4gbm9kZUxlZnQpXG4gICAge1xuICAgICAgbGVmdCA9IG5vZGVMZWZ0O1xuICAgIH1cblxuICAgIGlmIChyaWdodCA8IG5vZGVSaWdodClcbiAgICB7XG4gICAgICByaWdodCA9IG5vZGVSaWdodDtcbiAgICB9XG5cbiAgICBpZiAodG9wID4gbm9kZVRvcClcbiAgICB7XG4gICAgICB0b3AgPSBub2RlVG9wO1xuICAgIH1cblxuICAgIGlmIChib3R0b20gPCBub2RlQm90dG9tKVxuICAgIHtcbiAgICAgIGJvdHRvbSA9IG5vZGVCb3R0b207XG4gICAgfVxuICB9XG5cbiAgdmFyIGJvdW5kaW5nUmVjdCA9IG5ldyBSZWN0YW5nbGVEKGxlZnQsIHRvcCwgcmlnaHQgLSBsZWZ0LCBib3R0b20gLSB0b3ApO1xuICBpZiAobGVmdCA9PSBJbnRlZ2VyLk1BWF9WQUxVRSlcbiAge1xuICAgIHRoaXMubGVmdCA9IE1hdGguZmxvb3IodGhpcy5wYXJlbnQuZ2V0TGVmdCgpKTtcbiAgICB0aGlzLnJpZ2h0ID0gTWF0aC5mbG9vcih0aGlzLnBhcmVudC5nZXRSaWdodCgpKTtcbiAgICB0aGlzLnRvcCA9IE1hdGguZmxvb3IodGhpcy5wYXJlbnQuZ2V0VG9wKCkpO1xuICAgIHRoaXMuYm90dG9tID0gTWF0aC5mbG9vcih0aGlzLnBhcmVudC5nZXRCb3R0b20oKSk7XG4gIH1cblxuICB0aGlzLmxlZnQgPSBib3VuZGluZ1JlY3QueCAtIHRoaXMubWFyZ2luO1xuICB0aGlzLnJpZ2h0ID0gYm91bmRpbmdSZWN0LnggKyBib3VuZGluZ1JlY3Qud2lkdGggKyB0aGlzLm1hcmdpbjtcbiAgdGhpcy50b3AgPSBib3VuZGluZ1JlY3QueSAtIHRoaXMubWFyZ2luO1xuICB0aGlzLmJvdHRvbSA9IGJvdW5kaW5nUmVjdC55ICsgYm91bmRpbmdSZWN0LmhlaWdodCArIHRoaXMubWFyZ2luO1xufTtcblxuTEdyYXBoLmNhbGN1bGF0ZUJvdW5kcyA9IGZ1bmN0aW9uIChub2RlcylcbntcbiAgdmFyIGxlZnQgPSBJbnRlZ2VyLk1BWF9WQUxVRTtcbiAgdmFyIHJpZ2h0ID0gLUludGVnZXIuTUFYX1ZBTFVFO1xuICB2YXIgdG9wID0gSW50ZWdlci5NQVhfVkFMVUU7XG4gIHZhciBib3R0b20gPSAtSW50ZWdlci5NQVhfVkFMVUU7XG4gIHZhciBub2RlTGVmdDtcbiAgdmFyIG5vZGVSaWdodDtcbiAgdmFyIG5vZGVUb3A7XG4gIHZhciBub2RlQm90dG9tO1xuXG4gIHZhciBzID0gbm9kZXMubGVuZ3RoO1xuXG4gIGZvciAodmFyIGkgPSAwOyBpIDwgczsgaSsrKVxuICB7XG4gICAgdmFyIGxOb2RlID0gbm9kZXNbaV07XG4gICAgbm9kZUxlZnQgPSBNYXRoLmZsb29yKGxOb2RlLmdldExlZnQoKSk7XG4gICAgbm9kZVJpZ2h0ID0gTWF0aC5mbG9vcihsTm9kZS5nZXRSaWdodCgpKTtcbiAgICBub2RlVG9wID0gTWF0aC5mbG9vcihsTm9kZS5nZXRUb3AoKSk7XG4gICAgbm9kZUJvdHRvbSA9IE1hdGguZmxvb3IobE5vZGUuZ2V0Qm90dG9tKCkpO1xuXG4gICAgaWYgKGxlZnQgPiBub2RlTGVmdClcbiAgICB7XG4gICAgICBsZWZ0ID0gbm9kZUxlZnQ7XG4gICAgfVxuXG4gICAgaWYgKHJpZ2h0IDwgbm9kZVJpZ2h0KVxuICAgIHtcbiAgICAgIHJpZ2h0ID0gbm9kZVJpZ2h0O1xuICAgIH1cblxuICAgIGlmICh0b3AgPiBub2RlVG9wKVxuICAgIHtcbiAgICAgIHRvcCA9IG5vZGVUb3A7XG4gICAgfVxuXG4gICAgaWYgKGJvdHRvbSA8IG5vZGVCb3R0b20pXG4gICAge1xuICAgICAgYm90dG9tID0gbm9kZUJvdHRvbTtcbiAgICB9XG4gIH1cblxuICB2YXIgYm91bmRpbmdSZWN0ID0gbmV3IFJlY3RhbmdsZUQobGVmdCwgdG9wLCByaWdodCAtIGxlZnQsIGJvdHRvbSAtIHRvcCk7XG5cbiAgcmV0dXJuIGJvdW5kaW5nUmVjdDtcbn07XG5cbkxHcmFwaC5wcm90b3R5cGUuZ2V0SW5jbHVzaW9uVHJlZURlcHRoID0gZnVuY3Rpb24gKClcbntcbiAgaWYgKHRoaXMgPT0gdGhpcy5ncmFwaE1hbmFnZXIuZ2V0Um9vdCgpKVxuICB7XG4gICAgcmV0dXJuIDE7XG4gIH1cbiAgZWxzZVxuICB7XG4gICAgcmV0dXJuIHRoaXMucGFyZW50LmdldEluY2x1c2lvblRyZWVEZXB0aCgpO1xuICB9XG59O1xuXG5MR3JhcGgucHJvdG90eXBlLmdldEVzdGltYXRlZFNpemUgPSBmdW5jdGlvbiAoKVxue1xuICBpZiAodGhpcy5lc3RpbWF0ZWRTaXplID09IEludGVnZXIuTUlOX1ZBTFVFKSB7XG4gICAgdGhyb3cgXCJhc3NlcnQgZmFpbGVkXCI7XG4gIH1cbiAgcmV0dXJuIHRoaXMuZXN0aW1hdGVkU2l6ZTtcbn07XG5cbkxHcmFwaC5wcm90b3R5cGUuY2FsY0VzdGltYXRlZFNpemUgPSBmdW5jdGlvbiAoKVxue1xuICB2YXIgc2l6ZSA9IDA7XG4gIHZhciBub2RlcyA9IHRoaXMubm9kZXM7XG4gIHZhciBzID0gbm9kZXMubGVuZ3RoO1xuXG4gIGZvciAodmFyIGkgPSAwOyBpIDwgczsgaSsrKVxuICB7XG4gICAgdmFyIGxOb2RlID0gbm9kZXNbaV07XG4gICAgc2l6ZSArPSBsTm9kZS5jYWxjRXN0aW1hdGVkU2l6ZSgpO1xuICB9XG5cbiAgaWYgKHNpemUgPT0gMClcbiAge1xuICAgIHRoaXMuZXN0aW1hdGVkU2l6ZSA9IExheW91dENvbnN0YW50cy5FTVBUWV9DT01QT1VORF9OT0RFX1NJWkU7XG4gIH1cbiAgZWxzZVxuICB7XG4gICAgdGhpcy5lc3RpbWF0ZWRTaXplID0gTWF0aC5mbG9vcihzaXplIC8gTWF0aC5zcXJ0KHRoaXMubm9kZXMubGVuZ3RoKSk7XG4gIH1cblxuICByZXR1cm4gTWF0aC5mbG9vcih0aGlzLmVzdGltYXRlZFNpemUpO1xufTtcblxuTEdyYXBoLnByb3RvdHlwZS51cGRhdGVDb25uZWN0ZWQgPSBmdW5jdGlvbiAoKVxue1xuICBpZiAodGhpcy5ub2Rlcy5sZW5ndGggPT0gMClcbiAge1xuICAgIHRoaXMuaXNDb25uZWN0ZWQgPSB0cnVlO1xuICAgIHJldHVybjtcbiAgfVxuXG4gIHZhciB0b0JlVmlzaXRlZCA9IFtdO1xuICB2YXIgdmlzaXRlZCA9IG5ldyBIYXNoU2V0KCk7XG4gIHZhciBjdXJyZW50Tm9kZSA9IHRoaXMubm9kZXNbMF07XG4gIHZhciBuZWlnaGJvckVkZ2VzO1xuICB2YXIgY3VycmVudE5laWdoYm9yO1xuICB0b0JlVmlzaXRlZCA9IHRvQmVWaXNpdGVkLmNvbmNhdChjdXJyZW50Tm9kZS53aXRoQ2hpbGRyZW4oKSk7XG5cbiAgd2hpbGUgKHRvQmVWaXNpdGVkLmxlbmd0aCA+IDApXG4gIHtcbiAgICBjdXJyZW50Tm9kZSA9IHRvQmVWaXNpdGVkLnNoaWZ0KCk7XG4gICAgdmlzaXRlZC5hZGQoY3VycmVudE5vZGUpO1xuXG4gICAgLy8gVHJhdmVyc2UgYWxsIG5laWdoYm9ycyBvZiB0aGlzIG5vZGVcbiAgICBuZWlnaGJvckVkZ2VzID0gY3VycmVudE5vZGUuZ2V0RWRnZXMoKTtcbiAgICB2YXIgcyA9IG5laWdoYm9yRWRnZXMubGVuZ3RoO1xuICAgIGZvciAodmFyIGkgPSAwOyBpIDwgczsgaSsrKVxuICAgIHtcbiAgICAgIHZhciBuZWlnaGJvckVkZ2UgPSBuZWlnaGJvckVkZ2VzW2ldO1xuICAgICAgY3VycmVudE5laWdoYm9yID1cbiAgICAgICAgICAgICAgbmVpZ2hib3JFZGdlLmdldE90aGVyRW5kSW5HcmFwaChjdXJyZW50Tm9kZSwgdGhpcyk7XG5cbiAgICAgIC8vIEFkZCB1bnZpc2l0ZWQgbmVpZ2hib3JzIHRvIHRoZSBsaXN0IHRvIHZpc2l0XG4gICAgICBpZiAoY3VycmVudE5laWdoYm9yICE9IG51bGwgJiZcbiAgICAgICAgICAgICAgIXZpc2l0ZWQuY29udGFpbnMoY3VycmVudE5laWdoYm9yKSlcbiAgICAgIHtcbiAgICAgICAgdG9CZVZpc2l0ZWQgPSB0b0JlVmlzaXRlZC5jb25jYXQoY3VycmVudE5laWdoYm9yLndpdGhDaGlsZHJlbigpKTtcbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICB0aGlzLmlzQ29ubmVjdGVkID0gZmFsc2U7XG5cbiAgaWYgKHZpc2l0ZWQuc2l6ZSgpID49IHRoaXMubm9kZXMubGVuZ3RoKVxuICB7XG4gICAgdmFyIG5vT2ZWaXNpdGVkSW5UaGlzR3JhcGggPSAwO1xuXG4gICAgdmFyIHMgPSB2aXNpdGVkLnNpemUoKTtcbiAgICBmb3IgKHZhciB2aXNpdGVkSWQgaW4gdmlzaXRlZC5zZXQpXG4gICAge1xuICAgICAgdmFyIHZpc2l0ZWROb2RlID0gdmlzaXRlZC5zZXRbdmlzaXRlZElkXTtcbiAgICAgIGlmICh2aXNpdGVkTm9kZS5vd25lciA9PSB0aGlzKVxuICAgICAge1xuICAgICAgICBub09mVmlzaXRlZEluVGhpc0dyYXBoKys7XG4gICAgICB9XG4gICAgfVxuXG4gICAgaWYgKG5vT2ZWaXNpdGVkSW5UaGlzR3JhcGggPT0gdGhpcy5ub2Rlcy5sZW5ndGgpXG4gICAge1xuICAgICAgdGhpcy5pc0Nvbm5lY3RlZCA9IHRydWU7XG4gICAgfVxuICB9XG59O1xuXG5tb2R1bGUuZXhwb3J0cyA9IExHcmFwaDtcbiIsImZ1bmN0aW9uIExHcmFwaE1hbmFnZXIobGF5b3V0KSB7XG4gIHRoaXMubGF5b3V0ID0gbGF5b3V0O1xuXG4gIHRoaXMuZ3JhcGhzID0gW107XG4gIHRoaXMuZWRnZXMgPSBbXTtcbn1cblxuTEdyYXBoTWFuYWdlci5wcm90b3R5cGUuYWRkUm9vdCA9IGZ1bmN0aW9uICgpXG57XG4gIHZhciBuZ3JhcGggPSB0aGlzLmxheW91dC5uZXdHcmFwaCgpO1xuICB2YXIgbm5vZGUgPSB0aGlzLmxheW91dC5uZXdOb2RlKG51bGwpO1xuICB2YXIgcm9vdCA9IHRoaXMuYWRkKG5ncmFwaCwgbm5vZGUpO1xuICB0aGlzLnNldFJvb3RHcmFwaChyb290KTtcbiAgcmV0dXJuIHRoaXMucm9vdEdyYXBoO1xufTtcblxuTEdyYXBoTWFuYWdlci5wcm90b3R5cGUuYWRkID0gZnVuY3Rpb24gKG5ld0dyYXBoLCBwYXJlbnROb2RlLCBuZXdFZGdlLCBzb3VyY2VOb2RlLCB0YXJnZXROb2RlKVxue1xuICAvL3RoZXJlIGFyZSBqdXN0IDIgcGFyYW1ldGVycyBhcmUgcGFzc2VkIHRoZW4gaXQgYWRkcyBhbiBMR3JhcGggZWxzZSBpdCBhZGRzIGFuIExFZGdlXG4gIGlmIChuZXdFZGdlID09IG51bGwgJiYgc291cmNlTm9kZSA9PSBudWxsICYmIHRhcmdldE5vZGUgPT0gbnVsbCkge1xuICAgIGlmIChuZXdHcmFwaCA9PSBudWxsKSB7XG4gICAgICB0aHJvdyBcIkdyYXBoIGlzIG51bGwhXCI7XG4gICAgfVxuICAgIGlmIChwYXJlbnROb2RlID09IG51bGwpIHtcbiAgICAgIHRocm93IFwiUGFyZW50IG5vZGUgaXMgbnVsbCFcIjtcbiAgICB9XG4gICAgaWYgKHRoaXMuZ3JhcGhzLmluZGV4T2YobmV3R3JhcGgpID4gLTEpIHtcbiAgICAgIHRocm93IFwiR3JhcGggYWxyZWFkeSBpbiB0aGlzIGdyYXBoIG1nciFcIjtcbiAgICB9XG5cbiAgICB0aGlzLmdyYXBocy5wdXNoKG5ld0dyYXBoKTtcblxuICAgIGlmIChuZXdHcmFwaC5wYXJlbnQgIT0gbnVsbCkge1xuICAgICAgdGhyb3cgXCJBbHJlYWR5IGhhcyBhIHBhcmVudCFcIjtcbiAgICB9XG4gICAgaWYgKHBhcmVudE5vZGUuY2hpbGQgIT0gbnVsbCkge1xuICAgICAgdGhyb3cgIFwiQWxyZWFkeSBoYXMgYSBjaGlsZCFcIjtcbiAgICB9XG5cbiAgICBuZXdHcmFwaC5wYXJlbnQgPSBwYXJlbnROb2RlO1xuICAgIHBhcmVudE5vZGUuY2hpbGQgPSBuZXdHcmFwaDtcblxuICAgIHJldHVybiBuZXdHcmFwaDtcbiAgfVxuICBlbHNlIHtcbiAgICAvL2NoYW5nZSB0aGUgb3JkZXIgb2YgdGhlIHBhcmFtZXRlcnNcbiAgICB0YXJnZXROb2RlID0gbmV3RWRnZTtcbiAgICBzb3VyY2VOb2RlID0gcGFyZW50Tm9kZTtcbiAgICBuZXdFZGdlID0gbmV3R3JhcGg7XG4gICAgdmFyIHNvdXJjZUdyYXBoID0gc291cmNlTm9kZS5nZXRPd25lcigpO1xuICAgIHZhciB0YXJnZXRHcmFwaCA9IHRhcmdldE5vZGUuZ2V0T3duZXIoKTtcblxuICAgIGlmICghKHNvdXJjZUdyYXBoICE9IG51bGwgJiYgc291cmNlR3JhcGguZ2V0R3JhcGhNYW5hZ2VyKCkgPT0gdGhpcykpIHtcbiAgICAgIHRocm93IFwiU291cmNlIG5vdCBpbiB0aGlzIGdyYXBoIG1nciFcIjtcbiAgICB9XG4gICAgaWYgKCEodGFyZ2V0R3JhcGggIT0gbnVsbCAmJiB0YXJnZXRHcmFwaC5nZXRHcmFwaE1hbmFnZXIoKSA9PSB0aGlzKSkge1xuICAgICAgdGhyb3cgXCJUYXJnZXQgbm90IGluIHRoaXMgZ3JhcGggbWdyIVwiO1xuICAgIH1cblxuICAgIGlmIChzb3VyY2VHcmFwaCA9PSB0YXJnZXRHcmFwaClcbiAgICB7XG4gICAgICBuZXdFZGdlLmlzSW50ZXJHcmFwaCA9IGZhbHNlO1xuICAgICAgcmV0dXJuIHNvdXJjZUdyYXBoLmFkZChuZXdFZGdlLCBzb3VyY2VOb2RlLCB0YXJnZXROb2RlKTtcbiAgICB9XG4gICAgZWxzZVxuICAgIHtcbiAgICAgIG5ld0VkZ2UuaXNJbnRlckdyYXBoID0gdHJ1ZTtcblxuICAgICAgLy8gc2V0IHNvdXJjZSBhbmQgdGFyZ2V0XG4gICAgICBuZXdFZGdlLnNvdXJjZSA9IHNvdXJjZU5vZGU7XG4gICAgICBuZXdFZGdlLnRhcmdldCA9IHRhcmdldE5vZGU7XG5cbiAgICAgIC8vIGFkZCBlZGdlIHRvIGludGVyLWdyYXBoIGVkZ2UgbGlzdFxuICAgICAgaWYgKHRoaXMuZWRnZXMuaW5kZXhPZihuZXdFZGdlKSA+IC0xKSB7XG4gICAgICAgIHRocm93IFwiRWRnZSBhbHJlYWR5IGluIGludGVyLWdyYXBoIGVkZ2UgbGlzdCFcIjtcbiAgICAgIH1cblxuICAgICAgdGhpcy5lZGdlcy5wdXNoKG5ld0VkZ2UpO1xuXG4gICAgICAvLyBhZGQgZWRnZSB0byBzb3VyY2UgYW5kIHRhcmdldCBpbmNpZGVuY3kgbGlzdHNcbiAgICAgIGlmICghKG5ld0VkZ2Uuc291cmNlICE9IG51bGwgJiYgbmV3RWRnZS50YXJnZXQgIT0gbnVsbCkpIHtcbiAgICAgICAgdGhyb3cgXCJFZGdlIHNvdXJjZSBhbmQvb3IgdGFyZ2V0IGlzIG51bGwhXCI7XG4gICAgICB9XG5cbiAgICAgIGlmICghKG5ld0VkZ2Uuc291cmNlLmVkZ2VzLmluZGV4T2YobmV3RWRnZSkgPT0gLTEgJiYgbmV3RWRnZS50YXJnZXQuZWRnZXMuaW5kZXhPZihuZXdFZGdlKSA9PSAtMSkpIHtcbiAgICAgICAgdGhyb3cgXCJFZGdlIGFscmVhZHkgaW4gc291cmNlIGFuZC9vciB0YXJnZXQgaW5jaWRlbmN5IGxpc3QhXCI7XG4gICAgICB9XG5cbiAgICAgIG5ld0VkZ2Uuc291cmNlLmVkZ2VzLnB1c2gobmV3RWRnZSk7XG4gICAgICBuZXdFZGdlLnRhcmdldC5lZGdlcy5wdXNoKG5ld0VkZ2UpO1xuXG4gICAgICByZXR1cm4gbmV3RWRnZTtcbiAgICB9XG4gIH1cbn07XG5cbkxHcmFwaE1hbmFnZXIucHJvdG90eXBlLnJlbW92ZSA9IGZ1bmN0aW9uIChsT2JqKSB7XG4gIGlmIChsT2JqIGluc3RhbmNlb2YgTEdyYXBoKSB7XG4gICAgdmFyIGdyYXBoID0gbE9iajtcbiAgICBpZiAoZ3JhcGguZ2V0R3JhcGhNYW5hZ2VyKCkgIT0gdGhpcykge1xuICAgICAgdGhyb3cgXCJHcmFwaCBub3QgaW4gdGhpcyBncmFwaCBtZ3JcIjtcbiAgICB9XG4gICAgaWYgKCEoZ3JhcGggPT0gdGhpcy5yb290R3JhcGggfHwgKGdyYXBoLnBhcmVudCAhPSBudWxsICYmIGdyYXBoLnBhcmVudC5ncmFwaE1hbmFnZXIgPT0gdGhpcykpKSB7XG4gICAgICB0aHJvdyBcIkludmFsaWQgcGFyZW50IG5vZGUhXCI7XG4gICAgfVxuXG4gICAgLy8gZmlyc3QgdGhlIGVkZ2VzIChtYWtlIGEgY29weSB0byBkbyBpdCBzYWZlbHkpXG4gICAgdmFyIGVkZ2VzVG9CZVJlbW92ZWQgPSBbXTtcblxuICAgIGVkZ2VzVG9CZVJlbW92ZWQgPSBlZGdlc1RvQmVSZW1vdmVkLmNvbmNhdChncmFwaC5nZXRFZGdlcygpKTtcblxuICAgIHZhciBlZGdlO1xuICAgIHZhciBzID0gZWRnZXNUb0JlUmVtb3ZlZC5sZW5ndGg7XG4gICAgZm9yICh2YXIgaSA9IDA7IGkgPCBzOyBpKyspXG4gICAge1xuICAgICAgZWRnZSA9IGVkZ2VzVG9CZVJlbW92ZWRbaV07XG4gICAgICBncmFwaC5yZW1vdmUoZWRnZSk7XG4gICAgfVxuXG4gICAgLy8gdGhlbiB0aGUgbm9kZXMgKG1ha2UgYSBjb3B5IHRvIGRvIGl0IHNhZmVseSlcbiAgICB2YXIgbm9kZXNUb0JlUmVtb3ZlZCA9IFtdO1xuXG4gICAgbm9kZXNUb0JlUmVtb3ZlZCA9IG5vZGVzVG9CZVJlbW92ZWQuY29uY2F0KGdyYXBoLmdldE5vZGVzKCkpO1xuXG4gICAgdmFyIG5vZGU7XG4gICAgcyA9IG5vZGVzVG9CZVJlbW92ZWQubGVuZ3RoO1xuICAgIGZvciAodmFyIGkgPSAwOyBpIDwgczsgaSsrKVxuICAgIHtcbiAgICAgIG5vZGUgPSBub2Rlc1RvQmVSZW1vdmVkW2ldO1xuICAgICAgZ3JhcGgucmVtb3ZlKG5vZGUpO1xuICAgIH1cblxuICAgIC8vIGNoZWNrIGlmIGdyYXBoIGlzIHRoZSByb290XG4gICAgaWYgKGdyYXBoID09IHRoaXMucm9vdEdyYXBoKVxuICAgIHtcbiAgICAgIHRoaXMuc2V0Um9vdEdyYXBoKG51bGwpO1xuICAgIH1cblxuICAgIC8vIG5vdyByZW1vdmUgdGhlIGdyYXBoIGl0c2VsZlxuICAgIHZhciBpbmRleCA9IHRoaXMuZ3JhcGhzLmluZGV4T2YoZ3JhcGgpO1xuICAgIHRoaXMuZ3JhcGhzLnNwbGljZShpbmRleCwgMSk7XG5cbiAgICAvLyBhbHNvIHJlc2V0IHRoZSBwYXJlbnQgb2YgdGhlIGdyYXBoXG4gICAgZ3JhcGgucGFyZW50ID0gbnVsbDtcbiAgfVxuICBlbHNlIGlmIChsT2JqIGluc3RhbmNlb2YgTEVkZ2UpIHtcbiAgICBlZGdlID0gbE9iajtcbiAgICBpZiAoZWRnZSA9PSBudWxsKSB7XG4gICAgICB0aHJvdyBcIkVkZ2UgaXMgbnVsbCFcIjtcbiAgICB9XG4gICAgaWYgKCFlZGdlLmlzSW50ZXJHcmFwaCkge1xuICAgICAgdGhyb3cgXCJOb3QgYW4gaW50ZXItZ3JhcGggZWRnZSFcIjtcbiAgICB9XG4gICAgaWYgKCEoZWRnZS5zb3VyY2UgIT0gbnVsbCAmJiBlZGdlLnRhcmdldCAhPSBudWxsKSkge1xuICAgICAgdGhyb3cgXCJTb3VyY2UgYW5kL29yIHRhcmdldCBpcyBudWxsIVwiO1xuICAgIH1cblxuICAgIC8vIHJlbW92ZSBlZGdlIGZyb20gc291cmNlIGFuZCB0YXJnZXQgbm9kZXMnIGluY2lkZW5jeSBsaXN0c1xuXG4gICAgaWYgKCEoZWRnZS5zb3VyY2UuZWRnZXMuaW5kZXhPZihlZGdlKSAhPSAtMSAmJiBlZGdlLnRhcmdldC5lZGdlcy5pbmRleE9mKGVkZ2UpICE9IC0xKSkge1xuICAgICAgdGhyb3cgXCJTb3VyY2UgYW5kL29yIHRhcmdldCBkb2Vzbid0IGtub3cgdGhpcyBlZGdlIVwiO1xuICAgIH1cblxuICAgIHZhciBpbmRleCA9IGVkZ2Uuc291cmNlLmVkZ2VzLmluZGV4T2YoZWRnZSk7XG4gICAgZWRnZS5zb3VyY2UuZWRnZXMuc3BsaWNlKGluZGV4LCAxKTtcbiAgICBpbmRleCA9IGVkZ2UudGFyZ2V0LmVkZ2VzLmluZGV4T2YoZWRnZSk7XG4gICAgZWRnZS50YXJnZXQuZWRnZXMuc3BsaWNlKGluZGV4LCAxKTtcblxuICAgIC8vIHJlbW92ZSBlZGdlIGZyb20gb3duZXIgZ3JhcGggbWFuYWdlcidzIGludGVyLWdyYXBoIGVkZ2UgbGlzdFxuXG4gICAgaWYgKCEoZWRnZS5zb3VyY2Uub3duZXIgIT0gbnVsbCAmJiBlZGdlLnNvdXJjZS5vd25lci5nZXRHcmFwaE1hbmFnZXIoKSAhPSBudWxsKSkge1xuICAgICAgdGhyb3cgXCJFZGdlIG93bmVyIGdyYXBoIG9yIG93bmVyIGdyYXBoIG1hbmFnZXIgaXMgbnVsbCFcIjtcbiAgICB9XG4gICAgaWYgKGVkZ2Uuc291cmNlLm93bmVyLmdldEdyYXBoTWFuYWdlcigpLmVkZ2VzLmluZGV4T2YoZWRnZSkgPT0gLTEpIHtcbiAgICAgIHRocm93IFwiTm90IGluIG93bmVyIGdyYXBoIG1hbmFnZXIncyBlZGdlIGxpc3QhXCI7XG4gICAgfVxuXG4gICAgdmFyIGluZGV4ID0gZWRnZS5zb3VyY2Uub3duZXIuZ2V0R3JhcGhNYW5hZ2VyKCkuZWRnZXMuaW5kZXhPZihlZGdlKTtcbiAgICBlZGdlLnNvdXJjZS5vd25lci5nZXRHcmFwaE1hbmFnZXIoKS5lZGdlcy5zcGxpY2UoaW5kZXgsIDEpO1xuICB9XG59O1xuXG5MR3JhcGhNYW5hZ2VyLnByb3RvdHlwZS51cGRhdGVCb3VuZHMgPSBmdW5jdGlvbiAoKVxue1xuICB0aGlzLnJvb3RHcmFwaC51cGRhdGVCb3VuZHModHJ1ZSk7XG59O1xuXG5MR3JhcGhNYW5hZ2VyLnByb3RvdHlwZS5nZXRHcmFwaHMgPSBmdW5jdGlvbiAoKVxue1xuICByZXR1cm4gdGhpcy5ncmFwaHM7XG59O1xuXG5MR3JhcGhNYW5hZ2VyLnByb3RvdHlwZS5nZXRBbGxOb2RlcyA9IGZ1bmN0aW9uICgpXG57XG4gIGlmICh0aGlzLmFsbE5vZGVzID09IG51bGwpXG4gIHtcbiAgICB2YXIgbm9kZUxpc3QgPSBbXTtcbiAgICB2YXIgZ3JhcGhzID0gdGhpcy5nZXRHcmFwaHMoKTtcbiAgICB2YXIgcyA9IGdyYXBocy5sZW5ndGg7XG4gICAgZm9yICh2YXIgaSA9IDA7IGkgPCBzOyBpKyspXG4gICAge1xuICAgICAgbm9kZUxpc3QgPSBub2RlTGlzdC5jb25jYXQoZ3JhcGhzW2ldLmdldE5vZGVzKCkpO1xuICAgIH1cbiAgICB0aGlzLmFsbE5vZGVzID0gbm9kZUxpc3Q7XG4gIH1cbiAgcmV0dXJuIHRoaXMuYWxsTm9kZXM7XG59O1xuXG5MR3JhcGhNYW5hZ2VyLnByb3RvdHlwZS5yZXNldEFsbE5vZGVzID0gZnVuY3Rpb24gKClcbntcbiAgdGhpcy5hbGxOb2RlcyA9IG51bGw7XG59O1xuXG5MR3JhcGhNYW5hZ2VyLnByb3RvdHlwZS5yZXNldEFsbEVkZ2VzID0gZnVuY3Rpb24gKClcbntcbiAgdGhpcy5hbGxFZGdlcyA9IG51bGw7XG59O1xuXG5MR3JhcGhNYW5hZ2VyLnByb3RvdHlwZS5yZXNldEFsbE5vZGVzVG9BcHBseUdyYXZpdGF0aW9uID0gZnVuY3Rpb24gKClcbntcbiAgdGhpcy5hbGxOb2Rlc1RvQXBwbHlHcmF2aXRhdGlvbiA9IG51bGw7XG59O1xuXG5MR3JhcGhNYW5hZ2VyLnByb3RvdHlwZS5nZXRBbGxFZGdlcyA9IGZ1bmN0aW9uICgpXG57XG4gIGlmICh0aGlzLmFsbEVkZ2VzID09IG51bGwpXG4gIHtcbiAgICB2YXIgZWRnZUxpc3QgPSBbXTtcbiAgICB2YXIgZ3JhcGhzID0gdGhpcy5nZXRHcmFwaHMoKTtcbiAgICB2YXIgcyA9IGdyYXBocy5sZW5ndGg7XG4gICAgZm9yICh2YXIgaSA9IDA7IGkgPCBncmFwaHMubGVuZ3RoOyBpKyspXG4gICAge1xuICAgICAgZWRnZUxpc3QgPSBlZGdlTGlzdC5jb25jYXQoZ3JhcGhzW2ldLmdldEVkZ2VzKCkpO1xuICAgIH1cblxuICAgIGVkZ2VMaXN0ID0gZWRnZUxpc3QuY29uY2F0KHRoaXMuZWRnZXMpO1xuXG4gICAgdGhpcy5hbGxFZGdlcyA9IGVkZ2VMaXN0O1xuICB9XG4gIHJldHVybiB0aGlzLmFsbEVkZ2VzO1xufTtcblxuTEdyYXBoTWFuYWdlci5wcm90b3R5cGUuZ2V0QWxsTm9kZXNUb0FwcGx5R3Jhdml0YXRpb24gPSBmdW5jdGlvbiAoKVxue1xuICByZXR1cm4gdGhpcy5hbGxOb2Rlc1RvQXBwbHlHcmF2aXRhdGlvbjtcbn07XG5cbkxHcmFwaE1hbmFnZXIucHJvdG90eXBlLnNldEFsbE5vZGVzVG9BcHBseUdyYXZpdGF0aW9uID0gZnVuY3Rpb24gKG5vZGVMaXN0KVxue1xuICBpZiAodGhpcy5hbGxOb2Rlc1RvQXBwbHlHcmF2aXRhdGlvbiAhPSBudWxsKSB7XG4gICAgdGhyb3cgXCJhc3NlcnQgZmFpbGVkXCI7XG4gIH1cblxuICB0aGlzLmFsbE5vZGVzVG9BcHBseUdyYXZpdGF0aW9uID0gbm9kZUxpc3Q7XG59O1xuXG5MR3JhcGhNYW5hZ2VyLnByb3RvdHlwZS5nZXRSb290ID0gZnVuY3Rpb24gKClcbntcbiAgcmV0dXJuIHRoaXMucm9vdEdyYXBoO1xufTtcblxuTEdyYXBoTWFuYWdlci5wcm90b3R5cGUuc2V0Um9vdEdyYXBoID0gZnVuY3Rpb24gKGdyYXBoKVxue1xuICBpZiAoZ3JhcGguZ2V0R3JhcGhNYW5hZ2VyKCkgIT0gdGhpcykge1xuICAgIHRocm93IFwiUm9vdCBub3QgaW4gdGhpcyBncmFwaCBtZ3IhXCI7XG4gIH1cblxuICB0aGlzLnJvb3RHcmFwaCA9IGdyYXBoO1xuICAvLyByb290IGdyYXBoIG11c3QgaGF2ZSBhIHJvb3Qgbm9kZSBhc3NvY2lhdGVkIHdpdGggaXQgZm9yIGNvbnZlbmllbmNlXG4gIGlmIChncmFwaC5wYXJlbnQgPT0gbnVsbClcbiAge1xuICAgIGdyYXBoLnBhcmVudCA9IHRoaXMubGF5b3V0Lm5ld05vZGUoXCJSb290IG5vZGVcIik7XG4gIH1cbn07XG5cbkxHcmFwaE1hbmFnZXIucHJvdG90eXBlLmdldExheW91dCA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLmxheW91dDtcbn07XG5cbkxHcmFwaE1hbmFnZXIucHJvdG90eXBlLmlzT25lQW5jZXN0b3JPZk90aGVyID0gZnVuY3Rpb24gKGZpcnN0Tm9kZSwgc2Vjb25kTm9kZSlcbntcbiAgaWYgKCEoZmlyc3ROb2RlICE9IG51bGwgJiYgc2Vjb25kTm9kZSAhPSBudWxsKSkge1xuICAgIHRocm93IFwiYXNzZXJ0IGZhaWxlZFwiO1xuICB9XG5cbiAgaWYgKGZpcnN0Tm9kZSA9PSBzZWNvbmROb2RlKVxuICB7XG4gICAgcmV0dXJuIHRydWU7XG4gIH1cbiAgLy8gSXMgc2Vjb25kIG5vZGUgYW4gYW5jZXN0b3Igb2YgdGhlIGZpcnN0IG9uZT9cbiAgdmFyIG93bmVyR3JhcGggPSBmaXJzdE5vZGUuZ2V0T3duZXIoKTtcbiAgdmFyIHBhcmVudE5vZGU7XG5cbiAgZG9cbiAge1xuICAgIHBhcmVudE5vZGUgPSBvd25lckdyYXBoLmdldFBhcmVudCgpO1xuXG4gICAgaWYgKHBhcmVudE5vZGUgPT0gbnVsbClcbiAgICB7XG4gICAgICBicmVhaztcbiAgICB9XG5cbiAgICBpZiAocGFyZW50Tm9kZSA9PSBzZWNvbmROb2RlKVxuICAgIHtcbiAgICAgIHJldHVybiB0cnVlO1xuICAgIH1cblxuICAgIG93bmVyR3JhcGggPSBwYXJlbnROb2RlLmdldE93bmVyKCk7XG4gICAgaWYgKG93bmVyR3JhcGggPT0gbnVsbClcbiAgICB7XG4gICAgICBicmVhaztcbiAgICB9XG4gIH0gd2hpbGUgKHRydWUpO1xuICAvLyBJcyBmaXJzdCBub2RlIGFuIGFuY2VzdG9yIG9mIHRoZSBzZWNvbmQgb25lP1xuICBvd25lckdyYXBoID0gc2Vjb25kTm9kZS5nZXRPd25lcigpO1xuXG4gIGRvXG4gIHtcbiAgICBwYXJlbnROb2RlID0gb3duZXJHcmFwaC5nZXRQYXJlbnQoKTtcblxuICAgIGlmIChwYXJlbnROb2RlID09IG51bGwpXG4gICAge1xuICAgICAgYnJlYWs7XG4gICAgfVxuXG4gICAgaWYgKHBhcmVudE5vZGUgPT0gZmlyc3ROb2RlKVxuICAgIHtcbiAgICAgIHJldHVybiB0cnVlO1xuICAgIH1cblxuICAgIG93bmVyR3JhcGggPSBwYXJlbnROb2RlLmdldE93bmVyKCk7XG4gICAgaWYgKG93bmVyR3JhcGggPT0gbnVsbClcbiAgICB7XG4gICAgICBicmVhaztcbiAgICB9XG4gIH0gd2hpbGUgKHRydWUpO1xuXG4gIHJldHVybiBmYWxzZTtcbn07XG5cbkxHcmFwaE1hbmFnZXIucHJvdG90eXBlLmNhbGNMb3dlc3RDb21tb25BbmNlc3RvcnMgPSBmdW5jdGlvbiAoKVxue1xuICB2YXIgZWRnZTtcbiAgdmFyIHNvdXJjZU5vZGU7XG4gIHZhciB0YXJnZXROb2RlO1xuICB2YXIgc291cmNlQW5jZXN0b3JHcmFwaDtcbiAgdmFyIHRhcmdldEFuY2VzdG9yR3JhcGg7XG5cbiAgdmFyIGVkZ2VzID0gdGhpcy5nZXRBbGxFZGdlcygpO1xuICB2YXIgcyA9IGVkZ2VzLmxlbmd0aDtcbiAgZm9yICh2YXIgaSA9IDA7IGkgPCBzOyBpKyspXG4gIHtcbiAgICBlZGdlID0gZWRnZXNbaV07XG5cbiAgICBzb3VyY2VOb2RlID0gZWRnZS5zb3VyY2U7XG4gICAgdGFyZ2V0Tm9kZSA9IGVkZ2UudGFyZ2V0O1xuICAgIGVkZ2UubGNhID0gbnVsbDtcbiAgICBlZGdlLnNvdXJjZUluTGNhID0gc291cmNlTm9kZTtcbiAgICBlZGdlLnRhcmdldEluTGNhID0gdGFyZ2V0Tm9kZTtcblxuICAgIGlmIChzb3VyY2VOb2RlID09IHRhcmdldE5vZGUpXG4gICAge1xuICAgICAgZWRnZS5sY2EgPSBzb3VyY2VOb2RlLmdldE93bmVyKCk7XG4gICAgICBjb250aW51ZTtcbiAgICB9XG5cbiAgICBzb3VyY2VBbmNlc3RvckdyYXBoID0gc291cmNlTm9kZS5nZXRPd25lcigpO1xuXG4gICAgd2hpbGUgKGVkZ2UubGNhID09IG51bGwpXG4gICAge1xuICAgICAgdGFyZ2V0QW5jZXN0b3JHcmFwaCA9IHRhcmdldE5vZGUuZ2V0T3duZXIoKTtcblxuICAgICAgd2hpbGUgKGVkZ2UubGNhID09IG51bGwpXG4gICAgICB7XG4gICAgICAgIGlmICh0YXJnZXRBbmNlc3RvckdyYXBoID09IHNvdXJjZUFuY2VzdG9yR3JhcGgpXG4gICAgICAgIHtcbiAgICAgICAgICBlZGdlLmxjYSA9IHRhcmdldEFuY2VzdG9yR3JhcGg7XG4gICAgICAgICAgYnJlYWs7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAodGFyZ2V0QW5jZXN0b3JHcmFwaCA9PSB0aGlzLnJvb3RHcmFwaClcbiAgICAgICAge1xuICAgICAgICAgIGJyZWFrO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKGVkZ2UubGNhICE9IG51bGwpIHtcbiAgICAgICAgICB0aHJvdyBcImFzc2VydCBmYWlsZWRcIjtcbiAgICAgICAgfVxuICAgICAgICBlZGdlLnRhcmdldEluTGNhID0gdGFyZ2V0QW5jZXN0b3JHcmFwaC5nZXRQYXJlbnQoKTtcbiAgICAgICAgdGFyZ2V0QW5jZXN0b3JHcmFwaCA9IGVkZ2UudGFyZ2V0SW5MY2EuZ2V0T3duZXIoKTtcbiAgICAgIH1cblxuICAgICAgaWYgKHNvdXJjZUFuY2VzdG9yR3JhcGggPT0gdGhpcy5yb290R3JhcGgpXG4gICAgICB7XG4gICAgICAgIGJyZWFrO1xuICAgICAgfVxuXG4gICAgICBpZiAoZWRnZS5sY2EgPT0gbnVsbClcbiAgICAgIHtcbiAgICAgICAgZWRnZS5zb3VyY2VJbkxjYSA9IHNvdXJjZUFuY2VzdG9yR3JhcGguZ2V0UGFyZW50KCk7XG4gICAgICAgIHNvdXJjZUFuY2VzdG9yR3JhcGggPSBlZGdlLnNvdXJjZUluTGNhLmdldE93bmVyKCk7XG4gICAgICB9XG4gICAgfVxuXG4gICAgaWYgKGVkZ2UubGNhID09IG51bGwpIHtcbiAgICAgIHRocm93IFwiYXNzZXJ0IGZhaWxlZFwiO1xuICAgIH1cbiAgfVxufTtcblxuTEdyYXBoTWFuYWdlci5wcm90b3R5cGUuY2FsY0xvd2VzdENvbW1vbkFuY2VzdG9yID0gZnVuY3Rpb24gKGZpcnN0Tm9kZSwgc2Vjb25kTm9kZSlcbntcbiAgaWYgKGZpcnN0Tm9kZSA9PSBzZWNvbmROb2RlKVxuICB7XG4gICAgcmV0dXJuIGZpcnN0Tm9kZS5nZXRPd25lcigpO1xuICB9XG4gIHZhciBmaXJzdE93bmVyR3JhcGggPSBmaXJzdE5vZGUuZ2V0T3duZXIoKTtcblxuICBkb1xuICB7XG4gICAgaWYgKGZpcnN0T3duZXJHcmFwaCA9PSBudWxsKVxuICAgIHtcbiAgICAgIGJyZWFrO1xuICAgIH1cbiAgICB2YXIgc2Vjb25kT3duZXJHcmFwaCA9IHNlY29uZE5vZGUuZ2V0T3duZXIoKTtcblxuICAgIGRvXG4gICAge1xuICAgICAgaWYgKHNlY29uZE93bmVyR3JhcGggPT0gbnVsbClcbiAgICAgIHtcbiAgICAgICAgYnJlYWs7XG4gICAgICB9XG5cbiAgICAgIGlmIChzZWNvbmRPd25lckdyYXBoID09IGZpcnN0T3duZXJHcmFwaClcbiAgICAgIHtcbiAgICAgICAgcmV0dXJuIHNlY29uZE93bmVyR3JhcGg7XG4gICAgICB9XG4gICAgICBzZWNvbmRPd25lckdyYXBoID0gc2Vjb25kT3duZXJHcmFwaC5nZXRQYXJlbnQoKS5nZXRPd25lcigpO1xuICAgIH0gd2hpbGUgKHRydWUpO1xuXG4gICAgZmlyc3RPd25lckdyYXBoID0gZmlyc3RPd25lckdyYXBoLmdldFBhcmVudCgpLmdldE93bmVyKCk7XG4gIH0gd2hpbGUgKHRydWUpO1xuXG4gIHJldHVybiBmaXJzdE93bmVyR3JhcGg7XG59O1xuXG5MR3JhcGhNYW5hZ2VyLnByb3RvdHlwZS5jYWxjSW5jbHVzaW9uVHJlZURlcHRocyA9IGZ1bmN0aW9uIChncmFwaCwgZGVwdGgpIHtcbiAgaWYgKGdyYXBoID09IG51bGwgJiYgZGVwdGggPT0gbnVsbCkge1xuICAgIGdyYXBoID0gdGhpcy5yb290R3JhcGg7XG4gICAgZGVwdGggPSAxO1xuICB9XG4gIHZhciBub2RlO1xuXG4gIHZhciBub2RlcyA9IGdyYXBoLmdldE5vZGVzKCk7XG4gIHZhciBzID0gbm9kZXMubGVuZ3RoO1xuICBmb3IgKHZhciBpID0gMDsgaSA8IHM7IGkrKylcbiAge1xuICAgIG5vZGUgPSBub2Rlc1tpXTtcbiAgICBub2RlLmluY2x1c2lvblRyZWVEZXB0aCA9IGRlcHRoO1xuXG4gICAgaWYgKG5vZGUuY2hpbGQgIT0gbnVsbClcbiAgICB7XG4gICAgICB0aGlzLmNhbGNJbmNsdXNpb25UcmVlRGVwdGhzKG5vZGUuY2hpbGQsIGRlcHRoICsgMSk7XG4gICAgfVxuICB9XG59O1xuXG5MR3JhcGhNYW5hZ2VyLnByb3RvdHlwZS5pbmNsdWRlc0ludmFsaWRFZGdlID0gZnVuY3Rpb24gKClcbntcbiAgdmFyIGVkZ2U7XG5cbiAgdmFyIHMgPSB0aGlzLmVkZ2VzLmxlbmd0aDtcbiAgZm9yICh2YXIgaSA9IDA7IGkgPCBzOyBpKyspXG4gIHtcbiAgICBlZGdlID0gdGhpcy5lZGdlc1tpXTtcblxuICAgIGlmICh0aGlzLmlzT25lQW5jZXN0b3JPZk90aGVyKGVkZ2Uuc291cmNlLCBlZGdlLnRhcmdldCkpXG4gICAge1xuICAgICAgcmV0dXJuIHRydWU7XG4gICAgfVxuICB9XG4gIHJldHVybiBmYWxzZTtcbn07XG5cbm1vZHVsZS5leHBvcnRzID0gTEdyYXBoTWFuYWdlcjtcbiIsImZ1bmN0aW9uIExHcmFwaE9iamVjdCh2R3JhcGhPYmplY3QpIHtcbiAgdGhpcy52R3JhcGhPYmplY3QgPSB2R3JhcGhPYmplY3Q7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gTEdyYXBoT2JqZWN0O1xuIiwidmFyIExHcmFwaE9iamVjdCA9IHJlcXVpcmUoJy4vTEdyYXBoT2JqZWN0Jyk7XG52YXIgSW50ZWdlciA9IHJlcXVpcmUoJy4vSW50ZWdlcicpO1xudmFyIFJlY3RhbmdsZUQgPSByZXF1aXJlKCcuL1JlY3RhbmdsZUQnKTtcbnZhciBMYXlvdXRDb25zdGFudHMgPSByZXF1aXJlKCcuL0xheW91dENvbnN0YW50cycpO1xudmFyIFJhbmRvbVNlZWQgPSByZXF1aXJlKCcuL1JhbmRvbVNlZWQnKTtcbnZhciBQb2ludEQgPSByZXF1aXJlKCcuL1BvaW50RCcpO1xudmFyIEhhc2hTZXQgPSByZXF1aXJlKCcuL0hhc2hTZXQnKTtcblxuZnVuY3Rpb24gTE5vZGUoZ20sIGxvYywgc2l6ZSwgdk5vZGUpIHtcbiAgLy9BbHRlcm5hdGl2ZSBjb25zdHJ1Y3RvciAxIDogTE5vZGUoTEdyYXBoTWFuYWdlciBnbSwgUG9pbnQgbG9jLCBEaW1lbnNpb24gc2l6ZSwgT2JqZWN0IHZOb2RlKVxuICBpZiAoc2l6ZSA9PSBudWxsICYmIHZOb2RlID09IG51bGwpIHtcbiAgICB2Tm9kZSA9IGxvYztcbiAgfVxuXG4gIExHcmFwaE9iamVjdC5jYWxsKHRoaXMsIHZOb2RlKTtcblxuICAvL0FsdGVybmF0aXZlIGNvbnN0cnVjdG9yIDIgOiBMTm9kZShMYXlvdXQgbGF5b3V0LCBPYmplY3Qgdk5vZGUpXG4gIGlmIChnbS5ncmFwaE1hbmFnZXIgIT0gbnVsbClcbiAgICBnbSA9IGdtLmdyYXBoTWFuYWdlcjtcblxuICB0aGlzLmVzdGltYXRlZFNpemUgPSBJbnRlZ2VyLk1JTl9WQUxVRTtcbiAgdGhpcy5pbmNsdXNpb25UcmVlRGVwdGggPSBJbnRlZ2VyLk1BWF9WQUxVRTtcbiAgdGhpcy52R3JhcGhPYmplY3QgPSB2Tm9kZTtcbiAgdGhpcy5lZGdlcyA9IFtdO1xuICB0aGlzLmdyYXBoTWFuYWdlciA9IGdtO1xuXG4gIGlmIChzaXplICE9IG51bGwgJiYgbG9jICE9IG51bGwpXG4gICAgdGhpcy5yZWN0ID0gbmV3IFJlY3RhbmdsZUQobG9jLngsIGxvYy55LCBzaXplLndpZHRoLCBzaXplLmhlaWdodCk7XG4gIGVsc2VcbiAgICB0aGlzLnJlY3QgPSBuZXcgUmVjdGFuZ2xlRCgpO1xufVxuXG5MTm9kZS5wcm90b3R5cGUgPSBPYmplY3QuY3JlYXRlKExHcmFwaE9iamVjdC5wcm90b3R5cGUpO1xuZm9yICh2YXIgcHJvcCBpbiBMR3JhcGhPYmplY3QpIHtcbiAgTE5vZGVbcHJvcF0gPSBMR3JhcGhPYmplY3RbcHJvcF07XG59XG5cbkxOb2RlLnByb3RvdHlwZS5nZXRFZGdlcyA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLmVkZ2VzO1xufTtcblxuTE5vZGUucHJvdG90eXBlLmdldENoaWxkID0gZnVuY3Rpb24gKClcbntcbiAgcmV0dXJuIHRoaXMuY2hpbGQ7XG59O1xuXG5MTm9kZS5wcm90b3R5cGUuZ2V0T3duZXIgPSBmdW5jdGlvbiAoKVxue1xuICBpZiAodGhpcy5vd25lciAhPSBudWxsKSB7XG4gICAgaWYgKCEodGhpcy5vd25lciA9PSBudWxsIHx8IHRoaXMub3duZXIuZ2V0Tm9kZXMoKS5pbmRleE9mKHRoaXMpID4gLTEpKSB7XG4gICAgICB0aHJvdyBcImFzc2VydCBmYWlsZWRcIjtcbiAgICB9XG4gIH1cblxuICByZXR1cm4gdGhpcy5vd25lcjtcbn07XG5cbkxOb2RlLnByb3RvdHlwZS5nZXRXaWR0aCA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLnJlY3Qud2lkdGg7XG59O1xuXG5MTm9kZS5wcm90b3R5cGUuc2V0V2lkdGggPSBmdW5jdGlvbiAod2lkdGgpXG57XG4gIHRoaXMucmVjdC53aWR0aCA9IHdpZHRoO1xufTtcblxuTE5vZGUucHJvdG90eXBlLmdldEhlaWdodCA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLnJlY3QuaGVpZ2h0O1xufTtcblxuTE5vZGUucHJvdG90eXBlLnNldEhlaWdodCA9IGZ1bmN0aW9uIChoZWlnaHQpXG57XG4gIHRoaXMucmVjdC5oZWlnaHQgPSBoZWlnaHQ7XG59O1xuXG5MTm9kZS5wcm90b3R5cGUuZ2V0Q2VudGVyWCA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLnJlY3QueCArIHRoaXMucmVjdC53aWR0aCAvIDI7XG59O1xuXG5MTm9kZS5wcm90b3R5cGUuZ2V0Q2VudGVyWSA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLnJlY3QueSArIHRoaXMucmVjdC5oZWlnaHQgLyAyO1xufTtcblxuTE5vZGUucHJvdG90eXBlLmdldENlbnRlciA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiBuZXcgUG9pbnREKHRoaXMucmVjdC54ICsgdGhpcy5yZWN0LndpZHRoIC8gMixcbiAgICAgICAgICB0aGlzLnJlY3QueSArIHRoaXMucmVjdC5oZWlnaHQgLyAyKTtcbn07XG5cbkxOb2RlLnByb3RvdHlwZS5nZXRMb2NhdGlvbiA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiBuZXcgUG9pbnREKHRoaXMucmVjdC54LCB0aGlzLnJlY3QueSk7XG59O1xuXG5MTm9kZS5wcm90b3R5cGUuZ2V0UmVjdCA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLnJlY3Q7XG59O1xuXG5MTm9kZS5wcm90b3R5cGUuZ2V0RGlhZ29uYWwgPSBmdW5jdGlvbiAoKVxue1xuICByZXR1cm4gTWF0aC5zcXJ0KHRoaXMucmVjdC53aWR0aCAqIHRoaXMucmVjdC53aWR0aCArXG4gICAgICAgICAgdGhpcy5yZWN0LmhlaWdodCAqIHRoaXMucmVjdC5oZWlnaHQpO1xufTtcblxuTE5vZGUucHJvdG90eXBlLnNldFJlY3QgPSBmdW5jdGlvbiAodXBwZXJMZWZ0LCBkaW1lbnNpb24pXG57XG4gIHRoaXMucmVjdC54ID0gdXBwZXJMZWZ0Lng7XG4gIHRoaXMucmVjdC55ID0gdXBwZXJMZWZ0Lnk7XG4gIHRoaXMucmVjdC53aWR0aCA9IGRpbWVuc2lvbi53aWR0aDtcbiAgdGhpcy5yZWN0LmhlaWdodCA9IGRpbWVuc2lvbi5oZWlnaHQ7XG59O1xuXG5MTm9kZS5wcm90b3R5cGUuc2V0Q2VudGVyID0gZnVuY3Rpb24gKGN4LCBjeSlcbntcbiAgdGhpcy5yZWN0LnggPSBjeCAtIHRoaXMucmVjdC53aWR0aCAvIDI7XG4gIHRoaXMucmVjdC55ID0gY3kgLSB0aGlzLnJlY3QuaGVpZ2h0IC8gMjtcbn07XG5cbkxOb2RlLnByb3RvdHlwZS5zZXRMb2NhdGlvbiA9IGZ1bmN0aW9uICh4LCB5KVxue1xuICB0aGlzLnJlY3QueCA9IHg7XG4gIHRoaXMucmVjdC55ID0geTtcbn07XG5cbkxOb2RlLnByb3RvdHlwZS5tb3ZlQnkgPSBmdW5jdGlvbiAoZHgsIGR5KVxue1xuICB0aGlzLnJlY3QueCArPSBkeDtcbiAgdGhpcy5yZWN0LnkgKz0gZHk7XG59O1xuXG5MTm9kZS5wcm90b3R5cGUuZ2V0RWRnZUxpc3RUb05vZGUgPSBmdW5jdGlvbiAodG8pXG57XG4gIHZhciBlZGdlTGlzdCA9IFtdO1xuICB2YXIgZWRnZTtcblxuICBmb3IgKHZhciBvYmogaW4gdGhpcy5lZGdlcylcbiAge1xuICAgIGVkZ2UgPSBvYmo7XG5cbiAgICBpZiAoZWRnZS50YXJnZXQgPT0gdG8pXG4gICAge1xuICAgICAgaWYgKGVkZ2Uuc291cmNlICE9IHRoaXMpXG4gICAgICAgIHRocm93IFwiSW5jb3JyZWN0IGVkZ2Ugc291cmNlIVwiO1xuXG4gICAgICBlZGdlTGlzdC5wdXNoKGVkZ2UpO1xuICAgIH1cbiAgfVxuXG4gIHJldHVybiBlZGdlTGlzdDtcbn07XG5cbkxOb2RlLnByb3RvdHlwZS5nZXRFZGdlc0JldHdlZW4gPSBmdW5jdGlvbiAob3RoZXIpXG57XG4gIHZhciBlZGdlTGlzdCA9IFtdO1xuICB2YXIgZWRnZTtcblxuICBmb3IgKHZhciBvYmogaW4gdGhpcy5lZGdlcylcbiAge1xuICAgIGVkZ2UgPSB0aGlzLmVkZ2VzW29ial07XG5cbiAgICBpZiAoIShlZGdlLnNvdXJjZSA9PSB0aGlzIHx8IGVkZ2UudGFyZ2V0ID09IHRoaXMpKVxuICAgICAgdGhyb3cgXCJJbmNvcnJlY3QgZWRnZSBzb3VyY2UgYW5kL29yIHRhcmdldFwiO1xuXG4gICAgaWYgKChlZGdlLnRhcmdldCA9PSBvdGhlcikgfHwgKGVkZ2Uuc291cmNlID09IG90aGVyKSlcbiAgICB7XG4gICAgICBlZGdlTGlzdC5wdXNoKGVkZ2UpO1xuICAgIH1cbiAgfVxuXG4gIHJldHVybiBlZGdlTGlzdDtcbn07XG5cbkxOb2RlLnByb3RvdHlwZS5nZXROZWlnaGJvcnNMaXN0ID0gZnVuY3Rpb24gKClcbntcbiAgdmFyIG5laWdoYm9ycyA9IG5ldyBIYXNoU2V0KCk7XG4gIHZhciBlZGdlO1xuXG4gIGZvciAodmFyIG9iaiBpbiB0aGlzLmVkZ2VzKVxuICB7XG4gICAgZWRnZSA9IHRoaXMuZWRnZXNbb2JqXTtcblxuICAgIGlmIChlZGdlLnNvdXJjZSA9PSB0aGlzKVxuICAgIHtcbiAgICAgIG5laWdoYm9ycy5hZGQoZWRnZS50YXJnZXQpO1xuICAgIH1cbiAgICBlbHNlXG4gICAge1xuICAgICAgaWYgKCFlZGdlLnRhcmdldCA9PSB0aGlzKVxuICAgICAgICB0aHJvdyBcIkluY29ycmVjdCBpbmNpZGVuY3khXCI7XG4gICAgICBuZWlnaGJvcnMuYWRkKGVkZ2Uuc291cmNlKTtcbiAgICB9XG4gIH1cblxuICByZXR1cm4gbmVpZ2hib3JzO1xufTtcblxuTE5vZGUucHJvdG90eXBlLndpdGhDaGlsZHJlbiA9IGZ1bmN0aW9uICgpXG57XG4gIHZhciB3aXRoTmVpZ2hib3JzTGlzdCA9IFtdO1xuICB2YXIgY2hpbGROb2RlO1xuXG4gIHdpdGhOZWlnaGJvcnNMaXN0LnB1c2godGhpcyk7XG5cbiAgaWYgKHRoaXMuY2hpbGQgIT0gbnVsbClcbiAge1xuICAgIHZhciBub2RlcyA9IHRoaXMuY2hpbGQuZ2V0Tm9kZXMoKTtcbiAgICBmb3IgKHZhciBpID0gMDsgaSA8IG5vZGVzLmxlbmd0aDsgaSsrKVxuICAgIHtcbiAgICAgIGNoaWxkTm9kZSA9IG5vZGVzW2ldO1xuXG4gICAgICB3aXRoTmVpZ2hib3JzTGlzdCA9IHdpdGhOZWlnaGJvcnNMaXN0LmNvbmNhdChjaGlsZE5vZGUud2l0aENoaWxkcmVuKCkpO1xuICAgIH1cbiAgfVxuXG4gIHJldHVybiB3aXRoTmVpZ2hib3JzTGlzdDtcbn07XG5cbkxOb2RlLnByb3RvdHlwZS5nZXRFc3RpbWF0ZWRTaXplID0gZnVuY3Rpb24gKCkge1xuICBpZiAodGhpcy5lc3RpbWF0ZWRTaXplID09IEludGVnZXIuTUlOX1ZBTFVFKSB7XG4gICAgdGhyb3cgXCJhc3NlcnQgZmFpbGVkXCI7XG4gIH1cbiAgcmV0dXJuIHRoaXMuZXN0aW1hdGVkU2l6ZTtcbn07XG5cbkxOb2RlLnByb3RvdHlwZS5jYWxjRXN0aW1hdGVkU2l6ZSA9IGZ1bmN0aW9uICgpIHtcbiAgaWYgKHRoaXMuY2hpbGQgPT0gbnVsbClcbiAge1xuICAgIHJldHVybiB0aGlzLmVzdGltYXRlZFNpemUgPSBNYXRoLmZsb29yKCh0aGlzLnJlY3Qud2lkdGggKyB0aGlzLnJlY3QuaGVpZ2h0KSAvIDIpO1xuICB9XG4gIGVsc2VcbiAge1xuICAgIHRoaXMuZXN0aW1hdGVkU2l6ZSA9IHRoaXMuY2hpbGQuY2FsY0VzdGltYXRlZFNpemUoKTtcbiAgICB0aGlzLnJlY3Qud2lkdGggPSB0aGlzLmVzdGltYXRlZFNpemU7XG4gICAgdGhpcy5yZWN0LmhlaWdodCA9IHRoaXMuZXN0aW1hdGVkU2l6ZTtcblxuICAgIHJldHVybiB0aGlzLmVzdGltYXRlZFNpemU7XG4gIH1cbn07XG5cbkxOb2RlLnByb3RvdHlwZS5zY2F0dGVyID0gZnVuY3Rpb24gKCkge1xuICB2YXIgcmFuZG9tQ2VudGVyWDtcbiAgdmFyIHJhbmRvbUNlbnRlclk7XG5cbiAgdmFyIG1pblggPSAtTGF5b3V0Q29uc3RhbnRzLklOSVRJQUxfV09STERfQk9VTkRBUlk7XG4gIHZhciBtYXhYID0gTGF5b3V0Q29uc3RhbnRzLklOSVRJQUxfV09STERfQk9VTkRBUlk7XG4gIHJhbmRvbUNlbnRlclggPSBMYXlvdXRDb25zdGFudHMuV09STERfQ0VOVEVSX1ggK1xuICAgICAgICAgIChSYW5kb21TZWVkLm5leHREb3VibGUoKSAqIChtYXhYIC0gbWluWCkpICsgbWluWDtcblxuICB2YXIgbWluWSA9IC1MYXlvdXRDb25zdGFudHMuSU5JVElBTF9XT1JMRF9CT1VOREFSWTtcbiAgdmFyIG1heFkgPSBMYXlvdXRDb25zdGFudHMuSU5JVElBTF9XT1JMRF9CT1VOREFSWTtcbiAgcmFuZG9tQ2VudGVyWSA9IExheW91dENvbnN0YW50cy5XT1JMRF9DRU5URVJfWSArXG4gICAgICAgICAgKFJhbmRvbVNlZWQubmV4dERvdWJsZSgpICogKG1heFkgLSBtaW5ZKSkgKyBtaW5ZO1xuXG4gIHRoaXMucmVjdC54ID0gcmFuZG9tQ2VudGVyWDtcbiAgdGhpcy5yZWN0LnkgPSByYW5kb21DZW50ZXJZXG59O1xuXG5MTm9kZS5wcm90b3R5cGUudXBkYXRlQm91bmRzID0gZnVuY3Rpb24gKCkge1xuICBpZiAodGhpcy5nZXRDaGlsZCgpID09IG51bGwpIHtcbiAgICB0aHJvdyBcImFzc2VydCBmYWlsZWRcIjtcbiAgfVxuICBpZiAodGhpcy5nZXRDaGlsZCgpLmdldE5vZGVzKCkubGVuZ3RoICE9IDApXG4gIHtcbiAgICAvLyB3cmFwIHRoZSBjaGlsZHJlbiBub2RlcyBieSByZS1hcnJhbmdpbmcgdGhlIGJvdW5kYXJpZXNcbiAgICB2YXIgY2hpbGRHcmFwaCA9IHRoaXMuZ2V0Q2hpbGQoKTtcbiAgICBjaGlsZEdyYXBoLnVwZGF0ZUJvdW5kcyh0cnVlKTtcblxuICAgIHRoaXMucmVjdC54ID0gY2hpbGRHcmFwaC5nZXRMZWZ0KCk7XG4gICAgdGhpcy5yZWN0LnkgPSBjaGlsZEdyYXBoLmdldFRvcCgpO1xuXG4gICAgdGhpcy5zZXRXaWR0aChjaGlsZEdyYXBoLmdldFJpZ2h0KCkgLSBjaGlsZEdyYXBoLmdldExlZnQoKSk7XG4gICAgdGhpcy5zZXRIZWlnaHQoY2hpbGRHcmFwaC5nZXRCb3R0b20oKSAtIGNoaWxkR3JhcGguZ2V0VG9wKCkpO1xuICB9XG59O1xuXG5MTm9kZS5wcm90b3R5cGUuZ2V0SW5jbHVzaW9uVHJlZURlcHRoID0gZnVuY3Rpb24gKClcbntcbiAgaWYgKHRoaXMuaW5jbHVzaW9uVHJlZURlcHRoID09IEludGVnZXIuTUFYX1ZBTFVFKSB7XG4gICAgdGhyb3cgXCJhc3NlcnQgZmFpbGVkXCI7XG4gIH1cbiAgcmV0dXJuIHRoaXMuaW5jbHVzaW9uVHJlZURlcHRoO1xufTtcblxuTE5vZGUucHJvdG90eXBlLnRyYW5zZm9ybSA9IGZ1bmN0aW9uICh0cmFucylcbntcbiAgdmFyIGxlZnQgPSB0aGlzLnJlY3QueDtcblxuICBpZiAobGVmdCA+IExheW91dENvbnN0YW50cy5XT1JMRF9CT1VOREFSWSlcbiAge1xuICAgIGxlZnQgPSBMYXlvdXRDb25zdGFudHMuV09STERfQk9VTkRBUlk7XG4gIH1cbiAgZWxzZSBpZiAobGVmdCA8IC1MYXlvdXRDb25zdGFudHMuV09STERfQk9VTkRBUlkpXG4gIHtcbiAgICBsZWZ0ID0gLUxheW91dENvbnN0YW50cy5XT1JMRF9CT1VOREFSWTtcbiAgfVxuXG4gIHZhciB0b3AgPSB0aGlzLnJlY3QueTtcblxuICBpZiAodG9wID4gTGF5b3V0Q29uc3RhbnRzLldPUkxEX0JPVU5EQVJZKVxuICB7XG4gICAgdG9wID0gTGF5b3V0Q29uc3RhbnRzLldPUkxEX0JPVU5EQVJZO1xuICB9XG4gIGVsc2UgaWYgKHRvcCA8IC1MYXlvdXRDb25zdGFudHMuV09STERfQk9VTkRBUlkpXG4gIHtcbiAgICB0b3AgPSAtTGF5b3V0Q29uc3RhbnRzLldPUkxEX0JPVU5EQVJZO1xuICB9XG5cbiAgdmFyIGxlZnRUb3AgPSBuZXcgUG9pbnREKGxlZnQsIHRvcCk7XG4gIHZhciB2TGVmdFRvcCA9IHRyYW5zLmludmVyc2VUcmFuc2Zvcm1Qb2ludChsZWZ0VG9wKTtcblxuICB0aGlzLnNldExvY2F0aW9uKHZMZWZ0VG9wLngsIHZMZWZ0VG9wLnkpO1xufTtcblxuTE5vZGUucHJvdG90eXBlLmdldExlZnQgPSBmdW5jdGlvbiAoKVxue1xuICByZXR1cm4gdGhpcy5yZWN0Lng7XG59O1xuXG5MTm9kZS5wcm90b3R5cGUuZ2V0UmlnaHQgPSBmdW5jdGlvbiAoKVxue1xuICByZXR1cm4gdGhpcy5yZWN0LnggKyB0aGlzLnJlY3Qud2lkdGg7XG59O1xuXG5MTm9kZS5wcm90b3R5cGUuZ2V0VG9wID0gZnVuY3Rpb24gKClcbntcbiAgcmV0dXJuIHRoaXMucmVjdC55O1xufTtcblxuTE5vZGUucHJvdG90eXBlLmdldEJvdHRvbSA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLnJlY3QueSArIHRoaXMucmVjdC5oZWlnaHQ7XG59O1xuXG5MTm9kZS5wcm90b3R5cGUuZ2V0UGFyZW50ID0gZnVuY3Rpb24gKClcbntcbiAgaWYgKHRoaXMub3duZXIgPT0gbnVsbClcbiAge1xuICAgIHJldHVybiBudWxsO1xuICB9XG5cbiAgcmV0dXJuIHRoaXMub3duZXIuZ2V0UGFyZW50KCk7XG59O1xuXG5tb2R1bGUuZXhwb3J0cyA9IExOb2RlO1xuIiwidmFyIExheW91dENvbnN0YW50cyA9IHJlcXVpcmUoJy4vTGF5b3V0Q29uc3RhbnRzJyk7XG52YXIgSGFzaE1hcCA9IHJlcXVpcmUoJy4vSGFzaE1hcCcpO1xudmFyIExHcmFwaE1hbmFnZXIgPSByZXF1aXJlKCcuL0xHcmFwaE1hbmFnZXInKTtcbnZhciBMTm9kZSA9IHJlcXVpcmUoJy4vTE5vZGUnKTtcbnZhciBMRWRnZSA9IHJlcXVpcmUoJy4vTEVkZ2UnKTtcbnZhciBMR3JhcGggPSByZXF1aXJlKCcuL0xHcmFwaCcpO1xudmFyIFBvaW50RCA9IHJlcXVpcmUoJy4vUG9pbnREJyk7XG52YXIgVHJhbnNmb3JtID0gcmVxdWlyZSgnLi9UcmFuc2Zvcm0nKTtcbnZhciBFbWl0dGVyID0gcmVxdWlyZSgnLi9FbWl0dGVyJyk7XG52YXIgSGFzaFNldCA9IHJlcXVpcmUoJy4vSGFzaFNldCcpO1xuXG5mdW5jdGlvbiBMYXlvdXQoaXNSZW1vdGVVc2UpIHtcbiAgRW1pdHRlci5jYWxsKCB0aGlzICk7XG5cbiAgLy9MYXlvdXQgUXVhbGl0eTogMDpwcm9vZiwgMTpkZWZhdWx0LCAyOmRyYWZ0XG4gIHRoaXMubGF5b3V0UXVhbGl0eSA9IExheW91dENvbnN0YW50cy5ERUZBVUxUX1FVQUxJVFk7XG4gIC8vV2hldGhlciBsYXlvdXQgc2hvdWxkIGNyZWF0ZSBiZW5kcG9pbnRzIGFzIG5lZWRlZCBvciBub3RcbiAgdGhpcy5jcmVhdGVCZW5kc0FzTmVlZGVkID1cbiAgICAgICAgICBMYXlvdXRDb25zdGFudHMuREVGQVVMVF9DUkVBVEVfQkVORFNfQVNfTkVFREVEO1xuICAvL1doZXRoZXIgbGF5b3V0IHNob3VsZCBiZSBpbmNyZW1lbnRhbCBvciBub3RcbiAgdGhpcy5pbmNyZW1lbnRhbCA9IExheW91dENvbnN0YW50cy5ERUZBVUxUX0lOQ1JFTUVOVEFMO1xuICAvL1doZXRoZXIgd2UgYW5pbWF0ZSBmcm9tIGJlZm9yZSB0byBhZnRlciBsYXlvdXQgbm9kZSBwb3NpdGlvbnNcbiAgdGhpcy5hbmltYXRpb25PbkxheW91dCA9XG4gICAgICAgICAgTGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfQU5JTUFUSU9OX09OX0xBWU9VVDtcbiAgLy9XaGV0aGVyIHdlIGFuaW1hdGUgdGhlIGxheW91dCBwcm9jZXNzIG9yIG5vdFxuICB0aGlzLmFuaW1hdGlvbkR1cmluZ0xheW91dCA9IExheW91dENvbnN0YW50cy5ERUZBVUxUX0FOSU1BVElPTl9EVVJJTkdfTEFZT1VUO1xuICAvL051bWJlciBpdGVyYXRpb25zIHRoYXQgc2hvdWxkIGJlIGRvbmUgYmV0d2VlbiB0d28gc3VjY2Vzc2l2ZSBhbmltYXRpb25zXG4gIHRoaXMuYW5pbWF0aW9uUGVyaW9kID0gTGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfQU5JTUFUSU9OX1BFUklPRDtcbiAgLyoqXG4gICAqIFdoZXRoZXIgb3Igbm90IGxlYWYgbm9kZXMgKG5vbi1jb21wb3VuZCBub2RlcykgYXJlIG9mIHVuaWZvcm0gc2l6ZXMuIFdoZW5cbiAgICogdGhleSBhcmUsIGJvdGggc3ByaW5nIGFuZCByZXB1bHNpb24gZm9yY2VzIGJldHdlZW4gdHdvIGxlYWYgbm9kZXMgY2FuIGJlXG4gICAqIGNhbGN1bGF0ZWQgd2l0aG91dCB0aGUgZXhwZW5zaXZlIGNsaXBwaW5nIHBvaW50IGNhbGN1bGF0aW9ucywgcmVzdWx0aW5nXG4gICAqIGluIG1ham9yIHNwZWVkLXVwLlxuICAgKi9cbiAgdGhpcy51bmlmb3JtTGVhZk5vZGVTaXplcyA9XG4gICAgICAgICAgTGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfVU5JRk9STV9MRUFGX05PREVfU0laRVM7XG4gIC8qKlxuICAgKiBUaGlzIGlzIHVzZWQgZm9yIGNyZWF0aW9uIG9mIGJlbmRwb2ludHMgYnkgdXNpbmcgZHVtbXkgbm9kZXMgYW5kIGVkZ2VzLlxuICAgKiBNYXBzIGFuIExFZGdlIHRvIGl0cyBkdW1teSBiZW5kcG9pbnQgcGF0aC5cbiAgICovXG4gIHRoaXMuZWRnZVRvRHVtbXlOb2RlcyA9IG5ldyBIYXNoTWFwKCk7XG4gIHRoaXMuZ3JhcGhNYW5hZ2VyID0gbmV3IExHcmFwaE1hbmFnZXIodGhpcyk7XG4gIHRoaXMuaXNMYXlvdXRGaW5pc2hlZCA9IGZhbHNlO1xuICB0aGlzLmlzU3ViTGF5b3V0ID0gZmFsc2U7XG4gIHRoaXMuaXNSZW1vdGVVc2UgPSBmYWxzZTtcblxuICBpZiAoaXNSZW1vdGVVc2UgIT0gbnVsbCkge1xuICAgIHRoaXMuaXNSZW1vdGVVc2UgPSBpc1JlbW90ZVVzZTtcbiAgfVxufVxuXG5MYXlvdXQuUkFORE9NX1NFRUQgPSAxO1xuXG5MYXlvdXQucHJvdG90eXBlID0gT2JqZWN0LmNyZWF0ZSggRW1pdHRlci5wcm90b3R5cGUgKTtcblxuTGF5b3V0LnByb3RvdHlwZS5nZXRHcmFwaE1hbmFnZXIgPSBmdW5jdGlvbiAoKSB7XG4gIHJldHVybiB0aGlzLmdyYXBoTWFuYWdlcjtcbn07XG5cbkxheW91dC5wcm90b3R5cGUuZ2V0QWxsTm9kZXMgPSBmdW5jdGlvbiAoKSB7XG4gIHJldHVybiB0aGlzLmdyYXBoTWFuYWdlci5nZXRBbGxOb2RlcygpO1xufTtcblxuTGF5b3V0LnByb3RvdHlwZS5nZXRBbGxFZGdlcyA9IGZ1bmN0aW9uICgpIHtcbiAgcmV0dXJuIHRoaXMuZ3JhcGhNYW5hZ2VyLmdldEFsbEVkZ2VzKCk7XG59O1xuXG5MYXlvdXQucHJvdG90eXBlLmdldEFsbE5vZGVzVG9BcHBseUdyYXZpdGF0aW9uID0gZnVuY3Rpb24gKCkge1xuICByZXR1cm4gdGhpcy5ncmFwaE1hbmFnZXIuZ2V0QWxsTm9kZXNUb0FwcGx5R3Jhdml0YXRpb24oKTtcbn07XG5cbkxheW91dC5wcm90b3R5cGUubmV3R3JhcGhNYW5hZ2VyID0gZnVuY3Rpb24gKCkge1xuICB2YXIgZ20gPSBuZXcgTEdyYXBoTWFuYWdlcih0aGlzKTtcbiAgdGhpcy5ncmFwaE1hbmFnZXIgPSBnbTtcbiAgcmV0dXJuIGdtO1xufTtcblxuTGF5b3V0LnByb3RvdHlwZS5uZXdHcmFwaCA9IGZ1bmN0aW9uICh2R3JhcGgpXG57XG4gIHJldHVybiBuZXcgTEdyYXBoKG51bGwsIHRoaXMuZ3JhcGhNYW5hZ2VyLCB2R3JhcGgpO1xufTtcblxuTGF5b3V0LnByb3RvdHlwZS5uZXdOb2RlID0gZnVuY3Rpb24gKHZOb2RlKVxue1xuICByZXR1cm4gbmV3IExOb2RlKHRoaXMuZ3JhcGhNYW5hZ2VyLCB2Tm9kZSk7XG59O1xuXG5MYXlvdXQucHJvdG90eXBlLm5ld0VkZ2UgPSBmdW5jdGlvbiAodkVkZ2UpXG57XG4gIHJldHVybiBuZXcgTEVkZ2UobnVsbCwgbnVsbCwgdkVkZ2UpO1xufTtcblxuTGF5b3V0LnByb3RvdHlwZS5ydW5MYXlvdXQgPSBmdW5jdGlvbiAoKVxue1xuICB0aGlzLmlzTGF5b3V0RmluaXNoZWQgPSBmYWxzZTtcblxuICB0aGlzLmluaXRQYXJhbWV0ZXJzKCk7XG4gIHZhciBpc0xheW91dFN1Y2Nlc3NmdWxsO1xuXG4gIGlmICgodGhpcy5ncmFwaE1hbmFnZXIuZ2V0Um9vdCgpID09IG51bGwpXG4gICAgICAgICAgfHwgdGhpcy5ncmFwaE1hbmFnZXIuZ2V0Um9vdCgpLmdldE5vZGVzKCkubGVuZ3RoID09IDBcbiAgICAgICAgICB8fCB0aGlzLmdyYXBoTWFuYWdlci5pbmNsdWRlc0ludmFsaWRFZGdlKCkpXG4gIHtcbiAgICBpc0xheW91dFN1Y2Nlc3NmdWxsID0gZmFsc2U7XG4gIH1cbiAgZWxzZVxuICB7XG4gICAgLy8gY2FsY3VsYXRlIGV4ZWN1dGlvbiB0aW1lXG4gICAgdmFyIHN0YXJ0VGltZSA9IDA7XG5cbiAgICBpZiAoIXRoaXMuaXNTdWJMYXlvdXQpXG4gICAge1xuICAgICAgc3RhcnRUaW1lID0gbmV3IERhdGUoKS5nZXRUaW1lKClcbiAgICB9XG5cbiAgICBpc0xheW91dFN1Y2Nlc3NmdWxsID0gdGhpcy5sYXlvdXQoKTtcblxuICAgIGlmICghdGhpcy5pc1N1YkxheW91dClcbiAgICB7XG4gICAgICB2YXIgZW5kVGltZSA9IG5ldyBEYXRlKCkuZ2V0VGltZSgpO1xuICAgICAgdmFyIGV4Y1RpbWUgPSBlbmRUaW1lIC0gc3RhcnRUaW1lO1xuICAgIH1cbiAgfVxuXG4gIGlmIChpc0xheW91dFN1Y2Nlc3NmdWxsKVxuICB7XG4gICAgaWYgKCF0aGlzLmlzU3ViTGF5b3V0KVxuICAgIHtcbiAgICAgIHRoaXMuZG9Qb3N0TGF5b3V0KCk7XG4gICAgfVxuICB9XG5cbiAgdGhpcy5pc0xheW91dEZpbmlzaGVkID0gdHJ1ZTtcblxuICByZXR1cm4gaXNMYXlvdXRTdWNjZXNzZnVsbDtcbn07XG5cbi8qKlxuICogVGhpcyBtZXRob2QgcGVyZm9ybXMgdGhlIG9wZXJhdGlvbnMgcmVxdWlyZWQgYWZ0ZXIgbGF5b3V0LlxuICovXG5MYXlvdXQucHJvdG90eXBlLmRvUG9zdExheW91dCA9IGZ1bmN0aW9uICgpXG57XG4gIC8vYXNzZXJ0ICFpc1N1YkxheW91dCA6IFwiU2hvdWxkIG5vdCBiZSBjYWxsZWQgb24gc3ViLWxheW91dCFcIjtcbiAgLy8gUHJvcGFnYXRlIGdlb21ldHJpYyBjaGFuZ2VzIHRvIHYtbGV2ZWwgb2JqZWN0c1xuICB0aGlzLnRyYW5zZm9ybSgpO1xuICB0aGlzLnVwZGF0ZSgpO1xufTtcblxuLyoqXG4gKiBUaGlzIG1ldGhvZCB1cGRhdGVzIHRoZSBnZW9tZXRyeSBvZiB0aGUgdGFyZ2V0IGdyYXBoIGFjY29yZGluZyB0b1xuICogY2FsY3VsYXRlZCBsYXlvdXQuXG4gKi9cbkxheW91dC5wcm90b3R5cGUudXBkYXRlMiA9IGZ1bmN0aW9uICgpIHtcbiAgLy8gdXBkYXRlIGJlbmQgcG9pbnRzXG4gIGlmICh0aGlzLmNyZWF0ZUJlbmRzQXNOZWVkZWQpXG4gIHtcbiAgICB0aGlzLmNyZWF0ZUJlbmRwb2ludHNGcm9tRHVtbXlOb2RlcygpO1xuXG4gICAgLy8gcmVzZXQgYWxsIGVkZ2VzLCBzaW5jZSB0aGUgdG9wb2xvZ3kgaGFzIGNoYW5nZWRcbiAgICB0aGlzLmdyYXBoTWFuYWdlci5yZXNldEFsbEVkZ2VzKCk7XG4gIH1cblxuICAvLyBwZXJmb3JtIGVkZ2UsIG5vZGUgYW5kIHJvb3QgdXBkYXRlcyBpZiBsYXlvdXQgaXMgbm90IGNhbGxlZFxuICAvLyByZW1vdGVseVxuICBpZiAoIXRoaXMuaXNSZW1vdGVVc2UpXG4gIHtcbiAgICAvLyB1cGRhdGUgYWxsIGVkZ2VzXG4gICAgdmFyIGVkZ2U7XG4gICAgdmFyIGFsbEVkZ2VzID0gdGhpcy5ncmFwaE1hbmFnZXIuZ2V0QWxsRWRnZXMoKTtcbiAgICBmb3IgKHZhciBpID0gMDsgaSA8IGFsbEVkZ2VzLmxlbmd0aDsgaSsrKVxuICAgIHtcbiAgICAgIGVkZ2UgPSBhbGxFZGdlc1tpXTtcbi8vICAgICAgdGhpcy51cGRhdGUoZWRnZSk7XG4gICAgfVxuXG4gICAgLy8gcmVjdXJzaXZlbHkgdXBkYXRlIG5vZGVzXG4gICAgdmFyIG5vZGU7XG4gICAgdmFyIG5vZGVzID0gdGhpcy5ncmFwaE1hbmFnZXIuZ2V0Um9vdCgpLmdldE5vZGVzKCk7XG4gICAgZm9yICh2YXIgaSA9IDA7IGkgPCBub2Rlcy5sZW5ndGg7IGkrKylcbiAgICB7XG4gICAgICBub2RlID0gbm9kZXNbaV07XG4vLyAgICAgIHRoaXMudXBkYXRlKG5vZGUpO1xuICAgIH1cblxuICAgIC8vIHVwZGF0ZSByb290IGdyYXBoXG4gICAgdGhpcy51cGRhdGUodGhpcy5ncmFwaE1hbmFnZXIuZ2V0Um9vdCgpKTtcbiAgfVxufTtcblxuTGF5b3V0LnByb3RvdHlwZS51cGRhdGUgPSBmdW5jdGlvbiAob2JqKSB7XG4gIGlmIChvYmogPT0gbnVsbCkge1xuICAgIHRoaXMudXBkYXRlMigpO1xuICB9XG4gIGVsc2UgaWYgKG9iaiBpbnN0YW5jZW9mIExOb2RlKSB7XG4gICAgdmFyIG5vZGUgPSBvYmo7XG4gICAgaWYgKG5vZGUuZ2V0Q2hpbGQoKSAhPSBudWxsKVxuICAgIHtcbiAgICAgIC8vIHNpbmNlIG5vZGUgaXMgY29tcG91bmQsIHJlY3Vyc2l2ZWx5IHVwZGF0ZSBjaGlsZCBub2Rlc1xuICAgICAgdmFyIG5vZGVzID0gbm9kZS5nZXRDaGlsZCgpLmdldE5vZGVzKCk7XG4gICAgICBmb3IgKHZhciBpID0gMDsgaSA8IG5vZGVzLmxlbmd0aDsgaSsrKVxuICAgICAge1xuICAgICAgICB1cGRhdGUobm9kZXNbaV0pO1xuICAgICAgfVxuICAgIH1cblxuICAgIC8vIGlmIHRoZSBsLWxldmVsIG5vZGUgaXMgYXNzb2NpYXRlZCB3aXRoIGEgdi1sZXZlbCBncmFwaCBvYmplY3QsXG4gICAgLy8gdGhlbiBpdCBpcyBhc3N1bWVkIHRoYXQgdGhlIHYtbGV2ZWwgbm9kZSBpbXBsZW1lbnRzIHRoZVxuICAgIC8vIGludGVyZmFjZSBVcGRhdGFibGUuXG4gICAgaWYgKG5vZGUudkdyYXBoT2JqZWN0ICE9IG51bGwpXG4gICAge1xuICAgICAgLy8gY2FzdCB0byBVcGRhdGFibGUgd2l0aG91dCBhbnkgdHlwZSBjaGVja1xuICAgICAgdmFyIHZOb2RlID0gbm9kZS52R3JhcGhPYmplY3Q7XG5cbiAgICAgIC8vIGNhbGwgdGhlIHVwZGF0ZSBtZXRob2Qgb2YgdGhlIGludGVyZmFjZVxuICAgICAgdk5vZGUudXBkYXRlKG5vZGUpO1xuICAgIH1cbiAgfVxuICBlbHNlIGlmIChvYmogaW5zdGFuY2VvZiBMRWRnZSkge1xuICAgIHZhciBlZGdlID0gb2JqO1xuICAgIC8vIGlmIHRoZSBsLWxldmVsIGVkZ2UgaXMgYXNzb2NpYXRlZCB3aXRoIGEgdi1sZXZlbCBncmFwaCBvYmplY3QsXG4gICAgLy8gdGhlbiBpdCBpcyBhc3N1bWVkIHRoYXQgdGhlIHYtbGV2ZWwgZWRnZSBpbXBsZW1lbnRzIHRoZVxuICAgIC8vIGludGVyZmFjZSBVcGRhdGFibGUuXG5cbiAgICBpZiAoZWRnZS52R3JhcGhPYmplY3QgIT0gbnVsbClcbiAgICB7XG4gICAgICAvLyBjYXN0IHRvIFVwZGF0YWJsZSB3aXRob3V0IGFueSB0eXBlIGNoZWNrXG4gICAgICB2YXIgdkVkZ2UgPSBlZGdlLnZHcmFwaE9iamVjdDtcblxuICAgICAgLy8gY2FsbCB0aGUgdXBkYXRlIG1ldGhvZCBvZiB0aGUgaW50ZXJmYWNlXG4gICAgICB2RWRnZS51cGRhdGUoZWRnZSk7XG4gICAgfVxuICB9XG4gIGVsc2UgaWYgKG9iaiBpbnN0YW5jZW9mIExHcmFwaCkge1xuICAgIHZhciBncmFwaCA9IG9iajtcbiAgICAvLyBpZiB0aGUgbC1sZXZlbCBncmFwaCBpcyBhc3NvY2lhdGVkIHdpdGggYSB2LWxldmVsIGdyYXBoIG9iamVjdCxcbiAgICAvLyB0aGVuIGl0IGlzIGFzc3VtZWQgdGhhdCB0aGUgdi1sZXZlbCBvYmplY3QgaW1wbGVtZW50cyB0aGVcbiAgICAvLyBpbnRlcmZhY2UgVXBkYXRhYmxlLlxuXG4gICAgaWYgKGdyYXBoLnZHcmFwaE9iamVjdCAhPSBudWxsKVxuICAgIHtcbiAgICAgIC8vIGNhc3QgdG8gVXBkYXRhYmxlIHdpdGhvdXQgYW55IHR5cGUgY2hlY2tcbiAgICAgIHZhciB2R3JhcGggPSBncmFwaC52R3JhcGhPYmplY3Q7XG5cbiAgICAgIC8vIGNhbGwgdGhlIHVwZGF0ZSBtZXRob2Qgb2YgdGhlIGludGVyZmFjZVxuICAgICAgdkdyYXBoLnVwZGF0ZShncmFwaCk7XG4gICAgfVxuICB9XG59O1xuXG4vKipcbiAqIFRoaXMgbWV0aG9kIGlzIHVzZWQgdG8gc2V0IGFsbCBsYXlvdXQgcGFyYW1ldGVycyB0byBkZWZhdWx0IHZhbHVlc1xuICogZGV0ZXJtaW5lZCBhdCBjb21waWxlIHRpbWUuXG4gKi9cbkxheW91dC5wcm90b3R5cGUuaW5pdFBhcmFtZXRlcnMgPSBmdW5jdGlvbiAoKSB7XG4gIGlmICghdGhpcy5pc1N1YkxheW91dClcbiAge1xuICAgIHRoaXMubGF5b3V0UXVhbGl0eSA9IExheW91dENvbnN0YW50cy5ERUZBVUxUX1FVQUxJVFk7XG4gICAgdGhpcy5hbmltYXRpb25EdXJpbmdMYXlvdXQgPSBMYXlvdXRDb25zdGFudHMuREVGQVVMVF9BTklNQVRJT05fT05fTEFZT1VUO1xuICAgIHRoaXMuYW5pbWF0aW9uUGVyaW9kID0gTGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfQU5JTUFUSU9OX1BFUklPRDtcbiAgICB0aGlzLmFuaW1hdGlvbk9uTGF5b3V0ID0gTGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfQU5JTUFUSU9OX0RVUklOR19MQVlPVVQ7XG4gICAgdGhpcy5pbmNyZW1lbnRhbCA9IExheW91dENvbnN0YW50cy5ERUZBVUxUX0lOQ1JFTUVOVEFMO1xuICAgIHRoaXMuY3JlYXRlQmVuZHNBc05lZWRlZCA9IExheW91dENvbnN0YW50cy5ERUZBVUxUX0NSRUFURV9CRU5EU19BU19ORUVERUQ7XG4gICAgdGhpcy51bmlmb3JtTGVhZk5vZGVTaXplcyA9IExheW91dENvbnN0YW50cy5ERUZBVUxUX1VOSUZPUk1fTEVBRl9OT0RFX1NJWkVTO1xuICB9XG5cbiAgaWYgKHRoaXMuYW5pbWF0aW9uRHVyaW5nTGF5b3V0KVxuICB7XG4gICAgYW5pbWF0aW9uT25MYXlvdXQgPSBmYWxzZTtcbiAgfVxufTtcblxuTGF5b3V0LnByb3RvdHlwZS50cmFuc2Zvcm0gPSBmdW5jdGlvbiAobmV3TGVmdFRvcCkge1xuICBpZiAobmV3TGVmdFRvcCA9PSB1bmRlZmluZWQpIHtcbiAgICB0aGlzLnRyYW5zZm9ybShuZXcgUG9pbnREKDAsIDApKTtcbiAgfVxuICBlbHNlIHtcbiAgICAvLyBjcmVhdGUgYSB0cmFuc2Zvcm1hdGlvbiBvYmplY3QgKGZyb20gRWNsaXBzZSB0byBsYXlvdXQpLiBXaGVuIGFuXG4gICAgLy8gaW52ZXJzZSB0cmFuc2Zvcm0gaXMgYXBwbGllZCwgd2UgZ2V0IHVwcGVyLWxlZnQgY29vcmRpbmF0ZSBvZiB0aGVcbiAgICAvLyBkcmF3aW5nIG9yIHRoZSByb290IGdyYXBoIGF0IGdpdmVuIGlucHV0IGNvb3JkaW5hdGUgKHNvbWUgbWFyZ2luc1xuICAgIC8vIGFscmVhZHkgaW5jbHVkZWQgaW4gY2FsY3VsYXRpb24gb2YgbGVmdC10b3ApLlxuXG4gICAgdmFyIHRyYW5zID0gbmV3IFRyYW5zZm9ybSgpO1xuICAgIHZhciBsZWZ0VG9wID0gdGhpcy5ncmFwaE1hbmFnZXIuZ2V0Um9vdCgpLnVwZGF0ZUxlZnRUb3AoKTtcblxuICAgIGlmIChsZWZ0VG9wICE9IG51bGwpXG4gICAge1xuICAgICAgdHJhbnMuc2V0V29ybGRPcmdYKG5ld0xlZnRUb3AueCk7XG4gICAgICB0cmFucy5zZXRXb3JsZE9yZ1kobmV3TGVmdFRvcC55KTtcblxuICAgICAgdHJhbnMuc2V0RGV2aWNlT3JnWChsZWZ0VG9wLngpO1xuICAgICAgdHJhbnMuc2V0RGV2aWNlT3JnWShsZWZ0VG9wLnkpO1xuXG4gICAgICB2YXIgbm9kZXMgPSB0aGlzLmdldEFsbE5vZGVzKCk7XG4gICAgICB2YXIgbm9kZTtcblxuICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCBub2Rlcy5sZW5ndGg7IGkrKylcbiAgICAgIHtcbiAgICAgICAgbm9kZSA9IG5vZGVzW2ldO1xuICAgICAgICBub2RlLnRyYW5zZm9ybSh0cmFucyk7XG4gICAgICB9XG4gICAgfVxuICB9XG59O1xuXG5MYXlvdXQucHJvdG90eXBlLnBvc2l0aW9uTm9kZXNSYW5kb21seSA9IGZ1bmN0aW9uIChncmFwaCkge1xuXG4gIGlmIChncmFwaCA9PSB1bmRlZmluZWQpIHtcbiAgICAvL2Fzc2VydCAhdGhpcy5pbmNyZW1lbnRhbDtcbiAgICB0aGlzLnBvc2l0aW9uTm9kZXNSYW5kb21seSh0aGlzLmdldEdyYXBoTWFuYWdlcigpLmdldFJvb3QoKSk7XG4gICAgdGhpcy5nZXRHcmFwaE1hbmFnZXIoKS5nZXRSb290KCkudXBkYXRlQm91bmRzKHRydWUpO1xuICB9XG4gIGVsc2Uge1xuICAgIHZhciBsTm9kZTtcbiAgICB2YXIgY2hpbGRHcmFwaDtcblxuICAgIHZhciBub2RlcyA9IGdyYXBoLmdldE5vZGVzKCk7XG4gICAgZm9yICh2YXIgaSA9IDA7IGkgPCBub2Rlcy5sZW5ndGg7IGkrKylcbiAgICB7XG4gICAgICBsTm9kZSA9IG5vZGVzW2ldO1xuICAgICAgY2hpbGRHcmFwaCA9IGxOb2RlLmdldENoaWxkKCk7XG5cbiAgICAgIGlmIChjaGlsZEdyYXBoID09IG51bGwpXG4gICAgICB7XG4gICAgICAgIGxOb2RlLnNjYXR0ZXIoKTtcbiAgICAgIH1cbiAgICAgIGVsc2UgaWYgKGNoaWxkR3JhcGguZ2V0Tm9kZXMoKS5sZW5ndGggPT0gMClcbiAgICAgIHtcbiAgICAgICAgbE5vZGUuc2NhdHRlcigpO1xuICAgICAgfVxuICAgICAgZWxzZVxuICAgICAge1xuICAgICAgICB0aGlzLnBvc2l0aW9uTm9kZXNSYW5kb21seShjaGlsZEdyYXBoKTtcbiAgICAgICAgbE5vZGUudXBkYXRlQm91bmRzKCk7XG4gICAgICB9XG4gICAgfVxuICB9XG59O1xuXG4vKipcbiAqIFRoaXMgbWV0aG9kIHJldHVybnMgYSBsaXN0IG9mIHRyZWVzIHdoZXJlIGVhY2ggdHJlZSBpcyByZXByZXNlbnRlZCBhcyBhXG4gKiBsaXN0IG9mIGwtbm9kZXMuIFRoZSBtZXRob2QgcmV0dXJucyBhIGxpc3Qgb2Ygc2l6ZSAwIHdoZW46XG4gKiAtIFRoZSBncmFwaCBpcyBub3QgZmxhdCBvclxuICogLSBPbmUgb2YgdGhlIGNvbXBvbmVudChzKSBvZiB0aGUgZ3JhcGggaXMgbm90IGEgdHJlZS5cbiAqL1xuTGF5b3V0LnByb3RvdHlwZS5nZXRGbGF0Rm9yZXN0ID0gZnVuY3Rpb24gKClcbntcbiAgdmFyIGZsYXRGb3Jlc3QgPSBbXTtcbiAgdmFyIGlzRm9yZXN0ID0gdHJ1ZTtcblxuICAvLyBRdWljayByZWZlcmVuY2UgZm9yIGFsbCBub2RlcyBpbiB0aGUgZ3JhcGggbWFuYWdlciBhc3NvY2lhdGVkIHdpdGhcbiAgLy8gdGhpcyBsYXlvdXQuIFRoZSBsaXN0IHNob3VsZCBub3QgYmUgY2hhbmdlZC5cbiAgdmFyIGFsbE5vZGVzID0gdGhpcy5ncmFwaE1hbmFnZXIuZ2V0Um9vdCgpLmdldE5vZGVzKCk7XG5cbiAgLy8gRmlyc3QgYmUgc3VyZSB0aGF0IHRoZSBncmFwaCBpcyBmbGF0XG4gIHZhciBpc0ZsYXQgPSB0cnVlO1xuXG4gIGZvciAodmFyIGkgPSAwOyBpIDwgYWxsTm9kZXMubGVuZ3RoOyBpKyspXG4gIHtcbiAgICBpZiAoYWxsTm9kZXNbaV0uZ2V0Q2hpbGQoKSAhPSBudWxsKVxuICAgIHtcbiAgICAgIGlzRmxhdCA9IGZhbHNlO1xuICAgIH1cbiAgfVxuXG4gIC8vIFJldHVybiBlbXB0eSBmb3Jlc3QgaWYgdGhlIGdyYXBoIGlzIG5vdCBmbGF0LlxuICBpZiAoIWlzRmxhdClcbiAge1xuICAgIHJldHVybiBmbGF0Rm9yZXN0O1xuICB9XG5cbiAgLy8gUnVuIEJGUyBmb3IgZWFjaCBjb21wb25lbnQgb2YgdGhlIGdyYXBoLlxuXG4gIHZhciB2aXNpdGVkID0gbmV3IEhhc2hTZXQoKTtcbiAgdmFyIHRvQmVWaXNpdGVkID0gW107XG4gIHZhciBwYXJlbnRzID0gbmV3IEhhc2hNYXAoKTtcbiAgdmFyIHVuUHJvY2Vzc2VkTm9kZXMgPSBbXTtcblxuICB1blByb2Nlc3NlZE5vZGVzID0gdW5Qcm9jZXNzZWROb2Rlcy5jb25jYXQoYWxsTm9kZXMpO1xuXG4gIC8vIEVhY2ggaXRlcmF0aW9uIG9mIHRoaXMgbG9vcCBmaW5kcyBhIGNvbXBvbmVudCBvZiB0aGUgZ3JhcGggYW5kXG4gIC8vIGRlY2lkZXMgd2hldGhlciBpdCBpcyBhIHRyZWUgb3Igbm90LiBJZiBpdCBpcyBhIHRyZWUsIGFkZHMgaXQgdG8gdGhlXG4gIC8vIGZvcmVzdCBhbmQgY29udGludWVkIHdpdGggdGhlIG5leHQgY29tcG9uZW50LlxuXG4gIHdoaWxlICh1blByb2Nlc3NlZE5vZGVzLmxlbmd0aCA+IDAgJiYgaXNGb3Jlc3QpXG4gIHtcbiAgICB0b0JlVmlzaXRlZC5wdXNoKHVuUHJvY2Vzc2VkTm9kZXNbMF0pO1xuXG4gICAgLy8gU3RhcnQgdGhlIEJGUy4gRWFjaCBpdGVyYXRpb24gb2YgdGhpcyBsb29wIHZpc2l0cyBhIG5vZGUgaW4gYVxuICAgIC8vIEJGUyBtYW5uZXIuXG4gICAgd2hpbGUgKHRvQmVWaXNpdGVkLmxlbmd0aCA+IDAgJiYgaXNGb3Jlc3QpXG4gICAge1xuICAgICAgLy9wb29sIG9wZXJhdGlvblxuICAgICAgdmFyIGN1cnJlbnROb2RlID0gdG9CZVZpc2l0ZWRbMF07XG4gICAgICB0b0JlVmlzaXRlZC5zcGxpY2UoMCwgMSk7XG4gICAgICB2aXNpdGVkLmFkZChjdXJyZW50Tm9kZSk7XG5cbiAgICAgIC8vIFRyYXZlcnNlIGFsbCBuZWlnaGJvcnMgb2YgdGhpcyBub2RlXG4gICAgICB2YXIgbmVpZ2hib3JFZGdlcyA9IGN1cnJlbnROb2RlLmdldEVkZ2VzKCk7XG5cbiAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwgbmVpZ2hib3JFZGdlcy5sZW5ndGg7IGkrKylcbiAgICAgIHtcbiAgICAgICAgdmFyIGN1cnJlbnROZWlnaGJvciA9XG4gICAgICAgICAgICAgICAgbmVpZ2hib3JFZGdlc1tpXS5nZXRPdGhlckVuZChjdXJyZW50Tm9kZSk7XG5cbiAgICAgICAgLy8gSWYgQkZTIGlzIG5vdCBncm93aW5nIGZyb20gdGhpcyBuZWlnaGJvci5cbiAgICAgICAgaWYgKHBhcmVudHMuZ2V0KGN1cnJlbnROb2RlKSAhPSBjdXJyZW50TmVpZ2hib3IpXG4gICAgICAgIHtcbiAgICAgICAgICAvLyBXZSBoYXZlbid0IHByZXZpb3VzbHkgdmlzaXRlZCB0aGlzIG5laWdoYm9yLlxuICAgICAgICAgIGlmICghdmlzaXRlZC5jb250YWlucyhjdXJyZW50TmVpZ2hib3IpKVxuICAgICAgICAgIHtcbiAgICAgICAgICAgIHRvQmVWaXNpdGVkLnB1c2goY3VycmVudE5laWdoYm9yKTtcbiAgICAgICAgICAgIHBhcmVudHMucHV0KGN1cnJlbnROZWlnaGJvciwgY3VycmVudE5vZGUpO1xuICAgICAgICAgIH1cbiAgICAgICAgICAvLyBTaW5jZSB3ZSBoYXZlIHByZXZpb3VzbHkgdmlzaXRlZCB0aGlzIG5laWdoYm9yIGFuZFxuICAgICAgICAgIC8vIHRoaXMgbmVpZ2hib3IgaXMgbm90IHBhcmVudCBvZiBjdXJyZW50Tm9kZSwgZ2l2ZW5cbiAgICAgICAgICAvLyBncmFwaCBjb250YWlucyBhIGNvbXBvbmVudCB0aGF0IGlzIG5vdCB0cmVlLCBoZW5jZVxuICAgICAgICAgIC8vIGl0IGlzIG5vdCBhIGZvcmVzdC5cbiAgICAgICAgICBlbHNlXG4gICAgICAgICAge1xuICAgICAgICAgICAgaXNGb3Jlc3QgPSBmYWxzZTtcbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cblxuICAgIC8vIFRoZSBncmFwaCBjb250YWlucyBhIGNvbXBvbmVudCB0aGF0IGlzIG5vdCBhIHRyZWUuIEVtcHR5XG4gICAgLy8gcHJldmlvdXNseSBmb3VuZCB0cmVlcy4gVGhlIG1ldGhvZCB3aWxsIGVuZC5cbiAgICBpZiAoIWlzRm9yZXN0KVxuICAgIHtcbiAgICAgIGZsYXRGb3Jlc3QgPSBbXTtcbiAgICB9XG4gICAgLy8gU2F2ZSBjdXJyZW50bHkgdmlzaXRlZCBub2RlcyBhcyBhIHRyZWUgaW4gb3VyIGZvcmVzdC4gUmVzZXRcbiAgICAvLyB2aXNpdGVkIGFuZCBwYXJlbnRzIGxpc3RzLiBDb250aW51ZSB3aXRoIHRoZSBuZXh0IGNvbXBvbmVudCBvZlxuICAgIC8vIHRoZSBncmFwaCwgaWYgYW55LlxuICAgIGVsc2VcbiAgICB7XG4gICAgICB2YXIgdGVtcCA9IFtdO1xuICAgICAgdmlzaXRlZC5hZGRBbGxUbyh0ZW1wKTtcbiAgICAgIGZsYXRGb3Jlc3QucHVzaCh0ZW1wKTtcbiAgICAgIC8vZmxhdEZvcmVzdCA9IGZsYXRGb3Jlc3QuY29uY2F0KHRlbXApO1xuICAgICAgLy91blByb2Nlc3NlZE5vZGVzLnJlbW92ZUFsbCh2aXNpdGVkKTtcbiAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwgdGVtcC5sZW5ndGg7IGkrKykge1xuICAgICAgICB2YXIgdmFsdWUgPSB0ZW1wW2ldO1xuICAgICAgICB2YXIgaW5kZXggPSB1blByb2Nlc3NlZE5vZGVzLmluZGV4T2YodmFsdWUpO1xuICAgICAgICBpZiAoaW5kZXggPiAtMSkge1xuICAgICAgICAgIHVuUHJvY2Vzc2VkTm9kZXMuc3BsaWNlKGluZGV4LCAxKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgdmlzaXRlZCA9IG5ldyBIYXNoU2V0KCk7XG4gICAgICBwYXJlbnRzID0gbmV3IEhhc2hNYXAoKTtcbiAgICB9XG4gIH1cblxuICByZXR1cm4gZmxhdEZvcmVzdDtcbn07XG5cbi8qKlxuICogVGhpcyBtZXRob2QgY3JlYXRlcyBkdW1teSBub2RlcyAoYW4gbC1sZXZlbCBub2RlIHdpdGggbWluaW1hbCBkaW1lbnNpb25zKVxuICogZm9yIHRoZSBnaXZlbiBlZGdlIChvbmUgcGVyIGJlbmRwb2ludCkuIFRoZSBleGlzdGluZyBsLWxldmVsIHN0cnVjdHVyZVxuICogaXMgdXBkYXRlZCBhY2NvcmRpbmdseS5cbiAqL1xuTGF5b3V0LnByb3RvdHlwZS5jcmVhdGVEdW1teU5vZGVzRm9yQmVuZHBvaW50cyA9IGZ1bmN0aW9uIChlZGdlKVxue1xuICB2YXIgZHVtbXlOb2RlcyA9IFtdO1xuICB2YXIgcHJldiA9IGVkZ2Uuc291cmNlO1xuXG4gIHZhciBncmFwaCA9IHRoaXMuZ3JhcGhNYW5hZ2VyLmNhbGNMb3dlc3RDb21tb25BbmNlc3RvcihlZGdlLnNvdXJjZSwgZWRnZS50YXJnZXQpO1xuXG4gIGZvciAodmFyIGkgPSAwOyBpIDwgZWRnZS5iZW5kcG9pbnRzLmxlbmd0aDsgaSsrKVxuICB7XG4gICAgLy8gY3JlYXRlIG5ldyBkdW1teSBub2RlXG4gICAgdmFyIGR1bW15Tm9kZSA9IHRoaXMubmV3Tm9kZShudWxsKTtcbiAgICBkdW1teU5vZGUuc2V0UmVjdChuZXcgUG9pbnQoMCwgMCksIG5ldyBEaW1lbnNpb24oMSwgMSkpO1xuXG4gICAgZ3JhcGguYWRkKGR1bW15Tm9kZSk7XG5cbiAgICAvLyBjcmVhdGUgbmV3IGR1bW15IGVkZ2UgYmV0d2VlbiBwcmV2IGFuZCBkdW1teSBub2RlXG4gICAgdmFyIGR1bW15RWRnZSA9IHRoaXMubmV3RWRnZShudWxsKTtcbiAgICB0aGlzLmdyYXBoTWFuYWdlci5hZGQoZHVtbXlFZGdlLCBwcmV2LCBkdW1teU5vZGUpO1xuXG4gICAgZHVtbXlOb2Rlcy5hZGQoZHVtbXlOb2RlKTtcbiAgICBwcmV2ID0gZHVtbXlOb2RlO1xuICB9XG5cbiAgdmFyIGR1bW15RWRnZSA9IHRoaXMubmV3RWRnZShudWxsKTtcbiAgdGhpcy5ncmFwaE1hbmFnZXIuYWRkKGR1bW15RWRnZSwgcHJldiwgZWRnZS50YXJnZXQpO1xuXG4gIHRoaXMuZWRnZVRvRHVtbXlOb2Rlcy5wdXQoZWRnZSwgZHVtbXlOb2Rlcyk7XG5cbiAgLy8gcmVtb3ZlIHJlYWwgZWRnZSBmcm9tIGdyYXBoIG1hbmFnZXIgaWYgaXQgaXMgaW50ZXItZ3JhcGhcbiAgaWYgKGVkZ2UuaXNJbnRlckdyYXBoKCkpXG4gIHtcbiAgICB0aGlzLmdyYXBoTWFuYWdlci5yZW1vdmUoZWRnZSk7XG4gIH1cbiAgLy8gZWxzZSwgcmVtb3ZlIHRoZSBlZGdlIGZyb20gdGhlIGN1cnJlbnQgZ3JhcGhcbiAgZWxzZVxuICB7XG4gICAgZ3JhcGgucmVtb3ZlKGVkZ2UpO1xuICB9XG5cbiAgcmV0dXJuIGR1bW15Tm9kZXM7XG59O1xuXG4vKipcbiAqIFRoaXMgbWV0aG9kIGNyZWF0ZXMgYmVuZHBvaW50cyBmb3IgZWRnZXMgZnJvbSB0aGUgZHVtbXkgbm9kZXNcbiAqIGF0IGwtbGV2ZWwuXG4gKi9cbkxheW91dC5wcm90b3R5cGUuY3JlYXRlQmVuZHBvaW50c0Zyb21EdW1teU5vZGVzID0gZnVuY3Rpb24gKClcbntcbiAgdmFyIGVkZ2VzID0gW107XG4gIGVkZ2VzID0gZWRnZXMuY29uY2F0KHRoaXMuZ3JhcGhNYW5hZ2VyLmdldEFsbEVkZ2VzKCkpO1xuICBlZGdlcyA9IHRoaXMuZWRnZVRvRHVtbXlOb2Rlcy5rZXlTZXQoKS5jb25jYXQoZWRnZXMpO1xuXG4gIGZvciAodmFyIGsgPSAwOyBrIDwgZWRnZXMubGVuZ3RoOyBrKyspXG4gIHtcbiAgICB2YXIgbEVkZ2UgPSBlZGdlc1trXTtcblxuICAgIGlmIChsRWRnZS5iZW5kcG9pbnRzLmxlbmd0aCA+IDApXG4gICAge1xuICAgICAgdmFyIHBhdGggPSB0aGlzLmVkZ2VUb0R1bW15Tm9kZXMuZ2V0KGxFZGdlKTtcblxuICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCBwYXRoLmxlbmd0aDsgaSsrKVxuICAgICAge1xuICAgICAgICB2YXIgZHVtbXlOb2RlID0gcGF0aFtpXTtcbiAgICAgICAgdmFyIHAgPSBuZXcgUG9pbnREKGR1bW15Tm9kZS5nZXRDZW50ZXJYKCksXG4gICAgICAgICAgICAgICAgZHVtbXlOb2RlLmdldENlbnRlclkoKSk7XG5cbiAgICAgICAgLy8gdXBkYXRlIGJlbmRwb2ludCdzIGxvY2F0aW9uIGFjY29yZGluZyB0byBkdW1teSBub2RlXG4gICAgICAgIHZhciBlYnAgPSBsRWRnZS5iZW5kcG9pbnRzLmdldChpKTtcbiAgICAgICAgZWJwLnggPSBwLng7XG4gICAgICAgIGVicC55ID0gcC55O1xuXG4gICAgICAgIC8vIHJlbW92ZSB0aGUgZHVtbXkgbm9kZSwgZHVtbXkgZWRnZXMgaW5jaWRlbnQgd2l0aCB0aGlzXG4gICAgICAgIC8vIGR1bW15IG5vZGUgaXMgYWxzbyByZW1vdmVkICh3aXRoaW4gdGhlIHJlbW92ZSBtZXRob2QpXG4gICAgICAgIGR1bW15Tm9kZS5nZXRPd25lcigpLnJlbW92ZShkdW1teU5vZGUpO1xuICAgICAgfVxuXG4gICAgICAvLyBhZGQgdGhlIHJlYWwgZWRnZSB0byBncmFwaFxuICAgICAgdGhpcy5ncmFwaE1hbmFnZXIuYWRkKGxFZGdlLCBsRWRnZS5zb3VyY2UsIGxFZGdlLnRhcmdldCk7XG4gICAgfVxuICB9XG59O1xuXG5MYXlvdXQudHJhbnNmb3JtID0gZnVuY3Rpb24gKHNsaWRlclZhbHVlLCBkZWZhdWx0VmFsdWUsIG1pbkRpdiwgbWF4TXVsKSB7XG4gIGlmIChtaW5EaXYgIT0gdW5kZWZpbmVkICYmIG1heE11bCAhPSB1bmRlZmluZWQpIHtcbiAgICB2YXIgdmFsdWUgPSBkZWZhdWx0VmFsdWU7XG5cbiAgICBpZiAoc2xpZGVyVmFsdWUgPD0gNTApXG4gICAge1xuICAgICAgdmFyIG1pblZhbHVlID0gZGVmYXVsdFZhbHVlIC8gbWluRGl2O1xuICAgICAgdmFsdWUgLT0gKChkZWZhdWx0VmFsdWUgLSBtaW5WYWx1ZSkgLyA1MCkgKiAoNTAgLSBzbGlkZXJWYWx1ZSk7XG4gICAgfVxuICAgIGVsc2VcbiAgICB7XG4gICAgICB2YXIgbWF4VmFsdWUgPSBkZWZhdWx0VmFsdWUgKiBtYXhNdWw7XG4gICAgICB2YWx1ZSArPSAoKG1heFZhbHVlIC0gZGVmYXVsdFZhbHVlKSAvIDUwKSAqIChzbGlkZXJWYWx1ZSAtIDUwKTtcbiAgICB9XG5cbiAgICByZXR1cm4gdmFsdWU7XG4gIH1cbiAgZWxzZSB7XG4gICAgdmFyIGEsIGI7XG5cbiAgICBpZiAoc2xpZGVyVmFsdWUgPD0gNTApXG4gICAge1xuICAgICAgYSA9IDkuMCAqIGRlZmF1bHRWYWx1ZSAvIDUwMC4wO1xuICAgICAgYiA9IGRlZmF1bHRWYWx1ZSAvIDEwLjA7XG4gICAgfVxuICAgIGVsc2VcbiAgICB7XG4gICAgICBhID0gOS4wICogZGVmYXVsdFZhbHVlIC8gNTAuMDtcbiAgICAgIGIgPSAtOCAqIGRlZmF1bHRWYWx1ZTtcbiAgICB9XG5cbiAgICByZXR1cm4gKGEgKiBzbGlkZXJWYWx1ZSArIGIpO1xuICB9XG59O1xuXG4vKipcbiAqIFRoaXMgbWV0aG9kIGZpbmRzIGFuZCByZXR1cm5zIHRoZSBjZW50ZXIgb2YgdGhlIGdpdmVuIG5vZGVzLCBhc3N1bWluZ1xuICogdGhhdCB0aGUgZ2l2ZW4gbm9kZXMgZm9ybSBhIHRyZWUgaW4gdGhlbXNlbHZlcy5cbiAqL1xuTGF5b3V0LmZpbmRDZW50ZXJPZlRyZWUgPSBmdW5jdGlvbiAobm9kZXMpXG57XG4gIHZhciBsaXN0ID0gW107XG4gIGxpc3QgPSBsaXN0LmNvbmNhdChub2Rlcyk7XG5cbiAgdmFyIHJlbW92ZWROb2RlcyA9IFtdO1xuICB2YXIgcmVtYWluaW5nRGVncmVlcyA9IG5ldyBIYXNoTWFwKCk7XG4gIHZhciBmb3VuZENlbnRlciA9IGZhbHNlO1xuICB2YXIgY2VudGVyTm9kZSA9IG51bGw7XG5cbiAgaWYgKGxpc3QubGVuZ3RoID09IDEgfHwgbGlzdC5sZW5ndGggPT0gMilcbiAge1xuICAgIGZvdW5kQ2VudGVyID0gdHJ1ZTtcbiAgICBjZW50ZXJOb2RlID0gbGlzdFswXTtcbiAgfVxuXG4gIGZvciAodmFyIGkgPSAwOyBpIDwgbGlzdC5sZW5ndGg7IGkrKylcbiAge1xuICAgIHZhciBub2RlID0gbGlzdFtpXTtcbiAgICB2YXIgZGVncmVlID0gbm9kZS5nZXROZWlnaGJvcnNMaXN0KCkuc2l6ZSgpO1xuICAgIHJlbWFpbmluZ0RlZ3JlZXMucHV0KG5vZGUsIG5vZGUuZ2V0TmVpZ2hib3JzTGlzdCgpLnNpemUoKSk7XG5cbiAgICBpZiAoZGVncmVlID09IDEpXG4gICAge1xuICAgICAgcmVtb3ZlZE5vZGVzLnB1c2gobm9kZSk7XG4gICAgfVxuICB9XG5cbiAgdmFyIHRlbXBMaXN0ID0gW107XG4gIHRlbXBMaXN0ID0gdGVtcExpc3QuY29uY2F0KHJlbW92ZWROb2Rlcyk7XG5cbiAgd2hpbGUgKCFmb3VuZENlbnRlcilcbiAge1xuICAgIHZhciB0ZW1wTGlzdDIgPSBbXTtcbiAgICB0ZW1wTGlzdDIgPSB0ZW1wTGlzdDIuY29uY2F0KHRlbXBMaXN0KTtcbiAgICB0ZW1wTGlzdCA9IFtdO1xuXG4gICAgZm9yICh2YXIgaSA9IDA7IGkgPCBsaXN0Lmxlbmd0aDsgaSsrKVxuICAgIHtcbiAgICAgIHZhciBub2RlID0gbGlzdFtpXTtcblxuICAgICAgdmFyIGluZGV4ID0gbGlzdC5pbmRleE9mKG5vZGUpO1xuICAgICAgaWYgKGluZGV4ID49IDApIHtcbiAgICAgICAgbGlzdC5zcGxpY2UoaW5kZXgsIDEpO1xuICAgICAgfVxuXG4gICAgICB2YXIgbmVpZ2hib3VycyA9IG5vZGUuZ2V0TmVpZ2hib3JzTGlzdCgpO1xuXG4gICAgICBmb3IgKHZhciBqIGluIG5laWdoYm91cnMuc2V0KVxuICAgICAge1xuICAgICAgICB2YXIgbmVpZ2hib3VyID0gbmVpZ2hib3Vycy5zZXRbal07XG4gICAgICAgIGlmIChyZW1vdmVkTm9kZXMuaW5kZXhPZihuZWlnaGJvdXIpIDwgMClcbiAgICAgICAge1xuICAgICAgICAgIHZhciBvdGhlckRlZ3JlZSA9IHJlbWFpbmluZ0RlZ3JlZXMuZ2V0KG5laWdoYm91cik7XG4gICAgICAgICAgdmFyIG5ld0RlZ3JlZSA9IG90aGVyRGVncmVlIC0gMTtcblxuICAgICAgICAgIGlmIChuZXdEZWdyZWUgPT0gMSlcbiAgICAgICAgICB7XG4gICAgICAgICAgICB0ZW1wTGlzdC5wdXNoKG5laWdoYm91cik7XG4gICAgICAgICAgfVxuXG4gICAgICAgICAgcmVtYWluaW5nRGVncmVlcy5wdXQobmVpZ2hib3VyLCBuZXdEZWdyZWUpO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuXG4gICAgcmVtb3ZlZE5vZGVzID0gcmVtb3ZlZE5vZGVzLmNvbmNhdCh0ZW1wTGlzdCk7XG5cbiAgICBpZiAobGlzdC5sZW5ndGggPT0gMSB8fCBsaXN0Lmxlbmd0aCA9PSAyKVxuICAgIHtcbiAgICAgIGZvdW5kQ2VudGVyID0gdHJ1ZTtcbiAgICAgIGNlbnRlck5vZGUgPSBsaXN0WzBdO1xuICAgIH1cbiAgfVxuXG4gIHJldHVybiBjZW50ZXJOb2RlO1xufTtcblxuLyoqXG4gKiBEdXJpbmcgdGhlIGNvYXJzZW5pbmcgcHJvY2VzcywgdGhpcyBsYXlvdXQgbWF5IGJlIHJlZmVyZW5jZWQgYnkgdHdvIGdyYXBoIG1hbmFnZXJzXG4gKiB0aGlzIHNldHRlciBmdW5jdGlvbiBncmFudHMgYWNjZXNzIHRvIGNoYW5nZSB0aGUgY3VycmVudGx5IGJlaW5nIHVzZWQgZ3JhcGggbWFuYWdlclxuICovXG5MYXlvdXQucHJvdG90eXBlLnNldEdyYXBoTWFuYWdlciA9IGZ1bmN0aW9uIChnbSlcbntcbiAgdGhpcy5ncmFwaE1hbmFnZXIgPSBnbTtcbn07XG5cbm1vZHVsZS5leHBvcnRzID0gTGF5b3V0O1xuIiwiZnVuY3Rpb24gTGF5b3V0Q29uc3RhbnRzKCkge1xufVxuXG4vKipcbiAqIExheW91dCBRdWFsaXR5XG4gKi9cbkxheW91dENvbnN0YW50cy5QUk9PRl9RVUFMSVRZID0gMDtcbkxheW91dENvbnN0YW50cy5ERUZBVUxUX1FVQUxJVFkgPSAxO1xuTGF5b3V0Q29uc3RhbnRzLkRSQUZUX1FVQUxJVFkgPSAyO1xuXG4vKipcbiAqIERlZmF1bHQgcGFyYW1ldGVyc1xuICovXG5MYXlvdXRDb25zdGFudHMuREVGQVVMVF9DUkVBVEVfQkVORFNfQVNfTkVFREVEID0gZmFsc2U7XG4vL0xheW91dENvbnN0YW50cy5ERUZBVUxUX0lOQ1JFTUVOVEFMID0gdHJ1ZTtcbkxheW91dENvbnN0YW50cy5ERUZBVUxUX0lOQ1JFTUVOVEFMID0gZmFsc2U7XG5MYXlvdXRDb25zdGFudHMuREVGQVVMVF9BTklNQVRJT05fT05fTEFZT1VUID0gdHJ1ZTtcbkxheW91dENvbnN0YW50cy5ERUZBVUxUX0FOSU1BVElPTl9EVVJJTkdfTEFZT1VUID0gZmFsc2U7XG5MYXlvdXRDb25zdGFudHMuREVGQVVMVF9BTklNQVRJT05fUEVSSU9EID0gNTA7XG5MYXlvdXRDb25zdGFudHMuREVGQVVMVF9VTklGT1JNX0xFQUZfTk9ERV9TSVpFUyA9IGZhbHNlO1xuXG4vLyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuLy8gU2VjdGlvbjogR2VuZXJhbCBvdGhlciBjb25zdGFudHNcbi8vIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4vKlxuICogTWFyZ2lucyBvZiBhIGdyYXBoIHRvIGJlIGFwcGxpZWQgb24gYm91ZGluZyByZWN0YW5nbGUgb2YgaXRzIGNvbnRlbnRzLiBXZVxuICogYXNzdW1lIG1hcmdpbnMgb24gYWxsIGZvdXIgc2lkZXMgdG8gYmUgdW5pZm9ybS5cbiAqL1xuTGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfR1JBUEhfTUFSR0lOID0gMTU7XG5cbi8qXG4gKiBEZWZhdWx0IGRpbWVuc2lvbiBvZiBhIG5vbi1jb21wb3VuZCBub2RlLlxuICovXG5MYXlvdXRDb25zdGFudHMuU0lNUExFX05PREVfU0laRSA9IDQwO1xuXG4vKlxuICogRGVmYXVsdCBkaW1lbnNpb24gb2YgYSBub24tY29tcG91bmQgbm9kZS5cbiAqL1xuTGF5b3V0Q29uc3RhbnRzLlNJTVBMRV9OT0RFX0hBTEZfU0laRSA9IExheW91dENvbnN0YW50cy5TSU1QTEVfTk9ERV9TSVpFIC8gMjtcblxuLypcbiAqIEVtcHR5IGNvbXBvdW5kIG5vZGUgc2l6ZS4gV2hlbiBhIGNvbXBvdW5kIG5vZGUgaXMgZW1wdHksIGl0cyBib3RoXG4gKiBkaW1lbnNpb25zIHNob3VsZCBiZSBvZiB0aGlzIHZhbHVlLlxuICovXG5MYXlvdXRDb25zdGFudHMuRU1QVFlfQ09NUE9VTkRfTk9ERV9TSVpFID0gNDA7XG5cbi8qXG4gKiBNaW5pbXVtIGxlbmd0aCB0aGF0IGFuIGVkZ2Ugc2hvdWxkIHRha2UgZHVyaW5nIGxheW91dFxuICovXG5MYXlvdXRDb25zdGFudHMuTUlOX0VER0VfTEVOR1RIID0gMTtcblxuLypcbiAqIFdvcmxkIGJvdW5kYXJpZXMgdGhhdCBsYXlvdXQgb3BlcmF0ZXMgb25cbiAqL1xuTGF5b3V0Q29uc3RhbnRzLldPUkxEX0JPVU5EQVJZID0gMTAwMDAwMDtcblxuLypcbiAqIFdvcmxkIGJvdW5kYXJpZXMgdGhhdCByYW5kb20gcG9zaXRpb25pbmcgY2FuIGJlIHBlcmZvcm1lZCB3aXRoXG4gKi9cbkxheW91dENvbnN0YW50cy5JTklUSUFMX1dPUkxEX0JPVU5EQVJZID0gTGF5b3V0Q29uc3RhbnRzLldPUkxEX0JPVU5EQVJZIC8gMTAwMDtcblxuLypcbiAqIENvb3JkaW5hdGVzIG9mIHRoZSB3b3JsZCBjZW50ZXJcbiAqL1xuTGF5b3V0Q29uc3RhbnRzLldPUkxEX0NFTlRFUl9YID0gMTIwMDtcbkxheW91dENvbnN0YW50cy5XT1JMRF9DRU5URVJfWSA9IDkwMDtcblxubW9kdWxlLmV4cG9ydHMgPSBMYXlvdXRDb25zdGFudHM7XG4iLCIvKlxuICpUaGlzIGNsYXNzIGlzIHRoZSBqYXZhc2NyaXB0IGltcGxlbWVudGF0aW9uIG9mIHRoZSBQb2ludC5qYXZhIGNsYXNzIGluIGpka1xuICovXG5mdW5jdGlvbiBQb2ludCh4LCB5LCBwKSB7XG4gIHRoaXMueCA9IG51bGw7XG4gIHRoaXMueSA9IG51bGw7XG4gIGlmICh4ID09IG51bGwgJiYgeSA9PSBudWxsICYmIHAgPT0gbnVsbCkge1xuICAgIHRoaXMueCA9IDA7XG4gICAgdGhpcy55ID0gMDtcbiAgfVxuICBlbHNlIGlmICh0eXBlb2YgeCA9PSAnbnVtYmVyJyAmJiB0eXBlb2YgeSA9PSAnbnVtYmVyJyAmJiBwID09IG51bGwpIHtcbiAgICB0aGlzLnggPSB4O1xuICAgIHRoaXMueSA9IHk7XG4gIH1cbiAgZWxzZSBpZiAoeC5jb25zdHJ1Y3Rvci5uYW1lID09ICdQb2ludCcgJiYgeSA9PSBudWxsICYmIHAgPT0gbnVsbCkge1xuICAgIHAgPSB4O1xuICAgIHRoaXMueCA9IHAueDtcbiAgICB0aGlzLnkgPSBwLnk7XG4gIH1cbn1cblxuUG9pbnQucHJvdG90eXBlLmdldFggPSBmdW5jdGlvbiAoKSB7XG4gIHJldHVybiB0aGlzLng7XG59XG5cblBvaW50LnByb3RvdHlwZS5nZXRZID0gZnVuY3Rpb24gKCkge1xuICByZXR1cm4gdGhpcy55O1xufVxuXG5Qb2ludC5wcm90b3R5cGUuZ2V0TG9jYXRpb24gPSBmdW5jdGlvbiAoKSB7XG4gIHJldHVybiBuZXcgUG9pbnQodGhpcy54LCB0aGlzLnkpO1xufVxuXG5Qb2ludC5wcm90b3R5cGUuc2V0TG9jYXRpb24gPSBmdW5jdGlvbiAoeCwgeSwgcCkge1xuICBpZiAoeC5jb25zdHJ1Y3Rvci5uYW1lID09ICdQb2ludCcgJiYgeSA9PSBudWxsICYmIHAgPT0gbnVsbCkge1xuICAgIHAgPSB4O1xuICAgIHRoaXMuc2V0TG9jYXRpb24ocC54LCBwLnkpO1xuICB9XG4gIGVsc2UgaWYgKHR5cGVvZiB4ID09ICdudW1iZXInICYmIHR5cGVvZiB5ID09ICdudW1iZXInICYmIHAgPT0gbnVsbCkge1xuICAgIC8vaWYgYm90aCBwYXJhbWV0ZXJzIGFyZSBpbnRlZ2VyIGp1c3QgbW92ZSAoeCx5KSBsb2NhdGlvblxuICAgIGlmIChwYXJzZUludCh4KSA9PSB4ICYmIHBhcnNlSW50KHkpID09IHkpIHtcbiAgICAgIHRoaXMubW92ZSh4LCB5KTtcbiAgICB9XG4gICAgZWxzZSB7XG4gICAgICB0aGlzLnggPSBNYXRoLmZsb29yKHggKyAwLjUpO1xuICAgICAgdGhpcy55ID0gTWF0aC5mbG9vcih5ICsgMC41KTtcbiAgICB9XG4gIH1cbn1cblxuUG9pbnQucHJvdG90eXBlLm1vdmUgPSBmdW5jdGlvbiAoeCwgeSkge1xuICB0aGlzLnggPSB4O1xuICB0aGlzLnkgPSB5O1xufVxuXG5Qb2ludC5wcm90b3R5cGUudHJhbnNsYXRlID0gZnVuY3Rpb24gKGR4LCBkeSkge1xuICB0aGlzLnggKz0gZHg7XG4gIHRoaXMueSArPSBkeTtcbn1cblxuUG9pbnQucHJvdG90eXBlLmVxdWFscyA9IGZ1bmN0aW9uIChvYmopIHtcbiAgaWYgKG9iai5jb25zdHJ1Y3Rvci5uYW1lID09IFwiUG9pbnRcIikge1xuICAgIHZhciBwdCA9IG9iajtcbiAgICByZXR1cm4gKHRoaXMueCA9PSBwdC54KSAmJiAodGhpcy55ID09IHB0LnkpO1xuICB9XG4gIHJldHVybiB0aGlzID09IG9iajtcbn1cblxuUG9pbnQucHJvdG90eXBlLnRvU3RyaW5nID0gZnVuY3Rpb24gKCkge1xuICByZXR1cm4gbmV3IFBvaW50KCkuY29uc3RydWN0b3IubmFtZSArIFwiW3g9XCIgKyB0aGlzLnggKyBcIix5PVwiICsgdGhpcy55ICsgXCJdXCI7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gUG9pbnQ7XG4iLCJmdW5jdGlvbiBQb2ludEQoeCwgeSkge1xuICBpZiAoeCA9PSBudWxsICYmIHkgPT0gbnVsbCkge1xuICAgIHRoaXMueCA9IDA7XG4gICAgdGhpcy55ID0gMDtcbiAgfSBlbHNlIHtcbiAgICB0aGlzLnggPSB4O1xuICAgIHRoaXMueSA9IHk7XG4gIH1cbn1cblxuUG9pbnRELnByb3RvdHlwZS5nZXRYID0gZnVuY3Rpb24gKClcbntcbiAgcmV0dXJuIHRoaXMueDtcbn07XG5cblBvaW50RC5wcm90b3R5cGUuZ2V0WSA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLnk7XG59O1xuXG5Qb2ludEQucHJvdG90eXBlLnNldFggPSBmdW5jdGlvbiAoeClcbntcbiAgdGhpcy54ID0geDtcbn07XG5cblBvaW50RC5wcm90b3R5cGUuc2V0WSA9IGZ1bmN0aW9uICh5KVxue1xuICB0aGlzLnkgPSB5O1xufTtcblxuUG9pbnRELnByb3RvdHlwZS5nZXREaWZmZXJlbmNlID0gZnVuY3Rpb24gKHB0KVxue1xuICByZXR1cm4gbmV3IERpbWVuc2lvbkQodGhpcy54IC0gcHQueCwgdGhpcy55IC0gcHQueSk7XG59O1xuXG5Qb2ludEQucHJvdG90eXBlLmdldENvcHkgPSBmdW5jdGlvbiAoKVxue1xuICByZXR1cm4gbmV3IFBvaW50RCh0aGlzLngsIHRoaXMueSk7XG59O1xuXG5Qb2ludEQucHJvdG90eXBlLnRyYW5zbGF0ZSA9IGZ1bmN0aW9uIChkaW0pXG57XG4gIHRoaXMueCArPSBkaW0ud2lkdGg7XG4gIHRoaXMueSArPSBkaW0uaGVpZ2h0O1xuICByZXR1cm4gdGhpcztcbn07XG5cbm1vZHVsZS5leHBvcnRzID0gUG9pbnREO1xuIiwiZnVuY3Rpb24gUmFuZG9tU2VlZCgpIHtcbn1cblJhbmRvbVNlZWQuc2VlZCA9IDE7XG5SYW5kb21TZWVkLnggPSAwO1xuXG5SYW5kb21TZWVkLm5leHREb3VibGUgPSBmdW5jdGlvbiAoKSB7XG4gIFJhbmRvbVNlZWQueCA9IE1hdGguc2luKFJhbmRvbVNlZWQuc2VlZCsrKSAqIDEwMDAwO1xuICByZXR1cm4gUmFuZG9tU2VlZC54IC0gTWF0aC5mbG9vcihSYW5kb21TZWVkLngpO1xufTtcblxubW9kdWxlLmV4cG9ydHMgPSBSYW5kb21TZWVkO1xuIiwiZnVuY3Rpb24gUmVjdGFuZ2xlRCh4LCB5LCB3aWR0aCwgaGVpZ2h0KSB7XG4gIHRoaXMueCA9IDA7XG4gIHRoaXMueSA9IDA7XG4gIHRoaXMud2lkdGggPSAwO1xuICB0aGlzLmhlaWdodCA9IDA7XG5cbiAgaWYgKHggIT0gbnVsbCAmJiB5ICE9IG51bGwgJiYgd2lkdGggIT0gbnVsbCAmJiBoZWlnaHQgIT0gbnVsbCkge1xuICAgIHRoaXMueCA9IHg7XG4gICAgdGhpcy55ID0geTtcbiAgICB0aGlzLndpZHRoID0gd2lkdGg7XG4gICAgdGhpcy5oZWlnaHQgPSBoZWlnaHQ7XG4gIH1cbn1cblxuUmVjdGFuZ2xlRC5wcm90b3R5cGUuZ2V0WCA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLng7XG59O1xuXG5SZWN0YW5nbGVELnByb3RvdHlwZS5zZXRYID0gZnVuY3Rpb24gKHgpXG57XG4gIHRoaXMueCA9IHg7XG59O1xuXG5SZWN0YW5nbGVELnByb3RvdHlwZS5nZXRZID0gZnVuY3Rpb24gKClcbntcbiAgcmV0dXJuIHRoaXMueTtcbn07XG5cblJlY3RhbmdsZUQucHJvdG90eXBlLnNldFkgPSBmdW5jdGlvbiAoeSlcbntcbiAgdGhpcy55ID0geTtcbn07XG5cblJlY3RhbmdsZUQucHJvdG90eXBlLmdldFdpZHRoID0gZnVuY3Rpb24gKClcbntcbiAgcmV0dXJuIHRoaXMud2lkdGg7XG59O1xuXG5SZWN0YW5nbGVELnByb3RvdHlwZS5zZXRXaWR0aCA9IGZ1bmN0aW9uICh3aWR0aClcbntcbiAgdGhpcy53aWR0aCA9IHdpZHRoO1xufTtcblxuUmVjdGFuZ2xlRC5wcm90b3R5cGUuZ2V0SGVpZ2h0ID0gZnVuY3Rpb24gKClcbntcbiAgcmV0dXJuIHRoaXMuaGVpZ2h0O1xufTtcblxuUmVjdGFuZ2xlRC5wcm90b3R5cGUuc2V0SGVpZ2h0ID0gZnVuY3Rpb24gKGhlaWdodClcbntcbiAgdGhpcy5oZWlnaHQgPSBoZWlnaHQ7XG59O1xuXG5SZWN0YW5nbGVELnByb3RvdHlwZS5nZXRSaWdodCA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLnggKyB0aGlzLndpZHRoO1xufTtcblxuUmVjdGFuZ2xlRC5wcm90b3R5cGUuZ2V0Qm90dG9tID0gZnVuY3Rpb24gKClcbntcbiAgcmV0dXJuIHRoaXMueSArIHRoaXMuaGVpZ2h0O1xufTtcblxuUmVjdGFuZ2xlRC5wcm90b3R5cGUuaW50ZXJzZWN0cyA9IGZ1bmN0aW9uIChhKVxue1xuICBpZiAodGhpcy5nZXRSaWdodCgpIDwgYS54KVxuICB7XG4gICAgcmV0dXJuIGZhbHNlO1xuICB9XG5cbiAgaWYgKHRoaXMuZ2V0Qm90dG9tKCkgPCBhLnkpXG4gIHtcbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cblxuICBpZiAoYS5nZXRSaWdodCgpIDwgdGhpcy54KVxuICB7XG4gICAgcmV0dXJuIGZhbHNlO1xuICB9XG5cbiAgaWYgKGEuZ2V0Qm90dG9tKCkgPCB0aGlzLnkpXG4gIHtcbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cblxuICByZXR1cm4gdHJ1ZTtcbn07XG5cblJlY3RhbmdsZUQucHJvdG90eXBlLmdldENlbnRlclggPSBmdW5jdGlvbiAoKVxue1xuICByZXR1cm4gdGhpcy54ICsgdGhpcy53aWR0aCAvIDI7XG59O1xuXG5SZWN0YW5nbGVELnByb3RvdHlwZS5nZXRNaW5YID0gZnVuY3Rpb24gKClcbntcbiAgcmV0dXJuIHRoaXMuZ2V0WCgpO1xufTtcblxuUmVjdGFuZ2xlRC5wcm90b3R5cGUuZ2V0TWF4WCA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLmdldFgoKSArIHRoaXMud2lkdGg7XG59O1xuXG5SZWN0YW5nbGVELnByb3RvdHlwZS5nZXRDZW50ZXJZID0gZnVuY3Rpb24gKClcbntcbiAgcmV0dXJuIHRoaXMueSArIHRoaXMuaGVpZ2h0IC8gMjtcbn07XG5cblJlY3RhbmdsZUQucHJvdG90eXBlLmdldE1pblkgPSBmdW5jdGlvbiAoKVxue1xuICByZXR1cm4gdGhpcy5nZXRZKCk7XG59O1xuXG5SZWN0YW5nbGVELnByb3RvdHlwZS5nZXRNYXhZID0gZnVuY3Rpb24gKClcbntcbiAgcmV0dXJuIHRoaXMuZ2V0WSgpICsgdGhpcy5oZWlnaHQ7XG59O1xuXG5SZWN0YW5nbGVELnByb3RvdHlwZS5nZXRXaWR0aEhhbGYgPSBmdW5jdGlvbiAoKVxue1xuICByZXR1cm4gdGhpcy53aWR0aCAvIDI7XG59O1xuXG5SZWN0YW5nbGVELnByb3RvdHlwZS5nZXRIZWlnaHRIYWxmID0gZnVuY3Rpb24gKClcbntcbiAgcmV0dXJuIHRoaXMuaGVpZ2h0IC8gMjtcbn07XG5cbm1vZHVsZS5leHBvcnRzID0gUmVjdGFuZ2xlRDtcbiIsIm1vZHVsZS5leHBvcnRzID0gZnVuY3Rpb24gKGluc3RhbmNlKSB7XG4gIGluc3RhbmNlLnRvQmVUaWxlZCA9IHt9O1xuICBcbiAgaW5zdGFuY2UuZ2V0VG9CZVRpbGVkID0gZnVuY3Rpb24gKG5vZGUpIHtcbiAgICB2YXIgaWQgPSBub2RlLmRhdGEoXCJpZFwiKTtcbiAgICAvL2ZpcnN0bHkgY2hlY2sgdGhlIHByZXZpb3VzIHJlc3VsdHNcbiAgICBpZiAoaW5zdGFuY2UudG9CZVRpbGVkW2lkXSAhPSBudWxsKSB7XG4gICAgICByZXR1cm4gaW5zdGFuY2UudG9CZVRpbGVkW2lkXTtcbiAgICB9XG5cbiAgICAvL29ubHkgY29tcG91bmQgbm9kZXMgYXJlIHRvIGJlIHRpbGVkXG4gICAgdmFyIGNoaWxkcmVuID0gbm9kZS5jaGlsZHJlbigpO1xuICAgIGlmIChjaGlsZHJlbiA9PSBudWxsIHx8IGNoaWxkcmVuLmxlbmd0aCA9PSAwKSB7XG4gICAgICBpbnN0YW5jZS50b0JlVGlsZWRbaWRdID0gZmFsc2U7XG4gICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuXG4gICAgLy9hIGNvbXBvdW5kIG5vZGUgaXMgbm90IHRvIGJlIHRpbGVkIGlmIGFsbCBvZiBpdHMgY29tcG91bmQgY2hpbGRyZW4gYXJlIG5vdCB0byBiZSB0aWxlZFxuICAgIGZvciAodmFyIGkgPSAwOyBpIDwgY2hpbGRyZW4ubGVuZ3RoOyBpKyspIHtcbiAgICAgIHZhciB0aGVDaGlsZCA9IGNoaWxkcmVuW2ldO1xuXG4gICAgICBpZiAoaW5zdGFuY2UuZ2V0Tm9kZURlZ3JlZSh0aGVDaGlsZCkgPiAwKSB7XG4gICAgICAgIGluc3RhbmNlLnRvQmVUaWxlZFtpZF0gPSBmYWxzZTtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgfVxuXG4gICAgICAvL3Bhc3MgdGhlIGNoaWxkcmVuIG5vdCBoYXZpbmcgdGhlIGNvbXBvdW5kIHN0cnVjdHVyZVxuICAgICAgaWYgKHRoZUNoaWxkLmNoaWxkcmVuKCkgPT0gbnVsbCB8fCB0aGVDaGlsZC5jaGlsZHJlbigpLmxlbmd0aCA9PSAwKSB7XG4gICAgICAgIGluc3RhbmNlLnRvQmVUaWxlZFt0aGVDaGlsZC5kYXRhKFwiaWRcIildID0gZmFsc2U7XG4gICAgICAgIGNvbnRpbnVlO1xuICAgICAgfVxuXG4gICAgICBpZiAoIWluc3RhbmNlLmdldFRvQmVUaWxlZCh0aGVDaGlsZCkpIHtcbiAgICAgICAgaW5zdGFuY2UudG9CZVRpbGVkW2lkXSA9IGZhbHNlO1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICB9XG4gICAgfVxuICAgIGluc3RhbmNlLnRvQmVUaWxlZFtpZF0gPSB0cnVlO1xuICAgIHJldHVybiB0cnVlO1xuICB9O1xuXG4gIGluc3RhbmNlLmdldE5vZGVEZWdyZWUgPSBmdW5jdGlvbiAobm9kZSkge1xuICAgIHZhciBpZCA9IG5vZGUuaWQoKTtcbiAgICB2YXIgZWRnZXMgPSBpbnN0YW5jZS5vcHRpb25zLmVsZXMuZWRnZXMoKS5maWx0ZXIoZnVuY3Rpb24gKGVsZSwgaSkge1xuICAgICAgaWYgKHR5cGVvZiBlbGUgPT09IFwibnVtYmVyXCIpIHtcbiAgICAgICAgZWxlID0gaTtcbiAgICAgIH1cbiAgICAgIHZhciBzb3VyY2UgPSBlbGUuZGF0YSgnc291cmNlJyk7XG4gICAgICB2YXIgdGFyZ2V0ID0gZWxlLmRhdGEoJ3RhcmdldCcpO1xuICAgICAgaWYgKHNvdXJjZSAhPSB0YXJnZXQgJiYgKHNvdXJjZSA9PSBpZCB8fCB0YXJnZXQgPT0gaWQpKSB7XG4gICAgICAgIHJldHVybiB0cnVlO1xuICAgICAgfVxuICAgIH0pO1xuICAgIHJldHVybiBlZGdlcy5sZW5ndGg7XG4gIH07XG5cbiAgaW5zdGFuY2UuZ2V0Tm9kZURlZ3JlZVdpdGhDaGlsZHJlbiA9IGZ1bmN0aW9uIChub2RlKSB7XG4gICAgdmFyIGRlZ3JlZSA9IGluc3RhbmNlLmdldE5vZGVEZWdyZWUobm9kZSk7XG4gICAgdmFyIGNoaWxkcmVuID0gbm9kZS5jaGlsZHJlbigpO1xuICAgIGZvciAodmFyIGkgPSAwOyBpIDwgY2hpbGRyZW4ubGVuZ3RoOyBpKyspIHtcbiAgICAgIHZhciBjaGlsZCA9IGNoaWxkcmVuW2ldO1xuICAgICAgZGVncmVlICs9IGluc3RhbmNlLmdldE5vZGVEZWdyZWVXaXRoQ2hpbGRyZW4oY2hpbGQpO1xuICAgIH1cbiAgICByZXR1cm4gZGVncmVlO1xuICB9O1xuXG4gIGluc3RhbmNlLmdyb3VwWmVyb0RlZ3JlZU1lbWJlcnMgPSBmdW5jdGlvbiAoKSB7XG4gICAgLy8gYXJyYXkgb2YgW3BhcmVudF9pZCB4IG9uZURlZ3JlZU5vZGVfaWRdXG4gICAgdmFyIHRlbXBNZW1iZXJHcm91cHMgPSBbXTtcbiAgICB2YXIgbWVtYmVyR3JvdXBzID0gW107XG4gICAgdmFyIHNlbGYgPSB0aGlzO1xuICAgIHZhciBwYXJlbnRNYXAgPSB7fTtcblxuICAgIGZvciAodmFyIGkgPSAwOyBpIDwgaW5zdGFuY2Uub3B0aW9ucy5lbGVzLm5vZGVzKCkubGVuZ3RoOyBpKyspIHtcbiAgICAgIHBhcmVudE1hcFtpbnN0YW5jZS5vcHRpb25zLmVsZXMubm9kZXMoKVtpXS5pZCgpXSA9IHRydWU7XG4gICAgfVxuXG4gICAgLy8gRmluZCBhbGwgemVybyBkZWdyZWUgbm9kZXMgd2hpY2ggYXJlbid0IGNvdmVyZWQgYnkgYSBjb21wb3VuZFxuICAgIHZhciB6ZXJvRGVncmVlID0gaW5zdGFuY2Uub3B0aW9ucy5lbGVzLm5vZGVzKCkuZmlsdGVyKGZ1bmN0aW9uIChlbGUsIGkpIHtcbiAgICAgIGlmICh0eXBlb2YgZWxlID09PSBcIm51bWJlclwiKSB7XG4gICAgICAgIGVsZSA9IGk7XG4gICAgICB9XG4gICAgICB2YXIgcGlkID0gZWxlLmRhdGEoJ3BhcmVudCcpO1xuICAgICAgaWYgKHBpZCAhPSB1bmRlZmluZWQgJiYgIXBhcmVudE1hcFtwaWRdKSB7XG4gICAgICAgIHBpZCA9IHVuZGVmaW5lZDtcbiAgICAgIH1cblxuICAgICAgaWYgKHNlbGYuZ2V0Tm9kZURlZ3JlZVdpdGhDaGlsZHJlbihlbGUpID09IDAgJiYgKHBpZCA9PSB1bmRlZmluZWQgfHwgKHBpZCAhPSB1bmRlZmluZWQgJiYgIXNlbGYuZ2V0VG9CZVRpbGVkKGVsZS5wYXJlbnQoKVswXSkpKSlcbiAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgICBlbHNlXG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9KTtcblxuICAgIC8vIENyZWF0ZSBhIG1hcCBvZiBwYXJlbnQgbm9kZSBhbmQgaXRzIHplcm8gZGVncmVlIG1lbWJlcnNcbiAgICBmb3IgKHZhciBpID0gMDsgaSA8IHplcm9EZWdyZWUubGVuZ3RoOyBpKyspXG4gICAge1xuICAgICAgdmFyIG5vZGUgPSB6ZXJvRGVncmVlW2ldO1xuICAgICAgdmFyIHBfaWQgPSBub2RlLnBhcmVudCgpLmlkKCk7XG5cbiAgICAgIGlmIChwX2lkICE9IHVuZGVmaW5lZCAmJiAhcGFyZW50TWFwW3BfaWRdKSB7XG4gICAgICAgIHBfaWQgPSB1bmRlZmluZWQ7XG4gICAgICB9XG5cbiAgICAgIGlmICh0eXBlb2YgdGVtcE1lbWJlckdyb3Vwc1twX2lkXSA9PT0gXCJ1bmRlZmluZWRcIilcbiAgICAgICAgdGVtcE1lbWJlckdyb3Vwc1twX2lkXSA9IFtdO1xuXG4gICAgICB0ZW1wTWVtYmVyR3JvdXBzW3BfaWRdID0gdGVtcE1lbWJlckdyb3Vwc1twX2lkXS5jb25jYXQobm9kZSk7XG4gICAgfVxuXG4gICAgLy8gSWYgdGhlcmUgYXJlIGF0IGxlYXN0IHR3byBub2RlcyBhdCBhIGxldmVsLCBjcmVhdGUgYSBkdW1teSBjb21wb3VuZCBmb3IgdGhlbVxuICAgIGZvciAodmFyIHBfaWQgaW4gdGVtcE1lbWJlckdyb3Vwcykge1xuICAgICAgaWYgKHRlbXBNZW1iZXJHcm91cHNbcF9pZF0ubGVuZ3RoID4gMSkge1xuICAgICAgICB2YXIgZHVtbXlDb21wb3VuZElkID0gXCJEdW1teUNvbXBvdW5kX1wiICsgcF9pZDtcbiAgICAgICAgbWVtYmVyR3JvdXBzW2R1bW15Q29tcG91bmRJZF0gPSB0ZW1wTWVtYmVyR3JvdXBzW3BfaWRdO1xuXG4gICAgICAgIC8vIENyZWF0ZSBhIGR1bW15IGNvbXBvdW5kXG4gICAgICAgIGlmIChpbnN0YW5jZS5vcHRpb25zLmN5LmdldEVsZW1lbnRCeUlkKGR1bW15Q29tcG91bmRJZCkuZW1wdHkoKSkge1xuICAgICAgICAgIGluc3RhbmNlLm9wdGlvbnMuY3kuYWRkKHtcbiAgICAgICAgICAgIGdyb3VwOiBcIm5vZGVzXCIsXG4gICAgICAgICAgICBkYXRhOiB7aWQ6IGR1bW15Q29tcG91bmRJZCwgcGFyZW50OiBwX2lkXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSk7XG5cbiAgICAgICAgICB2YXIgZHVtbXkgPSBpbnN0YW5jZS5vcHRpb25zLmN5Lm5vZGVzKClbaW5zdGFuY2Uub3B0aW9ucy5jeS5ub2RlcygpLmxlbmd0aCAtIDFdO1xuICAgICAgICAgIGluc3RhbmNlLm9wdGlvbnMuZWxlcyA9IGluc3RhbmNlLm9wdGlvbnMuZWxlcy51bmlvbihkdW1teSk7XG4gICAgICAgICAgZHVtbXkuaGlkZSgpO1xuXG4gICAgICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCB0ZW1wTWVtYmVyR3JvdXBzW3BfaWRdLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICBpZiAoaSA9PSAwKSB7XG4gICAgICAgICAgICAgIGR1bW15LnNjcmF0Y2goJ2Nvc2VCaWxrZW50Jywge3RlbXBjaGlsZHJlbjogW119KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHZhciBub2RlID0gdGVtcE1lbWJlckdyb3Vwc1twX2lkXVtpXTtcbiAgICAgICAgICAgIHZhciBzY3JhdGNoT2JqID0gbm9kZS5zY3JhdGNoKCdjb3NlQmlsa2VudCcpO1xuICAgICAgICAgICAgaWYgKCFzY3JhdGNoT2JqKSB7XG4gICAgICAgICAgICAgIHNjcmF0Y2hPYmogPSB7fTtcbiAgICAgICAgICAgICAgbm9kZS5zY3JhdGNoKCdjb3NlQmlsa2VudCcsIHNjcmF0Y2hPYmopO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgc2NyYXRjaE9ialsnZHVtbXlfcGFyZW50X2lkJ10gPSBkdW1teUNvbXBvdW5kSWQ7XG4gICAgICAgICAgICBpbnN0YW5jZS5vcHRpb25zLmN5LmFkZCh7XG4gICAgICAgICAgICAgIGdyb3VwOiBcIm5vZGVzXCIsXG4gICAgICAgICAgICAgIGRhdGE6IHtwYXJlbnQ6IGR1bW15Q29tcG91bmRJZCwgd2lkdGg6IG5vZGUud2lkdGgoKSwgaGVpZ2h0OiBub2RlLmhlaWdodCgpXG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgdmFyIHRlbXBjaGlsZCA9IGluc3RhbmNlLm9wdGlvbnMuY3kubm9kZXMoKVtpbnN0YW5jZS5vcHRpb25zLmN5Lm5vZGVzKCkubGVuZ3RoIC0gMV07XG4gICAgICAgICAgICB0ZW1wY2hpbGQuaGlkZSgpO1xuICAgICAgICAgICAgdGVtcGNoaWxkLmNzcygnd2lkdGgnLCB0ZW1wY2hpbGQuZGF0YSgnd2lkdGgnKSk7XG4gICAgICAgICAgICB0ZW1wY2hpbGQuY3NzKCdoZWlnaHQnLCB0ZW1wY2hpbGQuZGF0YSgnaGVpZ2h0JykpO1xuICAgICAgICAgICAgdGVtcGNoaWxkLndpZHRoKCk7XG4gICAgICAgICAgICBkdW1teS5zY3JhdGNoKCdjb3NlQmlsa2VudCcpLnRlbXBjaGlsZHJlbi5wdXNoKHRlbXBjaGlsZCk7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuXG4gICAgcmV0dXJuIG1lbWJlckdyb3VwcztcbiAgfTtcblxuICBpbnN0YW5jZS5wZXJmb3JtREZTT25Db21wb3VuZHMgPSBmdW5jdGlvbiAob3B0aW9ucykge1xuICAgIHZhciBjb21wb3VuZE9yZGVyID0gW107XG5cbiAgICB2YXIgcm9vdHMgPSBpbnN0YW5jZS5nZXRUb3BNb3N0Tm9kZXMoaW5zdGFuY2Uub3B0aW9ucy5lbGVzLm5vZGVzKCkpO1xuICAgIGluc3RhbmNlLmZpbGxDb21wZXhPcmRlckJ5REZTKGNvbXBvdW5kT3JkZXIsIHJvb3RzKTtcblxuICAgIHJldHVybiBjb21wb3VuZE9yZGVyO1xuICB9O1xuXG4gIGluc3RhbmNlLmZpbGxDb21wZXhPcmRlckJ5REZTID0gZnVuY3Rpb24gKGNvbXBvdW5kT3JkZXIsIGNoaWxkcmVuKSB7XG4gICAgZm9yICh2YXIgaSA9IDA7IGkgPCBjaGlsZHJlbi5sZW5ndGg7IGkrKykge1xuICAgICAgdmFyIGNoaWxkID0gY2hpbGRyZW5baV07XG4gICAgICBpbnN0YW5jZS5maWxsQ29tcGV4T3JkZXJCeURGUyhjb21wb3VuZE9yZGVyLCBjaGlsZC5jaGlsZHJlbigpKTtcbiAgICAgIGlmIChpbnN0YW5jZS5nZXRUb0JlVGlsZWQoY2hpbGQpKSB7XG4gICAgICAgIGNvbXBvdW5kT3JkZXIucHVzaChjaGlsZCk7XG4gICAgICB9XG4gICAgfVxuICB9O1xuXG4gIGluc3RhbmNlLmNsZWFyQ29tcG91bmRzID0gZnVuY3Rpb24gKCkge1xuICAgIHZhciBjaGlsZEdyYXBoTWFwID0gW107XG5cbiAgICAvLyBHZXQgY29tcG91bmQgb3JkZXJpbmcgYnkgZmluZGluZyB0aGUgaW5uZXIgb25lIGZpcnN0XG4gICAgdmFyIGNvbXBvdW5kT3JkZXIgPSBpbnN0YW5jZS5wZXJmb3JtREZTT25Db21wb3VuZHMoaW5zdGFuY2Uub3B0aW9ucyk7XG4gICAgaW5zdGFuY2UuY29tcG91bmRPcmRlciA9IGNvbXBvdW5kT3JkZXI7XG4gICAgaW5zdGFuY2UucHJvY2Vzc0NoaWxkcmVuTGlzdChpbnN0YW5jZS5yb290LCBpbnN0YW5jZS5nZXRUb3BNb3N0Tm9kZXMoaW5zdGFuY2Uub3B0aW9ucy5lbGVzLm5vZGVzKCkpLCBpbnN0YW5jZS5sYXlvdXQpO1xuXG4gICAgZm9yICh2YXIgaSA9IDA7IGkgPCBjb21wb3VuZE9yZGVyLmxlbmd0aDsgaSsrKSB7XG4gICAgICAvLyBmaW5kIHRoZSBjb3JyZXNwb25kaW5nIGxheW91dCBub2RlXG4gICAgICB2YXIgbENvbXBvdW5kTm9kZSA9IGluc3RhbmNlLmlkVG9MTm9kZVtjb21wb3VuZE9yZGVyW2ldLmlkKCldO1xuXG4gICAgICBjaGlsZEdyYXBoTWFwW2NvbXBvdW5kT3JkZXJbaV0uaWQoKV0gPSBjb21wb3VuZE9yZGVyW2ldLmNoaWxkcmVuKCk7XG5cbiAgICAgIC8vIFJlbW92ZSBjaGlsZHJlbiBvZiBjb21wb3VuZHNcbiAgICAgIGxDb21wb3VuZE5vZGUuY2hpbGQgPSBudWxsO1xuICAgIH1cblxuICAgIC8vIFRpbGUgdGhlIHJlbW92ZWQgY2hpbGRyZW5cbiAgICB2YXIgdGlsZWRNZW1iZXJQYWNrID0gaW5zdGFuY2UudGlsZUNvbXBvdW5kTWVtYmVycyhjaGlsZEdyYXBoTWFwKTtcblxuICAgIHJldHVybiB0aWxlZE1lbWJlclBhY2s7XG4gIH07XG5cbiAgaW5zdGFuY2UuY2xlYXJaZXJvRGVncmVlTWVtYmVycyA9IGZ1bmN0aW9uIChtZW1iZXJHcm91cHMpIHtcbiAgICB2YXIgdGlsZWRaZXJvRGVncmVlUGFjayA9IFtdO1xuXG4gICAgZm9yICh2YXIgaWQgaW4gbWVtYmVyR3JvdXBzKSB7XG4gICAgICB2YXIgY29tcG91bmROb2RlID0gaW5zdGFuY2UuaWRUb0xOb2RlW2lkXTtcblxuICAgICAgdGlsZWRaZXJvRGVncmVlUGFja1tpZF0gPSBpbnN0YW5jZS50aWxlTm9kZXMobWVtYmVyR3JvdXBzW2lkXSk7XG5cbiAgICAgIC8vIFNldCB0aGUgd2lkdGggYW5kIGhlaWdodCBvZiB0aGUgZHVtbXkgY29tcG91bmQgYXMgY2FsY3VsYXRlZFxuICAgICAgY29tcG91bmROb2RlLnJlY3Qud2lkdGggPSB0aWxlZFplcm9EZWdyZWVQYWNrW2lkXS53aWR0aDtcbiAgICAgIGNvbXBvdW5kTm9kZS5yZWN0LmhlaWdodCA9IHRpbGVkWmVyb0RlZ3JlZVBhY2tbaWRdLmhlaWdodDtcbiAgICB9XG4gICAgcmV0dXJuIHRpbGVkWmVyb0RlZ3JlZVBhY2s7XG4gIH07XG5cbiAgaW5zdGFuY2UucmVwb3B1bGF0ZUNvbXBvdW5kcyA9IGZ1bmN0aW9uICh0aWxlZE1lbWJlclBhY2spIHtcbiAgICBmb3IgKHZhciBpID0gaW5zdGFuY2UuY29tcG91bmRPcmRlci5sZW5ndGggLSAxOyBpID49IDA7IGktLSkge1xuICAgICAgdmFyIGlkID0gaW5zdGFuY2UuY29tcG91bmRPcmRlcltpXS5pZCgpO1xuICAgICAgdmFyIGxDb21wb3VuZE5vZGUgPSBpbnN0YW5jZS5pZFRvTE5vZGVbaWRdO1xuICAgICAgdmFyIGhvcml6b250YWxNYXJnaW4gPSBwYXJzZUludChpbnN0YW5jZS5jb21wb3VuZE9yZGVyW2ldLmNzcygncGFkZGluZy1sZWZ0JykpO1xuICAgICAgdmFyIHZlcnRpY2FsTWFyZ2luID0gcGFyc2VJbnQoaW5zdGFuY2UuY29tcG91bmRPcmRlcltpXS5jc3MoJ3BhZGRpbmctdG9wJykpO1xuXG4gICAgICBpbnN0YW5jZS5hZGp1c3RMb2NhdGlvbnModGlsZWRNZW1iZXJQYWNrW2lkXSwgbENvbXBvdW5kTm9kZS5yZWN0LngsIGxDb21wb3VuZE5vZGUucmVjdC55LCBob3Jpem9udGFsTWFyZ2luLCB2ZXJ0aWNhbE1hcmdpbik7XG4gICAgfVxuICB9O1xuXG4gIGluc3RhbmNlLnJlcG9wdWxhdGVaZXJvRGVncmVlTWVtYmVycyA9IGZ1bmN0aW9uICh0aWxlZFBhY2spIHtcbiAgICBmb3IgKHZhciBpIGluIHRpbGVkUGFjaykge1xuICAgICAgdmFyIGNvbXBvdW5kID0gaW5zdGFuY2UuY3kuZ2V0RWxlbWVudEJ5SWQoaSk7XG4gICAgICB2YXIgY29tcG91bmROb2RlID0gaW5zdGFuY2UuaWRUb0xOb2RlW2ldO1xuICAgICAgdmFyIGhvcml6b250YWxNYXJnaW4gPSBwYXJzZUludChjb21wb3VuZC5jc3MoJ3BhZGRpbmctbGVmdCcpKTtcbiAgICAgIHZhciB2ZXJ0aWNhbE1hcmdpbiA9IHBhcnNlSW50KGNvbXBvdW5kLmNzcygncGFkZGluZy10b3AnKSk7XG5cbiAgICAgIC8vIEFkanVzdCB0aGUgcG9zaXRpb25zIG9mIG5vZGVzIHdydCBpdHMgY29tcG91bmRcbiAgICAgIGluc3RhbmNlLmFkanVzdExvY2F0aW9ucyh0aWxlZFBhY2tbaV0sIGNvbXBvdW5kTm9kZS5yZWN0LngsIGNvbXBvdW5kTm9kZS5yZWN0LnksIGhvcml6b250YWxNYXJnaW4sIHZlcnRpY2FsTWFyZ2luKTtcblxuICAgICAgdmFyIHRlbXBjaGlsZHJlbiA9IGNvbXBvdW5kLnNjcmF0Y2goJ2Nvc2VCaWxrZW50JykudGVtcGNoaWxkcmVuO1xuICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCB0ZW1wY2hpbGRyZW4ubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgdGVtcGNoaWxkcmVuW2ldLnJlbW92ZSgpO1xuICAgICAgfVxuXG4gICAgICAvLyBSZW1vdmUgdGhlIGR1bW15IGNvbXBvdW5kXG4gICAgICBjb21wb3VuZC5yZW1vdmUoKTtcbiAgICB9XG4gIH07XG5cbiAgLyoqXG4gICAqIFRoaXMgbWV0aG9kIHBsYWNlcyBlYWNoIHplcm8gZGVncmVlIG1lbWJlciB3cnQgZ2l2ZW4gKHgseSkgY29vcmRpbmF0ZXMgKHRvcCBsZWZ0KS5cbiAgICovXG4gIGluc3RhbmNlLmFkanVzdExvY2F0aW9ucyA9IGZ1bmN0aW9uIChvcmdhbml6YXRpb24sIHgsIHksIGNvbXBvdW5kSG9yaXpvbnRhbE1hcmdpbiwgY29tcG91bmRWZXJ0aWNhbE1hcmdpbikge1xuICAgIHggKz0gY29tcG91bmRIb3Jpem9udGFsTWFyZ2luO1xuICAgIHkgKz0gY29tcG91bmRWZXJ0aWNhbE1hcmdpbjtcblxuICAgIHZhciBsZWZ0ID0geDtcblxuICAgIGZvciAodmFyIGkgPSAwOyBpIDwgb3JnYW5pemF0aW9uLnJvd3MubGVuZ3RoOyBpKyspIHtcbiAgICAgIHZhciByb3cgPSBvcmdhbml6YXRpb24ucm93c1tpXTtcbiAgICAgIHggPSBsZWZ0O1xuICAgICAgdmFyIG1heEhlaWdodCA9IDA7XG5cbiAgICAgIGZvciAodmFyIGogPSAwOyBqIDwgcm93Lmxlbmd0aDsgaisrKSB7XG4gICAgICAgIHZhciBsbm9kZSA9IHJvd1tqXTtcbiAgICAgICAgdmFyIG5vZGUgPSBpbnN0YW5jZS5jeS5nZXRFbGVtZW50QnlJZChsbm9kZS5pZCk7XG5cbiAgICAgICAgbG5vZGUucmVjdC54ID0geDsvLyArIGxub2RlLnJlY3Qud2lkdGggLyAyO1xuICAgICAgICBsbm9kZS5yZWN0LnkgPSB5Oy8vICsgbG5vZGUucmVjdC5oZWlnaHQgLyAyO1xuXG4gICAgICAgIHggKz0gbG5vZGUucmVjdC53aWR0aCArIG9yZ2FuaXphdGlvbi5ob3Jpem9udGFsUGFkZGluZztcblxuICAgICAgICBpZiAobG5vZGUucmVjdC5oZWlnaHQgPiBtYXhIZWlnaHQpXG4gICAgICAgICAgbWF4SGVpZ2h0ID0gbG5vZGUucmVjdC5oZWlnaHQ7XG4gICAgICB9XG5cbiAgICAgIHkgKz0gbWF4SGVpZ2h0ICsgb3JnYW5pemF0aW9uLnZlcnRpY2FsUGFkZGluZztcbiAgICB9XG4gIH07XG5cbiAgaW5zdGFuY2UudGlsZUNvbXBvdW5kTWVtYmVycyA9IGZ1bmN0aW9uIChjaGlsZEdyYXBoTWFwKSB7XG4gICAgdmFyIHRpbGVkTWVtYmVyUGFjayA9IFtdO1xuXG4gICAgZm9yICh2YXIgaWQgaW4gY2hpbGRHcmFwaE1hcCkge1xuICAgICAgLy8gQWNjZXNzIGxheW91dEluZm8gbm9kZXMgdG8gc2V0IHRoZSB3aWR0aCBhbmQgaGVpZ2h0IG9mIGNvbXBvdW5kc1xuICAgICAgdmFyIGNvbXBvdW5kTm9kZSA9IGluc3RhbmNlLmlkVG9MTm9kZVtpZF07XG5cbiAgICAgIHRpbGVkTWVtYmVyUGFja1tpZF0gPSBpbnN0YW5jZS50aWxlTm9kZXMoY2hpbGRHcmFwaE1hcFtpZF0pO1xuXG4gICAgICBjb21wb3VuZE5vZGUucmVjdC53aWR0aCA9IHRpbGVkTWVtYmVyUGFja1tpZF0ud2lkdGggKyAyMDtcbiAgICAgIGNvbXBvdW5kTm9kZS5yZWN0LmhlaWdodCA9IHRpbGVkTWVtYmVyUGFja1tpZF0uaGVpZ2h0ICsgMjA7XG4gICAgfVxuXG4gICAgcmV0dXJuIHRpbGVkTWVtYmVyUGFjaztcbiAgfTtcblxuICBpbnN0YW5jZS50aWxlTm9kZXMgPSBmdW5jdGlvbiAobm9kZXMpIHtcbiAgICB2YXIgc2VsZiA9IHRoaXM7XG4gICAgdmFyIHZlcnRpY2FsUGFkZGluZyA9IHR5cGVvZiBzZWxmLm9wdGlvbnMudGlsaW5nUGFkZGluZ1ZlcnRpY2FsID09PSAnZnVuY3Rpb24nID8gc2VsZi5vcHRpb25zLnRpbGluZ1BhZGRpbmdWZXJ0aWNhbC5jYWxsKCkgOiBzZWxmLm9wdGlvbnMudGlsaW5nUGFkZGluZ1ZlcnRpY2FsO1xuICAgIHZhciBob3Jpem9udGFsUGFkZGluZyA9IHR5cGVvZiBzZWxmLm9wdGlvbnMudGlsaW5nUGFkZGluZ0hvcml6b250YWwgPT09ICdmdW5jdGlvbicgPyBzZWxmLm9wdGlvbnMudGlsaW5nUGFkZGluZ0hvcml6b250YWwuY2FsbCgpIDogc2VsZi5vcHRpb25zLnRpbGluZ1BhZGRpbmdIb3Jpem9udGFsO1xuICAgIHZhciBvcmdhbml6YXRpb24gPSB7XG4gICAgICByb3dzOiBbXSxcbiAgICAgIHJvd1dpZHRoOiBbXSxcbiAgICAgIHJvd0hlaWdodDogW10sXG4gICAgICB3aWR0aDogMjAsXG4gICAgICBoZWlnaHQ6IDIwLFxuICAgICAgdmVydGljYWxQYWRkaW5nOiB2ZXJ0aWNhbFBhZGRpbmcsXG4gICAgICBob3Jpem9udGFsUGFkZGluZzogaG9yaXpvbnRhbFBhZGRpbmdcbiAgICB9O1xuXG4gICAgdmFyIGxheW91dE5vZGVzID0gW107XG5cbiAgICAvLyBHZXQgbGF5b3V0IG5vZGVzXG4gICAgZm9yICh2YXIgaSA9IDA7IGkgPCBub2Rlcy5sZW5ndGg7IGkrKykge1xuICAgICAgdmFyIG5vZGUgPSBub2Rlc1tpXTtcbiAgICAgIHZhciBsTm9kZSA9IGluc3RhbmNlLmlkVG9MTm9kZVtub2RlLmlkKCldO1xuXG4gICAgICBpZiAoIW5vZGUuc2NyYXRjaCgnY29zZUJpbGtlbnQnKSB8fCAhbm9kZS5zY3JhdGNoKCdjb3NlQmlsa2VudCcpLmR1bW15X3BhcmVudF9pZCkge1xuICAgICAgICB2YXIgb3duZXIgPSBsTm9kZS5vd25lcjtcbiAgICAgICAgb3duZXIucmVtb3ZlKGxOb2RlKTtcblxuICAgICAgICBpbnN0YW5jZS5nbS5yZXNldEFsbE5vZGVzKCk7XG4gICAgICAgIGluc3RhbmNlLmdtLmdldEFsbE5vZGVzKCk7XG4gICAgICB9XG5cbiAgICAgIGxheW91dE5vZGVzLnB1c2gobE5vZGUpO1xuICAgIH1cblxuICAgIC8vIFNvcnQgdGhlIG5vZGVzIGluIGFzY2VuZGluZyBvcmRlciBvZiB0aGVpciBhcmVhc1xuICAgIGxheW91dE5vZGVzLnNvcnQoZnVuY3Rpb24gKG4xLCBuMikge1xuICAgICAgaWYgKG4xLnJlY3Qud2lkdGggKiBuMS5yZWN0LmhlaWdodCA+IG4yLnJlY3Qud2lkdGggKiBuMi5yZWN0LmhlaWdodClcbiAgICAgICAgcmV0dXJuIC0xO1xuICAgICAgaWYgKG4xLnJlY3Qud2lkdGggKiBuMS5yZWN0LmhlaWdodCA8IG4yLnJlY3Qud2lkdGggKiBuMi5yZWN0LmhlaWdodClcbiAgICAgICAgcmV0dXJuIDE7XG4gICAgICByZXR1cm4gMDtcbiAgICB9KTtcblxuICAgIC8vIENyZWF0ZSB0aGUgb3JnYW5pemF0aW9uIC0+IHRpbGUgbWVtYmVyc1xuICAgIGZvciAodmFyIGkgPSAwOyBpIDwgbGF5b3V0Tm9kZXMubGVuZ3RoOyBpKyspIHtcbiAgICAgIHZhciBsTm9kZSA9IGxheW91dE5vZGVzW2ldO1xuXG4gICAgICB2YXIgY3lOb2RlID0gaW5zdGFuY2UuY3kuZ2V0RWxlbWVudEJ5SWQobE5vZGUuaWQpLnBhcmVudCgpWzBdO1xuICAgICAgdmFyIG1pbldpZHRoID0gMDtcbiAgICAgIGlmIChjeU5vZGUpIHtcbiAgICAgICAgbWluV2lkdGggPSBwYXJzZUludChjeU5vZGUuY3NzKCdwYWRkaW5nLWxlZnQnKSkgKyBwYXJzZUludChjeU5vZGUuY3NzKCdwYWRkaW5nLXJpZ2h0JykpO1xuICAgICAgfVxuXG4gICAgICBpZiAob3JnYW5pemF0aW9uLnJvd3MubGVuZ3RoID09IDApIHtcbiAgICAgICAgaW5zdGFuY2UuaW5zZXJ0Tm9kZVRvUm93KG9yZ2FuaXphdGlvbiwgbE5vZGUsIDAsIG1pbldpZHRoKTtcbiAgICAgIH1cbiAgICAgIGVsc2UgaWYgKGluc3RhbmNlLmNhbkFkZEhvcml6b250YWwob3JnYW5pemF0aW9uLCBsTm9kZS5yZWN0LndpZHRoLCBsTm9kZS5yZWN0LmhlaWdodCkpIHtcbiAgICAgICAgaW5zdGFuY2UuaW5zZXJ0Tm9kZVRvUm93KG9yZ2FuaXphdGlvbiwgbE5vZGUsIGluc3RhbmNlLmdldFNob3J0ZXN0Um93SW5kZXgob3JnYW5pemF0aW9uKSwgbWluV2lkdGgpO1xuICAgICAgfVxuICAgICAgZWxzZSB7XG4gICAgICAgIGluc3RhbmNlLmluc2VydE5vZGVUb1Jvdyhvcmdhbml6YXRpb24sIGxOb2RlLCBvcmdhbml6YXRpb24ucm93cy5sZW5ndGgsIG1pbldpZHRoKTtcbiAgICAgIH1cblxuICAgICAgaW5zdGFuY2Uuc2hpZnRUb0xhc3RSb3cob3JnYW5pemF0aW9uKTtcbiAgICB9XG5cbiAgICByZXR1cm4gb3JnYW5pemF0aW9uO1xuICB9O1xuXG4gIGluc3RhbmNlLmluc2VydE5vZGVUb1JvdyA9IGZ1bmN0aW9uIChvcmdhbml6YXRpb24sIG5vZGUsIHJvd0luZGV4LCBtaW5XaWR0aCkge1xuICAgIHZhciBtaW5Db21wb3VuZFNpemUgPSBtaW5XaWR0aDtcblxuICAgIC8vIEFkZCBuZXcgcm93IGlmIG5lZWRlZFxuICAgIGlmIChyb3dJbmRleCA9PSBvcmdhbml6YXRpb24ucm93cy5sZW5ndGgpIHtcbiAgICAgIHZhciBzZWNvbmREaW1lbnNpb24gPSBbXTtcblxuICAgICAgb3JnYW5pemF0aW9uLnJvd3MucHVzaChzZWNvbmREaW1lbnNpb24pO1xuICAgICAgb3JnYW5pemF0aW9uLnJvd1dpZHRoLnB1c2gobWluQ29tcG91bmRTaXplKTtcbiAgICAgIG9yZ2FuaXphdGlvbi5yb3dIZWlnaHQucHVzaCgwKTtcbiAgICB9XG5cbiAgICAvLyBVcGRhdGUgcm93IHdpZHRoXG4gICAgdmFyIHcgPSBvcmdhbml6YXRpb24ucm93V2lkdGhbcm93SW5kZXhdICsgbm9kZS5yZWN0LndpZHRoO1xuXG4gICAgaWYgKG9yZ2FuaXphdGlvbi5yb3dzW3Jvd0luZGV4XS5sZW5ndGggPiAwKSB7XG4gICAgICB3ICs9IG9yZ2FuaXphdGlvbi5ob3Jpem9udGFsUGFkZGluZztcbiAgICB9XG5cbiAgICBvcmdhbml6YXRpb24ucm93V2lkdGhbcm93SW5kZXhdID0gdztcbiAgICAvLyBVcGRhdGUgY29tcG91bmQgd2lkdGhcbiAgICBpZiAob3JnYW5pemF0aW9uLndpZHRoIDwgdykge1xuICAgICAgb3JnYW5pemF0aW9uLndpZHRoID0gdztcbiAgICB9XG5cbiAgICAvLyBVcGRhdGUgaGVpZ2h0XG4gICAgdmFyIGggPSBub2RlLnJlY3QuaGVpZ2h0O1xuICAgIGlmIChyb3dJbmRleCA+IDApXG4gICAgICBoICs9IG9yZ2FuaXphdGlvbi52ZXJ0aWNhbFBhZGRpbmc7XG5cbiAgICB2YXIgZXh0cmFIZWlnaHQgPSAwO1xuICAgIGlmIChoID4gb3JnYW5pemF0aW9uLnJvd0hlaWdodFtyb3dJbmRleF0pIHtcbiAgICAgIGV4dHJhSGVpZ2h0ID0gb3JnYW5pemF0aW9uLnJvd0hlaWdodFtyb3dJbmRleF07XG4gICAgICBvcmdhbml6YXRpb24ucm93SGVpZ2h0W3Jvd0luZGV4XSA9IGg7XG4gICAgICBleHRyYUhlaWdodCA9IG9yZ2FuaXphdGlvbi5yb3dIZWlnaHRbcm93SW5kZXhdIC0gZXh0cmFIZWlnaHQ7XG4gICAgfVxuXG4gICAgb3JnYW5pemF0aW9uLmhlaWdodCArPSBleHRyYUhlaWdodDtcblxuICAgIC8vIEluc2VydCBub2RlXG4gICAgb3JnYW5pemF0aW9uLnJvd3Nbcm93SW5kZXhdLnB1c2gobm9kZSk7XG4gIH07XG5cbi8vU2NhbnMgdGhlIHJvd3Mgb2YgYW4gb3JnYW5pemF0aW9uIGFuZCByZXR1cm5zIHRoZSBvbmUgd2l0aCB0aGUgbWluIHdpZHRoXG4gIGluc3RhbmNlLmdldFNob3J0ZXN0Um93SW5kZXggPSBmdW5jdGlvbiAob3JnYW5pemF0aW9uKSB7XG4gICAgdmFyIHIgPSAtMTtcbiAgICB2YXIgbWluID0gTnVtYmVyLk1BWF9WQUxVRTtcblxuICAgIGZvciAodmFyIGkgPSAwOyBpIDwgb3JnYW5pemF0aW9uLnJvd3MubGVuZ3RoOyBpKyspIHtcbiAgICAgIGlmIChvcmdhbml6YXRpb24ucm93V2lkdGhbaV0gPCBtaW4pIHtcbiAgICAgICAgciA9IGk7XG4gICAgICAgIG1pbiA9IG9yZ2FuaXphdGlvbi5yb3dXaWR0aFtpXTtcbiAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIHI7XG4gIH07XG5cbi8vU2NhbnMgdGhlIHJvd3Mgb2YgYW4gb3JnYW5pemF0aW9uIGFuZCByZXR1cm5zIHRoZSBvbmUgd2l0aCB0aGUgbWF4IHdpZHRoXG4gIGluc3RhbmNlLmdldExvbmdlc3RSb3dJbmRleCA9IGZ1bmN0aW9uIChvcmdhbml6YXRpb24pIHtcbiAgICB2YXIgciA9IC0xO1xuICAgIHZhciBtYXggPSBOdW1iZXIuTUlOX1ZBTFVFO1xuXG4gICAgZm9yICh2YXIgaSA9IDA7IGkgPCBvcmdhbml6YXRpb24ucm93cy5sZW5ndGg7IGkrKykge1xuXG4gICAgICBpZiAob3JnYW5pemF0aW9uLnJvd1dpZHRoW2ldID4gbWF4KSB7XG4gICAgICAgIHIgPSBpO1xuICAgICAgICBtYXggPSBvcmdhbml6YXRpb24ucm93V2lkdGhbaV07XG4gICAgICB9XG4gICAgfVxuXG4gICAgcmV0dXJuIHI7XG4gIH07XG5cbiAgLyoqXG4gICAqIFRoaXMgbWV0aG9kIGNoZWNrcyB3aGV0aGVyIGFkZGluZyBleHRyYSB3aWR0aCB0byB0aGUgb3JnYW5pemF0aW9uIHZpb2xhdGVzXG4gICAqIHRoZSBhc3BlY3QgcmF0aW8oMSkgb3Igbm90LlxuICAgKi9cbiAgaW5zdGFuY2UuY2FuQWRkSG9yaXpvbnRhbCA9IGZ1bmN0aW9uIChvcmdhbml6YXRpb24sIGV4dHJhV2lkdGgsIGV4dHJhSGVpZ2h0KSB7XG5cbiAgICB2YXIgc3JpID0gaW5zdGFuY2UuZ2V0U2hvcnRlc3RSb3dJbmRleChvcmdhbml6YXRpb24pO1xuXG4gICAgaWYgKHNyaSA8IDApIHtcbiAgICAgIHJldHVybiB0cnVlO1xuICAgIH1cblxuICAgIHZhciBtaW4gPSBvcmdhbml6YXRpb24ucm93V2lkdGhbc3JpXTtcblxuICAgIGlmIChtaW4gKyBvcmdhbml6YXRpb24uaG9yaXpvbnRhbFBhZGRpbmcgKyBleHRyYVdpZHRoIDw9IG9yZ2FuaXphdGlvbi53aWR0aClcbiAgICAgIHJldHVybiB0cnVlO1xuXG4gICAgdmFyIGhEaWZmID0gMDtcblxuICAgIC8vIEFkZGluZyB0byBhbiBleGlzdGluZyByb3dcbiAgICBpZiAob3JnYW5pemF0aW9uLnJvd0hlaWdodFtzcmldIDwgZXh0cmFIZWlnaHQpIHtcbiAgICAgIGlmIChzcmkgPiAwKVxuICAgICAgICBoRGlmZiA9IGV4dHJhSGVpZ2h0ICsgb3JnYW5pemF0aW9uLnZlcnRpY2FsUGFkZGluZyAtIG9yZ2FuaXphdGlvbi5yb3dIZWlnaHRbc3JpXTtcbiAgICB9XG5cbiAgICB2YXIgYWRkX3RvX3Jvd19yYXRpbztcbiAgICBpZiAob3JnYW5pemF0aW9uLndpZHRoIC0gbWluID49IGV4dHJhV2lkdGggKyBvcmdhbml6YXRpb24uaG9yaXpvbnRhbFBhZGRpbmcpIHtcbiAgICAgIGFkZF90b19yb3dfcmF0aW8gPSAob3JnYW5pemF0aW9uLmhlaWdodCArIGhEaWZmKSAvIChtaW4gKyBleHRyYVdpZHRoICsgb3JnYW5pemF0aW9uLmhvcml6b250YWxQYWRkaW5nKTtcbiAgICB9IGVsc2Uge1xuICAgICAgYWRkX3RvX3Jvd19yYXRpbyA9IChvcmdhbml6YXRpb24uaGVpZ2h0ICsgaERpZmYpIC8gb3JnYW5pemF0aW9uLndpZHRoO1xuICAgIH1cblxuICAgIC8vIEFkZGluZyBhIG5ldyByb3cgZm9yIHRoaXMgbm9kZVxuICAgIGhEaWZmID0gZXh0cmFIZWlnaHQgKyBvcmdhbml6YXRpb24udmVydGljYWxQYWRkaW5nO1xuICAgIHZhciBhZGRfbmV3X3Jvd19yYXRpbztcbiAgICBpZiAob3JnYW5pemF0aW9uLndpZHRoIDwgZXh0cmFXaWR0aCkge1xuICAgICAgYWRkX25ld19yb3dfcmF0aW8gPSAob3JnYW5pemF0aW9uLmhlaWdodCArIGhEaWZmKSAvIGV4dHJhV2lkdGg7XG4gICAgfSBlbHNlIHtcbiAgICAgIGFkZF9uZXdfcm93X3JhdGlvID0gKG9yZ2FuaXphdGlvbi5oZWlnaHQgKyBoRGlmZikgLyBvcmdhbml6YXRpb24ud2lkdGg7XG4gICAgfVxuXG4gICAgaWYgKGFkZF9uZXdfcm93X3JhdGlvIDwgMSlcbiAgICAgIGFkZF9uZXdfcm93X3JhdGlvID0gMSAvIGFkZF9uZXdfcm93X3JhdGlvO1xuXG4gICAgaWYgKGFkZF90b19yb3dfcmF0aW8gPCAxKVxuICAgICAgYWRkX3RvX3Jvd19yYXRpbyA9IDEgLyBhZGRfdG9fcm93X3JhdGlvO1xuXG4gICAgcmV0dXJuIGFkZF90b19yb3dfcmF0aW8gPCBhZGRfbmV3X3Jvd19yYXRpbztcbiAgfTtcblxuXG4vL0lmIG1vdmluZyB0aGUgbGFzdCBub2RlIGZyb20gdGhlIGxvbmdlc3Qgcm93IGFuZCBhZGRpbmcgaXQgdG8gdGhlIGxhc3Rcbi8vcm93IG1ha2VzIHRoZSBib3VuZGluZyBib3ggc21hbGxlciwgZG8gaXQuXG4gIGluc3RhbmNlLnNoaWZ0VG9MYXN0Um93ID0gZnVuY3Rpb24gKG9yZ2FuaXphdGlvbikge1xuICAgIHZhciBsb25nZXN0ID0gaW5zdGFuY2UuZ2V0TG9uZ2VzdFJvd0luZGV4KG9yZ2FuaXphdGlvbik7XG4gICAgdmFyIGxhc3QgPSBvcmdhbml6YXRpb24ucm93V2lkdGgubGVuZ3RoIC0gMTtcbiAgICB2YXIgcm93ID0gb3JnYW5pemF0aW9uLnJvd3NbbG9uZ2VzdF07XG4gICAgdmFyIG5vZGUgPSByb3dbcm93Lmxlbmd0aCAtIDFdO1xuXG4gICAgdmFyIGRpZmYgPSBub2RlLndpZHRoICsgb3JnYW5pemF0aW9uLmhvcml6b250YWxQYWRkaW5nO1xuXG4gICAgLy8gQ2hlY2sgaWYgdGhlcmUgaXMgZW5vdWdoIHNwYWNlIG9uIHRoZSBsYXN0IHJvd1xuICAgIGlmIChvcmdhbml6YXRpb24ud2lkdGggLSBvcmdhbml6YXRpb24ucm93V2lkdGhbbGFzdF0gPiBkaWZmICYmIGxvbmdlc3QgIT0gbGFzdCkge1xuICAgICAgLy8gUmVtb3ZlIHRoZSBsYXN0IGVsZW1lbnQgb2YgdGhlIGxvbmdlc3Qgcm93XG4gICAgICByb3cuc3BsaWNlKC0xLCAxKTtcblxuICAgICAgLy8gUHVzaCBpdCB0byB0aGUgbGFzdCByb3dcbiAgICAgIG9yZ2FuaXphdGlvbi5yb3dzW2xhc3RdLnB1c2gobm9kZSk7XG5cbiAgICAgIG9yZ2FuaXphdGlvbi5yb3dXaWR0aFtsb25nZXN0XSA9IG9yZ2FuaXphdGlvbi5yb3dXaWR0aFtsb25nZXN0XSAtIGRpZmY7XG4gICAgICBvcmdhbml6YXRpb24ucm93V2lkdGhbbGFzdF0gPSBvcmdhbml6YXRpb24ucm93V2lkdGhbbGFzdF0gKyBkaWZmO1xuICAgICAgb3JnYW5pemF0aW9uLndpZHRoID0gb3JnYW5pemF0aW9uLnJvd1dpZHRoW2luc3RhbmNlLmdldExvbmdlc3RSb3dJbmRleChvcmdhbml6YXRpb24pXTtcblxuICAgICAgLy8gVXBkYXRlIGhlaWdodHMgb2YgdGhlIG9yZ2FuaXphdGlvblxuICAgICAgdmFyIG1heEhlaWdodCA9IE51bWJlci5NSU5fVkFMVUU7XG4gICAgICBmb3IgKHZhciBpID0gMDsgaSA8IHJvdy5sZW5ndGg7IGkrKykge1xuICAgICAgICBpZiAocm93W2ldLmhlaWdodCA+IG1heEhlaWdodClcbiAgICAgICAgICBtYXhIZWlnaHQgPSByb3dbaV0uaGVpZ2h0O1xuICAgICAgfVxuICAgICAgaWYgKGxvbmdlc3QgPiAwKVxuICAgICAgICBtYXhIZWlnaHQgKz0gb3JnYW5pemF0aW9uLnZlcnRpY2FsUGFkZGluZztcblxuICAgICAgdmFyIHByZXZUb3RhbCA9IG9yZ2FuaXphdGlvbi5yb3dIZWlnaHRbbG9uZ2VzdF0gKyBvcmdhbml6YXRpb24ucm93SGVpZ2h0W2xhc3RdO1xuXG4gICAgICBvcmdhbml6YXRpb24ucm93SGVpZ2h0W2xvbmdlc3RdID0gbWF4SGVpZ2h0O1xuICAgICAgaWYgKG9yZ2FuaXphdGlvbi5yb3dIZWlnaHRbbGFzdF0gPCBub2RlLmhlaWdodCArIG9yZ2FuaXphdGlvbi52ZXJ0aWNhbFBhZGRpbmcpXG4gICAgICAgIG9yZ2FuaXphdGlvbi5yb3dIZWlnaHRbbGFzdF0gPSBub2RlLmhlaWdodCArIG9yZ2FuaXphdGlvbi52ZXJ0aWNhbFBhZGRpbmc7XG5cbiAgICAgIHZhciBmaW5hbFRvdGFsID0gb3JnYW5pemF0aW9uLnJvd0hlaWdodFtsb25nZXN0XSArIG9yZ2FuaXphdGlvbi5yb3dIZWlnaHRbbGFzdF07XG4gICAgICBvcmdhbml6YXRpb24uaGVpZ2h0ICs9IChmaW5hbFRvdGFsIC0gcHJldlRvdGFsKTtcblxuICAgICAgaW5zdGFuY2Uuc2hpZnRUb0xhc3RSb3cob3JnYW5pemF0aW9uKTtcbiAgICB9XG4gIH07XG4gIFxuICBpbnN0YW5jZS5wcmVMYXlvdXQgPSBmdW5jdGlvbigpIHtcbiAgICAvLyBGaW5kIHplcm8gZGVncmVlIG5vZGVzIGFuZCBjcmVhdGUgYSBjb21wb3VuZCBmb3IgZWFjaCBsZXZlbFxuICAgIHZhciBtZW1iZXJHcm91cHMgPSBpbnN0YW5jZS5ncm91cFplcm9EZWdyZWVNZW1iZXJzKCk7XG4gICAgLy8gVGlsZSBhbmQgY2xlYXIgY2hpbGRyZW4gb2YgZWFjaCBjb21wb3VuZFxuICAgIGluc3RhbmNlLnRpbGVkTWVtYmVyUGFjayA9IGluc3RhbmNlLmNsZWFyQ29tcG91bmRzKCk7XG4gICAgLy8gU2VwYXJhdGVseSB0aWxlIGFuZCBjbGVhciB6ZXJvIGRlZ3JlZSBub2RlcyBmb3IgZWFjaCBsZXZlbFxuICAgIGluc3RhbmNlLnRpbGVkWmVyb0RlZ3JlZU5vZGVzID0gaW5zdGFuY2UuY2xlYXJaZXJvRGVncmVlTWVtYmVycyhtZW1iZXJHcm91cHMpO1xuICB9O1xuICBcbiAgaW5zdGFuY2UucG9zdExheW91dCA9IGZ1bmN0aW9uKCkge1xuICAgIHZhciBub2RlcyA9IGluc3RhbmNlLm9wdGlvbnMuZWxlcy5ub2RlcygpO1xuICAgIC8vZmlsbCB0aGUgdG9CZVRpbGVkIG1hcFxuICAgIGZvciAodmFyIGkgPSAwOyBpIDwgbm9kZXMubGVuZ3RoOyBpKyspIHtcbiAgICAgIGluc3RhbmNlLmdldFRvQmVUaWxlZChub2Rlc1tpXSk7XG4gICAgfVxuXG4gICAgLy8gUmVwb3B1bGF0ZSBtZW1iZXJzXG4gICAgaW5zdGFuY2UucmVwb3B1bGF0ZVplcm9EZWdyZWVNZW1iZXJzKGluc3RhbmNlLnRpbGVkWmVyb0RlZ3JlZU5vZGVzKTtcblxuICAgIGluc3RhbmNlLnJlcG9wdWxhdGVDb21wb3VuZHMoaW5zdGFuY2UudGlsZWRNZW1iZXJQYWNrKTtcblxuICAgIGluc3RhbmNlLm9wdGlvbnMuY3kubm9kZXMoKS51cGRhdGVDb21wb3VuZEJvdW5kcygpO1xuICB9O1xufTsiLCJ2YXIgUG9pbnREID0gcmVxdWlyZSgnLi9Qb2ludEQnKTtcblxuZnVuY3Rpb24gVHJhbnNmb3JtKHgsIHkpIHtcbiAgdGhpcy5sd29ybGRPcmdYID0gMC4wO1xuICB0aGlzLmx3b3JsZE9yZ1kgPSAwLjA7XG4gIHRoaXMubGRldmljZU9yZ1ggPSAwLjA7XG4gIHRoaXMubGRldmljZU9yZ1kgPSAwLjA7XG4gIHRoaXMubHdvcmxkRXh0WCA9IDEuMDtcbiAgdGhpcy5sd29ybGRFeHRZID0gMS4wO1xuICB0aGlzLmxkZXZpY2VFeHRYID0gMS4wO1xuICB0aGlzLmxkZXZpY2VFeHRZID0gMS4wO1xufVxuXG5UcmFuc2Zvcm0ucHJvdG90eXBlLmdldFdvcmxkT3JnWCA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLmx3b3JsZE9yZ1g7XG59XG5cblRyYW5zZm9ybS5wcm90b3R5cGUuc2V0V29ybGRPcmdYID0gZnVuY3Rpb24gKHdveClcbntcbiAgdGhpcy5sd29ybGRPcmdYID0gd294O1xufVxuXG5UcmFuc2Zvcm0ucHJvdG90eXBlLmdldFdvcmxkT3JnWSA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLmx3b3JsZE9yZ1k7XG59XG5cblRyYW5zZm9ybS5wcm90b3R5cGUuc2V0V29ybGRPcmdZID0gZnVuY3Rpb24gKHdveSlcbntcbiAgdGhpcy5sd29ybGRPcmdZID0gd295O1xufVxuXG5UcmFuc2Zvcm0ucHJvdG90eXBlLmdldFdvcmxkRXh0WCA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLmx3b3JsZEV4dFg7XG59XG5cblRyYW5zZm9ybS5wcm90b3R5cGUuc2V0V29ybGRFeHRYID0gZnVuY3Rpb24gKHdleClcbntcbiAgdGhpcy5sd29ybGRFeHRYID0gd2V4O1xufVxuXG5UcmFuc2Zvcm0ucHJvdG90eXBlLmdldFdvcmxkRXh0WSA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLmx3b3JsZEV4dFk7XG59XG5cblRyYW5zZm9ybS5wcm90b3R5cGUuc2V0V29ybGRFeHRZID0gZnVuY3Rpb24gKHdleSlcbntcbiAgdGhpcy5sd29ybGRFeHRZID0gd2V5O1xufVxuXG4vKiBEZXZpY2UgcmVsYXRlZCAqL1xuXG5UcmFuc2Zvcm0ucHJvdG90eXBlLmdldERldmljZU9yZ1ggPSBmdW5jdGlvbiAoKVxue1xuICByZXR1cm4gdGhpcy5sZGV2aWNlT3JnWDtcbn1cblxuVHJhbnNmb3JtLnByb3RvdHlwZS5zZXREZXZpY2VPcmdYID0gZnVuY3Rpb24gKGRveClcbntcbiAgdGhpcy5sZGV2aWNlT3JnWCA9IGRveDtcbn1cblxuVHJhbnNmb3JtLnByb3RvdHlwZS5nZXREZXZpY2VPcmdZID0gZnVuY3Rpb24gKClcbntcbiAgcmV0dXJuIHRoaXMubGRldmljZU9yZ1k7XG59XG5cblRyYW5zZm9ybS5wcm90b3R5cGUuc2V0RGV2aWNlT3JnWSA9IGZ1bmN0aW9uIChkb3kpXG57XG4gIHRoaXMubGRldmljZU9yZ1kgPSBkb3k7XG59XG5cblRyYW5zZm9ybS5wcm90b3R5cGUuZ2V0RGV2aWNlRXh0WCA9IGZ1bmN0aW9uICgpXG57XG4gIHJldHVybiB0aGlzLmxkZXZpY2VFeHRYO1xufVxuXG5UcmFuc2Zvcm0ucHJvdG90eXBlLnNldERldmljZUV4dFggPSBmdW5jdGlvbiAoZGV4KVxue1xuICB0aGlzLmxkZXZpY2VFeHRYID0gZGV4O1xufVxuXG5UcmFuc2Zvcm0ucHJvdG90eXBlLmdldERldmljZUV4dFkgPSBmdW5jdGlvbiAoKVxue1xuICByZXR1cm4gdGhpcy5sZGV2aWNlRXh0WTtcbn1cblxuVHJhbnNmb3JtLnByb3RvdHlwZS5zZXREZXZpY2VFeHRZID0gZnVuY3Rpb24gKGRleSlcbntcbiAgdGhpcy5sZGV2aWNlRXh0WSA9IGRleTtcbn1cblxuVHJhbnNmb3JtLnByb3RvdHlwZS50cmFuc2Zvcm1YID0gZnVuY3Rpb24gKHgpXG57XG4gIHZhciB4RGV2aWNlID0gMC4wO1xuICB2YXIgd29ybGRFeHRYID0gdGhpcy5sd29ybGRFeHRYO1xuICBpZiAod29ybGRFeHRYICE9IDAuMClcbiAge1xuICAgIHhEZXZpY2UgPSB0aGlzLmxkZXZpY2VPcmdYICtcbiAgICAgICAgICAgICgoeCAtIHRoaXMubHdvcmxkT3JnWCkgKiB0aGlzLmxkZXZpY2VFeHRYIC8gd29ybGRFeHRYKTtcbiAgfVxuXG4gIHJldHVybiB4RGV2aWNlO1xufVxuXG5UcmFuc2Zvcm0ucHJvdG90eXBlLnRyYW5zZm9ybVkgPSBmdW5jdGlvbiAoeSlcbntcbiAgdmFyIHlEZXZpY2UgPSAwLjA7XG4gIHZhciB3b3JsZEV4dFkgPSB0aGlzLmx3b3JsZEV4dFk7XG4gIGlmICh3b3JsZEV4dFkgIT0gMC4wKVxuICB7XG4gICAgeURldmljZSA9IHRoaXMubGRldmljZU9yZ1kgK1xuICAgICAgICAgICAgKCh5IC0gdGhpcy5sd29ybGRPcmdZKSAqIHRoaXMubGRldmljZUV4dFkgLyB3b3JsZEV4dFkpO1xuICB9XG5cblxuICByZXR1cm4geURldmljZTtcbn1cblxuVHJhbnNmb3JtLnByb3RvdHlwZS5pbnZlcnNlVHJhbnNmb3JtWCA9IGZ1bmN0aW9uICh4KVxue1xuICB2YXIgeFdvcmxkID0gMC4wO1xuICB2YXIgZGV2aWNlRXh0WCA9IHRoaXMubGRldmljZUV4dFg7XG4gIGlmIChkZXZpY2VFeHRYICE9IDAuMClcbiAge1xuICAgIHhXb3JsZCA9IHRoaXMubHdvcmxkT3JnWCArXG4gICAgICAgICAgICAoKHggLSB0aGlzLmxkZXZpY2VPcmdYKSAqIHRoaXMubHdvcmxkRXh0WCAvIGRldmljZUV4dFgpO1xuICB9XG5cblxuICByZXR1cm4geFdvcmxkO1xufVxuXG5UcmFuc2Zvcm0ucHJvdG90eXBlLmludmVyc2VUcmFuc2Zvcm1ZID0gZnVuY3Rpb24gKHkpXG57XG4gIHZhciB5V29ybGQgPSAwLjA7XG4gIHZhciBkZXZpY2VFeHRZID0gdGhpcy5sZGV2aWNlRXh0WTtcbiAgaWYgKGRldmljZUV4dFkgIT0gMC4wKVxuICB7XG4gICAgeVdvcmxkID0gdGhpcy5sd29ybGRPcmdZICtcbiAgICAgICAgICAgICgoeSAtIHRoaXMubGRldmljZU9yZ1kpICogdGhpcy5sd29ybGRFeHRZIC8gZGV2aWNlRXh0WSk7XG4gIH1cbiAgcmV0dXJuIHlXb3JsZDtcbn1cblxuVHJhbnNmb3JtLnByb3RvdHlwZS5pbnZlcnNlVHJhbnNmb3JtUG9pbnQgPSBmdW5jdGlvbiAoaW5Qb2ludClcbntcbiAgdmFyIG91dFBvaW50ID1cbiAgICAgICAgICBuZXcgUG9pbnREKHRoaXMuaW52ZXJzZVRyYW5zZm9ybVgoaW5Qb2ludC54KSxcbiAgICAgICAgICAgICAgICAgIHRoaXMuaW52ZXJzZVRyYW5zZm9ybVkoaW5Qb2ludC55KSk7XG4gIHJldHVybiBvdXRQb2ludDtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBUcmFuc2Zvcm07XG4iLCJmdW5jdGlvbiBVbmlxdWVJREdlbmVyZXRvcigpIHtcbn1cblxuVW5pcXVlSURHZW5lcmV0b3IubGFzdElEID0gMDtcblxuVW5pcXVlSURHZW5lcmV0b3IuY3JlYXRlSUQgPSBmdW5jdGlvbiAob2JqKSB7XG4gIGlmIChVbmlxdWVJREdlbmVyZXRvci5pc1ByaW1pdGl2ZShvYmopKSB7XG4gICAgcmV0dXJuIG9iajtcbiAgfVxuICBpZiAob2JqLnVuaXF1ZUlEICE9IG51bGwpIHtcbiAgICByZXR1cm4gb2JqLnVuaXF1ZUlEO1xuICB9XG4gIG9iai51bmlxdWVJRCA9IFVuaXF1ZUlER2VuZXJldG9yLmdldFN0cmluZygpO1xuICBVbmlxdWVJREdlbmVyZXRvci5sYXN0SUQrKztcbiAgcmV0dXJuIG9iai51bmlxdWVJRDtcbn1cblxuVW5pcXVlSURHZW5lcmV0b3IuZ2V0U3RyaW5nID0gZnVuY3Rpb24gKGlkKSB7XG4gIGlmIChpZCA9PSBudWxsKVxuICAgIGlkID0gVW5pcXVlSURHZW5lcmV0b3IubGFzdElEO1xuICByZXR1cm4gXCJPYmplY3QjXCIgKyBpZCArIFwiXCI7XG59XG5cblVuaXF1ZUlER2VuZXJldG9yLmlzUHJpbWl0aXZlID0gZnVuY3Rpb24gKGFyZykge1xuICB2YXIgdHlwZSA9IHR5cGVvZiBhcmc7XG4gIHJldHVybiBhcmcgPT0gbnVsbCB8fCAodHlwZSAhPSBcIm9iamVjdFwiICYmIHR5cGUgIT0gXCJmdW5jdGlvblwiKTtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBVbmlxdWVJREdlbmVyZXRvcjtcbiIsIid1c2Ugc3RyaWN0JztcblxudmFyIERpbWVuc2lvbkQgPSByZXF1aXJlKCcuL0RpbWVuc2lvbkQnKTtcbnZhciBIYXNoTWFwID0gcmVxdWlyZSgnLi9IYXNoTWFwJyk7XG52YXIgSGFzaFNldCA9IHJlcXVpcmUoJy4vSGFzaFNldCcpO1xudmFyIElHZW9tZXRyeSA9IHJlcXVpcmUoJy4vSUdlb21ldHJ5Jyk7XG52YXIgSU1hdGggPSByZXF1aXJlKCcuL0lNYXRoJyk7XG52YXIgSW50ZWdlciA9IHJlcXVpcmUoJy4vSW50ZWdlcicpO1xudmFyIFBvaW50ID0gcmVxdWlyZSgnLi9Qb2ludCcpO1xudmFyIFBvaW50RCA9IHJlcXVpcmUoJy4vUG9pbnREJyk7XG52YXIgUmFuZG9tU2VlZCA9IHJlcXVpcmUoJy4vUmFuZG9tU2VlZCcpO1xudmFyIFJlY3RhbmdsZUQgPSByZXF1aXJlKCcuL1JlY3RhbmdsZUQnKTtcbnZhciBUcmFuc2Zvcm0gPSByZXF1aXJlKCcuL1RyYW5zZm9ybScpO1xudmFyIFVuaXF1ZUlER2VuZXJldG9yID0gcmVxdWlyZSgnLi9VbmlxdWVJREdlbmVyZXRvcicpO1xudmFyIExHcmFwaE9iamVjdCA9IHJlcXVpcmUoJy4vTEdyYXBoT2JqZWN0Jyk7XG52YXIgTEdyYXBoID0gcmVxdWlyZSgnLi9MR3JhcGgnKTtcbnZhciBMRWRnZSA9IHJlcXVpcmUoJy4vTEVkZ2UnKTtcbnZhciBMR3JhcGhNYW5hZ2VyID0gcmVxdWlyZSgnLi9MR3JhcGhNYW5hZ2VyJyk7XG52YXIgTE5vZGUgPSByZXF1aXJlKCcuL0xOb2RlJyk7XG52YXIgTGF5b3V0ID0gcmVxdWlyZSgnLi9MYXlvdXQnKTtcbnZhciBMYXlvdXRDb25zdGFudHMgPSByZXF1aXJlKCcuL0xheW91dENvbnN0YW50cycpO1xudmFyIEZETGF5b3V0ID0gcmVxdWlyZSgnLi9GRExheW91dCcpO1xudmFyIEZETGF5b3V0Q29uc3RhbnRzID0gcmVxdWlyZSgnLi9GRExheW91dENvbnN0YW50cycpO1xudmFyIEZETGF5b3V0RWRnZSA9IHJlcXVpcmUoJy4vRkRMYXlvdXRFZGdlJyk7XG52YXIgRkRMYXlvdXROb2RlID0gcmVxdWlyZSgnLi9GRExheW91dE5vZGUnKTtcbnZhciBDb1NFQ29uc3RhbnRzID0gcmVxdWlyZSgnLi9Db1NFQ29uc3RhbnRzJyk7XG52YXIgQ29TRUVkZ2UgPSByZXF1aXJlKCcuL0NvU0VFZGdlJyk7XG52YXIgQ29TRUdyYXBoID0gcmVxdWlyZSgnLi9Db1NFR3JhcGgnKTtcbnZhciBDb1NFR3JhcGhNYW5hZ2VyID0gcmVxdWlyZSgnLi9Db1NFR3JhcGhNYW5hZ2VyJyk7XG52YXIgQ29TRUxheW91dCA9IHJlcXVpcmUoJy4vQ29TRUxheW91dCcpO1xudmFyIENvU0VOb2RlID0gcmVxdWlyZSgnLi9Db1NFTm9kZScpO1xudmFyIFRpbGluZ0V4dGVuc2lvbiA9IHJlcXVpcmUoJy4vVGlsaW5nRXh0ZW5zaW9uJyk7XG5cbnZhciBkZWZhdWx0cyA9IHtcbiAgLy8gQ2FsbGVkIG9uIGBsYXlvdXRyZWFkeWBcbiAgcmVhZHk6IGZ1bmN0aW9uICgpIHtcbiAgfSxcbiAgLy8gQ2FsbGVkIG9uIGBsYXlvdXRzdG9wYFxuICBzdG9wOiBmdW5jdGlvbiAoKSB7XG4gIH0sXG4gIC8vIG51bWJlciBvZiB0aWNrcyBwZXIgZnJhbWU7IGhpZ2hlciBpcyBmYXN0ZXIgYnV0IG1vcmUgamVya3lcbiAgcmVmcmVzaDogMzAsXG4gIC8vIFdoZXRoZXIgdG8gZml0IHRoZSBuZXR3b3JrIHZpZXcgYWZ0ZXIgd2hlbiBkb25lXG4gIGZpdDogdHJ1ZSxcbiAgLy8gUGFkZGluZyBvbiBmaXRcbiAgcGFkZGluZzogMTAsXG4gIC8vIFBhZGRpbmcgZm9yIGNvbXBvdW5kc1xuICBwYWRkaW5nQ29tcG91bmQ6IDE1LFxuICAvLyBXaGV0aGVyIHRvIGVuYWJsZSBpbmNyZW1lbnRhbCBtb2RlXG4gIHJhbmRvbWl6ZTogdHJ1ZSxcbiAgLy8gTm9kZSByZXB1bHNpb24gKG5vbiBvdmVybGFwcGluZykgbXVsdGlwbGllclxuICBub2RlUmVwdWxzaW9uOiA0NTAwLFxuICAvLyBJZGVhbCBlZGdlIChub24gbmVzdGVkKSBsZW5ndGhcbiAgaWRlYWxFZGdlTGVuZ3RoOiA1MCxcbiAgLy8gRGl2aXNvciB0byBjb21wdXRlIGVkZ2UgZm9yY2VzXG4gIGVkZ2VFbGFzdGljaXR5OiAwLjQ1LFxuICAvLyBOZXN0aW5nIGZhY3RvciAobXVsdGlwbGllcikgdG8gY29tcHV0ZSBpZGVhbCBlZGdlIGxlbmd0aCBmb3IgbmVzdGVkIGVkZ2VzXG4gIG5lc3RpbmdGYWN0b3I6IDAuMSxcbiAgLy8gR3Jhdml0eSBmb3JjZSAoY29uc3RhbnQpXG4gIGdyYXZpdHk6IDAuMjUsXG4gIC8vIE1heGltdW0gbnVtYmVyIG9mIGl0ZXJhdGlvbnMgdG8gcGVyZm9ybVxuICBudW1JdGVyOiAyNTAwLFxuICAvLyBGb3IgZW5hYmxpbmcgdGlsaW5nXG4gIHRpbGU6IHRydWUsXG4gIC8vIFR5cGUgb2YgbGF5b3V0IGFuaW1hdGlvbi4gVGhlIG9wdGlvbiBzZXQgaXMgeydkdXJpbmcnLCAnZW5kJywgZmFsc2V9XG4gIGFuaW1hdGU6ICdlbmQnLFxuICAvLyBEdXJhdGlvbiBmb3IgYW5pbWF0ZTplbmRcbiAgYW5pbWF0aW9uRHVyYXRpb246IDUwMCxcbiAgLy8gUmVwcmVzZW50cyB0aGUgYW1vdW50IG9mIHRoZSB2ZXJ0aWNhbCBzcGFjZSB0byBwdXQgYmV0d2VlbiB0aGUgemVybyBkZWdyZWUgbWVtYmVycyBkdXJpbmcgdGhlIHRpbGluZyBvcGVyYXRpb24oY2FuIGFsc28gYmUgYSBmdW5jdGlvbilcbiAgdGlsaW5nUGFkZGluZ1ZlcnRpY2FsOiAxMCxcbiAgLy8gUmVwcmVzZW50cyB0aGUgYW1vdW50IG9mIHRoZSBob3Jpem9udGFsIHNwYWNlIHRvIHB1dCBiZXR3ZWVuIHRoZSB6ZXJvIGRlZ3JlZSBtZW1iZXJzIGR1cmluZyB0aGUgdGlsaW5nIG9wZXJhdGlvbihjYW4gYWxzbyBiZSBhIGZ1bmN0aW9uKVxuICB0aWxpbmdQYWRkaW5nSG9yaXpvbnRhbDogMTAsXG4gIC8vIEdyYXZpdHkgcmFuZ2UgKGNvbnN0YW50KSBmb3IgY29tcG91bmRzXG4gIGdyYXZpdHlSYW5nZUNvbXBvdW5kOiAxLjUsXG4gIC8vIEdyYXZpdHkgZm9yY2UgKGNvbnN0YW50KSBmb3IgY29tcG91bmRzXG4gIGdyYXZpdHlDb21wb3VuZDogMS4wLFxuICAvLyBHcmF2aXR5IHJhbmdlIChjb25zdGFudClcbiAgZ3Jhdml0eVJhbmdlOiAzLjhcbn07XG5cbmZ1bmN0aW9uIGV4dGVuZChkZWZhdWx0cywgb3B0aW9ucykge1xuICB2YXIgb2JqID0ge307XG5cbiAgZm9yICh2YXIgaSBpbiBkZWZhdWx0cykge1xuICAgIG9ialtpXSA9IGRlZmF1bHRzW2ldO1xuICB9XG5cbiAgZm9yICh2YXIgaSBpbiBvcHRpb25zKSB7XG4gICAgb2JqW2ldID0gb3B0aW9uc1tpXTtcbiAgfVxuXG4gIHJldHVybiBvYmo7XG59O1xuXG5mdW5jdGlvbiBfQ29TRUxheW91dChfb3B0aW9ucykge1xuICBUaWxpbmdFeHRlbnNpb24odGhpcyk7IC8vIEV4dGVuZCB0aGlzIGluc3RhbmNlIHdpdGggdGlsaW5nIGZ1bmN0aW9uc1xuICB0aGlzLm9wdGlvbnMgPSBleHRlbmQoZGVmYXVsdHMsIF9vcHRpb25zKTtcbiAgZ2V0VXNlck9wdGlvbnModGhpcy5vcHRpb25zKTtcbn1cblxudmFyIGdldFVzZXJPcHRpb25zID0gZnVuY3Rpb24gKG9wdGlvbnMpIHtcbiAgaWYgKG9wdGlvbnMubm9kZVJlcHVsc2lvbiAhPSBudWxsKVxuICAgIENvU0VDb25zdGFudHMuREVGQVVMVF9SRVBVTFNJT05fU1RSRU5HVEggPSBGRExheW91dENvbnN0YW50cy5ERUZBVUxUX1JFUFVMU0lPTl9TVFJFTkdUSCA9IG9wdGlvbnMubm9kZVJlcHVsc2lvbjtcbiAgaWYgKG9wdGlvbnMuaWRlYWxFZGdlTGVuZ3RoICE9IG51bGwpXG4gICAgQ29TRUNvbnN0YW50cy5ERUZBVUxUX0VER0VfTEVOR1RIID0gRkRMYXlvdXRDb25zdGFudHMuREVGQVVMVF9FREdFX0xFTkdUSCA9IG9wdGlvbnMuaWRlYWxFZGdlTGVuZ3RoO1xuICBpZiAob3B0aW9ucy5lZGdlRWxhc3RpY2l0eSAhPSBudWxsKVxuICAgIENvU0VDb25zdGFudHMuREVGQVVMVF9TUFJJTkdfU1RSRU5HVEggPSBGRExheW91dENvbnN0YW50cy5ERUZBVUxUX1NQUklOR19TVFJFTkdUSCA9IG9wdGlvbnMuZWRnZUVsYXN0aWNpdHk7XG4gIGlmIChvcHRpb25zLm5lc3RpbmdGYWN0b3IgIT0gbnVsbClcbiAgICBDb1NFQ29uc3RhbnRzLlBFUl9MRVZFTF9JREVBTF9FREdFX0xFTkdUSF9GQUNUT1IgPSBGRExheW91dENvbnN0YW50cy5QRVJfTEVWRUxfSURFQUxfRURHRV9MRU5HVEhfRkFDVE9SID0gb3B0aW9ucy5uZXN0aW5nRmFjdG9yO1xuICBpZiAob3B0aW9ucy5ncmF2aXR5ICE9IG51bGwpXG4gICAgQ29TRUNvbnN0YW50cy5ERUZBVUxUX0dSQVZJVFlfU1RSRU5HVEggPSBGRExheW91dENvbnN0YW50cy5ERUZBVUxUX0dSQVZJVFlfU1RSRU5HVEggPSBvcHRpb25zLmdyYXZpdHk7XG4gIGlmIChvcHRpb25zLm51bUl0ZXIgIT0gbnVsbClcbiAgICBDb1NFQ29uc3RhbnRzLk1BWF9JVEVSQVRJT05TID0gRkRMYXlvdXRDb25zdGFudHMuTUFYX0lURVJBVElPTlMgPSBvcHRpb25zLm51bUl0ZXI7XG4gIGlmIChvcHRpb25zLnBhZGRpbmdDb21wb3VuZCAhPSBudWxsKVxuICAgIENvU0VDb25zdGFudHMuREVGQVVMVF9HUkFQSF9NQVJHSU4gPSBGRExheW91dENvbnN0YW50cy5ERUZBVUxUX0dSQVBIX01BUkdJTiA9IExheW91dENvbnN0YW50cy5ERUZBVUxUX0dSQVBIX01BUkdJTiA9IG9wdGlvbnMucGFkZGluZ0NvbXBvdW5kO1xuICBpZiAob3B0aW9ucy5ncmF2aXR5UmFuZ2UgIT0gbnVsbClcbiAgICBDb1NFQ29uc3RhbnRzLkRFRkFVTFRfR1JBVklUWV9SQU5HRV9GQUNUT1IgPSBGRExheW91dENvbnN0YW50cy5ERUZBVUxUX0dSQVZJVFlfUkFOR0VfRkFDVE9SID0gb3B0aW9ucy5ncmF2aXR5UmFuZ2U7XG4gIGlmKG9wdGlvbnMuZ3Jhdml0eUNvbXBvdW5kICE9IG51bGwpXG4gICAgQ29TRUNvbnN0YW50cy5ERUZBVUxUX0NPTVBPVU5EX0dSQVZJVFlfU1RSRU5HVEggPSBGRExheW91dENvbnN0YW50cy5ERUZBVUxUX0NPTVBPVU5EX0dSQVZJVFlfU1RSRU5HVEggPSBvcHRpb25zLmdyYXZpdHlDb21wb3VuZDtcbiAgaWYob3B0aW9ucy5ncmF2aXR5UmFuZ2VDb21wb3VuZCAhPSBudWxsKVxuICAgIENvU0VDb25zdGFudHMuREVGQVVMVF9DT01QT1VORF9HUkFWSVRZX1JBTkdFX0ZBQ1RPUiA9IEZETGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfQ09NUE9VTkRfR1JBVklUWV9SQU5HRV9GQUNUT1IgPSBvcHRpb25zLmdyYXZpdHlSYW5nZUNvbXBvdW5kO1xuXG4gIENvU0VDb25zdGFudHMuREVGQVVMVF9JTkNSRU1FTlRBTCA9IEZETGF5b3V0Q29uc3RhbnRzLkRFRkFVTFRfSU5DUkVNRU5UQUwgPSBMYXlvdXRDb25zdGFudHMuREVGQVVMVF9JTkNSRU1FTlRBTCA9XG4gICAgICAgICAgIShvcHRpb25zLnJhbmRvbWl6ZSk7XG4gIENvU0VDb25zdGFudHMuQU5JTUFURSA9IEZETGF5b3V0Q29uc3RhbnRzLkFOSU1BVEUgPSBvcHRpb25zLmFuaW1hdGU7XG59O1xuXG5fQ29TRUxheW91dC5wcm90b3R5cGUucnVuID0gZnVuY3Rpb24gKCkge1xuICB2YXIgcmVhZHk7XG4gIHZhciBmcmFtZUlkO1xuICB2YXIgb3B0aW9ucyA9IHRoaXMub3B0aW9ucztcbiAgdmFyIGlkVG9MTm9kZSA9IHRoaXMuaWRUb0xOb2RlID0ge307XG4gIHZhciBsYXlvdXQgPSB0aGlzLmxheW91dCA9IG5ldyBDb1NFTGF5b3V0KCk7XG4gIHZhciBzZWxmID0gdGhpcztcbiAgXG4gIHRoaXMuY3kgPSB0aGlzLm9wdGlvbnMuY3k7XG5cbiAgdGhpcy5jeS50cmlnZ2VyKCdsYXlvdXRzdGFydCcpO1xuXG4gIHZhciBnbSA9IGxheW91dC5uZXdHcmFwaE1hbmFnZXIoKTtcbiAgdGhpcy5nbSA9IGdtO1xuXG4gIHZhciBub2RlcyA9IHRoaXMub3B0aW9ucy5lbGVzLm5vZGVzKCk7XG4gIHZhciBlZGdlcyA9IHRoaXMub3B0aW9ucy5lbGVzLmVkZ2VzKCk7XG5cbiAgdGhpcy5yb290ID0gZ20uYWRkUm9vdCgpO1xuXG4gIGlmICghdGhpcy5vcHRpb25zLnRpbGUpIHtcbiAgICB0aGlzLnByb2Nlc3NDaGlsZHJlbkxpc3QodGhpcy5yb290LCB0aGlzLmdldFRvcE1vc3ROb2Rlcyhub2RlcyksIGxheW91dCk7XG4gIH1cbiAgZWxzZSB7XG4gICAgdGhpcy5wcmVMYXlvdXQoKTtcbiAgfVxuXG5cbiAgZm9yICh2YXIgaSA9IDA7IGkgPCBlZGdlcy5sZW5ndGg7IGkrKykge1xuICAgIHZhciBlZGdlID0gZWRnZXNbaV07XG4gICAgdmFyIHNvdXJjZU5vZGUgPSB0aGlzLmlkVG9MTm9kZVtlZGdlLmRhdGEoXCJzb3VyY2VcIildO1xuICAgIHZhciB0YXJnZXROb2RlID0gdGhpcy5pZFRvTE5vZGVbZWRnZS5kYXRhKFwidGFyZ2V0XCIpXTtcbiAgICB2YXIgZTEgPSBnbS5hZGQobGF5b3V0Lm5ld0VkZ2UoKSwgc291cmNlTm9kZSwgdGFyZ2V0Tm9kZSk7XG4gICAgZTEuaWQgPSBlZGdlLmlkKCk7XG4gIH1cbiAgXG4gICB2YXIgZ2V0UG9zaXRpb25zID0gZnVuY3Rpb24oZWxlLCBpKXtcbiAgICBpZih0eXBlb2YgZWxlID09PSBcIm51bWJlclwiKSB7XG4gICAgICBlbGUgPSBpO1xuICAgIH1cbiAgICB2YXIgdGhlSWQgPSBlbGUuZGF0YSgnaWQnKTtcbiAgICB2YXIgbE5vZGUgPSBzZWxmLmlkVG9MTm9kZVt0aGVJZF07XG5cbiAgICByZXR1cm4ge1xuICAgICAgeDogbE5vZGUuZ2V0UmVjdCgpLmdldENlbnRlclgoKSxcbiAgICAgIHk6IGxOb2RlLmdldFJlY3QoKS5nZXRDZW50ZXJZKClcbiAgICB9O1xuICB9O1xuICBcbiAgLypcbiAgICogUmVwb3NpdGlvbiBub2RlcyBpbiBpdGVyYXRpb25zIGFuaW1hdGVkbHlcbiAgICovXG4gIHZhciBpdGVyYXRlQW5pbWF0ZWQgPSBmdW5jdGlvbiAoKSB7XG4gICAgLy8gVGhpZ3MgdG8gcGVyZm9ybSBhZnRlciBub2RlcyBhcmUgcmVwb3NpdGlvbmVkIG9uIHNjcmVlblxuICAgIHZhciBhZnRlclJlcG9zaXRpb24gPSBmdW5jdGlvbigpIHtcbiAgICAgIGlmIChvcHRpb25zLmZpdCkge1xuICAgICAgICBvcHRpb25zLmN5LmZpdChvcHRpb25zLmVsZXMubm9kZXMoKSwgb3B0aW9ucy5wYWRkaW5nKTtcbiAgICAgIH1cblxuICAgICAgaWYgKCFyZWFkeSkge1xuICAgICAgICByZWFkeSA9IHRydWU7XG4gICAgICAgIHNlbGYuY3kub25lKCdsYXlvdXRyZWFkeScsIG9wdGlvbnMucmVhZHkpO1xuICAgICAgICBzZWxmLmN5LnRyaWdnZXIoe3R5cGU6ICdsYXlvdXRyZWFkeScsIGxheW91dDogc2VsZn0pO1xuICAgICAgfVxuICAgIH07XG4gICAgXG4gICAgdmFyIHRpY2tzUGVyRnJhbWUgPSBzZWxmLm9wdGlvbnMucmVmcmVzaDtcbiAgICB2YXIgaXNEb25lO1xuXG4gICAgZm9yKCB2YXIgaSA9IDA7IGkgPCB0aWNrc1BlckZyYW1lICYmICFpc0RvbmU7IGkrKyApe1xuICAgICAgaXNEb25lID0gc2VsZi5sYXlvdXQudGljaygpO1xuICAgIH1cbiAgICBcbiAgICAvLyBJZiBsYXlvdXQgaXMgZG9uZVxuICAgIGlmIChpc0RvbmUpIHtcbiAgICAgIGlmIChzZWxmLm9wdGlvbnMudGlsZSkge1xuICAgICAgICBzZWxmLnBvc3RMYXlvdXQoKTtcbiAgICAgIH1cbiAgICAgIHNlbGYub3B0aW9ucy5lbGVzLm5vZGVzKCkucG9zaXRpb25zKGdldFBvc2l0aW9ucyk7XG4gICAgICBcbiAgICAgIGFmdGVyUmVwb3NpdGlvbigpO1xuICAgICAgXG4gICAgICAvLyB0cmlnZ2VyIGxheW91dHN0b3Agd2hlbiB0aGUgbGF5b3V0IHN0b3BzIChlLmcuIGZpbmlzaGVzKVxuICAgICAgc2VsZi5jeS5vbmUoJ2xheW91dHN0b3AnLCBzZWxmLm9wdGlvbnMuc3RvcCk7XG4gICAgICBzZWxmLmN5LnRyaWdnZXIoJ2xheW91dHN0b3AnKTtcblxuICAgICAgaWYgKGZyYW1lSWQpIHtcbiAgICAgICAgY2FuY2VsQW5pbWF0aW9uRnJhbWUoZnJhbWVJZCk7XG4gICAgICB9XG4gICAgICBcbiAgICAgIHNlbGYub3B0aW9ucy5lbGVzLm5vZGVzKCkucmVtb3ZlU2NyYXRjaCgnY29zZUJpbGtlbnQnKTtcbiAgICAgIHJlYWR5ID0gZmFsc2U7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIFxuICAgIHZhciBhbmltYXRpb25EYXRhID0gc2VsZi5sYXlvdXQuZ2V0UG9zaXRpb25zRGF0YSgpOyAvLyBHZXQgcG9zaXRpb25zIG9mIGxheW91dCBub2RlcyBub3RlIHRoYXQgYWxsIG5vZGVzIG1heSBub3QgYmUgbGF5b3V0IG5vZGVzIGJlY2F1c2Ugb2YgdGlsaW5nXG4gICAgLy8gUG9zaXRpb24gbm9kZXMsIGZvciB0aGUgbm9kZXMgd2hvIGFyZSBub3QgcGFzc2VkIHRvIGxheW91dCBiZWNhdXNlIG9mIHRpbGluZyByZXR1cm4gdGhlIHBvc2l0aW9uIG9mIHRoZWlyIGR1bW15IGNvbXBvdW5kXG4gICAgb3B0aW9ucy5lbGVzLm5vZGVzKCkucG9zaXRpb25zKGZ1bmN0aW9uIChlbGUsIGkpIHtcbiAgICAgIGlmICh0eXBlb2YgZWxlID09PSBcIm51bWJlclwiKSB7XG4gICAgICAgIGVsZSA9IGk7XG4gICAgICB9XG4gICAgICBpZiAoZWxlLnNjcmF0Y2goJ2Nvc2VCaWxrZW50JykgJiYgZWxlLnNjcmF0Y2goJ2Nvc2VCaWxrZW50JykuZHVtbXlfcGFyZW50X2lkKSB7XG4gICAgICAgIHZhciBkdW1teVBhcmVudCA9IGVsZS5zY3JhdGNoKCdjb3NlQmlsa2VudCcpLmR1bW15X3BhcmVudF9pZDtcbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICB4OiBkdW1teVBhcmVudC54LFxuICAgICAgICAgIHk6IGR1bW15UGFyZW50LnlcbiAgICAgICAgfTtcbiAgICAgIH1cbiAgICAgIHZhciB0aGVJZCA9IGVsZS5kYXRhKCdpZCcpO1xuICAgICAgdmFyIHBOb2RlID0gYW5pbWF0aW9uRGF0YVt0aGVJZF07XG4gICAgICB2YXIgdGVtcCA9IGVsZTtcbiAgICAgIHdoaWxlIChwTm9kZSA9PSBudWxsKSB7XG4gICAgICAgIHRlbXAgPSB0ZW1wLnBhcmVudCgpWzBdO1xuICAgICAgICBwTm9kZSA9IGFuaW1hdGlvbkRhdGFbdGVtcC5pZCgpXTtcbiAgICAgICAgYW5pbWF0aW9uRGF0YVt0aGVJZF0gPSBwTm9kZTtcbiAgICAgIH1cbiAgICAgIHJldHVybiB7XG4gICAgICAgIHg6IHBOb2RlLngsXG4gICAgICAgIHk6IHBOb2RlLnlcbiAgICAgIH07XG4gICAgfSk7XG5cbiAgICBhZnRlclJlcG9zaXRpb24oKTtcblxuICAgIGZyYW1lSWQgPSByZXF1ZXN0QW5pbWF0aW9uRnJhbWUoaXRlcmF0ZUFuaW1hdGVkKTtcbiAgfTtcbiAgXG4gIC8qXG4gICogTGlzdGVuICdsYXlvdXRzdGFydGVkJyBldmVudCBhbmQgc3RhcnQgYW5pbWF0ZWQgaXRlcmF0aW9uIGlmIGFuaW1hdGUgb3B0aW9uIGlzICdkdXJpbmcnXG4gICovXG4gIGxheW91dC5hZGRMaXN0ZW5lcignbGF5b3V0c3RhcnRlZCcsIGZ1bmN0aW9uICgpIHtcbiAgICBpZiAoc2VsZi5vcHRpb25zLmFuaW1hdGUgPT09ICdkdXJpbmcnKSB7XG4gICAgICBmcmFtZUlkID0gcmVxdWVzdEFuaW1hdGlvbkZyYW1lKGl0ZXJhdGVBbmltYXRlZCk7XG4gICAgfVxuICB9KTtcbiAgXG4gIGxheW91dC5ydW5MYXlvdXQoKTsgLy8gUnVuIGNvc2UgbGF5b3V0XG4gIFxuICAvKlxuICAgKiBJZiBhbmltYXRlIG9wdGlvbiBpcyBub3QgJ2R1cmluZycgKCdlbmQnIG9yIGZhbHNlKSBwZXJmb3JtIHRoZXNlIGhlcmUgKElmIGl0IGlzICdkdXJpbmcnIHNpbWlsYXIgdGhpbmdzIGFyZSBhbHJlYWR5IHBlcmZvcm1lZClcbiAgICovXG4gIGlmKHRoaXMub3B0aW9ucy5hbmltYXRlICE9PSAnZHVyaW5nJyl7XG4gICAgc2V0VGltZW91dChmdW5jdGlvbigpIHtcbiAgICAgIGlmIChzZWxmLm9wdGlvbnMudGlsZSkge1xuICAgICAgICBzZWxmLnBvc3RMYXlvdXQoKTtcbiAgICAgIH1cbiAgICAgIHNlbGYub3B0aW9ucy5lbGVzLm5vZGVzKCkubm90KFwiOnBhcmVudFwiKS5sYXlvdXRQb3NpdGlvbnMoc2VsZiwgc2VsZi5vcHRpb25zLCBnZXRQb3NpdGlvbnMpOyAvLyBVc2UgbGF5b3V0IHBvc2l0aW9ucyB0byByZXBvc2l0aW9uIHRoZSBub2RlcyBpdCBjb25zaWRlcnMgdGhlIG9wdGlvbnMgcGFyYW1ldGVyXG4gICAgICBzZWxmLm9wdGlvbnMuZWxlcy5ub2RlcygpLnJlbW92ZVNjcmF0Y2goJ2Nvc2VCaWxrZW50Jyk7XG4gICAgICByZWFkeSA9IGZhbHNlO1xuICAgIH0sIDApO1xuICAgIFxuICB9XG5cbiAgcmV0dXJuIHRoaXM7IC8vIGNoYWluaW5nXG59O1xuXG4vL0dldCB0aGUgdG9wIG1vc3Qgb25lcyBvZiBhIGxpc3Qgb2Ygbm9kZXNcbl9Db1NFTGF5b3V0LnByb3RvdHlwZS5nZXRUb3BNb3N0Tm9kZXMgPSBmdW5jdGlvbihub2Rlcykge1xuICB2YXIgbm9kZXNNYXAgPSB7fTtcbiAgZm9yICh2YXIgaSA9IDA7IGkgPCBub2Rlcy5sZW5ndGg7IGkrKykge1xuICAgICAgbm9kZXNNYXBbbm9kZXNbaV0uaWQoKV0gPSB0cnVlO1xuICB9XG4gIHZhciByb290cyA9IG5vZGVzLmZpbHRlcihmdW5jdGlvbiAoZWxlLCBpKSB7XG4gICAgICBpZih0eXBlb2YgZWxlID09PSBcIm51bWJlclwiKSB7XG4gICAgICAgIGVsZSA9IGk7XG4gICAgICB9XG4gICAgICB2YXIgcGFyZW50ID0gZWxlLnBhcmVudCgpWzBdO1xuICAgICAgd2hpbGUocGFyZW50ICE9IG51bGwpe1xuICAgICAgICBpZihub2Rlc01hcFtwYXJlbnQuaWQoKV0pe1xuICAgICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgICAgfVxuICAgICAgICBwYXJlbnQgPSBwYXJlbnQucGFyZW50KClbMF07XG4gICAgICB9XG4gICAgICByZXR1cm4gdHJ1ZTtcbiAgfSk7XG5cbiAgcmV0dXJuIHJvb3RzO1xufTtcblxuX0NvU0VMYXlvdXQucHJvdG90eXBlLnByb2Nlc3NDaGlsZHJlbkxpc3QgPSBmdW5jdGlvbiAocGFyZW50LCBjaGlsZHJlbiwgbGF5b3V0KSB7XG4gIHZhciBzaXplID0gY2hpbGRyZW4ubGVuZ3RoO1xuICBmb3IgKHZhciBpID0gMDsgaSA8IHNpemU7IGkrKykge1xuICAgIHZhciB0aGVDaGlsZCA9IGNoaWxkcmVuW2ldO1xuICAgIHRoaXMub3B0aW9ucy5lbGVzLm5vZGVzKCkubGVuZ3RoO1xuICAgIHZhciBjaGlsZHJlbl9vZl9jaGlsZHJlbiA9IHRoZUNoaWxkLmNoaWxkcmVuKCk7XG4gICAgdmFyIHRoZU5vZGU7XG5cbiAgICBpZiAodGhlQ2hpbGQud2lkdGgoKSAhPSBudWxsXG4gICAgICAgICAgICAmJiB0aGVDaGlsZC5oZWlnaHQoKSAhPSBudWxsKSB7XG4gICAgICB0aGVOb2RlID0gcGFyZW50LmFkZChuZXcgQ29TRU5vZGUobGF5b3V0LmdyYXBoTWFuYWdlcixcbiAgICAgICAgICAgICAgbmV3IFBvaW50RCh0aGVDaGlsZC5wb3NpdGlvbigneCcpLCB0aGVDaGlsZC5wb3NpdGlvbigneScpKSxcbiAgICAgICAgICAgICAgbmV3IERpbWVuc2lvbkQocGFyc2VGbG9hdCh0aGVDaGlsZC53aWR0aCgpKSxcbiAgICAgICAgICAgICAgICAgICAgICBwYXJzZUZsb2F0KHRoZUNoaWxkLmhlaWdodCgpKSkpKTtcbiAgICB9XG4gICAgZWxzZSB7XG4gICAgICB0aGVOb2RlID0gcGFyZW50LmFkZChuZXcgQ29TRU5vZGUodGhpcy5ncmFwaE1hbmFnZXIpKTtcbiAgICB9XG4gICAgdGhlTm9kZS5pZCA9IHRoZUNoaWxkLmRhdGEoXCJpZFwiKTtcbiAgICB0aGlzLmlkVG9MTm9kZVt0aGVDaGlsZC5kYXRhKFwiaWRcIildID0gdGhlTm9kZTtcblxuICAgIGlmIChpc05hTih0aGVOb2RlLnJlY3QueCkpIHtcbiAgICAgIHRoZU5vZGUucmVjdC54ID0gMDtcbiAgICB9XG5cbiAgICBpZiAoaXNOYU4odGhlTm9kZS5yZWN0LnkpKSB7XG4gICAgICB0aGVOb2RlLnJlY3QueSA9IDA7XG4gICAgfVxuXG4gICAgaWYgKGNoaWxkcmVuX29mX2NoaWxkcmVuICE9IG51bGwgJiYgY2hpbGRyZW5fb2ZfY2hpbGRyZW4ubGVuZ3RoID4gMCkge1xuICAgICAgdmFyIHRoZU5ld0dyYXBoO1xuICAgICAgdGhlTmV3R3JhcGggPSBsYXlvdXQuZ2V0R3JhcGhNYW5hZ2VyKCkuYWRkKGxheW91dC5uZXdHcmFwaCgpLCB0aGVOb2RlKTtcbiAgICAgIHRoaXMucHJvY2Vzc0NoaWxkcmVuTGlzdCh0aGVOZXdHcmFwaCwgY2hpbGRyZW5fb2ZfY2hpbGRyZW4sIGxheW91dCk7XG4gICAgfVxuICB9XG59O1xuXG4vKipcbiAqIEBicmllZiA6IGNhbGxlZCBvbiBjb250aW51b3VzIGxheW91dHMgdG8gc3RvcCB0aGVtIGJlZm9yZSB0aGV5IGZpbmlzaFxuICovXG5fQ29TRUxheW91dC5wcm90b3R5cGUuc3RvcCA9IGZ1bmN0aW9uICgpIHtcbiAgdGhpcy5zdG9wcGVkID0gdHJ1ZTtcbiAgXG4gIHRoaXMudHJpZ2dlcignbGF5b3V0c3RvcCcpO1xuXG4gIHJldHVybiB0aGlzOyAvLyBjaGFpbmluZ1xufTtcblxubW9kdWxlLmV4cG9ydHMgPSBmdW5jdGlvbiBnZXQoY3l0b3NjYXBlKSB7XG4gIHJldHVybiBfQ29TRUxheW91dDtcbn07XG4iLCIndXNlIHN0cmljdCc7XG5cbi8vIHJlZ2lzdGVycyB0aGUgZXh0ZW5zaW9uIG9uIGEgY3l0b3NjYXBlIGxpYiByZWZcbnZhciBnZXRMYXlvdXQgPSByZXF1aXJlKCcuL0xheW91dCcpO1xuXG52YXIgcmVnaXN0ZXIgPSBmdW5jdGlvbiggY3l0b3NjYXBlICl7XG4gIHZhciBMYXlvdXQgPSBnZXRMYXlvdXQoIGN5dG9zY2FwZSApO1xuXG4gIGN5dG9zY2FwZSgnbGF5b3V0JywgJ2Nvc2UtYmlsa2VudCcsIExheW91dCk7XG59O1xuXG4vLyBhdXRvIHJlZyBmb3IgZ2xvYmFsc1xuaWYoIHR5cGVvZiBjeXRvc2NhcGUgIT09ICd1bmRlZmluZWQnICl7XG4gIHJlZ2lzdGVyKCBjeXRvc2NhcGUgKTtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSByZWdpc3RlcjtcbiJdfQ== diff --git a/site/site/public/js/cytoscape-fcose.js b/site/site/public/js/cytoscape-fcose.js new file mode 100644 index 0000000..9ad4f6e --- /dev/null +++ b/site/site/public/js/cytoscape-fcose.js @@ -0,0 +1,1549 @@ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(require("cose-base")); + else if(typeof define === 'function' && define.amd) + define(["cose-base"], factory); + else if(typeof exports === 'object') + exports["cytoscapeFcose"] = factory(require("cose-base")); + else + root["cytoscapeFcose"] = factory(root["coseBase"]); +})(this, function(__WEBPACK_EXTERNAL_MODULE__140__) { +return /******/ (() => { // webpackBootstrap +/******/ "use strict"; +/******/ var __webpack_modules__ = ({ + +/***/ 658: +/***/ ((module) => { + + + +// Simple, internal Object.assign() polyfill for options objects etc. + +module.exports = Object.assign != null ? Object.assign.bind(Object) : function (tgt) { + for (var _len = arguments.length, srcs = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + srcs[_key - 1] = arguments[_key]; + } + + srcs.forEach(function (src) { + Object.keys(src).forEach(function (k) { + return tgt[k] = src[k]; + }); + }); + + return tgt; +}; + +/***/ }), + +/***/ 548: +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + + + +var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); + +/* + * Auxiliary functions + */ + +var LinkedList = __webpack_require__(140).layoutBase.LinkedList; + +var auxiliary = {}; + +// get the top most nodes +auxiliary.getTopMostNodes = function (nodes) { + var nodesMap = {}; + for (var i = 0; i < nodes.length; i++) { + nodesMap[nodes[i].id()] = true; + } + var roots = nodes.filter(function (ele, i) { + if (typeof ele === "number") { + ele = i; + } + var parent = ele.parent()[0]; + while (parent != null) { + if (nodesMap[parent.id()]) { + return false; + } + parent = parent.parent()[0]; + } + return true; + }); + + return roots; +}; + +// find disconnected components and create dummy nodes that connect them +auxiliary.connectComponents = function (cy, eles, topMostNodes, dummyNodes) { + var queue = new LinkedList(); + var visited = new Set(); + var visitedTopMostNodes = []; + var currentNeighbor = void 0; + var minDegreeNode = void 0; + var minDegree = void 0; + + var isConnected = false; + var count = 1; + var nodesConnectedToDummy = []; + var components = []; + + var _loop = function _loop() { + var cmpt = cy.collection(); + components.push(cmpt); + + var currentNode = topMostNodes[0]; + var childrenOfCurrentNode = cy.collection(); + childrenOfCurrentNode.merge(currentNode).merge(currentNode.descendants().intersection(eles)); + visitedTopMostNodes.push(currentNode); + + childrenOfCurrentNode.forEach(function (node) { + queue.push(node); + visited.add(node); + cmpt.merge(node); + }); + + var _loop2 = function _loop2() { + currentNode = queue.shift(); + + // Traverse all neighbors of this node + var neighborNodes = cy.collection(); + currentNode.neighborhood().nodes().forEach(function (node) { + if (eles.intersection(currentNode.edgesWith(node)).length > 0) { + neighborNodes.merge(node); + } + }); + + for (var i = 0; i < neighborNodes.length; i++) { + var neighborNode = neighborNodes[i]; + currentNeighbor = topMostNodes.intersection(neighborNode.union(neighborNode.ancestors())); + if (currentNeighbor != null && !visited.has(currentNeighbor[0])) { + var childrenOfNeighbor = currentNeighbor.union(currentNeighbor.descendants()); + + childrenOfNeighbor.forEach(function (node) { + queue.push(node); + visited.add(node); + cmpt.merge(node); + if (topMostNodes.has(node)) { + visitedTopMostNodes.push(node); + } + }); + } + } + }; + + while (queue.length != 0) { + _loop2(); + } + + cmpt.forEach(function (node) { + eles.intersection(node.connectedEdges()).forEach(function (e) { + // connectedEdges() usually cached + if (cmpt.has(e.source()) && cmpt.has(e.target())) { + // has() is cheap + cmpt.merge(e); + } + }); + }); + + if (visitedTopMostNodes.length == topMostNodes.length) { + isConnected = true; + } + + if (!isConnected || isConnected && count > 1) { + minDegreeNode = visitedTopMostNodes[0]; + minDegree = minDegreeNode.connectedEdges().length; + visitedTopMostNodes.forEach(function (node) { + if (node.connectedEdges().length < minDegree) { + minDegree = node.connectedEdges().length; + minDegreeNode = node; + } + }); + nodesConnectedToDummy.push(minDegreeNode.id()); + // TO DO: Check efficiency of this part + var temp = cy.collection(); + temp.merge(visitedTopMostNodes[0]); + visitedTopMostNodes.forEach(function (node) { + temp.merge(node); + }); + visitedTopMostNodes = []; + topMostNodes = topMostNodes.difference(temp); + count++; + } + }; + + do { + _loop(); + } while (!isConnected); + + if (dummyNodes) { + if (nodesConnectedToDummy.length > 0) { + dummyNodes.set('dummy' + (dummyNodes.size + 1), nodesConnectedToDummy); + } + } + return components; +}; + +// relocates componentResult to originalCenter if there is no fixedNodeConstraint +auxiliary.relocateComponent = function (originalCenter, componentResult, options) { + if (!options.fixedNodeConstraint) { + var minXCoord = Number.POSITIVE_INFINITY; + var maxXCoord = Number.NEGATIVE_INFINITY; + var minYCoord = Number.POSITIVE_INFINITY; + var maxYCoord = Number.NEGATIVE_INFINITY; + if (options.quality == "draft") { + // calculate current bounding box + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = componentResult.nodeIndexes[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var _ref = _step.value; + + var _ref2 = _slicedToArray(_ref, 2); + + var key = _ref2[0]; + var value = _ref2[1]; + + var cyNode = options.cy.getElementById(key); + if (cyNode) { + var nodeBB = cyNode.boundingBox(); + var leftX = componentResult.xCoords[value] - nodeBB.w / 2; + var rightX = componentResult.xCoords[value] + nodeBB.w / 2; + var topY = componentResult.yCoords[value] - nodeBB.h / 2; + var bottomY = componentResult.yCoords[value] + nodeBB.h / 2; + + if (leftX < minXCoord) minXCoord = leftX; + if (rightX > maxXCoord) maxXCoord = rightX; + if (topY < minYCoord) minYCoord = topY; + if (bottomY > maxYCoord) maxYCoord = bottomY; + } + } + // find difference between current and original center + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } + } + + var diffOnX = originalCenter.x - (maxXCoord + minXCoord) / 2; + var diffOnY = originalCenter.y - (maxYCoord + minYCoord) / 2; + // move component to original center + componentResult.xCoords = componentResult.xCoords.map(function (x) { + return x + diffOnX; + }); + componentResult.yCoords = componentResult.yCoords.map(function (y) { + return y + diffOnY; + }); + } else { + // calculate current bounding box + Object.keys(componentResult).forEach(function (item) { + var node = componentResult[item]; + var leftX = node.getRect().x; + var rightX = node.getRect().x + node.getRect().width; + var topY = node.getRect().y; + var bottomY = node.getRect().y + node.getRect().height; + + if (leftX < minXCoord) minXCoord = leftX; + if (rightX > maxXCoord) maxXCoord = rightX; + if (topY < minYCoord) minYCoord = topY; + if (bottomY > maxYCoord) maxYCoord = bottomY; + }); + // find difference between current and original center + var _diffOnX = originalCenter.x - (maxXCoord + minXCoord) / 2; + var _diffOnY = originalCenter.y - (maxYCoord + minYCoord) / 2; + // move component to original center + Object.keys(componentResult).forEach(function (item) { + var node = componentResult[item]; + node.setCenter(node.getCenterX() + _diffOnX, node.getCenterY() + _diffOnY); + }); + } + } +}; + +auxiliary.calcBoundingBox = function (parentNode, xCoords, yCoords, nodeIndexes) { + // calculate bounds + var left = Number.MAX_SAFE_INTEGER; + var right = Number.MIN_SAFE_INTEGER; + var top = Number.MAX_SAFE_INTEGER; + var bottom = Number.MIN_SAFE_INTEGER; + var nodeLeft = void 0; + var nodeRight = void 0; + var nodeTop = void 0; + var nodeBottom = void 0; + + var nodes = parentNode.descendants().not(":parent"); + var s = nodes.length; + for (var i = 0; i < s; i++) { + var node = nodes[i]; + + nodeLeft = xCoords[nodeIndexes.get(node.id())] - node.width() / 2; + nodeRight = xCoords[nodeIndexes.get(node.id())] + node.width() / 2; + nodeTop = yCoords[nodeIndexes.get(node.id())] - node.height() / 2; + nodeBottom = yCoords[nodeIndexes.get(node.id())] + node.height() / 2; + + if (left > nodeLeft) { + left = nodeLeft; + } + + if (right < nodeRight) { + right = nodeRight; + } + + if (top > nodeTop) { + top = nodeTop; + } + + if (bottom < nodeBottom) { + bottom = nodeBottom; + } + } + + var boundingBox = {}; + boundingBox.topLeftX = left; + boundingBox.topLeftY = top; + boundingBox.width = right - left; + boundingBox.height = bottom - top; + return boundingBox; +}; + +// This function finds and returns parent nodes whose all children are hidden +auxiliary.calcParentsWithoutChildren = function (cy, eles) { + var parentsWithoutChildren = cy.collection(); + eles.nodes(':parent').forEach(function (parent) { + var check = false; + parent.children().forEach(function (child) { + if (child.css('display') != 'none') { + check = true; + } + }); + if (!check) { + parentsWithoutChildren.merge(parent); + } + }); + + return parentsWithoutChildren; +}; + +module.exports = auxiliary; + +/***/ }), + +/***/ 816: +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + + + +/** + The implementation of the postprocessing part that applies CoSE layout over the spectral layout +*/ + +var aux = __webpack_require__(548); +var CoSELayout = __webpack_require__(140).CoSELayout; +var CoSENode = __webpack_require__(140).CoSENode; +var PointD = __webpack_require__(140).layoutBase.PointD; +var DimensionD = __webpack_require__(140).layoutBase.DimensionD; +var LayoutConstants = __webpack_require__(140).layoutBase.LayoutConstants; +var FDLayoutConstants = __webpack_require__(140).layoutBase.FDLayoutConstants; +var CoSEConstants = __webpack_require__(140).CoSEConstants; + +// main function that cose layout is processed +var coseLayout = function coseLayout(options, spectralResult) { + + var cy = options.cy; + var eles = options.eles; + var nodes = eles.nodes(); + var edges = eles.edges(); + + var nodeIndexes = void 0; + var xCoords = void 0; + var yCoords = void 0; + var idToLNode = {}; + + if (options.randomize) { + nodeIndexes = spectralResult["nodeIndexes"]; + xCoords = spectralResult["xCoords"]; + yCoords = spectralResult["yCoords"]; + } + + var isFn = function isFn(fn) { + return typeof fn === 'function'; + }; + + var optFn = function optFn(opt, ele) { + if (isFn(opt)) { + return opt(ele); + } else { + return opt; + } + }; + + /**** Postprocessing functions ****/ + + var parentsWithoutChildren = aux.calcParentsWithoutChildren(cy, eles); + + // transfer cytoscape nodes to cose nodes + var processChildrenList = function processChildrenList(parent, children, layout, options) { + var size = children.length; + for (var i = 0; i < size; i++) { + var theChild = children[i]; + var children_of_children = null; + if (theChild.intersection(parentsWithoutChildren).length == 0) { + children_of_children = theChild.children(); + } + var theNode = void 0; + + var dimensions = theChild.layoutDimensions({ + nodeDimensionsIncludeLabels: options.nodeDimensionsIncludeLabels + }); + + if (theChild.outerWidth() != null && theChild.outerHeight() != null) { + if (options.randomize) { + if (!theChild.isParent()) { + theNode = parent.add(new CoSENode(layout.graphManager, new PointD(xCoords[nodeIndexes.get(theChild.id())] - dimensions.w / 2, yCoords[nodeIndexes.get(theChild.id())] - dimensions.h / 2), new DimensionD(parseFloat(dimensions.w), parseFloat(dimensions.h)))); + } else { + var parentInfo = aux.calcBoundingBox(theChild, xCoords, yCoords, nodeIndexes); + if (theChild.intersection(parentsWithoutChildren).length == 0) { + theNode = parent.add(new CoSENode(layout.graphManager, new PointD(parentInfo.topLeftX, parentInfo.topLeftY), new DimensionD(parentInfo.width, parentInfo.height))); + } else { + // for the parentsWithoutChildren + theNode = parent.add(new CoSENode(layout.graphManager, new PointD(parentInfo.topLeftX, parentInfo.topLeftY), new DimensionD(parseFloat(dimensions.w), parseFloat(dimensions.h)))); + } + } + } else { + theNode = parent.add(new CoSENode(layout.graphManager, new PointD(theChild.position('x') - dimensions.w / 2, theChild.position('y') - dimensions.h / 2), new DimensionD(parseFloat(dimensions.w), parseFloat(dimensions.h)))); + } + } else { + theNode = parent.add(new CoSENode(this.graphManager)); + } + // Attach id to the layout node and repulsion value + theNode.id = theChild.data("id"); + theNode.nodeRepulsion = optFn(options.nodeRepulsion, theChild); + // Attach the paddings of cy node to layout node + theNode.paddingLeft = parseInt(theChild.css('padding')); + theNode.paddingTop = parseInt(theChild.css('padding')); + theNode.paddingRight = parseInt(theChild.css('padding')); + theNode.paddingBottom = parseInt(theChild.css('padding')); + + //Attach the label properties to both compound and simple nodes if labels will be included in node dimensions + //These properties will be used while updating bounds of compounds during iterations or tiling + //and will be used for simple nodes while transferring final positions to cytoscape + if (options.nodeDimensionsIncludeLabels) { + theNode.labelWidth = theChild.boundingBox({ includeLabels: true, includeNodes: false, includeOverlays: false }).w; + theNode.labelHeight = theChild.boundingBox({ includeLabels: true, includeNodes: false, includeOverlays: false }).h; + theNode.labelPosVertical = theChild.css("text-valign"); + theNode.labelPosHorizontal = theChild.css("text-halign"); + } + + // Map the layout node + idToLNode[theChild.data("id")] = theNode; + + if (isNaN(theNode.rect.x)) { + theNode.rect.x = 0; + } + + if (isNaN(theNode.rect.y)) { + theNode.rect.y = 0; + } + + if (children_of_children != null && children_of_children.length > 0) { + var theNewGraph = void 0; + theNewGraph = layout.getGraphManager().add(layout.newGraph(), theNode); + processChildrenList(theNewGraph, children_of_children, layout, options); + } + } + }; + + // transfer cytoscape edges to cose edges + var processEdges = function processEdges(layout, gm, edges) { + var idealLengthTotal = 0; + var edgeCount = 0; + for (var i = 0; i < edges.length; i++) { + var edge = edges[i]; + var sourceNode = idToLNode[edge.data("source")]; + var targetNode = idToLNode[edge.data("target")]; + if (sourceNode && targetNode && sourceNode !== targetNode && sourceNode.getEdgesBetween(targetNode).length == 0) { + var e1 = gm.add(layout.newEdge(), sourceNode, targetNode); + e1.id = edge.id(); + e1.idealLength = optFn(options.idealEdgeLength, edge); + e1.edgeElasticity = optFn(options.edgeElasticity, edge); + idealLengthTotal += e1.idealLength; + edgeCount++; + } + } + // we need to update the ideal edge length constant with the avg. ideal length value after processing edges + // in case there is no edge, use other options + if (options.idealEdgeLength != null) { + if (edgeCount > 0) CoSEConstants.DEFAULT_EDGE_LENGTH = FDLayoutConstants.DEFAULT_EDGE_LENGTH = idealLengthTotal / edgeCount;else if (!isFn(options.idealEdgeLength)) // in case there is no edge, but option gives a value to use + CoSEConstants.DEFAULT_EDGE_LENGTH = FDLayoutConstants.DEFAULT_EDGE_LENGTH = options.idealEdgeLength;else // in case there is no edge and we cannot get a value from option (because it's a function) + CoSEConstants.DEFAULT_EDGE_LENGTH = FDLayoutConstants.DEFAULT_EDGE_LENGTH = 50; + // we need to update these constant values based on the ideal edge length constant + CoSEConstants.MIN_REPULSION_DIST = FDLayoutConstants.MIN_REPULSION_DIST = FDLayoutConstants.DEFAULT_EDGE_LENGTH / 10.0; + CoSEConstants.DEFAULT_RADIAL_SEPARATION = FDLayoutConstants.DEFAULT_EDGE_LENGTH; + } + }; + + // transfer cytoscape constraints to cose layout + var processConstraints = function processConstraints(layout, options) { + // get nodes to be fixed + if (options.fixedNodeConstraint) { + layout.constraints["fixedNodeConstraint"] = options.fixedNodeConstraint; + } + // get nodes to be aligned + if (options.alignmentConstraint) { + layout.constraints["alignmentConstraint"] = options.alignmentConstraint; + } + // get nodes to be relatively placed + if (options.relativePlacementConstraint) { + layout.constraints["relativePlacementConstraint"] = options.relativePlacementConstraint; + } + }; + + /**** Apply postprocessing ****/ + if (options.nestingFactor != null) CoSEConstants.PER_LEVEL_IDEAL_EDGE_LENGTH_FACTOR = FDLayoutConstants.PER_LEVEL_IDEAL_EDGE_LENGTH_FACTOR = options.nestingFactor; + if (options.gravity != null) CoSEConstants.DEFAULT_GRAVITY_STRENGTH = FDLayoutConstants.DEFAULT_GRAVITY_STRENGTH = options.gravity; + if (options.numIter != null) CoSEConstants.MAX_ITERATIONS = FDLayoutConstants.MAX_ITERATIONS = options.numIter; + if (options.gravityRange != null) CoSEConstants.DEFAULT_GRAVITY_RANGE_FACTOR = FDLayoutConstants.DEFAULT_GRAVITY_RANGE_FACTOR = options.gravityRange; + if (options.gravityCompound != null) CoSEConstants.DEFAULT_COMPOUND_GRAVITY_STRENGTH = FDLayoutConstants.DEFAULT_COMPOUND_GRAVITY_STRENGTH = options.gravityCompound; + if (options.gravityRangeCompound != null) CoSEConstants.DEFAULT_COMPOUND_GRAVITY_RANGE_FACTOR = FDLayoutConstants.DEFAULT_COMPOUND_GRAVITY_RANGE_FACTOR = options.gravityRangeCompound; + if (options.initialEnergyOnIncremental != null) CoSEConstants.DEFAULT_COOLING_FACTOR_INCREMENTAL = FDLayoutConstants.DEFAULT_COOLING_FACTOR_INCREMENTAL = options.initialEnergyOnIncremental; + + if (options.tilingCompareBy != null) CoSEConstants.TILING_COMPARE_BY = options.tilingCompareBy; + + if (options.quality == 'proof') LayoutConstants.QUALITY = 2;else LayoutConstants.QUALITY = 0; + + CoSEConstants.NODE_DIMENSIONS_INCLUDE_LABELS = FDLayoutConstants.NODE_DIMENSIONS_INCLUDE_LABELS = LayoutConstants.NODE_DIMENSIONS_INCLUDE_LABELS = options.nodeDimensionsIncludeLabels; + CoSEConstants.DEFAULT_INCREMENTAL = FDLayoutConstants.DEFAULT_INCREMENTAL = LayoutConstants.DEFAULT_INCREMENTAL = !options.randomize; + CoSEConstants.ANIMATE = FDLayoutConstants.ANIMATE = LayoutConstants.ANIMATE = options.animate; + CoSEConstants.TILE = options.tile; + CoSEConstants.TILING_PADDING_VERTICAL = typeof options.tilingPaddingVertical === 'function' ? options.tilingPaddingVertical.call() : options.tilingPaddingVertical; + CoSEConstants.TILING_PADDING_HORIZONTAL = typeof options.tilingPaddingHorizontal === 'function' ? options.tilingPaddingHorizontal.call() : options.tilingPaddingHorizontal; + + CoSEConstants.DEFAULT_INCREMENTAL = FDLayoutConstants.DEFAULT_INCREMENTAL = LayoutConstants.DEFAULT_INCREMENTAL = true; + CoSEConstants.PURE_INCREMENTAL = !options.randomize; + LayoutConstants.DEFAULT_UNIFORM_LEAF_NODE_SIZES = options.uniformNodeDimensions; + + // This part is for debug/demo purpose + if (options.step == "transformed") { + CoSEConstants.TRANSFORM_ON_CONSTRAINT_HANDLING = true; + CoSEConstants.ENFORCE_CONSTRAINTS = false; + CoSEConstants.APPLY_LAYOUT = false; + } + if (options.step == "enforced") { + CoSEConstants.TRANSFORM_ON_CONSTRAINT_HANDLING = false; + CoSEConstants.ENFORCE_CONSTRAINTS = true; + CoSEConstants.APPLY_LAYOUT = false; + } + if (options.step == "cose") { + CoSEConstants.TRANSFORM_ON_CONSTRAINT_HANDLING = false; + CoSEConstants.ENFORCE_CONSTRAINTS = false; + CoSEConstants.APPLY_LAYOUT = true; + } + if (options.step == "all") { + if (options.randomize) CoSEConstants.TRANSFORM_ON_CONSTRAINT_HANDLING = true;else CoSEConstants.TRANSFORM_ON_CONSTRAINT_HANDLING = false; + CoSEConstants.ENFORCE_CONSTRAINTS = true; + CoSEConstants.APPLY_LAYOUT = true; + } + + if (options.fixedNodeConstraint || options.alignmentConstraint || options.relativePlacementConstraint) { + CoSEConstants.TREE_REDUCTION_ON_INCREMENTAL = false; + } else { + CoSEConstants.TREE_REDUCTION_ON_INCREMENTAL = true; + } + + var coseLayout = new CoSELayout(); + var gm = coseLayout.newGraphManager(); + + processChildrenList(gm.addRoot(), aux.getTopMostNodes(nodes), coseLayout, options); + processEdges(coseLayout, gm, edges); + processConstraints(coseLayout, options); + + coseLayout.runLayout(); + + return idToLNode; +}; + +module.exports = { coseLayout: coseLayout }; + +/***/ }), + +/***/ 212: +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + + + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +/** + The implementation of the fcose layout algorithm +*/ + +var assign = __webpack_require__(658); +var aux = __webpack_require__(548); + +var _require = __webpack_require__(657), + spectralLayout = _require.spectralLayout; + +var _require2 = __webpack_require__(816), + coseLayout = _require2.coseLayout; + +var defaults = Object.freeze({ + + // 'draft', 'default' or 'proof' + // - 'draft' only applies spectral layout + // - 'default' improves the quality with subsequent CoSE layout (fast cooling rate) + // - 'proof' improves the quality with subsequent CoSE layout (slow cooling rate) + quality: "default", + // Use random node positions at beginning of layout + // if this is set to false, then quality option must be "proof" + randomize: true, + // Whether or not to animate the layout + animate: true, + // Duration of animation in ms, if enabled + animationDuration: 1000, + // Easing of animation, if enabled + animationEasing: undefined, + // Fit the viewport to the repositioned nodes + fit: true, + // Padding around layout + padding: 30, + // Whether to include labels in node dimensions. Valid in "proof" quality + nodeDimensionsIncludeLabels: false, + // Whether or not simple nodes (non-compound nodes) are of uniform dimensions + uniformNodeDimensions: false, + // Whether to pack disconnected components - valid only if randomize: true + packComponents: true, + // Layout step - all, transformed, enforced, cose - for debug purpose only + step: "all", + + /* spectral layout options */ + + // False for random, true for greedy + samplingType: true, + // Sample size to construct distance matrix + sampleSize: 25, + // Separation amount between nodes + nodeSeparation: 75, + // Power iteration tolerance + piTol: 0.0000001, + + /* CoSE layout options */ + + // Node repulsion (non overlapping) multiplier + nodeRepulsion: function nodeRepulsion(node) { + return 4500; + }, + // Ideal edge (non nested) length + idealEdgeLength: function idealEdgeLength(edge) { + return 50; + }, + // Divisor to compute edge forces + edgeElasticity: function edgeElasticity(edge) { + return 0.45; + }, + // Nesting factor (multiplier) to compute ideal edge length for nested edges + nestingFactor: 0.1, + // Gravity force (constant) + gravity: 0.25, + // Maximum number of iterations to perform + numIter: 2500, + // For enabling tiling + tile: true, + // The function that specifies the criteria for comparing nodes while sorting them during tiling operation. + // Takes the node id as a parameter and the default tiling operation is perfomed when this option is not set. + tilingCompareBy: undefined, + // Represents the amount of the vertical space to put between the zero degree members during the tiling operation(can also be a function) + tilingPaddingVertical: 10, + // Represents the amount of the horizontal space to put between the zero degree members during the tiling operation(can also be a function) + tilingPaddingHorizontal: 10, + // Gravity range (constant) for compounds + gravityRangeCompound: 1.5, + // Gravity force (constant) for compounds + gravityCompound: 1.0, + // Gravity range (constant) + gravityRange: 3.8, + // Initial cooling factor for incremental layout + initialEnergyOnIncremental: 0.3, + + /* constraint options */ + + // Fix required nodes to predefined positions + // [{nodeId: 'n1', position: {x: 100, y: 200}, {...}] + fixedNodeConstraint: undefined, + // Align required nodes in vertical/horizontal direction + // {vertical: [['n1', 'n2')], ['n3', 'n4']], horizontal: ['n2', 'n4']} + alignmentConstraint: undefined, + // Place two nodes relatively in vertical/horizontal direction + // [{top: 'n1', bottom: 'n2', gap: 100}, {left: 'n3', right: 'n4', gap: 75}] + relativePlacementConstraint: undefined, + + /* layout event callbacks */ + ready: function ready() {}, // on layoutready + stop: function stop() {} // on layoutstop +}); + +var Layout = function () { + function Layout(options) { + _classCallCheck(this, Layout); + + this.options = assign({}, defaults, options); + } + + _createClass(Layout, [{ + key: 'run', + value: function run() { + var layout = this; + var options = this.options; + var cy = options.cy; + var eles = options.eles; + + var spectralResult = []; + var xCoords = void 0; + var yCoords = void 0; + var coseResult = []; + var components = void 0; + var componentCenters = []; + + // basic validity check for constraint inputs + if (options.fixedNodeConstraint && (!Array.isArray(options.fixedNodeConstraint) || options.fixedNodeConstraint.length == 0)) { + options.fixedNodeConstraint = undefined; + } + + if (options.alignmentConstraint) { + if (options.alignmentConstraint.vertical && (!Array.isArray(options.alignmentConstraint.vertical) || options.alignmentConstraint.vertical.length == 0)) { + options.alignmentConstraint.vertical = undefined; + } + if (options.alignmentConstraint.horizontal && (!Array.isArray(options.alignmentConstraint.horizontal) || options.alignmentConstraint.horizontal.length == 0)) { + options.alignmentConstraint.horizontal = undefined; + } + } + + if (options.relativePlacementConstraint && (!Array.isArray(options.relativePlacementConstraint) || options.relativePlacementConstraint.length == 0)) { + options.relativePlacementConstraint = undefined; + } + + // if any constraint exists, set some options + var constraintExist = options.fixedNodeConstraint || options.alignmentConstraint || options.relativePlacementConstraint; + if (constraintExist) { + // constraints work with these options + options.tile = false; + options.packComponents = false; + } + + // decide component packing is enabled or not + var layUtil = void 0; + var packingEnabled = false; + if (cy.layoutUtilities && options.packComponents) { + layUtil = cy.layoutUtilities("get"); + if (!layUtil) layUtil = cy.layoutUtilities(); + packingEnabled = true; + } + + if (eles.nodes().length > 0) { + // if packing is not enabled, perform layout on the whole graph + if (!packingEnabled) { + // store component center + var boundingBox = options.eles.boundingBox(); + componentCenters.push({ x: boundingBox.x1 + boundingBox.w / 2, y: boundingBox.y1 + boundingBox.h / 2 }); + // apply spectral layout + if (options.randomize) { + var result = spectralLayout(options); + spectralResult.push(result); + } + // apply cose layout as postprocessing + if (options.quality == "default" || options.quality == "proof") { + coseResult.push(coseLayout(options, spectralResult[0])); + aux.relocateComponent(componentCenters[0], coseResult[0], options); // relocate center to original position + } else { + aux.relocateComponent(componentCenters[0], spectralResult[0], options); // relocate center to original position + } + } else { + // packing is enabled + var topMostNodes = aux.getTopMostNodes(options.eles.nodes()); + components = aux.connectComponents(cy, options.eles, topMostNodes); + // store component centers + components.forEach(function (component) { + var boundingBox = component.boundingBox(); + componentCenters.push({ x: boundingBox.x1 + boundingBox.w / 2, y: boundingBox.y1 + boundingBox.h / 2 }); + }); + + //send each component to spectral layout if randomized + if (options.randomize) { + components.forEach(function (component) { + options.eles = component; + spectralResult.push(spectralLayout(options)); + }); + } + + if (options.quality == "default" || options.quality == "proof") { + var toBeTiledNodes = cy.collection(); + if (options.tile) { + // behave nodes to be tiled as one component + var nodeIndexes = new Map(); + var _xCoords = []; + var _yCoords = []; + var count = 0; + var tempSpectralResult = { nodeIndexes: nodeIndexes, xCoords: _xCoords, yCoords: _yCoords }; + var indexesToBeDeleted = []; + components.forEach(function (component, index) { + if (component.edges().length == 0) { + component.nodes().forEach(function (node, i) { + toBeTiledNodes.merge(component.nodes()[i]); + if (!node.isParent()) { + tempSpectralResult.nodeIndexes.set(component.nodes()[i].id(), count++); + tempSpectralResult.xCoords.push(component.nodes()[0].position().x); + tempSpectralResult.yCoords.push(component.nodes()[0].position().y); + } + }); + indexesToBeDeleted.push(index); + } + }); + if (toBeTiledNodes.length > 1) { + var _boundingBox = toBeTiledNodes.boundingBox(); + componentCenters.push({ x: _boundingBox.x1 + _boundingBox.w / 2, y: _boundingBox.y1 + _boundingBox.h / 2 }); + components.push(toBeTiledNodes); + spectralResult.push(tempSpectralResult); + for (var i = indexesToBeDeleted.length - 1; i >= 0; i--) { + components.splice(indexesToBeDeleted[i], 1); + spectralResult.splice(indexesToBeDeleted[i], 1); + componentCenters.splice(indexesToBeDeleted[i], 1); + }; + } + } + components.forEach(function (component, index) { + // send each component to cose layout + options.eles = component; + coseResult.push(coseLayout(options, spectralResult[index])); + aux.relocateComponent(componentCenters[index], coseResult[index], options); // relocate center to original position + }); + } else { + components.forEach(function (component, index) { + aux.relocateComponent(componentCenters[index], spectralResult[index], options); // relocate center to original position + }); + } + + // packing + var componentsEvaluated = new Set(); + if (components.length > 1) { + var subgraphs = []; + var hiddenEles = eles.filter(function (ele) { + return ele.css('display') == 'none'; + }); + components.forEach(function (component, index) { + var nodeIndexes = void 0; + if (options.quality == "draft") { + nodeIndexes = spectralResult[index].nodeIndexes; + } + + if (component.nodes().not(hiddenEles).length > 0) { + var subgraph = {}; + subgraph.edges = []; + subgraph.nodes = []; + var nodeIndex = void 0; + component.nodes().not(hiddenEles).forEach(function (node) { + if (options.quality == "draft") { + if (!node.isParent()) { + nodeIndex = nodeIndexes.get(node.id()); + subgraph.nodes.push({ x: spectralResult[index].xCoords[nodeIndex] - node.boundingbox().w / 2, y: spectralResult[index].yCoords[nodeIndex] - node.boundingbox().h / 2, width: node.boundingbox().w, height: node.boundingbox().h }); + } else { + var parentInfo = aux.calcBoundingBox(node, spectralResult[index].xCoords, spectralResult[index].yCoords, nodeIndexes); + subgraph.nodes.push({ x: parentInfo.topLeftX, y: parentInfo.topLeftY, width: parentInfo.width, height: parentInfo.height }); + } + } else { + if (coseResult[index][node.id()]) { + subgraph.nodes.push({ x: coseResult[index][node.id()].getLeft(), y: coseResult[index][node.id()].getTop(), width: coseResult[index][node.id()].getWidth(), height: coseResult[index][node.id()].getHeight() }); + } + } + }); + component.edges().forEach(function (edge) { + var source = edge.source(); + var target = edge.target(); + if (source.css("display") != "none" && target.css("display") != "none") { + if (options.quality == "draft") { + var sourceNodeIndex = nodeIndexes.get(source.id()); + var targetNodeIndex = nodeIndexes.get(target.id()); + var sourceCenter = []; + var targetCenter = []; + if (source.isParent()) { + var parentInfo = aux.calcBoundingBox(source, spectralResult[index].xCoords, spectralResult[index].yCoords, nodeIndexes); + sourceCenter.push(parentInfo.topLeftX + parentInfo.width / 2); + sourceCenter.push(parentInfo.topLeftY + parentInfo.height / 2); + } else { + sourceCenter.push(spectralResult[index].xCoords[sourceNodeIndex]); + sourceCenter.push(spectralResult[index].yCoords[sourceNodeIndex]); + } + if (target.isParent()) { + var _parentInfo = aux.calcBoundingBox(target, spectralResult[index].xCoords, spectralResult[index].yCoords, nodeIndexes); + targetCenter.push(_parentInfo.topLeftX + _parentInfo.width / 2); + targetCenter.push(_parentInfo.topLeftY + _parentInfo.height / 2); + } else { + targetCenter.push(spectralResult[index].xCoords[targetNodeIndex]); + targetCenter.push(spectralResult[index].yCoords[targetNodeIndex]); + } + subgraph.edges.push({ startX: sourceCenter[0], startY: sourceCenter[1], endX: targetCenter[0], endY: targetCenter[1] }); + } else { + if (coseResult[index][source.id()] && coseResult[index][target.id()]) { + subgraph.edges.push({ startX: coseResult[index][source.id()].getCenterX(), startY: coseResult[index][source.id()].getCenterY(), endX: coseResult[index][target.id()].getCenterX(), endY: coseResult[index][target.id()].getCenterY() }); + } + } + } + }); + if (subgraph.nodes.length > 0) { + subgraphs.push(subgraph); + componentsEvaluated.add(index); + } + } + }); + var shiftResult = layUtil.packComponents(subgraphs, options.randomize).shifts; + if (options.quality == "draft") { + spectralResult.forEach(function (result, index) { + var newXCoords = result.xCoords.map(function (x) { + return x + shiftResult[index].dx; + }); + var newYCoords = result.yCoords.map(function (y) { + return y + shiftResult[index].dy; + }); + result.xCoords = newXCoords; + result.yCoords = newYCoords; + }); + } else { + var _count = 0; + componentsEvaluated.forEach(function (index) { + Object.keys(coseResult[index]).forEach(function (item) { + var nodeRectangle = coseResult[index][item]; + nodeRectangle.setCenter(nodeRectangle.getCenterX() + shiftResult[_count].dx, nodeRectangle.getCenterY() + shiftResult[_count].dy); + }); + _count++; + }); + } + } + } + } + + // get each element's calculated position + var getPositions = function getPositions(ele, i) { + if (options.quality == "default" || options.quality == "proof") { + if (typeof ele === "number") { + ele = i; + } + var pos = void 0; + var node = void 0; + var theId = ele.data('id'); + coseResult.forEach(function (result) { + if (theId in result) { + pos = { x: result[theId].getRect().getCenterX(), y: result[theId].getRect().getCenterY() }; + node = result[theId]; + } + }); + if (options.nodeDimensionsIncludeLabels) { + if (node.labelWidth) { + if (node.labelPosHorizontal == "left") { + pos.x += node.labelWidth / 2; + } else if (node.labelPosHorizontal == "right") { + pos.x -= node.labelWidth / 2; + } + } + if (node.labelHeight) { + if (node.labelPosVertical == "top") { + pos.y += node.labelHeight / 2; + } else if (node.labelPosVertical == "bottom") { + pos.y -= node.labelHeight / 2; + } + } + } + if (pos == undefined) pos = { x: ele.position("x"), y: ele.position("y") }; + return { + x: pos.x, + y: pos.y + }; + } else { + var _pos = void 0; + spectralResult.forEach(function (result) { + var index = result.nodeIndexes.get(ele.id()); + if (index != undefined) { + _pos = { x: result.xCoords[index], y: result.yCoords[index] }; + } + }); + if (_pos == undefined) _pos = { x: ele.position("x"), y: ele.position("y") }; + return { + x: _pos.x, + y: _pos.y + }; + } + }; + + // quality = "draft" and randomize = false are contradictive so in that case positions don't change + if (options.quality == "default" || options.quality == "proof" || options.randomize) { + // transfer calculated positions to nodes (positions of only simple nodes are evaluated, compounds are positioned automatically) + var parentsWithoutChildren = aux.calcParentsWithoutChildren(cy, eles); + var _hiddenEles = eles.filter(function (ele) { + return ele.css('display') == 'none'; + }); + options.eles = eles.not(_hiddenEles); + + eles.nodes().not(":parent").not(_hiddenEles).layoutPositions(layout, options, getPositions); + + if (parentsWithoutChildren.length > 0) { + parentsWithoutChildren.forEach(function (ele) { + ele.position(getPositions(ele)); + }); + } + } else { + console.log("If randomize option is set to false, then quality option must be 'default' or 'proof'."); + } + } + }]); + + return Layout; +}(); + +module.exports = Layout; + +/***/ }), + +/***/ 657: +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + + + +/** + The implementation of the spectral layout that is the first part of the fcose layout algorithm +*/ + +var aux = __webpack_require__(548); +var Matrix = __webpack_require__(140).layoutBase.Matrix; +var SVD = __webpack_require__(140).layoutBase.SVD; + +// main function that spectral layout is processed +var spectralLayout = function spectralLayout(options) { + + var cy = options.cy; + var eles = options.eles; + var nodes = eles.nodes(); + var parentNodes = eles.nodes(":parent"); + + var dummyNodes = new Map(); // map to keep dummy nodes and their neighbors + var nodeIndexes = new Map(); // map to keep indexes to nodes + var parentChildMap = new Map(); // mapping btw. compound and its representative node + var allNodesNeighborhood = []; // array to keep neighborhood of all nodes + var xCoords = []; + var yCoords = []; + + var samplesColumn = []; // sampled vertices + var minDistancesColumn = []; + var C = []; // column sampling matrix + var PHI = []; // intersection of column and row sampling matrices + var INV = []; // inverse of PHI + + var firstSample = void 0; // the first sampled node + var nodeSize = void 0; + + var infinity = 100000000; + var small = 0.000000001; + + var piTol = options.piTol; + var samplingType = options.samplingType; // false for random, true for greedy + var nodeSeparation = options.nodeSeparation; + var sampleSize = void 0; + + /**** Spectral-preprocessing functions ****/ + + /**** Spectral layout functions ****/ + + // determine which columns to be sampled + var randomSampleCR = function randomSampleCR() { + var sample = 0; + var count = 0; + var flag = false; + + while (count < sampleSize) { + sample = Math.floor(Math.random() * nodeSize); + + flag = false; + for (var i = 0; i < count; i++) { + if (samplesColumn[i] == sample) { + flag = true; + break; + } + } + + if (!flag) { + samplesColumn[count] = sample; + count++; + } else { + continue; + } + } + }; + + // takes the index of the node(pivot) to initiate BFS as a parameter + var BFS = function BFS(pivot, index, samplingMethod) { + var path = []; // the front of the path + var front = 0; // the back of the path + var back = 0; + var current = 0; + var temp = void 0; + var distance = []; + + var max_dist = 0; // the furthest node to be returned + var max_ind = 1; + + for (var i = 0; i < nodeSize; i++) { + distance[i] = infinity; + } + + path[back] = pivot; + distance[pivot] = 0; + + while (back >= front) { + current = path[front++]; + var neighbors = allNodesNeighborhood[current]; + for (var _i = 0; _i < neighbors.length; _i++) { + temp = nodeIndexes.get(neighbors[_i]); + if (distance[temp] == infinity) { + distance[temp] = distance[current] + 1; + path[++back] = temp; + } + } + C[current][index] = distance[current] * nodeSeparation; + } + + if (samplingMethod) { + for (var _i2 = 0; _i2 < nodeSize; _i2++) { + if (C[_i2][index] < minDistancesColumn[_i2]) minDistancesColumn[_i2] = C[_i2][index]; + } + + for (var _i3 = 0; _i3 < nodeSize; _i3++) { + if (minDistancesColumn[_i3] > max_dist) { + max_dist = minDistancesColumn[_i3]; + max_ind = _i3; + } + } + } + return max_ind; + }; + + // apply BFS to all nodes or selected samples + var allBFS = function allBFS(samplingMethod) { + + var sample = void 0; + + if (!samplingMethod) { + randomSampleCR(); + + // call BFS + for (var i = 0; i < sampleSize; i++) { + BFS(samplesColumn[i], i, samplingMethod, false); + } + } else { + sample = Math.floor(Math.random() * nodeSize); + firstSample = sample; + + for (var _i4 = 0; _i4 < nodeSize; _i4++) { + minDistancesColumn[_i4] = infinity; + } + + for (var _i5 = 0; _i5 < sampleSize; _i5++) { + samplesColumn[_i5] = sample; + sample = BFS(sample, _i5, samplingMethod); + } + } + + // form the squared distances for C + for (var _i6 = 0; _i6 < nodeSize; _i6++) { + for (var j = 0; j < sampleSize; j++) { + C[_i6][j] *= C[_i6][j]; + } + } + + // form PHI + for (var _i7 = 0; _i7 < sampleSize; _i7++) { + PHI[_i7] = []; + } + + for (var _i8 = 0; _i8 < sampleSize; _i8++) { + for (var _j = 0; _j < sampleSize; _j++) { + PHI[_i8][_j] = C[samplesColumn[_j]][_i8]; + } + } + }; + + // perform the SVD algorithm and apply a regularization step + var sample = function sample() { + + var SVDResult = SVD.svd(PHI); + + var a_q = SVDResult.S; + var a_u = SVDResult.U; + var a_v = SVDResult.V; + + var max_s = a_q[0] * a_q[0] * a_q[0]; + + var a_Sig = []; + + // regularization + for (var i = 0; i < sampleSize; i++) { + a_Sig[i] = []; + for (var j = 0; j < sampleSize; j++) { + a_Sig[i][j] = 0; + if (i == j) { + a_Sig[i][j] = a_q[i] / (a_q[i] * a_q[i] + max_s / (a_q[i] * a_q[i])); + } + } + } + + INV = Matrix.multMat(Matrix.multMat(a_v, a_Sig), Matrix.transpose(a_u)); + }; + + // calculate final coordinates + var powerIteration = function powerIteration() { + // two largest eigenvalues + var theta1 = void 0; + var theta2 = void 0; + + // initial guesses for eigenvectors + var Y1 = []; + var Y2 = []; + + var V1 = []; + var V2 = []; + + for (var i = 0; i < nodeSize; i++) { + Y1[i] = Math.random(); + Y2[i] = Math.random(); + } + + Y1 = Matrix.normalize(Y1); + Y2 = Matrix.normalize(Y2); + + var count = 0; + // to keep track of the improvement ratio in power iteration + var current = small; + var previous = small; + + var temp = void 0; + + while (true) { + count++; + + for (var _i9 = 0; _i9 < nodeSize; _i9++) { + V1[_i9] = Y1[_i9]; + } + + Y1 = Matrix.multGamma(Matrix.multL(Matrix.multGamma(V1), C, INV)); + theta1 = Matrix.dotProduct(V1, Y1); + Y1 = Matrix.normalize(Y1); + + current = Matrix.dotProduct(V1, Y1); + + temp = Math.abs(current / previous); + + if (temp <= 1 + piTol && temp >= 1) { + break; + } + + previous = current; + } + + for (var _i10 = 0; _i10 < nodeSize; _i10++) { + V1[_i10] = Y1[_i10]; + } + + count = 0; + previous = small; + while (true) { + count++; + + for (var _i11 = 0; _i11 < nodeSize; _i11++) { + V2[_i11] = Y2[_i11]; + } + + V2 = Matrix.minusOp(V2, Matrix.multCons(V1, Matrix.dotProduct(V1, V2))); + Y2 = Matrix.multGamma(Matrix.multL(Matrix.multGamma(V2), C, INV)); + theta2 = Matrix.dotProduct(V2, Y2); + Y2 = Matrix.normalize(Y2); + + current = Matrix.dotProduct(V2, Y2); + + temp = Math.abs(current / previous); + + if (temp <= 1 + piTol && temp >= 1) { + break; + } + + previous = current; + } + + for (var _i12 = 0; _i12 < nodeSize; _i12++) { + V2[_i12] = Y2[_i12]; + } + + // theta1 now contains dominant eigenvalue + // theta2 now contains the second-largest eigenvalue + // V1 now contains theta1's eigenvector + // V2 now contains theta2's eigenvector + + //populate the two vectors + xCoords = Matrix.multCons(V1, Math.sqrt(Math.abs(theta1))); + yCoords = Matrix.multCons(V2, Math.sqrt(Math.abs(theta2))); + }; + + /**** Preparation for spectral layout (Preprocessing) ****/ + + // connect disconnected components (first top level, then inside of each compound node) + aux.connectComponents(cy, eles, aux.getTopMostNodes(nodes), dummyNodes); + + parentNodes.forEach(function (ele) { + aux.connectComponents(cy, eles, aux.getTopMostNodes(ele.descendants().intersection(eles)), dummyNodes); + }); + + // assign indexes to nodes (first real, then dummy nodes) + var index = 0; + for (var i = 0; i < nodes.length; i++) { + if (!nodes[i].isParent()) { + nodeIndexes.set(nodes[i].id(), index++); + } + } + + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = dummyNodes.keys()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var key = _step.value; + + nodeIndexes.set(key, index++); + } + + // instantiate the neighborhood matrix + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } + } + + for (var _i13 = 0; _i13 < nodeIndexes.size; _i13++) { + allNodesNeighborhood[_i13] = []; + } + + // form a parent-child map to keep representative node of each compound node + parentNodes.forEach(function (ele) { + var children = ele.children().intersection(eles); + + // let random = 0; + while (children.nodes(":childless").length == 0) { + // random = Math.floor(Math.random() * children.nodes().length); // if all children are compound then proceed randomly + children = children.nodes()[0].children().intersection(eles); + } + // select the representative node - we can apply different methods here + // random = Math.floor(Math.random() * children.nodes(":childless").length); + var index = 0; + var min = children.nodes(":childless")[0].connectedEdges().length; + children.nodes(":childless").forEach(function (ele2, i) { + if (ele2.connectedEdges().length < min) { + min = ele2.connectedEdges().length; + index = i; + } + }); + parentChildMap.set(ele.id(), children.nodes(":childless")[index].id()); + }); + + // add neighborhood relations (first real, then dummy nodes) + nodes.forEach(function (ele) { + var eleIndex = void 0; + + if (ele.isParent()) eleIndex = nodeIndexes.get(parentChildMap.get(ele.id()));else eleIndex = nodeIndexes.get(ele.id()); + + ele.neighborhood().nodes().forEach(function (node) { + if (eles.intersection(ele.edgesWith(node)).length > 0) { + if (node.isParent()) allNodesNeighborhood[eleIndex].push(parentChildMap.get(node.id()));else allNodesNeighborhood[eleIndex].push(node.id()); + } + }); + }); + + var _loop = function _loop(_key) { + var eleIndex = nodeIndexes.get(_key); + var disconnectedId = void 0; + dummyNodes.get(_key).forEach(function (id) { + if (cy.getElementById(id).isParent()) disconnectedId = parentChildMap.get(id);else disconnectedId = id; + + allNodesNeighborhood[eleIndex].push(disconnectedId); + allNodesNeighborhood[nodeIndexes.get(disconnectedId)].push(_key); + }); + }; + + var _iteratorNormalCompletion2 = true; + var _didIteratorError2 = false; + var _iteratorError2 = undefined; + + try { + for (var _iterator2 = dummyNodes.keys()[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { + var _key = _step2.value; + + _loop(_key); + } + + // nodeSize now only considers the size of transformed graph + } catch (err) { + _didIteratorError2 = true; + _iteratorError2 = err; + } finally { + try { + if (!_iteratorNormalCompletion2 && _iterator2.return) { + _iterator2.return(); + } + } finally { + if (_didIteratorError2) { + throw _iteratorError2; + } + } + } + + nodeSize = nodeIndexes.size; + + var spectralResult = void 0; + + // If number of nodes in transformed graph is 1 or 2, either SVD or powerIteration causes problem + // So skip spectral and layout the graph with cose + if (nodeSize > 2) { + // if # of nodes in transformed graph is smaller than sample size, + // then use # of nodes as sample size + sampleSize = nodeSize < options.sampleSize ? nodeSize : options.sampleSize; + + // instantiates the partial matrices that will be used in spectral layout + for (var _i14 = 0; _i14 < nodeSize; _i14++) { + C[_i14] = []; + } + for (var _i15 = 0; _i15 < sampleSize; _i15++) { + INV[_i15] = []; + } + + /**** Apply spectral layout ****/ + + if (options.quality == "draft" || options.step == "all") { + allBFS(samplingType); + sample(); + powerIteration(); + + spectralResult = { nodeIndexes: nodeIndexes, xCoords: xCoords, yCoords: yCoords }; + } else { + nodeIndexes.forEach(function (value, key) { + xCoords.push(cy.getElementById(key).position("x")); + yCoords.push(cy.getElementById(key).position("y")); + }); + spectralResult = { nodeIndexes: nodeIndexes, xCoords: xCoords, yCoords: yCoords }; + } + return spectralResult; + } else { + var iterator = nodeIndexes.keys(); + var firstNode = cy.getElementById(iterator.next().value); + var firstNodePos = firstNode.position(); + var firstNodeWidth = firstNode.outerWidth(); + xCoords.push(firstNodePos.x); + yCoords.push(firstNodePos.y); + if (nodeSize == 2) { + var secondNode = cy.getElementById(iterator.next().value); + var secondNodeWidth = secondNode.outerWidth(); + xCoords.push(firstNodePos.x + firstNodeWidth / 2 + secondNodeWidth / 2 + options.idealEdgeLength); + yCoords.push(firstNodePos.y); + } + + spectralResult = { nodeIndexes: nodeIndexes, xCoords: xCoords, yCoords: yCoords }; + return spectralResult; + } +}; + +module.exports = { spectralLayout: spectralLayout }; + +/***/ }), + +/***/ 579: +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + + + +var impl = __webpack_require__(212); + +// registers the extension on a cytoscape lib ref +var register = function register(cytoscape) { + if (!cytoscape) { + return; + } // can't register if cytoscape unspecified + + cytoscape('layout', 'fcose', impl); // register with cytoscape.js +}; + +if (typeof cytoscape !== 'undefined') { + // expose to global cytoscape (i.e. window.cytoscape) + register(cytoscape); +} + +module.exports = register; + +/***/ }), + +/***/ 140: +/***/ ((module) => { + +module.exports = __WEBPACK_EXTERNAL_MODULE__140__; + +/***/ }) + +/******/ }); +/************************************************************************/ +/******/ // The module cache +/******/ var __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ var cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/************************************************************************/ +/******/ +/******/ // startup +/******/ // Load entry module and return exports +/******/ // This entry module is referenced by other modules so it can't be inlined +/******/ var __webpack_exports__ = __webpack_require__(579); +/******/ +/******/ return __webpack_exports__; +/******/ })() +; +}); \ No newline at end of file diff --git a/site/site/public/js/cytoscape.min.js b/site/site/public/js/cytoscape.min.js new file mode 100644 index 0000000..ff0d7c1 --- /dev/null +++ b/site/site/public/js/cytoscape.min.js @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2016-2024, The Cytoscape Consortium. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the “Software”), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).cytoscape=t()}(this,(function(){"use strict";function e(t){return(e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(t)}function t(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function n(e,t){for(var n=0;ne.length)&&(t=e.length);for(var n=0,r=new Array(t);n=e.length?{done:!0}:{done:!1,value:e[r++]}},e:function(e){throw e},f: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.")}var a,s=!0,l=!1;return{s:function(){n=n.call(e)},n:function(){var e=n.next();return s=e.done,e},e:function(e){l=!0,a=e},f:function(){try{s||null==n.return||n.return()}finally{if(l)throw a}}}}var u="undefined"==typeof window?null:window,c=u?u.navigator:null;u&&u.document;var d=e(""),h=e({}),p=e((function(){})),f="undefined"==typeof HTMLElement?"undefined":e(HTMLElement),g=function(e){return e&&e.instanceString&&y(e.instanceString)?e.instanceString():null},v=function(t){return null!=t&&e(t)==d},y=function(t){return null!=t&&e(t)===p},m=function(e){return!E(e)&&(Array.isArray?Array.isArray(e):null!=e&&e instanceof Array)},b=function(t){return null!=t&&e(t)===h&&!m(t)&&t.constructor===Object},x=function(t){return null!=t&&e(t)===e(1)&&!isNaN(t)},w=function(e){return"undefined"===f?void 0:null!=e&&e instanceof HTMLElement},E=function(e){return k(e)||C(e)},k=function(e){return"collection"===g(e)&&e._private.single},C=function(e){return"collection"===g(e)&&!e._private.single},S=function(e){return"core"===g(e)},P=function(e){return"stylesheet"===g(e)},D=function(e){return null==e||!(""!==e&&!e.match(/^\s+$/))},T=function(t){return function(t){return null!=t&&e(t)===h}(t)&&y(t.then)},_=function(e,t){t||(t=function(){if(1===arguments.length)return arguments[0];if(0===arguments.length)return"undefined";for(var e=[],t=0;tt?1:0},L=null!=Object.assign?Object.assign.bind(Object):function(e){for(var t=arguments,n=1;n255)return;t.push(Math.floor(a))}var o=r[1]||r[2]||r[3],s=r[1]&&r[2]&&r[3];if(o&&!s)return;var l=n[4];if(void 0!==l){if((l=parseFloat(l))<0||l>1)return;t.push(l)}}return t}(e)||function(e){var t,n,r,i,a,o,s,l;function u(e,t,n){return n<0&&(n+=1),n>1&&(n-=1),n<1/6?e+6*(t-e)*n:n<.5?t:n<2/3?e+(t-e)*(2/3-n)*6:e}var c=new RegExp("^hsl[a]?\\(((?:[-+]?(?:(?:\\d+|\\d*\\.\\d+)(?:[Ee][+-]?\\d+)?)))\\s*,\\s*((?:[-+]?(?:(?:\\d+|\\d*\\.\\d+)(?:[Ee][+-]?\\d+)?))[%])\\s*,\\s*((?:[-+]?(?:(?:\\d+|\\d*\\.\\d+)(?:[Ee][+-]?\\d+)?))[%])(?:\\s*,\\s*((?:[-+]?(?:(?:\\d+|\\d*\\.\\d+)(?:[Ee][+-]?\\d+)?))))?\\)$").exec(e);if(c){if((n=parseInt(c[1]))<0?n=(360- -1*n%360)%360:n>360&&(n%=360),n/=360,(r=parseFloat(c[2]))<0||r>100)return;if(r/=100,(i=parseFloat(c[3]))<0||i>100)return;if(i/=100,void 0!==(a=c[4])&&((a=parseFloat(a))<0||a>1))return;if(0===r)o=s=l=Math.round(255*i);else{var d=i<.5?i*(1+r):i+r-i*r,h=2*i-d;o=Math.round(255*u(h,d,n+1/3)),s=Math.round(255*u(h,d,n)),l=Math.round(255*u(h,d,n-1/3))}t=[o,s,l,a]}return t}(e)},R={transparent:[0,0,0,0],aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],grey:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]},V=function(e){for(var t=e.map,n=e.keys,r=n.length,i=0;i=t||n<0||d&&e-u>=a}function v(){var e=H();if(g(e))return y(e);s=setTimeout(v,function(e){var n=t-(e-l);return d?ge(n,a-(e-u)):n}(e))}function y(e){return s=void 0,h&&r?p(e):(r=i=void 0,o)}function m(){var e=H(),n=g(e);if(r=arguments,i=this,l=e,n){if(void 0===s)return f(l);if(d)return clearTimeout(s),s=setTimeout(v,t),p(l)}return void 0===s&&(s=setTimeout(v,t)),o}return t=pe(t)||0,j(n)&&(c=!!n.leading,a=(d="maxWait"in n)?fe(pe(n.maxWait)||0,t):a,h="trailing"in n?!!n.trailing:h),m.cancel=function(){void 0!==s&&clearTimeout(s),u=0,r=l=i=s=void 0},m.flush=function(){return void 0===s?o:y(H())},m},ye=u?u.performance:null,me=ye&&ye.now?function(){return ye.now()}:function(){return Date.now()},be=function(){if(u){if(u.requestAnimationFrame)return function(e){u.requestAnimationFrame(e)};if(u.mozRequestAnimationFrame)return function(e){u.mozRequestAnimationFrame(e)};if(u.webkitRequestAnimationFrame)return function(e){u.webkitRequestAnimationFrame(e)};if(u.msRequestAnimationFrame)return function(e){u.msRequestAnimationFrame(e)}}return function(e){e&&setTimeout((function(){e(me())}),1e3/60)}}(),xe=function(e){return be(e)},we=me,Ee=65599,ke=function(e){for(var t,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:9261,r=n;!(t=e.next()).done;)r=r*Ee+t.value|0;return r},Ce=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:9261;return t*Ee+e|0},Se=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:5381;return(t<<5)+t+e|0},Pe=function(e){return 2097152*e[0]+e[1]},De=function(e,t){return[Ce(e[0],t[0]),Se(e[1],t[1])]},Te=function(e,t){var n={value:0,done:!1},r=0,i=e.length;return ke({next:function(){return r=0&&(e[r]!==t||(e.splice(r,1),!n));r--);},Ge=function(e){e.splice(0,e.length)},Ue=function(e,t,n){return n&&(t=N(n,t)),e[t]},Ze=function(e,t,n,r){n&&(t=N(n,t)),e[t]=r},$e="undefined"!=typeof Map?Map:function(){function e(){t(this,e),this._obj={}}return r(e,[{key:"set",value:function(e,t){return this._obj[e]=t,this}},{key:"delete",value:function(e){return this._obj[e]=void 0,this}},{key:"clear",value:function(){this._obj={}}},{key:"has",value:function(e){return void 0!==this._obj[e]}},{key:"get",value:function(e){return this._obj[e]}}]),e}(),Qe=function(){function e(n){if(t(this,e),this._obj=Object.create(null),this.size=0,null!=n){var r;r=null!=n.instanceString&&n.instanceString()===this.instanceString()?n.toArray():n;for(var i=0;i2&&void 0!==arguments[2])||arguments[2];if(void 0!==e&&void 0!==t&&S(e)){var r=t.group;if(null==r&&(r=t.data&&null!=t.data.source&&null!=t.data.target?"edges":"nodes"),"nodes"===r||"edges"===r){this.length=1,this[0]=this;var i=this._private={cy:e,single:!0,data:t.data||{},position:t.position||{x:0,y:0},autoWidth:void 0,autoHeight:void 0,autoPadding:void 0,compoundBoundsClean:!1,listeners:[],group:r,style:{},rstyle:{},styleCxts:[],styleKeys:{},removed:!0,selected:!!t.selected,selectable:void 0===t.selectable||!!t.selectable,locked:!!t.locked,grabbed:!1,grabbable:void 0===t.grabbable||!!t.grabbable,pannable:void 0===t.pannable?"edges"===r:!!t.pannable,active:!1,classes:new Je,animation:{current:[],queue:[]},rscratch:{},scratch:t.scratch||{},edges:[],children:[],parent:t.parent&&t.parent.isNode()?t.parent:null,traversalCache:{},backgrounding:!1,bbCache:null,bbCacheShift:{x:0,y:0},bodyBounds:null,overlayBounds:null,labelBounds:{all:null,source:null,target:null,main:null},arrowBounds:{source:null,target:null,"mid-source":null,"mid-target":null}};if(null==i.position.x&&(i.position.x=0),null==i.position.y&&(i.position.y=0),t.renderedPosition){var a=t.renderedPosition,o=e.pan(),s=e.zoom();i.position={x:(a.x-o.x)/s,y:(a.y-o.y)/s}}var l=[];m(t.classes)?l=t.classes:v(t.classes)&&(l=t.classes.split(/\s+/));for(var u=0,c=l.length;ut?1:0},u=function(e,t,i,a,o){var s;if(null==i&&(i=0),null==o&&(o=n),i<0)throw new Error("lo must be non-negative");for(null==a&&(a=e.length);in;0<=n?t++:t--)u.push(t);return u}.apply(this).reverse()).length;ag;0<=g?++h:--h)v.push(a(e,r));return v},f=function(e,t,r,i){var a,o,s;for(null==i&&(i=n),a=e[r];r>t&&i(a,o=e[s=r-1>>1])<0;)e[r]=o,r=s;return e[r]=a},g=function(e,t,r){var i,a,o,s,l;for(null==r&&(r=n),a=e.length,l=t,o=e[t],i=2*t+1;i0;){var k=m.pop(),C=g(k),S=k.id();if(d[S]=C,C!==1/0)for(var P=k.neighborhood().intersect(p),D=0;D0)for(n.unshift(t);c[i];){var a=c[i];n.unshift(a.edge),n.unshift(a.node),i=(r=a.node).id()}return o.spawn(n)}}}},ot={kruskal:function(e){e=e||function(e){return 1};for(var t=this.byGroup(),n=t.nodes,r=t.edges,i=n.length,a=new Array(i),o=n,s=function(e){for(var t=0;t0;){if(l=g.pop(),u=l.id(),v.delete(u),w++,u===d){for(var E=[],k=i,C=d,S=m[C];E.unshift(k),null!=S&&E.unshift(S),null!=(k=y[C]);)S=m[C=k.id()];return{found:!0,distance:h[u],path:this.spawn(E),steps:w}}f[u]=!0;for(var P=l._private.edges,D=0;DD&&(p[P]=D,m[P]=S,b[P]=w),!i){var T=S*u+C;!i&&p[T]>D&&(p[T]=D,m[T]=C,b[T]=w)}}}for(var _=0;_1&&void 0!==arguments[1]?arguments[1]:a,r=b(e),i=[],o=r;;){if(null==o)return t.spawn();var l=m(o),u=l.edge,c=l.pred;if(i.unshift(o[0]),o.same(n)&&i.length>0)break;null!=u&&i.unshift(u),o=c}return s.spawn(i)},hasNegativeWeightCycle:f,negativeWeightCycles:g}}},pt=Math.sqrt(2),ft=function(e,t,n){0===n.length&&Ve("Karger-Stein must be run on a connected (sub)graph");for(var r=n[e],i=r[1],a=r[2],o=t[i],s=t[a],l=n,u=l.length-1;u>=0;u--){var c=l[u],d=c[1],h=c[2];(t[d]===o&&t[h]===s||t[d]===s&&t[h]===o)&&l.splice(u,1)}for(var p=0;pr;){var i=Math.floor(Math.random()*t.length);t=ft(i,e,t),n--}return t},vt={kargerStein:function(){var e=this,t=this.byGroup(),n=t.nodes,r=t.edges;r.unmergeBy((function(e){return e.isLoop()}));var i=n.length,a=r.length,o=Math.ceil(Math.pow(Math.log(i)/Math.LN2,2)),s=Math.floor(i/pt);if(!(i<2)){for(var l=[],u=0;u0?1:e<0?-1:0},kt=function(e,t){return Math.sqrt(Ct(e,t))},Ct=function(e,t){var n=t.x-e.x,r=t.y-e.y;return n*n+r*r},St=function(e){for(var t=e.length,n=0,r=0;r=e.x1&&e.y2>=e.y1)return{x1:e.x1,y1:e.y1,x2:e.x2,y2:e.y2,w:e.x2-e.x1,h:e.y2-e.y1};if(null!=e.w&&null!=e.h&&e.w>=0&&e.h>=0)return{x1:e.x1,y1:e.y1,x2:e.x1+e.w,y2:e.y1+e.h,w:e.w,h:e.h}}},Mt=function(e,t){e.x1=Math.min(e.x1,t.x1),e.x2=Math.max(e.x2,t.x2),e.w=e.x2-e.x1,e.y1=Math.min(e.y1,t.y1),e.y2=Math.max(e.y2,t.y2),e.h=e.y2-e.y1},Bt=function(e,t,n){e.x1=Math.min(e.x1,t),e.x2=Math.max(e.x2,t),e.w=e.x2-e.x1,e.y1=Math.min(e.y1,n),e.y2=Math.max(e.y2,n),e.h=e.y2-e.y1},Nt=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0;return e.x1-=t,e.x2+=t,e.y1-=t,e.y2+=t,e.w=e.x2-e.x1,e.h=e.y2-e.y1,e},zt=function(e){var t,n,r,i,o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[0];if(1===o.length)t=n=r=i=o[0];else if(2===o.length)t=r=o[0],i=n=o[1];else if(4===o.length){var s=a(o,4);t=s[0],n=s[1],r=s[2],i=s[3]}return e.x1-=i,e.x2+=n,e.y1-=t,e.y2+=r,e.w=e.x2-e.x1,e.h=e.y2-e.y1,e},It=function(e,t){e.x1=t.x1,e.y1=t.y1,e.x2=t.x2,e.y2=t.y2,e.w=e.x2-e.x1,e.h=e.y2-e.y1},At=function(e,t){return!(e.x1>t.x2)&&(!(t.x1>e.x2)&&(!(e.x2t.y2)&&!(t.y1>e.y2)))))))},Lt=function(e,t,n){return e.x1<=t&&t<=e.x2&&e.y1<=n&&n<=e.y2},Ot=function(e,t){return Lt(e,t.x1,t.y1)&&Lt(e,t.x2,t.y2)},Rt=function(e,t,n,r,i,a,o){var s,l,u=arguments.length>7&&void 0!==arguments[7]?arguments[7]:"auto",c="auto"===u?nn(i,a):u,d=i/2,h=a/2,p=(c=Math.min(c,d,h))!==d,f=c!==h;if(p){var g=n-d+c-o,v=r-h-o,y=n+d-c+o,m=v;if((s=Zt(e,t,n,r,g,v,y,m,!1)).length>0)return s}if(f){var b=n+d+o,x=r-h+c-o,w=b,E=r+h-c+o;if((s=Zt(e,t,n,r,b,x,w,E,!1)).length>0)return s}if(p){var k=n-d+c-o,C=r+h+o,S=n+d-c+o,P=C;if((s=Zt(e,t,n,r,k,C,S,P,!1)).length>0)return s}if(f){var D=n-d-o,T=r-h+c-o,_=D,M=r+h-c+o;if((s=Zt(e,t,n,r,D,T,_,M,!1)).length>0)return s}var B=n-d+c,N=r-h+c;if((l=Gt(e,t,n,r,B,N,c+o)).length>0&&l[0]<=B&&l[1]<=N)return[l[0],l[1]];var z=n+d-c,I=r-h+c;if((l=Gt(e,t,n,r,z,I,c+o)).length>0&&l[0]>=z&&l[1]<=I)return[l[0],l[1]];var A=n+d-c,L=r+h-c;if((l=Gt(e,t,n,r,A,L,c+o)).length>0&&l[0]>=A&&l[1]>=L)return[l[0],l[1]];var O=n-d+c,R=r+h-c;return(l=Gt(e,t,n,r,O,R,c+o)).length>0&&l[0]<=O&&l[1]>=R?[l[0],l[1]]:[]},Vt=function(e,t,n,r,i,a,o){var s=o,l=Math.min(n,i),u=Math.max(n,i),c=Math.min(r,a),d=Math.max(r,a);return l-s<=e&&e<=u+s&&c-s<=t&&t<=d+s},Ft=function(e,t,n,r,i,a,o,s,l){var u=Math.min(n,o,i)-l,c=Math.max(n,o,i)+l,d=Math.min(r,s,a)-l,h=Math.max(r,s,a)+l;return!(ec||th)},jt=function(e,t,n,r,i,a,o,s){var l=[];!function(e,t,n,r,i){var a,o,s,l,u,c,d,h;0===e&&(e=1e-5),s=-27*(r/=e)+(t/=e)*(9*(n/=e)-t*t*2),a=(o=(3*n-t*t)/9)*o*o+(s/=54)*s,i[1]=0,d=t/3,a>0?(u=(u=s+Math.sqrt(a))<0?-Math.pow(-u,1/3):Math.pow(u,1/3),c=(c=s-Math.sqrt(a))<0?-Math.pow(-c,1/3):Math.pow(c,1/3),i[0]=-d+u+c,d+=(u+c)/2,i[4]=i[2]=-d,d=Math.sqrt(3)*(-c+u)/2,i[3]=d,i[5]=-d):(i[5]=i[3]=0,0===a?(h=s<0?-Math.pow(-s,1/3):Math.pow(s,1/3),i[0]=2*h-d,i[4]=i[2]=-(h+d)):(l=(o=-o)*o*o,l=Math.acos(s/Math.sqrt(l)),h=2*Math.sqrt(o),i[0]=-d+h*Math.cos(l/3),i[2]=-d+h*Math.cos((l+2*Math.PI)/3),i[4]=-d+h*Math.cos((l+4*Math.PI)/3)))}(1*n*n-4*n*i+2*n*o+4*i*i-4*i*o+o*o+r*r-4*r*a+2*r*s+4*a*a-4*a*s+s*s,9*n*i-3*n*n-3*n*o-6*i*i+3*i*o+9*r*a-3*r*r-3*r*s-6*a*a+3*a*s,3*n*n-6*n*i+n*o-n*e+2*i*i+2*i*e-o*e+3*r*r-6*r*a+r*s-r*t+2*a*a+2*a*t-s*t,1*n*i-n*n+n*e-i*e+r*a-r*r+r*t-a*t,l);for(var u=[],c=0;c<6;c+=2)Math.abs(l[c+1])<1e-7&&l[c]>=0&&l[c]<=1&&u.push(l[c]);u.push(1),u.push(0);for(var d,h,p,f=-1,g=0;g=0?pl?(e-i)*(e-i)+(t-a)*(t-a):u-d},Yt=function(e,t,n){for(var r,i,a,o,s=0,l=0;l=e&&e>=a||r<=e&&e<=a))continue;(e-r)/(a-r)*(o-i)+i>t&&s++}return s%2!=0},Xt=function(e,t,n,r,i,a,o,s,l){var u,c=new Array(n.length);null!=s[0]?(u=Math.atan(s[1]/s[0]),s[0]<0?u+=Math.PI/2:u=-u-Math.PI/2):u=s;for(var d,h=Math.cos(-u),p=Math.sin(-u),f=0;f0){var g=Ht(c,-l);d=Wt(g)}else d=c;return Yt(e,t,d)},Wt=function(e){for(var t,n,r,i,a,o,s,l,u=new Array(e.length/2),c=0;c=0&&f<=1&&v.push(f),g>=0&&g<=1&&v.push(g),0===v.length)return[];var y=v[0]*s[0]+e,m=v[0]*s[1]+t;return v.length>1?v[0]==v[1]?[y,m]:[y,m,v[1]*s[0]+e,v[1]*s[1]+t]:[y,m]},Ut=function(e,t,n){return t<=e&&e<=n||n<=e&&e<=t?e:e<=t&&t<=n||n<=t&&t<=e?t:n},Zt=function(e,t,n,r,i,a,o,s,l){var u=e-i,c=n-e,d=o-i,h=t-a,p=r-t,f=s-a,g=d*h-f*u,v=c*h-p*u,y=f*c-d*p;if(0!==y){var m=g/y,b=v/y;return-.001<=m&&m<=1.001&&-.001<=b&&b<=1.001||l?[e+m*c,t+m*p]:[]}return 0===g||0===v?Ut(e,n,o)===o?[o,s]:Ut(e,n,i)===i?[i,a]:Ut(i,o,n)===n?[n,r]:[]:[]},$t=function(e,t,n,r,i,a,o,s){var l,u,c,d,h,p,f=[],g=new Array(n.length),v=!0;if(null==a&&(v=!1),v){for(var y=0;y0){var m=Ht(g,-s);u=Wt(m)}else u=g}else u=n;for(var b=0;bu&&(u=t)},d=function(e){return l[e]},h=0;h0?b.edgesTo(m)[0]:m.edgesTo(b)[0];var w=r(x);m=m.id(),h[m]>h[v]+w&&(h[m]=h[v]+w,p.nodes.indexOf(m)<0?p.push(m):p.updateItem(m),u[m]=0,l[m]=[]),h[m]==h[v]+w&&(u[m]=u[m]+u[v],l[m].push(v))}else for(var E=0;E0;){for(var P=n.pop(),D=0;D0&&o.push(n[s]);0!==o.length&&i.push(r.collection(o))}return i}(c,l,t,r);return b=function(e){for(var t=0;t5&&void 0!==arguments[5]?arguments[5]:Cn,o=r,s=0;s=2?Mn(e,t,n,0,Dn,Tn):Mn(e,t,n,0,Pn)},squaredEuclidean:function(e,t,n){return Mn(e,t,n,0,Dn)},manhattan:function(e,t,n){return Mn(e,t,n,0,Pn)},max:function(e,t,n){return Mn(e,t,n,-1/0,_n)}};function Nn(e,t,n,r,i,a){var o;return o=y(e)?e:Bn[e]||Bn.euclidean,0===t&&y(e)?o(i,a):o(t,n,r,i,a)}Bn["squared-euclidean"]=Bn.squaredEuclidean,Bn.squaredeuclidean=Bn.squaredEuclidean;var zn=He({k:2,m:2,sensitivityThreshold:1e-4,distance:"euclidean",maxIterations:10,attributes:[],testMode:!1,testCentroids:null}),In=function(e){return zn(e)},An=function(e,t,n,r,i){var a="kMedoids"!==i?function(e){return n[e]}:function(e){return r[e](n)},o=n,s=t;return Nn(e,r.length,a,(function(e){return r[e](t)}),o,s)},Ln=function(e,t,n){for(var r=n.length,i=new Array(r),a=new Array(r),o=new Array(t),s=null,l=0;ln)return!1}return!0},jn=function(e,t,n){for(var r=0;ri&&(i=t[l][u],a=u);o[a].push(e[l])}for(var c=0;c=i.threshold||"dendrogram"===i.mode&&1===e.length)return!1;var p,f=t[o],g=t[r[o]];p="dendrogram"===i.mode?{left:f,right:g,key:f.key}:{value:f.value.concat(g.value),key:f.key},e[f.index]=p,e.splice(g.index,1),t[f.key]=p;for(var v=0;vn[g.key][y.key]&&(a=n[g.key][y.key])):"max"===i.linkage?(a=n[f.key][y.key],n[f.key][y.key]1&&void 0!==arguments[1]?arguments[1]:0,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:e.length,r=!(arguments.length>3&&void 0!==arguments[3])||arguments[3],i=!(arguments.length>4&&void 0!==arguments[4])||arguments[4],a=!(arguments.length>5&&void 0!==arguments[5])||arguments[5];r?e=e.slice(t,n):(n0&&e.splice(0,t));for(var o=0,s=e.length-1;s>=0;s--){var l=e[s];a?isFinite(l)||(e[s]=-1/0,o++):e.splice(s,1)}i&&e.sort((function(e,t){return e-t}));var u=e.length,c=Math.floor(u/2);return u%2!=0?e[c+1+o]:(e[c-1+o]+e[c+o])/2}(e):"mean"===t?function(e){for(var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:e.length,r=0,i=0,a=t;a1&&void 0!==arguments[1]?arguments[1]:0,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:e.length,r=1/0,i=t;i1&&void 0!==arguments[1]?arguments[1]:0,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:e.length,r=-1/0,i=t;io&&(a=l,o=t[i*e+l])}a>0&&r.push(a)}for(var u=0;u=D?(T=D,D=M,_=B):M>T&&(T=M);for(var N=0;N0?1:0;C[k%u.minIterations*t+R]=V,O+=V}if(O>0&&(k>=u.minIterations-1||k==u.maxIterations-1)){for(var F=0,j=0;j0&&r.push(i);return r}(t,a,o),X=function(e,t,n){for(var r=rr(e,t,n),i=0;il&&(s=u,l=c)}n[i]=a[s]}return r=rr(e,t,n)}(t,r,Y),W={},H=0;H1)}}));var l=Object.keys(t).filter((function(e){return t[e].cutVertex})).map((function(t){return e.getElementById(t)}));return{cut:e.spawn(l),components:i}},lr=function(){var e=this,t={},n=0,r=[],i=[],a=e.spawn(e);return e.forEach((function(o){if(o.isNode()){var s=o.id();s in t||function o(s){if(i.push(s),t[s]={index:n,low:n++,explored:!1},e.getElementById(s).connectedEdges().intersection(e).forEach((function(e){var n=e.target().id();n!==s&&(n in t||o(n),t[n].explored||(t[s].low=Math.min(t[s].low,t[n].low)))})),t[s].index===t[s].low){for(var l=e.spawn();;){var u=i.pop();if(l.merge(e.getElementById(u)),t[u].low=t[s].index,t[u].explored=!0,u===s)break}var c=l.edgesWith(l),d=l.merge(c);r.push(d),a=a.difference(d)}}(s)}})),{cut:a,components:r}},ur={};[nt,at,ot,lt,ct,ht,vt,sn,un,dn,pn,kn,Kn,Jn,ar,{hierholzer:function(e){if(!b(e)){var t=arguments;e={root:t[0],directed:t[1]}}var n,r,i,a=or(e),o=a.root,s=a.directed,l=this,u=!1;o&&(i=v(o)?this.filter(o)[0].id():o[0].id());var c={},d={};s?l.forEach((function(e){var t=e.id();if(e.isNode()){var i=e.indegree(!0),a=e.outdegree(!0),o=i-a,s=a-i;1==o?n?u=!0:n=t:1==s?r?u=!0:r=t:(s>1||o>1)&&(u=!0),c[t]=[],e.outgoers().forEach((function(e){e.isEdge()&&c[t].push(e.id())}))}else d[t]=[void 0,e.target().id()]})):l.forEach((function(e){var t=e.id();e.isNode()?(e.degree(!0)%2&&(n?r?u=!0:r=t:n=t),c[t]=[],e.connectedEdges().forEach((function(e){return c[t].push(e.id())}))):d[t]=[e.source().id(),e.target().id()]}));var h={found:!1,trail:void 0};if(u)return h;if(r&&n)if(s){if(i&&r!=i)return h;i=r}else{if(i&&r!=i&&n!=i)return h;i||(i=r)}else i||(i=l[0].id());var p=function(e){for(var t,n,r,i=e,a=[e];c[i].length;)t=c[i].shift(),n=d[t][0],i!=(r=d[t][1])?(c[r]=c[r].filter((function(e){return e!=t})),i=r):s||i==n||(c[n]=c[n].filter((function(e){return e!=t})),i=n),a.unshift(t),a.unshift(i);return a},f=[],g=[];for(g=p(i);1!=g.length;)0==c[g[0]].length?(f.unshift(l.getElementById(g.shift())),f.unshift(l.getElementById(g.shift()))):g=p(g.shift()).concat(g);for(var y in f.unshift(l.getElementById(g.shift())),c)if(c[y].length)return h;return h.found=!0,h.trail=this.spawn(f,!0),h}},{hopcroftTarjanBiconnected:sr,htbc:sr,htb:sr,hopcroftTarjanBiconnectedComponents:sr},{tarjanStronglyConnected:lr,tsc:lr,tscc:lr,tarjanStronglyConnectedComponents:lr}].forEach((function(e){L(ur,e)})); +/*! + Embeddable Minimum Strictly-Compliant Promises/A+ 1.1.1 Thenable + Copyright (c) 2013-2014 Ralf S. Engelschall (http://engelschall.com) + Licensed under The MIT License (http://opensource.org/licenses/MIT) + */ +var cr=function e(t){if(!(this instanceof e))return new e(t);this.id="Thenable/1.0.7",this.state=0,this.fulfillValue=void 0,this.rejectReason=void 0,this.onFulfilled=[],this.onRejected=[],this.proxy={then:this.then.bind(this)},"function"==typeof t&&t.call(this,this.fulfill.bind(this),this.reject.bind(this))};cr.prototype={fulfill:function(e){return dr(this,1,"fulfillValue",e)},reject:function(e){return dr(this,2,"rejectReason",e)},then:function(e,t){var n=new cr;return this.onFulfilled.push(fr(e,n,"fulfill")),this.onRejected.push(fr(t,n,"reject")),hr(this),n.proxy}};var dr=function(e,t,n,r){return 0===e.state&&(e.state=t,e[n]=r,hr(e)),e},hr=function(e){1===e.state?pr(e,"onFulfilled",e.fulfillValue):2===e.state&&pr(e,"onRejected",e.rejectReason)},pr=function(e,t,n){if(0!==e[t].length){var r=e[t];e[t]=[];var i=function(){for(var e=0;e0:void 0}},clearQueue:function(){return function(){var e=void 0!==this.length?this:[this];if(!(this._private.cy||this).styleEnabled())return this;for(var t=0;t-1};var ri=function(e,t){var n=this.__data__,r=Qr(n,e);return r<0?(++this.size,n.push([e,t])):n[r][1]=t,this};function ii(e){var t=-1,n=null==e?0:e.length;for(this.clear();++t-1&&e%1==0&&e0&&this.spawn(n).updateStyle().emit("class"),this},addClass:function(e){return this.toggleClass(e,!0)},hasClass:function(e){var t=this[0];return null!=t&&t._private.classes.has(e)},toggleClass:function(e,t){m(e)||(e=e.match(/\S+/g)||[]);for(var n=void 0===t,r=[],i=0,a=this.length;i0&&this.spawn(r).updateStyle().emit("class"),this},removeClass:function(e){return this.toggleClass(e,!1)},flashClass:function(e,t){var n=this;if(null==t)t=250;else if(0===t)return n;return n.addClass(e),setTimeout((function(){n.removeClass(e)}),t),n}};qi.className=qi.classNames=qi.classes;var Yi={metaChar:"[\\!\\\"\\#\\$\\%\\&\\'\\(\\)\\*\\+\\,\\.\\/\\:\\;\\<\\=\\>\\?\\@\\[\\]\\^\\`\\{\\|\\}\\~]",comparatorOp:"=|\\!=|>|>=|<|<=|\\$=|\\^=|\\*=",boolOp:"\\?|\\!|\\^",string:"\"(?:\\\\\"|[^\"])*\"|'(?:\\\\'|[^'])*'",number:I,meta:"degree|indegree|outdegree",separator:"\\s*,\\s*",descendant:"\\s+",child:"\\s+>\\s+",subject:"\\$",group:"node|edge|\\*",directedEdge:"\\s+->\\s+",undirectedEdge:"\\s+<->\\s+"};Yi.variable="(?:[\\w-.]|(?:\\\\"+Yi.metaChar+"))+",Yi.className="(?:[\\w-]|(?:\\\\"+Yi.metaChar+"))+",Yi.value=Yi.string+"|"+Yi.number,Yi.id=Yi.variable,function(){var e,t,n;for(e=Yi.comparatorOp.split("|"),n=0;n=0||"="!==t&&(Yi.comparatorOp+="|\\!"+t)}();var Xi=0,Wi=1,Hi=2,Ki=3,Gi=4,Ui=5,Zi=6,$i=7,Qi=8,Ji=9,ea=10,ta=11,na=12,ra=13,ia=14,aa=15,oa=16,sa=17,la=18,ua=19,ca=20,da=[{selector:":selected",matches:function(e){return e.selected()}},{selector:":unselected",matches:function(e){return!e.selected()}},{selector:":selectable",matches:function(e){return e.selectable()}},{selector:":unselectable",matches:function(e){return!e.selectable()}},{selector:":locked",matches:function(e){return e.locked()}},{selector:":unlocked",matches:function(e){return!e.locked()}},{selector:":visible",matches:function(e){return e.visible()}},{selector:":hidden",matches:function(e){return!e.visible()}},{selector:":transparent",matches:function(e){return e.transparent()}},{selector:":grabbed",matches:function(e){return e.grabbed()}},{selector:":free",matches:function(e){return!e.grabbed()}},{selector:":removed",matches:function(e){return e.removed()}},{selector:":inside",matches:function(e){return!e.removed()}},{selector:":grabbable",matches:function(e){return e.grabbable()}},{selector:":ungrabbable",matches:function(e){return!e.grabbable()}},{selector:":animated",matches:function(e){return e.animated()}},{selector:":unanimated",matches:function(e){return!e.animated()}},{selector:":parent",matches:function(e){return e.isParent()}},{selector:":childless",matches:function(e){return e.isChildless()}},{selector:":child",matches:function(e){return e.isChild()}},{selector:":orphan",matches:function(e){return e.isOrphan()}},{selector:":nonorphan",matches:function(e){return e.isChild()}},{selector:":compound",matches:function(e){return e.isNode()?e.isParent():e.source().isParent()||e.target().isParent()}},{selector:":loop",matches:function(e){return e.isLoop()}},{selector:":simple",matches:function(e){return e.isSimple()}},{selector:":active",matches:function(e){return e.active()}},{selector:":inactive",matches:function(e){return!e.active()}},{selector:":backgrounding",matches:function(e){return e.backgrounding()}},{selector:":nonbackgrounding",matches:function(e){return!e.backgrounding()}}].sort((function(e,t){return function(e,t){return-1*A(e,t)}(e.selector,t.selector)})),ha=function(){for(var e,t={},n=0;n0&&l.edgeCount>0)return je("The selector `"+e+"` is invalid because it uses both a compound selector and an edge selector"),!1;if(l.edgeCount>1)return je("The selector `"+e+"` is invalid because it uses multiple edge selectors"),!1;1===l.edgeCount&&je("The selector `"+e+"` is deprecated. Edge selectors do not take effect on changes to source and target nodes after an edge is added, for performance reasons. Use a class or data selector on edges instead, updating the class or data of an edge when your app detects a change in source or target nodes.")}return!0},toString:function(){if(null!=this.toStringCache)return this.toStringCache;for(var e=function(e){return null==e?"":e},t=function(t){return v(t)?'"'+t+'"':e(t)},n=function(e){return" "+e+" "},r=function(r,a){var o=r.type,s=r.value;switch(o){case Xi:var l=e(s);return l.substring(0,l.length-1);case Ki:var u=r.field,c=r.operator;return"["+u+n(e(c))+t(s)+"]";case Ui:var d=r.operator,h=r.field;return"["+e(d)+h+"]";case Gi:return"["+r.field+"]";case Zi:var p=r.operator;return"[["+r.field+n(e(p))+t(s)+"]]";case $i:return s;case Qi:return"#"+s;case Ji:return"."+s;case sa:case aa:return i(r.parent,a)+n(">")+i(r.child,a);case la:case oa:return i(r.ancestor,a)+" "+i(r.descendant,a);case ua:var f=i(r.left,a),g=i(r.subject,a),v=i(r.right,a);return f+(f.length>0?" ":"")+g+v;case ca:return""}},i=function(e,t){return e.checks.reduce((function(n,i,a){return n+(t===e&&0===a?"$":"")+r(i,t)}),"")},a="",o=0;o1&&o=0&&(t=t.replace("!",""),c=!0),t.indexOf("@")>=0&&(t=t.replace("@",""),u=!0),(o||l||u)&&(i=o||s?""+e:"",a=""+n),u&&(e=i=i.toLowerCase(),n=a=a.toLowerCase()),t){case"*=":r=i.indexOf(a)>=0;break;case"$=":r=i.indexOf(a,i.length-a.length)>=0;break;case"^=":r=0===i.indexOf(a);break;case"=":r=e===n;break;case">":d=!0,r=e>n;break;case">=":d=!0,r=e>=n;break;case"<":d=!0,r=e0;){var u=i.shift();t(u),a.add(u.id()),o&&r(i,a,u)}return e}function Ba(e,t,n){if(n.isParent())for(var r=n._private.children,i=0;i1&&void 0!==arguments[1])||arguments[1];return Ma(this,e,t,Ba)},_a.forEachUp=function(e){var t=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];return Ma(this,e,t,Na)},_a.forEachUpAndDown=function(e){var t=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];return Ma(this,e,t,za)},_a.ancestors=_a.parents,(Pa=Da={data:Fi.data({field:"data",bindingEvent:"data",allowBinding:!0,allowSetting:!0,settingEvent:"data",settingTriggersEvent:!0,triggerFnName:"trigger",allowGetting:!0,immutableKeys:{id:!0,source:!0,target:!0,parent:!0},updateStyle:!0}),removeData:Fi.removeData({field:"data",event:"data",triggerFnName:"trigger",triggerEvent:!0,immutableKeys:{id:!0,source:!0,target:!0,parent:!0},updateStyle:!0}),scratch:Fi.data({field:"scratch",bindingEvent:"scratch",allowBinding:!0,allowSetting:!0,settingEvent:"scratch",settingTriggersEvent:!0,triggerFnName:"trigger",allowGetting:!0,updateStyle:!0}),removeScratch:Fi.removeData({field:"scratch",event:"scratch",triggerFnName:"trigger",triggerEvent:!0,updateStyle:!0}),rscratch:Fi.data({field:"rscratch",allowBinding:!1,allowSetting:!0,settingTriggersEvent:!1,allowGetting:!0}),removeRscratch:Fi.removeData({field:"rscratch",triggerEvent:!1}),id:function(){var e=this[0];if(e)return e._private.data.id}}).attr=Pa.data,Pa.removeAttr=Pa.removeData;var Ia,Aa,La=Da,Oa={};function Ra(e){return function(t){if(void 0===t&&(t=!0),0!==this.length&&this.isNode()&&!this.removed()){for(var n=0,r=this[0],i=r._private.edges,a=0;at})),minIndegree:Va("indegree",(function(e,t){return et})),minOutdegree:Va("outdegree",(function(e,t){return et}))}),L(Oa,{totalDegree:function(e){for(var t=0,n=this.nodes(),r=0;r0,c=u;u&&(l=l[0]);var d=c?l.position():{x:0,y:0};return i={x:s.x-d.x,y:s.y-d.y},void 0===e?i:i[e]}for(var h=0;h0,y=g;g&&(f=f[0]);var m=y?f.position():{x:0,y:0};void 0!==t?p.position(e,t+m[e]):void 0!==i&&p.position({x:i.x+m.x,y:i.y+m.y})}}else if(!a)return;return this}}).modelPosition=Ia.point=Ia.position,Ia.modelPositions=Ia.points=Ia.positions,Ia.renderedPoint=Ia.renderedPosition,Ia.relativePoint=Ia.relativePosition;var qa,Ya,Xa=Aa;qa=Ya={},Ya.renderedBoundingBox=function(e){var t=this.boundingBox(e),n=this.cy(),r=n.zoom(),i=n.pan(),a=t.x1*r+i.x,o=t.x2*r+i.x,s=t.y1*r+i.y,l=t.y2*r+i.y;return{x1:a,x2:o,y1:s,y2:l,w:o-a,h:l-s}},Ya.dirtyCompoundBoundsCache=function(){var e=arguments.length>0&&void 0!==arguments[0]&&arguments[0],t=this.cy();return t.styleEnabled()&&t.hasCompoundNodes()?(this.forEachUp((function(t){if(t.isParent()){var n=t._private;n.compoundBoundsClean=!1,n.bbCache=null,e||t.emitAndNotify("bounds")}})),this):this},Ya.updateCompoundBounds=function(){var e=arguments.length>0&&void 0!==arguments[0]&&arguments[0],t=this.cy();if(!t.styleEnabled()||!t.hasCompoundNodes())return this;if(!e&&t.batching())return this;function n(e){if(e.isParent()){var t=e._private,n=e.children(),r="include"===e.pstyle("compound-sizing-wrt-labels").value,i={width:{val:e.pstyle("min-width").pfValue,left:e.pstyle("min-width-bias-left"),right:e.pstyle("min-width-bias-right")},height:{val:e.pstyle("min-height").pfValue,top:e.pstyle("min-height-bias-top"),bottom:e.pstyle("min-height-bias-bottom")}},a=n.boundingBox({includeLabels:r,includeOverlays:!1,useCache:!1}),o=t.position;0!==a.w&&0!==a.h||((a={w:e.pstyle("width").pfValue,h:e.pstyle("height").pfValue}).x1=o.x-a.w/2,a.x2=o.x+a.w/2,a.y1=o.y-a.h/2,a.y2=o.y+a.h/2);var s=i.width.left.value;"px"===i.width.left.units&&i.width.val>0&&(s=100*s/i.width.val);var l=i.width.right.value;"px"===i.width.right.units&&i.width.val>0&&(l=100*l/i.width.val);var u=i.height.top.value;"px"===i.height.top.units&&i.height.val>0&&(u=100*u/i.height.val);var c=i.height.bottom.value;"px"===i.height.bottom.units&&i.height.val>0&&(c=100*c/i.height.val);var d=y(i.width.val-a.w,s,l),h=d.biasDiff,p=d.biasComplementDiff,f=y(i.height.val-a.h,u,c),g=f.biasDiff,v=f.biasComplementDiff;t.autoPadding=function(e,t,n,r){if("%"!==n.units)return"px"===n.units?n.pfValue:0;switch(r){case"width":return e>0?n.pfValue*e:0;case"height":return t>0?n.pfValue*t:0;case"average":return e>0&&t>0?n.pfValue*(e+t)/2:0;case"min":return e>0&&t>0?e>t?n.pfValue*t:n.pfValue*e:0;case"max":return e>0&&t>0?e>t?n.pfValue*e:n.pfValue*t:0;default:return 0}}(a.w,a.h,e.pstyle("padding"),e.pstyle("padding-relative-to").value),t.autoWidth=Math.max(a.w,i.width.val),o.x=(-h+a.x1+a.x2+p)/2,t.autoHeight=Math.max(a.h,i.height.val),o.y=(-g+a.y1+a.y2+v)/2}function y(e,t,n){var r=0,i=0,a=t+n;return e>0&&a>0&&(r=t/a*e,i=n/a*e),{biasDiff:r,biasComplementDiff:i}}}for(var r=0;re.x2?r:e.x2,e.y1=ne.y2?i:e.y2,e.w=e.x2-e.x1,e.h=e.y2-e.y1)},Ka=function(e,t){return null==t?e:Ha(e,t.x1,t.y1,t.x2,t.y2)},Ga=function(e,t,n){return Ue(e,t,n)},Ua=function(e,t,n){if(!t.cy().headless()){var r,i,a=t._private,o=a.rstyle,s=o.arrowWidth/2;if("none"!==t.pstyle(n+"-arrow-shape").value){"source"===n?(r=o.srcX,i=o.srcY):"target"===n?(r=o.tgtX,i=o.tgtY):(r=o.midX,i=o.midY);var l=a.arrowBounds=a.arrowBounds||{},u=l[n]=l[n]||{};u.x1=r-s,u.y1=i-s,u.x2=r+s,u.y2=i+s,u.w=u.x2-u.x1,u.h=u.y2-u.y1,Nt(u,1),Ha(e,u.x1,u.y1,u.x2,u.y2)}}},Za=function(e,t,n){if(!t.cy().headless()){var r;r=n?n+"-":"";var i=t._private,a=i.rstyle;if(t.pstyle(r+"label").strValue){var o,s,l,u,c=t.pstyle("text-halign"),d=t.pstyle("text-valign"),h=Ga(a,"labelWidth",n),p=Ga(a,"labelHeight",n),f=Ga(a,"labelX",n),g=Ga(a,"labelY",n),v=t.pstyle(r+"text-margin-x").pfValue,y=t.pstyle(r+"text-margin-y").pfValue,m=t.isEdge(),b=t.pstyle(r+"text-rotation"),x=t.pstyle("text-outline-width").pfValue,w=t.pstyle("text-border-width").pfValue/2,E=t.pstyle("text-background-padding").pfValue,k=p,C=h,S=C/2,P=k/2;if(m)o=f-S,s=f+S,l=g-P,u=g+P;else{switch(c.value){case"left":o=f-C,s=f;break;case"center":o=f-S,s=f+S;break;case"right":o=f,s=f+C}switch(d.value){case"top":l=g-k,u=g;break;case"center":l=g-P,u=g+P;break;case"bottom":l=g,u=g+k}}o+=v-Math.max(x,w)-E-2,s+=v+Math.max(x,w)+E+2,l+=y-Math.max(x,w)-E-2,u+=y+Math.max(x,w)+E+2;var D=n||"main",T=i.labelBounds,_=T[D]=T[D]||{};_.x1=o,_.y1=l,_.x2=s,_.y2=u,_.w=s-o,_.h=u-l;var M=m&&"autorotate"===b.strValue,B=null!=b.pfValue&&0!==b.pfValue;if(M||B){var N=M?Ga(i.rstyle,"labelAngle",n):b.pfValue,z=Math.cos(N),I=Math.sin(N),A=(o+s)/2,L=(l+u)/2;if(!m){switch(c.value){case"left":A=s;break;case"right":A=o}switch(d.value){case"top":L=u;break;case"bottom":L=l}}var O=function(e,t){return{x:(e-=A)*z-(t-=L)*I+A,y:e*I+t*z+L}},R=O(o,l),V=O(o,u),F=O(s,l),j=O(s,u);o=Math.min(R.x,V.x,F.x,j.x),s=Math.max(R.x,V.x,F.x,j.x),l=Math.min(R.y,V.y,F.y,j.y),u=Math.max(R.y,V.y,F.y,j.y)}var q=D+"Rot",Y=T[q]=T[q]||{};Y.x1=o,Y.y1=l,Y.x2=s,Y.y2=u,Y.w=s-o,Y.h=u-l,Ha(e,o,l,s,u),Ha(i.labelBounds.all,o,l,s,u)}return e}},$a=function(e,t){var n,r,i,a,o,s,l,u=e._private.cy,c=u.styleEnabled(),d=u.headless(),h=_t(),p=e._private,f=e.isNode(),g=e.isEdge(),v=p.rstyle,y=f&&c?e.pstyle("bounds-expansion").pfValue:[0],m=function(e){return"none"!==e.pstyle("display").value},b=!c||m(e)&&(!g||m(e.source())&&m(e.target()));if(b){var x=0;c&&t.includeOverlays&&0!==e.pstyle("overlay-opacity").value&&(x=e.pstyle("overlay-padding").value);var w=0;c&&t.includeUnderlays&&0!==e.pstyle("underlay-opacity").value&&(w=e.pstyle("underlay-padding").value);var E=Math.max(x,w),k=0;if(c&&(k=e.pstyle("width").pfValue/2),f&&t.includeNodes){var C=e.position();o=C.x,s=C.y;var S=e.outerWidth()/2,P=e.outerHeight()/2;Ha(h,n=o-S,i=s-P,r=o+S,a=s+P),c&&t.includeOutlines&&function(e,t){if(!t.cy().headless()){var n,r,i,a=t.pstyle("outline-opacity").value,o=t.pstyle("outline-width").value;if(a>0&&o>0){var s=t.pstyle("outline-offset").value,l=t.pstyle("shape").value,u=o+s,c=(e.w+2*u)/e.w,d=(e.h+2*u)/e.h,h=0;["diamond","pentagon","round-triangle"].includes(l)?(c=(e.w+2.4*u)/e.w,h=-u/3.6):["concave-hexagon","rhomboid","right-rhomboid"].includes(l)?c=(e.w+2.4*u)/e.w:"star"===l?(c=(e.w+2.8*u)/e.w,d=(e.h+2.6*u)/e.h,h=-u/3.8):"triangle"===l?(c=(e.w+2.8*u)/e.w,d=(e.h+2.4*u)/e.h,h=-u/1.4):"vee"===l&&(c=(e.w+4.4*u)/e.w,d=(e.h+3.8*u)/e.h,h=.5*-u);var p=e.h*d-e.h,f=e.w*c-e.w;if(zt(e,[Math.ceil(p/2),Math.ceil(f/2)]),0!==h){var g=(r=0,i=h,{x1:(n=e).x1+r,x2:n.x2+r,y1:n.y1+i,y2:n.y2+i,w:n.w,h:n.h});Mt(e,g)}}}}(h,e)}else if(g&&t.includeEdges)if(c&&!d){var D=e.pstyle("curve-style").strValue;if(n=Math.min(v.srcX,v.midX,v.tgtX),r=Math.max(v.srcX,v.midX,v.tgtX),i=Math.min(v.srcY,v.midY,v.tgtY),a=Math.max(v.srcY,v.midY,v.tgtY),Ha(h,n-=k,i-=k,r+=k,a+=k),"haystack"===D){var T=v.haystackPts;if(T&&2===T.length){if(n=T[0].x,i=T[0].y,n>(r=T[1].x)){var _=n;n=r,r=_}if(i>(a=T[1].y)){var M=i;i=a,a=M}Ha(h,n-k,i-k,r+k,a+k)}}else if("bezier"===D||"unbundled-bezier"===D||D.endsWith("segments")||D.endsWith("taxi")){var B;switch(D){case"bezier":case"unbundled-bezier":B=v.bezierPts;break;case"segments":case"taxi":case"round-segments":case"round-taxi":B=v.linePts}if(null!=B)for(var N=0;N(r=A.x)){var L=n;n=r,r=L}if((i=I.y)>(a=A.y)){var O=i;i=a,a=O}Ha(h,n-=k,i-=k,r+=k,a+=k)}if(c&&t.includeEdges&&g&&(Ua(h,e,"mid-source"),Ua(h,e,"mid-target"),Ua(h,e,"source"),Ua(h,e,"target")),c)if("yes"===e.pstyle("ghost").value){var R=e.pstyle("ghost-offset-x").pfValue,V=e.pstyle("ghost-offset-y").pfValue;Ha(h,h.x1+R,h.y1+V,h.x2+R,h.y2+V)}var F=p.bodyBounds=p.bodyBounds||{};It(F,h),zt(F,y),Nt(F,1),c&&(n=h.x1,r=h.x2,i=h.y1,a=h.y2,Ha(h,n-E,i-E,r+E,a+E));var j=p.overlayBounds=p.overlayBounds||{};It(j,h),zt(j,y),Nt(j,1);var q=p.labelBounds=p.labelBounds||{};null!=q.all?((l=q.all).x1=1/0,l.y1=1/0,l.x2=-1/0,l.y2=-1/0,l.w=0,l.h=0):q.all=_t(),c&&t.includeLabels&&(t.includeMainLabels&&Za(h,e,null),g&&(t.includeSourceLabels&&Za(h,e,"source"),t.includeTargetLabels&&Za(h,e,"target")))}return h.x1=Wa(h.x1),h.y1=Wa(h.y1),h.x2=Wa(h.x2),h.y2=Wa(h.y2),h.w=Wa(h.x2-h.x1),h.h=Wa(h.y2-h.y1),h.w>0&&h.h>0&&b&&(zt(h,y),Nt(h,1)),h},Qa=function(e){var t=0,n=function(e){return(e?1:0)<0&&void 0!==arguments[0]?arguments[0]:bo,t=arguments.length>1?arguments[1]:void 0,n=0;n=0;s--)o(s);return this},wo.removeAllListeners=function(){return this.removeListener("*")},wo.emit=wo.trigger=function(e,t,n){var r=this.listeners,i=r.length;return this.emitting++,m(t)||(t=[t]),Co(this,(function(e,a){null!=n&&(r=[{event:a.event,type:a.type,namespace:a.namespace,callback:n}],i=r.length);for(var o=function(n){var i=r[n];if(i.type===a.type&&(!i.namespace||i.namespace===a.namespace||".*"===i.namespace)&&e.eventMatches(e.context,i,a)){var o=[a];null!=t&&function(e,t){for(var n=0;n1&&!r){var i=this.length-1,a=this[i],o=a._private.data.id;this[i]=void 0,this[e]=a,n.set(o,{ele:a,index:e})}return this.length--,this},unmergeOne:function(e){e=e[0];var t=this._private,n=e._private.data.id,r=t.map.get(n);if(!r)return this;var i=r.index;return this.unmergeAt(i),this},unmerge:function(e){var t=this._private.cy;if(!e)return this;if(e&&v(e)){var n=e;e=t.mutableElements().filter(n)}for(var r=0;r=0;t--){e(this[t])&&this.unmergeAt(t)}return this},map:function(e,t){for(var n=[],r=0;rr&&(r=o,n=a)}return{value:r,ele:n}},min:function(e,t){for(var n,r=1/0,i=0;i=0&&i1&&void 0!==arguments[1])||arguments[1],n=this[0],r=n.cy();if(r.styleEnabled()&&n){this.cleanStyle();var i=n._private.style[e];return null!=i?i:t?r.style().getDefaultProperty(e):null}},numericStyle:function(e){var t=this[0];if(t.cy().styleEnabled()&&t){var n=t.pstyle(e);return void 0!==n.pfValue?n.pfValue:n.value}},numericStyleUnits:function(e){var t=this[0];if(t.cy().styleEnabled())return t?t.pstyle(e).units:void 0},renderedStyle:function(e){var t=this.cy();if(!t.styleEnabled())return this;var n=this[0];return n?t.style().getRenderedStyle(n,e):void 0},style:function(e,t){var n=this.cy();if(!n.styleEnabled())return this;var r=n.style();if(b(e)){var i=e;r.applyBypass(this,i,!1),this.emitAndNotify("style")}else if(v(e)){if(void 0===t){var a=this[0];return a?r.getStylePropertyValue(a,e):void 0}r.applyBypass(this,e,t,!1),this.emitAndNotify("style")}else if(void 0===e){var o=this[0];return o?r.getRawStyle(o):void 0}return this},removeStyle:function(e){var t=this.cy();if(!t.styleEnabled())return this;var n=t.style();if(void 0===e)for(var r=0;r0&&t.push(c[0]),t.push(s[0])}return this.spawn(t,!0).filter(e)}),"neighborhood"),closedNeighborhood:function(e){return this.neighborhood().add(this).filter(e)},openNeighborhood:function(e){return this.neighborhood(e)}}),Go.neighbourhood=Go.neighborhood,Go.closedNeighbourhood=Go.closedNeighborhood,Go.openNeighbourhood=Go.openNeighborhood,L(Go,{source:Ta((function(e){var t,n=this[0];return n&&(t=n._private.source||n.cy().collection()),t&&e?t.filter(e):t}),"source"),target:Ta((function(e){var t,n=this[0];return n&&(t=n._private.target||n.cy().collection()),t&&e?t.filter(e):t}),"target"),sources:Qo({attr:"source"}),targets:Qo({attr:"target"})}),L(Go,{edgesWith:Ta(Jo(),"edgesWith"),edgesTo:Ta(Jo({thisIsSrc:!0}),"edgesTo")}),L(Go,{connectedEdges:Ta((function(e){for(var t=[],n=0;n0);return a},component:function(){var e=this[0];return e.cy().mutableElements().components(e)[0]}}),Go.componentsOf=Go.components;var ts=function(e,t){var n=arguments.length>2&&void 0!==arguments[2]&&arguments[2],r=arguments.length>3&&void 0!==arguments[3]&&arguments[3];if(void 0!==e){var i=new $e,a=!1;if(t){if(t.length>0&&b(t[0])&&!k(t[0])){a=!0;for(var o=[],s=new Je,l=0,u=t.length;l0&&void 0!==arguments[0])||arguments[0],r=!(arguments.length>1&&void 0!==arguments[1])||arguments[1],i=this,a=i.cy(),o=a._private,s=[],l=[],u=0,c=i.length;u0){for(var R=e.length===i.length?i:new ts(a,e),V=0;V0&&void 0!==arguments[0])||arguments[0],t=!(arguments.length>1&&void 0!==arguments[1])||arguments[1],n=this,r=[],i={},a=n._private.cy;function o(e){for(var t=e._private.edges,n=0;n0&&(e?D.emitAndNotify("remove"):t&&D.emit("remove"));for(var T=0;T1e-4&&Math.abs(s.v)>1e-4;);return a?function(e){return u[e*(u.length-1)|0]}:c}}(),as=function(e,t,n,r){var i=function(e,t,n,r){var i=4,a=.001,o=1e-7,s=10,l=11,u=1/(l-1),c="undefined"!=typeof Float32Array;if(4!==arguments.length)return!1;for(var d=0;d<4;++d)if("number"!=typeof arguments[d]||isNaN(arguments[d])||!isFinite(arguments[d]))return!1;e=Math.min(e,1),n=Math.min(n,1),e=Math.max(e,0),n=Math.max(n,0);var h=c?new Float32Array(l):new Array(l);function p(e,t){return 1-3*t+3*e}function f(e,t){return 3*t-6*e}function g(e){return 3*e}function v(e,t,n){return((p(t,n)*e+f(t,n))*e+g(t))*e}function y(e,t,n){return 3*p(t,n)*e*e+2*f(t,n)*e+g(t)}function m(t,r){for(var a=0;a0?i=l:r=l}while(Math.abs(a)>o&&++u=a?m(t,s):0===c?s:x(t,r,r+u)}var E=!1;function k(){E=!0,e===t&&n===r||b()}var C=function(i){return E||k(),e===t&&n===r?i:0===i?0:1===i?1:v(w(i),t,r)};C.getControlPoints=function(){return[{x:e,y:t},{x:n,y:r}]};var S="generateBezier("+[e,t,n,r]+")";return C.toString=function(){return S},C}(e,t,n,r);return function(e,t,n){return e+(t-e)*i(n)}},os={linear:function(e,t,n){return e+(t-e)*n},ease:as(.25,.1,.25,1),"ease-in":as(.42,0,1,1),"ease-out":as(0,0,.58,1),"ease-in-out":as(.42,0,.58,1),"ease-in-sine":as(.47,0,.745,.715),"ease-out-sine":as(.39,.575,.565,1),"ease-in-out-sine":as(.445,.05,.55,.95),"ease-in-quad":as(.55,.085,.68,.53),"ease-out-quad":as(.25,.46,.45,.94),"ease-in-out-quad":as(.455,.03,.515,.955),"ease-in-cubic":as(.55,.055,.675,.19),"ease-out-cubic":as(.215,.61,.355,1),"ease-in-out-cubic":as(.645,.045,.355,1),"ease-in-quart":as(.895,.03,.685,.22),"ease-out-quart":as(.165,.84,.44,1),"ease-in-out-quart":as(.77,0,.175,1),"ease-in-quint":as(.755,.05,.855,.06),"ease-out-quint":as(.23,1,.32,1),"ease-in-out-quint":as(.86,0,.07,1),"ease-in-expo":as(.95,.05,.795,.035),"ease-out-expo":as(.19,1,.22,1),"ease-in-out-expo":as(1,0,0,1),"ease-in-circ":as(.6,.04,.98,.335),"ease-out-circ":as(.075,.82,.165,1),"ease-in-out-circ":as(.785,.135,.15,.86),spring:function(e,t,n){if(0===n)return os.linear;var r=is(e,t,n);return function(e,t,n){return e+(t-e)*r(n)}},"cubic-bezier":as};function ss(e,t,n,r,i){if(1===r)return n;if(t===n)return n;var a=i(t,n,r);return null==e||((e.roundValue||e.color)&&(a=Math.round(a)),void 0!==e.min&&(a=Math.max(a,e.min)),void 0!==e.max&&(a=Math.min(a,e.max))),a}function ls(e,t){return null!=e.pfValue||null!=e.value?null==e.pfValue||null!=t&&"%"===t.type.units?e.value:e.pfValue:e}function us(e,t,n,r,i){var a=null!=i?i.type:null;n<0?n=0:n>1&&(n=1);var o=ls(e,i),s=ls(t,i);if(x(o)&&x(s))return ss(a,o,s,n,r);if(m(o)&&m(s)){for(var l=[],u=0;u0?("spring"===d&&h.push(o.duration),o.easingImpl=os[d].apply(null,h)):o.easingImpl=os[d]}var p,f=o.easingImpl;if(p=0===o.duration?1:(n-l)/o.duration,o.applying&&(p=o.progress),p<0?p=0:p>1&&(p=1),null==o.delay){var g=o.startPosition,y=o.position;if(y&&i&&!e.locked()){var m={};ds(g.x,y.x)&&(m.x=us(g.x,y.x,p,f)),ds(g.y,y.y)&&(m.y=us(g.y,y.y,p,f)),e.position(m)}var b=o.startPan,x=o.pan,w=a.pan,E=null!=x&&r;E&&(ds(b.x,x.x)&&(w.x=us(b.x,x.x,p,f)),ds(b.y,x.y)&&(w.y=us(b.y,x.y,p,f)),e.emit("pan"));var k=o.startZoom,C=o.zoom,S=null!=C&&r;S&&(ds(k,C)&&(a.zoom=Tt(a.minZoom,us(k,C,p,f),a.maxZoom)),e.emit("zoom")),(E||S)&&e.emit("viewport");var P=o.style;if(P&&P.length>0&&i){for(var D=0;D=0;t--){(0,e[t])()}e.splice(0,e.length)},c=a.length-1;c>=0;c--){var d=a[c],h=d._private;h.stopped?(a.splice(c,1),h.hooked=!1,h.playing=!1,h.started=!1,u(h.frames)):(h.playing||h.applying)&&(h.playing&&h.applying&&(h.applying=!1),h.started||hs(0,d,e),cs(t,d,e,n),h.applying&&(h.applying=!1),u(h.frames),null!=h.step&&h.step(e),d.completed()&&(a.splice(c,1),h.hooked=!1,h.playing=!1,h.started=!1,u(h.completes)),s=!0)}return n||0!==a.length||0!==o.length||r.push(t),s}for(var a=!1,o=0;o0?t.notify("draw",n):t.notify("draw")),n.unmerge(r),t.emit("step")}var fs={animate:Fi.animate(),animation:Fi.animation(),animated:Fi.animated(),clearQueue:Fi.clearQueue(),delay:Fi.delay(),delayAnimation:Fi.delayAnimation(),stop:Fi.stop(),addToAnimationPool:function(e){this.styleEnabled()&&this._private.aniEles.merge(e)},stopAnimationLoop:function(){this._private.animationsRunning=!1},startAnimationLoop:function(){var e=this;if(e._private.animationsRunning=!0,e.styleEnabled()){var t=e.renderer();t&&t.beforeRender?t.beforeRender((function(t,n){ps(n,e)}),t.beforeRenderPriorities.animations):function t(){e._private.animationsRunning&&xe((function(n){ps(n,e),t()}))}()}}},gs={qualifierCompare:function(e,t){return null==e||null==t?null==e&&null==t:e.sameText(t)},eventMatches:function(e,t,n){var r=t.qualifier;return null==r||e!==n.target&&k(n.target)&&r.matches(n.target)},addEventFields:function(e,t){t.cy=e,t.target=e},callbackContext:function(e,t,n){return null!=t.qualifier?n.target:e}},vs=function(e){return v(e)?new ka(e):e},ys={createEmitter:function(){var e=this._private;return e.emitter||(e.emitter=new xo(gs,this)),this},emitter:function(){return this._private.emitter},on:function(e,t,n){return this.emitter().on(e,vs(t),n),this},removeListener:function(e,t,n){return this.emitter().removeListener(e,vs(t),n),this},removeAllListeners:function(){return this.emitter().removeAllListeners(),this},one:function(e,t,n){return this.emitter().one(e,vs(t),n),this},once:function(e,t,n){return this.emitter().one(e,vs(t),n),this},emit:function(e,t){return this.emitter().emit(e,t),this},emitAndNotify:function(e,t){return this.emit(e),this.notify(e,t),this}};Fi.eventAliasesOn(ys);var ms={png:function(e){return e=e||{},this._private.renderer.png(e)},jpg:function(e){var t=this._private.renderer;return(e=e||{}).bg=e.bg||"#fff",t.jpg(e)}};ms.jpeg=ms.jpg;var bs={layout:function(e){if(null!=e)if(null!=e.name){var t=e.name,n=this.extension("layout",t);if(null!=n){var r;r=v(e.eles)?this.$(e.eles):null!=e.eles?e.eles:this.$();var i=new n(L({},e,{cy:this,eles:r}));return i}Ve("No such layout `"+t+"` found. Did you forget to import it and `cytoscape.use()` it?")}else Ve("A `name` must be specified to make a layout");else Ve("Layout options must be specified to make a layout")}};bs.createLayout=bs.makeLayout=bs.layout;var xs={notify:function(e,t){var n=this._private;if(this.batching()){n.batchNotifications=n.batchNotifications||{};var r=n.batchNotifications[e]=n.batchNotifications[e]||this.collection();null!=t&&r.merge(t)}else if(n.notificationsEnabled){var i=this.renderer();!this.destroyed()&&i&&i.notify(e,t)}},notifications:function(e){var t=this._private;return void 0===e?t.notificationsEnabled:(t.notificationsEnabled=!!e,this)},noNotifications:function(e){this.notifications(!1),e(),this.notifications(!0)},batching:function(){return this._private.batchCount>0},startBatch:function(){var e=this._private;return null==e.batchCount&&(e.batchCount=0),0===e.batchCount&&(e.batchStyleEles=this.collection(),e.batchNotifications={}),e.batchCount++,this},endBatch:function(){var e=this._private;if(0===e.batchCount)return this;if(e.batchCount--,0===e.batchCount){e.batchStyleEles.updateStyle();var t=this.renderer();Object.keys(e.batchNotifications).forEach((function(n){var r=e.batchNotifications[n];r.empty()?t.notify(n):t.notify(n,r)}))}return this},batch:function(e){return this.startBatch(),e(),this.endBatch(),this},batchData:function(e){var t=this;return this.batch((function(){for(var n=Object.keys(e),r=0;r0;)e.removeChild(e.childNodes[0]);this._private.renderer=null,this.mutableElements().forEach((function(e){var t=e._private;t.rscratch={},t.rstyle={},t.animation.current=[],t.animation.queue=[]}))},onRender:function(e){return this.on("render",e)},offRender:function(e){return this.off("render",e)}};Es.invalidateDimensions=Es.resize;var ks={collection:function(e,t){return v(e)?this.$(e):E(e)?e.collection():m(e)?(t||(t={}),new ts(this,e,t.unique,t.removed)):new ts(this)},nodes:function(e){var t=this.$((function(e){return e.isNode()}));return e?t.filter(e):t},edges:function(e){var t=this.$((function(e){return e.isEdge()}));return e?t.filter(e):t},$:function(e){var t=this._private.elements;return e?t.filter(e):t.spawnSelf()},mutableElements:function(){return this._private.elements}};ks.elements=ks.filter=ks.$;var Cs={};Cs.apply=function(e){for(var t=this._private.cy.collection(),n=0;n0;if(d||c&&h){var p=void 0;d&&h||d?p=l.properties:h&&(p=l.mappedProperties);for(var f=0;f1&&(g=1),s.color){var w=i.valueMin[0],E=i.valueMax[0],k=i.valueMin[1],C=i.valueMax[1],S=i.valueMin[2],P=i.valueMax[2],D=null==i.valueMin[3]?1:i.valueMin[3],T=null==i.valueMax[3]?1:i.valueMax[3],_=[Math.round(w+(E-w)*g),Math.round(k+(C-k)*g),Math.round(S+(P-S)*g),Math.round(D+(T-D)*g)];n={bypass:i.bypass,name:i.name,value:_,strValue:"rgb("+_[0]+", "+_[1]+", "+_[2]+")"}}else{if(!s.number)return!1;var M=i.valueMin+(i.valueMax-i.valueMin)*g;n=this.parse(i.name,M,i.bypass,"mapping")}if(!n)return f(),!1;n.mapping=i,i=n;break;case o.data:for(var B=i.field.split("."),N=d.data,z=0;z0&&a>0){for(var s={},l=!1,u=0;u0?e.delayAnimation(o).play().promise().then(t):t()})).then((function(){return e.animation({style:s,duration:a,easing:e.pstyle("transition-timing-function").value,queue:!1}).play().promise()})).then((function(){n.removeBypasses(e,i),e.emitAndNotify("style"),r.transitioning=!1}))}else r.transitioning&&(this.removeBypasses(e,i),e.emitAndNotify("style"),r.transitioning=!1)},Cs.checkTrigger=function(e,t,n,r,i,a){var o=this.properties[t],s=i(o);null!=s&&s(n,r)&&a(o)},Cs.checkZOrderTrigger=function(e,t,n,r){var i=this;this.checkTrigger(e,t,n,r,(function(e){return e.triggersZOrder}),(function(){i._private.cy.notify("zorder",e)}))},Cs.checkBoundsTrigger=function(e,t,n,r){this.checkTrigger(e,t,n,r,(function(e){return e.triggersBounds}),(function(i){e.dirtyCompoundBoundsCache(),e.dirtyBoundingBoxCache(),!i.triggersBoundsOfParallelBeziers||"curve-style"!==t||"bezier"!==n&&"bezier"!==r||e.parallelEdges().forEach((function(e){e.isBundledBezier()&&e.dirtyBoundingBoxCache()})),!i.triggersBoundsOfConnectedEdges||"display"!==t||"none"!==n&&"none"!==r||e.connectedEdges().forEach((function(e){e.dirtyBoundingBoxCache()}))}))},Cs.checkTriggers=function(e,t,n,r){e.dirtyStyleCache(),this.checkZOrderTrigger(e,t,n,r),this.checkBoundsTrigger(e,t,n,r)};var Ss={applyBypass:function(e,t,n,r){var i=[];if("*"===t||"**"===t){if(void 0!==n)for(var a=0;at.length?i.substr(t.length):""}function o(){n=n.length>r.length?n.substr(r.length):""}for(i=i.replace(/[/][*](\s|.)+?[*][/]/g,"");;){if(i.match(/^\s*$/))break;var s=i.match(/^\s*((?:.|\s)+?)\s*\{((?:.|\s)+?)\}/);if(!s){je("Halting stylesheet parsing: String stylesheet contains more to parse but no selector and block found in: "+i);break}t=s[0];var l=s[1];if("core"!==l)if(new ka(l).invalid){je("Skipping parsing of block: Invalid selector found in string stylesheet: "+l),a();continue}var u=s[2],c=!1;n=u;for(var d=[];;){if(n.match(/^\s*$/))break;var h=n.match(/^\s*(.+?)\s*:\s*(.+?)(?:\s*;|\s*$)/);if(!h){je("Skipping parsing of block: Invalid formatting of style property and value definitions found in:"+u),c=!0;break}r=h[0];var p=h[1],f=h[2];if(this.properties[p])this.parse(p,f)?(d.push({name:p,val:f}),o()):(je("Skipping property: Invalid property definition in: "+r),o());else je("Skipping property: Invalid property name in: "+r),o()}if(c){a();break}this.selector(l);for(var g=0;g=7&&"d"===t[0]&&(l=new RegExp(o.data.regex).exec(t))){if(n)return!1;var d=o.data;return{name:e,value:l,strValue:""+t,mapped:d,field:l[1],bypass:n}}if(t.length>=10&&"m"===t[0]&&(u=new RegExp(o.mapData.regex).exec(t))){if(n)return!1;if(c.multiple)return!1;var h=o.mapData;if(!c.color&&!c.number)return!1;var p=this.parse(e,u[4]);if(!p||p.mapped)return!1;var f=this.parse(e,u[5]);if(!f||f.mapped)return!1;if(p.pfValue===f.pfValue||p.strValue===f.strValue)return je("`"+e+": "+t+"` is not a valid mapper because the output range is zero; converting to `"+e+": "+p.strValue+"`"),this.parse(e,p.strValue);if(c.color){var g=p.value,b=f.value;if(!(g[0]!==b[0]||g[1]!==b[1]||g[2]!==b[2]||g[3]!==b[3]&&(null!=g[3]&&1!==g[3]||null!=b[3]&&1!==b[3])))return!1}return{name:e,value:u,strValue:""+t,mapped:h,field:u[1],fieldMin:parseFloat(u[2]),fieldMax:parseFloat(u[3]),valueMin:p.value,valueMax:f.value,bypass:n}}}if(c.multiple&&"multiple"!==r){var w;if(w=s?t.split(/\s+/):m(t)?t:[t],c.evenMultiple&&w.length%2!=0)return null;for(var E=[],k=[],C=[],S="",P=!1,D=0;D0?" ":"")+T.strValue}return c.validate&&!c.validate(E,k)?null:c.singleEnum&&P?1===E.length&&v(E[0])?{name:e,value:E[0],strValue:E[0],bypass:n}:null:{name:e,value:E,pfValue:C,strValue:S,bypass:n,units:k}}var _,B,N=function(){for(var r=0;rc.max||c.strictMax&&t===c.max))return null;var V={name:e,value:t,strValue:""+t+(z||""),units:z,bypass:n};return c.unitless||"px"!==z&&"em"!==z?V.pfValue=t:V.pfValue="px"!==z&&z?this.getEmSizeInPixels()*t:t,"ms"!==z&&"s"!==z||(V.pfValue="ms"===z?t:1e3*t),"deg"!==z&&"rad"!==z||(V.pfValue="rad"===z?t:(_=t,Math.PI*_/180)),"%"===z&&(V.pfValue=t/100),V}if(c.propList){var F=[],j=""+t;if("none"===j);else{for(var q=j.split(/\s*,\s*|\s+/),Y=0;Y0&&l>0&&!isNaN(n.w)&&!isNaN(n.h)&&n.w>0&&n.h>0)return{zoom:o=(o=(o=Math.min((s-2*t)/n.w,(l-2*t)/n.h))>this._private.maxZoom?this._private.maxZoom:o)=n.minZoom&&(n.maxZoom=t),this},minZoom:function(e){return void 0===e?this._private.minZoom:this.zoomRange({min:e})},maxZoom:function(e){return void 0===e?this._private.maxZoom:this.zoomRange({max:e})},getZoomedViewport:function(e){var t,n,r=this._private,i=r.pan,a=r.zoom,o=!1;if(r.zoomingEnabled||(o=!0),x(e)?n=e:b(e)&&(n=e.level,null!=e.position?t=yt(e.position,a,i):null!=e.renderedPosition&&(t=e.renderedPosition),null==t||r.panningEnabled||(o=!0)),n=(n=n>r.maxZoom?r.maxZoom:n)t.maxZoom||!t.zoomingEnabled?a=!0:(t.zoom=s,i.push("zoom"))}if(r&&(!a||!e.cancelOnFailedZoom)&&t.panningEnabled){var l=e.pan;x(l.x)&&(t.pan.x=l.x,o=!1),x(l.y)&&(t.pan.y=l.y,o=!1),o||i.push("pan")}return i.length>0&&(i.push("viewport"),this.emit(i.join(" ")),this.notify("viewport")),this},center:function(e){var t=this.getCenterPan(e);return t&&(this._private.pan=t,this.emit("pan viewport"),this.notify("viewport")),this},getCenterPan:function(e,t){if(this._private.panningEnabled){if(v(e)){var n=e;e=this.mutableElements().filter(n)}else E(e)||(e=this.mutableElements());if(0!==e.length){var r=e.boundingBox(),i=this.width(),a=this.height();return{x:(i-(t=void 0===t?this._private.zoom:t)*(r.x1+r.x2))/2,y:(a-t*(r.y1+r.y2))/2}}}},reset:function(){return this._private.panningEnabled&&this._private.zoomingEnabled?(this.viewport({pan:{x:0,y:0},zoom:1}),this):this},invalidateSize:function(){this._private.sizeCache=null},size:function(){var e,t,n=this._private,r=n.container,i=this;return n.sizeCache=n.sizeCache||(r?(e=i.window().getComputedStyle(r),t=function(t){return parseFloat(e.getPropertyValue(t))},{width:r.clientWidth-t("padding-left")-t("padding-right"),height:r.clientHeight-t("padding-top")-t("padding-bottom")}):{width:1,height:1})},width:function(){return this.size().width},height:function(){return this.size().height},extent:function(){var e=this._private.pan,t=this._private.zoom,n=this.renderedExtent(),r={x1:(n.x1-e.x)/t,x2:(n.x2-e.x)/t,y1:(n.y1-e.y)/t,y2:(n.y2-e.y)/t};return r.w=r.x2-r.x1,r.h=r.y2-r.y1,r},renderedExtent:function(){var e=this.width(),t=this.height();return{x1:0,y1:0,x2:e,y2:t,w:e,h:t}},multiClickDebounceTime:function(e){return e?(this._private.multiClickDebounceTime=e,this):this._private.multiClickDebounceTime}};As.centre=As.center,As.autolockNodes=As.autolock,As.autoungrabifyNodes=As.autoungrabify;var Ls={data:Fi.data({field:"data",bindingEvent:"data",allowBinding:!0,allowSetting:!0,settingEvent:"data",settingTriggersEvent:!0,triggerFnName:"trigger",allowGetting:!0,updateStyle:!0}),removeData:Fi.removeData({field:"data",event:"data",triggerFnName:"trigger",triggerEvent:!0,updateStyle:!0}),scratch:Fi.data({field:"scratch",bindingEvent:"scratch",allowBinding:!0,allowSetting:!0,settingEvent:"scratch",settingTriggersEvent:!0,triggerFnName:"trigger",allowGetting:!0,updateStyle:!0}),removeScratch:Fi.removeData({field:"scratch",event:"scratch",triggerFnName:"trigger",triggerEvent:!0,updateStyle:!0})};Ls.attr=Ls.data,Ls.removeAttr=Ls.removeData;var Os=function(e){var t=this,n=(e=L({},e)).container;n&&!w(n)&&w(n[0])&&(n=n[0]);var r=n?n._cyreg:null;(r=r||{})&&r.cy&&(r.cy.destroy(),r={});var i=r.readies=r.readies||[];n&&(n._cyreg=r),r.cy=t;var a=void 0!==u&&void 0!==n&&!e.headless,o=e;o.layout=L({name:a?"grid":"null"},o.layout),o.renderer=L({name:a?"canvas":"null"},o.renderer);var s=function(e,t,n){return void 0!==t?t:void 0!==n?n:e},l=this._private={container:n,ready:!1,options:o,elements:new ts(this),listeners:[],aniEles:new ts(this),data:o.data||{},scratch:{},layout:null,renderer:null,destroyed:!1,notificationsEnabled:!0,minZoom:1e-50,maxZoom:1e50,zoomingEnabled:s(!0,o.zoomingEnabled),userZoomingEnabled:s(!0,o.userZoomingEnabled),panningEnabled:s(!0,o.panningEnabled),userPanningEnabled:s(!0,o.userPanningEnabled),boxSelectionEnabled:s(!0,o.boxSelectionEnabled),autolock:s(!1,o.autolock,o.autolockNodes),autoungrabify:s(!1,o.autoungrabify,o.autoungrabifyNodes),autounselectify:s(!1,o.autounselectify),styleEnabled:void 0===o.styleEnabled?a:o.styleEnabled,zoom:x(o.zoom)?o.zoom:1,pan:{x:b(o.pan)&&x(o.pan.x)?o.pan.x:0,y:b(o.pan)&&x(o.pan.y)?o.pan.y:0},animation:{current:[],queue:[]},hasCompoundNodes:!1,multiClickDebounceTime:s(250,o.multiClickDebounceTime)};this.createEmitter(),this.selectionType(o.selectionType),this.zoomRange({min:o.minZoom,max:o.maxZoom});l.styleEnabled&&t.setStyle([]);var c=L({},o,o.renderer);t.initRenderer(c);!function(e,t){if(e.some(T))return vr.all(e).then(t);t(e)}([o.style,o.elements],(function(e){var n=e[0],a=e[1];l.styleEnabled&&t.style().append(n),function(e,n,r){t.notifications(!1);var i=t.mutableElements();i.length>0&&i.remove(),null!=e&&(b(e)||m(e))&&t.add(e),t.one("layoutready",(function(e){t.notifications(!0),t.emit(e),t.one("load",n),t.emitAndNotify("load")})).one("layoutstop",(function(){t.one("done",r),t.emit("done")}));var a=L({},t._private.options.layout);a.eles=t.elements(),t.layout(a).run()}(a,(function(){t.startAnimationLoop(),l.ready=!0,y(o.ready)&&t.on("ready",o.ready);for(var e=0;e0,u=_t(n.boundingBox?n.boundingBox:{x1:0,y1:0,w:r.width(),h:r.height()});if(E(n.roots))e=n.roots;else if(m(n.roots)){for(var c=[],d=0;d0;){var N=_.shift(),z=T(N,M);if(z)N.outgoers().filter((function(e){return e.isNode()&&i.has(e)})).forEach(B);else if(null===z){je("Detected double maximal shift for node `"+N.id()+"`. Bailing maximal adjustment due to cycle. Use `options.maximal: true` only on DAGs.");break}}}D();var I=0;if(n.avoidOverlap)for(var L=0;L0&&b[0].length<=3?l/2:0),d=2*Math.PI/b[r].length*i;return 0===r&&1===b[0].length&&(c=1),{x:G+c*Math.cos(d),y:U+c*Math.sin(d)}}return{x:G+(i+1-(a+1)/2)*o,y:(r+1)*s}})),this};var Xs={fit:!0,padding:30,boundingBox:void 0,avoidOverlap:!0,nodeDimensionsIncludeLabels:!1,spacingFactor:void 0,radius:void 0,startAngle:1.5*Math.PI,sweep:void 0,clockwise:!0,sort:void 0,animate:!1,animationDuration:500,animationEasing:void 0,animateFilter:function(e,t){return!0},ready:void 0,stop:void 0,transform:function(e,t){return t}};function Ws(e){this.options=L({},Xs,e)}Ws.prototype.run=function(){var e=this.options,t=e,n=e.cy,r=t.eles,i=void 0!==t.counterclockwise?!t.counterclockwise:t.clockwise,a=r.nodes().not(":parent");t.sort&&(a=a.sort(t.sort));for(var o,s=_t(t.boundingBox?t.boundingBox:{x1:0,y1:0,w:n.width(),h:n.height()}),l=s.x1+s.w/2,u=s.y1+s.h/2,c=(void 0===t.sweep?2*Math.PI-2*Math.PI/a.length:t.sweep)/Math.max(1,a.length-1),d=0,h=0;h1&&t.avoidOverlap){d*=1.75;var v=Math.cos(c)-Math.cos(0),y=Math.sin(c)-Math.sin(0),m=Math.sqrt(d*d/(v*v+y*y));o=Math.max(m,o)}return r.nodes().layoutPositions(this,t,(function(e,n){var r=t.startAngle+n*c*(i?1:-1),a=o*Math.cos(r),s=o*Math.sin(r);return{x:l+a,y:u+s}})),this};var Hs,Ks={fit:!0,padding:30,startAngle:1.5*Math.PI,sweep:void 0,clockwise:!0,equidistant:!1,minNodeSpacing:10,boundingBox:void 0,avoidOverlap:!0,nodeDimensionsIncludeLabels:!1,height:void 0,width:void 0,spacingFactor:void 0,concentric:function(e){return e.degree()},levelWidth:function(e){return e.maxDegree()/4},animate:!1,animationDuration:500,animationEasing:void 0,animateFilter:function(e,t){return!0},ready:void 0,stop:void 0,transform:function(e,t){return t}};function Gs(e){this.options=L({},Ks,e)}Gs.prototype.run=function(){for(var e=this.options,t=e,n=void 0!==t.counterclockwise?!t.counterclockwise:t.clockwise,r=e.cy,i=t.eles,a=i.nodes().not(":parent"),o=_t(t.boundingBox?t.boundingBox:{x1:0,y1:0,w:r.width(),h:r.height()}),s=o.x1+o.w/2,l=o.y1+o.h/2,u=[],c=0,d=0;d0)Math.abs(m[0].value-x.value)>=v&&(m=[],y.push(m));m.push(x)}var w=c+t.minNodeSpacing;if(!t.avoidOverlap){var E=y.length>0&&y[0].length>1,k=(Math.min(o.w,o.h)/2-w)/(y.length+E?1:0);w=Math.min(w,k)}for(var C=0,S=0;S1&&t.avoidOverlap){var _=Math.cos(T)-Math.cos(0),M=Math.sin(T)-Math.sin(0),B=Math.sqrt(w*w/(_*_+M*M));C=Math.max(B,C)}P.r=C,C+=w}if(t.equidistant){for(var N=0,z=0,I=0;I=e.numIter)&&(rl(r,e),r.temperature=r.temperature*e.coolingFactor,!(r.temperature=e.animationThreshold&&a(),xe(t)):(gl(r,e),s())}()}else{for(;u;)u=o(l),l++;gl(r,e),s()}return this},Zs.prototype.stop=function(){return this.stopped=!0,this.thread&&this.thread.stop(),this.emit("layoutstop"),this},Zs.prototype.destroy=function(){return this.thread&&this.thread.stop(),this};var $s=function(e,t,n){for(var r=n.eles.edges(),i=n.eles.nodes(),a=_t(n.boundingBox?n.boundingBox:{x1:0,y1:0,w:e.width(),h:e.height()}),o={isCompound:e.hasCompoundNodes(),layoutNodes:[],idToIndex:{},nodeSize:i.size(),graphSet:[],indexToGraph:[],layoutEdges:[],edgeSize:r.size(),temperature:n.initialTemp,clientWidth:a.w,clientHeight:a.h,boundingBox:a},s=n.eles.components(),l={},u=0;u0){o.graphSet.push(E);for(u=0;ur.count?0:r.graph},Js=function e(t,n,r,i){var a=i.graphSet[r];if(-10)var s=(u=r.nodeOverlap*o)*i/(g=Math.sqrt(i*i+a*a)),l=u*a/g;else{var u,c=ll(e,i,a),d=ll(t,-1*i,-1*a),h=d.x-c.x,p=d.y-c.y,f=h*h+p*p,g=Math.sqrt(f);s=(u=(e.nodeRepulsion+t.nodeRepulsion)/f)*h/g,l=u*p/g}e.isLocked||(e.offsetX-=s,e.offsetY-=l),t.isLocked||(t.offsetX+=s,t.offsetY+=l)}},sl=function(e,t,n,r){if(n>0)var i=e.maxX-t.minX;else i=t.maxX-e.minX;if(r>0)var a=e.maxY-t.minY;else a=t.maxY-e.minY;return i>=0&&a>=0?Math.sqrt(i*i+a*a):0},ll=function(e,t,n){var r=e.positionX,i=e.positionY,a=e.height||1,o=e.width||1,s=n/t,l=a/o,u={};return 0===t&&0n?(u.x=r,u.y=i+a/2,u):0t&&-1*l<=s&&s<=l?(u.x=r-o/2,u.y=i-o*n/2/t,u):0=l)?(u.x=r+a*t/2/n,u.y=i+a/2,u):0>n&&(s<=-1*l||s>=l)?(u.x=r-a*t/2/n,u.y=i-a/2,u):u},ul=function(e,t){for(var n=0;n1){var f=t.gravity*d/p,g=t.gravity*h/p;c.offsetX+=f,c.offsetY+=g}}}}},dl=function(e,t){var n=[],r=0,i=-1;for(n.push.apply(n,e.graphSet[0]),i+=e.graphSet[0].length;r<=i;){var a=n[r++],o=e.idToIndex[a],s=e.layoutNodes[o],l=s.children;if(0n)var i={x:n*e/r,y:n*t/r};else i={x:e,y:t};return i},fl=function e(t,n){var r=t.parentId;if(null!=r){var i=n.layoutNodes[n.idToIndex[r]],a=!1;return(null==i.maxX||t.maxX+i.padRight>i.maxX)&&(i.maxX=t.maxX+i.padRight,a=!0),(null==i.minX||t.minX-i.padLefti.maxY)&&(i.maxY=t.maxY+i.padBottom,a=!0),(null==i.minY||t.minY-i.padTopf&&(d+=p+t.componentSpacing,c=0,h=0,p=0)}}},vl={fit:!0,padding:30,boundingBox:void 0,avoidOverlap:!0,avoidOverlapPadding:10,nodeDimensionsIncludeLabels:!1,spacingFactor:void 0,condense:!1,rows:void 0,cols:void 0,position:function(e){},sort:void 0,animate:!1,animationDuration:500,animationEasing:void 0,animateFilter:function(e,t){return!0},ready:void 0,stop:void 0,transform:function(e,t){return t}};function yl(e){this.options=L({},vl,e)}yl.prototype.run=function(){var e=this.options,t=e,n=e.cy,r=t.eles,i=r.nodes().not(":parent");t.sort&&(i=i.sort(t.sort));var a=_t(t.boundingBox?t.boundingBox:{x1:0,y1:0,w:n.width(),h:n.height()});if(0===a.h||0===a.w)r.nodes().layoutPositions(this,t,(function(e){return{x:a.x1,y:a.y1}}));else{var o=i.size(),s=Math.sqrt(o*a.h/a.w),l=Math.round(s),u=Math.round(a.w/a.h*s),c=function(e){if(null==e)return Math.min(l,u);Math.min(l,u)==l?l=e:u=e},d=function(e){if(null==e)return Math.max(l,u);Math.max(l,u)==l?l=e:u=e},h=t.rows,p=null!=t.cols?t.cols:t.columns;if(null!=h&&null!=p)l=h,u=p;else if(null!=h&&null==p)l=h,u=Math.ceil(o/l);else if(null==h&&null!=p)u=p,l=Math.ceil(o/u);else if(u*l>o){var f=c(),g=d();(f-1)*g>=o?c(f-1):(g-1)*f>=o&&d(g-1)}else for(;u*l=o?d(y+1):c(v+1)}var m=a.w/u,b=a.h/l;if(t.condense&&(m=0,b=0),t.avoidOverlap)for(var x=0;x=u&&(B=0,M++)},z={},I=0;I(r=qt(e,t,x[w],x[w+1],x[w+2],x[w+3])))return v(n,r),!0}else if("bezier"===a.edgeType||"multibezier"===a.edgeType||"self"===a.edgeType||"compound"===a.edgeType)for(x=a.allpts,w=0;w+5(r=jt(e,t,x[w],x[w+1],x[w+2],x[w+3],x[w+4],x[w+5])))return v(n,r),!0;m=m||i.source,b=b||i.target;var E=o.getArrowWidth(l,c),k=[{name:"source",x:a.arrowStartX,y:a.arrowStartY,angle:a.srcArrowAngle},{name:"target",x:a.arrowEndX,y:a.arrowEndY,angle:a.tgtArrowAngle},{name:"mid-source",x:a.midX,y:a.midY,angle:a.midsrcArrowAngle},{name:"mid-target",x:a.midX,y:a.midY,angle:a.midtgtArrowAngle}];for(w=0;w0&&(y(m),y(b))}function b(e,t,n){return Ue(e,t,n)}function x(n,r){var i,a=n._private,o=f;i=r?r+"-":"",n.boundingBox();var s=a.labelBounds[r||"main"],l=n.pstyle(i+"label").value;if("yes"===n.pstyle("text-events").strValue&&l){var u=b(a.rscratch,"labelX",r),c=b(a.rscratch,"labelY",r),d=b(a.rscratch,"labelAngle",r),h=n.pstyle(i+"text-margin-x").pfValue,p=n.pstyle(i+"text-margin-y").pfValue,g=s.x1-o-h,y=s.x2+o-h,m=s.y1-o-p,x=s.y2+o-p;if(d){var w=Math.cos(d),E=Math.sin(d),k=function(e,t){return{x:(e-=u)*w-(t-=c)*E+u,y:e*E+t*w+c}},C=k(g,m),S=k(g,x),P=k(y,m),D=k(y,x),T=[C.x+h,C.y+p,P.x+h,P.y+p,D.x+h,D.y+p,S.x+h,S.y+p];if(Yt(e,t,T))return v(n),!0}else if(Lt(s,e,t))return v(n),!0}}n&&(l=l.interactive);for(var w=l.length-1;w>=0;w--){var E=l[w];E.isNode()?y(E)||x(E):m(E)||x(E)||x(E,"source")||x(E,"target")}return u},getAllInBox:function(e,t,n,r){for(var i,a,o=this.getCachedZSortedEles().interactive,s=[],l=Math.min(e,n),u=Math.max(e,n),c=Math.min(t,r),d=Math.max(t,r),h=_t({x1:e=l,y1:t=c,x2:n=u,y2:r=d}),p=0;p0?-(Math.PI-a.ang):Math.PI+a.ang),Zl(t,n,Ul),zl=Gl.nx*Ul.ny-Gl.ny*Ul.nx,Il=Gl.nx*Ul.nx-Gl.ny*-Ul.ny,Ol=Math.asin(Math.max(-1,Math.min(1,zl))),Math.abs(Ol)<1e-6)return Bl=t.x,Nl=t.y,void(Vl=jl=0);Al=1,Ll=!1,Il<0?Ol<0?Ol=Math.PI+Ol:(Ol=Math.PI-Ol,Al=-1,Ll=!0):Ol>0&&(Al=-1,Ll=!0),jl=void 0!==t.radius?t.radius:r,Rl=Ol/2,ql=Math.min(Gl.len/2,Ul.len/2),i?(Fl=Math.abs(Math.cos(Rl)*jl/Math.sin(Rl)))>ql?(Fl=ql,Vl=Math.abs(Fl*Math.sin(Rl)/Math.cos(Rl))):Vl=jl:(Fl=Math.min(ql,jl),Vl=Math.abs(Fl*Math.sin(Rl)/Math.cos(Rl))),Wl=t.x+Ul.nx*Fl,Hl=t.y+Ul.ny*Fl,Bl=Wl-Ul.ny*Vl*Al,Nl=Hl+Ul.nx*Vl*Al,Yl=t.x+Gl.nx*Fl,Xl=t.y+Gl.ny*Fl,Kl=t};function Ql(e,t){0===t.radius?e.lineTo(t.cx,t.cy):e.arc(t.cx,t.cy,t.radius,t.startAngle,t.endAngle,t.counterClockwise)}function Jl(e,t,n,r){var i=!(arguments.length>4&&void 0!==arguments[4])||arguments[4];return 0===r||0===t.radius?{cx:t.x,cy:t.y,radius:0,startX:t.x,startY:t.y,stopX:t.x,stopY:t.y,startAngle:void 0,endAngle:void 0,counterClockwise:void 0}:($l(e,t,n,r,i),{cx:Bl,cy:Nl,radius:Vl,startX:Yl,startY:Xl,stopX:Wl,stopY:Hl,startAngle:Gl.ang+Math.PI/2*Al,endAngle:Ul.ang-Math.PI/2*Al,counterClockwise:Ll})}var eu={};function tu(e){var t=[];if(null!=e){for(var n=0;n0?Math.max(e-t,0):Math.min(e+t,0)},w=x(m,v),E=x(b,y),k=!1;"auto"===c?u=Math.abs(w)>Math.abs(E)?"horizontal":"vertical":"upward"===c||"downward"===c?(u="vertical",k=!0):"leftward"!==c&&"rightward"!==c||(u="horizontal",k=!0);var C,S="vertical"===u,P=S?E:w,D=S?b:m,T=Et(D),_=!1;(k&&(h||f)||!("downward"===c&&D<0||"upward"===c&&D>0||"leftward"===c&&D>0||"rightward"===c&&D<0)||(P=(T*=-1)*Math.abs(P),_=!0),h)?C=(p<0?1+p:p)*P:C=(p<0?P:0)+p*T;var M=function(e){return Math.abs(e)=Math.abs(P)},B=M(C),N=M(Math.abs(P)-Math.abs(C));if((B||N)&&!_)if(S){var z=Math.abs(D)<=a/2,I=Math.abs(m)<=o/2;if(z){var A=(r.x1+r.x2)/2,L=r.y1,O=r.y2;n.segpts=[A,L,A,O]}else if(I){var R=(r.y1+r.y2)/2,V=r.x1,F=r.x2;n.segpts=[V,R,F,R]}else n.segpts=[r.x1,r.y2]}else{var j=Math.abs(D)<=i/2,q=Math.abs(b)<=s/2;if(j){var Y=(r.y1+r.y2)/2,X=r.x1,W=r.x2;n.segpts=[X,Y,W,Y]}else if(q){var H=(r.x1+r.x2)/2,K=r.y1,G=r.y2;n.segpts=[H,K,H,G]}else n.segpts=[r.x2,r.y1]}else if(S){var U=r.y1+C+(l?a/2*T:0),Z=r.x1,$=r.x2;n.segpts=[Z,U,$,U]}else{var Q=r.x1+C+(l?i/2*T:0),J=r.y1,ee=r.y2;n.segpts=[Q,J,Q,ee]}if(n.isRound){var te=e.pstyle("taxi-radius").value,ne="arc-radius"===e.pstyle("radius-type").value[0];n.radii=new Array(n.segpts.length/2).fill(te),n.isArcRadius=new Array(n.segpts.length/2).fill(ne)}},eu.tryToCorrectInvalidPoints=function(e,t){var n=e._private.rscratch;if("bezier"===n.edgeType){var r=t.srcPos,i=t.tgtPos,a=t.srcW,o=t.srcH,s=t.tgtW,l=t.tgtH,u=t.srcShape,c=t.tgtShape,d=t.srcCornerRadius,h=t.tgtCornerRadius,p=t.srcRs,f=t.tgtRs,g=!x(n.startX)||!x(n.startY),v=!x(n.arrowStartX)||!x(n.arrowStartY),y=!x(n.endX)||!x(n.endY),m=!x(n.arrowEndX)||!x(n.arrowEndY),b=3*(this.getArrowWidth(e.pstyle("width").pfValue,e.pstyle("arrow-scale").value)*this.arrowShapeWidth),w=kt({x:n.ctrlpts[0],y:n.ctrlpts[1]},{x:n.startX,y:n.startY}),E=wh.poolIndex()){var p=d;d=h,h=p}var f=s.srcPos=d.position(),g=s.tgtPos=h.position(),v=s.srcW=d.outerWidth(),y=s.srcH=d.outerHeight(),m=s.tgtW=h.outerWidth(),b=s.tgtH=h.outerHeight(),w=s.srcShape=n.nodeShapes[t.getNodeShape(d)],E=s.tgtShape=n.nodeShapes[t.getNodeShape(h)],k=s.srcCornerRadius="auto"===d.pstyle("corner-radius").value?"auto":d.pstyle("corner-radius").pfValue,C=s.tgtCornerRadius="auto"===h.pstyle("corner-radius").value?"auto":h.pstyle("corner-radius").pfValue,S=s.tgtRs=h._private.rscratch,P=s.srcRs=d._private.rscratch;s.dirCounts={north:0,west:0,south:0,east:0,northwest:0,southwest:0,northeast:0,southeast:0};for(var D=0;D0){var H=u,K=Ct(H,bt(t)),G=Ct(H,bt(W)),U=K;if(G2)Ct(H,{x:W[2],y:W[3]})0){var le=c,ue=Ct(le,bt(t)),ce=Ct(le,bt(se)),de=ue;if(ce2)Ct(le,{x:se[2],y:se[3]})=c||b){d={cp:v,segment:m};break}}if(d)break}var x=d.cp,w=d.segment,E=(c-p)/w.length,k=w.t1-w.t0,C=u?w.t0+k*E:w.t1-k*E;C=Tt(0,C,1),t=Dt(x.p0,x.p1,x.p2,C),l=function(e,t,n,r){var i=Tt(0,r-.001,1),a=Tt(0,r+.001,1),o=Dt(e,t,n,i),s=Dt(e,t,n,a);return su(o,s)}(x.p0,x.p1,x.p2,C);break;case"straight":case"segments":case"haystack":for(var S,P,D,T,_=0,M=r.allpts.length,B=0;B+3=c));B+=2);var N=(c-P)/S;N=Tt(0,N,1),t=function(e,t,n,r){var i=t.x-e.x,a=t.y-e.y,o=kt(e,t),s=i/o,l=a/o;return n=null==n?0:n,r=null!=r?r:n*o,{x:e.x+s*r,y:e.y+l*r}}(D,T,N),l=su(D,T)}o("labelX",s,t.x),o("labelY",s,t.y),o("labelAutoAngle",s,l)}};l("source"),l("target"),this.applyLabelDimensions(e)}},au.applyLabelDimensions=function(e){this.applyPrefixedLabelDimensions(e),e.isEdge()&&(this.applyPrefixedLabelDimensions(e,"source"),this.applyPrefixedLabelDimensions(e,"target"))},au.applyPrefixedLabelDimensions=function(e,t){var n=e._private,r=this.getLabelText(e,t),i=this.calculateLabelDimensions(e,r),a=e.pstyle("line-height").pfValue,o=e.pstyle("text-wrap").strValue,s=Ue(n.rscratch,"labelWrapCachedLines",t)||[],l="wrap"!==o?1:Math.max(s.length,1),u=i.height/l,c=u*a,d=i.width,h=i.height+(l-1)*(a-1)*u;Ze(n.rstyle,"labelWidth",t,d),Ze(n.rscratch,"labelWidth",t,d),Ze(n.rstyle,"labelHeight",t,h),Ze(n.rscratch,"labelHeight",t,h),Ze(n.rscratch,"labelLineHeight",t,c)},au.getLabelText=function(e,t){var n=e._private,r=t?t+"-":"",i=e.pstyle(r+"label").strValue,a=e.pstyle("text-transform").value,o=function(e,r){return r?(Ze(n.rscratch,e,t,r),r):Ue(n.rscratch,e,t)};if(!i)return"";"none"==a||("uppercase"==a?i=i.toUpperCase():"lowercase"==a&&(i=i.toLowerCase()));var s=e.pstyle("text-wrap").value;if("wrap"===s){var u=o("labelKey");if(null!=u&&o("labelWrapKey")===u)return o("labelWrapCachedText");for(var c=i.split("\n"),d=e.pstyle("text-max-width").pfValue,h="anywhere"===e.pstyle("text-overflow-wrap").value,p=[],f=/[\s\u200b]+|$/g,g=0;gd){var b,x="",w=0,E=l(v.matchAll(f));try{for(E.s();!(b=E.n()).done;){var k=b.value,C=k[0],S=v.substring(w,k.index);w=k.index+C.length;var P=0===x.length?S:x+S+C;this.calculateLabelDimensions(e,P).width<=d?x+=S+C:(x&&p.push(x),x=S+C)}}catch(e){E.e(e)}finally{E.f()}x.match(/^[\s\u200b]+$/)||p.push(x)}else p.push(v)}o("labelWrapCachedLines",p),i=o("labelWrapCachedText",p.join("\n")),o("labelWrapKey",u)}else if("ellipsis"===s){var D=e.pstyle("text-max-width").pfValue,T="",_=!1;if(this.calculateLabelDimensions(e,i).widthD)break;T+=i[M],M===i.length-1&&(_=!0)}return _||(T+="…"),T}return i},au.getLabelJustification=function(e){var t=e.pstyle("text-justification").strValue,n=e.pstyle("text-halign").strValue;if("auto"!==t)return t;if(!e.isNode())return"center";switch(n){case"left":return"right";case"right":return"left";default:return"center"}},au.calculateLabelDimensions=function(e,t){var n=this,r=n.cy.window().document,i=Te(t,e._private.labelDimsKey),a=n.labelDimCache||(n.labelDimCache=[]),o=a[i];if(null!=o)return o;var s=e.pstyle("font-style").strValue,l=e.pstyle("font-size").pfValue,u=e.pstyle("font-family").strValue,c=e.pstyle("font-weight").strValue,d=this.labelCalcCanvas,h=this.labelCalcCanvasContext;if(!d){d=this.labelCalcCanvas=r.createElement("canvas"),h=this.labelCalcCanvasContext=d.getContext("2d");var p=d.style;p.position="absolute",p.left="-9999px",p.top="-9999px",p.zIndex="-1",p.visibility="hidden",p.pointerEvents="none"}h.font="".concat(s," ").concat(c," ").concat(l,"px ").concat(u);for(var f=0,g=0,v=t.split("\n"),y=0;y1&&void 0!==arguments[1])||arguments[1];if(t.merge(e),n)for(var r=0;r=e.desktopTapThreshold2}var D=i(t);v&&(e.hoverData.tapholdCancelled=!0);n=!0,r(g,["mousemove","vmousemove","tapdrag"],t,{x:c[0],y:c[1]});var T=function(){e.data.bgActivePosistion=void 0,e.hoverData.selecting||o.emit({originalEvent:t,type:"boxstart",position:{x:c[0],y:c[1]}}),f[4]=1,e.hoverData.selecting=!0,e.redrawHint("select",!0),e.redraw()};if(3===e.hoverData.which){if(v){var _={originalEvent:t,type:"cxtdrag",position:{x:c[0],y:c[1]}};m?m.emit(_):o.emit(_),e.hoverData.cxtDragged=!0,e.hoverData.cxtOver&&g===e.hoverData.cxtOver||(e.hoverData.cxtOver&&e.hoverData.cxtOver.emit({originalEvent:t,type:"cxtdragout",position:{x:c[0],y:c[1]}}),e.hoverData.cxtOver=g,g&&g.emit({originalEvent:t,type:"cxtdragover",position:{x:c[0],y:c[1]}}))}}else if(e.hoverData.dragging){if(n=!0,o.panningEnabled()&&o.userPanningEnabled()){var M;if(e.hoverData.justStartedPan){var B=e.hoverData.mdownPos;M={x:(c[0]-B[0])*s,y:(c[1]-B[1])*s},e.hoverData.justStartedPan=!1}else M={x:b[0]*s,y:b[1]*s};o.panBy(M),o.emit("dragpan"),e.hoverData.dragged=!0}c=e.projectIntoViewport(t.clientX,t.clientY)}else if(1!=f[4]||null!=m&&!m.pannable()){if(m&&m.pannable()&&m.active()&&m.unactivate(),m&&m.grabbed()||g==y||(y&&r(y,["mouseout","tapdragout"],t,{x:c[0],y:c[1]}),g&&r(g,["mouseover","tapdragover"],t,{x:c[0],y:c[1]}),e.hoverData.last=g),m)if(v){if(o.boxSelectionEnabled()&&D)m&&m.grabbed()&&(d(w),m.emit("freeon"),w.emit("free"),e.dragData.didDrag&&(m.emit("dragfreeon"),w.emit("dragfree"))),T();else if(m&&m.grabbed()&&e.nodeIsDraggable(m)){var N=!e.dragData.didDrag;N&&e.redrawHint("eles",!0),e.dragData.didDrag=!0,e.hoverData.draggingEles||u(w,{inDragLayer:!0});var z={x:0,y:0};if(x(b[0])&&x(b[1])&&(z.x+=b[0],z.y+=b[1],N)){var I=e.hoverData.dragDelta;I&&x(I[0])&&x(I[1])&&(z.x+=I[0],z.y+=I[1])}e.hoverData.draggingEles=!0,w.silentShift(z).emit("position drag"),e.redrawHint("drag",!0),e.redraw()}}else!function(){var t=e.hoverData.dragDelta=e.hoverData.dragDelta||[];0===t.length?(t.push(b[0]),t.push(b[1])):(t[0]+=b[0],t[1]+=b[1])}();n=!0}else if(v){if(e.hoverData.dragging||!o.boxSelectionEnabled()||!D&&o.panningEnabled()&&o.userPanningEnabled()){if(!e.hoverData.selecting&&o.panningEnabled()&&o.userPanningEnabled()){a(m,e.hoverData.downs)&&(e.hoverData.dragging=!0,e.hoverData.justStartedPan=!0,f[4]=0,e.data.bgActivePosistion=bt(h),e.redrawHint("select",!0),e.redraw())}}else T();m&&m.pannable()&&m.active()&&m.unactivate()}return f[2]=c[0],f[3]=c[1],n?(t.stopPropagation&&t.stopPropagation(),t.preventDefault&&t.preventDefault(),!1):void 0}}),!1),e.registerBinding(t,"mouseup",(function(t){if((1!==e.hoverData.which||1===t.which||!e.hoverData.capture)&&e.hoverData.capture){e.hoverData.capture=!1;var a=e.cy,o=e.projectIntoViewport(t.clientX,t.clientY),s=e.selection,l=e.findNearestElement(o[0],o[1],!0,!1),u=e.dragData.possibleDragElements,c=e.hoverData.down,h=i(t);if(e.data.bgActivePosistion&&(e.redrawHint("select",!0),e.redraw()),e.hoverData.tapholdCancelled=!0,e.data.bgActivePosistion=void 0,c&&c.unactivate(),3===e.hoverData.which){var p={originalEvent:t,type:"cxttapend",position:{x:o[0],y:o[1]}};if(c?c.emit(p):a.emit(p),!e.hoverData.cxtDragged){var f={originalEvent:t,type:"cxttap",position:{x:o[0],y:o[1]}};c?c.emit(f):a.emit(f)}e.hoverData.cxtDragged=!1,e.hoverData.which=null}else if(1===e.hoverData.which){if(r(l,["mouseup","tapend","vmouseup"],t,{x:o[0],y:o[1]}),e.dragData.didDrag||e.hoverData.dragged||e.hoverData.selecting||e.hoverData.isOverThresholdDrag||(r(c,["click","tap","vclick"],t,{x:o[0],y:o[1]}),b=!1,t.timeStamp-w<=a.multiClickDebounceTime()?(m&&clearTimeout(m),b=!0,w=null,r(c,["dblclick","dbltap","vdblclick"],t,{x:o[0],y:o[1]})):(m=setTimeout((function(){b||r(c,["oneclick","onetap","voneclick"],t,{x:o[0],y:o[1]})}),a.multiClickDebounceTime()),w=t.timeStamp)),null!=c||e.dragData.didDrag||e.hoverData.selecting||e.hoverData.dragged||i(t)||(a.$(n).unselect(["tapunselect"]),u.length>0&&e.redrawHint("eles",!0),e.dragData.possibleDragElements=u=a.collection()),l!=c||e.dragData.didDrag||e.hoverData.selecting||null!=l&&l._private.selectable&&(e.hoverData.dragging||("additive"===a.selectionType()||h?l.selected()?l.unselect(["tapunselect"]):l.select(["tapselect"]):h||(a.$(n).unmerge(l).unselect(["tapunselect"]),l.select(["tapselect"]))),e.redrawHint("eles",!0)),e.hoverData.selecting){var g=a.collection(e.getAllInBox(s[0],s[1],s[2],s[3]));e.redrawHint("select",!0),g.length>0&&e.redrawHint("eles",!0),a.emit({type:"boxend",originalEvent:t,position:{x:o[0],y:o[1]}});var v=function(e){return e.selectable()&&!e.selected()};"additive"===a.selectionType()||h||a.$(n).unmerge(g).unselect(),g.emit("box").stdFilter(v).select().emit("boxselect"),e.redraw()}if(e.hoverData.dragging&&(e.hoverData.dragging=!1,e.redrawHint("select",!0),e.redrawHint("eles",!0),e.redraw()),!s[4]){e.redrawHint("drag",!0),e.redrawHint("eles",!0);var y=c&&c.grabbed();d(u),y&&(c.emit("freeon"),u.emit("free"),e.dragData.didDrag&&(c.emit("dragfreeon"),u.emit("dragfree")))}}s[4]=0,e.hoverData.down=null,e.hoverData.cxtStarted=!1,e.hoverData.draggingEles=!1,e.hoverData.selecting=!1,e.hoverData.isOverThresholdDrag=!1,e.dragData.didDrag=!1,e.hoverData.dragged=!1,e.hoverData.dragDelta=[],e.hoverData.mdownPos=null,e.hoverData.mdownGPos=null,e.hoverData.which=null}}),!1);var k,C,S,P,D,T,_,M,B,N,z,I,A,L=function(t){if(!e.scrollingPage){var n=e.cy,r=n.zoom(),i=n.pan(),a=e.projectIntoViewport(t.clientX,t.clientY),o=[a[0]*r+i.x,a[1]*r+i.y];if(e.hoverData.draggingEles||e.hoverData.dragging||e.hoverData.cxtStarted||0!==e.selection[4])t.preventDefault();else if(n.panningEnabled()&&n.userPanningEnabled()&&n.zoomingEnabled()&&n.userZoomingEnabled()){var s;t.preventDefault(),e.data.wheelZooming=!0,clearTimeout(e.data.wheelTimeout),e.data.wheelTimeout=setTimeout((function(){e.data.wheelZooming=!1,e.redrawHint("eles",!0),e.redraw()}),150),s=null!=t.deltaY?t.deltaY/-250:null!=t.wheelDeltaY?t.wheelDeltaY/1e3:t.wheelDelta/1e3,s*=e.wheelSensitivity,1===t.deltaMode&&(s*=33);var l=n.zoom()*Math.pow(10,s);"gesturechange"===t.type&&(l=e.gestureStartZoom*t.scale),n.zoom({level:l,renderedPosition:{x:o[0],y:o[1]}}),n.emit("gesturechange"===t.type?"pinchzoom":"scrollzoom")}}};e.registerBinding(e.container,"wheel",L,!0),e.registerBinding(t,"scroll",(function(t){e.scrollingPage=!0,clearTimeout(e.scrollingPageTimeout),e.scrollingPageTimeout=setTimeout((function(){e.scrollingPage=!1}),250)}),!0),e.registerBinding(e.container,"gesturestart",(function(t){e.gestureStartZoom=e.cy.zoom(),e.hasTouchStarted||t.preventDefault()}),!0),e.registerBinding(e.container,"gesturechange",(function(t){e.hasTouchStarted||L(t)}),!0),e.registerBinding(e.container,"mouseout",(function(t){var n=e.projectIntoViewport(t.clientX,t.clientY);e.cy.emit({originalEvent:t,type:"mouseout",position:{x:n[0],y:n[1]}})}),!1),e.registerBinding(e.container,"mouseover",(function(t){var n=e.projectIntoViewport(t.clientX,t.clientY);e.cy.emit({originalEvent:t,type:"mouseover",position:{x:n[0],y:n[1]}})}),!1);var O,R,V,F,j,q,Y,X=function(e,t,n,r){return Math.sqrt((n-e)*(n-e)+(r-t)*(r-t))},W=function(e,t,n,r){return(n-e)*(n-e)+(r-t)*(r-t)};if(e.registerBinding(e.container,"touchstart",O=function(t){if(e.hasTouchStarted=!0,E(t)){p(),e.touchData.capture=!0,e.data.bgActivePosistion=void 0;var n=e.cy,i=e.touchData.now,a=e.touchData.earlier;if(t.touches[0]){var o=e.projectIntoViewport(t.touches[0].clientX,t.touches[0].clientY);i[0]=o[0],i[1]=o[1]}if(t.touches[1]){o=e.projectIntoViewport(t.touches[1].clientX,t.touches[1].clientY);i[2]=o[0],i[3]=o[1]}if(t.touches[2]){o=e.projectIntoViewport(t.touches[2].clientX,t.touches[2].clientY);i[4]=o[0],i[5]=o[1]}if(t.touches[1]){e.touchData.singleTouchMoved=!0,d(e.dragData.touchDragEles);var l=e.findContainerClientCoords();B=l[0],N=l[1],z=l[2],I=l[3],k=t.touches[0].clientX-B,C=t.touches[0].clientY-N,S=t.touches[1].clientX-B,P=t.touches[1].clientY-N,A=0<=k&&k<=z&&0<=S&&S<=z&&0<=C&&C<=I&&0<=P&&P<=I;var h=n.pan(),f=n.zoom();D=X(k,C,S,P),T=W(k,C,S,P),M=[((_=[(k+S)/2,(C+P)/2])[0]-h.x)/f,(_[1]-h.y)/f];if(T<4e4&&!t.touches[2]){var g=e.findNearestElement(i[0],i[1],!0,!0),v=e.findNearestElement(i[2],i[3],!0,!0);return g&&g.isNode()?(g.activate().emit({originalEvent:t,type:"cxttapstart",position:{x:i[0],y:i[1]}}),e.touchData.start=g):v&&v.isNode()?(v.activate().emit({originalEvent:t,type:"cxttapstart",position:{x:i[0],y:i[1]}}),e.touchData.start=v):n.emit({originalEvent:t,type:"cxttapstart",position:{x:i[0],y:i[1]}}),e.touchData.start&&(e.touchData.start._private.grabbed=!1),e.touchData.cxt=!0,e.touchData.cxtDragged=!1,e.data.bgActivePosistion=void 0,void e.redraw()}}if(t.touches[2])n.boxSelectionEnabled()&&t.preventDefault();else if(t.touches[1]);else if(t.touches[0]){var y=e.findNearestElements(i[0],i[1],!0,!0),m=y[0];if(null!=m&&(m.activate(),e.touchData.start=m,e.touchData.starts=y,e.nodeIsGrabbable(m))){var b=e.dragData.touchDragEles=n.collection(),x=null;e.redrawHint("eles",!0),e.redrawHint("drag",!0),m.selected()?(x=n.$((function(t){return t.selected()&&e.nodeIsGrabbable(t)})),u(x,{addToList:b})):c(m,{addToList:b}),s(m);var w=function(e){return{originalEvent:t,type:e,position:{x:i[0],y:i[1]}}};m.emit(w("grabon")),x?x.forEach((function(e){e.emit(w("grab"))})):m.emit(w("grab"))}r(m,["touchstart","tapstart","vmousedown"],t,{x:i[0],y:i[1]}),null==m&&(e.data.bgActivePosistion={x:o[0],y:o[1]},e.redrawHint("select",!0),e.redraw()),e.touchData.singleTouchMoved=!1,e.touchData.singleTouchStartTime=+new Date,clearTimeout(e.touchData.tapholdTimeout),e.touchData.tapholdTimeout=setTimeout((function(){!1!==e.touchData.singleTouchMoved||e.pinching||e.touchData.selecting||r(e.touchData.start,["taphold"],t,{x:i[0],y:i[1]})}),e.tapholdDuration)}if(t.touches.length>=1){for(var L=e.touchData.startPosition=[null,null,null,null,null,null],O=0;O=e.touchTapThreshold2}if(n&&e.touchData.cxt){t.preventDefault();var w=t.touches[0].clientX-B,_=t.touches[0].clientY-N,z=t.touches[1].clientX-B,I=t.touches[1].clientY-N,L=W(w,_,z,I);if(L/T>=2.25||L>=22500){e.touchData.cxt=!1,e.data.bgActivePosistion=void 0,e.redrawHint("select",!0);var O={originalEvent:t,type:"cxttapend",position:{x:s[0],y:s[1]}};e.touchData.start?(e.touchData.start.unactivate().emit(O),e.touchData.start=null):o.emit(O)}}if(n&&e.touchData.cxt){O={originalEvent:t,type:"cxtdrag",position:{x:s[0],y:s[1]}};e.data.bgActivePosistion=void 0,e.redrawHint("select",!0),e.touchData.start?e.touchData.start.emit(O):o.emit(O),e.touchData.start&&(e.touchData.start._private.grabbed=!1),e.touchData.cxtDragged=!0;var R=e.findNearestElement(s[0],s[1],!0,!0);e.touchData.cxtOver&&R===e.touchData.cxtOver||(e.touchData.cxtOver&&e.touchData.cxtOver.emit({originalEvent:t,type:"cxtdragout",position:{x:s[0],y:s[1]}}),e.touchData.cxtOver=R,R&&R.emit({originalEvent:t,type:"cxtdragover",position:{x:s[0],y:s[1]}}))}else if(n&&t.touches[2]&&o.boxSelectionEnabled())t.preventDefault(),e.data.bgActivePosistion=void 0,this.lastThreeTouch=+new Date,e.touchData.selecting||o.emit({originalEvent:t,type:"boxstart",position:{x:s[0],y:s[1]}}),e.touchData.selecting=!0,e.touchData.didSelect=!0,i[4]=1,i&&0!==i.length&&void 0!==i[0]?(i[2]=(s[0]+s[2]+s[4])/3,i[3]=(s[1]+s[3]+s[5])/3):(i[0]=(s[0]+s[2]+s[4])/3,i[1]=(s[1]+s[3]+s[5])/3,i[2]=(s[0]+s[2]+s[4])/3+1,i[3]=(s[1]+s[3]+s[5])/3+1),e.redrawHint("select",!0),e.redraw();else if(n&&t.touches[1]&&!e.touchData.didSelect&&o.zoomingEnabled()&&o.panningEnabled()&&o.userZoomingEnabled()&&o.userPanningEnabled()){if(t.preventDefault(),e.data.bgActivePosistion=void 0,e.redrawHint("select",!0),ee=e.dragData.touchDragEles){e.redrawHint("drag",!0);for(var V=0;V0&&!e.hoverData.draggingEles&&!e.swipePanning&&null!=e.data.bgActivePosistion&&(e.data.bgActivePosistion=void 0,e.redrawHint("select",!0),e.redraw())}},!1),e.registerBinding(t,"touchcancel",V=function(t){var n=e.touchData.start;e.touchData.capture=!1,n&&n.unactivate()}),e.registerBinding(t,"touchend",F=function(t){var i=e.touchData.start;if(e.touchData.capture){0===t.touches.length&&(e.touchData.capture=!1),t.preventDefault();var a=e.selection;e.swipePanning=!1,e.hoverData.draggingEles=!1;var o,s=e.cy,l=s.zoom(),u=e.touchData.now,c=e.touchData.earlier;if(t.touches[0]){var h=e.projectIntoViewport(t.touches[0].clientX,t.touches[0].clientY);u[0]=h[0],u[1]=h[1]}if(t.touches[1]){h=e.projectIntoViewport(t.touches[1].clientX,t.touches[1].clientY);u[2]=h[0],u[3]=h[1]}if(t.touches[2]){h=e.projectIntoViewport(t.touches[2].clientX,t.touches[2].clientY);u[4]=h[0],u[5]=h[1]}if(i&&i.unactivate(),e.touchData.cxt){if(o={originalEvent:t,type:"cxttapend",position:{x:u[0],y:u[1]}},i?i.emit(o):s.emit(o),!e.touchData.cxtDragged){var p={originalEvent:t,type:"cxttap",position:{x:u[0],y:u[1]}};i?i.emit(p):s.emit(p)}return e.touchData.start&&(e.touchData.start._private.grabbed=!1),e.touchData.cxt=!1,e.touchData.start=null,void e.redraw()}if(!t.touches[2]&&s.boxSelectionEnabled()&&e.touchData.selecting){e.touchData.selecting=!1;var f=s.collection(e.getAllInBox(a[0],a[1],a[2],a[3]));a[0]=void 0,a[1]=void 0,a[2]=void 0,a[3]=void 0,a[4]=0,e.redrawHint("select",!0),s.emit({type:"boxend",originalEvent:t,position:{x:u[0],y:u[1]}});f.emit("box").stdFilter((function(e){return e.selectable()&&!e.selected()})).select().emit("boxselect"),f.nonempty()&&e.redrawHint("eles",!0),e.redraw()}if(null!=i&&i.unactivate(),t.touches[2])e.data.bgActivePosistion=void 0,e.redrawHint("select",!0);else if(t.touches[1]);else if(t.touches[0]);else if(!t.touches[0]){e.data.bgActivePosistion=void 0,e.redrawHint("select",!0);var g=e.dragData.touchDragEles;if(null!=i){var v=i._private.grabbed;d(g),e.redrawHint("drag",!0),e.redrawHint("eles",!0),v&&(i.emit("freeon"),g.emit("free"),e.dragData.didDrag&&(i.emit("dragfreeon"),g.emit("dragfree"))),r(i,["touchend","tapend","vmouseup","tapdragout"],t,{x:u[0],y:u[1]}),i.unactivate(),e.touchData.start=null}else{var y=e.findNearestElement(u[0],u[1],!0,!0);r(y,["touchend","tapend","vmouseup","tapdragout"],t,{x:u[0],y:u[1]})}var m=e.touchData.startPosition[0]-u[0],b=m*m,x=e.touchData.startPosition[1]-u[1],w=(b+x*x)*l*l;e.touchData.singleTouchMoved||(i||s.$(":selected").unselect(["tapunselect"]),r(i,["tap","vclick"],t,{x:u[0],y:u[1]}),j=!1,t.timeStamp-Y<=s.multiClickDebounceTime()?(q&&clearTimeout(q),j=!0,Y=null,r(i,["dbltap","vdblclick"],t,{x:u[0],y:u[1]})):(q=setTimeout((function(){j||r(i,["onetap","voneclick"],t,{x:u[0],y:u[1]})}),s.multiClickDebounceTime()),Y=t.timeStamp)),null!=i&&!e.dragData.didDrag&&i._private.selectable&&w2){for(var p=[c[0],c[1]],f=Math.pow(p[0]-e,2)+Math.pow(p[1]-t,2),g=1;g0)return g[0]}return null},p=Object.keys(d),f=0;f0?u:Rt(i,a,e,t,n,r,o,s)},checkPoint:function(e,t,n,r,i,a,o,s){var l=2*(s="auto"===s?nn(r,i):s);if(Xt(e,t,this.points,a,o,r,i-l,[0,-1],n))return!0;if(Xt(e,t,this.points,a,o,r-l,i,[0,-1],n))return!0;var u=r/2+2*n,c=i/2+2*n;return!!Yt(e,t,[a-u,o-c,a-u,o,a+u,o,a+u,o-c])||(!!Kt(e,t,l,l,a+r/2-s,o+i/2-s,n)||!!Kt(e,t,l,l,a-r/2+s,o+i/2-s,n))}}},gu.registerNodeShapes=function(){var e=this.nodeShapes={},t=this;this.generateEllipse(),this.generatePolygon("triangle",Jt(3,0)),this.generateRoundPolygon("round-triangle",Jt(3,0)),this.generatePolygon("rectangle",Jt(4,0)),e.square=e.rectangle,this.generateRoundRectangle(),this.generateCutRectangle(),this.generateBarrel(),this.generateBottomRoundrectangle();var n=[0,1,1,0,0,-1,-1,0];this.generatePolygon("diamond",n),this.generateRoundPolygon("round-diamond",n),this.generatePolygon("pentagon",Jt(5,0)),this.generateRoundPolygon("round-pentagon",Jt(5,0)),this.generatePolygon("hexagon",Jt(6,0)),this.generateRoundPolygon("round-hexagon",Jt(6,0)),this.generatePolygon("heptagon",Jt(7,0)),this.generateRoundPolygon("round-heptagon",Jt(7,0)),this.generatePolygon("octagon",Jt(8,0)),this.generateRoundPolygon("round-octagon",Jt(8,0));var r=new Array(20),i=tn(5,0),a=tn(5,Math.PI/5),o=.5*(3-Math.sqrt(5));o*=1.57;for(var s=0;s=e.deqFastCost*g)break}else if(i){if(p>=e.deqCost*l||p>=e.deqAvgCost*s)break}else if(f>=e.deqNoDrawCost*(1e3/60))break;var v=e.deq(t,d,c);if(!(v.length>0))break;for(var y=0;y0&&(e.onDeqd(t,u),!i&&e.shouldRedraw(t,u,d,c)&&r())}),i(t))}}},wu=function(){function e(n){var r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:Le;t(this,e),this.idsByKey=new $e,this.keyForId=new $e,this.cachesByLvl=new $e,this.lvls=[],this.getKey=n,this.doesEleInvalidateKey=r}return r(e,[{key:"getIdsFor",value:function(e){null==e&&Ve("Can not get id list for null key");var t=this.idsByKey,n=this.idsByKey.get(e);return n||(n=new Je,t.set(e,n)),n}},{key:"addIdForKey",value:function(e,t){null!=e&&this.getIdsFor(e).add(t)}},{key:"deleteIdForKey",value:function(e,t){null!=e&&this.getIdsFor(e).delete(t)}},{key:"getNumberOfIdsForKey",value:function(e){return null==e?0:this.getIdsFor(e).size}},{key:"updateKeyMappingFor",value:function(e){var t=e.id(),n=this.keyForId.get(t),r=this.getKey(e);this.deleteIdForKey(n,t),this.addIdForKey(r,t),this.keyForId.set(t,r)}},{key:"deleteKeyMappingFor",value:function(e){var t=e.id(),n=this.keyForId.get(t);this.deleteIdForKey(n,t),this.keyForId.delete(t)}},{key:"keyHasChangedFor",value:function(e){var t=e.id();return this.keyForId.get(t)!==this.getKey(e)}},{key:"isInvalid",value:function(e){return this.keyHasChangedFor(e)||this.doesEleInvalidateKey(e)}},{key:"getCachesAt",value:function(e){var t=this.cachesByLvl,n=this.lvls,r=t.get(e);return r||(r=new $e,t.set(e,r),n.push(e)),r}},{key:"getCache",value:function(e,t){return this.getCachesAt(t).get(e)}},{key:"get",value:function(e,t){var n=this.getKey(e),r=this.getCache(n,t);return null!=r&&this.updateKeyMappingFor(e),r}},{key:"getForCachedKey",value:function(e,t){var n=this.keyForId.get(e.id());return this.getCache(n,t)}},{key:"hasCache",value:function(e,t){return this.getCachesAt(t).has(e)}},{key:"has",value:function(e,t){var n=this.getKey(e);return this.hasCache(n,t)}},{key:"setCache",value:function(e,t,n){n.key=e,this.getCachesAt(t).set(e,n)}},{key:"set",value:function(e,t,n){var r=this.getKey(e);this.setCache(r,t,n),this.updateKeyMappingFor(e)}},{key:"deleteCache",value:function(e,t){this.getCachesAt(t).delete(e)}},{key:"delete",value:function(e,t){var n=this.getKey(e);this.deleteCache(n,t)}},{key:"invalidateKey",value:function(e){var t=this;this.lvls.forEach((function(n){return t.deleteCache(e,n)}))}},{key:"invalidate",value:function(e){var t=e.id(),n=this.keyForId.get(t);this.deleteKeyMappingFor(e);var r=this.doesEleInvalidateKey(e);return r&&this.invalidateKey(n),r||0===this.getNumberOfIdsForKey(n)}}]),e}(),Eu={dequeue:"dequeue",downscale:"downscale",highQuality:"highQuality"},ku=He({getKey:null,doesEleInvalidateKey:Le,drawElement:null,getBoundingBox:null,getRotationPoint:null,getRotationOffset:null,isVisible:Ae,allowEdgeTxrCaching:!0,allowParentTxrCaching:!0}),Cu=function(e,t){this.renderer=e,this.onDequeues=[];var n=ku(t);L(this,n),this.lookup=new wu(n.getKey,n.doesEleInvalidateKey),this.setupDequeueing()},Su=Cu.prototype;Su.reasons=Eu,Su.getTextureQueue=function(e){return this.eleImgCaches=this.eleImgCaches||{},this.eleImgCaches[e]=this.eleImgCaches[e]||[]},Su.getRetiredTextureQueue=function(e){var t=this.eleImgCaches.retired=this.eleImgCaches.retired||{};return t[e]=t[e]||[]},Su.getElementQueue=function(){return this.eleCacheQueue=this.eleCacheQueue||new rt((function(e,t){return t.reqs-e.reqs}))},Su.getElementKeyToQueue=function(){return this.eleKeyToCacheQueue=this.eleKeyToCacheQueue||{}},Su.getElement=function(e,t,n,r,i){var a=this,o=this.renderer,s=o.cy.zoom(),l=this.lookup;if(!t||0===t.w||0===t.h||isNaN(t.w)||isNaN(t.h)||!e.visible()||e.removed())return null;if(!a.allowEdgeTxrCaching&&e.isEdge()||!a.allowParentTxrCaching&&e.isParent())return null;if(null==r&&(r=Math.ceil(wt(s*n))),r<-4)r=-4;else if(s>=7.99||r>3)return null;var u=Math.pow(2,r),c=t.h*u,d=t.w*u,h=o.eleTextBiggerThanMin(e,u);if(!this.isVisible(e,h))return null;var p,f=l.get(e,r);if(f&&f.invalidated&&(f.invalidated=!1,f.texture.invalidatedWidth-=f.width),f)return f;if(p=c<=25?25:c<=50?50:50*Math.ceil(c/50),c>1024||d>1024)return null;var g=a.getTextureQueue(p),v=g[g.length-2],y=function(){return a.recycleTexture(p,d)||a.addTexture(p,d)};v||(v=g[g.length-1]),v||(v=y()),v.width-v.usedWidthr;D--)S=a.getElement(e,t,n,D,Eu.downscale);P()}else{var T;if(!x&&!w&&!E)for(var _=r-1;_>=-4;_--){var M=l.get(e,_);if(M){T=M;break}}if(b(T))return a.queueElement(e,r),T;v.context.translate(v.usedWidth,0),v.context.scale(u,u),this.drawElement(v.context,e,t,h,!1),v.context.scale(1/u,1/u),v.context.translate(-v.usedWidth,0)}return f={x:v.usedWidth,texture:v,level:r,scale:u,width:d,height:c,scaledLabelShown:h},v.usedWidth+=Math.ceil(d+8),v.eleCaches.push(f),l.set(e,r,f),a.checkTextureFullness(v),f},Su.invalidateElements=function(e){for(var t=0;t=.2*e.width&&this.retireTexture(e)},Su.checkTextureFullness=function(e){var t=this.getTextureQueue(e.height);e.usedWidth/e.width>.8&&e.fullnessChecks>=10?Ke(t,e):e.fullnessChecks++},Su.retireTexture=function(e){var t=e.height,n=this.getTextureQueue(t),r=this.lookup;Ke(n,e),e.retired=!0;for(var i=e.eleCaches,a=0;a=t)return a.retired=!1,a.usedWidth=0,a.invalidatedWidth=0,a.fullnessChecks=0,Ge(a.eleCaches),a.context.setTransform(1,0,0,1,0,0),a.context.clearRect(0,0,a.width,a.height),Ke(r,a),n.push(a),a}},Su.queueElement=function(e,t){var n=this.getElementQueue(),r=this.getElementKeyToQueue(),i=this.getKey(e),a=r[i];if(a)a.level=Math.max(a.level,t),a.eles.merge(e),a.reqs++,n.updateItem(a);else{var o={eles:e.spawn().merge(e),level:t,reqs:1,key:i};n.push(o),r[i]=o}},Su.dequeue=function(e){for(var t=this.getElementQueue(),n=this.getElementKeyToQueue(),r=[],i=this.lookup,a=0;a<1&&t.size()>0;a++){var o=t.pop(),s=o.key,l=o.eles[0],u=i.hasCache(l,o.level);if(n[s]=null,!u){r.push(o);var c=this.getBoundingBox(l);this.getElement(l,c,e,o.level,Eu.dequeue)}}return r},Su.removeFromQueue=function(e){var t=this.getElementQueue(),n=this.getElementKeyToQueue(),r=this.getKey(e),i=n[r];null!=i&&(1===i.eles.length?(i.reqs=Ie,t.updateItem(i),t.pop(),n[r]=null):i.eles.unmerge(e))},Su.onDequeue=function(e){this.onDequeues.push(e)},Su.offDequeue=function(e){Ke(this.onDequeues,e)},Su.setupDequeueing=xu({deqRedrawThreshold:100,deqCost:.15,deqAvgCost:.1,deqNoDrawCost:.9,deqFastCost:.9,deq:function(e,t,n){return e.dequeue(t,n)},onDeqd:function(e,t){for(var n=0;n=3.99||n>2)return null;r.validateLayersElesOrdering(n,e);var o,s,l=r.layersByLevel,u=Math.pow(2,n),c=l[n]=l[n]||[];if(r.levelIsComplete(n,e))return c;!function(){var t=function(t){if(r.validateLayersElesOrdering(t,e),r.levelIsComplete(t,e))return s=l[t],!0},i=function(e){if(!s)for(var r=n+e;-4<=r&&r<=2&&!t(r);r+=e);};i(1),i(-1);for(var a=c.length-1;a>=0;a--){var o=c[a];o.invalid&&Ke(c,o)}}();var d=function(t){var i=(t=t||{}).after;if(function(){if(!o){o=_t();for(var t=0;t16e6)return null;var a=r.makeLayer(o,n);if(null!=i){var s=c.indexOf(i)+1;c.splice(s,0,a)}else(void 0===t.insert||t.insert)&&c.unshift(a);return a};if(r.skipping&&!a)return null;for(var h=null,p=e.length/1,f=!a,g=0;g=p||!Ot(h.bb,v.boundingBox()))&&!(h=d({insert:!0,after:h})))return null;s||f?r.queueLayer(h,v):r.drawEleInLayer(h,v,n,t),h.eles.push(v),m[n]=h}}return s||(f?null:c)},Du.getEleLevelForLayerLevel=function(e,t){return e},Du.drawEleInLayer=function(e,t,n,r){var i=this.renderer,a=e.context,o=t.boundingBox();0!==o.w&&0!==o.h&&t.visible()&&(n=this.getEleLevelForLayerLevel(n,r),i.setImgSmoothing(a,!1),i.drawCachedElement(a,t,null,null,n,!0),i.setImgSmoothing(a,!0))},Du.levelIsComplete=function(e,t){var n=this.layersByLevel[e];if(!n||0===n.length)return!1;for(var r=0,i=0;i0)return!1;if(a.invalid)return!1;r+=a.eles.length}return r===t.length},Du.validateLayersElesOrdering=function(e,t){var n=this.layersByLevel[e];if(n)for(var r=0;r0){e=!0;break}}return e},Du.invalidateElements=function(e){var t=this;0!==e.length&&(t.lastInvalidationTime=we(),0!==e.length&&t.haveLayers()&&t.updateElementsInLayers(e,(function(e,n,r){t.invalidateLayer(e)})))},Du.invalidateLayer=function(e){if(this.lastInvalidationTime=we(),!e.invalid){var t=e.level,n=e.eles,r=this.layersByLevel[t];Ke(r,e),e.elesQueue=[],e.invalid=!0,e.replacement&&(e.replacement.invalid=!0);for(var i=0;i3&&void 0!==arguments[3])||arguments[3],i=!(arguments.length>4&&void 0!==arguments[4])||arguments[4],a=!(arguments.length>5&&void 0!==arguments[5])||arguments[5],o=this,s=t._private.rscratch;if((!a||t.visible())&&!s.badLine&&null!=s.allpts&&!isNaN(s.allpts[0])){var l;n&&(l=n,e.translate(-l.x1,-l.y1));var u=a?t.pstyle("opacity").value:1,c=a?t.pstyle("line-opacity").value:1,d=t.pstyle("curve-style").value,h=t.pstyle("line-style").value,p=t.pstyle("width").pfValue,f=t.pstyle("line-cap").value,g=t.pstyle("line-outline-width").value,v=t.pstyle("line-outline-color").value,y=u*c,m=u*c,b=function(){var n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:y;"straight-triangle"===d?(o.eleStrokeStyle(e,t,n),o.drawEdgeTrianglePath(t,e,s.allpts)):(e.lineWidth=p,e.lineCap=f,o.eleStrokeStyle(e,t,n),o.drawEdgePath(t,e,s.allpts,h),e.lineCap="butt")},x=function(){var n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:y;e.lineWidth=p+g,e.lineCap=f,g>0?(o.colorStrokeStyle(e,v[0],v[1],v[2],n),"straight-triangle"===d?o.drawEdgeTrianglePath(t,e,s.allpts):(o.drawEdgePath(t,e,s.allpts,h),e.lineCap="butt")):e.lineCap="butt"},w=function(){i&&o.drawEdgeOverlay(e,t)},E=function(){i&&o.drawEdgeUnderlay(e,t)},k=function(){var n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:m;o.drawArrowheads(e,t,n)},C=function(){o.drawElementText(e,t,null,r)};e.lineJoin="round";var S="yes"===t.pstyle("ghost").value;if(S){var P=t.pstyle("ghost-offset-x").pfValue,D=t.pstyle("ghost-offset-y").pfValue,T=t.pstyle("ghost-opacity").value,_=y*T;e.translate(P,D),b(_),k(_),e.translate(-P,-D)}else x();E(),b(),k(),w(),C(),n&&e.translate(l.x1,l.y1)}}},Wu=function(e){if(!["overlay","underlay"].includes(e))throw new Error("Invalid state");return function(t,n){if(n.visible()){var r=n.pstyle("".concat(e,"-opacity")).value;if(0!==r){var i=this,a=i.usePaths(),o=n._private.rscratch,s=2*n.pstyle("".concat(e,"-padding")).pfValue,l=n.pstyle("".concat(e,"-color")).value;t.lineWidth=s,"self"!==o.edgeType||a?t.lineCap="round":t.lineCap="butt",i.colorStrokeStyle(t,l[0],l[1],l[2],r),i.drawEdgePath(n,t,o.allpts,"solid")}}}};Xu.drawEdgeOverlay=Wu("overlay"),Xu.drawEdgeUnderlay=Wu("underlay"),Xu.drawEdgePath=function(e,t,n,r){var i,a=e._private.rscratch,o=t,s=!1,u=this.usePaths(),c=e.pstyle("line-dash-pattern").pfValue,d=e.pstyle("line-dash-offset").pfValue;if(u){var h=n.join("$");a.pathCacheKey&&a.pathCacheKey===h?(i=t=a.pathCache,s=!0):(i=t=new Path2D,a.pathCacheKey=h,a.pathCache=i)}if(o.setLineDash)switch(r){case"dotted":o.setLineDash([1,1]);break;case"dashed":o.setLineDash(c),o.lineDashOffset=d;break;case"solid":o.setLineDash([])}if(!s&&!a.badLine)switch(t.beginPath&&t.beginPath(),t.moveTo(n[0],n[1]),a.edgeType){case"bezier":case"self":case"compound":case"multibezier":for(var p=2;p+35&&void 0!==arguments[5]?arguments[5]:5,o=arguments.length>6?arguments[6]:void 0;e.beginPath(),e.moveTo(t+a,n),e.lineTo(t+r-a,n),e.quadraticCurveTo(t+r,n,t+r,n+a),e.lineTo(t+r,n+i-a),e.quadraticCurveTo(t+r,n+i,t+r-a,n+i),e.lineTo(t+a,n+i),e.quadraticCurveTo(t,n+i,t,n+i-a),e.lineTo(t,n+a),e.quadraticCurveTo(t,n,t+a,n),e.closePath(),o?e.stroke():e.fill()}Ku.eleTextBiggerThanMin=function(e,t){if(!t){var n=e.cy().zoom(),r=this.getPixelRatio(),i=Math.ceil(wt(n*r));t=Math.pow(2,i)}return!(e.pstyle("font-size").pfValue*t5&&void 0!==arguments[5])||arguments[5],o=this;if(null==r){if(a&&!o.eleTextBiggerThanMin(t))return}else if(!1===r)return;if(t.isNode()){var s=t.pstyle("label");if(!s||!s.value)return;var l=o.getLabelJustification(t);e.textAlign=l,e.textBaseline="bottom"}else{var u=t.element()._private.rscratch.badLine,c=t.pstyle("label"),d=t.pstyle("source-label"),h=t.pstyle("target-label");if(u||(!c||!c.value)&&(!d||!d.value)&&(!h||!h.value))return;e.textAlign="center",e.textBaseline="bottom"}var p,f=!n;n&&(p=n,e.translate(-p.x1,-p.y1)),null==i?(o.drawText(e,t,null,f,a),t.isEdge()&&(o.drawText(e,t,"source",f,a),o.drawText(e,t,"target",f,a))):o.drawText(e,t,i,f,a),n&&e.translate(p.x1,p.y1)},Ku.getFontCache=function(e){var t;this.fontCaches=this.fontCaches||[];for(var n=0;n2&&void 0!==arguments[2])||arguments[2],r=t.pstyle("font-style").strValue,i=t.pstyle("font-size").pfValue+"px",a=t.pstyle("font-family").strValue,o=t.pstyle("font-weight").strValue,s=n?t.effectiveOpacity()*t.pstyle("text-opacity").value:1,l=t.pstyle("text-outline-opacity").value*s,u=t.pstyle("color").value,c=t.pstyle("text-outline-color").value;e.font=r+" "+o+" "+i+" "+a,e.lineJoin="round",this.colorFillStyle(e,u[0],u[1],u[2],s),this.colorStrokeStyle(e,c[0],c[1],c[2],l)},Ku.getTextAngle=function(e,t){var n=e._private.rscratch,r=t?t+"-":"",i=e.pstyle(r+"text-rotation"),a=Ue(n,"labelAngle",t);return"autorotate"===i.strValue?e.isEdge()?a:0:"none"===i.strValue?0:i.pfValue},Ku.drawText=function(e,t,n){var r=!(arguments.length>3&&void 0!==arguments[3])||arguments[3],i=!(arguments.length>4&&void 0!==arguments[4])||arguments[4],a=t._private,o=a.rscratch,s=i?t.effectiveOpacity():1;if(!i||0!==s&&0!==t.pstyle("text-opacity").value){"main"===n&&(n=null);var l,u,c=Ue(o,"labelX",n),d=Ue(o,"labelY",n),h=this.getLabelText(t,n);if(null!=h&&""!==h&&!isNaN(c)&&!isNaN(d)){this.setupTextStyle(e,t,i);var p,f=n?n+"-":"",g=Ue(o,"labelWidth",n),v=Ue(o,"labelHeight",n),y=t.pstyle(f+"text-margin-x").pfValue,m=t.pstyle(f+"text-margin-y").pfValue,b=t.isEdge(),x=t.pstyle("text-halign").value,w=t.pstyle("text-valign").value;switch(b&&(x="center",w="center"),c+=y,d+=m,0!==(p=r?this.getTextAngle(t,n):0)&&(l=c,u=d,e.translate(l,u),e.rotate(p),c=0,d=0),w){case"top":break;case"center":d+=v/2;break;case"bottom":d+=v}var E=t.pstyle("text-background-opacity").value,k=t.pstyle("text-border-opacity").value,C=t.pstyle("text-border-width").pfValue,S=t.pstyle("text-background-padding").pfValue,P=t.pstyle("text-background-shape").strValue,D=0===P.indexOf("round"),T=2;if(E>0||C>0&&k>0){var _=c-S;switch(x){case"left":_-=g;break;case"center":_-=g/2}var M=d-v-S,B=g+2*S,N=v+2*S;if(E>0){var z=e.fillStyle,I=t.pstyle("text-background-color").value;e.fillStyle="rgba("+I[0]+","+I[1]+","+I[2]+","+E*s+")",D?Gu(e,_,M,B,N,T):e.fillRect(_,M,B,N),e.fillStyle=z}if(C>0&&k>0){var A=e.strokeStyle,L=e.lineWidth,O=t.pstyle("text-border-color").value,R=t.pstyle("text-border-style").value;if(e.strokeStyle="rgba("+O[0]+","+O[1]+","+O[2]+","+k*s+")",e.lineWidth=C,e.setLineDash)switch(R){case"dotted":e.setLineDash([1,1]);break;case"dashed":e.setLineDash([4,2]);break;case"double":e.lineWidth=C/4,e.setLineDash([]);break;case"solid":e.setLineDash([])}if(D?Gu(e,_,M,B,N,T,"stroke"):e.strokeRect(_,M,B,N),"double"===R){var V=C/2;D?Gu(e,_+V,M+V,B-2*V,N-2*V,T,"stroke"):e.strokeRect(_+V,M+V,B-2*V,N-2*V)}e.setLineDash&&e.setLineDash([]),e.lineWidth=L,e.strokeStyle=A}}var F=2*t.pstyle("text-outline-width").pfValue;if(F>0&&(e.lineWidth=F),"wrap"===t.pstyle("text-wrap").value){var j=Ue(o,"labelWrapCachedLines",n),q=Ue(o,"labelLineHeight",n),Y=g/2,X=this.getLabelJustification(t);switch("auto"===X||("left"===x?"left"===X?c+=-g:"center"===X&&(c+=-Y):"center"===x?"left"===X?c+=-Y:"right"===X&&(c+=Y):"right"===x&&("center"===X?c+=Y:"right"===X&&(c+=g))),w){case"top":d-=(j.length-1)*q;break;case"center":case"bottom":d-=(j.length-1)*q}for(var W=0;W0&&e.strokeText(j[W],c,d),e.fillText(j[W],c,d),d+=q}else F>0&&e.strokeText(h,c,d),e.fillText(h,c,d);0!==p&&(e.rotate(-p),e.translate(-l,-u))}}};var Uu={drawNode:function(e,t,n){var r,i,a=!(arguments.length>3&&void 0!==arguments[3])||arguments[3],o=!(arguments.length>4&&void 0!==arguments[4])||arguments[4],s=!(arguments.length>5&&void 0!==arguments[5])||arguments[5],l=this,u=t._private,c=u.rscratch,d=t.position();if(x(d.x)&&x(d.y)&&(!s||t.visible())){var h,p,f=s?t.effectiveOpacity():1,g=l.usePaths(),v=!1,y=t.padding();r=t.width()+2*y,i=t.height()+2*y,n&&(p=n,e.translate(-p.x1,-p.y1));for(var m=t.pstyle("background-image"),b=m.value,w=new Array(b.length),E=new Array(b.length),k=0,C=0;C0&&void 0!==arguments[0]?arguments[0]:M;l.eleFillStyle(e,t,n)},H=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:R;l.colorStrokeStyle(e,B[0],B[1],B[2],t)},K=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:q;l.colorStrokeStyle(e,F[0],F[1],F[2],t)},G=function(e,t,n,r){var i,a=l.nodePathCache=l.nodePathCache||[],o=_e("polygon"===n?n+","+r.join(","):n,""+t,""+e,""+X),s=a[o],u=!1;return null!=s?(i=s,u=!0,c.pathCache=i):(i=new Path2D,a[o]=c.pathCache=i),{path:i,cacheHit:u}},U=t.pstyle("shape").strValue,Z=t.pstyle("shape-polygon-points").pfValue;if(g){e.translate(d.x,d.y);var $=G(r,i,U,Z);h=$.path,v=$.cacheHit}var Q=function(){if(!v){var n=d;g&&(n={x:0,y:0}),l.nodeShapes[l.getNodeShape(t)].draw(h||e,n.x,n.y,r,i,X,c)}g?e.fill(h):e.fill()},J=function(){for(var n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:f,r=!(arguments.length>1&&void 0!==arguments[1])||arguments[1],i=u.backgrounding,a=0,o=0;o0&&void 0!==arguments[0]&&arguments[0],a=arguments.length>1&&void 0!==arguments[1]?arguments[1]:f;l.hasPie(t)&&(l.drawPie(e,t,a),n&&(g||l.nodeShapes[l.getNodeShape(t)].draw(e,d.x,d.y,r,i,X,c)))},te=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:f,n=(T>0?T:-T)*t,r=T>0?0:255;0!==T&&(l.colorFillStyle(e,r,r,r,n),g?e.fill(h):e.fill())},ne=function(){if(_>0){if(e.lineWidth=_,e.lineCap=I,e.lineJoin=z,e.setLineDash)switch(N){case"dotted":e.setLineDash([1,1]);break;case"dashed":e.setLineDash(L),e.lineDashOffset=O;break;case"solid":case"double":e.setLineDash([])}if("center"!==A){if(e.save(),e.lineWidth*=2,"inside"===A)g?e.clip(h):e.clip();else{var t=new Path2D;t.rect(-r/2-_,-i/2-_,r+2*_,i+2*_),t.addPath(h),e.clip(t,"evenodd")}g?e.stroke(h):e.stroke(),e.restore()}else g?e.stroke(h):e.stroke();if("double"===N){e.lineWidth=_/3;var n=e.globalCompositeOperation;e.globalCompositeOperation="destination-out",g?e.stroke(h):e.stroke(),e.globalCompositeOperation=n}e.setLineDash&&e.setLineDash([])}},re=function(){if(V>0){if(e.lineWidth=V,e.lineCap="butt",e.setLineDash)switch(j){case"dotted":e.setLineDash([1,1]);break;case"dashed":e.setLineDash([4,2]);break;case"solid":case"double":e.setLineDash([])}var n=d;g&&(n={x:0,y:0});var a=l.getNodeShape(t),o=_;"inside"===A&&(o=0),"outside"===A&&(o*=2);var s,u=(r+o+(V+Y))/r,c=(i+o+(V+Y))/i,h=r*u,p=i*c,f=l.nodeShapes[a].points;if(g)s=G(h,p,a,f).path;if("ellipse"===a)l.drawEllipsePath(s||e,n.x,n.y,h,p);else if(["round-diamond","round-heptagon","round-hexagon","round-octagon","round-pentagon","round-polygon","round-triangle","round-tag"].includes(a)){var v=0,y=0,m=0;"round-diamond"===a?v=1.4*(o+Y+V):"round-heptagon"===a?(v=1.075*(o+Y+V),m=-(o/2+Y+V)/35):"round-hexagon"===a?v=1.12*(o+Y+V):"round-pentagon"===a?(v=1.13*(o+Y+V),m=-(o/2+Y+V)/15):"round-tag"===a?(v=1.12*(o+Y+V),y=.07*(o/2+V+Y)):"round-triangle"===a&&(v=(o+Y+V)*(Math.PI/2),m=-(o+Y/2+V)/Math.PI),0!==v&&(h=r*(u=(r+v)/r),["round-hexagon","round-tag"].includes(a)||(p=i*(c=(i+v)/i)));for(var b=h/2,x=p/2,w=(X="auto"===X?rn(h,p):X)+(o+V+Y)/2,E=new Array(f.length/2),k=new Array(f.length/2),C=0;C0){if(r=r||n.position(),null==i||null==a){var d=n.padding();i=n.width()+2*d,a=n.height()+2*d}this.colorFillStyle(t,l[0],l[1],l[2],s),this.nodeShapes[u].draw(t,r.x,r.y,i+2*o,a+2*o,c),t.fill()}}}};Uu.drawNodeOverlay=Zu("overlay"),Uu.drawNodeUnderlay=Zu("underlay"),Uu.hasPie=function(e){return(e=e[0])._private.hasPie},Uu.drawPie=function(e,t,n,r){t=t[0],r=r||t.position();var i=t.cy().style(),a=t.pstyle("pie-size"),o=r.x,s=r.y,l=t.width(),u=t.height(),c=Math.min(l,u)/2,d=0;this.usePaths()&&(o=0,s=0),"%"===a.units?c*=a.pfValue:void 0!==a.pfValue&&(c=a.pfValue/2);for(var h=1;h<=i.pieBackgroundN;h++){var p=t.pstyle("pie-"+h+"-background-size").value,f=t.pstyle("pie-"+h+"-background-color").value,g=t.pstyle("pie-"+h+"-background-opacity").value*n,v=p/100;v+d>1&&(v=1-d);var y=1.5*Math.PI+2*Math.PI*d,m=y+2*Math.PI*v;0===p||d>=1||d+v>1||(e.beginPath(),e.moveTo(o,s),e.arc(o,s,c,y,m),e.closePath(),this.colorFillStyle(e,f[0],f[1],f[2],g),e.fill(),d+=v)}};var $u={};$u.getPixelRatio=function(){var e=this.data.contexts[0];if(null!=this.forcedPixelRatio)return this.forcedPixelRatio;var t=this.cy.window(),n=e.backingStorePixelRatio||e.webkitBackingStorePixelRatio||e.mozBackingStorePixelRatio||e.msBackingStorePixelRatio||e.oBackingStorePixelRatio||e.backingStorePixelRatio||1;return(t.devicePixelRatio||1)/n},$u.paintCache=function(e){for(var t,n=this.paintCaches=this.paintCaches||[],r=!0,i=0;io.minMbLowQualFrames&&(o.motionBlurPxRatio=o.mbPxRBlurry)),o.clearingMotionBlur&&(o.motionBlurPxRatio=1),o.textureDrawLastFrame&&!d&&(c[o.NODE]=!0,c[o.SELECT_BOX]=!0);var m=l.style(),b=l.zoom(),x=void 0!==i?i:b,w=l.pan(),E={x:w.x,y:w.y},k={zoom:b,pan:{x:w.x,y:w.y}},C=o.prevViewport;void 0===C||k.zoom!==C.zoom||k.pan.x!==C.pan.x||k.pan.y!==C.pan.y||g&&!f||(o.motionBlurPxRatio=1),a&&(E=a),x*=s,E.x*=s,E.y*=s;var S=o.getCachedZSortedEles();function P(e,t,n,r,i){var a=e.globalCompositeOperation;e.globalCompositeOperation="destination-out",o.colorFillStyle(e,255,255,255,o.motionBlurTransparency),e.fillRect(t,n,r,i),e.globalCompositeOperation=a}function D(e,r){var s,l,c,d;o.clearingMotionBlur||e!==u.bufferContexts[o.MOTIONBLUR_BUFFER_NODE]&&e!==u.bufferContexts[o.MOTIONBLUR_BUFFER_DRAG]?(s=E,l=x,c=o.canvasWidth,d=o.canvasHeight):(s={x:w.x*p,y:w.y*p},l=b*p,c=o.canvasWidth*p,d=o.canvasHeight*p),e.setTransform(1,0,0,1,0,0),"motionBlur"===r?P(e,0,0,c,d):t||void 0!==r&&!r||e.clearRect(0,0,c,d),n||(e.translate(s.x,s.y),e.scale(l,l)),a&&e.translate(a.x,a.y),i&&e.scale(i,i)}if(d||(o.textureDrawLastFrame=!1),d){if(o.textureDrawLastFrame=!0,!o.textureCache){o.textureCache={},o.textureCache.bb=l.mutableElements().boundingBox(),o.textureCache.texture=o.data.bufferCanvases[o.TEXTURE_BUFFER];var T=o.data.bufferContexts[o.TEXTURE_BUFFER];T.setTransform(1,0,0,1,0,0),T.clearRect(0,0,o.canvasWidth*o.textureMult,o.canvasHeight*o.textureMult),o.render({forcedContext:T,drawOnlyNodeLayer:!0,forcedPxRatio:s*o.textureMult}),(k=o.textureCache.viewport={zoom:l.zoom(),pan:l.pan(),width:o.canvasWidth,height:o.canvasHeight}).mpan={x:(0-k.pan.x)/k.zoom,y:(0-k.pan.y)/k.zoom}}c[o.DRAG]=!1,c[o.NODE]=!1;var _=u.contexts[o.NODE],M=o.textureCache.texture;k=o.textureCache.viewport;_.setTransform(1,0,0,1,0,0),h?P(_,0,0,k.width,k.height):_.clearRect(0,0,k.width,k.height);var B=m.core("outside-texture-bg-color").value,N=m.core("outside-texture-bg-opacity").value;o.colorFillStyle(_,B[0],B[1],B[2],N),_.fillRect(0,0,k.width,k.height);b=l.zoom();D(_,!1),_.clearRect(k.mpan.x,k.mpan.y,k.width/k.zoom/s,k.height/k.zoom/s),_.drawImage(M,k.mpan.x,k.mpan.y,k.width/k.zoom/s,k.height/k.zoom/s)}else o.textureOnViewport&&!t&&(o.textureCache=null);var z=l.extent(),I=o.pinching||o.hoverData.dragging||o.swipePanning||o.data.wheelZooming||o.hoverData.draggingEles||o.cy.animated(),A=o.hideEdgesOnViewport&&I,L=[];if(L[o.NODE]=!c[o.NODE]&&h&&!o.clearedForMotionBlur[o.NODE]||o.clearingMotionBlur,L[o.NODE]&&(o.clearedForMotionBlur[o.NODE]=!0),L[o.DRAG]=!c[o.DRAG]&&h&&!o.clearedForMotionBlur[o.DRAG]||o.clearingMotionBlur,L[o.DRAG]&&(o.clearedForMotionBlur[o.DRAG]=!0),c[o.NODE]||n||r||L[o.NODE]){var O=h&&!L[o.NODE]&&1!==p;D(_=t||(O?o.data.bufferContexts[o.MOTIONBLUR_BUFFER_NODE]:u.contexts[o.NODE]),h&&!O?"motionBlur":void 0),A?o.drawCachedNodes(_,S.nondrag,s,z):o.drawLayeredElements(_,S.nondrag,s,z),o.debug&&o.drawDebugPoints(_,S.nondrag),n||h||(c[o.NODE]=!1)}if(!r&&(c[o.DRAG]||n||L[o.DRAG])){O=h&&!L[o.DRAG]&&1!==p;D(_=t||(O?o.data.bufferContexts[o.MOTIONBLUR_BUFFER_DRAG]:u.contexts[o.DRAG]),h&&!O?"motionBlur":void 0),A?o.drawCachedNodes(_,S.drag,s,z):o.drawCachedElements(_,S.drag,s,z),o.debug&&o.drawDebugPoints(_,S.drag),n||h||(c[o.DRAG]=!1)}if(o.showFps||!r&&c[o.SELECT_BOX]&&!n){if(D(_=t||u.contexts[o.SELECT_BOX]),1==o.selection[4]&&(o.hoverData.selecting||o.touchData.selecting)){b=o.cy.zoom();var R=m.core("selection-box-border-width").value/b;_.lineWidth=R,_.fillStyle="rgba("+m.core("selection-box-color").value[0]+","+m.core("selection-box-color").value[1]+","+m.core("selection-box-color").value[2]+","+m.core("selection-box-opacity").value+")",_.fillRect(o.selection[0],o.selection[1],o.selection[2]-o.selection[0],o.selection[3]-o.selection[1]),R>0&&(_.strokeStyle="rgba("+m.core("selection-box-border-color").value[0]+","+m.core("selection-box-border-color").value[1]+","+m.core("selection-box-border-color").value[2]+","+m.core("selection-box-opacity").value+")",_.strokeRect(o.selection[0],o.selection[1],o.selection[2]-o.selection[0],o.selection[3]-o.selection[1]))}if(u.bgActivePosistion&&!o.hoverData.selecting){b=o.cy.zoom();var V=u.bgActivePosistion;_.fillStyle="rgba("+m.core("active-bg-color").value[0]+","+m.core("active-bg-color").value[1]+","+m.core("active-bg-color").value[2]+","+m.core("active-bg-opacity").value+")",_.beginPath(),_.arc(V.x,V.y,m.core("active-bg-size").pfValue/b,0,2*Math.PI),_.fill()}var F=o.lastRedrawTime;if(o.showFps&&F){F=Math.round(F);var j=Math.round(1e3/F);_.setTransform(1,0,0,1,0,0),_.fillStyle="rgba(255, 0, 0, 0.75)",_.strokeStyle="rgba(255, 0, 0, 0.75)",_.lineWidth=1,_.fillText("1 frame = "+F+" ms = "+j+" fps",0,20);_.strokeRect(0,30,250,20),_.fillRect(0,30,250*Math.min(j/60,1),20)}n||(c[o.SELECT_BOX]=!1)}if(h&&1!==p){var q=u.contexts[o.NODE],Y=o.data.bufferCanvases[o.MOTIONBLUR_BUFFER_NODE],X=u.contexts[o.DRAG],W=o.data.bufferCanvases[o.MOTIONBLUR_BUFFER_DRAG],H=function(e,t,n){e.setTransform(1,0,0,1,0,0),n||!y?e.clearRect(0,0,o.canvasWidth,o.canvasHeight):P(e,0,0,o.canvasWidth,o.canvasHeight);var r=p;e.drawImage(t,0,0,o.canvasWidth*r,o.canvasHeight*r,0,0,o.canvasWidth,o.canvasHeight)};(c[o.NODE]||L[o.NODE])&&(H(q,Y,L[o.NODE]),c[o.NODE]=!1),(c[o.DRAG]||L[o.DRAG])&&(H(X,W,L[o.DRAG]),c[o.DRAG]=!1)}o.prevViewport=k,o.clearingMotionBlur&&(o.clearingMotionBlur=!1,o.motionBlurCleared=!0,o.motionBlur=!0),h&&(o.motionBlurTimeout=setTimeout((function(){o.motionBlurTimeout=null,o.clearedForMotionBlur[o.NODE]=!1,o.clearedForMotionBlur[o.DRAG]=!1,o.motionBlur=!1,o.clearingMotionBlur=!d,o.mbFrames=0,c[o.NODE]=!0,c[o.DRAG]=!0,o.redraw()}),100)),t||l.emit("render")};for(var Qu={drawPolygonPath:function(e,t,n,r,i,a){var o=r/2,s=i/2;e.beginPath&&e.beginPath(),e.moveTo(t+o*a[0],n+s*a[1]);for(var l=1;l0&&a>0){h.clearRect(0,0,i,a),h.globalCompositeOperation="source-over";var p=this.getCachedZSortedEles();if(e.full)h.translate(-n.x1*l,-n.y1*l),h.scale(l,l),this.drawElements(h,p),h.scale(1/l,1/l),h.translate(n.x1*l,n.y1*l);else{var f=t.pan(),g={x:f.x*l,y:f.y*l};l*=t.zoom(),h.translate(g.x,g.y),h.scale(l,l),this.drawElements(h,p),h.scale(1/l,1/l),h.translate(-g.x,-g.y)}e.bg&&(h.globalCompositeOperation="destination-over",h.fillStyle=e.bg,h.rect(0,0,i,a),h.fill())}return d},ac.png=function(e){return sc(e,this.bufferCanvasImage(e),"image/png")},ac.jpg=function(e){return sc(e,this.bufferCanvasImage(e),"image/jpeg")};var lc={nodeShapeImpl:function(e,t,n,r,i,a,o,s){switch(e){case"ellipse":return this.drawEllipsePath(t,n,r,i,a);case"polygon":return this.drawPolygonPath(t,n,r,i,a,o);case"round-polygon":return this.drawRoundPolygonPath(t,n,r,i,a,o,s);case"roundrectangle":case"round-rectangle":return this.drawRoundRectanglePath(t,n,r,i,a,s);case"cutrectangle":case"cut-rectangle":return this.drawCutRectanglePath(t,n,r,i,a,o,s);case"bottomroundrectangle":case"bottom-round-rectangle":return this.drawBottomRoundRectanglePath(t,n,r,i,a,s);case"barrel":return this.drawBarrelPath(t,n,r,i,a)}}},uc=dc,cc=dc.prototype;function dc(e){var t=this,n=t.cy.window().document;t.data={canvases:new Array(cc.CANVAS_LAYERS),contexts:new Array(cc.CANVAS_LAYERS),canvasNeedsRedraw:new Array(cc.CANVAS_LAYERS),bufferCanvases:new Array(cc.BUFFER_COUNT),bufferContexts:new Array(cc.CANVAS_LAYERS)};t.data.canvasContainer=n.createElement("div");var r=t.data.canvasContainer.style;t.data.canvasContainer.style["-webkit-tap-highlight-color"]="rgba(0,0,0,0)",r.position="relative",r.zIndex="0",r.overflow="hidden";var i=e.cy.container();i.appendChild(t.data.canvasContainer),i.style["-webkit-tap-highlight-color"]="rgba(0,0,0,0)";var a={"-webkit-user-select":"none","-moz-user-select":"-moz-none","user-select":"none","-webkit-tap-highlight-color":"rgba(0,0,0,0)","outline-style":"none"};c&&c.userAgent.match(/msie|trident|edge/i)&&(a["-ms-touch-action"]="none",a["touch-action"]="none");for(var o=0;o{ +throw Error("map is read-only")}:n instanceof Set&&(n.add=n.clear=n.delete=()=>{ +throw Error("set is read-only") +}),Object.freeze(n),Object.getOwnPropertyNames(n).forEach((t=>{ +const a=n[t],i=typeof a;"object"!==i&&"function"!==i||Object.isFrozen(a)||e(a) +})),n}class n{constructor(e){ +void 0===e.data&&(e.data={}),this.data=e.data,this.isMatchIgnored=!1} +ignoreMatch(){this.isMatchIgnored=!0}}function t(e){ +return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'") +}function a(e,...n){const t=Object.create(null);for(const n in e)t[n]=e[n] +;return n.forEach((e=>{for(const n in e)t[n]=e[n]})),t}const i=e=>!!e.scope +;class r{constructor(e,n){ +this.buffer="",this.classPrefix=n.classPrefix,e.walk(this)}addText(e){ +this.buffer+=t(e)}openNode(e){if(!i(e))return;const n=((e,{prefix:n})=>{ +if(e.startsWith("language:"))return e.replace("language:","language-") +;if(e.includes(".")){const t=e.split(".") +;return[`${n}${t.shift()}`,...t.map(((e,n)=>`${e}${"_".repeat(n+1)}`))].join(" ") +}return`${n}${e}`})(e.scope,{prefix:this.classPrefix});this.span(n)} +closeNode(e){i(e)&&(this.buffer+="")}value(){return this.buffer}span(e){ +this.buffer+=``}}const s=(e={})=>{const n={children:[]} +;return Object.assign(n,e),n};class o{constructor(){ +this.rootNode=s(),this.stack=[this.rootNode]}get top(){ +return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){ +this.top.children.push(e)}openNode(e){const n=s({scope:e}) +;this.add(n),this.stack.push(n)}closeNode(){ +if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){ +for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)} +walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,n){ +return"string"==typeof n?e.addText(n):n.children&&(e.openNode(n), +n.children.forEach((n=>this._walk(e,n))),e.closeNode(n)),e}static _collapse(e){ +"string"!=typeof e&&e.children&&(e.children.every((e=>"string"==typeof e))?e.children=[e.children.join("")]:e.children.forEach((e=>{ +o._collapse(e)})))}}class l extends o{constructor(e){super(),this.options=e} +addText(e){""!==e&&this.add(e)}startScope(e){this.openNode(e)}endScope(){ +this.closeNode()}__addSublanguage(e,n){const t=e.root +;n&&(t.scope="language:"+n),this.add(t)}toHTML(){ +return new r(this,this.options).value()}finalize(){ +return this.closeAllNodes(),!0}}function c(e){ +return e?"string"==typeof e?e:e.source:null}function d(e){return b("(?=",e,")")} +function g(e){return b("(?:",e,")*")}function u(e){return b("(?:",e,")?")} +function b(...e){return e.map((e=>c(e))).join("")}function m(...e){const n=(e=>{ +const n=e[e.length-1] +;return"object"==typeof n&&n.constructor===Object?(e.splice(e.length-1,1),n):{} +})(e);return"("+(n.capture?"":"?:")+e.map((e=>c(e))).join("|")+")"} +function p(e){return RegExp(e.toString()+"|").exec("").length-1} +const _=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./ +;function h(e,{joinWith:n}){let t=0;return e.map((e=>{t+=1;const n=t +;let a=c(e),i="";for(;a.length>0;){const e=_.exec(a);if(!e){i+=a;break} +i+=a.substring(0,e.index), +a=a.substring(e.index+e[0].length),"\\"===e[0][0]&&e[1]?i+="\\"+(Number(e[1])+n):(i+=e[0], +"("===e[0]&&t++)}return i})).map((e=>`(${e})`)).join(n)} +const f="[a-zA-Z]\\w*",E="[a-zA-Z_]\\w*",y="\\b\\d+(\\.\\d+)?",N="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",w="\\b(0b[01]+)",v={ +begin:"\\\\[\\s\\S]",relevance:0},O={scope:"string",begin:"'",end:"'", +illegal:"\\n",contains:[v]},k={scope:"string",begin:'"',end:'"',illegal:"\\n", +contains:[v]},x=(e,n,t={})=>{const i=a({scope:"comment",begin:e,end:n, +contains:[]},t);i.contains.push({scope:"doctag", +begin:"[ ]*(?=(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):)", +end:/(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):/,excludeBegin:!0,relevance:0}) +;const r=m("I","a","is","so","us","to","at","if","in","it","on",/[A-Za-z]+['](d|ve|re|ll|t|s|n)/,/[A-Za-z]+[-][a-z]+/,/[A-Za-z][a-z]{2,}/) +;return i.contains.push({begin:b(/[ ]+/,"(",r,/[.]?[:]?([.][ ]|[ ])/,"){3}")}),i +},M=x("//","$"),S=x("/\\*","\\*/"),A=x("#","$");var C=Object.freeze({ +__proto__:null,APOS_STRING_MODE:O,BACKSLASH_ESCAPE:v,BINARY_NUMBER_MODE:{ +scope:"number",begin:w,relevance:0},BINARY_NUMBER_RE:w,COMMENT:x, +C_BLOCK_COMMENT_MODE:S,C_LINE_COMMENT_MODE:M,C_NUMBER_MODE:{scope:"number", +begin:N,relevance:0},C_NUMBER_RE:N,END_SAME_AS_BEGIN:e=>Object.assign(e,{ +"on:begin":(e,n)=>{n.data._beginMatch=e[1]},"on:end":(e,n)=>{ +n.data._beginMatch!==e[1]&&n.ignoreMatch()}}),HASH_COMMENT_MODE:A,IDENT_RE:f, +MATCH_NOTHING_RE:/\b\B/,METHOD_GUARD:{begin:"\\.\\s*"+E,relevance:0}, +NUMBER_MODE:{scope:"number",begin:y,relevance:0},NUMBER_RE:y, +PHRASAL_WORDS_MODE:{ +begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/ +},QUOTE_STRING_MODE:k,REGEXP_MODE:{scope:"regexp",begin:/\/(?=[^/\n]*\/)/, +end:/\/[gimuy]*/,contains:[v,{begin:/\[/,end:/\]/,relevance:0,contains:[v]}]}, +RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~", +SHEBANG:(e={})=>{const n=/^#![ ]*\// +;return e.binary&&(e.begin=b(n,/.*\b/,e.binary,/\b.*/)),a({scope:"meta",begin:n, +end:/$/,relevance:0,"on:begin":(e,n)=>{0!==e.index&&n.ignoreMatch()}},e)}, +TITLE_MODE:{scope:"title",begin:f,relevance:0},UNDERSCORE_IDENT_RE:E, +UNDERSCORE_TITLE_MODE:{scope:"title",begin:E,relevance:0}});function T(e,n){ +"."===e.input[e.index-1]&&n.ignoreMatch()}function R(e,n){ +void 0!==e.className&&(e.scope=e.className,delete e.className)}function D(e,n){ +n&&e.beginKeywords&&(e.begin="\\b("+e.beginKeywords.split(" ").join("|")+")(?!\\.)(?=\\b|\\s)", +e.__beforeBegin=T,e.keywords=e.keywords||e.beginKeywords,delete e.beginKeywords, +void 0===e.relevance&&(e.relevance=0))}function I(e,n){ +Array.isArray(e.illegal)&&(e.illegal=m(...e.illegal))}function L(e,n){ +if(e.match){ +if(e.begin||e.end)throw Error("begin & end are not supported with match") +;e.begin=e.match,delete e.match}}function B(e,n){ +void 0===e.relevance&&(e.relevance=1)}const $=(e,n)=>{if(!e.beforeMatch)return +;if(e.starts)throw Error("beforeMatch cannot be used with starts") +;const t=Object.assign({},e);Object.keys(e).forEach((n=>{delete e[n] +})),e.keywords=t.keywords,e.begin=b(t.beforeMatch,d(t.begin)),e.starts={ +relevance:0,contains:[Object.assign(t,{endsParent:!0})] +},e.relevance=0,delete t.beforeMatch +},z=["of","and","for","in","not","or","if","then","parent","list","value"],F="keyword" +;function U(e,n,t=F){const a=Object.create(null) +;return"string"==typeof e?i(t,e.split(" ")):Array.isArray(e)?i(t,e):Object.keys(e).forEach((t=>{ +Object.assign(a,U(e[t],n,t))})),a;function i(e,t){ +n&&(t=t.map((e=>e.toLowerCase()))),t.forEach((n=>{const t=n.split("|") +;a[t[0]]=[e,j(t[0],t[1])]}))}}function j(e,n){ +return n?Number(n):(e=>z.includes(e.toLowerCase()))(e)?0:1}const P={},K=e=>{ +console.error(e)},H=(e,...n)=>{console.log("WARN: "+e,...n)},q=(e,n)=>{ +P[`${e}/${n}`]||(console.log(`Deprecated as of ${e}. ${n}`),P[`${e}/${n}`]=!0) +},G=Error();function Z(e,n,{key:t}){let a=0;const i=e[t],r={},s={} +;for(let e=1;e<=n.length;e++)s[e+a]=i[e],r[e+a]=!0,a+=p(n[e-1]) +;e[t]=s,e[t]._emit=r,e[t]._multi=!0}function W(e){(e=>{ +e.scope&&"object"==typeof e.scope&&null!==e.scope&&(e.beginScope=e.scope, +delete e.scope)})(e),"string"==typeof e.beginScope&&(e.beginScope={ +_wrap:e.beginScope}),"string"==typeof e.endScope&&(e.endScope={_wrap:e.endScope +}),(e=>{if(Array.isArray(e.begin)){ +if(e.skip||e.excludeBegin||e.returnBegin)throw K("skip, excludeBegin, returnBegin not compatible with beginScope: {}"), +G +;if("object"!=typeof e.beginScope||null===e.beginScope)throw K("beginScope must be object"), +G;Z(e,e.begin,{key:"beginScope"}),e.begin=h(e.begin,{joinWith:""})}})(e),(e=>{ +if(Array.isArray(e.end)){ +if(e.skip||e.excludeEnd||e.returnEnd)throw K("skip, excludeEnd, returnEnd not compatible with endScope: {}"), +G +;if("object"!=typeof e.endScope||null===e.endScope)throw K("endScope must be object"), +G;Z(e,e.end,{key:"endScope"}),e.end=h(e.end,{joinWith:""})}})(e)}function Q(e){ +function n(n,t){ +return RegExp(c(n),"m"+(e.case_insensitive?"i":"")+(e.unicodeRegex?"u":"")+(t?"g":"")) +}class t{constructor(){ +this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0} +addRule(e,n){ +n.position=this.position++,this.matchIndexes[this.matchAt]=n,this.regexes.push([n,e]), +this.matchAt+=p(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null) +;const e=this.regexes.map((e=>e[1]));this.matcherRe=n(h(e,{joinWith:"|" +}),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex +;const n=this.matcherRe.exec(e);if(!n)return null +;const t=n.findIndex(((e,n)=>n>0&&void 0!==e)),a=this.matchIndexes[t] +;return n.splice(0,t),Object.assign(n,a)}}class i{constructor(){ +this.rules=[],this.multiRegexes=[], +this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){ +if(this.multiRegexes[e])return this.multiRegexes[e];const n=new t +;return this.rules.slice(e).forEach((([e,t])=>n.addRule(e,t))), +n.compile(),this.multiRegexes[e]=n,n}resumingScanAtSamePosition(){ +return 0!==this.regexIndex}considerAll(){this.regexIndex=0}addRule(e,n){ +this.rules.push([e,n]),"begin"===n.type&&this.count++}exec(e){ +const n=this.getMatcher(this.regexIndex);n.lastIndex=this.lastIndex +;let t=n.exec(e) +;if(this.resumingScanAtSamePosition())if(t&&t.index===this.lastIndex);else{ +const n=this.getMatcher(0);n.lastIndex=this.lastIndex+1,t=n.exec(e)} +return t&&(this.regexIndex+=t.position+1, +this.regexIndex===this.count&&this.considerAll()),t}} +if(e.compilerExtensions||(e.compilerExtensions=[]), +e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.") +;return e.classNameAliases=a(e.classNameAliases||{}),function t(r,s){const o=r +;if(r.isCompiled)return o +;[R,L,W,$].forEach((e=>e(r,s))),e.compilerExtensions.forEach((e=>e(r,s))), +r.__beforeBegin=null,[D,I,B].forEach((e=>e(r,s))),r.isCompiled=!0;let l=null +;return"object"==typeof r.keywords&&r.keywords.$pattern&&(r.keywords=Object.assign({},r.keywords), +l=r.keywords.$pattern, +delete r.keywords.$pattern),l=l||/\w+/,r.keywords&&(r.keywords=U(r.keywords,e.case_insensitive)), +o.keywordPatternRe=n(l,!0), +s&&(r.begin||(r.begin=/\B|\b/),o.beginRe=n(o.begin),r.end||r.endsWithParent||(r.end=/\B|\b/), +r.end&&(o.endRe=n(o.end)), +o.terminatorEnd=c(o.end)||"",r.endsWithParent&&s.terminatorEnd&&(o.terminatorEnd+=(r.end?"|":"")+s.terminatorEnd)), +r.illegal&&(o.illegalRe=n(r.illegal)), +r.contains||(r.contains=[]),r.contains=[].concat(...r.contains.map((e=>(e=>(e.variants&&!e.cachedVariants&&(e.cachedVariants=e.variants.map((n=>a(e,{ +variants:null},n)))),e.cachedVariants?e.cachedVariants:X(e)?a(e,{ +starts:e.starts?a(e.starts):null +}):Object.isFrozen(e)?a(e):e))("self"===e?r:e)))),r.contains.forEach((e=>{t(e,o) +})),r.starts&&t(r.starts,s),o.matcher=(e=>{const n=new i +;return e.contains.forEach((e=>n.addRule(e.begin,{rule:e,type:"begin" +}))),e.terminatorEnd&&n.addRule(e.terminatorEnd,{type:"end" +}),e.illegal&&n.addRule(e.illegal,{type:"illegal"}),n})(o),o}(e)}function X(e){ +return!!e&&(e.endsWithParent||X(e.starts))}class V extends Error{ +constructor(e,n){super(e),this.name="HTMLInjectionError",this.html=n}} +const J=t,Y=a,ee=Symbol("nomatch"),ne=t=>{ +const a=Object.create(null),i=Object.create(null),r=[];let s=!0 +;const o="Could not find the language '{}', did you forget to load/include a language module?",c={ +disableAutodetect:!0,name:"Plain text",contains:[]};let p={ +ignoreUnescapedHTML:!1,throwUnescapedHTML:!1,noHighlightRe:/^(no-?highlight)$/i, +languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-", +cssSelector:"pre code",languages:null,__emitter:l};function _(e){ +return p.noHighlightRe.test(e)}function h(e,n,t){let a="",i="" +;"object"==typeof n?(a=e, +t=n.ignoreIllegals,i=n.language):(q("10.7.0","highlight(lang, code, ...args) has been deprecated."), +q("10.7.0","Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277"), +i=e,a=n),void 0===t&&(t=!0);const r={code:a,language:i};x("before:highlight",r) +;const s=r.result?r.result:f(r.language,r.code,t) +;return s.code=r.code,x("after:highlight",s),s}function f(e,t,i,r){ +const l=Object.create(null);function c(){if(!x.keywords)return void S.addText(A) +;let e=0;x.keywordPatternRe.lastIndex=0;let n=x.keywordPatternRe.exec(A),t="" +;for(;n;){t+=A.substring(e,n.index) +;const i=w.case_insensitive?n[0].toLowerCase():n[0],r=(a=i,x.keywords[a]);if(r){ +const[e,a]=r +;if(S.addText(t),t="",l[i]=(l[i]||0)+1,l[i]<=7&&(C+=a),e.startsWith("_"))t+=n[0];else{ +const t=w.classNameAliases[e]||e;g(n[0],t)}}else t+=n[0] +;e=x.keywordPatternRe.lastIndex,n=x.keywordPatternRe.exec(A)}var a +;t+=A.substring(e),S.addText(t)}function d(){null!=x.subLanguage?(()=>{ +if(""===A)return;let e=null;if("string"==typeof x.subLanguage){ +if(!a[x.subLanguage])return void S.addText(A) +;e=f(x.subLanguage,A,!0,M[x.subLanguage]),M[x.subLanguage]=e._top +}else e=E(A,x.subLanguage.length?x.subLanguage:null) +;x.relevance>0&&(C+=e.relevance),S.__addSublanguage(e._emitter,e.language) +})():c(),A=""}function g(e,n){ +""!==e&&(S.startScope(n),S.addText(e),S.endScope())}function u(e,n){let t=1 +;const a=n.length-1;for(;t<=a;){if(!e._emit[t]){t++;continue} +const a=w.classNameAliases[e[t]]||e[t],i=n[t];a?g(i,a):(A=i,c(),A=""),t++}} +function b(e,n){ +return e.scope&&"string"==typeof e.scope&&S.openNode(w.classNameAliases[e.scope]||e.scope), +e.beginScope&&(e.beginScope._wrap?(g(A,w.classNameAliases[e.beginScope._wrap]||e.beginScope._wrap), +A=""):e.beginScope._multi&&(u(e.beginScope,n),A="")),x=Object.create(e,{parent:{ +value:x}}),x}function m(e,t,a){let i=((e,n)=>{const t=e&&e.exec(n) +;return t&&0===t.index})(e.endRe,a);if(i){if(e["on:end"]){const a=new n(e) +;e["on:end"](t,a),a.isMatchIgnored&&(i=!1)}if(i){ +for(;e.endsParent&&e.parent;)e=e.parent;return e}} +if(e.endsWithParent)return m(e.parent,t,a)}function _(e){ +return 0===x.matcher.regexIndex?(A+=e[0],1):(D=!0,0)}function h(e){ +const n=e[0],a=t.substring(e.index),i=m(x,e,a);if(!i)return ee;const r=x +;x.endScope&&x.endScope._wrap?(d(), +g(n,x.endScope._wrap)):x.endScope&&x.endScope._multi?(d(), +u(x.endScope,e)):r.skip?A+=n:(r.returnEnd||r.excludeEnd||(A+=n), +d(),r.excludeEnd&&(A=n));do{ +x.scope&&S.closeNode(),x.skip||x.subLanguage||(C+=x.relevance),x=x.parent +}while(x!==i.parent);return i.starts&&b(i.starts,e),r.returnEnd?0:n.length} +let y={};function N(a,r){const o=r&&r[0];if(A+=a,null==o)return d(),0 +;if("begin"===y.type&&"end"===r.type&&y.index===r.index&&""===o){ +if(A+=t.slice(r.index,r.index+1),!s){const n=Error(`0 width match regex (${e})`) +;throw n.languageName=e,n.badRule=y.rule,n}return 1} +if(y=r,"begin"===r.type)return(e=>{ +const t=e[0],a=e.rule,i=new n(a),r=[a.__beforeBegin,a["on:begin"]] +;for(const n of r)if(n&&(n(e,i),i.isMatchIgnored))return _(t) +;return a.skip?A+=t:(a.excludeBegin&&(A+=t), +d(),a.returnBegin||a.excludeBegin||(A=t)),b(a,e),a.returnBegin?0:t.length})(r) +;if("illegal"===r.type&&!i){ +const e=Error('Illegal lexeme "'+o+'" for mode "'+(x.scope||"")+'"') +;throw e.mode=x,e}if("end"===r.type){const e=h(r);if(e!==ee)return e} +if("illegal"===r.type&&""===o)return 1 +;if(R>1e5&&R>3*r.index)throw Error("potential infinite loop, way more iterations than matches") +;return A+=o,o.length}const w=v(e) +;if(!w)throw K(o.replace("{}",e)),Error('Unknown language: "'+e+'"') +;const O=Q(w);let k="",x=r||O;const M={},S=new p.__emitter(p);(()=>{const e=[] +;for(let n=x;n!==w;n=n.parent)n.scope&&e.unshift(n.scope) +;e.forEach((e=>S.openNode(e)))})();let A="",C=0,T=0,R=0,D=!1;try{ +if(w.__emitTokens)w.__emitTokens(t,S);else{for(x.matcher.considerAll();;){ +R++,D?D=!1:x.matcher.considerAll(),x.matcher.lastIndex=T +;const e=x.matcher.exec(t);if(!e)break;const n=N(t.substring(T,e.index),e) +;T=e.index+n}N(t.substring(T))}return S.finalize(),k=S.toHTML(),{language:e, +value:k,relevance:C,illegal:!1,_emitter:S,_top:x}}catch(n){ +if(n.message&&n.message.includes("Illegal"))return{language:e,value:J(t), +illegal:!0,relevance:0,_illegalBy:{message:n.message,index:T, +context:t.slice(T-100,T+100),mode:n.mode,resultSoFar:k},_emitter:S};if(s)return{ +language:e,value:J(t),illegal:!1,relevance:0,errorRaised:n,_emitter:S,_top:x} +;throw n}}function E(e,n){n=n||p.languages||Object.keys(a);const t=(e=>{ +const n={value:J(e),illegal:!1,relevance:0,_top:c,_emitter:new p.__emitter(p)} +;return n._emitter.addText(e),n})(e),i=n.filter(v).filter(k).map((n=>f(n,e,!1))) +;i.unshift(t);const r=i.sort(((e,n)=>{ +if(e.relevance!==n.relevance)return n.relevance-e.relevance +;if(e.language&&n.language){if(v(e.language).supersetOf===n.language)return 1 +;if(v(n.language).supersetOf===e.language)return-1}return 0})),[s,o]=r,l=s +;return l.secondBest=o,l}function y(e){let n=null;const t=(e=>{ +let n=e.className+" ";n+=e.parentNode?e.parentNode.className:"" +;const t=p.languageDetectRe.exec(n);if(t){const n=v(t[1]) +;return n||(H(o.replace("{}",t[1])), +H("Falling back to no-highlight mode for this block.",e)),n?t[1]:"no-highlight"} +return n.split(/\s+/).find((e=>_(e)||v(e)))})(e);if(_(t))return +;if(x("before:highlightElement",{el:e,language:t +}),e.dataset.highlighted)return void console.log("Element previously highlighted. To highlight again, first unset `dataset.highlighted`.",e) +;if(e.children.length>0&&(p.ignoreUnescapedHTML||(console.warn("One of your code blocks includes unescaped HTML. This is a potentially serious security risk."), +console.warn("https://github.com/highlightjs/highlight.js/wiki/security"), +console.warn("The element with unescaped HTML:"), +console.warn(e)),p.throwUnescapedHTML))throw new V("One of your code blocks includes unescaped HTML.",e.innerHTML) +;n=e;const a=n.textContent,r=t?h(a,{language:t,ignoreIllegals:!0}):E(a) +;e.innerHTML=r.value,e.dataset.highlighted="yes",((e,n,t)=>{const a=n&&i[n]||t +;e.classList.add("hljs"),e.classList.add("language-"+a) +})(e,t,r.language),e.result={language:r.language,re:r.relevance, +relevance:r.relevance},r.secondBest&&(e.secondBest={ +language:r.secondBest.language,relevance:r.secondBest.relevance +}),x("after:highlightElement",{el:e,result:r,text:a})}let N=!1;function w(){ +"loading"!==document.readyState?document.querySelectorAll(p.cssSelector).forEach(y):N=!0 +}function v(e){return e=(e||"").toLowerCase(),a[e]||a[i[e]]} +function O(e,{languageName:n}){"string"==typeof e&&(e=[e]),e.forEach((e=>{ +i[e.toLowerCase()]=n}))}function k(e){const n=v(e) +;return n&&!n.disableAutodetect}function x(e,n){const t=e;r.forEach((e=>{ +e[t]&&e[t](n)}))} +"undefined"!=typeof window&&window.addEventListener&&window.addEventListener("DOMContentLoaded",(()=>{ +N&&w()}),!1),Object.assign(t,{highlight:h,highlightAuto:E,highlightAll:w, +highlightElement:y, +highlightBlock:e=>(q("10.7.0","highlightBlock will be removed entirely in v12.0"), +q("10.7.0","Please use highlightElement now."),y(e)),configure:e=>{p=Y(p,e)}, +initHighlighting:()=>{ +w(),q("10.6.0","initHighlighting() deprecated. Use highlightAll() now.")}, +initHighlightingOnLoad:()=>{ +w(),q("10.6.0","initHighlightingOnLoad() deprecated. Use highlightAll() now.") +},registerLanguage:(e,n)=>{let i=null;try{i=n(t)}catch(n){ +if(K("Language definition for '{}' could not be registered.".replace("{}",e)), +!s)throw n;K(n),i=c} +i.name||(i.name=e),a[e]=i,i.rawDefinition=n.bind(null,t),i.aliases&&O(i.aliases,{ +languageName:e})},unregisterLanguage:e=>{delete a[e] +;for(const n of Object.keys(i))i[n]===e&&delete i[n]}, +listLanguages:()=>Object.keys(a),getLanguage:v,registerAliases:O, +autoDetection:k,inherit:Y,addPlugin:e=>{(e=>{ +e["before:highlightBlock"]&&!e["before:highlightElement"]&&(e["before:highlightElement"]=n=>{ +e["before:highlightBlock"](Object.assign({block:n.el},n)) +}),e["after:highlightBlock"]&&!e["after:highlightElement"]&&(e["after:highlightElement"]=n=>{ +e["after:highlightBlock"](Object.assign({block:n.el},n))})})(e),r.push(e)}, +removePlugin:e=>{const n=r.indexOf(e);-1!==n&&r.splice(n,1)}}),t.debugMode=()=>{ +s=!1},t.safeMode=()=>{s=!0},t.versionString="11.9.0",t.regex={concat:b, +lookahead:d,either:m,optional:u,anyNumberOfTimes:g} +;for(const n in C)"object"==typeof C[n]&&e(C[n]);return Object.assign(t,C),t +},te=ne({});te.newInstance=()=>ne({});var ae=te;const ie=e=>({IMPORTANT:{ +scope:"meta",begin:"!important"},BLOCK_COMMENT:e.C_BLOCK_COMMENT_MODE,HEXCOLOR:{ +scope:"number",begin:/#(([0-9a-fA-F]{3,4})|(([0-9a-fA-F]{2}){3,4}))\b/}, +FUNCTION_DISPATCH:{className:"built_in",begin:/[\w-]+(?=\()/}, +ATTRIBUTE_SELECTOR_MODE:{scope:"selector-attr",begin:/\[/,end:/\]/,illegal:"$", +contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},CSS_NUMBER_MODE:{ +scope:"number", +begin:e.NUMBER_RE+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?", +relevance:0},CSS_VARIABLE:{className:"attr",begin:/--[A-Za-z_][A-Za-z0-9_-]*/} +}),re=["a","abbr","address","article","aside","audio","b","blockquote","body","button","canvas","caption","cite","code","dd","del","details","dfn","div","dl","dt","em","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","header","hgroup","html","i","iframe","img","input","ins","kbd","label","legend","li","main","mark","menu","nav","object","ol","p","q","quote","samp","section","span","strong","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","tr","ul","var","video"],se=["any-hover","any-pointer","aspect-ratio","color","color-gamut","color-index","device-aspect-ratio","device-height","device-width","display-mode","forced-colors","grid","height","hover","inverted-colors","monochrome","orientation","overflow-block","overflow-inline","pointer","prefers-color-scheme","prefers-contrast","prefers-reduced-motion","prefers-reduced-transparency","resolution","scan","scripting","update","width","min-width","max-width","min-height","max-height"],oe=["active","any-link","blank","checked","current","default","defined","dir","disabled","drop","empty","enabled","first","first-child","first-of-type","fullscreen","future","focus","focus-visible","focus-within","has","host","host-context","hover","indeterminate","in-range","invalid","is","lang","last-child","last-of-type","left","link","local-link","not","nth-child","nth-col","nth-last-child","nth-last-col","nth-last-of-type","nth-of-type","only-child","only-of-type","optional","out-of-range","past","placeholder-shown","read-only","read-write","required","right","root","scope","target","target-within","user-invalid","valid","visited","where"],le=["after","backdrop","before","cue","cue-region","first-letter","first-line","grammar-error","marker","part","placeholder","selection","slotted","spelling-error"],ce=["align-content","align-items","align-self","all","animation","animation-delay","animation-direction","animation-duration","animation-fill-mode","animation-iteration-count","animation-name","animation-play-state","animation-timing-function","backface-visibility","background","background-attachment","background-blend-mode","background-clip","background-color","background-image","background-origin","background-position","background-repeat","background-size","block-size","border","border-block","border-block-color","border-block-end","border-block-end-color","border-block-end-style","border-block-end-width","border-block-start","border-block-start-color","border-block-start-style","border-block-start-width","border-block-style","border-block-width","border-bottom","border-bottom-color","border-bottom-left-radius","border-bottom-right-radius","border-bottom-style","border-bottom-width","border-collapse","border-color","border-image","border-image-outset","border-image-repeat","border-image-slice","border-image-source","border-image-width","border-inline","border-inline-color","border-inline-end","border-inline-end-color","border-inline-end-style","border-inline-end-width","border-inline-start","border-inline-start-color","border-inline-start-style","border-inline-start-width","border-inline-style","border-inline-width","border-left","border-left-color","border-left-style","border-left-width","border-radius","border-right","border-right-color","border-right-style","border-right-width","border-spacing","border-style","border-top","border-top-color","border-top-left-radius","border-top-right-radius","border-top-style","border-top-width","border-width","bottom","box-decoration-break","box-shadow","box-sizing","break-after","break-before","break-inside","caption-side","caret-color","clear","clip","clip-path","clip-rule","color","column-count","column-fill","column-gap","column-rule","column-rule-color","column-rule-style","column-rule-width","column-span","column-width","columns","contain","content","content-visibility","counter-increment","counter-reset","cue","cue-after","cue-before","cursor","direction","display","empty-cells","filter","flex","flex-basis","flex-direction","flex-flow","flex-grow","flex-shrink","flex-wrap","float","flow","font","font-display","font-family","font-feature-settings","font-kerning","font-language-override","font-size","font-size-adjust","font-smoothing","font-stretch","font-style","font-synthesis","font-variant","font-variant-caps","font-variant-east-asian","font-variant-ligatures","font-variant-numeric","font-variant-position","font-variation-settings","font-weight","gap","glyph-orientation-vertical","grid","grid-area","grid-auto-columns","grid-auto-flow","grid-auto-rows","grid-column","grid-column-end","grid-column-start","grid-gap","grid-row","grid-row-end","grid-row-start","grid-template","grid-template-areas","grid-template-columns","grid-template-rows","hanging-punctuation","height","hyphens","icon","image-orientation","image-rendering","image-resolution","ime-mode","inline-size","isolation","justify-content","left","letter-spacing","line-break","line-height","list-style","list-style-image","list-style-position","list-style-type","margin","margin-block","margin-block-end","margin-block-start","margin-bottom","margin-inline","margin-inline-end","margin-inline-start","margin-left","margin-right","margin-top","marks","mask","mask-border","mask-border-mode","mask-border-outset","mask-border-repeat","mask-border-slice","mask-border-source","mask-border-width","mask-clip","mask-composite","mask-image","mask-mode","mask-origin","mask-position","mask-repeat","mask-size","mask-type","max-block-size","max-height","max-inline-size","max-width","min-block-size","min-height","min-inline-size","min-width","mix-blend-mode","nav-down","nav-index","nav-left","nav-right","nav-up","none","normal","object-fit","object-position","opacity","order","orphans","outline","outline-color","outline-offset","outline-style","outline-width","overflow","overflow-wrap","overflow-x","overflow-y","padding","padding-block","padding-block-end","padding-block-start","padding-bottom","padding-inline","padding-inline-end","padding-inline-start","padding-left","padding-right","padding-top","page-break-after","page-break-before","page-break-inside","pause","pause-after","pause-before","perspective","perspective-origin","pointer-events","position","quotes","resize","rest","rest-after","rest-before","right","row-gap","scroll-margin","scroll-margin-block","scroll-margin-block-end","scroll-margin-block-start","scroll-margin-bottom","scroll-margin-inline","scroll-margin-inline-end","scroll-margin-inline-start","scroll-margin-left","scroll-margin-right","scroll-margin-top","scroll-padding","scroll-padding-block","scroll-padding-block-end","scroll-padding-block-start","scroll-padding-bottom","scroll-padding-inline","scroll-padding-inline-end","scroll-padding-inline-start","scroll-padding-left","scroll-padding-right","scroll-padding-top","scroll-snap-align","scroll-snap-stop","scroll-snap-type","scrollbar-color","scrollbar-gutter","scrollbar-width","shape-image-threshold","shape-margin","shape-outside","speak","speak-as","src","tab-size","table-layout","text-align","text-align-all","text-align-last","text-combine-upright","text-decoration","text-decoration-color","text-decoration-line","text-decoration-style","text-emphasis","text-emphasis-color","text-emphasis-position","text-emphasis-style","text-indent","text-justify","text-orientation","text-overflow","text-rendering","text-shadow","text-transform","text-underline-position","top","transform","transform-box","transform-origin","transform-style","transition","transition-delay","transition-duration","transition-property","transition-timing-function","unicode-bidi","vertical-align","visibility","voice-balance","voice-duration","voice-family","voice-pitch","voice-range","voice-rate","voice-stress","voice-volume","white-space","widows","width","will-change","word-break","word-spacing","word-wrap","writing-mode","z-index"].reverse(),de=oe.concat(le) +;var ge="[0-9](_*[0-9])*",ue=`\\.(${ge})`,be="[0-9a-fA-F](_*[0-9a-fA-F])*",me={ +className:"number",variants:[{ +begin:`(\\b(${ge})((${ue})|\\.)?|(${ue}))[eE][+-]?(${ge})[fFdD]?\\b`},{ +begin:`\\b(${ge})((${ue})[fFdD]?\\b|\\.([fFdD]\\b)?)`},{ +begin:`(${ue})[fFdD]?\\b`},{begin:`\\b(${ge})[fFdD]\\b`},{ +begin:`\\b0[xX]((${be})\\.?|(${be})?\\.(${be}))[pP][+-]?(${ge})[fFdD]?\\b`},{ +begin:"\\b(0|[1-9](_*[0-9])*)[lL]?\\b"},{begin:`\\b0[xX](${be})[lL]?\\b`},{ +begin:"\\b0(_*[0-7])*[lL]?\\b"},{begin:"\\b0[bB][01](_*[01])*[lL]?\\b"}], +relevance:0};function pe(e,n,t){return-1===t?"":e.replace(n,(a=>pe(e,n,t-1)))} +const _e="[A-Za-z$_][0-9A-Za-z$_]*",he=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],fe=["true","false","null","undefined","NaN","Infinity"],Ee=["Object","Function","Boolean","Symbol","Math","Date","Number","BigInt","String","RegExp","Array","Float32Array","Float64Array","Int8Array","Uint8Array","Uint8ClampedArray","Int16Array","Int32Array","Uint16Array","Uint32Array","BigInt64Array","BigUint64Array","Set","Map","WeakSet","WeakMap","ArrayBuffer","SharedArrayBuffer","Atomics","DataView","JSON","Promise","Generator","GeneratorFunction","AsyncFunction","Reflect","Proxy","Intl","WebAssembly"],ye=["Error","EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"],Ne=["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],we=["arguments","this","super","console","window","document","localStorage","sessionStorage","module","global"],ve=[].concat(Ne,Ee,ye) +;function Oe(e){const n=e.regex,t=_e,a={begin:/<[A-Za-z0-9\\._:-]+/, +end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(e,n)=>{ +const t=e[0].length+e.index,a=e.input[t] +;if("<"===a||","===a)return void n.ignoreMatch();let i +;">"===a&&(((e,{after:n})=>{const t="",M={ +match:[/const|var|let/,/\s+/,t,/\s*/,/=\s*/,/(async\s*)?/,n.lookahead(x)], +keywords:"async",className:{1:"keyword",3:"title.function"},contains:[f]} +;return{name:"JavaScript",aliases:["js","jsx","mjs","cjs"],keywords:i,exports:{ +PARAMS_CONTAINS:h,CLASS_REFERENCE:y},illegal:/#(?![$_A-z])/, +contains:[e.SHEBANG({label:"shebang",binary:"node",relevance:5}),{ +label:"use_strict",className:"meta",relevance:10, +begin:/^\s*['"]use (strict|asm)['"]/ +},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,d,g,u,b,m,{match:/\$\d+/},l,y,{ +className:"attr",begin:t+n.lookahead(":"),relevance:0},M,{ +begin:"("+e.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*", +keywords:"return throw case",relevance:0,contains:[m,e.REGEXP_MODE,{ +className:"function",begin:x,returnBegin:!0,end:"\\s*=>",contains:[{ +className:"params",variants:[{begin:e.UNDERSCORE_IDENT_RE,relevance:0},{ +className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0, +excludeEnd:!0,keywords:i,contains:h}]}]},{begin:/,/,relevance:0},{match:/\s+/, +relevance:0},{variants:[{begin:"<>",end:""},{ +match:/<[A-Za-z0-9\\._:-]+\s*\/>/},{begin:a.begin, +"on:begin":a.isTrulyOpeningTag,end:a.end}],subLanguage:"xml",contains:[{ +begin:a.begin,end:a.end,skip:!0,contains:["self"]}]}]},N,{ +beginKeywords:"while if switch catch for"},{ +begin:"\\b(?!function)"+e.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{", +returnBegin:!0,label:"func.def",contains:[f,e.inherit(e.TITLE_MODE,{begin:t, +className:"title.function"})]},{match:/\.\.\./,relevance:0},O,{match:"\\$"+t, +relevance:0},{match:[/\bconstructor(?=\s*\()/],className:{1:"title.function"}, +contains:[f]},w,{relevance:0,match:/\b[A-Z][A-Z_0-9]+\b/, +className:"variable.constant"},E,k,{match:/\$[(.]/}]}} +const ke=e=>b(/\b/,e,/\w$/.test(e)?/\b/:/\B/),xe=["Protocol","Type"].map(ke),Me=["init","self"].map(ke),Se=["Any","Self"],Ae=["actor","any","associatedtype","async","await",/as\?/,/as!/,"as","borrowing","break","case","catch","class","consume","consuming","continue","convenience","copy","default","defer","deinit","didSet","distributed","do","dynamic","each","else","enum","extension","fallthrough",/fileprivate\(set\)/,"fileprivate","final","for","func","get","guard","if","import","indirect","infix",/init\?/,/init!/,"inout",/internal\(set\)/,"internal","in","is","isolated","nonisolated","lazy","let","macro","mutating","nonmutating",/open\(set\)/,"open","operator","optional","override","postfix","precedencegroup","prefix",/private\(set\)/,"private","protocol",/public\(set\)/,"public","repeat","required","rethrows","return","set","some","static","struct","subscript","super","switch","throws","throw",/try\?/,/try!/,"try","typealias",/unowned\(safe\)/,/unowned\(unsafe\)/,"unowned","var","weak","where","while","willSet"],Ce=["false","nil","true"],Te=["assignment","associativity","higherThan","left","lowerThan","none","right"],Re=["#colorLiteral","#column","#dsohandle","#else","#elseif","#endif","#error","#file","#fileID","#fileLiteral","#filePath","#function","#if","#imageLiteral","#keyPath","#line","#selector","#sourceLocation","#warning"],De=["abs","all","any","assert","assertionFailure","debugPrint","dump","fatalError","getVaList","isKnownUniquelyReferenced","max","min","numericCast","pointwiseMax","pointwiseMin","precondition","preconditionFailure","print","readLine","repeatElement","sequence","stride","swap","swift_unboxFromSwiftValueWithType","transcode","type","unsafeBitCast","unsafeDowncast","withExtendedLifetime","withUnsafeMutablePointer","withUnsafePointer","withVaList","withoutActuallyEscaping","zip"],Ie=m(/[/=\-+!*%<>&|^~?]/,/[\u00A1-\u00A7]/,/[\u00A9\u00AB]/,/[\u00AC\u00AE]/,/[\u00B0\u00B1]/,/[\u00B6\u00BB\u00BF\u00D7\u00F7]/,/[\u2016-\u2017]/,/[\u2020-\u2027]/,/[\u2030-\u203E]/,/[\u2041-\u2053]/,/[\u2055-\u205E]/,/[\u2190-\u23FF]/,/[\u2500-\u2775]/,/[\u2794-\u2BFF]/,/[\u2E00-\u2E7F]/,/[\u3001-\u3003]/,/[\u3008-\u3020]/,/[\u3030]/),Le=m(Ie,/[\u0300-\u036F]/,/[\u1DC0-\u1DFF]/,/[\u20D0-\u20FF]/,/[\uFE00-\uFE0F]/,/[\uFE20-\uFE2F]/),Be=b(Ie,Le,"*"),$e=m(/[a-zA-Z_]/,/[\u00A8\u00AA\u00AD\u00AF\u00B2-\u00B5\u00B7-\u00BA]/,/[\u00BC-\u00BE\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF]/,/[\u0100-\u02FF\u0370-\u167F\u1681-\u180D\u180F-\u1DBF]/,/[\u1E00-\u1FFF]/,/[\u200B-\u200D\u202A-\u202E\u203F-\u2040\u2054\u2060-\u206F]/,/[\u2070-\u20CF\u2100-\u218F\u2460-\u24FF\u2776-\u2793]/,/[\u2C00-\u2DFF\u2E80-\u2FFF]/,/[\u3004-\u3007\u3021-\u302F\u3031-\u303F\u3040-\uD7FF]/,/[\uF900-\uFD3D\uFD40-\uFDCF\uFDF0-\uFE1F\uFE30-\uFE44]/,/[\uFE47-\uFEFE\uFF00-\uFFFD]/),ze=m($e,/\d/,/[\u0300-\u036F\u1DC0-\u1DFF\u20D0-\u20FF\uFE20-\uFE2F]/),Fe=b($e,ze,"*"),Ue=b(/[A-Z]/,ze,"*"),je=["attached","autoclosure",b(/convention\(/,m("swift","block","c"),/\)/),"discardableResult","dynamicCallable","dynamicMemberLookup","escaping","freestanding","frozen","GKInspectable","IBAction","IBDesignable","IBInspectable","IBOutlet","IBSegueAction","inlinable","main","nonobjc","NSApplicationMain","NSCopying","NSManaged",b(/objc\(/,Fe,/\)/),"objc","objcMembers","propertyWrapper","requires_stored_property_inits","resultBuilder","Sendable","testable","UIApplicationMain","unchecked","unknown","usableFromInline","warn_unqualified_access"],Pe=["iOS","iOSApplicationExtension","macOS","macOSApplicationExtension","macCatalyst","macCatalystApplicationExtension","watchOS","watchOSApplicationExtension","tvOS","tvOSApplicationExtension","swift"] +;var Ke=Object.freeze({__proto__:null,grmr_bash:e=>{const n=e.regex,t={},a={ +begin:/\$\{/,end:/\}/,contains:["self",{begin:/:-/,contains:[t]}]} +;Object.assign(t,{className:"variable",variants:[{ +begin:n.concat(/\$[\w\d#@][\w\d_]*/,"(?![\\w\\d])(?![$])")},a]});const i={ +className:"subst",begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE]},r={ +begin:/<<-?\s*(?=\w+)/,starts:{contains:[e.END_SAME_AS_BEGIN({begin:/(\w+)/, +end:/(\w+)/,className:"string"})]}},s={className:"string",begin:/"/,end:/"/, +contains:[e.BACKSLASH_ESCAPE,t,i]};i.contains.push(s);const o={begin:/\$?\(\(/, +end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},e.NUMBER_MODE,t] +},l=e.SHEBANG({binary:"(fish|bash|zsh|sh|csh|ksh|tcsh|dash|scsh)",relevance:10 +}),c={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0, +contains:[e.inherit(e.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{ +name:"Bash",aliases:["sh"],keywords:{$pattern:/\b[a-z][a-z0-9._-]+\b/, +keyword:["if","then","else","elif","fi","for","while","until","in","do","done","case","esac","function","select"], +literal:["true","false"], +built_in:["break","cd","continue","eval","exec","exit","export","getopts","hash","pwd","readonly","return","shift","test","times","trap","umask","unset","alias","bind","builtin","caller","command","declare","echo","enable","help","let","local","logout","mapfile","printf","read","readarray","source","type","typeset","ulimit","unalias","set","shopt","autoload","bg","bindkey","bye","cap","chdir","clone","comparguments","compcall","compctl","compdescribe","compfiles","compgroups","compquote","comptags","comptry","compvalues","dirs","disable","disown","echotc","echoti","emulate","fc","fg","float","functions","getcap","getln","history","integer","jobs","kill","limit","log","noglob","popd","print","pushd","pushln","rehash","sched","setcap","setopt","stat","suspend","ttyctl","unfunction","unhash","unlimit","unsetopt","vared","wait","whence","where","which","zcompile","zformat","zftp","zle","zmodload","zparseopts","zprof","zpty","zregexparse","zsocket","zstyle","ztcp","chcon","chgrp","chown","chmod","cp","dd","df","dir","dircolors","ln","ls","mkdir","mkfifo","mknod","mktemp","mv","realpath","rm","rmdir","shred","sync","touch","truncate","vdir","b2sum","base32","base64","cat","cksum","comm","csplit","cut","expand","fmt","fold","head","join","md5sum","nl","numfmt","od","paste","ptx","pr","sha1sum","sha224sum","sha256sum","sha384sum","sha512sum","shuf","sort","split","sum","tac","tail","tr","tsort","unexpand","uniq","wc","arch","basename","chroot","date","dirname","du","echo","env","expr","factor","groups","hostid","id","link","logname","nice","nohup","nproc","pathchk","pinky","printenv","printf","pwd","readlink","runcon","seq","sleep","stat","stdbuf","stty","tee","test","timeout","tty","uname","unlink","uptime","users","who","whoami","yes"] +},contains:[l,e.SHEBANG(),c,o,e.HASH_COMMENT_MODE,r,{match:/(\/[a-z._-]+)+/},s,{ +match:/\\"/},{className:"string",begin:/'/,end:/'/},{match:/\\'/},t]}}, +grmr_c:e=>{const n=e.regex,t=e.COMMENT("//","$",{contains:[{begin:/\\\n/}] +}),a="decltype\\(auto\\)",i="[a-zA-Z_]\\w*::",r="("+a+"|"+n.optional(i)+"[a-zA-Z_]\\w*"+n.optional("<[^<>]+>")+")",s={ +className:"type",variants:[{begin:"\\b[a-z\\d_]*_t\\b"},{ +match:/\batomic_[a-z]{3,6}\b/}]},o={className:"string",variants:[{ +begin:'(u8?|U|L)?"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{ +begin:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)", +end:"'",illegal:"."},e.END_SAME_AS_BEGIN({ +begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\(/,end:/\)([^()\\ ]{0,16})"/})]},l={ +className:"number",variants:[{begin:"\\b(0b[01']+)"},{ +begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)((ll|LL|l|L)(u|U)?|(u|U)(ll|LL|l|L)?|f|F|b|B)" +},{ +begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)" +}],relevance:0},c={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{ +keyword:"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include" +},contains:[{begin:/\\\n/,relevance:0},e.inherit(o,{className:"string"}),{ +className:"string",begin:/<.*?>/},t,e.C_BLOCK_COMMENT_MODE]},d={ +className:"title",begin:n.optional(i)+e.IDENT_RE,relevance:0 +},g=n.optional(i)+e.IDENT_RE+"\\s*\\(",u={ +keyword:["asm","auto","break","case","continue","default","do","else","enum","extern","for","fortran","goto","if","inline","register","restrict","return","sizeof","struct","switch","typedef","union","volatile","while","_Alignas","_Alignof","_Atomic","_Generic","_Noreturn","_Static_assert","_Thread_local","alignas","alignof","noreturn","static_assert","thread_local","_Pragma"], +type:["float","double","signed","unsigned","int","short","long","char","void","_Bool","_Complex","_Imaginary","_Decimal32","_Decimal64","_Decimal128","const","static","complex","bool","imaginary"], +literal:"true false NULL", +built_in:"std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set pair bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap priority_queue make_pair array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr" +},b=[c,s,t,e.C_BLOCK_COMMENT_MODE,l,o],m={variants:[{begin:/=/,end:/;/},{ +begin:/\(/,end:/\)/},{beginKeywords:"new throw return else",end:/;/}], +keywords:u,contains:b.concat([{begin:/\(/,end:/\)/,keywords:u, +contains:b.concat(["self"]),relevance:0}]),relevance:0},p={ +begin:"("+r+"[\\*&\\s]+)+"+g,returnBegin:!0,end:/[{;=]/,excludeEnd:!0, +keywords:u,illegal:/[^\w\s\*&:<>.]/,contains:[{begin:a,keywords:u,relevance:0},{ +begin:g,returnBegin:!0,contains:[e.inherit(d,{className:"title.function"})], +relevance:0},{relevance:0,match:/,/},{className:"params",begin:/\(/,end:/\)/, +keywords:u,relevance:0,contains:[t,e.C_BLOCK_COMMENT_MODE,o,l,s,{begin:/\(/, +end:/\)/,keywords:u,relevance:0,contains:["self",t,e.C_BLOCK_COMMENT_MODE,o,l,s] +}]},s,t,e.C_BLOCK_COMMENT_MODE,c]};return{name:"C",aliases:["h"],keywords:u, +disableAutodetect:!0,illegal:"=]/,contains:[{ +beginKeywords:"final class struct"},e.TITLE_MODE]}]),exports:{preprocessor:c, +strings:o,keywords:u}}},grmr_cpp:e=>{const n=e.regex,t=e.COMMENT("//","$",{ +contains:[{begin:/\\\n/}] +}),a="decltype\\(auto\\)",i="[a-zA-Z_]\\w*::",r="(?!struct)("+a+"|"+n.optional(i)+"[a-zA-Z_]\\w*"+n.optional("<[^<>]+>")+")",s={ +className:"type",begin:"\\b[a-z\\d_]*_t\\b"},o={className:"string",variants:[{ +begin:'(u8?|U|L)?"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{ +begin:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)", +end:"'",illegal:"."},e.END_SAME_AS_BEGIN({ +begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\(/,end:/\)([^()\\ ]{0,16})"/})]},l={ +className:"number",variants:[{begin:"\\b(0b[01']+)"},{ +begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)((ll|LL|l|L)(u|U)?|(u|U)(ll|LL|l|L)?|f|F|b|B)" +},{ +begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)" +}],relevance:0},c={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{ +keyword:"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include" +},contains:[{begin:/\\\n/,relevance:0},e.inherit(o,{className:"string"}),{ +className:"string",begin:/<.*?>/},t,e.C_BLOCK_COMMENT_MODE]},d={ +className:"title",begin:n.optional(i)+e.IDENT_RE,relevance:0 +},g=n.optional(i)+e.IDENT_RE+"\\s*\\(",u={ +type:["bool","char","char16_t","char32_t","char8_t","double","float","int","long","short","void","wchar_t","unsigned","signed","const","static"], +keyword:["alignas","alignof","and","and_eq","asm","atomic_cancel","atomic_commit","atomic_noexcept","auto","bitand","bitor","break","case","catch","class","co_await","co_return","co_yield","compl","concept","const_cast|10","consteval","constexpr","constinit","continue","decltype","default","delete","do","dynamic_cast|10","else","enum","explicit","export","extern","false","final","for","friend","goto","if","import","inline","module","mutable","namespace","new","noexcept","not","not_eq","nullptr","operator","or","or_eq","override","private","protected","public","reflexpr","register","reinterpret_cast|10","requires","return","sizeof","static_assert","static_cast|10","struct","switch","synchronized","template","this","thread_local","throw","transaction_safe","transaction_safe_dynamic","true","try","typedef","typeid","typename","union","using","virtual","volatile","while","xor","xor_eq"], +literal:["NULL","false","nullopt","nullptr","true"],built_in:["_Pragma"], +_type_hints:["any","auto_ptr","barrier","binary_semaphore","bitset","complex","condition_variable","condition_variable_any","counting_semaphore","deque","false_type","future","imaginary","initializer_list","istringstream","jthread","latch","lock_guard","multimap","multiset","mutex","optional","ostringstream","packaged_task","pair","promise","priority_queue","queue","recursive_mutex","recursive_timed_mutex","scoped_lock","set","shared_future","shared_lock","shared_mutex","shared_timed_mutex","shared_ptr","stack","string_view","stringstream","timed_mutex","thread","true_type","tuple","unique_lock","unique_ptr","unordered_map","unordered_multimap","unordered_multiset","unordered_set","variant","vector","weak_ptr","wstring","wstring_view"] +},b={className:"function.dispatch",relevance:0,keywords:{ +_hint:["abort","abs","acos","apply","as_const","asin","atan","atan2","calloc","ceil","cerr","cin","clog","cos","cosh","cout","declval","endl","exchange","exit","exp","fabs","floor","fmod","forward","fprintf","fputs","free","frexp","fscanf","future","invoke","isalnum","isalpha","iscntrl","isdigit","isgraph","islower","isprint","ispunct","isspace","isupper","isxdigit","labs","launder","ldexp","log","log10","make_pair","make_shared","make_shared_for_overwrite","make_tuple","make_unique","malloc","memchr","memcmp","memcpy","memset","modf","move","pow","printf","putchar","puts","realloc","scanf","sin","sinh","snprintf","sprintf","sqrt","sscanf","std","stderr","stdin","stdout","strcat","strchr","strcmp","strcpy","strcspn","strlen","strncat","strncmp","strncpy","strpbrk","strrchr","strspn","strstr","swap","tan","tanh","terminate","to_underlying","tolower","toupper","vfprintf","visit","vprintf","vsprintf"] +}, +begin:n.concat(/\b/,/(?!decltype)/,/(?!if)/,/(?!for)/,/(?!switch)/,/(?!while)/,e.IDENT_RE,n.lookahead(/(<[^<>]+>|)\s*\(/)) +},m=[b,c,s,t,e.C_BLOCK_COMMENT_MODE,l,o],p={variants:[{begin:/=/,end:/;/},{ +begin:/\(/,end:/\)/},{beginKeywords:"new throw return else",end:/;/}], +keywords:u,contains:m.concat([{begin:/\(/,end:/\)/,keywords:u, +contains:m.concat(["self"]),relevance:0}]),relevance:0},_={className:"function", +begin:"("+r+"[\\*&\\s]+)+"+g,returnBegin:!0,end:/[{;=]/,excludeEnd:!0, +keywords:u,illegal:/[^\w\s\*&:<>.]/,contains:[{begin:a,keywords:u,relevance:0},{ +begin:g,returnBegin:!0,contains:[d],relevance:0},{begin:/::/,relevance:0},{ +begin:/:/,endsWithParent:!0,contains:[o,l]},{relevance:0,match:/,/},{ +className:"params",begin:/\(/,end:/\)/,keywords:u,relevance:0, +contains:[t,e.C_BLOCK_COMMENT_MODE,o,l,s,{begin:/\(/,end:/\)/,keywords:u, +relevance:0,contains:["self",t,e.C_BLOCK_COMMENT_MODE,o,l,s]}] +},s,t,e.C_BLOCK_COMMENT_MODE,c]};return{name:"C++", +aliases:["cc","c++","h++","hpp","hh","hxx","cxx"],keywords:u,illegal:"",keywords:u,contains:["self",s]},{begin:e.IDENT_RE+"::",keywords:u},{ +match:[/\b(?:enum(?:\s+(?:class|struct))?|class|struct|union)/,/\s+/,/\w+/], +className:{1:"keyword",3:"title.class"}}])}},grmr_csharp:e=>{const n={ +keyword:["abstract","as","base","break","case","catch","class","const","continue","do","else","event","explicit","extern","finally","fixed","for","foreach","goto","if","implicit","in","interface","internal","is","lock","namespace","new","operator","out","override","params","private","protected","public","readonly","record","ref","return","scoped","sealed","sizeof","stackalloc","static","struct","switch","this","throw","try","typeof","unchecked","unsafe","using","virtual","void","volatile","while"].concat(["add","alias","and","ascending","async","await","by","descending","equals","from","get","global","group","init","into","join","let","nameof","not","notnull","on","or","orderby","partial","remove","select","set","unmanaged","value|0","var","when","where","with","yield"]), +built_in:["bool","byte","char","decimal","delegate","double","dynamic","enum","float","int","long","nint","nuint","object","sbyte","short","string","ulong","uint","ushort"], +literal:["default","false","null","true"]},t=e.inherit(e.TITLE_MODE,{ +begin:"[a-zA-Z](\\.?\\w)*"}),a={className:"number",variants:[{ +begin:"\\b(0b[01']+)"},{ +begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{ +begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)" +}],relevance:0},i={className:"string",begin:'@"',end:'"',contains:[{begin:'""'}] +},r=e.inherit(i,{illegal:/\n/}),s={className:"subst",begin:/\{/,end:/\}/, +keywords:n},o=e.inherit(s,{illegal:/\n/}),l={className:"string",begin:/\$"/, +end:'"',illegal:/\n/,contains:[{begin:/\{\{/},{begin:/\}\}/ +},e.BACKSLASH_ESCAPE,o]},c={className:"string",begin:/\$@"/,end:'"',contains:[{ +begin:/\{\{/},{begin:/\}\}/},{begin:'""'},s]},d=e.inherit(c,{illegal:/\n/, +contains:[{begin:/\{\{/},{begin:/\}\}/},{begin:'""'},o]}) +;s.contains=[c,l,i,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.C_BLOCK_COMMENT_MODE], +o.contains=[d,l,r,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.inherit(e.C_BLOCK_COMMENT_MODE,{ +illegal:/\n/})];const g={variants:[c,l,i,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE] +},u={begin:"<",end:">",contains:[{beginKeywords:"in out"},t] +},b=e.IDENT_RE+"(<"+e.IDENT_RE+"(\\s*,\\s*"+e.IDENT_RE+")*>)?(\\[\\])?",m={ +begin:"@"+e.IDENT_RE,relevance:0};return{name:"C#",aliases:["cs","c#"], +keywords:n,illegal:/::/,contains:[e.COMMENT("///","$",{returnBegin:!0, +contains:[{className:"doctag",variants:[{begin:"///",relevance:0},{ +begin:"\x3c!--|--\x3e"},{begin:""}]}] +}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"meta",begin:"#", +end:"$",keywords:{ +keyword:"if else elif endif define undef warning error line region endregion pragma checksum" +}},g,a,{beginKeywords:"class interface",relevance:0,end:/[{;=]/, +illegal:/[^\s:,]/,contains:[{beginKeywords:"where class" +},t,u,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{beginKeywords:"namespace", +relevance:0,end:/[{;=]/,illegal:/[^\s:]/, +contains:[t,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{ +beginKeywords:"record",relevance:0,end:/[{;=]/,illegal:/[^\s:]/, +contains:[t,u,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"meta", +begin:"^\\s*\\[(?=[\\w])",excludeBegin:!0,end:"\\]",excludeEnd:!0,contains:[{ +className:"string",begin:/"/,end:/"/}]},{ +beginKeywords:"new return throw await else",relevance:0},{className:"function", +begin:"("+b+"\\s+)+"+e.IDENT_RE+"\\s*(<[^=]+>\\s*)?\\(",returnBegin:!0, +end:/\s*[{;=]/,excludeEnd:!0,keywords:n,contains:[{ +beginKeywords:"public private protected static internal protected abstract async extern override unsafe virtual new sealed partial", +relevance:0},{begin:e.IDENT_RE+"\\s*(<[^=]+>\\s*)?\\(",returnBegin:!0, +contains:[e.TITLE_MODE,u],relevance:0},{match:/\(\)/},{className:"params", +begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:n,relevance:0, +contains:[g,a,e.C_BLOCK_COMMENT_MODE] +},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},m]}},grmr_css:e=>{ +const n=e.regex,t=ie(e),a=[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE];return{ +name:"CSS",case_insensitive:!0,illegal:/[=|'\$]/,keywords:{ +keyframePosition:"from to"},classNameAliases:{keyframePosition:"selector-tag"}, +contains:[t.BLOCK_COMMENT,{begin:/-(webkit|moz|ms|o)-(?=[a-z])/ +},t.CSS_NUMBER_MODE,{className:"selector-id",begin:/#[A-Za-z0-9_-]+/,relevance:0 +},{className:"selector-class",begin:"\\.[a-zA-Z-][a-zA-Z0-9_-]*",relevance:0 +},t.ATTRIBUTE_SELECTOR_MODE,{className:"selector-pseudo",variants:[{ +begin:":("+oe.join("|")+")"},{begin:":(:)?("+le.join("|")+")"}] +},t.CSS_VARIABLE,{className:"attribute",begin:"\\b("+ce.join("|")+")\\b"},{ +begin:/:/,end:/[;}{]/, +contains:[t.BLOCK_COMMENT,t.HEXCOLOR,t.IMPORTANT,t.CSS_NUMBER_MODE,...a,{ +begin:/(url|data-uri)\(/,end:/\)/,relevance:0,keywords:{built_in:"url data-uri" +},contains:[...a,{className:"string",begin:/[^)]/,endsWithParent:!0, +excludeEnd:!0}]},t.FUNCTION_DISPATCH]},{begin:n.lookahead(/@/),end:"[{;]", +relevance:0,illegal:/:/,contains:[{className:"keyword",begin:/@-?\w[\w]*(-\w+)*/ +},{begin:/\s/,endsWithParent:!0,excludeEnd:!0,relevance:0,keywords:{ +$pattern:/[a-z-]+/,keyword:"and or not only",attribute:se.join(" ")},contains:[{ +begin:/[a-z-]+(?=:)/,className:"attribute"},...a,t.CSS_NUMBER_MODE]}]},{ +className:"selector-tag",begin:"\\b("+re.join("|")+")\\b"}]}},grmr_diff:e=>{ +const n=e.regex;return{name:"Diff",aliases:["patch"],contains:[{ +className:"meta",relevance:10, +match:n.either(/^@@ +-\d+,\d+ +\+\d+,\d+ +@@/,/^\*\*\* +\d+,\d+ +\*\*\*\*$/,/^--- +\d+,\d+ +----$/) +},{className:"comment",variants:[{ +begin:n.either(/Index: /,/^index/,/={3,}/,/^-{3}/,/^\*{3} /,/^\+{3}/,/^diff --git/), +end:/$/},{match:/^\*{15}$/}]},{className:"addition",begin:/^\+/,end:/$/},{ +className:"deletion",begin:/^-/,end:/$/},{className:"addition",begin:/^!/, +end:/$/}]}},grmr_go:e=>{const n={ +keyword:["break","case","chan","const","continue","default","defer","else","fallthrough","for","func","go","goto","if","import","interface","map","package","range","return","select","struct","switch","type","var"], +type:["bool","byte","complex64","complex128","error","float32","float64","int8","int16","int32","int64","string","uint8","uint16","uint32","uint64","int","uint","uintptr","rune"], +literal:["true","false","iota","nil"], +built_in:["append","cap","close","complex","copy","imag","len","make","new","panic","print","println","real","recover","delete"] +};return{name:"Go",aliases:["golang"],keywords:n,illegal:"{const n=e.regex;return{name:"GraphQL",aliases:["gql"], +case_insensitive:!0,disableAutodetect:!1,keywords:{ +keyword:["query","mutation","subscription","type","input","schema","directive","interface","union","scalar","fragment","enum","on"], +literal:["true","false","null"]}, +contains:[e.HASH_COMMENT_MODE,e.QUOTE_STRING_MODE,e.NUMBER_MODE,{ +scope:"punctuation",match:/[.]{3}/,relevance:0},{scope:"punctuation", +begin:/[\!\(\)\:\=\[\]\{\|\}]{1}/,relevance:0},{scope:"variable",begin:/\$/, +end:/\W/,excludeEnd:!0,relevance:0},{scope:"meta",match:/@\w+/,excludeEnd:!0},{ +scope:"symbol",begin:n.concat(/[_A-Za-z][_0-9A-Za-z]*/,n.lookahead(/\s*:/)), +relevance:0}],illegal:[/[;<']/,/BEGIN/]}},grmr_ini:e=>{const n=e.regex,t={ +className:"number",relevance:0,variants:[{begin:/([+-]+)?[\d]+_[\d_]+/},{ +begin:e.NUMBER_RE}]},a=e.COMMENT();a.variants=[{begin:/;/,end:/$/},{begin:/#/, +end:/$/}];const i={className:"variable",variants:[{begin:/\$[\w\d"][\w\d_]*/},{ +begin:/\$\{(.*?)\}/}]},r={className:"literal", +begin:/\bon|off|true|false|yes|no\b/},s={className:"string", +contains:[e.BACKSLASH_ESCAPE],variants:[{begin:"'''",end:"'''",relevance:10},{ +begin:'"""',end:'"""',relevance:10},{begin:'"',end:'"'},{begin:"'",end:"'"}] +},o={begin:/\[/,end:/\]/,contains:[a,r,i,s,t,"self"],relevance:0 +},l=n.either(/[A-Za-z0-9_-]+/,/"(\\"|[^"])*"/,/'[^']*'/);return{ +name:"TOML, also INI",aliases:["toml"],case_insensitive:!0,illegal:/\S/, +contains:[a,{className:"section",begin:/\[+/,end:/\]+/},{ +begin:n.concat(l,"(\\s*\\.\\s*",l,")*",n.lookahead(/\s*=\s*[^#\s]/)), +className:"attr",starts:{end:/$/,contains:[a,o,r,i,s,t]}}]}},grmr_java:e=>{ +const n=e.regex,t="[\xc0-\u02b8a-zA-Z_$][\xc0-\u02b8a-zA-Z_$0-9]*",a=t+pe("(?:<"+t+"~~~(?:\\s*,\\s*"+t+"~~~)*>)?",/~~~/g,2),i={ +keyword:["synchronized","abstract","private","var","static","if","const ","for","while","strictfp","finally","protected","import","native","final","void","enum","else","break","transient","catch","instanceof","volatile","case","assert","package","default","public","try","switch","continue","throws","protected","public","private","module","requires","exports","do","sealed","yield","permits"], +literal:["false","true","null"], +type:["char","boolean","long","float","int","byte","short","double"], +built_in:["super","this"]},r={className:"meta",begin:"@"+t,contains:[{ +begin:/\(/,end:/\)/,contains:["self"]}]},s={className:"params",begin:/\(/, +end:/\)/,keywords:i,relevance:0,contains:[e.C_BLOCK_COMMENT_MODE],endsParent:!0} +;return{name:"Java",aliases:["jsp"],keywords:i,illegal:/<\/|#/, +contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{begin:/\w+@/, +relevance:0},{className:"doctag",begin:"@[A-Za-z]+"}]}),{ +begin:/import java\.[a-z]+\./,keywords:"import",relevance:2 +},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{begin:/"""/,end:/"""/, +className:"string",contains:[e.BACKSLASH_ESCAPE] +},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{ +match:[/\b(?:class|interface|enum|extends|implements|new)/,/\s+/,t],className:{ +1:"keyword",3:"title.class"}},{match:/non-sealed/,scope:"keyword"},{ +begin:[n.concat(/(?!else)/,t),/\s+/,t,/\s+/,/=(?!=)/],className:{1:"type", +3:"variable",5:"operator"}},{begin:[/record/,/\s+/,t],className:{1:"keyword", +3:"title.class"},contains:[s,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{ +beginKeywords:"new throw return else",relevance:0},{ +begin:["(?:"+a+"\\s+)",e.UNDERSCORE_IDENT_RE,/\s*(?=\()/],className:{ +2:"title.function"},keywords:i,contains:[{className:"params",begin:/\(/, +end:/\)/,keywords:i,relevance:0, +contains:[r,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,me,e.C_BLOCK_COMMENT_MODE] +},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},me,r]}},grmr_javascript:Oe, +grmr_json:e=>{const n=["true","false","null"],t={scope:"literal", +beginKeywords:n.join(" ")};return{name:"JSON",keywords:{literal:n},contains:[{ +className:"attr",begin:/"(\\.|[^\\"\r\n])*"(?=\s*:)/,relevance:1.01},{ +match:/[{}[\],:]/,className:"punctuation",relevance:0 +},e.QUOTE_STRING_MODE,t,e.C_NUMBER_MODE,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE], +illegal:"\\S"}},grmr_kotlin:e=>{const n={ +keyword:"abstract as val var vararg get set class object open private protected public noinline crossinline dynamic final enum if else do while for when throw try catch finally import package is in fun override companion reified inline lateinit init interface annotation data sealed internal infix operator out by constructor super tailrec where const inner suspend typealias external expect actual", +built_in:"Byte Short Char Int Long Boolean Float Double Void Unit Nothing", +literal:"true false null"},t={className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"@" +},a={className:"subst",begin:/\$\{/,end:/\}/,contains:[e.C_NUMBER_MODE]},i={ +className:"variable",begin:"\\$"+e.UNDERSCORE_IDENT_RE},r={className:"string", +variants:[{begin:'"""',end:'"""(?=[^"])',contains:[i,a]},{begin:"'",end:"'", +illegal:/\n/,contains:[e.BACKSLASH_ESCAPE]},{begin:'"',end:'"',illegal:/\n/, +contains:[e.BACKSLASH_ESCAPE,i,a]}]};a.contains.push(r);const s={ +className:"meta", +begin:"@(?:file|property|field|get|set|receiver|param|setparam|delegate)\\s*:(?:\\s*"+e.UNDERSCORE_IDENT_RE+")?" +},o={className:"meta",begin:"@"+e.UNDERSCORE_IDENT_RE,contains:[{begin:/\(/, +end:/\)/,contains:[e.inherit(r,{className:"string"}),"self"]}] +},l=me,c=e.COMMENT("/\\*","\\*/",{contains:[e.C_BLOCK_COMMENT_MODE]}),d={ +variants:[{className:"type",begin:e.UNDERSCORE_IDENT_RE},{begin:/\(/,end:/\)/, +contains:[]}]},g=d;return g.variants[1].contains=[d],d.variants[1].contains=[g], +{name:"Kotlin",aliases:["kt","kts"],keywords:n, +contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag", +begin:"@[A-Za-z]+"}]}),e.C_LINE_COMMENT_MODE,c,{className:"keyword", +begin:/\b(break|continue|return|this)\b/,starts:{contains:[{className:"symbol", +begin:/@\w+/}]}},t,s,o,{className:"function",beginKeywords:"fun",end:"[(]|$", +returnBegin:!0,excludeEnd:!0,keywords:n,relevance:5,contains:[{ +begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0, +contains:[e.UNDERSCORE_TITLE_MODE]},{className:"type",begin://, +keywords:"reified",relevance:0},{className:"params",begin:/\(/,end:/\)/, +endsParent:!0,keywords:n,relevance:0,contains:[{begin:/:/,end:/[=,\/]/, +endsWithParent:!0,contains:[d,e.C_LINE_COMMENT_MODE,c],relevance:0 +},e.C_LINE_COMMENT_MODE,c,s,o,r,e.C_NUMBER_MODE]},c]},{ +begin:[/class|interface|trait/,/\s+/,e.UNDERSCORE_IDENT_RE],beginScope:{ +3:"title.class"},keywords:"class interface trait",end:/[:\{(]|$/,excludeEnd:!0, +illegal:"extends implements",contains:[{ +beginKeywords:"public protected internal private constructor" +},e.UNDERSCORE_TITLE_MODE,{className:"type",begin://,excludeBegin:!0, +excludeEnd:!0,relevance:0},{className:"type",begin:/[,:]\s*/,end:/[<\(,){\s]|$/, +excludeBegin:!0,returnEnd:!0},s,o]},r,{className:"meta",begin:"^#!/usr/bin/env", +end:"$",illegal:"\n"},l]}},grmr_less:e=>{ +const n=ie(e),t=de,a="[\\w-]+",i="("+a+"|@\\{"+a+"\\})",r=[],s=[],o=e=>({ +className:"string",begin:"~?"+e+".*?"+e}),l=(e,n,t)=>({className:e,begin:n, +relevance:t}),c={$pattern:/[a-z-]+/,keyword:"and or not only", +attribute:se.join(" ")},d={begin:"\\(",end:"\\)",contains:s,keywords:c, +relevance:0} +;s.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,o("'"),o('"'),n.CSS_NUMBER_MODE,{ +begin:"(url|data-uri)\\(",starts:{className:"string",end:"[\\)\\n]", +excludeEnd:!0} +},n.HEXCOLOR,d,l("variable","@@?"+a,10),l("variable","@\\{"+a+"\\}"),l("built_in","~?`[^`]*?`"),{ +className:"attribute",begin:a+"\\s*:",end:":",returnBegin:!0,excludeEnd:!0 +},n.IMPORTANT,{beginKeywords:"and not"},n.FUNCTION_DISPATCH);const g=s.concat({ +begin:/\{/,end:/\}/,contains:r}),u={beginKeywords:"when",endsWithParent:!0, +contains:[{beginKeywords:"and not"}].concat(s)},b={begin:i+"\\s*:", +returnBegin:!0,end:/[;}]/,relevance:0,contains:[{begin:/-(webkit|moz|ms|o)-/ +},n.CSS_VARIABLE,{className:"attribute",begin:"\\b("+ce.join("|")+")\\b", +end:/(?=:)/,starts:{endsWithParent:!0,illegal:"[<=$]",relevance:0,contains:s}}] +},m={className:"keyword", +begin:"@(import|media|charset|font-face|(-[a-z]+-)?keyframes|supports|document|namespace|page|viewport|host)\\b", +starts:{end:"[;{}]",keywords:c,returnEnd:!0,contains:s,relevance:0}},p={ +className:"variable",variants:[{begin:"@"+a+"\\s*:",relevance:15},{begin:"@"+a +}],starts:{end:"[;}]",returnEnd:!0,contains:g}},_={variants:[{ +begin:"[\\.#:&\\[>]",end:"[;{}]"},{begin:i,end:/\{/}],returnBegin:!0, +returnEnd:!0,illegal:"[<='$\"]",relevance:0, +contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,u,l("keyword","all\\b"),l("variable","@\\{"+a+"\\}"),{ +begin:"\\b("+re.join("|")+")\\b",className:"selector-tag" +},n.CSS_NUMBER_MODE,l("selector-tag",i,0),l("selector-id","#"+i),l("selector-class","\\."+i,0),l("selector-tag","&",0),n.ATTRIBUTE_SELECTOR_MODE,{ +className:"selector-pseudo",begin:":("+oe.join("|")+")"},{ +className:"selector-pseudo",begin:":(:)?("+le.join("|")+")"},{begin:/\(/, +end:/\)/,relevance:0,contains:g},{begin:"!important"},n.FUNCTION_DISPATCH]},h={ +begin:a+":(:)?"+`(${t.join("|")})`,returnBegin:!0,contains:[_]} +;return r.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,m,p,h,b,_,u,n.FUNCTION_DISPATCH), +{name:"Less",case_insensitive:!0,illegal:"[=>'/<($\"]",contains:r}}, +grmr_lua:e=>{const n="\\[=*\\[",t="\\]=*\\]",a={begin:n,end:t,contains:["self"] +},i=[e.COMMENT("--(?!"+n+")","$"),e.COMMENT("--"+n,t,{contains:[a],relevance:10 +})];return{name:"Lua",keywords:{$pattern:e.UNDERSCORE_IDENT_RE, +literal:"true false nil", +keyword:"and break do else elseif end for goto if in local not or repeat return then until while", +built_in:"_G _ENV _VERSION __index __newindex __mode __call __metatable __tostring __len __gc __add __sub __mul __div __mod __pow __concat __unm __eq __lt __le assert collectgarbage dofile error getfenv getmetatable ipairs load loadfile loadstring module next pairs pcall print rawequal rawget rawset require select setfenv setmetatable tonumber tostring type unpack xpcall arg self coroutine resume yield status wrap create running debug getupvalue debug sethook getmetatable gethook setmetatable setlocal traceback setfenv getinfo setupvalue getlocal getregistry getfenv io lines write close flush open output type read stderr stdin input stdout popen tmpfile math log max acos huge ldexp pi cos tanh pow deg tan cosh sinh random randomseed frexp ceil floor rad abs sqrt modf asin min mod fmod log10 atan2 exp sin atan os exit setlocale date getenv difftime remove time clock tmpname rename execute package preload loadlib loaded loaders cpath config path seeall string sub upper len gfind rep find match char dump gmatch reverse byte format gsub lower table setn insert getn foreachi maxn foreach concat sort remove" +},contains:i.concat([{className:"function",beginKeywords:"function",end:"\\)", +contains:[e.inherit(e.TITLE_MODE,{ +begin:"([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*"}),{className:"params", +begin:"\\(",endsWithParent:!0,contains:i}].concat(i) +},e.C_NUMBER_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"string", +begin:n,end:t,contains:[a],relevance:5}])}},grmr_makefile:e=>{const n={ +className:"variable",variants:[{begin:"\\$\\("+e.UNDERSCORE_IDENT_RE+"\\)", +contains:[e.BACKSLASH_ESCAPE]},{begin:/\$[@%{ +const n={begin:/<\/?[A-Za-z_]/,end:">",subLanguage:"xml",relevance:0},t={ +variants:[{begin:/\[.+?\]\[.*?\]/,relevance:0},{ +begin:/\[.+?\]\(((data|javascript|mailto):|(?:http|ftp)s?:\/\/).*?\)/, +relevance:2},{ +begin:e.regex.concat(/\[.+?\]\(/,/[A-Za-z][A-Za-z0-9+.-]*/,/:\/\/.*?\)/), +relevance:2},{begin:/\[.+?\]\([./?&#].*?\)/,relevance:1},{ +begin:/\[.*?\]\(.*?\)/,relevance:0}],returnBegin:!0,contains:[{match:/\[(?=\])/ +},{className:"string",relevance:0,begin:"\\[",end:"\\]",excludeBegin:!0, +returnEnd:!0},{className:"link",relevance:0,begin:"\\]\\(",end:"\\)", +excludeBegin:!0,excludeEnd:!0},{className:"symbol",relevance:0,begin:"\\]\\[", +end:"\\]",excludeBegin:!0,excludeEnd:!0}]},a={className:"strong",contains:[], +variants:[{begin:/_{2}(?!\s)/,end:/_{2}/},{begin:/\*{2}(?!\s)/,end:/\*{2}/}] +},i={className:"emphasis",contains:[],variants:[{begin:/\*(?![*\s])/,end:/\*/},{ +begin:/_(?![_\s])/,end:/_/,relevance:0}]},r=e.inherit(a,{contains:[] +}),s=e.inherit(i,{contains:[]});a.contains.push(s),i.contains.push(r) +;let o=[n,t];return[a,i,r,s].forEach((e=>{e.contains=e.contains.concat(o) +})),o=o.concat(a,i),{name:"Markdown",aliases:["md","mkdown","mkd"],contains:[{ +className:"section",variants:[{begin:"^#{1,6}",end:"$",contains:o},{ +begin:"(?=^.+?\\n[=-]{2,}$)",contains:[{begin:"^[=-]*$"},{begin:"^",end:"\\n", +contains:o}]}]},n,{className:"bullet",begin:"^[ \t]*([*+-]|(\\d+\\.))(?=\\s+)", +end:"\\s+",excludeEnd:!0},a,i,{className:"quote",begin:"^>\\s+",contains:o, +end:"$"},{className:"code",variants:[{begin:"(`{3,})[^`](.|\\n)*?\\1`*[ ]*"},{ +begin:"(~{3,})[^~](.|\\n)*?\\1~*[ ]*"},{begin:"```",end:"```+[ ]*$"},{ +begin:"~~~",end:"~~~+[ ]*$"},{begin:"`.+?`"},{begin:"(?=^( {4}|\\t))", +contains:[{begin:"^( {4}|\\t)",end:"(\\n)$"}],relevance:0}]},{ +begin:"^[-\\*]{3,}",end:"$"},t,{begin:/^\[[^\n]+\]:/,returnBegin:!0,contains:[{ +className:"symbol",begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0},{ +className:"link",begin:/:\s*/,end:/$/,excludeBegin:!0}]}]}},grmr_objectivec:e=>{ +const n=/[a-zA-Z@][a-zA-Z0-9_]*/,t={$pattern:n, +keyword:["@interface","@class","@protocol","@implementation"]};return{ +name:"Objective-C",aliases:["mm","objc","obj-c","obj-c++","objective-c++"], +keywords:{"variable.language":["this","super"],$pattern:n, +keyword:["while","export","sizeof","typedef","const","struct","for","union","volatile","static","mutable","if","do","return","goto","enum","else","break","extern","asm","case","default","register","explicit","typename","switch","continue","inline","readonly","assign","readwrite","self","@synchronized","id","typeof","nonatomic","IBOutlet","IBAction","strong","weak","copy","in","out","inout","bycopy","byref","oneway","__strong","__weak","__block","__autoreleasing","@private","@protected","@public","@try","@property","@end","@throw","@catch","@finally","@autoreleasepool","@synthesize","@dynamic","@selector","@optional","@required","@encode","@package","@import","@defs","@compatibility_alias","__bridge","__bridge_transfer","__bridge_retained","__bridge_retain","__covariant","__contravariant","__kindof","_Nonnull","_Nullable","_Null_unspecified","__FUNCTION__","__PRETTY_FUNCTION__","__attribute__","getter","setter","retain","unsafe_unretained","nonnull","nullable","null_unspecified","null_resettable","class","instancetype","NS_DESIGNATED_INITIALIZER","NS_UNAVAILABLE","NS_REQUIRES_SUPER","NS_RETURNS_INNER_POINTER","NS_INLINE","NS_AVAILABLE","NS_DEPRECATED","NS_ENUM","NS_OPTIONS","NS_SWIFT_UNAVAILABLE","NS_ASSUME_NONNULL_BEGIN","NS_ASSUME_NONNULL_END","NS_REFINED_FOR_SWIFT","NS_SWIFT_NAME","NS_SWIFT_NOTHROW","NS_DURING","NS_HANDLER","NS_ENDHANDLER","NS_VALUERETURN","NS_VOIDRETURN"], +literal:["false","true","FALSE","TRUE","nil","YES","NO","NULL"], +built_in:["dispatch_once_t","dispatch_queue_t","dispatch_sync","dispatch_async","dispatch_once"], +type:["int","float","char","unsigned","signed","short","long","double","wchar_t","unichar","void","bool","BOOL","id|0","_Bool"] +},illegal:"/,end:/$/,illegal:"\\n" +},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"class", +begin:"("+t.keyword.join("|")+")\\b",end:/(\{|$)/,excludeEnd:!0,keywords:t, +contains:[e.UNDERSCORE_TITLE_MODE]},{begin:"\\."+e.UNDERSCORE_IDENT_RE, +relevance:0}]}},grmr_perl:e=>{const n=e.regex,t=/[dualxmsipngr]{0,12}/,a={ +$pattern:/[\w.]+/, +keyword:"abs accept alarm and atan2 bind binmode bless break caller chdir chmod chomp chop chown chr chroot close closedir connect continue cos crypt dbmclose dbmopen defined delete die do dump each else elsif endgrent endhostent endnetent endprotoent endpwent endservent eof eval exec exists exit exp fcntl fileno flock for foreach fork format formline getc getgrent getgrgid getgrnam gethostbyaddr gethostbyname gethostent getlogin getnetbyaddr getnetbyname getnetent getpeername getpgrp getpriority getprotobyname getprotobynumber getprotoent getpwent getpwnam getpwuid getservbyname getservbyport getservent getsockname getsockopt given glob gmtime goto grep gt hex if index int ioctl join keys kill last lc lcfirst length link listen local localtime log lstat lt ma map mkdir msgctl msgget msgrcv msgsnd my ne next no not oct open opendir or ord our pack package pipe pop pos print printf prototype push q|0 qq quotemeta qw qx rand read readdir readline readlink readpipe recv redo ref rename require reset return reverse rewinddir rindex rmdir say scalar seek seekdir select semctl semget semop send setgrent sethostent setnetent setpgrp setpriority setprotoent setpwent setservent setsockopt shift shmctl shmget shmread shmwrite shutdown sin sleep socket socketpair sort splice split sprintf sqrt srand stat state study sub substr symlink syscall sysopen sysread sysseek system syswrite tell telldir tie tied time times tr truncate uc ucfirst umask undef unless unlink unpack unshift untie until use utime values vec wait waitpid wantarray warn when while write x|0 xor y|0" +},i={className:"subst",begin:"[$@]\\{",end:"\\}",keywords:a},r={begin:/->\{/, +end:/\}/},s={variants:[{begin:/\$\d/},{ +begin:n.concat(/[$%@](\^\w\b|#\w+(::\w+)*|\{\w+\}|\w+(::\w*)*)/,"(?![A-Za-z])(?![@$%])") +},{begin:/[$%@][^\s\w{]/,relevance:0}] +},o=[e.BACKSLASH_ESCAPE,i,s],l=[/!/,/\//,/\|/,/\?/,/'/,/"/,/#/],c=(e,a,i="\\1")=>{ +const r="\\1"===i?i:n.concat(i,a) +;return n.concat(n.concat("(?:",e,")"),a,/(?:\\.|[^\\\/])*?/,r,/(?:\\.|[^\\\/])*?/,i,t) +},d=(e,a,i)=>n.concat(n.concat("(?:",e,")"),a,/(?:\\.|[^\\\/])*?/,i,t),g=[s,e.HASH_COMMENT_MODE,e.COMMENT(/^=\w/,/=cut/,{ +endsWithParent:!0}),r,{className:"string",contains:o,variants:[{ +begin:"q[qwxr]?\\s*\\(",end:"\\)",relevance:5},{begin:"q[qwxr]?\\s*\\[", +end:"\\]",relevance:5},{begin:"q[qwxr]?\\s*\\{",end:"\\}",relevance:5},{ +begin:"q[qwxr]?\\s*\\|",end:"\\|",relevance:5},{begin:"q[qwxr]?\\s*<",end:">", +relevance:5},{begin:"qw\\s+q",end:"q",relevance:5},{begin:"'",end:"'", +contains:[e.BACKSLASH_ESCAPE]},{begin:'"',end:'"'},{begin:"`",end:"`", +contains:[e.BACKSLASH_ESCAPE]},{begin:/\{\w+\}/,relevance:0},{ +begin:"-?\\w+\\s*=>",relevance:0}]},{className:"number", +begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b", +relevance:0},{ +begin:"(\\/\\/|"+e.RE_STARTERS_RE+"|\\b(split|return|print|reverse|grep)\\b)\\s*", +keywords:"split return print reverse grep",relevance:0, +contains:[e.HASH_COMMENT_MODE,{className:"regexp",variants:[{ +begin:c("s|tr|y",n.either(...l,{capture:!0}))},{begin:c("s|tr|y","\\(","\\)")},{ +begin:c("s|tr|y","\\[","\\]")},{begin:c("s|tr|y","\\{","\\}")}],relevance:2},{ +className:"regexp",variants:[{begin:/(m|qr)\/\//,relevance:0},{ +begin:d("(?:m|qr)?",/\//,/\//)},{begin:d("m|qr",n.either(...l,{capture:!0 +}),/\1/)},{begin:d("m|qr",/\(/,/\)/)},{begin:d("m|qr",/\[/,/\]/)},{ +begin:d("m|qr",/\{/,/\}/)}]}]},{className:"function",beginKeywords:"sub", +end:"(\\s*\\(.*?\\))?[;{]",excludeEnd:!0,relevance:5,contains:[e.TITLE_MODE]},{ +begin:"-\\w\\b",relevance:0},{begin:"^__DATA__$",end:"^__END__$", +subLanguage:"mojolicious",contains:[{begin:"^@@.*",end:"$",className:"comment"}] +}];return i.contains=g,r.contains=g,{name:"Perl",aliases:["pl","pm"],keywords:a, +contains:g}},grmr_php:e=>{ +const n=e.regex,t=/(?![A-Za-z0-9])(?![$])/,a=n.concat(/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/,t),i=n.concat(/(\\?[A-Z][a-z0-9_\x7f-\xff]+|\\?[A-Z]+(?=[A-Z][a-z0-9_\x7f-\xff])){1,}/,t),r={ +scope:"variable",match:"\\$+"+a},s={scope:"subst",variants:[{begin:/\$\w+/},{ +begin:/\{\$/,end:/\}/}]},o=e.inherit(e.APOS_STRING_MODE,{illegal:null +}),l="[ \t\n]",c={scope:"string",variants:[e.inherit(e.QUOTE_STRING_MODE,{ +illegal:null,contains:e.QUOTE_STRING_MODE.contains.concat(s)}),o,{ +begin:/<<<[ \t]*(?:(\w+)|"(\w+)")\n/,end:/[ \t]*(\w+)\b/, +contains:e.QUOTE_STRING_MODE.contains.concat(s),"on:begin":(e,n)=>{ +n.data._beginMatch=e[1]||e[2]},"on:end":(e,n)=>{ +n.data._beginMatch!==e[1]&&n.ignoreMatch()}},e.END_SAME_AS_BEGIN({ +begin:/<<<[ \t]*'(\w+)'\n/,end:/[ \t]*(\w+)\b/})]},d={scope:"number",variants:[{ +begin:"\\b0[bB][01]+(?:_[01]+)*\\b"},{begin:"\\b0[oO][0-7]+(?:_[0-7]+)*\\b"},{ +begin:"\\b0[xX][\\da-fA-F]+(?:_[\\da-fA-F]+)*\\b"},{ +begin:"(?:\\b\\d+(?:_\\d+)*(\\.(?:\\d+(?:_\\d+)*))?|\\B\\.\\d+)(?:[eE][+-]?\\d+)?" +}],relevance:0 +},g=["false","null","true"],u=["__CLASS__","__DIR__","__FILE__","__FUNCTION__","__COMPILER_HALT_OFFSET__","__LINE__","__METHOD__","__NAMESPACE__","__TRAIT__","die","echo","exit","include","include_once","print","require","require_once","array","abstract","and","as","binary","bool","boolean","break","callable","case","catch","class","clone","const","continue","declare","default","do","double","else","elseif","empty","enddeclare","endfor","endforeach","endif","endswitch","endwhile","enum","eval","extends","final","finally","float","for","foreach","from","global","goto","if","implements","instanceof","insteadof","int","integer","interface","isset","iterable","list","match|0","mixed","new","never","object","or","private","protected","public","readonly","real","return","string","switch","throw","trait","try","unset","use","var","void","while","xor","yield"],b=["Error|0","AppendIterator","ArgumentCountError","ArithmeticError","ArrayIterator","ArrayObject","AssertionError","BadFunctionCallException","BadMethodCallException","CachingIterator","CallbackFilterIterator","CompileError","Countable","DirectoryIterator","DivisionByZeroError","DomainException","EmptyIterator","ErrorException","Exception","FilesystemIterator","FilterIterator","GlobIterator","InfiniteIterator","InvalidArgumentException","IteratorIterator","LengthException","LimitIterator","LogicException","MultipleIterator","NoRewindIterator","OutOfBoundsException","OutOfRangeException","OuterIterator","OverflowException","ParentIterator","ParseError","RangeException","RecursiveArrayIterator","RecursiveCachingIterator","RecursiveCallbackFilterIterator","RecursiveDirectoryIterator","RecursiveFilterIterator","RecursiveIterator","RecursiveIteratorIterator","RecursiveRegexIterator","RecursiveTreeIterator","RegexIterator","RuntimeException","SeekableIterator","SplDoublyLinkedList","SplFileInfo","SplFileObject","SplFixedArray","SplHeap","SplMaxHeap","SplMinHeap","SplObjectStorage","SplObserver","SplPriorityQueue","SplQueue","SplStack","SplSubject","SplTempFileObject","TypeError","UnderflowException","UnexpectedValueException","UnhandledMatchError","ArrayAccess","BackedEnum","Closure","Fiber","Generator","Iterator","IteratorAggregate","Serializable","Stringable","Throwable","Traversable","UnitEnum","WeakReference","WeakMap","Directory","__PHP_Incomplete_Class","parent","php_user_filter","self","static","stdClass"],m={ +keyword:u,literal:(e=>{const n=[];return e.forEach((e=>{ +n.push(e),e.toLowerCase()===e?n.push(e.toUpperCase()):n.push(e.toLowerCase()) +})),n})(g),built_in:b},p=e=>e.map((e=>e.replace(/\|\d+$/,""))),_={variants:[{ +match:[/new/,n.concat(l,"+"),n.concat("(?!",p(b).join("\\b|"),"\\b)"),i],scope:{ +1:"keyword",4:"title.class"}}]},h=n.concat(a,"\\b(?!\\()"),f={variants:[{ +match:[n.concat(/::/,n.lookahead(/(?!class\b)/)),h],scope:{2:"variable.constant" +}},{match:[/::/,/class/],scope:{2:"variable.language"}},{ +match:[i,n.concat(/::/,n.lookahead(/(?!class\b)/)),h],scope:{1:"title.class", +3:"variable.constant"}},{match:[i,n.concat("::",n.lookahead(/(?!class\b)/))], +scope:{1:"title.class"}},{match:[i,/::/,/class/],scope:{1:"title.class", +3:"variable.language"}}]},E={scope:"attr", +match:n.concat(a,n.lookahead(":"),n.lookahead(/(?!::)/))},y={relevance:0, +begin:/\(/,end:/\)/,keywords:m,contains:[E,r,f,e.C_BLOCK_COMMENT_MODE,c,d,_] +},N={relevance:0, +match:[/\b/,n.concat("(?!fn\\b|function\\b|",p(u).join("\\b|"),"|",p(b).join("\\b|"),"\\b)"),a,n.concat(l,"*"),n.lookahead(/(?=\()/)], +scope:{3:"title.function.invoke"},contains:[y]};y.contains.push(N) +;const w=[E,f,e.C_BLOCK_COMMENT_MODE,c,d,_];return{case_insensitive:!1, +keywords:m,contains:[{begin:n.concat(/#\[\s*/,i),beginScope:"meta",end:/]/, +endScope:"meta",keywords:{literal:g,keyword:["new","array"]},contains:[{ +begin:/\[/,end:/]/,keywords:{literal:g,keyword:["new","array"]}, +contains:["self",...w]},...w,{scope:"meta",match:i}] +},e.HASH_COMMENT_MODE,e.COMMENT("//","$"),e.COMMENT("/\\*","\\*/",{contains:[{ +scope:"doctag",match:"@[A-Za-z]+"}]}),{match:/__halt_compiler\(\);/, +keywords:"__halt_compiler",starts:{scope:"comment",end:e.MATCH_NOTHING_RE, +contains:[{match:/\?>/,scope:"meta",endsParent:!0}]}},{scope:"meta",variants:[{ +begin:/<\?php/,relevance:10},{begin:/<\?=/},{begin:/<\?/,relevance:.1},{ +begin:/\?>/}]},{scope:"variable.language",match:/\$this\b/},r,N,f,{ +match:[/const/,/\s/,a],scope:{1:"keyword",3:"variable.constant"}},_,{ +scope:"function",relevance:0,beginKeywords:"fn function",end:/[;{]/, +excludeEnd:!0,illegal:"[$%\\[]",contains:[{beginKeywords:"use" +},e.UNDERSCORE_TITLE_MODE,{begin:"=>",endsParent:!0},{scope:"params", +begin:"\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0,keywords:m, +contains:["self",r,f,e.C_BLOCK_COMMENT_MODE,c,d]}]},{scope:"class",variants:[{ +beginKeywords:"enum",illegal:/[($"]/},{beginKeywords:"class interface trait", +illegal:/[:($"]/}],relevance:0,end:/\{/,excludeEnd:!0,contains:[{ +beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{ +beginKeywords:"namespace",relevance:0,end:";",illegal:/[.']/, +contains:[e.inherit(e.UNDERSCORE_TITLE_MODE,{scope:"title.class"})]},{ +beginKeywords:"use",relevance:0,end:";",contains:[{ +match:/\b(as|const|function)\b/,scope:"keyword"},e.UNDERSCORE_TITLE_MODE]},c,d]} +},grmr_php_template:e=>({name:"PHP template",subLanguage:"xml",contains:[{ +begin:/<\?(php|=)?/,end:/\?>/,subLanguage:"php",contains:[{begin:"/\\*", +end:"\\*/",skip:!0},{begin:'b"',end:'"',skip:!0},{begin:"b'",end:"'",skip:!0 +},e.inherit(e.APOS_STRING_MODE,{illegal:null,className:null,contains:null, +skip:!0}),e.inherit(e.QUOTE_STRING_MODE,{illegal:null,className:null, +contains:null,skip:!0})]}]}),grmr_plaintext:e=>({name:"Plain text", +aliases:["text","txt"],disableAutodetect:!0}),grmr_python:e=>{ +const n=e.regex,t=/[\p{XID_Start}_]\p{XID_Continue}*/u,a=["and","as","assert","async","await","break","case","class","continue","def","del","elif","else","except","finally","for","from","global","if","import","in","is","lambda","match","nonlocal|10","not","or","pass","raise","return","try","while","with","yield"],i={ +$pattern:/[A-Za-z]\w+|__\w+__/,keyword:a, +built_in:["__import__","abs","all","any","ascii","bin","bool","breakpoint","bytearray","bytes","callable","chr","classmethod","compile","complex","delattr","dict","dir","divmod","enumerate","eval","exec","filter","float","format","frozenset","getattr","globals","hasattr","hash","help","hex","id","input","int","isinstance","issubclass","iter","len","list","locals","map","max","memoryview","min","next","object","oct","open","ord","pow","print","property","range","repr","reversed","round","set","setattr","slice","sorted","staticmethod","str","sum","super","tuple","type","vars","zip"], +literal:["__debug__","Ellipsis","False","None","NotImplemented","True"], +type:["Any","Callable","Coroutine","Dict","List","Literal","Generic","Optional","Sequence","Set","Tuple","Type","Union"] +},r={className:"meta",begin:/^(>>>|\.\.\.) /},s={className:"subst",begin:/\{/, +end:/\}/,keywords:i,illegal:/#/},o={begin:/\{\{/,relevance:0},l={ +className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{ +begin:/([uU]|[bB]|[rR]|[bB][rR]|[rR][bB])?'''/,end:/'''/, +contains:[e.BACKSLASH_ESCAPE,r],relevance:10},{ +begin:/([uU]|[bB]|[rR]|[bB][rR]|[rR][bB])?"""/,end:/"""/, +contains:[e.BACKSLASH_ESCAPE,r],relevance:10},{ +begin:/([fF][rR]|[rR][fF]|[fF])'''/,end:/'''/, +contains:[e.BACKSLASH_ESCAPE,r,o,s]},{begin:/([fF][rR]|[rR][fF]|[fF])"""/, +end:/"""/,contains:[e.BACKSLASH_ESCAPE,r,o,s]},{begin:/([uU]|[rR])'/,end:/'/, +relevance:10},{begin:/([uU]|[rR])"/,end:/"/,relevance:10},{ +begin:/([bB]|[bB][rR]|[rR][bB])'/,end:/'/},{begin:/([bB]|[bB][rR]|[rR][bB])"/, +end:/"/},{begin:/([fF][rR]|[rR][fF]|[fF])'/,end:/'/, +contains:[e.BACKSLASH_ESCAPE,o,s]},{begin:/([fF][rR]|[rR][fF]|[fF])"/,end:/"/, +contains:[e.BACKSLASH_ESCAPE,o,s]},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE] +},c="[0-9](_?[0-9])*",d=`(\\b(${c}))?\\.(${c})|\\b(${c})\\.`,g="\\b|"+a.join("|"),u={ +className:"number",relevance:0,variants:[{ +begin:`(\\b(${c})|(${d}))[eE][+-]?(${c})[jJ]?(?=${g})`},{begin:`(${d})[jJ]?`},{ +begin:`\\b([1-9](_?[0-9])*|0+(_?0)*)[lLjJ]?(?=${g})`},{ +begin:`\\b0[bB](_?[01])+[lL]?(?=${g})`},{begin:`\\b0[oO](_?[0-7])+[lL]?(?=${g})` +},{begin:`\\b0[xX](_?[0-9a-fA-F])+[lL]?(?=${g})`},{begin:`\\b(${c})[jJ](?=${g})` +}]},b={className:"comment",begin:n.lookahead(/# type:/),end:/$/,keywords:i, +contains:[{begin:/# type:/},{begin:/#/,end:/\b\B/,endsWithParent:!0}]},m={ +className:"params",variants:[{className:"",begin:/\(\s*\)/,skip:!0},{begin:/\(/, +end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:i, +contains:["self",r,u,l,e.HASH_COMMENT_MODE]}]};return s.contains=[l,u,r],{ +name:"Python",aliases:["py","gyp","ipython"],unicodeRegex:!0,keywords:i, +illegal:/(<\/|\?)|=>/,contains:[r,u,{begin:/\bself\b/},{beginKeywords:"if", +relevance:0},l,b,e.HASH_COMMENT_MODE,{match:[/\bdef/,/\s+/,t],scope:{ +1:"keyword",3:"title.function"},contains:[m]},{variants:[{ +match:[/\bclass/,/\s+/,t,/\s*/,/\(\s*/,t,/\s*\)/]},{match:[/\bclass/,/\s+/,t]}], +scope:{1:"keyword",3:"title.class",6:"title.class.inherited"}},{ +className:"meta",begin:/^[\t ]*@/,end:/(?=#)|$/,contains:[u,m,l]}]}}, +grmr_python_repl:e=>({aliases:["pycon"],contains:[{className:"meta.prompt", +starts:{end:/ |$/,starts:{end:"$",subLanguage:"python"}},variants:[{ +begin:/^>>>(?=[ ]|$)/},{begin:/^\.\.\.(?=[ ]|$)/}]}]}),grmr_r:e=>{ +const n=e.regex,t=/(?:(?:[a-zA-Z]|\.[._a-zA-Z])[._a-zA-Z0-9]*)|\.(?!\d)/,a=n.either(/0[xX][0-9a-fA-F]+\.[0-9a-fA-F]*[pP][+-]?\d+i?/,/0[xX][0-9a-fA-F]+(?:[pP][+-]?\d+)?[Li]?/,/(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?[Li]?/),i=/[=!<>:]=|\|\||&&|:::?|<-|<<-|->>|->|\|>|[-+*\/?!$&|:<=>@^~]|\*\*/,r=n.either(/[()]/,/[{}]/,/\[\[/,/[[\]]/,/\\/,/,/) +;return{name:"R",keywords:{$pattern:t, +keyword:"function if in break next repeat else for while", +literal:"NULL NA TRUE FALSE Inf NaN NA_integer_|10 NA_real_|10 NA_character_|10 NA_complex_|10", +built_in:"LETTERS letters month.abb month.name pi T F abs acos acosh all any anyNA Arg as.call as.character as.complex as.double as.environment as.integer as.logical as.null.default as.numeric as.raw asin asinh atan atanh attr attributes baseenv browser c call ceiling class Conj cos cosh cospi cummax cummin cumprod cumsum digamma dim dimnames emptyenv exp expression floor forceAndCall gamma gc.time globalenv Im interactive invisible is.array is.atomic is.call is.character is.complex is.double is.environment is.expression is.finite is.function is.infinite is.integer is.language is.list is.logical is.matrix is.na is.name is.nan is.null is.numeric is.object is.pairlist is.raw is.recursive is.single is.symbol lazyLoadDBfetch length lgamma list log max min missing Mod names nargs nzchar oldClass on.exit pos.to.env proc.time prod quote range Re rep retracemem return round seq_along seq_len seq.int sign signif sin sinh sinpi sqrt standardGeneric substitute sum switch tan tanh tanpi tracemem trigamma trunc unclass untracemem UseMethod xtfrm" +},contains:[e.COMMENT(/#'/,/$/,{contains:[{scope:"doctag",match:/@examples/, +starts:{end:n.lookahead(n.either(/\n^#'\s*(?=@[a-zA-Z]+)/,/\n^(?!#')/)), +endsParent:!0}},{scope:"doctag",begin:"@param",end:/$/,contains:[{ +scope:"variable",variants:[{match:t},{match:/`(?:\\.|[^`\\])+`/}],endsParent:!0 +}]},{scope:"doctag",match:/@[a-zA-Z]+/},{scope:"keyword",match:/\\[a-zA-Z]+/}] +}),e.HASH_COMMENT_MODE,{scope:"string",contains:[e.BACKSLASH_ESCAPE], +variants:[e.END_SAME_AS_BEGIN({begin:/[rR]"(-*)\(/,end:/\)(-*)"/ +}),e.END_SAME_AS_BEGIN({begin:/[rR]"(-*)\{/,end:/\}(-*)"/ +}),e.END_SAME_AS_BEGIN({begin:/[rR]"(-*)\[/,end:/\](-*)"/ +}),e.END_SAME_AS_BEGIN({begin:/[rR]'(-*)\(/,end:/\)(-*)'/ +}),e.END_SAME_AS_BEGIN({begin:/[rR]'(-*)\{/,end:/\}(-*)'/ +}),e.END_SAME_AS_BEGIN({begin:/[rR]'(-*)\[/,end:/\](-*)'/}),{begin:'"',end:'"', +relevance:0},{begin:"'",end:"'",relevance:0}]},{relevance:0,variants:[{scope:{ +1:"operator",2:"number"},match:[i,a]},{scope:{1:"operator",2:"number"}, +match:[/%[^%]*%/,a]},{scope:{1:"punctuation",2:"number"},match:[r,a]},{scope:{ +2:"number"},match:[/[^a-zA-Z0-9._]|^/,a]}]},{scope:{3:"operator"}, +match:[t,/\s+/,/<-/,/\s+/]},{scope:"operator",relevance:0,variants:[{match:i},{ +match:/%[^%]*%/}]},{scope:"punctuation",relevance:0,match:r},{begin:"`",end:"`", +contains:[{begin:/\\./}]}]}},grmr_ruby:e=>{ +const n=e.regex,t="([a-zA-Z_]\\w*[!?=]?|[-+~]@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?)",a=n.either(/\b([A-Z]+[a-z0-9]+)+/,/\b([A-Z]+[a-z0-9]+)+[A-Z]+/),i=n.concat(a,/(::\w+)*/),r={ +"variable.constant":["__FILE__","__LINE__","__ENCODING__"], +"variable.language":["self","super"], +keyword:["alias","and","begin","BEGIN","break","case","class","defined","do","else","elsif","end","END","ensure","for","if","in","module","next","not","or","redo","require","rescue","retry","return","then","undef","unless","until","when","while","yield","include","extend","prepend","public","private","protected","raise","throw"], +built_in:["proc","lambda","attr_accessor","attr_reader","attr_writer","define_method","private_constant","module_function"], +literal:["true","false","nil"]},s={className:"doctag",begin:"@[A-Za-z]+"},o={ +begin:"#<",end:">"},l=[e.COMMENT("#","$",{contains:[s] +}),e.COMMENT("^=begin","^=end",{contains:[s],relevance:10 +}),e.COMMENT("^__END__",e.MATCH_NOTHING_RE)],c={className:"subst",begin:/#\{/, +end:/\}/,keywords:r},d={className:"string",contains:[e.BACKSLASH_ESCAPE,c], +variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/`/,end:/`/},{ +begin:/%[qQwWx]?\(/,end:/\)/},{begin:/%[qQwWx]?\[/,end:/\]/},{ +begin:/%[qQwWx]?\{/,end:/\}/},{begin:/%[qQwWx]?/},{begin:/%[qQwWx]?\//, +end:/\//},{begin:/%[qQwWx]?%/,end:/%/},{begin:/%[qQwWx]?-/,end:/-/},{ +begin:/%[qQwWx]?\|/,end:/\|/},{begin:/\B\?(\\\d{1,3})/},{ +begin:/\B\?(\\x[A-Fa-f0-9]{1,2})/},{begin:/\B\?(\\u\{?[A-Fa-f0-9]{1,6}\}?)/},{ +begin:/\B\?(\\M-\\C-|\\M-\\c|\\c\\M-|\\M-|\\C-\\M-)[\x20-\x7e]/},{ +begin:/\B\?\\(c|C-)[\x20-\x7e]/},{begin:/\B\?\\?\S/},{ +begin:n.concat(/<<[-~]?'?/,n.lookahead(/(\w+)(?=\W)[^\n]*\n(?:[^\n]*\n)*?\s*\1\b/)), +contains:[e.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/, +contains:[e.BACKSLASH_ESCAPE,c]})]}]},g="[0-9](_?[0-9])*",u={className:"number", +relevance:0,variants:[{ +begin:`\\b([1-9](_?[0-9])*|0)(\\.(${g}))?([eE][+-]?(${g})|r)?i?\\b`},{ +begin:"\\b0[dD][0-9](_?[0-9])*r?i?\\b"},{begin:"\\b0[bB][0-1](_?[0-1])*r?i?\\b" +},{begin:"\\b0[oO][0-7](_?[0-7])*r?i?\\b"},{ +begin:"\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*r?i?\\b"},{ +begin:"\\b0(_?[0-7])+r?i?\\b"}]},b={variants:[{match:/\(\)/},{ +className:"params",begin:/\(/,end:/(?=\))/,excludeBegin:!0,endsParent:!0, +keywords:r}]},m=[d,{variants:[{match:[/class\s+/,i,/\s+<\s+/,i]},{ +match:[/\b(class|module)\s+/,i]}],scope:{2:"title.class", +4:"title.class.inherited"},keywords:r},{match:[/(include|extend)\s+/,i],scope:{ +2:"title.class"},keywords:r},{relevance:0,match:[i,/\.new[. (]/],scope:{ +1:"title.class"}},{relevance:0,match:/\b[A-Z][A-Z_0-9]+\b/, +className:"variable.constant"},{relevance:0,match:a,scope:"title.class"},{ +match:[/def/,/\s+/,t],scope:{1:"keyword",3:"title.function"},contains:[b]},{ +begin:e.IDENT_RE+"::"},{className:"symbol", +begin:e.UNDERSCORE_IDENT_RE+"(!|\\?)?:",relevance:0},{className:"symbol", +begin:":(?!\\s)",contains:[d,{begin:t}],relevance:0},u,{className:"variable", +begin:"(\\$\\W)|((\\$|@@?)(\\w+))(?=[^@$?])(?![A-Za-z])(?![@$?'])"},{ +className:"params",begin:/\|/,end:/\|/,excludeBegin:!0,excludeEnd:!0, +relevance:0,keywords:r},{begin:"("+e.RE_STARTERS_RE+"|unless)\\s*", +keywords:"unless",contains:[{className:"regexp",contains:[e.BACKSLASH_ESCAPE,c], +illegal:/\n/,variants:[{begin:"/",end:"/[a-z]*"},{begin:/%r\{/,end:/\}[a-z]*/},{ +begin:"%r\\(",end:"\\)[a-z]*"},{begin:"%r!",end:"![a-z]*"},{begin:"%r\\[", +end:"\\][a-z]*"}]}].concat(o,l),relevance:0}].concat(o,l) +;c.contains=m,b.contains=m;const p=[{begin:/^\s*=>/,starts:{end:"$",contains:m} +},{className:"meta.prompt", +begin:"^([>?]>|[\\w#]+\\(\\w+\\):\\d+:\\d+[>*]|(\\w+-)?\\d+\\.\\d+\\.\\d+(p\\d+)?[^\\d][^>]+>)(?=[ ])", +starts:{end:"$",keywords:r,contains:m}}];return l.unshift(o),{name:"Ruby", +aliases:["rb","gemspec","podspec","thor","irb"],keywords:r,illegal:/\/\*/, +contains:[e.SHEBANG({binary:"ruby"})].concat(p).concat(l).concat(m)}}, +grmr_rust:e=>{const n=e.regex,t={className:"title.function.invoke",relevance:0, +begin:n.concat(/\b/,/(?!let|for|while|if|else|match\b)/,e.IDENT_RE,n.lookahead(/\s*\(/)) +},a="([ui](8|16|32|64|128|size)|f(32|64))?",i=["drop ","Copy","Send","Sized","Sync","Drop","Fn","FnMut","FnOnce","ToOwned","Clone","Debug","PartialEq","PartialOrd","Eq","Ord","AsRef","AsMut","Into","From","Default","Iterator","Extend","IntoIterator","DoubleEndedIterator","ExactSizeIterator","SliceConcatExt","ToString","assert!","assert_eq!","bitflags!","bytes!","cfg!","col!","concat!","concat_idents!","debug_assert!","debug_assert_eq!","env!","eprintln!","panic!","file!","format!","format_args!","include_bytes!","include_str!","line!","local_data_key!","module_path!","option_env!","print!","println!","select!","stringify!","try!","unimplemented!","unreachable!","vec!","write!","writeln!","macro_rules!","assert_ne!","debug_assert_ne!"],r=["i8","i16","i32","i64","i128","isize","u8","u16","u32","u64","u128","usize","f32","f64","str","char","bool","Box","Option","Result","String","Vec"] +;return{name:"Rust",aliases:["rs"],keywords:{$pattern:e.IDENT_RE+"!?",type:r, +keyword:["abstract","as","async","await","become","box","break","const","continue","crate","do","dyn","else","enum","extern","false","final","fn","for","if","impl","in","let","loop","macro","match","mod","move","mut","override","priv","pub","ref","return","self","Self","static","struct","super","trait","true","try","type","typeof","unsafe","unsized","use","virtual","where","while","yield"], +literal:["true","false","Some","None","Ok","Err"],built_in:i},illegal:""},t]}}, +grmr_scss:e=>{const n=ie(e),t=le,a=oe,i="@[a-z-]+",r={className:"variable", +begin:"(\\$[a-zA-Z-][a-zA-Z0-9_-]*)\\b",relevance:0};return{name:"SCSS", +case_insensitive:!0,illegal:"[=/|']", +contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,n.CSS_NUMBER_MODE,{ +className:"selector-id",begin:"#[A-Za-z0-9_-]+",relevance:0},{ +className:"selector-class",begin:"\\.[A-Za-z0-9_-]+",relevance:0 +},n.ATTRIBUTE_SELECTOR_MODE,{className:"selector-tag", +begin:"\\b("+re.join("|")+")\\b",relevance:0},{className:"selector-pseudo", +begin:":("+a.join("|")+")"},{className:"selector-pseudo", +begin:":(:)?("+t.join("|")+")"},r,{begin:/\(/,end:/\)/, +contains:[n.CSS_NUMBER_MODE]},n.CSS_VARIABLE,{className:"attribute", +begin:"\\b("+ce.join("|")+")\\b"},{ +begin:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b" +},{begin:/:/,end:/[;}{]/,relevance:0, +contains:[n.BLOCK_COMMENT,r,n.HEXCOLOR,n.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,n.IMPORTANT,n.FUNCTION_DISPATCH] +},{begin:"@(page|font-face)",keywords:{$pattern:i,keyword:"@page @font-face"}},{ +begin:"@",end:"[{;]",returnBegin:!0,keywords:{$pattern:/[a-z-]+/, +keyword:"and or not only",attribute:se.join(" ")},contains:[{begin:i, +className:"keyword"},{begin:/[a-z-]+(?=:)/,className:"attribute" +},r,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,n.HEXCOLOR,n.CSS_NUMBER_MODE] +},n.FUNCTION_DISPATCH]}},grmr_shell:e=>({name:"Shell Session", +aliases:["console","shellsession"],contains:[{className:"meta.prompt", +begin:/^\s{0,3}[/~\w\d[\]()@-]*[>%$#][ ]?/,starts:{end:/[^\\](?=\s*$)/, +subLanguage:"bash"}}]}),grmr_sql:e=>{ +const n=e.regex,t=e.COMMENT("--","$"),a=["true","false","unknown"],i=["bigint","binary","blob","boolean","char","character","clob","date","dec","decfloat","decimal","float","int","integer","interval","nchar","nclob","national","numeric","real","row","smallint","time","timestamp","varchar","varying","varbinary"],r=["abs","acos","array_agg","asin","atan","avg","cast","ceil","ceiling","coalesce","corr","cos","cosh","count","covar_pop","covar_samp","cume_dist","dense_rank","deref","element","exp","extract","first_value","floor","json_array","json_arrayagg","json_exists","json_object","json_objectagg","json_query","json_table","json_table_primitive","json_value","lag","last_value","lead","listagg","ln","log","log10","lower","max","min","mod","nth_value","ntile","nullif","percent_rank","percentile_cont","percentile_disc","position","position_regex","power","rank","regr_avgx","regr_avgy","regr_count","regr_intercept","regr_r2","regr_slope","regr_sxx","regr_sxy","regr_syy","row_number","sin","sinh","sqrt","stddev_pop","stddev_samp","substring","substring_regex","sum","tan","tanh","translate","translate_regex","treat","trim","trim_array","unnest","upper","value_of","var_pop","var_samp","width_bucket"],s=["create table","insert into","primary key","foreign key","not null","alter table","add constraint","grouping sets","on overflow","character set","respect nulls","ignore nulls","nulls first","nulls last","depth first","breadth first"],o=r,l=["abs","acos","all","allocate","alter","and","any","are","array","array_agg","array_max_cardinality","as","asensitive","asin","asymmetric","at","atan","atomic","authorization","avg","begin","begin_frame","begin_partition","between","bigint","binary","blob","boolean","both","by","call","called","cardinality","cascaded","case","cast","ceil","ceiling","char","char_length","character","character_length","check","classifier","clob","close","coalesce","collate","collect","column","commit","condition","connect","constraint","contains","convert","copy","corr","corresponding","cos","cosh","count","covar_pop","covar_samp","create","cross","cube","cume_dist","current","current_catalog","current_date","current_default_transform_group","current_path","current_role","current_row","current_schema","current_time","current_timestamp","current_path","current_role","current_transform_group_for_type","current_user","cursor","cycle","date","day","deallocate","dec","decimal","decfloat","declare","default","define","delete","dense_rank","deref","describe","deterministic","disconnect","distinct","double","drop","dynamic","each","element","else","empty","end","end_frame","end_partition","end-exec","equals","escape","every","except","exec","execute","exists","exp","external","extract","false","fetch","filter","first_value","float","floor","for","foreign","frame_row","free","from","full","function","fusion","get","global","grant","group","grouping","groups","having","hold","hour","identity","in","indicator","initial","inner","inout","insensitive","insert","int","integer","intersect","intersection","interval","into","is","join","json_array","json_arrayagg","json_exists","json_object","json_objectagg","json_query","json_table","json_table_primitive","json_value","lag","language","large","last_value","lateral","lead","leading","left","like","like_regex","listagg","ln","local","localtime","localtimestamp","log","log10","lower","match","match_number","match_recognize","matches","max","member","merge","method","min","minute","mod","modifies","module","month","multiset","national","natural","nchar","nclob","new","no","none","normalize","not","nth_value","ntile","null","nullif","numeric","octet_length","occurrences_regex","of","offset","old","omit","on","one","only","open","or","order","out","outer","over","overlaps","overlay","parameter","partition","pattern","per","percent","percent_rank","percentile_cont","percentile_disc","period","portion","position","position_regex","power","precedes","precision","prepare","primary","procedure","ptf","range","rank","reads","real","recursive","ref","references","referencing","regr_avgx","regr_avgy","regr_count","regr_intercept","regr_r2","regr_slope","regr_sxx","regr_sxy","regr_syy","release","result","return","returns","revoke","right","rollback","rollup","row","row_number","rows","running","savepoint","scope","scroll","search","second","seek","select","sensitive","session_user","set","show","similar","sin","sinh","skip","smallint","some","specific","specifictype","sql","sqlexception","sqlstate","sqlwarning","sqrt","start","static","stddev_pop","stddev_samp","submultiset","subset","substring","substring_regex","succeeds","sum","symmetric","system","system_time","system_user","table","tablesample","tan","tanh","then","time","timestamp","timezone_hour","timezone_minute","to","trailing","translate","translate_regex","translation","treat","trigger","trim","trim_array","true","truncate","uescape","union","unique","unknown","unnest","update","upper","user","using","value","values","value_of","var_pop","var_samp","varbinary","varchar","varying","versioning","when","whenever","where","width_bucket","window","with","within","without","year","add","asc","collation","desc","final","first","last","view"].filter((e=>!r.includes(e))),c={ +begin:n.concat(/\b/,n.either(...o),/\s*\(/),relevance:0,keywords:{built_in:o}} +;return{name:"SQL",case_insensitive:!0,illegal:/[{}]|<\//,keywords:{ +$pattern:/\b[\w\.]+/,keyword:((e,{exceptions:n,when:t}={})=>{const a=t +;return n=n||[],e.map((e=>e.match(/\|\d+$/)||n.includes(e)?e:a(e)?e+"|0":e)) +})(l,{when:e=>e.length<3}),literal:a,type:i, +built_in:["current_catalog","current_date","current_default_transform_group","current_path","current_role","current_schema","current_transform_group_for_type","current_user","session_user","system_time","system_user","current_time","localtime","current_timestamp","localtimestamp"] +},contains:[{begin:n.either(...s),relevance:0,keywords:{$pattern:/[\w\.]+/, +keyword:l.concat(s),literal:a,type:i}},{className:"type", +begin:n.either("double precision","large object","with timezone","without timezone") +},c,{className:"variable",begin:/@[a-z0-9][a-z0-9_]*/},{className:"string", +variants:[{begin:/'/,end:/'/,contains:[{begin:/''/}]}]},{begin:/"/,end:/"/, +contains:[{begin:/""/}]},e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE,t,{ +className:"operator",begin:/[-+*/=%^~]|&&?|\|\|?|!=?|<(?:=>?|<|>)?|>[>=]?/, +relevance:0}]}},grmr_swift:e=>{const n={match:/\s+/,relevance:0 +},t=e.COMMENT("/\\*","\\*/",{contains:["self"]}),a=[e.C_LINE_COMMENT_MODE,t],i={ +match:[/\./,m(...xe,...Me)],className:{2:"keyword"}},r={match:b(/\./,m(...Ae)), +relevance:0},s=Ae.filter((e=>"string"==typeof e)).concat(["_|0"]),o={variants:[{ +className:"keyword", +match:m(...Ae.filter((e=>"string"!=typeof e)).concat(Se).map(ke),...Me)}]},l={ +$pattern:m(/\b\w+/,/#\w+/),keyword:s.concat(Re),literal:Ce},c=[i,r,o],g=[{ +match:b(/\./,m(...De)),relevance:0},{className:"built_in", +match:b(/\b/,m(...De),/(?=\()/)}],u={match:/->/,relevance:0},p=[u,{ +className:"operator",relevance:0,variants:[{match:Be},{match:`\\.(\\.|${Le})+`}] +}],_="([0-9]_*)+",h="([0-9a-fA-F]_*)+",f={className:"number",relevance:0, +variants:[{match:`\\b(${_})(\\.(${_}))?([eE][+-]?(${_}))?\\b`},{ +match:`\\b0x(${h})(\\.(${h}))?([pP][+-]?(${_}))?\\b`},{match:/\b0o([0-7]_*)+\b/ +},{match:/\b0b([01]_*)+\b/}]},E=(e="")=>({className:"subst",variants:[{ +match:b(/\\/,e,/[0\\tnr"']/)},{match:b(/\\/,e,/u\{[0-9a-fA-F]{1,8}\}/)}] +}),y=(e="")=>({className:"subst",match:b(/\\/,e,/[\t ]*(?:[\r\n]|\r\n)/) +}),N=(e="")=>({className:"subst",label:"interpol",begin:b(/\\/,e,/\(/),end:/\)/ +}),w=(e="")=>({begin:b(e,/"""/),end:b(/"""/,e),contains:[E(e),y(e),N(e)] +}),v=(e="")=>({begin:b(e,/"/),end:b(/"/,e),contains:[E(e),N(e)]}),O={ +className:"string", +variants:[w(),w("#"),w("##"),w("###"),v(),v("#"),v("##"),v("###")] +},k=[e.BACKSLASH_ESCAPE,{begin:/\[/,end:/\]/,relevance:0, +contains:[e.BACKSLASH_ESCAPE]}],x={begin:/\/[^\s](?=[^/\n]*\/)/,end:/\//, +contains:k},M=e=>{const n=b(e,/\//),t=b(/\//,e);return{begin:n,end:t, +contains:[...k,{scope:"comment",begin:`#(?!.*${t})`,end:/$/}]}},S={ +scope:"regexp",variants:[M("###"),M("##"),M("#"),x]},A={match:b(/`/,Fe,/`/) +},C=[A,{className:"variable",match:/\$\d+/},{className:"variable", +match:`\\$${ze}+`}],T=[{match:/(@|#(un)?)available/,scope:"keyword",starts:{ +contains:[{begin:/\(/,end:/\)/,keywords:Pe,contains:[...p,f,O]}]}},{ +scope:"keyword",match:b(/@/,m(...je))},{scope:"meta",match:b(/@/,Fe)}],R={ +match:d(/\b[A-Z]/),relevance:0,contains:[{className:"type", +match:b(/(AV|CA|CF|CG|CI|CL|CM|CN|CT|MK|MP|MTK|MTL|NS|SCN|SK|UI|WK|XC)/,ze,"+") +},{className:"type",match:Ue,relevance:0},{match:/[?!]+/,relevance:0},{ +match:/\.\.\./,relevance:0},{match:b(/\s+&\s+/,d(Ue)),relevance:0}]},D={ +begin://,keywords:l,contains:[...a,...c,...T,u,R]};R.contains.push(D) +;const I={begin:/\(/,end:/\)/,relevance:0,keywords:l,contains:["self",{ +match:b(Fe,/\s*:/),keywords:"_|0",relevance:0 +},...a,S,...c,...g,...p,f,O,...C,...T,R]},L={begin://, +keywords:"repeat each",contains:[...a,R]},B={begin:/\(/,end:/\)/,keywords:l, +contains:[{begin:m(d(b(Fe,/\s*:/)),d(b(Fe,/\s+/,Fe,/\s*:/))),end:/:/, +relevance:0,contains:[{className:"keyword",match:/\b_\b/},{className:"params", +match:Fe}]},...a,...c,...p,f,O,...T,R,I],endsParent:!0,illegal:/["']/},$={ +match:[/(func|macro)/,/\s+/,m(A.match,Fe,Be)],className:{1:"keyword", +3:"title.function"},contains:[L,B,n],illegal:[/\[/,/%/]},z={ +match:[/\b(?:subscript|init[?!]?)/,/\s*(?=[<(])/],className:{1:"keyword"}, +contains:[L,B,n],illegal:/\[|%/},F={match:[/operator/,/\s+/,Be],className:{ +1:"keyword",3:"title"}},U={begin:[/precedencegroup/,/\s+/,Ue],className:{ +1:"keyword",3:"title"},contains:[R],keywords:[...Te,...Ce],end:/}/} +;for(const e of O.variants){const n=e.contains.find((e=>"interpol"===e.label)) +;n.keywords=l;const t=[...c,...g,...p,f,O,...C];n.contains=[...t,{begin:/\(/, +end:/\)/,contains:["self",...t]}]}return{name:"Swift",keywords:l, +contains:[...a,$,z,{beginKeywords:"struct protocol class extension enum actor", +end:"\\{",excludeEnd:!0,keywords:l,contains:[e.inherit(e.TITLE_MODE,{ +className:"title.class",begin:/[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/}),...c] +},F,U,{beginKeywords:"import",end:/$/,contains:[...a],relevance:0 +},S,...c,...g,...p,f,O,...C,...T,R,I]}},grmr_typescript:e=>{ +const n=Oe(e),t=_e,a=["any","void","number","boolean","string","object","never","symbol","bigint","unknown"],i={ +beginKeywords:"namespace",end:/\{/,excludeEnd:!0, +contains:[n.exports.CLASS_REFERENCE]},r={beginKeywords:"interface",end:/\{/, +excludeEnd:!0,keywords:{keyword:"interface extends",built_in:a}, +contains:[n.exports.CLASS_REFERENCE]},s={$pattern:_e, +keyword:he.concat(["type","namespace","interface","public","private","protected","implements","declare","abstract","readonly","enum","override"]), +literal:fe,built_in:ve.concat(a),"variable.language":we},o={className:"meta", +begin:"@"+t},l=(e,n,t)=>{const a=e.contains.findIndex((e=>e.label===n)) +;if(-1===a)throw Error("can not find mode to replace");e.contains.splice(a,1,t)} +;return Object.assign(n.keywords,s), +n.exports.PARAMS_CONTAINS.push(o),n.contains=n.contains.concat([o,i,r]), +l(n,"shebang",e.SHEBANG()),l(n,"use_strict",{className:"meta",relevance:10, +begin:/^\s*['"]use strict['"]/ +}),n.contains.find((e=>"func.def"===e.label)).relevance=0,Object.assign(n,{ +name:"TypeScript",aliases:["ts","tsx","mts","cts"]}),n},grmr_vbnet:e=>{ +const n=e.regex,t=/\d{1,2}\/\d{1,2}\/\d{4}/,a=/\d{4}-\d{1,2}-\d{1,2}/,i=/(\d|1[012])(:\d+){0,2} *(AM|PM)/,r=/\d{1,2}(:\d{1,2}){1,2}/,s={ +className:"literal",variants:[{begin:n.concat(/# */,n.either(a,t),/ *#/)},{ +begin:n.concat(/# */,r,/ *#/)},{begin:n.concat(/# */,i,/ *#/)},{ +begin:n.concat(/# */,n.either(a,t),/ +/,n.either(i,r),/ *#/)}] +},o=e.COMMENT(/'''/,/$/,{contains:[{className:"doctag",begin:/<\/?/,end:/>/}] +}),l=e.COMMENT(null,/$/,{variants:[{begin:/'/},{begin:/([\t ]|^)REM(?=\s)/}]}) +;return{name:"Visual Basic .NET",aliases:["vb"],case_insensitive:!0, +classNameAliases:{label:"symbol"},keywords:{ +keyword:"addhandler alias aggregate ansi as async assembly auto binary by byref byval call case catch class compare const continue custom declare default delegate dim distinct do each equals else elseif end enum erase error event exit explicit finally for friend from function get global goto group handles if implements imports in inherits interface into iterator join key let lib loop me mid module mustinherit mustoverride mybase myclass namespace narrowing new next notinheritable notoverridable of off on operator option optional order overloads overridable overrides paramarray partial preserve private property protected public raiseevent readonly redim removehandler resume return select set shadows shared skip static step stop structure strict sub synclock take text then throw to try unicode until using when where while widening with withevents writeonly yield", +built_in:"addressof and andalso await directcast gettype getxmlnamespace is isfalse isnot istrue like mod nameof new not or orelse trycast typeof xor cbool cbyte cchar cdate cdbl cdec cint clng cobj csbyte cshort csng cstr cuint culng cushort", +type:"boolean byte char date decimal double integer long object sbyte short single string uinteger ulong ushort", +literal:"true false nothing"}, +illegal:"//|\\{|\\}|endif|gosub|variant|wend|^\\$ ",contains:[{ +className:"string",begin:/"(""|[^/n])"C\b/},{className:"string",begin:/"/, +end:/"/,illegal:/\n/,contains:[{begin:/""/}]},s,{className:"number",relevance:0, +variants:[{begin:/\b\d[\d_]*((\.[\d_]+(E[+-]?[\d_]+)?)|(E[+-]?[\d_]+))[RFD@!#]?/ +},{begin:/\b\d[\d_]*((U?[SIL])|[%&])?/},{begin:/&H[\dA-F_]+((U?[SIL])|[%&])?/},{ +begin:/&O[0-7_]+((U?[SIL])|[%&])?/},{begin:/&B[01_]+((U?[SIL])|[%&])?/}]},{ +className:"label",begin:/^\w+:/},o,l,{className:"meta", +begin:/[\t ]*#(const|disable|else|elseif|enable|end|externalsource|if|region)\b/, +end:/$/,keywords:{ +keyword:"const disable else elseif enable end externalsource if region then"}, +contains:[l]}]}},grmr_wasm:e=>{e.regex;const n=e.COMMENT(/\(;/,/;\)/) +;return n.contains.push("self"),{name:"WebAssembly",keywords:{$pattern:/[\w.]+/, +keyword:["anyfunc","block","br","br_if","br_table","call","call_indirect","data","drop","elem","else","end","export","func","global.get","global.set","local.get","local.set","local.tee","get_global","get_local","global","if","import","local","loop","memory","memory.grow","memory.size","module","mut","nop","offset","param","result","return","select","set_global","set_local","start","table","tee_local","then","type","unreachable"] +},contains:[e.COMMENT(/;;/,/$/),n,{match:[/(?:offset|align)/,/\s*/,/=/], +className:{1:"keyword",3:"operator"}},{className:"variable",begin:/\$[\w_]+/},{ +match:/(\((?!;)|\))+/,className:"punctuation",relevance:0},{ +begin:[/(?:func|call|call_indirect)/,/\s+/,/\$[^\s)]+/],className:{1:"keyword", +3:"title.function"}},e.QUOTE_STRING_MODE,{match:/(i32|i64|f32|f64)(?!\.)/, +className:"type"},{className:"keyword", +match:/\b(f32|f64|i32|i64)(?:\.(?:abs|add|and|ceil|clz|const|convert_[su]\/i(?:32|64)|copysign|ctz|demote\/f64|div(?:_[su])?|eqz?|extend_[su]\/i32|floor|ge(?:_[su])?|gt(?:_[su])?|le(?:_[su])?|load(?:(?:8|16|32)_[su])?|lt(?:_[su])?|max|min|mul|nearest|neg?|or|popcnt|promote\/f32|reinterpret\/[fi](?:32|64)|rem_[su]|rot[lr]|shl|shr_[su]|store(?:8|16|32)?|sqrt|sub|trunc(?:_[su]\/f(?:32|64))?|wrap\/i64|xor))\b/ +},{className:"number",relevance:0, +match:/[+-]?\b(?:\d(?:_?\d)*(?:\.\d(?:_?\d)*)?(?:[eE][+-]?\d(?:_?\d)*)?|0x[\da-fA-F](?:_?[\da-fA-F])*(?:\.[\da-fA-F](?:_?[\da-fA-D])*)?(?:[pP][+-]?\d(?:_?\d)*)?)\b|\binf\b|\bnan(?::0x[\da-fA-F](?:_?[\da-fA-D])*)?\b/ +}]}},grmr_xml:e=>{ +const n=e.regex,t=n.concat(/[\p{L}_]/u,n.optional(/[\p{L}0-9_.-]*:/u),/[\p{L}0-9_.-]*/u),a={ +className:"symbol",begin:/&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;/},i={begin:/\s/, +contains:[{className:"keyword",begin:/#?[a-z_][a-z1-9_-]+/,illegal:/\n/}] +},r=e.inherit(i,{begin:/\(/,end:/\)/}),s=e.inherit(e.APOS_STRING_MODE,{ +className:"string"}),o=e.inherit(e.QUOTE_STRING_MODE,{className:"string"}),l={ +endsWithParent:!0,illegal:/`]+/}]}]}]};return{ +name:"HTML, XML", +aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"], +case_insensitive:!0,unicodeRegex:!0,contains:[{className:"meta",begin://,relevance:10,contains:[i,o,s,r,{begin:/\[/,end:/\]/,contains:[{ +className:"meta",begin://,contains:[i,r,o,s]}]}] +},e.COMMENT(//,{relevance:10}),{begin://, +relevance:10},a,{className:"meta",end:/\?>/,variants:[{begin:/<\?xml/, +relevance:10,contains:[o]},{begin:/<\?[a-z][a-z0-9]+/}]},{className:"tag", +begin:/)/,end:/>/,keywords:{name:"style"},contains:[l],starts:{ +end:/<\/style>/,returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag", +begin:/)/,end:/>/,keywords:{name:"script"},contains:[l],starts:{ +end:/<\/script>/,returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{ +className:"tag",begin:/<>|<\/>/},{className:"tag", +begin:n.concat(//,/>/,/\s/)))), +end:/\/?>/,contains:[{className:"name",begin:t,relevance:0,starts:l}]},{ +className:"tag",begin:n.concat(/<\//,n.lookahead(n.concat(t,/>/))),contains:[{ +className:"name",begin:t,relevance:0},{begin:/>/,relevance:0,endsParent:!0}]}]} +},grmr_yaml:e=>{ +const n="true false yes no null",t="[\\w#;/?:@&=+$,.~*'()[\\]]+",a={ +className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/ +},{begin:/\S+/}],contains:[e.BACKSLASH_ESCAPE,{className:"template-variable", +variants:[{begin:/\{\{/,end:/\}\}/},{begin:/%\{/,end:/\}/}]}]},i=e.inherit(a,{ +variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),r={ +end:",",endsWithParent:!0,excludeEnd:!0,keywords:n,relevance:0},s={begin:/\{/, +end:/\}/,contains:[r],illegal:"\\n",relevance:0},o={begin:"\\[",end:"\\]", +contains:[r],illegal:"\\n",relevance:0},l=[{className:"attr",variants:[{ +begin:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{begin:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{ +begin:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{className:"meta",begin:"^---\\s*$", +relevance:10},{className:"string", +begin:"[\\|>]([1-9]?[+-])?[ ]*\\n( +)[^ ][^\\n]*\\n(\\2[^\\n]+\\n?)*"},{ +begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0, +relevance:0},{className:"type",begin:"!\\w+!"+t},{className:"type", +begin:"!<"+t+">"},{className:"type",begin:"!"+t},{className:"type",begin:"!!"+t +},{className:"meta",begin:"&"+e.UNDERSCORE_IDENT_RE+"$"},{className:"meta", +begin:"\\*"+e.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"-(?=[ ]|$)", +relevance:0},e.HASH_COMMENT_MODE,{beginKeywords:n,keywords:{literal:n}},{ +className:"number", +begin:"\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b" +},{className:"number",begin:e.C_NUMBER_RE+"\\b",relevance:0},s,o,a],c=[...l] +;return c.pop(),c.push(i),r.contains=c,{name:"YAML",case_insensitive:!0, +aliases:["yml"],contains:l}}});const He=ae;for(const e of Object.keys(Ke)){ +const n=e.replace("grmr_","").replace("_","-");He.registerLanguage(n,Ke[e])} +return He}() +;"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs); + +// Copy button plugin +class CopyButtonPlugin{constructor(options={}){this.hook=options.hook;this.callback=options.callback;this.lang=options.lang||document.documentElement.lang||"en";this.autohide=typeof options.autohide!=="undefined"?options.autohide:true}"after:highlightElement"({el,text}){if(el.parentElement.querySelector(".hljs-copy-button"))return;let{hook,callback,lang,autohide}=this;let container=Object.assign(document.createElement("div"),{className:"hljs-copy-container"});container.dataset.autohide=autohide;let button=Object.assign(document.createElement("button"),{innerHTML:locales[lang]?.[0]||"Copy",className:"hljs-copy-button"});button.dataset.copied=false;el.parentElement.classList.add("hljs-copy-wrapper");el.parentElement.appendChild(container);container.appendChild(button);container.style.setProperty("--hljs-theme-background",window.getComputedStyle(el).backgroundColor);container.style.setProperty("--hljs-theme-color",window.getComputedStyle(el).color);container.style.setProperty("--hljs-theme-padding",window.getComputedStyle(el).padding);button.onclick=function(){if(!navigator.clipboard)return;let newText=text;if(hook&&typeof hook==="function"){newText=hook(text,el)||text}navigator.clipboard.writeText(newText).then(function(){button.innerHTML=locales[lang]?.[1]||"Copied!";button.dataset.copied=true;let alert=Object.assign(document.createElement("div"),{role:"status",className:"hljs-copy-alert",innerHTML:locales[lang]?.[2]||"Copied to clipboard"});el.parentElement.appendChild(alert);setTimeout(()=>{button.innerHTML=locales[lang]?.[0]||"Copy";button.dataset.copied=false;el.parentElement.removeChild(alert);alert=null},2e3)}).then(function(){if(typeof callback==="function")return callback(newText,el)})}}}if(typeof module!="undefined"){module.exports=CopyButtonPlugin}const locales={en:["Copy","Copied!","Copied to clipboard"],es:["Copiar","¡Copiado!","Copiado al portapapeles"],"pt-BR":["Copiar","Copiado!","Copiado para a área de transferência"],fr:["Copier","Copié !","Copié dans le presse-papier"],de:["Kopieren","Kopiert!","In die Zwischenablage kopiert"],ja:["コピー","コピーしました!","クリップボードにコピーしました"],ko:["복사","복사됨!","클립보드에 복사됨"],ru:["Копировать","Скопировано!","Скопировано в буфер обмена"],zh:["复制","已复制!","已复制到剪贴板"],"zh-tw":["複製","已複製!","已複製到剪貼簿"]}; + +// Additional language definitions +(function() { + if (typeof hljs === 'undefined') { + console.error('Highlight.js core not available'); + return; + } + + + // Language: rust + (function() { + /*! `rust` grammar compiled for Highlight.js 11.9.0 */ +(()=>{var e=(()=>{"use strict";return e=>{const t=e.regex,n={ +className:"title.function.invoke",relevance:0, +begin:t.concat(/\b/,/(?!let|for|while|if|else|match\b)/,e.IDENT_RE,t.lookahead(/\s*\(/)) +},a="([ui](8|16|32|64|128|size)|f(32|64))?",i=["drop ","Copy","Send","Sized","Sync","Drop","Fn","FnMut","FnOnce","ToOwned","Clone","Debug","PartialEq","PartialOrd","Eq","Ord","AsRef","AsMut","Into","From","Default","Iterator","Extend","IntoIterator","DoubleEndedIterator","ExactSizeIterator","SliceConcatExt","ToString","assert!","assert_eq!","bitflags!","bytes!","cfg!","col!","concat!","concat_idents!","debug_assert!","debug_assert_eq!","env!","eprintln!","panic!","file!","format!","format_args!","include_bytes!","include_str!","line!","local_data_key!","module_path!","option_env!","print!","println!","select!","stringify!","try!","unimplemented!","unreachable!","vec!","write!","writeln!","macro_rules!","assert_ne!","debug_assert_ne!"],r=["i8","i16","i32","i64","i128","isize","u8","u16","u32","u64","u128","usize","f32","f64","str","char","bool","Box","Option","Result","String","Vec"] +;return{name:"Rust",aliases:["rs"],keywords:{$pattern:e.IDENT_RE+"!?",type:r, +keyword:["abstract","as","async","await","become","box","break","const","continue","crate","do","dyn","else","enum","extern","false","final","fn","for","if","impl","in","let","loop","macro","match","mod","move","mut","override","priv","pub","ref","return","self","Self","static","struct","super","trait","true","try","type","typeof","unsafe","unsized","use","virtual","where","while","yield"], +literal:["true","false","Some","None","Ok","Err"],built_in:i},illegal:""},n]}}})() +;hljs.registerLanguage("rust",e)})(); + })(); + + // Language: typescript + (function() { + /*! `typescript` grammar compiled for Highlight.js 11.9.0 */ +(()=>{var e=(()=>{"use strict" +;const e="[A-Za-z$_][0-9A-Za-z$_]*",n=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],a=["true","false","null","undefined","NaN","Infinity"],t=["Object","Function","Boolean","Symbol","Math","Date","Number","BigInt","String","RegExp","Array","Float32Array","Float64Array","Int8Array","Uint8Array","Uint8ClampedArray","Int16Array","Int32Array","Uint16Array","Uint32Array","BigInt64Array","BigUint64Array","Set","Map","WeakSet","WeakMap","ArrayBuffer","SharedArrayBuffer","Atomics","DataView","JSON","Promise","Generator","GeneratorFunction","AsyncFunction","Reflect","Proxy","Intl","WebAssembly"],s=["Error","EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"],r=["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],c=["arguments","this","super","console","window","document","localStorage","sessionStorage","module","global"],i=[].concat(r,t,s) +;function o(o){const l=o.regex,d=e,b={begin:/<[A-Za-z0-9\\._:-]+/, +end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(e,n)=>{ +const a=e[0].length+e.index,t=e.input[a] +;if("<"===t||","===t)return void n.ignoreMatch();let s +;">"===t&&(((e,{after:n})=>{const a="",$={ +match:[/const|var|let/,/\s+/,d,/\s*/,/=\s*/,/(async\s*)?/,l.lookahead(B)], +keywords:"async",className:{1:"keyword",3:"title.function"},contains:[R]} +;return{name:"JavaScript",aliases:["js","jsx","mjs","cjs"],keywords:g,exports:{ +PARAMS_CONTAINS:w,CLASS_REFERENCE:k},illegal:/#(?![$_A-z])/, +contains:[o.SHEBANG({label:"shebang",binary:"node",relevance:5}),{ +label:"use_strict",className:"meta",relevance:10, +begin:/^\s*['"]use (strict|asm)['"]/ +},o.APOS_STRING_MODE,o.QUOTE_STRING_MODE,p,N,f,_,h,{match:/\$\d+/},y,k,{ +className:"attr",begin:d+l.lookahead(":"),relevance:0},$,{ +begin:"("+o.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*", +keywords:"return throw case",relevance:0,contains:[h,o.REGEXP_MODE,{ +className:"function",begin:B,returnBegin:!0,end:"\\s*=>",contains:[{ +className:"params",variants:[{begin:o.UNDERSCORE_IDENT_RE,relevance:0},{ +className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0, +excludeEnd:!0,keywords:g,contains:w}]}]},{begin:/,/,relevance:0},{match:/\s+/, +relevance:0},{variants:[{begin:"<>",end:""},{ +match:/<[A-Za-z0-9\\._:-]+\s*\/>/},{begin:b.begin, +"on:begin":b.isTrulyOpeningTag,end:b.end}],subLanguage:"xml",contains:[{ +begin:b.begin,end:b.end,skip:!0,contains:["self"]}]}]},O,{ +beginKeywords:"while if switch catch for"},{ +begin:"\\b(?!function)"+o.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{", +returnBegin:!0,label:"func.def",contains:[R,o.inherit(o.TITLE_MODE,{begin:d, +className:"title.function"})]},{match:/\.\.\./,relevance:0},T,{match:"\\$"+d, +relevance:0},{match:[/\bconstructor(?=\s*\()/],className:{1:"title.function"}, +contains:[R]},C,{relevance:0,match:/\b[A-Z][A-Z_0-9]+\b/, +className:"variable.constant"},x,M,{match:/\$[(.]/}]}}return t=>{ +const s=o(t),r=e,l=["any","void","number","boolean","string","object","never","symbol","bigint","unknown"],d={ +beginKeywords:"namespace",end:/\{/,excludeEnd:!0, +contains:[s.exports.CLASS_REFERENCE]},b={beginKeywords:"interface",end:/\{/, +excludeEnd:!0,keywords:{keyword:"interface extends",built_in:l}, +contains:[s.exports.CLASS_REFERENCE]},g={$pattern:e, +keyword:n.concat(["type","namespace","interface","public","private","protected","implements","declare","abstract","readonly","enum","override"]), +literal:a,built_in:i.concat(l),"variable.language":c},u={className:"meta", +begin:"@"+r},m=(e,n,a)=>{const t=e.contains.findIndex((e=>e.label===n)) +;if(-1===t)throw Error("can not find mode to replace");e.contains.splice(t,1,a)} +;return Object.assign(s.keywords,g), +s.exports.PARAMS_CONTAINS.push(u),s.contains=s.contains.concat([u,d,b]), +m(s,"shebang",t.SHEBANG()),m(s,"use_strict",{className:"meta",relevance:10, +begin:/^\s*['"]use strict['"]/ +}),s.contains.find((e=>"func.def"===e.label)).relevance=0,Object.assign(s,{ +name:"TypeScript",aliases:["ts","tsx","mts","cts"]}),s}})() +;hljs.registerLanguage("typescript",e)})(); + })(); + + // Language: bash + (function() { + /*! `bash` grammar compiled for Highlight.js 11.9.0 */ +(()=>{var e=(()=>{"use strict";return e=>{const s=e.regex,t={},n={begin:/\$\{/, +end:/\}/,contains:["self",{begin:/:-/,contains:[t]}]};Object.assign(t,{ +className:"variable",variants:[{ +begin:s.concat(/\$[\w\d#@][\w\d_]*/,"(?![\\w\\d])(?![$])")},n]});const a={ +className:"subst",begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE]},i={ +begin:/<<-?\s*(?=\w+)/,starts:{contains:[e.END_SAME_AS_BEGIN({begin:/(\w+)/, +end:/(\w+)/,className:"string"})]}},c={className:"string",begin:/"/,end:/"/, +contains:[e.BACKSLASH_ESCAPE,t,a]};a.contains.push(c);const o={begin:/\$?\(\(/, +end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},e.NUMBER_MODE,t] +},r=e.SHEBANG({binary:"(fish|bash|zsh|sh|csh|ksh|tcsh|dash|scsh)",relevance:10 +}),l={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0, +contains:[e.inherit(e.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{ +name:"Bash",aliases:["sh"],keywords:{$pattern:/\b[a-z][a-z0-9._-]+\b/, +keyword:["if","then","else","elif","fi","for","while","until","in","do","done","case","esac","function","select"], +literal:["true","false"], +built_in:["break","cd","continue","eval","exec","exit","export","getopts","hash","pwd","readonly","return","shift","test","times","trap","umask","unset","alias","bind","builtin","caller","command","declare","echo","enable","help","let","local","logout","mapfile","printf","read","readarray","source","type","typeset","ulimit","unalias","set","shopt","autoload","bg","bindkey","bye","cap","chdir","clone","comparguments","compcall","compctl","compdescribe","compfiles","compgroups","compquote","comptags","comptry","compvalues","dirs","disable","disown","echotc","echoti","emulate","fc","fg","float","functions","getcap","getln","history","integer","jobs","kill","limit","log","noglob","popd","print","pushd","pushln","rehash","sched","setcap","setopt","stat","suspend","ttyctl","unfunction","unhash","unlimit","unsetopt","vared","wait","whence","where","which","zcompile","zformat","zftp","zle","zmodload","zparseopts","zprof","zpty","zregexparse","zsocket","zstyle","ztcp","chcon","chgrp","chown","chmod","cp","dd","df","dir","dircolors","ln","ls","mkdir","mkfifo","mknod","mktemp","mv","realpath","rm","rmdir","shred","sync","touch","truncate","vdir","b2sum","base32","base64","cat","cksum","comm","csplit","cut","expand","fmt","fold","head","join","md5sum","nl","numfmt","od","paste","ptx","pr","sha1sum","sha224sum","sha256sum","sha384sum","sha512sum","shuf","sort","split","sum","tac","tail","tr","tsort","unexpand","uniq","wc","arch","basename","chroot","date","dirname","du","echo","env","expr","factor","groups","hostid","id","link","logname","nice","nohup","nproc","pathchk","pinky","printenv","printf","pwd","readlink","runcon","seq","sleep","stat","stdbuf","stty","tee","test","timeout","tty","uname","unlink","uptime","users","who","whoami","yes"] +},contains:[r,e.SHEBANG(),l,o,e.HASH_COMMENT_MODE,i,{match:/(\/[a-z._-]+)+/},c,{ +match:/\\"/},{className:"string",begin:/'/,end:/'/},{match:/\\'/},t]}}})() +;hljs.registerLanguage("bash",e)})(); + })(); + + // Language: yaml + (function() { + /*! `yaml` grammar compiled for Highlight.js 11.9.0 */ +(()=>{var e=(()=>{"use strict";return e=>{ +const n="true false yes no null",a="[\\w#;/?:@&=+$,.~*'()[\\]]+",s={ +className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/ +},{begin:/\S+/}],contains:[e.BACKSLASH_ESCAPE,{className:"template-variable", +variants:[{begin:/\{\{/,end:/\}\}/},{begin:/%\{/,end:/\}/}]}]},i=e.inherit(s,{ +variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),l={ +end:",",endsWithParent:!0,excludeEnd:!0,keywords:n,relevance:0},t={begin:/\{/, +end:/\}/,contains:[l],illegal:"\\n",relevance:0},g={begin:"\\[",end:"\\]", +contains:[l],illegal:"\\n",relevance:0},b=[{className:"attr",variants:[{ +begin:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{begin:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{ +begin:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{className:"meta",begin:"^---\\s*$", +relevance:10},{className:"string", +begin:"[\\|>]([1-9]?[+-])?[ ]*\\n( +)[^ ][^\\n]*\\n(\\2[^\\n]+\\n?)*"},{ +begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0, +relevance:0},{className:"type",begin:"!\\w+!"+a},{className:"type", +begin:"!<"+a+">"},{className:"type",begin:"!"+a},{className:"type",begin:"!!"+a +},{className:"meta",begin:"&"+e.UNDERSCORE_IDENT_RE+"$"},{className:"meta", +begin:"\\*"+e.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"-(?=[ ]|$)", +relevance:0},e.HASH_COMMENT_MODE,{beginKeywords:n,keywords:{literal:n}},{ +className:"number", +begin:"\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b" +},{className:"number",begin:e.C_NUMBER_RE+"\\b",relevance:0},t,g,s],r=[...b] +;return r.pop(),r.push(i),l.contains=r,{name:"YAML",case_insensitive:!0, +aliases:["yml"],contains:b}}})();hljs.registerLanguage("yaml",e)})(); + })(); + + // Language: dockerfile + (function() { + /*! `dockerfile` grammar compiled for Highlight.js 11.9.0 */ +(()=>{var e=(()=>{"use strict";return e=>({name:"Dockerfile",aliases:["docker"], +case_insensitive:!0, +keywords:["from","maintainer","expose","env","arg","user","onbuild","stopsignal"], +contains:[e.HASH_COMMENT_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.NUMBER_MODE,{ +beginKeywords:"run cmd entrypoint volume add copy workdir label healthcheck shell", +starts:{end:/[^\\]$/,subLanguage:"bash"}}],illegal:"{var e=(()=>{"use strict";return e=>{ +const r=e.regex,t=e.COMMENT("--","$"),n=["true","false","unknown"],a=["bigint","binary","blob","boolean","char","character","clob","date","dec","decfloat","decimal","float","int","integer","interval","nchar","nclob","national","numeric","real","row","smallint","time","timestamp","varchar","varying","varbinary"],i=["abs","acos","array_agg","asin","atan","avg","cast","ceil","ceiling","coalesce","corr","cos","cosh","count","covar_pop","covar_samp","cume_dist","dense_rank","deref","element","exp","extract","first_value","floor","json_array","json_arrayagg","json_exists","json_object","json_objectagg","json_query","json_table","json_table_primitive","json_value","lag","last_value","lead","listagg","ln","log","log10","lower","max","min","mod","nth_value","ntile","nullif","percent_rank","percentile_cont","percentile_disc","position","position_regex","power","rank","regr_avgx","regr_avgy","regr_count","regr_intercept","regr_r2","regr_slope","regr_sxx","regr_sxy","regr_syy","row_number","sin","sinh","sqrt","stddev_pop","stddev_samp","substring","substring_regex","sum","tan","tanh","translate","translate_regex","treat","trim","trim_array","unnest","upper","value_of","var_pop","var_samp","width_bucket"],s=["create table","insert into","primary key","foreign key","not null","alter table","add constraint","grouping sets","on overflow","character set","respect nulls","ignore nulls","nulls first","nulls last","depth first","breadth first"],o=i,c=["abs","acos","all","allocate","alter","and","any","are","array","array_agg","array_max_cardinality","as","asensitive","asin","asymmetric","at","atan","atomic","authorization","avg","begin","begin_frame","begin_partition","between","bigint","binary","blob","boolean","both","by","call","called","cardinality","cascaded","case","cast","ceil","ceiling","char","char_length","character","character_length","check","classifier","clob","close","coalesce","collate","collect","column","commit","condition","connect","constraint","contains","convert","copy","corr","corresponding","cos","cosh","count","covar_pop","covar_samp","create","cross","cube","cume_dist","current","current_catalog","current_date","current_default_transform_group","current_path","current_role","current_row","current_schema","current_time","current_timestamp","current_path","current_role","current_transform_group_for_type","current_user","cursor","cycle","date","day","deallocate","dec","decimal","decfloat","declare","default","define","delete","dense_rank","deref","describe","deterministic","disconnect","distinct","double","drop","dynamic","each","element","else","empty","end","end_frame","end_partition","end-exec","equals","escape","every","except","exec","execute","exists","exp","external","extract","false","fetch","filter","first_value","float","floor","for","foreign","frame_row","free","from","full","function","fusion","get","global","grant","group","grouping","groups","having","hold","hour","identity","in","indicator","initial","inner","inout","insensitive","insert","int","integer","intersect","intersection","interval","into","is","join","json_array","json_arrayagg","json_exists","json_object","json_objectagg","json_query","json_table","json_table_primitive","json_value","lag","language","large","last_value","lateral","lead","leading","left","like","like_regex","listagg","ln","local","localtime","localtimestamp","log","log10","lower","match","match_number","match_recognize","matches","max","member","merge","method","min","minute","mod","modifies","module","month","multiset","national","natural","nchar","nclob","new","no","none","normalize","not","nth_value","ntile","null","nullif","numeric","octet_length","occurrences_regex","of","offset","old","omit","on","one","only","open","or","order","out","outer","over","overlaps","overlay","parameter","partition","pattern","per","percent","percent_rank","percentile_cont","percentile_disc","period","portion","position","position_regex","power","precedes","precision","prepare","primary","procedure","ptf","range","rank","reads","real","recursive","ref","references","referencing","regr_avgx","regr_avgy","regr_count","regr_intercept","regr_r2","regr_slope","regr_sxx","regr_sxy","regr_syy","release","result","return","returns","revoke","right","rollback","rollup","row","row_number","rows","running","savepoint","scope","scroll","search","second","seek","select","sensitive","session_user","set","show","similar","sin","sinh","skip","smallint","some","specific","specifictype","sql","sqlexception","sqlstate","sqlwarning","sqrt","start","static","stddev_pop","stddev_samp","submultiset","subset","substring","substring_regex","succeeds","sum","symmetric","system","system_time","system_user","table","tablesample","tan","tanh","then","time","timestamp","timezone_hour","timezone_minute","to","trailing","translate","translate_regex","translation","treat","trigger","trim","trim_array","true","truncate","uescape","union","unique","unknown","unnest","update","upper","user","using","value","values","value_of","var_pop","var_samp","varbinary","varchar","varying","versioning","when","whenever","where","width_bucket","window","with","within","without","year","add","asc","collation","desc","final","first","last","view"].filter((e=>!i.includes(e))),l={ +begin:r.concat(/\b/,r.either(...o),/\s*\(/),relevance:0,keywords:{built_in:o}} +;return{name:"SQL",case_insensitive:!0,illegal:/[{}]|<\//,keywords:{ +$pattern:/\b[\w\.]+/,keyword:((e,{exceptions:r,when:t}={})=>{const n=t +;return r=r||[],e.map((e=>e.match(/\|\d+$/)||r.includes(e)?e:n(e)?e+"|0":e)) +})(c,{when:e=>e.length<3}),literal:n,type:a, +built_in:["current_catalog","current_date","current_default_transform_group","current_path","current_role","current_schema","current_transform_group_for_type","current_user","session_user","system_time","system_user","current_time","localtime","current_timestamp","localtimestamp"] +},contains:[{begin:r.either(...s),relevance:0,keywords:{$pattern:/[\w\.]+/, +keyword:c.concat(s),literal:n,type:a}},{className:"type", +begin:r.either("double precision","large object","with timezone","without timezone") +},l,{className:"variable",begin:/@[a-z0-9][a-z0-9_]*/},{className:"string", +variants:[{begin:/'/,end:/'/,contains:[{begin:/''/}]}]},{begin:/"/,end:/"/, +contains:[{begin:/""/}]},e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE,t,{ +className:"operator",begin:/[-+*/=%^~]|&&?|\|\|?|!=?|<(?:=>?|<|>)?|>[>=]?/, +relevance:0}]}}})();hljs.registerLanguage("sql",e)})(); + })(); + + // Language: python + (function() { + /*! `python` grammar compiled for Highlight.js 11.9.0 */ +(()=>{var e=(()=>{"use strict";return e=>{ +const n=e.regex,a=/[\p{XID_Start}_]\p{XID_Continue}*/u,i=["and","as","assert","async","await","break","case","class","continue","def","del","elif","else","except","finally","for","from","global","if","import","in","is","lambda","match","nonlocal|10","not","or","pass","raise","return","try","while","with","yield"],s={ +$pattern:/[A-Za-z]\w+|__\w+__/,keyword:i, +built_in:["__import__","abs","all","any","ascii","bin","bool","breakpoint","bytearray","bytes","callable","chr","classmethod","compile","complex","delattr","dict","dir","divmod","enumerate","eval","exec","filter","float","format","frozenset","getattr","globals","hasattr","hash","help","hex","id","input","int","isinstance","issubclass","iter","len","list","locals","map","max","memoryview","min","next","object","oct","open","ord","pow","print","property","range","repr","reversed","round","set","setattr","slice","sorted","staticmethod","str","sum","super","tuple","type","vars","zip"], +literal:["__debug__","Ellipsis","False","None","NotImplemented","True"], +type:["Any","Callable","Coroutine","Dict","List","Literal","Generic","Optional","Sequence","Set","Tuple","Type","Union"] +},t={className:"meta",begin:/^(>>>|\.\.\.) /},r={className:"subst",begin:/\{/, +end:/\}/,keywords:s,illegal:/#/},l={begin:/\{\{/,relevance:0},b={ +className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{ +begin:/([uU]|[bB]|[rR]|[bB][rR]|[rR][bB])?'''/,end:/'''/, +contains:[e.BACKSLASH_ESCAPE,t],relevance:10},{ +begin:/([uU]|[bB]|[rR]|[bB][rR]|[rR][bB])?"""/,end:/"""/, +contains:[e.BACKSLASH_ESCAPE,t],relevance:10},{ +begin:/([fF][rR]|[rR][fF]|[fF])'''/,end:/'''/, +contains:[e.BACKSLASH_ESCAPE,t,l,r]},{begin:/([fF][rR]|[rR][fF]|[fF])"""/, +end:/"""/,contains:[e.BACKSLASH_ESCAPE,t,l,r]},{begin:/([uU]|[rR])'/,end:/'/, +relevance:10},{begin:/([uU]|[rR])"/,end:/"/,relevance:10},{ +begin:/([bB]|[bB][rR]|[rR][bB])'/,end:/'/},{begin:/([bB]|[bB][rR]|[rR][bB])"/, +end:/"/},{begin:/([fF][rR]|[rR][fF]|[fF])'/,end:/'/, +contains:[e.BACKSLASH_ESCAPE,l,r]},{begin:/([fF][rR]|[rR][fF]|[fF])"/,end:/"/, +contains:[e.BACKSLASH_ESCAPE,l,r]},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE] +},o="[0-9](_?[0-9])*",c=`(\\b(${o}))?\\.(${o})|\\b(${o})\\.`,d="\\b|"+i.join("|"),g={ +className:"number",relevance:0,variants:[{ +begin:`(\\b(${o})|(${c}))[eE][+-]?(${o})[jJ]?(?=${d})`},{begin:`(${c})[jJ]?`},{ +begin:`\\b([1-9](_?[0-9])*|0+(_?0)*)[lLjJ]?(?=${d})`},{ +begin:`\\b0[bB](_?[01])+[lL]?(?=${d})`},{begin:`\\b0[oO](_?[0-7])+[lL]?(?=${d})` +},{begin:`\\b0[xX](_?[0-9a-fA-F])+[lL]?(?=${d})`},{begin:`\\b(${o})[jJ](?=${d})` +}]},p={className:"comment",begin:n.lookahead(/# type:/),end:/$/,keywords:s, +contains:[{begin:/# type:/},{begin:/#/,end:/\b\B/,endsWithParent:!0}]},m={ +className:"params",variants:[{className:"",begin:/\(\s*\)/,skip:!0},{begin:/\(/, +end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:s, +contains:["self",t,g,b,e.HASH_COMMENT_MODE]}]};return r.contains=[b,g,t],{ +name:"Python",aliases:["py","gyp","ipython"],unicodeRegex:!0,keywords:s, +illegal:/(<\/|\?)|=>/,contains:[t,g,{begin:/\bself\b/},{beginKeywords:"if", +relevance:0},b,p,e.HASH_COMMENT_MODE,{match:[/\bdef/,/\s+/,a],scope:{ +1:"keyword",3:"title.function"},contains:[m]},{variants:[{ +match:[/\bclass/,/\s+/,a,/\s*/,/\(\s*/,a,/\s*\)/]},{match:[/\bclass/,/\s+/,a]}], +scope:{1:"keyword",3:"title.class",6:"title.class.inherited"}},{ +className:"meta",begin:/^[\t ]*@/,end:/(?=#)|$/,contains:[g,m,b]}]}}})() +;hljs.registerLanguage("python",e)})(); + })(); + + // Language: ini + (function() { + /*! `ini` grammar compiled for Highlight.js 11.9.0 */ +(()=>{var e=(()=>{"use strict";return e=>{const n=e.regex,a={className:"number", +relevance:0,variants:[{begin:/([+-]+)?[\d]+_[\d_]+/},{begin:e.NUMBER_RE}] +},s=e.COMMENT();s.variants=[{begin:/;/,end:/$/},{begin:/#/,end:/$/}];const i={ +className:"variable",variants:[{begin:/\$[\w\d"][\w\d_]*/},{begin:/\$\{(.*?)\}/ +}]},t={className:"literal",begin:/\bon|off|true|false|yes|no\b/},r={ +className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{begin:"'''", +end:"'''",relevance:10},{begin:'"""',end:'"""',relevance:10},{begin:'"',end:'"' +},{begin:"'",end:"'"}]},l={begin:/\[/,end:/\]/,contains:[s,t,i,r,a,"self"], +relevance:0},c=n.either(/[A-Za-z0-9_-]+/,/"(\\"|[^"])*"/,/'[^']*'/);return{ +name:"TOML, also INI",aliases:["toml"],case_insensitive:!0,illegal:/\S/, +contains:[s,{className:"section",begin:/\[+/,end:/\]+/},{ +begin:n.concat(c,"(\\s*\\.\\s*",c,")*",n.lookahead(/\s*=\s*[^#\s]/)), +className:"attr",starts:{end:/$/,contains:[s,l,t,i,r,a]}}]}}})() +;hljs.registerLanguage("ini",e)})(); + })(); + + // Language: properties + (function() { + /*! `properties` grammar compiled for Highlight.js 11.9.0 */ +(()=>{var e=(()=>{"use strict";return e=>{ +const n="[ \\t\\f]*",t=n+"[:=]"+n,s="[ \\t\\f]+",a="([^\\\\:= \\t\\f\\n]|\\\\.)+",r={ +end:"("+t+"|"+s+")",relevance:0,starts:{className:"string",end:/$/,relevance:0, +contains:[{begin:"\\\\\\\\"},{begin:"\\\\\\n"}]}};return{name:".properties", +disableAutodetect:!0,case_insensitive:!0,illegal:/\S/, +contains:[e.COMMENT("^\\s*[!#]","$"),{returnBegin:!0,variants:[{begin:a+t},{ +begin:a+s}],contains:[{className:"attr",begin:a,endsParent:!0}],starts:r},{ +className:"attr",begin:a+n+"$"}]}}})();hljs.registerLanguage("properties",e) +})(); + })(); + + // Language: markdown + (function() { + /*! `markdown` grammar compiled for Highlight.js 11.9.0 */ +(()=>{var e=(()=>{"use strict";return e=>{const n={begin:/<\/?[A-Za-z_]/, +end:">",subLanguage:"xml",relevance:0},a={variants:[{begin:/\[.+?\]\[.*?\]/, +relevance:0},{ +begin:/\[.+?\]\(((data|javascript|mailto):|(?:http|ftp)s?:\/\/).*?\)/, +relevance:2},{ +begin:e.regex.concat(/\[.+?\]\(/,/[A-Za-z][A-Za-z0-9+.-]*/,/:\/\/.*?\)/), +relevance:2},{begin:/\[.+?\]\([./?&#].*?\)/,relevance:1},{ +begin:/\[.*?\]\(.*?\)/,relevance:0}],returnBegin:!0,contains:[{match:/\[(?=\])/ +},{className:"string",relevance:0,begin:"\\[",end:"\\]",excludeBegin:!0, +returnEnd:!0},{className:"link",relevance:0,begin:"\\]\\(",end:"\\)", +excludeBegin:!0,excludeEnd:!0},{className:"symbol",relevance:0,begin:"\\]\\[", +end:"\\]",excludeBegin:!0,excludeEnd:!0}]},i={className:"strong",contains:[], +variants:[{begin:/_{2}(?!\s)/,end:/_{2}/},{begin:/\*{2}(?!\s)/,end:/\*{2}/}] +},s={className:"emphasis",contains:[],variants:[{begin:/\*(?![*\s])/,end:/\*/},{ +begin:/_(?![_\s])/,end:/_/,relevance:0}]},c=e.inherit(i,{contains:[] +}),t=e.inherit(s,{contains:[]});i.contains.push(t),s.contains.push(c) +;let g=[n,a];return[i,s,c,t].forEach((e=>{e.contains=e.contains.concat(g) +})),g=g.concat(i,s),{name:"Markdown",aliases:["md","mkdown","mkd"],contains:[{ +className:"section",variants:[{begin:"^#{1,6}",end:"$",contains:g},{ +begin:"(?=^.+?\\n[=-]{2,}$)",contains:[{begin:"^[=-]*$"},{begin:"^",end:"\\n", +contains:g}]}]},n,{className:"bullet",begin:"^[ \t]*([*+-]|(\\d+\\.))(?=\\s+)", +end:"\\s+",excludeEnd:!0},i,s,{className:"quote",begin:"^>\\s+",contains:g, +end:"$"},{className:"code",variants:[{begin:"(`{3,})[^`](.|\\n)*?\\1`*[ ]*"},{ +begin:"(~{3,})[^~](.|\\n)*?\\1~*[ ]*"},{begin:"```",end:"```+[ ]*$"},{ +begin:"~~~",end:"~~~+[ ]*$"},{begin:"`.+?`"},{begin:"(?=^( {4}|\\t))", +contains:[{begin:"^( {4}|\\t)",end:"(\\n)$"}],relevance:0}]},{ +begin:"^[-\\*]{3,}",end:"$"},a,{begin:/^\[[^\n]+\]:/,returnBegin:!0,contains:[{ +className:"symbol",begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0},{ +className:"link",begin:/:\s*/,end:/$/,excludeBegin:!0}]}]}}})() +;hljs.registerLanguage("markdown",e)})(); + })(); + + // Language: nix + (function() { + /*! `nix` grammar compiled for Highlight.js 11.9.0 */ +(()=>{var e=(()=>{"use strict";return e=>{const n={ +keyword:["rec","with","let","in","inherit","assert","if","else","then"], +literal:["true","false","or","and","null"], +built_in:["import","abort","baseNameOf","dirOf","isNull","builtins","map","removeAttrs","throw","toString","derivation"] +},s={className:"subst",begin:/\$\{/,end:/\}/,keywords:n},a={className:"string", +contains:[{className:"char.escape",begin:/''\$/},s],variants:[{begin:"''", +end:"''"},{begin:'"',end:'"'}] +},i=[e.NUMBER_MODE,e.HASH_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,{ +begin:/[a-zA-Z0-9-_]+(\s*=)/,returnBegin:!0,relevance:0,contains:[{ +className:"attr",begin:/\S+/,relevance:.2}]}];return s.contains=i,{name:"Nix", +aliases:["nixos"],keywords:n,contains:i}}})();hljs.registerLanguage("nix",e) +})(); + })(); + +})(); + +// Expose available languages globally for use by other scripts +if (typeof window !== 'undefined') { + window.__AVAILABLE_LANGUAGES = ["en","es"]; +} else if (typeof globalThis !== 'undefined') { + globalThis.__AVAILABLE_LANGUAGES = ["en","es"]; +} + +// Auto-initialize when DOM is ready +if (typeof document !== 'undefined') { + function initializeHighlightJs() { + if (typeof hljs !== 'undefined' && hljs.highlightAll) { + hljs.configure({ ignoreUnescapedHTML: true }); + hljs.highlightAll(); + + // Add copy button plugin with dynamic language detection and autohide configuration + if (typeof CopyButtonPlugin !== 'undefined') { + const docLang = document.documentElement.lang || 'en'; + + // Dynamically discovered available languages from content/locales + const AVAILABLE_LANGUAGES = ["en","es"]; + + // Build language configuration for all available languages + const langConfig = {}; + AVAILABLE_LANGUAGES.forEach(lang => { + langConfig[lang] = { autohide: false, lang: lang }; + }); + + // Use detected language or fallback to first available language + const config = langConfig[docLang] || langConfig[AVAILABLE_LANGUAGES[0]] || { autohide: false, lang: 'en' }; + hljs.addPlugin(new CopyButtonPlugin(config)); + } + } + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initializeHighlightJs); + } else { + // DOM already ready + initializeHighlightJs(); + } +} diff --git a/site/site/public/js/highlight-utils.js b/site/site/public/js/highlight-utils.js new file mode 100644 index 0000000..53f5848 --- /dev/null +++ b/site/site/public/js/highlight-utils.js @@ -0,0 +1,48 @@ +// Additional highlight.js utilities (auto-init is handled by bundle) +window.highlightCode = function() { + // Find all code blocks that haven't been highlighted yet + document.querySelectorAll('pre code:not(.hljs)').forEach(function(block) { + if (typeof hljs !== 'undefined' && hljs.highlightElement) { + hljs.highlightElement(block); + } + }); +}; + +window.highlightContainer = function(containerSelector) { + // Highlight code blocks within a specific container + const container = document.querySelector(containerSelector); + if (container && typeof hljs !== 'undefined') { + container.querySelectorAll('pre code:not(.hljs)').forEach(function(block) { + hljs.highlightElement(block); + }); + } +}; + +// Re-highlight when content changes (for dynamic content) +document.addEventListener('DOMContentLoaded', function() { + const observer = new MutationObserver(function(mutations) { + let shouldHighlight = false; + mutations.forEach(function(mutation) { + if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { + // Check if any added nodes contain code blocks + mutation.addedNodes.forEach(function(node) { + if (node.nodeType === 1 && (node.tagName === 'PRE' || node.querySelector('pre'))) { + shouldHighlight = true; + } + }); + } + }); + + if (shouldHighlight) { + setTimeout(function() { + window.highlightCode(); + }, 100); + } + }); + + // Start observing document changes + observer.observe(document.body, { + childList: true, + subtree: true + }); +}); \ No newline at end of file diff --git a/site/site/public/js/highlight-utils.min.js b/site/site/public/js/highlight-utils.min.js new file mode 100644 index 0000000..6420223 --- /dev/null +++ b/site/site/public/js/highlight-utils.min.js @@ -0,0 +1 @@ +window.highlightCode=function(){document.querySelectorAll('pre code:not(.hljs)').forEach(function(block){if(typeof hljs!=='undefined'&&hljs.highlightElement){hljs.highlightElement(block);}});};window.highlightContainer=function(containerSelector){const container=document.querySelector(containerSelector);if(container&&typeof hljs!=='undefined'){container.querySelectorAll('pre code:not(.hljs)').forEach(function(block){hljs.highlightElement(block);});}};document.addEventListener('DOMContentLoaded',function(){const observer=new MutationObserver(function(mutations){let shouldHighlight=false;mutations.forEach(function(mutation){if(mutation.type==='childList'&&mutation.addedNodes.length>0){mutation.addedNodes.forEach(function(node){if(node.nodeType===1&&(node.tagName==='PRE'||node.querySelector('pre'))){shouldHighlight=true;}});}});if(shouldHighlight){setTimeout(function(){window.highlightCode();},100);}});observer.observe(document.body,{childList: true,subtree: true});}); \ No newline at end of file diff --git a/site/site/public/js/htmx-reinit.js b/site/site/public/js/htmx-reinit.js new file mode 100644 index 0000000..87a2524 --- /dev/null +++ b/site/site/public/js/htmx-reinit.js @@ -0,0 +1,130 @@ +/** + * htmx-reinit.js — re-initialise non-HTMX scripts after every HTMX swap. + * + * In the htmx-ssr rendering profile, scripts like highlight.js, cytoscape, + * theme-init, and tag-filters run once at page load. When HTMX swaps a + * fragment in, those scripts don't see the new DOM and the content goes + * "dead" (unhighlighted code blocks, unwired filter pills, etc.). + * + * This module fixes that by exposing a registry — + * `window.RusteloReinit.register(fn)` — and a single listener on + * `htmx:afterSettle` that walks the registry and calls each handler with + * the swapped element as scope. + * + * Handlers MUST be idempotent. We help by tracking processed nodes in a + * WeakSet keyed by handler id, so double-decoration is impossible even if + * a swap re-emits a region that contains an already-processed node. + * + * Usage from another script: + * + * (function () { + * window.RusteloReinit.register('highlight', function (scope) { + * scope.querySelectorAll('pre code:not(.hljs)').forEach(hljs.highlightElement); + * }); + * })(); + * + * The registered name is used to namespace the processed-nodes WeakSet so + * each handler manages its own idempotency. + */ +(function () { + 'use strict'; + + if (window.RusteloReinit) { + return; // already loaded + } + + /** @type {Array<{ name: string, fn: (scope: Element) => void, seen: WeakSet }>} */ + const handlers = []; + + function findHandler(name) { + for (let i = 0; i < handlers.length; i++) { + if (handlers[i].name === name) { + return handlers[i]; + } + } + return null; + } + + function runHandler(handler, scope) { + if (handler.seen.has(scope)) { + return; + } + handler.seen.add(scope); + try { + handler.fn(scope); + } catch (err) { + console.error('[RusteloReinit:' + handler.name + '] handler failed', err); + } + } + + const api = { + /** + * Register a re-init handler. Calling register twice with the same + * name replaces the previous handler. + */ + register: function (name, fn) { + if (typeof name !== 'string' || typeof fn !== 'function') { + throw new TypeError('RusteloReinit.register(name, fn) — name must be string, fn must be function'); + } + const existing = findHandler(name); + if (existing) { + existing.fn = fn; + // Reset the seen set so the replacement gets a fresh chance at the DOM. + existing.seen = new WeakSet(); + } else { + handlers.push({ name: name, fn: fn, seen: new WeakSet() }); + } + // Run immediately on document.body so the initial page gets processed. + if (document.body) { + runHandler(findHandler(name), document.body); + } else { + document.addEventListener('DOMContentLoaded', function () { + runHandler(findHandler(name), document.body); + }, { once: true }); + } + }, + + /** + * Force-run every handler against the given scope. Used by tests and by + * the htmx:afterSettle listener. + */ + runAll: function (scope) { + for (let i = 0; i < handlers.length; i++) { + runHandler(handlers[i], scope); + } + }, + + /** Return registered handler names (debug). */ + names: function () { + return handlers.map(function (h) { return h.name; }); + } + }; + + window.RusteloReinit = api; + + // Hook into htmx lifecycle events. afterSettle fires after the DOM is in + // its final state for the swap; we want this rather than afterSwap to + // avoid running against elements that get replaced moments later. + document.body && document.body.addEventListener('htmx:afterSettle', function (ev) { + const target = ev.detail && ev.detail.target; + if (target instanceof Element) { + api.runAll(target); + } else { + api.runAll(document.body); + } + }); + + // If body wasn't ready yet, wire on DOMContentLoaded. + if (!document.body) { + document.addEventListener('DOMContentLoaded', function () { + document.body.addEventListener('htmx:afterSettle', function (ev) { + const target = ev.detail && ev.detail.target; + if (target instanceof Element) { + api.runAll(target); + } else { + api.runAll(document.body); + } + }); + }, { once: true }); + } +})(); diff --git a/site/site/public/js/layout-base.js b/site/site/public/js/layout-base.js new file mode 100644 index 0000000..dc77057 --- /dev/null +++ b/site/site/public/js/layout-base.js @@ -0,0 +1,5230 @@ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + else if(typeof define === 'function' && define.amd) + define([], factory); + else if(typeof exports === 'object') + exports["layoutBase"] = factory(); + else + root["layoutBase"] = factory(); +})(this, function() { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // identity function for calling harmony imports with the correct context +/******/ __webpack_require__.i = function(value) { return value; }; +/******/ +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { +/******/ configurable: false, +/******/ enumerable: true, +/******/ get: getter +/******/ }); +/******/ } +/******/ }; +/******/ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; +/******/ +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = 28); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +function LayoutConstants() {} + +/** + * Layout Quality: 0:draft, 1:default, 2:proof + */ +LayoutConstants.QUALITY = 1; + +/** + * Default parameters + */ +LayoutConstants.DEFAULT_CREATE_BENDS_AS_NEEDED = false; +LayoutConstants.DEFAULT_INCREMENTAL = false; +LayoutConstants.DEFAULT_ANIMATION_ON_LAYOUT = true; +LayoutConstants.DEFAULT_ANIMATION_DURING_LAYOUT = false; +LayoutConstants.DEFAULT_ANIMATION_PERIOD = 50; +LayoutConstants.DEFAULT_UNIFORM_LEAF_NODE_SIZES = false; + +// ----------------------------------------------------------------------------- +// Section: General other constants +// ----------------------------------------------------------------------------- +/* + * Margins of a graph to be applied on bouding rectangle of its contents. We + * assume margins on all four sides to be uniform. + */ +LayoutConstants.DEFAULT_GRAPH_MARGIN = 15; + +/* + * Whether to consider labels in node dimensions or not + */ +LayoutConstants.NODE_DIMENSIONS_INCLUDE_LABELS = false; + +/* + * Default dimension of a non-compound node. + */ +LayoutConstants.SIMPLE_NODE_SIZE = 40; + +/* + * Default dimension of a non-compound node. + */ +LayoutConstants.SIMPLE_NODE_HALF_SIZE = LayoutConstants.SIMPLE_NODE_SIZE / 2; + +/* + * Empty compound node size. When a compound node is empty, its both + * dimensions should be of this value. + */ +LayoutConstants.EMPTY_COMPOUND_NODE_SIZE = 40; + +/* + * Minimum length that an edge should take during layout + */ +LayoutConstants.MIN_EDGE_LENGTH = 1; + +/* + * World boundaries that layout operates on + */ +LayoutConstants.WORLD_BOUNDARY = 1000000; + +/* + * World boundaries that random positioning can be performed with + */ +LayoutConstants.INITIAL_WORLD_BOUNDARY = LayoutConstants.WORLD_BOUNDARY / 1000; + +/* + * Coordinates of the world center + */ +LayoutConstants.WORLD_CENTER_X = 1200; +LayoutConstants.WORLD_CENTER_Y = 900; + +module.exports = LayoutConstants; + +/***/ }), +/* 1 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var LGraphObject = __webpack_require__(2); +var IGeometry = __webpack_require__(8); +var IMath = __webpack_require__(9); + +function LEdge(source, target, vEdge) { + LGraphObject.call(this, vEdge); + + this.isOverlapingSourceAndTarget = false; + this.vGraphObject = vEdge; + this.bendpoints = []; + this.source = source; + this.target = target; +} + +LEdge.prototype = Object.create(LGraphObject.prototype); + +for (var prop in LGraphObject) { + LEdge[prop] = LGraphObject[prop]; +} + +LEdge.prototype.getSource = function () { + return this.source; +}; + +LEdge.prototype.getTarget = function () { + return this.target; +}; + +LEdge.prototype.isInterGraph = function () { + return this.isInterGraph; +}; + +LEdge.prototype.getLength = function () { + return this.length; +}; + +LEdge.prototype.isOverlapingSourceAndTarget = function () { + return this.isOverlapingSourceAndTarget; +}; + +LEdge.prototype.getBendpoints = function () { + return this.bendpoints; +}; + +LEdge.prototype.getLca = function () { + return this.lca; +}; + +LEdge.prototype.getSourceInLca = function () { + return this.sourceInLca; +}; + +LEdge.prototype.getTargetInLca = function () { + return this.targetInLca; +}; + +LEdge.prototype.getOtherEnd = function (node) { + if (this.source === node) { + return this.target; + } else if (this.target === node) { + return this.source; + } else { + throw "Node is not incident with this edge"; + } +}; + +LEdge.prototype.getOtherEndInGraph = function (node, graph) { + var otherEnd = this.getOtherEnd(node); + var root = graph.getGraphManager().getRoot(); + + while (true) { + if (otherEnd.getOwner() == graph) { + return otherEnd; + } + + if (otherEnd.getOwner() == root) { + break; + } + + otherEnd = otherEnd.getOwner().getParent(); + } + + return null; +}; + +LEdge.prototype.updateLength = function () { + var clipPointCoordinates = new Array(4); + + this.isOverlapingSourceAndTarget = IGeometry.getIntersection(this.target.getRect(), this.source.getRect(), clipPointCoordinates); + + if (!this.isOverlapingSourceAndTarget) { + this.lengthX = clipPointCoordinates[0] - clipPointCoordinates[2]; + this.lengthY = clipPointCoordinates[1] - clipPointCoordinates[3]; + + if (Math.abs(this.lengthX) < 1.0) { + this.lengthX = IMath.sign(this.lengthX); + } + + if (Math.abs(this.lengthY) < 1.0) { + this.lengthY = IMath.sign(this.lengthY); + } + + this.length = Math.sqrt(this.lengthX * this.lengthX + this.lengthY * this.lengthY); + } +}; + +LEdge.prototype.updateLengthSimple = function () { + this.lengthX = this.target.getCenterX() - this.source.getCenterX(); + this.lengthY = this.target.getCenterY() - this.source.getCenterY(); + + if (Math.abs(this.lengthX) < 1.0) { + this.lengthX = IMath.sign(this.lengthX); + } + + if (Math.abs(this.lengthY) < 1.0) { + this.lengthY = IMath.sign(this.lengthY); + } + + this.length = Math.sqrt(this.lengthX * this.lengthX + this.lengthY * this.lengthY); +}; + +module.exports = LEdge; + +/***/ }), +/* 2 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +function LGraphObject(vGraphObject) { + this.vGraphObject = vGraphObject; +} + +module.exports = LGraphObject; + +/***/ }), +/* 3 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var LGraphObject = __webpack_require__(2); +var Integer = __webpack_require__(10); +var RectangleD = __webpack_require__(13); +var LayoutConstants = __webpack_require__(0); +var RandomSeed = __webpack_require__(16); +var PointD = __webpack_require__(5); + +function LNode(gm, loc, size, vNode) { + //Alternative constructor 1 : LNode(LGraphManager gm, Point loc, Dimension size, Object vNode) + if (size == null && vNode == null) { + vNode = loc; + } + + LGraphObject.call(this, vNode); + + //Alternative constructor 2 : LNode(Layout layout, Object vNode) + if (gm.graphManager != null) gm = gm.graphManager; + + this.estimatedSize = Integer.MIN_VALUE; + this.inclusionTreeDepth = Integer.MAX_VALUE; + this.vGraphObject = vNode; + this.edges = []; + this.graphManager = gm; + + if (size != null && loc != null) this.rect = new RectangleD(loc.x, loc.y, size.width, size.height);else this.rect = new RectangleD(); +} + +LNode.prototype = Object.create(LGraphObject.prototype); +for (var prop in LGraphObject) { + LNode[prop] = LGraphObject[prop]; +} + +LNode.prototype.getEdges = function () { + return this.edges; +}; + +LNode.prototype.getChild = function () { + return this.child; +}; + +LNode.prototype.getOwner = function () { + // if (this.owner != null) { + // if (!(this.owner == null || this.owner.getNodes().indexOf(this) > -1)) { + // throw "assert failed"; + // } + // } + + return this.owner; +}; + +LNode.prototype.getWidth = function () { + return this.rect.width; +}; + +LNode.prototype.setWidth = function (width) { + this.rect.width = width; +}; + +LNode.prototype.getHeight = function () { + return this.rect.height; +}; + +LNode.prototype.setHeight = function (height) { + this.rect.height = height; +}; + +LNode.prototype.getCenterX = function () { + return this.rect.x + this.rect.width / 2; +}; + +LNode.prototype.getCenterY = function () { + return this.rect.y + this.rect.height / 2; +}; + +LNode.prototype.getCenter = function () { + return new PointD(this.rect.x + this.rect.width / 2, this.rect.y + this.rect.height / 2); +}; + +LNode.prototype.getLocation = function () { + return new PointD(this.rect.x, this.rect.y); +}; + +LNode.prototype.getRect = function () { + return this.rect; +}; + +LNode.prototype.getDiagonal = function () { + return Math.sqrt(this.rect.width * this.rect.width + this.rect.height * this.rect.height); +}; + +/** + * This method returns half the diagonal length of this node. + */ +LNode.prototype.getHalfTheDiagonal = function () { + return Math.sqrt(this.rect.height * this.rect.height + this.rect.width * this.rect.width) / 2; +}; + +LNode.prototype.setRect = function (upperLeft, dimension) { + this.rect.x = upperLeft.x; + this.rect.y = upperLeft.y; + this.rect.width = dimension.width; + this.rect.height = dimension.height; +}; + +LNode.prototype.setCenter = function (cx, cy) { + this.rect.x = cx - this.rect.width / 2; + this.rect.y = cy - this.rect.height / 2; +}; + +LNode.prototype.setLocation = function (x, y) { + this.rect.x = x; + this.rect.y = y; +}; + +LNode.prototype.moveBy = function (dx, dy) { + this.rect.x += dx; + this.rect.y += dy; +}; + +LNode.prototype.getEdgeListToNode = function (to) { + var edgeList = []; + var edge; + var self = this; + + self.edges.forEach(function (edge) { + + if (edge.target == to) { + if (edge.source != self) throw "Incorrect edge source!"; + + edgeList.push(edge); + } + }); + + return edgeList; +}; + +LNode.prototype.getEdgesBetween = function (other) { + var edgeList = []; + var edge; + + var self = this; + self.edges.forEach(function (edge) { + + if (!(edge.source == self || edge.target == self)) throw "Incorrect edge source and/or target"; + + if (edge.target == other || edge.source == other) { + edgeList.push(edge); + } + }); + + return edgeList; +}; + +LNode.prototype.getNeighborsList = function () { + var neighbors = new Set(); + + var self = this; + self.edges.forEach(function (edge) { + + if (edge.source == self) { + neighbors.add(edge.target); + } else { + if (edge.target != self) { + throw "Incorrect incidency!"; + } + + neighbors.add(edge.source); + } + }); + + return neighbors; +}; + +LNode.prototype.withChildren = function () { + var withNeighborsList = new Set(); + var childNode; + var children; + + withNeighborsList.add(this); + + if (this.child != null) { + var nodes = this.child.getNodes(); + for (var i = 0; i < nodes.length; i++) { + childNode = nodes[i]; + children = childNode.withChildren(); + children.forEach(function (node) { + withNeighborsList.add(node); + }); + } + } + + return withNeighborsList; +}; + +LNode.prototype.getNoOfChildren = function () { + var noOfChildren = 0; + var childNode; + + if (this.child == null) { + noOfChildren = 1; + } else { + var nodes = this.child.getNodes(); + for (var i = 0; i < nodes.length; i++) { + childNode = nodes[i]; + + noOfChildren += childNode.getNoOfChildren(); + } + } + + if (noOfChildren == 0) { + noOfChildren = 1; + } + return noOfChildren; +}; + +LNode.prototype.getEstimatedSize = function () { + if (this.estimatedSize == Integer.MIN_VALUE) { + throw "assert failed"; + } + return this.estimatedSize; +}; + +LNode.prototype.calcEstimatedSize = function () { + if (this.child == null) { + return this.estimatedSize = (this.rect.width + this.rect.height) / 2; + } else { + this.estimatedSize = this.child.calcEstimatedSize(); + this.rect.width = this.estimatedSize; + this.rect.height = this.estimatedSize; + + return this.estimatedSize; + } +}; + +LNode.prototype.scatter = function () { + var randomCenterX; + var randomCenterY; + + var minX = -LayoutConstants.INITIAL_WORLD_BOUNDARY; + var maxX = LayoutConstants.INITIAL_WORLD_BOUNDARY; + randomCenterX = LayoutConstants.WORLD_CENTER_X + RandomSeed.nextDouble() * (maxX - minX) + minX; + + var minY = -LayoutConstants.INITIAL_WORLD_BOUNDARY; + var maxY = LayoutConstants.INITIAL_WORLD_BOUNDARY; + randomCenterY = LayoutConstants.WORLD_CENTER_Y + RandomSeed.nextDouble() * (maxY - minY) + minY; + + this.rect.x = randomCenterX; + this.rect.y = randomCenterY; +}; + +LNode.prototype.updateBounds = function () { + if (this.getChild() == null) { + throw "assert failed"; + } + if (this.getChild().getNodes().length != 0) { + // wrap the children nodes by re-arranging the boundaries + var childGraph = this.getChild(); + childGraph.updateBounds(true); + + this.rect.x = childGraph.getLeft(); + this.rect.y = childGraph.getTop(); + + this.setWidth(childGraph.getRight() - childGraph.getLeft()); + this.setHeight(childGraph.getBottom() - childGraph.getTop()); + + // Update compound bounds considering its label properties + if (LayoutConstants.NODE_DIMENSIONS_INCLUDE_LABELS) { + + var width = childGraph.getRight() - childGraph.getLeft(); + var height = childGraph.getBottom() - childGraph.getTop(); + + if (this.labelWidth) { + if (this.labelPosHorizontal == "left") { + this.rect.x -= this.labelWidth; + this.setWidth(width + this.labelWidth); + } else if (this.labelPosHorizontal == "center" && this.labelWidth > width) { + this.rect.x -= (this.labelWidth - width) / 2; + this.setWidth(this.labelWidth); + } else if (this.labelPosHorizontal == "right") { + this.setWidth(width + this.labelWidth); + } + } + + if (this.labelHeight) { + if (this.labelPosVertical == "top") { + this.rect.y -= this.labelHeight; + this.setHeight(height + this.labelHeight); + } else if (this.labelPosVertical == "center" && this.labelHeight > height) { + this.rect.y -= (this.labelHeight - height) / 2; + this.setHeight(this.labelHeight); + } else if (this.labelPosVertical == "bottom") { + this.setHeight(height + this.labelHeight); + } + } + } + } +}; + +LNode.prototype.getInclusionTreeDepth = function () { + if (this.inclusionTreeDepth == Integer.MAX_VALUE) { + throw "assert failed"; + } + return this.inclusionTreeDepth; +}; + +LNode.prototype.transform = function (trans) { + var left = this.rect.x; + + if (left > LayoutConstants.WORLD_BOUNDARY) { + left = LayoutConstants.WORLD_BOUNDARY; + } else if (left < -LayoutConstants.WORLD_BOUNDARY) { + left = -LayoutConstants.WORLD_BOUNDARY; + } + + var top = this.rect.y; + + if (top > LayoutConstants.WORLD_BOUNDARY) { + top = LayoutConstants.WORLD_BOUNDARY; + } else if (top < -LayoutConstants.WORLD_BOUNDARY) { + top = -LayoutConstants.WORLD_BOUNDARY; + } + + var leftTop = new PointD(left, top); + var vLeftTop = trans.inverseTransformPoint(leftTop); + + this.setLocation(vLeftTop.x, vLeftTop.y); +}; + +LNode.prototype.getLeft = function () { + return this.rect.x; +}; + +LNode.prototype.getRight = function () { + return this.rect.x + this.rect.width; +}; + +LNode.prototype.getTop = function () { + return this.rect.y; +}; + +LNode.prototype.getBottom = function () { + return this.rect.y + this.rect.height; +}; + +LNode.prototype.getParent = function () { + if (this.owner == null) { + return null; + } + + return this.owner.getParent(); +}; + +module.exports = LNode; + +/***/ }), +/* 4 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var LayoutConstants = __webpack_require__(0); + +function FDLayoutConstants() {} + +//FDLayoutConstants inherits static props in LayoutConstants +for (var prop in LayoutConstants) { + FDLayoutConstants[prop] = LayoutConstants[prop]; +} + +FDLayoutConstants.MAX_ITERATIONS = 2500; + +FDLayoutConstants.DEFAULT_EDGE_LENGTH = 50; +FDLayoutConstants.DEFAULT_SPRING_STRENGTH = 0.45; +FDLayoutConstants.DEFAULT_REPULSION_STRENGTH = 4500.0; +FDLayoutConstants.DEFAULT_GRAVITY_STRENGTH = 0.4; +FDLayoutConstants.DEFAULT_COMPOUND_GRAVITY_STRENGTH = 1.0; +FDLayoutConstants.DEFAULT_GRAVITY_RANGE_FACTOR = 3.8; +FDLayoutConstants.DEFAULT_COMPOUND_GRAVITY_RANGE_FACTOR = 1.5; +FDLayoutConstants.DEFAULT_USE_SMART_IDEAL_EDGE_LENGTH_CALCULATION = true; +FDLayoutConstants.DEFAULT_USE_SMART_REPULSION_RANGE_CALCULATION = true; +FDLayoutConstants.DEFAULT_COOLING_FACTOR_INCREMENTAL = 0.3; +FDLayoutConstants.COOLING_ADAPTATION_FACTOR = 0.33; +FDLayoutConstants.ADAPTATION_LOWER_NODE_LIMIT = 1000; +FDLayoutConstants.ADAPTATION_UPPER_NODE_LIMIT = 5000; +FDLayoutConstants.MAX_NODE_DISPLACEMENT_INCREMENTAL = 100.0; +FDLayoutConstants.MAX_NODE_DISPLACEMENT = FDLayoutConstants.MAX_NODE_DISPLACEMENT_INCREMENTAL * 3; +FDLayoutConstants.MIN_REPULSION_DIST = FDLayoutConstants.DEFAULT_EDGE_LENGTH / 10.0; +FDLayoutConstants.CONVERGENCE_CHECK_PERIOD = 100; +FDLayoutConstants.PER_LEVEL_IDEAL_EDGE_LENGTH_FACTOR = 0.1; +FDLayoutConstants.MIN_EDGE_LENGTH = 1; +FDLayoutConstants.GRID_CALCULATION_CHECK_PERIOD = 10; + +module.exports = FDLayoutConstants; + +/***/ }), +/* 5 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +function PointD(x, y) { + if (x == null && y == null) { + this.x = 0; + this.y = 0; + } else { + this.x = x; + this.y = y; + } +} + +PointD.prototype.getX = function () { + return this.x; +}; + +PointD.prototype.getY = function () { + return this.y; +}; + +PointD.prototype.setX = function (x) { + this.x = x; +}; + +PointD.prototype.setY = function (y) { + this.y = y; +}; + +PointD.prototype.getDifference = function (pt) { + return new DimensionD(this.x - pt.x, this.y - pt.y); +}; + +PointD.prototype.getCopy = function () { + return new PointD(this.x, this.y); +}; + +PointD.prototype.translate = function (dim) { + this.x += dim.width; + this.y += dim.height; + return this; +}; + +module.exports = PointD; + +/***/ }), +/* 6 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var LGraphObject = __webpack_require__(2); +var Integer = __webpack_require__(10); +var LayoutConstants = __webpack_require__(0); +var LGraphManager = __webpack_require__(7); +var LNode = __webpack_require__(3); +var LEdge = __webpack_require__(1); +var RectangleD = __webpack_require__(13); +var Point = __webpack_require__(12); +var LinkedList = __webpack_require__(11); + +function LGraph(parent, obj2, vGraph) { + LGraphObject.call(this, vGraph); + this.estimatedSize = Integer.MIN_VALUE; + this.margin = LayoutConstants.DEFAULT_GRAPH_MARGIN; + this.edges = []; + this.nodes = []; + this.isConnected = false; + this.parent = parent; + + if (obj2 != null && obj2 instanceof LGraphManager) { + this.graphManager = obj2; + } else if (obj2 != null && obj2 instanceof Layout) { + this.graphManager = obj2.graphManager; + } +} + +LGraph.prototype = Object.create(LGraphObject.prototype); +for (var prop in LGraphObject) { + LGraph[prop] = LGraphObject[prop]; +} + +LGraph.prototype.getNodes = function () { + return this.nodes; +}; + +LGraph.prototype.getEdges = function () { + return this.edges; +}; + +LGraph.prototype.getGraphManager = function () { + return this.graphManager; +}; + +LGraph.prototype.getParent = function () { + return this.parent; +}; + +LGraph.prototype.getLeft = function () { + return this.left; +}; + +LGraph.prototype.getRight = function () { + return this.right; +}; + +LGraph.prototype.getTop = function () { + return this.top; +}; + +LGraph.prototype.getBottom = function () { + return this.bottom; +}; + +LGraph.prototype.isConnected = function () { + return this.isConnected; +}; + +LGraph.prototype.add = function (obj1, sourceNode, targetNode) { + if (sourceNode == null && targetNode == null) { + var newNode = obj1; + if (this.graphManager == null) { + throw "Graph has no graph mgr!"; + } + if (this.getNodes().indexOf(newNode) > -1) { + throw "Node already in graph!"; + } + newNode.owner = this; + this.getNodes().push(newNode); + + return newNode; + } else { + var newEdge = obj1; + if (!(this.getNodes().indexOf(sourceNode) > -1 && this.getNodes().indexOf(targetNode) > -1)) { + throw "Source or target not in graph!"; + } + + if (!(sourceNode.owner == targetNode.owner && sourceNode.owner == this)) { + throw "Both owners must be this graph!"; + } + + if (sourceNode.owner != targetNode.owner) { + return null; + } + + // set source and target + newEdge.source = sourceNode; + newEdge.target = targetNode; + + // set as intra-graph edge + newEdge.isInterGraph = false; + + // add to graph edge list + this.getEdges().push(newEdge); + + // add to incidency lists + sourceNode.edges.push(newEdge); + + if (targetNode != sourceNode) { + targetNode.edges.push(newEdge); + } + + return newEdge; + } +}; + +LGraph.prototype.remove = function (obj) { + var node = obj; + if (obj instanceof LNode) { + if (node == null) { + throw "Node is null!"; + } + if (!(node.owner != null && node.owner == this)) { + throw "Owner graph is invalid!"; + } + if (this.graphManager == null) { + throw "Owner graph manager is invalid!"; + } + // remove incident edges first (make a copy to do it safely) + var edgesToBeRemoved = node.edges.slice(); + var edge; + var s = edgesToBeRemoved.length; + for (var i = 0; i < s; i++) { + edge = edgesToBeRemoved[i]; + + if (edge.isInterGraph) { + this.graphManager.remove(edge); + } else { + edge.source.owner.remove(edge); + } + } + + // now the node itself + var index = this.nodes.indexOf(node); + if (index == -1) { + throw "Node not in owner node list!"; + } + + this.nodes.splice(index, 1); + } else if (obj instanceof LEdge) { + var edge = obj; + if (edge == null) { + throw "Edge is null!"; + } + if (!(edge.source != null && edge.target != null)) { + throw "Source and/or target is null!"; + } + if (!(edge.source.owner != null && edge.target.owner != null && edge.source.owner == this && edge.target.owner == this)) { + throw "Source and/or target owner is invalid!"; + } + + var sourceIndex = edge.source.edges.indexOf(edge); + var targetIndex = edge.target.edges.indexOf(edge); + if (!(sourceIndex > -1 && targetIndex > -1)) { + throw "Source and/or target doesn't know this edge!"; + } + + edge.source.edges.splice(sourceIndex, 1); + + if (edge.target != edge.source) { + edge.target.edges.splice(targetIndex, 1); + } + + var index = edge.source.owner.getEdges().indexOf(edge); + if (index == -1) { + throw "Not in owner's edge list!"; + } + + edge.source.owner.getEdges().splice(index, 1); + } +}; + +LGraph.prototype.updateLeftTop = function () { + var top = Integer.MAX_VALUE; + var left = Integer.MAX_VALUE; + var nodeTop; + var nodeLeft; + var margin; + + var nodes = this.getNodes(); + var s = nodes.length; + + for (var i = 0; i < s; i++) { + var lNode = nodes[i]; + nodeTop = lNode.getTop(); + nodeLeft = lNode.getLeft(); + + if (top > nodeTop) { + top = nodeTop; + } + + if (left > nodeLeft) { + left = nodeLeft; + } + } + + // Do we have any nodes in this graph? + if (top == Integer.MAX_VALUE) { + return null; + } + + if (nodes[0].getParent().paddingLeft != undefined) { + margin = nodes[0].getParent().paddingLeft; + } else { + margin = this.margin; + } + + this.left = left - margin; + this.top = top - margin; + + // Apply the margins and return the result + return new Point(this.left, this.top); +}; + +LGraph.prototype.updateBounds = function (recursive) { + // calculate bounds + var left = Integer.MAX_VALUE; + var right = -Integer.MAX_VALUE; + var top = Integer.MAX_VALUE; + var bottom = -Integer.MAX_VALUE; + var nodeLeft; + var nodeRight; + var nodeTop; + var nodeBottom; + var margin; + + var nodes = this.nodes; + var s = nodes.length; + for (var i = 0; i < s; i++) { + var lNode = nodes[i]; + + if (recursive && lNode.child != null) { + lNode.updateBounds(); + } + nodeLeft = lNode.getLeft(); + nodeRight = lNode.getRight(); + nodeTop = lNode.getTop(); + nodeBottom = lNode.getBottom(); + + if (left > nodeLeft) { + left = nodeLeft; + } + + if (right < nodeRight) { + right = nodeRight; + } + + if (top > nodeTop) { + top = nodeTop; + } + + if (bottom < nodeBottom) { + bottom = nodeBottom; + } + } + + var boundingRect = new RectangleD(left, top, right - left, bottom - top); + if (left == Integer.MAX_VALUE) { + this.left = this.parent.getLeft(); + this.right = this.parent.getRight(); + this.top = this.parent.getTop(); + this.bottom = this.parent.getBottom(); + } + + if (nodes[0].getParent().paddingLeft != undefined) { + margin = nodes[0].getParent().paddingLeft; + } else { + margin = this.margin; + } + + this.left = boundingRect.x - margin; + this.right = boundingRect.x + boundingRect.width + margin; + this.top = boundingRect.y - margin; + this.bottom = boundingRect.y + boundingRect.height + margin; +}; + +LGraph.calculateBounds = function (nodes) { + var left = Integer.MAX_VALUE; + var right = -Integer.MAX_VALUE; + var top = Integer.MAX_VALUE; + var bottom = -Integer.MAX_VALUE; + var nodeLeft; + var nodeRight; + var nodeTop; + var nodeBottom; + + var s = nodes.length; + + for (var i = 0; i < s; i++) { + var lNode = nodes[i]; + nodeLeft = lNode.getLeft(); + nodeRight = lNode.getRight(); + nodeTop = lNode.getTop(); + nodeBottom = lNode.getBottom(); + + if (left > nodeLeft) { + left = nodeLeft; + } + + if (right < nodeRight) { + right = nodeRight; + } + + if (top > nodeTop) { + top = nodeTop; + } + + if (bottom < nodeBottom) { + bottom = nodeBottom; + } + } + + var boundingRect = new RectangleD(left, top, right - left, bottom - top); + + return boundingRect; +}; + +LGraph.prototype.getInclusionTreeDepth = function () { + if (this == this.graphManager.getRoot()) { + return 1; + } else { + return this.parent.getInclusionTreeDepth(); + } +}; + +LGraph.prototype.getEstimatedSize = function () { + if (this.estimatedSize == Integer.MIN_VALUE) { + throw "assert failed"; + } + return this.estimatedSize; +}; + +LGraph.prototype.calcEstimatedSize = function () { + var size = 0; + var nodes = this.nodes; + var s = nodes.length; + + for (var i = 0; i < s; i++) { + var lNode = nodes[i]; + size += lNode.calcEstimatedSize(); + } + + if (size == 0) { + this.estimatedSize = LayoutConstants.EMPTY_COMPOUND_NODE_SIZE; + } else { + this.estimatedSize = size / Math.sqrt(this.nodes.length); + } + + return this.estimatedSize; +}; + +LGraph.prototype.updateConnected = function () { + var self = this; + if (this.nodes.length == 0) { + this.isConnected = true; + return; + } + + var queue = new LinkedList(); + var visited = new Set(); + var currentNode = this.nodes[0]; + var neighborEdges; + var currentNeighbor; + var childrenOfNode = currentNode.withChildren(); + childrenOfNode.forEach(function (node) { + queue.push(node); + visited.add(node); + }); + + while (queue.length !== 0) { + currentNode = queue.shift(); + + // Traverse all neighbors of this node + neighborEdges = currentNode.getEdges(); + var size = neighborEdges.length; + for (var i = 0; i < size; i++) { + var neighborEdge = neighborEdges[i]; + currentNeighbor = neighborEdge.getOtherEndInGraph(currentNode, this); + + // Add unvisited neighbors to the list to visit + if (currentNeighbor != null && !visited.has(currentNeighbor)) { + var childrenOfNeighbor = currentNeighbor.withChildren(); + + childrenOfNeighbor.forEach(function (node) { + queue.push(node); + visited.add(node); + }); + } + } + } + + this.isConnected = false; + + if (visited.size >= this.nodes.length) { + var noOfVisitedInThisGraph = 0; + + visited.forEach(function (visitedNode) { + if (visitedNode.owner == self) { + noOfVisitedInThisGraph++; + } + }); + + if (noOfVisitedInThisGraph == this.nodes.length) { + this.isConnected = true; + } + } +}; + +module.exports = LGraph; + +/***/ }), +/* 7 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var LGraph; +var LEdge = __webpack_require__(1); + +function LGraphManager(layout) { + LGraph = __webpack_require__(6); // It may be better to initilize this out of this function but it gives an error (Right-hand side of 'instanceof' is not callable) now. + this.layout = layout; + + this.graphs = []; + this.edges = []; +} + +LGraphManager.prototype.addRoot = function () { + var ngraph = this.layout.newGraph(); + var nnode = this.layout.newNode(null); + var root = this.add(ngraph, nnode); + this.setRootGraph(root); + return this.rootGraph; +}; + +LGraphManager.prototype.add = function (newGraph, parentNode, newEdge, sourceNode, targetNode) { + //there are just 2 parameters are passed then it adds an LGraph else it adds an LEdge + if (newEdge == null && sourceNode == null && targetNode == null) { + if (newGraph == null) { + throw "Graph is null!"; + } + if (parentNode == null) { + throw "Parent node is null!"; + } + if (this.graphs.indexOf(newGraph) > -1) { + throw "Graph already in this graph mgr!"; + } + + this.graphs.push(newGraph); + + if (newGraph.parent != null) { + throw "Already has a parent!"; + } + if (parentNode.child != null) { + throw "Already has a child!"; + } + + newGraph.parent = parentNode; + parentNode.child = newGraph; + + return newGraph; + } else { + //change the order of the parameters + targetNode = newEdge; + sourceNode = parentNode; + newEdge = newGraph; + var sourceGraph = sourceNode.getOwner(); + var targetGraph = targetNode.getOwner(); + + if (!(sourceGraph != null && sourceGraph.getGraphManager() == this)) { + throw "Source not in this graph mgr!"; + } + if (!(targetGraph != null && targetGraph.getGraphManager() == this)) { + throw "Target not in this graph mgr!"; + } + + if (sourceGraph == targetGraph) { + newEdge.isInterGraph = false; + return sourceGraph.add(newEdge, sourceNode, targetNode); + } else { + newEdge.isInterGraph = true; + + // set source and target + newEdge.source = sourceNode; + newEdge.target = targetNode; + + // add edge to inter-graph edge list + if (this.edges.indexOf(newEdge) > -1) { + throw "Edge already in inter-graph edge list!"; + } + + this.edges.push(newEdge); + + // add edge to source and target incidency lists + if (!(newEdge.source != null && newEdge.target != null)) { + throw "Edge source and/or target is null!"; + } + + if (!(newEdge.source.edges.indexOf(newEdge) == -1 && newEdge.target.edges.indexOf(newEdge) == -1)) { + throw "Edge already in source and/or target incidency list!"; + } + + newEdge.source.edges.push(newEdge); + newEdge.target.edges.push(newEdge); + + return newEdge; + } + } +}; + +LGraphManager.prototype.remove = function (lObj) { + if (lObj instanceof LGraph) { + var graph = lObj; + if (graph.getGraphManager() != this) { + throw "Graph not in this graph mgr"; + } + if (!(graph == this.rootGraph || graph.parent != null && graph.parent.graphManager == this)) { + throw "Invalid parent node!"; + } + + // first the edges (make a copy to do it safely) + var edgesToBeRemoved = []; + + edgesToBeRemoved = edgesToBeRemoved.concat(graph.getEdges()); + + var edge; + var s = edgesToBeRemoved.length; + for (var i = 0; i < s; i++) { + edge = edgesToBeRemoved[i]; + graph.remove(edge); + } + + // then the nodes (make a copy to do it safely) + var nodesToBeRemoved = []; + + nodesToBeRemoved = nodesToBeRemoved.concat(graph.getNodes()); + + var node; + s = nodesToBeRemoved.length; + for (var i = 0; i < s; i++) { + node = nodesToBeRemoved[i]; + graph.remove(node); + } + + // check if graph is the root + if (graph == this.rootGraph) { + this.setRootGraph(null); + } + + // now remove the graph itself + var index = this.graphs.indexOf(graph); + this.graphs.splice(index, 1); + + // also reset the parent of the graph + graph.parent = null; + } else if (lObj instanceof LEdge) { + edge = lObj; + if (edge == null) { + throw "Edge is null!"; + } + if (!edge.isInterGraph) { + throw "Not an inter-graph edge!"; + } + if (!(edge.source != null && edge.target != null)) { + throw "Source and/or target is null!"; + } + + // remove edge from source and target nodes' incidency lists + + if (!(edge.source.edges.indexOf(edge) != -1 && edge.target.edges.indexOf(edge) != -1)) { + throw "Source and/or target doesn't know this edge!"; + } + + var index = edge.source.edges.indexOf(edge); + edge.source.edges.splice(index, 1); + index = edge.target.edges.indexOf(edge); + edge.target.edges.splice(index, 1); + + // remove edge from owner graph manager's inter-graph edge list + + if (!(edge.source.owner != null && edge.source.owner.getGraphManager() != null)) { + throw "Edge owner graph or owner graph manager is null!"; + } + if (edge.source.owner.getGraphManager().edges.indexOf(edge) == -1) { + throw "Not in owner graph manager's edge list!"; + } + + var index = edge.source.owner.getGraphManager().edges.indexOf(edge); + edge.source.owner.getGraphManager().edges.splice(index, 1); + } +}; + +LGraphManager.prototype.updateBounds = function () { + this.rootGraph.updateBounds(true); +}; + +LGraphManager.prototype.getGraphs = function () { + return this.graphs; +}; + +LGraphManager.prototype.getAllNodes = function () { + if (this.allNodes == null) { + var nodeList = []; + var graphs = this.getGraphs(); + var s = graphs.length; + for (var i = 0; i < s; i++) { + nodeList = nodeList.concat(graphs[i].getNodes()); + } + this.allNodes = nodeList; + } + return this.allNodes; +}; + +LGraphManager.prototype.resetAllNodes = function () { + this.allNodes = null; +}; + +LGraphManager.prototype.resetAllEdges = function () { + this.allEdges = null; +}; + +LGraphManager.prototype.resetAllNodesToApplyGravitation = function () { + this.allNodesToApplyGravitation = null; +}; + +LGraphManager.prototype.getAllEdges = function () { + if (this.allEdges == null) { + var edgeList = []; + var graphs = this.getGraphs(); + var s = graphs.length; + for (var i = 0; i < graphs.length; i++) { + edgeList = edgeList.concat(graphs[i].getEdges()); + } + + edgeList = edgeList.concat(this.edges); + + this.allEdges = edgeList; + } + return this.allEdges; +}; + +LGraphManager.prototype.getAllNodesToApplyGravitation = function () { + return this.allNodesToApplyGravitation; +}; + +LGraphManager.prototype.setAllNodesToApplyGravitation = function (nodeList) { + if (this.allNodesToApplyGravitation != null) { + throw "assert failed"; + } + + this.allNodesToApplyGravitation = nodeList; +}; + +LGraphManager.prototype.getRoot = function () { + return this.rootGraph; +}; + +LGraphManager.prototype.setRootGraph = function (graph) { + if (graph.getGraphManager() != this) { + throw "Root not in this graph mgr!"; + } + + this.rootGraph = graph; + // root graph must have a root node associated with it for convenience + if (graph.parent == null) { + graph.parent = this.layout.newNode("Root node"); + } +}; + +LGraphManager.prototype.getLayout = function () { + return this.layout; +}; + +LGraphManager.prototype.isOneAncestorOfOther = function (firstNode, secondNode) { + if (!(firstNode != null && secondNode != null)) { + throw "assert failed"; + } + + if (firstNode == secondNode) { + return true; + } + // Is second node an ancestor of the first one? + var ownerGraph = firstNode.getOwner(); + var parentNode; + + do { + parentNode = ownerGraph.getParent(); + + if (parentNode == null) { + break; + } + + if (parentNode == secondNode) { + return true; + } + + ownerGraph = parentNode.getOwner(); + if (ownerGraph == null) { + break; + } + } while (true); + // Is first node an ancestor of the second one? + ownerGraph = secondNode.getOwner(); + + do { + parentNode = ownerGraph.getParent(); + + if (parentNode == null) { + break; + } + + if (parentNode == firstNode) { + return true; + } + + ownerGraph = parentNode.getOwner(); + if (ownerGraph == null) { + break; + } + } while (true); + + return false; +}; + +LGraphManager.prototype.calcLowestCommonAncestors = function () { + var edge; + var sourceNode; + var targetNode; + var sourceAncestorGraph; + var targetAncestorGraph; + + var edges = this.getAllEdges(); + var s = edges.length; + for (var i = 0; i < s; i++) { + edge = edges[i]; + + sourceNode = edge.source; + targetNode = edge.target; + edge.lca = null; + edge.sourceInLca = sourceNode; + edge.targetInLca = targetNode; + + if (sourceNode == targetNode) { + edge.lca = sourceNode.getOwner(); + continue; + } + + sourceAncestorGraph = sourceNode.getOwner(); + + while (edge.lca == null) { + edge.targetInLca = targetNode; + targetAncestorGraph = targetNode.getOwner(); + + while (edge.lca == null) { + if (targetAncestorGraph == sourceAncestorGraph) { + edge.lca = targetAncestorGraph; + break; + } + + if (targetAncestorGraph == this.rootGraph) { + break; + } + + if (edge.lca != null) { + throw "assert failed"; + } + edge.targetInLca = targetAncestorGraph.getParent(); + targetAncestorGraph = edge.targetInLca.getOwner(); + } + + if (sourceAncestorGraph == this.rootGraph) { + break; + } + + if (edge.lca == null) { + edge.sourceInLca = sourceAncestorGraph.getParent(); + sourceAncestorGraph = edge.sourceInLca.getOwner(); + } + } + + if (edge.lca == null) { + throw "assert failed"; + } + } +}; + +LGraphManager.prototype.calcLowestCommonAncestor = function (firstNode, secondNode) { + if (firstNode == secondNode) { + return firstNode.getOwner(); + } + var firstOwnerGraph = firstNode.getOwner(); + + do { + if (firstOwnerGraph == null) { + break; + } + var secondOwnerGraph = secondNode.getOwner(); + + do { + if (secondOwnerGraph == null) { + break; + } + + if (secondOwnerGraph == firstOwnerGraph) { + return secondOwnerGraph; + } + secondOwnerGraph = secondOwnerGraph.getParent().getOwner(); + } while (true); + + firstOwnerGraph = firstOwnerGraph.getParent().getOwner(); + } while (true); + + return firstOwnerGraph; +}; + +LGraphManager.prototype.calcInclusionTreeDepths = function (graph, depth) { + if (graph == null && depth == null) { + graph = this.rootGraph; + depth = 1; + } + var node; + + var nodes = graph.getNodes(); + var s = nodes.length; + for (var i = 0; i < s; i++) { + node = nodes[i]; + node.inclusionTreeDepth = depth; + + if (node.child != null) { + this.calcInclusionTreeDepths(node.child, depth + 1); + } + } +}; + +LGraphManager.prototype.includesInvalidEdge = function () { + var edge; + var edgesToRemove = []; + + var s = this.edges.length; + for (var i = 0; i < s; i++) { + edge = this.edges[i]; + + if (this.isOneAncestorOfOther(edge.source, edge.target)) { + edgesToRemove.push(edge); + } + } + + // Remove invalid edges from graph manager + for (var i = 0; i < edgesToRemove.length; i++) { + this.remove(edgesToRemove[i]); + } + + // Invalid edges are cleared, so return false + return false; +}; + +module.exports = LGraphManager; + +/***/ }), +/* 8 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/** + * This class maintains a list of static geometry related utility methods. + * + * + * Copyright: i-Vis Research Group, Bilkent University, 2007 - present + */ + +var Point = __webpack_require__(12); + +function IGeometry() {} + +/** + * This method calculates *half* the amount in x and y directions of the two + * input rectangles needed to separate them keeping their respective + * positioning, and returns the result in the input array. An input + * separation buffer added to the amount in both directions. We assume that + * the two rectangles do intersect. + */ +IGeometry.calcSeparationAmount = function (rectA, rectB, overlapAmount, separationBuffer) { + if (!rectA.intersects(rectB)) { + throw "assert failed"; + } + + var directions = new Array(2); + + this.decideDirectionsForOverlappingNodes(rectA, rectB, directions); + + overlapAmount[0] = Math.min(rectA.getRight(), rectB.getRight()) - Math.max(rectA.x, rectB.x); + overlapAmount[1] = Math.min(rectA.getBottom(), rectB.getBottom()) - Math.max(rectA.y, rectB.y); + + // update the overlapping amounts for the following cases: + if (rectA.getX() <= rectB.getX() && rectA.getRight() >= rectB.getRight()) { + /* Case x.1: + * + * rectA + * | | + * | _________ | + * | | | | + * |________|_______|______| + * | | + * | | + * rectB + */ + overlapAmount[0] += Math.min(rectB.getX() - rectA.getX(), rectA.getRight() - rectB.getRight()); + } else if (rectB.getX() <= rectA.getX() && rectB.getRight() >= rectA.getRight()) { + /* Case x.2: + * + * rectB + * | | + * | _________ | + * | | | | + * |________|_______|______| + * | | + * | | + * rectA + */ + overlapAmount[0] += Math.min(rectA.getX() - rectB.getX(), rectB.getRight() - rectA.getRight()); + } + if (rectA.getY() <= rectB.getY() && rectA.getBottom() >= rectB.getBottom()) { + /* Case y.1: + * ________ rectA + * | + * | + * ______|____ rectB + * | | + * | | + * ______|____| + * | + * | + * |________ + * + */ + overlapAmount[1] += Math.min(rectB.getY() - rectA.getY(), rectA.getBottom() - rectB.getBottom()); + } else if (rectB.getY() <= rectA.getY() && rectB.getBottom() >= rectA.getBottom()) { + /* Case y.2: + * ________ rectB + * | + * | + * ______|____ rectA + * | | + * | | + * ______|____| + * | + * | + * |________ + * + */ + overlapAmount[1] += Math.min(rectA.getY() - rectB.getY(), rectB.getBottom() - rectA.getBottom()); + } + + // find slope of the line passes two centers + var slope = Math.abs((rectB.getCenterY() - rectA.getCenterY()) / (rectB.getCenterX() - rectA.getCenterX())); + // if centers are overlapped + if (rectB.getCenterY() === rectA.getCenterY() && rectB.getCenterX() === rectA.getCenterX()) { + // assume the slope is 1 (45 degree) + slope = 1.0; + } + + var moveByY = slope * overlapAmount[0]; + var moveByX = overlapAmount[1] / slope; + if (overlapAmount[0] < moveByX) { + moveByX = overlapAmount[0]; + } else { + moveByY = overlapAmount[1]; + } + // return half the amount so that if each rectangle is moved by these + // amounts in opposite directions, overlap will be resolved + overlapAmount[0] = -1 * directions[0] * (moveByX / 2 + separationBuffer); + overlapAmount[1] = -1 * directions[1] * (moveByY / 2 + separationBuffer); +}; + +/** + * This method decides the separation direction of overlapping nodes + * + * if directions[0] = -1, then rectA goes left + * if directions[0] = 1, then rectA goes right + * if directions[1] = -1, then rectA goes up + * if directions[1] = 1, then rectA goes down + */ +IGeometry.decideDirectionsForOverlappingNodes = function (rectA, rectB, directions) { + if (rectA.getCenterX() < rectB.getCenterX()) { + directions[0] = -1; + } else { + directions[0] = 1; + } + + if (rectA.getCenterY() < rectB.getCenterY()) { + directions[1] = -1; + } else { + directions[1] = 1; + } +}; + +/** + * This method calculates the intersection (clipping) points of the two + * input rectangles with line segment defined by the centers of these two + * rectangles. The clipping points are saved in the input double array and + * whether or not the two rectangles overlap is returned. + */ +IGeometry.getIntersection2 = function (rectA, rectB, result) { + //result[0-1] will contain clipPoint of rectA, result[2-3] will contain clipPoint of rectB + var p1x = rectA.getCenterX(); + var p1y = rectA.getCenterY(); + var p2x = rectB.getCenterX(); + var p2y = rectB.getCenterY(); + + //if two rectangles intersect, then clipping points are centers + if (rectA.intersects(rectB)) { + result[0] = p1x; + result[1] = p1y; + result[2] = p2x; + result[3] = p2y; + return true; + } + //variables for rectA + var topLeftAx = rectA.getX(); + var topLeftAy = rectA.getY(); + var topRightAx = rectA.getRight(); + var bottomLeftAx = rectA.getX(); + var bottomLeftAy = rectA.getBottom(); + var bottomRightAx = rectA.getRight(); + var halfWidthA = rectA.getWidthHalf(); + var halfHeightA = rectA.getHeightHalf(); + //variables for rectB + var topLeftBx = rectB.getX(); + var topLeftBy = rectB.getY(); + var topRightBx = rectB.getRight(); + var bottomLeftBx = rectB.getX(); + var bottomLeftBy = rectB.getBottom(); + var bottomRightBx = rectB.getRight(); + var halfWidthB = rectB.getWidthHalf(); + var halfHeightB = rectB.getHeightHalf(); + + //flag whether clipping points are found + var clipPointAFound = false; + var clipPointBFound = false; + + // line is vertical + if (p1x === p2x) { + if (p1y > p2y) { + result[0] = p1x; + result[1] = topLeftAy; + result[2] = p2x; + result[3] = bottomLeftBy; + return false; + } else if (p1y < p2y) { + result[0] = p1x; + result[1] = bottomLeftAy; + result[2] = p2x; + result[3] = topLeftBy; + return false; + } else { + //not line, return null; + } + } + // line is horizontal + else if (p1y === p2y) { + if (p1x > p2x) { + result[0] = topLeftAx; + result[1] = p1y; + result[2] = topRightBx; + result[3] = p2y; + return false; + } else if (p1x < p2x) { + result[0] = topRightAx; + result[1] = p1y; + result[2] = topLeftBx; + result[3] = p2y; + return false; + } else { + //not valid line, return null; + } + } else { + //slopes of rectA's and rectB's diagonals + var slopeA = rectA.height / rectA.width; + var slopeB = rectB.height / rectB.width; + + //slope of line between center of rectA and center of rectB + var slopePrime = (p2y - p1y) / (p2x - p1x); + var cardinalDirectionA = void 0; + var cardinalDirectionB = void 0; + var tempPointAx = void 0; + var tempPointAy = void 0; + var tempPointBx = void 0; + var tempPointBy = void 0; + + //determine whether clipping point is the corner of nodeA + if (-slopeA === slopePrime) { + if (p1x > p2x) { + result[0] = bottomLeftAx; + result[1] = bottomLeftAy; + clipPointAFound = true; + } else { + result[0] = topRightAx; + result[1] = topLeftAy; + clipPointAFound = true; + } + } else if (slopeA === slopePrime) { + if (p1x > p2x) { + result[0] = topLeftAx; + result[1] = topLeftAy; + clipPointAFound = true; + } else { + result[0] = bottomRightAx; + result[1] = bottomLeftAy; + clipPointAFound = true; + } + } + + //determine whether clipping point is the corner of nodeB + if (-slopeB === slopePrime) { + if (p2x > p1x) { + result[2] = bottomLeftBx; + result[3] = bottomLeftBy; + clipPointBFound = true; + } else { + result[2] = topRightBx; + result[3] = topLeftBy; + clipPointBFound = true; + } + } else if (slopeB === slopePrime) { + if (p2x > p1x) { + result[2] = topLeftBx; + result[3] = topLeftBy; + clipPointBFound = true; + } else { + result[2] = bottomRightBx; + result[3] = bottomLeftBy; + clipPointBFound = true; + } + } + + //if both clipping points are corners + if (clipPointAFound && clipPointBFound) { + return false; + } + + //determine Cardinal Direction of rectangles + if (p1x > p2x) { + if (p1y > p2y) { + cardinalDirectionA = this.getCardinalDirection(slopeA, slopePrime, 4); + cardinalDirectionB = this.getCardinalDirection(slopeB, slopePrime, 2); + } else { + cardinalDirectionA = this.getCardinalDirection(-slopeA, slopePrime, 3); + cardinalDirectionB = this.getCardinalDirection(-slopeB, slopePrime, 1); + } + } else { + if (p1y > p2y) { + cardinalDirectionA = this.getCardinalDirection(-slopeA, slopePrime, 1); + cardinalDirectionB = this.getCardinalDirection(-slopeB, slopePrime, 3); + } else { + cardinalDirectionA = this.getCardinalDirection(slopeA, slopePrime, 2); + cardinalDirectionB = this.getCardinalDirection(slopeB, slopePrime, 4); + } + } + //calculate clipping Point if it is not found before + if (!clipPointAFound) { + switch (cardinalDirectionA) { + case 1: + tempPointAy = topLeftAy; + tempPointAx = p1x + -halfHeightA / slopePrime; + result[0] = tempPointAx; + result[1] = tempPointAy; + break; + case 2: + tempPointAx = bottomRightAx; + tempPointAy = p1y + halfWidthA * slopePrime; + result[0] = tempPointAx; + result[1] = tempPointAy; + break; + case 3: + tempPointAy = bottomLeftAy; + tempPointAx = p1x + halfHeightA / slopePrime; + result[0] = tempPointAx; + result[1] = tempPointAy; + break; + case 4: + tempPointAx = bottomLeftAx; + tempPointAy = p1y + -halfWidthA * slopePrime; + result[0] = tempPointAx; + result[1] = tempPointAy; + break; + } + } + if (!clipPointBFound) { + switch (cardinalDirectionB) { + case 1: + tempPointBy = topLeftBy; + tempPointBx = p2x + -halfHeightB / slopePrime; + result[2] = tempPointBx; + result[3] = tempPointBy; + break; + case 2: + tempPointBx = bottomRightBx; + tempPointBy = p2y + halfWidthB * slopePrime; + result[2] = tempPointBx; + result[3] = tempPointBy; + break; + case 3: + tempPointBy = bottomLeftBy; + tempPointBx = p2x + halfHeightB / slopePrime; + result[2] = tempPointBx; + result[3] = tempPointBy; + break; + case 4: + tempPointBx = bottomLeftBx; + tempPointBy = p2y + -halfWidthB * slopePrime; + result[2] = tempPointBx; + result[3] = tempPointBy; + break; + } + } + } + return false; +}; + +/** + * This method returns in which cardinal direction does input point stays + * 1: North + * 2: East + * 3: South + * 4: West + */ +IGeometry.getCardinalDirection = function (slope, slopePrime, line) { + if (slope > slopePrime) { + return line; + } else { + return 1 + line % 4; + } +}; + +/** + * This method calculates the intersection of the two lines defined by + * point pairs (s1,s2) and (f1,f2). + */ +IGeometry.getIntersection = function (s1, s2, f1, f2) { + if (f2 == null) { + return this.getIntersection2(s1, s2, f1); + } + + var x1 = s1.x; + var y1 = s1.y; + var x2 = s2.x; + var y2 = s2.y; + var x3 = f1.x; + var y3 = f1.y; + var x4 = f2.x; + var y4 = f2.y; + var x = void 0, + y = void 0; // intersection point + var a1 = void 0, + a2 = void 0, + b1 = void 0, + b2 = void 0, + c1 = void 0, + c2 = void 0; // coefficients of line eqns. + var denom = void 0; + + a1 = y2 - y1; + b1 = x1 - x2; + c1 = x2 * y1 - x1 * y2; // { a1*x + b1*y + c1 = 0 is line 1 } + + a2 = y4 - y3; + b2 = x3 - x4; + c2 = x4 * y3 - x3 * y4; // { a2*x + b2*y + c2 = 0 is line 2 } + + denom = a1 * b2 - a2 * b1; + + if (denom === 0) { + return null; + } + + x = (b1 * c2 - b2 * c1) / denom; + y = (a2 * c1 - a1 * c2) / denom; + + return new Point(x, y); +}; + +/** + * This method finds and returns the angle of the vector from the + x-axis + * in clockwise direction (compatible w/ Java coordinate system!). + */ +IGeometry.angleOfVector = function (Cx, Cy, Nx, Ny) { + var C_angle = void 0; + + if (Cx !== Nx) { + C_angle = Math.atan((Ny - Cy) / (Nx - Cx)); + + if (Nx < Cx) { + C_angle += Math.PI; + } else if (Ny < Cy) { + C_angle += this.TWO_PI; + } + } else if (Ny < Cy) { + C_angle = this.ONE_AND_HALF_PI; // 270 degrees + } else { + C_angle = this.HALF_PI; // 90 degrees + } + + return C_angle; +}; + +/** + * This method checks whether the given two line segments (one with point + * p1 and p2, the other with point p3 and p4) intersect at a point other + * than these points. + */ +IGeometry.doIntersect = function (p1, p2, p3, p4) { + var a = p1.x; + var b = p1.y; + var c = p2.x; + var d = p2.y; + var p = p3.x; + var q = p3.y; + var r = p4.x; + var s = p4.y; + var det = (c - a) * (s - q) - (r - p) * (d - b); + + if (det === 0) { + return false; + } else { + var lambda = ((s - q) * (r - a) + (p - r) * (s - b)) / det; + var gamma = ((b - d) * (r - a) + (c - a) * (s - b)) / det; + return 0 < lambda && lambda < 1 && 0 < gamma && gamma < 1; + } +}; + +/** + * This method checks and calculates the intersection of + * a line segment and a circle. + */ +IGeometry.findCircleLineIntersections = function (Ex, Ey, Lx, Ly, Cx, Cy, r) { + + // E is the starting point of the ray, + // L is the end point of the ray, + // C is the center of sphere you're testing against + // r is the radius of that sphere + + // Compute: + // d = L - E ( Direction vector of ray, from start to end ) + // f = E - C ( Vector from center sphere to ray start ) + + // Then the intersection is found by.. + // P = E + t * d + // This is a parametric equation: + // Px = Ex + tdx + // Py = Ey + tdy + + // get a, b, c values + var a = (Lx - Ex) * (Lx - Ex) + (Ly - Ey) * (Ly - Ey); + var b = 2 * ((Ex - Cx) * (Lx - Ex) + (Ey - Cy) * (Ly - Ey)); + var c = (Ex - Cx) * (Ex - Cx) + (Ey - Cy) * (Ey - Cy) - r * r; + + // get discriminant + var disc = b * b - 4 * a * c; + if (disc >= 0) { + // insert into quadratic formula + var t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / (2 * a); + var t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / (2 * a); + var intersections = null; + if (t1 >= 0 && t1 <= 1) { + // t1 is the intersection, and it's closer than t2 + // (since t1 uses -b - discriminant) + // Impale, Poke + return [t1]; + } + + // here t1 didn't intersect so we are either started + // inside the sphere or completely past it + if (t2 >= 0 && t2 <= 1) { + // ExitWound + return [t2]; + } + + return intersections; + } else return null; +}; + +// ----------------------------------------------------------------------------- +// Section: Class Constants +// ----------------------------------------------------------------------------- +/** + * Some useful pre-calculated constants + */ +IGeometry.HALF_PI = 0.5 * Math.PI; +IGeometry.ONE_AND_HALF_PI = 1.5 * Math.PI; +IGeometry.TWO_PI = 2.0 * Math.PI; +IGeometry.THREE_PI = 3.0 * Math.PI; + +module.exports = IGeometry; + +/***/ }), +/* 9 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +function IMath() {} + +/** + * This method returns the sign of the input value. + */ +IMath.sign = function (value) { + if (value > 0) { + return 1; + } else if (value < 0) { + return -1; + } else { + return 0; + } +}; + +IMath.floor = function (value) { + return value < 0 ? Math.ceil(value) : Math.floor(value); +}; + +IMath.ceil = function (value) { + return value < 0 ? Math.floor(value) : Math.ceil(value); +}; + +module.exports = IMath; + +/***/ }), +/* 10 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +function Integer() {} + +Integer.MAX_VALUE = 2147483647; +Integer.MIN_VALUE = -2147483648; + +module.exports = Integer; + +/***/ }), +/* 11 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var nodeFrom = function nodeFrom(value) { + return { value: value, next: null, prev: null }; +}; + +var add = function add(prev, node, next, list) { + if (prev !== null) { + prev.next = node; + } else { + list.head = node; + } + + if (next !== null) { + next.prev = node; + } else { + list.tail = node; + } + + node.prev = prev; + node.next = next; + + list.length++; + + return node; +}; + +var _remove = function _remove(node, list) { + var prev = node.prev, + next = node.next; + + + if (prev !== null) { + prev.next = next; + } else { + list.head = next; + } + + if (next !== null) { + next.prev = prev; + } else { + list.tail = prev; + } + + node.prev = node.next = null; + + list.length--; + + return node; +}; + +var LinkedList = function () { + function LinkedList(vals) { + var _this = this; + + _classCallCheck(this, LinkedList); + + this.length = 0; + this.head = null; + this.tail = null; + + if (vals != null) { + vals.forEach(function (v) { + return _this.push(v); + }); + } + } + + _createClass(LinkedList, [{ + key: "size", + value: function size() { + return this.length; + } + }, { + key: "insertBefore", + value: function insertBefore(val, otherNode) { + return add(otherNode.prev, nodeFrom(val), otherNode, this); + } + }, { + key: "insertAfter", + value: function insertAfter(val, otherNode) { + return add(otherNode, nodeFrom(val), otherNode.next, this); + } + }, { + key: "insertNodeBefore", + value: function insertNodeBefore(newNode, otherNode) { + return add(otherNode.prev, newNode, otherNode, this); + } + }, { + key: "insertNodeAfter", + value: function insertNodeAfter(newNode, otherNode) { + return add(otherNode, newNode, otherNode.next, this); + } + }, { + key: "push", + value: function push(val) { + return add(this.tail, nodeFrom(val), null, this); + } + }, { + key: "unshift", + value: function unshift(val) { + return add(null, nodeFrom(val), this.head, this); + } + }, { + key: "remove", + value: function remove(node) { + return _remove(node, this); + } + }, { + key: "pop", + value: function pop() { + return _remove(this.tail, this).value; + } + }, { + key: "popNode", + value: function popNode() { + return _remove(this.tail, this); + } + }, { + key: "shift", + value: function shift() { + return _remove(this.head, this).value; + } + }, { + key: "shiftNode", + value: function shiftNode() { + return _remove(this.head, this); + } + }, { + key: "get_object_at", + value: function get_object_at(index) { + if (index <= this.length()) { + var i = 1; + var current = this.head; + while (i < index) { + current = current.next; + i++; + } + return current.value; + } + } + }, { + key: "set_object_at", + value: function set_object_at(index, value) { + if (index <= this.length()) { + var i = 1; + var current = this.head; + while (i < index) { + current = current.next; + i++; + } + current.value = value; + } + } + }]); + + return LinkedList; +}(); + +module.exports = LinkedList; + +/***/ }), +/* 12 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* + *This class is the javascript implementation of the Point.java class in jdk + */ +function Point(x, y, p) { + this.x = null; + this.y = null; + if (x == null && y == null && p == null) { + this.x = 0; + this.y = 0; + } else if (typeof x == 'number' && typeof y == 'number' && p == null) { + this.x = x; + this.y = y; + } else if (x.constructor.name == 'Point' && y == null && p == null) { + p = x; + this.x = p.x; + this.y = p.y; + } +} + +Point.prototype.getX = function () { + return this.x; +}; + +Point.prototype.getY = function () { + return this.y; +}; + +Point.prototype.getLocation = function () { + return new Point(this.x, this.y); +}; + +Point.prototype.setLocation = function (x, y, p) { + if (x.constructor.name == 'Point' && y == null && p == null) { + p = x; + this.setLocation(p.x, p.y); + } else if (typeof x == 'number' && typeof y == 'number' && p == null) { + //if both parameters are integer just move (x,y) location + if (parseInt(x) == x && parseInt(y) == y) { + this.move(x, y); + } else { + this.x = Math.floor(x + 0.5); + this.y = Math.floor(y + 0.5); + } + } +}; + +Point.prototype.move = function (x, y) { + this.x = x; + this.y = y; +}; + +Point.prototype.translate = function (dx, dy) { + this.x += dx; + this.y += dy; +}; + +Point.prototype.equals = function (obj) { + if (obj.constructor.name == "Point") { + var pt = obj; + return this.x == pt.x && this.y == pt.y; + } + return this == obj; +}; + +Point.prototype.toString = function () { + return new Point().constructor.name + "[x=" + this.x + ",y=" + this.y + "]"; +}; + +module.exports = Point; + +/***/ }), +/* 13 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +function RectangleD(x, y, width, height) { + this.x = 0; + this.y = 0; + this.width = 0; + this.height = 0; + + if (x != null && y != null && width != null && height != null) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } +} + +RectangleD.prototype.getX = function () { + return this.x; +}; + +RectangleD.prototype.setX = function (x) { + this.x = x; +}; + +RectangleD.prototype.getY = function () { + return this.y; +}; + +RectangleD.prototype.setY = function (y) { + this.y = y; +}; + +RectangleD.prototype.getWidth = function () { + return this.width; +}; + +RectangleD.prototype.setWidth = function (width) { + this.width = width; +}; + +RectangleD.prototype.getHeight = function () { + return this.height; +}; + +RectangleD.prototype.setHeight = function (height) { + this.height = height; +}; + +RectangleD.prototype.getRight = function () { + return this.x + this.width; +}; + +RectangleD.prototype.getBottom = function () { + return this.y + this.height; +}; + +RectangleD.prototype.intersects = function (a) { + if (this.getRight() < a.x) { + return false; + } + + if (this.getBottom() < a.y) { + return false; + } + + if (a.getRight() < this.x) { + return false; + } + + if (a.getBottom() < this.y) { + return false; + } + + return true; +}; + +RectangleD.prototype.getCenterX = function () { + return this.x + this.width / 2; +}; + +RectangleD.prototype.getMinX = function () { + return this.getX(); +}; + +RectangleD.prototype.getMaxX = function () { + return this.getX() + this.width; +}; + +RectangleD.prototype.getCenterY = function () { + return this.y + this.height / 2; +}; + +RectangleD.prototype.getMinY = function () { + return this.getY(); +}; + +RectangleD.prototype.getMaxY = function () { + return this.getY() + this.height; +}; + +RectangleD.prototype.getWidthHalf = function () { + return this.width / 2; +}; + +RectangleD.prototype.getHeightHalf = function () { + return this.height / 2; +}; + +module.exports = RectangleD; + +/***/ }), +/* 14 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + +function UniqueIDGeneretor() {} + +UniqueIDGeneretor.lastID = 0; + +UniqueIDGeneretor.createID = function (obj) { + if (UniqueIDGeneretor.isPrimitive(obj)) { + return obj; + } + if (obj.uniqueID != null) { + return obj.uniqueID; + } + obj.uniqueID = UniqueIDGeneretor.getString(); + UniqueIDGeneretor.lastID++; + return obj.uniqueID; +}; + +UniqueIDGeneretor.getString = function (id) { + if (id == null) id = UniqueIDGeneretor.lastID; + return "Object#" + id + ""; +}; + +UniqueIDGeneretor.isPrimitive = function (arg) { + var type = typeof arg === "undefined" ? "undefined" : _typeof(arg); + return arg == null || type != "object" && type != "function"; +}; + +module.exports = UniqueIDGeneretor; + +/***/ }), +/* 15 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } + +var LayoutConstants = __webpack_require__(0); +var LGraphManager = __webpack_require__(7); +var LNode = __webpack_require__(3); +var LEdge = __webpack_require__(1); +var LGraph = __webpack_require__(6); +var PointD = __webpack_require__(5); +var Transform = __webpack_require__(17); +var Emitter = __webpack_require__(29); + +function Layout(isRemoteUse) { + Emitter.call(this); + + //Layout Quality: 0:draft, 1:default, 2:proof + this.layoutQuality = LayoutConstants.QUALITY; + //Whether layout should create bendpoints as needed or not + this.createBendsAsNeeded = LayoutConstants.DEFAULT_CREATE_BENDS_AS_NEEDED; + //Whether layout should be incremental or not + this.incremental = LayoutConstants.DEFAULT_INCREMENTAL; + //Whether we animate from before to after layout node positions + this.animationOnLayout = LayoutConstants.DEFAULT_ANIMATION_ON_LAYOUT; + //Whether we animate the layout process or not + this.animationDuringLayout = LayoutConstants.DEFAULT_ANIMATION_DURING_LAYOUT; + //Number iterations that should be done between two successive animations + this.animationPeriod = LayoutConstants.DEFAULT_ANIMATION_PERIOD; + /** + * Whether or not leaf nodes (non-compound nodes) are of uniform sizes. When + * they are, both spring and repulsion forces between two leaf nodes can be + * calculated without the expensive clipping point calculations, resulting + * in major speed-up. + */ + this.uniformLeafNodeSizes = LayoutConstants.DEFAULT_UNIFORM_LEAF_NODE_SIZES; + /** + * This is used for creation of bendpoints by using dummy nodes and edges. + * Maps an LEdge to its dummy bendpoint path. + */ + this.edgeToDummyNodes = new Map(); + this.graphManager = new LGraphManager(this); + this.isLayoutFinished = false; + this.isSubLayout = false; + this.isRemoteUse = false; + + if (isRemoteUse != null) { + this.isRemoteUse = isRemoteUse; + } +} + +Layout.RANDOM_SEED = 1; + +Layout.prototype = Object.create(Emitter.prototype); + +Layout.prototype.getGraphManager = function () { + return this.graphManager; +}; + +Layout.prototype.getAllNodes = function () { + return this.graphManager.getAllNodes(); +}; + +Layout.prototype.getAllEdges = function () { + return this.graphManager.getAllEdges(); +}; + +Layout.prototype.getAllNodesToApplyGravitation = function () { + return this.graphManager.getAllNodesToApplyGravitation(); +}; + +Layout.prototype.newGraphManager = function () { + var gm = new LGraphManager(this); + this.graphManager = gm; + return gm; +}; + +Layout.prototype.newGraph = function (vGraph) { + return new LGraph(null, this.graphManager, vGraph); +}; + +Layout.prototype.newNode = function (vNode) { + return new LNode(this.graphManager, vNode); +}; + +Layout.prototype.newEdge = function (vEdge) { + return new LEdge(null, null, vEdge); +}; + +Layout.prototype.checkLayoutSuccess = function () { + return this.graphManager.getRoot() == null || this.graphManager.getRoot().getNodes().length == 0 || this.graphManager.includesInvalidEdge(); +}; + +Layout.prototype.runLayout = function () { + this.isLayoutFinished = false; + + if (this.tilingPreLayout) { + this.tilingPreLayout(); + } + + this.initParameters(); + var isLayoutSuccessfull; + + if (this.checkLayoutSuccess()) { + isLayoutSuccessfull = false; + } else { + isLayoutSuccessfull = this.layout(); + } + + if (LayoutConstants.ANIMATE === 'during') { + // If this is a 'during' layout animation. Layout is not finished yet. + // We need to perform these in index.js when layout is really finished. + return false; + } + + if (isLayoutSuccessfull) { + if (!this.isSubLayout) { + this.doPostLayout(); + } + } + + if (this.tilingPostLayout) { + this.tilingPostLayout(); + } + + this.isLayoutFinished = true; + + return isLayoutSuccessfull; +}; + +/** + * This method performs the operations required after layout. + */ +Layout.prototype.doPostLayout = function () { + //assert !isSubLayout : "Should not be called on sub-layout!"; + // Propagate geometric changes to v-level objects + if (!this.incremental) { + this.transform(); + } + this.update(); +}; + +/** + * This method updates the geometry of the target graph according to + * calculated layout. + */ +Layout.prototype.update2 = function () { + // update bend points + if (this.createBendsAsNeeded) { + this.createBendpointsFromDummyNodes(); + + // reset all edges, since the topology has changed + this.graphManager.resetAllEdges(); + } + + // perform edge, node and root updates if layout is not called + // remotely + if (!this.isRemoteUse) { + // update all edges + var edge; + var allEdges = this.graphManager.getAllEdges(); + for (var i = 0; i < allEdges.length; i++) { + edge = allEdges[i]; + // this.update(edge); + } + + // recursively update nodes + var node; + var nodes = this.graphManager.getRoot().getNodes(); + for (var i = 0; i < nodes.length; i++) { + node = nodes[i]; + // this.update(node); + } + + // update root graph + this.update(this.graphManager.getRoot()); + } +}; + +Layout.prototype.update = function (obj) { + if (obj == null) { + this.update2(); + } else if (obj instanceof LNode) { + var node = obj; + if (node.getChild() != null) { + // since node is compound, recursively update child nodes + var nodes = node.getChild().getNodes(); + for (var i = 0; i < nodes.length; i++) { + update(nodes[i]); + } + } + + // if the l-level node is associated with a v-level graph object, + // then it is assumed that the v-level node implements the + // interface Updatable. + if (node.vGraphObject != null) { + // cast to Updatable without any type check + var vNode = node.vGraphObject; + + // call the update method of the interface + vNode.update(node); + } + } else if (obj instanceof LEdge) { + var edge = obj; + // if the l-level edge is associated with a v-level graph object, + // then it is assumed that the v-level edge implements the + // interface Updatable. + + if (edge.vGraphObject != null) { + // cast to Updatable without any type check + var vEdge = edge.vGraphObject; + + // call the update method of the interface + vEdge.update(edge); + } + } else if (obj instanceof LGraph) { + var graph = obj; + // if the l-level graph is associated with a v-level graph object, + // then it is assumed that the v-level object implements the + // interface Updatable. + + if (graph.vGraphObject != null) { + // cast to Updatable without any type check + var vGraph = graph.vGraphObject; + + // call the update method of the interface + vGraph.update(graph); + } + } +}; + +/** + * This method is used to set all layout parameters to default values + * determined at compile time. + */ +Layout.prototype.initParameters = function () { + if (!this.isSubLayout) { + this.layoutQuality = LayoutConstants.QUALITY; + this.animationDuringLayout = LayoutConstants.DEFAULT_ANIMATION_DURING_LAYOUT; + this.animationPeriod = LayoutConstants.DEFAULT_ANIMATION_PERIOD; + this.animationOnLayout = LayoutConstants.DEFAULT_ANIMATION_ON_LAYOUT; + this.incremental = LayoutConstants.DEFAULT_INCREMENTAL; + this.createBendsAsNeeded = LayoutConstants.DEFAULT_CREATE_BENDS_AS_NEEDED; + this.uniformLeafNodeSizes = LayoutConstants.DEFAULT_UNIFORM_LEAF_NODE_SIZES; + } + + if (this.animationDuringLayout) { + this.animationOnLayout = false; + } +}; + +Layout.prototype.transform = function (newLeftTop) { + if (newLeftTop == undefined) { + this.transform(new PointD(0, 0)); + } else { + // create a transformation object (from Eclipse to layout). When an + // inverse transform is applied, we get upper-left coordinate of the + // drawing or the root graph at given input coordinate (some margins + // already included in calculation of left-top). + + var trans = new Transform(); + var leftTop = this.graphManager.getRoot().updateLeftTop(); + + if (leftTop != null) { + trans.setWorldOrgX(newLeftTop.x); + trans.setWorldOrgY(newLeftTop.y); + + trans.setDeviceOrgX(leftTop.x); + trans.setDeviceOrgY(leftTop.y); + + var nodes = this.getAllNodes(); + var node; + + for (var i = 0; i < nodes.length; i++) { + node = nodes[i]; + node.transform(trans); + } + } + } +}; + +Layout.prototype.positionNodesRandomly = function (graph) { + + if (graph == undefined) { + //assert !this.incremental; + this.positionNodesRandomly(this.getGraphManager().getRoot()); + this.getGraphManager().getRoot().updateBounds(true); + } else { + var lNode; + var childGraph; + + var nodes = graph.getNodes(); + for (var i = 0; i < nodes.length; i++) { + lNode = nodes[i]; + childGraph = lNode.getChild(); + + if (childGraph == null) { + lNode.scatter(); + } else if (childGraph.getNodes().length == 0) { + lNode.scatter(); + } else { + this.positionNodesRandomly(childGraph); + lNode.updateBounds(); + } + } + } +}; + +/** + * This method returns a list of trees where each tree is represented as a + * list of l-nodes. The method returns a list of size 0 when: + * - The graph is not flat or + * - One of the component(s) of the graph is not a tree. + */ +Layout.prototype.getFlatForest = function () { + var flatForest = []; + var isForest = true; + + // Quick reference for all nodes in the graph manager associated with + // this layout. The list should not be changed. + var allNodes = this.graphManager.getRoot().getNodes(); + + // First be sure that the graph is flat + var isFlat = true; + + for (var i = 0; i < allNodes.length; i++) { + if (allNodes[i].getChild() != null) { + isFlat = false; + } + } + + // Return empty forest if the graph is not flat. + if (!isFlat) { + return flatForest; + } + + // Run BFS for each component of the graph. + + var visited = new Set(); + var toBeVisited = []; + var parents = new Map(); + var unProcessedNodes = []; + + unProcessedNodes = unProcessedNodes.concat(allNodes); + + // Each iteration of this loop finds a component of the graph and + // decides whether it is a tree or not. If it is a tree, adds it to the + // forest and continued with the next component. + + while (unProcessedNodes.length > 0 && isForest) { + toBeVisited.push(unProcessedNodes[0]); + + // Start the BFS. Each iteration of this loop visits a node in a + // BFS manner. + while (toBeVisited.length > 0 && isForest) { + //pool operation + var currentNode = toBeVisited[0]; + toBeVisited.splice(0, 1); + visited.add(currentNode); + + // Traverse all neighbors of this node + var neighborEdges = currentNode.getEdges(); + + for (var i = 0; i < neighborEdges.length; i++) { + var currentNeighbor = neighborEdges[i].getOtherEnd(currentNode); + + // If BFS is not growing from this neighbor. + if (parents.get(currentNode) != currentNeighbor) { + // We haven't previously visited this neighbor. + if (!visited.has(currentNeighbor)) { + toBeVisited.push(currentNeighbor); + parents.set(currentNeighbor, currentNode); + } + // Since we have previously visited this neighbor and + // this neighbor is not parent of currentNode, given + // graph contains a component that is not tree, hence + // it is not a forest. + else { + isForest = false; + break; + } + } + } + } + + // The graph contains a component that is not a tree. Empty + // previously found trees. The method will end. + if (!isForest) { + flatForest = []; + } + // Save currently visited nodes as a tree in our forest. Reset + // visited and parents lists. Continue with the next component of + // the graph, if any. + else { + var temp = [].concat(_toConsumableArray(visited)); + flatForest.push(temp); + //flatForest = flatForest.concat(temp); + //unProcessedNodes.removeAll(visited); + for (var i = 0; i < temp.length; i++) { + var value = temp[i]; + var index = unProcessedNodes.indexOf(value); + if (index > -1) { + unProcessedNodes.splice(index, 1); + } + } + visited = new Set(); + parents = new Map(); + } + } + + return flatForest; +}; + +/** + * This method creates dummy nodes (an l-level node with minimal dimensions) + * for the given edge (one per bendpoint). The existing l-level structure + * is updated accordingly. + */ +Layout.prototype.createDummyNodesForBendpoints = function (edge) { + var dummyNodes = []; + var prev = edge.source; + + var graph = this.graphManager.calcLowestCommonAncestor(edge.source, edge.target); + + for (var i = 0; i < edge.bendpoints.length; i++) { + // create new dummy node + var dummyNode = this.newNode(null); + dummyNode.setRect(new Point(0, 0), new Dimension(1, 1)); + + graph.add(dummyNode); + + // create new dummy edge between prev and dummy node + var dummyEdge = this.newEdge(null); + this.graphManager.add(dummyEdge, prev, dummyNode); + + dummyNodes.add(dummyNode); + prev = dummyNode; + } + + var dummyEdge = this.newEdge(null); + this.graphManager.add(dummyEdge, prev, edge.target); + + this.edgeToDummyNodes.set(edge, dummyNodes); + + // remove real edge from graph manager if it is inter-graph + if (edge.isInterGraph()) { + this.graphManager.remove(edge); + } + // else, remove the edge from the current graph + else { + graph.remove(edge); + } + + return dummyNodes; +}; + +/** + * This method creates bendpoints for edges from the dummy nodes + * at l-level. + */ +Layout.prototype.createBendpointsFromDummyNodes = function () { + var edges = []; + edges = edges.concat(this.graphManager.getAllEdges()); + edges = [].concat(_toConsumableArray(this.edgeToDummyNodes.keys())).concat(edges); + + for (var k = 0; k < edges.length; k++) { + var lEdge = edges[k]; + + if (lEdge.bendpoints.length > 0) { + var path = this.edgeToDummyNodes.get(lEdge); + + for (var i = 0; i < path.length; i++) { + var dummyNode = path[i]; + var p = new PointD(dummyNode.getCenterX(), dummyNode.getCenterY()); + + // update bendpoint's location according to dummy node + var ebp = lEdge.bendpoints.get(i); + ebp.x = p.x; + ebp.y = p.y; + + // remove the dummy node, dummy edges incident with this + // dummy node is also removed (within the remove method) + dummyNode.getOwner().remove(dummyNode); + } + + // add the real edge to graph + this.graphManager.add(lEdge, lEdge.source, lEdge.target); + } + } +}; + +Layout.transform = function (sliderValue, defaultValue, minDiv, maxMul) { + if (minDiv != undefined && maxMul != undefined) { + var value = defaultValue; + + if (sliderValue <= 50) { + var minValue = defaultValue / minDiv; + value -= (defaultValue - minValue) / 50 * (50 - sliderValue); + } else { + var maxValue = defaultValue * maxMul; + value += (maxValue - defaultValue) / 50 * (sliderValue - 50); + } + + return value; + } else { + var a, b; + + if (sliderValue <= 50) { + a = 9.0 * defaultValue / 500.0; + b = defaultValue / 10.0; + } else { + a = 9.0 * defaultValue / 50.0; + b = -8 * defaultValue; + } + + return a * sliderValue + b; + } +}; + +/** + * This method finds and returns the center of the given nodes, assuming + * that the given nodes form a tree in themselves. + */ +Layout.findCenterOfTree = function (nodes) { + var list = []; + list = list.concat(nodes); + + var removedNodes = []; + var remainingDegrees = new Map(); + var foundCenter = false; + var centerNode = null; + + if (list.length == 1 || list.length == 2) { + foundCenter = true; + centerNode = list[0]; + } + + for (var i = 0; i < list.length; i++) { + var node = list[i]; + var degree = node.getNeighborsList().size; + remainingDegrees.set(node, node.getNeighborsList().size); + + if (degree == 1) { + removedNodes.push(node); + } + } + + var tempList = []; + tempList = tempList.concat(removedNodes); + + while (!foundCenter) { + var tempList2 = []; + tempList2 = tempList2.concat(tempList); + tempList = []; + + for (var i = 0; i < list.length; i++) { + var node = list[i]; + + var index = list.indexOf(node); + if (index >= 0) { + list.splice(index, 1); + } + + var neighbours = node.getNeighborsList(); + + neighbours.forEach(function (neighbour) { + if (removedNodes.indexOf(neighbour) < 0) { + var otherDegree = remainingDegrees.get(neighbour); + var newDegree = otherDegree - 1; + + if (newDegree == 1) { + tempList.push(neighbour); + } + + remainingDegrees.set(neighbour, newDegree); + } + }); + } + + removedNodes = removedNodes.concat(tempList); + + if (list.length == 1 || list.length == 2) { + foundCenter = true; + centerNode = list[0]; + } + } + + return centerNode; +}; + +/** + * During the coarsening process, this layout may be referenced by two graph managers + * this setter function grants access to change the currently being used graph manager + */ +Layout.prototype.setGraphManager = function (gm) { + this.graphManager = gm; +}; + +module.exports = Layout; + +/***/ }), +/* 16 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +function RandomSeed() {} +// adapted from: https://stackoverflow.com/a/19303725 +RandomSeed.seed = 1; +RandomSeed.x = 0; + +RandomSeed.nextDouble = function () { + RandomSeed.x = Math.sin(RandomSeed.seed++) * 10000; + return RandomSeed.x - Math.floor(RandomSeed.x); +}; + +module.exports = RandomSeed; + +/***/ }), +/* 17 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var PointD = __webpack_require__(5); + +function Transform(x, y) { + this.lworldOrgX = 0.0; + this.lworldOrgY = 0.0; + this.ldeviceOrgX = 0.0; + this.ldeviceOrgY = 0.0; + this.lworldExtX = 1.0; + this.lworldExtY = 1.0; + this.ldeviceExtX = 1.0; + this.ldeviceExtY = 1.0; +} + +Transform.prototype.getWorldOrgX = function () { + return this.lworldOrgX; +}; + +Transform.prototype.setWorldOrgX = function (wox) { + this.lworldOrgX = wox; +}; + +Transform.prototype.getWorldOrgY = function () { + return this.lworldOrgY; +}; + +Transform.prototype.setWorldOrgY = function (woy) { + this.lworldOrgY = woy; +}; + +Transform.prototype.getWorldExtX = function () { + return this.lworldExtX; +}; + +Transform.prototype.setWorldExtX = function (wex) { + this.lworldExtX = wex; +}; + +Transform.prototype.getWorldExtY = function () { + return this.lworldExtY; +}; + +Transform.prototype.setWorldExtY = function (wey) { + this.lworldExtY = wey; +}; + +/* Device related */ + +Transform.prototype.getDeviceOrgX = function () { + return this.ldeviceOrgX; +}; + +Transform.prototype.setDeviceOrgX = function (dox) { + this.ldeviceOrgX = dox; +}; + +Transform.prototype.getDeviceOrgY = function () { + return this.ldeviceOrgY; +}; + +Transform.prototype.setDeviceOrgY = function (doy) { + this.ldeviceOrgY = doy; +}; + +Transform.prototype.getDeviceExtX = function () { + return this.ldeviceExtX; +}; + +Transform.prototype.setDeviceExtX = function (dex) { + this.ldeviceExtX = dex; +}; + +Transform.prototype.getDeviceExtY = function () { + return this.ldeviceExtY; +}; + +Transform.prototype.setDeviceExtY = function (dey) { + this.ldeviceExtY = dey; +}; + +Transform.prototype.transformX = function (x) { + var xDevice = 0.0; + var worldExtX = this.lworldExtX; + if (worldExtX != 0.0) { + xDevice = this.ldeviceOrgX + (x - this.lworldOrgX) * this.ldeviceExtX / worldExtX; + } + + return xDevice; +}; + +Transform.prototype.transformY = function (y) { + var yDevice = 0.0; + var worldExtY = this.lworldExtY; + if (worldExtY != 0.0) { + yDevice = this.ldeviceOrgY + (y - this.lworldOrgY) * this.ldeviceExtY / worldExtY; + } + + return yDevice; +}; + +Transform.prototype.inverseTransformX = function (x) { + var xWorld = 0.0; + var deviceExtX = this.ldeviceExtX; + if (deviceExtX != 0.0) { + xWorld = this.lworldOrgX + (x - this.ldeviceOrgX) * this.lworldExtX / deviceExtX; + } + + return xWorld; +}; + +Transform.prototype.inverseTransformY = function (y) { + var yWorld = 0.0; + var deviceExtY = this.ldeviceExtY; + if (deviceExtY != 0.0) { + yWorld = this.lworldOrgY + (y - this.ldeviceOrgY) * this.lworldExtY / deviceExtY; + } + return yWorld; +}; + +Transform.prototype.inverseTransformPoint = function (inPoint) { + var outPoint = new PointD(this.inverseTransformX(inPoint.x), this.inverseTransformY(inPoint.y)); + return outPoint; +}; + +module.exports = Transform; + +/***/ }), +/* 18 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } + +var Layout = __webpack_require__(15); +var FDLayoutConstants = __webpack_require__(4); +var LayoutConstants = __webpack_require__(0); +var IGeometry = __webpack_require__(8); +var IMath = __webpack_require__(9); + +function FDLayout() { + Layout.call(this); + + this.useSmartIdealEdgeLengthCalculation = FDLayoutConstants.DEFAULT_USE_SMART_IDEAL_EDGE_LENGTH_CALCULATION; + this.gravityConstant = FDLayoutConstants.DEFAULT_GRAVITY_STRENGTH; + this.compoundGravityConstant = FDLayoutConstants.DEFAULT_COMPOUND_GRAVITY_STRENGTH; + this.gravityRangeFactor = FDLayoutConstants.DEFAULT_GRAVITY_RANGE_FACTOR; + this.compoundGravityRangeFactor = FDLayoutConstants.DEFAULT_COMPOUND_GRAVITY_RANGE_FACTOR; + this.displacementThresholdPerNode = 3.0 * FDLayoutConstants.DEFAULT_EDGE_LENGTH / 100; + this.coolingFactor = FDLayoutConstants.DEFAULT_COOLING_FACTOR_INCREMENTAL; + this.initialCoolingFactor = FDLayoutConstants.DEFAULT_COOLING_FACTOR_INCREMENTAL; + this.totalDisplacement = 0.0; + this.oldTotalDisplacement = 0.0; + this.maxIterations = FDLayoutConstants.MAX_ITERATIONS; +} + +FDLayout.prototype = Object.create(Layout.prototype); + +for (var prop in Layout) { + FDLayout[prop] = Layout[prop]; +} + +FDLayout.prototype.initParameters = function () { + Layout.prototype.initParameters.call(this, arguments); + + this.totalIterations = 0; + this.notAnimatedIterations = 0; + + this.useFRGridVariant = FDLayoutConstants.DEFAULT_USE_SMART_REPULSION_RANGE_CALCULATION; + + this.grid = []; +}; + +FDLayout.prototype.calcIdealEdgeLengths = function () { + var edge; + var originalIdealLength; + var lcaDepth; + var source; + var target; + var sizeOfSourceInLca; + var sizeOfTargetInLca; + + var allEdges = this.getGraphManager().getAllEdges(); + for (var i = 0; i < allEdges.length; i++) { + edge = allEdges[i]; + + originalIdealLength = edge.idealLength; + + if (edge.isInterGraph) { + source = edge.getSource(); + target = edge.getTarget(); + + sizeOfSourceInLca = edge.getSourceInLca().getEstimatedSize(); + sizeOfTargetInLca = edge.getTargetInLca().getEstimatedSize(); + + if (this.useSmartIdealEdgeLengthCalculation) { + edge.idealLength += sizeOfSourceInLca + sizeOfTargetInLca - 2 * LayoutConstants.SIMPLE_NODE_SIZE; + } + + lcaDepth = edge.getLca().getInclusionTreeDepth(); + + edge.idealLength += originalIdealLength * FDLayoutConstants.PER_LEVEL_IDEAL_EDGE_LENGTH_FACTOR * (source.getInclusionTreeDepth() + target.getInclusionTreeDepth() - 2 * lcaDepth); + } + } +}; + +FDLayout.prototype.initSpringEmbedder = function () { + + var s = this.getAllNodes().length; + if (this.incremental) { + if (s > FDLayoutConstants.ADAPTATION_LOWER_NODE_LIMIT) { + this.coolingFactor = Math.max(this.coolingFactor * FDLayoutConstants.COOLING_ADAPTATION_FACTOR, this.coolingFactor - (s - FDLayoutConstants.ADAPTATION_LOWER_NODE_LIMIT) / (FDLayoutConstants.ADAPTATION_UPPER_NODE_LIMIT - FDLayoutConstants.ADAPTATION_LOWER_NODE_LIMIT) * this.coolingFactor * (1 - FDLayoutConstants.COOLING_ADAPTATION_FACTOR)); + } + this.maxNodeDisplacement = FDLayoutConstants.MAX_NODE_DISPLACEMENT_INCREMENTAL; + } else { + if (s > FDLayoutConstants.ADAPTATION_LOWER_NODE_LIMIT) { + this.coolingFactor = Math.max(FDLayoutConstants.COOLING_ADAPTATION_FACTOR, 1.0 - (s - FDLayoutConstants.ADAPTATION_LOWER_NODE_LIMIT) / (FDLayoutConstants.ADAPTATION_UPPER_NODE_LIMIT - FDLayoutConstants.ADAPTATION_LOWER_NODE_LIMIT) * (1 - FDLayoutConstants.COOLING_ADAPTATION_FACTOR)); + } else { + this.coolingFactor = 1.0; + } + this.initialCoolingFactor = this.coolingFactor; + this.maxNodeDisplacement = FDLayoutConstants.MAX_NODE_DISPLACEMENT; + } + + this.maxIterations = Math.max(this.getAllNodes().length * 5, this.maxIterations); + + // Reassign this attribute by using new constant value + this.displacementThresholdPerNode = 3.0 * FDLayoutConstants.DEFAULT_EDGE_LENGTH / 100; + this.totalDisplacementThreshold = this.displacementThresholdPerNode * this.getAllNodes().length; + + this.repulsionRange = this.calcRepulsionRange(); +}; + +FDLayout.prototype.calcSpringForces = function () { + var lEdges = this.getAllEdges(); + var edge; + + for (var i = 0; i < lEdges.length; i++) { + edge = lEdges[i]; + + this.calcSpringForce(edge, edge.idealLength); + } +}; + +FDLayout.prototype.calcRepulsionForces = function () { + var gridUpdateAllowed = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; + var forceToNodeSurroundingUpdate = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; + + var i, j; + var nodeA, nodeB; + var lNodes = this.getAllNodes(); + var processedNodeSet; + + if (this.useFRGridVariant) { + if (this.totalIterations % FDLayoutConstants.GRID_CALCULATION_CHECK_PERIOD == 1 && gridUpdateAllowed) { + this.updateGrid(); + } + + processedNodeSet = new Set(); + + // calculate repulsion forces between each nodes and its surrounding + for (i = 0; i < lNodes.length; i++) { + nodeA = lNodes[i]; + this.calculateRepulsionForceOfANode(nodeA, processedNodeSet, gridUpdateAllowed, forceToNodeSurroundingUpdate); + processedNodeSet.add(nodeA); + } + } else { + for (i = 0; i < lNodes.length; i++) { + nodeA = lNodes[i]; + + for (j = i + 1; j < lNodes.length; j++) { + nodeB = lNodes[j]; + + // If both nodes are not members of the same graph, skip. + if (nodeA.getOwner() != nodeB.getOwner()) { + continue; + } + + this.calcRepulsionForce(nodeA, nodeB); + } + } + } +}; + +FDLayout.prototype.calcGravitationalForces = function () { + var node; + var lNodes = this.getAllNodesToApplyGravitation(); + + for (var i = 0; i < lNodes.length; i++) { + node = lNodes[i]; + this.calcGravitationalForce(node); + } +}; + +FDLayout.prototype.moveNodes = function () { + var lNodes = this.getAllNodes(); + var node; + + for (var i = 0; i < lNodes.length; i++) { + node = lNodes[i]; + node.move(); + } +}; + +FDLayout.prototype.calcSpringForce = function (edge, idealLength) { + var sourceNode = edge.getSource(); + var targetNode = edge.getTarget(); + + var length; + var springForce; + var springForceX; + var springForceY; + + // Update edge length + if (this.uniformLeafNodeSizes && sourceNode.getChild() == null && targetNode.getChild() == null) { + edge.updateLengthSimple(); + } else { + edge.updateLength(); + + if (edge.isOverlapingSourceAndTarget) { + return; + } + } + + length = edge.getLength(); + + if (length == 0) return; + + // Calculate spring forces + springForce = edge.edgeElasticity * (length - idealLength); + + // Project force onto x and y axes + springForceX = springForce * (edge.lengthX / length); + springForceY = springForce * (edge.lengthY / length); + + // Apply forces on the end nodes + sourceNode.springForceX += springForceX; + sourceNode.springForceY += springForceY; + targetNode.springForceX -= springForceX; + targetNode.springForceY -= springForceY; +}; + +FDLayout.prototype.calcRepulsionForce = function (nodeA, nodeB) { + var rectA = nodeA.getRect(); + var rectB = nodeB.getRect(); + var overlapAmount = new Array(2); + var clipPoints = new Array(4); + var distanceX; + var distanceY; + var distanceSquared; + var distance; + var repulsionForce; + var repulsionForceX; + var repulsionForceY; + + if (rectA.intersects(rectB)) // two nodes overlap + { + // calculate separation amount in x and y directions + IGeometry.calcSeparationAmount(rectA, rectB, overlapAmount, FDLayoutConstants.DEFAULT_EDGE_LENGTH / 2.0); + + repulsionForceX = 2 * overlapAmount[0]; + repulsionForceY = 2 * overlapAmount[1]; + + var childrenConstant = nodeA.noOfChildren * nodeB.noOfChildren / (nodeA.noOfChildren + nodeB.noOfChildren); + + // Apply forces on the two nodes + nodeA.repulsionForceX -= childrenConstant * repulsionForceX; + nodeA.repulsionForceY -= childrenConstant * repulsionForceY; + nodeB.repulsionForceX += childrenConstant * repulsionForceX; + nodeB.repulsionForceY += childrenConstant * repulsionForceY; + } else // no overlap + { + // calculate distance + + if (this.uniformLeafNodeSizes && nodeA.getChild() == null && nodeB.getChild() == null) // simply base repulsion on distance of node centers + { + distanceX = rectB.getCenterX() - rectA.getCenterX(); + distanceY = rectB.getCenterY() - rectA.getCenterY(); + } else // use clipping points + { + IGeometry.getIntersection(rectA, rectB, clipPoints); + + distanceX = clipPoints[2] - clipPoints[0]; + distanceY = clipPoints[3] - clipPoints[1]; + } + + // No repulsion range. FR grid variant should take care of this. + if (Math.abs(distanceX) < FDLayoutConstants.MIN_REPULSION_DIST) { + distanceX = IMath.sign(distanceX) * FDLayoutConstants.MIN_REPULSION_DIST; + } + + if (Math.abs(distanceY) < FDLayoutConstants.MIN_REPULSION_DIST) { + distanceY = IMath.sign(distanceY) * FDLayoutConstants.MIN_REPULSION_DIST; + } + + distanceSquared = distanceX * distanceX + distanceY * distanceY; + distance = Math.sqrt(distanceSquared); + + // Here we use half of the nodes' repulsion values for backward compatibility + repulsionForce = (nodeA.nodeRepulsion / 2 + nodeB.nodeRepulsion / 2) * nodeA.noOfChildren * nodeB.noOfChildren / distanceSquared; + + // Project force onto x and y axes + repulsionForceX = repulsionForce * distanceX / distance; + repulsionForceY = repulsionForce * distanceY / distance; + + // Apply forces on the two nodes + nodeA.repulsionForceX -= repulsionForceX; + nodeA.repulsionForceY -= repulsionForceY; + nodeB.repulsionForceX += repulsionForceX; + nodeB.repulsionForceY += repulsionForceY; + } +}; + +FDLayout.prototype.calcGravitationalForce = function (node) { + var ownerGraph; + var ownerCenterX; + var ownerCenterY; + var distanceX; + var distanceY; + var absDistanceX; + var absDistanceY; + var estimatedSize; + ownerGraph = node.getOwner(); + + ownerCenterX = (ownerGraph.getRight() + ownerGraph.getLeft()) / 2; + ownerCenterY = (ownerGraph.getTop() + ownerGraph.getBottom()) / 2; + distanceX = node.getCenterX() - ownerCenterX; + distanceY = node.getCenterY() - ownerCenterY; + absDistanceX = Math.abs(distanceX) + node.getWidth() / 2; + absDistanceY = Math.abs(distanceY) + node.getHeight() / 2; + + if (node.getOwner() == this.graphManager.getRoot()) // in the root graph + { + estimatedSize = ownerGraph.getEstimatedSize() * this.gravityRangeFactor; + + if (absDistanceX > estimatedSize || absDistanceY > estimatedSize) { + node.gravitationForceX = -this.gravityConstant * distanceX; + node.gravitationForceY = -this.gravityConstant * distanceY; + } + } else // inside a compound + { + estimatedSize = ownerGraph.getEstimatedSize() * this.compoundGravityRangeFactor; + + if (absDistanceX > estimatedSize || absDistanceY > estimatedSize) { + node.gravitationForceX = -this.gravityConstant * distanceX * this.compoundGravityConstant; + node.gravitationForceY = -this.gravityConstant * distanceY * this.compoundGravityConstant; + } + } +}; + +FDLayout.prototype.isConverged = function () { + var converged; + var oscilating = false; + + if (this.totalIterations > this.maxIterations / 3) { + oscilating = Math.abs(this.totalDisplacement - this.oldTotalDisplacement) < 2; + } + + converged = this.totalDisplacement < this.totalDisplacementThreshold; + + this.oldTotalDisplacement = this.totalDisplacement; + + return converged || oscilating; +}; + +FDLayout.prototype.animate = function () { + if (this.animationDuringLayout && !this.isSubLayout) { + if (this.notAnimatedIterations == this.animationPeriod) { + this.update(); + this.notAnimatedIterations = 0; + } else { + this.notAnimatedIterations++; + } + } +}; + +//This method calculates the number of children (weight) for all nodes +FDLayout.prototype.calcNoOfChildrenForAllNodes = function () { + var node; + var allNodes = this.graphManager.getAllNodes(); + + for (var i = 0; i < allNodes.length; i++) { + node = allNodes[i]; + node.noOfChildren = node.getNoOfChildren(); + } +}; + +// ----------------------------------------------------------------------------- +// Section: FR-Grid Variant Repulsion Force Calculation +// ----------------------------------------------------------------------------- + +FDLayout.prototype.calcGrid = function (graph) { + + var sizeX = 0; + var sizeY = 0; + + sizeX = parseInt(Math.ceil((graph.getRight() - graph.getLeft()) / this.repulsionRange)); + sizeY = parseInt(Math.ceil((graph.getBottom() - graph.getTop()) / this.repulsionRange)); + + var grid = new Array(sizeX); + + for (var i = 0; i < sizeX; i++) { + grid[i] = new Array(sizeY); + } + + for (var i = 0; i < sizeX; i++) { + for (var j = 0; j < sizeY; j++) { + grid[i][j] = new Array(); + } + } + + return grid; +}; + +FDLayout.prototype.addNodeToGrid = function (v, left, top) { + + var startX = 0; + var finishX = 0; + var startY = 0; + var finishY = 0; + + startX = parseInt(Math.floor((v.getRect().x - left) / this.repulsionRange)); + finishX = parseInt(Math.floor((v.getRect().width + v.getRect().x - left) / this.repulsionRange)); + startY = parseInt(Math.floor((v.getRect().y - top) / this.repulsionRange)); + finishY = parseInt(Math.floor((v.getRect().height + v.getRect().y - top) / this.repulsionRange)); + + for (var i = startX; i <= finishX; i++) { + for (var j = startY; j <= finishY; j++) { + this.grid[i][j].push(v); + v.setGridCoordinates(startX, finishX, startY, finishY); + } + } +}; + +FDLayout.prototype.updateGrid = function () { + var i; + var nodeA; + var lNodes = this.getAllNodes(); + + this.grid = this.calcGrid(this.graphManager.getRoot()); + + // put all nodes to proper grid cells + for (i = 0; i < lNodes.length; i++) { + nodeA = lNodes[i]; + this.addNodeToGrid(nodeA, this.graphManager.getRoot().getLeft(), this.graphManager.getRoot().getTop()); + } +}; + +FDLayout.prototype.calculateRepulsionForceOfANode = function (nodeA, processedNodeSet, gridUpdateAllowed, forceToNodeSurroundingUpdate) { + + if (this.totalIterations % FDLayoutConstants.GRID_CALCULATION_CHECK_PERIOD == 1 && gridUpdateAllowed || forceToNodeSurroundingUpdate) { + var surrounding = new Set(); + nodeA.surrounding = new Array(); + var nodeB; + var grid = this.grid; + + for (var i = nodeA.startX - 1; i < nodeA.finishX + 2; i++) { + for (var j = nodeA.startY - 1; j < nodeA.finishY + 2; j++) { + if (!(i < 0 || j < 0 || i >= grid.length || j >= grid[0].length)) { + for (var k = 0; k < grid[i][j].length; k++) { + nodeB = grid[i][j][k]; + + // If both nodes are not members of the same graph, + // or both nodes are the same, skip. + if (nodeA.getOwner() != nodeB.getOwner() || nodeA == nodeB) { + continue; + } + + // check if the repulsion force between + // nodeA and nodeB has already been calculated + if (!processedNodeSet.has(nodeB) && !surrounding.has(nodeB)) { + var distanceX = Math.abs(nodeA.getCenterX() - nodeB.getCenterX()) - (nodeA.getWidth() / 2 + nodeB.getWidth() / 2); + var distanceY = Math.abs(nodeA.getCenterY() - nodeB.getCenterY()) - (nodeA.getHeight() / 2 + nodeB.getHeight() / 2); + + // if the distance between nodeA and nodeB + // is less then calculation range + if (distanceX <= this.repulsionRange && distanceY <= this.repulsionRange) { + //then add nodeB to surrounding of nodeA + surrounding.add(nodeB); + } + } + } + } + } + } + + nodeA.surrounding = [].concat(_toConsumableArray(surrounding)); + } + for (i = 0; i < nodeA.surrounding.length; i++) { + this.calcRepulsionForce(nodeA, nodeA.surrounding[i]); + } +}; + +FDLayout.prototype.calcRepulsionRange = function () { + return 0.0; +}; + +module.exports = FDLayout; + +/***/ }), +/* 19 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var LEdge = __webpack_require__(1); +var FDLayoutConstants = __webpack_require__(4); + +function FDLayoutEdge(source, target, vEdge) { + LEdge.call(this, source, target, vEdge); + + // Ideal length and elasticity value for this edge + this.idealLength = FDLayoutConstants.DEFAULT_EDGE_LENGTH; + this.edgeElasticity = FDLayoutConstants.DEFAULT_SPRING_STRENGTH; +} + +FDLayoutEdge.prototype = Object.create(LEdge.prototype); + +for (var prop in LEdge) { + FDLayoutEdge[prop] = LEdge[prop]; +} + +module.exports = FDLayoutEdge; + +/***/ }), +/* 20 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var LNode = __webpack_require__(3); +var FDLayoutConstants = __webpack_require__(4); + +function FDLayoutNode(gm, loc, size, vNode) { + // alternative constructor is handled inside LNode + LNode.call(this, gm, loc, size, vNode); + + // Repulsion value of this node + this.nodeRepulsion = FDLayoutConstants.DEFAULT_REPULSION_STRENGTH; + + //Spring, repulsion and gravitational forces acting on this node + this.springForceX = 0; + this.springForceY = 0; + this.repulsionForceX = 0; + this.repulsionForceY = 0; + this.gravitationForceX = 0; + this.gravitationForceY = 0; + //Amount by which this node is to be moved in this iteration + this.displacementX = 0; + this.displacementY = 0; + + //Start and finish grid coordinates that this node is fallen into + this.startX = 0; + this.finishX = 0; + this.startY = 0; + this.finishY = 0; + + //Geometric neighbors of this node + this.surrounding = []; +} + +FDLayoutNode.prototype = Object.create(LNode.prototype); + +for (var prop in LNode) { + FDLayoutNode[prop] = LNode[prop]; +} + +FDLayoutNode.prototype.setGridCoordinates = function (_startX, _finishX, _startY, _finishY) { + this.startX = _startX; + this.finishX = _finishX; + this.startY = _startY; + this.finishY = _finishY; +}; + +module.exports = FDLayoutNode; + +/***/ }), +/* 21 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +function DimensionD(width, height) { + this.width = 0; + this.height = 0; + if (width !== null && height !== null) { + this.height = height; + this.width = width; + } +} + +DimensionD.prototype.getWidth = function () { + return this.width; +}; + +DimensionD.prototype.setWidth = function (width) { + this.width = width; +}; + +DimensionD.prototype.getHeight = function () { + return this.height; +}; + +DimensionD.prototype.setHeight = function (height) { + this.height = height; +}; + +module.exports = DimensionD; + +/***/ }), +/* 22 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var UniqueIDGeneretor = __webpack_require__(14); + +function HashMap() { + this.map = {}; + this.keys = []; +} + +HashMap.prototype.put = function (key, value) { + var theId = UniqueIDGeneretor.createID(key); + if (!this.contains(theId)) { + this.map[theId] = value; + this.keys.push(key); + } +}; + +HashMap.prototype.contains = function (key) { + var theId = UniqueIDGeneretor.createID(key); + return this.map[key] != null; +}; + +HashMap.prototype.get = function (key) { + var theId = UniqueIDGeneretor.createID(key); + return this.map[theId]; +}; + +HashMap.prototype.keySet = function () { + return this.keys; +}; + +module.exports = HashMap; + +/***/ }), +/* 23 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var UniqueIDGeneretor = __webpack_require__(14); + +function HashSet() { + this.set = {}; +} +; + +HashSet.prototype.add = function (obj) { + var theId = UniqueIDGeneretor.createID(obj); + if (!this.contains(theId)) this.set[theId] = obj; +}; + +HashSet.prototype.remove = function (obj) { + delete this.set[UniqueIDGeneretor.createID(obj)]; +}; + +HashSet.prototype.clear = function () { + this.set = {}; +}; + +HashSet.prototype.contains = function (obj) { + return this.set[UniqueIDGeneretor.createID(obj)] == obj; +}; + +HashSet.prototype.isEmpty = function () { + return this.size() === 0; +}; + +HashSet.prototype.size = function () { + return Object.keys(this.set).length; +}; + +//concats this.set to the given list +HashSet.prototype.addAllTo = function (list) { + var keys = Object.keys(this.set); + var length = keys.length; + for (var i = 0; i < length; i++) { + list.push(this.set[keys[i]]); + } +}; + +HashSet.prototype.size = function () { + return Object.keys(this.set).length; +}; + +HashSet.prototype.addAll = function (list) { + var s = list.length; + for (var i = 0; i < s; i++) { + var v = list[i]; + this.add(v); + } +}; + +module.exports = HashSet; + +/***/ }), +/* 24 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +// Some matrix (1d and 2d array) operations +function Matrix() {} + +/** + * matrix multiplication + * array1, array2 and result are 2d arrays + */ +Matrix.multMat = function (array1, array2) { + var result = []; + + for (var i = 0; i < array1.length; i++) { + result[i] = []; + for (var j = 0; j < array2[0].length; j++) { + result[i][j] = 0; + for (var k = 0; k < array1[0].length; k++) { + result[i][j] += array1[i][k] * array2[k][j]; + } + } + } + return result; +}; + +/** + * matrix transpose + * array and result are 2d arrays + */ +Matrix.transpose = function (array) { + var result = []; + + for (var i = 0; i < array[0].length; i++) { + result[i] = []; + for (var j = 0; j < array.length; j++) { + result[i][j] = array[j][i]; + } + } + + return result; +}; + +/** + * multiply array with constant + * array and result are 1d arrays + */ +Matrix.multCons = function (array, constant) { + var result = []; + + for (var i = 0; i < array.length; i++) { + result[i] = array[i] * constant; + } + + return result; +}; + +/** + * substract two arrays + * array1, array2 and result are 1d arrays + */ +Matrix.minusOp = function (array1, array2) { + var result = []; + + for (var i = 0; i < array1.length; i++) { + result[i] = array1[i] - array2[i]; + } + + return result; +}; + +/** + * dot product of two arrays with same size + * array1 and array2 are 1d arrays + */ +Matrix.dotProduct = function (array1, array2) { + var product = 0; + + for (var i = 0; i < array1.length; i++) { + product += array1[i] * array2[i]; + } + + return product; +}; + +/** + * magnitude of an array + * array is 1d array + */ +Matrix.mag = function (array) { + return Math.sqrt(this.dotProduct(array, array)); +}; + +/** + * normalization of an array + * array and result are 1d array + */ +Matrix.normalize = function (array) { + var result = []; + var magnitude = this.mag(array); + + for (var i = 0; i < array.length; i++) { + result[i] = array[i] / magnitude; + } + + return result; +}; + +/** + * multiply an array with centering matrix + * array and result are 1d array + */ +Matrix.multGamma = function (array) { + var result = []; + var sum = 0; + + for (var i = 0; i < array.length; i++) { + sum += array[i]; + } + + sum *= -1 / array.length; + + for (var _i = 0; _i < array.length; _i++) { + result[_i] = sum + array[_i]; + } + return result; +}; + +/** + * a special matrix multiplication + * result = 0.5 * C * INV * C^T * array + * array and result are 1d, C and INV are 2d arrays + */ +Matrix.multL = function (array, C, INV) { + var result = []; + var temp1 = []; + var temp2 = []; + + // multiply by C^T + for (var i = 0; i < C[0].length; i++) { + var sum = 0; + for (var j = 0; j < C.length; j++) { + sum += -0.5 * C[j][i] * array[j]; + } + temp1[i] = sum; + } + // multiply the result by INV + for (var _i2 = 0; _i2 < INV.length; _i2++) { + var _sum = 0; + for (var _j = 0; _j < INV.length; _j++) { + _sum += INV[_i2][_j] * temp1[_j]; + } + temp2[_i2] = _sum; + } + // multiply the result by C + for (var _i3 = 0; _i3 < C.length; _i3++) { + var _sum2 = 0; + for (var _j2 = 0; _j2 < C[0].length; _j2++) { + _sum2 += C[_i3][_j2] * temp2[_j2]; + } + result[_i3] = _sum2; + } + + return result; +}; + +module.exports = Matrix; + +/***/ }), +/* 25 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +/** + * A classic Quicksort algorithm with Hoare's partition + * - Works also on LinkedList objects + * + * Copyright: i-Vis Research Group, Bilkent University, 2007 - present + */ + +var LinkedList = __webpack_require__(11); + +var Quicksort = function () { + function Quicksort(A, compareFunction) { + _classCallCheck(this, Quicksort); + + if (compareFunction !== null || compareFunction !== undefined) this.compareFunction = this._defaultCompareFunction; + + var length = void 0; + if (A instanceof LinkedList) length = A.size();else length = A.length; + + this._quicksort(A, 0, length - 1); + } + + _createClass(Quicksort, [{ + key: '_quicksort', + value: function _quicksort(A, p, r) { + if (p < r) { + var q = this._partition(A, p, r); + this._quicksort(A, p, q); + this._quicksort(A, q + 1, r); + } + } + }, { + key: '_partition', + value: function _partition(A, p, r) { + var x = this._get(A, p); + var i = p; + var j = r; + while (true) { + while (this.compareFunction(x, this._get(A, j))) { + j--; + }while (this.compareFunction(this._get(A, i), x)) { + i++; + }if (i < j) { + this._swap(A, i, j); + i++; + j--; + } else return j; + } + } + }, { + key: '_get', + value: function _get(object, index) { + if (object instanceof LinkedList) return object.get_object_at(index);else return object[index]; + } + }, { + key: '_set', + value: function _set(object, index, value) { + if (object instanceof LinkedList) object.set_object_at(index, value);else object[index] = value; + } + }, { + key: '_swap', + value: function _swap(A, i, j) { + var temp = this._get(A, i); + this._set(A, i, this._get(A, j)); + this._set(A, j, temp); + } + }, { + key: '_defaultCompareFunction', + value: function _defaultCompareFunction(a, b) { + return b > a; + } + }]); + + return Quicksort; +}(); + +module.exports = Quicksort; + +/***/ }), +/* 26 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +// Singular Value Decomposition implementation +function SVD() {}; + +/* Below singular value decomposition (svd) code including hypot function is adopted from https://github.com/dragonfly-ai/JamaJS + Some changes are applied to make the code compatible with the fcose code and to make it independent from Jama. + Input matrix is changed to a 2D array instead of Jama matrix. Matrix dimensions are taken according to 2D array instead of using Jama functions. + An object that includes singular value components is created for return. + The types of input parameters of the hypot function are removed. + let is used instead of var for the variable initialization. +*/ +/* + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +SVD.svd = function (A) { + this.U = null; + this.V = null; + this.s = null; + this.m = 0; + this.n = 0; + this.m = A.length; + this.n = A[0].length; + var nu = Math.min(this.m, this.n); + this.s = function (s) { + var a = []; + while (s-- > 0) { + a.push(0); + }return a; + }(Math.min(this.m + 1, this.n)); + this.U = function (dims) { + var allocate = function allocate(dims) { + if (dims.length == 0) { + return 0; + } else { + var array = []; + for (var i = 0; i < dims[0]; i++) { + array.push(allocate(dims.slice(1))); + } + return array; + } + }; + return allocate(dims); + }([this.m, nu]); + this.V = function (dims) { + var allocate = function allocate(dims) { + if (dims.length == 0) { + return 0; + } else { + var array = []; + for (var i = 0; i < dims[0]; i++) { + array.push(allocate(dims.slice(1))); + } + return array; + } + }; + return allocate(dims); + }([this.n, this.n]); + var e = function (s) { + var a = []; + while (s-- > 0) { + a.push(0); + }return a; + }(this.n); + var work = function (s) { + var a = []; + while (s-- > 0) { + a.push(0); + }return a; + }(this.m); + var wantu = true; + var wantv = true; + var nct = Math.min(this.m - 1, this.n); + var nrt = Math.max(0, Math.min(this.n - 2, this.m)); + for (var k = 0; k < Math.max(nct, nrt); k++) { + if (k < nct) { + this.s[k] = 0; + for (var i = k; i < this.m; i++) { + this.s[k] = SVD.hypot(this.s[k], A[i][k]); + } + ; + if (this.s[k] !== 0.0) { + if (A[k][k] < 0.0) { + this.s[k] = -this.s[k]; + } + for (var _i = k; _i < this.m; _i++) { + A[_i][k] /= this.s[k]; + } + ; + A[k][k] += 1.0; + } + this.s[k] = -this.s[k]; + } + for (var j = k + 1; j < this.n; j++) { + if (function (lhs, rhs) { + return lhs && rhs; + }(k < nct, this.s[k] !== 0.0)) { + var t = 0; + for (var _i2 = k; _i2 < this.m; _i2++) { + t += A[_i2][k] * A[_i2][j]; + } + ; + t = -t / A[k][k]; + for (var _i3 = k; _i3 < this.m; _i3++) { + A[_i3][j] += t * A[_i3][k]; + } + ; + } + e[j] = A[k][j]; + } + ; + if (function (lhs, rhs) { + return lhs && rhs; + }(wantu, k < nct)) { + for (var _i4 = k; _i4 < this.m; _i4++) { + this.U[_i4][k] = A[_i4][k]; + } + ; + } + if (k < nrt) { + e[k] = 0; + for (var _i5 = k + 1; _i5 < this.n; _i5++) { + e[k] = SVD.hypot(e[k], e[_i5]); + } + ; + if (e[k] !== 0.0) { + if (e[k + 1] < 0.0) { + e[k] = -e[k]; + } + for (var _i6 = k + 1; _i6 < this.n; _i6++) { + e[_i6] /= e[k]; + } + ; + e[k + 1] += 1.0; + } + e[k] = -e[k]; + if (function (lhs, rhs) { + return lhs && rhs; + }(k + 1 < this.m, e[k] !== 0.0)) { + for (var _i7 = k + 1; _i7 < this.m; _i7++) { + work[_i7] = 0.0; + } + ; + for (var _j = k + 1; _j < this.n; _j++) { + for (var _i8 = k + 1; _i8 < this.m; _i8++) { + work[_i8] += e[_j] * A[_i8][_j]; + } + ; + } + ; + for (var _j2 = k + 1; _j2 < this.n; _j2++) { + var _t = -e[_j2] / e[k + 1]; + for (var _i9 = k + 1; _i9 < this.m; _i9++) { + A[_i9][_j2] += _t * work[_i9]; + } + ; + } + ; + } + if (wantv) { + for (var _i10 = k + 1; _i10 < this.n; _i10++) { + this.V[_i10][k] = e[_i10]; + }; + } + } + }; + var p = Math.min(this.n, this.m + 1); + if (nct < this.n) { + this.s[nct] = A[nct][nct]; + } + if (this.m < p) { + this.s[p - 1] = 0.0; + } + if (nrt + 1 < p) { + e[nrt] = A[nrt][p - 1]; + } + e[p - 1] = 0.0; + if (wantu) { + for (var _j3 = nct; _j3 < nu; _j3++) { + for (var _i11 = 0; _i11 < this.m; _i11++) { + this.U[_i11][_j3] = 0.0; + } + ; + this.U[_j3][_j3] = 1.0; + }; + for (var _k = nct - 1; _k >= 0; _k--) { + if (this.s[_k] !== 0.0) { + for (var _j4 = _k + 1; _j4 < nu; _j4++) { + var _t2 = 0; + for (var _i12 = _k; _i12 < this.m; _i12++) { + _t2 += this.U[_i12][_k] * this.U[_i12][_j4]; + }; + _t2 = -_t2 / this.U[_k][_k]; + for (var _i13 = _k; _i13 < this.m; _i13++) { + this.U[_i13][_j4] += _t2 * this.U[_i13][_k]; + }; + }; + for (var _i14 = _k; _i14 < this.m; _i14++) { + this.U[_i14][_k] = -this.U[_i14][_k]; + }; + this.U[_k][_k] = 1.0 + this.U[_k][_k]; + for (var _i15 = 0; _i15 < _k - 1; _i15++) { + this.U[_i15][_k] = 0.0; + }; + } else { + for (var _i16 = 0; _i16 < this.m; _i16++) { + this.U[_i16][_k] = 0.0; + }; + this.U[_k][_k] = 1.0; + } + }; + } + if (wantv) { + for (var _k2 = this.n - 1; _k2 >= 0; _k2--) { + if (function (lhs, rhs) { + return lhs && rhs; + }(_k2 < nrt, e[_k2] !== 0.0)) { + for (var _j5 = _k2 + 1; _j5 < nu; _j5++) { + var _t3 = 0; + for (var _i17 = _k2 + 1; _i17 < this.n; _i17++) { + _t3 += this.V[_i17][_k2] * this.V[_i17][_j5]; + }; + _t3 = -_t3 / this.V[_k2 + 1][_k2]; + for (var _i18 = _k2 + 1; _i18 < this.n; _i18++) { + this.V[_i18][_j5] += _t3 * this.V[_i18][_k2]; + }; + }; + } + for (var _i19 = 0; _i19 < this.n; _i19++) { + this.V[_i19][_k2] = 0.0; + }; + this.V[_k2][_k2] = 1.0; + }; + } + var pp = p - 1; + var iter = 0; + var eps = Math.pow(2.0, -52.0); + var tiny = Math.pow(2.0, -966.0); + while (p > 0) { + var _k3 = void 0; + var kase = void 0; + for (_k3 = p - 2; _k3 >= -1; _k3--) { + if (_k3 === -1) { + break; + } + if (Math.abs(e[_k3]) <= tiny + eps * (Math.abs(this.s[_k3]) + Math.abs(this.s[_k3 + 1]))) { + e[_k3] = 0.0; + break; + } + }; + if (_k3 === p - 2) { + kase = 4; + } else { + var ks = void 0; + for (ks = p - 1; ks >= _k3; ks--) { + if (ks === _k3) { + break; + } + var _t4 = (ks !== p ? Math.abs(e[ks]) : 0.0) + (ks !== _k3 + 1 ? Math.abs(e[ks - 1]) : 0.0); + if (Math.abs(this.s[ks]) <= tiny + eps * _t4) { + this.s[ks] = 0.0; + break; + } + }; + if (ks === _k3) { + kase = 3; + } else if (ks === p - 1) { + kase = 1; + } else { + kase = 2; + _k3 = ks; + } + } + _k3++; + switch (kase) { + case 1: + { + var f = e[p - 2]; + e[p - 2] = 0.0; + for (var _j6 = p - 2; _j6 >= _k3; _j6--) { + var _t5 = SVD.hypot(this.s[_j6], f); + var cs = this.s[_j6] / _t5; + var sn = f / _t5; + this.s[_j6] = _t5; + if (_j6 !== _k3) { + f = -sn * e[_j6 - 1]; + e[_j6 - 1] = cs * e[_j6 - 1]; + } + if (wantv) { + for (var _i20 = 0; _i20 < this.n; _i20++) { + _t5 = cs * this.V[_i20][_j6] + sn * this.V[_i20][p - 1]; + this.V[_i20][p - 1] = -sn * this.V[_i20][_j6] + cs * this.V[_i20][p - 1]; + this.V[_i20][_j6] = _t5; + }; + } + }; + }; + break; + case 2: + { + var _f = e[_k3 - 1]; + e[_k3 - 1] = 0.0; + for (var _j7 = _k3; _j7 < p; _j7++) { + var _t6 = SVD.hypot(this.s[_j7], _f); + var _cs = this.s[_j7] / _t6; + var _sn = _f / _t6; + this.s[_j7] = _t6; + _f = -_sn * e[_j7]; + e[_j7] = _cs * e[_j7]; + if (wantu) { + for (var _i21 = 0; _i21 < this.m; _i21++) { + _t6 = _cs * this.U[_i21][_j7] + _sn * this.U[_i21][_k3 - 1]; + this.U[_i21][_k3 - 1] = -_sn * this.U[_i21][_j7] + _cs * this.U[_i21][_k3 - 1]; + this.U[_i21][_j7] = _t6; + }; + } + }; + }; + break; + case 3: + { + var scale = Math.max(Math.max(Math.max(Math.max(Math.abs(this.s[p - 1]), Math.abs(this.s[p - 2])), Math.abs(e[p - 2])), Math.abs(this.s[_k3])), Math.abs(e[_k3])); + var sp = this.s[p - 1] / scale; + var spm1 = this.s[p - 2] / scale; + var epm1 = e[p - 2] / scale; + var sk = this.s[_k3] / scale; + var ek = e[_k3] / scale; + var b = ((spm1 + sp) * (spm1 - sp) + epm1 * epm1) / 2.0; + var c = sp * epm1 * (sp * epm1); + var shift = 0.0; + if (function (lhs, rhs) { + return lhs || rhs; + }(b !== 0.0, c !== 0.0)) { + shift = Math.sqrt(b * b + c); + if (b < 0.0) { + shift = -shift; + } + shift = c / (b + shift); + } + var _f2 = (sk + sp) * (sk - sp) + shift; + var g = sk * ek; + for (var _j8 = _k3; _j8 < p - 1; _j8++) { + var _t7 = SVD.hypot(_f2, g); + var _cs2 = _f2 / _t7; + var _sn2 = g / _t7; + if (_j8 !== _k3) { + e[_j8 - 1] = _t7; + } + _f2 = _cs2 * this.s[_j8] + _sn2 * e[_j8]; + e[_j8] = _cs2 * e[_j8] - _sn2 * this.s[_j8]; + g = _sn2 * this.s[_j8 + 1]; + this.s[_j8 + 1] = _cs2 * this.s[_j8 + 1]; + if (wantv) { + for (var _i22 = 0; _i22 < this.n; _i22++) { + _t7 = _cs2 * this.V[_i22][_j8] + _sn2 * this.V[_i22][_j8 + 1]; + this.V[_i22][_j8 + 1] = -_sn2 * this.V[_i22][_j8] + _cs2 * this.V[_i22][_j8 + 1]; + this.V[_i22][_j8] = _t7; + }; + } + _t7 = SVD.hypot(_f2, g); + _cs2 = _f2 / _t7; + _sn2 = g / _t7; + this.s[_j8] = _t7; + _f2 = _cs2 * e[_j8] + _sn2 * this.s[_j8 + 1]; + this.s[_j8 + 1] = -_sn2 * e[_j8] + _cs2 * this.s[_j8 + 1]; + g = _sn2 * e[_j8 + 1]; + e[_j8 + 1] = _cs2 * e[_j8 + 1]; + if (wantu && _j8 < this.m - 1) { + for (var _i23 = 0; _i23 < this.m; _i23++) { + _t7 = _cs2 * this.U[_i23][_j8] + _sn2 * this.U[_i23][_j8 + 1]; + this.U[_i23][_j8 + 1] = -_sn2 * this.U[_i23][_j8] + _cs2 * this.U[_i23][_j8 + 1]; + this.U[_i23][_j8] = _t7; + }; + } + }; + e[p - 2] = _f2; + iter = iter + 1; + }; + break; + case 4: + { + if (this.s[_k3] <= 0.0) { + this.s[_k3] = this.s[_k3] < 0.0 ? -this.s[_k3] : 0.0; + if (wantv) { + for (var _i24 = 0; _i24 <= pp; _i24++) { + this.V[_i24][_k3] = -this.V[_i24][_k3]; + }; + } + } + while (_k3 < pp) { + if (this.s[_k3] >= this.s[_k3 + 1]) { + break; + } + var _t8 = this.s[_k3]; + this.s[_k3] = this.s[_k3 + 1]; + this.s[_k3 + 1] = _t8; + if (wantv && _k3 < this.n - 1) { + for (var _i25 = 0; _i25 < this.n; _i25++) { + _t8 = this.V[_i25][_k3 + 1]; + this.V[_i25][_k3 + 1] = this.V[_i25][_k3]; + this.V[_i25][_k3] = _t8; + }; + } + if (wantu && _k3 < this.m - 1) { + for (var _i26 = 0; _i26 < this.m; _i26++) { + _t8 = this.U[_i26][_k3 + 1]; + this.U[_i26][_k3 + 1] = this.U[_i26][_k3]; + this.U[_i26][_k3] = _t8; + }; + } + _k3++; + }; + iter = 0; + p--; + }; + break; + } + }; + var result = { U: this.U, V: this.V, S: this.s }; + return result; +}; + +// sqrt(a^2 + b^2) without under/overflow. +SVD.hypot = function (a, b) { + var r = void 0; + if (Math.abs(a) > Math.abs(b)) { + r = b / a; + r = Math.abs(a) * Math.sqrt(1 + r * r); + } else if (b != 0) { + r = a / b; + r = Math.abs(b) * Math.sqrt(1 + r * r); + } else { + r = 0.0; + } + return r; +}; + +module.exports = SVD; + +/***/ }), +/* 27 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +/** + * Needleman-Wunsch algorithm is an procedure to compute the optimal global alignment of two string + * sequences by S.B.Needleman and C.D.Wunsch (1970). + * + * Aside from the inputs, you can assign the scores for, + * - Match: The two characters at the current index are same. + * - Mismatch: The two characters at the current index are different. + * - Insertion/Deletion(gaps): The best alignment involves one letter aligning to a gap in the other string. + */ + +var NeedlemanWunsch = function () { + function NeedlemanWunsch(sequence1, sequence2) { + var match_score = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1; + var mismatch_penalty = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : -1; + var gap_penalty = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : -1; + + _classCallCheck(this, NeedlemanWunsch); + + this.sequence1 = sequence1; + this.sequence2 = sequence2; + this.match_score = match_score; + this.mismatch_penalty = mismatch_penalty; + this.gap_penalty = gap_penalty; + + // Just the remove redundancy + this.iMax = sequence1.length + 1; + this.jMax = sequence2.length + 1; + + // Grid matrix of scores + this.grid = new Array(this.iMax); + for (var i = 0; i < this.iMax; i++) { + this.grid[i] = new Array(this.jMax); + + for (var j = 0; j < this.jMax; j++) { + this.grid[i][j] = 0; + } + } + + // Traceback matrix (2D array, each cell is an array of boolean values for [`Diag`, `Up`, `Left`] positions) + this.tracebackGrid = new Array(this.iMax); + for (var _i = 0; _i < this.iMax; _i++) { + this.tracebackGrid[_i] = new Array(this.jMax); + + for (var _j = 0; _j < this.jMax; _j++) { + this.tracebackGrid[_i][_j] = [null, null, null]; + } + } + + // The aligned sequences (return multiple possibilities) + this.alignments = []; + + // Final alignment score + this.score = -1; + + // Calculate scores and tracebacks + this.computeGrids(); + } + + _createClass(NeedlemanWunsch, [{ + key: "getScore", + value: function getScore() { + return this.score; + } + }, { + key: "getAlignments", + value: function getAlignments() { + return this.alignments; + } + + // Main dynamic programming procedure + + }, { + key: "computeGrids", + value: function computeGrids() { + // Fill in the first row + for (var j = 1; j < this.jMax; j++) { + this.grid[0][j] = this.grid[0][j - 1] + this.gap_penalty; + this.tracebackGrid[0][j] = [false, false, true]; + } + + // Fill in the first column + for (var i = 1; i < this.iMax; i++) { + this.grid[i][0] = this.grid[i - 1][0] + this.gap_penalty; + this.tracebackGrid[i][0] = [false, true, false]; + } + + // Fill the rest of the grid + for (var _i2 = 1; _i2 < this.iMax; _i2++) { + for (var _j2 = 1; _j2 < this.jMax; _j2++) { + // Find the max score(s) among [`Diag`, `Up`, `Left`] + var diag = void 0; + if (this.sequence1[_i2 - 1] === this.sequence2[_j2 - 1]) diag = this.grid[_i2 - 1][_j2 - 1] + this.match_score;else diag = this.grid[_i2 - 1][_j2 - 1] + this.mismatch_penalty; + + var up = this.grid[_i2 - 1][_j2] + this.gap_penalty; + var left = this.grid[_i2][_j2 - 1] + this.gap_penalty; + + // If there exists multiple max values, capture them for multiple paths + var maxOf = [diag, up, left]; + var indices = this.arrayAllMaxIndexes(maxOf); + + // Update Grids + this.grid[_i2][_j2] = maxOf[indices[0]]; + this.tracebackGrid[_i2][_j2] = [indices.includes(0), indices.includes(1), indices.includes(2)]; + } + } + + // Update alignment score + this.score = this.grid[this.iMax - 1][this.jMax - 1]; + } + + // Gets all possible valid sequence combinations + + }, { + key: "alignmentTraceback", + value: function alignmentTraceback() { + var inProcessAlignments = []; + + inProcessAlignments.push({ pos: [this.sequence1.length, this.sequence2.length], + seq1: "", + seq2: "" + }); + + while (inProcessAlignments[0]) { + var current = inProcessAlignments[0]; + var directions = this.tracebackGrid[current.pos[0]][current.pos[1]]; + + if (directions[0]) { + inProcessAlignments.push({ pos: [current.pos[0] - 1, current.pos[1] - 1], + seq1: this.sequence1[current.pos[0] - 1] + current.seq1, + seq2: this.sequence2[current.pos[1] - 1] + current.seq2 + }); + } + if (directions[1]) { + inProcessAlignments.push({ pos: [current.pos[0] - 1, current.pos[1]], + seq1: this.sequence1[current.pos[0] - 1] + current.seq1, + seq2: '-' + current.seq2 + }); + } + if (directions[2]) { + inProcessAlignments.push({ pos: [current.pos[0], current.pos[1] - 1], + seq1: '-' + current.seq1, + seq2: this.sequence2[current.pos[1] - 1] + current.seq2 + }); + } + + if (current.pos[0] === 0 && current.pos[1] === 0) this.alignments.push({ sequence1: current.seq1, + sequence2: current.seq2 + }); + + inProcessAlignments.shift(); + } + + return this.alignments; + } + + // Helper Functions + + }, { + key: "getAllIndexes", + value: function getAllIndexes(arr, val) { + var indexes = [], + i = -1; + while ((i = arr.indexOf(val, i + 1)) !== -1) { + indexes.push(i); + } + return indexes; + } + }, { + key: "arrayAllMaxIndexes", + value: function arrayAllMaxIndexes(array) { + return this.getAllIndexes(array, Math.max.apply(null, array)); + } + }]); + + return NeedlemanWunsch; +}(); + +module.exports = NeedlemanWunsch; + +/***/ }), +/* 28 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var layoutBase = function layoutBase() { + return; +}; + +layoutBase.FDLayout = __webpack_require__(18); +layoutBase.FDLayoutConstants = __webpack_require__(4); +layoutBase.FDLayoutEdge = __webpack_require__(19); +layoutBase.FDLayoutNode = __webpack_require__(20); +layoutBase.DimensionD = __webpack_require__(21); +layoutBase.HashMap = __webpack_require__(22); +layoutBase.HashSet = __webpack_require__(23); +layoutBase.IGeometry = __webpack_require__(8); +layoutBase.IMath = __webpack_require__(9); +layoutBase.Integer = __webpack_require__(10); +layoutBase.Point = __webpack_require__(12); +layoutBase.PointD = __webpack_require__(5); +layoutBase.RandomSeed = __webpack_require__(16); +layoutBase.RectangleD = __webpack_require__(13); +layoutBase.Transform = __webpack_require__(17); +layoutBase.UniqueIDGeneretor = __webpack_require__(14); +layoutBase.Quicksort = __webpack_require__(25); +layoutBase.LinkedList = __webpack_require__(11); +layoutBase.LGraphObject = __webpack_require__(2); +layoutBase.LGraph = __webpack_require__(6); +layoutBase.LEdge = __webpack_require__(1); +layoutBase.LGraphManager = __webpack_require__(7); +layoutBase.LNode = __webpack_require__(3); +layoutBase.Layout = __webpack_require__(15); +layoutBase.LayoutConstants = __webpack_require__(0); +layoutBase.NeedlemanWunsch = __webpack_require__(27); +layoutBase.Matrix = __webpack_require__(24); +layoutBase.SVD = __webpack_require__(26); + +module.exports = layoutBase; + +/***/ }), +/* 29 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +function Emitter() { + this.listeners = []; +} + +var p = Emitter.prototype; + +p.addListener = function (event, callback) { + this.listeners.push({ + event: event, + callback: callback + }); +}; + +p.removeListener = function (event, callback) { + for (var i = this.listeners.length; i >= 0; i--) { + var l = this.listeners[i]; + + if (l.event === event && l.callback === callback) { + this.listeners.splice(i, 1); + } + } +}; + +p.emit = function (event, data) { + for (var i = 0; i < this.listeners.length; i++) { + var l = this.listeners[i]; + + if (event === l.event) { + l.callback(data); + } + } +}; + +module.exports = Emitter; + +/***/ }) +/******/ ]); +}); \ No newline at end of file diff --git a/site/site/public/js/reinit-handlers.js b/site/site/public/js/reinit-handlers.js new file mode 100644 index 0000000..3c7ba1f --- /dev/null +++ b/site/site/public/js/reinit-handlers.js @@ -0,0 +1,114 @@ +/** + * reinit-handlers.js — registers the standard set of HTMX swap re-initialisers. + * + * Loaded AFTER htmx-reinit.js and after the underlying libraries (hljs, + * cytoscape) so the handler bodies can call them directly. Each handler + * scopes its DOM queries to the swap target — passing an Element to + * querySelectorAll only searches that subtree, so handlers are O(swap) + * rather than O(document) on every swap. + */ +(function () { + 'use strict'; + + if (!window.RusteloReinit) { + console.error('[reinit-handlers] htmx-reinit.js must load before this script'); + return; + } + + // --- highlight.js ------------------------------------------------------- + // Highlight every
     in the swapped region that isn't already
    +  // decorated. hljs marks processed blocks by adding the .hljs class, which
    +  // makes the selector self-cleaning on re-runs.
    +  window.RusteloReinit.register('highlight', function (scope) {
    +    if (typeof window.hljs === 'undefined' || !window.hljs.highlightElement) {
    +      return;
    +    }
    +    scope.querySelectorAll('pre code:not(.hljs)').forEach(function (block) {
    +      window.hljs.highlightElement(block);
    +    });
    +  });
    +
    +  // --- theme-init ---------------------------------------------------------
    +  // Normalise the theme state on  from the cookie after every swap.
    +  // This is the single authority that reconciles the theme-toggle's inline
    +  // script (which adds `.dark` but never clears the stale `.light`, and the
    +  // reverse) into a consistent state: mutually-exclusive class AND the
    +  // `data-theme` attribute. The attribute is what DaisyUI keys its base
    +  // tokens on (`:root,[data-theme]{color:hsl(var(--bc))}`); without it the
    +  // light `--bc` never resolves and low-opacity text renders invisible.
    +  // Mirrors theme-init.js so first paint and post-swap agree. Idempotent.
    +  window.RusteloReinit.register('theme', function (_scope) {
    +    const matches = document.cookie.match(/theme=([^;]+)/);
    +    const theme = matches ? matches[1] : 'dark';
    +    const resolved = theme === 'dark' ||
    +      (theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches)
    +        ? 'dark'
    +        : 'light';
    +    const html = document.documentElement;
    +    html.classList.toggle('dark', resolved === 'dark');
    +    html.classList.toggle('light', resolved === 'light');
    +    html.dataset.theme = resolved;
    +  });
    +
    +  // --- tag-filters --------------------------------------------------------
    +  // Re-arm the filter pill click handlers in the swapped region. The
    +  // legacy script set window.tagFiltersInitialized = true and bailed on
    +  // subsequent runs; bypass that flag by re-binding only pills that don't
    +  // yet carry our data-reinit attribute.
    +  window.RusteloReinit.register('tag-filters', function (scope) {
    +    if (typeof window.filterPostsByTag !== 'function') {
    +      return;
    +    }
    +    scope.querySelectorAll('[data-tag-pill]:not([data-tag-reinit])').forEach(function (pill) {
    +      pill.setAttribute('data-tag-reinit', '1');
    +      pill.addEventListener('click', function (ev) {
    +        ev.preventDefault();
    +        const tag = pill.getAttribute('data-tag');
    +        if (tag) {
    +          window.filterPostsByTag(tag);
    +        }
    +      });
    +    });
    +  });
    +
    +  // --- scroll-to-top -------------------------------------------------------
    +  // Wires the footer "Scroll to top" button emitted by UnifiedFooter (SSR).
    +  // The Leptos on:click handler is WASM-only and compiles to nothing in SSR;
    +  // this provides the equivalent for the HTMX profile via event delegation.
    +  // Runs once: the button lives in the footer which is never swapped.
    +  (function () {
    +    if (window.__rusteloScrollToTopWired) {
    +      return;
    +    }
    +    window.__rusteloScrollToTopWired = true;
    +    document.addEventListener('click', function (ev) {
    +      var btn = ev.target && ev.target.closest('button[aria-label="Scroll to top"]');
    +      if (btn) {
    +        ev.preventDefault();
    +        window.scrollTo({ top: 0, behavior: 'smooth' });
    +      }
    +    });
    +  })();
    +
    +  // --- content-graph ------------------------------------------------------
    +  // ContentGraph.render is idempotent against the same container id (it
    +  // tears down the previous instance internally). Only fire when the
    +  // swap actually contains a graph container.
    +  window.RusteloReinit.register('content-graph', function (scope) {
    +    if (!window.ContentGraph || typeof window.ContentGraph.render !== 'function') {
    +      return;
    +    }
    +    scope.querySelectorAll('[data-content-graph]').forEach(function (el) {
    +      const containerId = el.getAttribute('id');
    +      const focusId = el.getAttribute('data-focus-id') || null;
    +      const theme = document.documentElement.classList.contains('dark') ? 'dark' : 'light';
    +      if (containerId) {
    +        try {
    +          window.ContentGraph.render(containerId, focusId, theme);
    +        } catch (err) {
    +          console.error('[reinit:content-graph] render failed', err);
    +        }
    +      }
    +    });
    +  });
    +})();
    diff --git a/site/site/public/js/tag-filters.js b/site/site/public/js/tag-filters.js
    new file mode 100644
    index 0000000..fb9083d
    --- /dev/null
    +++ b/site/site/public/js/tag-filters.js
    @@ -0,0 +1,181 @@
    +/**
    + * Tag Filters - Client-side tag filtering for blog posts
    + * Filters posts within the current page without navigation
    + */
    +
    +(function() {
    +    'use strict';
    +
    +    // Prevent double initialization
    +    if (window.tagFiltersInitialized) {
    +        return;
    +    }
    +
    +    let activeTagFilters = new Set();
    +
    +    // Global function to filter posts by tag (supports multi-selection)
    +    window.filterPostsByTag = function(tagName) {
    +        console.log(`🏷️ Filtering posts by tag: ${tagName}`);
    +        
    +        if (activeTagFilters.has(tagName)) {
    +            // If tag is already selected, remove it
    +            activeTagFilters.delete(tagName);
    +            console.log(`🏷️ Removed tag "${tagName}" from selection`);
    +        } else {
    +            // Add tag to selection
    +            activeTagFilters.add(tagName);
    +            console.log(`🏷️ Added tag "${tagName}" to selection`);
    +        }
    +        
    +        if (activeTagFilters.size === 0) {
    +            showAllPosts();
    +            updateTagButtonStates(activeTagFilters);
    +        } else {
    +            filterPostsByTags(activeTagFilters);
    +            updateTagButtonStates(activeTagFilters);
    +        }
    +    };
    +
    +    // Global function to clear all tag filters
    +    window.clearAllTagFilters = function() {
    +        console.log('🏷️ Clearing all tag filters');
    +        activeTagFilters.clear();
    +        showAllPosts();
    +        updateTagButtonStates(activeTagFilters);
    +        updateShowAllButton(true);
    +    };
    +
    +    function filterPostsByTags(selectedTags) {
    +        const postCards = document.querySelectorAll('article');
    +        let visibleCount = 0;
    +        const tagArray = Array.from(selectedTags);
    +
    +        postCards.forEach(card => {
    +            const tagElements = card.querySelectorAll('.ds-badge-ghost');
    +            const postTags = Array.from(tagElements).map(el => el.textContent.trim().toLowerCase());
    +            
    +            // Check if post has ALL selected tags (AND logic)
    +            // Change to hasAnyTag for OR logic if preferred
    +            const hasAllTags = tagArray.every(selectedTag => 
    +                postTags.includes(selectedTag.toLowerCase())
    +            );
    +
    +            if (hasAllTags) {
    +                card.style.display = '';
    +                card.classList.remove('filter-hidden');
    +                visibleCount++;
    +            } else {
    +                card.style.display = 'none';
    +                card.classList.add('filter-hidden');
    +            }
    +        });
    +
    +        const tagList = tagArray.join('", "');
    +        console.log(`🏷️ Multi-tag filter result: ${visibleCount} posts visible with tags ["${tagList}"]`);
    +        
    +        // Show a message if no posts found
    +        const filterText = tagArray.length === 1 
    +            ? `tagged with "${tagArray[0]}"` 
    +            : `tagged with all of: "${tagList}"`;
    +        showFilterMessage(visibleCount, filterText);
    +        updateShowAllButton(false);
    +    }
    +
    +    function showAllPosts() {
    +        const postCards = document.querySelectorAll('article');
    +        postCards.forEach(card => {
    +            card.style.display = '';
    +            card.classList.remove('filter-hidden');
    +        });
    +        
    +        hideFilterMessage();
    +        updateShowAllButton(true);
    +        console.log('🏷️ Tag filter cleared: all posts visible');
    +    }
    +
    +    function updateTagButtonStates(activeTagFilters) {
    +        const tagButtons = document.querySelectorAll('button[data-filter-type="tag"]');
    +        
    +        tagButtons.forEach(button => {
    +            const tagValue = button.getAttribute('data-filter-value');
    +            
    +            if (activeTagFilters.has(tagValue)) {
    +                // Active tag button styling
    +                button.classList.remove('ds-btn-outline');
    +                button.classList.add('ds-btn-primary');
    +                button.classList.add('tag-filter-active');
    +            } else {
    +                // Inactive tag button styling
    +                button.classList.add('ds-btn-outline');
    +                button.classList.remove('ds-btn-primary');
    +                button.classList.remove('tag-filter-active');
    +            }
    +        });
    +    }
    +
    +    function updateShowAllButton(isActive) {
    +        const showAllButton = document.querySelector('button[data-filter-type="show-all"]');
    +        if (showAllButton) {
    +            if (isActive) {
    +                // Active "Show All" button styling
    +                showAllButton.classList.remove('ds-btn-outline');
    +                showAllButton.classList.add('ds-btn-primary');
    +                showAllButton.classList.add('show-all-active');
    +            } else {
    +                // Inactive "Show All" button styling
    +                showAllButton.classList.add('ds-btn-outline');
    +                showAllButton.classList.remove('ds-btn-primary');
    +                showAllButton.classList.remove('show-all-active');
    +            }
    +        }
    +    }
    +
    +    function showFilterMessage(count, filterText) {
    +        // Remove any existing message
    +        hideFilterMessage();
    +        
    +        if (count === 0) {
    +            const message = document.createElement('div');
    +            message.className = 'tag-filter-message';
    +            message.innerHTML = `
    +                
    +

    No posts found ${filterText}.

    + +
    + `; + + // Insert after the filter container + const filterContainer = document.querySelector('.filter-container'); + if (filterContainer) { + filterContainer.parentNode.insertBefore(message, filterContainer.nextSibling); + } + } + } + + function hideFilterMessage() { + const existing = document.querySelector('.tag-filter-message'); + if (existing) { + existing.remove(); + } + } + + // Initialize tag filtering when DOM is ready + function initializeTagFilters() { + console.log('🏷️ Tag filters initialized'); + } + + // Initialize when DOM is ready + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initializeTagFilters); + } else { + initializeTagFilters(); + } + + // Also try after hydration + setTimeout(initializeTagFilters, 500); + + window.tagFiltersInitialized = true; + +})(); \ No newline at end of file diff --git a/site/site/public/js/tagline-rotator.js b/site/site/public/js/tagline-rotator.js new file mode 100644 index 0000000..55b2f57 --- /dev/null +++ b/site/site/public/js/tagline-rotator.js @@ -0,0 +1,246 @@ +/* tagline-rotator.js — rotate ontoref definition-phrases by audience + focus. + * + * Source of truth: /r/taglines.json, projected by gen-taglines.nu from + * .ontoref/positioning/taglines.ncl (drift-checked at export). This file never + * carries phrases — only the rotation mechanism. + * + * Mount: + * + * Sure-footed, and light on your feet. + * + * + * Effect: drives typed.js (typewriter) when window.Typed is present; native fade + * otherwise; static first-phrase under prefers-reduced-motion. Hybrid driver: the + * home mount starts on data-audience (default "all"); any element carrying + * [data-tagline-audience=""] switches the (unlocked) rotators to that queue on + * click — reach-vs-qualification: a routing gancho, the "all" queue is curated, not + * the union. A mount with [data-lock-audience] ignores door clicks (door/about pages). + * Language: data-lang fixed → → localStorage 'ontoref-lang' → 'en'; + * follows .lang-btn / [data-lang-switch] clicks and cross-tab storage changes. + */ +(function () { + "use strict"; + + var REDUCED = + window.matchMedia && + window.matchMedia("(prefers-reduced-motion: reduce)").matches; + + function detectLang(el) { + var fixed = el.getAttribute("data-lang"); + if (fixed === "es" || fixed === "en") return fixed; + var h = (document.documentElement.lang || "").slice(0, 2).toLowerCase(); + if (h === "es" || h === "en") return h; + try { + var s = localStorage.getItem("ontoref-lang"); + if (s === "es" || s === "en") return s; + } catch (e) {} + return "en"; + } + + function focusLabel(focus, labels, lang) { + if (!focus) return ""; + var m = labels && labels[focus]; + if (m && m[lang]) return m[lang]; + return focus.replace(/^diff-/, "").replace(/-/g, " "); // slug fallback + } + + function Rotator(el, data) { + this.el = el; + this.data = (data && data.by_audience) || {}; + this.byTier = (data && data.by_tier) || {}; + this.isTier = el.hasAttribute("data-tier-rotator"); + this.focusLabels = (data && data.focus_labels) || {}; + this.audience = el.getAttribute("data-audience") || "all"; + this.lang = detectLang(el); + var sel = el.getAttribute("data-focus-target"); + this.focusEl = sel ? document.querySelector(sel) : null; + this.typed = null; + this.timer = null; + this.idx = 0; + this.queue = []; + this.rebuild(); + } + + Rotator.prototype.phrases = function () { + // Tier rotator (data-tier-rotator): a parallel axis — cycle the per-tier lines + // (0 → 1 → 2) from by_tier, ignoring the audience queue. Door clicks never + // switch it (mount carries data-lock-audience). + if (this.isTier) { + var t = this.byTier; + return [].concat(t["0"] || [], t["1"] || [], t["2"] || []); + } + var q = this.data[this.audience]; + if (!q || !q.length) q = this.data.all || []; + return q; + }; + + Rotator.prototype.text = function (i) { + if (!this.queue.length) return ""; + var p = this.queue[i % this.queue.length]; + return p ? p[this.lang] || p.en || "" : ""; + }; + + Rotator.prototype.renderFocus = function (i) { + if (!this.focusEl || !this.queue.length) return; + var p = this.queue[i % this.queue.length]; + this.focusEl.textContent = p ? focusLabel(p.focus, this.focusLabels, this.lang) : ""; + }; + + Rotator.prototype.render = function (i) { + this.el.textContent = this.text(i); + this.renderFocus(i); + }; + + Rotator.prototype.rebuild = function () { + this.stop(); + this.queue = this.phrases(); + this.idx = 0; + if (!this.queue.length) { + this.el.textContent = ""; + return; + } + if (REDUCED) { + this.render(0); // honour reduced-motion: show one phrase, never animate + return; + } + if (window.Typed) this.startTyped(); + else this.startFade(); + }; + + Rotator.prototype.startTyped = function () { + var self = this; + var strings = this.queue.map(function (_, i) { + // escape typed.js control chars: ^ (pause), ` (html), ~ (smart-backspace) + return self.text(i).replace(/([\^`~])/g, "\\$1"); + }); + this.renderFocus(0); + this.typed = new window.Typed(this.el, { + strings: strings, + typeSpeed: 38, + backSpeed: 16, + backDelay: 2400, + startDelay: 250, + smartBackspace: true, + loop: true, + cursorChar: "▌", + preStringTyped: function (i) { + self.renderFocus(i); + }, + }); + }; + + Rotator.prototype.startFade = function () { + var self = this; + this.render(0); + this.el.classList.add("tagline-rot", "is-in"); + this.timer = setInterval(function () { + self.el.classList.remove("is-in"); + setTimeout(function () { + self.idx = (self.idx + 1) % self.queue.length; + self.render(self.idx); + self.el.classList.add("is-in"); + }, 350); + }, 3800); + }; + + Rotator.prototype.stop = function () { + if (this.typed) { + this.typed.destroy(); + this.typed = null; + } + if (this.timer) { + clearInterval(this.timer); + this.timer = null; + } + }; + + Rotator.prototype.setAudience = function (key) { + if (!key || key === this.audience) return; + this.audience = key; + this.rebuild(); + }; + + Rotator.prototype.setLang = function (lang) { + if ((lang !== "es" && lang !== "en") || lang === this.lang) return; + this.lang = lang; + this.rebuild(); + }; + + function init() { + var mounts = [].slice.call( + document.querySelectorAll("[data-tagline-rotator]"), + ); + if (!mounts.length) return; + var src = mounts[0].getAttribute("data-src") || "/r/taglines.json"; + + fetch(src, { credentials: "same-origin" }) + .then(function (r) { + if (!r.ok) throw new Error("taglines " + r.status); + return r.json(); + }) + .then(function (data) { + var rotators = mounts.map(function (el) { + return new Rotator(el, data); + }); + window.__taglineRotators = rotators; + + // Hybrid driver — door affordances switch the unlocked rotators' audience. + document.addEventListener("click", function (ev) { + var t = + ev.target.closest && ev.target.closest("[data-tagline-audience]"); + if (!t) return; + var key = t.getAttribute("data-tagline-audience"); + var group = t.parentNode; + if (group) + [].forEach.call( + group.querySelectorAll("[data-tagline-audience]"), + function (b) { + b.classList.toggle("is-active", b === t); + }, + ); + // reveal the matching door panel; data-door="all" hides every panel + var door = t.getAttribute("data-door"); + if (door !== null) + [].forEach.call( + document.querySelectorAll("[data-tagline-panel]"), + function (p) { + var match = p.getAttribute("data-tagline-panel") === door; + p.classList.toggle("is-open", match); // class wins over any [hidden] override + p.hidden = !match; + }, + ); + rotators.forEach(function (rt) { + if (!rt.el.hasAttribute("data-lock-audience")) rt.setAudience(key); + }); + }); + + // Language — follow the web lang toggle and cross-tab changes. + document.addEventListener("click", function (ev) { + var b = + ev.target.closest && + ev.target.closest(".lang-btn, [data-lang-switch]"); + if (!b) return; + var lang = + b.getAttribute("data-lang") || b.getAttribute("data-lang-switch"); + rotators.forEach(function (rt) { + rt.setLang(lang); + }); + }); + window.addEventListener("storage", function (ev) { + if (ev.key === "ontoref-lang" && ev.newValue) + rotators.forEach(function (rt) { + rt.setLang(ev.newValue); + }); + }); + }) + .catch(function (e) { + // leave the static fallback text in each mount untouched + if (window.console) console.warn("[tagline-rotator]", e.message); + }); + } + + if (document.readyState === "loading") + document.addEventListener("DOMContentLoaded", init); + else init(); +})(); diff --git a/site/site/public/js/theme-init.js b/site/site/public/js/theme-init.js new file mode 100644 index 0000000..98ee972 --- /dev/null +++ b/site/site/public/js/theme-init.js @@ -0,0 +1,21 @@ +// Theme initialization script to prevent flash +(function() { + const getThemeFromCookie = () => { + const matches = document.cookie.match(/theme=([^;]+)/); + return matches ? matches[1] : null; + }; + const pref = getThemeFromCookie() || 'dark'; + const resolved = + pref === 'dark' || (pref === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches) + ? 'dark' + : 'light'; + const html = document.documentElement; + // Class drives Tailwind `.dark` utilities and the custom `.dark {}` token blocks. + html.classList.toggle('dark', resolved === 'dark'); + html.classList.toggle('light', resolved === 'light'); + // Attribute drives DaisyUI base tokens — `:root,[data-theme]{color:hsl(var(--bc))}` + // only resolves the light `--bc` under `[data-theme=light]`. Without this the first + // paint falls back to the dark-default `--bc` (light grey) on a light background, + // making low-opacity text (tag chips) invisible until a theme toggle sets it. + html.dataset.theme = resolved; +})(); diff --git a/site/site/public/js/theme-init.min.js b/site/site/public/js/theme-init.min.js new file mode 100644 index 0000000..f021197 --- /dev/null +++ b/site/site/public/js/theme-init.min.js @@ -0,0 +1 @@ +(function(){const getThemeFromCookie=()=>{const matches=document.cookie.match(/theme=([^;]+)/);return matches?matches[1]:null;};const pref=getThemeFromCookie()||'dark';const resolved=pref==='dark'||(pref==='system'&&window.matchMedia('(prefers-color-scheme:dark)').matches)?'dark':'light';const html=document.documentElement;html.classList.toggle('dark',resolved==='dark');html.classList.toggle('light',resolved==='light');html.dataset.theme=resolved;})(); \ No newline at end of file diff --git a/site/site/public/js/typed.umd.js b/site/site/public/js/typed.umd.js new file mode 100644 index 0000000..0a4e41d --- /dev/null +++ b/site/site/public/js/typed.umd.js @@ -0,0 +1,3 @@ +!function(t,s){"object"==typeof exports&&"undefined"!=typeof module?module.exports=s():"function"==typeof define&&define.amd?define(s):(t||self).Typed=s()}(this,function(){function t(){return t=Object.assign?Object.assign.bind():function(t){for(var s=1;s0&&(e.strPos=e.currentElContent.length-1,e.strings.unshift(e.currentElContent)),e.sequence=[],e.strings)e.sequence[u]=u;e.arrayPos=0,e.stopNum=0,e.loop=e.options.loop,e.loopCount=e.options.loopCount,e.curLoop=0,e.shuffle=e.options.shuffle,e.pause={status:!1,typewrite:!0,curString:"",curStrPos:0},e.typingComplete=!1,e.autoInsertCss=e.options.autoInsertCss,e.autoInsertCss&&(this.appendCursorAnimationCss(e),this.appendFadeOutAnimationCss(e))},n.getCurrentElContent=function(t){return t.attr?t.el.getAttribute(t.attr):t.isInput?t.el.value:"html"===t.contentType?t.el.innerHTML:t.el.textContent},n.appendCursorAnimationCss=function(t){var s="data-typed-js-cursor-css";if(t.showCursor&&!document.querySelector("["+s+"]")){var e=document.createElement("style");e.setAttribute(s,"true"),e.innerHTML="\n .typed-cursor{\n opacity: 1;\n }\n .typed-cursor.typed-cursor--blink{\n animation: typedjsBlink 0.7s infinite;\n -webkit-animation: typedjsBlink 0.7s infinite;\n animation: typedjsBlink 0.7s infinite;\n }\n @keyframes typedjsBlink{\n 50% { opacity: 0.0; }\n }\n @-webkit-keyframes typedjsBlink{\n 0% { opacity: 1; }\n 50% { opacity: 0.0; }\n 100% { opacity: 1; }\n }\n ",document.body.appendChild(e)}},n.appendFadeOutAnimationCss=function(t){var s="data-typed-fadeout-js-css";if(t.fadeOut&&!document.querySelector("["+s+"]")){var e=document.createElement("style");e.setAttribute(s,"true"),e.innerHTML="\n .typed-fade-out{\n opacity: 0;\n transition: opacity .25s;\n }\n .typed-cursor.typed-cursor--blink.typed-fade-out{\n -webkit-animation: 0;\n animation: 0;\n }\n ",document.body.appendChild(e)}},e}()),n=new(/*#__PURE__*/function(){function t(){}var s=t.prototype;return s.typeHtmlChars=function(t,s,e){if("html"!==e.contentType)return s;var n=t.substring(s).charAt(0);if("<"===n||"&"===n){var i;for(i="<"===n?">":";";t.substring(s+1).charAt(0)!==i&&!(1+ ++s>t.length););s++}return s},s.backSpaceHtmlChars=function(t,s,e){if("html"!==e.contentType)return s;var n=t.substring(s).charAt(0);if(">"===n||";"===n){var i;for(i=">"===n?"<":"&";t.substring(s-1).charAt(0)!==i&&!(--s<0););s--}return s},t}());/*#__PURE__*/ +return function(){function t(t,s){e.load(this,s,t),this.begin()}var s=t.prototype;return s.toggle=function(){this.pause.status?this.start():this.stop()},s.stop=function(){this.typingComplete||this.pause.status||(this.toggleBlinking(!0),this.pause.status=!0,this.options.onStop(this.arrayPos,this))},s.start=function(){this.typingComplete||this.pause.status&&(this.pause.status=!1,this.pause.typewrite?this.typewrite(this.pause.curString,this.pause.curStrPos):this.backspace(this.pause.curString,this.pause.curStrPos),this.options.onStart(this.arrayPos,this))},s.destroy=function(){this.reset(!1),this.options.onDestroy(this)},s.reset=function(t){void 0===t&&(t=!0),clearInterval(this.timeout),this.replaceText(""),this.cursor&&this.cursor.parentNode&&(this.cursor.parentNode.removeChild(this.cursor),this.cursor=null),this.strPos=0,this.arrayPos=0,this.curLoop=0,t&&(this.insertCursor(),this.options.onReset(this),this.begin())},s.begin=function(){var t=this;this.options.onBegin(this),this.typingComplete=!1,this.shuffleStringsIfNeeded(this),this.insertCursor(),this.bindInputFocusEvents&&this.bindFocusEvents(),this.timeout=setTimeout(function(){0===t.strPos?t.typewrite(t.strings[t.sequence[t.arrayPos]],t.strPos):t.backspace(t.strings[t.sequence[t.arrayPos]],t.strPos)},this.startDelay)},s.typewrite=function(t,s){var e=this;this.fadeOut&&this.el.classList.contains(this.fadeOutClass)&&(this.el.classList.remove(this.fadeOutClass),this.cursor&&this.cursor.classList.remove(this.fadeOutClass));var i=this.humanizer(this.typeSpeed),r=1;!0!==this.pause.status?this.timeout=setTimeout(function(){s=n.typeHtmlChars(t,s,e);var i=0,o=t.substring(s);if("^"===o.charAt(0)&&/^\^\d+/.test(o)){var a=1;a+=(o=/\d+/.exec(o)[0]).length,i=parseInt(o),e.temporaryPause=!0,e.options.onTypingPaused(e.arrayPos,e),t=t.substring(0,s)+t.substring(s+a),e.toggleBlinking(!0)}if("`"===o.charAt(0)){for(;"`"!==t.substring(s+r).charAt(0)&&(r++,!(s+r>t.length)););var u=t.substring(0,s),p=t.substring(u.length+1,s+r),c=t.substring(s+r+1);t=u+p+c,r--}e.timeout=setTimeout(function(){e.toggleBlinking(!1),s>=t.length?e.doneTyping(t,s):e.keepTyping(t,s,r),e.temporaryPause&&(e.temporaryPause=!1,e.options.onTypingResumed(e.arrayPos,e))},i)},i):this.setPauseStatus(t,s,!0)},s.keepTyping=function(t,s,e){0===s&&(this.toggleBlinking(!1),this.options.preStringTyped(this.arrayPos,this));var n=t.substring(0,s+=e);this.replaceText(n),this.typewrite(t,s)},s.doneTyping=function(t,s){var e=this;this.options.onStringTyped(this.arrayPos,this),this.toggleBlinking(!0),this.arrayPos===this.strings.length-1&&(this.complete(),!1===this.loop||this.curLoop===this.loopCount)||(this.timeout=setTimeout(function(){e.backspace(t,s)},this.backDelay))},s.backspace=function(t,s){var e=this;if(!0!==this.pause.status){if(this.fadeOut)return this.initFadeOut();this.toggleBlinking(!1);var i=this.humanizer(this.backSpeed);this.timeout=setTimeout(function(){s=n.backSpaceHtmlChars(t,s,e);var i=t.substring(0,s);if(e.replaceText(i),e.smartBackspace){var r=e.strings[e.arrayPos+1];e.stopNum=r&&i===r.substring(0,s)?s:0}s>e.stopNum?(s--,e.backspace(t,s)):s<=e.stopNum&&(e.arrayPos++,e.arrayPos===e.strings.length?(e.arrayPos=0,e.options.onLastStringBackspaced(),e.shuffleStringsIfNeeded(),e.begin()):e.typewrite(e.strings[e.sequence[e.arrayPos]],s))},i)}else this.setPauseStatus(t,s,!1)},s.complete=function(){this.options.onComplete(this),this.loop?this.curLoop++:this.typingComplete=!0},s.setPauseStatus=function(t,s,e){this.pause.typewrite=e,this.pause.curString=t,this.pause.curStrPos=s},s.toggleBlinking=function(t){this.cursor&&(this.pause.status||this.cursorBlinking!==t&&(this.cursorBlinking=t,t?this.cursor.classList.add("typed-cursor--blink"):this.cursor.classList.remove("typed-cursor--blink")))},s.humanizer=function(t){return Math.round(Math.random()*t/2)+t},s.shuffleStringsIfNeeded=function(){this.shuffle&&(this.sequence=this.sequence.sort(function(){return Math.random()-.5}))},s.initFadeOut=function(){var t=this;return this.el.className+=" "+this.fadeOutClass,this.cursor&&(this.cursor.className+=" "+this.fadeOutClass),setTimeout(function(){t.arrayPos++,t.replaceText(""),t.strings.length>t.arrayPos?t.typewrite(t.strings[t.sequence[t.arrayPos]],0):(t.typewrite(t.strings[0],0),t.arrayPos=0)},this.fadeOutDelay)},s.replaceText=function(t){this.attr?this.el.setAttribute(this.attr,t):this.isInput?this.el.value=t:"html"===this.contentType?this.el.innerHTML=t:this.el.textContent=t},s.bindFocusEvents=function(){var t=this;this.isInput&&(this.el.addEventListener("focus",function(s){t.stop()}),this.el.addEventListener("blur",function(s){t.el.value&&0!==t.el.value.length||t.start()}))},s.insertCursor=function(){this.showCursor&&(this.cursor||(this.cursor=document.createElement("span"),this.cursor.className="typed-cursor",this.cursor.setAttribute("aria-hidden",!0),this.cursor.innerHTML=this.cursorChar,this.el.parentNode&&this.el.parentNode.insertBefore(this.cursor,this.el.nextSibling)))},t}()}); +//# sourceMappingURL=typed.umd.js.map diff --git a/site/site/public/kitdigital.html b/site/site/public/kitdigital.html new file mode 100644 index 0000000..a3c36a4 --- /dev/null +++ b/site/site/public/kitdigital.html @@ -0,0 +1,325 @@ + + + + + + Agente Digital - Kit Digital España + + + +
    +
    +
    +
    + + Kit Digital España + +
    +
    + + + +
    +
    + +
    +
    + +
    +
    +
    +

    Agente Digital Autorizado

    +

    Soluciones de Digitalización Kit Digital

    +

    + Ayudamos a pequeñas empresas, microempresas y autónomos + a digitalizar sus procesos mediante las ayudas del + programa + Kit Digital, financiado por los fondos + Next Generation EU. +

    + +
    +
    + +
    +
    +

    Soluciones de Digitalización

    +
    +
    +

    Sitio Web y Presencia en Internet

    +

    + Desarrollo de páginas web profesionales y + optimización de presencia digital. +

    +
    +
    +

    Gestión de Clientes (CRM)

    +

    + Sistemas para organizar y gestionar la relación + con tus clientes. +

    +
    +
    +

    Business Intelligence y Analítica: IA

    +

    + Herramientas de análisis de datos para tomar + mejores decisiones empresariales. +

    +
    +
    +

    Gestión de Procesos

    +

    + Automatización y digitalización de procesos + internos de la empresa. +

    +
    +
    +

    Facturación Electrónica

    +

    + Sistemas de facturación digital y gestión + documental electrónica. +

    +
    +
    +

    Comunicaciones

    +

    + Soluciones de comunicación empresarial y + colaboración en la nube. +

    +
    +
    +

    Ciberseguridad

    +

    + Protección y seguridad de sistemas informáticos + y datos empresariales. +

    +
    +
    +
    +
    + +
    +
    +

    Segmentos de Beneficiarios

    +
    +
    +

    Segmento I

    +

    + Empresas de 10 a 49 empleados +

    +

    Ayuda de hasta 12.000€

    +
      +
    • Sitio Web y Presencia en Internet
    • +
    • Gestión de Clientes
    • +
    • Business Intelligence y Analítica
    • +
    • Gestión de Procesos
    • +
    • Facturación Electrónica
    • +
    • Comunicaciones
    • +
    • Ciberseguridad
    • +
    +
    +
    +

    Segmento II

    +

    Empresas de 3 a 9 empleados

    +

    Ayuda de hasta 6.000€

    +
      +
    • Sitio Web y Presencia en Internet
    • +
    • Gestión de Clientes
    • +
    • Facturación Electrónica
    • +
    • Comunicaciones
    • +
    • Ciberseguridad
    • +
    +
    +
    +

    Segmento III

    +

    + Empresas de 0 a 2 empleados y + autónomos +

    +

    Ayuda de hasta 2.000€

    +
      +
    • Sitio Web y Presencia en Internet
    • +
    • Gestión de Clientes
    • +
    • Facturación Electrónica
    • +
    +
    +
    +
    +
    + +
    +
    +

    Sectores

    +
    +
    Comercio al por menor
    +
    Hostelería y turismo
    +
    Servicios profesionales
    +
    Salud y bienestar
    +
    Educación y formación
    +
    Construcción
    +
    Transporte y logística
    +
    + Agricultura y alimentación +
    +
    Industria manufacturera
    +
    Servicios financieros
    +
    +
    +
    + +
    +
    +

    Contacto

    +
    +
    +

    Solicitar Información

    +

    + Complete nuestro formulario para recibir + información personalizada sobre las soluciones + de digitalización disponibles para su empresa + mediante el programa + Kit Digital. +

    + Ir al formulario +
    +
    +

    Proceso de Solicitud

    +

    + Te asesoramos gratuitamente en todo el proceso + de solicitud y implementación de las soluciones + digitales más adecuadas para tu negocio. +

    +

    + Para consultas urgentes: + + contacto directo + +

    +
    +
    +
    +
    +
    + + + + + + diff --git a/site/site/public/kitdigital/formulario.html b/site/site/public/kitdigital/formulario.html new file mode 100644 index 0000000..51357cf --- /dev/null +++ b/site/site/public/kitdigital/formulario.html @@ -0,0 +1,768 @@ + + + + + + Formulario de Contacto - Kit Digital España + + + +
    +
    +
    +
    + Kit Digital España +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +

    Formulario de Contacto

    +

    Solicitar Información sobre Kit Digital

    +

    + Complete el siguiente formulario para recibir + información personalizada sobre las soluciones de + digitalización disponibles para su empresa. +

    + +
    +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + +
    + + + + + + + +
    +
    + +
    + + +
    + +
    + + +
    + +
    + +
    + + +
    +
    + +
    + +
    + + +
    + +
    +
    +

    ¿Necesita ayuda inmediata?

    +

    + Contacte directamente con nuestro equipo de + asesores especializados en + Kit Digital. +

    + Contacto directo +
    +
    +
    +
    +
    + + + + + + diff --git a/site/site/public/kitdigital/images/ai/multilin-kitdigital.ai b/site/site/public/kitdigital/images/ai/multilin-kitdigital.ai new file mode 100644 index 0000000..d0050f4 --- /dev/null +++ b/site/site/public/kitdigital/images/ai/multilin-kitdigital.ai @@ -0,0 +1,4441 @@ +%PDF-1.6 % +1 0 obj <>/OCGs[5 0 R 42 0 R]>>/Pages 3 0 R/Type/Catalog>> endobj 2 0 obj <>stream + + + + + application/pdf + + + multilin-kitdigital + + + Adobe Illustrator 29.0 (Macintosh) + 2025-07-12T20:16:54+01:00 + 2025-07-12T20:17:40+01:00 + 2025-07-12T20:17:40+01:00 + + + + 256 + 76 + JPEG + /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgATAEAAwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4q7FXYq7FULqenwajZ vaT19GRkLgdwjh6b+PGmKpA35d6NUiKWaFCGUhStQC/McWILKRQLXrxqD1xVfH5DsEYutzKkrMHL xiNBVSrIFULxAR0DAe2KrrfyvZ6SYLtbm5la2eqISrcmkCwhaEAfZoo6UxVBeXNP8u1tLi1Msy26 PJazTIhJWDmsm/2/tXVd/AeGKpTb6f5VuUAs7m5nWGJbc+nFF0lJ4hQwXi9SeR/1q7nFUXC/lS21 Uy2l3LDPHNMgiSNRCGV2kKUAAJXk3Hf7OKqd3/hC7dP311Dd3U0kojQUleS5Z4+J6gdeHGu37XU4 qubStAlns1S6vJprrjJaBljKSFkMzh1YKpqbjmwbxp44q1NZeXZJbmH17pzbmWGWNY4fTHGViQqU 41WSRuPhy6Upiq+5h8qW9oIrx7llExueFFWRX4mIE8KEsvCpbcmlTtiq23g8rWF1NDJNLBdsgtnJ SNVX60UkSix1Vadfly8NlWuWg22pW+rNLctJya7YtGgURSEt6hAG/J5lUd6FRtxqFV0eg+V3hgFt NcpC8EhgkiQA0g+GSQyEVoGjU8TtVunxYqhlsfLEtusqSXLQGQxM5ihCq0ilN2HEqDwPJgfi236Y qjXsPLkk9rdma4mcoKKypwZbYKnJ0+EbfVCdutenQYqts/8ACi2M7wyytCYZY3vfSQUDH1WQ7VNO B+AilPh9sVR9t5K0a8tLe5huJOEiRSRSoFRmWkZXkKUpxjpxptU98VVl8h6YjFo5XjJ4V4BaH0kV UqCDWhQMa9Tiq6XyJpTqkau0cSBl4IqL9tAh4kCq17064qntjai0tIrYO0oiUL6khBZvdiKbnFVf FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYqtmhhmjaKZFkicUdHAZSPcHbFUOmk6UkolSzgWUEkSC JAwJFDvSvTFWn0fSJKl7G3fls3KJDUHfeo9sVW3Onxel/o1pbM+wpKoC8QCB9lT0GwxVBDSZxEIR p2miIAgR8TxAb7Qp6dN674q3Jpt1IayafpzkBRVgTsn2RvH+z2xVuXTbqYkzWGnSE1qXBb7VeXWP vU1xV0en3kXL07HT05rxfiGHJePGhpHuOIp8sVcmm3ca8UsNPVaEUUECjChG0fcbYqtGlTgADTtN AUkqOJ2Lfap+7713xVXEOqgUFtZUowpV+jmrj7H7R3PjiqpFpNk6h7uxtTcV5EpGrCoYkEFlBrU1 +eKqn6H0g1/0G33bmf3Sfb/m6dffFV36M031PV+qQ+r/AL89NOXXl1pXqa4qiEREUKihVHRVFB+G Kt4q7FXYq7FXYq7FXYq7FXYq7FXy3q357fmVb6re28WoRrFDPLHGPq8BoquQNyngM6WHZuExBr7S 6mWqyXzUrT88fzZvLhLa0vFnuJDRIo7SFmPfYBMjm0WmxRM5+mI6krHU5ZGhufci0/Nz85nhs5Yr lJTfySRWsMdvbvKzRU5/u1UuKV7jMUjRCUwTXhgGRNiPq5b8mfi5qHn7kvb8+/zRRirahGrKaMpt oAQR2PwZnDs3ARYH2tf5rJ3sr/Kv83vPfmDz7pekapexzWFz6/rRrBEhPp28ki/Eqgj4kGY2t0OL HiMojcV97bp9ROUwCdmdeY/MfnS313zO9lK66Podk844rbcVkFg068vUBlasgH2ajx2zCxYsZjC/ qkfP+dTkTnIE1yA/Qh7n84r+yiuXudKhWNDcJZ3D3LKsjWt8lk5lpC3pAmUN4e4yQ0ANUe7p3i+9 B1JHT8XSyP8AO26aMXX6DD2bwl09O4LSmVdMXUSnH06cQCULV96dsT2eOXFv7v6XCv5ry/FWp6n+ cd5p6T3phs7mJbeAQxQ3TNavMZ7xHeK59AFgyWg48gF969TDQCW2/Pu35R6X5olqSN/x1/Uh7786 78apBNb2cSafHDK01k849di4tvSmn/dH0IV+sFgyluShj2yUezxw7nfv+fLvOyDqTfl/YyLU/wAz 5bTyhpmvrZ2ytqF7JYkT3RS1Hpif98lwIzyjf6vVDwFQwzHho7yGFnYXy36dPi2yz1ES7ylr/nTM fWMei8Slss6W8s5W4VvQhnrNGIzwhf6xwSSpqynbLP5PH87r3bcyPnsw/M+T0uza7a0ha8RI7sop njiYvGslPiCsyoWAPQlRmulV7cnKF1uq4EpJ5mvLq2S3MEhjLFuVO9KZzntDq8uGMOCRjd/oc7RY 4yJsWxTU/Oh0uNJL/UTbq9QnLcmm5oACds0Gn1muzEiEpGvd+lzJ4sUeYQOhfmVFrIRLe+eK7fkT aS0Eg4k/y8lOw5bE7Zfqsmvw2ZSPCOoIYY44Zcgnf6a1X/lpfMD+VtV/Pk3flsfc79Nar/y0vj/K 2q/nyX8tj7nfprVf+Wl8f5W1X8+S/lsfc2dZ1Sg/0l9/fH+VtV/Pkv5bH3NfprVf+Wl8f5W1X8+S /lsfc79Nar/y0vj/ACtqv58l/LY+536a1X/lpfH+VtV/Pkv5bH3O/TWq/wDLS+P8rar+fJfy2Pud +mtV/wCWl8f5W1X8+S/lsfc79Nar/wAtL4/ytqv58l/LY+5dFrGqNKim5ejMB18TlmLtXUmYHGeb GWnx0dmWfV7z/f3689HdG76vef7+/Xirvq95/v79eKuMF4BX1v14q+Jtf/47uo/8xU3/ACcbOzxf SPc6GXMouP6zpuhW94lsIbm6uGey1aOcrMqwrxdFjRqr8TfaP9M1suHPqZQMuKEI1LGY+m5bg2Rv y5ftbhcYA1uTsbSm3uLi2mWe3leGZDVJY2Ksp8QwoRmyyY4ziYyAlE9DuGkEg2Ez1W3L6Vpt/HZR 2lvIrQGVZvUeeWI1eV0LFl+14fwzA0mWs2TEZmcgRKuGhGJ5RBqjy/G7bkj6Qaofeyb8iv8Ayami f9HX/UHNlnaX9xL4feGWl/vB+Oj6A1z80NN0HV76y1OCRo4J4Ibc2/BnYSwrK7srunwpz341Ptmi x6MziDF2Ms4iSCu8z+ernSr/AFWzS0jnFhFpskfMmrm/vPqzAgfyD4h744dMJAG+fF9gtZ5aJHu+ 0oNvzi0R0t5ktbmC3Zuc73MaitsIbuRpY/Td60ewdd8n+QlvuP2+n/ikfmR+Pj+puD82dJjtI/r+ nXcdxzl9W1jhT/R4oTAyNJykH+6ryJ6rXvtUUIOilexH6+f6io1Arcfj8FFa1501ay85/oW2sFms o47WW7uVhupnCXEkitVoUeOPisRoZWUe9Acjj08Tj4id9+7p+OiZZSJVSEl/M3yhfSaXz028uGPo 3lp+7hPoCW1kk9Rv3u3p2/PnSu3SuTGjyRvcd32+7vY+PE1sqSfnD5cjjWU6fqHqPG00kQih9RLa OAXPrP8AvacDC/MUJbqKV2wDQT7x+267u9P5mPcXXH50eT4hflFuZ/qEohIjWIGU8nUmLnIlaGNt moT2BxHZ+Q1y3U6mO/kzWwvYL6wtr6CvoXUSTRchQ8JFDLUfI5hyiYkg9G8GxaS+b/7u1+b/AKhn Je1H04/ef0Oy7P5l8q/mL5jvdWvJrJ7+C8iivLhtPmQFfSgoAEBQIsnJWpVuVSuxoTW3s3TDFESA IuI4h579/d+lyJ3RMtq5IHyPPJaX9zc21ytreW1q7W9+wLxI8hCVEaiQEt9n4ge/sw2Gu0wljAP0 ExsfH9HPbfZhIGM6ib5/Zf4+L2k+Z7uPyhpGqQywXVxdLAtxNIyqjOYyZTsY1B5oQenHfbbjnFHS x8ecNwBdfPb8f2uQZkRBYzJ+Z+vRycZP0XEkgVrd5pfTqskZdGcCV6Alk6E7HMwdmYyNuPzoefu9 7T+Yl5J/5b846nq2sGzkjs1hV51b0peb0gChyKt1R3VCFUrufiHEc8TUaOOOHF6unTv/AGb/AKO6 3HlMjWzJdbvmsNJubxWiU28TScpyViFO7lQSB8hmFhhxzEe/ubJGgSwiLzv5rnuDFAdGkAidy8c7 OeSQ+p9gsnw1HUE7fI5szosIFnxOfd5tAyyP81EDz1qkF/HDfvp0Vs0lWmWRiPRi5ic8uXEMhglP U9KU6lYfkYmNx4yf0nl87H45nxiDvTIf8aeVOQUapbsxcxgI3L4wCePw13+E7eO2Yn5PL/NLb4se 9pfO3lJm4rq1sWJYcfUFfgJDbfNT9xwnRZv5pXxY96rF5s8uSTtbpfxeshCtGSVapNKAECpr2GRO lyAXwmk+JHvU385eWEALahFQsqbcjRn2VTQbGvjhGkyn+Eo8WPem2k31pfpbXdnKs9tMQ0UyGqsO VKg5GEDDKBIUQQmRBiSEf5k/NJdF1jUNNOmeubERsJTd28XqCSNXNEc8l486fFSvbtX1R55BW35z 207MF0xSqRc3lW+tXT1HB9JBRuREjLxDcevyNFV9v+b31q/+o2ukLPcOkbQRx39ozytJGX4Igarc CCGp8xXFWdabd3V3p6XF1aPYTvz52krI7pxYqKshZTUCuxxV8Ta//wAd3Uf+Yqb/AJONnaYvpHud BLmVfSY9PvYV0p0t7W7nnDDV7mV0SONVNUKiq7n9o/25gayWXFLxgZThGP8AdxiCSb53z+H9jbjA kOHYEnmVtp5euZjZSTTwWllfyyRQ3s78Yv3VObN1YDfuMObtKEeMRjKc8YBMYjf1cvL7URwk1dAH qoalcWMot47W0S3NvH6c8qO7ieQMay/HTiD2FP7LtLiyR4jOZlxGwCAOEfzdufvYzINUK/SzD8iv /JqaJ/0df9Qc2Q7S/uJfD7w26X+8H46PpLU9L8hapqU8Oo2lpdahFJGtwssYZw8sVY+VR0aNNu23 iM5uGecRQJDtZY4nmFXVrPyTc3lpq+px2U13A8Mdndy8GcM7j0VVu/xvVfA74I5pxBiCaKmESbI3 Q+n23kC7Ki1tbQi2RBEXh4L6chlWMxmRVV0czygFag8j44fGn3n8f2BfDj3Ii28t+R4bcWtvY2Kw Izw+kqx05zlC6H/Kf0k26/CPDCdRkJskqMcR0dqFp5JuL6PU71LKW9UxLHdMUaTaXhFQg1oJWoPf IxzTAoHZJhEmyFPStD8h2MaXWn2djbKwDq6qkZAmjPGoahHKNiAD22yU885cyUDHEcglR8rflnc3 VpbrpsSRW1zPDFb/AFV47WW5ccJAWaMJIyi3K7NQAU6ZMavIL3O7HwYdycf4Q8k6nFLdfomznS+d biWQwr+8dSSHNQN6s1fGpr1OQGoyCqkdknFE9E8t7eC2t4ra3QRQQoscUaiiqiCiqB4ADKiSTZZg UkXm/wDu7X5v+oZyftR9OP3n9Dsuz+ZfKeu+Rtcvpr64sdFvHWC7l9SS5jYTzpNtGyqyxqwUxmvp ii8h88yIa7HGMImcd4jkdhXPrtz69zP1yJNHmjNI8pXkOipNc+Wbi5MCs11bvDJHM5kDRLwIUyMV Z1f4RsFr4ZUdYPGA8SPCT/OFbUT1oXRG/e2A1jJA6cq6vWPKWj2p8naTZ31oZPQiRjBfRLzSQVrV WUUKkkA0rTrnN67KfzE5RPM/wnp72/H6oiwmbeXtAaRZG0y0aRFKI5gjJCleBUHj04Dj8tsxhqMn Lil8yy4I9y620TRbWVZbXT7aCVPsSRQxoy7cdioBHwmmCWachRkT8UiAHIK+o20lzYywRSejLJGR HKVVwj78W4uGU0PYjI45CMgTupFgsWXyTeQ38s8EtibdkaNLd7G3BIZxUO6IjcWi5RsPevsM786D EA8V9/Efxz3avCN9PksPkjV6QKb+ydIhHyVtNtuJaEUVwoGxbfkAdv2eOH87Df0y6/xnr+P12jwj 3j5Ib/lX2sV+HULJAQRIg022KOCBs6BVqPhX5U65P8/D+bL/AE5R4J7x8lW+8h6rLKTBfWQgLM/1 eXT7d0BcgMAONN1RasQW96bZGGugBuJX/WP4/Qk4T3j5I/RvKE9pfC4vGsp4uBEkUdlBEzOSpR+a qCPT40A71J8AKs2rEo0OIf5x/G7KOKjvXyTX/DPlv/q1WfQD/eeLopqP2exzH/M5f50vmWfhx7gm Om2dnaSJHawR28bSB2SJFQFiRViFA3OHHMyyRJN7hZACJpPdX/LrTdU1WfUZtR1CF7iRZJbeCWNI jxijiVaemW4j0g32vtE9jTPVHnkB/wAqj0iiqdX1QooChDLb02I3/uOvwj/MnFVSH8qdMinjnTWN VSSOOWJTHNDCSZanmWihjYuhYlTXbFU60byxLpVy051rU9QUxGP6vezRyx9QeYpGjctuvLvirz69 /wCcbPKt3eT3T6pfK88jyso9GgLsWIFU982se1pgVQcI6KJ6lR/6Fj8p/wDV1v8A/kj/ANU8P8sZ O4L+Rj3lv/oWTypSn6Wv6eFYf+aMf5Xn/Niv5GPeWv8AoWPyn/1db/8A5I/9U8f5YydwX8jHvKc+ T/yK8veV/MVprtpqF3PcWfqcIpfS4H1YmiNeKKej+OVZ+0p5IGJA3Z49KISsFkuueQdI1ieeeeWa Ga4aQySQsFYpLbLbNGag1UBA4r0b2JGa5ykDF+V2kxyxy/W53lj4Dk4jaqx3YvQCSpb++HWvTb3x Vq8/K3TLyFYp9RvSkaRxQgNGoWOKN4VVgEo37uTjU9aCtd6qq11+WmjXE0MouLiFoSD+6ZVD/vpJ iXFPiYtN1Phiqxfyy0pLu1uo7qdGtEhWOMBPT5QemQ5TjQlmhBbx39qKqa/lVoojije6nkihSRI0 ZYf92wSwMWIQFiPXLLy+zTbbFUdB5EtLd2lgumin+sNdxXCwwCVZXd3PJ+FZBSVlo1dvfFU+0uwj 0/TrexjdpEt41jEkhBduI+01ABU4qicVQOq6THqCxh5CnpkkUANa08flms7S7MjqhEEkcLfgznHd Dml3+Ebb/lof7hmq/wBC+P8Any+Qcn+UJdzv8I23/LQ/3DH/AEL4/wCfL5Bf5Ql3O/wjbf8ALQ/3 DH/Qvj/ny+QX+UJdzv8ACNt/y0P9wx/0L4/58vkF/lCXc7/CNt/y0P8AcMf9C+P+fL5Bf5Ql3N/4 Rt/+Wh9vYY/6F8f88/II/Py7mv8ACNt/y0P9wx/0L4/58vkE/wAoS7nf4Rtv+Wh/uGP+hfH/AD5f IL/KEu53+Ebb/lof7hj/AKF8f8+XyC/yhLud/hG2/wCWh/uGP+hfH/Pl8gv8oS7nf4Rtv+Wh/uGP +hfH/Pl8gv8AKEu53+Ebb/lof7hj/oXx/wA+XyC/yhLuXJ5Tt0dXE7kqQeg7ZKHszjiQeM7IOvJF UnnxeI+7OmcB3xeI+7FXfF4j7sVcQxFKj7sVbxV2KuxV2KuxVjPkLz/o/naw1C/0mOZLSwvpNP5z qFaR4o43Z1UFqKfVoK74qybFXYq7FXYqg9aluodGv5rSv1uO3le34ryPqKhKUUg1PLtTIzJo024I xOSIl9Ni3z5e/mf+bFiUF7eTWpkqUE1nBHyp1pyhFc1h1GUcz9j3GPsjRT+mIPukf1sh8led/wAz 9Q1aA6hJPJpkkbv6jWcaRn4aqfUWJdvpzG1urzwwylHmK6eY8nC13Z+jhA8IHHf8438ren6V5jkl mWC7Aq5okq7bnoCPfMLsv2glOYx5QN+RHf5vP59GALiifMHmjStB+rfpBnH1tzHAI0LksorSg/z+ jOrdeoWPnjyve3UNpDfL9bndo0t2VlcOqq7Kaig2kXvQ12rirtO87+WNQ1JtNtb5GuxI8USE09Vo 0WR/S/mAVxv37VGKp7irsVdirzP8xbj8449dRfKSf7ifRWjRpauxkqeXP1+TfKm2Y+U5L9PJ3XZ8 dGcf77678/0MW+uf85Kfyy/8itO/5pyu8v4pzuDs38Ga19d/5yK05DdXVtJcQx7vH9XtZNv9W3Ak +7HiyhIwdnT2Bo++X6dmaflr+blp5qkOm6hCtjragssSk+lMF3Yx8qkFe6knxqd6W4s4lt1db2j2 UcHqieKH3e9W/N3zZq3lrTtJu9PuvqqzX6RXj+mklYOLM4o6v4dt8vdQhdG8yeb/ADzqAu9BuP0H 5VtpCjXbJDNd3TL1CpIsixj3I+/oFUb5k8ya3Y/mR5Y0W2uiunahHKbyEpGTIUViDyK8l6fskYqx DzxffnJ5Ws4b+48yW0tvdXa2kMcVvAWUyK7qTygGwCYFZ95N0v8AMKzubhvNOs2+p27oBbRwRJGU eu5JWKKu2FWV4q7FXYq7FXln/OQHnvzX5O0LRrvy06Le3mopbPG8ayiRWjdhHRhtyZR03xVhPmjz p+ePlObT9F1vzBpX6a80TKljOscSW+mwpxE0kjvFGpPJwBy5igbqSuBUZ5e/MPzh5V/MHRdA8web LDzjofmEmGO9tTAJLW4FAOQh6KWZR8RoQaihBGFVPy35g/O/8yBqvmryvrltoujWl1Jb6PpE0COL gRANSZ2R2HJWUFq9a0C0xVNv+cTjM3kHWTMoWY67cmRR0DfVraoG574qlOq/mF5184edvMGmaH5u 0/yZoXl+Y2kUt39XM13cRlkZh6u/DkjbrQAcdicVSW+/P/z7B+XWqxCW2k8zaNq0Wl3uuW8aSwfV 5lmKXKKoMRLPbsoITjSm1SMCvRPynbz/AHeorfzeetO83+VZIWMvpQpHcxXBpwWiKrJ4nme32RWu FXq+KoW6PrSC0aOX03UlpkPED2rWuYOpPiS8IxnwkfUNgPK22Gw4rF9zx3/nIcUvNDHhFP7/ALUe R1gqnqfZn6Z+8fpeheRtU0+DyXoqSzKrpZQ8l3J+wD0GVjtTT441KYBDpO0ME5aiZA/iKDlYXmrF rZSBLKOA+nr/ABzickhqNXeMVxT2/X+lyYjgx+roEx85X+q2cumGw0hdUEsxiumaJpTDDIVSRqru oKsa7GoHTPSnRpAdbNlbtfWvkCSO6jSS4MQgVZTKJTGAjQwy1J4KxNQaUKhlHIKrbTzGY1XUbHyJ J9Yae+aO4hRBWRIwfU5iMPW4+xy49qVbpiqZf428zOzRL5YuYZEumtzIwldGRHiHqR0iSoZZGILF V+HZm3xVX0TzjrlxeW9vq2hXFjFcA0u+EnFZHldY4mQK1KIo5OW670C4qy3FXnH5gzfm8mtoPKop pXpLQxrbOxkqeXP1wzD2ptmJnOW/TydzoBozD979d+f6GJ+VPzn1m00vV7jzBcHUr0egulWvpxQg s3qeqzNEifCKLWv0Zj49bQPEbPR2Gq7GhKcRjHDHfiO57q5s7/KrW/OOt6Zd6h5hULDLIp04iNYq pQlyANyvShOZWmySmLk6rtTBhxSEcfPqwL85NIi8u+ctJ8yaUogubhjPKifCDNbupLkD/fgcBvH6 TmPqzwSEg7XsjKc2GWKe4G3wP6npnn7yW/mq302FblbYWF4l23JPUDhAQUpVaVrmxeWS27/LFrHW RrPk/UToNzI1byzCerZzD3h5KF+j6KHfFUfqfky81HzboHmKa7jSTSI3SeBI2pK0ikEoSx4irdDX FVT8wvJkvmzSbSwjultDa3kd4ZGQuGEaOnGgK9fU64qyjFXYq7FXYq7FXkP/ADkZpep6hp3lNbC0 mu2h1y3lmEEbylECtV24A0UeJxVDf85C+RtS1O68u+a7DR/8QRaHK66rooBZ7i1kKt8CqCx48WGw JFQabHFUD+XNj5C8w+aI1svytudFtrSMz/pi9iMIjuY2HBFViK9DupLA/sgb4qkv5f8AmTzt+VVt qnke58oanrcy3ssuh3lrGxgmWQBRyk4sqp8IeorSpBApgVb+Umo/mF5U/L25FroE31281+6kuYp7 eYlV+rW3Hig4txduQDdPhyrLOUeQdp2XpcOYyGWXDQ23A+/uS298q2/k3zrr0/mzyFceadH1yf6/ pd7aRNO8Ek7F3t34mmzScfiIPw1AIO1odbIAEgcmbafd2+hfl+2saV+U88Uer3Ig1vQKK8jWcIcx zNEytI/29lMQ/arQcWJYsT/L3RHvPzl0rW/JPlbVfKugRRzDXRfLJHbvVXHBVYkbtxHAMaMAeIpg V9M4VQ80c/1hJvX9O3QH1I6Df3LHMTLjyeIJ8fDjjzG33tkSOGq3eL/85A3EE9zojwuHThcCqmu4 ZMxc+eGUCUCJDd6z2bgYiYP9H9LI7axvrT8ttP1jT4De3MVlBI9py4kxrGAxWgapAFaZrM/YMcl5 OI2d6cKWaMtXLHI8I4jv8UD+VP5jW2q6vLpupwRW97Nvp8qcqMAPijPIt8VNxTrv7Zldl6PDhlsP V3lt7Z7MljxicCTEfV+t6D5n03UL6C3Gn3xsbqKRmV/VkjVqxsqhlQ0ekpjNGB8O9Dvnl2NWcXn6 7muVsfNlheJavPEwQQO6yFFWJZ/ThPFkZXcgU38RtiqnaQebPXewt/OFs+oSMZbtWVJpFJCqTErL xRaQuVUKFB5bHlVFVeSy/Ma0iM8vme0MSTwF2miiCiNQPWiqkK/bdj70C7g1xVVHl38wJDHONfiM yszs1CyM4ieNCEVURVDPXhxO9CSxX4lWV6NBqcGmwxancLd3ychLcKAoccjxJCqi14UrRQK4qjMV fNOn+W7wV1BLQXVvZPG1wjgtHuSVEgBB4twIOcbjzSIMgLEat7jJqI/STRlye26R+YHl+400TXLi wliAElqwJIoP918R8Q8KZv8AD2tglCyeE9zy+bs7LGVD1ebBtRt7r8wPOlsRCyaRZ0BDjpCG5OWp +1J0A/ocwBnOrzAR+gfd+12uMjR4Dv6z9/7GZ+fbc3d95asGnnht7zUGjuPq8skDMotpXA5xlW+0 ozonmkg8yWk3lu41Gz02+vPqt35f1S4aKe5ln4T2yJ6csTSMzI3xnocVXCKTV9Rv/r8mp3dvpOma Y9tp+nXDwu73SyGVyFeLm/wDdm6VxVApqM8GnalaWs+rWy22q6Tws9TI9eBbidOSLMs0zOkgFaNS nvXFU383+VbNdf0T0rzUIf0vqUi3qx3tyqlDDLMURefFByUfZGw6Yq15utzpE3l3y/ZyalLp19Ne S3cVrcSNezC3tzKsSTSOH4luo5jbFWOXVzNZyeY47KLWNMjTy9NOkOpzu8nqiUASxn1p+O21ajFX sNkS1nASakxoST1J4jFVbFXYq7FXYq7FXYq7FXYq7FXYq0/Hg3L7NDy+WRnVG+SRzeYfmn/yrvnp f6e+t8eM31T9H8OFOS868venTNd/g/COD6N64eXm9F2R+b9Xh8PS+K/gzryn+jv8M6Z+jPU/R/1a P6r61PU9PiOPOm1aZsMdcIrk6XW8fjS464uI3XK3j99/yp//ABNL9T/Sv1/61+4/R3p+l63Pb6v3 pz+zT6MwT4XFtd+T1WP8/wCCOLw+Hh/iu683pnmn9Dfpfyx+lPU9T63/AKLTj/fcBw9bj+x6vD7O 3PhmxeNPNhtz/wAqy+sXfL9I+r9YufW4V/vPTn9enbjw5UrtTp/uzFCF/wCQU+hN/wAdL0PqdvWn ++qy+jSnxVrWvLbpXauKoxP+VY+t8H131vrU/L7H9/U+tWvw8/s/5dKfs8sVZl+XP+Hv8Ot+gPrH 6P8ArU9PrX2/U5/HT/J5dK4qyjFXYqxvyl/hv07/APRfPhVfrPrUpx+LjT/J+1138c0nZP5ep+Fx V14vjXw589+9z9b4tx4/hSQ3n/Kvf0g/97x5fF6NPRr3p+1T5beGarP+Q8U/XX9H6f1/L4OZj/M8 PT482Y6F+hPqI/Q/p/Vq7+n15f5dfir/AK2dJovB4P3NcP459fm6vUeJxfvObHPzN/Rv1fSPrX6V +tfXD+j/ANCel9Z9b0Xr/e9uHLpvmY0Md0//AAx9X139Lf4h/SH6HuvU/TXD6z9Q4n1/qn+6uVad e9O1cVV/MP6A+v236M/xF+k/qNt9a/QPHn9X4n6v9a5/uuX2qU3+jFXWH+EP0Jd/WP0x9d/Senfp P9Icf0j6/qx/VPV5/B6XT7P7NaYqzDzJ+i/0v5d+u+v9Y+vP9Q9Hhw9X6tLX1uW/Dhy+zvWmKqWv /of/ABh5W+t/WP0jzvf0b6XD0a/Vj6vr8vi+x9nj364qhPMP+Ff8Qah+l/Wr+gpPrtafVvqPrHn9 n976nLw7e+KpV5Fp+mYvR/xV9V9FvR/S/p/UeHEcf+LK0+zXFX//2Q== + + + + proof:pdf + uuid:65E6390686CF11DBA6E2D887CEACB407 + xmp.did:a403d444-aa06-4dac-9df8-6adbb43f04fc + uuid:8e60a2fa-ae7b-1a48-a2b8-e74008b29e75 + + uuid:c109f12d-a74d-dd4c-a279-1670e10a011f + xmp.did:f8f2a57e-8bc4-4f2c-a00a-5d9a8b5810df + uuid:65E6390686CF11DBA6E2D887CEACB407 + proof:pdf + + + + + saved + xmp.iid:f8f2a57e-8bc4-4f2c-a00a-5d9a8b5810df + 2025-07-12T20:16:03+01:00 + Adobe Illustrator 29.0 (Macintosh) + / + + + saved + xmp.iid:a403d444-aa06-4dac-9df8-6adbb43f04fc + 2025-07-12T20:16:54+01:00 + Adobe Illustrator 29.0 (Macintosh) + / + + + + Web + Adobe Illustrator + Document + 1 + False + False + + 1920.000000 + 1080.000000 + Pixels + + + + Cyan + Magenta + Yellow + Black + + + + + + Default Swatch Group + 0 + + + + White + RGB + PROCESS + 255 + 255 + 255 + + + Black + RGB + PROCESS + 0 + 0 + 0 + + + RGB Red + RGB + PROCESS + 255 + 0 + 0 + + + RGB Yellow + RGB + PROCESS + 255 + 255 + 0 + + + RGB Green + RGB + PROCESS + 0 + 255 + 0 + + + RGB Cyan + RGB + PROCESS + 0 + 255 + 255 + + + RGB Blue + RGB + PROCESS + 0 + 0 + 255 + + + RGB Magenta + RGB + PROCESS + 255 + 0 + 255 + + + R=193 G=39 B=45 + RGB + PROCESS + 193 + 39 + 45 + + + R=237 G=28 B=36 + RGB + PROCESS + 237 + 28 + 36 + + + R=241 G=90 B=36 + RGB + PROCESS + 241 + 90 + 36 + + + R=247 G=147 B=30 + RGB + PROCESS + 247 + 147 + 30 + + + R=251 G=176 B=59 + RGB + PROCESS + 251 + 176 + 59 + + + R=252 G=238 B=33 + RGB + PROCESS + 252 + 238 + 33 + + + R=217 G=224 B=33 + RGB + PROCESS + 217 + 224 + 33 + + + R=140 G=198 B=63 + RGB + PROCESS + 140 + 198 + 63 + + + R=57 G=181 B=74 + RGB + PROCESS + 57 + 181 + 74 + + + R=0 G=146 B=69 + RGB + PROCESS + 0 + 146 + 69 + + + R=0 G=104 B=55 + RGB + PROCESS + 0 + 104 + 55 + + + R=34 G=181 B=115 + RGB + PROCESS + 34 + 181 + 115 + + + R=0 G=169 B=157 + RGB + PROCESS + 0 + 169 + 157 + + + R=41 G=171 B=226 + RGB + PROCESS + 41 + 171 + 226 + + + R=0 G=113 B=188 + RGB + PROCESS + 0 + 113 + 188 + + + R=46 G=49 B=146 + RGB + PROCESS + 46 + 49 + 146 + + + R=27 G=20 B=100 + RGB + PROCESS + 27 + 20 + 100 + + + R=102 G=45 B=145 + RGB + PROCESS + 102 + 45 + 145 + + + R=147 G=39 B=143 + RGB + PROCESS + 147 + 39 + 143 + + + R=158 G=0 B=93 + RGB + PROCESS + 158 + 0 + 93 + + + R=212 G=20 B=90 + RGB + PROCESS + 212 + 20 + 90 + + + R=237 G=30 B=121 + RGB + PROCESS + 237 + 30 + 121 + + + R=199 G=178 B=153 + RGB + PROCESS + 199 + 178 + 153 + + + R=153 G=134 B=117 + RGB + PROCESS + 153 + 134 + 117 + + + R=115 G=99 B=87 + RGB + PROCESS + 115 + 99 + 87 + + + R=83 G=71 B=65 + RGB + PROCESS + 83 + 71 + 65 + + + R=198 G=156 B=109 + RGB + PROCESS + 198 + 156 + 109 + + + R=166 G=124 B=82 + RGB + PROCESS + 166 + 124 + 82 + + + R=140 G=98 B=57 + RGB + PROCESS + 140 + 98 + 57 + + + R=117 G=76 B=36 + RGB + PROCESS + 117 + 76 + 36 + + + R=96 G=56 B=19 + RGB + PROCESS + 96 + 56 + 19 + + + R=66 G=33 B=11 + RGB + PROCESS + 66 + 33 + 11 + + + + + + Grays + 1 + + + + R=0 G=0 B=0 + RGB + PROCESS + 0 + 0 + 0 + + + R=26 G=26 B=26 + RGB + PROCESS + 26 + 26 + 26 + + + R=51 G=51 B=51 + RGB + PROCESS + 51 + 51 + 51 + + + R=77 G=77 B=77 + RGB + PROCESS + 77 + 77 + 77 + + + R=102 G=102 B=102 + RGB + PROCESS + 102 + 102 + 102 + + + R=128 G=128 B=128 + RGB + PROCESS + 128 + 128 + 128 + + + R=153 G=153 B=153 + RGB + PROCESS + 153 + 153 + 153 + + + R=179 G=179 B=179 + RGB + PROCESS + 179 + 179 + 179 + + + R=204 G=204 B=204 + RGB + PROCESS + 204 + 204 + 204 + + + R=230 G=230 B=230 + RGB + PROCESS + 230 + 230 + 230 + + + R=242 G=242 B=242 + RGB + PROCESS + 242 + 242 + 242 + + + + + + Web Color Group + 1 + + + + R=63 G=169 B=245 + RGB + PROCESS + 63 + 169 + 245 + + + R=122 G=201 B=67 + RGB + PROCESS + 122 + 201 + 67 + + + R=255 G=147 B=30 + RGB + PROCESS + 255 + 147 + 30 + + + R=255 G=29 B=37 + RGB + PROCESS + 255 + 29 + 37 + + + R=255 G=123 B=172 + RGB + PROCESS + 255 + 123 + 172 + + + R=189 G=204 B=212 + RGB + PROCESS + 189 + 204 + 212 + + + + + + + Adobe PDF library 17.00 + 21.0.0 + + + + + + + + + + + + + + + + + + + + + + + + + endstream endobj 3 0 obj <> endobj 7 0 obj <>/ExtGState<>/Properties<>/Shading<>>>/Thumb 48 0 R/TrimBox[0.0 0.0 1920.0 1080.0]/Type/Page/PieceInfo<>>> endobj 44 0 obj <>stream +HWKd)[dØА%@`_Wɳ0i1@MM'22?:qg;>}>Ro]ӟ.vǿrˬl[Hf2u}{yQ7Ը܏ t1ߝWǏTk]wt_:fd7˵O?\n8nu7+.1 g;.(GEvu,&s;{=Q}:kvA5OM:g +(B9zv ܂g& NU݊Z Oy@wDO" wq# k +$jy։L [:"s8QgdjP)ZW"VHTS>vGod0:R2֕aHt +{I֡R;CHhtݢXAqu/aOtu៚j̔۲80VaUe+V2dN}ShL(,'dzEE-D=[ıPe:|۽> +o@h +Ĝf.os>,k#UF5"IU_A7r[dBt_qT?Âk||P>Yd VxcžxD1 N7xEerNx4oɬDjv~6SA4 y[|V4Lnn1^?݉N4:cb,yt̡Et=_e*3*·rOt.s!PUg2+&]m@5BjC̨SLN&TऒKH AS[ +*#;#eN.E&7.8rB[!+;rQ4 +ŧ׳T75eA_/-݁}[ЏtH5@$;,C,'G,>tbL9c?hJ[Vk!7h'U}28 &B<PrA-Ę(R-X8])" kܝhD:Q܎]w/D1Jx `)ZTma̹\<ĆdM9¢5H#MT95M,KL8r]Vsv0|Oc6Лh?_,swWͅAºa]4TXK"T^׹Ode!weo斞7.Jyrc纲<2+p>Inwn3yPx>+5޹ۗAo:d/Zuu/#Gc~Sa_)ůz1r%=Nt49o|+V׀S+&9I^ |٥IWG ,j8mLQ`fM[y$W܈u@}p x'{ao 3hc؂,L}Np"}š5Y% TY6HrMq-6=ؖ"$,o{ǘ4N)}3'TL 7 OJ:abH(DS@ΗЧR:c`IUqE6kIb C>D-Rhb__lso2LwO3=@ҽ1[B%H$Ujܣaq;HmcMx꼇ZP$[Rw,9FP3]PtAW=ũȨ$jm]ir.dbQLDa8^QӢuQpj{m+%pHX8PN sOV¦)NW$QvCMeۢh bVqM~RU_ʤJ:Qk@Rl8TЂ&$κȰH@MKR*Ա +LT?4!}7lfQvb?bAԮlV^UMQm n㶵S)RαF Vº#Z ΆzFM PGoɴHp͸Vƶllx0)=-@) Dc5]> "{V(w:;;^$*NBD?+E.VTfgx! ndX4EB#)<-UdOeT$O< 옂- Z' ieї( < F$Hz3$4T}xQЖʾ7ƊP^C;U>RSqi̩Gc8t1:-t%lXUQ@EmX8(gCDY'B$P|Ѽ+Q7ge +ٍ{3vbI> +avLVO2_'xHu *iImWoR!arQukA0(i$1gh-Y6$7[/fK`UxH4Ub|\_YI qis6_uEpUućE`Ӫ db[Rv؞IaNk&ZШnW`m&^0#29a\8+U0wB.Q8Р¤K>Eꜩk9pxrЖXD #R~Gs}*cm]R{jm05g IM7[Zo>y'Ap*"<)ˬvv}sP k}7, (1|HY5TS!͝YG">3F%R99RtY5evMS.Ǹh6g) H?ׂiWpJwkŽعzŎ]8[%<*&۞[ +j vsgs~jӳ2Ad-jRa51ƲLF +|AR̦ hs].Bya(1C[ "E%&fSbFM㴟SaqC&.n[@i$A3d,Q}/rġ(^RF%H] BH¡m0gM ? + 'EE/ I1F,d"(+DkK3X`0SAKg _ jČlb[P'xYE@\;, 2:ARa~KxK2ng/{I**_C3$>&faoHR|MW[R&hv1oB}!eBͬ1) +j,|PY8HK*$ WbH,sR IUvIh!DRȕXaFH +;bC6fhK1Mޘ'P0ՋkA,AqmҡV׳m*PӺ!w&9&*2|:(@:V]\XSF %|Mjh0hԼBO_.az.fgr2,vp&,RvSjvk13zhiNwL!4xRXM]l"԰_.*CKk9^o,32F'{4 vF!^ [N|SpL7u1^c;RHeo!u?91{+'0 ZS(3.udZv@p*XfUXЅo;A&(@ \_vW@@Lg&z(G:C4X˓ +"XBCю:sWHUZO41V*츑iGx +xͫ|78Z}ͷx+NML{&+|xa:ًCѲʶW@<|8?8\V`" +m<ų%À/-cdk ͬѳM s.:јf=Vd檁Y&E6,^.*gamaR}p;Zq?v&>޳, R4KN*>(ef_ GR Xƌ'M?8'?TX9N9>e I48CxcbyyVɫ.:n[m읻; 'G4baS'eׄq4% +s*[%7}}^DnOʖ8Xvq*Rva}?ն;{AUx-,Fl#6:O%uaXc]#V3єE:Q|-|1e ,H7Y5R␀op++XT^;>dN^tRnas-d.]0Mm 4]'%T{a X{9[:[:ڵF}P_4~q2&}ɦfPkZ)$sZy2e&(8<` =ÈChQvzOx0"@F%b{R#I%i1]gqhe#=P6BV$}u~-Yމݻ;teS,UHoW6 Má Pewo7Z\A;1v hbK/MۓʓץJIr +^?ɶIM럶T-k~aK=؆%'{pDx3=܃a)HO%f5fְ;BE&k*M)nrc,>E`rEl #֎U$_IL޾a|g S8y*hIJUNCܡٕp˨`&:D*]ҏn0ϟ i=l}V(nE]q-lJE&ĶI Ѭ wpBc9{(/0w(z`!ZRDM@̰k\Ti05m".RXCdjOdLNUY%,mT?o͋owjՍd~:O͞|7Ce +޳_i<_yVܶtg'Qܜ@J?TկjKiB.;@1[|1-UfT/w2cs?䑟즫' +LF)y!yx]?Tī`=[oNAaY_7cal3%|u^6`Nǖi +yUeU~Ϝ uOsf+Xvq lY~1ַSKFio>ۧ eSS 0X\}͘>M6@rC`tNR;63s|+iƊ'@7*ǸV#s}x'Sg;p޸v q;(n zeX.b?R4bS/RqTZw-ME +),oe_5\yUXz$ cB:Ԫ#LBh_+hY^z6_9l?Z"&B42cËg'xIp\@jCU b¡P!ُʹO ;HqtFgt3ŒklX8Arj"`wJYdqi._G,ѧ-aIl Wdv]+>^08,h&\] ~jԾqVV \sX$m?l;ǓA7GhA5:Ga\bUkiR2Vx ngׂ!Sj>BlݜmM֢6?DsBAMGA1DtT |.MjPyr[78mU)^{9?WˊeWܵ4R%E,l([ytta"uuHeFF +frEXVbRvX\OڢXXX7.zـ[l,[>/IB] ݎJK.Iц[n%8 ۉ"!N@I̞g/ dI61U?a&D#'fy;I =`UT ^2 Em)嫙ջĉ!78^Oxg`]3%? %}]g6r+ ;Z3K#9Q/wTh`4r +O!MQ% +-k& E.fzP4X^8ɏEtv6+ԱW'\SlpZ{8Kd~~H]A/^ࢩqpu;|^Q!Y$NԒ6wxx{-83(JpfDTo>Ut EYh"Uj륤ep9\xBb:yWhP!+w;FG. ` +42/ I-&Q0L݀J7;w#X"˴Ѣ6gowkN;|9|mM-5?#>Oݍp[X{Kj+woI}Kku8|'(T\wPPܱ'WM=}IWsx`_sXgXn;n;dJbx,gju3 >j y$Gw.fH+O.v"c5qǢdƆ{ZR +kxik5G&K|u _BE0E7wi\:3@MYF& 5dpwPm$(l gDc%0>jZn}Η)LCo?ܹ-yu΋>UWn>U^)ݚ.[f؛ +Ql4an/$AG8&(}W]'!umۇgZ%g\qe0K i]XDWFחd̹qx}2ao눐SC5`jHU@:Sd\gȞ$Ň2["nn!%My֑O.Ё[gk( 1[*v"~v#nt[HqR&>lPo~O&C`Q27XIh&:q,@z/U\,brN,޶86x=VٻBlw|U +  B'B+&."F](?=+A<[xTW4x,rG|_1TaL ZȊFr|Y*ϕ훼&JERx7/+\(48 `kpXB4+F$Cpyo`,%2{K.`uTN'uR:8-S)JJJ_'>Ҟ[(y;Vc &LH$][-rB#W Hڐt% ?"Yo F훅lvkWUI`S'%A,h֍~e4M'<63^Iݏ"HHSRGsedaFCz*q +ѭE?h@7;4BW惝R:.y 0˒(,.*đ:e;~$TP-qdRv%Ww hka$e4<$>8<7b;@"$qSʼnb^s9rALY7G\K>f)XSE#'{=bDA i^"tTAVF駏r֋[0fέCzij0[JǑry[( NR{biX%5K@u0DD8 FȞT/mQeќהi:?KBăNo]j0k꜆O3#>񢸸8pP*rJK {rmqͽX;*6="XWhz-ii}3NY2Q0R8w }p]QVĤ.nyh +؎&:q8s@ =S 9dj+cRXcog~,צso0|*8"j^\ ߨi*OFe>R ?Yͬb$d{a鷤XVyaG%6] +/@[Q=U× C4sES`;+28فQ e#@d-ҁ}*sP`sN +|IV"!80 +&CL$F)A8 W*0tfM8GE-_m!Fһ+o=2' JT[No+\4wsQk-ϋ|Z/{{J#U:!ePD2!$Y<&gl_DTg$`WZu}ՕHu?XüBnQ\I}y pmjEe25.)twSONV7B{l1*C>YRIzaP!a\"|m2V #!i$z8<BFIVtRt#yfg i}Lsj'?IZVgQLccw"_?eAcs!+YH=VBFixM{{>Bw/ +}G>cտ, o|5ZdD7 +Э#D! ֢BÞdR%vc +Gֈ5ZfI4+Z!vLYɹjp=YWEXLU aŸRMVA/7kV^4p?ښN~xUU"2^[/Ƅû7SXc"%hߙOeE3j(@"y#YƼǑ9too&b|-n#:N2tz:x)ZPć#XV: "cM _%,7pߧ p |-$B3 Y~VYL2^HS_&$;ҌE3 k"+H+pm 8hZYD2dyGUS* s:9D.aкC>y$]KRU.+xlJf,dN&< +)}%әIrҬ!1K&s RtΨx&cķf:Zp"Oκx#ܠoLF < *sZiC)x[,+ +n@ l9.\Vi2y'& @ lONd$#]{ hHh@.j + ò/oLd8U3sFއ.' BGy](xM¥ixq~b"_q"ѓYvDVe`B*P=u+Ҝt3|I7 Iwh@# +_ҖJ6 %Ӗsl8i̢1{,Dqt<' R7a-"M:Ǟ47~%q)FFf`rbdn@s;845=rۘvƸ J)@_,@M\ C{"SiP'nJDLAf?|tcnz/Yqӓqm+mȯɕ-RCa>J8 Hh^.̈H*%8IǺzN֓9 CC8e1A !FxΏðׅ8bHrEh_,V'EsYL +s][b}O : qR: +p5iQVb\ユ4$NO^!<)}3J31\g]g%2kkO<ʹ2hPIMĥ45(ۆmϬX#}pQ* CQ["!݃p6%k=yW Q1w!{J]dvy;'rI@m8'sDaUTQ`=#6h-*GC:'+?d_W, ",& /+SEnJtC^x7ppX, ̼r3gAqqcG{Z _a-̵:pX`^_|xr.7&1%ÑHw9F?n^m:a A\1y'&]NfkRzI c. ̽0_ y+^Z'*X6N5W00jxA%42nD{+5BP]5ֺ^yMY;(}Xչ~fI!v<~uqd"N;b:<Qo ߍF18G>®G!W9 +IN;q|K]wނ]su+m +/ +>1}~}iޟ"H|w?f~~ޯy_~l&OqkRd'|% ċB5MP[| &rSR>{;˓=~ٙg 5o,,1:!A"zj}`8%O Wxd{-e:MjV+\?&aЌ"Lj91\?lNDf>M|E7xCqiScj `´4MB89% +F|$s.ip@zoVA~0(w-!i3բ?UCvX)#̓oڧ؀ +E  +ׅV VU 5PGE,4Iu?JZ4)dMK:$C<$ (J! XH>r//PuBZpRS@IZRi +uU!t ]y<}޴9fU>-Ӧ9=5"Ԙ;>ZVٍ⟬K c4{ L̓9brLDh-A@ 2e$ H*9.qN%IH =>:( o sERIsU< .` Vgm EV9;Xͮs||ᷮc\ rl ֒ھ/6EҶjJ8p{[} +&=/-mLrsr3i9L#p𓡇`Wh}̀.F3m9Ԋ)a,l3f79 ɦT=+Op/L8okpHjTB$Ӗ*by@Q + !}ŷ= WhKCI4\.>egobh2q1%}μt2҆ + nX!4e &HOBv7e.)#K9 W.Y* @\&QWU"i.hx1-pWA̻읛VwOR^+  &҂RDi|L5:6cAUskWOMhos]&lU.IYpE~daR7 I'J՛@ 7k㦎JsUgO[( +c +b3vMC>̇Udwqw֪QV"Mݶg}a5t}ive۵Bc~K<ŶުD5٣Tޮd[o|w0%YMW'l3^eSx"Dy,%_ :fF/e\>lDܜc+9YU ?S+ZW^86-`#{\6D,UQnQbAyZwDb E-7^1P]8)l)"w57ςf+kB^\K43JQ6zNM.ǂT_qP'Q4ht+';HfZӁp~haPi޾ͫP(rO9}N:`;U ]CptB' ̛tm/$,]cj`r,{]C Ko+cǒo&cCp(q` LySU { L3zXyJi!oҵp6k5D#,Srq؎理gבit@$xWXz @[i|I>9<(q&4g䝂yυ~ $TkכgS=C,nWCʫ!yx\«fU粖"(Y9ĖW^e'2F ]%Aqv0| !?e637/A"i}&@!,eyj +jF4#`! Py>fΔFMjE`*lZ,"wK) |4߰rK+l-YT: /# VL%--qGfhPCо7Ԕ, &JyMZ ^`LY K6%!j04hspKLh5}MaeNUZ'D*\s&ڼC"էhJj9Hj#msX&De v(RA,Lv7XؤVS;=tMf۲(rAw>Gw\3sǼݱ+E=Ick4cNR#T?| x:KvL"4kho4KA'*QaFte;Evh)(s퍗T C%?T;*il?B&în"YF%iɱDTQ&wNd!N]&뮒HjUDi~l}.7+dHU*YJ2*id.yIꭁU%pt̛JvWIM#7K!7!-4}nhc1< *e, K!뮐K }86}K>vc?*2ou;_ PV'qe1}iKP 4msl>lgՅQ<uq⮊Ks,IļPD.&@\AMj|Y:&m&W4{W(*'%^.tWfh8^+yYā"0γԗsW)|RK33 +dRxV!o٣Ԩ*9u}.VzJq4W*9vƫ s|Zf0nqwfUΓ͸T^Rbj.IT-]YD\"e R鋋OBsF|5pF9S;>};mV%ttbLsUGCmG(h^OþUG*l.EKLwdڄ29|.:crbz=7,^adhAr4"^}+JdgX /H%zfG3i<TaPΝ;Hهa?iqDKr̽*pL6u(V!,ylvi:, ؜ g~^KJ !x01nBc~ y=P\cBpّ1GVTA]a8]KZZXtދ[v +&D'ehodfA_G5 #UIGO8t4Y/sj1g|*+t"KP4Nd(OT2S$Mhn'G\:U\6#0D8DgmK=cYO.Dz-iɏF6=X\d 9* +JO"kA¡' Hg~-PJzJIǯs>H&J$[CN>VI\)iYɂ` fD3+**=PbIzp=$20<;cXr8{XB)Hl};%$m+1'aZpεkgzfN#sԜYaok@"*gJkuǭ8~q$/PTT +`3@s 8#/PJIƹ1 LnIgFSP4Id~+M2pd'Js\dn<eD1^ܑ07JZ2f#n6.C\b1KO.$ݗ]/:htY|n- '+ZEeVzbEbޜ|*БD'%=u @/"^%\BMwf+'Ϛ'jy6X]|ùY}e,jx{ djF;V (},,|HbW +d5>h ]t]s>mCׯ E{[=۬QCrdnCUA?XO9#9f0-z4W>Qg)diisO9z"3q@w? [(ߝ 8&p77=CQ*AC>Qؕǩd*~j7 Wۄt黜2}{!vUex]eV4II4ڄsB_ "_2C9 b]|OwE1ɹA4>L4#zM%E!K]`$L "#bgP/ttm!eA}i)+E7`ӦXT5Ҹ\4P ˔%0)l9]lZ\r(8ow.eZ&Tmx)G4Z2w8>I.I`)s.gU E9"(]j+.yb `})ד5X{`^<4g>쏀e;'B{!lcX3`~奄_|<ߓ7ޓu~fgRe:}p%FlcǜG> ǿxU?_/%:cK<E)!PCvd\KeYB>@Bɝ)Y朆GIgn5v]іe|Ȋ%%DSȌ\JŧȤ*A9Di5aAR7䎽uSG|[xJ.x<1Lm$i +ϔ+!n.XEoQY*=Eɞ:wmSU!Z.FfF,~"iAR._JSN^c.Ȳ;GYA\o+eulS_"s0xֺ|V"C̚ __@]%3XJ@50$9Vd$^M BL.2N[s̺9V$|/sla\uv[n#B׌3 N`Ll ojJ-?8D?QLH6d n8Dn@(\vCGR->3frG$FtO:}/Ŀaٿ'ˎz_LsD _Pt=@ǩq0sDv]~{EHbvO+-<#KkwWJ^98kbBQy^sQuJզt9mTR`0bW9q,FT=ɸ4`Ol9 kS3bwB^Sm&70>FV9y>YE~e 00EETFjZp+qu@+/͖u>򎛏tL@uT63EJYu?&n4N?%Ԃ] eY+N{pT>cn#XWh>(4n%z zJDmf=":GNMYZ4!i1%!E up-iRsHdo-oIaj)tqud]WN>Bc< X9mRL(zˆk2si:WmRQ4inɤpúr&ƊY8S u9|1D'D-JrWrB=Ѳع+J$-Zca6ޖ%}v'#=RA3#2n_9((`*@}YiYJIhUpJM +&vRvvb drP m'5KU\rsY[^7&N` |sJ9PhVMGhr\<f#! &[g'<_F'g9HfZmIдhBu66Nqm(ʂ8xrZ;E$ U,9&(vǸ4O̙3FJ ZQE`X)bmvmUѕgбciް͠hЈ"6_Qɟi'ЃSmݍFd3ؿmc{)s=fǣ7ãڂ rϿ^Dk_x2ի'yϻW`~d},(Py +TVP!M\Dm2Swd.$W`9~~)J4zHC6 +Q?nac`lIr_>@,B _Ǒ8^ҹ?ڗYܿY[稠3Xl-HAyi+,#gu`bAۏV΅Hd4\?ʰ҂eX UFhדN +m}$Llz,V+_ [m,zJ`okXgeQ +$̼qz E:8t~F>}5o,g0\Vc@:3QWb5,+_z`~W1Vi@xU!AЊ4_Y\vZHJ;}8Dbv eU%LED6`",njwvuIHoLTtG@wAdA;.NUH;E,_o+Os!Jw%;\""-S.~X鈑#p6_{||NMtw$?$v#NptAU+sz͊w E`} +s:"Ģl}Ɉ!Ԣ:J׮T?jKCc/T]K UE9{9)z(a4:V3> !i)A"V"[;i(X]8LS`45AD6Xsi0dK*8~x{]W7mOl{쑲N{\Vl~=H9?QW3ۿ.?"أ?1g;SW}g/9n:? +}z?wm_owɪAϫg[y9?穟~Nz~Hx~LA#NbR1YTI.)]m3-nK>PvXRdQɫ]ŽZ:~AfqےR,x0X׺,.{JmU_Å׋pܷ[)$oe諱Inᦧv mVs%@M/ ɁC [vuk 7WI)C cyRUMVpG`Q6# ]f$aG QqVͷhRu s8BK*ٳ\PMW.s$Pӗg/޾aA)'ghjmb7Xd#X[ zRֺDr2L·;E#9ԽY{t7et?Xi@X#H:O]qrCwc=^r &>|G"]FXuӧ:eY4_lUi(kT!]zەt%GEZ$Mm9 UL͙Iql=|Z9q9lYlj&aso%nod`AY4ۏL?R?L?.@6ݔ%K"YU<%QPVЃ}hf63mj;HHtreMeNߐWfpwEcĒ])U\dr5JEN"B _Odb+, /ѐbB$1{&l~p_:t*jYNv$:p< IQ@5u+*J\!DUv[zWB3nnlT]=EmV>Cϭ=i?龴y܉x§f }C۔9 {r A?o=?[=-0-q[}C18ވb]+ziF"'j\B*|gI$ѽBw @>yv\{U0'Jz1) l4Ugsoc˺_`8uI+Q3F9lfKˢ<^s,Jw]̋|>]i|6<Cu/Am``~ŖmlX%TSܔj}_'(-vnb+Rc)7r?7GYX~'@Mxع3ED9m2gxc̋"?֙4SҼyO__=̏id=O9#.knʧ2B--ׁT+M)Wi n!ٚfyW)6FI(Z69bcOmS|Lh5 _h}j1)}s3oO +yD8ǟG;eq9EgY 9'h؃@ iԮ|:T Ȥmhةo19ɎI*FiI_j2UhI7UW5Cœ*D~S;ç^Ъ:^Ur tW/ugcw!~wtwYUǕ}cJfd`2NWێ }ﯨ(ݥ7 ؇ICCJ;6zcTP!y^XuO)r<*GpWd*6c>†+!$]gP҃2g Q rϪ.NEjT?aT +W% *r\v"WAGfBԅԃFr12]M}E^Z˗N,e6nrq執A&{Md]ωy9Зn='ܾphjX7$~i Jk_n? :ڏrnj7jiʂS?tiiH}Axi!?xnxw2/~Mpd4uM|)TK_RCB@ #!6w9v{z8N)@"^,FÞG3g/ GAS +aN8.jn;2i4#7s<8jԐh Db_kdL؊V`i N" ]ʍϒB,6GS +SvuȖ[4IC:5d!m֞`VJ^\܂|O|T֩5o'S"xǴQaTG=@9&ܮ=` NjĮ?}Plp!#LY=-kYI m+ (kTKe׹p İYQ:/{v çRRv+(8]),][$>* YI,8)ߛlD D0JiXNj+:ᐮWBqsVnǼN~j`] 訛ѵ͚-m]L}DeRꙤ 6Q>vm:U \3K.Y~ HLt{D9z]VAs`J^%%K",+, jmɰMwʶRqeGUM5Cg WQ 86p &۠/+׺ʶ慺1P_aPSA'fR8Hj=5Š*]sz=@9IhXbz&>ͱڗD +){R f1R.D R[!aWML\f9=X#Y[w]B8wyqЇ@&//=]4=9H:ߓݾ)9Xށk,XLKOؐa) ?caXR0 A-Lq#w  w0!$Ncىm!M0mɻt- G1G7^SeF/BIȽF +&Af6ˤa +nH/x}UeNRqIRUJx¶xS4/zŪ*^o(7E&K+۽W :# LE<ò\m9@ +yNuo`x g0~HjX +H={љ;|A,{~z^,r}9SLR;)xZxOtD 5?`Xy `F0r)H& + ֍H04LT4p"j(BB%JP@lP ,+]?FW"@ ;|XYKM/+; ^,UY( ނ2kW=hlavX@/q`@C ھ{PAQȕES4F/FW[|ag2_%! Uz<3jdzST -:DX t~BA|? HT={V>=O`z.*nr59B+e7#_uf E:G_!rGШ" b&C7Wo;9^?#WLNBtyS傂%c5..ӅEa$zNs)0 TLf`S榁7L &@SePQz'q4hkj&q2 ʅZTa$^A$`|gd_]=>$a+r<ץduXŚMs3en;#爎+C_\^b]1i.rhv5(Ae)22#5"NBuhd4lO:]Wh\cd{|"ƛlr |OvoaIsF_ʫ%r ?OY̢}3"(ɔdY" đ _I_fBiE?&WgGA_ŞaV˂i*JEe^5.m@9hwYy%mw9- +:RK)E /{\bUg#tqyWro)BFw3( n6ÙmC fZ8 N-&/fqe)ȕ(x%."*ClA휙nrڐ Sǣ뮔hKٍIw[ J +,uurD@}Aq!sU`QiĻH}&) PwwIDZsH{DMI:aF kU{Ĕlol0t\yu09-$잕/U_(Ia;~y#OmpQQ:J  +_؂ đ H.]ͳkPh#i^H¶J~'6 IU/bhpЪd\oDFTY`V2 +KWVL@ PԆ"OH-Ո'jL#ANz$ɔ)=oUuʨ<dƅAtȠнjPOTs$q>Y qvvw"mM0" lI` +}g)bׄV@2ǐ(7l ¹.Nnϛ~t'"Z4@5QO11%^q2+ǡGxTGRIq?H\ٿ Y6b5|0N@ 0 7 ji6ΐu=&DuR*eϸ%vf7opHA0OJ5,0]sr#ZIp)tw10GC \Kj2G^[^O>nO +8듲lJ\d+R8@MJ,𷺥lfAVq89Gqi~ [oϥ# *^K;ǧZ_Zq3{_ h60DG/;<~|qVMYf.c3vQJJ#AԔ#|Qaj+ ڮۓp[pݸk΋?, 7l(| +V(] +)I-!嶬ACzafD(NQSsQO(kv$Ƕ:Ǝgaϝfgڸi.٬qCM˾<?]ENVT3IfaO̊JS1N9r Ʉ&Z:. <6/:D7NH F^,a/ҖE>Y'CQ1 D4T0(>i0qt%LE?}+' 3FAcl 8Qt]J'N]'eNK + ܺS~74zhJpQΣd%TuSp/  ΕȿF! Ku6g۾T\C qo>QrVSCMԇC&H;Lއ6^L6@5ɘ*Rx%ѻPѲ%i5 ǧU3X45)&wLZqב%:v G7cO\nz+w%ݐ|~t򀳜PL='H k0{][]>{JX׍f`TqUFϥy°XHU1"me%3_]55ӿ02t[h 3oyf9ism/Ž^3p33_T?Zw8qww_o_:W;w2}~}Nj̶111=/>̶BW"[ϙmC>{a5&4vǷ3'OkͶǟYee!ޣ}Ze/wX;ChYѫB 43۶uᚶ5.Qv#s- }2vZi;ifX(=唶9{`5\s}ZIAңU\{OOmG}ja  aۤn K+;>fҷC+'۵~6:UfR‚JEgoy|˝tS>D_SX eySyҲnte7F|`3D[aЎWq%ٲSSY'KLOuI{Her#[e4Tfŭ,*Mg101q&xff/Blѥ0s 5 `8&Vd~SM!J%b,ߨ,@8#8'l=#Pb}.F+m% +zvxX75-IxG@яݘR\.䈐9Y*槲Kn!مٖ'[o~q$+@(_c0z] b. Dz<_wbID'dy! 7U?s ,nåJT'mzCUJrys!NTWP1!ئ +`3/M# +hMp,$.pqʝFS1{ZP+i|\Ȁқ>Ex<—EV(/ӭۨσ]G 0 fږ@c{oo}bY\xNk4ĘR C4!F9\&C5(CB]n}=?*N#ո>r'ӛ_P jBSMjG{"@? +%J# N⚺y$Op47TwJl$?Z +Ȭ6˪4  <@WH]AYeQ.%֣ʖF Wnyo>I[jMw7k}}o5:1Ě-_ y]ҙhT<R#:Fw>_)~YtεY1^6*㐖mשIҬ>EJQ7) I+=t +ti5OZ}ocvI^eIFv/Bm\^EՅZvSB0W6Ԫ1߆8zmp\H{^gw  y~l/J ӽUqwސY}]GDP;tV~sP#Qb~]Q{G44|T| =QKe)ՠ3] ze|tsVz/dM7=jCw[A19=QcUg8쐚b mϺS;y^& ~a؞dSA UH aT!HoVxP|ڸ}zlhxj\NNE EH6EO>2\Gw3D@/G(gʇVI[5AVB"VvDY)Ij}E{{L~1Xg.ɡ8XCB#[uַVo.o&+a .5i׷p]QGDmbKeœZV#$12BT1Y] mzDSl<MEtQqQn$ +؍q9xdb#JnKeG.tuRz#aW]2p/]֊췞6]cքأ{ʺGxٽqv jA%Td%V]㓮p$m8v-G:F23TzR0=K2Uo GVJdZ]#YWtc|*O.'J0b㌥Pѝ j &Jw +Bh+CL1tjo6V,d8X0'̱i'eθ/CWds0eQ +*|'c9Ԣ2!'a](DŽ"^#3Ty22uY¬5a5*+ +fv@fE,Eտ%.?tFeh?ȖlBr2y8-~=G@V  &PG0(e~ʗJE"w6xb63*uxo! 5Xյ9٤Q`K /&u|QF2MplmѢU~|Sj$k T0#~Y.G H=s9[F^i}-6}KL*2ꥭR\`Gd ʹ.喵| Yp2z?˟lri܏TI7ԝDx8lu; qƂ>.yf`6%QC +Fc3c{Gz -M7--)^?j{*(G#CDxEtC{$ ⑳ +| `ԖȮj Vǂ]~DVŭ]leK)Z+Zl\!|yDҺstBw -1ԋbެeAIBVlkt8m"#Yc$ ;꣥NZ=*+I'd5uZP` 1$I ݝVẌC*q|Jjnx>=HIHk蛆 [X> !$ &2>I8swBΡfʱfps낾BY7Vim~oG4U\c[p΋gP˝-8neH=1mY1Tm D\eef߸5Abii9]Fd|p!o݅ +VJ%!o}p8g•5z{ZEUѻ4%?Q-گQ)^H\'}F鸟g2MpWɥfMUS}U|b}pH^K0=P7nhM,n4Q:5^}olѹ\Fz7Ŷ)apD3+N+@zjx,=ZP _зգ'v JR]>}.S8yzAMius'WhFC\ ҬՎ`Hg4݇`,ݯ(&SPwGKY1|,0 <эӢw)$u3&w|~nE!%BA? N@%JУm,9c#d>5D.%KeD#)VV+c̵E))R/؋s25!rn"*0Xu⁝#%E п0xIMM(Gm/9`Yύvv iOtaQqR*=х8Ý)}ư!V7s wa:mn*%A}N-$S:PW"|c֪T 3UH㠅V++f*NTU٘*R:y;i:~r5d׹"OU1=J'SU˓|Q  3]{ + 0V"n&$NC\B7b+ CkAྵid_ kxթÚ+Uח6V(D4HDJN +MR%:dMM1eA4R)f $GLW}$GR?8ǘeZ<ΖE [N7#ILKM+J:vKeL@5i8)#ܕKgHw?Bk-hd] y5GDx(Ѩ&Ĩ*њz͏ՐMvƴ<+UZz-t*亪*p'1=seȸ=&iW-pk 4ԄJ˄ $ +yBt$r\k{L4#*IGc-= 2/7`~4SxЗhC,`ۭzÿUϓ>[hߙ5{~@^;юjqfyWc C!r`MY9\U2XO/6Ý K;T1VJSRNLE2K%\y>kSmH4ײfl_;Z2q,h:j&{&07]t6Y!T +. +uTUr+m ]n3oa7~gY +E% 4Ao:[6pbQ (Y ?_t 93]:)w$68#w-g]dT;~ueyIoPf9dB¨D!w0y#[PDf'Xl͞\m?s2:+Fۍiz?Vȸe/6bHM)Ew=m{ma6[gҩυ@1$&;0>o=^Gz+k%72滊ǪW pݿ]xSZx%`MԈpt,ꅛϛ\dqhᣤbYYJpAГv@iȔnTeRHDq8ؒDԓ~AD7DE> J>$|Qɳo$hs܉]vy ztKJHn1-’sݑ?hBxcڃ6@WK8 )|"%Kyzu?*Na@C2YL*7Xv# ͹l).'=b꒏8"[vh@c_8t*xGӺrRo(hYYWF4&5k)vjj] 9(7`5,A(xrq ᅗ:_klYDp ь o,g.r:]#2G7.M8T& nn}U'zVϷFh*BW?6eVAI3My}g=eOd tٰuf;!'8CHna!F +*xAb};GL Y|,BϞ#ps>x;\j4HF 3n t%V**xdJ[aַي\ s% &PhKʎvv[mE%5} +|MJq|k'DNKa熥 +^oԇPOR^IpP/厌En +zBэjzӇӰ Z° ( +ȹ֜nwg܏>:cMClOR$@;@rt=)`Tb/0GYH#oc_sT㰵$z&D<-ᬡ`8t78q% +7 m\Z,xBei,Ak~z!ѝ̺gS0I_-5s5$iq%SC46ޮՄ?YADwt+'lnY\*>yU&.q %n >~=2.mU7n}bWlLeg$/f;zͮk\C_Wa{5blvv' 8?O GSP.sP,`ڗ;ؑKZm!l'0."Onyw/'=y=:z "14&N`fMљR&np,Eʙe$N#9~] +% h0H]eIKLHvʽ# +숋 j g% 'CMgNJtyPQ7V%oV|uT#QJ{M1&o%ZyIBЙ'V 7׭oTa ln5m {+h""b×`Ҥά|Y}#]'lJo/#.=rVK#`p>]HhO"O1x3=#wBJ^{L~+4>(Bu=n(!hW/k㗖 9mlxϘ2;Ti[+ҾmY-?vKX^&.<2{ֵ0WvjuްݓNujiYrw)QV*5jZ]OQ5ѐ2/j|v, ض{}v8jW۠ f /@ P.=~s5@p r޵yIil0J2,ْĬ[jb*9w]7L{Ŧd_oh VsPmld<&*)&ܐW%P  1ԊIZ.0琼5>Ǹ4rۼ'AG7>d:x6bOULk%<]aS$)IpHOW8"\b2@>شjC_ũ]S>lKIݥkvT&)Y=Z+;x3$X8#  j1N5jO &"fH'JK^o1w+rd2Fsn<<7%{f| +JH&p$B!$srA,L/`ir{KܓrS:!-ӋvK{@$܍k{̆FyABMưBvr["qFG)% nfu-2ʇ, c0dg("X57/OlnJ$4exQg"HG74!:5V kFb V8;n%rmܬS}PR߉&H<=H-TvR_#6)-~灲xMnQ (ggK/']Ih3?CIQ(FQ"5*26+Q/ؽlhJMe4hB(g Nbf6Wb9$&s(b +ީqdQjDWP|r9b;LAҫ\c]FZx*U?ӷ:ĻTꕖPuA954J Xg:KC(s gV(< hϠHg-M1MR~y`\j V\I~B8|w$)+ͰwS+hdaRE0|²ʅU=x8iK6FQg}п=І`MSpV1#eǸ1~&0uzlUDsS7{ u)0Q2 uR:wd`HlcIӉ#|lId*"7"].TCMұ6kuu֎眞`yd7 ÈYH{V5z23%Bڝey5LXu֧-S +?*e'5ĜXϗXf7u ~0 <8pn4w"+| %I=li.jg&Xz<29t!pt+Da~~% ,7,zRu,Lӭ8=#jqPU[|,>A-;xݕ3?[6-'ԥ\7E{ vMHS +BP w<P>΃8 M`ʴ9HHδ} 4әV-wN:qȴ\XhΧ<昴%W%wNh!A#?t]m"FR?Yd򆆦,薘t*ٲC_A^ 7Kv& 5e V2He;^M{:7WLMߪPs+o*`H2NK N;NP]|I\Z:uvƷtFU~JQfgjʋ3l%KBt}!A惫¬C '>:+|Z}gS/C8y&+-u%ĺ,%9wDI>8'H +zIEuS?jۄ>+ qqqRU¡ZB{cuW0 +}WP>>O4kW!ߵsiȍ +@iHAbYgRrp#eќO1)L}jqSkWrJ:k CkO#U", Gw_g KSC pj;^h~T%`lԟ a=4_0!%S2ďm,GR:ѻk Jaf&dCCt} C0ckw(jzLJ)\;٤:߶3#pөv=<!@ICe0pٛdӮM& X!0IOwl ÄbMfw֍#~|7B)@OZhVhU~9{| +!vqĘlc4UL|.bQ [$YlQf")_ea_bV\B@p"4yKdw{z'&.@⽿)`D .ZH2߂K0 wMiE|!%Jq&gkFEC{~{E@ӆZ6q.s:I|}RLow fkg&.$ O.B~p|88dU{Ə/YvCl0 6n*r TO]\r a)}f + q@{];bz}$t>j:OWl2 ~ՉD(7?f4]α.s: )&Wq\tUFª.OA=+/nIfِײ# uüb73 +BUΏ=>M-vkq;7fPR1A; _γSϧxZ-U(%F_ /MӅ,ǫ +%}>oqq@̅ Is.x%.^{FFؓ+#7#w'Mw >q+/ba +=xN̚jEӄ>ГSY79՟,09({ĂwOcjXH(;?$rrƠe;2ylf h3vFb- +}lU v,Zb((m(?Xi:V:52#uޞu+#dY`ujJ6傪|iL%g~ nTo/[<R5 tM ,d<`5|2[d1}9V/_X,JXhDVaLEq!{?|UfKљEm)|1O2ny]B4wYE%Pzd$4Ս⮨Pr2_SI7?畎YsdF)JkkQ U8+z/W5*X.l;dR]"$0&0^Y$[nG{r*<yqS&&_ Vo@eռ}C'!~)G+FF«Q}Gc. DZD!* +l^N8w7_#RVB~28blV +6·:V(r:.F8[?W~]PˊY@`XQ2CVk 1iDG` ;;jV/,:ȒLZёZitt!y]C PSDS$9n8y g42RPĮO-;r8S4 ǤZ84-O0 E@jBD6$Lt /?#\6G&OiY$FB F(x $ +'qaMvh׮6az<3I(dNJ~Vڕ/UkBF<*QjѠ8F}DT9M+پ`k?|-} 6 ML'&s:k]KG#eZ j[VkȫrµHk X%kh} h4r^z.-`Z".=l\Ϣ~j ;i\@l5Fz`M%wT;HmVwEM%DDQhJUFM{U-]0КI팂kzƤ~>j^2=ۮEU+׌myPj=&QEVT]&p";>Uv6~uLi_&ߕb HrS]zdp7ai?,M ѵi#Yb@Lއ6E[0'['!Zu7=.9dTSЀc'd]猼K8_ +}l,%Y7ы1G +kYjօNeoKf@ÌWT cÑ2# TVa=^^空/OP.)?k')4&bQF"R.Όyu< ZgHe۩le6E% QCTAZ' OQ5:ϱ|c \8S\n*\l] -uu,&"(ompmf /^\0I8s(. lm~.:n[tɒ9x<[@,+Vu(mfT^0 .H+=Okj,dg@_>d:K/D +?2Hc b%lVlM;!+4Ώպ噅ϓ+v)zol.Fulw1NˡIj1̋nG$E(>FbՁG[)+M!6zR6p@!:<{P+s^y'i!a4@ґӭ[i2Usq9qy:ꡩxuۯ;gwEgcye#jޛ1dyR +gN.ӝ(UpnlO]/\ayJLCM@p4L#:qux.i;>qԸE9|o\t.b5qU4lKG[}"eZk#īxaOq_t\}\\E\Rw%4$ٍAV]m\sk 2J* 5p='-ݸΒ/Q$ӏ,<,3÷BM"utANb[Ol:z -x}WuAh8v\[%y_\^EKF! ZHcXY @5c +MA}I&%/ e\*Eq+t_(;m;/;/HϜ!/ rZQ8GJx PY +QJAqʹ`Va2boG緃 +cǒچ|+cDO-o"_Ed B]ߓ{gFGWj#qNJDjoǸ^5'Gŗ{n+ }]}FR$$:4w:Y 8TGCw8T >uHih96T~GwVe;h(sֈ6jUعܠ0,;tRijkpᯱyXg,t}jjuVODe ps&gUU,Ԝr\hz8|ʻZRD4_^E /dҕojaPt陮:`\kgw,^},tqṚ8±-.$VT +ž 3$sf%[Si,leeXǼ=޻ PWۉER𯐶qHMV嚂^(G=xb(MNLO"o;B<7Z@_d.f`%رLb+8y(u*g1ʉ*r=@ gx v=ɛ&Sw;?0aDt&(5L"%s+QB +4l_tmsrG9gH\1l>o1;uP$A\Eݎn*ꎋFG=QbQ^ BKv Miyj睭fSt@gUBK˻.e|3`G2ZU|e4\G$S!O=-x~IrJj&:. W3v99*WnA&փN2 6/ӠBo-n#۾ħ ^2"V^|*S>]GuUY{MA;4^jΐ/jIr\ǁ>/Pm1IrE%e@pL}5Swyb%VTO,tBbdL2@NC8}N]P BX,.1)"6[u5="qx4 I. <5*ƁeEz/ׁ0aXT#%:O.H;FU|GP jgK5KV PmnݳN ǟ&A7_'R!0 +쳧IK`X$0߬&?c#bt^dpX>#Aҏ%"fU=(Hx?L47h W|K֙_RuJ4n"[6Ê}`Ag@x;&Eg6w@E@ﱓ]Ř-1 ښ!,éQ/"&UkxƤe)t$G|"$Xܘ)_tknLϖv\\xnvrp7AVrղ{󀹁a=j6Qo8N܏V/ga)iP][CteT\]Swp/=c[m;Yԣh/y;hKETzwge'7re|6&]zɬ1v?tubMB4sBvDc?ԁ,c]ډm7=΄ QY(:^!u7^Lv_S-}@(ކbEL*ƚ\AG?W]s,޻O(egmҾ^mR%Kfp=`9>S-9SS/hc(?/ϣZ=y9v@34KiA@] #/4H^$Ӗ^KD6թoK κ͌i #S\>+V'I0V14Ϙr/xC6g#<8M'K,YYI6Yv'# Bޢ4vG{y?:',KސDyDn%_e.7"Ech1@;[:el0̔6Pg %vh3z~zd.ߏ?$X~u1PW uJ#V0&O*v)Pq"' +rkjte=8 +"_IR,Je{I};>e-rl}d@+Q&IȔ;c2@CXgpfGtp*T} 2ސ=p:p}Sd,ɌN9T D4)x?q?3ޯmLXJTo"է0.K!F:Yf/Mɑq~߂OE]aAx,C<ȝ"GvJOZM{LvGkP2?GVpuU]y1~qf<\e4`ۭh:Xe[ihZHZ5c: 91qDa"i<_и46D藙BpyW~莐lU,-. UiB'j3^ Wl4Y6}׹TcVx̅pRެڬ.jڲ0_ ^>7/I,c1IKӠLA t+:*4, t K{FrsJ"BU+#\XOI7ۮuaqK0Zޱ &&X#5N!>ؑ mT|*0,jIFc#$?LC[-R{Vת_quӫ 7ۮlNu􇧢r, +#h ݈&ђ?aaZH'5@ўG{e/OڭT.NI\bVX0+3K^3dHc= +T+?~Eߑw\`}ʘ&ʷ?l28u r]x"58J'GڻnYM0(BLN,w~{ɳ&RPɊ26rnL'cqfդ gx`/yftxYui;\qp VKrc*=6;|VM-w:prInz {4ڵsl@%u0n¹;= 2dz#'=ٵ5rȡ^ >C+8jx`옴XoG?x ;30q^{YU+Qu҂r +uyJt/?%A!Rt`59Uj˝!I \߶%i;S8CƵuK?y-RV<+&o} 7VJ=t &Xc O3:aW%dcEG<oqr76sN2Ӑvws1cMpFђ|mRb*ƺa 1iP3_sgtO~ľ ;F4bK~e1cmАi\kp#+JM=]SK[);ͥPӸ=b!PSU1?nzXSnxSAog22s6e{j崛U#2š.]f~1~) + ,R <ѬBf׎yc"Q%, OCC}LK,TdZoD* W\` "Vc@sN1,u(!jOk18S:_5,1&Gy\޺XsD1d|NYRP`V㾭T.#ks.p'uV)`aM![]DG vI~_N} 6JcE!`^q|xg:o*CQ_GS-wX9*R͜iõ) mwy∮hA'=#M|if=yd.Dhj2M*85]sDW+I ۴]aճ|Р7u3MzժeCWkAVېIkmZ}A%q\'q*J94h/x=;W-M蟻e/e<i/n _9ߏzhߵ#:{<ߴSVCyg>;i\SNBS=' H\0f9m䇏%Vm_%h:z߲V +#V˓i]~ oYz +o'AhaUJх8~`ff' LnXERxVAh;ɟ%yU8>MR"PB9I`ў|g짊}< g֮'*Q[Y bTRA[n6eptWn&)G +|V 4rqMRk0cjgζR J=uVGݏ  +EV:VJ'Ԩ硂{O0 :QFɳ; 16]/F.~Y4ä́m앥aWrC%NӀ5w +<QJj灿2,ek} Uj*G@v]51SFcr"B)B6@o|kk|Zb#KhtbQpWؓw-0HX{y^T'{H(hW4 9+ѳX$\U +!(kF)t(M}6XS+n4g[ZS8ƈ߄?,}JY-)leE@bL_<9 h9\ tH`Du'˳Ru9wX^ETB'm]XW.0ćI&0AufaELœ~E:U$(+ŵꤐ ܡo秞\ ۭa4r,m^m QG`:31%PȮ:[MR" Kq +㈨(1Z>-h*EݥҞ_׻X%iv+NRI Xc5g_iĪk|qղgΣvV,yCG# %zM2dmk[m9K-(U"֪(U$FZ8]ϔk.x_iîAZQ]B$Fʙ,4]錪 /j^bH/y_f.ںVZJJ>Ũ,jr=HUv @<9M(^SY^C8 C+7\q$K$ +HP1wK/Ma-w׬z|y t:BUA`꾖tr>VOK^CH !z1ZJq2H5* +^;O݉eIWQU@kT&A4N Fa'.axJ[14,Db#F2eP^bQ bGapx, lSwR޼Ǽj$\ ܜK + +ZඨĄ`IٍE l<(CHX~e*zZcgMP +N MFW;JU)7I&v,C *e<3/?t+6c宰V n&jC0.șW$!Ջs6Jv+I2޴CY ${A5̞yf/:iaX#ט$S3~@6Mp+AQ46N9 ? 4iPՒ,>'1{?nt0#̎X |_(V/NCMUufGck3TPB1mD+Mȿ(>@slX鰮oU.KG!Fߚ ?yg[pD ׯhh`hi 8fAuqU`K6gId٠k^bgk7 +z[9@,?W9?9V_IkCNJV Mh&(jHPvE*JpU( +9p|e)kE ; +()Vhi(@hBpBĹic$8WN*Ҝ>6p>rJ;`aKLk6F3"tx^M/-mheL(Ѕ5rx-ZrB5<66dH9pe7Ul[vⲆHrEhPO@+.*_W@KnJ yveS[ b~K'=V<*>H6:hٚvd*h +{{VƵ}P0 + *m`uqfzӕELKM؀gL^˂\*b#wnj6VkĐ_eqZqpέx5?oّgc߹Q݇z -im,=h1JsI:O5\'li#k Y5qȗt B.i70!wdhy;&JBVczaB47\K5o];<`VX솨3i!pio +ޛlL7^8#1 կ۱ryxBMpUsy\KV(@ۛF'e CfO= MbC= MɺZrNHpgEO m}41'UEoT)@ 6wYǺ(#ڱFFh"-GQ*x qhL 6̋SE)bb]廿helV.ESp%!{Lq#;vP0xޠG}I0kQ_=tz31fYu@]D mv9({Þ;JW+<8y2{ENdf >8v~= \6lmEZ21?xafEu/8G_m'.ݒ(fE\&&ٮepΩiޘ `}MV8$?kki\lj9q#db[Y!#O ;dc dg?(db?B-8L6>YZ?s6a0쎫}m5;;iZ"z^VZSۀH{\A;ӱG*WCO0Q0yu;2k%R|@2[]YnUfpjhKNdIEz0KW,$9 F^e@I%Q1Ycfx}:&hFVZ9ea{J5l +ltjpȔpR_δ^sh4&nzZ%kʢMMWMIg(f?2w}_8ce 3jLu +OjsKީR`vmr^n;߳Fڪa0M'wNtF D`uP8 vUJP<>73ioa HK%14ldAB!m,p9mF8ZEu8ޅuG%yXrK?Ŵ1q}F:0S ,}+Z0׆#{Iq7/ ӫPmdFWz2U'}4jԛbCXJD60JH +c& +ZCGj^vWf՗[e <&Qe_ ~(oSs\J_zC'z$Wt'pw.2syN3.~w/l=a\Tk* S;8|% Lj' r r•˺ڒR 6 ϏoO 4Bx4f<-4n?lk(_C^88I,HF+.=\ !DdTM%9o$u$Yc̶xk +m b)/  W1@t%VJ5JESp(?3 0!yT-6uFىX  +ٸd^}q)޺[X68 [!BϗMwBÒv;ʓ?C,J~"<$@d)b`f]Jnɋ;y,U4y!٣?`?U]1#KQDȬW4 2ZRRR& "䭱K>0ýir+nx"h[2x*VNGW Ep3_ 5a0Pʙ*l!dtB +ldm-6߫j݊`p*uq3OV񳹭<}Os·mYmF=["76xl{RpQϏ ^MOdw%62SJ^CcYaV2bs)wJ.t*-6bEGX}d!)q2vc'Uk3Aye~xǗ< ϖi{Du]?JZ**`^[w}oŸz.3i@2v6MefO2([+wyW鹟=ۙ;ng3 5|U5Q<h䞘\gb ΛAà{ihx-h7x!$4Z>}a!еr$!MhpCmګ6b%g2%-p&g誚PMC0d&a?1 Zr LAe}H>q@&c{D&fiS\'xJZRRazq奋d#iQi 5r6=ςs/lj4"c;$C`Q̐=Z[ۖ2^ h OB^vDC[ h 4S%%gy9x-s֊rUghy +_4j YCFǡ(*^/b)6A,~5M.6s,A8KHc'[0!Nt!+g AbWJbrnTWIz@<'^1 ANv,@9h;h }Ba#6GngڌsX ,.J zpbMLU2WFl!@7{k{)yړjjJVce>-A Pk9&> <(91׾-TGLKٿH\h3~PsytjEf'*v :_T>߰'3۟I7 tYO΅O'rv(Oڻ˵6"&||bS{' /H[MIq7ZRƛC1&;וty5KK3_Bd2j(* V~~epk5<--E Wzg"\,(7 flOk/ɄuGO)׀;\x"vò@ڦz"}ߌ<s z xiEx:a'G]q⍡"$D/~^0SkLPkOW6ԇhmkkqNVpW%ģ/1^7 ImbBrǥ%L,p(ۣXf?Yg ѻ)d[qa ۥqo]$.++?/;>7oSh}?^3[O +(?=6D XYK wVЃ4!/+kDNM ?Gync//B#W5q±}Lm0#xKFQBZVs^osK- +^̝zڒ8XΛT3}/uTIp]Pͅ[%dD01f|Aw Ns[a{N#5D,Ib=`8+‰fRTob;$p2,ϣt}zx}dž1\DqOПmKty7o70ZFB,!yzqm`L˳]oURykmʽޥйI[C$#[ !P\Sg +d^~T/ݩn6j|S8ǁboLP$(Y93npMঁ "Ma] fiBcU|N yo@kbt]z~׶o1TZ~?윦f{QgV;7zHZ;fs5n+Eh|Ux>0_ 4qjP31Qj]>b**"A§=4nGdH=4r蔥@ vq >w%Ucoŧa!J*OQE3ONj-6_<^Kd51y +}G6jkoV'#cxGV>g[kt.(]PGbN*؉#+jcr5*^*t LY!9zC+A'tPM$hU&B|݇]=̗`(V-P\P([mιc?ΊG7ϴITt6uR'?HrhƬn+rX%?: 9T w>2lQxI)lUxU |FCoGZ#r@qhaEFY6k54dY&mC‚E "LY ~8'7Ӯ +v-K^ϸ?X4<<K}?>߷v0)2w,a"2? +!LG֚.xiw,eidvsoS)A[;s +8"gv2rA +MkHѽQvE[!9aˌp{@pq{ˌݹ LLU\}>8gq:gw}~qQFO|`w|G 1e eP˯ʳJ oM(t&u˴b-tʱ*/e +3\Gnm1_H2Y/4jψM4&۾YW2n(U`Iăj4i IbqHdx+$v;^쳄6 +{Aee4xфŠf& E {@H OcNe[BBMlҚ&Zsj+x`aE[ eԍdafνYEwA`*{VNsQ%n3Pb7; dZWP'j{CʳCe2V]>Й0q 7;Nɝ|4lFL#JI &Sn~Z\xlݝ t'ipKN"M$0~ +,+y'."s(3ql j1P(͢6"Iʐ~´f&L ?oyTD\4ҘagtC9Of<ymeض\ed+&h+a rJ"-[OCpZ*ۿ|!;[c ?-"GDY +,/u; +-( {#JtgP;Y7~}>(I]io3TF4Z, +$ϟ7ēG^^[$V&\iûmVv0eQh6W948bsBU#5/WCwK=0\Z-|ym$yڙu=# G}/"c=W0HV! )7&$ CɨG*>絍L pP c3k#wU+Uk9_ 9i3iZ]mh`p0a5OmLe)`2OÔ6ZkEq &ĴE ]VЙ~s)pu8y2S.v3MV `WP:4> ae8@AF +*BQF-j=20hvTn_6:Go$`qf)WV" p"ajz4/0{N_EzU0q)O5:G[3[+V}>4c6Fjm쉌={E5H'o adkvBC,ea-D(9s@8t´Yc&Ch,VfAg;Swٴzp&8꩹m&J'63ڳaQ G<WB~SGuKy@;5U|?0l _=kvA-Z@΂a2Y.u!W6|GP2+F);Kb‰M=:6}$dă.2^{?IS^,ӴaEŴ,œQӷJ׸ -T3-ݏ[NWT8yӂ_ϸ`@>j +w/y~).1Um -vpm ۹a%dצNfg(rPmBA^PUbm WW>\洭s9i(&5½1oM9'9=2f ?7Rߩanrۀ&Rq@#\smSZ P@fΠnߣN$mщio>LHfH6b|:a/CYb3ktC8-aCZFGGk 1!7^%ʩC^mBz P>sOtz[ W*4ޫڦNvd+P_[U%X6M.gb;ۀm˖;[60K'k-m̐U(5@kdϛ+!"f@1|4;AUUq%qhM{2G6u?{Uu4.M}w LGcϮƼaWNMtMkwx+0S4^ +!nav)]W-wI[syœk/d~Bw3 ؑ77a|KҬ=LAbJO!ٳ[`! +nĀ`+C<76;MZ!$YN{iQ]n8pd6eS:)^w"'q{#:Jg3f+gkl֤5Z5I?@y1Cfx֕L~ C%V +vlG{]"擌F i ۣN9t=2ΟquwH\M|5!Y8l:Jb׮Q1I.XT$̈́q $-vLY0 sŀ"7(J|{O>C&[+ /d'76aRR=smg|6vҲ~/Nu 7әw M;O{d?.=y:o$9uơeǂB 66#_$?XĂbE2svuK +ख़-ycfW< ;™d4\!c|'Kh~n-/ Wp~cJ,FDФܕf+얇 +!(^-j-L@KO 3`a|^VZ]sv4ؘlyd]3SoT}A0XQjTkivb[hOf~kzc=D߆5I uG0yNc0k4ר-Q; 0\X|D_4Cp{33emPl'l w\S# DAgZ~5CjVB҄xqVrR~Jn/ވ6vɦGQX;JhѣھRZmi% -)F%a z={g7Z4^FǭVX.#վY7p0ieW-'@a!-{ .=BgjqQ2WH0o9u4Y;jST= y2ܥ*c~b(<@`Voצ*Τt:维t:I$p &nXҩƹS[ d@,@]aJ?^ՑiMe`OI~W,aX!5XyBfγuwθm'±V5V3='ȒK=cgr'(pyȬO{ϳ$炎qؐIvhIdI4$15b[ f, Ly˛iEaN"GG[mfF>]EytF+;7( nOAjgxf*']G>szٙT_j"J|4|- +e2ˉ|yCk3n417Gy<n%Wf!%L:$kĦ ~·Ht[q_'|A+ovpa…5Vvy4/>&PI =n}RL d̨saH5|qVO[%inb8eŸUi.&5㷨CÏˏJ4ta\gBqOogJ +왟/(l S;LՑ$yq1~9U Bs1lkVc8Ԏjr+,'=']j|o$XF'GR X4s#IOMlpRc͒~"I)°H2V$m⅀_Xks SρI[}c2;$ 汈Mgh) 0ӻ t3;>c7,: qt(͘3]{FtҶSFܴ.sPr { FK&=1_c,{[ <\ K#-Q6܍]Kl! 4s1oY/꧂'zߨ&C 0h T'/.PnzJF8tڼ`$zl9ʭECT?=% ~j72o e϶)q:K< mU  yrfx5t`27TNPiY8 [9UA9y(I3ˇ|F*e9gkȅOe6p#w_6F.ȮJ 8lilUȦ΅4s>tƻ*"A K ,@v% =6yѿ~V ڻw~]/Go; a_fzХ]Ξ,6^N>{ZJd4֒Z|EխO@(In*? =X]t2ꘓ ~$ /bɝ*X2$~ruZ+Luz M&(df+ hZehǙ>I|rw-?vdG>AM'fqp\z"0 Nzb"hW{#v,bQ▨Ow@>wAÄ?9_G s@Bˆa3Q>XN(a4ҫ]+Dk>!akX ׈)uĉDV" 3Pɿ'XW}^ d 4h-C@q`n'b(-pr':f[fPc8.{%s:iQa!1gȫIMbg= +alB ]Ȱ32qlr +މ"=LQ?w*q冁s +_ yw}h"YnOb-{5zX\`@Y / -!Ny{q.{&F"6M 6Gy %y`r Nssg]}QE$!@;.:uO߾+6JsR >|BBKzµ +E#zuZ-̃yf"m?~`Gv" >jF{ll"+jg0)h@_)C,齝uaOKL`k^  aƶ˖7IcgIBųrI-/ 5^egamk@%ɡ#Фʼnw̑_Κ906{?YlQ^&L&խEJ؜zXӹCҠ'$L#GSMI-Z +E4jy>Rr~do,{8 2uu(~9 #J<^f@{IZat^_V`E TuRk|KD/1T3 {t%kڸ!a$]6\% 2\N9}WNn#P__մ$90cxzg4k]P~'񵰸DZ^OW)Te@+hZ%a&,/kd]-\&Z;vniv K3 r1pnalfT2z-!B$DW&tjWDXUeI3w9[we?l"Lrqb+[DLI%Ӡ5 |`Cޖ ~MԶkDzRV\/أ8UBY?526IY8%v*\ڄ;v\%n{:n/wDjv' Q}e"ly% u|ԫ#&__RV\`,\c0`fzcϺB%nK-:*2PƇg?|&Dw1yx Av] =% +ף  +QZuo bisPkS1t'fO>ED åb'vr4^io]{v@3s53YNVw]aJg͠H$vyDG@&9|i `f|>1߬LMhUqlAܧezg SS[XK JhxNKB +oit'H,hi-BOWSb s%Y_v%f+ImQ3@^\RKra?Pj^-x][4LѺ +@{g9]>Eo>|6/k`\0&,y*4F.ԣa_Br ]r̈EÒ_U4lZQa0v ,7&nj$#]%% ]i"}n}wgC2UN(yZa3Q@v [(`y$?<&8tut^7jh>#&egBqQKGN]ۂm{Y%,C),|IqLt !ECY=y +EVOöaSYIeLJ5L> +ya? ?_Ab*Ous Q%[̧Q5fbkZ q.'-q˰:"tV GQ3-Kiifd,˖p\9pPpokk^VDy8``{u%.kk%W%bʜRYG](Z^qF$Dsqep*z:viP$R8똕y탄S ,fƘt?M{?d;:ՈA_J}Pfr'@f +R^㗳& BWxߊ7A7ʹtsl5:1mX'+T>%]eb49RMiZ7}fI ;"^/-}[Rl򔤜p?iQv=7kob pөQqt5Of)d!"\j`b ('*G+o%YԸ^>DG@aB7yLq| iN涙9.z!K +x!4Yru[S.AƆASi,>;~8 xb{iˑ)+8{ClհlGelO䩺{AΫ [J }czs9rOmpJ?吿c:^K*%Fɳa<">V[< +rUeptP +˞b˝O9sAXMDѦuz_L}?ee(NnjĚNM,7Yz#A+?uX'"3vÛ= q\ơ%T$*7LMk'T1K*<^~(""AˀH&<Ew$OW$c?W gn P2r\d8~cʛC{d9}(=yr[<$AJ 1F"|q͢r`wBW'?rXy~q7QZ6X{mvn2*:t75Wp#'LJ!p/S\Z"B}^YʕW:Pil |Kqd-$",c\ihP_7D\S.O$DC^]liXGuN@ _ |$ob@h{l*0߈Ӏq?*<J]uquNo̦o\'d?qV]ŀƬY+Y:yEcum=^:o$aoBJWbNd +4r&J^fV/^*ӧƋc9gMjR +qIi,5NA]jwtI&T mثÆɧ$TV&;/>FCx"A/L +d*j@bsQ(g לּ-*Q}U^![)n#ZH>g[q`>-M(z? -ؚ騙fnA5rsΫkYx-PBfESέkxqT4'omw2UqDuMj8)VT/#vS/6-! &Ȁ2Sl)F;LUU9•mQrJia3(ņy;ypA$P=Zik36keE`0E⻙0?pDP 7Sy܀]-NJ.v&ɔd1~ܟl9)guƟq4[f8Ҝ'\72aͲ̏.m@<#Z6b)ao}j@>M"p"J"yLp(qm]|hynվ%q{ѴB3qmFE;Og=O!4.hSw xs +ԨUp05k?;Đ%OY"g:TdYqr@ڌByίERh y obȯb)FgkQmXkbZ1f1cE_̐>޳?U&ULV92Cqynx4tYr06wB%G2r>M@/Kd̘R+.f)Mw>X[͉v+ +oP[*6%NC" 99ktAx4wuuy!7llΜ;NT4Ñtk-s`b+g_{sl;hx ._ʬ~Y:i o6ړr'hJJwϛ;m/%E(KS%y^e*kaK`f íB ~רIԐX7i$G}穚 _DRoJu K5 W'u7_܋h4R{X$={h$0 + RwOYΏMftQK^7nZ]bxgj T[)) q9d2P-LFܝ^J+9v%cj&$Lo"JP+NDS%2w]v?}P&Lu!=' T!㌖vv;Ur@QL aXwfCRT|G4ʱkWYlggy϶ulSS/X9BKfkX{O >0yOYd7TĮ-♰i(=Y 1%F_tt!n6˭R@hZl7/\DWKڸ5ҚΙU炼{]I>r L9y۸dh!љ`n=RUϚ5lpse)YOU* 1`ie8z12+~S-ОjiG'O(3f9R4Hr"C@/'/Kߐ_|a`Zfxì z|ў+uLZF AFg@DB44 A Dk(`6a Cm%$`of~!aXf- )JRy-0(Nx[lO.p +~&Gpߝ^O{J\3c9ٜo{Bkr|D`2xrEc"Z--uG:zX: vA +36d‡K( $l뙄0_4@<ګ%u]γ +o *I'SD $꾉SɱF? mV=NE "*IhUY^?%뤓s\e1 Mv;1(bכU{8 +P5W$,@)7`Ԯ~S/tT> Âe8ڽ[kYwEcU>n?n]dο(^10FͥGe5d3aM"\\ڧan?SpA;(W4vy rJ2ջ!3:i-8DQj@MWӆ#up1IM`?>8AoLjڛGTZyl@HXf)(|[=/D^ T`"S 瑦{7i3 «4P:75\MH@j@܃ zߪaZ] ;w ;AɚNz?ӻ6oh'6k缠M}P(Wg\KHwE)0K (֠YXF%F=&ƭ+}fWV!`>l:&簸EPi,!$G[?G`-..~#.ÁdKd֑γבuT?…v)sMpaXfޝ[73*D^:e¸PLA֊Q{3|_abe n fjLg;TP ߟKv;ڧi#3- "㧩=kLV(lj 3/cYfdȞw2k'_0{ÿB?$x^uS$#@DҴbaZҼWI$Yk<^CM΃²wAX;|| ݒo"dKьgLf*^3J<*I,j\kBRt(ґ`A޸[ NI3|qP}aW-b\ƧR`"lٙNi2'tGxފ|`WIr$ Up_7tov$\JҢzcv"HC\po<^x=ZzgQQ~?GOޥӸxxcN9^xo iog^߼DU,cO0]-iH/ALS"ۣI{;s)ڗ_ߐ«ĉiLuyM|t0ω9[pX& dgW͙\j7 +-^\|2 @j&V(<@C*$ .~lzx.J6qh[ǝh37-sUp}4q/&?L]H)$fEgVD|XI8=EPh5 QW/yrTLQ &Id՝Z7]lMVFumW*X.L4a"x <$_!)eLRI0MJ^?w~l0g+|88lOD OMCUq%xWl,]A +>}j6h"Ҡ!_lcm䆡zW(&X VwQo-QI3^SmMDhp'7nڞ HGT58ۋw+R`CBty]_ +Lg .SuUdMQ=T=|u}Nߔɝ檷eBRף5|2qpSxxmX@u +採:Wc=6%nԻ eIWGLTOF݊c,"$i ۲x[<}5CQNe\1_8FNq +pJaw35ӇA{g2;!X%£߽+qOpVu{] dHĐm|@~ )n;&y}zdi!';4}`Z_66[j넴N9f\‘?jP\gXooʲ +3Gtה +8tyM -gFMw\`bOljlBqˀC^ +&QVrp]ݗԇ-Ȥhnw׭Tӂ@CR;ֿ;$Tuf3bd_$Fs{n`0Ӧ,cnJ+DBzI檒6 vQ'.MuaP:@uv)2=ҏ:KJmk`nMeXKzxsAm>Cޱhh+.u<#GKMYDJ|FcG$DDP6ؖ3S.h-h<ђQxXc}}|g%x+,>s4q@! +pu>=`@W:j;,wV92~ X+-{f,fL3Xa΢9iw SI-s +]* 4Xwdz?jpc%뺏+>_JF!yfVΤ\- d.~ &]ja/ &sf/ 86yz]eloz-VOYY wn(hSF7=Ta.TZ"݄$wf.\R6)i0 }<`sHrhs +XtP%8EhC,vJpalڜW]wyCJkjȨ\]l]zlj6>jQvZ<$\PjŎޟ X/jB V̯qt4y\~N$COx3W7qnk&--@PA:5y7+Wk[U6 +v,eƉLKa_NCwK[DotjA`f4jl>[]_:&MF'n)K /pU3 {B-_79GW>&3ћ uL +\u˖.|[} A%vttFO&C!>j`Zj^-T{g S~2nr$֌U⩵_o)7l{V aƝx0SUi"vS?COg}tn%h8 cY-ށ9MF]5q;" +iC"w"YYtlyvm(Ygx/}QgVġ>@hlB9fZlTWlD/lFҗ@6me>ɮa8 CUd<Ḭ,m1&m-(@IxI¯=Kl Rȋ-F j1b vX[n4,-t_dX]DY=,)_Kzw]~Z.£[5v $ƛm>ԛ}fŮة'BeJ;Zr +2:fT"jX=q>XRmC$d wdʾejS :@z9?9OP0)^)qekH4‹g +ҒܷS>Kȶq|iNDK-3n;@Fp8EհG x?̯j#u|N +g1v~ajרEW+]ir;iL#vg-KR[l$˱x9>' MshUE5JU\ŏt(O̤tԔ +,Pid VJ/O.JP딑Xq68c]`V 8d=N17gҜ#ĕ L$7pgR_:rаZqz|Elս'c99قXέ2fW~lefhO^:8u:m ا ?X\&L .Tt\MaP [0› {W֡cPqȜ!GDFOdfJP'j#qv8W5*Etb_x0L%w( P2rr+,+PeVqϜ-w}g[ Jꨜoy[I+ d+i2{XNM48#7 #˧m)/['Iiu-Q.?TQp[G>Sw 'zB~;A5X]۞ 'N{ FDDζxWsu8SʪJuwo;*bzqHpة6LCY$V "cA)p'1^֨5HB BiXBֵE@o~$0NufƧ]~L}eћTQMLu}6k~EϽ5rze2{d7cmVհA +k B#,`2bٴ*.#~Uʓ#++G6u:_2a+J;0x"}//,9)&fcshݔm:+)+*D|chˤp.Qc$թT_~rݤ&qGt= {Yu, 2npW2f蟸na5n¼5! +Y%Kw>6Ř7OiK]-*B&r2P&ڋNwQxZP4nZYq&Oエ8<&V1=֤sw/943FߋXS$AWBgb$3$/ 4}R'V,^͚~9fYR 9Q&C]w$>BmֶA#i+O#*"DZFcTSkgQSs7:!U|u9s҈灛neizݑ!ZgwS+9g+zu +^0Z ض#qojȍM@D|fCb +e1r3[֖-Ё [ mTT*g0 ɀS5Iª5+jVS[-qtBs~6CXq%DɫK~nD?, 604Z\ndž8sV8-^QϽVޫhcásҌK0/Z,Ҁv ++#ثcOЬihUEz=nGCE) fyiyAVʪ6T!x-dyr`dǙ`Sٓ4lta_'V>kh{=g&Lw@olfۀnv. +hڈ[GܩS>x]y- +M3fM O.LU+ehkFuG ͮ {jӸ<r:R%uAuw29#/l\ⱅ~ 7.+V9#U <^{ƈ?mkХy@gNkq@pY-Hn"%7۵iஞS[G&aoޥFB4HԲ38IąwӾp'n\ŇQa_n9혪*`UI/<2lUY_9}?zWE4VW^Uc?f~eAAww#"mBRwcC"/qi`'E]^<B6y  K 12H/0rj./fuKGL#1ѲlZñEpW/y: +#ki' $L˙mVVFj -Xzv剘,Td* JA +XmV폼UTz${$yE+ ,&Ź#WK0u X_Oᥦ*z-Uزc6љ+::YO͖6i=;CȮGƪ>[؉;52BX['I5UáUg#~cv~*OV\ ,+V}Vb:, kefRsmYtyYffs{<KuכiL r~ۓ陇 +zU* ) +簾06ope vJff"T{ϾWq*^6 +jlJ uO|"p7-qA2j(\q*v˻ߓlLfc2׏VnO]gEEnxbM ER~MI]lmƕjؙ5&t!eNLlY&빁p o^a#MNʡ6ZUOr:“zi2ePЂsv{{<’?M[I†@ Bq'PXX(jRc[N*7MwH7? ԗ*$np)pӤ[Su,hT7j?/è1quBKyAO~#BU.!;)gpVeVe U:hg^F; G5 {iAVvue]]7Q똝Qx.`LЗy(ތ}jS5Љ>HMƋBYho%vvǽQb~) >-/]vK׿Lk+zɳCsvy]c Fܐ؃5{IC}\-40z!}jѫH;!C / g_9ղ'|oŎ߯hU~~Nwfziat)(j8`ZM' +Ilg׋ M "w$s8툓Sz36<`F_1$8vayڢUQixo[ȯ6% ]4`߆^I@uξtW,7TDdqXoxƕʿA0SKhX@j{n۾U:r ;xBV|9(":*6B{hJ&MnZ5PҭCy +'׫qe&+fcI&8c"(Ƭ*kmqVBXzR3B$!iȋRas$S)6lKFsLpuy^-ɍ8pߧ^d[1JE ,bf,YpQvT9ԾŰ0X%r$qHhn/cwB꼪+̇B>g2KS%n\ ߭̕sMbHIЕ^ x8T8u^9¸$&xiRnh.ֈPi4yp3R  +P'hmە8\_А,0Ȋ]#qw]wwmެeFO@-A +V JE.ዿ<7m[bwe[{U\B+@J4dq"h 12u5eѪC?SZZAv:\r{RǬ?A]3]?!6l^N;V9_:?XIN5hɫ`!(.}Ai]l~޼FRny-eѬJYCAb\q,>Bh/L|KG!sĄ{Pv& +5h vyM.pAxM\oa*cr^R0t꽌Ԇ(n>ȨF1L]TMb53.$¡N?^ə sz 7mݢ_R:#"ZkyDȪUq'xtUWU5-+:|lq6q(`3Rpc"tVO_?UɻUAʃAa-Kz *sI\E@IR_Hw!VXiUmҨ,TI,A*Qѿ4]q\Ʊ^jP@c2u و'c2L,ser +]mCME~iFH\"6;uxq %jQK\ǓɎgq|kfe P/Ehb[ p)a0"{Z:_!SKvgԶz:E2Q|׵zEoΗ  B 與]׾2@I̡2F(cR_w~Ak Y=מaO&Vmؐ2so1ofF['g]CͨnHtsšp0§6z@CNS%ͨGhti=)nHYiͅKߺ|:R)eLn--f6{F.Y&yn:LiӂǙ pdr? ݱnh< G"()GN9i`M0avppi\:2%/ 6`&H0jEGWLy!*@x:NĕV}$L d}`cEnj;{E>ԒzIaI*D^WV*8i)JY'̑;Gbs`Y:!G$<9T3LICH6xy+qScl)˱[\|o]n7ˊkUajU-ϢT[ b{pD#SB~hb$ %6K6gq*#PXݤ*SʀUkƝ6UųUURϼ* + + ̫J6eu LI ==Z}IB>NˆȗSս*NTBۭy9O4wP4dy$Y |VfڭB0Hܜy)ù#G$̯mS-ը:8\}aHp0#h׊|m`kr\I +a}:SfYuEIi@x'f8ظ4 o_,teɶ#sEt7[Ąҹ -W|Fv.ܣ ˓=J{ۆ=*sѷ +ݖ̑ZfPPBZ!n` }W\WlLDÊ'DgWewghhB7]:^vxI!'=53TgB1L!3qNO֌Ky_rrK-lI6,W4iN*Omp[b#/g'06,A3})wgYmkZXvLq Ԁm.ݜT7K_;,ubC!K;N6%51]ٞ +c" @D8STB ÐMjhuqdףp9oi"@ +V&DQluJinR&_ +G2=>ty '{vu%G:ȶQ)ĝsZn$*ѼSfFQ2J4]C~{ܾ ܗEu+*P-#79-s!F;aA԰g[wjR-ZLaO'0 ~Dۃ܏I=`-ޠ-Do~O3LԂ";_S!ѵ+:%aY1cB.JO( ,M1a8K0a/93V̲2\wHjU{r)!r+m5_} P+OnQ>!Uc LS٨j>ėM(&8ISڸv Neqy%\xuGpdHTTrĔ3ILL +a `=9@R^Np@Dh sH+E{[̫/Wswe^oePg~>{cZgch[|v5 +xvͭq(g3gȼdfj=33WBDT'өF^5F2-wBZ~)H5sDյ1O({1T=I~jK"7=YG RbU:_ؿbB2RQ9$#}?g4<ЮW1D:8<^@7+θEZ$"{)AJ.N#f*(}" P[Gnf$;}KOH?b4) Ei}ړz2(;Bq=-7%yE$W0|zrXg_3Ǽv~mA|Zɥp\np;J/3?iYSxu~m=Om~E:,2` ɜDvxoKxg"vG39x[' &&ZU:G [FսΞD#|Γ?oz}=19wcU\8Šo|) l\`X +(RvoV얺bIVfuC7tB(Fa(DY궎^ӓcU/NN y̡KlȮ+V񥟕EiS]uCP텏hw> ߳@a:YAڑ]׉ʨ&!^ul0$8& +Mq/\gm-KX֘[67mËv +o*=o!N5>w|"!v4htF1Ӻ6Jdn/ ƙG:㩜ht v! +encu!Qgh'$VO#=`~'$M"Qa"JdDIQ"1}+5J} nةձ?7F.AYx8ש>w+BaQZs190`+ \ +f}GiwppS ++ZNZGZLfQ ńxMdE>^1 uT"&\%Ia~uZ Q܎xni&cfot +Gv$;@ϡxv~c61R"xGB`˳^i-*);=3tmNPPѭ j]c$oe^o/O8IupMJd$:3fbH"py "/}~5N{!=7*T#9D?LwIA&ѽ #6PGH }l@=z}Ds1 +6=OzFH`|S߰X%Ւ8C9/m֩c~@SQmG?uKB +GyHvK{ICffIig,RFM#`{2z=oҞ-{go~oPnX2ٕme4J.0T\FRa+Ŷ>.JN;s͈k)n8*y8yX=}QsX0NCtqfgk4 +?طE G >E CEޤdkf ?k/ؙP{ی,ҭdvw3>*b&(^ڈ֐~떌c}`{{ZL(*i0&'n"7H\m륔\9׭:bSAxeSgq}rGIWokՕvzyhm:FC)g8"!n(x<0j 6cLh@tmCv.#t`c/ [ib0w$ E;U4;s엦܉bd#()qwf} +bLG,ey]Lsԫf^ Aŀ1j[E6ؕ꼺zN w'! Wa0t +, }HFxuW4]V1 +W]ٕU7a9+N(@g59|p)?O!GihfbpE:Q`q>0 u#3tDZɿ:Gsu)wm:)Y_")Fkl >~G`juu &53ד I-4 :ҿ@9S邴bvNLoW*JdF wkR߀CuJ:h|VA# |ݓ{ ?}cR ~a*nqTOү`d;#:NWj'e}>qYnFP &f]lb* ] *~2d٪p~,d܁:%̝%*ږ@0 HKUc Wh)s:"}@iri61ܬ]&Rav8K "X{v?lRkɚPttb8Y+2𗚙B X7& Q챦1-GZ@!ϴtY7М2ΐ[ +('}z>V^b0EiRܦ+ ˂I|ᚲ=5W5[cB_ILj/(Nx [|8^0݉n|Ñ+ l1 >umv:L̑z +lJ (]男"$'+ʔdj^Jy}_W0-MlOjUAo*b@Dr Hl\;zХ=tu&rVA@{ Lw$(^D_ܷru\hihoP޵lXZOV5뚆M[C6N m1p]٩JIScam@&8'tI$~g6+So L z [,@nu.f%JĪ2cyEbxoI[b:U]2wҋU,vEdy~Yw:{fHB566f,^pV`)6l\;J9[ӈm&ʼnqog] !t[Wl[LP@K䶲1!Lp9AԒ--t,\u4|Te|=u I%O(ê},a'^7\"ܫF"gpe"HWu|2b+5uٿokIT2ŵ*B^0})Ri.;,;p673IȄ!%ޒ0%c9R(;O|J;jϧqrd#Ņχ\uuҫ|>le{NA#0GX\Zpb@b-,+ִl +py͋d +uôLtڸOwkA=bqs:IHZ{o7~wy~P +𿠡c*y{%l;FA4+}+䒤-'`ZjP3gc!TpF5DQdLēkqoD~uKr>l, B90G(`92 ~8X<:c}DTAw כvT㇧Sv ,E'8.t٣nie>F0}Ś$]1ko9%@i :a,G:uE`)٭^^ѡy:ym; +˝y.B gςlWlB`VdU{ +֐!UᦞS ڒu t߮Z|c(JYrAϽA T>@3>  XH]FٟwoE +sovNEy.\*MUni9dM9͍CH+V|uGϢҮ uz9GROP؅_JiK,)WeE_L2ڇm'ڏYϓR\U+stffЃp b™ʶ˖$)F |Ej α ]^: 88jF[y'axif+ih:~!DS*NXiU hu8AVE]`ZDdU}}O~?? Ѐ[ +z~ע'j1o 11!$|:GޙX˸B(C)9dsVTq~q/nbc{\3[+jI?)\2_ +thN}eMe@!B8,[{`B*l6TЬIڇ<Z8}6GOqFXFCyҶG9qR) Dj^͛rj ePAf+J~(*& 2hrt%q,qdeb} , j*[$sjd(6\&u6edB iq!Ů Ju:öҡ:rǂ/7Oxb=M4=N4ulک/l4ϯ=Mr"Z̆Ze@0vL'< 蚤lA;?Y&I͠L۱wc@ +Wo?SD+|t78I)ǕlgXy +btIQbGL,uy(89t|-}IJkk%낓Z|괝T!5Z=nUyO?辟Uê q "ڇ( +^'?xYR6noi9l0!"M^lwGIct"5nn`ZͯnB>?ͻ.#]D R;T5Ph-5Tܫd Pir+%3D;,ǻ8ѷmse-b>98\Hyx- +K+{Mo00XeD7`ޘ!>GPsxbY\}߷;U6JPPX˻mq N[tȓ+̌y ::&TÅuΞS0oACSWu@WȍX8!8E住}`}Ė_Bm=i$" fn*2Pfxĕ{)[He'D"[r4@6=EYk$ճNh\UW{N X6|.g.h#3E:壖V}},&6L]'ZqR-NҙoVmAMҒZr"h9%*"Ġy7_ٹư\6zIY.*vr҄ +\Vs!Q~ͨ |yO+mPN4,ŏ|g׳~T;[?jJLDr>)CaYπX:Ϯ!VԳ]WXҖ5˘iR6+0WH`uJ?26Z^xE/Io 3 6,0d|N_~Z"Cӯ +4:аfZ+b(/Gh.)-F +~~}Y +n:Yܝ^J(ڄX߄a8Д#Ru#N 4%jBRɟ_2Fգ JF=lc c6x +M~AiBm[8BhH#D=9F|+o_4'}n}pV'+ GRǛ]NI1LD~_WJy,K.ʺ3h#^luCy?@O.<3UACh+v0gK'kݼO(%j*Zr8(0ňΪt 6lXtߛוTտEi[Hdf&)_4Ïf??a}V>u]ܻaH!)!˨R$Ԃ1߯uw4nXX}l E KUocݴMC7'Z#xܣU%Q]T +=bѯH2S^ (a$E;c.و|B9,{t?wKΫ-tֽ@2ٗ6;l\B`I $^aFZ + \|4-Yp[gq)JDIGrGpĴH+S*2鍞 7NG×%iB. E_E&q2QŅнW.+覇Ype}EI^=s~sWK8)td/sGL"{h$HPjR.[ʻ Ǿ%oqİݞƆLJWvn_nk?u/RJ&GI,X?_:^տz /?|mK׼{^D<:r˽Ƶr7Vs.)򄩂ﮖzN\=!L(N:ʥmm" qP ٭b+ȸ R0@֊6 #n:z(hy Q'|;Jk^2OY9i;ᳪNR v~2Иk8 +f>< Pyrr . +{I/jts5D#59HkR7;(#J imbfmv,.uN+ +M4'&UIQ(/((e)#R&:8Q>:Gf}7߀?X#? &|C!aM/؞i#+GѝM L->Sgo<1[xլ(3zİ^y}8+2h +_;! n_HOI-揤%j~$S h9u8Gr}exQV>L II&-E'vjr:5 $sꕦ٘1)iwgbl[Nѓ!=}ΥS +n{)<Ӝ'Ld+xb%l{^ɀO*HS$A\ģH_5U`wR8Va:k _r&@ O(Qf=XCmְHm8 ?_x_#AIԸyTA?;ò:8n?OP?ڭ?7`/,̑[>QLKh"xjf䎾σAKrlx +RiR mgV@6+uT-HzmGØD%T>E2Iܬ7I8a33UI[,Zm]Jݞy;zɷWw28YO4 =v$Q=}E,aӛbGXS򟏴؜SPi;㕙U*Z<_+/X;[!VI.$Uݟ|1 x OҠ??3$%& gdROP0*6q;_k@&@7]ll%%n#؏Y;f<2YN-Ɍ}aϥ s]r9UP68vղfk6/AvrF8DDZrϤ! XlGb03E#vDiB8?AcP-a=LikQ4O6S,َ^7$(H <"0B:GwUpM30(}vtvXn;ư'9uu +"6*g%Wgj09 Vsu)fQEL. ?nL~dzcpT|;Q#k{d~##e2wd~<Ȋ,mF{tL41m^ioi]P眚dBbwi0%B:Kl?#tԍ(~z}PN)%X\WJܾzВN˩WF}3*pj,DZ$ͥx˥L%^o$2Ia q,Jq=yWGc;M6lޜ*ʊI&tߵ%ϬCç#%% +t~1y1ao9g0v9R>f{G-'5OA0WvmM>A>+r Ⱥ&^D"UeU]V<])D=G8GWVR5@Ic4H  Co~eS]2j7ͧD:,=v7J:Hjﭷ?:(MYY{#rG˾lY[{G;_lּֿ<^@$זkPM w3xp6N8kApx* 7ddu|CD=FСawTF[/D^,&uz% O:J7rjQ"~FDUA+%/OUUFTژ1S`0^(j[i #H wŌ(idz(g:ªjCy3ٱrb+1|VWz-xq9 *[U[ғNF2wNK GcᦴhmQ@do`$)vjJʓWқN7'eLH&6i'bD<|d'_ % ԏ"ö7w(3wy+ .[~oze+&g{ JS]Ec_>NBAǿ˶F,ꇾQj ֣:j/\)*C3KpmLW' >1yɡR¼:)GB#S]?GB(w,} ]tHUK;Pf '>RWĤ7PL2ظPrSNڭQ/8 v"eB7(904G`稕ɶAƠ&M>&ؤ"ii?4ʞ <ܨу$uhȞ>1vmJn0JD`GudM4~i>QyT( 7Qhw9:T::m3صnט`^莸WGfNJݒI9;tu.ȁ\AgsT87ۃ-qȱ[Ve[i\it>#ke6H;ncjzUq!k17 @Z2>=|KJMIBl{ B9W*no'Zx/E-%Ɵ3(ޠuwSq=cn 8}^SHDm)GB9zsOwݗm;d=[Ifop6>E tL%0='27#fK4^^WI_O/3r,sTM$Q;@gQ00uIDH1,yBo8fO}q+|ҾDeW :( ~;-^K͕PoUMB-ӻV +]vF~deXG~Mĥa{eRr^IFQaHѺ&]%э :2(e`0 ̲z=P-(nd.IYn2$nsxQe-z3%;jFWS'J n `H)jvlViF2z +՚ z( 6Ş|Z^eoj\ɩ+Blx(S'>@\ކB(ޣ0m^GS9u̎%kΩA~L /y=,tQh?0-`8Ѵ7unXYztc3 Q?hti p GD'o˲&[eCl*k*hmkusc÷Ab\c8N3qä\Q浓u;>&2ŇcL]a(ЧUÞ5c_|jauSQz7n4H[; /Mq{r ݧJ_# Ls $e7:Ĭ`6cS,dH5̉XI7Z-ǽu[ج棹[ #^kg|}u;t (KI{av7V鏊F]ZMߋWfun=Q5nmndYEv<֖H]UqoJ3C/5"Ւhވ=7O7'ư)dȚoj&՜ؚ +#lA# %CIpV#ֺZP,ُd掭/䊎ͪm}yeBöy5?OE@IC6V_* yqBKf`n6&|Jqgۚ&&,-;굀Yl;\zj՛1u:?pi`c`)S($U S 9^;ːCwƅ,7r* +"+k6sgپ.^slB=Bq~Iz|F͎Q"As4jV4aLbso9G + %33Kv@urBO ~AuzDSLg9JfA>R8f;`nKP{$NPnTpqfnO5˪ 5~n#y=cYT s74 +%˥NHʻϑ+SpvMf~`7Dx!}:_hCԏ?}hKעl$CQLQ"(܅B2h$Zs"e.B[<Ʒ_/}U2PcM%],C.Cl,}CEasbBzI$ҋO à$Fea8KgIj wFQ^Өt"yΉ[lf.cM*3!#] m"6ie܂;EClSEP(h\`+">'ԩFM  eɕN$OasBrUg@r]ۤb ,_ͨ{ "0PCĠ)$'QPEUߺb +1F?).yP*EQxsď p8qF+!pZr8 jD0b6Bƿ] yM #FWK;{URe ,!K("XmI%MR4xkI`R{$hx,Ӥہ&j/ZS0K*UV]۽߬YH;]JHMD71uK˾02DT)z88aVԞHeto.٨ oc :Lԇ_>ߓwvXϾB dcDl!}5S4Oq" nba]ũNڑY5wZ%Glr (N֝8w]]OQdkHEFFϪz<++ 4&C0hDl~p26] +WS[D +M;/" gA5Bu'> +U9yѥ;4sk1jZg@eˋ}d,Z(TRȜ>fM0[vinmڠQ8=B]&eR^C6G3qp `M]H%`Th͒2}oͻ"Ce'OJ<(<lHExz' +dJ'qGi<&ߪ(9 + Uʶ },*lg%W;3/Hۅpe'oJg~[ut[p9Cf_p,**pxs`z@xWJ` 6F:&ߟύD۰g(9o)bgP6cA bzl 80?IxRO^9)'h:uE[L76<4^ h&PA邒_{OqMLDŜ:`0dC5 + g9i߻/I'C!Hz,3UgxKx7#>=UZճ%r>,/lLۀδOhYյȠ ]z&(3 +ܠ)!$s"0~M_y9k T.M NG袌"$}7Hm†ʸU +l +)r?ig"2LIz5x2|m$i^H9&͢+b4` &Ȟie9;V᧲]gHBB:+vtԦk⫺VENbH~r,u- 8OܖmneSx/"́Ḱ8C}\3a܌:}1 |&?EfstO a>8T +]HSvf:X ~hBǘh[<m'6Xv>Gk`8ly,|y% U8pʲaYIcV|/t "܋#뼭{޺7dwN1 ,iP,gݭy*$%8֓PN{Ϡχyk$k<\5=#dLvǖ |didAqJ78.4WH*`5R R4AczAR@ 葠_{{]䧶y1Rb6?pNpkSӠwvdmgېTQ#dQ[J`zܚ+IF ҳ$a23@B B>4%Wv32$gl^i)J●90U+dbPiz6*]jӉ掭Pީ7bISC;ڈ|ȴTC#QǩϗIypEfG\%O5;5}rvI?t xlKG5@2^8@AlU\x= >i:6-v<iT@o&;ꞗvdi2~uj a7|ʷb\3`EGYNQC!'1Nalˬmh4qaeo%yܮ]t~3pz] |硭uhk:_w/UL@0(޺=I:M +ߤѧӳ(7 +=v/82,)!'5IL:=ce:tzOHzUW޶)`5%Iߍֱ7WSriqju<?`"yLgͭ,Txb"pGUZO_gx[K|𧿌jD-ZYI?́ŘwO\)i}6+g&3 +b=;b ڀGh{z&QI̢EX2]e@zW*09 ׏ ůa7<0zBG@,(<3,]Ve'ۥN&c ŧ_FymvzzJNue ,n '{Eo@[fLA'9?S;:k`$y-.<tU^hJxS=~~֌GH>? ;[9i<'C6åaZ~[n?@ $(k"~˴fRlzpI$ }elZo-uXfRŊw#œM/OaDübG0~zWHt{1ՖϘ8mٱ˰o6ʭƅ#p{^eVMLh21yɼ?DaD׬FȜx:r2X}l_(`wmJd;o@< 'VvשEj=oƶǛ rs1DF/)RUٳ;8%2h2KdD$cf]2RUڶ GFu|R]r.]S2Զ9*#$+'rA-"0|'kֹޕ+}RRu`^H" tC\f"lZt++]/Yi{qveϼ U4=%>Ź3vw+ ' N2\d0wݙlQ̺1&!БO@+Plht4[98N!e,,/:9U4y0qIh6.u%ؗ[c!f2A? :OJJgL A_/TLXBdwW{cD%"ªkJ ez^Ia*t.dz!μSdLnmLٖ6Z?r|~iz害ǡYŒZk܉d_@?CnT=PjH9qf^Z<I6X T$oc=-]tl© 'ёvJvkM8 :1v5ɧ . +~]_̳>;[1SI?n8=v딂U50]V⌯bCN'K5U>n^|$(ςwmz6 +__n/?& +{ ړ[Z6`*Tg$,([cLyȿ{]˽H|#v|k^2;c{-../6u=~PP/\Uk4DNf; V$+:r' l>z:B(2̳E|֡T1 td׶9^bFb*2eVb|f90UR!rjtӿkZI6b mbE f ;V 'p{a⳥Uϩ7b!%+AAnwvC✩: SD`k GHHBX`h;n[- Ln͐yW`cOWmJ +tP O68[}07HT3^%0lS'jy:8AmZœ(@,6=T*ezҵ_4ȑO88;J wtǴyi, so?ݷ,aMa$pfY2LAEPKޖ8tpCQj[㚻hmPn`8ZO;jsg{97@,ZM(𦃊o  n3K&\ Ukh~!$#Ӵhe+8FH&ڏPh⍽xh|RnH4ನhR]-V^M&H꨷;Hԯ'F2Go&] F S~vhJ[I~_ LJtswG9r}~ci#{-CQ(v\@ۄSߴz&|, {Vګ]$ b]gmGCq %t\2 +G R"3619l*_|vVݠe q4H1]e7yx?.'jv^BMDAMf8fS^DauKb(,>w,uvE(mWG͈1T?$?ƻ!뀆6@=^x x 8H9Ģ?B#\t1_:f8jTTEBuK5xp@/Ћ!_G,D"ca- l/ZLM |#)lvbC > Zy(b3d<+ WqwN[Co8bv{;sSOҬM +ڗxO]vO2'FD KKsE7Oh 3Ш W 4W>6)ߥPMd^4s WiU%h, A(ݴtK,d0 r$q#Wۡf`Rz1˪2*c㵨1jE |^lYk~$>6njQE'6[i|)p(q&#?pc3' J +"8- ]O6J ;z]]EgPmSG j15xx~A]h?Uh9W^"_w NpEUMQJn#p?>&U7af!K[w! -n_*ΨYHV]u!Z$0tM&*2e8Yɓ Z9FeVwᇝEԮ&ٌg: cWY +}i^~(%88aqN=B 3G(IݕzmgCBK#Q!'D4MHSȅzbmwZVQ!G} }сy|M)=ѹrt8ϏEXt |;Nb3xcWOk>e-=UTM$ؗd]ܗF甛|ز$C}8lLqUTbߖ\shҞ~RT40FH4r* u7)&*y +Xof_e?sKqZ5_ɯ 5*jm6V7^0\e:~xlԬ+b.[|~L|(A5ǫH16V0 +^222-iW =:m&%IٕR/mKu~ JWC))SHķ"NY~B=\VWj)))QmkZS_?A~%#jj`n0XZTW򓁪Iܸ+xz"RgiN{(/|eYAc5n+ӣ|!x ߜ묯ɿQ:?*Pԝ&&" %ydp9Sma2EDqߔ^GZ M WBu>z|@'/6wwONNSC]`$L C !4hSڣMx@;]|laʸAx=3W=zՏ3jWN?j-O!@NSfeÐ4wNkV^R)ʊbޭʱ ś̺ZZ3;v_*嵓 +.I:ۦR58]F `5iFJLl/s[D6r4PYd([ϪXxlrelhYA*WT5+"9T+|M#ȝf|/!7õ,aUZ(iqBVU Ro]˕ٞhf&kwH۝01 -i'>`gV7GW֍{ub6HfsC8` Y'Hr:Z Y' ~X1mr:0u _X\LCRvy(]LiHKDwφŤCf +%]m) ȼiG릫ł;h +R}ZX32"'Zf_uܐ{HUmű߇SH f\sw.q?~D`dUwϚdJI*Tez1j!CŜ[ehJԡ|evUHNep^nfHwH@V,Sx5|{3 mSRQTN.#C3+̂{p.\rדvP&1n0Ґ j-'k8쿒l<|੣2`u:ֱ + "Y_]7@H&H[iJٯ寛&+qIݱXTMa +@VPOvOZA|?x\:n8&V1&60.Uʬf\=SvJ03$mksj_6xm՗x^y6V4ѺvCU鄯iiE뷴|f3Q;,litꜦFUּ "k_.(ʢkVYdkR?;0 f 614reGj+Dw7?bUPFi&92z|VDCt*9#G"#+з(1 f07iCm#&Fvdм'e$ ͚k-rE;cd:(i^vz +W蟇EM\fGZtHҀf-]oRӑۖߞnS4% ZkTfvcHOu(Ѫ%"hbWT(8l}.7'-Ũ/AĽ:YSnDjqL1T2[Q䧎D!jΗ(~V6`d+!J@,J% kQz27zM^tI mƤ}xZ%|oD5CN%E[ҩqye{cg X膴$X6[XTdql`CL ڲh>iMvYwi}Y[_\G=/_Ͽ{8r:?2&a&:e~]lV7B-\o[>W6Z?ٹy>᳓qٯ9x+<@|_ShftTR`.BUH:W32ؼL¼aod@ηZT]ॼ?l_a|Fͭ365w<<1{G_PIͅO"ׂF?@A x_A]cbNWIIuDjR~ H+# `uD~g,}%!1LmH_V ӭhO11,HmLw~l O)ԫ'O+N~X`"Hq(C;Ax6v]z.[ѣi +~iZ5t7"hwKYo]^t[\Stxj;([LfHC9|7Qx&SQc6:Idc7Ѝ"َNL0]'_ 0PWɲtsJA_lQs0Zs:B&Ji +'k8B}Fr+Ouz@Pg*U֛s}Fh4qsYU[1tYJR欂D3yHt'ME!to3t3/'ԒWlQ*eԥIⳎwgmo8]gϓWL29!ҳl[&EFN掶/7Û#B2ZgP;J:TGg %~ftࠨ![HOA@0vQH>O@W2}d龄IL^%b+;|;o(7L9]Rz&_ m<i5!9& M^(:KU_n,6 q0q  f*Ǖ)p.WyLb9(<+D6qľ)=x0˃…q|+#uvQem=O3ڣhaڵ'~BrW"X>w`͜sVX~AQ ,lPaX{_W%؆ۈ|8b\w*jPDC,1i+Djd[ Zzйi_Z5̼y|ӊ}tej:@f`]:uiO;tl7@T~RYw~o{~Fs;G6,a?3YP`eUs7Vx2co%k"橦'wkGB?}]K]l\ cNT][ג;f= 4V}Rezu|p62BRr =TyOiLJ}ELuO#DLCڒ墑 P-- SHJO\"M}>{(Ȗ\DUELXMՂX֓j`3DX O.xj'8kIh[t|]{{[*c[*&3tHŪok4ĔotLc;gzͼ.JXYMqSÙWR}\W(Idi-v+ }ӌ‰!\vQ=QJb0>kMV!I5/ ͫ]=j1Eq<Tqo*SjIDmvX[ %{CңܭJxtƻ>2`!V9Ueu@o#?!S3zl/:i<ƫkC/jjHCՇbϯ]S+=#N^D@@Dg{/} P@HuSAF(Q\Dp6 pRE' /o"&4U{bvtΣ].`G*8bMƑ9`QU*űaw=[l{"B4㪒fUe24zut xhedGҭ 1W +dh1ylY߆ @&ğ#4f<tUSJw&':н/pmϭ#Gm}}{y-5us?~ h?͏ȅX={O͢qgTO:FLza0m$#^9#XM[ewYN6LPOZ-ls f}DDU:QV$o +%?ԤkH1FceaKj7n&E w-n?HtS>T$}\bوRH۳~UT| +Jw/V2Ɛ&38p j/p1ᇣ,QBgg6qMMv/\q>؞?P}}UҍhHoT@^*4}v;ؽml$v(W<DU+ v`O/Ӆ b)Ώ]|ʪ]vJϛr%k_B\h.tH{>vaHW{0*'TER%if$BJؾQmGޕ.U{&H`9ǒ7u9*! [T$pe +qUi0a56jf~LɆ&Dnv15 ^Z_[S%~hJR[yg<#D~~F{X "qFiЍ)FjlDq'.k}l1m.]Z/Q +'܅, `we!yƖC5.m+Qo {[J8akQҴ+\B(w] m%9p;V IM)Ox B-g g1P9 ľ(S>/Z~J[x5$ih hNMf\KkM8%U;VkGnH!4 +_An֪˯UI=UQs5-v{&q?kZ$ +i.@ٕ˥/B3_v'u?>!cvE#\2sP1. +^.i )95+M j$)YmxVsiW%Ѩ #A&H|EEp4%q>յ!uShSS͈3]E#FMUB[#aN0-+DKZ Se-o=(rxs%_6} +%5GHG.܆9*T@n, + ϟ bֈPcW S09!%whYBͰ<DačgyuOɹ?u{*r$FEKh%&*H8XpEk~S$ kXJ7ܜITsx{c҇Tҕ$Bx̅&. 03EloR^천/潷Gwb#*  kOw{֍|hҶw-)X$@lfiLL `cZ*e)5`2WpV$/= &NKED+#ċP]!*RU:J"͂$t.ndXE9Qw:VY"`}t5cnYcevOƽ,ۡ/O~Z[,\{L/uzثiBEFt/r3mcwξCQ{%%T*2.1`+%dO1v[ĸfo"?Xh/|{|Fȧq~ܿsW a Ky>ϤZgקcB?O赥ދ{cKgkTt/mz~3߶8i;Oi-?vD)9Ӟ8u+n!9执|bRhRmNbgnmON^jLdSpͧfOokñvDE]R;዁|ECgc}[F%iEP??Ȏx>֑m^1o>:>cSY킲80t'mpy";>G-8ΫmUWB6_>c0²v Ci)ӕP(+N?؋ߞOrc_a6lgMr1lAn%tY|QC!e~ Np2%2+Lk%#ZU(f(\9<9 0`21cf ]e~*G~X!ajj8Tn v Wȇ͌Ih:<>梕V0~7s ke'De۫O^CĖc[JN?^1{yem?-}y}sj\Wonћ2;L!#<8 Zmnp@M/͎;8pfhnaNk.GONS *43hxkC_44(bifrm樒EN|g8Rm).,fCڡ ^:YiyЇ KMTS\4 ]lm,ڲ8w*G(᤺̽Zaf^.:fJu6&jXN%X|"Τn,-o,XSB cBHD$cE%ROV2aHW"QIN+C8Ւ]e& +B>抎+ʗ9 +|dT>7oC{qq):ncr?j4_213Q 0FN/"pqf2R N#:c.V7+v+pEӅA_iݽO%7<{فhw[~`IDz[6,a+<Ţo +>#=}U#ɗ8l+ 3U-tTDKwutye_k`Uks}@q "->q B FFd#q2L=1<+#JRZn?8!Ϫ/ vhƅ CT)D ;$F],M:MK]Px3ȧ +, مm6qCVHM`avx_J?[7yj"r`@#[mHbP&$T C*]yYEU%e]ݺFA(Qi,9iHxR=sB;\g| 8\WpT(4JN"SO3;p!U>6BŘ]V@$kVG&Ih${&ٵOQfT%E`դBוTi`lc\ػ}9FS9 JCA7 Ĥ/(l5vgP)*4DvxV=nqGYX"&6\ +3Z aal\jDƉTL2Lb|XٷYˋXهoŨ)SS?NLj"\ +؏;?߻yLN1Ejdo7UZw ͺ —B[k[ hb{EhӒ{)2BmT2IQ1LR\ݙ?V./γa1qOeXh[$5Tݳ7UX{M^*uZH#L&%Yx1h3"J9R4fW3BzV$8=[MIDyn^rLSс:Ɣ/ ̠V#jރ!vaUqZeM~KS0} *ڊʩB3EVeQ15u.!RlK7gW8OjrW&90Qm8v 8_]dk՞5†~+ɶ=luCM;Fii5<$ PM. =6lds?^Z=2et[`__ZX_|׶`MH +R tE`*Xilr![c2K#nʐH)Ք>WqO6,__YQߥ~wҔ5-u}G?!  Y-XvRHe]2Uuj%cqјC}A~ OfS;D Bk",h,4?-u׭1(D%OrirQm=+L:/SKA{G!Hmvì + kG Tdp!X(ӁVȾ^b:x>Ujm+ wh{8nr Nr0mՎA,D8qAdߙӽ"1Sӏ+K3oaêqzݕ9]L $,%v F0~۾J /'KBvK'7~/tmF?F.sl2"]$P A;[WrɄ5Gy +;Ƚ|{U,3*ˉLμq63~7 !B@VEqW\qUNW9ߖ3[-g9bGJ%q KG\2CE\"juz$v6ꉖޔ Xӗ[Qs7( 12mٝLUi;55B#Dq[᠛pY.d"Q#&GA6Xh+]Ȥh.^B{7nUsFhcw16*ys=A`k:2BAYY!iXξ$;3mZ&FuHjn{"Hye>d'X4Pc87bx<ƱkD,`{T''KaAIu힧f]2rNOdƓ79F׶@FS$Oq]3=^]ǧOOrƝ ^{:}IkuѯooǙ_9g<~ ~t*E.*2U\mm=vQ_%Q|(xᚭqy˯O~>h͕Gy۪n3~?67ݜ0_55gQ|BiI7W}Vuʐ908m}_?=k]pg^ZZa!߻飅ah~,@M-6hrEMd)>ēeTРe*y+/9},\"LA]r;PU +3_iu(SOU~8D?hP_fSZU8D@ú̊lW1*4pȢM/x2z\8؉X(.mrс[qi)х;(ԏP5_7#f@?MoT3``[^d)xp@O>2і_>#C)K|QO&LENj hR:38g5>i0='ID^ZɕoiҔo<nUc/ybyk_P$Y%8 J_KX)0e(\!#Qo)8d͠^+_xے13к1J aF{Vm ϝ+(4IO'{Bb- *.M@,=4YhŬ`};;}Qѵ8e$1xOwڼO< ue!ґƺyFQC;4.#U8KFa~<Ycc9ʉ?v.c7i8b .h\D>U(w^La'8Uꅙw2ٔ7ζ\[}W^rl/ErqH}t9ܱ!C,!$ +B<""X{gB ,[ {"3]@\~.GLCJ;L )x5P mșjTwB8.|dFߔԍMb6ChrC^*x&0Lpńq踚E{6כM)ٛs_P~g+`nNi';sBRs1T=eblċ:ćN alMY/Of{տW`4K\xjHߐOq7!C(gWa،03 +,G?{!s9a2 t_}oؾP 7f?!Flgh}5*jqIV`-*ow?s|5M%2@/׾C^+)k:'@bt7#]j<Ty>E,, iErbgߠݵ9;ՉL8AVD⠠9{rj#9 +6J~ j?,%"[^-znT"o9_HDu9Lm' z*FxU䛉j @r!xd?ulPW2@ tŃ}^}>Fec }֖8OK^-^k,73Я$3KWg(|y7 GN;y|-PYy&ˢ(nn-$ٗE7o.KLep3Ɩ|֫xH IW q[)ҦƆ!FץYMK<=Pֆ ٟI+]rbg3l(^\e,7e+Kۓ 0PG5IvjjF +9D@XKa!jXD١$nm$Zˍ!=;ϧJ]WYau0S. <<:_ՖpXg~yY7 bTC^ ۇ">! V:=2YVfw +fn3x![E]%Up"Q d %HjqyYA (*t)x>)-=ӱ[8mE] 5"0CJNJȚ9JPG=eoNA}ƕ2]I]z(3K>?ȥwD?~vhT$F} (`HY9/l^eCؠqV]ر{yy9)C4(yK4H +)Q6=ucfm$Mڤc3'/g-LEbPK,1΄I=DBt3.u`̌hqǃcԸKjD?В&\6[CL//Kbۣê@NΧ:/Žpن=kF#^C hSa`62ֶ d$m2o*TdLqypyZʞ*)ΤTOLWc+5IQ^bӬ;)oC ?5wMtSˠ'̿ܞ@xgIH:řHK 7#>&]d>ۓx?STv\a[kֺqcߚ E? +|YX_ޱēDP0o3+ +%F^RBt?_V+]|qyF-o"o~񖿏!9N) +hw8+.BLnby8$*v*WwG`iZܧm݋OebX L:35fG@/͖7Ds+ٖ8:)]iSTu !qgkpgյ{Ea!E+kaC%]qXI?ߢnqtyYV͢ +w;<#;%3ˑL#Y*ZNq +)D{H4f uxEIlC RT|>z|{ѡ"GGi^Dgh< 񛑍O` +཈,WLV憐jy=3!JJ&qfGP?`u'74&gX T(rKۊըXyxAJ[͝K&RP %^3 2X!H$q(؄!&|y[XE`b%GX |u4}u aP{)Mۮs6HlEٕ< w^Q M{SbPwS'$_rU˗u17-%ud1sdf7xc )-MkM1n<&lL*1JTl\)A 5"WuAN.V$8.79rqQ.hb7=܊ZWDr¬(ܜE9{Zb,`71keU jrBH9x#ԇqKAY{eSf$x^'Swٽ:W ";ՁW!%ƭ ?bdv'eм%hqꞤwFfkD4V7eSlֲqΗeNnh_04#%:@5qBe>mxp1)!II4y+I ࿹?d,Igc y8I|7Fb8&/"7n)|+GgGs3>3im5쮄$8,v4_._ʙPCJ=cKONf i>_}؀f!?6I?6æp?MDp2N.Ea"7ؿ7o?nyM'o?Q~<}a t<'#I>]#5zÿ &dDh_]dL ڟomBdԨp||jVތ*skX&x7_F)H}˪>:IOfp63V_f,JTb N㫲S|\v8m>-{dDs}-+'r"N{`Tqth84UmY+b'[9΍^+E]sS u/NHe>iwyO=Eooysܰj_#vK.Dx{/pwֳ:[o_/#JF:Q'v?ۡ/Ϸ%sV:_," DK + hO j"'.0aげLp]Hysa1f 8}t6/Oܪɵ^Mv/n"1FW{^xy-LʨFxnAX'tsO9mAC +rIGRu05y.f9ryE ]/YJLJpQeшUE<ĭvӅY~rTXlڽlǵx|Eڎ_`mv3$״{LY LAkf5HJTrD0H[#qfnɮ0 +Wc꧌OƜl–kfMLݼڟnPAet,:,Z sj͓-vg Đ í%G->xnƐw_0(U+}gA:c=xUs/z<'~ >FzaLd`(*~m +сgýk%;?q%ÒSK5UK w,)PnIu~Œni;Xk?dG_L\U+(L%s?FXFq)EyqiTTCJz|ehf%!ZȮٳsj . <ɺ-_ݕ1h9zjf=uT ^]Tp[ڇ77j+Ud<~-oܽgιNR]^#{<[@=`QHҡ,iN.iJrH,}3"S.NU%2AeJIy[լ$UtJ +QN54BeQ~\ߛnS|>WÃNJՀwTZ9E)^߀ \\H˄y8rK +q@5-6(|6ɠ͒赧h='s2Kgrg"bݴL +X ѥa3PcK3 6FHgQ,;*ki",뻮OvmO+me0|?19L<z`FKT U0UIvY]ϝōgC%+[F>,y 8zE<6\ Nq-q5ƌJLLjr@ )@~sh2U(QFfhU`]LΖ)51Qek8T$)֤&amZI\Z͖&7jmpikrm9%(S@{Q`k&S>Y>,}ZA +L R<ۧ3H3RPR5ťl)Rv9\Osے$urKҭ1t%lphmS%h[vVdUh^uǺjfF\kM;<K :`:J i"ȜxPz+z/\Po6![TɊQ9$ +.)ӛp?1L<C$P$x*V:[Ag PNp]. miߙq}c/?f̤h92< ]m QCRhH>]%:t(]t*}Y +l +.+Y.V6V->h?} ?L MS~hQil28vZXՒM!-'ecɲJY Xq0G2JKxQCT_rUAKՒIo>ŷ[:#,EzPj`Z$b7-B9f24D^ QTu$vr%s0=txK"CK굴WCR;X~ˁ8$ے[&>({Vp uY;4Lmp(NC@YnyWjOe8j_"C$q[6UE2gҴA? +`$XTvE$,&'TE)([ +8TrH<∺9 x=M馐vi,ag|ad|G掣+^2 c8TF OTBrP8vLj``8R ls d͸b/PAd))C6mR9DdۅY7Ab +jN j~~ hT@( /ć=O:`;Q3eU ET[H,6Kv)b}iX+FlY2\魪K|e_m}CLM[eGyJw01+n eaa 4QM[;:xj|UHO!#e',X6/tEW/*}13C2S(SdFNd!י0ePբ WGP=P4QV!xgF܏oAY 2 ;B8΍ /wl5C; +bqX3m*IԔ6L$|V RЙ|oNh"Qf ]P K҉&`fk^[zlpt?@eۗ˒PPJas݀UäG[{u:J(/L: ^! +/ Ϳ_١ ^x5rW(x0yb MxaȔEkcW zIdsHZ J,2)*A*Um_Q%1&ߪsBUq|j3oss:q5syl> R#_.غ'5axÀ`,pnBFd rVF s3Q=څ}P1U_k.eqG72x6Ᶎ.Y(7v P1 ^= +|h.tG Ƀ<2/"E[ B@'FE8oB@N> + V0JΎsXӳ_uʿBdaTw2f \ +Ae)W$k0EYV0I-UvaNe<@S/觙EfJjp@I)[}&##иMz/yɱFZpx+|˦w)*]h1Nv g ؽYS&qIA[ b^|.2udEDnn}V ul_9~2.&\宴bg%\㰖 r +%hd!rY?aW~!rWSSHSN7<trԔk$c[xPwek2UZQR2T;+[Y`_ƭ2rXTZNIGڀaƶ .|*\\ayB<&6CIw_ Pk:Y7/=KײBޮ%N/*EZK }E/jlD*z+6:'̉tx$bjc !Ҏ)}FP* þ% ]ࢯ^Q= 羿 l݅(8 dь˫.A=`V![w6+Cevkx/5J)S>Ov$ð;/㚪7NI>E6_l@̈R_UK1۪h/  %ШqX(׍P9~k<% wY9.c7p :`37G :H1aQV*g|gZb_h]=Y^6I~;6Bq3膏M|mܲ)GʚIsJv&$ wrt' T%`wuS@B +mF?p^~{K}סa5~8ۗ1?᷸Tq<٭`,ڒM'# ~=0 ђ2yȅޯ=JbQW/\* M#|VMۭ@=&`L̇K|unC^uHED%+jƆZ5iH(&Ӎ=Zd_my@=YRt!Wd}G3x u75^~3f+392o*EG7k6GZfRZ`Bؿ y6E~(8ې[Ln tmkhY-?~$& +h> BZ|!DvRl Q;#"P|m%wE–Sę)P٫aCP/`"$)b7P|Q'Z7aEyri +͙5/#A۠6[ wc:N{hdWIȺ (˜ۓ~1%;]qDžy*;y}{N Jg:Nb4%Yr=yNb%*9SCDUPcEI.=)_zn%%nk֘{?&t7Lr;iO#$Վ#qV|Bq#?tDFJ_f/Dvܨ`^x3Z&:qeCk!jʾؙ/7T@Ǜiao +媘Ljo 1Bz'{I$d%PDnCLnZS}3Iɣ쁅>C cJW> `vY1%Cq]I1堼"¾5l_~)VU\ZUF=磰9y^eю:Em߳S.q[hda~:2jgb*X ,Nx:yjM#yO#WtoǞfd +`V7U1?]͏a`'"m:{RN4F j45 C (t6uE#eTFIjDWv@ hj@^}\.Q2áLI,H08Mf'zLh=f$Oܤbdj"BMro;2H;Q  +ykآW̳ksj0= :QMqVp;Nӂ#M7԰b 5Թ D)CkZW\ QXkF'5Qq K:N}c Po2}PոvSp qHs'Ҷ:^Ԓ~lpGA |1n/3rz_hXg\۳o',5eFp*wDJ4/r`NPh63}H +Ph0 +SKjvQǀ*.r! ( e)~8\8х ]IֈHN+A$et +鰝t%IU&qtTV? Opur1 r?}D%Zz8q3rK-7E}c= m0+B{AJI|DFГ1u +nEQ׏;ROW 魗0bRR{)gƖv&MV@#!u'6ن9t +!G:z}#MJ-EbM9LJy EtXa&+e÷OĉYCHດYl2PW \S陙O=10HIxn~goa917BYS.-` xފ7V{ԋJ"FF6㖏}@?cǟ{shٛ$R -&ws[4Z|8E]c}T0R9 ,ŕӃwp'WVU9b0$G'8F$^A !"ΰ5/c0wXsܬ *TLDJI © Xe'9J(½r0/٧IQg)3P+zbUq"b"X=3S&? +FTB@apm{$VJ,0 gڻ)EdRO i\d0FRF!qYO^M 6$.qu8|\7;Ab-x!Q봚DiB"9xB˄Ѻ".@K_LIQl^ow;yeܹj?b% kR|4;0wOoȋ3#).DCH+GD\3! OUoҬ~[ԟrǿJ/S'?Ygn´V4Q.nCa.]mja $ Z&l^ ͗1eMDBMry{3ߥv4Ӏ):U^dv~gXIlP8J]^)o ߂s>>:ovَ1t4 + +(4ﲚX).I+<ģ{2t\QDI~՜O$U͵?4!l]LZ[d"?ޘ2Q߷ȯ#!T4<Řg&Xz9y HXaɜ] 1i֘mN[&pg |7+',߄elce ci.Uۻ_Q=FJke?R'#U}YWg֥='$*9"ʼoJGuVAFe4w12E h-+cQB nZQ\N./ىrReƭfjQg,;Ҭ.3d~6OB̍Cv^>Ӏv}>eˬ&Kg+;TgsN>+}FM1d:Vw؂'BH[1EbaGbh(w4ϬwO%ޘ7ZD"[и ++-jP1_wk*ݷ7D} Z 6;iɃ --ByO<ǻbeBc߽P [92gwK9R_tҡwwWW6Y][G0Zׅ'TcP5Їw{CUW 7):{Y +u{0TCwT!oE +ph&斃wRAV^蘖uiQeom5jQ!#iu0wTQm8C,BBzAv\^Ϭ4LNnU<8csrݞiSv0aKBQ<3ʻAU +T§[PcAgަ38Hl&7TG)-N=;n\Q-4›o%W-)CvgSBhٳ Mby EN|ǡk +H'fmm՜7yA֥y)wO'չ*6jeĿZS\emY%INQ7 /EĹ +1/_XZDGKS8<,| oap.LU06}4)H;c'~pᩭ>PLv'^%k\6Fȕ_/XTriQM˶TE4SA;/ԡ c>xTk L7+%7DWy i`Q=*2pbi3qCF0S(;A9L;h1ƇYIkAyWH~~h)*R, rá?dĒa@%EŴ|btUڧ#*B[Uk]`Qa⠘dgoTPtLv5R|uۯ +?pp}C;TҋS`R$pBVri!FGpKp)HS/3d+1*H!hʟZc {f(µY7_Ձt5 5ls94Z'L:]~I/~>(y]b +} }ϓqymP <6,0_/2{'4Ltܫ6kX_քnjj^ڔ/$EsG]fpX\s1Q,ĔjxmAB@9Qs)n@oqI0g(THOi.No|x.t93jL$`hd{נz?CN4z\œ)&ؗ x hpjuD#IjRƝ'GLψ 'dQЎM4oO BVA¸e4Kv68X,ļa@DVT}{נ!N-ڸ O?t[+oݒJ_:jEa*Sp%&Luu嶓׹ RU:vBO/DV{pTm{`/?xqR#epR9#9s*w8_l"I0`2I|pIKᡆӸ +v<8Pg&p<0d1cGҔ9#A.٢[NC\PCydU݄Zc͇hXBԦ}*~e{A~l{W6$Gc 1޾&bI6Go6rhQ20P?R +m\<^L##^I`lNj_b5(-nā3~-qlM$狻'H~h)"i Ԯd]}PؠL%3r+1C{z^ +'/)0 vKL$ +F*)LLWI$ +) M/o۬o} +!ɘL}o #(M +ye!uv! #F %!$$ѓ2=3K$^ +э*bio RSTP씆,)3k|.i#yh,T^aUTYdE:‡2

    GǗ M5sfJx.4/fp}J|WMrYLɵ')'_ޓZa4 EZbF[Dk~A},!zP6)WUf ^Tp%yZ%t d?jфE5$LAD~+YLɳ^Bǣл:yWaǵÆ~w^;;y@¬\4''F›jîK^P̈́5'-#xvN|h`y>^F,)]9NK+cr3u0vDu'4nFk2_"=}G^5ɹR& }9x9\wtڦq՟qҥ몗2>nDNEb$҇ٛ%ˮ%/AkW iK zhz뚆M#>0S̷dž6i0ĂM2;_A 5;:??vmE"9-gyhat;3т~T&]-_c(0KpPv̄MT~~F(J3A>SIR@&7Pw]$E9B olnIq5npj}!EhVҠD! cPDPs0cb,cmt%U)J +O +MyCl dX)Q`\]1ÞH;iOs${@vT4MJlW71l͸5L:$H.b$FTĂSkqP!i ptn +"Hu59:߶^26{/=nd;u'`nwxAau; $٦9d QBi'4D6pӐ|MNd'B‰g=1㞫L8=^dza'mi|O ݒo768 W8@K)}/8ܠ@A5-@P \8hBmA(c2̸}?g~KLT0HZd\ aыK~3/ +N]!JDa" I,f L}aIscWP!(TcUܰRƪdf+:Y/Y:/vΩISYT"U6u1;]f&9ztOhY4k VXDEܾF9Pj0q=saby >H޸ZHX׹Y!L]WR+T:sX߹ZjDE \*hB-f\bU5^iɸYF +3+s^m)H?O +sa>QUC34PH47zԱ`q뺁P,@t;W*`W|9y Z "yY--瓷8 Sb@2v&Sj,:Wcw '&QgKtrw}Ga%tY6xbsRZ0' w&=IO~9~&{d.nck$Af^- !{#؃aX %>AatݛNB9ăK¹;Q.2$ڼLWޟ7R^ϳg\3P25iȰFp!9uIP=Qy% @;\Av;cE:H/ޏQ/k-Z6?trI)&_#x@1{e`tZ%ce=RR4̗ r,ǔ_Ճ>88V Հf*X px0({2`6&'-$<\,JWԞ  6k%?8N9/Q,k- ~[W^#l?x׎a٩w2IӁ!Ʊַd |SSilƄ񨴯y׿YkTwΖi|R-sm;-ndBGE}<}hDPۏ=Y"RɻFv#kIL~x{坕9T2a7v;棌||ܡG$k^2`Qoՙ]( eQ̏;ߗvB\(#2]PA1U]v1M*:?`-5W?!m@QN9,.d>~]& Z7i^WJ|?] Ao7@R*/BVE6Ms;:>E :ɰ?h uºx +7)r.O~TcRӇ;x:hzzfߐ̻K`M᱙WN9Z~8#8 QDtB]wl8 +`b7; jסDq;%nք pD97S:W}1.rj_ !_ ޱ~k8v:ܚ +;:b0J7}b̈kK/4Qx!4x([$`vaw="ȑzo'}/Jc͈krN(us9yG/É́u%@ E,~h=ogn^^{sM;ItGqSW|r!Ͻyxv8b`Wo :gkrvӚRԸNf}~J2X'X;5lprixSM1kWFXwG`A|*v+ۮPk~~`_mWr8 Wtzߓ2׮߯3[%ݲM /e}_|Z6]\vTw왃-)R[&^\K`5zc&ipk?يKT>9\ߩ(Yn(i2ݓ![JZﱥ1ݙ{Wֲٖ̮.I&_}m'N3}vQf||\˂ mUu%7mV^OXLDe/EREQ8^Ŷ.vLƉ FNŨV@gyl͐li879_~[kK"0pzǡ"[]lKl?q}.`9#%ό8?sgy +OgюO)=yN-@WaDW`Bb2aK(+ O98/hS@p:'6[6ዏtd8Ay$ۜ9-F gtmrओ0|wLdә/of^X6&KiAYvTøΆ/E^Nrh;OAJpޛ\o[f]m]+^!οf9ҍ=N&"r@VѲU:J_Q;}34\u"VP/:ת ڢ[%/Pq:VgSl롦9L0tOUgYg?sftHBCճ,O*"+PFgVA!R}ݟE|s6+zpi3PZbAP}.&e:(0P_/S#L"cu҇^@hMK5l_+CЉ=a0[ZZs^U*?nTIqkU>Igs oAxbgt]ߴXDz~ʚ ȜNp$:>w_HW\_1=NzNul VuQZ\V;[$`W 02) jcT/c%V3Ayn3~&XQ*@Kx3Eh)G$|=Եl` + . ?ЃFR8C\8 H֟li!Z-9OA<սnqaz|3WIT8ҥrwgTI>]nAJZL s$&\D 8CjG2 ,ڌGSMБϤjR_(w +( 5ivi.itGI0,  +(~7E#I9vӀ:ˡڲl4 l~4z;'xdA?˴%ʚ`~ML&I7rXZB ms\NK^d(!! 5L8iXBNdC%pY-]U2h잶CV-.qqN!Rϛ+N-!'tr-sV +ϛ'sΑczqhZp e>J<v7Aa[50'T-d`-eСZhƶHM=:%_Zj&{/g/8iD6~>T-XuqJ&J]"d{ ӍN0}lxUeg.NFnզӹ[ jIύ෕y[Ղu^~0!LxKmPU*WZ e+͉_4:Anu`\O;+c S*CRKG\>y"V255n9ߪ-cB}nu(q&q+xq濜8_?/{h fR1\DOnʶ_q>TE匙>nd ]QPLU@vAtv2,X-jHG r37Ya wmRh `ƉH(|(9UrI4D4-Z*Qj%W^2՜LQpXݛ݇mTA4;6A fqZfs\J3L{@}vڋqwへ'/mvx1*qП%=`/^WW`F+'$4,%Z4_׳?4'JpI}g˝pDl::3מ OCPϤ|86$/az^ƿX=J>#nQh{)Ua}Sj;ɐv X0dˋ -H.,x ++Ur?Sq_΋)J[rmWK䤝ܞO +uF5;ƒ?0yQ%Kp)FM>EdP}ЌQ clː]3ҽ՝k4*/ +:4Қ + +KWDI{F-!slt8fc|*0Ȇ@W#'U$-8d^+-}Ůyx8v!FMR_lgd?CTe#YA+ lݓErUď7z}I4d9;Wы,_u Z!+`ٯIn)!||Iv TG +h \Zz3NH@YO#Tf|IE Ԡ Cbvtgb̵5!xtJMt]4<^xuƎ(YOļqМǣϊS\ҬU%T`r'=-LӻuiV{;HE0T+>:+)ê.\ݻQh5YƘ$ynFuzg`J Yfa+kĿgýpMIl2SEVF$A[LF&ސrrf e).]o;{q-[z{ߚ +OxJk8lyh QlEbE4FTT$9(I%6+6?WT>SUYq|Zڶ>hIV' N}|oC +KNjL8HIbPwajJ9C*Z F:Gx8ppk(d"6>g٪{CҲlw`z-ӼR0:៿,`dL}^ +h*({~)8lɠ\йp4%4P8Lɠ#aƽeqg,jфh A.XGAٛ, 1//̅m2oBy= ɌiN`D;\jo,B9fi(Btz~!<Z}ŭo^_{@Kx}y8Eֆ0iz-r񈕈1Kt6|܆p6)s,;np e\(ϢLP0:M iISs!( +4L I#';&S`YIs2ۛ(0(^+x:i0O"]~ũopRSۢN +\Ժ2x`bX%uy^b%syVt4sZIsrmme,!"6nPKfm,+g1.bhJ +Փ_?0qCi Ym%9ijBQx')'~bOa\"M|Y_NldSE yCT=~Z"kYya|c.}Xo r +2 (D!,/0:E*j55M LILݿbR{+8֞ Pҋ}Zxct)Y_q[kt5b8"^\1SCYY^v@Ar !1f8Ӆqnpߦ!>䳐'6,PI3;yZ/h-P.iYC4"Vvq@a2IB.¡WyçI7Bsk>*ae{P*)ȧ"QzΓJzLxteb=i2NErFoL R+?)lA6Kԍ]٢(K;?C cE=uF icm2M?Q%m)B1UKm/c2  /Cˍ9&7 J}e-z`wtK=wAx.4xd/w„  QxܡϠDqPO?C&dA&1D ]DŽ=(Hh3!&♢LjM;f_ O -AT.'M@n9"@LۛI2^^-;8^_0e9{Y "Hv j4@)e#"Ա={_xxf(|k5ڢɎ6S, %`pX @s `íH(|LM>NGQ/`3V2w=^* -r`IU_fG+dшSљF|ht-JBSl!_) 1h½ cj_3~Π0ߣcu`yhQUg_Ъ/4.88#ی3+Օ=4MhYCVXxZEM/颕x4[,XOZO({IWRJEz~w` <-L *H%$,\}L"'<Ǧ3d6$pEDf)L,+`Qˆcz_~*n6n٬6̬kJI /,[fk8Fz.U+TCaaݺ +TEeJF0|XAN"Z6c5>,]6E+0JgWG`3,iP`U;ދ?f!mJ NfwkU5~Ff3iw@ mhLVpItlwO;! =.\f6¥C(<Jntw4@8[0hSUM2NmUMȶ7o'DcwW*߭95H3HC0Uj|3U&WXv-ZKronV7Ԑ 2t,(HI,*+lh~fOm-ȞVtYy|U zC%{+=oPfBFe~>ĭ~KHlȋ#geQI272 P&,TZ6ZaeCaIOL@>a8vN.7h,3.H|0}EgzcITflYx0.O +]EʁG[Y&"T#ێx,-Z2сARq8{|IvZiL,J*ltx4;U9L+ILW3>*n0&\75 +VKJnqEYatny'>/owO]}SK]@caM!ʹe0=HHe'򐝙EYdRʲUr g?Gw28Y+|j%+| hL" p^,[qc5cf~e{"|*}P>=k[[QT` +69?&OkWhpPje-Zwaj~*v=`BhizNU*Br^,dY{80>]!|Ӄaw]na-!p?W?/Ѫe{k/qpZXVs AkkeǴp{1昿H1}ݱF1헿g F+Y,O,Zz^UBrw#˙m ' i&̞m#v%V>qFdׅ1"z{e+xڣ/aqPV-U _h9:<}!Y<hhܬע~~TMCXMye V KMGri_+YZgZj7ޢuۙ ŗiy,jA׮jVUuS@qA_ZVUUqnY&]k/fb]xi cN b>]w)]˦Zpw,z(GIkeǶ_07;Dƌ=pz4\Z^m%nnRUPSoh7VX"Tg{Rvw@2 MMepI{wk=#Ҝ71f/g3aMîv>57$tD}Rk@D}5K'X5~ij0UЖE% 8>G׮q/WKnk9{w܀ Q-F2+}/IB(C27pnQ J + Wm+2 Ax96Tr*k0K~̓hV*g s5[^V%0uk]䘹he-xhlfK2݆v>oSŨBTCwε*? I'ʾW_t/Ѱx#N2n}'ܹ$=䛎 d/FvԘgC;hZE` jֲW!M&4)y˜ +i:I ş$A3&8ğ hV *@QQg6따E{Wt84ezj֫홬u[7,K?kc?Vummz^ڥXG]VRh vq9뎥,.G}E9AaARs y׽Y,tk̹DCipudҺAz7it<[".JThvmSdNE<75jm7DVբ8wЭş`sF5z6,D %e4ky R1RL#A,^ݕ凵q򪀠R§Oe{q/XKj4dₕ ,6^ 8HQ?L^Z1zB?HSpfΟu{yju)c(D]<{NN&t[tKRo +Ꜹ |(8q+8ee/>,?ɹgݰ\d<|S1ΚT +mVqfI̷*gH3='Zp,gEM km\d39%1N[ǼȡT˃ѱ+L'YpB;zD,Ai_IS7YobԜC(ҧs@OV̢q56 Rl&+HsHU}KQ;³k9arm2*Fmm(!N='+-dw8 c6f) J+( mdC9uLW/%6a ܟ,i.}A̻Řlo{=I6>ݯwf(jZi5u!ClDl$6nFslWvpkdwO/ӾtᨧciEg?.]J Hv+%JR0@Rʭ}L8ŧ/(j zZe ;o&&R[@MBo1K)"dǟOd(]ELQWAP&Qr[9я4ŋ/<<T1*Uɡӡ#b,b';[y,Y},g3_a2 C<2p+Ai𽘸3QdI^E.4unx7):yپY.;j!ʔl,[It%>DVxb6Gi[OS/s%Ԡ\A#8|0f!1Ц=e"c 3`wʘ.]<Еw2EF`xuy*LЭ?i<25^k`C)W.>lIeGJ'߶uz;Z2x"F7'unQ@,yipb|Ŗ ' 4 +&=F`/G"+^N+Wp+((׳OA@4dx v:FTk<9 +\U|$υ(Q't.܏LS&Lj☋a28l})0FQ'& ߙIya& pq P5c hz +KJ[.+{G)Bޝeıy俓DVmط|ћ4 .D.G$K%:-) :ΆHU!+ϐ 81,gLr#q0)|hѫ:D.\IJ^++Q=$5QE~ +kNmneqSY˲%prU9,aH2`W?N/D'0;$؀)l}XXcL*DB!ǩ1&A'F+4|bVW#.yg.0؎{|xM;UEB!P: +$v$91ܔTg?cQcfuƿESrE(.< 5x΃:V9zz/1޵v\SNG`&g$X-Z/3O.yy5auS<[#H4>؝> ?{>W2'Cz|ڛ64GJ[rnvH,vK3ݫ +P r͜"4yQ`I >/~Vڱ8 >T~Dct(-Ebʣ0>DHZ߀X &I= ebyXY -Eb}X2$&%a#|Ȥ_ a8LZzNWpaYJGa W/Kşfw E:{qW`FphRkdȬ2˖b7h Ͳca(,/әc{أ0ЬL4 dT5,Z8 EfɐYZdKfiYd >,vf"p~MY=,tl4͢A8ѬjR4fɠYZhV'M239trY\>L0[\&86f2` fiY2`Q 03\erY\\&M" Ȃ%j."",X$ bYLVEd0Y]LVd7]"/`) $Ga򙅵' _b-[@BI6IhaZEhZ[r:a--XSkuZ,kyZ-P89-N21L `j-ODkњA6mD0mu>;Չha"Z0&MXӤh\-[F+,fvyDk YUшbOJs9VJ (|w4+'Tfw;akטa<̶8 +J[U2S4Q8\.MC:Y*Wx ,;HǡGt5R=r퀼ۑ^fu`|ʲ\6V!/ K w=<v\s.iT#va(ǖ)8GDL o<,GImm6f`T{fNtLمsGTO݅s=!",w/Nq7vl3@=.iy8Rjj}fgpe.p~:AzU~Wze1# qӢC_v􀢇&'E4qSC# +*:[Y3Qh'!͝wuJ)ŮzHn"ct=/vM +XC:fXeM!X?>[C!<? q?^ z|8B"e=|9wase96n|/^d!h~.5jϹ)S5 x%Ò c#G "d{y,LB")ҫ f2HKׅ傧%nq“6r([ R`и?FmOyN"Pt<(!T6X Ơtb$t -Zvu!TE=m@7>TfMCx4"=O{9W1f;ovͪYgHI$&{M.[>jF4 oȦ+FT.sG:?CSd?iTA2jp]W@vtgEܟvO:I_=çNs=fML(LlL~ݤl_H_AՊ"m:l:C6-6b|Q_=2SܝHA^QJxP-sR7tmq%ل${ + +dsX'g,GȉTs:WhdSCBE j!0v%nd6A`Œ%ܔΡ3H wePbugL'T +BfVl2"jw6PqyJ׍҉9Hw7#ޔJ] x]9FEgKLegGuZ/)2K6%^ԡP8=]wNKrn/RHSXb7⩋iw,ʮa; \ +ҶGGD7/6j.$wJF O( +eLۤ4_{tBB(T]U$Pȡ`̻.s5R&&%fRxS /+4<Ǣi^)W(=pX +dR骰Xd$MLa|H907;u\?'\Z[k}koh+ʲHVG7Ӕ7Ӧ&%Ƥ%UŻG@PB^pfw![X!}jTDio?j բlr2=;͞C"vf()N-Oh%1E1  .=SX#co1Syl*njݭ2 +xME㛥쪩NIAs42ҵtuq`0~1*^?/4]:M✞2|Ct?H;"DJd F;dtf.8!\[#]]mXSʗ7b292Uuo\7Vi.^Y=}G;l +8{TA> h"7&-ou٩)m-Z)ir?Ŧ#h,M"fJ;b7[KR*V Q$yXj[ET;AQ%nܚ)Hgnl_VGWGDW`/E^lHlX %nCg Ca‰\L1m`TBDGwȎ#Ǘˆ؜4[ 4О:bOVVMx36g{ ) "{Pjau,]'iV#P[^ْOS$s|@{ C/sgplgտI#ZơohGQ>cB]R$"ȿ45 +Dͪjk +u175׸!_4n{dH]ʭ@ɸK>w1o­q(eN d76FNJ}hc6אM t14xSC&gC0ZjMc ;x9tP*m,ɬOk6a}j:P<Ҕ 9()_֛Y-1%!%h[L"SK@t0A,IjY"46QkR9VBVJd0b D\˫mrɑ$~NQ?iH"G ܞѳkf @JzDdn4TIH\:̰KffPL: X> +2@<~Baی&p*ZVW(wW6eI\%B0 )I:a20RY9GԓE*DtUvtqBKY(2L\ E*@ p|@إq~W3qܠ5մѡ[P{Qǫ8H?p*LRƋ~=6%\tW׫ͨAq.6pS8wx]CזɐV$/!Ha ՙmX67-yp!C7CpVGj7QV L?9Lxڅ [x?Eϰ6ԶJYcJr +nX<̀9%a%U{,=bN{W'bzRYY}b<G%/f^X}'kf˱/ʂǥM SRva˸]..uq2ۖQ/:Ԣ% Vm)a%Tq$˟H XV:lR auRo,UBއMѢ4EYO*gZ`&=^C+%ϷK]Vj 3X8lxl{[/vJj3r\e9^9a,q+@:Z*AP =ÐbhמdFn@ӧ5.HAsJ$U NtR<OjC*9a"!8Tr!+"IhDeT&9*k dۿ><{ \̱oLK(SϫCݞe\LM[=l~Mo` Bgf&f;ETsTF} b9Κ^TY,H%ʁ6'RL8Q34A$f-y>mU[,gp7}W+7/=gRTxDYMQtu-ptU^d[ ҵZ}9@"WVV(A-*Sq`&@"K}#?-F]xfw9ܓqL rURTXYÉJUcU* +8Vv!N`zN@*-î|έ-ޭYÐ knny"JT%@%,x-OW6ܿ%y7HTT^HV hk{pgq(e-U\yEJr%waJj؈"MƒScW!y)3&LA=60/'쫌?HџzN16ϱtقibh,dmQ~:Ȋ"m1$O~`saKb =@C$6z$"%Jz#һh0v$ʎq<q~lktP@' ;!7;OlFО2uBUN>Sh [9حo}mo[Y*KsaSKo5Tħ\M3t81%9}M) AeѡYcK׶A6s2U|@qf[8-ԩcZ)D5_a_&w6YzO \So\JSm;E8텆TM}EP[a|6e㚔BD?&NhYy ne!4E.5'>>#R;\P5P7=\+l+4iq;}Ofey_%.\%$8\S^m@AUն&bGMmJ`0zcx$F^jc{/,bn9yw{GB" s/*q["RF:{{}hp̼ۆ@r"泣aS_#v{ +9>5}Ұ~yNe,5ZzbN脈Z|.+yILKSlU4D:^u6=zo + +DIs+;T'(Ze1hIG<WO/;aBE( ]`TNv6(݄`z[)kfpZ*lNJQW4 ~G Os$y{䐜aUQn| i^fHK-Q)1e@,n=UjX|#z5D̈́,XGmb !fK+A}P7 걶~B"Å~f8AL&# sҘ)bX'%?uA0.Ve⪉OꅸttI@nar= y;0tƞadx ,\<о&~[=4Vyħ ,$6!6Tg#޽rtMK{ `cF {8"N\.&bYf/&lwsOvȴϾOIʊ 3k%V9&x"j`fob쵮2RP8z e~;p-7.`SnHݩ wKŽ0ePuM~qEרtflftەbYT!*D|e]mAcH)tބT2iZn}1fAJ0[Ȁ ,xƜL7} :qxĶ&6?w+rg^&<{#/}Cp sJƬ1LZ-PA\_ԗUPA[IȔ>LO% + EI@|I +>qyk: +IY,yOd&,7)N:2faXre Y:nZ-NkPݓ_Pb_PРqCȡ<0 l2%${&JٗX^n63ۏd,Vyϭ,y3ܽBDr3ǵn v/iՏ肦&*û2wH&`ڱ{Xg)K,54T ӉemfXdZz;_@w1RIhտ+O#ZZ4p]*~AloظV20i2LS-h)mс?&?BlˊC#~ e< ?I0d.lP#e %cJFƮ[] lNj C-(inhŹVT=57TgaFVfPM/xtŘFl|O%"bXݚܪX|6i;!$\yysFy2|<)0@f!CiSW"*X]rr>!"`TRM:L>%4fz^Z¥WQ {㛺ֲI/xޚ)֦&b-5 +w5̶B NOŶ7ZcDǗ34bd"v(M\A KwG 'İIYI9?P_KAm`Ry2ft4zҠ@/3R͹K+Di~)ڏ:\Wbo&&xNJ@b:й NHݐ|4.907>/VȭɁBN,K[h{\0 6YHn-ŽF \ Ʃ{!3<&O]ޕ9dWsTD@ ː#4ChShIY(`VD6g4.V4\s<_4{ZUa1.U#҄.Չ9#h&_|dLtECw:k +sb(K[A~O2M@t\Q7S VbE%v^lM*龢>ů謠:$ML:Bkx4v߆Fv +zfIjlϏHF(XnGCY`xFJU[h>_q̯r-). AHg/1|!f(PORTͣ\֜DI&[N-M~mxil7HJB ED!Pe.EEi8rc@1݁A.SPi@nEH<qDe3-^L{dYϯJ j (E{$r@d̎Sro v +,k$ +MŲ)sf5lo|xi +Ox˶{pes0ْM'W*V<ַ)5l!k +C3 +8s`/JQb1g8/Λ5a q}vy'49 n' RgI FH[~JY-z=ʆXY''VFSpW +g10R:L"ҮmEl2J5!y  += |Ȍ߿[B):PODt$E2_%IHWJ}yOtA8gs+UO4R_H hњ`X 7c/dn`nɟvKSQt"/B#g(\'ݷ|1?T@FId"3Z:a:.H"C4,X^ʂÈ)MҜ#UF""ib+~>ii앀6+̜ӰYcM1NMP )?Y"⊤Z9*tD)!3"ڌD+g y@\P,ġ '`8zN.+2/R/x +4~=!iXn/qesppisisiztuw}wȻx n;a:VFL˸v 6& 岵=uKG ,|@B/҂>``%˕lTU \pA*,gD:ypSe1۷$OZ4E! `CNHTZ]E.qGzm1;&D +emBhJN nEtUr͂l-HN] z ^nWI`R`7QfX,gP25 _3H. WLr~@F/Y4Oq?$ \oxeǯ|.D+D_rxtد×*ŏZA<TN(iZq@%ބ *H 5pg7K{"˜p#kA|h?ÊJP49,dXRwTNf5:|4ej!B\h``TY0=cmSªb(@?ذ+N(u(k0IaU,"ONQ>%7]%ن(i4SnT&@E֝HpXȪ|frJE,RjbA% Fxz . ` +FGnF)(/Bs୉z}uMȂ@1?ӬԴ6}bbۭ)F՗z\  }k`(l !Onzw7UpLdD ӄ]dQ ElqtǝMA-|1]p(>?V~a0zƊ~X򅱌,,,,,,,,,,,,n-i-i-skقIJr-H4ڳkAlF0]fc&+7yì|U6.dy-ftCEu[:`[ϫ#ۤ,pKO܅j)DM+ήBEOA{>bx>W涤y<6i4S ii3X.q*m/ZciLT(K6wxdr 2Z<_זL`7De`>}JwsK m38Gz`u6tWW +>|iVr˦ *N4SP!$SXqxEM;>;ORCԻ+CQg,}0z" =,ts<ǩ^,1 ,1pfP)^d6:;;eg6PF-ΈxbǹNcλzm_n߽quǍ(8`F h$$ +>pȫy0-Â?b`ݙSؒQ$k'DCjY8zE.c*k#-p®S|ddhC0&w:oMrbWYIPFZHMͭ 8D<^Ip%c}֍[ns[]Ȝ@ !.1 +xɲ w/q cx4&T߅ :#jόr;,aF[U jǪDoN㹰V6D"r4{ V7!DFkY@!>~J&Oݢ48 Ԇz5x` Yf7ve=7rlr- s<9vN(;' crr<8AsB)0OJh;%,%pg0xBiBE0 &Y 2_Ϛکkm׵v7tѵـ+0hu|YBRYI +|BA@Aq ` ʲ ;GA.QEH ^EBд0Bᴇwf"hXҘJTb.= |,U5a+sdz!iˌզ{:[Yzfeut0|{Dj rWb4T#j%ⷿ^\y5]#~LjQ9)yfuAShhwgn|i" "lX{sMfx3̀jLt.;A<)}M QLp A&TC_ 6CPg7EtA&1l5^g ?sN=)"'h#(fg +l7dmd1{L8wyAsvGQ\8=qD)^p37ٜ퉣 7}Q`ڟ ű^g?UubjeOe}k(f:}q͊EmʵT$gɝ7۵j&[d +p[ 4Q^>Ûo:ζ!t`}xWِϐ ܖ|Kkn JݞOy)Z!oxׇ~vkf羔n}4"_7vh`ThrM1KYo~](V)+ͧ~bX|c|%-\[[ބ2h,՗&I'XGk '=HPTR!tBeI{ײ{sgi6NeI:1c\ɪt/fm!~% z4 +I("AS ]zh$:E$N%H;r)C:_EUTF?!gQ0py^SֆGw2bdPP{9ϾI(~nS]V6i^5tXys} f_T!)IT/HB\²j'9Ol5,ݖiqwzKJ)\iI;b&_gdGbu"ZJ5>)\ i#aZqP=؂X~[|+.+@e-Wy)HʋTi"yG(J`煎:J8^H~ @q4e/w?/y胝}Il+?%鋮QHgG6 +wD"zt6 cZPyuHV:K6Ez?Ƕ̀^h%A/Cj6))ҐP- +9q~$t26] ZXɯii)h|#ǭSN"= }zCd<8橫#Ę0$uNr8p` 9G%SEUDZ@߮6jό,3N7V/}w,ju$tPY(*bGcc#`5_f%1v1lWlϦ+xq>ɻQdeͧT XY!vr!Vb. o"ui!fQ:j9Ai1\`n7PŚAtPa[TlheA|; (0ĈӍb:u—"HA0fɒ1<aɖ`|C ++Q`egM~BJ M d83>p,)mEe+4`qVpJ݆[c`&SbfU\InC}:8qN ̟R}e($oe%N[vspW5Jˣ:\FG +[be +-4OT@xb؊R]Muū<ek~8`{;@bAu\6ˌ%dڭa'ɿZEF,o@1[kJin^;0Sc,޲pp S5+ krHfG(I98+|5 Fɍu$ze=G씶gCEۣs=Gݭ +φas=PnڙuqeP@ϭ{,Ҡɓ/*lRjՒ߇+QWf}p-"w <1%4d`ǻgzf+?JW]]MIД|IW=I'i?$IZf{Ovt_0E=Vտi Gu]8O,q:wٵ4?ZxTH?&S)ddV%[U΁~{o~Keٲ{5"$!ԮQcVhQ.?rx&΋]JqH2q %Ydf0d8CYC@F Km/b֋}۷t9 +%c;\p|r;RETTw6K̖%[߿kPnOOb@յ_'/ߋ/ gɔ47x:""U'iH.*낻æދd_m~Vٛb[øtioW ;gIƏǮ4ybɶnqD ;Z(ں:QO r/Y8ن[P"Úlw˵ѝҁAn 67QgKv6^uTYo%(wnFAa '{p㫒OrR ?-l{@׎0ekۍYvk |T8PuAW5ςؾf8+mHG'@ic=~K.A"{&Y[9ҼֿF"ѦjRsi'˺$i:|ͳ|񼗒ҽbȢ,Znr4ȒiK[^H5,}O*Dc;͐ϏUr>zYO5'[*= /ghb͐EZb5Z>a9~oOڼIFeN]Z:Odpnǝsʖr Τ6Nz mS2-ٞQP _3y\`9J&OX=9sƶ" VVIn>Gdzra}U_80Y:gj(s3z{_6m7zx MtGp55VW\W{XcXBa$Ĕ\.1'JVUkQNeGXTbS=W(s~Oec2OeO]Wȅ٥إuXZ.}0dty^"<{m! }~.S<ЪO}cgHGY²mkd*rrދ,K^:=KX{}|Ksx^[XPF +ikM3綷#ID*?Bg 'U%ǭ:/ ?$8/돈Jzm7R$1`d5K>$[Csy`HL.$I<*{F/!1>^O~dk\ݩ}HUvgqg1S;SS͏j0ո3Q;QS&w}TT34~yJUAU~yO; ЍlUUh[[ +|G{}vKPӁiFVYp_;/]V{Kpx= L +*׵9a86[?k}sa]n8Çlf^D$ԉ&ע[6d5ٹ%l"UoPcWW;XܰrJo4O/c❻ yU@iFeGĵtNTI"' k%溶UH -SU[앰ɷFv_V.s:"1x3NJbؘX9YO);`cjhIjAU7hA6"?7L YbPo]$L@MtDnbWN5"UiCVk^)ary7}L"r;'j竨644ػ%֎EkGZe<>*"uBf;UX۵rmF'C'P"b5)iLkLmmޔA-:bx`'zjO #^hZti\J~xޡC\E޾`Oadaq= rF?Gˍ"ωKrK'qi*.%.C\IvWYZړ'i9_eH5EY_P,@X/&,糰7e>!,MV\`Pp`w|GJPb 2$>R )+ ے !"[r,`ܚ{+fV~! ërٯaBʗFKx{.&@m2S8'iVN<[APӒ:5nyەenDGD/rTrʅ.U?].-w W脷6Ɇ.MNz3pL.6Nj,B0Uʓ= +5SmG!t7Zz5MYHHS21#CVl3d6 /y)4,x8 +N18W?b; 1'STũ_^f¦b ʥE) vcYTHUR6 28V8ة-T!t gc'9װ@t}\& +rbWra-u?~ ;gY[I5 0fevaUfd.jэ#f6ђ ^«EW|"jn5^}Q2DFei:(&BބMGB[=ԼIެAJ\\LoHgYq"/FKH5­azaF+ge MJ^wQ 8DƉ؃OоENL]0Ή͠Fm=Q}6 uvg)rV="\lBu\/K;wؘ(FZ8&9fN/Ȋ;5*ЀEd>Tgr7ZhE6PbMm! ^ BGb S/s49!!@n +V-9"7``HDENXa*|S~p)X3Vi`287\Դ8`؋.h뢟gz#LI +_ hc|Hff:́QB8 +#d֨8pm:pXyʝ~q"6ocaڜ<:J!A"V(ٯ,q .$Cc w-3C'\#˘ИW_mt>ޔZUTaYߙxsB@1G4UmuL1RS+ 8i ߠJ$@9=kC hB6X$|]_gN))tQᛶNu8JH>mGFeFϖ|{)v%SAR56R[aZGARx39LkT!PY TC})\Xq)9֖PuzX%jKbkH]"@y+0M8psaˬr@2Iܵ~y춭'8ˑV)]0(LP:WYg45iƊ +-@QTpa}vpvSJ {"l~AV(2^׹Fs.)F鹶Sz;Ȫ|`9V>sU*BGW>Wˠ^!jZOn!t@$%^EbhKJF +s>K?zlxQy s>>Qkxq:JsRD-paQ!@ +g(]%%*[.YXkd4US:$U) +/:rl];flBJb:y>F|4BLl_K*tˋPۛrCI2-,5EE:*,ѕ#(حWy1D:zyߞߦ7m}3ަ79Oַ-;+&.s#Rmr'#6Dţ!>iTߋuX +%Ϡ>PoT r]s$eǵpqzA+&+8N'Ze_SϳVt%^(a~L\M +͖WF 3.@q68\Zј2F ‹$*aj [{) Ks,~d1]/E[[E1t`j 4z'[^jCBs4-ټKbNj$aќ{Ւz6 ޗ肀ADZM  F@Ad'KEY3,`h(j^ikGÏ)+Q!fؕ!zqdK)\;O,eP@ԹZĆ+2:jgCV; %0ID2;=R[W+Jmq& + \?Khh+`wx;3JJ6<2;ƕ Z<VBڿES_ ^uKJpGLl/< +6iCwc)UJW+(:g(Nȩ +50lRx2Jea-Kcwf;H悱,)n@ok`Z]F$ ΜҢZ7n'E0)X r +v_8"m0ctk=e?Ap+*qZbO[J0Pm~[sWzxi'Mb9T2[ M)VV+l /ZKBcuO:rQlzUvC&pLCƱD"ݯU>-uo~j [~jHV[G9h ưgc&w$RkSؑ1]Hdظ'~,^kU^ՄN8,+sA/sc E,n be0^,Pn0mmm=n&J5'o.(|y3I/ +ɗ9 +FVÉM88/%*h8B+jɑƁ{/Ѐ><hoaDYYzg`6deJ")Q)GʤWjA4~7ƈq!m^50 CTNiz*:М'RE.c +T,4F*`ME{Q&ι/[h ]&T98j^Ǖ 2XpH)5x栞RLR5|X z[i0L&r@%Kd|_R'Fh T['@J1 R<μbzNf[JMNrUÚfCbJ2刷,&,V*huWlҶRe2|cJD)M%QExF"58k Xu=R`H\jNjփ+B7)QSC{{Ucik4Ac)r妃:IWrDh4Z c+4{$ + ~%fߑw+.2&e"Z!+ǠOwCmQaFw-kU[gs0/duOȞWkn.X~R\wcNźڼhY}z.G^ElZv̐65j!̔Q~`y$$}Q qta-֘Qx~9(u GtDCBf1{z[o$6\qI);)iTBzMMxA 1# ^L; s] N"1BCrh\ނ*"s̢L[[XE$*H҆$p$'Ū#af(K"pݕ#dIy|)=Kx+]ϵNGV;S!mQd*+e!**r fwH>kD]'g wT4|%Tv ⌫ @ +ej2QHRy͇>d嫏0]r.7qðQq>y>y2D-B-lhLѥvo'\z~@K-jO > LĠA1lu60Q?F!Ul$adLJSEшji=e|zLwIO5gr~.( yle;@ " ioM_N~Yl]{Eyz6߀l8&'CD{?Y/!@'BUmI嫮 Eicj!"Y:`L"Dh%l6R#11Vh%NLVtiRq ӖF ?cXO=k*P١zBsrq[KkBp|:vcc^uxugO0]*iAy\v\* !=9ypɅʬUT+H:|G ,1@}MxE~g冕0dUW:(C}ǻdqn, +"0PL9FƂMyU~+N0j*)G] +ڻ 'ۿ#28("dCK~ݍ9S:}D'$ u]6 +IOg_rquzz݋pk{έ7:}=龥Z oPCjmzZ'j=o~ vx: B˘jFP +3c21nuz0'qI~(4#5tq="@bd #&EI)Zn=N:`fi'kW2GFJ *T$r#0oswvA#QvFx~ZԊr2J_VV7je+&VO,VƏʟ*Hv*YI?TAbAkSǽϿ[-?bK}W%:40+1FIxÛ@&DGLTC궾R)u{]ɹDUZUls"},nn/j!z Ce,ŨU~nY!|4}z[zeMC ^Youü! ?ya{TP"CvZ;aҍ<˵Ξl6kropb9E(nsE #_|e.6%'V7U~.@],f, ؕOUÀWze&|q-XuWmH@8աT6 +-**jSRTJ׽@P>ž*wCbš5Ogw+nʀe ɀVPD_Hr@+U@V3!tC* +r]l #[a"~6|7UH u` 5g;2~w{) f雍e_ҨJ5;䎕 -nR% %BRvNB7T_ʠӴg[ ,BTXn?=M7w~ӕfhװ0;b/#fxݙ猦5\PqDŽZu:W2%DqIFp^'(_Sh_&ϬkRl{x u0]YK)or"G"?H;IGRAOZYvoliN58[la"EŎ\͛-stSS94Ii8SNȲf`=z/߇alǯizqpQ&|VrJ@Ds_٤cB"˺\ڐ{J "5G}!On}#Ѽ/,f&C/8+byUˍݭ*$|'BGע~ @v?!h Rb П$ZğwG[PSO2l:۟ଡ଼,9ꭲ9a޹F]F*'A= + Y(Ja-fl_JM>Zى ߚuR+lgp~MU쌛 5#/Vs왲*}apQSFaK)V(w궈Db(V%}6^緁[ks,~has[suBZ ORCU#7*2$+d)rԫT!m ?9PJ?&xJ DEB#ܚh^S#a4&-ڸJ-GQ!VUϾ3ʝv` b>:qt/|C,rIxW(ADYNP֔-q56 + I1єhqiqEX<8Z>zUTIm :?X\{K.EA<8lJ楅%0[ `ZWP7Nqqx|iSo|).k/*m?7J-il\*R&dRY}gܣ߿(Sa +(ng1}Yi8M+NƷP7Z!;>stream +8;Z\rZ#.E5$q8]mPpif*4jn>.<1:U$Ydu;WVG:FZmUCU-Ei$4:H$TVejtc +H/%`6W9U0;SPXCm)Z,Uh,FXJGAt"Mkk_n83*rGGZN@)&.9;YeE%9,p^.65W9(1u.X +:q-KnQL=GNa+"]`._59T$@c/Y4(XbO@gZ&c=G:p%,Nc:]HdH+6F)%h1AT-(&E4Los +*j,:nrFT7c8?IP&G]1U`e'o^4)9naTc7p%giU>haV]Z7s+:+Qs?N:'+s8N'!!<<'$ +!#lZ95l~> endstream endobj 49 0 obj <> endobj 51 0 obj <> endobj 52 0 obj <>stream +%!PS-Adobe-3.0 %%Creator: Adobe Illustrator(R) 24.0 %%AI8_CreatorVersion: 29.0.1 %%For: (Jes\634s P\616rez Lorenzo) () %%Title: (multilin-kitdigital.ai) %%CreationDate: 12/7/25 20:17 %%Canvassize: 16383 %%BoundingBox: 162 -711 949 -479 %%HiResBoundingBox: 162.297029211162 -710.625733233512 948.687049969076 -479.301285601677 %%DocumentProcessColors: Cyan Magenta Yellow Black %AI5_FileFormat 14.0 %AI12_BuildNumber: 192 %AI3_ColorUsage: Color %AI7_ImageSettings: 0 %%RGBProcessColor: 0 0 0 ([Registration]) %AI3_Cropmarks: 0 -1080 1920 0 %AI3_TemplateBox: 960.5 -540.5 960.5 -540.5 %AI3_TileBox: 564 -846 1356 -234 %AI3_DocumentPreview: None %AI5_ArtSize: 14400 14400 %AI5_RulerUnits: 6 %AI24_LargeCanvasScale: 1 %AI9_ColorModel: 1 %AI5_ArtFlags: 0 0 0 1 0 0 1 0 0 %AI5_TargetResolution: 800 %AI5_NumLayers: 1 %AI17_Begin_Content_if_version_gt:24 4 %AI10_OpenToVie: -59 82 0.94 0 0 0 2292 1243 18 0 0 18 47 0 0 0 1 1 0 1 1 0 1 %AI17_Alternate_Content %AI9_OpenToView: -59 82 0.94 2292 1243 18 0 0 18 47 0 0 0 1 1 0 1 1 0 1 %AI17_End_Versioned_Content %AI5_OpenViewLayers: 7 %AI17_Begin_Content_if_version_gt:24 4 %AI17_Alternate_Content %AI17_End_Versioned_Content %%PageOrigin:560 -840 %AI7_GridSettings: 72 8 72 8 1 0 0.800000011920929 0.800000011920929 0.800000011920929 0.899999976158142 0.899999976158142 0.899999976158142 %AI9_Flatten: 1 %AI12_CMSettings: 00.MS %%EndComments endstream endobj 53 0 obj <>stream +%AI24_ZStandard_Data(/Xlk *Ж()6T"ڛYGڀ5WvZ fP% W  1n @SW10:4DiQv*pJHB08'pSfj*f-jyWu!T_|tso?]=E3{&agT|/eYhd;՞|,'X /)ax3WGf02#C~H z.rNuE 7 xTȤ2"}t/_c Ҭ&E  *zhBa(lGQg;fz^k=?hY[vG:"GxWv488X2y1@Ea+2x8a$"@ĕf0`2 D2P@e0 +E ቖa0H O<# 4("aP-j0RRd AZTTY ddc^q P C+ 3 DEP"B`P_Ԡ!SQPTvxǨa20P QB3`AP bR=eZBH01.PL}H(00,b 3( Ba2C1 J.j0tܲ`PDTdtd$)hA .z +Q:01>De#3| +c=qH T$nILMe&0@$ 0PP@$ хv? 0aS P|. EBP8$2 LX@( COß+0:ؕЎ:T.HAB xDVMfX`#ѨȾϳbA7ƈ8&E$O, hcXQ"Lp + ^0a@m{_a,;.krT樿}7~Uf,ִ*i^#l:F氛;$McUf^9,9|9iy|Q`:$Zٗ82Ghe-A;Y<ʔ:عܯŖUMsvuNdZtTTR sYA c=|P;4B_Q3mv feލd<2jw^fR%0^xa&o68pIhC @@8AyXa*4H f(p¡a!B Lz\%:&SFd HA p@=&8*<`*TЁ "Da 0 9,.4P,.8,h 8~ *4x @ Hp.ƒ *P`h- (HР ^O>ܽt#DCw,\^{ I$ΟHГ3tngfO|)2WJmr|*y,ےs^!gwquLBBue\wή^-ֵ̈_N~_lVͽO{YKC4Nījub6L<)iTdNu3ܸb&DrczHe]6dv/)v/!,a8#sݜY}|GiN^Z4^]]]NղYY9snW +ai7rlTZOIJ[.;g$ +јI 槴(vf?"ĶU>idRGhZ{LI5%ɗX~$  l4UIL;|ϳĻSR'kʺҠ>S'oGOlzfnd\sʟyP}'+yVGfԏه/ =? +YmtG]Y5qUZWgEJIu<k'!̜ a|%Vږ磲߫g2n=eKkWީ%}L֑9&M9KHHþ gw:wom)*H]*~5K'OzP0W2?"[Jy)טHzBwLfzLJ罕(vo{x77[o>IŚ+byBs7/VݑRUsN5#ڦGiftR V*fu%rgogB04Ų3!)0,aW&I=sYv᬴4E9MVIޘFl}䦇]Ǟ3{h)vkī׮,|%!8ui짜ҐD39rxBN&yȞX&7ZS'?ha{ ~N +_dǹiщ?@ +Gr|8i*ϯ8e7~lˣ{vO<{̘l٭HҬYl{mtW{)5;HɼY9"# "KSJakg twN:v?D!x*DDhcӑ΄㹴a +]Ǣ1K;;1Yo*Vޫ\uY:>l {u_\ȿ2XQfc* GeZ6#&u8>!o-~*wia ϰT|eV=4bvmU/ 'kR՛xbձ`}T*tU/*{b1i 룯Xw4]4)1Q{%}f +oZef#5>Lg~ڬ^mfMխ^Bv%]Vγ&`V捌,5D3eQ +ҌGս*STVkw^h%4KqFHbPybLsxUrz+ozsTϒN Ro=\Xt%&oiOoxthͲ+;nͽǥI%ۖdGsN^I.:qTV_CMM6,c8ݟI\eT5Fs $8ailPꎣ>#/YΎW)DybupG$ݕ񙻜%S>N醿LakPh~w$H>d:zy$UJ7cWy2X&=}函hrwey?-IعhHZGRmE{棰LŸd:<6"S|t:EezTV~5tr:첗i$?]n5(?, +I +k#Ajo2#os"VJŸ#BuwQeL_t ؎=##ò$>wA;nY:NzSyhCv a|Inζ!ô4u P:h`@p`0! <@8pC ( +pAÂL pࠁ8pp@.88pL=Bpd( 8h`4L1n锫#J#U>$(:,@!C4Í60hKc +p!0n{X6vܥb*7~fjgO~t.:.197hvSe3WjKfzut'U +#$b9ﻵ5w_E5qPij2, LX C642qFYT TTa<7  34-|Ca ]`ԥXaH(6}ɌKd0&[w! JF3xthMcȴIr:,I#4v]na>uU7a%]=$+hZ8vժKj$vǝ|{c.io=wMM~Jӎ_կNO)gnbww\e\i#/sZ^~QlJ[ڳs!.l`簄^9|&M$c5!:*"yݜ}余g0y&xzrhy` Z(oėh֢~(WG// OI4^]Qؤ&*iEbAO;*W}+ؙLL˦u +ݑsfBw}u);xy8W]. bswպq7>ѬrtTcexWSib}:^Tp{T;R>,Iy Ul5ʐhZyek}|[M[տ\BٕNJ ݛ.jgɠs󮜅Qk?\;M.ClNmY_6[^sV-uZd؍lHE(FҜY*dSgz^_'VʓB9!UMݮP2:bI|8fU~$}H\8V69A'u +`KT5$;gUU[ޘ4?e>KOڃHήյ"7S :܎оUM׆Sf'8|,M!ԜK]XI]g4(9z:KJ>S3A].ai9i6vfuR㩋r>xǍ='ԛ#V ¶*rxigN4tZyV,a]}WVLKUh:]ugVoI!s'7B|_Tuh6SdiRf +j +=Lk!Z "\xp2 ""T@ЭU!ʛG,4ypwٔؖK4"죲vT6VUߎQlYf4sHfOMݬ,W6ˆ2Ė 2%^R vwGkqҮ:w^Q^#1M. +oNôVKTyz!^hL;B˫uիjiu%dYs3_sM,m訙 eRuUSCo@k%c:S_XYGƩ2+NMIVԐLl;VGbUNMөOϦƮs9Z5 F:?U Mo*B^6 +yYlK(Z/ЦR:J tҕ$ghU6VUA4L4z>.z&3X5ʿHR=ae"W'L?:,ooR7zzFEB᯵K3Kv?X(O&v8lγHe!BÈRR kpd#vt.kq >]2Sa+̔qI5Uݤ>Qbl̳ì7{D=q4gXIXvak;WwDZPUX?/K,6*SבvH$ӿ'EN)eȖq=R*3e'g)1hasì+5zKFػ#̬ndtf%ku7;3qVgnri퍮Sr:1 +dWSOёk@*is +glUmGufȈRҙFPhȊTID)+"MБO>3vv=Bm/FmV4$+Ij,ħT5e?|z*SUx4x>wGd<)alGf[V}l)]m|M^>oщF 5`9)ohy3̪+V6w#,|NѯbA2(轧q+cR,8gų=S^KiZ5i~PZeN&~$2:y9Hft7)?!Mi4%i)eeFue ݕ/Bf̾uTqX5]GE:Ib-<ƫRa"\Hxq9x5=ޙECOľPً'~ԬDp92gͭz sTD5H!!ùj{ղ\ⱎme8W{;-s:2]BB*W}ԩ3kOK5gڹ*w 3. +6ļru-?b3gwXEva-Q*i}9oZuN!gm:?R컚T͉q\h$;k&M=I*. KթIRe+$]3uH\ó¼ +=-)x'cڻAJֱDt[.fLHx;H.a~G9!=M4zآ*#T$":kh7U +Z ;wE57_Mhlr= {5'X#Y5}׵kQ]{Ԫjd:i{/+SЖ-%r> +JVBW2ύ-O|SNu]uC5{R\Th7u-*!4!K̇Qt(Qj 8Ut-d hdYD}=245Hi Vgb[]QeL}Ɯ͘uePj2OJ;u8? &ʔI{\hKê啛ud^%$y$<|xOc{Q5'_E7?G+aTey?s:_$l4DQS !H  @4A>D^6F"0$R@DFV$h$  ReJwo=< rMvXu=g{_*9p!7.hN*@nZ^_}>[ţwڷ*a xd@ogY$q"[F T 7 6f1q閫{$=1[G+&`x pg~5vc}JQPID'J7(b^lDwL*l{-OWgG$nsjSrm1>ub{1p[>r#6 ~a(rvL3;zQO)Rw4z +!JϞ].\&v[W.e9ERkU:5o pa{c݆R]hp݃YB3Te{@ F̆hؗ?"f}ʙ갡֧_kG ǴӘDJIU@N}:W[$LH&,Fr(̇d9LLuAL 8@:D+ +0CmstebgkѢg1Yz(_̷1v 4 ?,ڃRl>h*HO5+~Q"_l9-u2G + O_Z*ZO0WE'(kc8 +)4XBaJ֋*gd2v>4nu 'P^d;_@k4?!"J52հWysrm:Y&ޑӇ(Uμg%BV*<0r[kժsUږ+@Fn)r?h)0DsT9Gs&>7| 2S O؇t[6ꝵz's8JUW+\} F ՗w!۰8̈́토n@7dM4|ڦMųTYJ{AU`,_ΨkO5&7;fv:5ECcm& j$NX'cj#%N8TxGSӗcL wNcnsZ^ hE47[WT%Hi[@QInW?Ea%nzDY]8;~:}Y ZȀwǵi,6/;rw2]^Yr'k,BPq,7p̨Y ] ճW@I?pC2;NsE*?/`QoidhR1Uʀ)|?i m``ITn&=x :~:I"yr;譃7\U@`PF3N!NrZ1? +_BdlpqM犐Qȑcfj~9HP ]B7\.G=ّh8uIᩍ*>ncW8!{Е P>>3m+~Rą `gf>Ef1*+zW(%6RJN'\}‡v hK +w{ŘbkRi!hWs[S+kOeF αE/~:kp%m7bjAqږu.FPx*ܦ%ժQwV(|^hIq22_-zdC2FܨČ.06Ue~X1#LS\r* KmmFc Yf\a^.2!Bw[!e^wV}X׊6mrKI1o5 +~VMb*HbFoJMdN-о1&oetZHEݢ2_ީ PRZd4l[Mmρ^7{V_{?]h|ܽt"(Eo~pit qR?}o3bsR{SJ ~03K"<zohRcg0Bc$[8h2ň{"}t,k~F^ 9@.Z24?e*gϑ˧!)r6Wn> 0q^ ,җ";ʍ_ރ= :8D@~Ai2yhn rc|'. +]KC6]V&l?YЈV_-F:gRLRk!@ht2S@9jD?hXLqe]5 +Et'~Ĉkz*SST){1i{%OG)=!̃L}f&,:hgpA =t$BU1Qn[^Ipeu%oڲBE>K}AP)cS9opl}*ofB0.ljd/ABl/4ӕ y۷SlBjԿ`4|B1+c`E"EbWٟ~Dѕ,ZP)dEϣyctJTwon- jaX -r `Q +hg|4JeH30{C(j^6A$Ze[*єHׇ~|Jo5-vr,pƇy&>(kC +w)qŐ3$FD!u(b͑MG +vno[6Ȋ{`F'\ƃ(O7U+gn\$Gʷ!*Z6n~B%(']!Oɟ0\ȤIH2JլW  (]Z27G ÁT@xo!**)/KȢV^ӌ9[`]W @"CB;C4]~TEX !j}Csˑy_Pf+~#w NJr,׬ سQ0i" JK1e<=P2#>2FUKo{gBO3c8OMeɓ^[LKu  4jLBef}9K._y!&(3]ebaۛ}@kvchB~;{M&}vr+⽒8$82]-+4L3.@W{yxiHrݼJYt3`Iu_g5F˶!lcؤsKZѨ#ĽnQQ2.阈2WQ!R +Ɩ$|N_ȸ3-nXH[X'rV0.pk}^ х !|f0|B%\uQe@w[ Q H8 ^ PPg ̽ +Į~v]*C)N-*"X?%x +`)?@˼kY@Kr<l-s@^`V\p-Txz]Ϧ SE³,0sM9-cOqOS@/ +)D# ] R@AǗ7C{p E Y ̐T՗>E$n]E=UpN~fHu,tqAbp՗HD;OOo+(9!@ՐmB vK("nm(x# +y|uTHYCivzgƗaEl<]1%Oa3Y^P%}934uҚ(Dj +^m0ȃ4s¢ -v ;.jq@IcgH^ C+An0q2-Y7ɫoaJ{Gcbd+S6-,Vڱ`NnwLa:fcɱ"pTTu@ c1MηZ٘#G({q}a2 ,beǬ-si4%|#j3R漞:{ +#Xx!4uH0WҼ+' GK۞L&8!(Y"x#%(X' +sl{GFGbCuɢ7vSys-|a 1TRl}q#`s<y;z9_=sDbWCo.5Y$Kg,XzhKsm5qI>M1OrBaOe\Q꾯 yzʷ,HϢ5#1T\Q?wfb+!gIe+ ]L-p+'?9GpuP nuB+{B [(|s{O'zv !kcd +hj-7>y+{u*^ACҮ< MiY/x{ IA  +&JM#˒MD(,H} K!hxjmcz̶Pkݣu?\(r0?@ ۋ]|~dTt}ɐF0R%Oq6:)x~EAiϴ"Y$%).-knuzB 01{E9L+ݪSFi6HPV_5x^|(&Daxq+?$od d!XBXi+g aRπ$|"}Q${)Y;:: 9ٷey,:՜qd;̷4ڀo#~R# ~{ଦ) 7qbY:dTZdˮI~~J?Bitly0g;#9_|)adk1IN-GF#Px& +k[!=jI4!6] +~p+fx`2~e(&x@8j̿/&,O $0(nL-M1s8S +J=f>U07lnr\h U'DW4ʴD1nE$"X͟pa*\Gۧxlև_,_ɮ +^cP^IIC<24 )ves` $Mb% cbZ%7ؽh3s=] 72#ܧfp3R?5sOQlHKY8g>wgTpgNb=cMB4@IЄ]΄f$^ GR7"Y`7]r@4xRI5sCXK[@Š:lfelf^q?mAՑMMA W@xdHi>R4!eTX@SlN`Hy\Z1 XZ%Vհ$07,R؛F85a(u/n)0ZBDm i1J.؄Z!4̟B+_OE+ٞ =ah'ިoL1R 6ON?t kJ3AS4jLLqv&sWAvNbvMB ('hri'p9yn@ptQ#(q@nYf5bZHۘ^Ѻ.͏v ~^ƔÀY,:.7ƎΧPŸr|)?^jrԉ4N~ݯj򲙣F73<]It~'degRB%ReOY?`FZxEŸ?b&W]|EgPA?zA- +1ql +蝚/>tbr2@Kv |W@j*ϋ~>R Xec~,yF#ak,F:$TpT)t40g *T/iBHUeTzʌH鶪ex<ȣWې~i@k S[F٠YnU Ccon +[݉sN)zjJɤ. yEH.Y"^#R#9.803x *,`K*&^1ѩ McDTTOǯ$}v >YX(N{?O) +^(@OK87M%,`yfH(\ ;8 "* (T7Py6II%ʐ 1*_JEϥۗ*>* |+}aKC)fcwȳ^q\/]9}7-x~ 2irkROLe8%3sa5 26(܆%.x`HrJX!(D{vv$)LCBk:+T!c&Ci +_H`aWYh +ڤ'0i"4MK|~X*҃TJ-Ȼ8-\6ۗ@%r@obU#-`Nߟ(+?t?#P?6 +3ِCstf>oR^H6j PݬOXlEо`P܆w!0"mh0ydy+a5)Q[^)mDxа=gBT=Prg>G9Bc1ԢѥB<9CGJ*{C!ӱ+zXAsēDBITQ2KS&GԁJoBO:|S'MTBm26Fg38QbJp(.r)oit2г=)aGo' +ŅNF?Kn!S!̓Hk~7ϮuCBy#:UGxVtQ11P[P<5GϟJo(i .E+]dzx`U=4eiǓ\H;CNT&A| +7 x;wH),A*w +z f6CsyYvvaΝi.ٙ1,hiL;ԏA&r'+KR`Zu!ȝJ&;`ar!OlSUSi>{,PUۈ^ѝtg%/uδܹAbJXTB0SԲ.vr d^,2,vqIZEAb%wi5cV-di"j :01,LKg^<~ &/)8PeX0 ١̏.5$Q0ԟM)us0Cj q),Yb` ++`E0~!p޹/OE1WIRDXs#\B)_. Ehݳ㦓beL`-X~*[zE!h VvZ$ZHN@:1QVx>5Q{:=qd>hW%_$Bzģm> +]w>B(1Poj4;ZsUVRzc/#0sbS k P:H5\r +W _UhE&J,v{RDFG xDbTBiU +a#7g+-Ԫ=p&8k!OTq73 +k2JCTeDO_yRT?LP <38ţRս0Y,g,l5<=P +nglb0Jc8PHEM2 ՚0Aw=^9DP"ˡ 8ctY SYdTpS3lWF2i7^y(H +U1OXkK0bER%Ζb.˟.Igm ^bkHBo*S ID*Ԝy '$Y >]:1ܾ)=Wnկ)JHi oՓ %g><W6'2( o։VxV^h\&j +`L,5fNIvb34U&aC˷y㮯~ ?pG m$gv(4:\cuzGA'tqc-T(B&l@3ݾ8 @v$}|D-=3ȭ|r3K/wJQTJ|'w;iM%K:d7wp'M3E5i9 >'v"3Dh{jZFy5T哪O*Tpu'9׳!9$|̸4н8{vgwF֔ boПSv0jƢ̷EdC"*G xdHAcp KkpΖo䡱R663\6dJhwy +?|)r"C5 d1OoB M;H]Fo+0Z>%6Ƌp:N ZJ. +Hh"^b^a; !Ndzw-Y,Ijr[ "htqȹpb iP=pPJ|Au읺Jtm:Z0`~='o?Y%"Ø}yt- +~]du58閟HYL NeT=Zx[ }lmY[ˊZY47ap߻Lej nv +UVúxܒcg"W\Iu5JPce@Z,"0W/(qlXrˆP9eVQ;zJ^I8NiF@v̲}yBlⰼsT rώmVX#ZZvsvol?J/U/* +MZQ8zpj @VǴŽ2As4V $ Ң~+m؎fB6[v@7& +Ǥw[Q˹n+Now;q`U65.;l]nqFkwJ݁x*T77EPgvW;:szы6zc@na> +|@bA|Z)dxٻ:nًkD{voR;9ޏ[N_B>zM@D ]\-n,H%:9 2zյ;.̗zi!@f-'Lw3sȝiW˛%m8tԃ_Y)'ms9n^񳚌cl׮~TRB$%K9`QZïggFGP # +oGȷ0M QX,,{AcjM5Yxzz9ܬ}DvݶH1q=Ѐ ^B4{nJ@#DpP[ ;Gn, `y+:scrM> ju²eDsmE#]bHyL1/][raڒFH-u]8Ms8Vcf}Ŭ (Uؚz|덶[{ Z]Esסz. +G@2b((+zÈ~9➄?]D1Q7 %5dQ(E4rJ9Zz9:PlY=d`hý;f@K=2`7o +JΊzC}|:cƕu#typ&雎pXm͉ ϒ6)L¸"F  a6Qh4_13K>F|gpweM$ o G ?Eq`\aiVi~?mF6.B 6eT !E-)KA%4D8z⠔GpiD#[WI,IhQ`iy3-{À|8c c^7^;bvQ7^YYs(jZ+5Ĉ1ЮXqL&Wo>u*\ǨYNTNV B/{ATC/χr扐-}2v}ltkbaJ|uR*WZ@/b0E* dCrǿ⣳Rmr*V,},cYRCUaJg,6/ͳ%5\ ; il"!21pDr1]EokR^G$zaK_l"'RM쀘p!׍6lD5urbBu#d\4kJ/`UY!aT'tПRW)me%[VGxT ?9=h)mۜ GUg]@|mp +A9ZZYf!h(QMqǍg"2Ab%#C2bf޲(o/[7 C]K]\HSK!D9 (1#ۿC`4Jb>3HXY`0hsCR}_s']Oב-aY`[XڟSgdQLj +9?E׎بXAnE;}8ìp J +mmVݫkd1;eӠҠtMvniBXQmv>>N5/Ɓ6^pZ"ZI콱3}ާp^/1JU_i0ř]f NF8aayl@8f,{46~$4tv-~YF+x&ɏ 7@z""{&-FI\^9DϠ.Fkz㷭/fj*_ 0RVAL7'.N6'݉ٻaw?x=ҕlFXS'NU D(%I_MR\>IFj<:dwb) ϘDĠ-Ю\.]࿸ Y tȌ tpdmBN-MCR7e49HwB$X` p-5ᵂ +3UVI}R<bpbJ0;'Flt0q(*QVt{df_.(* wZ?2/%Ϙ/@3?uz%l'?SL^O4+1P oXD|MZ XZ3wv 1SЉ?mbmzHQlɋT%b6E+K|MՌ{/bTcJ(JuV`*7AZox=qfJϗ=g9Pfq;=#^+',d29;0ZxsLx&b1M\i[ k(.TnE^Y(FFMEMyiǎ;w> [\F4}>fpJC"GFh!b wIC@Qe&UUĂPىvu/ANe|~ 48uSyqu9j>:Myw+Xג6= mx:h2O=nv59SoT.Нa?ԧR #Km!5;3g:2M Շpt>arHQ+] \s6&Ӑ-NpX s?'0>quv)k}FeL?76T ̼$u'5rE&}i?X@@D &mrzL@ApJF9bLP&}Y 3k_eWV)Bق$Xna[HGu EK4|;?!RwEO u.e™]"-N!)J_wf3(cshg,̏،ٌtӵa)(RM-Rf0K@AA&tY/k/{Kv P}>q 5u-(lf,.RqRZnt4u˥`RUV h}jx4%rĩdI\8ɖ[:0_ \``Xr0I0)z,A_/_S# :?T-9 xǨBIj +PPA^!A.^W EtWS]f\TwSS0]|6H aA՛"8!]=[tLl5>xl8t/nB,?elgİOӈ6őL/`Z219=ʭZJ(E^Z-T8#n6zC4T%Zթ\[ +V#``Cyr)}6a B[ȼSRg>.Y/d.f"F+uKϦ>HMEhQfiUX>sq_--ӤCv |&! {ؗ|uJ9?Kqk.\ۅB(k5 89_u߾Ro1B 徴B}.h=Xi`Im{H1I͑T&ReI&00Tm_LnqI>=CGi@JSJ)yK*K+_)m,sjä6.mzZDhFoGidWg_8g: 'H=+KzcNz\i-7,^@ʽ(.fd^lw fFZXhO8#5ʕϙFϒ!f+  )x`)A_ v/pxr_ʋײspQ؅pDSk,- ;X,*#.)b0lr>YX͹(,"b)9o17WC7L@ivT0\\EbfZn0Ql^K"w@b`G2o?υ藓i/E )S f'.EDsWBE͉|7:`96 &'i;`{eBjɣ2R H 42$$ͬV49)<D0NWeT=+?fJ%(@mgVx5Ë!veQG8hrd!JKO#(״y0V^0/Y5lK)qawmn 6nq&\uP'LbRg +u5ufΤLmEj`zE)UOq5P5`f?}-,9p3;lU!:>K+Ŷœfo=a.M^V@EONl6hLycɤgBS(l;ũP'x(+htN$՞duͪGY;wSa~zxkFa½˻J3o3\Ww?`W^`qg]Ddo.5cuzRUi4~z .CWC>l5ߊbԹG]M9XSU+I!Ą,&iX+,mX62[>Yt0ɣ`+.4Cm̷ӌZcނ-dq}n (!A@\Z +rCu6Ul;~~ +CK*<{f`9l@-~y0OL @)h0Ìը)([f)j@-ֱͤkseE0aqе#`Nj>rzXK?, |^#8$$4T ,ʆ +#m+AIegRIYL2y'=d~zVy' _g^A>90GM3rˡKr`E.+=g% L%%dVJ>f*j)M'3Xx>ogA4K$> BnJ\8>lR?H㟦$(>Or^D} P@:h~'(M!N?OKe_]=}ȩv$5vb1Kt]`zye"-˒Z6N@]qZ,A!HNT[Hڱ% 35 mKЦ7u@@$MAX˝vԞ{:r4fSuhZѻ +*|)S{ `#9, b=0*.B`z،/ljeUG/({1s&t 6QS .T֯ Ж}H'êQ_mV6R]Gslύ 0DxCp%v}R@xޕ8bۆUgU0LA%FUx*HUEPe +. 4TlJT 9Q3a*`tk Qu;e +" "MPG)8 trǵ~ikRcFl ܞa3 %dT?rL6`LJ:M/Ev=: <58'dTX>猸&z  USl&Iu*ߒ@v=a(r$pYҾV9l=h=}${ͤ.+50K u?q?kU;%s-:>G?,.vDiO3c.8 Lc5vO}-'֝Bz}bUe9F +hC :nƬ+OeuهQhTNc`Os(7y~scy W)usv^ D(-F5U(v]ZKEO0h%&`7Hfq m4xk> p8$] 8EЄor$Z_CYOD c АTX`e;4@\"ւ + P!W7j\YlrJ]@ Vk]Quc_$:r'[1SqDb{詮 #3_w;>Qa]@rq0R\ **NF'=npB(.QxmvAjR?vڨ!Au?㇚`̻2|FOiax")K,B0.qڤַ_ 1QCb qF]T`_Q)W&0bz=x#Ad3Rձ +ߦ`4FPjLH05 -İ ԖjBcXB봚M?v&LIcs>t& +RkKII`ԦVW4D)ZG3^-9TMftman`ַs5Q +(Z[T@E({6L^@;L aaw$ Ab\Nk>>?cљDc}9 A|xWH{PmsEGe/<9Wg!n$AuŅZ>i!-»=¹s!W,!>욾qbTW[$z)r ?LL^IL+I>p#D я#E0L̚&)}D_i'̯ +ڍl.'v.ؾO"knn(Ji+WB;%ӚĹ!_tdҩ=H)k i*n*‹{y{d ^*)\UI|+6\cPN{f$ϬﬕeY~4 SiƧ*Q\+/?\H)\$٣yL ls]^ۀwy=Uv-XZuډV>6\!35 +4Li3ԜrTHZHߎ.=Z',]IH!< $ }L L' xNjۃܵ^e[qI[rn!r tR8c(ACG7ւ P*(u$oweA͸ x!vlYl *weuf 'Yɲhb T=S))n +\dcemQ-cZKc-mtL7fŦLPVtLX/km7אvk@{=$jhqQ8 0o0N&yix|jP.Hj۠6BWk13cnоzbrBm"ۻYg~4$lVjh5Vޭtx L2#(Cedk;yOP3^iǒsN?|4^%Bm3:Ikv[%efIhkm!'O[Hf$L2u8śD_w2I\޲߁8df{ OJZ' U%cK #8\+Mm*jyv`Օayel٤)R0MrY DO kφL>^PP8(- /N%hh.PTv`27+Zy QTX(D-/""0GJz35% @ EjuG8v:)jJtphqI|L g\'{㹺6ҍ9N*{wsU@Ù?$lp޵ +7%C{%pi 2cRz]w'~:'&s%ɯuOTż{aJ#p2 <](]2z*n׆xof ޗl8MR8qmAtRphhᵈױJ`%زb(+jY~{9vAPk4D T)aĈN(>UД`3XM2xܐoNd_y.*9.ݺ-5IQ!zL38t*Xe Rq hlԱޕ8  QP(b 9'P%E(]tlMhLL$TL(CR!M@Hb$Bx1Af !SPB9V\WM D>B +@a6 1t_"&DIB̜!뒅pGP% 6Tm z" +EB- +E"DIW:5aicZmv -%-[Ct 4 lY +l٪ ^)x[ +-ĒQ +e(f&!&x8AFPϡ7=jJ(R +T)T %цW8z2#P5Li`G`j C(%CU:<,!D +Z.] +k$!'Az0$^ + N!S1ӸJ"x.WX2P;`%hK`R4'Ń\J2=dAyloHH[(v1ưK"{.3 KH4" PM=JB< @-:(U`*ױ&DjdJI%B +V)BBI`W" β*:.-ktuL/JDeV"fʱD@DUuj‰ӠF NHeDʴH{"LdxrOZW^$9&8pŗ ?s^8YwxSQBa; L%,R:6hO^2 Tprk$V:j"UMJJEA"M 'OiYBl N*.h*um7w#V"89NZ1"3!D5ҪOO *;GD ܠPDb}D )mIZQM?>VE4Lp+u + +SA }+~ܙo2K.v!A-dgF[F^O7dPPOTSRR*QCkE*&HIMM4)%at@UX+F|j[nlF! ,[BBC0Bgaq +ftL5K̄A -iZ"j>|V0t%~ e0i2Eʄ)paC $HJFaCf])] ƐLJhBa3t>Ct-[5??.+'IbBᰌ#.H#85pT?Q#V`kNAI*J9L*:H'r΍~=4Gc6k+~PhbzS;UUE螸+vk&K3vϮ>.q-RDZiq"$ +RXe +jhfWq +Z<ƙ/F|Lr FL$(;1D&Ū$EJ4T +CCu `3C{;ϳoYe9"{$$b"(!A<DOL#ØB"iЊQH`QХzЃt0Vޔj=4(/>C݈gf٦LQ\ձtMב^4c%3*tɌhd+qB :"FѐyAwAGB]‰CRD"4Lqo<u/nEūNxU +/\۟V}ӟQJ6Hbc>UȪ<>kƑ2D.*t.hd{qLd±Ývu&{%8B.'#+!U +ɐ|'q *ׅQg*cZgC(O4,JGdL$ D%f"ER̐HdfdJ OX3Utff:udʆgT cՄckGua>^2TEHi$8 WMbF) +VmMEkOEU t9K5ZԈtň6\wI_ʹFD 2%3we6¿$dho#"5j$Ũ%4ϐ%"jMqB.E{LZi:C:>R DbN3uj 5y9LkdY=H&3'я ++.jͥj:Yq->J 4ScE1N!-84]%1i-=]\e &(7HuL;HG' +ʈO22/"23:xx<:4[fL#/%n GIbOS94 knF7R5) )L5.x jGDէ`L ?fcq'rUm$]OBbnra"MN9G6W y>ĺ]J}ı>b4pùt]QG&Q[E!"2b{\4eWfyj#Tբc»~fz::[6U5)Rz5+V^ͼƎ:QZmSL[ߴ'tRfHΫbyFX3JLk1KJ(6C4$JA]QQ*hN:IJU*H̥jN +  .rz $DwSpfa  _S#-naxqMhHKhlMMiE#1UHrG +u)3(8 2nBhTIlh8I2!Pr2%)R(ϸ"ct _QR"aÒ CеH&kSPOGn&$™*TRV&}fTlRAHqK2dpdhD~HHhLB29J +2$Grݬ0]QNVΪkTĕ0NpdnD\_b# ?zO TdJ[Fu +xmd4~J:,|97FVi\-Duȳ\|BL\E#EGd912KRWJW-Z|CJwU+E 9T/U1bk ƁrT," Z&‡ $a !4"!P͇FEf[AO +'L%3 S'$" $5r'"?P#ȠU +f!¥yPBfTҠIr F+z(2Q( "BjpeF*@J!XˆԄq+ +j.z<\& ' (H 84r"AS 4":H5Hfd'swБPS},~R[-dWB 2G*jz0p/#VXB8Ph$yГ&gg.i[ĨU8(xEmqh(DUGBt JVr[O + +*m!!y@/N ,sRƤbʼng4B7)uM?~ < +>6TyG +O}e%/񨤰a'")+C >^ */E:h7!w;@nYLDUu7JA(#P T|A7ث^4$,h ;Cc3%-Fĉ>^lj`ʥj. x|fץH4;qJwGBQ wȔ7(KOon(zo-ZkOyMz_D;̶]I:Y=ȆerQXfeZ ?g,`JHF +>0@& lUZax u=&8Tm +BJA٣o)+%ŁmUӞ lÁ1+BMeWUc+Si7(1]+we3uBZ% 1?u~<`#ǹ6_K:H,̋xjnGWy +{s[?gl.!8TS<F@71h2)l/j<ШlWI4Rq$pkȱ {+j[; ~KGqef-O]A %UjNlf(=6n`؂ 2= JÕc`-W}Lrsƍ)} Ѿ#8Ukʸo@3YO@%(/!TzC`~qN7Y9FA}πɁ jobWTT >ܷvp?ԐwkԗP 4 8!ÍHSm8F LGnvi0=`3T;һ6湔!{JM˲ۏAIEe[?i 2s̼eTDm`haC$>:f܊X{p6QT]|(13T>rTc:عk_ +8u-϶LҀLI1'2Ug՜֣ 9}Kd[vqJIIKQ.&$hΫ: +|H%}M=fVy<,0f+]=m Q| E3kEmJgrNdQFuOEC Ѻq{u`zOb @ 6/UJ + +G7}Vjc%f/aa"tu7i:Zl.<ä9Iȕ%Oo`d\n߰nK7SHv;Ɗ#6@ 8h Wy]d*@n^ׇb̑gC81%4=7iYIp*j)UɊsM#t:ggC[tqٳPcB_+4v֠@'dz\E_^"3kQȓ8#x3~l]:|EsKHǸj/NJ61 do${r_{zHvȧ-=w܋@ToCWk4[ݴAdžx^ +I:AѬF3w/{+\[K iMf,$VKT+od0=1՛N(jCRqV ' $!(}=)wj=.tdx0r| ɾ/0"|. %hY 2yU^юJ>php aoP653e-pS$YH\Ķ 9L5c]T@#tO2g[`m6@lް `T[ϋXA+Ѱst-xD.0]in,ظ8y-b_G@AYqQ U͆wۋ +n7=NV7sPHp>)aib0G7J +KH!@X|H@tGmwISpyabXD?[Cη]|ARGDCѦE% +5ď#0LX#rNXoKifR^ +eҖ*m*}1x[Ҕ7d)F-ڍBٷpomzn65ziTwXKZRϙlf;(%Gn،X_{'r߻:cwA̅ w1h%6WT@Ɍy^;ʨ401 ";:o7E*jG@Cٽ@cY}Rp<b%uhρZ!|xf S$԰#<4Li@TШͷzS/˩C.:Bi"N]Y2ELqdVrm~ԕXh' ,T5 \¦0실IqEVDB Fg٥D/YN ٟ$Jx'(6)i6Z~*68u% qx;H8i@Xo.H,ЧAԟ?(iNq[Mj C7jXmfI&Ay[+\>`9hCȍ*`fA:PȊ5绉-ТeK娱ؽ3X o@.r1u 'i#|c-‰GGɆS䣇KC0a4̸]rIkheGLİɁ , 0"|(+N4Ez0CI b$ +<'6c7B^Oeo>O=JpA JD#R;kvj 0[ )ޛ&6:1bx: >>f 8/Z״x9CFA57u?U^]z䅜 xِ&LcNkY |nihf^a NE rˡ +xSڂDU!yk 5}pU9`9t޹$[҆el8x(b(Poe^ӵr΂[˙(5EC ]y=e8&\HD6t +>>sjm-* ͒%[g+*e?oҬk҆5fTԏ]il;$Zԗ[Rtȷ +_ +:kBtx(X +Mҡc9aƉQP?HxK |y0%L2SvLs2)u~_eLgqsoa5?9h-#@-QlPV:G$)jj3cn}{]$OYŒW+hCvMB{njx4dPP4F77y$h[2UUX{NsOѷHO>|[5* +f|zm%,XZŨ*9=сO$> !L$!տ: bYX 覗ݎzBbCJͬe\DQsD逽 yg_qOD<F24}]cX3*B̹ZxiM:hƋP@bz&Ml` +'4F%KGd{. 7#Xq62)``&P3Xr!%^^\-hJ+7j ph3,W|S%&u#OF ͚țlc y ټͫX(J_?Z=Ute4ร:CB""rmT4ٹ]^=Q !؉<xƋ|8~uh ɶ'3\akk`+yjQ)"XmcbqǤZQhw?٭a'":uuVyٳϳR ~cK\D^7F)0HmY#Nt1gv +E*w:w{+(8"¡BYn }̔:i|)GKU G<%mBRI'WgҺs#K PAH2q#y)'6K~WAv.H1b(E!oȗ#i0}Adu@<" DSd!&E+c  B$N1|6뉈GdMc!2TsU VYX)D:#q|,v;%  " +; 11".!!یCjkDeo~*xS}Ŀ$!!wH|(oeC$bj['Ce>S"r 1V%ޑJ8O7a|!fǼ?x j!V%b +9?&>ȴ'k &m&"!DH#$&N(!;sNdz'$  lOAr= R +E2ȃ=ѬW ]M=ȸQ`A)2 d"TJrJ^R r/&"?r0@Ȅ_S1A#,Ri*< "ѯŚ +Hk"2bl +ͱgBTGYqE +}y8W$g`@,2~T+ ]a,"5Et [$pw5@}4{hBj}1o}|.E惶.-E|X0 !/R=q׿PE0`F#^0&D`z$$HB)ρќzD ӣY# Fŋq z9cz#chgˣYyIFC'0e+##x))ȟGqɛ1P1gwIg;1B&#Fcp!QIFbOkjB;$5JH 㘱K52;]#סquPDZQuXFH4uV$6v:ppht,lx(A7BGv7)F{s 9(4 Fqħ98rqQ`0Ezrr֕#`D )9RFLIg:AsD8wA[tґlF"RЊ#h7:*R&!@1':K\;sf-ЎǺ2FYkÁK"b8 +P&a%;YS9ؑn:>Y KVȠX:a#㾁nd7>( Ϩ#KyVunJF%VG+U׈ձ nH6G 7/mCflCB8mHx+-|1uh{. "h쫑ɮk80!:tjkDkƎ#ՠC;RUFgGʨXphGjc jhGONqkw4oiH4cH?hGFAEc7*֦nhdЎd]#!uvdv,3"܎vuEj9AU8Xp;fy;fߎ3#(>qxK3B&O^r'\,c@ЫҫȪ=WgʰxCNBd%c##ÙwYdp2J;r 2H1ĆyJC4ewcYwTC@A10;|c4 ;ud=C +y!XcxƸ9= ,H 1bFhP1OQ,1jGF RU"hz0aa +05(#FȈ4#_` x#($/DˏS_b/b܏b,D\/G ༐$/ VAWB]H뢟 $O]\yYG\"{$A"0)H%S\$&?E"$ "d_'ŢL,)8[D pT EWy;#GDU"NU#IdvU#MEnKĉI@x*P +H|>"J؁)zZH~t=RGZ\X +S +ĎD-)V)^A +ᑴr(lF/ +ؑJ1%w(2f +u0 ঠY9:"( (ΑИB~~hu} >>ts$=ƗpGĝdl UM XŏkbP&mGo0$8"{$`\tsF<0T#Zx2#nH5x;)O2ɥ.О"8㓜,"ӊxO:@PA25_%BF"V(Й B`ޟsC t:EI!Z?gBˣC`CB)X^I O!xOXj!N8?!xI$A@ KTzK"iJ(Hdr \Ҕ` @E8dJ~)w 2% ׀-jǔd$Wj>-hJ4EM)ަ : UNIIs0jQ1SC)ai 5lS4 @N0$ئ`. W@O@WU?Mٔ}[bx+ԆJgJвll D6PWkWr ZC%]5^X YLÝbIx2LvJiEd :7АI VE/y! !AC 4K`Y y%Kg`$Fך[i{Zr +O--^`-f̵[2[k" *Ȗ#9!mcXIyX?cw %1)od 9طhҥb5\BXlB ƥ3@aHV$( ӰN#smy!Z "t +ͼD/ mV/F>)wڅ\Kp{ `&s!u @ nɾ_Lr`/HKBkADZd$0Xh`*G0_{`TCB(&@ ?Ξ0ҽBn-w8Мq+H0> GZaT2GLK}@(Ɛח$JIZLTxؓ +TTxɁ +zlO.SeV3S1I~LwZ[s  GH!Ȥ8 +92Ua,Ʉ +.3}x2ɀBOH?K mL|'H,Y&^[FnNe Q7A 1TM@ Ffl_=y +f26%|39%vPq33AQB=$o&&Z$E(vHP@$PW4y40p{4KFv8'͍@ciL&\UA&ӀChfREhW>M./Zj#aq! +jC\P 1r-W]MjHE͚±&^| + B]Akv}ͅ@l,6-2s5(=>6&/~PlqƝ}3>7mèE>h{Mf{ mޭzx0&ytđ =Z~ )V;ntA{x:3oO՛%D{`79AIN0(r8(rP|f8pq188pR{n~'7䶁,m>6H{D'sgdi?ihR4(p@ Ngd hfFNe@pTi>坆ġ]Q '8/'11q[8C~$ 19z PN/rAI kcg0?s36'o8Gs~0iC' Q!i/8Kyylsب{Q u^#0p:W󘫙0;Ͳ7 ,j۹cu;N@ܙZ`+I<хQXZ˓+c]VUt@֟ =iQM1=V2 4VmDQۣI$JHjl-ƔA\`Wj#95-ghe#΃@lP >PAF)VeMnl%@+ץŎ[R AV8ef@]tR +& +9âͺIw+`R@]ə`t~b52) nR@[L + +9`BT7)(sH1i#QR0po2=@YL:Sw(mϞJP& +H-Yd3 +K5O%Dwҭƙ(JMO[l +9Djn9 +C-w(з~dZMQ' +DUe 8}jR(pDnnO%:O T#e 9|n r|s|oUM'( #>7Ę z*`5_ NPPNM 1Um2vr rⰓc oAMpo\"z)o JqL/q3E| PV dĵ91{+Ip9V/ +|ͣuYXL$V5&BPeDk~&tByz<(g\H%Y$ !3@}1Fl\؈FLG ~5x(@Gp,G*YF`%v #V2 wOD@*,@\R?#L"h@I_NV8SRΫjZ}]: +ͪKTx*πb MNz>ED;u=g; .1.jf@π>g nq(B=qTeU[rf7 e)au$hJċCF,]@l +lKw!\W6/ \6uYH>xhVQ'+j tO+.ZrQm2i:Z `*Lho[0Pͺ(?CJ JI` g(8W^`xq +PW~YGo +$(Wݫed1_/ "u0l|*\ٯ`{u +i7@)"6!?:lXCLʦIS6o@ +XCR WX RP`E. d ` P[^/P"8x@4#VD#S9eR$[};UliH]{lس/*F()2Cb`rܸ7k7r* `ifwxe-j(4>Jm.qOm͓7PTWJQ"(TQ ABj'kU+&Fg`ԋMǙ щRx_w2.{ʹ*~tW%`Y5%y' &Z[Ag4!i97 , '_ Bgl\֍ew ٿu$"1&C1AdJnŸG!xwl_LhaNpT֨о-ӮdM.͐Êbrz@^N?'Vjӣ0io.`0yTFLϽE)ăWoYᅩ7Q Dt~QV?gY45^J8)L1nU~&[ QӜ&q,u+gP>DzdH%dߨx+WTߤxyAJl{Y<3~? BdPRE~uu}!('6vߎs96yp⟟  #CڶVE_)$[[~=l2Lϖ_ YMX*"Ҧ ɿj>aLO#-_wݤ[S-yS²)&,N:2AB8+Qg ̱%@l A!x$3_|&RSƿ rPAw+꾵^sxŭ=+K凨 +#='o(JH :VEbhNתE"3{}goʯl4e~{2!pTC:,qreT;XV&j3!d<ɡ}#Rh"R9Zl}[iQXڏo'?*R$|,pKy}Ug^ٟ;8Vq^< ErDž`_VqRٞ+,%nI,?*Rh0CO}nHl&`XRUsԞ5}V?w S|p ++ Ypq^7פ )DsY AI$ !Px}9շPsjXbI/^hiUk%)L0IEKRLKK()V-{6(~pO,賻;S-FޖpX۴!Dm7?9Som4h}!!w dDb~Xʈx ?@nU$U@>ay$)& FԐ7->[[9Ӧ6=TqQyovF`Y5; >g-[mtgUBCrkv@l5_ۆ6|=x:h -caI$~JFwC7'/J>h0*8IB-kil‡W `j,{aY"ûn^unz2J]?{ ׃K#oH~Uk7l=柪C,ߕ""SHOGp :c>(.nIвKlϵFhG=UPj_B ?Ft$J ,r+;dً[R; ]Xտ^&uiT0EoK#J O}uǝ?+zD}9iTA1ɱM{L6 'L`p+!2޵ BM/O1L9!m?L/8yX'f"@ҬJ&.s1+֤'1VtNZ4ӃwaHA!Cj5E6v"C+ѲG KH/sDz!{tr ң7rDz T6YAB_͛%Zn"כ";W[! IdeϮ[4O9#b/ij] (˂lL@wJr?́hFXOv zqY*ݢkۃ5 ~?ߝ\ - 윏HyFWS.5;q$zx!؜qsW |# Ůͷry:g¯oyD94fG0n!KҐc@0_03Il]ڡגmyI +[yVv,/O|9uqJAT^~( )_\3%g5prGFs8Ks}3#O9.y؁=k~N#?Tָ -$ظ="J*C|Q@Ǖ~w< A+/#ŷF]K$k( +nOLFk0,|0xR^$OdR/4O}ѩ`lؓdSbq'~n{ 6YR(P">bpXu +1Y?BÇy"ހ\ ?cܖ=㹻b+qC 5:Hxuq/a_f.943]D +~ uF"%~!xcax`3j]+ܕu$rB6"l7 Omo + k׫6d +qCw@i i{Q"()ҔXYՇ\eɛv YW6oS֠/lvv*\Ј٬J;,(ܢ}"SjpG}SJ_7 S0Q.1'ٯpI͌li8bd]J747eqMg(qm{3!H dp+ߧ_׏4ݘn Ii w(T%_m1:(+^޴)h۞EC]g.UTy.Mj^>[G"u4#kحd1US7ٺ5ZF[t?|TY Rjᅶ&^v&&mŞ7YN )@@vO45Og19`0Ht:l_-U7@B\[sZy0LgD=5qo!kub_jT~,}K@||D LOGھn[ +Qm9-ҒΗle$ć}-yzj +8?RF_`t%?T_҉qEwP|$YU*Fu~Q.LKvqDc_$ LMk_/aMb 3t.!\Ԕо95LGrNqǠ9iChi=7a aU0[11&C#A/űsR)QyG9XLmjιc(~trqB**!:谁kk5pn'JK(UBgF!/oN~f^OWU'̠Khnl vsFt7\%W.%0^! $70.o8Rpc .-bN6KQWN9ЪRJ\]ЗSL7<`<*WD[5[a<#RNb蓪RA=4Oo[!igM.k_rQ_AEykNȨ7x'!L\nѧR"EOC>*pk+?XpK !՝T(|9'?.]. |\?c9JyĽ4Q-9}"p|.8>@ՙ5eƗ9PZqƃm1HgeU:2|qg/iEKD⏟E:PUƦn.8]+Ew[١%%zgEɳx kY|}7P$yx .~eÿg. + .7xT@/57\6.N[A*pLâ>,l',W' +Jqq0m@fӊX̉]>Un O>)?5>C7 $wU\ܓ\T@8p"ZmK%Ȳ}ŧ֟~+k(_qõb?UU0?KScKjhDuyRQ1TK^ΛI>~1Mju/qK1đIC >@ O.Kp <#y0!Q,?U+~_ma ?bx?n3ibJd7'1k;.`R!s@j)b%gf(dIeU!q &d׃7Ai#瑞hpӖ!/x}O? mJ r^>5"oԗzin&wų: +RT;>p``7@v.ic|,IEVU&eE~*[]?L`̷qz6U|T**C۶uGP,|""E_H${F˦=(V*MTVHipF7io:0M{{Io̤X6oG(}M{ *}kS.ʤ-0 ej+|_--}ީPTR7Q_ǽ_)@yW7ļܢr"TOJ-" Vg/v0d+Fl"u4蝮}ޛ]$M{c(7NGzoߗ~\޿T{C"}vK! ިCu@zo 0.ӗ{ozom\?qޫK|?…ރ$@cC2~ ۺm{ȱ uV7P +#D#C :NM< .TMB1<{(ՂprD֩{KAL]f;LQ{8^M{K8M_L8ǂ` x{d[Q,wTyo[F7 , @1wgA8{+*cM<;H(zoBI{kH;V~tĄ Ƴ81 +#{ZnLVV&{;=!ȸ͉O=`{3CrQQ)޻B7]]7dixgcJL wni[8U؀i*\fWhnjH]P23M)ľͣL+ +B}Zo{oWaߠc{3p=܅m0-l { hs)pi: +RG$nS{ԍ>Tx]Jۋ F قJ;YPu,_}ZQ58k n7'OG嗝i|rqK 8| Q4]g +ԉ*xfE۲A@[S"oH٪ɖy0:IN坽IqrlXA$ s<T6fܾBeb e=]o~_DњPVƊKv^ 6LjGmO5Yra)$>N%&}j㎵ּm% ,l^<2غ7Z#Nþȵ^bNVZh+e^-Yz"_ 8uo61SirQS[}h/Ƒ(dgi,k§1\k3P8WaPpB(ZMMKy똮ӄ + ޝDm-*~]XZ+dPHpL4&?GGL%;q~K.7ͬݠ {3_PBֽ$AXG +!!d32>͑ͯ{N>VWGLyWW׆ANpbSChLOŴX#gزjcY+I49 Ug)'^)NN' _+8A/P-{"^=؜܆ S/2ˁgi?>ռE2=.H]DsO؊G ,3 +nkr~Q"*! z&׉dwP+&Ĭ9鳪Mˇ X|5>uyaZm +1u:wwrӋuG:9 f6VJeVZKolc/kzѵ"LoF1_l@#!eK1sc8D|]K?wfaZ@< .3+i۹^S!cU|2_5[rؤj[%hK:7ϳƉ$TSѱN |V3B"mҷ|#i-迡2>c$Vʞg>k +J36~SYPzn2htRLKx|tǫȱ 3b?/Z~h3m?g g*_"pzgJz9=RH` #kȸ{A;+% ;0ˍ>&)Ɲ )[λh[ȸNE,ni+ܢPRoA*UPE;HqgF9wM;KfrwNLP` ԽssIӉa+Lh*Oc&F1F] #;kq b?2k0 o)LyrݞCUPol|jDtZX+/k/:vQ >C9 ?HW>J@Φ +`Rw!~XsS{ B9Q5o[γ|y<ݢrƹӚlS>2'qH)vN¹)K=!5Ŕ}cvL{3:{nrA([<̀ et,YS!b2J6>y~݌KS J;I endstream endobj 54 0 obj <>stream +ycih0ojW$ XȆ֣" {o<=e o4kc3CڪYxM{!ƙΦpQɌJK+gg6 8% .glF]s,a <ԟXVJeA}@]+&OjFB:DP6XqGӠ]<:k#$I*0.K:rfI\ݬb,4~R{.Rj/Q⮱u;36,J)ܖ^gKyL9ԥ:+7R '#nP@(U63'jA寴IZH;nT)q ;Şuқ$sO$Pg +΃˷u0N>e"2᎚yjqQe|TtAf)b`C'CS{܄S[gz*H֠ר &jKRSu:7QU[VĪ10a5gcVBm׬*-&寋j2Hɽk?}`jDNt!Ug V˥4qU->N֛YA tȡ5HiqVk؟&V{OCu`YW`_>\s-ˁ]%r]%GQd24|Ķ=Ji9,' $+٬$隵}f*!ٯȶWW% P + aaS>S [BrGT9dMÐlɰ53eȯ0m$, zY +{W0,y,g;ؤ~ó< "mp<F$+UjDl0;$| n-m1mZ9nCj]Sv hv@.֪ a$)kAQض^[i9v/9_CƦȶ.Gb/vn `cliJr2P{)\۽0qA,%q?)Wyw7su>8Tnw [/(3wh,QtsE)]9$K&ϊ6-e1AWM|5[xE/Ņh^(hu첕.w~Ca ^2j]}oFT\NLc&M5/\b{qz ;C/sPmb/4%sݛLՅor3#:|ysg1CroUZk.0c +]ޗ-~hs$#/IԋbaWwr t7 &XVՔNطmVOR9ęKi$cTzO4[ YR o/5SeQa/ G2\/@L@0ZoXQSdG7!ey,<޾q+C"܌@ L=szL#q}.0lNz) 9qӸ8fDk7`~MVL<0GpEӀ-,AUg2+8#c:1H^Y.*6" +jgNM\ +&זq88T +!` +nHVxOcӑV=!F[I^ģ--'˧}c&)9{.VOGfz2`K N xIvRH*X x;<$WA/VBIE(栾z ?U)_#=0&K(X/k(dDrj3ul L##j.oB( `G0To z̠Lk%s{#+ 8blYMZ\ +cGg~E).DE&-噌&,F,`RÏP'ĺgu9f3(} za=+h`[= џRLYbk'x}Y/t1 fcrqFILaS~MoqX`B "RQxI`rL Xj0yDKcTTA9řI wb +p!|!,G>HL؟¥.W0-Bƥ$^q0ĝ9 VI lFj%O^@aRA mPk$2'1lvVUY+Mʚ ư䷭Q{i:׃Al"t6W,3<|B~I/1g-CpPj [? :&H3G-Ü5{.[q{q{It&(_ +G.l'TPbYJ&M-̃fޙ@.*xkc/KAƎ:e1 TjF9ƆPit'v ,ԋo[z;cx0~%Oȱ䩸mcHtuGC.WB湰o:V4/ 噗0BgC1"GrE8Й9ׅX:oم>ʔ/<>RvfaADVtjY*+Yp;39M44>y r z,G9@SsӼM2q7"1#OM9n,JxJfyxF4cO)% .A.ĭڼz#87O>-|7pޖpg[1x:sPP/6Z +BQǃ9۩H;C-.1q(dds$Au/ͅ, !.򏀦05j0]ɑ tG! TE7T,eՕDc\5$j0wQit7iID(dokIȠљ)Mvt-{btGamy3Zܙi ǦMI\!e|vn 4'QcŦFF8Yh 11H{\X䮿eǁ!E}:ylcuFUZWޯ&kO g6w +άig[ %W>n Wke+1/sFuem] = +3}uŏAԦ ;͂%!%m¯q aވ3\h\l<簾 + + [}1ɓ?P|Ʈv*R7%=V?$*~]lolm5.<&kl +%0 O[/̄ &_}/kCD`*X/A)$M,$B)\P>J6H\3W-{>ЈMQFވ/Ex##Ee)I.Q6$l^㘣lFkoh \jTEC̒Rg%Hxb, {9vAEg![ : Ri܎kI׫!QdQ¬\)9eO] ʾlwb}#{Y$B|-wbӭ8땏efqt__ U);ņƫ62?vX 2~O%\8s$jI;G2`0;66h͕ Tk*IU#6v|-/Jvd8+O-T(\d43K׼x6; *fLB(L;{ i[l)S#eD$%"mB"%0ߺ {<`s`WaSQ ~lذ 6hđ}vXbÚu\젍_eZU97< @6JP}P4{o)ROa`Ѳa1Jv}zV[]7Oamc!m8L1li`4:3Bݾ-Q{Y]KZ{&$:Ri1* !Un)6~eT>bI6>$Rٞ@nkbh2V;$x{m97=Tg0s9ۡ ܆q[f%Pgn8P6(#.6͑2 ln@h(>EmO>sOAwVv~]cOA_ǨڎQʚAMe ih{B(/}6$Q@f@W:A O{lYcdZmtKݾtĨ S8=Os6޵efp'\>}]\q_6W!#mgJj07|p".;Bf*<$j7pV;->(w-m\/Y> %W/^50F(1ir艷2EB "e4 +I^Pr +IlT|_Xx7z`8B:Csitwv`[u(œ:j,pv}@~xG0RkW`e@?n|_A +ņP#bk¼G7o) `d7(rCs6{PcwC7rMF,mzmAh^=g_xg\dn* s5x+'݇ q :̾$[EfeIͺ7lŨgE6/ 5":3˃XB_nd:Gګ#a8[%]d 487q`6!#-k(ٸT"'&X_t!, +>&ո#L3j[R~edꫣ<ظ-,[ ,`( '}TV,ɸ$/ֶY>H*7 QhMWa;>q|0gJ;Qƌ +( yy2K+f+bILsSڷ(MN@t)dr*4 '?hCmi2YQ§y]NY;˖[vLSf:akNzpZޘY#2 A[.=;\4DV$[*I_n[Ia,(e91~N-W172. Bʋ9ڽJ0GU%ΜMt.DҚuR; U|E7%U-M M6E&YnXD +:S!"[aLZqyɲfx5Ly^1PY%W/(ސ&?8;NPН9@p8O 㧳=Mq7$YN@/-0a6!(E:RNRL߂NU0 KЉN ^}%@0-ڲ->f1"]B@\Dd6ce=c2!# н̨ gQͅy(( B%l_mPX~a<b31l*MYE蒯V7 D'i R2'l8G2z6qBH(at+ nܮKNu5hT*~C-'p:Zv+Go͆k'ppS~Ze,?JK? ]ÈƠV=8rli]@ÕpV-%MLic% $\zgDdp]!eQ.=z0xBꗺ}uJ3{>Ү^*^ۭCɬR=$WEFx֕6ab9nbV`p'> pTB :!ґ[ֹB#.)-+qIoKrKG6,Z2}:_z~%}8 WuX|\,G 0w mbq_cݲ歎CQD;5>d- {_U*z&<ܶhp,qèf7g5*6wU8,?_P;onswxD")+#?bq^pOhΙ +bKRR=|ȜBvFE +*"##DL"KBg8a.çVة>~Wषx|P^5m zɺ(PG8ߺDŶ4|rA~ө xy I?{)Tl#pmF~bDqv=P?V$lpcx'hcЬ-Mᜏ;B ^N;&ފ{H&,<*H4WY`cW25,o~CI+>4~''Y} 7V_d2k4Pek;&z, mC_'e0q{ldD8$q2_ [yyl651٭\hIT{(mv{U..{$8wi&lǽ*B~oy3&,W 2~nO_oBLaξܬ['I\]*d #pB8bऽbO;YV‘Rdx3&~)EfW }<@ AHk-5U?d x}Di B#γ|vx^*C 2*Cyh@~wt@Ĵ`tf`b/"~Ƚ/V7rӪK껌%S;30`MIhs PӸs`h.ed2o`+WKG Dg׼W>%o +L tץXP^AYdBW%N~E%5n²6zk^G @<~) U?}gbi:byOqԳyB8g/v_ccԺ710cG#Kre>}V}!ԳiޜZj gZGlg#u~mಣGw}A^*V>i4S'*{ﺜuv(뺖ed*`+e THV50i-$VDAӨ8@_* Lj_M6s//2 4w$A`jwTYs>Ъ؇>Y66U Z)4l/uf]GLaк2kc*g  Ul?6@:mAԳLu֎y Eo:M +B@"}:hh' ٟ\}(E}G3t)J&SgOÿ$> } Bf*=2 oF>Q0!@s>y^uehC>fHJUsz9ev>cxwޟk7srhUP6Q&cHg 2J_oM`ۏSlvFά1t79Bԡu.P'#ׇ.g :7B}^A +G?F@bh7t7 uGxW3~pt#DA7s%~+kdj2:)4oAigwd! +@}_4S*,)$0]7$phFHgFD.!_CfBM;{4n䫛R{$2̺(T]u%zkfM!J)@d0whr62 L +q'0ؕJ$@wOc@u<8UQ!~`:g/mTŏ@je9v_GqYCvN889/V}Dh6zh'x'x0MDDN"3*S/&`dp67gԙq,i +XXa[F_vJbChg#Ÿy B4~#m;(sN̻af;}F<_Sֳi5eHyCLC~ xWDy̽.6b^ Lad4p߄0a7ẚ/}B8cHmnGswLs7t^ +z: oCUf2G=|>&Om~3, XPHm[劽y8u5fa9sD*'>}Ikedv̮DzF7ϛ!>zv;3|ouLLYLdc/}9voo,FL\k jq]MCVݲK'e1nw^lЍ!\gh!?z@ c^w_ܼLZq6߸e,Zlܘ?n֭s9 AJ`)0Dy0WvF=x4 m&կ4JhLyHs{"ffƻڨK#(G̙ui@Dz0~͠\{shF(}#Bdu&skZc͸);b=57PQٝZ;Ф8`: I>{r0AE>h"mɾǰر2zn&\{cw8vwe7i3E44 yĽCXc<5Gr[٨X54Gd0xkݠL@ԚXsAz C Kk HLDYh| (#<(6"H࿣i|7_8;G.ޙ|%˪b/t,+`D:Ff6;gueǼSر0qg4̝\cW(Gt׍B =] _S(W=zSu~s꬛-gK[fn.L^&.gLy;ylGD ?XW2kr a5oF0jƿd$:ߕ8⹺skjE Iģ4 =RY1{j4ޙNc]ݴ*@~kkL_[-Vj#$GH\GBe6s7&zMB;2/]D^4yDiA8ok D%a])(pt C6gmz͠\aY7_:׉8 7a.ӷ6Dy&S?ؑB )3pQ9?`"*;("*?6u1!&!Ir{>Nخ߀O̳quL [}rn3f[?79g6b9+׹.nXLZ7DZn."%D k#Q 6 k"Oº/`!Y?X?>}4;y=w#`Y1xh ]Y\',uaŲpօ&;h8;_9,|L[Ό[}or='lqt'ra~Я?8Zq\vGt)X4uEnC(k#4Oc7_:s8u<~D7sr;./$I5~qp/wN"ޟɋ_旸2: Q>{(#}=ϣ^64prΡ^}O$I=}u# 1^è6?؇2{"^]cW(ȽDge +~ULZȾd&|N]IB8S'Gs(u=jc +PQ'HbXDh3iz#¯N>zH?4y:,=Tzx,;Ø6 ΰmIh@}Ghk;&Rvg?[$0o];w6/(l]B}&7$< &8 m$ϯ]TYxE}p$'TFsc0ug=ܙ7aٵe@}_s]yOC\քB } !\K0~ ŏDc_JE::oMKizc_MI<?m5q[ 5pЮ7[ξ3ߛd2hF;m +MΕ/n cw&Oi5rM`]]#HG!?ļ&Ϥ_I2}m4 `\= N$Un;2~ߩue~]Ia/b~zUI6)WDSȤf`v$"=@&A aq +:v}G'gg=^?T+]9iB~vyAvyC:G1;p0J_; +=eB3(i?2I_;|;*KE( "цbMy~>uǽ < QH6q>hC* F0JH_3WyȾ{y8G>,:öϮ˜-ܙ66[gglg]1Ixeh08ށ8u^]F=8c旺[~vo +^]cFYw^̺0sk YWfO!d3~m.ζ,ݍKe:^Cԝ|ዋaʲm6 +%1sm _1*M܇3_38}C5q', ХO +OC&\W8wtN] E ;.~u;"H_cxm(}5EB S?Њ"~Q;CwRM4 WXh?}G98`=z$Dg$ŽOt9@ mvSkM x 7s>Nyz᝷!8:xwl-ej'Tv +;yhmAAJw IA IO92L?G>z6߽y`mS8: PAG/D9m ,ӗօ$A~w_}38g@A~Mԕ6bgwop/~.h]14K-6q߀㾌}dY8}5k|֭s]b_7W u1OPR)4yB:ֹٷsxGD}"Žd oXq8rG1 eP*WJa17s^h2!p,E>'mi,L( Tpn麐_ {, x>}eh+f߼22l<,lÆ=/YVe.x;t^vAvb*p.g2\ y/p.?sae6k&$47;9\}C֭se昽fjW9tpIDzr]~ɻ0y10 \jl=ecY>ĕ8`:kwl=Q6z"žľ4 NB/q K&@rL:{haob=IwiĻqt',g yu' [W;S6q]ܺ[4upI# s9ŲhC˶c.jw l69ZYYUXL.0a1U1U1a1mQ1QLTLVYL[YUL7Օ z kio|kdDmMi80Qir昨YZLVZ]SWÖÁj+JKUUEeEuڲn]M]mii[WZXVZ**)**)*+,-,,-V\Wjk˂֖VkJkJkKkJJ K*JⰥeueeaJ+j JںXjS++,+,.. ZXWSZZUW\SUXUXYZSUWiZV[sV* kJkKUUjQamiiW[SVګ+)---, +KnmiY,햖J+K[^ii\ڪ)-)-ԖJͲڲj]]YiWZ[ڬ,mV;^iiTTWSYXګ+-וJUjYX\\SYYXZTZ\ک,,)-K5źʚbai,\]YM]aam]a]mUmiQaY]aYa]MYiVXZ[XX**,m֖Ք֖ +J +Kuuj[WZ,++)UՕVjJu5ͺriSYZ+.)*Kuuu5uuuuuUuuEu5ueAeuťʲʺZUiUVXXUV*mՕ kJUu5U5U媪šҪʢfeMeaiYTڪ,mj* JźʚVU]UQiTUW[V\UYSTU\S\TWWTW\TW\XVS\UWUSY\TY-9>hon*$)"[$*b؏erWR8nXq,-]8Qx4ɴp'ā;ox!NBLހɉm)@!r0^E@ T xb! &d°*B SAlKP%}MT\2 YNF<@5w ᩨTrhOE <^\ +,POWMR:=L114 iƴ|B aP4H eyK/?q(NCpr͐7_2jhp!H\/q! i`x4ĴxШ + ?'$.D2,$҆eqX=L ҹ GꧢSQGX${%a +Rhl]R)d3򆥦*OG!8gf^~$0c#^=.\ + \,SȒxW%8_͍3ba0¸0>djr)Z$~b,妠J\X4T)bJxɆzB"[1kd_J!?6ð=nhx )Qs4X6MLYHxB3g8GLB349LNf(eIoۂ^;Fh%IkRg–; y#&TB'-0e5%ѧ%-';G +H2?29B"QyɹCSVwHOw=_$Rī G61oz)4QӰo@fd¶$RHLB"!4G@?!E%cCKHy #+}dK[؍" LQE2 X:m\QqŌT9%{KbƐkW+(k 8'6'gEB"QEa-q=aawc]m XTe Z\wKtP$w(1C I;ʉ- +k9Ư?.V^!AJ9@R9CN7?Rda xĆ% JrX-a%DLpTzf6A 79rs0EmicRJ#:Ur3(0d +NiRkbv# +Њ2,8ʔdrI'jzʫz̀W&WV\Hc; 5]PI4WR扪vr3 ˚YsǏK!A.VRgǥDvP-;b# MpL!I7!(~j.LEEn5POk(1 {xbU7&0;(?j)=A+MjNU8{XAs;0`uLKU XF18 +`{6c)w&sLJWz`28pĀODoJ`}ErvJfrJ`gZ +VV:=q6 +|qQhF\5DX;`v6&a4TL GTgH1%|[DRPi -oy!7𡹕A)7٭ P}+l ;sY"a-|nrMQZՎʾ`Ϡ%6sKDm$ +P'pK6WTQt|rt})$QS&zFED" +< rd[ 8ƥu쪈X -9]LQqE J!\Fw-eIQGro֘d}$r,7=q(;E$s;R`VM[ 3 P.* *ZԜ3E2bSr{x򶙂%Ӕ'@0A5 jVƖMU8QuBxL!)D(}N7q~yBhxm 1Q[$'[g&#z߱GMRS쑎x2ICJZvSßei^AFPmkDnE;D|' )j0A8riIZ(fn[pY-HW啞t^b<{ +d݁+r0[RWR$t '>${LDE<)@]} 6h/L̅b (! |?[¦|ɨ&$|޼2ڄ9i`N\D?UaPVtZʉ?I/*GxR*oJwϢP}@avTg}3e +Ԙc~C@GmwG}WGݖ%vɍ߱W`pTl'97 Jtȸf)mt) e Q6dYȪw,5i_UV;%4WY"QN8z%*{z81m33#$wO o`6\ 5hۀs$'n@Q&⅌RO"b{xyŠe-wX,4Xu dG~ҍnq pFP u˕%ە+BSzC%k{X% I!zhL4v!zAUUi_LP={%/b)F]H;f/emk i7M‘Ў!i2f>f}%Ʋ)Ğ!%n:/x0k91d3`(n&+:7 +??)}mfF 1**h+07KZsD;P AoJ_[ +{tNLC[Ii պhY|*$w_"jsଗLh"Xw9inJF<>;OQAO@uSa9W)(ْ_vb$~7D/3RJPډ_;AQ1aIeU`p8 Jʕ~DDe啮Eut9o !!!#$)+099(ab_C,+]!uEUI7xqI?P J4>A "siPŕ:OE\A@g-XOYBiK*= * t#s#s'̊k!Jk#C(B:obP~ GBkEFhr>4v|-q=z-,1T0&= HZ.?>.=3TQm),j%5;$46ʈup@ v+ݳ 1)@c+@>pð5AՔH.&v#|JOV.2>F# Κ5~ȕ㓾aٝXm%R?4\"7"a~",]JţV` T4~ѯZxsADY+&="*^Y؅(G3f.^<)Xk餭lXIed6٦N[_ʸ1~k]4L[mo]PmN7qH ^m .. \}6QfF F_[#繌v7TqplI}( 3X@{Ϥ^ ץVPv.!U N:*DdՋJ5±3fHz. 2WYjEgJEKZKGlI|*U^:>db`*=jd?bЬ' 1'()88gމu4,TIa Dio +jI>UbOEKR($}ˏ2>p*DZm^AGHE~Y&~%wz>+$}JA5CHR@*6isjmP_i4T~A +I"} (b#o^:$TB`cՃ3 m+f> 6g.G7ܽqgfB?9,%,baxHkgI?'X)ThHUH?)3luL|E FDv߉Qm~ᝐ',O" +N)wf^[KafSwF uD9mỲ[%,14˜Y7foH[ȤXH LhoNJ @kQCdDI@}0?*%S0$4%G-@$v&$5>%p >Hz($= j˚']HJGfz { RViXS&Ԫ~ CF >Dk(VGѰrM@(9:aIni&%֍>AI)U$zJ_m/`[ݯ1xjr0 +b[e'UFSP_Rߙ U5E7}$Z軆zjxҏ#E6O'$^5n B~?Gui$Ž.؃2{*^/@xhQy$LwZFA0ug +9\b__@BSzˆ4C0#}b^=DWOɠxتaqpq6/)P2ivjWᰤ3,9|RiTfO6,^SPkrF2- +\8\F@g*0g(5Uu#tW5oଫXXz'D{3mƽ;hvRe[78,?#_ +EZ(IŢWPc GPIK: +%iɪ<+ac`rڵ<<,0i!@:wp"ANAY;4 5% RȪ3z_q&~no8 ` P88X^5"1|#&{"`} n|e)UV@. wzzx~/"wЏ50$ǽ%ѰmI,J@0 RDOwثyh+~vFEf)[ȬD@Я9+A(}Y{VS(PA0d|b^54ke_&gl2 .G@>{( $홄ϊH1~j]!@ڪ&MahٝXȾݐ% ᱮaY'8$ bq9?H"*CHH[!)'))1-v BF=뢍2zlt ۿ)[0ƂqLHIQ̳@ mKBiHJet ",֝)tswީuSX*g:9i_TT#UIb*W32m ?;@@/$5"s ~AHq'̝@},vՋ1QSJHmϰ744q^[d +j\2:?+㏫qvIU|G}hMvPe #V[޾y@'#0uesslèg[S"*P›Fkm;5EMg0%G R*3&ί'L +uXS'eȾĺh[Lo37A~yBRiU'˰gż2dMXԛ}.9ik}dV];u7M#֑:6ˮ4*7z A_(݀p'QVHp_UV(5f`T*f~N/ 73Zf;veJH˃gEJaөm4n +M^$}*$oa, v GBk4V: ±QVDzkHA*-e*a5AN0,u KL$JfZmN6S("%U Nj_*HaΚY[D;xh|~m Lf5r/cK_f>}F>[K3 Ɓ_h<ԇů4 +afiKYM4 -@2g,"T..k]Dm)^ fzeHU;2Y7ϭ?}BK\VsaI`c- c޸/tv8 Π!nrL#Fy4p!\Vs0.6&ue4PBy+wj@mያi950욗rַr]-88֔@-5L-UZl"L2rŨbc|%8>\ۮ+m, kZ:S>D MT&5#%WyȳpnR,0԰Ϊ_VFf ayp RDz. ;Pӧnaڶ6v3Nخui60封kxps1 T3#3B5n`?juD,pVڭ٭KAV.RaS;DZm nݣPp}óKf솤"{+IՆˈAq\?JR,,v9x3͠luH0>zvV+s.XaB^AC)5ZU\2p[J;mEc1jcK i]9c+ٍ!0\'/<ٕ4QUȹ2H8\E@gZ ^bVDvFwF~ձSwXL |!g̮ɲVΦuL]Z:x&\W<>ݗ1>a/ɾx <~nE7p8+~糏;acnxhvN8P$" +[V.-]& ºN쀉)PR:2rY +32,R12bMbW@*'0*#)]*X5t& Dq>:8bpwNvt +B r-(aE`\g$7rCHy)Q. +-B# ؏B +LyWD +xF8Ǿ[Glgev÷\<*z\Y\Xe_7H498vl΢bR@u+V((8Av@xGq0.eiQ%d +Pb ؁]$l$z(('!@JZV8uܟ>f]U,Yص+׎b )qkOD.'Ų3C3A4ym t u_c17Po `܂Vx6/~w2p b88%p58XJJ<A62 +.`URvua#zGxJP̼v#< v4$){-C`tSrzW*+H1IP:zӇH(wBjAʚKXb*=Bm@ZO9u^-au)ĦcJ<'A.UO*xVpXx6҉olV;FUN 6v^=.1@0h-1">{4S.7 Z/$#gAjM\N(gJA]{JY14 2nn'.Lt&#ݠο簮,Pm|dz(;z3ԕ3o3SyҪp ^^du^Prd`a`c8|uo`6ΒY.Ma˪*V+tA%ܠLAfN" tn9 Ĭeh ^LH9 u+6-Y,6 L4)ޡ򩽤fBR /x7+Ñm z:]t >ʒ.nv&Fǃ߆q Xc|cl>*Ykb#B v H7`kw H(]A +@D$t(~iMb`xTLZO)]22ba\ׄ0(tx8t4 ^7T<+xqIuAۯ̇$0c+_Ʊ^<8]\*7q%Δ ^$hԊu'9(?h|q{($-, ȋ;94\76z( VY[C{sƺ!ʵɭ==gnض\/H: &"?|3ѩ@$ׁW1#bN#2ǒN#ɚ^+ɄVa6z~D bru5FAF %0 rDg]F|Ƃ^ +Tٯ]AH +hXT[*ZK  .ĨucҸ2i]j$$ҬnFTxŖX-eH.ȮBp` .!W&Ȱ`lFjAl6wth<.?:FL 8z@$kBl+1**c(: ,zY;JiPZ +~\vVam%Gㄮ j)b|Wk[p_0q)_Q!R*S%G`2itMa,v . Dz)s +Q ?rrCԊG2 ơeͶdkA7!p`662T8/1c+O\aہ1( SZކMk#FZC>J԰-b1Ac*[2&vGmcb 6t+dw[s.8XilkQѿL`o%Gl-;j{45t}X&Y*l2=&@G|by0Jޱ$X`0eTKww+"$~c` VQg c]Sn޾%5a +|( LG"y 'o.r Ƭ,Ơ^`YI9 0f!#ؔ_~,D,ŀSؿ&w_K+(bxW@a5pC7းEP1{ˡl20 +!vdvX=퉌>KSL-$u ZWgҮd 0vR1&Kz*ktb2{yb0nubCbbcaWth)ol񆂈'$0a霆"gh#z0bA " aԂ!;4` +VȈ7$}ÊIrGΈ=cU rHZB0 [H>F@nOf%cTfR[XxGDL)Dz Coll F!\*bI%cRO44c112n8XH<>¡;V6> nch\ØdB {;#>f`F lU$΀@\E#+}@\H&Ma Ók ߺƕQ%>YD{Zwc*۳q1 X30q)ȇ3"`C!_ 43 k\j 8=^km\jcph046uq+Ɯg6\)(5yN 8݌a:f_yQI *i0DG:-Jgc![zCll k /hM @O<!.|6ҁl˜@`#kޖK}!*{1@g-'n8 (.u-h[&%n#ěh|` KMl#&5^[J +~O0(U `bQ9!G?#η[q4xY[\@gOG :1\6X7xi}Ax +񐮨 IW:F&^>]q߀/ <* cQCkIS [CAPD@pO0!&b nL+th1!MD_-8ufzoq,?~¡;Na|$ BL&)a|ԸE!b36hxjq8jq@jm(jcd6q׀FǣG) %8G Rl>Ճďf!@ jWMB?`3 y BEa9Xt:Rj.dLqGn| *zt@ld&"B~T!D@)!+Fa3|'G ;_cZc!]Vc!ƯrTj_JYqI|7xr:a +xMo+%d;C\(Á:d_e" %,TIP#XIN$aB41FlJҰD-R=72FaĂ 7G#ؤxW sN\M13)&u_ q<@ 77Df e =|ih8M'9AQӘ|MpA I0aq|.tB lFjb $m\2di<T8X +;NeT(ݰ)K!:J<15$SC#WB alHQS,M>_ 5NA +%׋ 5,50H, +I3!q!.#)!?"!}x.yqY2{\MTQFF h4œPOQ4 w`RF74q!>TĹ؁$t% q,!$sħn!yjh͌ 4] tXLi#!_76\sLg;<׼dT1j +J8+!YT@y\9 󀢚xI*b'8D7ctiJdNr /eZ\ʉw +!^eĩr|ԍo0ĩrGk "Ȉ_'g8wTT k:pf2$<"/&a:V7;X7qj"04b3jܤ -e؝A8>7q|j>4q7芩怃'(8~E;G܇.Ó^8K7\b`r5M!>R7'nf) RtCgG7o + xA!F|p.S_xON'T-`;D98\p~uYĈGlaXQ$H:8|0yQfg<MURbF9q\ +P 77qr&`m 5pó܅^c8 0ktUULEd*Gq +z^lU,,WTWOtY:U,~=b^mzLTxO5y4|r\N:>P%?5RTGLA{AImm|N]W"=N2{ju ] ETl }74\2c@ ""<MX#~:y W"R#޻qs/T#R)"hosަp cOofPV*MX6:,rTʙ)5uqRȤtLb/&u14zg1kzU*44cg2MD~H3U +MRDy?6%NE&cTJY+"m<4PO yh7S"A߄TdzǥCD\%T ߄dt~@DtK:km`$(cBw1%M?f}@5S-HXz($< }&:Q/R($LĊxqQ(aԣ6s [XpO?*Dϊ*};C?V(#ŸbCGu~}N8?o0pl4^ՂkDe[nLYsvǒ[,dߏK/kgpp? G$>|~zezYWbո$!@c}W44kAD QY:V:>3FdٵEZG &:.@@vQ(2ktvseѰg`7h)kg|PL&a]R$&7b9NZS:_ج N Q!"{TŶe WF[hM @g-d$#' .~ Z]#sx} +2')@Mt)Gg-ujXJwR(?} +V,~#BØgYdXz IoKb ʧWƏ`'1g4-DވVc=6&p9sjV\D`MR +J[K_L-sxY}7`9W@}԰a,+  TD~(ưWP*( ʞ4Zh'U-(WҺBTLMLj4;*v8wS+ sVN, E<'9X%k"ģwJ]\m'UF/5'xL ‘5㳖2aFQE_*óQٛRv/yHſB~LZW +\DFU+,=gJFf8gYhco0te+m')a_pZ30AsJ(N0dd4 4 *"ƥךٛN ;(ǥ* HAfzqY;:#ΰ7NPv<:k%Oާ\2~juN_0"C.<uIΡge>8B:zPh?BB%ADH݀kbgBM BP~v)8QJ<sUХ!k꛰\JYbl +i=3joP5PA|zT +nⲖQ. ?"/z) ǦJY$}ZFy!˿;2zE{A I Q"V-.=CI%SC +Io"U,S<@ ȾO xG`Zd[;Be-TJ@b=|P`mUܙHuvJ7Nخ0{er֭q^n9,4OCVqv8j/ Aً0oWFOT)xܩ:sL`Z킶%50 I_U+fjn`\tXK(b*IF NP*g {r@z6&]d P[8egP*/Qٙ@^"KB2lKLA mJBg79Ưm;S6뢧dUΠu&cz Fh^) gG,yaľ2- Mfdվbp%,1``?TXRBS9,(%CZcŰNPe' LDPŚvm +9|{afyr,X/,To͞O+0!ɁɪU y@538@. (GО{]f2ƻ^侌ZEOM\?6sD1xjs Y4/ca/r^ypviov6 9"_;`Ii\9i꙽"~A>:.jgFy=6$ϯ},DU' D2~keΖnVFCT>`x7&QTXIߞø7  a:5ao7Hh]e|n$BoX+jbm>~IܡB,"'%Ĕ60ڨu־1"S_Ղ3~B~#Kxh9̳&S@ԩ2vj/ ɕ?{A'y~o4wRVٲ^_a Y7[7X!wŦU/.)b&8M!HKmNsr<˺ʆeM9cdnsԶ.l}n^лb0tg͡\ `|V/4WDQ)V* 5.$ej\54}" &LEeԼe_n09DwQ%H3[Ih+yJa[̳g:̜SA|^q #g6y^HASad!OYCT_HiN0uiNjx%CnBC9cFZ."Gq>|b$U[c J/&*FP~-Nz} &Z3: C^XW($Omi8c: [5ow`Mv,+{ĐH }u;Wirx| +L4~!@˾;˓wy0siŌ8ØL}ȾcԺ.f 7u^ fdںK$Jh}'&ې +If"aDDϾ`IˠRE?4 m=h<zdXK QHN>|QTЦ |6a8G;á+W*% 6tݐ޾+ ĵN¼c:7 X8Wi~+'-Eby + +i:ot4Q *+AŊV.4k}Fzm,z{7b]Pe TgQiepu~}M\{w2}"0 3a꘹VFuVϼ{ Zw{(WPI#uT +M[9J-3Ф}=5o8elnͦ_h7-SkcCzSXRJal[fom;Xkip RH\KIbV;GZ7)Gre=a(]) j߰D{!X/28xKtQYz;#6?. F@o{fpudzJd74&~:}\d/0)1{gtfWJ*m:c(DCXnSZw xjwsvnղ%|&݂S"bky _/uf] ^HY N^e(VnC:H2'VěӺÑR/ Qz>%-u].g3Zy* KF^EDm%԰Cf:&4 +LP:*Ga7y89Rg֍K m +Hh#Lߣxchq zХߝY=,.n%›3]vayFmN* !Ya=A_;!^ ġ U +X:.k DL FFh{ :j*E}9xuٶzxWCh<B4Ÿ{C#AqdK<(tQ9-9aIs[prJx]@  .ɩu*=ՃMI N,~@OR&.vftB"ecDZ.!tUJOy嘼>$ D[`rZ_rJoHRzK`2JXD qR**GRHOCV y[BкӺ“SÑћ+ԚX<3l}v0kILgetzy۽ dMtU ˚ IL]AIZ 5o jDGw ;}u"߼@e=vD$vf`G?P筁*V[^BkirTXL{R cF/o=> pvR +D8Mb]zwR6scf>2~He1tl=5p5ϣ^GPnP@eG,{3U +ŰPE6u_דDRhAn[:8kBRm AJ鼵'rOKƥD*cf^o\j +JNg +KJiCo#]0a>~!BY-&&D>| #]WɃ!N<Ӑkp嵃ei#(!.#]uӪ"TأD@v-Q_imY~o%?U?ZQ!fbEP'BRcV'&WľÓ`uƒٗN;g*EbM OL=&}#ShF*t(u&ZM\̾{!( \H Mӧlm jŷb +:wŦ]yvR~߇C,"6Й IϠCo*G 鼅Sh/0' vtDv} XT +4Fo8B;F5~vgs0tiss-y왷 Swv@};wi2qfdv-4I>d8 ,kwsu hIhA]Q{%( ]B<*~iى2 ;ijm3S{$<%kX䣝B4>}٘ ZWsYR\\ -h.!O{('YOVoh^q 6 }"PC8s1!nJc@UR2Upa}#P>a E Qw(lx`G J/u[Ь1)cFXm BP}5)bE;,ӘG@u?7d_mU}~#faGDJV25,x*hE;~3(Lb?8СquNBc= H$/se WQKatpD\x?QkNo0vctn:MB7#chi\:>R/SVBIAI1vh?JP~J԰0cahbYO͗ȭ3{lưn>=GWgn #ض}DTP%uqԫvCL)X>`1I˜`R>Kb'q38!i&n$ĭ9#~}Ȏ@$ 4cl̍K"lCcB]κco{ɾ bwscԶ/s@%ˁ& y +:z$T0˙ $ Y+.&;<5V.R(_Ȥ3IJ^!!`X۫QZJf݀e:AYSbJk8Z{Ş Ӫc,8Su`#Gu]ll |k,f]tB^񮎹C uA90 gFVͩqvšs.k6VePg_%Dۤ*5s^L~y3&&ՌL:'Ѯ-$]4$_"MIhX2bJw )prc^?~簯] +~$PpԇCTpU:*Ch[yn1}m]ODZ}\}sX7qy&d M?:1OCСq{,Q)!C9e&䫩``Q$"iUp?P"v7VK Ƨgj= +4ow("=7YKa? ( be_5>՘H_ZxXsar=`vZuYI"(  +*WˇhJfw +f7khܺHldmi']6s ZP JRgz1V8&agZ%cSF@qH^PTLHw6F6n΢=Fg V +,k%QD×&sOƁg`ro6B4%`6kAZ>*:wu-)أD@vOu*(64(sm|]]SS00ŔƁ Z赐 VݤpóKYHhuO39ZG`" 6eĬ+5D$s_ETyg]Ղv a * +(wAI( LX`Xv_*EwDTv $t_Ҷ0{e\B9.e ׅ8TĎR;ϣ^@ _4m9ԮՍOOe2ͺܗ9{AH0\EBe Y@t#09"09pu)Yj1 ׵HAQQ8Tw: UIQsBrLNey)y9KGNi=END1Ί23frb +r{Z9Tٟ{M+QK%~_XKlw~m0{B/UÊ˒@<$8@UVow5WFYV)a/c)6O xb]$M\uuAi"ݑ_RmT@!dΝ#ċ~g[?uzqJDҗ滋Sё:dվ.(K@Qڠɧ{kmW\}ea7NJ*<^ڼr47o%k^=?\>_=7;2;i\/Ѽwϒ5zYuqnU~rSJ;]GͻlvsZ8_l0G=o3 . +'q;@ ++?GY}~6H=9k5B9 7=v5 <YU|UONu՚o[Q;EN!,bYzvqr-} cdS`C~orKtZT|P>YYxp/w];Y *%a~]Bf%Yds[@ف 7 A3 +w +M@"fbTzzOQGg)Zݗ.{nAz$^9ZBp2 G gCnjA~2(:mo5]/׏YϐPb >ID"Aؾ?f,3^“֝NVgF9hw%XX9*Շވ*jj9:4j4noo@Gߔ8{<;ς]f|sQG7uGt޿j7by pB!"HNWC珪:؜鋈Io\nFmÝGsBrɄOl/nv|^1xm)\3ϻ1/h%ôdr{80o&a6=BLydv>3MX);ٟn1ܑ;`7at]9ײiuT~73S͗_Js![֏/'TYfn1 {j7yo;QÚ͔IDWc@hS0o6#ZV7O;5\=9 NwTF:Rz?M &n9Xe}mxWh~/4_{^ T7/DGSb.C48yĻ`1Y|lڨsN 4z?Cϕݪ$'(n>umQD5a]:cT6IP(:I΁xW2CO+(׺xT;5Yx?1[ `Gql= ALS؝ 7ξ5߂[lނ;j(ϡ^':v)|ddxT o4Gh~[9͍n"MEJ< ^V"JXi2z>Yr>xc5Oj> >@~~wm1 PrߦTcC:U^&[oqWNιzCVF@o?7 ,:|WLXL>K,zL* c~. |S5)"$l~&P/Q_J^o9 0?铣v7(?d@OJ(91~k% mc2{k]e WsIїmC)iY~8$')(@4_{v +Ism,؊Cō#hdthە?h0+\l)ʱYrmJ~ AA!: +!n"K1'"%S_HysJ3"J]RƼExl>LvO鲛scSD;C锨s`M9Cm ׂBC|GJ-Z福BXg'pZQ050;c/Zݸ[Jb R ^BJY:%,\M˗p#=4;8,T>T~rP-N6q.5Đuĺ-7Y& b\O:@~#䛚ql2"PbŔ=xDNuwf]K-W|96L׎*_s 6\”=甐ɓxЍ'͢bBrnU ,@sPGGe:jvJG^\iŜ\0<=%3mdIdYb՚i6.bfQ-  +>N!BLyr͑xjQY5_;I-\Z  Q8Vu . F΀"29-QeY MW|>ƴqy鐭OBfIdjꖧ\oFȸ%SP%v3cA/ J 4Ԯc3Faҁ/ +K&Yu}Qs [3-@:ՖmAs!uR |>m_#bnQ"d*|/B[Xm̤W̢\Y2:OiZӿDQm$on!]6ia*e6!B9QB}uwF +̜DqJ$k_)?5fOe?Sn]{GPmaܸ9]_@56]518CX ] șzꅘȠ + ftzOk-NZBs,]=+lL4v-{A +ՅC)EcoŽ.7T8>[CM 8׿)u +IG+v +׹O1 Ϙ|ǮKzI*7N> m A;q(ŨQ.mɊT(nsON)7"JVxk7&* +bF{k5! &DjCrg"f|<ø!JmÚ K 0G 0E6 3Ds $c 5r*CT!N>ovyLހd^#ŸJJ=běfx{p=$otr , 9O<2e@|\yS!.W",bOq\˒-\p43j-}3$@pQ86ﶱM9ዝ&B;m|*QY0Y{jxW^X0eTO#26 + eZMׅ u.PԘn^wtCd&g9/4`ean( # +< qYE0yZgbd>ol|r'-Nt (y "fΥs}zbAD.V"c=0F@XHX @nf,v=動,v@d%J`,D'U VU%')l2&@`HEyʜƍUmZ̈́cZqN C9jA1W$PZqJnNAhNF=9b±-ݒB|='C$ȪiԼuuhODSb&$}|k&}SjކpR{O({~CcƋ<Ȃ;vL7 vYbONzHuOBѼQh%o5g fh8GEǠo3ܒHMķ$Ԃ𢝈<82"YbG%fRYQ_ ^o!LFY'f-8m`s)#tQ|OnyYWpƂ/[1O3H@|<<g1 f(v~Taqhny۸%(TЏ>%֩pM*)&̥ Enlcqh@T:-P0f%EL,{3=׻aDs'xSg4⎢dj!ЖLsg5BwyvB4ҼĬflI Ӑ#O 6zhϖ]mH\-gPf>Z s\m#2Oqeݐbn3u|۰#mٛ&eVԳT)ZcEvQ[7"`;xvl| 0;&+X+ +OAݐd"Q:ٓ}`u>=RmbSM&[,X@2V0Hf`ܮno JK|層 ,ߣcx銿xeK>#QwvvE]s5 t +Xd418b7b8-2f861-82a3-7abeb7d7adba47a41fb1-e009-4cf4-b4a4-ea0df48d8c30950 6235d631255-5b2c-43a1-9dc3-e25c12f565897d38b4ce-66da-4f53-8c9c-60e4a43907178034638.25212523713. A !"< ZM\y#gN4Dognh :#i8Ь@.d,vf&x;]gWNmX<9EŬ!P7`0 X8kM#@&L^^l<4:Ya4306506-6cdc-4da7-8c1b-643650ebd67782787d56-ff10-4654-a757-addad3bed4345ml10SVGFilter / : /XMLNode : (fxmlnode-nodenamvalu1typ/ArrayeTurbulenc;children/baseFrequency(0.02attribute; ,numOctaves(tstitchTilnoSresultturb,feCompositoperator(inSourceGraphicinin2id)AI__1do%yw10wxxh/Def ;4fractalNois4GaussianBlu12bstdDevifeOffsedyddoSpecularLightPointL-1000-5xz2zsConstant(Expone(1tyll-color:whurfacespecOu2arithmetk1k1k431322litPaiMergNodBevelShadow24xMorphologydilaaadius1.bb-b20bDisplacementMapyChannelSelecAx(Rx3s3feMatri41m3animtotorestar(alwayfromadditrebeg0saccumunond5NcalcM(lineafillfreez58ccccccccc8c1cccccCoolB-15nD_n66ern3Erod66_(_1 1;20 15;200 200; 15 20;1 1 1RrepeatDindefin(spliremove7cPixelPlay(50 5;20 20;Diffuseyellow;green;blue;indigo;violet;red;oran5Dielev6azimu81dl5re10001122red6882(-1y4x130.0.nStatAI_0x1(5ddo2Floodfloodblack; opacity:dsC3550n0Grayn-xnOnCompBlurT1nentTransfFunctableV2FuncG.7 0B1XferFir(AWoodgraӨ3," 5:H HT .jB0(@0A`  @PXcBhr˅iF{!8Uax4ΤZ* dRA(Y K cNtGq8̽/hO &\K՗ f|>,T8<JKo9ĤB"N -9J"o #Z-/~ + {[eoKT0gj[Bf>Lipaz V$ґBZ]v> tH-~Ah%X>( Wʍ(x!+q6D5-]:&C2>"8MPkFv𴔈GX KtT}kOpܟ/c|)Hڄ__%K(W _)x]˾IGy\XDn̘s9ΈBo)Mh F8^(F7١Iq,O9|ܞ;ۢ% $ݕp@DDOe0ᒯ8P,s G1걅?U;F;X-Fv+1~7*[ +U5-Dr#z6u?S^}"*sf/LЗbe" ETd@_זX*[D:;o}0eN!#2f#o|RTcVC]58Pe>$CӾ+3tR 73x/аʨr+fWs(!ɴ _6P˓Ni Ss$;}*}g($F?qvA:D2ސ~f`W2v}:`HH[),BKx|Gaֈ;6WcX<0Kgxѻb3  >nN>C ufP,~P$EkNNEEBaisy*X-'ojPH. U>QTQQe IaMdLf'N +Ps;H5E = +,ZOR ;=o ]4pL #%z:m*(ě!t6}E81~2 c;`92 +’Y"I NlQUiaHºPbzvQKe5 9D^{ןZ6CZ#>]GN?]?I/^,y׈6UJҿ@J#bUX@zAgcTEވDΆ˕*(ehcvbb~!;t971QWh)q'g;Uӝ&h{]rOhÿ6:ȨF +Eg.@"Hm Q$U2p( +v[:؟j )t`IUYW4@GeO! O^]&]O0SjB9b}X@02*zm! }qcǛyeZ>&e(ą{LB!873O@!TjW@F lMGɨ ]RYՋd>3ip`nʫnA14e߼`xl}B@a,EWj NCXcue/5HӜdMD4 W3.9 ɏku|3 J d ]hĪ]A, 'sF +%g4=u29<DWM&kz>џ\*lT16QkT' Cs$]tu+chT:#. "W#InpJK/ƶͫy0ϴJl$zMVB@I(((80Tn3uZcTC;J'"`4RJ)SJC|涿9-4J_h`B(*Sְ4hbʋq&)rAc%"!K>igUٝ0uOb+EUvQ]dP]HuUwĒx9 .h $[bt1hآ<cuf׸cc\ϋbOy[ .9ci`*A\RLv%-U^IpG߱ $5~(cY/eQ6O 6{C/h~a~_okx1u~Q"CEa& +˨ 1q1tC^cCqDc9=d֊4 +d I/RalhCV&42- y1;<bKݳyx q)&Kaűü,P=~1{)ŬҗEy c/z!\B2db2hL*(.Pa<@\dc^ svW"ՒnfSL *(&80  &`" (We@"Bb2 g̩VJK*e!Sc`(H"@`( ,`E &IID,PY@d"@0Ä䂄 ɄL8`D‚#~dC} Պr K ֪ϤjCÎHjQ8S+Rq)/eEs3Udunl~ѩ[ b *@qeeZIce31sߡ%MTu7a~{е'OwTPxإs;BQ6ēkʓJIOŤQe/JջgSfvolύD)cdƺ=E*" z{lvOYqYг2<ࢇC?<"g +CqYu"Z,;6ec&dNjQ:Ku(񬱳ҩH"* cyNqm61Ļc$D()wlYe(qljN>ҨyèTa\Eq,{\aQfm?v?i0۷Y"av<Ӊs1֘k!cIl-JC<IdBd +#PAA +&$g^`x]a‚DdƉDDI@qA\p` *$$ +8,T. Ʉ!D@xx`k}H\$`Ra H\$@Ra"b1XX4`0q`40a& IS!L&x<30 .P@0 + +p1H0&aaHxX@<4 ܫ_Kg[=CTCdj n?kԼ/ۚGjCEf "0a vxJXd$o/kJkvfYxMh{hMgMiGR5-Ruj--׻!O6CJYVΤOOj_ilh̆&XNZ^^1a| &z"Ј C  ( F!cFJC_VhN3(j M͵w"q[d*Jцϗ(WgK5ٹϛgvKdʌHi2:d{cC8!S.;٨ꮕ +wwxB}2=ٴ L~Gjt.kv/utחy۪T]-L]HtǺRgdjO0koCP纣RH)kh3|"w+e2U<񻪘dUg憆9kx`NU՟P_yGgٝeCЗnDk{uhYyI-ff!˼\諸a74 0XGhM}ϳCygJZmw cdKv_vԫ.ԨT_27l_aIz,zu)%:SFz   ,%"+j)>ӥ6zmd>Uox`ݣϫ˶0>Cf7wO<ãjh47<0?ti"%O{ȬIK6<063_IuM7<0Yo0W_S%"*k': t0[ L"0D:tj_^EG& +ziED#uH?ŢRT@kQ}OI}IŃm=<6yK; `"0D!`P"0L@ $$"a:Ѐa?`q a (" +zIiv%2dαH&8CC!!rA2DĄTD&PAaAY "R,`Q!YTPP4D @Q`Q5 m۶6f3jf3lv;ߝwwݝwwF۶ѶmUUUUUUUUr\r\r\.U.].wVrX.[\.er\U\.Wler\.[\.lf6ff6333f6|3Vf+f63ۙlf3Άf4ٙٙٙ"""R!""&"fftziiv++++SnfONOO~w~xB<hx<3?c?__jUUU䪪*l۶m&6l۶ɶm۶m.۶m۶m۶m۶m߾}o۷߶۾߷m}۶mlm۶reeo_۴^ڦDk2ۦ߿鶶{~w{zzzKUUU_zUիHW>Wff>e+3󙙙JW>W"""""""""""""""GD;Uճwwwwwwwww;3333=юv33nfff733of'^UUUz^W}U}_߷}߷mm}߾}}߿_Z****:J**""*=UUQUNUSQUj}{̬ZϪOFFWtiO[IwWRn55]ҭѥi.)]ZҮ9mnt[kzk>%)mґ[].ݪ)]ڞ~-MMS-7um3KS+lktZzpuWMVKTXzIwv[ҡrהnmK䵜w25Ș0x ,P>"\6( +\2B0Yh0d-q,v%IMK>w;"Eʞe?L4/0B/IKKL ˰t'ܖ&%H9I'ršՐp2_Y'*"289$ +Jqx|*q4k!lYO  +!T!ucx[m視zx rI8ddOpw9~%Sng5b1M!gɠ#Ԙ5~$TqX7|D&R)1W, 0ƹJ\XFkxHy!٥KDK +LLv(ossg np5 {ϖ~\p:O]|$Om-!a6j 7FGE9Y7do|VoĹjD IaIB 3utΡ-,4Bf֓KbNlNV,X:uLyc%*x^ρT"::aC5;="I5Շ=yp|F PWupPkP;-x逬Bsr{-'GF* 4zX[dnJ2-Wj d *D>8w?}H̸Lsڋ ۂݸL)ĭH6e9:ֆG#~T˄ED.C[}IQ(W" ܏u + + DU܍ Bs=IoKCLv*I&;h}YR8G>K.玙C}ج?l +o +;2B:P # '3æ'gѠMΥ9>oi*N$C [xA0&qͰKECz0d+TwjBJ.ݎm_5tԽN*:{ާXG"Y֝|*9#4\Lw %6pO3vBcX{y"DiE''ZMAlB#bq>[k$>(b47m:p'T Lrӝц>ѳڄjQrkA;m0G>WTۛ+=jΈ3{J*ìp%9V[8TUFh];}kn{Lo9- rW ¡ ,WRgqr$,}긱\!:i!lAʞpֻ#Q]Bw;%UbgtH/ 쀩Y)s<չۊRl?2=ݻa:) eZYR[pqܵy6M5z2$]7ݞFCK0RŜS"03FTz->.ТM!!9X>#k뮒NV8O:C5{Q^ǻUQ$WaW|Vۙ-O"n vN >o4O+\tXoЪ=%y_=kуUݽB m2o;<'eJ᳼;-Ҋڀ^$4u"(bcZG|ND"SV60NłH3潦" +V>1>3gR.\+x́uSXD֧X :CD\[ ۫qRgؼ@>wW\z8Zp,+gӯx',s/(,1,V*r)'nL'gzzxT+}Y!=x4%C.Q[h24{{Hoh.Y1vd,fl7$HqxE)W4S,?aV|7"`5'H~̵@t !ĽȮn8"P &ۭ0H}!צI&I +( E%GH/u^ȆCR[b~:YtrէLG /؀aA1I0žT]|1Y^Ipx612siά95%eCso|[-0jDio~J1 p|LDĔR'v2es(^LGԯ㴋%O#f%I1M!DRI64 I1#(DI4F|N_B w.>vl?XMİ4{Gs ׶ fnRK (,8<uے*7;Bwt2xl$*a +ur r9=IԶw8B="WS4+ ; o.J*ޛ8~,}~5cdּ!U^E S&C :xa1)O=-0=zt$էN+)؂$ +ZPنy'6[NKi`]+^i_[ZMD-QtAt ܗ‹SU) e82R*1-i8aa_[Nq,}Y6m1ElP}9Su:Vie@ZZ6>ݫtQtΫh(sjBY^ĭ2K XET] -ѼV^'6͈XE5Ƹv]|wc">stream +~P](yocu0|?lp6~:FT;CFQ=f9. [*p$@r[e>o6h&)%ajq11Bv`RݘZ($fd Xu" gƪ2QYl8NrbxqtK\a(ŏ!Ayoᚙs9j/i$J#CD@=h]8b2=3 OaD5/x $'mhܝF,X"K[J.6*q8L7|OѵU.a@^aA(@ULߧ[= D=eM5rÑn6N*3N1MhuWyHc9+H.Z]k-*IIkM)!xa$G>yO `S,WZ ^.{#i@<4"z5zƩrUiؤ:E+jޫbmaF,q=є|Ev%*{Wi$OίT BKXsXI%]n_W0(<3\xR G7clKђޯrTHf(bC_d}VF?mu04YJ%v/ҖKP {22 GTISFɑ*z<[nonmP.1# QDt;ʿpw 4- .q,:P3iJj?*р=8~>i2I.bt 47HUG\1.yё!vd1X8'+.AQʲX+pk# +? ySrcM|eEEBf0Il}J/ފ[Ƃ3,Lbg^UБxhS-ZO*f=ȄLjGl/nț!j\(> /7^T+}KǶeg0 XİJRRVVbw#m,)Ώ5V;_NڰQ'-o˰fnpBar9aK݄ߡu7Մoy՘01v.يނ1R?{U^4H( ܤv9R'Ҁ7(d;I=@Τ" nB#[WlJq`0f}{8@`BJǽe~{1PYtzf(WpGPp)F%wHiM'ݩ׮D&v 5UZܢ^71%֋DfT9Rr)9]&g-U͕VZRHJeH*عNē1mQH%\v) +l`eoqo:ENefT:xB> +2Oh/ꅲmWl[ +?I_jP;zꍥLT~r"]c(J9Q03OdDCE*T]zo8s:>~+ѯ`cլTSAZ +;4k&)eN| wN"@P::F/ԐkhL!@.ʝ Rή9K:q#螖Q3S::3 <>+/){JU^flqA 剃Wv +[y Ãn(Nw@cnpy2ΐ-t)-_MLT(X4Fg-ԋQsNiM3APTp"yE"4}}W?yW +<]rNRo?اW'4Ԡ;HkcF5-ZIDdʖjU 5gU\yџE?E@˰]I+1tOz~1JVa`}U X,Se&R]*FauevLzpx*gZQ3tpQ,G!y L+ i!x? x"4?Qm0J%ޯs)ʻLg&3POVȿL 褋bl?˦,uf>5lJoM‰-)zvL{6jqߘG[Uoh9J.LusMDðj)apdWrGi۰R`pq*_R'8O3Uf@Vi؊ۃ$ ,YIep\pHO?B9nO"ͩ;,Vi&B4' m*o i녃cdtF+mTDXʌS&Mđʤb\$gN.g_rDDR\dn m1A,ua n f}{_HfԻ۵FW"atm`6j@P"udEnqh͐ńY۟ɺBf%33OHXOeҋ&>$(ބ"eS޼};1fQS)cIdg)c_L<:Xt_řOۺgѧ~6n'N4$#Uݖ8;nԘ!(!oEc8""6.SZzE= )L7dU9MVZPdGj4ꅡbdÉ }X +NdzÖ:9'dnx6T6y [ľ <`\Alf9ShAE'T*7zM4p2LTc(;0 &C+&QK}&XT"X0i~=PGl2"`VlbAdDdnIW=l?-#~QL#l "F]*Fv4krqdwRf-G}[8AP p%xt]Iz6KE.%JXdL 2a׶VB+x.\pBHn!}W&tl\IPܚ45-;3u+:O nAઅkyK pr "PqzJ. Tkur)B1JϔuHw/⬧Hnݍ0fFCagje{=]*9g.Q^<~mx6=xw(cB3MlU$) :)mt.9529cȑy>V["¥#7ߊun4DG0{lyY1nG ~Mp2G 6uhb3q7PPCH +d"l>$ )v '*`aX!C7@O p^-@00B*ln 0H$Rա#D<wk!&&N4o>c?j@&Hfw?NxN!M#ܷ[f56&OI]X{ 5|t_oBw/3 +~- DaK~iN#62C6M1[DωJ\EՃ>+, ~trdL;xۯP>0C˗o!6@h\m,pLz0dk&`X*FUҸgbl+!oh**Z*&7Ae]/Z07FA 9b,q;MA?l` +[WϯxҳCrġ0r蹊!Y.Xp4Ϋ +66K@p A̩TD"eUQF3?7o }%x2(\9ZѰб"@`oa  ^ĥ B]H)YHJ̩O(!YЩ\atUUnZZ~@%/ل#9Xԯ7b|0%,ܙX Ъ(5VzoMAN Koz .hXi} m,EAPcp5 ]יTQ"I_H \rTU~ ciX3I+&ᓓeOQڥ;0[}мgRqu*A%wD,Kb2$!5' Aa z(B(ke9),"[͞<%ړR5Tr%X5=&h1sz5q>wddu+m 4}POȧ-z +Ht⃽!f6(L!K@~@5lRѸ+B'֐;@qBC$JcbZO5}K\H$"/]8<|SŇr]R9}JgbNٞasUNukuI*{bd>$F<cb:W|;.-DC;ն0וmL@e 'V&X +~~sӾ{c3-)$j;15J NcJ9a +yz0m}ߙ< +^xa\Do \.wʔF g䌛d4KXE\L/kXt &:Ba=ޅNߠ=T5%lRB7IJ@Fob;WotϾ%]Kp +K:) j|FoNU~Fz̃7D!!jf&KҺi'<7̌*èjRϠ>Z +H{P]ł$Z#.eU3SntJ+l}0WM +1$4bo4=I,U@6hy3:/Z:6U.a2DVK=Y~|IMHgS^?_l&tiYW=h\;~omtS~˿4Y?#=Y 8=iCJW<ؿ0WLVƧe}z/`hc~ 'v:)jF(-CV9 *<s0zU6BVp,{a7}n!Lf\ +g? 𿃰2 + +> t, ,ShGCC9yl}宐/p^'% +kPywW TRN-nvE^cԙn\Ö玖pAIlD -G$;@:Ѡwܤ10}\Yr7ij0 G"E8?,A\Rİ.:ILC̡'Z>DgYqjYٗ!k}_IcۆJ6}~g&+5ȫz D~faFu$ i|>hUH{O9rƒ=B;B8VwVLdt=@,geLK27Tה`n+GCQY#vƅ:~|ȹs_o1[~0n/;kr)B oa4%f7NV!1 *t` o!;iOy5ju A1.5u˸Q:MZ{\IA2t٭g}Te:M^g1讴zC!f:0ٵ64Yp09RJ Þ\ +M5G}Ťu%rUK&"HP!jݸYhq:cc35RO6ҝ ]|.#/t̽ +xfǿMjNm;.8"xhQXL#چ$raEȯL0c[]`5pG +h)2ӳ2)|`”/r]F;,]PyKJ*'y XQ\ۚ*pft %3G[aBtNܮ*i1#:}OӻC8ֱAD QgaCd!.F!\nwgX2tDzj^蘔F)Z./BgkQxEz37ý©ڽ%ʽM֮9¥> $c2|GdK-V'1-Ij=8.A"E5oMS/tb&A^n*ẁJg+ R9>-:$޺^2cs"‚B;5f߆tCN\;[l3hLordFzګ\jhOQvxZrªhޡfbub +^@)G򽠐݇ +ajP R/cph/֍ +M-; Ė2}!8)ኰ^[_Sckt Wk-_n:~ɷ8K /kT!۹{t{XXHLeةtQ^YȜ<߲rmR@ +$y@$fY;]y4F!a`)|8E6lUة)LN~ycDm|I̼c--X% +;Uc=2+8%8)peOfjmȚV+ޭj1Ps XѸ7THf>᪍6E9p={,lD>f?353d92إ1c4~eQ8N8 Y[`cqj9v3WX},ep&3ip(yux cijغ +I738$9|uIBR .|1EǝςWin CM>۳`GV#g{u lwnFzSDu6A6Sݫ7?Xm|e9GH@|fu1gLM1,Z7(Vd`M!LȵJd0N0UM 2޻NCTkv"RKgʈ,퀒"FJX+ %f Ûf>ܠnsO%AܵV{)k-Ģ_Ym| yĪ-bHwld!#֏5I ݺQnD&׶ Xp8gĔ[.u@>*ׄaA)y8О/òXD׹H0^\qd+TaaW/s=2e>>3XI)8XIjPْ]Ui/<uԽ'Z"!Z +U k@[Aժyu* HZJuЗKvGPau!"噹!eYTf -_U`YYONgb"jᬙ: +|cp4r"Y_mOP)(P%4o|2.3E]t\ˇԪr.l +ā P99r-!LTN_ש:P`#?2xGe@,-cp41*սQtX 4Oj &A1>F7\NoZvc^&trOy6+Khcp CF}#Q#QX1.6BBP )̞XkìDMNUW)'/'!>H5;5 !VǞ{rFHu2 -πq]eC i};]%SG&\XvC cL4#yHk8)&Ԛ>sAUl3xnPGWI@}sFw-x)lǓlZ0GKUqT;we%-*!(t<\cS?g'ntb-pE •*+\~XGd8}) +?](B8?Mg{v"",Zv%2Q\^<"T9g#_x~U9dZPLtkߊ<2I + ^/ֹɘ =T&ԎȎڄ1 Ai0*fL22R4Y"3Vc>Rsk<! o+m=+hDWhKb"/+̸Z|MkJPV 6ȴ\q곋@ +M_fR=Ikb1 DXpH@( 0 +=HK:a Ėc L*g1Hr}% +bBU'0tB7k,!;nlQՒ1~z]n]Pr33'4'g@H0$&AqH TH]SbdU,,$~8DA`9hUED7?/|A@HYrI'tfv + 9r/[PǼp爒Au;DXCSsM+86hrpf) MB~t֗N ;f Uk9cj2Z@kS$ Ԓ1߆|G&aqvLT\06" Vܓ-N;;F}!?a&Zg3ߊXg!iV2~gK/_UWg%ܚx|WN|HXFWcMzMyr'_q)K~ =<@O?D p8T\wSU>cmՁd(4 V4sk-i(K V1{DHKάÜ\5Vk& ?UcVflx䯦p3))pTCc]r3΍nrϟ6H}wɤz@54.7{LCǒd痡qY>  6یE*Si> Hx\Q(WT +'.&-:z@eH #@ظZY' =te[2tPB6LB-ӎd)|1z 4X,knh*)H%Ġ8-瀒VcNhǜl`yо؝$tV7o\Df >Xv,pvuLuMǕAbzG3tYP,`SQoAN0U芉W':rJ˷UVpj3tsa8R#bM,U'R`ӇQz6;O"R6GsPs؝hn8&q` JiIftǠPeBfUGU9w04H@r*`eGO5GY`8#*N a7NI^`&qFȑ8z~-i5]ڡ]YNڕƴ5 e\2\0p `D346-LeR wI:&wјJ^@pyApJ_}Vvn8nańfv>+lJH,KD 1l4#wpzCbzmڂ0T?uތi:S8anLr"9JQh]~dմ#dT ZwCy|J< 8jN*Q]ta$.tJ᤯4"%Ժ^J~:ldjO-fn ;zBckK TluNJ75cj(앝K OLQDā{UTˠX L@K +9i!LQ.cS-9ֹLR`zc([[ktykXd" '0٭uUV9ΰ*"s sBBG ָTE)HҶ"]h %]m<7(~{QKY;FՒ;9D;BO?DN.HfeG6s9i\86Lb!QjO#2&Re +XLvh(֯BX#¹AW?[`Bng$܂!HTMy 6:<ހۑ²²6> 46~HԻHTOkk u~r~"##az c6L8র[踔 @natYg6Jai//cTK>{U(2%ǂm1x p(X[_W(h(ScfZX!!h`dm: C.;վG=KMۥ'N^#)UJesRm/? ޿A~<<0"%C)x땎VD\ '4b76x͙p~ߍW2*md W;h% ]"B~YYjeGr.1f}:qI=GQ! S\q} \޲;6El p*][!f$µX[ߊZ,bó]L~ +H`]+Ӱ+/e]"WR0|TS~" |X#>j6{Okm@L823t>S )TL2V1u +-@~Mn_xNq i$q@MZ'6)+o;%63ں`vQ/4mBV|q5l (|*Sy[yMKxwݍ=FK /$IX$1(Iט5'SGp4Š"ǫ;E9}mg~`\S!*֡y$_RQv;4v6ÆVy{S[M +ݬrw" NZ(4‰2gKq;JՄ6th%P)tnO(U6{-Ož;%.( sh?4M7{|'\Mko;+OÏ.(8Nm +Lr#"0Y%c8)+hf=0Vz2F1Xhgb+-ȡ*~#, +:D0 RLL3mi!iϭ?nh)a>#O-O+>q:VUຩ򇂡U +%PflHX*Qt jXU#~0ªA/ExRD0}d#4^DT) 8 BH JBhQ$(($DA(-zC!s 2l0DA1/pB!M+&@)>+ΜWRf ~(^2, +x0&-z,$ + +Vc .&_6bYL2x*- 6C\ +/G4̚w$Ink Cc[pBƨuƍ=[g*~T9`̃6\0 +x2;cx@dp/z (#kSY `LX*)0aP0 Y0ִߵ0(F+ +xDl\md#!46ƣ39*:b0!f \q@nbeweDmb(I#3+a1}\NJK@,n!O" +N/ f +B(s Q8#g^zD⌓OEմpt9Q`0`@F /ė=d8߹i|,&5x,*3̏%Kd MOmMn+Wp0Ƹ{'zR130$# +VX;E01!1f&dD&kI2`EcV\H8A10vlNEeQ [4D_@nuNFuvi +Sɲhu6oYŹ }Uu uApCwS,* Pu0XA.M*] uT.j.A* u]րlEO `T ;w uS*1p}AצO[.(M{4 Sթo0́rUӭn1ӟf]{]x[ pm;R._jZS 8m`WAsa +֣\I +@G/z[[ٜA`iv +ܥnc7$PE.XIOX4R?<oh&5@c՟U+SgZAPYuAm 5u4@~+=_X璷m )V,XӜrRנMߠ +̱,{u[͑-#ݡ@UiY6nn\TulY6ٲ,ٲlpim핷pʶ?S]MHMd՟̵82vk*c8KZkq$4JZ\mAKs&tA5ݡlK>,um9 V]rKAzCwiG\qmCy.oNąI!ʍJ[R2`eTV8&NyJc4*\y"Xq*kC&)cb:V!(pJʘ*Dq89ŃQ("n+qA ˘N,$>lrš e,y h;g!~;a(-lF[Itmj/Q'>Um|2G[14m9KUb/ +ZhZVHHheV +Vh."p*O36  +8*3?@hf%Ш,+Kv!g2.WAd(hEP/*jv֒#Y,<2(Q$ BF; E`)^x B ;. r8q[h[QPҪAsr\ԭ5kgf?dq: Y!W",@2qRH? +! +Z ERM\Tf)e01SmؒL8R|+AQy +Dz +L5:h ;zC|2c{= ;JӉ2k\Jۺ#Q+P\\02r2seI9؇\iI*؉I@]MoLeEt0 #T^:+&ryɝ12O-Qs}JQsdt#'KHP.dc+Qxc`OjьSUrLEVG2MExI + ?M!Y4\O|XW=HEdKSq(10bCJD ]q&k/ok h%9JR%" + ŻdZZ ffEet΁ +$h.z{4`.hR;\k$Rkr2)9N`C[XŕP[7qS/WFT*ӷVKFNKj` S#tL tͶuGod@]4/ a>[μ[c9) +-80HqH#giMa8Lj-%d˳(o/pNS:k%,ӡdHc>.Ov(`UT3|\9Gt6VGZ mG2Q`&|wȍ-)(F?, Da+Y;1x$=Gr^T\yLm9n`\ qkYF-@jE7S7ʤDNTȢֶfMI-E ǕT'`*MVVBKBktFpI uLOL{EP\irWpz Q)&,JQ^m[ߏ:Nb&mL4mdR])v)TY+&nkF B5VHbH)Q6P1SBt-^h[Bc!=C%sIXB-{}|iш$UB '<`,)@Z={t|i#9(Bt``XD2<88*`xن$M[2A6˰iyn:M}Usdb4(Q0Z6SdhdE%| E0:΅rC0s,&TC0HPlr&ˬ-ѰX=E4P;A Q6y[Z +*h`F6 +(dTԞ" RU/r(fEq <,Y05pYarx2wkp],װx=ay/ +Jݨd ,[ָM6 +GFBJs\LM#24GD41+E@[#ydCuhi]Е0Bp0}nZܴD8CnR ƸP>M'OSii)bE""jb! EGr:B@J L T6!2 ݹvLJQٍW=X| XS0(6Ml0 ^684ai܀F!IWd-b!H-.fS£TJ:e6la 3 +*SpPT%]Cp`_x)06H۰=V21 b 9Im8DF|0I ٶU0sp,lLF$a5xi[49<fFAFTԸ{^0woFWOLte2Ą4+KJ"LhP&ydWc$da,09[/5*:at0ܴE,* +}Jы,c"b+eqQJ P!S(10ԖB=3$j +1(pCmM|vʹM3m&1EAFԼ5e8Ӓqe8Ρ)Tdnb(XX9~Im+ +F`&'VPH㥣j@;ٶa PF"84<6ط((<bq˧ *⊂XV'F#Ȍ&z|03A,+\uh +yxmOQx9|f$G)[) +0V\7QI2&ʘqo&Qp^ݛykfF( c:9[VՂd`a,lmsS}n"N"`Y[3,^Zi/-i/-'# +'ϩzUlR#NlR#ĂEE{XBY(1`jK +%Em  C)n#wzgELk|vWL|_3:u"X^˛pi)<_QJ{i +\"qyb0ۈOtv:GrΡdΤP܀y q8(9:Mda3wTöaaq8-Q)BIID +(O0xjmJ/'Q$axQY +^nkSD#o Da +v)iQ6l_fr~"'X,%D) 'ud3Z6nDVѲQFB}UVQu\T ͿKX0HmLHW,1{#:-B8eDR 2F2ʘ( +[[F1'&VA((0N)0IrRڄ9uMh])^PF8dc#5 ca9[ qYzb*̢Ms2B>wmyNBzA'Agu>iYCF MKs^əP@D1jb.,IQj"6AB kjbM[c0M0%$ȉ ~+Fh] )`s 5 8pP$!pOvw&6k{<e3|vr? +crPDl#6;G'>5« LQ #0 *s(=f˪ L69<$ȘBS8+$q ;pD@W#55J#9QsyJKr,v(Aiɡ6)qDsh +'a9f1dsqgX# + {8>KЕ`myNQTyNQ@!1ΑjY\Б-&5a͘T>x}sܸkuQ̡Rx8EC3 +Dy(2O(j +#) m!Kn ^v$0s\ q LQ@hK)rE+nQ %@_GFH +eXh|O^j ؕ0pSDDȺJ1\pZFcX NU)9+ VdR3 + )ƻ/䮙qKgz{!15?aB +>+@Yi Z(r+ PV:C 7it͢mMR{ʋL8E+% ]oVBd׬ R4,a$W9YV%**?1mH!f9 ]V%Åd^FU@8S)2dvbVPHFTk*,,  \ALa$R218 ##YFTIs:&X{l1܆rkiڏ/֨XWfCYwhb-V %Ԣi܈mO5U{_@˘3"ļ>~` B~-wVWa"jwh&S4xk)'d:i<Ԓ2æf_LcO"VKzЄkB/!&!S(uėړf,@B$}~ ú꿺,S}M:/xluҙuXtgG4kd,:扄匒\92Zd+JCȮ& l4i^qaSElx^r ^(;Srjc,&cvv@eLq#iIV&1|-Q%ڧ{9V#Dx,b!9qϋ39 ,uЙeqn@z $j"LXAp2qJDO1[*wj+s<5,+3^oȁ_c:ߧ.~\_ +Д{)Ds<{=j(+sXS ǾO|NC/@?U5d[$Oxu.\~pSW|Y` y Hn(ٿ+^u&)6(ZRj}q֟ +. 8wXܗe~SR^7 +tmd>I3X.+Ju//1b$e`i 6t| +8OΧ!9dVlaf PPskfjmf|<+kef(x0Y%@LxFOD=OLpT)R {|~r¯\YȌkʉ-,QmbT&\~V'5 t,1"qT(D#`{DռV L5оTwa,ܤf77U3%J; jWsf.PˍFjʗ]N8'U__o%*VcEWI½¿,$ԜEV{UmpX;*2!eG Cm<_o>sXB,D^3 C*ըwSWY?kJܭ[+C&%۴Y;7-_xQGnۛe9G3 +_5]c:L hnK:+u?n1xA]h+hl,mSR5V2Aty5RcDgCE"ϒC9= S,p2Bl N7W`3\̭1s Zf=K-͡T2$^FR1՘̼R_ :&4A ګp{^xSMU:;tr4mīK9yq0u*Gl6QwWYnmC?!Bs)XXRVf34ja CߟQHW6W3vӄ@P% ryަW|&)PG1|j +^C }ؘZ u>O !d=p&/67!r֣V~tb AvZ_6eц b'XS/4.9굩dR -O޻-157R_5kXՂTI( O%S# +)6hԂ& 0::qTu"`)-LXE& sL%~@fɻj[{"k0)>'64> ltF$Hp$RpI$!ŭURaͳ'3\fH(2 +;}7 +>O#lSZ΀耱Kf +l@kޠbuI̐~GJq]Xp>~^8Aʟ~5 ab[.\t\E_B!UU=`ʦ@,p'9pޚ~LۨˌTzeBM؈Hœ?vtAu<ڃ&Mh#?ɫ+KX5|u(ydF!Âsx7fRkzdZͩY@bxw(%R^(P<՛c@hޣ+3LR,z?4lw9 ^Vd^y^9z7 -+DkݳD\ʮU pFd@R]7R~.kxxwκ>W'R20Ηz{EAލo37d_w_941S>nkI7Igt)/B` ^K &ڤ1!qjZMQ#?!` egL$30>Xco F]}G!&tDv((EH9Pl1c/_ò1F'- z8H2$?L=C)xSB`9wQ OŹBK#, qX1HET:M.ܽ[ !If҇e;_*c<}lSFぅ9X-bp'K:kә3I|Xί"x<7F-O=F i1}) ڰIFTI]0]eE}߈x^KnjC(ژ&(kS2L40v[թar G RR̹4S}EEdP[ i!%p8vLFe$LuEVAE.ڠz((8Q{i.D4I>rUvv=1 tT#ps?HLf\)xcɂp"KGrCրIg6}^(vՍ+6[^9DK?$69,|mڑZƕ{]pv6؍>,<) :j,Q}-E:}D +dzDKhY]5z^f(q&vtxnsD*(MZh _m0(ȦAGmzX}uOJ;L~@G.7aw8mpдkB6GKzèez.P|9OܿV}s@^]EpF!b*W yJe @}}]"yWf*,/Cp DFb,.gtLЖIk/Bߏ~nneaaƕ1 Lt'ϵgP45L.?D,Zԁߎ%%JHL: +wk(?#1.L +hty61?fAi* a̧$046̼"]b݀yB~*|- KQ]s7 +]e cB4*WTܕ:::8ˇ .Di#V&1O%]Af(uɈO&b"Wg1 .ܻPV7(XYR"[iYT##yG]j +/B5۠r$[CyȚϒZܶo]ylH&˚pIAD A*ihDXLm[, Ou.{hT''r3b sؑ#$6Lkȭ0CtrƩ8[%w]Qj eA\Nf>%Ŋb5v<\ڣ3(jwg gJ: :6vɳ|Y;CH$HjUI3|GW<<~7svĴgJ |gpLI_sG넪Y}L+27h E6`X%;"B +,eJ89#{彗uo‘؁s>+T#ʷgppP#;s!Dchdwd<ؔn.e(]AS`51ߨC5ژw θyޕO4BPM[[9@#Ԣcva|.]cu$+4i43"PM9Rӌ1T#C)fUh$ I_u5 鞁 ? M-Il tǰnI80o=-]2N삃*/Jt v]vjf%zK밟o9EbPX@n~"8Y\ +$̀cFZ]+MBA6ZM[yi\qK~T~#{!W7F9kbQXymԔS׋& +x1^*W`ZjWvOOˈ1:j_[)6|sh8US-t=|< &JaMO1)(XnS/HΎ0V UuY?oT,E"8s7 ǖgZLqX8n;Smg\e>"I`oT^v|gca1BvT !N =SP]З8u2rB"3~s"]>tvT ,Z`Z14 uBҳo* flGz1n~Th\|x\:%쳄 z[Tqm|XWc2$$~N5aPc$`WcXmL\ Xl \Ck,},5͍l5&֜ ո)Ja Lp.!6-;*n4 <_ZV?8+ tK-r[M<Fdj4=BOab*nI:P'` fŲM2"G/Ank!̊Z7qɅRYB&ÀiC4SVzPŒw_%gn ,fx6tY˜bCV +_ wV ?TY78VzJ ߻F`wNl 3&Kk}l\܌}5pok(9nn5$Xg؊Gu F8\~uu\ ȟDFt  C.)㷼4DR.s^ RK }&Ov)L?L^VPi4 :ZS:Spj}`Ąp0`[^#8 ',J%pR#׳< +*B@Ig?F,tNgH۷f2}I"J5}2t=|d9 +Waf NO1o}w'wW3m({.I'n!c`(yZp6-̂Xh8)G1n"SADaD8-qؤ):$M.&K2/7Uv“` QC6t><[HV9͸7& 2S萵FNțpy䅺V|lW /3-S-*YJv\׎&׽dы |'nc{oDE&Aw*6# +@8D!FD5L(~_­ +bwpE0fM"1ty(|ם96BEF F 8W [ $?9qndSkr7ArwS4ܟ[ŀ}\Syci:elu#WZoHz[Z1q%hoݪdWے=6Hp*}~Z>_m qJTh৓OπFrXpJp,8M#1A;dUkM |a9N2lV?ʌ($؍xN mR6 4k!غ,.tWqu7},qjUp{fՒٝ1,󲿆%^J!ཉ9?Ifr[m=:>f$ +h43/fCySZzǤ}ӏ2>Tu !D"`d *uys@ PM-ne !-Q*k_ܹȉI `Fn/8ck0O&B?@OBxLQȟ!ʮR~d2 [4YFQ+'ά}"&2x1/0}ۇWD`d۾"pB~EH۪t]Pt[U|߼~{:G]>@O.ߡPmG*Ƅ #Sm۪zOҨg 1 \e$0_f:Oic5gZ[*NB8!JFIA VehsnX&Q=leݪdћΝuFt[9󬎛B7͙!݅m ~L`kpl^6j13$Mu nʷ[hݥŰ+ kͳ@Dk(U,ˤ~C­OѺ'M(y~{Y"7c2dȱ$H9M7q#95f4խ$T3re:i-1I6LdzbU0X2vtB!il-[#uW0Hr.!@+c!2xVS`ש1jWuc;7O_q#mXM!>g;?uCM/(x.i܁;;V,EJ. ڷ)9l7/m~K+YBC^rGbȟ."*|v{ػ68'P]bһh`j:Q_M y)7:j[z63-B@Cg7Zbɪ' +f̮wQ$XN)9U:ZQW< QTvq\aiw*b"QA<&hʢcÉes;4mxՇ\x4"H XZ!{lXܭn#erO-ذ];XW='U]eJa$S T*PcOQ\H&YQ~YtO{ܐqw;m\ӁǞ]VcmU-yf +uQ'f +/M6,-Ml/l|Jqa/zsӑy6i! R'p=1b>> + `/, (hfgG1п$&{o Dԟ@k(ksyqi3 qpv=G۝:y t"&|fq\ w%hok}iSc^*[%FƼyksTZ0iXbat,6(z(?`l oN{/W +Tv)y*:q 3o@D\( BKϯǽ^mVDN`y]HkҴKC lyI V4𤩤a:Mv*m)=jVoQA:;U,6ȑh.Tnˆ8x-^|Y Mڨ"oûްB*] Pw( Pj;($'c +pc.4+xaOW냘ZDPc:B.D&QtBjԷi;S +Xr顩/,#g#i23$[ڬ:( tu ۣ}[s"o}-1kn:l}0o~ۻ oK3O/Sh>,8h&+*k܄H xj`XɁ+8>~2 e]%XLxw(2WHw7gc;D)ӔA0/kơ ŐחvmG_2Ȅ5 ,m֤kRպܧL' u)~iP%=-;&!e݊6[S`54"_P&>\&Hib5Q㺆|G9LIP7-O PD%w&H_46Y _n<+YA"odN0 jޚ6b9-&2AVe@X ./K10һ*TAvZ"i*J¢၎/TNDq:;ù(2$8j'xu BNnزɚ ͫ! 3X8X( pC!=6{uxzjpZj{,;aT˩vKȳ1,> vƘd"JNCJ(Ȓ GNȃNL, " +Fc5ְP*R<_Bƺ{P؉/0f?!W˻\T[T/`VLP~$8nXn ܧd*j|dKCU#H}F_3RjXpx#^j<1?A)>0}'a,O6^⧾9V6 G]ߢrW[ nqJw #JZ$d-jqŹ +ɂp·>*1^!M9ؔHZ~hgUWBV6${m){Diju%@.$/=7=c 50`рsK)f8sN#B>q'MUrsB_~lU ٤QpK<!]5"Lr8b]\+# | vcTQs>k؞IٱSx؁[uO{z&xjU$H_P &`Yvg@3^AHcu=>*Fu6CC GJ3U~Qma`+gk.6fqpޔICvEV* =U x0n7͓Gf +[&C!!nYq*b~g~Т_{LYK |NXI .R*;*mɓ"kCkj0i8gĉkbm$=me%/JJUMU lzNزt̜&Nԡ<*[w ;J,}jdT-(@ZZju1K.HgJ)2d0SQKȹOTi]J}h)Q7OPu$s2\ .}}j )HJH''ZaH&7#c.OWd/0DB +Aߊ^zy( cP6ƅ 4N=YxFMg5a:'FS\Tusv0 Qk*{WIêQ=t"p>G#mYoLU[CVBuپ)1/N]>Ƴj ^D?S~Ua3@BSiCW.&V7_]f9t.y8Ϛ¡]үQU3"<(d45#QѦٺd81w.XĎȖF"ϩ1&heF'|U-(;&NvJʦ`Wkf8Cbۡ}i/iO%"k?;᳅q,My[|f} w)p'NlS/rTM3'2*Wq(jT>qdOmI%s5yJ+jfiLRg>c^+3O$h`7&aHAOz`sTdB=ǭ>$%1W[tTnėo3J{X*>1oسQú\F,^ vQҏ`s/.28roM}y?1,8 pH٪ 3Ny0i']qPN18x`- ExɐTF,g Z/BfɛiЗٙx3*@u7 J]ЬZ^vm'[LC|dtK"sX*,-}pgKD\C#i=l3w77m+oȻ\R/دNjl##Rϰe1n$<zHAhD nN؜Q_ad) [fGHTDWB?&\ +d5z +0sJ$Ub1P\R Rju0.,JL +AcL^] ͨ-ُ74w"!Yh$@s")h'2I+>u Ą4IV>fH,eWs9~iF# IrˆF@I&R؁?8qB(;S*zz25T_T8"+4nQR84ѓᒞ.I>Wpf7(C!@ЍHNVN'ZtlA͕()): +X }<],O#^xT!H dh5RţD1F <əE |y9"L$C :> +冞1 +rXsӹ(x+?4Ɍw-C)!K0:<BmB@ ܔ OB.K*#&L;+#Iԁt剩art/.#y+x | jمtj/^ +R^~0]v| "W %L%v{bߐ#8nѲ#l[6]o6q7l YKҀMs׾e7,{Lj7*gCٿ9KA~b#c/+rкaA0θD]eyVG*Kg3kEhy՗CwSYWq&Ml캵vkRUTnUPtCg|Q^e<,֗MҤ,7!3] +={![[\iH94νvF._ڍ8 c4"/BbZK<٥SMJ.^2$/ +˭K\~F7݅Y2_dE mҵFOn}# <>k+e% Ye9  փ(Y^@~?{Gxl{KHIBpUc%PM8Wvr/*g(t%O"fa*UB!40*^rm8Jd]GEu(D[iWWhl.U19s{.$:ifs=w\+uusd l0mQ=q5ݴS{Y8J}Q0ޡ_80:^l\:,+jwixM=~(;Cms +9Dd6tX(0t(4? ؃V9/p6ȕ!cwb*ʋƢ  D T}f W2)A劮I[3ϫ6ߐW`p2UHe\>wR+؞,!q0:llT}^Ѫ%t*2k|'ޏ!WgR\Ȩ6+x-(k"A*WʨN +Qd~l+Ilt:8^\Oh ++pW8pľ^"' +Zά90H#OiHr4%ݡjo\`QG_ HfeegEJl]D >:S#,3}cjB0Xd*R`J۪gu' 帆B +g w XAd!$ܰ[/BefdNK`f䛑KNL赕jb!ːfj(!W4ޯCP 飋S`%A0 Q_`ɦNc2jP»r-ZSqquMxO4ӎ|{Xs> H%A]Hy4)Ⲡh.Ǡ$;@jJkZHcA7CS~=SN:E ANGp` +|b +F[8U7-yc +yn 11Pr +Oj;D9GhmF rT//6''iX0áZF:4$̾O/`Z"GC4FSDžA^Ke̡sɏ)769ȓD%Ծt39׼1NE3tMfG/6hi~0Ϟ1lii>o\ܙyS~I?1o_ Fm~twMJ.U5:Sp,h:9^D#[CYE:% 5 XN eȗe&MeYji~>5fe , h?4  ++h11', #,BQ k G"(Gp\{`8JVG,JP9猪vmvnI%/J4|7K鈌#qugndׄ:., $!ս 3u ?8yI:"fX}14k 3#ݧLj&FG;=RF{nqsljrtaQX<-"9 a{peQ`瘮)( :AYFo.DԻ#gH%NG5 r\$*3L'j1m/i!aٿ 49_c'x" +{bZַa`Us@Zʪ-?}7䀦B@t Mmsա-y)ۈں^.C:끲D p4d_CcFē'ޔU:3 'uz5n*#HU8&XzmGsƯr -,5Yh[vXӹ0_qi.6cKib0\:.+i"~I#5VQú8Lo( 5U%1Qx F:.1dj'u&w8j_pn*V<#Cs +P38ňNJ(frbk~&>81Gmso]H&s3^RlU]Tޛȭ:i1("6*n#>;:?QJDgTy5?ٻzOAj` gLn r t/ +2e +td0 #WK r9^B6N9lFB1j X.nӣ_ X:*D|x0 .+ĮQ4hja!v,ބbgC% ]3bo*(8ǜ GAiHcT$ hx +ĥ`$8@p 0VNRѭ +D&vu$H8e< $>o:hq<>&y:fK&Ihe +)L<*)KMt4m-D*:bD"âbæ)\,&QH3J8asa]'d*ipiCHE"..AEɓAv҈Ccy$V ,D +)eUA b[H Vjuj {H92g2a2#SJiQ@H`j%*TH " %  AI`DI0ЀA;0ǒi)X244"JʦPxzf"t0"X-#qo 80y*"hcDr2-J>V@J,,]#CQ=t ڝB`t#ٍRaRP8 +b @؅]5$\`Jj Gb]xlH!)lD@PJA"dqx É(.DlufOB JH|a@èrl(2(t Y +ODd%J A06+4$#u5H6L. (aAS@60,da&M 207\8hܔC mb`L"H:=H \Faӂ dD4O1т6T+yJ62QEk-M1ݎV;ZEkS𓏃KLPJHJvڔ Ȑi +TxvhȶQl@A] {ihB҉Fb\:aSp8#\Q-jEĈûeV<9'$[ݧXQή+yX+҆] RP]k 7:hv:0YDL2< +$%1 ce1L2Q\AtՑM8E 5cr[(:2&q $2 \,&mr,:$'&y DcS\!! gpDZAd'@D}#D;Dt 01#92t`" br+$]B7٣?kC]wo{?_zm%ɭmԶ]ߨqazkSygNӝ%ڞ_}'j+s~W"ӧKmN֦֦֦O>⏮EIw>}ԦOg_}:eQkvm}5>]6ݧJm&=auƞom=OG[+:7J oRR;[/qPUW +6T9~|aմ_59_>KPs({#wFٰY5Pg g/A+ +uܞӧoO*WK>W;iޞ1jRK۳Qr)+tr}Y-#[i?Gpyž3˨Ƹg_eն=`]~ܞ{RV>6ko@T%R6Tm8gKմS{5sJrk=y6}C5sqa ɳ;=okU¾Nګtȱ'E֞~kk=@+gmv95ɾ=٧&ur 66y{jMmSfkè\({jOC~,YۯT Qz=]6|M)aOVmWvmO.3)갟?6䍰3ΞajvsG5{P{?9nONҝ={N:+c8F;|) BݧQ_j=*=UK='Ԝ]BnZ7aϨf~kr|-wJ6cB05ZVdIlLDxN' DŽIUDPA0(čCDaXx!NL0p1L2LpZL.$0 uh@'TAe 7 .a@LG{$+5nD30aFɁtZCL1*  ǤFi:F hJ8:@ص!9\6ubXFcyLgDUj@`epDX@@$J*. X, B@a`dtD0@0 bʄqVI@\lD6` DRK +C +0,)\@a<\:6VJV&@X#$ˀĕD]%atim"24n#ien#ae)T`1@* H8VmS@Dl`l[3(2*Qkزpeg 'Cݗ%\)[rSgk]U_*ʨRBOgluUV\UUCe +l|fCUS*Vb|E9Rv,a($UʕJ(UȗUJנ4%K)v#_(@>lȒ,!Of 5 dfȗǐzݐ!ܐݿ4jȗ ;dVyJ~ CVjK+? J1JrrܭF<3 yY|$+(`[֞˼3N˒y:Ieɑ\<\RzyB){*=k1ϭN}xMٵJyץGst~R?'-WBW])_9ʎ;}zG(!.z+AӮt?I(}}ݧܿ]GS/{JЯ-=b,#R>]BM/˲j2toiɻ^dQl}yizp;%__=V]o>5Cנl9njS<-#kSOU-ymZԗ7]nO?=Wq† }{j'/}׿>O_|O姼oN9ݯ?vR)]}^^{R$Or=g˞ܲU5o~v'nnn?oR.KڳڭնԴY~+έݞst2zɓݻyvso_w~?ֽp7܇ }?9뿾k}KQ5ߝRs1͐B30 $ HWB(,($&BFN9( B@$ Nzo gpN3̈́n7xH7WR#h@4D)*v.xK(|4㐱^^d.s#7Dj:&~{."آ= +88=rTZ#o>ӎ$jbУpSHQG!Odݕoȇ` n5&4{aGǐ (;wn FzJW A yE[|b-PXQ<+Bw XsdU}.g1;>t!-u[ ΓEf8R iӝJ5;砅 m2'BK|?Eޓ'"Aɂs>"@t-#0N`uJ!<ـAK[@,{?!OFc2+A|pxksҎRA5ȲZ8EuFG~+'$a?Fa@CD•S9q"͵a >3K0Y}EߝP?;G)SIM(Jўt-aZl6?W.bWqaSg>:Rc' ӸCͲQ̚ i~完\';\Q_T{ uեY{@h2"SU&*@ec7s0'+ eltJf`hnaqӭŽQSz8 PO(`t2\tkUgƉ[ɾ%uo-a-^a}OW H)gwA7*%jl=4 + A:يsw|3ʁ#:/0SWM!A;vh t5ֿ )'EH?vD%r@Dk"෴5UUXuĹ1"J3jgATzzg c45D>#੒%Yf SXqzцB(l'\Uu%$~^n~phb Jzrh Iϻ"H׌`6?4+ 7C :3,Tc|&R\fYp[qvTEj~,H2Rwf}`HT(J H͇Ȱuin'޶d28=<""n9Ë06#JC~C۲N9XLU!Z4jp6l]mn&ͅzI +i)Z q fWAl-t&mg\4TPghP[w٠:Wh M}h(K9PkcSvUc_rDс@av `4^(ti_Lt x)zmRpU¬ LL!᳙? }T6O v gT RpiZg}Ate5S^@TKt5&&؂Cj =w/r}kInt>po0SpvrlW3y؏Ȣ T)#xq>_2_z|a9c(:!,K7g*ǾFv +*:81yZt&]P+]{(w-^ibϣfhk + {4wc09ݠC ?l,,j.u/u(ZFiPifZs!>u|[Qt)2)IY_EkV%4 @,8LURC>)sK uC,.Ld&EjEFđq _} ^ZP1 {l4mg2<_^VMO qyO:&H[="H YY10b)o*I +"%> 8\p`*=`*Eb +U~=@;駠7&\56: Ǟ1)̙?;* +{w->" SwRF_$ .Rӱ>>6LjÄ R3%YI13Fl +ZUU$~SX V٘hA7vn +aڋCn9% KN|+Ԁa/J7u\tX[}by).(9' u=оɥ~⍤TlϤܭ(BQgfaWNl9a9LmAU,o59YSR qW{Pa1QQA*Aq}7n +oege@砡J,Yaۃ@CO㺫HBBʉe +Cr/ͷOey3)Uq#4CQKw ȒLd?t_Xe6hv&ECq!!l&DAe0])ӤIYtI)/6X -zP `XYڰdqa/JRՈy1ge1 nmwX +E110^+׿+ ˎ昑[BܳVJ9\8I <$, +nxr =㧴aM^xqU&f 1룖uO:D[u]Yz~mdD' odD.9`r\-D3?NGGOqws^?e + @ս;Q^!v0J"2>}Mq؜/۾BV?*N`vM4#**E]`obOR.Dmi`SmdE(çBx~H]bȸXnpu4zlWR,mD}$I?=h2:(Q?HMn{DJmE?\n`N2-GcD}J89dAl?إ#9 ].FR^V; +6AKve-aeU Mf0њ.g ut([.$ 7@= /CUPw,2-.[aAH4 ^ ($r$GHsQ}uajӮ@ Z8$G# >W}Zg\$5IW^p/5(OHj~{\r&t DeTdll t%F6<#LkSSeK~c%p "0u}Z $OAWP:` q>~K*bL#<IZLemNW:-80_&jsvaX$eE?IryA'K4'Qamor_ rRv}ESXSw*dB%< +RIJb~2`K~ 7!\bY ǰD ta#gؖoCfݕ08dM1S Y]arΙk=$jCe?Da3 C +=7# Ѭ 6N@YVHW 2M"b$lvQHQe<絧e87+ʁ&7q߲ѻQrwh-XV% +O(`tRk<[_y~S]Dטy=چ9mZO!&4.z=T̔Y͒Ca&q$RDM(z0 n2lEGd8ޙNYyjOf7[FjJW6#/DNeqnVJ P 'fr~GSH]t:eA:5Ȁ<!Dlo7NtpCq.+oꖸ𹨋[9m,ngX6νJf\9!>'YU + oDfqDH}amSqh,tP%r4[ɱfk2XJ&D@.ֆ=ӪfgCv%-v8 gKJD0#jL44rtK+Z,ve,4h'yyJ;ꬖOAE=.Jg-`jC zwKܸeE7Vov`8"@=ڱǪ8ȣ*~lUB)fd&(u78@y vke#o-qm)f ⧕yi't` w}? +B:LEA+WCɱF#pJ.t$!䫏b0 ljže.FD8H׌xW8Er3V/ }|>< W lBYT" +-oTϺDyRuu@a_XgDJu{!啶B핲![~z| ZohD;^~pbDGW*LP~=LH]lꐄf\"{6u9rkE>UM%`W!u1ogc,Z!hT3"Q}#XB RCo y AYW.I 8Kq@ lؐgrǻ+\siPʉ%DR:gzAm꾻G*9"dǁ +ѥ&ODRqƝ5e"&w j\ +KlTcϪ&lL,P'["eqrch%X@<̊2>@h1,~!R.6ർ2`vgZ0qh1x_6?Qݍ +oОǬ?VR@NTL @w L_C 5 QƈF9`ѽlI[\D¡sk%Nՠ"$(գM +J~969mwMy/['yιc +v4 +7RTr0aq$@U{iaaQ8ӳPhk +7)ZYqņ᜙63{6BOZa~U4SB^yƘQB1س8 .0B;qUC>-"80 lx. m1M3@v PO1ԗ1⮓֭~cv7yP`.GGϞ DVWϒՕ%\b2jY]Y'hYWe{QVl .$5%@@ט%$ _xZ)Q)1C Tv1ɲ5=YНTz IcQ;zа9S.܁Mޣ1u'.btPq_NC Y޾SmރaL?tS~;}JLo` #_.~S5V,h)& +VbѮjqd3A\ +yR~XMA[g*Gsђ*η5#Jm6,k_F3o%C`<;dhgs"j͘  _16J˺@j B*y0 +lLC4L̻]ZO(\:F]ݙz*z mc9*th*!zBݖJ[_9B. ͞>QY2URklQK\jՅ}Il>CVB9Պ5c rHIۡ}6>Ϡ :l#ˑ 4}E^9iˍǨuuW: {9Äho2U:z~B[2 nz8Xb(F5il)֫*,ĩj +\RWk$ڨ zٳWcl10<^Gn}>S/Wi1$C:+w) .=nB,Yd[KmzSJZPBꚈ.ǣVq57Uj4S5ۏۗ~R<[]$l8+((*:laJ.j}*!N᎒mJ/1("AFt̬!28Go9m@K5QEZ:Vz1:ǣ獦zA>3gط<<4`>*bIlB-R.H bm~] n۾Ya>x5kM&[ jO ~,r6Z&EVy"G+ѩO YAWc-*~PnC ͡.}xas$Sq˷z&C+>\\<@۞G7PH+wU+@s@| bMPp7V9G՝hB e`󒫋U-zxEGڐn`׆ȋo +*E}]dDKPyJ=vRG^aء;c&oʦ(өLoѰ&T:?phF~IG@{ZG~`(FX<?tخ ' <7:]ԁ#`&ܻ?"OD!'o,dUhꯋ>QO':P}J + 0{)OtC&3 ;Y$p~ +<-@'Zx|`!~6M9=0Y&$>0EDq^H!Fl0B,yeH31#q pݒU+Q WF)4jQr;tx7#^#ʨ:f51'U#g0r4N"kÌ/pݍ]Ev-gr!A( BZ$1 KQ).P?i46;YP^d iAչBNow/N}d̴g7W|RQrԁ$ɜ!㾈 +˺`lbv'YhO)([%(E0E议Lx["WxakGIZ\ +M5/c jba5c+.} ^rmMeM 1ǮޥVN7СQ%ܕmLGcaCශCˣUJq/DBىf?})uƒ "~Es/D;0͆8E@FcqqgbPfw+ȮڦPI”"rƦءj,n[ ! +ěnzpD@/q $fxCpd@#C=NÔ'2_O\l9P>„6ѕӲ$F$KD:5&(>nz:; Д0ћr"q!jA3>vx_~55V%˺Ĥa7)a:݀7t[M}sڲyvH +-0%diwk(J\JZmDD/-*pIV ,5Ut[8>4*Mi%D`#xzg! ; +{]F gywp3Eʆ;z.}Vu6׳CB`{H `霼\a?9>"e!:;MV|STGNs"R/w{Tvj@T8 ;.GQ@oao8 bZd$R="[TA()S_SH[/)D9MK5LdD;5ڃ::olN9Q &ݙ=l==wzU!sES:SG~VjUJJͽ\Fϑפ]yKg#YZQ XTziREqTx^:s 5ϭ+$QVAET Z%7 +SIlDdwPT) 2P4DA"4ȇ?5;T%Ggm. +s>ᚈ\/ HjҴ- 1wRIj&+O͝L +Gc,PgI; >G$ Jp$̓BCiEMBT|Y_^ 8/T_HBP\)[}Çɘm>ISwy?b@1D~K鲆Fޒuc &ޜ`HPHB^"z &"B%fY@aV'w(V-(8Gt1$\fԻ1Q =%B~1jrX~Apt`]%6"|shsWҢ;DN{XHtݭ#tWg+"8|unBԟ/Hj(yν۽ΐ?y-;dϏ?Q™]О~[tD/?Gp-ZK:7g]]Drڅs%PйB +rc+H]9E*[UxFGZ w$n3q0_kEw75sbϝr@ @q=ݟ9{[Zo`eLju-!{bw#5W U) mا0gԱ pd|\86 A`#.] uS>.ߐ棠ɬ<Zm[7 R}ЕĻĂa`yG ӎcFHFům/D()mL I1ir(2db )ɤ229E~]ꉲfEV)D[b#/C?,Κ,Ty@t/{,,Jr\eY$,\7EK>|⦹B մDTl[\VdUldg3(jt|:R*tnH#S*:{Ĥ$oN0YCISe!v^LR o+*&51Kx|b + +:M1%vgX-R ܂^<ι|EyD9\bu)?SDh&`YR"Okw($z#^7c+ CXR-zWJ>惜@wѺĬX{V-3  +^C':rGZ,YKP0k0e ً8>wISXk%1eKfUVU i-3oŽQP)wa椡hѴ Dgyc5Kh̨i؋v$!hlɯ9k}@{|ԜB 55["vX=js W6%AV|*飻nwlCAw2wFtbm6 ]s_tDU4]ՌHW~a5p:]Y\t7[Ʌ<22v@llg[p0{~h F[A9}Dƿ|tRFf)GTe 8⪓jRD{L6ب%]?P}ҿ`Ic` ɥyjyjjt hU#*٠'cF{ܳY.nGv>ļ$;z'%sjJ>Wqbj–gz=[p^u5$a_7K7C쵔qKOuBm7\"oC~D,=p5+ sojYE:F`d&"H_d>?$kR`Llu ㏪sv_cC ۰M4%Qdq02B!gZN/Hc؃րv"5!KnV+u -wx! 4l-wI4DaD>urc{Xfm ٹnokjy-@hR&pkh}&ֈ -`% AJgچ:IB Pݡ*d5q(">֙nwIr+~:{FIHή%\Q8m65 z\ٷUܬ6#b$ߡYn2KGDKr  >N@9o'I>}8ʡ0zw!,MuHPAdjNJm2ⓔk{&E5w#'Ǐ*7׊MS5DPym!'qkts5 0؊I_C3Ri;-|aHÈo$wbdh\NQ E8cg+U&q$0Q>&:Kz|pO l]ͳ'Np"ML׀Lz[pdHF=̈' QZ'.yq͡ YzL +QiVD걔Bw+I_& +_GLB)/Gz>@Nbd8_*9c4ed[E8^r0\OY +iB촖rsM;Wn^G7*7Bvdk{xxB\^`2M>_Vt:50UO!kЏ]9euBi+䩸n,7j?J0gsMXYPr{'5r01tnQj|^hocp &c$J(oIXq5zUԍl +٥E0; LR]RoCD es 4{Gcdth\55x&F6SkG{Mw=I%r[!76בJ~PRLJd ';VB1 +Px]%'x8Q(v=)mD-)#8WVAO6C/%̋Zӟ YH;7m!\_ۛ/Zf#Hߋя3k  ߵ p,}"IׇQ:0{A{XW>i2H5Jw&U)m2dAdMhX_}<7rVخ\jM{ftNкӇYV~?'k 1tCu?=@S6d*,e1hLE&[mq5grKb +u(yTuBʐ0G4lv%L_PUI+C((K# Hѓ=0bEPKejgt,e=cWg[H(h鼶̬ ,wy ƈar)c--&U‚Vl-uXY"UՋ>MU5kjX>3yCKLN <LPdU&^m5 +B} V垃qoLIܚqȈb}Xx 5RtjS<\^1N5#ԧJ@3^;v/5P*׵1kew4,Wε.*,lX{^Bԯjz/y,UWκ3LoVeBUT& neg]72~žKCkRV@H?Ɖ-ˑ=B\c f(W6["1Ǜ,[&qj6+y陉a0$i{yLV^0ĥٔ@WȝFJ|ib ډ]mJ(4ƞg^].qI +D?q`Z4Y3ߕSC74Z.DUGijcԷxv<$àIW_%0Xߗ;`7Cvp + juغ)7(oQ`$:eټDRM[2_dx1\I@]DpKœ'4_#%|M ]>U5MPK؆wϹ+XX{{tXSpWYLG/ + ia6~\Ij~=לS(1wúHxF"KJF VӗKǁ+7vS#'j`b&~|Cj+08rzgdvjVvKUw`US3ʧXyuu ; RMظߪ SvRrQ{lξBilс'}s602nH(iEڂR 5`60&[#y R/0$!όJ݂gG0Y}z}m!TⳁCO ?LNļMv!S?" >Z1*\coW*cZ`aG ]AE; _<7dMxT.# +jp%>JbAc9Vʼn5l6fYjdUh(.Τ('䗆3fꞎ$=q!h[>&V!'C?),G6`Ƅ&GC͚ip!uG5pњ~sڊ +߹ ++]0Y ͢I 6Mz VZ` +B-*~/u[5R|Ɵ5?1WfQ3}ωYULTL*5a>stream +*.0"%'wf9 +I|DH%lF&&{ycZf7Zo%+_"Ղ.°T6d\}r ? %RX_ŭF +?3Ikr% ( aNE% QRպ g4jҀ #k,Rlg7eRQQ﷧7xXSj3n#gg,z5J̬nfU- Y{狞!DaCMy|bq %ZYj341udL'K ڂi1ptT +$pq&Dz% s56d\+~Ƭ;,s,:&3)$AYE?% B~{i? +[qWO  !ϔV^t2>4PA'" el{_վF* _m&E~$4] +1&d5\]tx=4eET-S`g +Vt<ンf.=\׮,@$<0rtz\\唾ճha+xǹ~j琎sw9%Kɐ Ǖ %*tbԢ蹉 8J,tq؆4.E&Fi^Ev#[A!V{\}#b F˪OLFw=m{@u喨])P^Hjy? )1dCY"e\u"%.Wئee*]U]D9I= )5|+_mq;F+ea"Y_H;߇dLYza@ކP_ H/h=EgUp00,T?}X, BqEd0):UoR+n]x'jMU^.^{.JH)4eM/c8oVrc`294<cㄵtVU!B8rnp3a?Q[yY}ʸO:4zR}(7N5C R0 CT0߭yDSLߑ>;@TOb 'C:Рjoy`Kļ8kJm: )F!";dv/^Fj~X5S5+)Q$ g~!b!vWWc{G ܋gG<Ojm^@ HJ@a&#Ԕ1zWQ]OsaVdQRiU%$q'W^nOWEWC"̭3V wd9rThOuqiǾ\_#j`.3Yi|B;o"{(Ӡo HDs6b5J3yW"a7ޫ4ˮt%0T7ib8|;|ZѦ5bD0I**{GԸ_5ٓKZfI0J +D[<3 mx TC观R0l}WW1\z^ٯH[4m@p |v+ +Fyxiæ7ykP>* ln}krc#nd=f}c~q|M<^kS}|c!OZdd1Lc:c_ +˶`Acw+qc_}omЎǥ1k!jL376.-a1؋jWzk^Gxx-g=k>TUA䄖+kήb*ɍ*O@/tpŰC*@rS4n"໑)b XaXAW2f4kYp22 dr@nCUdVAYLR"WV*r*UiΜéj1*VnmԎ,DAQFLj٨EÃ|=TnJwAiSڄX\7 +<=5ZOs,gIDPƍann4~kU:W1pυ^#x_9A7b%eb0t:/̸>1̧l~-̭"綹^UBs|s!78I. nfMyp[EC.H&?/B@.7er;x{^yNE||W~Pf,۶l l-m!l"8m[}[nnDeI4M,M$˒tIi1xղX.mrdO>P @@$( @37,d!E.2D6ٍ徂rB|႙5g -ӧ3 ȝG#|DxX,ޅRS٫,e,BUY, ?5L6!B~/ܿ;Doy'R("g5ELX7ĤdbCQZZ +dv`9{Ugc~XȺOfHJ~%OFu%`֑ŴgH5S4%p*ZɘDcF@  2Z]nFCPdt ]k6VetKJrx_=rlf3ڠ;@@}fU(Ot*x@%̈/TDTh8\N*.h@8@bB/%TX̙^ՠF@-E&D'\g UIu#"JοS 4Iyg&daցga&8#XulF 5Ɏ|3@-N9_aF_[;%Ac@64 0˕䟖x`TjV#4eܥH dbkcN31_ %ңy ?5sVtYpzq5M8 $</2,)xQp4 yrB){5:N+:_((Iy!!j +w@6 +$T˼[ \_-π"CfoINjkKny^iFC]K ZMQk+aE>7kwE[mP5UE-|Sr":+ lyB٘`ex7tPz{R1߆PQWHOZ "|!qrw1AS[%\ĤDg11P^ZPBG-_ io7;VCoʯ)q,J[L}ԛ䭞 :_ғ3q+lyu +%Q9`5s@Y(?MBȻ`k ?0(l mf#jEZC/b+D4&#G[ZhKEHڗ!^@ ee4WHɕo Ǹ6dsfe KKFf(.5V+&FvEco|8]:"?핉Xd⨏H6YVwߎ^O)jd_l؍]35U,ռ>fR rT~y*I'˰}aG4>ٱru@yj3;qɮQY>TF^r֕?6ոRL 1q@_ݏV>c"^$6|4Fj@G+g'\.9MU*UҶeȧևR K$F=MV>fRq{o-rF9q9ڗ13}/Ct:`Ut0q(A{ ݟ̎^=ˊVe'Ǵl7a9qz&oS舝i԰-GG +D1IЙ*X`1YAk9&y6cPxSX̪x *} }v6ܨ*f&g4Y(;1$TG`4zg+ u~cw0(îOڱ6,xPGWp}#C!u s'XBLMSLL r2(s;MQaD)rs`$vlƠG[E/cYE{`|aEJR{oMc ՖL353J=w-Ky)5?/IۉVG[I[J vOQR-80G@KSe)Ú^zKx~foЪDVm}il.t!YOW#{|\uCvf5{?@'#N4I%oI.q0RaAF +mW^i E3;fcPI-5ab规4y1FZE֙Cc1K 4KJGTDzBnPO]T C[.J.ovX'!\]Ej1F$,$f.sk VZ/' 2O"e@vFeuqv顣WF8t5-vP<t+٩G} +t' ]O+A|)f#٪Y%K${^#ހ~9Vz0ㆊl[ +i@i$ +nCpr +E̛f9qi{`Dekhqpl 6,a(XUd,dbֵ5P4F^@l]|10nC(īTZ_ef.F8.yճ٦ +⢃4(,+)O5CҭÎ?xhQs[t 1B~gcm|_c3Rf•@Kr0%-,À؄Rc +4BaGx]MM2i5L?ASJӛVa;Q2ô)do;zcap4B$lj5u+LX;_<]ˣ*Nk-K Ei5EdUifP_`IECbp`($炻1︦'Z7D{G~{WeLopA`]?`nK[Ψ>mMb`45 X Lm*[ Vץr \?DPdH,hYP,\tuPj'&{0H: 9T!f+T\NZGk+}ICd)/D U n(/p\CM2|mn_Hn!WB889kl'"Q U݃#(y Ĥp`k0O +u09"If@>xO9r?=}@Qi3KlmaLF^Hci@=](tSc ( $O7ԥ`@POjL!40 L{ii`Oj%Y}I?'(}n;599!2hE[ u(RGr`RX`F= +Z*Sw,ݟXx,\Hpc)~{.GOo"CD9ѓNh#[B_OB &n u@H]z{č_ [(y=Z LD PvkֳЊőQ&w u߰^d}ڋSݸ.tg608$!'D!Sָb@p)?֮`$&{%KbX/ RP헆HAȲ+d;\RHsp]`&} +|]7m83sij>T,RЗp.е_|_.SCRa@j{#2VXp!El)<*UԒ!) I"ZD0ܽ +JGPZ$ Df,f!h{yRF cՇgJ/VPyI ϛMTIhEߝ-p`-6GX4?-TF)}]ɖOrq_ԃcmc \%` +!LQ4\}%c"BMlVM[n M#P;\*3HӬ[]hNC2v_4fN{oB.s1 ּR` 6k"@TA]xX#4Kd6XVλِ6̯X*!a5 _y䡌+'l_)LLO|̓q$'J.ZRbU$MJ qQ1G%瑂a\c`Y1J1b_4{6"Q R[%uɍ#{lM|'_$TX/KC,k'l\Q䤖^O- +1Uڵ${h;xzҮѶsqkӇJfQjDĥdDgtqC(2$a<0}䃁1<֪{eg/$N1Tx{P;b'CN A 87<- Y( ?]"_c B<@UnQ%0d| b,?2[%e0\t*Z]tcXc `R@?A@4-g[1^x`xl 3P Zhs+(/y+7"nAY(2SU6hnA"sc2(Bp CuΥ*JD\;(Be TD > #ş(wׇg0/쾕U]LL~B/kW 7Ϳ ( "gs*ݑj +DZ똋^L|g'1p'yG J~ă V<%umgcOp/i#n!SРI>Fmk#pe%-˺leСjp bphGy!.Y뢜wrkEEw/w2( G< HC܇K F jU2.C>J&UET˃B+g[/6Ƞdb\I(RiJ ґkL'h;qXwg/|ccMo7U&Xo03edO,CMurx@dHGT`&3 FE +S4zL#C> BJP)W(H`d+~tNKjq4> -?H1h'{8D T*lۉ&82'V$uPiT c.O"ZYᇓ%ҝV06oVf1m/ríb:@I_S㣁n+DOǘc΃yekJ血+G3)j7 NJe#40bWqD[7"fa\rY-0⹓71z[/ 2%/VpFTB< )˒֙MEhSJHɡm&V<"2joSǕ9$3|[DpKHeq,ˈZ&\CGM?98{㞢}52txQa>~0*6AP哴vY] ~4f@['@t犮#1}I iiIB]>~YM+JۮE vS܀]Ӆmlvc#b;c|0~;_V y (HO"O.{D6XNyM+^ ,6nM1sku#2Cr+LЇA!k^^W@ܿNk.H xVǪ) 쑸ލ@ W|Մio$7N4c$"=e}y |^$ 8 tkD7qƞ^.] ᶇVS %uu 5iX̏W}cWωr[oc9Ⱥp=Fr;p!:juLfٽ- +7M$u1?JE(i=o]6corB%\ !\ +LDsj _yxͿc{u~UR-ՀХF.fsHW 8)UQRLzf:F"T8XEd5CTXLb฼MS^Xt',+ :,aa"s4;d..lgrDW9=XVPjE/v5~X8ЏmJ<)ReeھvKY "͈[ Bnd>lv.\P2Á Ri̼sԩ+ELҳD!*%SP7% /kpѲ4_;7 2aQ6Ьo[/4{nJ: Pz}%ʑrJoD=Tx/a?ǠT*CL%>[gT]V) Z4q&}T# EArZcd|Pg7H&@HSPm;4k]®~mne`'J 9 +&Oi-M,gWZӉq7ԭXsW.LG[hG/?C tdh2I9L ݾ~ғq~E{[#a HZ:83! qow6|*-b|EX&?wїE+R1taST"!=2-ln!RbS'?*$tQ0F4gm=\eXfWU)~?zOs_LFW`.rW-:$ZG)hOǙ1jN, +T{@}-34e^[ Rȫ]m%B9k#av>LNT_1+"%8ozˬnޣD0`B4Hv !7%tV;ϰ3` +k<_²/ R+q\`gg1uC{\Gc!u3^f)~2kg,.Щ cK)ߴf]4WtKU08γ*B0J|n`Ɛljg`[e[La1Sw#ŕvэ!o\a./~ +k9KrB.cRe%/})ҭM&` CģX;ݱºܬrACoH!iu_7HQP/ڡD+ RK#HA} +C1Z +P]oDS=E.r[ЁQ@f0$0NT"iIg[y9_NW¯;UPp(&fiR~T@Un#vcAÌEJȅw.BA +8'dߠNi^}\Eʤ,6*۽?CZESE0?ɥ +ɀZ@P: 7`=R:CC"*Ih$WuJ +P'C>#|geB,gHeBZwErD~hh ;!y_jG8ρMŁUm` T44b g@ v3xlt3+$: yr1zKnɓBPpz'?29't;{Q< ^R=8q +5V=7W`K +N BT]VфMTATwPA^ڶi` >}"z[\I\Rl 8bo~Pk=jnJOGKŤb_qPl\I[C*, now j p:oX3]o"JASewՇ!kl凱#FDqw`#HABA: “oa9@ őALGch@9ID3)Fq!KDICg`5 EG(.7J6;F]:'(~['Aٓ EςE.k1_`Eu3Fd bv6H{!q-+rrye3/};c U못 ق{w]Ʈ?d%["ie;Dcu%p/aފmZ7j.F١hPB1]c]nڝ;30йu޻Z"nvЫCƒQC[%O 35B*P˞[vj\^<rbGpl&ɒL>3Z@Aˁ G|[ 7Zng>U;)6p(&S=q%ٹFt?~DgRjTd[Sa"'9V{|n;gB:Qq/6Ȱ" +;ߒYX`ZC#0!CAw0`K#ͯW2ʏܜ|n fC̭PG[Dw! +%θb`\4.YZVIDdtA/dHN-u{X7>^Tq%:iLrτF-i +Uݼ($5]:Ygo#;"XIQ"|ZCXI1HB |g-]r43 J, +7e*NPe"T1U"hNJ\6-V;a7SЀyby}1IF (`%ܝP^Ug<|5zBɠm9s+ m*c85'M<z5|g;|#)1KU7)M5Bg\0{mlϷBK" /yZfZCɬL(K:zYD飇oFg"vfdCBqATN +\Cűa>Fys^)D($@)x>- +֫e#:3 L8LzvBsro)a+|wS)J%W[ɬyv k'M&?o^x #dtG8PAp , :vHg +Ȝr}hEv~.!\]inS",:UݘhRVvx +AfYQaGx Xx>{֜X)wHo$)H2}=HQ$`EKė| +:g>E(, kPysck +4y`Z +bIhN[SaC;0Rq߬v#|b!DeGC -ME AS&怓`97tI2zI,V%Q|JO]WuDxT2Ƨ9u.f6yB^F H +/*۠*]IoTR+"W1C*!8LUq87%a$&닆Y_..$c1ږjO>P #vsݞ*A]b*o%X rADrqY`taK[]պ.0  +-RQ H+2 W\*8;Kɛg2˛]WYN1YI=8$PB>;N,(Ce=Z|,I8`aд jRXqC6ev*B F6,Wߊnj +Π역`>cd$B[_$+ƻ6*i'!GVbgp\;JƐYfEOod%\}kѓ +m$( jҨٜ*:rs!1v ([FeE{Clqt`PsuA˹=U\o V]fAN<`f9 {Jwd12:+P)-Gh ,To( }7Zk@ +w"{,l-&i`FE׫'̃r{ϼ9`cUO6Ɓ +>k'EʂHs-Oz~Y>"j9HJ8K 䝹!Og䰋7yڇn`yn*` +%LTYkʇhG΁8_1 +!JirÑcb53iD-2*3JLJx'FMJFv"^Dg!q$޾L=/ k֛UgAsL޾hRCCdzFF,*aO;YD6+0>bY\h&_$!ϾE)s6SFA=-VE80)j\BpZV"w4FE+•#"4WQ91j1dKS0qd$M5ɒns RT*(IzEԔOIuZ)${=u#BtZjͭx5{mre3w]8 @#g^ +>=0 +LNvyPRyp}׭86o-"J1s?T?1< yeA8o/n摙/a34d`$Zܨʕ: +ȊT=&&AJ_ oG=ύ0#eA:WL;b +>cY+aOlf4t?Ovp&AER|^ S˔LݻD~D}n"SfhUiئ"F+™|ХP%_UAN*BvVWjQS4kfAa&GHT 1|X3VkB'2ǸՄAwMڜˮ>ZW{>@eģ/ (]}hl9Ѫ+'P/\Gbk(hK{wU8p7J'z 'B ,P5&vVו:Խ|7ޫAd{g Ri09եOX3frr[ {QCqoœ"|sLj.CR`Fs,fwcvUՅDgD_kDb/`5Me㸈b%3faꌳ'Q1G wO>F5BMF%_j[ 4QP\lnWL(_XC6"o$K9Y%Ѫ9u !a愲we3fTجQA'LƒR;}BU$0zN[l'uj뱸LuLۍ6=[5zz׳*|LhIjrSM%Aw[J~|& B(p kykqrPވX6,f=bsS^{YLmMBzK3Bpf8$誨 7߸Q=Fd^ YX&o[lt{!$'wiѻa[.‘t%RlݎsIzsVEKx%hp&Z +@9\#$|UkxvWAL@W[j -%@D%_$_G`ZH"/1CpŝH2Į+JF곁uEsN`dW17hεn'ķ}CR5@elar_* DtttW<.Ľ3,͋{rE 7ml̋w ;,{LE݄1t/ +#}l/ 'tݐWun~EmBad|!Qԁ&MbӝH24X ]`BΊ=x.gσ\IOpZA(,z]Eҍo Ӈ+~4ʴѵӲh pGRD] Bֲ($ UcbXtDPG7ptp؁;"0MXE55Ï_nCoU mvAjOu97y{cNEdnKKͮUYqfHtE͊|&ępbc[XY6d(F 36RxrܙwKoDwil$NK[ ݼ+ymiOUa-6]@n ERί!$6$m1p/v,M?\4˯/KkX: =s]o;d&BiFgߖZxHw)"{0!2ƋOe1"cx"yF? cm=.^#cWyu7%͂nD(< + в2qY{Jv粤%;[Rh*7 jk!7IZE,6dɖeOV, Y =EԩOA' `WM's⨐EyzRUC +- IQ^g~%WARtE@"ɋ7m~<[+J:Zgea?PV-l0Zt)=8{Y@̈)1$U580CbH 3[ؽ\`LZ 5bBarGK\|؃pH},pLMA4Kx< pwW2VcUp=df>|) B'|Yx3*ȁ>+ɇtJbqns;OK|aQ2E&2zjD֓UgF\ySdv) 7,2Fck`)lfU@Vi6_1]\R%A!ҎU L˯]c02pn젻@{>Rw|dhJ N3 (,jOYB!%gDof[ߘb f'Q(Wmav 2<˃+n*<65R,+3sqFV6/!k5(S,ZpMFJ`MwJӛ(2Z /0zD]nαJQz^z}4|ی7*`x '6fD͙!Inɕk}srj3zbH@2 $ Fi&2H +33$2 ۣj 4 CX0v 8PjW]]-/A +5z$uwJQvmm ,@CYVY9xsScg.\Ue5C'3 ꙢTL8K +**-CRAeQPFDU%BtP+W"bChx URev33+3wNtw9( , ]#P2]4:Q& +j!xU:lH1I%gR.CU0D Cv`N͛A6):.C%08lː$t؋ ~ PV,xqXZ4͆3h+yDdx:#sBfNضmnf(Tecxvw;FC73Df6Z&"zm%3$33¶:;{U" }?W}~sR^;=੗z~?E=ep!o} DDe@RmGpqXUv0~ؗ6hOd:[GY&jTd_: dl+ e59ȶ]VLe6j-+C%ar2%C{XXhD"VYbe0#Vaa˒(Us>8)dm>g &m1휼$ #90F"QX},$*BJN6& +b 0@CX NIu{4cՈLK!?G:~&T_NsIn+C5se.\+,PܢÓAUhڎ%"|)'`AM~woPŀo|cP0휼(1ϕnڗWVR1_t[("q/}꙾ =8i5hXm>0u +)d5hZb{{Ss'Bie%2n x}OQIZ4%ګIs_ +Nw3Yo}X'ET* Fw_Du^;zs^E7<m D< w|e }!ӷ,N +x񭛨jn؝!9мy4tYK ;ʕ%vbĬY{0a ؘV7%?/4W8-/l#bb~rfE?ʶ0WYa;)Yx쏟Zr5H_,rzCYIw= +7AH$֗΋f[3 rh2\[^fp?<9-} ǥ XFDݦ10-۾cO@;^n;y [7펶=)@rT{R[za;h]tp(~Ct,t`G_yHbЄ֟uc,ZH|)سyC}cX\<AQ߿VZC7݉,lx2lrœAlIP*Eu?}Ot }τ.ډZ@1#٥׈pe&= d" +%ڟDq`Gr{zմ 1(G#ܪ[7P,@] Y4Pyl2ջqu ɾql|Bib-\s׷4^vhJvѭN;\+kaCFڠ(ƕ ?:?ǭM>φT%73fB_PU E>4CbkWoubʶ\=3|51Y^7itN!z~.-%.c%!]^8',>te LPhˈő1+V>%'񜛈ѐHFEH T!酎s8ԛ0qEбWE7I 4Z3x–1 4r_3I˟Ԟ +"W{"o|o(eZ2jћPl |qvt`-=\J\;p'@#~Oze5Ũr<)59*ZsmaQQ1Z:drS m"wgvX#S7Ui></|DO%D!ˊ5Fo޴{=I.׮xx?vѧSsU!KwFEYGn{[@fuDbȉW2|r ~|)GsL:{Kw:!RL`z' 6 ]eot7DNVYfߤkA 2=*S'YIl@[J,`|-_CkH+'3,*l:*0'eT"Y +(R@ˍMUg~=mBNFlYe '^Th57|X!p?ml|@!ej|oOq`svebFgU7&Y%cTRd#3"I4R3d}q3+ C9Dzr}KAL&C(|dZ̐GHPQ#¿[pZHP_J&À hي^iԾ)rcfi܎ .C6ߵyw@qf 5VCr;X~WܰF*Z+!!*F")Y&Wm( +N`XZQt>;LMDe؏4aAWs 'ڧwd.gbyJ|Cz.`]ED^p6ܲ͘0d6\#Ҕx,wce FTG 3s[cڲ؂bn M|ćj?HP$1ibC+󖂄VU)CNw7vG_ CM,ޞwsw hgL]󺜊~!8+S11~{yw=nȩ 素U.Ba0#?Lfer66mn MU@[Vc=1KC4l#x\8lY`e))B?+a>ĻԢwD ȊH^4̠٤`7C6e)ЌzAvMvEk  B#WuAa.I  m3]kDdM!k7旔Σ}BfuýL AٽĽڝ"ِi P4D~?~ޮi=\i=j)zECSS^re  {lǙ v! ٷMͲ7 tu7=u =5)aRGj?FA F<-[Aͳ0vA@ș1lvӲ;^ƺb,{@YSri#x8' p 6Ͼ\c">'&(m(>e[TiS>όyE"s"n{HHBa؇F1cRp @&QG2z4j<AC/F{ T\i3q!V[a)@ TA.09.* +z-}!m;[x߁]-!SYxK0C)rv99=$u2D9C¡y.i +)?|}'pMvFA#%. +QO66j?fH b:P<k`%o.@ѥaSNa61[ EBk =G&l^y@x`nNC_EzVKB:6d_=0Pi="LS 0BPIyk +5J"|nMYtݤKQDH>0)fu yd 6à*\ജDYΖ`PcGq bp4,"T(Oɹ) Ġ6p\ŏ‚2!2c̞MGFVq́@;FBcX +N`I>D!#Q|G7T:ѭERI-o)˚.Pb>0yVL\ ecS0do?# 3~( KsⲎlG=O.ԗ݁usz؍j4Gsx)!HdRÊz9aۇM1\MG_:Wb +lj-xxs,0B9'1"É20|H”?Gd7M+}ڜ{"?~5\k0| u9LᾬXT#H2D5nF30JEp=zLYҷ{ :x/Ċ6Am!* +oѱȤʍYӖwz/"$Ƙ2-UʤcLëݽi| LX*B貂u /̩t/ȁ#v~=*+Q@K/4SBx PƲD@+f]؇ԈBX0⇎rpf@FdJRcvWX 6?$]sk{g#O,r`l>?|#+HQ2m:;Y])7(?D@VN R/D"TH z +X`#tF),71*%ȘJ|(Q0h>*NkwG!<EE!yax1u .GQDnO$.S}a"vE 2 "OxHiž|#5~a Q4X9I 5ZIC2R qP3óɔk`R)11%K<P*Zg yfdv'`$cWC!zOv@J8 g*?  :DC$BC@=DS +#ϙ.b2Z 'aQvVcU~A#Y*7;QHMZ;3ΙP1*)LhˢٖJ6KIcbZ6a.̂A8FTKrFe5 ﮅtJ9ˢ~"E]1b +\PARwXpװuj$~ZnHb oCWJ犏Nz,AFPФ,u׼9q"O5{@|G]nBd:&+&(2{`E +H,'EMM}^, bESV"tm > 1" լ1AR-*Չ '@Z2mZ8̣a51A75Sљ$9DNXe6czYiM HuNԶ@<$$X.*!9~T F1T!)u]oj +D2 !W ؜hƖ#y IB px+[6h#,#5/*AX:xIe7n$24ꐿ!C7hiP|G@2vj8N,@^c}Bd*֗HXF[|ӱnxr8D@O>6tDjA7Ͽe6&&, +d)!*Q޷P54<!Dm>U@6<{`w ◉d&<( J}m8y4SUC. ؉Gv<~Ļ{!cW=d抖Gd +!Uv7yDy8 )9J +ʘR]"e +oέ՛7 RL4ti&}u96&^W𥴨tGSҪ"ΗڣJRb2z=Yii2LT{ W2b*`jc9~ Z‡F!r^6CxJ\Q317L] +F`#m\.1 n-^uQOp?+V +dgʋō\z"fAK9o ,x;K3aub6Mq>5#Y!@$c]e +?tKvŭNH,^!d:l^%GK%| L td)d&C2YF crJ@(\~lF!݈<12֫13 h)SRqۙPcԭ{@r&f@@K3>ՋZ:yML/ vڰcjnz1Ϥ& HQ(.kY1$`BX_s,9{n{k\BTYU3 Ӈ38ÍU~O@Dv>]īN _" ) +PnMN1z̈́=lٱ}6ޫE~]0/1w\SwR;4fM^h";\_C'E4CDfYsCv@\-=.(\![U(Ŝ.[3"q$t=ٶ.j:bS$9Vd KZ٥S6A/G"$2`MUhIek[oPߋdԒԈcs,Ldosh_tԂȘ~/H,ft$}MF=Qߞ>dTS; +1kP{a@ _lXfB|% kRQTW3Z SKm ;Imp0bLGHEtfh7CS֊759 lY'.ZD8NF4_UY &{ŏe F +=vDY"1<">&4tʀ9x _"O=Ag3/bӰWL`IZAI8wF.ۥv̎b1=y0:v"An+cbg@FЏp3*E$Q +Q&(y>ԉ߂݀y/++:.zn+#olo+U#) D4{c} )mf"ԮktTʠfRB0GęC EЇ0vH 56i9'Iy.;;.,S;˭yf@kR>QS8``7]He!I pR XcHYQYHì%~h +jZ}G +U?>B㣞uޗnז>;3!<[0r ) pb9˟#"^'~of'+~ՕoE/U1֕7<#ބA{tdO#oC&: Z',*3P'݄ (E~0HZ1z  hPo|~֧nZ\mGwcFuz%LP{,ve⏗ + +c^==fF!Ru^ +2C7 5$V[k0w0=8Le?tH\&i"8lx*AYFV} :o* +Yӕ(!oq.H裉J*ΰ\ȡd}K?5Tiu""PsbL߃ Tij2a'dL׵ F䄁I5*ψ8"Ic F V|ә"Bc= r&i27~F^2 PBHiAU*E&#DP ?\zTPBc߫|`.iط+< =lle`;ֈO ϘQb)+X-=s[!,V2m_R+mݐX.xNcqw#ח.<&.p9=ef\k}x=UWs.jjsbG҆_1u{N{ЬCI/R5M^, (e>kv +N,EemzX ,NׂS4- hVQX0Y!َvnzin@4=L9h G _2c1~zfsYn3~  8Xh_Î- 9Fo 4,8 wP#yQQͱr[dnE.:+p$&&033H$ ƼJ|3#OlZ>IPˋJ;tXscs(3_aa&ʦB}hP+@p>tio\ǁ7A'i0#бoc哩t&7ݣQgC9aZuS962 +nN8„I,j^6Gg yKžB ς`{ Źf7j9+hT]RE@U*\,/kbjZ=C&3`c[~ܡc|z*+i_ +M[z"࡫xS(qQMB㨱> wc>lVQ~IPZ[hpJK $dŒ}`nV>e^]`8eFDw.-2<υ+pv.f44F9sQ~`os,HbB[cs=)Yć nwɏRۃ JJh +i $d4PtIPnLu [B{@Vuq.+ϋ;D&j|B + tX +hL!KD΅^&d"^t ў Bޗ$-jˑY}PI ű6HmfLldD|U@M.'TQpB5qC@h⛂.nOf6_GH,1ZbΦ ;Ǧ3b[HGNSmfi^8I~(E3] +RݝmkAqp ?9%g3 +r[9e[ʫbylwn&XTԳ +[=(g]XʝUG Q oo  XJ#w">Hq2uh4U.~G!L1?x5WP+#MaPNP-7,A%:; 碎6ǀG"vbQw6VYF'rjoM̐+D&n1Ql{ ,G1rz.MVxc"j".3T1kg7m@kкEhun(=Ed柶ا&e9n;{E "MZ!p@]7s/ Čt|0G|?ߐp4Qƚ(,lWpYBuySy$*wL#3\l ϒ\oIR=)`gJt(~CӺ**X="S2J@uЇiJw׀=H\54(ܯiԎ"! ?YSlQmjm\ؚ=Lt2IS9LyCVr=.6Q:}R L`zGzCh3*A'GqJ%vyw$GtS^1~|ޢZmLd rVY;;=u la8DJ_;FIJSV f@7 2CF"n⌦^?q#+)W'7SYQ3 nz0_Lh6lU)35BU{?8'l>Z?Vu #-zPi8Fgs8/upa?Þqm'v.1.*crWҋE_DŧZ_ ~* YFJzkFL dB ׂv D.陋&6)yLK MޥE]bH=ڂlH 2jr>]",C$4Nf0WUI""ݍ DHz %i(ƾ tŎ⢙J^l,癖풬`[v4Cvpgtv}l-G& QQ, 't5gȽ\j:葖zF702;kc2{g;, +ȹh޶(U/хVL}$ExuEE'cc)e %R{ǃݯv3=\%}Ǝf1jQ+h>K#7;Ov.QB4!#OYvrbN|].^cXKzQDXí^Ţ3 @O<#-I!f1+wt#oM%gYڲOZk7Rј"%C,E`P@Yj_ɀe;w?DE댽A" EU +7*]'Cb_. MнxHH;Zn02Y]@?]zv`..0F4uY3#*Y0,_:yD‰MJ˸]L彬He;,j;(Z&MÞVBR h[lyny6&&&p UvךS,5%;˅]H'6tHY & 3Ẻ++Wfmr]ɦk\O` +(x`JZO˒"U!b D͟JF({2,cēmV7WyYi]20 &*N9yJBΓiA 59aBB-p}tNSG(i:īF=oӾ%Cdљ{%śH8נdB>FF= \Fj2v;3+ze;k ڵqPȒ˺҉Kf5{FUJ j ZXroB"mcDcݴ|ܸaD(ePV ŶjS*(/2L?Ș8rN9Z +QGŭJvo&[|blއ{J2juX]e$*Gv6Y{ +哆 +rE FI?} @ WzqSor8}FeLA+>tə(V@Qrr}, +r;g^qy&-&6̏F9ZnJRT`HD,}U>1iU$׿ Mk/ ̇ck8* w_rr!e}GE7|ZrߺӌMV'-ˢܨv|~ El'}C:(FqZ7SZ23ZXs0JOvgl{aC8F=4pGѲ ȴ&x4Ka70K?WSm>v>.g Ed);ʿ]rt7x? eObEiEJ;}ҋk hE4y9[!2+)FU4Q +_OvF&L;c=8y+ +]8F8[Љ.pec@F9ޅH /XCm?%O6 jBޜl0uswq)k0(q\=A鋟^)5aI>,fJ@7aE! ME$6ɻa -7M`]Q?\)j^1;Q\7X6j+1ԡF0:Ζ7yRxא|+^(?c3MFB!Tb <׷Dy}.z°7 _57E{Gϭ`Pⲍ-b6;MH*GQ.3KS?f;~8C)bf]bxudf!qצ?b QOmfd8b1""ZeM}'r*XAD&`)t9d25<)K!#WS$HId>$Y٧bd4 n!ˇQx'L8-p$u+"n,؏vh63oqg3C.B O +LX- fۨ-*-!)YZ(}\\# +v@y#@Å ,;Cx|!{`Htu6qռ?-'LJ҆n kVDKKQt0q39ufZ 0!,K`9&~]YGMVG1TPEHFe`Cwʻ߳W2557=r0!3u^ՃRkNY8&ydR!΀9CXY( Х˘y&GGGJps46ȱy aѿObKyK}_ uHpKOLc3~@cHjti4'צhNUvA?N K:jFe`Ct_ݱi_gWi^=AMB*7^4i4[+"L.J0ꗲ\卷Yi<5V4|G]Yni(f(.0˼<9<)!h['gD) 4Zldg[y6 8c?]{t(ʛoZ +#r cd}t:KI*)񸸯iQr +BR<އj&B73奰IoKV#62bSܚ)zt z}wvm5 ix#t<+FqT + m^9H2Ev <%0" (Ovm_qǠ?޿Q@,ha ҿrP0uD Wc +'@$\sVEfwcNvTY@ -M,rm3}.Wg涘vQa.M:&dKel7$["T;1I}ϋ @N_R(;H *!w|,ޛiUK1 |Ȕ{`om6W HG )U%#ImpLǘc|m3Y, _Kx[N^2 OFF.)ёhxtS`Z -M!Va DwG +x xXW!Y5j.>JfNHUc62 eiL +̶̓m~-E0kKR394AJeG̢fï>"ǻRه0{!4ҿi"SIvytъFhv51])c /pdR$i D +r4.3YăH1H#xMBYiygNP8}d/3LHaB%Iv/Bx@fReZ]"b =T!J p4I6".JDd>S1 S  +(r@#z<™]Qaqj 3[M>HS#ap,F<T0Ђ@vACNy.Ք6!\9hCR5^rb[8 JP׬lroc3 n*£x$KGc Y+w>8SN>ڬ@"\iL5UW,YgmDp>U8Z3=yȅUm\+] A} N;!Wi撚`AoJϞ+όR ea_alt qӻ^` /=?jr)+7ö́ hF/46]"šChHL %% +$ofrzDM 7w :Ms 5Z0'~LZnnB߭}봆՚T LbgU3"<2TM-}Wc-bW GX1AM~el+'^ ~|H&5>DN (~rPS6!#uL‰YҢǍ~MAe}`yN:{l 1XDpz%{Sa }`ϥ(W jR.ڊQԣ6AP!-Z"3BG&'\İ1qyPYjȖNdfHO~!?o7e֖޽=^ f̷q&K8@Al4VYpj&R =+YeإO-( 6ˍ +x,:du)%dr`^zquc ֝ 1C`zz:WYm0]KA:odqv@dْR*Nj:?#a. έb*8Xa%F H3 n,wN!2ev!;&4@ "Nlhĵ1hВ Z%~[]w'L(E$nI! + h ]ЋQdp pSVu^A6@P]ko?LK3=$풰x̹c SYTqi'#И&Z$UopbI 278̏ +PHp!.wv8~ 7B.Wn{PSm +З жaRj\ +F2ۿ˸ߘ<:CI蟁s*|,rH +ljaN[CQOS I7.Pͱ~<\e3,4grVDMKy0@sE*I&}d0e 586E +MهO8q#&CϤ6d&T>fą#QRd@6ή;JԾr |pu銳/9(isEEoKh80@LiYF$)i$PJ(I 7^a5`!>O֡ϏzxEF-f^ev Չ`0 P]Fu#^ܦjBTRDU]2`vΙ7+ e=aNq,Ԃ<SUEj%D/$cqܣԤ6.{u+4>ksΜȍi-{͇rjƱIJzC -wn-4ψuyQ>j8>0Kω*lT)9Cx0 # 7/)n&*]%4}Drm*S<%f;RϠ3|;K*6vn_]Z^+5 +|+ؑƅUsQz$xWaW휞ᇾ"&{:ͼHjT6MCfձ`/=U>BRWYӿ'L@sWrg >8cނoE;a ,lKA顷ЮŽrQӱ-Gxo ߒY0@YZ50#fqu ] Ki,h!!*LǕ!U'7vE +u& +fo{2:{ul#زW]Q^ҟ`>UsyK7F1u(ڀ6?! +!%NPk%r'Kv mB9E# Mv8iodeL3_\CvlJ̓ ~n62N*_ sKڷ2c,ϻ6XUHݴ:;$u1p։B9Pؑ4@ ++(5+|Gud_ + +=R_ῑȮ_ɎΒ1D"YFx5l(c/RxY$o~G8Gy0'[,.yG a:!n͗G8)F(~k/ʷ +EV ٚmn2 ꬓa-*$?LkEdUNF+E.g?##T!ƙNQdwW;a %qY&td4wv - !%r@`wވu%' Ǣu U1^t;[QշpA%hB$zMY׵]d#N{(Q~$ZoˇF` _XjH쌇U)M5虾AX~g%P*K^o6C +h*76o8%fAw)-r,afUFgC|;a=S ]yJMF\9yۿk폮Յbfhx/RO>k d4L.x,$f B_2\:Ù%c$"B-\njFs~jp#C:H_D9Gf#`]EF20+ DH"uZ^3-kNX$^}ށ͇I 1L+} + ]f0I#W`0" Ya?]Ȯ7 F$F-g{2c3F䝐:4[a*\ڼi1"]lvtϸvtE뼨8kCM4͂2爵D 9us ]ɫ]$!S@fuҏЫB!%n #TX2l'V*nBnhp:Wз̢ͨi52tsęW,ٞc@uYܘcn(ȑqFhR[sddqHPX?/},l1_H_0+O,e%Ѭf Z +0nH!8D?70p JCQ~wi +g9[Bn/ZxXk#7f4bBƲOSh+{;IWw qj> l"X ++rPDXhRYZf Fޢ`qE6DnꆁJf6%P"7V׉{b( +x1'k;zUl9U;t$\ ı#'ユ$$uKmzf8P@Ɔ];Vlso"}|c#`xm);f L.Y;Іޘ9uP[ȿ sf2ߒ@maȚ曍ظD!a쀴 ޴ZPת ˋ7i՚). $*ݽLG$d5 |E6?;%s-ļcӬ*mۀ*=`M'VnguExUhiOy A%o|O5 &ǽJT)4&ZcA0dV*ʁXgT+8iLxkLf]^" ݆X&>g*2h[,i @~O>MZ \ƕ!eQHtu'ε7'_3*us? <Ф&JXFt'/ҹ>w 8q߂:c< p40 +}*4d>֫˷eVGE$(kRx+ϓbA$*JT%T2D-,σRw x v<ز؉Y4_hZ$g@eP8 N\#gcw `钙A$0XKRWЂk mW y!*> C ]trP8 *)E$&Jo +Y»3%flB^s2~$xP',kl@_O +]&% +Q$S~S|q;R:JC2e%~[cy '̀͐UNF^a}2罄bs &jsOt%gc⃁xr,@FjkT3dьF beԴAu-sS~"`c[у^byI}Zì>+%!a7}K !=IlqCFۖ-*hH鹃 ء~~` zl$4"`RqAs pFqHww;OʏIr֌.fq1/[7aÒ,<=` ܇Q= +%i"*rUhUR~!0I8jj4KH"hFwwd;@Y>zБl%pZ<L؞#5 i`_.K_*9lX2C].l)`שjn@~X4?C8DZp7[;t.9VEeSwPE5*tR㶨ᆦaЌD8i1ybPAlqE'}&ON换GΎN?1d^S+C[}x+mc<ֻ_j-ӽ@r_&`j/E(B%+辁 ؊>8@3.dƃnY#Mc5ċ&Ѽ|,3DKtT)nu_k~\zrFLX!pVK'hӢ#M +t}Rk:m$'ovV<e6`%x l58.82X+_Xn6tS=pMG :T}}Pp~-d|o; ~6 1W͌1Ǒio,jd 3XU b@֟r[FDfC+'CU-tѭlW_)]hFl&y>D"J ]M9LAb͏xݢ?|p2ݵm($?#0ZSYI3DgHP)HL.$2 5E$;SH\(+Ws},,+D 4oz* Q4RdXb?v HuH2,ķFQ/N44ƗMrf{ +,+R=^.܍{>1yBn\@m1ĭFjfmα5uҡ5-"C%gGW(@8 +wg37#,qd GSa[%bYf u@.9j:~ZIFgGIs hbYCdylJHX0BjYinŠT4K8*1Ŗ(*+@08V)JKzTL2z:06JNaPU@`b%PY4,r#EJ .L=0JU㍑bKQ8 n /9y C``a^CoDMZ_B<\E=)o(=,2q|<*K 8!>|r]Q$l"!(p`t;*ng2Q'opH/H PL#4ञ, jNj=Qm/M^Q.PܘmDvLw%'\e6GE'#\0L۟'؂,oX5FE{1yz4Ȝf;VšAnއl$l 8׿=ۘcAۃy>̲gR;Պ=@Kf|K5"#c.Dţ[caEc"ӽ`c![^l@M#Awu%`x- جC)NJW/^U6Bb&.SceW" &xHgT\"B3ϡ&xo1 (QA/GTHX[C]c: +2. 3(&ߙqČ [BŇ/GM^(kҟoJW tF!D@abuAVvm6㨸{^ H9=J9IY ?E09 +,w,;6hڿ~kEу*ʻXRJ +9^Q#ajG$UVAYx܍n4ѻW +C *;w7SSգ/q=8)' +!D0B/" | yZ@tuXʼB[o 2CQ0+G3e¦ + >zunΙ!nyb>6` Z$áCmQVHDfNW%dT*3j:)I ${;BM6rJnS7#W:Pp9T\". )z; ۨes(iL4 kXbaA23 ocngE8_7q?a3Aȟ(bFH%*~X ʼnD^=bg sp|_(úh1I.X `'{[7kVQ{8X]PNMv(V% gn9?N42#g$oqd~yM< _VLpsYkB -I5P# 0Aw=nviZ +(@`5NP1 Ctվo`c磟qϢ$iljUKΈ|co;Wr|O0$Px2S8|q55b<7(xګhѣW+sJA# `";k 2 te^"aيZR9 Ieh D6/_TUU\ 9e <7U9bTH>Þe EeЧ.0e,?i6h + VQבCq +'| 9OWaxpHxKwIR|fP D~iv Ttg2m6BdMT!_'” E( +I4ʒ7)^"?sV>K9gtg |*UP&A;GPv& (#6 +K@(?5͔;xj?>mPd4Gi[Ku-Xf djn@X;@Lg2WCBR>KkZ#ʡnkhGR+N8L|ֻ +{C+5!?,`Y*Depbr6N$DQ ]inK\lE[? +)Eks!`{ bV"B㝶u#n0fV tJg^FS?+E1xqPf df&i60j0}ȶrv{job\ʪ?e%BZ+Vړ 4SÁ5U(玌ʀhF‡,_@~o`\-qmTB4e pHΕkΗm˖QsyM̓x&Q pU'\8Wx"F1CU !NWcy8 fGY.g}Ƌ2e1ǰĀ|7q 1m&"@h\>C gnǠsX›NjND45⇄ATjC3v5: ؇Y26O!E"裕Y)$c税QDS`3#f1yD:>Q!295." G)khrB +Ҩ(mB̸r,Y1ă ƕ l +\ !KjU%n%Ge _fǻ (߫M%z6;x*<Q2?!5"(cL/!H +RಇL==mYQC (FY1&HA}D>訠G9zۑLAWƊTQ% +rG.-l#=6*h It 1}+>rcXt6"kG +cSvPA?WrBh[b*UmƉ+?* +)QfkݩOE;$qȽty#ÌEDV/Rop0S2t2ʯu];aTM a[}=`mlcZOd0෴ bz'#RHiϙip$Ӌ8YSCGz`t:CMf=J-rCصw1]UKam,j2$J`4, Ue*LL `UG,A 쉴mH +H ʹam#jy4)!T<&qu:f86*5_ѦJۦ-(un<E^ }|,- yX4N 7V},IkThGٟUj9X+{btBdu 08>uM.+FP=̃=y0&_T7^}vuOm^lGGTQ ]`'|2lGUWiCE0]Se2 +yDs@ n^e72Ѿ)ED԰kV)QUsθFx| >ʭnxU1=H:^ٜbmęN2Ia԰)9t GcIɴ̗s?%$_B(z{x#-@.$HR[-G +8Sϊ6Bs.ACGaj`Q#sWo)TQ +x=%*()E"_i?a|Ǎu'[`!W`I +Q!6H6y[QL`Q1}@xenޫ>4j2F9^"+bH>{;0UFPaֳN3NoRMv8n_p۳ԍ+wpN#e4ɈecB(QZC=h2}A((Bֈ^gJҷZ. +N; d \$=%2:fW{C?TjD$6$y]mdI\@_K^-_ +_ktYpu69&a? }Ea Vm8&$#7Tq!#1HOANU[NB=5;SsjUbH+<ԁrFmDv9pBNmϼ&U[w8$,9/1g>ɵX۪+dhU#G2 "57~4<%y!F*4>@< U+uk:zi2k_m!)%JPOqim%8Ae3.fzx8CP쩋JU*U΍L-*/WTR6.}|McStP{;mW:@ r^ö]3'4.2pm\V]srt? wM{Gg\Re#*-C26o@/F3z 0Jǘu|an|5r:FATݴy /3Lj%P|%p8K)7+_ϼYBӊkQ:h 1`UBD"WTrG:uM藌 ) Z#oʴ,*iTJ S#xxslw!gFNhz .S\8[LL^<34cS$4hKt|w*W3*Ao4Q!vYYwq;(,'ϒR@g#`}-Y6r5O">6dRKXR4Ky!T#OփSzkmj$k{&Aqq +F-k <acԡcMFD{Q 8_D +[&>jɯuRu5Bm6_0gG t^ݷ +4KRt^ N厸'Q'ryXvJ:j7j_C%\V(4H*O:=Swtk'(U_ǯ/|?4F2}-hǎr_^1[c3{2ޥSC10G/ǟ2L8,\4{KBnqݙ7ϫٮ,^(e]r-57 +ZJzT)f;}uJ@ojg&1O 2hlvDZNbg02Z̍hLXf:Q`kHA Zw}kI_ 뾡9! -0~ F!^Lx TȂKGkQ,iܲ})$&d]`;F\j*TZ^CF"\W_ lWDCѠ\c>Rvyy .lt[| "Ol~ɪ4j C阐ƚ:J2XhZ nfbAўW/wx®wyXrڲGFId1xN7>[L{sġ4DF!JR߶7+v={!Uߡ2.I@91󥫣H" ,8K?51d:'&ߧ˨4HZw!q! MjI=[BqQ O֫Kyh.TF6POh#~E.@e} \rT@˵6q4GPδ2 IVWJLIC8"-X\3Ф|ɴԙ/C]q]&8Oh^`#"BϤ;JQ+P"WMޑ>@H +<I􁲳 0>AcB {ڭ[L=lbd"˲By~~kU[MQ @CK^-2k䯂|#0f[1p3?&KA \É4 CVM_ Ʌyg;Xfg&;M 6~T yK)pwx4aVw,yꪨ浠ѰDy,yTFiB(/0*j<%i`CoW+6O]ḦF +EYmqNnO\|?F0Hy'b,&s"uz7*Y duma@7]  *j&ۍpaի@".\߸Y+)ICCZkD}; z.1F~(|,F!'G mJX 9{Jɛ$%u̇7[KK93B>:Y"Fi"Xtt2vmb̘pB`OLl h2:gb$9!8AaL̀AI ,f̎1@8#b)L D% ف- +#M/iS=2f!1S!a9^|d}1@Xږffff;42ۈȟH3;Vhloڶ=bHl<ԋ3 +רZThfԶ!IYBP-%3HX@,V̬TYYg`YbV%(s`%f xmVkZUՊjBZD*{A<B4gיQц}:>7VmQWJU!U?>~!O$!NdDDDCOz9`_~LJ$ ~"l"h մEHɂ4TPb[V5m[E 8l{Km)= gc}8|pGƝ"9+R `rNlCu_)Zp +of:DGH_ "[>L-J:4U'>phn뮻껂[TP#3Yr_a;yUlbS]v٭>x,h3I$Es* (6LdN$A 2  'ZLf][㱍T5ӉΈ?匘-hbBjH,ϛ}Cf%ZgJ,..:$V7 QV º$)2k焘e+(q.߉icӒn,)s:b1dUvF޶8eGC!. b*ᬏ*(N#v.9;QIH}&@m*Ply,pFkIpl|mQp2B-5zDn~ $*Sǡ_FrgḶW`:N͠qO!>bp^S2Hw##8QF [c?ƔC M ~zu ؒY]$7^=?FMQOYÂu)6aq^p6!-)󖫷@E%sn%T endstream endobj 57 0 obj <>stream +@]O/Ff9 t4joY7:dq RYr7+sL+N" +avYA#] +'}%MPل kNX `W<QVSn=Pݟ WVzhįKj-.4"B;CGg懡'ަE2wJ+:vg#$Bd CttPRDo)'*R!9]] >,-`,G\65ea <7фlX`?8.C{wEEV[Uaiiㅦ#2YyuLJ :' p&;!R1L% XV&VVGGmST9RXY_(2qyQ&Tw5耢U6c"G++DZ}$3cQ?dN=24QD慆e3dSOz% _ݩPC#G%ki6 \A@||Q| +B͵QˎL+@0KUСY5I1|1 N5O| UG4f*{V0EۏK!c46AEb#2v;San GAf z˺% ї]%+!pM|3, Y2~;o^6lfӵ1Y<2dQ kSbƌ2`, Q6~L;rsDOgMvm~? 3cVm i#n/f.jpUn@?a3D1єqj2^x9zM[/cct!.Br!Ec " +hˉ^Mow$*G&uݩ6Iw`}Ң3 O">\>oݴ}dX7zxJ| jw%C +n 3RGH96*J 4ǿ1:8fg}myzY,߉vCP4=|C9,kU1^ISYq> +XDaꁡio=\! $ <(T jhXS6~`X<$^c #ĘlŘw(a3e7 jrn Πzƒ9Akf&]DFlKQzvTIHi8ǔ]|v7$:wKND7G P28Ps,0I^â'!ºs }H}n>i?[>p +HNۦkYb#\3B3ݻ <*x+-44V`1"nWi>ʂۻ, +SGI=fEޯ? +NDnn-lK{R4+*5EwOX/xۂb&ho1Eq*g/1SHqX]wmfREֺ-)y!yZu;jO@hJ3A:ᣂuy3"3\b~'Dk R"ţ{T\Xq bpn@A<aоBBܖ +GRtwN/߽׭kY->k _'2òi5GxOaݑQj,--(AB;-q1zv)F^BPP)ddmHG3B}IdWq:hͨMv6aAU!*v_²cF3-O]WJG whl@Ӵ3RLjlǙ~'UOhҳjFS*}յ@夼%F_Z?QPMrݔ؎,0|X̭o$t䉥Pxg cvr^(\g.Yv5T+[N x _$!Zo]+4,rnҰlܪm.] 7ڶS~dX]fBR~mkjNI4? ;KZљ6u3d.PNsaAŖ 2g\Ԡ]b`Y3It2.SjFKD+QUUwۥunjOŽ$TqGvĀMjc׺iz +\E&_V/!Ih){yyk +K715:-XrC&C6V * Uތ=v6޼x_lSdi>GTfFc|”!%fː +/v\œLVg gBqYq}5>hȉHLej`?Pպ<ʹcp+!]%$=,+|?U]zG]ZE -;'E)*sG'4/BR +,!_?  E>?OcNk:Β5HZz+>5cmX\n+0 , +M{\tGLw5LѼ=d JʹB4 JySib\ӰPHGc64֒2MCܫl_,| ++/TH`ϗ\I]uif,X솏iu+w=&Ň(2$fgQЩ\*gcuc%| Pt9NU+Y4DUz+,uT[q\$hdS JfeΛ2Pfķ쩛QU(z2I41.I8ym4 4}x7#)M> 2ؼ +eH\wSj]26܆ _nçCi-X6~/裪W͑Dl +|s(=*`2Gv)kqlsx09X+(k؃ȣ giCiCntŠ6,)zk]l.VOœ8w_mwEUg lVZRd)Zv,J 0Kz׫tؔӋHIp.;a.w EBB7Tl_PC k:e! ~EKhsч+`k B΀6,@"C5Z-l5 t +0NTUUٿt-&3f7vG?>tsi@m!<7U`/GbYj7^5Rg>5:u .yCX; +靄fs8A͖_]\݇u|oe*ݽ)=[LNAZ*[K2 l]o!MG를 +bFKya/#FGƒ{hϬpSbĒq&׋np:;ao3!hTLXF=%q.BxJnfߢ8 +Z8DHJÊq,zфB5H `R y8Gt0j}yU;%|FQqf#*܏gҫ/$/`%b061$~h X}:XoIevLrA B.R)muձdWK#W d` F%LrvEn% _zD,>jCP*:CGGYP̢;ŵb +Hi6y+Иj`8E +2]3щ"r Y, +`PQXir6&UtVR3'Fe qRcg %:EݝRQ%Ԝ-^̩cJnl;Sx1۲o魆F +$h뤫"Y0dXɛkAjD4 /@[fv1S a2F[']ضu_~vgj`DZ&&᭦4Q,7.k3Ybru:5J95nT9;Z}qrJBV6Ú-+ހXg+v2ۼ1 ˷Ӄ}Xt&ڮ{Hֲ/-6M_ +U:'v9T|֓)[~PIPeHBZG>ځ.MMf}^QÑق%A)yHwp4Q=z=HB`IUӃ,kT4;wRǕUؔ3dͿ[x9q Rj1闰m/@I$yޗ #v'IƹOBҬMSɼxK_dzM QN!?p 6HWWA?#ocQ CmLҪ OqJhIϪ AJ4)WlPrkJU4gE>]$q*LƮ +K OY KQ~I ++9f3Nd/CO=PjU̇KK7!p Ydg7*:5=Dq *x6tWGseۤtLycLM:ם7Y2V{Ѷ[pt^)5By7TD㣾[*npshW;~Գ$0RDP*9Ü&FIr2 gbguQ/|<?/nNh A`͟CM˗L6>5CCk|!*P- +t4.Lb@Ţo{W +Q#d k~wib)w>_ n< ;W|.:H}܌-^xS/Q$8 lH-t)a6{K?Cm@ B.pյIޚ yCrLe  +%zܟ<-?-ָIC'ćp``d ͜)͂-?zfd2}m"_M$ 4P큘F~.x q坟(hӜvS8= @Kf#I&uLtk[y\?| +h],gDW*^.|>_rpgq~g$!LcA^Oޢo((2Ouy" cD"*F,a( >WT:KbBok;+&@3fGrVˊ`o +'M=ٟOf~m@Rv2h b';;&FAӾDԗ.F5]na_Fc(IdV v2s(3wN]}׵f>xdŀf<\Lk7x4X6.Vr6% Jm@V?" ]zT82]<5p)j1O܉U@fNNc&X9W[2"NpkOǁY,/P^3Fv̘l%[^ + PDѮF,M;Gra CAc , @s"=:kyS^+Qǡ>Ji_wۤPCƖ1eȸ©wiݣc˞ hJD)C|c[õ2&.壿6v̑ djT ”9?zE(PFdiAJw醃]\vn}_QnT]!@ uڨd)ZE?R^!H* YѢCD[Cy [p1P|l[6} BOV`nPHw+q (9a&Z\n#eT|i_ pyǝ㷀j~ |0@L,7W*)X3х[]T!BlSC rx>"k2J9xH(UAkW 7&~K +J;OO*F5@F5`1iT)-z>!wX_#8IhTce DpgTCioq˺OhM HOoR*:4_V'-}i8vH= I-:>}A7,j} +P6T3ZСgqbf2ey⡡yGjѣ"LQPAU6Ue"1㏤JW>if7+z§i_E$!Hu,6{f9[9EqX wjFu$befT_9 +Q.QM&":z]cu0nr k*>$O# ?in~OƪGi}XY#H]^Dh`GoFUK.Z%̰g ƴ)v?1 .sqo w^"weʒQ&/|^W*Hyʠolgd.F ~9A4)Gb7H<†LA5h3D5G/}/F! aT l7RAqVUPS]%2 ,[&UjsKr uՔ;/9X%mЊ1X?j̪IU +%K^,aMc#NpҪwX;(6{|S?GgFvD/TWb%k, /@Uo}fTmz"c$2LBnTO!H[S+ +MTҷ6p`!!d +vY ?DAͻO`mG8j꥕o+ejl#yB²8[3se8UJ %;rqk͑`cpݘ}".UBr3_.qPIJ+c;+ A~eP 5MV@k y5a0R G[}8Q-"סGGV~?{|q8fg!EBgiՊC1 Ӻ5 L _,/Hhf 7$b\TDRQijrk+HOo2Tq}&cyݹa'\B(I3j'᫣%S;oB إ]KP.d+Q%[WlFQ0 ;5\Iؒ(1=1ì*;CH2ӉgGfrl]qqW$t!y$a".G]jKTCYDp3K taA*N9ҤP6O]Y;A#dEQ %W/a6hZE5L,߯ϕ+C _} ZЛ@Fd ۟R0TcvܺMԙTnZi6)rKZ;rczCXB֗;td,#`BH MY攘˛VdBLE0{>8LmE^0}1KD*P*Cɾ8( +XG C>s +1Z8$ůq˕B*DQi#e+#,6z݊>0­U1,!Ţ!g3KVeSr{`StGiݼ +B#} E;=ǼIED/Yqo_Q^pR;z͞L$WZ2iꆹ@]%$,*߅jn "~ 2RQ+tkRQdI{|PR8XY?(֌%1f|wT@"q S]:pQTk(g'G1x C]ֈ[)kp0qxu"Ãj ejVboJ*P/DdN୕ckeZqhGajRͲ:=݃?c@fՌ8~XgZ_RL>Fd>oF`N@ ݉` +W]YnBF`"y= TAx 9y 9A0rQń!sTE JF~(B PAԭlfp!҂-R>KҧDpΛo|RvFLJܭGBdȻ'-""Qu MZ|J"zJGT#ѿ()&[(=/j@fVĔU%Atxr g|EZJbbEfa%Bhah2?0`YdLCQGRYŏ3ݚ1kx2Zj dJ}8XyWujMy@SYQ8ZC}z(_r^Bp$[icuYp ΡXsà NTUŵbe,5E;\VEt$WY1kEЏ6˔V@p2,0 DeC&5iZ Z_XGk<ΗVL(ُl0b&_6 L!% o?92逨Y!Q>8gnBxf~@OrrRKDwr aͧg  (G[*eئ@#?|BͱҒV@Mu6h::dg8L +^/F@+_DD}SmcڃH/K) ixsFѓ%eZKjQ"2SO/RQ5qr(K +M: P: 2u>I;>W V%\kЋce9"LKe` (SGCnRD\U#Cylr&Bwx&?ߺB.ُ-X8+%%R~o%r jg{1T2ɪ +9 An~c>=0ŵ]2J.A[lQxFp a`8ftQ.L +#؈0u1]ɷQWm<6qe-#[Q^kvHC0 +)ee1 bA{YEu  HeCY D7-؈ȹ|n?a'vAĘ rV +{zϒ:I SNh>D:DSGv" b~b?I~!B!xZ`)aQ}5b8&p&d!B87 14'oBYBqSm"~rEd{F߀&f\lU"C* Z0ִ1C<FxqFb!LIvZ@cGS$:)DY24ך\@,2ލKHX|Z/V8_B#W~Z4WD +ÇXrsP#iMɢ X]XTq|aϢ]sU ΅D`irVVR:&RepVb~"GyIJj~ z r u{BhWA\]̳@E D.z +/j],r`Py_Qj&G;Cv(y4nP+EkR$@vۓ-):C0ĎVhKDX*x:cImA[Dہ)7^nyRk]y'u!w .) + +ɕ&M) X[kr*'7Z[˰mK{dT=eo\݈`O# v4Jw)_r<Uu:snbmZbC:;?Fo`AtبFO%Kn {PܯY}|Pͯ| b M+aeSgՒtpXbUt#VtƮLIed:$eDć⟤#XHƹJѴ $t|Ѓ{IAgԣgnq\xS<1'H<[bwzVB1sq{$*gQk]cj(QNxfL;E-QX]E}cl^$xgPC|Š܃B&0XVb>qcKtZ&un4܉8Y[xۘIpQ1^0Nu,͊]fV@/͵BMV?{ 6mojժ {+O1˧F}c+$]kdIgOY\Tf"3Z$I_@ ?#l/zgLZU@cB+iӬh(f@Y?G +vVpxZIKVi}0cK= {_ Hf`ܣ[WJ6gζQ<3l`¥Lhme;  CY2M_rTWI){m+TXdGD̈́)r[` 8 NְNvRjMk'm7w Wy4hst}uP+,V!IƷ҈Urʛuh~iܵ#0EFUGҭJ,,s=7@+LKzѣS *<-$:Eć0Kh+63:tX k$ H.Yh˼M$ӍWw9AɺQh'0f(ā!7|#ţ =`KsBg)n@wB0IWf ͫ8篆ĬMT*iE;9P)R\poIm6Gq`wEP[:!H7V#Ո +6ɑ̈Χ< +8aIxY?s0AH*U'p޲P(v04"D&rYoeM2ۋ^H*!ڻ8?%vE{%r8a2[ c}bLQ8c("CINHF)4f3T4Ij_o^FwhmۖXqO1793Vo(yFbP"1!:ыF&,@&dnDN1jX'rHþkp +L;E":Gq(8"'&8טr*S7&B0 U687ֿRpȐs"Gi;q۪^YEWf=|djCz{źcGX>8WrرIWHWdBAv4uŸEojcPxuUb1јXgYadX90b3IdEu≹i ~yAd\+BXe06aEABK~ <22y"C&Զ"zSϊ_w;\[?l؅^;iJHNP\-3l{>{הJ Ǡ}֟_ےYzgfb'n8ui.諶,w\2/Qa!E0eyYUu6['EG8Ndqd .Ȣ^Ň7?%< 0/Rּ_O_92촘ޙE,о%<7?xof}\s.Sj}9eX3K(7B +-H8Lr{fQ.eABYid*lؿeoїm&vg5ݑ` 1 QD.񩾍nvj}c7,ʉ<ˋbv`H9GqLzT/AkoWR Li]YēN_/NGk!4E+}wFyC FNsxmB[)Y_ӻz@MDcLzꎌEw`r*ĥ~ivgF'}-E ,ɓ/巛5!ZUk)B ܅9x hdDD' Q0~^D-?c `2vjL0L?x̘/ `n z@e!țaɟJD5 *|< ;ڎJeeho`۞"SEZ!|NmOCvgRӠc +O +sJ;G t( UwJI3 &Hhkq!^\l Y^K2#yz`9TlKlGG«HY`{UViP p!y;/ "`4,S  +#B <\D~^byTXyG' ,Y9#΃6L%0{w"־C.Zw|3exEkWW9;֦QESD߄Sle0C K.vad藂H(N,Vx\Ast=ߩ{&s¥I5YyP "ĢܦcWW8!lX_kz]]2cgwL]D]l#^o "g_3"x#+>f}a0x hZ3p%'St}X3rA^1%B{ʺjBw9B7 2 |߉E[DlqmX2̒D>!@UGTH05L.}-$rC 5ݍB "H= gZyibzӏ|"* w#|R50P mwFkDn f +nm?#y_ 5:( „>bS`xoXGe$]y*KhrhHGrAMCϒ8a/T9Ȭ`\a}ͦb >j4T+?^- +(.ʐބmZgp$ PYBt?m )Ct$ߠn_l1QFҺwQG(wl(? .B47I5Hו"2ab^I8GA'i+GtFKs +MVBVb}jmSNP;'G YqVAWm +]\]Ipv@d)6^'QuʝL315\ .`p#n*nRW`2q$ȩ%pU.!]Hl%+I ٕB1ΐޱ YE|l:^'#Fw+ҷ`DD7Kܯ +>Poo&V3ń4y[GrTjYcPNbh~9 9ﺤ4=bu 찆0ny)3t ,O"3&AH+O"&\!qnQhTԤp|J@*Sb@Dzq $ Ur%Sin82F u|smř]&ORrGgq XPKAnd{00 .L@@|jۮ0{/ $\ PD̿C9+mK]NZ{r$6=#S g_4\cezT;#ʀ3Í $uB #\wfLN,=$R3XS>j HќY|y?u%栊öN`+wP օYñuDx֊ GX>%aWg hʔ$z~iѢAKG=A?&q(-DD46P`/&]NEK)0BV$4D\MࡪCHiE6NX^t7U NQ+}/^S"譍h4/oS#ڶ:mwߨ%]fԐnj83Q6NP|jӋ5.?ݨn +!W8X6,0 ra^`HNBWdhSO|Ʉ`\[%qYdѴq-!u럩!9&Q^c]Z^t.I~~T2 钋%c!Nslq3a=hrN ֑#ևQPP, +Y_y-Wy3x+O ./?{e'$ ;FvސdtF&:2tN,UZR *Z1p>2urV|QkD=yGj`IĘ +\xMʝ &<mn W;`we%rh#\6 +GAP KX +I AP3ZP0Mhuԝm8@--Ry -Ll|@FdˆA+e};$k4JG,k&bfD*"Je1BOۡwp~t5S[\b2CkA)dggtnN?hF얪+);MNUULVhb"O:zbL[yRi `faжB5r"uF~1!ՠ9}r`U'D[C(ܟ,iE~1F\g{ix'6"(LkO`1`4!meb_҅=1Ҹ)" Alb1v>x%3bn}]2;RGXC݌v0@ׂ8]nMs-;5`E>ƙ'Kfv>}|IapжnإvjFD! shvy)pٰp^n(.|]y!$83L]yJd%"Vhkng:y,֮jMgA@mqkHد$&]񛫩l(! H\, 0YYHPK_׵.ױ0OeJBL/_+4Ć_{4*'= @(i[-yUJR^l# jXj0Qn/ ^,Q s92!n`MPj3.ݍg@\Huôi(?-:NZF{t =TMKN/M'רW_*:I{an5ʘ-úUiHh.ߙS} \;KKR0;7ඊwQ":'S^qd4IIsuzAw5fQ]8!s`+!P$q#HQ0#aH d3<6ܙYp*|C,0cf-o7.t<ϩedgGFX/b%4H䔉crF2ǐMQ$^PYA90/5cG=qk.0kUL]-4F#%6@ z0pEi6VR'٨ne +fU9h:WR +vl,Mg߯u+3tS8>mnE['Q#Gfsi|Ვ=:03z\ |Upk@dӸ_Ӥ"U%qBLنB|5!Ŕ(àa70=U =.Jz҅h@K+qJG-,;I BC:@nCx=(_衼<ޠWxѱ Zb7#HF#( }o +qE(kA;#F,|֔a ti+K<t,r2a,'V#NE˝Uog-yj]X eĨݨ8-bzu`Ǵs|Z 6%T/%8JA02`^p'3fS(WA m8lDo6h=)ѓL #%H2d*E!P9&ϢəUA)^ŐTDW~qͺ7ݐ]ߥ+3ЕADýųkdEU A}'~%>.sRqQ:A"3s۽O40e'54O"/B/"DR\9\E۟+}-~yL\iDP @<s "H)x}cZp#܁!}RCdx.}W@rFu[o};'葸6 += +FL4Q8TWkV!FڱarODT-&-6FPHi(aɇiyh{!D") ӿeM:[A7tp+|JƁyt{}Ez$7[ (Q PM|p "m##Rrwx+4Aq%jFsN-Zۄz#H=!վhԐy*fYQyS uѬcKp;k9d(WkA81ykp1NgK:mEs,Jx D[*kNr6c:PgQ֔B/+xȳA/I wn{`ܕ]q h2 43FrjK=JZkP Nhe*BpSH_ӥJ aA ќKR},mA+蜥-5k@)jvM#i"~£oV1cBrvtV&SϢG﵋koZ65:-:"7hFpI `Ke⨊,8R:*9nt5>GME l +G-a.~G}o˨S?@ok}lIbG\kLvZaI +զ'Q3D BʣтM"2Ɗz9fXpKJ%ʨ|DwTY9fg.L{=T9M=صu o.8KAP ^":&d{d6}B[Rp_C.^IG2i^e$`pG#d0ONtKIj[#'haiUɶֳⓤ424e +rh^ E$}xoeh/s8ۢ"FưxGaZf=R(v#/ % FE8K-iWV qȹX4yvaW{ ۛӎ}']lrVJ91iݴbh@=ҏ!!3G->j'iI(Ƽ3,T߫)7ffU.`JjФ)H^~Ry`X6kGyw ^6O9+z[CXW= W.|J>(m']e5 GfcP 4$2쫮2ޢ`_xۈBןIĄ|3] iF/|i k@TڴUXfN`B(ўLpl.NP?xA?Z xU~&gvANQ]'st$Uj7hI\B&ttB=7#0-@uiADR\ pfZ1A^#g?.[Ix; lYe7YzoZu|`3ug1 ooaAAM-OVNmW|oeڻBeG&L=rY2 p<}Y$`4N$)vǬY#km!ݒ<:XB6tSFG&dVea=Y.%͒t!a`{-/5D?Kկ,p|"|=h!yҁ4{%`Ôhyy0{QCroq} !cj"{Ol\LqpY5Z/({ cMe!˞QN. _<|^W5[Ē% 9Y˰|(ѓ]ղ[E 3C=<"uSlq`ܛ&HeGWpÍsXնArG?4m$ɜB{Fֵ%d)cP7r`ZL#w 's*H^h#kQj~$ӻ[`.6Yz$UeAܙl׈&8&_-~">(J,|{'k {vSnnr\(hq\?$u 0cM\U(H)yT}k)V"J6LI݆HB{z +Q;NQxLy@{r!zq.8AĹ$~փh!NP)fIYÞ0t)Xhť3= Fs Fm7^MW_ha7@8AடOT$[v+VR&}ϲ|^]&/SDNe.|;F$ TƸǝTmmg7FwH9v'ud0 aH*6<Ȭ'A`oo]l.;ĩyqr]/ȜpR'!)lNHǿfs76 +r E2iPx ˙' I<|Et35!-l6iBO=l\јO%پ!~8z EbJAHEpJO TsK PoÝBll2@e<[me6oVg; Y z^8T*2ix tO/-L(efRmP&KQc.`@L?(L1ˋk|ka!bIMl3|Dp;=r G0mFqz2]R+?=nI⩅v_PzvCuL7K_@EQ%Vo+\ɆW !!!EWb1}C ߭bЕLMұLx% + LUtUc .oe|MjlLe!EH-bs"x+‡'`h-gY] LVZ!q N_bM:էE_Ėpei>@PQ < g; 9 lɡ0<ܘmW >'qYt6DuDɐ8W]ϵ !a(J]VۼXd-lUؾ̑jl(ٷS9oC)7ćfBn^6KEW2x^ sxb?>-sc݄s5+Yq!>ozF9h_C dRY!ڋX.%)yC)7e^L:%Dh$6y\V 2@扑d1,>ȬeL5vPME(&Mxbãi*N "8rj+h=B͉Dd U}6בZ +cr 1gUPD' t(sQ#˿D^W*Ou#:M834f +VKKuypܩɐ~I#װeyrA'I ٫RKZZ]kUJsM.l@jwPz\N~[bꠎ\,?p w'77FJSR iEꚫ>&̲nHEV>@!~sac(<#ѻ\ +쇞Uys+v" .`''Yye6 xǰX[YŗzxH“;KiH.Z3E^zj mjݜ -Bep%Si27/-^;,?q6%U!Lp +P}T0DjF8ޙ8--{+x%GWض}`H+ddg.I[B7C:@?B5oT[1u}<xN\B\ ,OyC,eÌg &rFP1H_JNQzfٝMB[kY 9*x|UupUl(]ݒӏO笎Nt&[*㻉? ODW-pD f1 (aѐ2D=U78PV)_LfzmH Ic&m= S3">M4`{P۫ q.986 1:ؙz`s/+Ô%q[זzjUlah]%/)zAlX83B>Yj,klf8@FӧۡԤJLCKU`&>/ +7ٍ : +؈MOV$5IP9mfR +,Z@] ƬI;m ~w_Tt98G"QL)*G(P0 63!%8zIqV4 $m+]|v6F +;o\7QSLչIdɛdԨ'9ZJ1t,G%8e%DmmC:)׮2J{ޤʈM Y0EY>+ Ā"\.霈!Y_{|Pls1zԻō-ے SDtar:ꝈF|NEC}SOcL-&D>9:s1{PԸ) +K_xQM>A7-hJ]$In]Yg {ar+mU𢱌5 v!g%}oSB`FHI(`ve8>ݲ5%NhT,V})3A N6!f'ѹyyoy}uGXbX|HFpMh(䘻Gi,+V&x|vpˆ,d*[Fy''D?!"PVJ+ !l#<" +Y3)?ɜ}.WE#ZT-Jɺ:Ŝ-W@FDih\cA -ȋDػ#VZڐ҉R3pWNVgw +p&Tz8w<\дwo<܋YE ב$ + 5"IVc7J^l8|ެcPAi5bώ +E1ͽlpP\@Ck^$E=4֊ N%B2]3ձֽGSD~A=_s \LeNTs iP6ž(xAQbDۜ-zsXX́OPN3\GȜbc9q_&H|YU?[o }$Օrpķ1E|.%sXP" ljГTZXhNBegZ}Ӓ8^o$;,6(},@6 I6-&73,p|FeG\Yg3Ca F2c_HR'"Ψ'>go ͏:~/d㤣%:2$ln'm'$QE`RL/TZ}Tmi@̽gw' +.#V)j_ㅽ椝kZ9)LپAO&s3_v}VޅOQw*;*nM^`؀T# XY9o 5=^%.ZG0_UաbujB K;Ū=k/&̦Bcᰢ4ʬr0dfdc (l>F}h7 +_ISW +ZGkmC"4|jdrLr͏,4{pG,$ 9+^tjOk>霯:DFlKGDZ8SʄuD%O~K`Y>ڊ#Ґ]k:@ˤoN'{o> +[fL=(|yi(k|q3]RqcCehg_qlA.uYֻPZwq-u|dq%+2?O 11(4 j zqp( nk LEn9YρQڅNqނC58im.aF^8ŋ*zUW[~Gq m_4\$Nh nQKY/fj&esaMU$P۪Q 8-3 +2'.3_]wV9c]! +:,lzL}D[%lkW;@2!áQ6>1O|׻Ee@S`fۥ@Ff=H:K)"O& kɍ#bRJM3F"zzcQSa`@ .PRTz.PHLH{mOM"6oo~2!e$xM5q+eK{ sɞB]qFmxiDW:ݎqZp4S*ut~b& +;u +wFj/qo/ldu h#luPHs<%`P/ͪ L:?f΢^׈'Ǚ-zl~` Ô_X<ށnpCh v~XȄ޷ 32Иx$ syWTIf+C_x + igEUO~^',¥̥nXjnأ^￾ow{r#xc8_pm{:=F^/W2:l +GX +XY g>]_W Ct"UD@[vc= ۄM ⥩cK"ٺ71:\%Ak2ޫTl! rax7Stz 4F84G ㅐ?0I\C7lFѠ+7J>́ӊ~0CW~Wvx`b*igt>)əQ Wzkpop/Gѳw@0͠yN2{}yl@CY&;atsR+T@_˲ͳ/3ʭ*_x:f#cIOlͨjD0$TC( tΪVJT5 i ˶1]N_& l@ullH +487^ (a03۔0llx*&] I\nC>DWT"ƽp5c*7p]mwFP:X.N F ,D&4;)sgKjzA `@x1 d燬h\Qd*ZF(GX%HgSO"m_cYK7 aK42Q:gj j)uph Z}"'$yoIQdl(50Ӎ"Uį G:f}UcU>Yf Š2~jL3ٵZc8d3%{_G"\VDt|y^bz ={`b S6R\@RRQ7"&sD xRz҃+\dPy +ۥafd?@ac@Ґ Ch]g[٘׬Y7KQG$W*b[RGHh.?{ +F'9T/}I\_Y)Xf zsi`TܩH+c" n6LYS.`BiPm/ϫf"0J-B⪫m,豼 $6{%'#iS0dǐn ĪdMs2[CqPRc8āqT[@+Eޅp:$EyYb|/ș X,+Q&$)C F7Q]eȽ7_A?œvDkQ}!WH../)e YT8s:ì [?}2yl5hꊾqDPڝѷ)VE{F槶XՆt?Ľ"dnKB+}:ǾdsyP#Q$$~.9"-(u#]򠙪X-ҠAi]85 +q#^6W(` ÖLL*e.fl"DVBDI?: +W8"u爌cpb~OVwT'nC 2 -ڰ!=E;6b"Z}8lj:b_r&I)wQlnlSMwwZ `rFsN\S8%0=0`d,LkΡa=$`䙱.˵6! XgؿGο(c4qKi$=4k㽭7*)~8D?#9܍:QFE[,n\^VÐah%P$WS}+tZLt#oblܧjontÄ ~DR'7}ea$wfH`0up07.v0plFɊȟ3D0>M9h1R ]YDc<>[_L}sv:U&e!ؕq66Vhm^õ]ƗH(&n:ߣִ<#싢w*0U%5 tWϱǨY +CBpD,:1$ѩ"Ba*9.HpW("K0gUWK`O~xn=B;htAN,9M$1h80Z䬏Fwbr5}jE}vdJ?֊`#8BZgI@O7LNA-+(YMdFSu_L<9Äf}4g!y½**e:\' +Lf_ #䱧^dbt!ɡ3^Ic$! +Ŕ E\(q0h)1H?(ZS4EgWJ\M \"Nr OGW\x:I?_O톆 ~&h䙅A{cnwvlWEMbZH?4k0'ߏt3^ЖIlX ƕ0U-TMp85*NmT0$a=?Z:֔6SE; w^nf=L}mM;RmOkE+n1yQH6<9VΤ[ހV"^ix$k[T'S!L T˂FN޳¸RՍgYK'܍ĤzTV@Zv_ % .WSL@bIř7 w^ we@ e}3Tl04A6ŊfnW B*ƯIq.l1)} #5O"4LXM9h자ep1tsK SE!Dq3X8KPҪE=tMPg(;-$Qoh ypJc'추Æcf":.J]t{T0>էBȚ0JD`2dݕ7 <_A`^hp;Éz[c;C|@vyxQϫQvl3\PUl!34ĭUʃiNG{Ԧ}< +q^4hzo:dp,<[0^~8;Dy!!1B:phq /'1;6U JWRl|D5w.'+ dV4j\ApK^ppج] `y;X*#+S$ycP|Q7W@xK ڤfAkf(:w-Ss +zD+l_ гIp!d%$bE\1ƈ^%Z("#ħH:T!S]/guhZC 38yp\YSMzf\ڪŦFIHv%AUj䐃`/"y=],C.Šs ;K˚Ϳ]Yh80q(QwBTץjaV(A`od0q՚m[ϰ(+O؏l rexU b"p~ZBPT>[V' +)h1N>hP&䓃(sX)tpUM SSIRm3Y[{6a +zc9<3HHhX~őDg#T5YzB1Lb/2 < KITC*գSG@zdמ[G/K6gv?)ήƤQeռŠ0 #R&FY(dDwbxbZeޅs➉ 3L J|c4EETqHہB 1SXEh3÷taץ7 +5[?v2 F!QkJ]̸s66~r" 4Z&@ +ܪg &nӟZ!yhO"pUbt@eJQfHbucmnÐ7?jB?Ni%jH4#/rK#Ŭa#Gv!BSS5d]knTR ][_5i#\dC9a:=,}4SS +TO 3˼ܝ@W(&Y_K`ͨX _ EFS>/E#XuR`!7#5Fc12~ #-3 +e B!"":dKokpϿs԰Hv'&#yR +*!!qsxh[p,4i ӌg5AIVB` "ߛ=:\F&)X5Al Z9"HI $ + ȊvP|40% Z9r%nbC;5Vő9 Z*D F3-= Jr!JA[0W>E۠sYP!h _lo_O 7EAQY5ps@ ;T(3ł +7spj%8?4(K+ [UV"-v1஦a1A=4ΐ( qC.\>&0;()ܶbB-['XCrl`kHa1'8dpEB5 -VUՉ0|<&~&:D'A PY܉UjXĬ02?#K`$Mn  <( :\BPN=1 Fئ*/DNbW-@eEد>)`[ +MtO&8 +@nq8_3j|;ad +$N78*F[ &4cw0 O[LM<$cIpUpl߫"5_y{dN*=qe}~Mc.)qܺ6!]=I^ F\ b@9xH]lu$crIOrbH*69tN=Ţ,r'> %""*Dx$xUHekVbB'-eݷ?j@?BSp7cΜJ4cZKEC|AaqMS eiUt꓈{X,|Q>!单:]pw\6t? XCA;nJ.-nmrPqm +֮gCG$' S/e u +Ph gm)3 +mB۵sჸLXD--dNz*ep7h,'݁BFQ%!2II vsZdxFVw-ttA1A^vV`~Ts* +Q4LW}V .K>f̎8!Gގ@4 +Y-:VP0ϖCslζ +Qj saP&:ƨ)(iZ`J +X3xį"I}F/"~՘%Y+(uJ}f_u5uRte$&\t)]کiP^Yܕ|>4w6 !GLL XhdDi e65Zdǐv5g. Qӌax5*a4[5w5f_r3ۘ{Tf¾K>TB0CTE/aqs*kSQ Z\]OQrGx"^\]w:̽i?Gcuę8q8u j=QkJAf[*fmfcI fCz.(SVϧbh]C*']+q0`&:B̥ +eye`JC2. xoCI 9CWQf"طDㅿ@=0v/YUURH ȝZ!OH-*2LP3F8~Se!K[ֳT)fn"ڣBR{B*eͅejeC3,l&[ CRݲAp|y' tW   mLG<7tftAwcyeJR`A&y5/ bƯ l4Pѱ0*c;J;YZF0wƦid}l{Z:U7*2:'LeEdk*X{,Ja <gGIs~'J ~F!j'GŚG2 ̇1ܾl3(oی/JaJ8QD>EWHAH#I˸{K%AP^`u^{}zA&̟f-6.:jdZ YHJorΘЃc}9X֛d8o,,o n>J^^7 (=tҾٖd/fg 3a!Fu-P9r#"$$q`rrs<1jqh đΡg}0B&T3 !Q09}Tbl; IGGn5=aGOȈ_>ՍP)f<&ZE Z fE&ĥRUv-}vriX(YN "҄b-d[. yJ%GvZs V!u9ӢQ{.YU`Z5d%@JzA{,=pW'gOjIƪlš^E74? 0~ŗ\G(k$^혣%nh:i鏘t?*h  cfb +(4 `FN(•zn-7s ع<1#a-t{B"O> rT.c&(eG(O3MCZMC0H#Fc~igtALUo`\b9;<񵞙.W2]"n ʻؒ ?˸lF :94ীpf#R*@1ϒ]7l$g,NBƲ<6E; %F +4o~5|g8[~Y|/Igvk\=="0,%W[PC9׽(]uM'1ȖD/WB#G`&@vGR#eJ[OL]h&!4ںjm]7ќ4M&{OPWNpf>#FfwȇB'ZeK= +i <%+}ElTQiTԏ~+1vF>NE!.-ja\1BȜS(GU|6EͿ`1VfsUbצabzՅ:c2"ESt}< +rS9Cafʑ""6srIL^ZnHцڤ>zӉ$t9])VD~!%_>g`'0l vv>fBWtk/I.p03K&R߱+<]Ж\`E7A_2AZKv󱲜ۯIbfQ͠wPn)BMo#9CHi0@DchEU [,Jܴ-V*i6v{-zeGؼ41f/e39Ǜרh*.0·XcLl8CJ*uTvGt#!ES ˑJ#AɤMEd8fLZACuWdL]YqD.#;>t5!$]jZwlp̆6 MTR̃ +jq#V[fEAѮ:%,_ q3VˎMOcz3rzT 14ʤ oJI sdѺadG'h LG+9C<rD쥴UAG0UzQx;d!j4R\DMRʞ|6;O)>kQCu#%\sů#c=O#$ 8\/x8SDoc˨Zi׽BLҌ9ܹF5ׁc 1Tz6bEI_X0= MAо.F&/+'"PG;^s@L⽲ۺI@*2| Q%b[]kA*Į7lYkΫG3Zp{9! S k$uDz7DZKgJQb1R6&BVlMB#˜Zc-|h@ha̴fj*tq] (jbbUs K$nZ=]VgA.RnY?DJ2T-{#IY( xz?w.:^WsJbU.!ϴeh#Y7}-Sc S~& [6IF$C2r#xm\2zv}ի݆$wGtF荆'~I"=4k">x=f +ܦIEg0۱}C^  [H,4q (@_15 {Ciϳrh`7!`(KS| (ٝ`*|N_{nX @ɜ<\&jrёBm<8co"i*}hhmkh5#WcZ%I#MG_έ/u'b +ZphbY_. +*-it4Y +P\"c8,%A 5*3<-NބMʼn +vغ֧YVQTFt%54b هL9;u(b<k\~( Rw) ؞U_oIk ѓ^u},~Q " "vm6b_ ?_>,wn61oXUP`cbǵ?¶Nvԣ&AcHHiSSyU4P781 ybel ? hcvw"^5{oCyA/.TY +7?ǩ`x61Wsm%D +{@$gUPVoːujDDyo޼r +<Ȱ +ΞtWeFgWFŕXpM +͈Du[P̧7&XX?!ȇmrA  s$l=NFƃXq?Zd%y{QPai{73xt c +/W n_<= +6A@V٢ʜ#AVYv5&>< iHg~ؤ/[ Mw~C#- +G%W.L(^ŽJ|5-rj:-F5˧78 'T +(+-gCı4*@NOK[Vs*W/蔩ڢ=kI r,loe6]T&ޡQ &򙋸ĵ- m'Z!&x)CU GgB¯-k!C<:8A1 o.2JZoFrQfZE;oNl-?h|OQ]VlٱFmni7#ҖI2|#l2 ԧZoxV$&"ӿ1Eā=m1q/Ǫؤ? wd|ĺ_qoK9^(_=.{&WqR$|=@?켎j `&<2fA]R/ m5QB+K0}IHzQ&|K JDtX|nD}\wC\YoJǫ'dZ@k,!?v6y,zv&*xS!amMHeE* e;sƑy~5]WT'jc~s_Jg{ q#8iK!sw EUšhʉpx'U1q%]JD5;,3[ǐ,H"&kWn jԔ^ҟt [;:s3:9'[kr%f)MCBw8d|A9V<0[qhYB@H$6LacV()81Kr}tX'z34ݚBŧl]dtz7*OhFq$0Bo5ݿ I4ʞfX$~mH>>AoS8_qQk /Y'ws>|VxY?5o!;]C|rgī~eL6uI% Vݠv^J)pS(da1XZ?ݪtWPtxXG\q]|D@d[R#HQ= v 1΂|YnүpIk8 -&v~F;[U.BqV*Lr5dWwDF 05UBHAL& m"n@6GsAJBGKxcA_(u5Lm+ů,MUWR\a-|p81Dj~r:4Qhݫt&f[Obk[74n#6qkf-G6m›bɍmSULDrq9wh"&c&pN! &ᥱ )m: Bp^c=yg&Y j`>2j:Lv&aXíS!4c*2fy! $؛Io2#zй-L>j@&zzxM݄:;oS}T .$f,ݼ^SGg5eZ Q&LnEX#HGydGoN@rO~O蒹b z{d z'R2j_EV%23:hq2Zll#՗`X6 DcȞ隧$SbA}Q՛^xnDUm+{ЂH&ҥ+}I 3p">iNu ؝`1Oz!BPKpk!~tf*汼%K28_M[穄a^H97s1K*mO+H$2H~6rP1f"F?|Y(:G3(6¯3 (Mb%0-n҄_ x0̷zךm?%*,XՃzy *O6^jM>Ƈ0htjiLb =؀` I̧e6zcL&+h9dn` H=D0-N`H˟/#J1â"sqw: l`VO 񧑠9?W 緅H +>`ǘStLXQߐѫB1|xW&QN$W}O ڈP%,3XlsXS+`JH\iV",qeDWX7'ZIVtRX,4cAMYۭ FHQe%! URZB(ӎ%$ZsH[B& p@wZ@ÂI܄6̑9#ZtF暗]hYDąLkhu[T" ZI# 4[L+X8>ǐ*;/wUS{YC넑":˟n1P+;w= @Z oʻQOTy׀9t4<(Cۂ#E$\aLw8!QܬT7`Q T +u~)| ޲[ QGm2~"Hcw=)ko93 +Ldyq/K2#|㖁s +ͣ'pB{ك+ldHSX;eJኲ;x6g#h+X {™A!/>7tlp /J,$/4SdJʡh[Di&85xY:2$- (OOPĆHi%;H,85h80 r<><uEyNu 5gMF=A@Moir ,zV|*P>8-/jPh=z4VU1t,AQSht>0x6 D"RR6!M8Oֱ(+^~&{v=_E/pax +bB ~w3p81Ⱦ]&+vnL,'>[h%\*z3о?g8,#>l& z00fHZI,Qa3bLVG=\ ~SD]yo77.m +E)sj! +%kSaZӿ́'kǞV`9YGu CvM6~m'QFs~`3KS.Rb=ᨫj^tV:܊@hAA + CgJmRl!a+a|feke2]"BQ1-+]/M aiL)QBv1]E{m5Vj;Mt Kܔ,sjRGDM|ŏ3 [r/=4/\rz*Ixp=Kxv0o״ Nh4$Z ӆS[b'Nmܿ]5msrVOBF,iJ ݇m X#ز҄]"Tꣷdz4&+/xvJ^|~R7ucLupK=@;p1gf\A'H.bn^x֛ +LN,ļe )Ml_Ń PD@\7:sԘ`ADM(K^ #TpÎBC ܛb +3%ߵ|"i&02&DUYʯ-2+|}k^Y݂L uƄeoLGb:ISb]7%,悠m̕Z r=rݐݒ ۜj+S`ac 02,$AT9"!m@qo`zňK5g[&sŜ,]L3x:2? D*R;u/(ku<-r,HB!<.3$nhnAܮ 珻Ĩ{IB$maA˃ ߬'*y}lGc ١C/K4Y0'DcŻ X(50L4>PEߜ~$AS$\eغ0|03/}։(/N+!(0S3Ҁ{XD;ǬD8Suz~!GQ kO,d3H_J8LRU&&cCS2\ +QٿL8Ge0h6B% } W'*c2vT1"y`VuNе UᦋN-[w#wRy;n ^0Ab d*5V0"h|c/U MR+)|?[c`ӈ=9KjtfBY9O1͂s()rN3b̠P.> cb! +-aC j;uu<4a6^oe)d@$[hk!EXhq,te35GGWanե1آ;G]ՈrlF+oA)/k5sPE 7KxPh.J{qV&W`Gz n`eaL 0!7R=e:49Jww*1tbhP?ջ/mZ~yUt y-*nODd>N`"2#_9Œn=vϫ&z@d0Ѝ*on1+22ҝ6ajny=>p,r?Zo=7=r3}8T)V/ +l*V}{#:ϩaai`\5?eP]bV1Ƨ MQ@* 1/zC`)#rd>!ng&A;4 o!v 8)<|^r8J"fA7}K 죐ISI0ӥׯ§՘}/D?:~J +GRw5%Lx+:sn :rNOzS8?)@b!||s=U#vqܡ@{:40BPY e Ҷ@_%q%>WeaWDAR>Ü{ THjkIh?t5 O>Q4/bwVCIko.6+sAW3!uSf17v)FQARAq3 @ =͔96$$"GL ¹@/¨7?CNcU{H[! \oTΛ3SVs1ψ>R}ޤᐋ-_箣 REtmxxlʼezq00N$V125œ 0 =s{ *pP23Vϙx,Gt,c$Z4JYH_daF6 ¶oʁ==vV7Z: M6.4ךBn[c 4R8=d* +|c` +^Xg-h D s4Ad΂H &H.B}aDeF>$W[W[(61'ćgb~ֻy'-gfvhÀP`9z+q9ٲ2 |rhh#RcFE6M79-[-)+HՠmbmDxԡ-k%84 ^m3/{ȩ3oB F.y !Y)}4V,T{rsK4[_k7BBZ_/KÅ +AsMRL ^wȪ$Fʜ)%-j5iDB\6>ϫ74++86Y95!+: awM'@1 :T\%M*Sg5f)JQ 0.fRv^ݕt;6W8#>_(@0bc`nlKjw P Iy![вOUj}`PZ5Q#\9 ѭk61y_T#9C v4f,GL/EB=෉lTұH).GF8]s#L¡_ )j9SVv52( +'#N]HX=IcJ˹ QČۍ;+m oT.w + :..^:3&/ 1-mPNJ>7}~c4wJh$a5ǘҽȘ^o]]7. 81"FbԝSY O4qMF>Rc.7dat{縪1ql&O,)=1*6德"~ &$,Rv@W `SO#OQl>z -!*<[3+[C1jɣ3In2DQlU||̅6M  I^;zTHڸWʅO#na%ODo|;I{x~f55hL}Z;F{'掾ggbsd/n0z<aM6q4`?NϚ2__8Py/ז3ђt`dheQdOb'0`j3dk2Jc|&rLe߶NW?U%9~j0.FD׆PY|}1롰^`4Oq&xa6UwNO"sp˸u$udAtzC+AfDLErYO% ?t&(SAgꎎYF""`|*wwW6 !_TFrJ9>dĩgP^u^9֣'SHU +R\Ӵ`LԉVKJWJQU?!>cK5h6m yS:8̽8ko.CJy[o$`*QڼW]%הuPx}Sֹj\_S,qz^@1W jYʌD1LLcp;A1# +NMRt? ̇7bͭJv0MbW~ќ00aa$bDD wV>@ܯx&[ӟ GF*MUV Ga1FwΚI4M_\J5>Y7H(}F |*{TD gfE,~GMdH~E=5RE` T1:@oH(BvSA]xE=yDqgnpO%IרEY~ܳ-)ڇOrEX#e8&&jOAQ 5T l]D"Μ@/a>.$bz;m!D勞:~AwI3,֘>Ckl֕;.C[}`A :ΛMz_XYUMX1ZNCc[C1CZɳ1fYo ?TKc7zXw+qPpmpמ72zh* Sʢup؜@vו(@9 BJ? +?M\6X1b-8M;nP<ČnG =3Mz\9/Θ\sy4g^f9{8M+dw/}1I iq|iAs*S#Q!Fd/hj1^7FLXfECJ-Y~<_L]܌*W/ߵXa 2kV=Ւi> 'YΨ_*?q.At vW$paZ=Eih>"JN]!is(φZ570RHs'Vyl">Pj$`Jőr=7D,[mD;J9(H5 Dų]-C8ſJWlc+KSKA@ +hoYAazL] l +{J}lR`lb)^zI:yȫe^ȳJĢz9xvd[funJyбaH@6nRi o  +U#:d֘o$c5TLAh_rE?RɪOYM߁*u.E"/cHb1`8L#2g*Ɖ\DM K# ;;b'F%ZdPS#FHAs9j0c+E Vڶ`6Dm/YAJ/LdkN̠KE{vƎJwYC*.ntCsp&\cЊo7$,1Jz_CA}hKW &ܸuAYf<P[vfJ%XAS"&ZR^ 7 9%Cuº*gӎ,^Y@4uILO\B/SZxJţ&*Xhv\H˝`%3dT36 =*p2|諛KoU79"aHS7uX@cv1}ʍ ?3* +]냕%1icS2d'V5,7mh`xlV?gT4[S!ٹ#dsN0l !tT#erS<3j ִ/@m^c$|SpݦE>g]^Um:߹}:nGq# j%͉\jհ\'%P 7VƩx_JY(HOES$eW=HE""/LU/[*X]+^ ۂꊷll[N]CzAzGxW7fJ" dtn%$B^agtP2BsXHO=!,OIѼ!m[F@nѠ*+g?rLz<%c +0u^L`{ Dܷ8G$RPO. h=$$B4aM̻3s7<GT+9N/\p2QRalXB@xTs1;ikjvT?T mh<" +D^g8_olDWJP0ACHelxS1?4ض(%hD]2'(C/l?1'Dai#hL-d-KZ**j ;+͓2pܴnޞ d[)  K匿W5bPI qa`_}Zkhux7κ5!`'nt[LU?vTC;L;"5LfVx8 [H=?ŪJWǞYFR$X8*͆m掀VXV3jqЂ28׆oS">{7C2[l?h ŵ=>N=ʠF*X QV:)~:sqZgBEalq E,%[ʰ?]l`hL$I] Y'V=3x(R?a6H,+LE`cJ K=/t (0@QQTGr-Yj&1x]N]xıuxNG^eݚ8Q$B(އؙ[ҟuyܵ +ME&:Y/TC19+iA暪&g-ZZ.3 hz-IIg?*"TҋCv|/%~Rgŭ[te{wWW?r䏖N' !L?,;4K\:(yt^Uzfeu Ie.+M( :z }5Ν34RƜ趷` +s3D\+V^CM7Fci0Pd#n$l@s=1Ɯ-V3= #96ޖ|h40G+ G4_tHPh%qeۨ> }Y$@zKgWܛyI,2¶ JepݡJՠpX^p cdeIع9)9 x,FvlEѝDqTFY%p5XE'z.76Pq<t($G7-ɘ4OJ0Eg1{<ǡgp\|8YS0k˘_P1դC"1&4rk-$5v*"m{.5_WzwHjD_uJHTJK %ia2$Ӎ&ĉ܉DD-`Vq)]ʰdw.E0$kmƸ$+*㙎r78dG4%ʭ3nwp N΄ Lso DŽ$'I 6Bp&~1#3MC\lr/M1"`E`3sӳ=[i A:0Xiby-+Hwr+(@@=oY,#9q33ZD +"MǠz߉Mᾳʼndagq Lq48Cϼ}P?ϥ[%`.gװ'i_TRNXA'U8=;gؒK$ϔn !Ey ;@"N [#_qH*qsWvԶ baȽ`9V ;47 .0UrB Ԑ0c@l&ۻDאlo#0VXf2:9Nlϐ;,1gXYRn$]B8dcX5''|D)fL)SOd&>bɝa8=*Kdh֨T;D  P$( ,$ { +Jfo.T]ߎNL顕-i&l۔L>/K|Ha'`adWp*Z0Ӡ9G)`0Q2a @T *A>D8L!xƑ2VF\Rx޷hI{Z,^@%kl*4mh#{-S"h.Ҩ*Hy&_03/0"af-DÎa?bñN\u-j0D7@PJ~7~Qp s q +iQv^6>)L˧q_XSޠ`MEآ;B +g޴y@ S QNsqEZ-5]-m)F[`S8Dv +薷,Q-eU{&;!,- ?Lu +VꤤA z <.C3rl FB7.3NHP|O0vT#yrql=oFi2tsg-;:l&$\ p9=0'NY՟{Ȼ}'(|z/N:PCÚxFp߇DQA>onnJlዦfJiS1XEW"PVbGIlv+,Z!珗5%R=w2)׸Q"fzKTC.2׀Fy*@`MnR' e,VQ't#(lQ6>3)~틕Ңo46&ݺhh##¥z>stream +&7ΓEs(Imdg $W(LiHE3Ao;>ġYuMy'k63N++[g`ir5; 0&KXYf=[29֜Q`ay'2z'($JU'^r-e@+[{;,Q=5p^MI#xH]1(1tĭ7\G=a2keȗ57Z Vl0ē5K"J0N+HD8B!mņ\g-L6t,  D8hzooIf Q Q13ql0   o@H9bu[sO'7꾴 ζ\?O' (5xr\@]CL1ƆڕkLOeybxo|Bso`+ʰ9W< SSB"f[\ܗ)ښ{lR[/RHiӾԬ:)u:=a +;x{6 ׇZǤU&#RuuѣIȟ/* v Uu~?yzD[ /#С}`Z&sUe˹fFΒGIsr`YCpp=2 n)ef$9;Cvϭ2}R$̭&^GӈY+U 8 f[eiT3c>ɯb&(0JO#{D:R<WKA:f (r}h~AUR0O_FAC,'rX !GɻkIhѳsTtZ.Bi[E;`}x&6\pnFq> D,⥓WyѰMKފHgVD{IDslk~E/p~A? J~W@ 71~n}X.x.u56ۘNԝ(zYD7Zf.4_N7Eآ(DXY ;(-C"7f- ++ {`?q-T󑪹=}uj0qf^Y ;[.kN cZ"N鮉Iet~dg`w"dU7(.֒GhPw{Z@"Q`]Ms%.D c`|cJIY){ Z.KR/) 6ad8Z6iqjD{shcGZC{'a 3}ȑB +)ao[*{sk_Ӑ=W Kby:`9Gb)_>_d X4@f*AIN%H{KQz]@au&oo mGgGegq޹Ɨc!CrYe}zP,QbPB(hF2e;LcA`{-Z7 eErN42aRtI29FȦgaD}ʹu +1lYx4:aɥ[5Kn>Y , +# +x_ZA^_$"ڈAO]ݐ@}WlƓ445{RK[3mJ(~?VE^ yLoUG sybo,6j4o@|O]}[J^_Dr`EDU@yhY>b,.wk xoh.O-Z@fLEmbo 6(y/{*Q y 죤uGXdMc2Hʚ=bTf< 86¨>qh% V4b/D0 |gy]Y>,Hʨ$zGOMʊe_:<JƗ&s$%3D6 e,}%] b ^'-! VǺ1D(2bݾPXj>TDdFSFۤ52TZԶ%h& uj?JX&.}! G#0tG0g8^ VK.Љaz Ďi2p+[}`:+Û5ӡDS"T +=m^P#hyp@P7S .#r5"!>F].^[6];)@Ǽ ck6KtG|4Ai/ ڮa)*y50{k{ E*?nn9G1 ŃL݃*_OCnf烁6-<&=8S13HgFCruk2vj?? ^d٪^ÝٌBF!ۉ~8%9tSLIsI(3[=WX:Z5M2`cs䈢LJә(9ٰgZk눼PpIu4˻4CEu-4qH8VOY;j̊7LY[OGEeH+:pW傔[ +xVs|)/#dKAv-GwbJmlT\tP>j's:bZP9jH'\Ne"yf[% LG>Owc3d#;KGB`dg.DD4TVx%^&(VB*Zjk@{~©+NBۧql1?="6SS켘eaga"vsT="1j-a-XCU?ʱO߰EuW3,l67ޭ%Oct>$;Hyax5þcF9_IɴJtFRԿq(a @Q'_:uWy'ݵA`m$%Tt?,C9Hф%@@kH}LEڄ8tKq}PlAtДq Q%"Y0, ^ rpx4 K/!g 0j/ NᲣ}l0Mqjmd4-f sp`GG p@+$[G ߫CO ZBn/PҼ-N ܡߩoM~ȧ-]ȭf`"UDhYYC[J(A=Xj+ +%@ӅY9 4AP@CM\\8])d`:$ (TUOc67y_ ̿14cnΞ)QIga54ED:# .Ųwl( +Rb~3[++@k.j~^ +(=D kvD<$B#@S'¨87apn@Fuxm𷦐눢dc + dFXi͓V+ٛt.U bN-N"0IѾ rbĆb_1#D<x.K'EU7y\\ș տ˪~Ldg^y%@\jk_Z1 +.R*13paaE/L*):AɀrЉɰfSBwQIUCW"!5KEΦ+ -.#H٬mb9{i{ ԁa[ 熁ʒtD'*?4IƱ҅ڨ#)`S JrL㙮R3&vGqFקZhvhݺVL|uސzi9e{iᎵo9y,JV[laLڤtv*Pn\<| .iY؋z`fmaCA"P9 F3S4$ t/~gj2D$!N[[Rb&noJAU/ɕMd'⸏%"::ʖH?xo@K48 P#!@_߼abn(U HQyX 7QPto}J-|![*>*?9YpH6X&qNUu9v24W/f)sAʦTD[NMe} uv;+g=yч}_`/gP?-Lu=,-%6SkR /+K@R1O KZ[i?q8#ẁ"HP&zf`>5wxO/nfL@Dn)<|0hѣ?Y"*) b]>IbB?ETtވgX&ONt%4OpӍ'˨6Ja@cڤy7 +h0Iy;)5o5yd%-5[!tU5tX-"qѸKjz(xYi|҉h3b\b:%VÇYj]DyEBn%?G~GJWD_F)xj eD'grq_V9ui$$8!+F[!+Rԟzڛ#Xݦc1:@A؇,cusyq" gݚ]=b%u|o&B2{%Fpm`V!љliL˺y7 -"[Ho?&+zb@%3sTs>e `j}.A5[.fIQe ̸tJBp_yKLUU$"΂3Ԙm$0< +(f[D;ʧXU~!rh:;) .QW%I|_+I* +*$+"HY bAQŨvc@92Ft9Hv$Z.Q@\3)g95y2 C-nвTn'VD5.Lh9O)ѕ%r jH +xFJFFQR,{C6[>¥o[3GhN%M p ^1ZYZ$N7]t*:t"b?=-DUS4"aikuKd_=ኴXrR7 k>Ug:bbƸ'ʌl L󵐈ceV4tfD6J=)qgrD2hdyIWZ=!qlTҳt +׳liTumtY~4(: ussg+,mTrjώ,e["ܽh$S|VsP_~h&. +l#&rB4H1HuAH"|,8s.{zPi{Br|^KN{i0[DF FG3܁ļUY<@oĖ6u,9&:L]cjL$Q9zҬ +b|/#p=BaǬx?ͥxM?'Atcң~*[qLUdB8 +)}Zض"T:FJy[xvϵWsӍu۫ Ȕ k+βiĩm%,kTO ynQ}-,=/ou^ ֯^\8/* 9CtX<²h[imD)H߼0ޅ/q†J c)3܍ќ(2)=z>B (|P{b3Zh:|r*3Us3_2ʼnT BRca_@_%>!q64ۊ{kkPxiC BHK͵/B Y7B.\>%>lY_+>:[G X#:e l]V׊>~ \cU c! Y߽E?fWא;Y#X 6 @J"4384đǖ$|14π+ql.̤UIXsBazj Q\lNdBXI U՛P]:?4U +@ +ԧ&4 Dw{f{<za sՉ Y]tٝQPE E`ZZ + 5C>nxaLș֛e }VFjSz\ЎvWJl\kD%ǞA3_V,HUf3SKfbqGtfa)7c FRrbmSTY8|ԒFa9KGq*.Tր4dF)PVVw`.(ZmCUaYD(g0Փ{N +=1ev9Hэ#B8i)ך 0{_ϥR)uvfӕóEzP3@<r6-&7mބ&=fC)#cC wpeU_'%F3Cu`"v*WVx;bΊjw4KжUm w'G@#IzDa7Z8c SӡR. UW"rM L^JInʝ9IĎ-ׇP02qo#3;4ѓ:G-V>zVԬň6U#ڇK!h ʻDrqP20 h}U,#7Ud>rRmu4k9?=2V:nˀF٤~3͈i2 +xe$8bo]k*?ٮp'.JoHw;L9^@jJ X"<*jJ ++sh>jL z2N.d +X +2چ}'_0*QrqxҶEܗ"X˱+a8 OcvHI[6󯆗~t^/5R㪉!,i 1>'`Nkit/6;(8 +G.KBZhR,M;2ZwqS_;*dYց'5(ߜ;r"+!f&ǭ U;<35yT stMHT < Wx'UxI?@^dA h&?$-ݴ4mꢶ@fb5nZ`5`lަUq]JHy6eL)M ˄U>4K g*4ڝsLUhP0HqI4h\6h큙5Pg0H#7>Ȅoި %h^}[h"$/:(Jը*%ڢ!'D=TRS`4bm3VPh(kczy*D~ -}Dɕ//G!O_P|1)#k0bG58ci:. 2f6BEXVc +VԲgvtGbw"$æQ^r> 'q!jcuBa+H3 F>"QO[,HR(>8_Ίřb,AL4mEƜ{T:B<qB,@gSkcꪛ84,bbM5̞3)1`FYmiDL$/n eC@.q6tB69""^Q,j +U +^}vs!+PqNmQeʁa|baX]IPHX$m٨h@t g=u^.RJǁo2W4YNx&{R 6PG\HF J1> p3 P$ڄM-m!)UMTR|9BXA6)a7V_Pt|aMOAͯdB Imz5EYJZ5a&ci5{bSʡzmb \Bc1N`lB6&qcĒbg".&*Ц۳"k(fcLs@;eQrApʀ‡ '(U$U7 ,?`IZbqi?9'%tЬf\˱1O\bRfjclFDC hM2 +:epDif2V/)4fZ+\jQ[M-h=-¯} Z0Swlk`rvYv6RGpj>#z{Qr6Wn@J6Wc4i'#0v^U{ 3c*L1FxIA}pqf ~hˠq~#n!-.!(i,9Eo$StZ655 hRBOq ~h%㣐x", ӹIYXbzQu;+?[3XM#,q]݅g' s7zrP&R0ڔ;N[-83ItX1eua8fL%|l~`xg/#Q)LxMa< $ȿ4…dbI7lb씄hݕSXþQ `-|%Iq.Ԫ#"TY<0G/(CvDﳰȈyXG,_vЋ]'L~j6ç Md 7Beny5UK{AG飚YTGFEnX+0}z(iE=mwHɓ6pii3p9V#O!O)%zM5E~nx!%gfdt(ָGc7I/2/&lD~.^&6yBDaZ^"DhټSlz]יz0A{lh,(YhJ:K@*xDȦctf\p|~}9NۘͶQ՞'ô(ηE#k63;trfb4*qKtܭrqղ "y5 g%ALڌ޲ w8/Oe"N~XmY~yAS}bqxVl'*ԽAX-qg5r,^Zu#pf&cor82Һ \y.,y,y)h&| 3ڎK"^^o%׏}NРmdЙZ8#J> E 2 ԱX\#,9AYQ#hБM]aF1 O4#.K=EgBHUR1uvku,\ʻGɠ^>ɒdwZ9dco}MvX>XLQڷ1 [QI!d]oFq)e%|kU[_応*(zUĤU `ZR 9hӺB:ϠL3ք& K?{غfP5dJ^X;ヰ?P[&vP|19\S$E֤>{ʵ^NqN497dlP 'z?Gv,jX4 KE~~$# "V>`(7 Cx+{~veu ڧGk,+ոNE4՝6< k1 S:\;! Cm;=kz Q%2kr2b|$ ʠ΢W!tan\+ \ʏ04MZ:bEsTNڄbo!>1*)EYLa0J\ʤ)x6P!^0b4)Oa5t )[dHe^4NĶ*52 PLFbC uu*w("1fKD->L!$R)CA#G ƿG/˽=|jW0/  +j2S%@Lf[DUP!2 IP98lN, +YQG@d/#!X_mBA]Sl{傺n/ AV(,k0d)h"Q53%,tđJ*$A%gI6Z'Iȃi=e\pwTftFw0R@nl:n̬Plf.1'W y)s2{TD/eUxYb] ]u&B;cR:06 AږC<3_4BtdP?k2}=_V4qt@ԭihi"z| Km6F*VٙQ/a.Co$[+ޒT73T\i8(LeXsVTſ&)/hW^ɷL+y(xd5Q4~=&,/ HL2hvwmqBLr28ՎYW} +N(IЧ-&+=lhˍ:8WϴΡy/]7&i C;~k$5L*/q%HnKz|TL$?0svPb-ylW_*adcױThI + +#r1\tL!Wɜ&:Jujeۿ&'Y<$͜MiFGơpx&+,_yp/!Ift9 +]-c"3J/`EH,@kAr * z!b}ə૥V3G.n'7LkaNr˭3$dXnBKu=r~T?,b7φG_Cz Ne,r<;@c乭u&P=Z&>QiƄ9M1{Hy 8KVvUH3rі! <)4ȁ+u%jp4ѳL1Opx;>A"dmnP#*ܾ8#>Z͕l:>i:S"`Ji?Ǝ~PByctLk +ҍ(0f[@ӛk@9 Ok[;9e*5Pdގ' lNE2".w`EV`Ym.8M|{&7:Ȟw,(#+B)[`c 3;Qh eD#i/|FnaW' ձ9~~SD&2I?Lha)* +hC-si~2oԈYHǖ +E$mGTM)+/&N +eͳN!- RMHPvsAPt}# +-%žὴ%"Y2<-;z8'%U FӠ=]$7IPa6#BFVũ{&*AekΝ)[qT ai(8'_ow%:R7d)ƈ/p8MGO!aKTR$4ոewj$v8kl;#ԺMi5QXdʕ0ng×&łMrēE==#ED@%쑸e?ч'\fܛWDdF58FcŷG<.f60$39=``*۫O; &Mwɝ/ҀKn1biLhյLp~B8-t`"$%h'8!z +!*׉fR:^jd +i<Ecf.ZnR u͇P~~Ih +:J``ISKșkӞjc7ܾ>bt9vL%6Sk̟'^O? 5SR?jhOnlMXl&d QP[dƤ +b92?K4{HBc"p53䧀T*R"dn-pƟz";DeQ C^z=*rz1Ǟp;TeT K~!c=Y|"]R)a;pyJጛ:| ǩ9z?,!GHzKSI]ɇu z\,vs/g&Ѹ7,oKMpEះCU%3GeCg?`4w='::%N 0t$CXmC~';1b^ל/Vg_w8#~9uBע[X%B":%!ˊ;_ 血 >ҕ~ #193Պ8tIVp8ėK~8Y? W %H^b߰7ְ0*+?"BcrDsN"H$JfMr& + BbD<2H#Y "3.yoC^' 00H!_d3$JYn'hQ p"aXo>L^FZz ;BIy4Whз{ KT=$DDܶ?}{IG&?ڗ9)+*y +{S f~d m "?((ֽJ -a+!&=YC1 /< Z y@Ʀ3X(6v[;"0cqN?\8:I`3dv3 ~ 9ОILdl,B+UD>]xt45b`֖lgdMTPڄ!6`?/@;4ReIda]5!}mșM]j"["CY0lD?KN5n oOU.H3l\D CZUФtzxpdNR}2~4XG\jSe`hhvFcxҺ +~v# +Hu$t)-|GqcO>ݪ%Ү!~Hgų -FokY6E4j +Gzw=+OvrO^ (z`e(Nj&l;H*6gHY3 +~jW΂Wq'hFx9z+}NE㧯>` DY7{1{[OꩩҐ1VnEH#GF֤gWJ,mDB(Mskf8;R_ԁxJ)G&$^D"orXb =LP\1 $#B٦gdm}U+Ba`g%N` LLˡ}C npp. ~W-^xMLb_[ ~Þ<;npZ&n>4sb֥fjce>8z Pzx>ŘW !.^՛KaƊEJ⦻ga`ξAY rL6/ +)G oӋw !`GF*U4*?-̏ z+ +?;`K/A`1mO8Gv|aG p(d4\`<^ф݂%;&DsqTa \q}1=Y7DDJ*׷ g c~g<!WOEH^%rWW`[JE"e>atbMd?]ʨE~V*_Te:`3-Gw4k qG$LeI7CԱo>}"fD7?cp(J'L䌘CG![w-ӏȂ +QAS3~2QbV1BW}jA-:ur0y2͝M Ae /SVʾ[-g,+Ou#r6g .Hr-!ܷUEKt"p2$=1]zXܡZ?ۮO2eHVZ+g;~9>rM/Z$i-RKw{=z A} F„ܱ>(ي[ iv -ԞI*Eʞ+{7j5`hI+JvV +CJ#lӱ%>Vگ֗zjVSzT,,K!}7-O|f +1u&_$s{%(d + +EL[q|j4W ) ]3IM1i.[*|sPN';%}I74V͊_BMb"}, `2$IFȍ1Q_l)(6? ) hŠ 0bdH +ɏ/kE3Uԯ/D2,e~n+9 v5Kӣ1T+,|r:zfXj{3C;!RpD 쒭L;SژZ>+ֱB Z癈eBuFRJOLFx NR|sш!o"dY.in'tS B@J+e ls 5Gĥt~YU'ZnDI&.Y~#xw^Ph0ufI<Ơ 6jshA -DZpסbfҘ:gu? ˟.ȊbI&ܖk6*f&tzp(]g]f FF &B'g}|Va*f.9 (x4 =ZyDv9堒]ncjT]z7/8L|o֐@YXx_'kjTlLqCMi"s27%r"m~`Ys%0'R\*F@Al=/=ґe VUxVsh2)ߜN YNo*3wV0""6n#CMmf(9ջ|ƤK:f t;@TM ȡ>ka!BfEo$nx*{&Hh%(!K }4v}LѺ>PTp0X9AUT9ezm27^`@ͽJR:Flx\3$tDtwV yğ`~pE, wQE^xDE ]GfX|O=z+;+(g×d~d{C~+1h^ .GE Īlq^6ve]z 3,#Hq4[U`{]AE[Ntbo"JOԶ,نT8ȋjv +.ph+ ^}&!ŢK=yÀBԩ˗AR.&po +p@$QxJ(Jk&j2+#\X r= t<\Y l*V*Ϣ+;}}B]3* +ľ՘&?q(`PXJq=DWʫ_3P#o2*rE|>jq9ʒ2Im`*}N,Cl,}=cgVJ+Vf΋IDI ^/<^αCrY3kɔJˡ åQ`-L:`[p8/ vN8r~|Bi򣋥ƽT*N8{>';%ٳMux.zP0q6cId)r/Yp`44(hX]fTqQf,8mjA&2HS̫H23%NHfB jF \Z[ +R("*-]ňptMVc$\F`H JqR`o౸Ƃ~n@n<_ 47ɮ8ӻ Z4+w Y$!Zb!#˽,#XTCT 7 *-,030fM06L~BocĦO;1eUM&B%f#r|'uoXZZ@ST)Toz/W:{-irf>Ek4FLz sU'X[&^eM/ΈvoMbTj6ƭLX2UF K.CDNAKu3ub"M$[wO1my(#&R@ukc8w+ !3"IFS#1Ϧ2.m-!S 8~<n D`r@pY͌, +qkȸD5똨uӌگM<#L5`pM_[e7 HرҘZ'1û ˥#@gHjhJ +0M@6d9 gH0JO FIy C-Zl JД1|E;Y Bl#a/?{p&6ުy0`8($?npMR)ŝ>V04#$M##d2VG\HeZϒ&YO1jqha(C*ՙ0JgbFUBѫAUbqgP鍱|e +VNCm;<õ@8L7&A +1Bؕ"` B +.1#b ̃ Σ0y5OӬ +?fҽ\W +IQ<#Iij0 h4?dtJ@I2;h +w<'p%z Dgdk[Ma'zN_[+N%% 6;K:-tʙ.. h}}9$Ykkkd=,cg ԸiN爛1n@-OMMO^r)с0m3PWJs}҅E1Eڤy&9Y$xB q +VZ'z0>,@8Edt4DZ2avYTȒ+TܣXNHӿLrwy1*FO(PAI*C\3Eb{WecBr.txL{{oN3֪4VULa*6u4 YDل"Gӑ Y&6sq # 5 +{*Hbuj"# P!M?Gs:+h)i'ݑ2@S''1GI9GK$g2CnAb/a>ׁTs <)%CSTq5 ϷATòOY>1LViPcHS/>'/ >)k­x[?{#Vp4ϹO@)A|F X." +ELTמzŤ4솻B/g|ސy8?7=`CCTitTGt*yT":4 Xiy?u)VՇYĭdOqed%14xb\:>LFt4BIPX~p|=Im^=f~e̛ +!ՀiT`h :hx ^50H.2#-ELmP#`{F;\&a60iJ/lOrI;=ͅf=)1QkHl8ά$*q?vk~+OUĒŐIMN9692DuNnF6ubpDR"sp5T|"˅Ib#T\,ǘ8DG :ʭeiri'PRZ@t,eRRd9_,v^b1X6l[٥],7h]o,HEyQ ٵ0t)0/ +E7eebp#] +6.)[R< 1:nP.Z2TSɿ"If2 +YFYެZvSe*;^qMl4ue ? oavae8}'2g`h6ӴmdE {"Jf|e+^o +KUXHoC3۵W4EUe> >rg̦B^JT*B|Uh];O(9 rVB ;oYnbAenTprH翧4:-DL]Ojp3uT43V|13OƤ9[_k>P#v}ȯ#5ngC!3j` "9}'q: &4M%HVm߉Q 6'햤Z=A۠v ђ:̀0u΂Oбp&IjLf r2Tk;ͣ\#.Ɇg#73.4O 8 IEOfvڽwXGEPf#|XE@jpGEQo#[+Y '[ \Q0b%ܱOoV/U>9w +b/,ky<ZXn\sC:,!z((׫RgBXXjntH{b0P$^O?L=RZN'YOjF BF vFf;̮́r0ҶsC+Ez<+;jA/eV5]AOAE&6/z$+d! GxT>II .up-17>fVr1bIBaֽ22-˜rZ1r蟜Vo=]<VSY҅;@x)U^o[Z2/ݜ-IBlŲJ(.Dْ9Wb2M `0oRWjF#a`d- ^4Mf[CZoJ6LÂatBu_C4L0%ҿFM([{ u?Ѹ݊?))&(D ||Y£DԺ %(ܴE=R=_B0=Mܢ)rdrW2k,OnVb,6\%\~}ř 6O#5 +M^E #sqH;\ӗs7!렅";w%zٺRf>4"PAdm^\!`gAw&רWfXA/Fe'[U$HZXV<եify):x5,ھgSZX$Dik3DkBe꓈Ꟙ',&ڝ1 }] 0Gk")G}%#>ᝎxӴ#F$a^7Бcah>$EG695=[=ʋacˆͶkڙz7KvJ)ȴ?[ZC?z@ZS!2t bmVhBHJe%\t72wTdIك[ej'pZӡ'BvFJ82WyPIyLhW1|l$qpEsCa[eN)ϵ'vМv9 vFl@Q4x R0mbJ@*a\Cw q_o0Jmssg260ZrկcI^q';bRq`QF%c9ŸO&~:v̟>ƒ:TUr3/튏C}dC`W 5͂ SF~l~GL8i!ζ5HC1JR*a<ʚQ0,p;1z:gh1zm4M@U(ʁT!2@\9E, |㙑zLKӠќstyx OdNj P0"omrs3{Xj=bL5&uǐ,L,g$ "8xKŢЉKl j`DM` M,Wke,˓$[F mКu0ppn7ovbu6=6uz !Vq5(mQSsfvdڰBMnwޒ SK.F w%}vt 4/?# }{IIW~&-0Ēy#L-j1]XɁo2kkHuKº[41?SRE: `tU뤠]1 sK@Y)6%Q+a3X8btqg +CU!&Yt#WâY0dBGâ`lC՞ jP:pcz\чZqM(`Ծ`ZSb7OZ˨ +Z߀nm@ YN+~!pgC ؄KX# )HآTtZ~t@^F`Z@ ő`;tF4j d҇`2'C4rf'20R(p6D.`"&\ysh[Ϙtxć6 0QDAD\R0Tek,ҦLY zkTyagס~jh@Arm +ir`% RIT6PSi|klw? ]\RXsd #߮| +5 ~?έs?OiTM7X3"c#f `c'%GH+=?~md8~^`јAχL\j*EV +J!VHDzv/z\M;LG;r4ᬚx5 +ҏe$8g0K-pC"2:YR޾91 +p,eeғQBa@Nd|8a#RO<|JoS9y>Idw3ޤG˵؂ZA%!/b8d;-.Uq+e{C;S: zV!uHvg,U4C}A΃5"meOÓy,928CYrWyPDX:7 +*yɉc"^ iKY>y"mf,8KP+"ʵ׮fP^]_,0Rky,hih)Sy?6LܐẙGABe:Sn`@uC׷WiVoHD 5&@$DD3WdfueNJ⁲/:{! ,&a@5D_6ᆾwr^R6g!~rWOx|x?"@iՓ>>޿@DD1G["q4% iNq7ӾF>@!2_T$& +,{ + !eN`ؤ$"9K5i N;e7*7n\ q16p;(L,ӋjYG o~@>&$wVQ0]\A>Kf@bԴ! o]NR|*AM%q7Bqҟkg` @JIKQ0ɻLGؒ +C ̑Wi0ZElQaY@K$ BbXzk6H]AD\fЙ&c;|ۭ|IS"0ET3 +JCaX/?MNak\KBRFaﵚfFL/Ԅ)& 8%:['J<&w|>S9ڝrp\0 + 4DoϟB. [̳cπ+0E{puf'"ފv %暙HZ \uS~>1a%cPYr%͈/N@nEC'3C- `'1ڢ°57mk2c{B=in38bhm@ q1NQa̎dbY13<H@ +<(\ n\[U jL[!yT@? $ĜN }F2] +{*H\a`̨KJozm+Mn\OYp/>Hta Q #Y+UVK%Fԝ3Pzj~׍WZ "&ЃV4Ps6eD cRK +:zƬNtE6sN/Ó4vNlqUkD!FgcAy9\F;ె`>8x,3-/ENnZkj"}ti$;;ϥZKi'w*d_ $-CY)'JGGn>E>1G.M :9(hQZ_o}n_e)xywЕsGWnz (Fd -w:O {Eq]"p﹗VZ^\ߏfgu(f#|:2l-:a[J.DO T+[dHɆ s׃sjdinjXk.4 F\ 3rZuLo8"a>F +uzefW¦&mVР-ly ʺy]?? +ŕȚ^ @ȹl t(ť֢ O5v`W<"=_v^X@U'wknSn +z?dPlS9\q\ N#UT~DggK]|H icWX ~r6=9^ ӌ'KO~*47")oB! E_s&&*Ћ0w%=@ -d39G`ؠrc@$`{8nBXτZ$a'k +M[WLѶ>_&,:uIl213Ud$ۇٝ1M^$1o{\v|r{CQb̅zhm^JcHoB\R*ogYVRQ{C$RboS[ V\98 0ZR*t묛vZua-(JM')u/=>ML!pl6*-!2(tW$~DyOq&/QXo%Ƕp?mF5`Xp+w< GɈa> #cZxTK$[}PxdDm.R,o&?GFZ"! p#a|B& +Gin`]/bCL7ak&UMu3@UݪWSV=zvx=\g-E 9 e/L@ct*LN}(P.°2JM x{qEShŒ[r6b_y9p}X&cEN 7NW=`n e 4_D|' NuNPNEOBDj7\\/u{~]KaʌVEcXs* &޻ H͒E֫͘!J G İT"@QQH!^K4e,3 p(?/{ :cWYeeC~SLp+ʙDoGfL-+. 6"a2nQFC/z{(9 #>қ 1"#»~ +N:6٦Y{`eXi,?Or's +I~+ެ+VQdDXaeS)ۜx1ɑҕ4jg^;b}HjF@-OԴz[h`BAJ\#xvς!e +`9 Z-K4Yh!@?{@g M!A ά$TK7օEDkK2 @Sv(VձG uɁX '4;g_r*>̍㻝QF7t4E3BŔ$u;et+6Lk 'lf s/Ԙ`@9#X&x+dh4p.puԫ0{t7 cya`+wܰ{85TXu4XPAMf|s/KncmWE"äCUYFݿR%7?ltN;h¢ӠvYWEicJM] v.nN P/@TܑgPۚfT2l J a3QdU(,uYr?L^j` Jլ&E,} =%?jM&5FĄڄB[DD$N[Tl9rwFic^W%mit6ƚ!&z2 t%7״kNxs:\T^5%$VtbfhE֣qj.v5[B,+S߯Hqȥޠޫ9ԦLQ0A&$4 䃽Q'qrƞwzKSOB,{hu1DW6pbc}kb ZAQ#3ASSX{&)dHaR%uv<٭[JY;=e(N\ ]ӋyB~93fSQ 5x2WBdԶRܗBeFt)/HXln v:/7tP4FX)^vQ҈b"VN&4s-gV$ WآXih nK r:"H\Ĉ^"^'@A{^*F7'iD$K~>۬*xa+I㞽Wls@v?$`րf39I%1Z(7 >W.W g}\^%U7~/O|p;Ivn`|OQYw .kU<{!.-Z5onEɕQ+Pe0\qTn<K)=I5܃>R]v4upVr,)$>:<LN溳iA:>s066ݽb/]3ต{i![3D"j2Q)Iҙ^EA|A7jBxmZ6&%`@ą83RreXn^1eqaV9қ2M.P/RJ l~߸dBckI l8H1L( gDbOүKo5l4)~O$w㻌8t;|$xH.~j\E:aňPbw㻤ү.Ƹ)r6Ò:r_V<q +X,r7R0%^\avfn3ς^r90xɅ3L9u+ )D޹ĹıLlKa=‘Jn4}%IӃLPqیaI$Ʉs41Dya89Cnc8WlǡG'<Ά vHh8IwD@$EI4" + HV@82S9h'vpV5z<-P64/t *lE#Q+RG?`&DiJU|>`BIJp9jS! )VR{tll/-=pAkX3իwh7if[r p!&!d =`wG.l{q*L5/Ev 1 +") xd'Uԍyĥ4Wpzy͟H@ODvf6@ЦPZr"h8 +]sP6y\iRmvV5w-4FcN<*! 䅲NZ0aB(bB{}]@$Y8.غ# \j*q qrf%f%d~ec{<NWtMtOVa˓8tOme[-eƌ yK>žwAXMfp%1R($Nōum"I'z6Țx7HX9> #[7rwdR 8+V9 ҢA֒}l,7BTb̋g:_Gkq&p4s0Y: 81XPڴi:шT " 3JqG! hQ 7 -:-sV}h/ЧGSMV$!d(Y;"C˴3D4$/W9GIx=2W"U$R5IE)Ʌ&f?m&:<4˙W;>,A7H<{.V?[$ufezDDsW&XdG8am#CK!VE/yXg o*I7*b"2QȟbjRR #jB^̍I$31V@+6d.Y{pG,z><ij#mq =1DLF5eEOy;6r0W~qbrkAa8{i5 oVD~ =m:`FԙC?:`#|$v%!yN}`+l6yv$d?ӝՋ5''A ໢@v+3-ZzGJo6$UZn7LPK(RY!:1YeM@z*DbmaǛyL+z1(Kٖ2T#68LP$\oT=a#{`A_H/3TD>%.Ȧ8ǹ +,A1>S) Ԃy2i2v4D@)qѴ|ϼX61/)Ӏik@o(,h1}m8h f#>yFnр:^HEb5Yx|ܘ@[Dyy=%f[q<(ߟHBq A6wtڞ;ޕ8ޠ:v\4ה"w85jdʩkL.5L[& (FDUb`Aд#VŹA a"w(SNKO!䄉`U\g3}CvJh,;)יMOꏛ0FiMG&UH^tU"? *G G> 9V0 % OWkk ,`j3< SyK(_q4T/@ܵ$,U7p`LEE*i)s-uFH,lRЍ<(ox/tz-9ZEM(e+y9; .PYޯZÍ?-HG&8MGl Pt?w%"L( P-lLX+ "ѡdq4:/bFir!2DHt-1Ԕ{ܔ 3:F<8cD36}LkZ)g"MV!n cPh_O $J M8Аa['҈/ Xf8\xЛ^J#ub.VZ]rM)1Jw W4oeJ3:mAw@Ap lRn2c'&f֦0*v4 $'va>p}H?ܗZD0W*;ǻC-zp13.VUŪHB𑳋 += +KwXV7/C$'K⃸(P6F*2n 6L@M&R]:-t .H޶כ!b93[/ gq)NȄqnK3}:zxXMw5ֳfʶ59 Gx[Y[fRLV)1भ^my%$1a#n\<8!6 +~{ECԹ$Q[jIQCUv0*}2$QyJ=ӮAQ Ľ,ԣ#vt+GU@?t^RnAj:Q]O>ջY`'K8nrڒ I,ev%C,8xSԃOr×G|%${"X5Ƞ7PڑĆx㸐ֳK%I6ͪ"%b: +*Kx!ѮBɵz[T![//;C +DۇLZ#t^Z"E)!v.i( Qu(,PCJFF1E&irݹWq;JX)߀쳩%/5N;^![tD BV}+$ <[A]d!]K}s}_"Ou[-cv ݆{3C[LFD|gͤ߸!l*7rZESr2CNG]FNqȼIۃyPh)UGDUOX)j-ek +D6|D1ٴZK%~X\lT5u͖RswCcq )\:TxבZp=YSc?ClF @iЮlqs3rqD*}rkb` 澥囘) +;u4k2ۊ.ρhڢZ`8!q* S컆'zÕ\UWNłm?Ԥ0ǿrm6]Cmo!GٲC;]4]WɬY+I^D$hY>:pB*pđ;!%)."Bj\<&CJ=tUnN'Kt@t.C6/aRb4;i ,F -bh:ؐ% :04$+p1lS!BEa&C?|)T/VujՕTg=̛2Bwh.}PaaGFD$PrLŊcG2 0=4ׄ_EJWz* cW:dP) "PϜQO6 # eaA|pc\$ sn>< +#TWtr"J6lg'yLPEh7oZtS|QDZƟ@;I*s1_2g1p@B&N#*ܓQg!Bt y}JBσд,T,0D:ѹ/!+s{W&I[pAj"A2reiEeFr׸!?oL謉myM=d8[r!#sv"h`ztkt`OrK|[SYM%PtFQ@M,lZ8jˇ=O#h0^.YXZR!}kS,F+; #U e0Cj GR-e8x@7=v|nq WF"hFlL^;1bgxX[CL֠*Al.?,k" A,M-䂙q1DpDLssk9q@Qxp֘ ߱fI3ba-EA_daz[`(:pV.ti-s+pSYmUm{Dz4ܢ+Dh/Fj7ڀf0Rb<4Cp *:oH05Li(>JWSɓ踏dƕaP-SG1޹H2yReaL5q>'#Tvd'\.m [rT)pEv*A TА^U+a?נ.|Irrf4n zF 1<MZ|O.5*eR}Y6 9Dʙrp"f4`o +Ćh_j>F[Jk=a.ӏEqˎ*' +QJ?68NF3͇1m@:^\cq8o4Di]/H筕ߗEE< |)4=[۱D^d@/ߏmPI90KB:ݺgpeĽqaZ:S +MMo6'甛#qB:4r? 6%0 $+z2{q6Qs*SAdvD3 ]pFBu(RQ3 5y݊mP[C,9/ET@=="PsH7 H5!<ı)OL JP432;Quǚ_I,,EPGCFlR5 M Q+.IX-+fx:á1Rg}he2=DrUqiEeL_N02oTxZTCiKPʇ'yrB= Td oɼCk4ڼ-=D%#YaD g)wƘ-/􂎘da^j?ztU֧ `#^ {=BVAi+'x,{uv(C)]l1 Q6)]OAT  /yKa /4 2ը7bē>FSHcYVStzRs(ӽ<<{zr vQ`͠ID L[" + `w-ri>GPqi9gSivi:9$`nJ,?qSlo.uAا&(w HQ t,(DedUH.P̛jP;r8.s`E#T + a|ÓO#HŻAqB61Q t?j~ 1XWSⴆn0s"1/Wq}9s&ED7<`#uKqZ&kg>soh8?C +yHNx콑:'NL^f2+ao~ض;v҈ H{z$qtAhJ""Vwk,J:T09X((ǒI0F T22/jZ f7: N"m._9; ׯ{u v,ng&L>Y0[@N{'7zIɬM. Iduvxl Eex<kxm*ơ3.4k}AY#2{f~7+A`ZN>Y0` /P(}3 ԕjyk)dG0|wo?Pb@yY"hl2w̎+0~OJH[x*P=1&"hLyl2mipɮ8 w lܵf̺sX+=S܏`4ayR^s=)ZI&kzY^)p6MGG>|t<6@"{3fr@b%FqW:߃~[a^`~&Q_*%M`ݻ4= KġIb ҝM2:ch WSm> ++jv2W'9eD?T~k%p>]j `fg\ZEgK!\be8V8WC+I]5='BL_賅(-r+r~ "5?Nf=2ӂp|ʍ4x2iA0_;Fٗ*DKG\  V* A=#5WL@푲zT34+)1 ˆ  +Z3Wۓ="qXc~ K̋ JlFuKM鉬m:\9/-+pUL!HRc?ʸ!@!M&Oykm #X9Ё7hpZeZ%0Oڙ]ϔkIGk_*6q6fٛK^7ChuWɨ(Z)@u 2WAQjHfϾZY ,1zچ\dT7GTpy<-~. ׌.vl8Y#_ȖM̈˅ώ-MCF$m:=ـ{6;:Ê2kyqӐtXѵkFP.fѪh0D:i乃) C ?!"/T%A ,ܟ7A{v.7E8E'MRC'2|B308+zA20T*|FԮW 拁.AσkBNZ ȂYA,bF rcꚶ)ڀaw,7e:[JB{joX0k) dD ^XF-G `;s 6\վIDVzK[B!k e'nOg;/E6'א M]#" &˟{yg`#Vn8 + FDZk>`1L,q3,~#o٠j0lD< 6 i{aodnǤ~nX#KJX5Hb6*UkZThGA-LU۴(=-OvX+ |ToqP'VKBHux]]z_^ydY!&+ɹz R?(p`iMvP<7{:,-%sIxvF(]Sš_T*2}+ o/sn[<"Yb4Q~N33OS߲Yՠؾ=`' dm9 ! +^GH+Q?1u +g%!oab(?H`Ra +D +fkMz86f+f~d * KHiT &@Ps:XKu3'.Co0XI=֗&AwrQ9;Ai \!W]tG =껪1-!EXluE@}%aCg* .u@ArNooBbKލ +u #j{bp7z7YPsD;۬Mlo66A7C>jj-hFX^G j\].{ 6.yܝ}6jB[#R Ӗ:vƀzEZ朻2?o.e;rZy7U cuIƼR݌RTX11zU63N4t "<HF bY+ t(^ v@9kV`ic@aD a!>W +25MN^LFfi1~c[Q$Ulh⇐ٝ(Fg(x~=O=,hO_k4c3_ k>٦w:^ꁎFڵݤ4Ay+Nɒ ;1m;HZ@!YWqO:,&䏈 +b~X74FA?rzLhEdEq##` X+զLbwl`A0sHj¨*=y^x'd`if+ @!'6]9CD:zb7{He}ZA%u2нQR +,WKgڥN}(mbl&$<7r/a*__A` h&$0%-%W]^"y9gS5ځQ6IfrMN4ӹP={b6‡!8+=t(E +9;=|]%y a}sRrJFX赥 +ޙ0XD> F=EZ|quh*A;\YQ$Uyg"U|g#M7 +Eh"s-CB+nMȼߧgɡgzڭ>I`;F8@K=D¡i7#2i +N3f*n9V]iD8`Ի^i.96~[ +dL\VREm t\X8zFlYem`dlzjq2:Fa0Gڠ qз5WM8aڇ5ea3,@6bQ4PhBT^5v =B24m($3xÃW ԢU> jB +㪿B`+e 𳔆ehDӢٔ}̔K E׍3W&:JƦJF0\Ъ:HGU!`f1gF9h.r@I&0ɠ V;bӭ.CQ%ATGPMa&23ٶPstQ3!ǫ%2=]ڏhylK*z^N gV@^@qmxCyZmX@8L?"z,JX2wuk|"]:M*<!F-,@dtTlk"kP3ӷ"D4E!I$.qa}?L]Õ%#i}6O -K[I#Oʼ%u$\Y `\^泝ѭ s? nݹ] *T, D=AfUB"Vg-)Xg"MX $( P\AvӾRn\3W+ʸ?(\@FHL/@{y}Nl1ZuUkň+m7`3ero~uΚѯA#mvq~'fPhDy,c_~aDْ\w,Z}EFA}ay6oIL!DIҧƤ7=ǒb{īGi +Z#rCÑw\Ad۰_CԴD&_pqR}.r_E3;I:дq9Q^'0*wGRJu|=^> ׈bgq) Q҄ȒjdRUˤ[ࡥvooQ)No+f(@6 1ħԊ`(!x4Y~&0-bF*@t}V[+q$#g$e։E 9@¤!R[cr|i.Xgt.`, !kw1-Ld6-U^2rw|NF5Ef.;|)RT +TR%m2j {p&"Th[[.DEO +,N` }u5D$ !3Y=Hqq{q/q6sҀ14xzC\(N]Ulxy|@qoMBF8yȆӛk C^f-RxpۼĤenI.@IFgUQP[D3XbGP8A7y4';kh5)F+(ꪈcaC)u?l>ayDm(hmRt x؄P'.#TL^o4 ܁PÊ{ Q3+[1qʐZ˝6^{ɯE ,ADw!V$i E4rjǎJHh +нqsPݱݪyr1V1u~q8<T[{p~jD`/Gja4;L7d/ 0&%錗$gTu{ͅ&%tЈT$⥒#;u;C!;;OwCE9Aڋ{H'P᠛lR0X&ɾl%nG2`lfBkV LB6 &0R߱J?&X&HqxPůF kƔV Uguꧨm~D E$R.B@~/T4YEt%J=OSA|$\FWb7rhaQ g^зb'K0(F}E +ɯ00;"O5ˎёc&cDZ4w;D0/KƙpXǃ2o63-= 2䔞UlW["Dx]S2Yd1?F__Ou?!.AZzhwqߟ_ݰ5M2qMdF4N arH+葯j}qZ bLv(Ó-{+CXsO8Hʜak ,`?"'C.2@ +՞@^IR"!lY@fωv2ve1z 0bU=8T'!( D24;_tkSHM@6eQ5TzYb,D1] 8tK98obS^emlbvL7X6Y2Q@c$z[h-^e/BEBr(!9a2J -k({w4q|U ކ>}IA4LrrG>?dm%z;&%vʼU 6ay"K%ӐJ0a|K_.bP-m!S rgmzD '!*f|ȟ×%Yc3^W1Rf&%3),ÐQDЫ ŠT5Lҫ? + b^{2 D {@XѹGEtXą OסM%QZXᘼH Iм" 3 h'Z'8S\\{ Eg}1x.*YAkAǶ?^K}$ R)`zY[! &3Jx0pY["Ӱ@~:OyCXݗ( Lf%#@VRgIo9^B ZKlp%"{"ߧ4/ZWlhͲ\uy4jZ=OZt=̬DtPY> +k𗡔,bmiHp|})*ƺ91^h%ɧ9217"m,u3tkg#~i.)]bcԾij__-CM&|.C.RfsNֺlGl6~o8 +l~' ZѺ2nܥ_5EF)6 치zjmeWخP̱`eDDs[c\F5j$6IpE4A]h\TeUj{rbnvt03aИ~L9cO^50#\ ڥdEIR[vΊÃ9xF>?F۠_7>#md0x{kgD@@0ݪ@Ԡ>T95*\zUF"-:NCT'*n|Px\cĵI3c}Hv93;z';&J HZgC) Jog hnAH&c$0$kd(9mEX<C?| 3B0\x" !GlL$*z6<6x>a[4 La" ;G +h̕eɘ͆(SW- !o@pbC#u3.AQl~e1(51Z'`G0/OV!!mƉ?]4ODҏղ>h-d>pGmtF27"r{kk!F$aX 2\mYra]KtC2x%j14;24nkG5KP˛D&dWt;Xk<ຏQqnŃkX9VQ$n4c W!M 8SetK(,=6PƷ"D$Ijo$kn*a P5] %47Ȩer +vuձ\G-؜J cTA 8ӌoF|/<q[.]M\fbx )#4:܎/tt+0*LU"7D?x8/\K5%u7@=PҺRߘx0,K4Y-UΨQtVSQ e<ԁm.rs@Sn Bꢜ$j]D!F]`kD,)\iD8thf8>ݡH6w%Ц%EL?V6rW.mF_&1$K1TA]E{uo2|͙zZ;nYyQ`MaUa-I +94 EܱOrk*]^>+KWEB +lwځhQ3@3gCǧXv{WzEMHy,,ޕ0\ MK^PD=AR 8([ +H( H# 0$'Kg_W8DzK,…$}4&"2OkA. v̗bDJ4T + !7We^*P>1 熃Ym%GؙR$N^MJحn@#_G7K|HH(Ɔ_EG8p{I3@dH#$?J$q~"%r-?47 q{ipbQMQ<;^r!gnX MGr)a}6xY7$wkX8'LY"SU3$I%Hӻ,9wMr%YHk8rhLFNOX?M$SJ쯖.#prHȹeS_ +=^J$O=aK$P<9wsc(Ar#dm4.q +:4\ +C2~g8vOdM,}ܬi rbq_b]!u# M,y//] =HLQ_]b/ˢSZPK$XfD Stٹ%Pl8M~%~PTA;gx%//{73C% gR;Czׇ 6ng%9qrK -(لVN?6$ 8np>9 g>Ts"@$JUE: +HP><0 b5DokRK%&]m + Olpki>č=}׷>'ZߐrphVȥ)(ګSFOxp4U3=D MQidM]rΧAZ#!nzC=D-]o1rRKE'G#] +mVôrr/]A(wO9o.[nz—\D&Νf@٤ޟc1r;CC5TSje5NZ"R8'">0-u?Һ&DZˣYr" QC#Lv@~SA7QS<8:2N|Pq`Ҵ{"N`#-?8Qs #xrOuE\:2xE /:du]-r).z@ly=<$* +%e>+:yFjdk!ycX!$-4jsh$]b:&_8Dh[zv{bOLm,E<[ߊ`[4ULrgS- r5geA`]J".h(>-;4T$4_)Gޝ:`t,G##ao;S(ͳ&4To&r,IPceA9ADu7iŭJqE Hû4p B#n4 W9+luc%HknGsI%87v-GXHx +̣yd5*A-ͥ;_FXAQሷEdDXgf!@!c)nc0H3k[ipmIT5}.TBèLy=}@7, (MTT=uf.?j1UP}3%MnuDfMrDU5hiT,U5i!l^H75}mhC:/Lj :%LU'O/mCvb*wBnݰ + J-,o>9Ehe"! /E&>[SlTǜ|<}Ʉq.eEAaerdphQ&'j<<Fj\g {?Z)zT NP^@R.83ClCK>;MzַC[r&E)a; cN016JkuodʄFƤJlT Ŋ>M%X> +G>E !6.o'^)d-+׹mjR4ҝ gx^[yZaljR!QAs ְg-e-eʺm U[)tYu&%jn"X͜Ci˜TJ,@au֪%֎?3iyg lΚ +4mtB ^d'6Wz/é}M5wI9=q4kdnQls}tft iQ>\E1j>5{5^ bAXA1 >hz +pқװrOHyw A. mC hgiG?8FdXH?2s*yІLA$*@{WqPK=`ް9OmXYbәz/r9~b.r9ΠjL¦7ʚ =r#l!meb#nZ88FCߔHK/3ե НxO l .VBd{GTgF͹g52wt lu3Ek|cg})h&M!SP.a $%148I lŷDr)r];BBT㦐3_].][ts'U^<;ˤ_7b*Tz?:a~E qC[\(%IIXm:$q~p\`/Arofl97rjynz6bWD\m3 " gGZFǵXz5nsjkL[dBz ]qTC9Ⓒ60#Rb`Ǯ,@D;.[+D>߇Il~h7i.{υj=tx:bl*iǮUeg!ikv;=[_?Xyo$??3ňr>hӜ3u? DˬYύ2ȴ\)o+l408x.G]i"@ֿ \' +"qs{ eeme)6aŽ"Z `"iQf;=i/V1nMƉKz{[XLrkHaAҴG΂&R+!-ӰEdR8¶:8B8:~g~~j +.&N,`J(莌Q1 4P8TB$Yr Fpn\R"'94"aKS 1AڡUQ+TJ0P,Q@MFR׻o[>F&.@<5j* sh@{Jce-sd{N7B )^tLn +oW_N+Sn#:"ǁ ~^iuIN␳d&""͑YR=6+"h#h/q`5p`=.0f+"-8>l d/ R=C5jɚ'*V0P%CL=exiĕaZdM#S)*r2l8ư7vՎ0NITX"T5s}Gk޳ء$6S \t|(S`ױS %paR q`ۓ 0"RȃٴxOzHP45ST%DK@VC|IT_cCRjxE8h4 + \HTsDF T\9C[D!0:M"K"u/ș?ݒms4 3)k{E K#Ih){ DSKVT@6G^O4L6 ׼_DS.iD{t]:i2pJ <-Z%;6q`iJ,ݍ ;#$Cu0rLLF,]=%l^yrc!.aU@WەV$~_Br:SB?S ]`2(`>W@.#~b<{R A2vxMCp8atlGz]RvӇ1ȊA1T.kl4N +#ta4B +`Fȿ{!4V3L0| FDTrcoL䷫?Sl͗<_jp/?J $6׶|ˁX凋UAܪ.e 6U19gzhlA_p X]22iFH΃mPw?]0/MߓÆ VT d*aSr6^>+J#D@*[{ԍv9hP0wX.C{i@ fhE^gD K|;J1yg'"[3'*GFG42UD\sJ F/|!+!\{3R胕{GbEuڝ;=R5d#0 \[wia +N8% 'B 5O"vir{5z8ef`灇 @h q1X6aȊ%G & +(ԆHy4z )aF Y^25ˠƝTv(N%1? Z+EEFJ*#%ߓY]I{"LLk7"/{rz~#;нRЏ@U Bvz[`3՘TAEb7ldkς/#bqsbžhX##mK *qqOIF:#|%x񰈨7zܼy{Iqr Fў'|ZtP')8{BF;x<]L&Lb pobB3VDwbHf5_DFW۟PZK~WBrEʀv[ә6:"pH>,ZK8`=@^ԳɁM{-hf6h`0ęFVm'8 (@b-@ o{6nFV8FDZy3D\(H7 +BatktE/$ endstream endobj 59 0 obj <>stream +b KBȬlQC".IeAp)Ib㞉n7 e7ܦF Hgu9ǡki M 5kְ~`6l3KRxѐ9!fa䃌~/6< %_a1D3yXNcmMv Edi !yt#O9Qmx!]ljsAJȢwE|س<׃QwZe"|!MVaTN0q׽('p05 Tdi7m203oVY4U fs؎*1Lbw1gh&>3(8e\ÃS>oDwqe #k.k=B Z_ ++r:]Ɇ e8‚Z'y 9$71iт RHbpҷ)EZV(RS#mO3WO CV KNCշh)q˟# "IKͼ!`B`VGޅʑܓLNR1+hkn qbQkpႶ!P"kl[dGd6^#| +$P^w"fCe{ZDm=|Pe8',xbh@ * L.K#=EHfrgmbp8 @N\i 0E~.-(Zא~L:ܳB^ԎB9$_xM)oU1+_^:E5I]I`;E4FTYg +n`mtXhAT-2tU_~_¿u? V_r1g@F *io̔`VL8T<;92y!lEDR.C{UwDg2RPNwyb8uk `B^}\K +[]ݎF[a"p%s;ALbwTE[0d6,  u ]4Cu9%}%+0S)a8ȩWDs\ Gʸ>":,pX*f(Y n̓]8s`еR_0 +_e@᎞$_&Y^Qxasv#txCgԑ+ +ԱEӅP!|9Gр01"J3^D nzdЦˍ¥kxQ&^=c09lO@8-|$ב>h Gm W]G@~Jx E~`:8˻HBNd`LZ}[>ʂ#FR;j}~W©DDK^ԡK.л2uW0E͈̾ ]"LjR ]AWƂ%yPbeͩd"Ğt,ڦ/c%*3M<JF% -,V_Wؑ4d:_g0R $b{nU +DtzT6x-hԚ>!t3<^&Pu#E֯M3 +E?C\ 9{P0rjfxT6Җ H^ kmJv7\8T/zgEt@ƅPQ>UNPH2M!F()8D|3=OE. L!#\LϪ9Kr}d<&?]CQ+ g`aś_eRR}m2;"B RָY.LAPlu`$5 +`%>|661Ӆs0Ȁ  ["AtQ胙TF1@ n_<45F}46YNLTZ/ODzoq}чB#I=*Fj?<'Y>$ Ɉ 3TDFߘ[?NJd Uv"}dn2#` a &U /qWLGWHr(aw\d D,iPo]%w9H٪nf˪Z;[8lCJoURT\R{|G +F,E(uAA􊨧޻"LqvJ\4A%CI9t]s'=M÷ʘ1Z 3-\UD")) +գmG v$*-~`i1躤(rlMZ wѸ4ZlRuDVԻU q/!t$CR.{<|(s{S v,ݽp +C]n< *T҅fH-%?ذmƅG4wIfS+ R׉ai(!:0建F㇀} dOp8NHx96阨N&c0Q|'j RYjag9w7./ +d~"a0ʱVjd[{3Jm3g-Qc`U%>VY;H$4|SalUcP֣i +iP#Jͧ2ZP7׶վdl)!ROX;#+D z,zc&lj@,(ܢ1JHCڌkh%ͮp `/FlJb+R ֐kKRf6R &i]@s?x~ +ojŻXsqǀK[ iPNme$kA`X.!5 ̻[Ѝ*DJ@~("QŊ]ݤ9?)tt& +VQ%m4FCCL:VHp(JyTjˣl1ۑX,Dy<!@XV+ A9޻@XeUFl6(濙6t!dzMO c ] +00ʕpq60NqR[rFm9eWlFZ7n ^uAPPep):]L;.{bRq2{ڡ,o%Q)^6pl%+Q&e80`nXYUoLe 8pT}P8e|А].9llNj3V vE(2אع M܋jG4"qYjd AIb{f?Ær:9By>Ht$ >~ X'vPiwY4j +J]Sh׫y3TMMC鍡3)35r6̀,^DɊT'7QJ&(1-Z?{Ra&,~p療Ut1w2o) +*,,zĵ!hĝ(|ϑ$6XK/5P ݅̈́M߆@Uk +C0TkƛcN} +/+BP2()k H 8(cjYqQr5ǒ0~3C0aJமY:$ܧ E34pgt9nۺ$("v8|-i[b [ˡ}X\hMaixA8i]u5ĸGfd1~&qEJEV9|a7%Df"q!ps$;IU=䤑…(J:R5ql{RդؑY)TA(?Y]CF"GS+S-DzwXZx"YO~SxdفT@m蘎ϻ_Σ?:d^O%Ч~ s-p& +hF)}(*OԂ4t腈nGO(ǁ!!tGy,Qa!ף=Pm UjI?s 14C~ubg 7CثH: kMfDW^\ɿXAHN bHT_f%ŝ0¼p5̪'6mU[ ytW4i.o.E4]} I"6gMl+rb7%iJƵ+OVepW+C c(OgG*jslԨ5ǡޚԋBSA@'^Q +#=)Z rs #BIf6bt;s٧lf +jFH;©F:W'aB'M- #QyHIk!K+pӁ򎘼Ep:*a@X; +Ni1NUro]::/c5P!P P;`]*Bu4e&DKsK"/Ax%Q/M(PĕeF1EAՑ {Li\J}>LϬ?'.DNceׇmIPҖ~e| *(oƲB64G kPc]+\C|K'd?KW͆sl '*nri<& ]&hPQ5exs*D4Ӣ7k6AʦEν{@TZl1y 0Û-k>QGSAKTzi.HY!k_JDmEDZHp89^$rɋ)5vh0I)-鈊G$pMY LRBDKjf=y4kKf9 66`&;o{#X¤çe'^@X&p⒚ /Hui6/C%[lE3$fqx.]6Uu3ХN(?aS!$'&!/ +>'p>gր0k{3J4xm][! !ꩄp"jjb7mEʆ%]E("ѳqMXI1l.,avIwj3RkL o5?E2#dTkSvaf}^t]LC6J6}t2eqs,n-~gYqkC?ㆣtZTϬ#=D#PϞO +eqch,#/[ilcq>9+2J)CSm8 $9uNuy@~n3ѹr +u5;>6G 9+I&0@,2 +*gD žtT%Ob9d?TTYVTa,$IjJgP]5S5Xq)vc5o^ 9!ǼLbz1&2P4P/a51&;j 0$j*RK/-\ܑЛ G-0ρq M^'yJO.cP$QưE(OGO{3e=_q(EjG(G'/ƒ׆+B`?@9/ g Y-{XeG(^8dWd2n B +ѐ-Gc&Clqqo\LVћ9 GY ȣ+R͘Lpl|Dzk + DqN!uIrFCC̗@D]+=#͕[مf[rp 8gcǹL + OhA4Ube;,5G~+PX`:Du@!cis3T6!cS- S'EoJ\CcbVzCsl jcʾ6F[m&}mxi,hTsdMMT{KT#ށ3FfC3 cVboi{SYzI ͯo{(eti'_kqSň8HUɂ@xŭ5qlW#虃L~R〞zWl%s- C95AeX 8?[1vbD4>IwSnZOzt4AwH1YW!yV޲M`8Ns/o>xݖzg-hdk5:sfh\`? zrH@DDHhe +ˈQ&q,zHR6-ìOϧsX# +ͨޑ*zu;]Hy:wHۆ41Im߁{e5Z^FMccD%_۵ T# [?WV2Wغ!A7)EQ rwH{:*[y Yÿ`~ѷ5@Uq؎QQY+-HcFrNv Ka,Ec^ c.ճ Ş!U𒉐 $Z"%{8o*#.%FLlekK3>Q A'q!NXLp2^Bs~-IS*'WʴG'fv Qd:pq}s-۲tDv*.lW*ڎan~6Sa]ę]WMp5|^+e1~6E;_ WzHcEM8[DIj4Bmt<9+V+({lsImlbȚ{ @v sWxoCXOmpۻޡ̈́ި'ld,cNpl lSOGyP'4˓ &cXA6f.]%N`&Bcw6ZMCW/bV)[ טHİ@)D%F+*$ɌR哂hh?5reN&pbm7μufl~aPyø + |\I0%?_bJˆYD pCp +a +Mm5=A&V_hL78d$RXxf- FՌ;\h߰5G +cߤD6ϝBg +g~!IF`a%L2y宧R-?|f|&>ctOOQ%L,GUiuѰYŚ5asT1V5{n\İW;Z +փԱk/~˲z$Y{㫈Jk`lG~VPHp"W'݆A3} qP?V(lN'S=;~eqgF#yM,dF '\7$?+$ոҧ.(/8!@" ;gȆ| #3K~զ2$S!R4g ]v g2,]b$U:.K&s9H?q]1#~/&f4qӊ!">Q EJ}akLqQ @Xm(qAwV(0؆ "%\+-YfhYL٣8C!PkWg qy.Z9]֘ &DUKKV:q0MD^{0E]5lrN34>zh>>@AXȆtuZpK?gu>"d9H>R U7(Lݾ{f5&7 +}*^p@ - </k`߲il;ѿk{f WƏr,ՓPvs`B G:6c8 #o΃w aXHiE%ԍW7Xm1 i~[ 嫔 bpNY$3,2!Fޑ?%~(̑Bf;E[GTJ#5ЬF)-_ +ls6+ELr}JNXhZ{2q͡U HsޏAڴ##PNw z0d|](ߊնPh:~H{&/jWb:Nmg s-ݺv{) f=O4Wa}5 +:#/x$TKfwH +xprqֆN +6Q8MT7 +! o'V$f%t ѢQd/+ pBRԿ}SD ;رqm#j +DHQk%>FRD X(<y}VHDfD#; +P9~*\%î~[ѠHZ'i`LW7/(Z +0 Mk.Foʘ\!˱zNWJQ(*dɡid&pNK"03d62d p2%e.iji@ǒ 1"&Ӓ|7~JRpJu$0 +=+}/w ȿWj*AU6eʼF"&@~㧌m8Y).@DM$ xZËKyM[K bgнϜ9PR2Ȝl$đ8sCj (4P5@xM Zt?/o#'E\ Z^?NnѩKs4:Q{'DRW{@_bݭzh wk|݂,H \26C)_?)L΃?%3ILCMc*l3ظŝ^+,x<%e'QQ2/j&(ۭ6`HPpCL sՃduL8US'][ʖXt#4#ϋGbZXZ@-Ih,u5u욤12߽P%I"=MP1K#o{cĂ,pWAqm)Hv6),4<\F3qvLY1{kpn<$ln+JA r/J?6)J͞9{jwPkjFL!Joڻ|Z{zDu2- +dPRb ԓ#o |ij] Hs3sϘS(Ep$၏]yY)VZda2 +CD!1^]&,w@ ,GsN +dCA:5m&TGUPpDnC=aG@Ղk_TI1E m[kέ=8HmyyB@.H:O&AN=C +L@iG{F|RVXd|:[@S@[g`"u3Wrn4OU 71Iȗ;p kHFC;;:#m7huuAZrk(ę$S3Xj@[.G@H06Yދ2Pbyդ*XBfeMQ,>al8.-6\Ѹ3fHtoArq5k+@l+,䡹.i,Б&t/@p%zD1ME᪯ 1:;mJdPa7Plу}OX{(25`{ԑo5+ Ͳ30Jpe"v_C3*\a GW!xQ@6!i+:[_ dWJ2Ԣ%EιfӼ 3t_t;Z4.Y kbVN<25eA@eP]ztӓ<=J*[6&ƽdW&EG5DJhǻbUjt⊞q4di.2i[L X>לR~YA$ NO.w/ѕ1S~ؘA7F7^":)kݲۍAt-׭NEmΗyjdiW +YY0WPfI* 9SMmaHq\Y)!P8QlJ԰{[R8r0})MEފ`ʴ+J Ŏ>M==kexs B| 6-+U%Mt/!HKl!!3REzvR{!>}XF$>aa8;o bxȈQ+&'AmSso/M&n( =  Wcu@zU [iǂdi` giǕB:Dse%Ioٝ'$Q=FTͫ&须J\* hj'hRzM{eM=6y)2ʾq{-DL \FgJ O +3hSO)%tO@jY JAO"hxP@VSC|tuE5duB "yDp-K7,V|y\¾pn/ hA,?4 +ym+ {gTcZ}{&jmTlyh*os\aeSA{^ԡmϔ(BOjA$18 ̾gVXv^A(XbmDZ<30Z^6>Q=gQ0K45{:ސa|A]TE3UŝEuAC5OiIxА' +gϡ +Yi "f3M͖ QjŔc$>Cc[$7^P{S +F⇺+4 +W +sS47S͒jfBcT,0 kN1ίP 5]`0Ci%ҁvPfw,p0dG2.,BfLvR4iY'#̽d؃u{}22Cĵ[jJg q/y&-!)Ky0_,)&7AR@ VNgSQleus畻VB-08K{8CP(DbwR^,ө!VSj'6_)N2 l`' !8,I>`uKY8+!2&Ȍ7N'brG[#h+gN +kf8(#`ķ(%\- 2@D@hM¬W/F %ICbv(4*{G Auwpa4CCEX*s57)E%;a3#UDSUhX5Z ՆɇE$Ⲳ{ D _Ő(CfC6 zJN5I2%E;CƱ0JGгMM4fZkFtۦ}PS{x 1cu:Ql#OV!̊t̀3VBB+a3{.> +1 LF N ~-մ*v"`;v"% Fz67mo#Rx qCgԹB&cMn4)f t 3XJy Sl[:Vyn؄e|2Hzjft+T+A|j(~?%.!i8;&..J[R?+Yޖ  ? / `4J#ճ1Aq3}nK~DtEJb*ǨHcOS&,a=KoժZ4 SFvq\m/pmuYLu\-|@< 4@>"R]_K4&hH2?!S8ډI] I)4<\/kCZYP;u>- d~s'NF,AnIfo VrАtS{c(7LO~6nZC(mh 1[>\JV*&"U8 i7IxjJҧ53@/BgfgP_.P/9&5oELF 2,|^rMl +ЉԶ)6+BH<j&k,7ߣNC +aܼ(v[v]M9r]6ƦVUrQՠ52Nʸ?OEsmuŐTR8՝l"ʞ?ɝd2 &Dn|'=~2WKH;Q[ cY YBLV6iD~bXal-ޟQFap;XM~x^S8XEx'bҶ-_# FĂ 08]? N hWs _M[h`Mlz]@ ҧSr=xʞ sE0K +,LDhd6d 2u}JyBq'}>%OjT|>af3<]">K)P5߫D'D/F0̮禞vHo#U׉P ubyiT#P;X@ZzC$`GA^ZꎰfN*X-_2:,άD=(!g'R8uu\2e,l4?J9Çk3[&fE,8m [3OrS^AKѸj B[.3tɪǛ0ޙ[U'D*sc3xjGI@')fm3}.T$H{-Q0WT*Ƒ3ƣ +Vamm7;⣹* v Yե !MV$aQ'FgL(^ @He0B8>R%`_`>I,@(!{A~jWFw}9o2+D"*P3'&̇3 yF +cԻ݆>CB)v3W SP?#ӈ?I-Ϯub)+)S k3ceDst.]UxrC߬xqtֿFeh~:s$]~WDH3OK VlǸ7yFy#*JP.  +O.j_+B:",we SHC<`_`U Q̒B)Z(6>/jkш^7gvsCq鞟Fy+}]W/.p=C0lo(_3a( %;Μ}D'3Ƃ>r'_Tixʔ1%1USZ0Ɏf8OrIl>&>A;-A04j:KYuCD$8o{Ga\I^컸VX0S\CNj`PSN +msK *Qs;*`1(lO`ScHG+IVkDёIsd:j"JBFO}#yvk2,V&@48ȍ]#d /s !øM%ŔU?v^/E;8}[yHHBʚ%E8u0dbaoFܯp7;Fq!㧗B޺!*JtH??r!ߧ$6\iNE*/F&-B)iJo[ͿT#Y+`⣦x5HI2PL2anW_<"_?AW3F5h? % x}X)c%%>/7;Kf5 +RB50 AoD$$ +.@E"Wi?hJy挤x13]K/HD ƌ S7SA"B➌jY W\X}kY#=~ } +_igdKLoW$݆Yٍ4A;\HEG0}m-ޥydС|w42Ww:@%AMՅ8LᑑSQ}<}o&<ҶdRBANhQBN.A-&!xUԱEŀ(_4I7WfIk@޷T[2 E*Sd?X-Ö?+m)M/v^G 1xSh*防3pQ"\ma2Ϭa8^B)(ay&nGxG J$QTs}fN$ʀ\ +eIȫ6wɠ`X3$f(^KKgJx\hX6 aGӇJ=oP3:=E!N&1![6WS˛XA#_x"Ue4>ۮ8t{Tm r{B]H +cMu!6 $M}AdyPмY 'ɫi'NRܸ[|-W.3T ƭʼn[)S4)>05u=!N[C$ܩh# Kӝ a3n;/˳98P[UP3#HtN5TOIS@/T7;h?h6RmD,&q3Dݘs5Pue2{᭄;@CE͚،>)EId!-6㈳/ ezt#t~ QFC; -](Tbz(va E8mx3="x+*v)\wɆ a 1{=.&Sap_"Xmu&KWkv5ljN v ҷs&aC,@׮.T`TB±JB t dZn'{'9Pe/̇䢼e=صj$!bi͔⭋[!]H2 7CHf@ :ߨ+6ěc +_j)_GU6qPݛ _džUP¤P652b%It#ÇblT2?ffl,>GG{o2y=:vG-UqpR"C6 c5 a Xʺ>@Q9DMc#L0_\Q_x(P="*Wr }} ^:x }9kȫŨr؇Klx11V c"0R/ 4CɪtC;GC׶85ŚR9ۂQ8rA@ z! + 隌r6囈<+#ғ[p$s$+; ?HW!5%b:0'!pgeQCS^S`\Jh#m(xPLs**StΡ0C\БrPz؛(M9#bG2J .*CeVl)B)>UFl^e1Lf=X$`']q=t=w34p7Ȝ kWӐC1-A{z9ۍ׶i0R_K@D^}\EзM^tzqHĜQds#ݖ6DQ(S DпpH1g@}xhd*4GzFKaAf+\^Mήy,'\X(8P&kk@NPRpkT%AN!uglyCTZP'n&=qIM敹cVemDFeQ#CC o[?* %.P%/bZPV6;j#*GrbKHn1Na<!33K0z'amx>Ѻ\˽IIwe#q=Nz2%*/9HWw _LH@{Ql8`5<'ߦssSo%eWTubY}ABk.qFY%a*BYWGU(k3֘iXjƜW?GLVOj=pڋfX Wz qyjK 6>7l5uiHPXy)Ksf)BL/0 DT0v 5VLya&K-G ʓ$v!N ^ɔ$RO1Osapzhh,<=f! "DᾡH#z3P%ȷ&5jx5J $cQZ&xllRCn8(a]c.xOu=h{2}Br&js-2u58GY=pCU!?D/żH7LI&(@[.E4H.dX۾x|xOx~nPTް+īg}43i ɓ6 g3hIF+r(@L/ujDhf"+J3B$,zed 7- =ARJL,u@:cȨk^3L6/+7ʑM +P犬ei5ފX6upUdUQqWm~m32{Ef#Y -ODݯ ѾHd rG;>®IVXԣPQZIT0@H%d< NVMw:<Z#*P.C{h ]¯@UVH)G{se ֎Ȗf<w+(*;.*q8A1 +as)/O,oh$do|AҽoQ#]B'hZI/l$pg +GF=-qJ9(|4+/0blc\je#{)III ,sH + sG5PC}S:* P̜ +ZU9v.A >BXG/RoƋAL%"i&&)/ +JI fRs/_\!)%lx l fd71>~OΞix?m:mb^diJ$$0Ѹ1A$fh*zC< Sd;BRll"]O:Ŗ&sG^Hצކ{^qq +}0BCvG'~ ¶M";4f%Y?#@.0n#Pd\u&V4h  ߺePA,# C2#Z[_~G6QiZנS*Bƽ Rsx43t`z'Qh&tz6;ݮF182nU6{m-{8'>ݮlM/RΎSF="{.9 QO/?,R IS'NQض.f*G]@%DS~@SGy'Zi;E;9õE4ޔj IMB *J\ٟU{(u~5 +j^Вd55uVϧ,")->Ɖuq28v@A+_P[3Kxz {:Szib|%3D@:c)v!!2UsՂ&*?UD ).'H^Oθ7xjD`92Rm6A0Y[9a%hYN8T HaK67!l.GA%纉e_J{Lh5D=cev ӽUY!28ZTD2 Wd$pӟKg2"=g*ƺ;)zʼn/ *? +_xuhx(%]>!'uOLd;MlQ3?!T ,eD-cq|P{WnL-FVCqM7L +*@qGF#zXE@lS?~ ]M͡]Ei]Ri4K8nZl88@d<.aHsڱYr!h[euIO9Fxuly:bshXahc(v613@PяHH?aDaJ3N(^/ Rav?%}%ƖXF[ߞdiPzd4ͼthu7*ǣAODkDxq4 tMè%MD$;8?SigmڨRi)co"L3i4p|io%́IU#G"nF:'&̳K'"&#F87`uZ!KXQɳGF^@ +mSyh!~G)9/}d03ܜ*{I1fS>Vը1P@\te^but.%❍GtA|Q1rizpDQĸ]uiEˍQf)sbL;2ys`.daЄڦf@6%}5 %=wq5\aiVD7S)/ :7ֳX F@;C1F̜Y̳Մ{ @m3fō&4͎`/]Et`Wcܫ2L\76 C⯙X00teAw \3*X6M0?Է~WO?lr"RZIbv.0ӣn/߃nq<gKA%rvMXiP*<(kcV-St`ZfOdz*YlE%lSb\+ԹRIptu@u(|,s2M/{3*0*0-݂D +ڋ@X/nJӧfL* 7>6^ 8DC+C[JH)nO&l`H+j A4bAT6Bd]]'@RuZpұ2L JRR?5D^2].^ `uq(Ed8SD?5j# ZR0Db23MsTWIX$ +?d8<~dV9C3Ey $1P uI$cOڙ*ki,dǣTHY^6bS|j.%u*ͥrKrJ# s&e[c"E1DՆu K@77+3X^FeME3hԩh{FNɦ;Z"L(/1L|K.|96_좖0s6D_sEK +zYC:DJh4ߟa^&! 6jhdվcϰΖ t,GK!ULG;"Qv4\]\6> +%r?l4Qٙd|1%Q6"-R>K‘{5f~A"zϜdgڍ }]hu š}UC y[HB(G&Fz"~ +>3r{.wc/Nl6 +DT@k\"31=j@Jsߍ\&0&{]! ȊTOӈZ5 +4v?,:#hZ8 !eI +>\䍜9V(}zt#  g r=fc87{>MB!>Q̗2#X;T>4&3ܵ`2u> γk8KD3' +r˰eG @B (,uQ'4t]\Q* XatP'HtҬw#328e$IXhQ=d{._qƭ]POc=2L ՒA`̛5iⷠ}s4Y3KQB]~yGL*&HDUpg~" +P5Ξ#0XvԾl +%MG{ٯnVҭV+U"FHjⱤ6͒'Xn(ԫzWx΂*_WrE+G5i1LT#Xlbg q FNo;#-Ƕ0߽v)6`!BXfKhM2mOĔYO)pOg`eI1xεSW0xS_)"r5/Ʈ%ݦ#3O_#ǖ+Miٚ-ևۧ&4ݫyЧ:oem" e;Kߌ?Bk93tŊ.RU>yڍDT gzwmJR<{UZjE9XzB!]Q {X8)>Jt)(k[*UEIbOB9Y, !t?ѣă>E +Y^W1D "5 5M!C TmC4'B_h,o8sS2RgD;eG"5;c&bꤾEjacgIK)uP@qnDCܟt+;/<2'W0zsE7SfN:x}qp4Z=ΰ}8Ts-GAGG$B *܎ n5 +)F zpUm&:ܙK+hZ@sg1R~PQIeV=fiun35i5[ʽ@vxZ]Ii# _O'?oMKAVf Eu&Fѵ Ij%$d'PM.pwfVJuox'g9(QNCx&֡t_TyiY QdnYHa +vxf(h5}]h[cu"|PΕ6P"?yc05qz,(GJG5jڲFk pڠ0;{>K*9{s M'>SSZ;{vԖX>\T2܊Fp(EbnDnR 4^'T,3j_jKQڙS^Lڡ.Q)9WG)GNV_TРq}ѝU9iBL3A7#}m6{wMu D_kehg%(uKqbcHlp5;Z"COHUES t/3GJ/]edmm&Ǚ{a\(d٦ u1T^Z*:47)I)cbmY8))"zv"@c] LnXrKVtO3!IMNr՚;?NnuTf4.HӅjɻGoQHT;,J14 ~H*eY|[]C޿B1[+QQB<|}~ _+6gr_(٠ +7j)IՃ0iZJ`i] Cʷ,A~w\# b=Z*AiÆ;NfS2؂Q®XӶc֡m} +wbEe<Ÿ_c9W޼/\M,L]5`52f@Wo' i1 +X_ld˯  ]x;IrL !#MG1;v,[GR.:KV|*I[?8a>W@ˈ2`VW Q_ԀC"o}44ˠ$% +|k*6f:!8d::߻8B書,'xߦ5!&RZ Yѓj$%l0JZ܇k">hzi] K}3^$L4OTmI:$RC4Iʁ ˮmld5:Pk0b5t)׭di)z͍.< +蘊IDo k*[mᕟMT}tG@T$4˒`NFWlS q1_tzhbSn='T t2HNM-?-j$vcٯv=PT\?fފ؉D4r!jooHɾqe#FF BOu-T+7? acLeC_>(JT#Acdz\[oAv'[N\;ޟN6/")a$-5/52| 'ϑZ0Z@읚mnnB^U#2}&Nc 14Yv晆6X.yPϳ_Pp 0D3`BH m-7Ԛn; +b˱#-O+>נF8M$(761c)cgE1m!f):"3 jmalW&UeqB2 pC5 GXa&~a q8GMȸpdlrP+sQSȟ>µ5$7yS=eIqe,S.ODF&^ )ěn+2y,VLD`V(\}c>V$ZN1}*87}$b" Y/;S{qkF7Vf-qg$SR:*XGmSUq̩:][ +$]O!@pXv{p.$xRMwg,쪍"!g:+8,^Qs\9./re +Z|X}cAk;K? 81}ɿ]Bbp!.z,.t!_PN_yyu/F?R2ŵu”9Q BzZ +Nc̻QAjq_#oҮD j;9F*JoݮqJW } t.' "]kE"xru/}[:f[@g"hG!h}bh@E * y?!ʼn0Qv1[j'Iﶫ0I̍|l7!#DbtNO=Qrq~]N则˒ϐc' 1o +3.]]m6lÑƹ Z.ġbR!3I|,'p"f"\bK^mTÖ/]!Kl/p(|R;##vq!X$D6&qTc^zCQ.cƎ)4e?q! 4$2+XntD9#Kn8$p|slhvJƼVO'㌻$r['ũrKrHl=l1#7;l%i2I7$2S.9|SV^Дnq:~Yb'$~Y K,!"qsڿOH˹! 7`';ϸ҉4w6Vx"ڞ'O܉y;%ҨBH(4ATt + +HN>:,E9e #;12fs@ ƙ5Z*s`߶+h6ixAO9Tb2*k`vM8c)Dgn0%0,M>2YDR l\NϋVk7[WIpexh+"l&dl]=5 '~ʃZ\NR Yu)QrLm LtrZGLSlrpl{x1;EQ4CpbS<͊a&2GbHlfAEj4^NLprcku 0*BFX$Zp0{]O&/ gfk1o)$-u&3b@CY7@ -MZ;Isoj$:N\d%_P5vi!vh"˒!ٸF ZO_b{{uh" kz m0.?` $ѽR%Cq.]5k&$)N`~V=zXpJc&O9!F'W"!cE +/:W<-; 2=UCgX!yYjEBh&Ջ(W1Le4O̐ QUCsӥ;08K%/ Z[ۥO9q̑ຂ?6 j{l=jYqrmmrQ ElID;?g٥Fꬢ5l,;y:\nz{Z^wmDBJT1u>x*䨶C'aZ\l[t73 vA!p> m5^ERCGDpiDw]= S$_x<bkk.q /?8]7ø$'ι,:?j'EJփ 9+ eG%N d@~;NB1\䟡.5N֖K%qd7,{f\}McA֍X ۠ "X6]q6o4rL@3-ѠH/A}=OyQ84G"'ƪ6i"6K+t@HxL>TL/0I]ꠟ ȓUfhn;3wIR+/L˯>ɨRGЉX(iSөTޑ2•!UG† rnH]Ax)V1&E^cϯ,allTL.d{y"HxaG0*dg ރRo%"EA! fAU ܝU1,PEI"G0n9vkI4_ip۶{i)?y=4T~ANh)Dz\<0r$1VC +ܑ|&(MW:ׅ/Tp&`J)R}9؃ v-YPT*ۢ(/Q/' y/h#=j:#&KH;I []JDŏAĹ#!w}kS_ka@8h`6]xG@E&|hP[LsIVuN"ʢ +,A^啱۔g𸱚@kN+EV+f734!,6a- (uaǽL? JGD=mwIO)yxO5=hYRHTI wt_O-=5\ZKXd^&og[ytid+CQ`xtA@4 ʋgliCl2#aׂpJ]B5|iɧx +^mߧJ2mѻOw$Ig*ru%M4dщ]fH/ZzGU~ O E}U/0=͙YƼ`৑Lҽ$?/k6`بѦːŨ+MǍVMrki}R$[̒35 4O/QmTv :DqDzWuxV0~K!^p] >G6^|$&DbnF@ݢ+e ܗ~'3`!ڸC]h"]k)@ceۋMLz;<짶DP*n3Ê۟P͎!ffrAޘȈC.H84LT + 2UH:*mn)VHQf]>EJ)LH+˂0mevt){R1=8/C\`ѽSEn6[YU%k.N: kő_Hi9a3'2dØ#m݌Erp>d @{@ߥr,Nj bhA֓9lHw'3Oiv!!62qhvC`uys"#W՘+8q1lnEC~VkrMMp<JYS@*'n>pTwOGHX_IWՀ8"ѷ2c]!7;%gFW/lT95-~[Lj!(@WސT&4VsUQG^~pB%ȴd r->( +ϠUk S$jlԱ(Z"aQ1;ϵE|֢W4Ÿ :g7-*O*;j{6(\+8jݔokFc'!q|D c'%J DR|g#+deFHid'ek\"q r|AHqKX |#݀C=l|ꈳoc[L|кuвڇÂ's8 0~le|}n,gCǀ?H&k*ttB z_1׊-bZ@r26ɐ?U+eWT[@49Eq&\g"6,_s3-?oOr02M.#@P-T KC$QG50E9kATHr&1=GHHtMT-b7>S\Oe<&(4:n(Ok%;jyp7xk!=6cF_6Vo1*E0t +ži f^+7ty#l3Y xB.~%K+ݝDhcz~2z@h~c8"V'MXh ,Qm@\r2wkک= !v?PDg,?ԥg"OpnK (f +7_l?wʺד Wo=n)69ve,csкLP"TrE,sίtV.vYY2Q?d"lOWt=kf5bN3k?[ 7Lշؔ:koS+?¬NB? V)VR(g) DIM] h6 ITLrx{a&x%#R7J|~ HV*![1s/w>hCbϳt| a} %%Xm͗s`j&+vH +_W=G>#gF/3@?"^ }?:Y&b%6R 狢|cKQden7շPo6Dhz=x!DYY='+wϓr21VQ6][*:MGAez83<_dcPR](*a {fFJL@~r/R:{x @׶C. $c)wMuQ0nHVQ ##yht&HGFP҄:7)>4YbŚ.#։3+hRl>t O@6Tgȴ| \-ZMj H!g d$@V~fNGra@GꋨX̎ұ{B|)FtTs]9(elE˗ZęԔ<SQlɡ( +;9|zT2n٦++AA!qpZpu͎i^lyvvOqjj 퐑Dͭ?½;%B!,ubV[$aM%a`yܽ=IA.IrFKT̵U%:c`_(Y)'뜄)}  11op̒ +k5@2 'V8Y #L+1ߋ~%TY  ;e .}3$2Q!D=rPѲm,>v=.n6m˯#8Y z# ptW}$JN['PhX?,!P ߋr}0`3.}48u@U&hK؊Z1H4ȈFCo#m5܎-pum +w 1ִL2(o\jnbCB͔c^o<fj:, ɽ5'5;ҴPl*6[Vв &6K$ftˮq?v$?Sm?m iakv[:fa*~5`;PYR?Ofih?;PKVsy  (RK!7QcPI)$5)ҶBZhX枉.O(`\ %Y~6D_4-E`_sΟv9L@؊LZJ(*5rU7aP)B1^r–Y/RM'%TWTK{ +`0S|/+-ZJi [ta0cb¾aOHqxNj[b1%Y~<Á OCeAA2]T \'\!eψn%o< ܌Pp=¹!1!Ɋ=E9fT{ku_BV=:tm]H +&-l #We%0J% |Y!ȿi#';s7_16,d#iЪS/FCU{#~FsKl9 ULtmմ&$JB_ \oE-_Pч!2rǶ~[, ʣ.C>ڒžM=F2RHWxJ#L D&J-[.>XGomZ$۽W6;i1)N[\y?Tg>:![0Z4`Of٫]4XcKA8:<#5b5ƴ" +9faSb01E>R8v V̞zfDV{mxZReWrz-zbglj,l ޙF_KmR#BS A@vm#7[dN1F+7ХV68_fD%|724vc5|h :2$ 3,(Fԏ-iMF6JA*&W5!0Ӎ4Xmɑ`5nw)ԡJmt-՝B6ת|*^4^-jU)CTgaBӮ2+*/EXd<`ncbDDEQ&ˏ_RL%<:F:6 xxB. +-P'nx\xhaÔX0—BJJm\hzQlo]!%.9v + h+XO'a=ކj5 'p -s覘fVnH3H"]  {sX1\Twu@YZF&b_#*Uj$A@p# MpI&>fw&-\n3X'i %x_,Ik2kH[iZdI |4E$( ۲K} :\4ϲj7+-H:Ǚ/oPk!1o")"&ncI+ Όa~}Ϙ@+]7T0hѡmq'euAխn޶WF$H 2c‡!S$0?EAut"%- Ӣ!$2CzlA-gR $riDEeK!clP[\sǣXObɞ3a&R&r@ |zakR`mx?W>%kKY!ёLOS/1OiO?*o*e6dK Bf!FFs{4sjRmSQ0I&3z5]p6^"N qyd-yMs'r +sx7 3w(|/_ձUlpmq=DTPO?^粯||wN"9Fx1("i1:cP&񃹸ߚH@Z [f| +AY/Fi׏ɴ8@rETg7'pG,SQT=z;47bp-8gjb8$m US'#)\*>E-jB}N$ELYAɈKN~+|E+AU,$v>n!$ +:#gL">mPJH+I5 gYı}#ʢ᾵u0bS1O_a1n1cG_9E6(`3Z㈰`䚷RfZ{(v4a~>fT^j6aN14 rd"xk5I^civ3N6֥ wM<Dž;Gw1!mHުnl}#Q;,H2a̯rjÐcV`ςvƎoO:twb-~\ -үeE +LmAiZz( +~7hDAAT'!1ҕܒa)B3MJƜ1k(}w4#5"6l (E"=g$Aў2.R +c^`{^=&eه=: LKy^A$ԇ 7hb_TzԽfZ2S[!3)i#}|x /maK:J*ICXfQ9O}?2dVxb>OuJHf<R3(Fb' )>Ԉ=H3OFciFM^phD;8rW<=< 8%{l'*\{Sg!0/F"yOַgv\t&6P-*aNHaIFrj#X$C]`dTgdS)$pN(gэ-HB;C=.Z@>*Z#揂dDWo=NS-F)V<c(̑ў&1H,j!! 9oSM#8[`c'믰baC=V,lsgC2n3p2H_OFj,xLVi1Y:^wF%cF 1,OxE\Au16##n/d̦L<4?f&Z=@G{AB0uFe6DՃagK|Ddrb‰ vzR|v .r',"QD#׾' sw$Co~UpI8&P.W2~a#왉tfi%2{ʉ 4OkmPjGAArYppR,6Xz>} { +ٳ,M/=WUvn DfdHSU/YB]A5گbN\}TombqES6,e.>lCITerpmF(X." H-ig6u +f(5jKa% l@(6I66+}n+^fh|sދ>O'f3`.^ۦM5csS[ofx~;YהemtPzvQf7u7pZМsv5S>zވ0WuڹֲfEd yjwb&]fkJξ$B8A%[Q L{!5ݠ7pl'6s9PrQP-!͊Q,ƱCXan 7̽`l@FWF7;/=n4#% Du'3//&7F2Aor aw) H˵%R4?{l)A(sw|=L+Z]9v̹UHXXei_ PYr_?_UpIBݲmb=BM]}8Atlز t{3JU:DZ 8IKU@j-ghɠ2=\ɳbrpv8>fWgɺ nGCguD֐G))f1{^T >{mV~iu/hdШo8cspQZ+^)_,vO|mca5g +Eau o6"q K5q^1H^y#vA91 % nT"Nfv87 +tt$kBGZ)0#n`#r/1{` ?Kv0vW.U Eb#6.秊L4G\!,Ϩ(h$*=ODc&01OV1Rqx'nfjvQLA,iv~\cDNU'vJ^$0h;ZMgqmƍa([cy 冈-~ c^EgbHZKq:HGB0y[Tt1bW8L8X3r3L}Wrǿy{Lf'-i `cg};pH757\'ŷ&-A $:@(' \pyoP#o\!;QXRls@vho4 ȬVԖо_<Dr7@FCD)M5o0)ɯ3f6Six9߇beK!}K-a57Ji3?6DCn"x҆_Z" hׯTrQ|G숓qOpV/ȣG0ϝE$z MLhlpZexGL9 )g&aXG_.N ȭ+gaexO3f+)0%G `&g"6#F=jkfG0UdbkPrLq,_o,Ռh#0?7=O֑H +?q.P{xPm,~Ӈ{\BÂA\ NB3abfF4䈐r= (QFxt7\ ITZ$E2eֻ֔Nia~f>'˰-ªdo" +nNJ KfBLk %l#pT:޲>tȾ0A֏%}Q^$G*3t|þ`.e4<_]_cUaf.=H{RR\I?MN rK^a+һiG)mCU౐,C}Ԩ݌^Jb\m3;%(>'" <vhW.P}Ê_6Ќ|a~O̊bP0JgvzHJ9FuR hqɱBdt"npdHG .:SMOi|@D)}qҷ7[e"bim\6~DAa;P@1iH$ [(?[܎yx<ƓHmjM (^фXH)핤q 1&,9@pC;)BL + AE&)uua$i~JEDp5>B1lεN@⯙ҠE[jO/Al종jBPvayhzI +bF[u=@^Za*$u)-˚PY>!`JQ.ͰTc'ۤ-\q],~ehC;Z]nkۋ㞛O5QH!!VUPtW8dP +n'k$Xz: Jb&/D.*d?(1)Iyk(,)knQ]exψ({EPUh1tjbѽtW3oX[@q2 ٭gO GW?ܖTsP! J-^jg$2s4-a&e Che,S9YT󶽧%4N_K_G)" ǕqQj.X1w8?-i{aDњQ:,^*nkl_NyDm-H&-*i`ؕ5fF~zQH +23DП +tߏ q )1᳃Hp8NhKbn:SZ'7'5QpK hR&;p)cu}zx<&9@yȘnČ-`C?T1 ~ #% ~mEk:wO&hki[mꇙh{m_(c9V R!e1K-㾳SEw-eb J ]3d,:n@{>%,9()"a`#(J 0+M +HE4F%;,JM8asqbxYu#{# 'VF*Sd*߄*[ <$P#AWIG0Y\&"Biޡ^*T)s6o7[V@1J)( k*iǘipSBjLwcȥ8Mxx-*d6*Xsg+`Ahoxl=#ZDXsFC1 GDqt1$hOΡ#7Lkwt7:ۂ9e]ϩK5˻ +)j@0{9ui*/.*SMzBDq@NuP0T}ie%Nz^$y؆ikVjiCDw4cѲ)`:oOxIc*P_`8Bpxv 9dtMV{xtF |`^k!q-Ir QPaE\ˤ?#p +8=,e]h%I]qA`MtD b 8>(whbWTO<o7.5{ͩedX͵t,Cp..V:E%m܏l$*ӁAm,ȋY#H,1̻k[uwV@QC{JF\FythG;F7n|4娕ya;SI+ׁS 4%fw ļ 712DƹR[h5/fZp2w!_,1GbmyiF)Q@u`%a,[$7 c=g;j4A31$s}d#!A(0QLr|db@$4E + LV<60A @}G eQ:i [B[cQxo1̸hYkH Ӿ%7b &IaoS\FV į~ l(0E'oz`7Ž +4:} 9!,*`Uɬ K4$M\U?oݻɫҳ5#+_ӫ뒞@ʧֳ +ĒΓb`+Z>eq2%41L/ +2Ɨxܮx"4ho u +=m7]0b"~z`A2PCB͛ Ĩ(}!,i +~)_s+[F1fQcI7cD\P}D7 +L.GRs4=%sؠE)Rq + ir%oC)%5;4@ +E$(RY_L2QWѶx:֤*aԶN-\gxaAI"{;8^ҁ jeh.KׄFCE&t9 ,Ys.n KS=xMp}RpuQ'9vrkLFO b(#qޭ(H\@w0]f i{Ts(G߾-lYvmN"˞Ù\ "]FG5+jgQ|N 8_R|bUZ +S.(d0g_Fn^* +5؄ nɹْÍdlQi*Ăm=ۅaT|Vs;y"-8á~z#@Vݖ9-9\xVŒ8535cp8aQ%>^> HG}V1͟ y$ܻėW+B +>T5=bo UMJuDzB!wvj`]tɊCwY;Y:>xGåiI-L6泼oy}f{{ dIJ":yؠ> TO8H2 ΛkVPkf(RC) +)]٢]wsk.-1FLUj3Vֶ +.! + +hkp;6U4\2=ݵ2p{>4уSZY" F¼' _ɨư ` +ROtED F$]Yj)[:G(6"IGWԸ}X/ R#xgvpVME?-H<"a?^ +]˨aTۅknff֖|d8|2RS&܁I܊ AՇl0>]"CD5l;}f?;31)Ey96:3@_8x4.9-kogQ+Uz4C׈;E@(M$=$JDF[3MVD"-^L}WVJC ?4F3SO,+-njg@؎bPLWdZ)(i-DoH!niD~mvTl&/=gI8pWj +p|[Y\YEzKɽ[uuqx.ׇ^ahق#, + W eFLW8Qѩ_pKsB_'ޟ[ :ko-;àp4Yy/;e V6( +'s}%ݕy G=XiFQp?+# yZA 1zC6m۰Lm֣ͩXjzl/ԮiPNplQ( My)*~(DH<ls[P V< &޷`myRiwkg-*Ar,[{e11`M1.*( jyg͎IF7j3̭˺QC5Dr wA|@|;C\Kf}5ڙqP@0ETn>2w+cXC L]%3UqmOZ]8[P$5|=lE-g+cHS8\Yq'-omA )xQFF֐X0h1"Z(s* f;sgHF6?} Jש@N3ƈ۽܅l4p%:T?I0j_² +`*4%O + \7ʇ>?cr8x12FAZSѴTeK=iSfɲXb}F㤛N@Ee#&õ308Vπܖ賧Ŋ +q8H0~U`LwX# jP4w~`icXKavTr)9mvG6ŵ\֮Po+eB g|3 L8jseۂCE>]E/χo +_BK`M;P|t/6(W%1y:9e$51ZYm:\uF |1@G3C +p}ވ&d#s 0Z!/Tw8tQ';Z+ 5X&Sӛo^td:bt$w} ^) nMc^s[kV*.W)x{ 1~T,ծkRT_bL^W=UM5z#j"T9Ja5!IcBMjoiqfWA lÅUvdܥ#)'лAaeqH}7 .h74#Ŭ6?RuhD+ z._)SsqY+y]5KyUJI?<@H|Ȉ8eA>sb"_r,lPx::`\ZN[U2!T<y l6bWqAW(}K!b&OHX1+!.D[H}"ĢG;4:sNl㝍Q1,:ЛZ28fzj|oXyҁ\SfWx6Mp4*i\{nlH&=Z8NIOj9V.8.oR&TVK.9Riۜs ؍Ր\egOw{QȖycbwQtO҆lyccQĵ]HV +@lZc={B1l ڂ-!~DF,q;GK#CBooGiLj-7_"z,6u`qߦvW6Ww6yȒ@QTRJֿ]MW]!P!Cвep *Ƀ՗QMWcXn ́tECý0ŝ9xW Y +ʹIJ G/ն4ܗ`]ii|n{Dg°vKpVx ҈Nx1߸茒D[h#m|dwSlɇw7p0%ބ$aǼOc4s7FbRLpXL*Ǒ` \)g}+׫ePϿ&LtC&}tP%0[L0a?a%& +^ ZB]tnj8 y#x) ap':Umz}d1sɴLҭsò\ܖtυ+6nSl)D ѻ +fMG0EA/wjXO=qChuEqHfϗ\ڌ=xmI'LuvD [žie NG@@kz'8 nދVoْ*RXb8ј)R֌%(~\!yFJl#Q<<ަaR-|qޱCGLן6L1W3=X^i]Dp)QDˍ:^ٱ]U T H|q$T646od,}y摇 4~+R;R &OYn$Kqolq? ;bb/_A e,mᄓՂ<t=RhUJQFe?b@ ͢wJ`juC[BGEPU{T QV]YV*3Z. 5tPNhGvP $ GIt++;aA)+_6, 2jGR-N88B?R(6M;V=` 2yUI_H"܆ïfԤ'F2Gq^:jA"&- +CKý&]zֺnx}%v8#r1~BNĴQ$tK] =CVޖhIN9/ +k_>Il=&0Ӕ$TfZ*v5*gv/#z|+&6SF-V-MDKch󄒬QD&LCZ΋x0BQx(KLO„JRk^[|p s4hF.Ac>2jK"sFF?̎ XŦ唡ьgɘ Ҕa(xX#SrV@sBStd.-% YeKŐB=5H_%&hI҅#Ku8*{ `PvrvL 2p%"qCpg=S +ŗ jqYY#ʺ/hH& yOtT?FZyv o*1DՆO j<l>83e`TV#~3؟ZZ>ODt|0w?AInXnIO@\CݤioAd:HY>Bqhy}῀Q#"RK.N2ݣ:)Ś6ޙ% $~#N߿D4qq A8!cDFyܿXpqRkP!&(daGsC3zIl!k"xe{k 91A8yǏ=A3)y!;$9㻯C['ԕ방N:?;/W=2ZRNܟJ94.nJU + +yH8`YУY jilkGr /79JaH@ILF0J|7ˤ: d{@,}%|4 )/`R9"PlqGX0-dZU˴]Rb-^&,ԮmH%ӥ!QO 00V '{>i.!CDx`oi2GPd}'xV!xPZchUax\:OgE`DY!$!"esND#)Vp +DRҝghȂ8D}c-~gS^K- s +w~Qr_%=r]H ;YY! Jxj5q +Cb] +(h/ws +MVIthB}2ETY?"@ 8@pWȅU@ա9EPDKkTb{tMe2bZ"m3+cf`$'jTHpKT3-l n0 D` jS<0@2:ώ;ۤ(:t,̍\yX'Ճ2\ T) Ff(oxwG.ŧڵrG<}c+teyKY0BH@2=D$pz8kdxep-4r̩Z&M=cѹCl WYh]~d̳@BT $h&z |zb +9LQqL`lssPBvx2sEcWAtvm;h&1 8CD0[ @o]Ak!!@sp#OJ"m&ĎSvTfT-+ !X^2Z4HIpSLg!- ]%Kd(k%PXm=uwkb燊%*̊@тI.?ͅ_^B!ꚅ .psH~hc2iGù 6Eꅷ:Oa^Z d"FY%N0(a*(uz ;;Iqz +hG=fzMqL.YE6f(pETPXsw۩AM~al'9*jCw7Bf?ZvV˗h{3fz ˣUi2{f*T||78X8` 7$˦po6Z^+ ni]P=qϜ-G9>C5C1nd_})+<`n\SQΙ2h~S& +"Ѭý'/*;D*X_= +Ř6Ez4b\x58O+@\d6Ra(X$%#h@57~h7r҂/[v"a廣׻ EH~bL +јzw}Xmkj4!" s4Uo|w ֆPc" +g6@ a"Tgi­7{A6%I `)yK?}ߡU1̖2~(o@|T2*@r=cQ3͘#`Iddl!EΚk/3]ܘu|wáȰDHkyy=C/S]ʹ O +UC_A( +b"mNu2f/?]->n`)~YrM>o¨׊'َ_r2KZd}l Xζ}`& (;ގQ;t棬|Li1|D`7Rtc:) +۾xp~d +awfj20n$aC,QO4Bsf\\9[!h +vwXm.YwPq(*P|"yLʖ40?`#Ï1|_ +kç VXb>X^TG$ h fkE=j +7*!;%m/>[A0hM6Ҹ!w6 ^X_go~-ӿՐ/cPBsB8-*.X>w> ɐ"Z_ i ojsn;fAc]$|v(jJb[Wb. B0$[z h=tbT3CԸo,=E\x/` +)(^d|ˍ\+p[ v sϖOf.-wЎFP8ChV nP3L2Uo~莀pteFW)zӵN|[{X>:˂xcb$C@ޣ]9るCz,`QтO8"1q ' MP?xSA!xJYNėhXFp^Q?R¥?R2LSCF#@jsGk5oY#g +1jF\,r7S<0L<$:o AHcJu~5qnޯW1];L #L;LN܏](4ei1gLbka i; 28jx'NLh\fr7RJW +MVeDVd#sNA&\JyTju8b ?JP]HZ6[Yy;"^s9^B9[ a\ɤ~yu8vqEj"Ŋظ@wgJ%sށs[ʹD>*|h5vuj;u7|I(هMn>off,@2YbS"ӌbCDWLNMYA-a`̿NT-C,/Ckb挦v(4,i;XIdB=_kƯȦfwZ'9X9Ҩs *)nq ~ n(ېhBm^93KYgP5t`̮`S6?!'k>'_(9rZgQsbvw.=Иu[G$bv,+қSx+'‰@}>K[e6:.-R:h@ !Mt#&gA(?Ӭ%Eaɥ>T\2j&? g{x;4TP-I$=4ӧS@m2R7Xц.=NnR. g3vZQ7<5Jl%"Q1{ED)í`7EEcT$D߀<+1cqpt91?"AԴf ;i0%Ʃ"3p t+F /(Ē-u}?ic]"#|k xk9nӍؖP䝉E{}9 ?,lߕPZ94ѯ~9Z's /mR{0ZRv° (3W =IA@D +k$>Pw1' +siWIT1mԒH3M@Lv"iJC}8dWN(/!Ju.t6x@6.ePGp#'{ ׉355ź|-j'I [`^וyCk~ +fEKx6Ac;?vR=1B}(4_+"["&ؿG_q9"iU9?Cݱwl/ݲwqWCaVJυ,FF*?X%&=Piȱ3> F9}2!uWgթ`Ce7(C &#qӂ@t~4AIเ_@GhF4>cLOXc\?lduӥ2|x@h.HX[B5"KM7n7-Iwm +| 6c9#hj""~ +O22!Y@+wJIqx +NJ+1Y.%ͨ,H6lc$dؓ__FQx2SDZjLqws|-o\*s/uArYs*+9-sE۞e@6e_r-˖ v`Uh-Ye”$~,a k(@=zd[a=n?#Il+W/jhܮѫ ^]v=kDT6)oͮi1j2r~s_Qiߒ׽B-OBхC1GCp@."3) 3bَx U7<%I.e2&p_xm f]/&|˩LJ|UY&*b!{"wn74z37YčuWF"9+pVIB2Mw 6j2]U%dÇ0 h"k1!JGwh~Mٷ" I;7.ֈƥ:Rs YlC0 lz?^'p2[UVSYm  + [J0݁u{ГBx Ƽ& J*V~WxI$#j} Ƥ>`HƮ+qR[ ĥIfhaqΑ| Eҳϻ,CDGQdd_?`8J Dg.!" ׄVo!ćMQ0؃%j"L /NCEt&`.b +ө?]CjfH[ BBx d"9BV/~7u+TؗAMM<\$۴7 |hsubϸPw-sy7׷\x'u +C $ُtc,'ZZB# y^XmDH$EMTbǴ#̀9ȊXD^Qa1$j k'3[}׎ + Ʒ*c",]ato]hP1q2y~ɔQm|5R  5olbyʄf.S\ T '{R)g @1;acF2;N}Ǜ 0 ~iFw!jnA(z؜S䑷G5ABk)6uKc4ɨ@xƮi$6BJ3FzQ7emH]Ktx!K) D:jF,d2{U4J?KO@DKdW'>stream +Y`~w'Ƌ7t# -JEλSЍūEU^; GWP7H\7E2Ԏe+b.Z4$tXy}<;f;~_gAkW)DkӧhjRp#v;yEcӵ#ĥ'p3MGAjE +T tC5݀(g6I\"4X&DX6ڃn0|8({ܔk={X~5l|(P)ݯr Ob^ UT4d4릲D}}/R 2eYY<ڊ@ +pGsڗ tx NN";R[a%j x +J1#Ab MjErG›'CX;^^di6_( +dn +b+%1ezB 2tiF:ǣhŨx-jXұq%ijM + 09HP,δ_x%7bX >]go`r W]hpJ^,hO@ņ=w%g| ^Ɨ6z< +S%pO =.T6ѡ82 MK,< }ֻ0ddQԆ#J#bY /˹"^v'F<)ј%'DN%, +L eDf9sÇG)lƿF(VQ aZ2 +ÃnnVC% 9xbFThs%>ԹZ,n泊 W7F-W[ggԨֶB}6wQe@,d-X/gI(K]0Kˈh}z , ۇˠOCrZ$]Ht(`ji +dwW!9| ٗ}/3.MvB#~>^JZMr%RIKV5#sL$!HhEu%.$oþnK,1w37(ݍP)J΀bphcR5œ[n2JW1'-(^+ǿ?goHʹʟD0?S2Aih, "vDHnFpЬMi576@"q zVB{`'cT0e!$@A#5-6)C &{oO\9sȐ\!4G4uF  rWV&]?Q!KO} +D]R`~9c-mhL#AdKa[#Azb +rz}^7xǬy1A3oJ-Ӡi{E&86Xr?ZIhGq=G=Gd6,'(6dz/QAI1GFf6&ƧitҒf!+)n;XsJn%xD9=Q9w3*x!gH?~F4mtH+쨣aɖH]+r6jxKݫgʗ;5P^d0yvL⺼UC@̻df1 Ŏ_+Dz3+-@ ۖoCCLCt1cVN0AC5E8=Pt + R\:0 bDB}&SIC %L񍰊='}D&x<3%K&2a\\U&crkϊFQM"\G0C݆u6R,=>6'BA4ݱm?2f'oiϘ*~ 8:ݯ_UwcR̫K˃;|*iCBcF [GQ*H #;hIȀ(Fͻ8\ȜqOG"V9%C`LC w?cDXSɩ9J!/ʡATpcD=V-Ɗ9Kѥfܐ p&x꣞6\isA8xZ*|)]ZMO8 &2S.p\ ~RqQf\TcpM$ Ӵ!c-fufARNʹlǵ&m$6Xf%Ѣj&Ғ"Qǁn5&ʳ~E=7V)Rbsٔ, XomBC O֕ g޵" Vmq[~?J_*SS]f/'q|D'q?Z<@0[rό[i{|YT4jfڎo8||6g;*܄Xfh5Esj MQ.g ngL]MgP3! 9 eJ$(ϰum7cI3w4 krl<)yxN{dxppaX0s`5еS`o6B3 CoMH4=TfDpR GçuMAȯiQJZPBœ>ᒰ5N-9@O\ƄmYeB ;6̖CAr}qwb\Vb~N^{p'+6FR6&KۉEw*_PИdQbs830"IoیeNdǻYZ qѡ&/l1VT58 AJɇK&K#ȪB y(:A!''c[2"xoR.`н{iqw$2fJw5JnQD! 2jZYݣ8c`'ĘfGK,ơuR1w1=]°`^嫴Yt0= +kc(o6 +7P%Nz}Bsc3@cG +M‹l CKǼ)Ä?`@ I nȍHb+Opa6tyjT OꂱR`0p(A/ +kԛ_ki쬼^2:'MDG"U J)-2TrmǪ9tmu<ͲIu+'Áa" Cn rshN$2z&)nGo=_ v%88J'0Uba[+$ِ_NceËaybN%i+ΈR -@C dIҼ:RDMAZAh*b,jM(]gz Etn?nO)BayvL +x<8F ch [ta *kh=+Ȯ,2v)Rc^KQ } O#Jȍ3ipb=_o|@ICEjp9,HT\h`RV$(1;HA`FCe1Q3?e4@X"d̊ {" aefx^$ʆ/f5%B]R¹k0OWAfV(ۃ4S +<8ؑOn@u㨢E<|iOojdbQndն }&]7P!F!&1\G*8l/g1z\QEY ̂^ϯ/T=Wn*l\5‰΢}TZ7աb W"?7<3JrIJq駗v'xc+9ck.kw屩ƫP"Eg[Ħhf -SY򣋗HR.A|4pɺr$D +fy'tM])Y #SʻEc.qAv BOB~䧎 l}㋎^ě?>2$'(EYi$(:Ѡka} +EWhL!Z L OD+?ĈAG-Fij-ɺ0cOte2^`Gzm@109 +H@cT,2 Q&tG"̷ґʟ1ub0YDy΂!}NFWl8'D5/ha{hqgӹm: +4$*xҲ䱎SMx/+ΠRSi?шPD%:OJAMn`)8X8!͎ƺ-1w-= 扒TB̬G=17NW2ip *2 q lPW20m{7\Y|;g5lhk \9݂V/$+!j-< 'k̥ebHrʦgޙs]CV)iݾځl$,A*)?d^08/^j9TQWidBٱ?q昃XbLq6ҏn~'gT[ĴX GdML~=D3 vd-<9tNcJ +뜡|ݔ+/ݑ2 bz_yRd&94P (,Jk2DJĩ]5=eO ݚg +Zq7V՚ҘX*4/yj72˭*ET'8}d:SI{(*U[CZ*R /Z'>Mm׆S%󁬯7P%Rnf7Mhmj/L"g̼|*tS ],;RΒ+8A ZeODT`Q,+d@Fgrdk#OLH\@ش],5pzhڳ6(̈Awh\zѿFh~X +B%ҴmA +I;0m8KDC=7K`~:.LA03`TWDWFQ MJޜZ鋦  ЪO_t6%YVӜy*FYЃ6 N8؟ƈ$IJdIA#j$ *KfZ HG]?B [ݛniIo5hE1+Mqͨ'[Ts ,RgT&#hB6E/YE_0&KԬUOt:3IMD!KwJF9IيXGv92pA 7Q* =m@k0/]'s߲ghΆs$*̽]\ wF\%( n, ]q nܤP$X7tDoHմ;ٵPx7a1=U/!jkE7)3,+'5ڂѓ9{ o=7FhYumhhd3bu?ش;/|ۼSA]Zц6ILڛފkv)g|# -ז5V!6FAk,)L|oȕhsU}`g#PָplAu +!5&g\>. #?cC0DmEo͜n\>qHgSҐdAvE}=<𠽂$h4T3Xd N&AF(E/(9 Z$j(o +z{wF,<%o鳷QaH;SuW̋zAGOe8FVD2@:R"dQDr̀{#s2')kcݯj. } +ߩxs7BqC$]C AdCvWQu؉kO`;RcϰY<莼y9s$ f +P0Ҵl%UN4@w8+l(e+ 9 ޡ m  +u!=*f`Щ( +|o#J֋?#}[{yѸ. E68ۮkNQ r [;0)upqEhR%?7X@]Z/mc}t#~,8/IYAPf(-3 7݅JE_ۧF9ت0jf<[#Sڟ{˴`}8|QU3eW[Gt*O= m&[o1@T]N.`3Š#3 k@aIKe"mrRN :TY00N R%s픭 +1$ iIb8YeAptLu\*;GdZ|_b@GM/v*ձj Ks'ݼF1=OMrC0=TZf"9Z6~dai~E@F/Jm Mn"̳ vW@8JH^󜑄e90p=Eh++P_ކ硕f[Ay9B00dr̴K@if@؛̼~2ĊHmrQzH ++-LX[XA+M.M!d3KKؑM1ch8r80ԦHՇlGHHb֑4ECh:hN⏚o5^Rf-LWLvK~9FH]WgG]Fͫ;VO۵ D.Wm:/9 w0Z_4( QDۊo {gL(1tk \dYa8ywpXDsI-c%ZڔG٬&V(OvMswOg>N-LȌ:bF OwtvVhҨ( \\u}s)D@PcۣpDKF=jRne +ѳ((xf3v}h +W?VN=)X{±D4зQdnz&&ӝ=!#XJ +H-&=%C+Xl(dV1 j@e-%Qf9_ EmT*nmϡ@!%^)оQy޵2X9D(lpQZБ|SHϴC Z5d-xq"Um[.b EWuga}0 6fݣ@ѱϻ`#/DK5h:&/ BUzDSiEvno4Dq]qynrGaSYn3뉎:W0?!q]"|CZ6 QdHY̶Jc[HTH͚@`r=hW("d:ٶ+qgP/s˷; GTHb>rBoV%@3iЏ}NUͫkٵ)Xe}G ( !c= `PI!M]06$a ϥɈ +#oQNyO,S]bn~T{9%1679;F):K ᵷ24Vv}PMC*OA=?=$`#"uI{ +UJE XŋBoްw,% + vL0"8"bF|C6!XBkpro\aۍ+HPR- Q,{nt [W!(y84J}jehv vDV(y[h/&S5^5]Ibq(Ƣ_i`(Bv2 &w~m +p e6=q{Ϯ+iHтGD&d+ٓPۍGUomܒv8̱k%5 T%N#u{s}; 5& p+"7V]EW@CXH$ +cecBMhSTVlܸ#1d,]OD$y)n#IϢ><'Q%`Ȳ"Mv%]#); T|tǎ 0\K85[P~$oGk9 pQ  ",:Y+o\,WL=!HOWqa`>ES8)>$;_? +CA\K{X"y?5T)ԡM7'eyx5h*R AF%; 7E62)+ơFG3tŎ2'm)7.! ;t>'B\$JBtȜDFs\ $+VռP7lF^hl[U* ~NaR066Q,`ըI0}1vv$!?e Q`K6nԬwR֛5Q +އiXrJm/ +]TqIBN@LUdf9e"ʈ'-)YP]$&%; fm3h%"H 2 +#ۊ#pUtneeAL4|v/6g(daJP'kfvuצPm 3a*KތcV`U"@̂|$㵕8.7Խ R4UM{hmo_Q$buJuUsII;vC){g5oIu#5~miLq(J\'k +f< :~ԋrk2[t P.xH3"WJ^6.ĶIF|iO6]By@@#r|U4iT^xJpI b)H)fl i^-8D֝hm;AvOJ5l 1ȫFcI5g{FɒOh('Ptv8)2SRLe!lsTl\˞ ;GVXfnYAvD#2p>B&Gp7yپa炾%0 k2ȝw#F=LkslicFmCmրh7Hy2B] t @D^oӠtZś\2G Z&6Y"2mfyG2wG ҋ<~3Lo3AC>qN9~t;<48Qs~['ÎF`T,bZgٲ9ܛ1a: ސp2wQEaʏdfdL?b7-_1pm 99CSBl{Wd gy?)2oa +$'% +y{ +Sb@xlmEz y٣1PBi"٩1OLw84Qf:+$ff(vO%E/rGP9 ^S@+n}5-®;}hcDe$~a[`Ρ/YpOpH/ӍfV$ x\'D_v{یr0YNQ 94cs$<:R%AJO;k< y05Te;#ET©8+[ [+}6gJ]$aXJ\R %4t])TYw;5L_ۆe>3q63ȭ=VD-"BSO496)iB'uD|W"c@m5s[FXSyc+h a +Lzp;/ #^n]iǭuuQ9_Ix+-Z};̸l"G(eiԂ=١H<3m](_Dw؂{d`U!gTƑI+" fxbLl!=$\ pіVAP!̠,P Ѓ^ +,XI+6i}_<(Wl&8 Rbvp}mH\Db1“ fD&t[0%)e}|p 6auC³I~X@YWnl_p-[CLgl}kc;!щ^*vI MDb 2-7u O,j:ͬySC70)DA#sGdVɷ5/_v2~;9S]m&v@tYÎ TJӸۉ q&CXBM"1l)" +O+̀>~By3"mcwq#x[dp3ҹCG63n=U?ZeKpzJ!kOoV)F~@0[r2$GN:D"űQ)Dҳ$z||'DZUb5DPSna R4qtYy 䳘[I w.Ÿjm}J湍}*_ Mim, Hz_S5Qh<Į'kq<(.P~hqg^3U+w PvN4 TECl%x~\C9bn_dPS&-栕&0a )ub߉zK/́g|Fv#^[s\.lIOoU#:2Xjv<Kw -S_|NzھA! rse*;}$h,fsM)ZrHw(/s|2ߍYi?XܘWTQ>14͊9LHBnuW^w=Նl +FT?i\9͒KmA 㔍"'slaaw BެC>!WG{3 o2MJ=88u2? &'B +SѝTsU\Gq9vG;m\j,/MSۺp;eͥV`́%}FpK^U%ق_ qǝ>e +aМ5 >{K,N+b*zr~12F_ݵ(_mdjwH v+9bs͆!i W.m'SR%qBs| x'1WDQ_愲rt>P@SLM9ˇD3?QT gЬ$hJk|zXf2jdjErl'k6M9'{{&QZHxz.6BqԳn+>VQ6RV yȢ2K)♅vb+w]&E6gN/Re*&sm' + L&;^[3ְM*ԇ+}YǍuYie%B +lalW4WҝcCICn@oL׋E77X.sCg*e: " +=ň;e&[kc΢gN*ݩm1IX"}O]\rs{ż#n|Z*Q<c2ce +6k)g)1D(ࠑp#zFh.^PDE6$C'mtNO0Z^;ז^ +r>;t˝ +KRA NF'ЂEOnR?uKM޹ 9; T| VTwnʠ!o>DX_o( z1 >^V'P&Ch=f+\"`X&/n4@lL*PG+e8>J +a Tv!&jRM*n #Nz1j0WϦA8z2HURy> ^C0BB$Carx`H/_2):p`MTQ؞2Yncfnz0Ơoyb ( 77Z,߉G  +\lrmǴ19Rk8W៞D#]qW-xPZ8#,o^k$|Lj~)ocpCmͬj!:?o3PFx̮H =c I{B]-X)fEFAʯ$iG2mI%i F/xa0$Ƚ.ź)f晘KQ*6T]a`xv=& ejUBJ=dEb4=&Vi@ɍ>3љdf-;PI'Q1_1clsDw,Wy LFDA4GԴwg'V}lv+f+z^/+XkWӨ@<F1O3oAF=zI+"mxC֦w|6LUKApph/ۆS˕Y9vCR@2 + A(~ ﴤ$޷ +u9N@7F2sbNCN5C ߽tot4tq* +T@%jm='c,;a#sJQs+5W\\i](3@(yY ڹ^*zsx_Pl*/V/` +zJlnw m/BƌDo,2/ØL~һ 2,^o,Hzӈ5}M7E}t yͪD @L8nho1:AQ5)h{;] dHˮ9;@X k\f:ّ3XxOP/ +JfZ-+j(%T4GK @4| )&v>@/Y_z YM=9e^H!ȿ--Oh:wVe4fh\}¾G!(=؃ZiY-H]+{1I6V-ĸ껭ю{"Ƴ9# +bcO2._m^GqX5 )ibZruDZ+E<5B b }"}˛hN-1!o_7)C|j +Ej|@~ӂRXӡp5z'8XK2/|OeJb=OA &ޱQ$r=Z:@!T{7[ tț\tkEZ#mh5Gf$rJRgOFx$1pry423$*Lԉyޚ] bɽDɨ}y8Њ^ݫ1&U")]mw&}im9Ζ+KP[_4hGy#߽㠛]yR@E2f$j7-BN3 (64J&b)3o+$,@qJfr~zSQM2?] MS U MRӴEQU3Oӌ1)F7bSDx-$ ̀OI7}6[7Q"އSM,T^Nm^SP``޲-rVp\yAJdliENC7KY}&e)ڢ*T<*3gzVï:b`Y}NVB{&]2^b +h:1mѾ҉W^,@Dw'o/}5ۆ1(`Pѻ3n:*M$zN;uK#Gi6;Rmm͊EKB1^ Q<9 ;WmǩDN(B|{)- ~+I{LjX1gـnLa WcDclv:=Fm&stX#k,_.o3 \桡4tx-Ic8{xP{2HYB(6~AuhzGw]HxDCh!#vbwJ)aP/Ov5td'a1qL9qcJvh;yƋM&D?Թ,=0ztmrVig82){K'r^*-Oأ"L7?nC:&3>Vչ}Q_O9B+'h4P̸7$|)!+Vl0wn 1BhCrK-g !W6›JE13(J6Ze@ p6۲\`⏿Sb:Y 3 ֛g&˯[9ldF9% x[s/{J]D Ԑܐ11/ P,JGW^D~쀻ZѵaXY#.~JxzEqt7۰`Brę%N͖O$8wЇL4 jf~P1_:>3VOL긴y ++<(9r2T?ͯY 03\Ā9^o "TBʈYV7`y& Y).- C*L @,1q+!8n" ܨ/;aCGh|.L:lVTY0JG} +\K7]}d8v#NC]$@1ۜEjзff|C#^+['ELDxK6Ζ#{Wckѯ޽>żb}r8rzkbE.|XKQ |$6 }81Pf ϢV. *!)ȷSҦHBi _Z~S{U_FߡѬ{^ ack3{`)tlQ*!y 8^0l`7oLWhZhb$'#Vz}(?E- QIJ2^&+lpO"B3,ҖvNjSKՊ2kݸje}h(]@ӸÎGOOǎi$HT߃v9,Vz [VToYTaO\h^n+5$Vo5n _]ދGB/2jHۭYױ\UpM_ o W4(q򇖽+:d#*E'εF(|e[R]k9>䦿.G0-~Y mݐ¥7ve9p ӿ%yP)VU{hJG#$4^ͭlŸ81 oL*=4A|M$M7&Aczh`5FhtU|k!A,8<0@~f՞ JԪ!N_+hr؄ +{GKd,,aC~:w-]F+5gX5! +E1M^z(K ]y脂O*IoDbei-sI)F@&I,Ed8*7T*7O$J6!QH$\y:#ǢAM%wQ8,N:V9t-I6vznu<vZ5t8DJs`gq)XԱBqvk|FcI: M mb8Gf|σE7w#LXK9Jc+rbX%8-6Nzs7f4mG~fg+h3SFrb_Ԍuwx*ܶ +iELP_>'TwZ +] 7["7!x"\"u 3d=Tb[0;05fX>&iUGxH ֥}vq{Aj500a5J.8e%f>/n}kŲ$oTG)G3!V9!jgY~N._3R'i8@XNsJ􂅷gdC͏b`@̀ A)Fz0~hA;nHMmyВ X xY vb^Xn ߋlE2z5f ߫#[UHټb9ю2,2˔KG(c ns VϞuCBRvYR6щOE + @Q5F-~:NBtp#+&v!Q>#VNyܵ,Q ]7dę$`"h:bdNd3iI1sBh&m0Rz>p,A_-%-^(@!'&Y>Yz-nXMidQ:+#E }]7X"#4~cJ#!Q<" KzYw ?#` $M5~Oh)!Z.B\X=g܁x? )^nDjL3%RN#*}Bxt1K] Ɯ955=2WOcR{䄾#^7s'C4;*ϋvaa.i;̋"Pi36HHw ec"l gN\.\\Oe!L,DaRdF6 i$oG)cDx CRK6{)yjm)յ/k3txD l+6|w}pk`rKk)pFHƀ|~xgϳ1in+s-!oqw2@1Ļ`na[V u\Ql ZYdlg}DVPw~-2*&1rAt47EGlG۷ !`B +׵K~pptF[?sG\onWyo"2zu2H™w\{%Ҟ҅zPZD/6OEZ /AQv7j-oLj ÷o\cg ܱ35͟Nk#йQhDu~1QV9N/iZLP EI4-y5}HjRyrT}+a4$VPжTTx*apRL'S3%  +u69Ǐ1G'[=B8aN^?*D-!fхcP"U0 ,b0PBhM3{Ż>8GYb / qhRVA,rF]*}ۃ=8B4H=B)ܸNv^Ku H$7Τ*0?4?f2)Y} Yʚ\l͕嫒6ЏYQJ-Yo!+֨* HKDUXj[G̾m"} 8Tuib/jPBr$' ݽPjrO($% ?9?>*` oqf`G]}ki~n;&`w`hUK"hq?"]kkxz.j= + c%(3#ULjgVy'-8`_˻K1KxqW:'-T\fr54CxDqCIBzna[Up-eus`c&n* pc*Ipz*PL%xb##Ps>6 C#N!"ka8nWjHL0@ MiHWxAL?IK a#:?"aGnW(=H9bE9n,rx qu<c,^P, +.(Հ9w+!炿F:T SNa"XجYCɍnWߜ=ĥ8ۏ r f:Y\JgmSDـ/)kwZx_&K"|&w+O4ϻ;?;Ryj T[.;CO`*,'?]wc&:\V=l$gG&ȪΚKjަ[#lÌŽCi%ڒ'.`aVYr[<^>Å5:'#U8-5IѶU4[!~yyr0M.keIj'A/%KH6_ MjZ疡T$Zm;郤MC2yZ +!NDٮ]GW}w%l|'ו'hu\3 +gj<+p+iU*LAЭ_'4>])47,/T`yw1+ݰymy>Zϣt7?Z^j"(qƘZ=^S js r*)D͖deևC +ϏC)I*(?l,4u1;"{_,Ocώ70Qg!z]b{[|AAx |ABqŧ3[ܓt"?N?I/6 SyR6J.@A\Qf5+·_F,:[Xyn :}hgbG`b+'k͎'OB8?v@qrג/]%X@o!! [ʖ2~bxQf +̤*SWQTݫKu; M, +GF +@(NjhcF6ˡ[IH_leVꦚ{d{ܒTMұEj A#7pL1ЯFy߂c+@`R;Ԁ\@C/dNEuJjIǒeNP#& - a]hJc +g (߹k*L-q? =&d)۷>80kˇ"FLO 鐰U⨝QY"5*boTB`UFĈQ;k*~eRb(j}D̷Zhfof._ *+tS|X;AaC꺒C_setr02;"`.`G{ϮEדl^JYQ.XZrc -&Al5 ;$)1WcZ\;ŶldYof[Ao/_s0SvՀ*O Z e;1XhW%,mGuBwMpZ=9)|7<o +R'd3ھе@ԵQnW;Oފ)jPvav\G !m P~P `, ݑ5"qsJnUlҙ`.^mC5h?7d_ҟ S`74z_%\l!רQ:@2V?n#N<;sNR5|nQi3: w[LW8TqN`=t [WȨZ@]#"xtN#2=0>ɰ21s pOD, :TDbߑiq1p j7,R:%$ rɬ5=jGXBHBPQރ)'?h9Q!+&Mr C.8:_B~[T:O 2"K PY!ȡd]ffQ2+↸ۓѴ 2Qn&xt[ },]G,!`@UyUV]0Mi8c]yp c0 qMJ +nC]w8 +xm1^mT_;06NpӰE뵂m,G橸&@; XlRbX҇Z*wЕdI=|2o%ٺ/@QTM׈EXPv~/D1;<_1' +Exj?h5xx?x$/و%!:T;M +:u&3Umic#&T. oi?ya%.6fP%!|p11 _4FMר/S p>Hi&B{xJ}73bܓY෺{w bA?2aEtW Ιzyi]0n\1]1?2-.r* )\$]A]RY.: @\F}TRuܓ"ŰeQ#!M!v?~`hB-AJLD|'p.$]P>9eeZtx|ˇW.(^lf@5?R;d:B#? +Ȳ[" |CLnN4A07et:21ې3Ս7u.',~؉zE6k ?X@>Rİ{[@mꛨe,Lqd%ʛ*J +YGU +_L~PIJں LqD +iI*?.[dJr(Z?hM,c |gס-niAQ S;[Tj_Њ$ X>dI4Q;m)2E&4)=5Ӿ@E}\d&|i/+V[3"'JVyk$`1d h: +YΝsH[~9Dr魷<&yGNu@Y+?//|yHĚ֠;U=*3?GYtc}ras0+%H{Ћkk2"=>߽QTe% 0q7EeŜuHyARAd@O.t癶)<ՄT ̢iU #--E6BtTaգ] cn +JwOvjXC_FTrJ'vsª}[N똙h! E ݺ=N,|pf6tsCM =<H+=ux܅;a +f@ʏ6lW~|XY63Jۮ +57YvL1 _D&y+H2`I";'Ihٯ-ͣ#5/c#^9jБ^}LqK]c"&zϙG!xD\.G+TGTe4T޳ϢsX(8;TUI)pOaArz[`64'pq!#ReJLUa:(4Ug %6K9{ZǀLG9}7tc*>703',$MGgcG&hjlnp< +m! +ZLaSٺCQ}w7.Q 6W} Up.Z#ze_ + `5w` `y:-0 Zŭ96%h2\4[=o`:I/WR_}ːɦ,Wַŏb%"xE9-Ҵr?rX2(F9 dsbn+gq>hD3tO Tqq|<'P8Vpa#pLr?4"rcPRÞx‹gj,օ#ˀnqht~*~>_m.Žo烕$5x?$@yvY!MaSq.c+b7(L OK^2Ǩ\=I ȸf`Zt +,VCDq 3~ ǤNX-:X[zH<ҷ$vAXnG_ݖ>֐dTܨdhEЬI qgtZp.@D}ƪ]gͷH +}#IܗbZl[r*E1TnǮ?Lo`qC2cH:sggm#G{*>{r3D%ДD_3f䈊uY/lkceܒw(A/JD!$@ d&"YЦCH,@ + Q̙$kitdBSr{?3'e )xb<04E4 +Ot!NJ^`{yfqɻ4ɕ"Z!ՙ&%H(WTBH㦛.茌XM%쒍>\B->ы)9ۦx״O_k\*Xã<<O3R"K + q?h@T-)+JǏ/ިG(g3\+5Z!- +dit97_KZ䍤YzØh_T>mL,! ̡Ͳ&>T<:;U< w vgo@9e~Ua)HZIss|!rZZ;*~\"XiSq7B1䃥Z \ݡW/iO_æ܄vb쬨%!g*lu9?݀:}cE}ofLg:+xJJ {v ӊ1ƙL|p+/B^]$ߒ]'*_s Ȋ7]E{͠G3.R`!#;J$  $:ɦ0hCtS>Wnj|(q#NbSU \8L` 9 |:mg +Y$sؔ mji{hQ +(Ҁn"i4~C2.'IRL<d䜒@gtAND:z7Q<vNXFbƅb?QDM^4guv(/0{+ {\D{KQʩxXDA]#u&/3}9E99P\.А=#Cɷ 4B# : M!_j@lEj QBqno)VW +nV" ~©D\_XXşaݪot>7ఴ/&잂!B"`ZLKŕ[.JMKDœBO`tMHt@P}Kxj5Q@MX7R bLjU?ȗ9n_DQ"wYCZffCfnvA?]l+O򲂖HF;D^y|M`mjn!&u6^#[.A"3sv'$/i]rmqU7.YMMEa]Gۻ{`nXW"ԍ DH6\**Y$eL1 1*bP~:AW"Xhyhw{-^DjphBcDu}(A?xhNљJ:ҽ(ٴļ +~wsСͪT<kQ4ydFW^ + +(y\֬Ulr/;XaFJu8F&nu`ۨ#Ə:ڋO i5سl 'J݌4w9ǾrvǧHAm^:jAOnBMTHCHm-Emt2:ok۴h 䬸R]ԻI | (ÌZK7sڮ_! +|2zM!.l`gIkFSc;< ~1X*;{i}4XLN.Te>ndc'}14cS}"ڥi=ڇ,~3~edvnj[#kF8&lvq%rG_:K>NY7jSr&]+EF:drw럊]䶰 6z'-zaA1$ljzpҮ&4Գr"ڭ%Me%DB!+Z/2\f6S؄"}0Be,$h^U0XPO{IAtSlZ`ShN<3e؁bnN仓ICXWV5v5# +Dɠܦ#"3w%< )3G(OT?-qUVj-Ta7U{ 2tT$q^G͇cdnYЪ.&{Gؙ4>eo۱(O8v3M/!3PU@6YSuRhv%<ϓlv6"?p=]f"H^E0s!f@NIzIMJ +C@Pm9MF?B'=b7Sg]VixCh2: x" I˯Zj(8Joq|Bt,DHc~\Ii X#2Ѣֽe:2;ҽD܀,oӓ,r| =WCs 8V!$ƕ!o) Hܣ 5B'Rĕ8}f/n$d!ߘvxH.B;p74& +L +6T&ե_-٨JD곴KFp@ h'"JPR2^LO_&. jOpb%:rR~ $9s֥ܹLs{m9 +3rR +!D u`\(΁%4fȜ3d& +?t!nh:ȿݫe2'gWpP*B@FREM# Mќ-w!_a0bL8{Z;bvVxY=Pg89e%]{y˅2dG +7Tݘr\I 5l3+G!o!q~ O&Z׉rr[s'jVLOކֹ|,ȷ|Wۋ =Kh|{?d|fy/ԘXn[V-0NLNiU4+~^%Kh.X3lJɐ +}\ gyvݖ.\Cq5 yZOBωif{8fFT)>906{#฼).33xHi {1.zKQZKZ3߾Lc|4ST)65:H;f +J5[ Wf!od s^z>dEniXjX 0(r˸/M#h ERnv}JLÎGn^$oPӢ P!^dPz%>g]E1Ei/)J"T5)dK(uYrx©' +znj87` $p>dX{:sQFssZ\ѹOv-20r&6*Dfk.Ԡ :#6PMG.EMdZka_l-@twJh;p1 ~Mxg[n?! QUk])w9cXpD:H(anŅ+d0 +kHF>`ŕCKn Q])Kiw2G/2!t'+b1*~ әv1~Υ xHXt3\m{wr=&X Ev!zBÝ--PǕMG'|: +vv-g1(p(-Vhg=(R]`3 +$JnnNxT tZwL#Б^cdB6 #XJx:Zq W8 7q-9dޝAZ JЈVf@ą'°7&0tO[7g 69Q_Xh?XewhREǦȀB8Cc<5|uA:RMf''zT Ik=6slƶ DjHDǫ%* k %͟ɽΠ<,Q~ʴ{)Y&{c83TLc7FcT&r 97G\PwRԚ=.U|Hԃu7q@3V>]01#|vi3aRo!c-zl1 +t(߼&9S+0(DTJ~]9)@2 kkj!hɠ,}'4J#qQXy_67cHfӘ:@xF&ҵF^;r,oc'`g|`9-6z kM]ǔƝNП@Yf[{pN8Z-K=p4jG dj`R`qҩڏoY<9"Y|Gت15kBa>%1B &ЈeAĨC}V͜-5c>d_Iލf]Ɩ戇=>(o=~D^_MeWC!5K0|Uomx9  u.$9~)+$H}}|5}:7UܕBa8!)Eg5POTs5" +{S݉rM/}r 6~7iyqpvW.zߩn":':˿_84?&Z}Ͳ|mLNqlLvsұ8Ƈշ#n' Cl_!bZ3u_mߊ~}&e8ݒ485H]%g > BuY>˒͓*MT2͈Hur[aq(BV hyKto_#r.Eŷk-$q{x=1$~waVIleC*\+59$I"h(45 l  +,D\F@B +d D~ 7!Ρ: D zREcPM'Z>y#*&{C +Nhtܲfoa!^'5G9[a7EsP;8n w={fhO䓂 -=v>Kp QZd;ç⢚K %RWv`ͅL:ep9Y:1<*١V;Ui'34 x2]wc-%rN'7I!508+')c@ v[d̓'P l\Vm]v%Zi^ l$$y (;8=z"h¤\nw`N?w-.Bx+hrbcXf0JFϜ&%+sM)j&0!mŜW +> ҃'DL:'->4H'_YHK0vp@k<( +zY<2G9Tp4dm3S-"BxS];6v(2ܢ\oc_2⌎J5!Ly]qѽNLFc<~So~lPnZ=/v8/-UH5ۯ-D#eNci^E/.ψ+>]"%_p%O:7^ NQnvC[2?%+<7s$BifNVh:/;ÏCfs Cs 9SIc}ml$ ggaψ$k/`sW%{F`fTG ti9L?.st +B Cl>6["Q)(c&;xrQ8L o绢U$"45#vppiP$x_=}/8OvB@Rԑemdfe蚢o싟cdž/6A"Jl­6=ǿ RkWJ@rb1S -fz#/D3 -ZG5|"3.0 PJ\{h0Tv3z,Xϱ6g@ qػ Yf9n [qbEwxٛd`Zaipi2+cFI !TCD;Ζ )1$"\?w@۰Qe](a(#}9 ,YP$h!?$L6gW- +A#J +ENƈ~({t]>\Mu5T1;gM|E۠\Iq,񈀇;}v&oM3w.yC?!"+Gvd OfH:aCJtsxH2tnNG` i9=H!b; ;rM6)mOA}L@#J]Ӭd.m\p(M$g5b:ºQ&d\ZOf@~L$! o}#60qMb) yfa H<{pZT2`جMctJpGn}@ n`QALQɌ= -S \yK@Ɍ^7ѵO3L1eȐy^q5eO$CeyS#JV=i-"*j5kLZe"$T1ei : tӺAhpbr l ~X2dゼ -ޘWp e.S$R W䛂牑tb2]ͤcF:H^״[ +=|eYdҪ;.,931vk9N0TGqœe`_Uߒutp^T4 F1NWh7La:ohUgDo8_Q |_hȽv5RsxGFNF5RbP>IDuݵܶ2V;82+e0)V>K#l;~.Q:H h\*T#gd#;h.ٹq6E-rk^j!bzfђ;TO?o +7744׋TNJ {*h觬d~Vr?MU{5t +2+}DWƖ`Naii xx,g9CdI$8 V9oF'.zje6Zׄ4`2C6Oy3b{"vy0wIvE9 _eI"WO'g;2@XXGL -ȭ`MYLPfNu]ޥV SQ 1 Rĉɇ[ҿbENNCuޗ GjrJ^E!}b@E9'fv𝅑SoPsuTWSy%a\!I,mB; %} ;r +@klGD/Y +?Gj/u5^b2)i(j^Q˗zNHM-k2hQzo#2Kφ ۓM}\WL%\E?%գb>?-^0[S׎KngJSiVD3qi /ĞCGnV 䥝B{³Dt93Rv?y/7hOiTN۟+<˫$/#$=-ܙ1haa=-ΞdkjvUb6<`Ɉק%@$z7oY<ˢu}U6$ő/H|B[p,0cT""nL}XhR-ͤnJaocm 4@A}"f.AJg;| LĆ``6rȔgvjnئ&i3Hd TpZRjI^e\rݘs\R)wzQu Br?Ýe%N˵ b^p\Q`XS,%MI7~E)Y]+2T9<qF #g ז}H| Lۀ8iosMd4^n&g]PE( +"6: 20BbHswh2]"Q&ַ7=1Ҍ8*茨ߕ8̘!KͬDzSvJ%16Z`^&o7hh:zgGQygdķ4ȅZ Ϙ}VW!҅`/*VW uJ`!47rcE,-ЊFocQl>Zu=L\<3XBKh$WQ\! ttD tpBgܯ}i+3zX,W=^GpzXސEh>s],GiE/nt7 WVl!]>@>yhqؘfSbr΢ᦈWP:&!8LY#*?UW/FQVhC}gur`1^ +IA0kV3*)rVpݼ$X.Dȟ(׈Q$sݣ Sզ)9#Mg!m7k +i!ˠ~<TBMi4Ҽ#?#YS܂:dGhfvt)`Б  X\⠄0iϡ'8C^t-1(\u32% Y,u>~OZr/RY"Wn< v3LRE WSət.[%Gƻ;`(}%dJV tZ3j}5cRh? Ƴ8MX'6 ;TԈm7nKhC]b&ְF1,cfQ $'6yn,Ƹk?"П?_1;Ga_Ϗ22+XtZc| ؼ8T+B<דtq]#Wq3kך}+@<>/ s#PuŒZ4/av 7gK z+X.kL6@whuIaSmZ,p\Ku`ˊg5! :@ _t$s7us/ E7gpjZ&) :W9H]Hev>17}gǯR%V %f_)G%${wWh."R+{4%*.^+¡g=L޺1WݞB^h4&`8hE_uU4֙᠄خ,K\F2F+ \WJyN) V¦84 ʮroHQS7i@7슂X>喴=5"U7)4US8K{~a Hʨ4ݘ?'^ n4=zWE$ RaiMe #zORrt/٦{"%u3s8I0i\p[r҆񷔟k\gv .!C6Cp^`VǹQSa`ڇݯXizU#n|)<%V*oc=^\N`B +i񶰿 1>GL.?$4G#*"d} 1ꓐL4N2,iy9+wX˂ _YI_~WgsxpGZv12J2NҦ1:)2񪍈705;井QGijێǶ5$L2@#|8':BI(C\Lwaܛiz編0\DMckuA5kh| "%\EӁdZAus +pf]t9$;'k Bdd$ +"Я-ac^`b&/"=;62s~iYhY_̔ƔoEkD$r ^֌jܽ{ܳHE%&o$)t#%=Ʉt2AOݜs B&_hH˝w$@~M2!QڀI0*1H,y3_עҗڔ&~"\D=?B+fZ,s!K@Kz^~Ѡ4~*鏖1<0zw Oo#ٳŢ'sWTP)l1]lDCժ]>N0nP+76xnivs~K|;~8b3LfuY bTwwuzhdQ@v.6/BJaoyB0&"j_ 5<뜭4`YMTz9oCcnL념Ifơ~貥+1Q_]!#.ի='ڄ!gf!*#7G[5 z!_`?d33tsz7\cgbh@J`gƫq}ۯ?3EsQ}΂v ?̫JJ~]S|z]YFz!o4PGdɞ__Fl7'6>79ٿ8lc"Z +M8#VG2 0`D|9*oI fMм. +ڕw7;unk Gt BlITIcwʲ}hTۺXwZ_dSaHi INj$(a1,V^Rj|2[2 hbgEڳ’m(Bɲmi 9zQyAm mТE<n) OeQ\"{Ks@RB K֖'YaLS!ȤŪ-] ,]ey f|ܴw'2!\ +QI⍋$Ʉd +*܄(5_RTɄmw7ZAs)G䟾 *$' +T*c![$oZI$lՁ QA%!Ki5m+HTMOMpI]5 sx?;.ҿTE?I0K.j{Xx6b+z?ԻkP6eķ (źJȚ7d\Ld MI "E믭'AYgAM4vFDkq8^nHxYk9Wx|5cS6-}1OϘb*n!tAzcB\Y|[fp/j'Xb /{eT_щM8-*vVDc}ssY%QR* Llp7|-Y-w^~y[5.R۶e֌F1 8Kn܁G9@Ia4d>;Bb!Վh +&k`jwBc|nW$S4A-oy:/)Qgݘ'25_@hy ?5Hh1'Gۡ Hz`)<]|watJhaҐ\PUR&p3^B)QW}9PgDH>Kyk. BV +2Do1쵈B@)r.Ud%J$.:zGaٟk&.\he~5\]Be +b1U +IBBXnX5I5ˆNaD6J y#!,Ft'(8( +)O{֦f"m` ܡo8aSW"jBhfݐU}_,&Vp҂*b"g34q7Lߟ9[Е.W6 [w3"Rl 2 sB +Ȏb ?/%}J2aT7]6#B"D*U5Ѫȅfl 2(\8[ֽoH6kK6(6%dzU{G+dKtF(P"][d/ ;}=uM J 9 潜۸pa+б@PXljs7`:,8? pKZj5V-=R%T+n=楰8`n+.DGO,jŹm` jRS,tϰi~UJ %[*Mr%\DRy97&, 8zi4T%l)9<- ZĶ^5rq E q>}օ [9؍CD)ioDK5jfqNqdS"sHEWm辁< /V +- Mʈݲ2D?Lfg7"?Q6@5d|"\7x+P%72S%c!W`(>o9fLPʭS#i3HkM_k\(.Tbm=-dm~E.4hf#NzU/@H>8"S;7]5zgQ_Tl%/0S$U6l6dd8b၄ultT-٥5*ȯ|{ 2aE$J'5yf<oNT肎3iL_Z3߸ "Bfdh=in)bX ;(;0^ +xGmn~&c֤KC1r$;N8cͼw|{v}y ^29k5AhBC6ͯd +0hDOKEO$PT0W=/2Q4 +sbtEmY83a҂uF,J7i^)!ýtY(^o"P1Q7e|q;TQ^d$m' iVq~!3Ho 2 *sX>uhB +1k5XN:A!Y '5JUAН7`8Qq +s%%$h4/Kh}x1Oʷ"DW.9mipky6O= &/mԨD*"a>rLQ̈{bygxYk 40ëFlb*`j:8%Th9C5¦ͻqȷYjJ}KlaHupb|3P܊, ;A$ 2 HQ:&6]6-axPDWۡ] -,YFt zAsƻ{Qx8*i"G'2!zĢ?m%WC +5w':P8'#.4*^<*b_nzFG5#SIAx``gY=O290D +а6@bȍO8}'hUu4Y,}\ +jFϽm|\zRJSތ`ȶ,|p.ƒ,uz~ėjX_ zW:bk-qC6h\4` NcLj?#ɰ +dM!![3b3擼(Y9a4[بcE_.81`#3ޏy5E]3j0뚸}Ķ;}#Gr["&r 5myf*kFeܥ<-R\'vQF@rCwaA'qrlPS *yEX&"lj4ZfV r<$p!UwPfoNI:c;іsv"@ ޒ 85;2/(;9s1İ: +ng/j5&%ЂH E +7tJXy VQ &`o: +0KdUeIu}ĺ4!zllFyG$묟 IH2Ǘ9<ᔋ GV[VbZ12j +MKk VWy}C"\p"5O63!2JU*Ьm|te8_\Pj:#v=rn& 5O+a]+$2,L ^ș{OM +JU*;5oNh/ڼ `ԍeK\@P | FD1dfDYeq*R&>h,$v#]TɕgzjF/T\Dl6uԱ6=r:T:srVѪjHo  IVLAbn]&fEH$Ϡ 2I$pI$7վ~VuK>n3R]ɂ(ef~B&G‚*PԷ% ԾfVK+^]Ss}OS~N/1Z,lό}(l'\PL`]E :#0=DsN-hnjI`({ Szİàj[ v +$OZ| .Ê_{Va{icYBVB$zi53$Y){⑈Q u:giebHyFь@bXI9#0QyJ= ^} UrF6KbA3%A3@9HԧL*5ݟ>V,M.Fvg~ZYϴ2჈of-CM( ) M=7\~5Vˆ&(VPɇ|΅!5ZOn=w7z1@5lL!_MP]oxO@S`bvrmk YOݮ갾>Q| `()$MpYi)jqDn76TgxDQ !4_DboTG4Z /HY'X$ ~.o& =a)?miyBԠo6nhnQ_",#Er<(욣\ +v9 uC.J 93O"ܕg3 g4Pif,`%XdvwfktoH!6yL~VtH1L " +RD@˾BLb6ٍf@*ò:ꂰ<60"F߭GP':K˼8G +A@[xK"B"ep ֽ7 ζ?#!P q8@/ U|v:2%d a^])+E,S^|gv9oդyk7`JFV +1j ; ]Z6'ؾ0XMp Vڍt%i<0D\P3]J ph놻f+E[ɫSoV@>/EdBtD7ezM]'M1fwϵNzh<.*k6>~$@MyH{h]jnPӫct-a"nd!`r l::5knd;+_:oZZ׊/< "j+}/k滹[Z\Ȕpes2(Kr\TEmyZdtLڞ=>fd΋&iZTNERQ5bX?ܻXyClNf cs;9[B(" 9z'cRhCBHb B4=G[x!MbP|FRJT" R}\u`nӆQ7>]'HN>O+v}Kغ_J쳤EіiX!-c~D< Y{S\vE8ƀhK!'^͆?~MMHV9eh E "%/lΙhjǠ r +PXqg[?W8J97SlHe-`(ȁtDv[#VA[Q^В%PZ_"3?eȂgM-ЬhxXSʳJ$ ]ЀzcǑ}|FUӆ=՘lE ΨY)eNm$2]hCVc_%v_IL#| XRZ~q7iy#tlJXQ}P+'#0* 0ͦF]iWƝo)i|iK 7> CRw%ۑ|6e5 +OQ+nbqucx?7P\HV3n譁 G (里ؠRcm. +d,%l&~mD YrFbKƓ;+/"`Ѩ L(n{.`s? H8/|]{88IJj[𸱺;]U'|@3ZemٯU"`uůBeOV #O`}Icb^s\oçw(җ/VZD!Ӯ?ǖyӭcXkf F$gnFE(׆|l::}:hc7%cGa=c> notfJ/AC@g 2KĀvJ?Bg| +pMPB-o&nK48OF5| >4,1`yU|^d|An.8vZ1$rB΂76K:5L<%g[Fg(Sg U1u:)Du:H-m e w8_,uWQ?*O8{# +<;` PU׸xRe$KW'Ajr{5~5 r @ `Dp~"Z+s|NL-(iZS"g|`5wLP;;͕T#ҡb#{|˒iA> Xlu92^Gņx@ZCmPB~9bktI dףSE)Y{')a{/AzF5OQGḽ>PszrP?nϑ 7Z d}yN}=o.],':M}8Ls(<,@/]dz?Ϩ_(=P1- )<<" $_d2`D Q9㠳 =Y*qwz2:ْ ;##) 3aLݝb ޿Z{_%TPn"i#b.తg3+"Y=W5lu0d>M1P45,(܉ + h!V$ ~sV@k4-Lt+:ڱ65},8=xt&mV};A8Y>TG}KX+iǥ?b~2p =TQ2,mCW(t&h/9;%V|Sۛ\N3Ck%ӽP]PͭlmͺfU!_N12Tj;c(u!i!9LsdaPԃ)#z?`v)tkw& ?7\Fq+Q-ə34^ґ3r,ѰIҀ4\xJdFm Ym(ܼO +/HY#FAqKd)ĭ VAizt,c5;^Aܕ +4E4T!-7̹.j.hE1K~ڟkeR;qw.IgxF34AQUA#EmD~4x)2]ް姿'b18>gY$O)4gib<#軼Z?Ħ͍8#^Р`gcHAܙJ#X'P O P:!,Ib vfYPHQ69~ n_96aR+ +v3 p^|X$c|/­ P?rQTV5"Z8%FT 8*jveUDZh  m^ߌ-)SNBOaih1 -j;tf+"Zm=ʷ-AͶmݺ5[vEۊ:#˩+=v萡۶섢6lm: E$׺mk띹|t^ +dgy$dZr!r*zCbK'f-C2̃l"2qJfcOא{A߭Q Og3gK8.3ȷXiYĽQZeAAC}r=Aj78H˴%Ѷ*ڪue,B%J#C5j463uY*fdd(Ԋ"o*6 +k$p,SR&P3mffffG瘪mdP(I6۶nY7h+ ½V2=hmI"4= Xx>x~$~[ER;jc"R^*wT(5w9$ϯV.du7θc;޿zǩ߱Vv7Ez^'~?x»E|f%#x:Bh){^'_7omLe CaFʧ@V ڪRg׭I; UUTUWn3a ,8p۠\N +6Wvpv:kPYv`a} ,tpa?wZg_&4f6h[lMn-Cpm;Kn+hm;4pn@m"6gC*X!?0L?dYQpfb.T:NP4 2A2"*0LjF51B +anІ`"`*)4yb]Y'Yp}1S1ԤFG9=/OJ O-<<&g:!JrN#x .";!ׯN<F`ߔ8mtD? K)ݼ$y%/'tE:hp-?˜\JQnˍKI;levv4-O伄 HJ!SoZÒ4T링 \j!7m`um2|JoVǓZvcp%&ƫhv:C$p +$(|fOX$e+U|QΣw5q̼3J-3IM|pN^,0\&1e-ax5Q-T`~(R]*;72 + Ld h%ˇ.TP/oP)Pcz -"4Z=(@@}O]uNΖ*m;>#,F&]wNi!y+MB ,^199E 1 O +Gdnf'!M H;!#%Fמ +p\7b9"TI-:)oZȦ&BrDKBOavSa>%=^&T@t\0L82fY C`Ţ~jwd|lBlm{Y~S"j5wH'x +0ʰF h(qr4(Dy ,- !Suz]葶yцW}>hpmӻ%ᅴ':Ngӈ3d<w>ƈZZn \zc|:!$ZPDXڔᅷ*=NT*5oYfЦs'~oeh\w{DZyJu!+~TKݟ" +34`H7rg!+e5"gE +#H0 @kw&QodJ_Y%(wVkPu4ڑ|zS{.pHݯ]a|I!DI 7:(+aB<Z0-_M +V^rP IJiaob}D+JG"qJ(L)u]L 01kaGY"} y=<{:R:(1eG`MzR8nz*j hycq:xNlj5f/6aUY%skd^kQ;{@LS7}9AaEo|VP̖[=\gu6(L۩I eU@J q@n\d#jjK߉%ݜFXX%!5娔1p]jσFEyTtH)>Ŀ4tW3>/?g"|fdip/tNJgd?fL12:C駑R{OC|}An$^ 0 <|zL Ut63u!9?B/oy.o% n] QG#4sz|Y(ש05Z3DhnxP~-NJ-ekR +sX H=^-qs8OkUGAT{ v%էq"]$TBMѷG.1^PWpBl(T%2ԫRGSm^y4˧S#XaD90*UKW3M&мVʾs*4kWCbr;aBʲpI.ŸΎ&IxYkWJ Z XҩPzg6-;AyC۬\И0'MkZ6E.S9 +6Їm< ia8S&O 1E@v2nXqbT" s;W췐 Ʃ=W*S H2L\AQr?ʃi P hZb訡s ])t:6礎N6Q8zz F)@P[hJ3&N@j~t/[Dӻ$uLGQz2J[3C?mvBR8 a4Ig:A`m+/xG=9wepP "cyjx0LS]BRD9vi9iLG>stream +d ),>,LeaQsX4-5l9>KW5fi04'wgS$~f۹rտE2dL{V"O̶a’ +9OIPeIlsc3MtXZɥԻn!(Y|)/Y!Ya"-i8.YH?J49,%wq& +͸.<)r@weSs&D-"0< q5RzS{~Z^ AE+"ԝ&2%ʰCȒiD" a갡3 V9A}EE6r4"WDo̵e>; 1?PoZS}4kA)#zl:c֐UF!- +(iRf(w15%@ng\ oT1LK`A \i +8ӛ l4-٪ M]LxEU|&0mJF4OLjiɬ6"13 욆ìQ6.b*CB٩"֋0zM$҇1Spz6)/ʕӔo6.\ٛ+l]bGuybţ*1(z5*$Gt _VXlP+)$nZJ Y[{B)6[L6*HG%&FQ]T, +rp8~c%wPA n-TqLqHh5]3k_:PA%%1iKm(5J9ϴ-:''+Q}[`yw[ RJE07J.`AN/D#4b>ȲR:{MBܽ n(>BYl| ~ O./T7=K 3niPs㾭Bokvu˜WϪy20]QsI͑d6'C`<ɖy fc!2}6F{jOwC&WZs"%!q넪.H*/q{NT~ώ2)s3 IE]49.rg$ѓ9V=:Ox5dVOO 8a(wE5EV$= %@_]w]uotnK +: M˓J;vpQ 0:5~`B)-l4o 0`M$/fYN{mM,=@<4f88/{6z~e00BdZDVW@_ӭNQ6¶zHi~RBgbs}R|db͵\6l{o0帶\凑`Hcm@s 7K^4sh 16s} bԭB,zH-d/ B×2QߘobkiG8H|&l?A5ux{d.yt+VOZcPQA:Եr7a܀f\I(O=su;W\+l3]%fIکSJBYx ; +PrG.߮GT>M1&uXsn*Lg`uP=O:N*wׇH2{4LLGAO{8%ok!@9(@r4:qp.Z샭HH !՗]Mhn)V(\[f ܓ7n2 #[*K' +zBEwO16D(YOJZy$Z,_M٠,ߑ  ue+0h`߫X%ildf +ҹVJT2 daL ְ^P=KICrK#.7@S,k"FE'P8b$/w +kO9bb ;ʞa-h wO!bHfexhQLظjY(u/#7ǣvz^O>?2xV:;b(pNub~3k}9χՙ 0}[cJ?Lēa98a7g_(j"X"lg&Y :Q!bMM9sZm]NGG1XcU[tӡF#J50[ǁ=@:T`"*aN=-Ѻ"r"wTgW_r)5.LtSHm]-Ù&u+?^rj-e{o:8xݘE +"{n-_?daN'RSs8yѵ.MFhyK# $R\PQ9@.Z<ƟM^m(\Efs)쒹&27W*Sxx6ߗ0tvݬK?'CeT;(5;FRޤfjwfƦIZIE .:Lzc{Y±nsLD65>1TR*.^0*֧rҲtC1Waj$'e>Lwr/xԻ͠[Zd# ,a))UR>?!^ e_t1_l:QL%jKIf.B+ܱyq RHj muBQ-ڹd+"҆D0H/:˂=x/%h~I7/aMM'<7C$9W 絀*3)Sj +jItQb"$!6M4G(iu[w\ZVoTM*92[- %MDUmBj'\ՏsJ/$͈i/{/SOˠnJmm"<$3˸Ya6&pfrTUs4>gL5rk ?;׎\K#Xȸ +(o2d"X7_{9G c8 D jh<YѡX_cLH%u`M>e8 +94YK;w,"Z.x{^aD?&3$jQt = ,5|zW$1Y)QGTfrʣ؀0灣ai}b] ^"%K e^3+]j@:QriXB*;)y5`yRO +ou(/zxKN%\9. z,P~B3{ҩ>*V_Q}Щd{k`F"]̻*gtV!eMMHZ9sQÛ$]e=( IG%R%sYc#2?t ?M~\b `Ӣ]hǭ;k#oAs㞖2z.~ ޭ>g3( +,"X&ӗ-/<\ tFSxjR"xj{Q,5!LMc0^V,OYO p\30<#O Q3d\t.24vgL8;ho@ +E"s{t- +},}M@1KsT?4ՍF<㪇qUFVo[.T'v鿠a4sZGO}f^lxʤ% uNy'2$ft =Du_^芘)_׮a \SrtRFʡuM!*l2+ +խVdb1ۑ++k.5A !s ίDT_ic("R_ZC^EOQ6I)ea*p穮o1G1AMKl>A%?!++j\UM=l$ Mtj SpqEuof\oi@q0@k0zbAottx! +k(L;Mw]^i+af¸ +k*^L匐9Tih,n[v)#b6J=SVL朎J j XYCOc/ ՙLd|:C=kh꽚`8~tB.{~-=3!g,aK 0a/*L/ Rd: `VzPF p}i#|^bC0U$-Ȥ<mdv>>]+!n&Reʋ ~-DrG4`Uj_@FSua0G qDxԭQp{ 6ڻ[7>i5y vX`om,S_«"x uvqRDI2M56{!qn #˪Nml<>h ?uB@9'!=2eލOa&3Q̗^UWRsq4'mqu'iX+YW3.E.=ʈ9ݿSnQe[]V-Y.Z`w"!!h:Ul)=#GoOu=+)9S0EuBK38G#j@>i1d+ KaJ.ay*zC꣰ +oúHNYN:O{幉 )"Ԛs".iCp/!X.%'` 34~JEkQfX""5lb Ę׃C>~~Ѫd4VP&A=_Y"ˆ+)ApKb+{*GMz8P7 +BJe7jSu`$(2=@`r-wKu+KOCEBΩS Ҟ]T:CbLP*݄? +wDY`*lݐ|ݲGS"c!FID>24$#QV4?` #>!4>9Kw[SX.7~_2r&wĻX@#*fuR%끳 +q(Ft6=C* 4ݬgۆ3g]>jC*C/q{ރp>fgٰ2J5Z1N@B׉;#r=@K[Ą(q +lQ + +q=p:\Aok~KvPNOךnyf_e? 2Z 󶖱s],-?C KkZi Sw5^)0Çڼc\ +/D;Ko/d]b]H#Y׆?$!OdXg՛pvA1hn<(e. A >3b͘я,A҂p*'2= ;15 s*v.0&hU/bI9YL 1VNhv.os@cF}9[cdRŽuv7ѩ(L2iI}w' ?Y6B?-h9GѸY.چP)gFƆ HᲢ ]ܼG$B.b"%ao'5`8xkŠffpo`Ʈ㝷u]r!JKT5Cg*ptʌw@5쏍` ; O]N#_6׃ӹջCPNX,D؟e9,iu34Ϗ5di/N ?= 9w2d,$iӕ!aU4\փ +6 y/:r!3Ծfx0eo?gP] ҉2İuhhQGaEd'o +ky1x`A`8<+(/JyrJHswC<4E&R<\xɐrZűid.P^-z)>F(F9qJNX'< ;? \1\Gb0?Se*MƔ'!NPmzƪ5Fx+ލu мu_eX3yPx2:<ݱ%ɋ +lwl􋔛bXA6 %rI aS5I:n-QB~ oShU +!&yРL%Nzh53i6?%I;׈ʅMϱ'QU#;H~Ȃ&3 oCV;{DXb:\`LŽwcmZv˻@{+daL4ܨQR;|&*X_rbBo:fT9!!A$Չ=oF5/QmIEQ|T4OYs$鹫B]1@/.=fJ {t)s7#)a/#N( h˛PuPW:}[З2d:*r-Ø-p0QҰDuC)t%Xg犦󧃴jp?a /hA>)ԈoD28UkE#ECO$Enz'lt&Ȑȱy흑(w(2 '2_M9OݾiO=29Ie/rucm}T.uJY&E7Tl^af1 Y (&krhaWḪ;7w@Fw;p#&u!Z,9x\@Q]nwzZq7?.* [Vll,(DJ%þ-[a[{gacZ_S#H{v(Yd,/ߨ@R^&Fe9t*a Œ ++EܲDSܺgJhJ \~q8S^lm NyGEu:`f)/,nOQ.?ESI:+*̞ *J⌵1p}(Q^iN@$TOkݒRq6rFb|hg9zY\FyEB<3-w cWDw}==cB#` +E¥贊z |MJEHHid0 U:-,j k+Z,Pi|dpK +~3!H c\ؑC=osf4RZѠ\,凤)KQ(ɻ^1'ͽt"M l~ qL {cw^Hh*~>XRceu2~Ȧw߯ie3pgaf'~v5Lnɜ>NcW l-K͝# {S=G HNYb/s ](K}O&BOhZt"[:G~ɫ4TF s;v=N|kGWOE| 4ShUvYWutSȾ|I%b*PKqMBy48Y;vV?c)nbZS`du|uA܅"G=X^byfisy½K-3VC'yp!f(1E$I2GG1Č)cC)"Ju r 6!{rT/ j1 IJ|ʟY ]-}ۮimӁ~m'B@W$; ``j?BQG0>>NZC.jL0.`C2_P9All j}eC]ӁUL'k""_$ /sHG BCA#¯{yLPKx85эLFqBPךxhw=bzizU:G@6`\+ޡ)Ñ1c3̱$CkҌ|ʲ҆ #fZ🵪OE6㹆2|l| Z0߰bǔ4)Gd{X2*iTȠإIiSyH,2>;glp0M z5S-h~?Zr\Z%pEroPF/_3~ |XՀD2FF ]}408sQI4ۀoXCyI<8Sn`UVh74dq +XSLK n[Fjr'6ve;PE XH5PD6FHȏ}HSt?2L^naWb~8J7f!6%D4tv8&e0>k[aڏ4({<**1yޛJV?[bM8dx8/zW@xa[/aCBgSn̍a$xehKs椌ESP`LK\3`;C@בLg렄VskT`% ,8zO Y>iȢ9IuЏ("! &!}05[&MFhD,)d 1-15TP C6C8wRU& (S62|U LF|C\-TfDE{4d*|*I4:eʲ`ղQU| *w`Bh~ħ9oqn\ U/ݫ'o̬^ӛ/_+C!s߮rK=C)Ӝ<]~8E`Ja(洭 ʫPGJW Ba +n +dc +zxx遊e>z%C8 Mr5%C#,Ba-&4|0mq[QuXL'.v=X(yg~@.-hSi ^\ǜ@?T*n\ܸ=:,/~P2AZ:?l-'{“ +i&B^o)A/T?ha5|d+ؼF?1|U?|lA+T*5rgDw! lHME2s-X6eذ s#R9@,|܃*4bl( G-}-Mw2ܵg7u7ZfU!,f^+;Xle\+}.b`" `>րM +YA&uyxOqEd| +n1%̋kb>.ɗ :]G +&$[BbI懧~j $7R/R/"?}N6Hk(,lm8 y 32%j/ʞn@T_6f$߀,119]Q{U#lߣp.QQ:WJ>'A!&iCi`(P/ +4̋٢-bnZ!xM1DPJkKyg_"ohna]pwW5 yD~o \*[.KJht` n}F=!CO>e32K2c Ʊ =.sҿoTa"S 'M"D?q*Ck8Sr\maKs;}tBs-m4' R< )VR#RJ>dY1K2Gvj.$%m !hv] ؃aIl>?xx@I{qɰ^zcO&;㿘~Z%smo*_OɎJ4lr8t Pёm>T`V:Ik+'dBQ{+tŀ/ԑg&>t]hs8³bX7eR-?]//Zk+Ma4rhCA+:ƇUׁqvo\=ucʇ^{5WC[q(3XK.}y%/}!' qwYQ5_ƌ'A?d[?)>݀ VepQz9}.s1mo!O3ۢP%e@h: #xlR,opbg:7,zG:! YG/l~+pB aRʼT7pHPa4J%UԦoIHH8#F6mEqo C0_M>] %%1T(z+[SmNʨ-bQA6FR@V Z>5p(nb8NA1kvWo:\>%T5O"" Z=05ʂ~)hZ`o|3cn]^b2B8RX%c +U^)3|lQCpJ!TlfϗWE…:ECe'G,A ylmh/ךZq3WkL{Ǫ.0Pt˛#g7Ԗf‰8[ IlXf-W o5wk}HCCnigsnB3Ό/R4o/́AD!=Zsh8Rߟ2zl/1y1^Nf H-[FG#,YL2!Qεh +J҇{.%`hC8T"QE_>!INyjt] + 3] >>xB#by~d]01u1eyF$'6$V*> +6*tm#.Yeغr*)3|M +0ܘWs-"ńY5i "&w<%CpP=H}C3^+ǬǶ;16~0jl* ҬQZ ˫,%NI՘Efxg:ZJN+>4EFfCs@v*P6@F䞣:@0q0N!řneDEN,Lۥ}TThEfLDH{G}L4(Y5ѩw.N^/blk+ 2sgآo1!L E_?)5@Fb4 + o[,Hթ`Y<҂kJh wjxp(35/ǸA g"X(TȁѻFٲLQ ' Pu-cxڀZAqstEa0h_|~sYOw,L~9qJr%9)=Z'Rh&$bjcp!NjI$Qb%OR@Cc2"jNؘ hpn!v2s<ߍSfnh@&K <@"Lr+bj -NŲa$ܑȯRCx̑u J4zI)."Xmtl9Kttz[b0Sc/ZH+sR((y^Zʊ]K`\էRV l<3>@0hM uL#vZSN6ބ~}ک܋GA7BI#(FJH87(ʺ2O"WılK;J\űa$<]]b-MS[*QopTȭQn/.{TU͕A Be/Ua3b)O Pd`ef O7w(\ ?199dǔBLomϛ@"–Dzԛ ,Ds/KԿX ^0L&c R_]E61Z:*%tiVɷiFyiu%9FW]doZ˥Ly%z9:ѻ h~0Ur݄lOv܆(4(/[hEnS8K6/>%ʘ.>fɀk;z0^[*j' `ˀBBkMT39%Dd3 QMKj=T1놨@gH4C٭EDK8Po@LXNn`(|#=h_nWuCr8Y.IlC4^ˠň@Y"zFYnb)8/,[4s\K <;?cwb5H xIt;;9qx {&Dz7#og T6aVu%_c~ (֬VLWr,eoYĪesr>Ai@D#'l_ .#)Î 4~n}JŦ$ G%K]?LuF1}gQK&A|]F1ǃK-d ❒UJJbZrƁ|u$v74Q[w +)D293$X:׏L'9BqaiHIao `旫$1=149YRlc2KK%V2M>C7Ev*2.s[ h>jmLEE +i(}OCN[~Lr߇1XqLJ]sƒR|Dԅh! 6x6HFFoZu 95!TQ:glM])l&:3I]񁳩=ةE/^wnG^Vqk1{hSӁR8o e}{m_8 E@:~nC+|)owl9e*AF8?0á,)NF[,M#o2/R2j|%6^9y1S䦥2 +?=。=` SE$dI<II,AF#Id4xAE#)@`wwvqPO} ;qVÕ` uл3@00q77z׻A!8q,GFU/*( ̪._ˬʌq{b_eXE + +OAk@Qh` )2o@;*vTh}llG-Uf~c6Rc3k`jaDgB(0 .5G=V@K9WLv mګmzvֿ^Q1۔pjGQۋk[Uk[sm_ ZK;4q`pk.}WR0;E3u$u^-ЪW=ilHT &ujo6~Gx+wɵ}XLwu뵽zB^wX|ؾ8r<} m`MeБ,/loچ۷@a۶M'bBcT0-bx@(J&}&*$&,FnH%- 3 sSKA@~A^E! +E2&y 4弍RP3| :*3V\X"}MX='9|a1ANj8TU?}-h`݆M7ڍL(R>ؼU2"RUMѢ$j$SxvuAA<֗ښ#g +|Ocb+E)6)ɿ~TBC-LsXK@B'Tb +d%Q qEC} PC`I8~/,C\Ct L03iRfSj߭=ū `,SgYS4N;~>8IB`)y:!%C2,ҫֵLXFܫ;,ѷs& +1|U`G?@-N(ANkw–Z 9'sP8-WE%Q6(fCߘ\/ѶYԝ)7z..TC}1GHpAc,d@p r%$rns,hKl(FU.߯?[B=vVi,Zt׺`HSd$֞d-Gd/;[6vIKu.!\$0M )ʀdQJQ@Qv̡2 m2ۡP%1ɕ-D{N9jE#'%wy0sx;:UxH<͂168Y*Tr$##ݰޚ&XA\t//(&)t@f +ZǾf&솏4sy&֩a+YVmx)dqB> WOzv3fURLEΒx-MC]#zMBdxg#n?*oOPC/t{#W ^Lih&4R9H"Y8J<s02"2T(Ӳ+fU̳fĸ xի_价9TCsMOh7{ELk4&AӗsM]4 ؄ ?Ec+1@M%Wģ W,gU)؍ fLdQj'67atT#60{Έs@V/Hfs5Q^XBF5[ +!k|pQ.>0D16!݆SZ~8he0))"VH:"0TA7dIBlǛH@-\syc(8t5#U] (KK7B_,V/6F`fBE!ȁD%-wZE !M=^A|r|DբB^dΗVY6זMn]QUI&V<u|PBuע,dZ$iܦҘo2[f\Nji~ ;pԉll|=b)VX>8sq{ڢ+E +Fڈ2|y#fl±u؍ jH { /n{9mK/B?0vb\" Cgj5+ޟ{c/gܸ.3eA>Y=Yվr$Oo8կ*D֐Y`HnNr8~zpz. 6tqg9|^\7_B) jySbX5n2nz+Ϭ,NbpG +veք( +4&Ui9eS)w1P:b)󇴐_J\c + g{T^^L"]N;R/ly$kT?G/vj2]Sw!nkus ~ D.faH. +Ґg&aWMwƗ.|6ji\nηfd22O-E@>ElhK.dگ/S'"\h +i +oEՐ;PP*S^Tft؛*u W74hΔ(v:" A|3˞X}I}ϫGBf 0^|Ck~'%U׃ `4CjE" s@V .:^G':)EK%.e23 4&*eQ32>mzzCzj>8 ʧ`3i;ɇŐ#Wx:5AOm pJ:C.tW&9'hm=BYe) +ӕos 5IHrmՍPd:Bh>UCBC:%>V [~kb_ȋT 6t;hY z6$ Lr';}Z"ٺ[1^azx4e:\PͿG)QV6=Đd:%V4Ai HH峒%E +V-=-n!dQmJ}SQdQ +UQLLQ5o ~B.ѩi6)$+T^'Ŷ/mR3Z'&~]P S A_%NtaOgpm| ǼU1hHƺT ʹլGMD$e%=J͒JRxYGrVNX֒\l ,|'T## buQ  ט,\z:" +䂻h 8}i$[u@:h&W[Z3AYs.{_R[-XR[Hf#Qɢil I2JǜɝΒ'9 Bg? /ĵS젬vD M$7BUݿhԋP_rZaLr?`PN2[!K B ToW-Oeb|j#0Z&f8bS `O :FW ?BR.8,' T{c؊P#=c ͖p""r8K|@SnX L CTom۾ [`+N?RC 7-b#,JB Ί ݍՠ O&cir +wء7+*/La S$< ñwЩ{a1 +%TB;APZy؞&kgD7夶UE)w,܆t~zZs)'*br- +[_ ҈v̀3aL 'slzC#BmRzd%㔔fŗ}NtiXr0)Jz~m4DC@S9G*wx {Sa@)ŤPv9~xl"F sMpVTCFב>؁E?X^ҸµZICw/+d8 <LBrZk;јzGGxij:-;LT/*8$֑ыfwvE'7=]Mvz3pr }EKh)>x&spru/ /PF?L- C\0~6O:&U:t)M7,s*sH#OEK8[Aa 5>7-Z]@.fcfQFe?XRi4*[7J8Q(F% :TcvkVT SV pM0(|T烧>4(A Ih1^\-Pgb=`[\b \܌P*'q>WQn D5:Rm7*Ǫ!k>A6'SjG +z|ߊ(^ _ ٯ𥀸ziN(j|9F_C#:;"`"Ko mOQhOā<Bg"P[?a)ǃjCdЍuF7^O5)P#|U$'. E*qpGFAQSdO+<` 0:偕+ +𠃮/E75jNAi CA%c.seAd3kJ`*oʌjAA`B)蹑${Eqo` ٵ8q +EAA>5N녜Q+8[{t\(';i3Ԅnv p=C7͒b +]Ntjh6/.^;{ +%mB?*Ci_!wPNxL2TAq4?6D?F+/w$h=4Ļ`ˮf1ƾo oMPiDgwgkxSGڗ,+T<$s߯_6G;v pa$3P02+r,LL3S%FB:}.#$1sz'""peG΍""zê\oi2Oa CYm)tbލ1Re?=N]2ޙé +\Vyd#rTh'F_ !hݡfE6.!%e`!/CI&C 02{U˂(.Ϲ0[xOGhũGYZ`SNt9i8~1'E +Kem>7hg}QB$M +O"ˌkw0sY>55''W1d{:dJ\ۚ>fΔ:e20xvFGJJ@#zmN??$<Wpw\:&.I/r^ ! ti*Ym i%~Q3w*GAK^Ÿ`?3YtōB cw&~$Wnt)]đ%h9טE Gr}nR>~v2UMgVh<#_j1. xW#$ƻg~fwAx괊EYLOS5'KHe!縈N VV +S)}9$!$<%ѕ@LgДˊ̀E1^y_ kvy38#.k[I5@x/Rzq5׿;B:CVAx,_vRRRh4?HAO2/K(Kcv& jBK>n:[ori2W#r?h`Gi1sX};QFOr#uj=X-7m3B@̓k?ݼ2ڇx`CӘp{E'&ptG4%D% J^"3%hqi*p3Ke6ͥhA rXdN*E;}4͇~]r Sk鼻)EhNDRUq)wbڐjam&<9RVHacZhl4b1 +Nc.~Hx|th cMԒ?愥olz$&;Vc5OrײB6rAc}WBCp%H$ |ƞ4Dk)Io1e.s"K?,fp+HRrٟ;,UrZк^mYq83*.pw% bS|5 Hg/C+ӂ=*mz7(C)zğ92() |%\4o3e.VnH8q]}V02:A):,؞'<E w(-B5/rO8&bQ$opn:'I$$2dqӎ'~6gF}ߎ޾ ?2tN]`Pܔb  U~cZN-kګPʑj=xpBxmXD‡[0MorCtK<``] uГ,~X&@b"d-iq0;HS$i%=NtQ,eb2b_Vo^X@dw,  (ӝR9HsbZV 2򌰭Ta(QR:4 AcØuBx͢f1 /PA/ʓC/t9:(#EmbbJל [g63;'$NJ}[vXʸeMd6Ef:O.ͬS{KJ0 0 i3s>2ГVu1TB| e!KAF}rvg^%@r r=m}t}T3q8 6 #BB$KVkj+ 8he}A");4 =3K!-ݑޣQ>J EąoT+-) Q>Y#`98WLQ8=YA\hFއ,ܔ(rA_0 d8|S}Isf7Ch!|ԇ-%Dinne=*4! BE 9N6dE=(Hq-8|/mbp& @KN,,A{{;8K\x^3zOC"JA))$&Wjp(?B.W\qQ +)+y(Ts\rvW{0%we-[~')ޅykh oeM5Ca`rOp ~a/oe)į3oyX@7 gT%%!8D[M&x f;운tޞ n> PUlS`Ia fr>JBT Es|Ȳ-Qg2Bl~e=@ w~׉RHQ#Xp(qIljÜ҇Nd 0TEW̭i) SW bV+ńXe>Vx)lp(J}"ڄ@j% 6}/Jכf +8I:ıiZE>*LGAIN];%AG/7> <۪X/]2y%}]5NOOΌLr`mʳ]׍]: Ғ N5mлU&#qK0]59Td³iG(yZnS?`:OCz!D=$T wK1"dc& ,e#9bޙ&~0Y4Xs 7eYBh`{z2?`WGA ysDE*s變k0!W]Ke#ZVKsngHuLxZ򐿼G>GABZ\L;y 0t| f6>Ųf16*02&oO1 + ,nR 0@! ڡ>5,ܐ8sQMA1puDAvb)#ǝ +j=vkVLF@kTJ2 +`Z ^$9 O ]Ѹ +BN +08 ;8TR*vKG,5wzu)Cd89Pc Ez +GTwQJ|D`dLbrD) cօl_WV Sk׽N Poq,*)E@cV6a5B}l| :_9C7pb$>ߥ^U1:iL%V*^v D +g0t5E =Zk[EH>8N>a#$ ɢlw8dRH@NEF^8!(즽DUI. *@,I^!o/1сRK.]4|(v|XHX D4;'˄tfN̨`k:+jJ ?`gsQҦǶ" Om,[dD?8r̈́HoG\;Ө\ܹhP,߭|M~:4T9ʜ`Ok1Vٓ.ffLFpLMa!L8Z1Vwr0!w{i;H^[`:? ~/ ^sR +Bq?O#9>s5TlSOENh9EPVs_y4C#@_s5%G({L=r8_yYE7pbw0/? +~oìЉPIJyNpCNiN8طc|NÆHfWISrv6(s 3GxlɔrvU&GJܔ(6s*<#:HNbl;)Џ$bM L ܾfsZ2$:zŚړP'F mIp"hk[Q!v oם?FmsrJ!*I 9˻?#)r:QSR\ O#)Qqޕ1R:8syaps{Y_ ,Ug8B g<2jUg Wf*BXFi(%h2 +P\-A*0^6/8 tg)M葒l^Dɼ:IEvGP5ˈWj½1ѮeŴrPG.Z>vKbك ӾGrB9y%iktHq7B3IdCeڬj4LOQ$el|nRdUГʎ4RDꟅ#E"eO% "{4ZF4[O9)~GEGQ^57.;ޛU3DžÀ5>]ƩTWyv4mX 4Eȿlahh4{X@(`Q3` ]ˆ`;Ry>JQ+B1ױ4+E'ӭP2'?{3M`pK[be Վ8ϳEQҢUtpejQ +.1~Z+<|lD }Rg:r5;͂‚/4ʯSAI`<5͐XQ@@v% K<4pMv]5oل3Wy,Or*2-F$O_a\ ܛq)Ɗi|*FAU!ENhb&f (݉/QL +|F8`ئٕCXVPdГ1{|ۗ(+jF]T?a$vwys4y@ujo+Z2dܔ35R1~ݼо{}5xG[8®!@\dI" +w2xSl{5r P.;MxN,b20!]qw5U晶ulM_v1Fm\`v+o3 & 9z; =ɩڧ$JLKW!i< b~OW\`^% G.p9q\yeWg)-Ôg_㺲ji0PSeyBBMc#<y#֭yM|?|׃-F +Lf0?ho1^NlCքĤa?, 2,z@|1nDAdo-́_@.J*JU'b R0:<<,q:{ fA5[J!ݩD{&I}%7Nkv_ +i W/lk5Lgbpd8lU@hx3=+[!/ wS JoB,,iHBP !\vH)"X.)D7Q]I%M.d?ɂg-j#鰃 uc#S}=]_,J& GuaSyiCæ gg==<[W^|@͵8%(V%|rFfS??/)<Ч}gQaN+G'Q4֑j,Y( %*@:{]϶Dyώ= RNXgoʂ7̗sעMmW0K}yQa)NbK޺e/mAz+@Sٻ=>_qL̮3Ut)fm}q|?  Lmts,r_:׬DrbPB-wn}5LIמu?!8Qp:o`I`=7 7佻Fm h3c9?&.gܹ9؆54OAHRΜJP/0Kc3M%z~t%qԷoβVO #~U :`b? p-^ghb4[ O,/2;1iVA,ͼ&Z ~: ᤂCҤxv"1wiewOGu*( +}xC@?@,,MD NH" ~g{oBBJGɢ72٩ ]EHL&%twq /<3kRC--r3P$DŽ<-g[*Ԫf3F=Dh5Qs7QzE:`0lNLKm!cMSM +u?;baY=jR~'&pߖkmc6!FvƇ{CuwJґ?A/}>T۾Pmu( 3wf $xo ˕Mʞ8$Ҥ: OF3o"t*3 NRHA $=4]ɔ9wA-3]+]ó8ƚwף"7-bX0CFsGt0$`Le7olđulq.ѳ\W@4r"> ,o@dk:h0H7X:ylY< Gqy wCv)\QP̯.B=!QNR`vAjˉ5zX} l}eF,P`RD(V$# nAz[M0+id}Y {p6k~[=eq=. BtABA +/H%Pg8oxJǘ!+j;pk] +}ZB;8Be$wq0 GJYj5@}EQy% TI +pt%'S +̜dyMZp(AquPO(Tْ4N#|C$9 +@x'q7MbkL;%И ><mi{&K08iTpM { _}u`0,NJZ;/ +di (iE)7*vuD 5遝ϔ#*T"]JPn-1l/"E_ĤC|"iEY"6R_6=)H*OqM dDkۦDQ9:󏑠܋Mv/ٷnt 0qRǻ[--zSݱ<]cŶU G{Jߒ5: +7ZYGNns}IXo\ʕ/GN{/X1ƽ_eT#>n:80\^ +]B`ݔ- kUt +Rr4f"Gx*wۑ=)c˕|;4iczE1IwŻ&lYߥ@8veuAY3TWO^pn< U80K+~.x^V0) TK<±6qz$`=3TvAJ PIjl7Hd0"ۄx'UቒyAC}m$>yg3+CBӌI5yf:naG~)=5;;F6(5x_􍖌sOV+nȖ&|eVSX 87F#p˓ )YrD`^u G +2*7]]'K) $_'B"9/`q-Se|,iVB׏1Q6oߥ7/ci"  |2qHNr}Z:iB&+AT.yR|.!<8J kVȶ!:d[.dg&9B`J %ɮ/kr/`駠0?Gw +ũLi¦.2)`]zh"\0--~wLrLb@K&'8Pf ֍ʫKGf951sUTN~@W\>&6jwk^NfG*Z8͋Rp,o@e +bRr}־.J:tdyRT SQC\<ɋ(˱^ֶ6/VK޶ ^&Sxl|]D qLeʇAnbߠ(Dk5O:3LStLۮB !()C6ɓh ,sMYRTӋT +*i;k@`8%:٦tsƓlAsߣyWs +{Gd~ vdb*IȋJ_RF^{@ʒ{wcQ r(]I;ѼVB~] '`3=g~c&|wˑ=EIQ͛ z{:/AX/ 4(X^ヱԳWf?\R + +0 h>Z\zqvr_h%4 ѰOMbQkڀGuo%%h4xK.:Κ7\􇱣. xuP -Q!Y-&&mB;x4i A^\ :4&}I/@7R+Pv2u-(ڝr̢m(7$qf[? E3z|_Y3jFvdQ EvDYDB QI=X|nVNnܽQN'\@ԅYMk}}7A4,h@мv! gkI7^@A5 +&D t"2U'f9&f6[Ia&eL˜OgfsnLՇ7] +?~f ƼI%KEH(H#gNgTL^5ZT_G%S{,C!u HqS:K'ɫ$"Y]|IdrB^I2M]7P.B r~LwX +4D71:i +;{*T ^X.auU CJ^/T9Jj2tem +w>!˶''reʈ۞sG^t.6$lRS=m%!: +B̕K"E s 2Ң$\}₏;[.뜀E޲PO4F’9uٟ?.iyjP,fܐNMƓ_@Up?"rJD|"G%ixcIIKS_zUkw(]ML#,Ub'dk8"XSC56m%5 ucT#KF뼭8=Y0G20l ʍ~HA j0ɠY7ZI_kP\cdrZ,H8wCj3g᭚Ղ zC5.S$W8 &/mD;n|ʩ Q,g=U;y6}4a-҅Z B^Xv@\"+٣l71UW`}wT+8Sթ˪*tB˄U *+['3JӃYuo?W͑9H|@c/6U_F/;/p ^r~) 7d=$S +,*H.-%t@6W^%ڶmKm[m{xVZmĴ=;b"8>^`(%_e~AI'C6 ! O?DIXPs$9@.ivh^ZmU$$$Cz <L^9b}Z#bj1`2c&'L"' 莽 ̙<',)ʐ7Q q7Si43 !5R ҃3HH&V^8 < Zkp\QCx U!̠Q pJx:<E !&ZaiQWSgaGXңN-ċ)+oUvkU^GTtZsoR`1 %s"^n|$ɏF&?]E0W7(xi]: RgJ$*kDVut: f:TDJԶS +L3:%*PjU|ޠF:jhwTyIXy on/"kY9I~},y<79'VVq WCWU:XkWj{Z`5z׭HXUm g 8kж`t`DZB;7{lj~牅h;mE*J[bm۠ԙQG1TDAZ +Zi:LmtDTVhMiRM- Fak*Ad%V:hV[mVo5#hm{ ڎoж޶xyFwm[[VODp0?  \RޑM,%f1I:r1BH1q5긨e/!( ÁZe faa8p!30""./\WM ;bƝknbPt~OR4G3Qtk#@06Ǝj!Li&"ڴjA}yEa!mȆ\A e8̋^TR~J5v^ !J$?NhhĄ%oj46ƣtjT?@ C["7jGmfK.[w0V^uPRfYIKa^shZ=Bxm2ҵOV^ `Ok#KjuD}ʡy$w8 ց3jqGhPu.I*P]9"뛥B#z +Ĵ|Yx2uxඣMde_a80x)G&)x>¸AXnTO8=(R(ȕ3%zIZ,&x5ʇ@Qoi*~A-ME --aNF<3NvObL33 D|- kgzўŖjh5]}(M+S+bB9șeD9kCu$mͧ stC,)F(% +ϾhZ >_ >ޢѸvEX4&GGfya"&=F[*^mEW|(0`.Tw|'Lңݷ>;lFp5~23Ӿj{*6K2fg>K4K(뷩3KED2,d_z{:<$16@=iz ;"}wPڋvc)j?RT3G}!>Q'6+Զ~ ZpA'ЭEԆ̂tZucO1;q#/D"WBhG:#|VY7%R8UkN4͆uôXzq:FX g̚QhKudvKR ׳as75x(M:bĀSFƏAs eqB=gscP{8,K;\½{j2X+8:_n'>ִbv`_ϊ,^/-~W/eAkb`Xp/=xfZPk`$Ә#CJLTe,Ah2m޲md[{(o}`#Ud%ZUVݘQHBeaoa y Dߠ+"x>_k+B+;P ԃ1e{&2!@\5Džq\zoYp*# kpQ{F.R +gj ־ `[ժ]'Ȓy4׳*hGŷe!b|Jk"*_mi:tyq ?p3W̹p1DOKy;BMTrzUi>N]4@MB]}.&a6!sٯ#: +ZaNO ic/;m!ZR^حx],ٴ[~ lx3O-te΄Ț)6V/ <7c؆X5I<=֖mf tjb-#CU+wfuKR}qi#`3+uzE_*)aKа2 ?O5V>lʰ@5kW,ŦZyMW=qbT:1tl3ő4SC/);,t mAiR$qc uaؘ#HxTKh+Xt5U^%~ 3Cc+}eA (jB75ilj>63B4:k'tu$6(THR$8xvZv(מû7:.}FKbY͘nQE3NR_HI!ℨٔ-/rcz0JkűLe) Kp 4g +RDv3]_@+p0v.GtcvzrPKE5޾/ (ӾѤN'C *ߊI%ԦQU}l.S!`p r͛P +Y4M>kzhs\&Ns܇2wN\!&G H6@nF(g~ܴKH&b +`RR콍TNӪLES%#iQq, M_FU{`yU+,APJVG>:WKIWf3|C8"j]r>`fdn@ᇸ|@v4K9} {H*ۏB݂9P+lƒ!Ays*m4#)-*ARs`IS 3"kqNCpBA>3*xA6ˠflk̾;@!H TdD|~DRQ[{}1n1؟߿PJY @*p +uyw~ј]^/Vuw1:"/WPRhf`Tm`s \d0冋ra|`+#o,ֵBw%t=7` ,P9z:Zs] LלoխҠ W ~~'."HΰT {f lŽnݭZ=WV髯@gbt!][ͨ1lzKȺ ]gSbABlkgK"BcoYTa#.-Eyf2F[orשV#݊`W+>1-иv /Jf,[<Ϊ38;idS6@(qN)+ћLdQ\_!uI9Nf̄+S'㬟8+]XO0L_ 0z nǍF扊. +0߀ duv8 ni h\/So!3,YW*:ʵ2@IT_QO"k4?X(a'&fy[joDztN O.3}}8 SdF96ڈA #q9zŖN1D2Jj%sYa-p!Q w(E>pn$4,W+hPų2) +ˬ'{l(it +Mß{7?r)yJ} -ݐFvDe0MLvR1@0`|l"; ="ߚBk1Vsk-}x0zB6Hi`"ƥ5yF:xoB TT$"?4BنY"lewUI1#.. f4ltkF' {!SaXݑqb ,gd<X@+# pxvʻ;:%k^僻&X-HVi x7e "HnWKN Һ_o.'2zZ,~+8ޮ$/^$ֱeZ(7{10~2Ҏ*K ^ܴ*n C>}6:v7CVAG:s-UC :J0Jb( +pZG*67\{dT|ZsvcQjj<$Tr"`JQ)}:fTSE&sxxCבm |s^ _}2q?ICi5PWȡcHz]=Q΂<00F'B&֬юQ ki뱥00ת~–1S%SDsA[?lPYFD'>599~&!cⴅ fD FC_V ,4 a:Y9UhyD1G1Xa+wx 3gU ynJԆM)-;nN\=/!%E*9+fZu' wcu 'q-x+xE v}ٍhcz {snUP_+@ʭ 4^ ˾eG̩1Ác#] /;D# \(UUA[oTm618L4<=pΞO x3c0K?-{tiu#:%Z^yXB T=:"dmC,v5; }#vP(k-\b*,n=6͓r& J?w9M}T bC~>+ي4?7#xW[dB%5}꼷FY-"m5QV)L=_L{J2e/sYy\6 JI8稶DL<)N++0;u߈j@ոj;2ˋrnGJ/φ:BJA![Zu.]A:Z іFazߧW܋V¾IzO YVecčuCYtIJD3!lxyj'\ TDKd2| OIcE羧QF x_)jr*u5i+F<鹟\.)6j˃#Lܽ=_<_ݜ40-svK?--G;3LLs?S՛&jΦI~Nw2M-!:Z*oor7;B(d +UĪ&M#L :I{g/Y[e'k5Q3nտ]P!(ܖYL6Hϩb9!¦ñ" T3})ݿlI8kz<胇KE/P,cf#6\r?ߜ!DT'G+h70&Age +}3!Rr#cTū+iNcWN`,UB6d}yFV;s"U8=\HD\GNqaQni,u' +p!]ilf4XYkHvDLţrg(\HOԐ 1.AEC%gbag3"KMFQĥ,‰H]āS'ЈD ^~&23RĐ4,DmrbF-֓qϿ w83nP 5I^&n^Nwk<23 uBw^)ۭM)[lۍ*q;rGi66BU8`@1zɁ{EZYҭqRrɍ_1n:Y?st}lT#NcjʹQ'PZ9쮨H_lN(JK}a䈇 Ƣ\/& 7 I!L9qY>_`eMD.:!ٌ{Y&M(2`i1|Βأ(4Rد .sӅ^lA~#U AtO_ÍEHuB`hUemf m!Y'AʦF%?]:]G~x2Z~*ڭLƶeT)жG< +g\ۭLqŐ7ǂh-Bbtɫ\j~`#4X#׺_V(+pC{S~4Ta3 '-6H_U&/HuΟ#Uɯfۤ|7nl !y=҈ {n♴R3qMw1Rpy*%OG_uߛ 6/E.׃y#*Yr?,JP("K ׋B2e]W<8ag^"QMt^'yn*)5'p.1פ+K*Ւ +=T-+ݗ/r\L^\y(sg3@_D6mt/ whHLR2h >y4_'ᓙ—5;;ȇmyI`oY= [AаSD:a(w#8@q(#l/3/HPfjd*C$|k`Hhf D<(.e>yU>xC[w+WqLs Uo<m72čH)Y2W._rʕ%f^h/ (K̯w16{Ћ4YD.]>4;+kvPF=42̓tzg) @Dk'$)00!ђ| B zvG+.]O+I484H/ _wT>C%Y- 8YdlTkV& L?H;gaIf Ӛfs v1epu{Y >DBө.<.:"Q::TZj2%{X[q]S\% 07nB,dj0D֙c%q_P!uBu$i}wk(_Ts" ]dG 8xt1-Z$X-> z?w5XpĶp&ոHH{, Q0̱֎`"g+ɾ1RTsHJ\엩t wXroN{3  ə:5QMv30.b_#,Xֵs`6=aF6rHep34ov~/|Y@DТbډ%+WR 'K?+;BWUNĶ giV"2A: jybZT{-'<>tC@._c1a ѭ֢rwv L`%?d2!!Y8^JI*!P+ChKRޚ_;FCfpA ǬBb$I|]D+YiQБz|XY, uIj40Z(Vjʏ<-tf9S2_*pPadq@/A4*UX n<3LR.Cg"K2Ohq +O& `׿%֦ZT5EI$MU䴔ײVZ@]kޖ74PԖ.,!S4SZ¿C=rł Iyvٓ$fc4 u( +\iq@OJi?4a3 SyLJ@;CR 9ŚLÆj؋(񰁆b9!h$к6yW/YT.=4߭0XfhB163ܢJi%ܷLOWUhӪ:Hpvyi!2܀d$hĝ*]K7( nA<(oI[| +mF1W',%۱%{?,HVtfC#id.x0wdPARST +'#@ZA+-+kt'#Ao=B 6'+#"o@vK@Mu +Vb3@Y0Ng9%3)I+q\;%v> Ir[u-~-1.b 5,dyJ,;d`̸/y;%~V&s$4D?vQ:^JrڗSK[! tV3YfFxr5B_͍wpղT*¼SG705L@͖VHc.yf)fHۺg gRqoTfh{At6}k%ĸs V2Y}P+eݑKAȅ""/NLƹ/ttb/ʿ +/s}f0o~Zx/pġ$" +S6w)V<70|>(G]'D +Ib+>= 5  ̠BlS +ة>eq:=㨓S}35Hm*V!PC},;^ U07}N˾6~p ajVr" +Ik$/]kב _F1t1%Tҵ?725Nu}%Z\?\AK4,>%YY+]B7m<3&ٲ. 7JB\)4: +QcpMh švH M0H˷43Hy`_U.'JըYC(29X]2)Rn1uKy^_;W^y;`,X A5`2Q)Z\`ƈi j͗VjϒqPPe[Z YͧUp~XIH8Vi~CЅi>uN#$i(N\bMecĤB43̹b +d6AۚVnS~P'@gpLu>lo6D}=F8%vz N| +c•SUZ/h%oM+8*KQ%f8mU.;S +N5v@˜6 ¦p73Rp)d(wx;NzLnĐ'p%}bFA[d|HFc.ڀvV?l[xZ\zG/!1@M X ,ַ.GpM//3r /7jqX\;X!.H8n t9,]x0@As# +)_mC5A<ۂ*Z +(3b<0>[*kerM]8Dj+ +UghdQq{3lxW?q!&,rxT⁒ @k:&Z j7lR6U℻JGB rrBuLPT"HXg  5op'38b͊LÄOΠ>&YW(w,0"Pͫ}c QŗEI ytcMjL`EV]yqh +Y[s#Bӥ&C5@F;;rRbs?"eYx5kd;Fg()}PK ´XuA - +D:̽,,ؙ]6 @̻`YZ\[fɕʲ}v|zQm/Y&md⑷hŠ Nb'e9xf]7VƹjJ(Ih4 *a]Sl < `-A`v+;aQF騎ٔ G9Iuٵ)&]G,zKjiqe4w1*ZA\1*(G FĀFy(0<3NrXFwt2NZj~ Rم-MМVpU)`kx"ApA?=Y([) ؗn&.) ˆ:0f>h1~N} cKN6wq )%ax(BBiGNXQE342fN'Oce( 3`@ @Y].Xaz4( ћ&8Br4Y P=+}7g|C~alFe, 'VĿNhe.! }dr5RZ *ΞwlTln4B-%~\ *,я"a3 K_5-X+\ldORΫecw=NThK`Q)A( *OpW=nAn>GUUf٨8vHpwtZb,Q:HXNc2{x]ڦVZVP7^Pa@oY)&ex?.wU"YF5=*n⎬9\y2(;9#Nj{_ꭑDp6)V_BWZ!_u1tgz`)j;$ i,բ#ԍ)(uUg_->%$64cu Ujj CAXr^PB'@| yߐpud9;;g)as|EA" )USrEU9)5b +6K^YˠTEzbǝCk^EТԻɏF(!2Y%;Åv+CxdafP+\{ }:o;L:J>Yh7zw%a<_D)gf/+m ;1=,*# +.2uY?~.> 'ä6q 'Bh䐇b˼%dwB0p""I vbZ7NwkuGwhO)4@Ql"t)#Y`𛥢I=T! }eMC3ђ6aBgq,U[N,u[b1ـ&=|N L˟ySSmEk{fu/>~~ /⨺~n(AVN_4쭖;[^PL:}pt4i:?*eYጇOx FK`˅5@8Z]=N7= /c>fV̤@p<M-1&11猼ex/b: J'AKa^?M@n8_6T$CiVރГZ HMxuĆXI>Q+T|^>Bh, Yº?mI >h˿.yc͗:؍5*I\UzH֕8^jа5P6|,$#]B];Zh +sM]nuY"p繫(poTcv_c oZv,JUtG+=_pI6/7H\3 V/$drE6,%2|%`o' dͻNZ3>i4qѪ awagJ #>@@Jo]538,&z%+ev7wSpoEFH)>s/f6,4DÛ!jv7,ػAY&<Y4 "wh ̈́Kڳ]q:d v!M_lz28*ۤ5Ld|vRsxR^Yg*K̥+dw\D3D e3Vqz:;BS"x7g2qb|w?;yK'OtUpp)=+ -'h;卦gHpW3jcfD [|]|v)ΉۥxʣFIڢ[O{(nLx_WtA +\ +󞅨xɶƼmn~1KF.I* 1 \W,D= +;s_w&jBή\"djZ/U!x n"(ؐh\33q MP &ȑ sH=R8"J#͎om8zXd ڇnZ5?EPV@ ZO"._<<1NYј&d;ACvU{E4fhU$\l$@|J9 +QN +7?w̬al&wZSX9 W l`XKJ,j>\EMB`yzV6ur + @L 1 aW//ܟJF Nw7=}qLkS\CR=]ܮN^ q'(h^t?RmrྑJ&HE +ٸ? 3t6A/S!\]+e7@JбUFQqN,$OqF 4jY`&C(ouzGbP;pduvp[agLydFp ( I qwjĒWxJ$IxΕoq&#m/ %p&()N "mɷXkRHYmEZnnDrxb`eOIxpy#Z$mrG>HNu&Uo/ _ S;7wIM Uyh'9hܻCQ +QJ4 qtR *VBO\{" +DMJzٖҕXJv+mI\,e5鍘|Ze J^DPoSD Bd YgUWG{[HuxElcl# +QʶAx V0!u&ӄݝsNC!C@>E +ݓNsPS׳ݚ^M1qO>9ߕ-2͓ `L: >t-hvkmmc\^٭bϬƀy,j7c_4 Y%K{S>hV7L+5/F[%6Ǖ@O4<q&mN+U5ˑlW54yQ;"_Y}NX#tۻz>Kol+9Y2Huv4_yd_M&qn 8!6lj1'JYVj%i\0gI0K}e7$YIJW +6771IEN(:Mory!W*frM +ѣxI!NCUȹ?CLK ' @Jc$ڹW3'7Rct̉lH7Xjə NM6JUC.vgܰ(&5#r(a@| 7CJ]<r"h4+B %jC\ߧn35BMjaΓX [Jͨf +f/= _KAH)/8!haHc򴾧ͱ97``zixn%RåwiÑ܆ys<1"Otr\x]H2Ut':6xw1hz0C ˼SޛR#Ec%f>/c5 +SCp*f ɷ\o`lhX*R~!3b-#RPn#`# +5x +SXI)d|-R B֤iihk<] +dPlGLV2`oQ߇l W%V˨ D[ٟBL }ib3 Qi^z!P:SeV$ـVK9S' ЌlL*͜Df-" +cr';tG<`,ܙr[F<$r2*2A"Mi:!pbS6*F*DΣ?B2P!rw(WQ'gQںvr.#7PӶlO4|/`[kʖ2dp}BcTWf!*i<(x@,P Btӥoi?3NAγx Y3 /Ft?onL_›Oׇ\F]o?N),OOYR'tS +c3wMNXib\VZ.%"nz6GIzYi[u[r)5iՊu_"FϹJ4Gf 6ѾVlN/ y?#B9K4otb0߱thM +h@d˫wG3.D"a*o(*^#DŀLD rjd i X5рI%sDA +- + O/Js +v~+#3"Ly2L X'YcNٿ~h|3HiMg&X%(21b& 7ˬZrNDIn{39!b$)U){Hߺn[[ f4 Ƶ<} =l|S~.=z_*]},[PZdqֱXSJ _#m+5#:mI[A:S;Ozp˶4eb h?81N#R-V^꭮caZ~ީq䝍W%ju%=ɢ٫V@aVcV84[Uְ?C@rSh" NCpԓRRt"e> F_n;VH2T'`Ify128[-wi"2x& : $kvRp>'kvKzEaVh?>C[GC-?o!Iyƿ2lQQqq|fYDw9[ ɲ>+FKjAQ?n<ᨈLVa x!dzcOw=۠rr0]ߪ4*rf[Xߍ\ |aYO?izj~m(:.Lb[؜]p[ j(mߔl4rTD% [<\XG B.Fy%be7\aI"^Û7Fdwp}K3x!p"C> \z0~!}d BညzӋ<+3݁05>"]q@ʼnG:\aIH{܊89`ԓ&;fU.5s#V^ʅ|9Eыۇ^&{[!w{xu +1.>U+㫪ZVm8 ˿nՈ%Š׋$#㥇َ"kO[0nYjؗ+5#V0 78M͈4H̖5&%TUX=lpbd1&wEs: + +w;@MTIe`"DGOs7o㙕M<|1bWGa(zj +ɞ>Byjs@9PCt(QY"NGz)Kztju_,ZM *E=Bnv(̐`ΪSU5E*ƑX((-sd4L' QދMN@½1BtK.oOOf}AuP+Q # >of9m /Q 4MlKc kO/hwEgu8uz3Wu9(TXkZs6g;l@_N8jd/F$_{:3E1n*{&D2}2[43е"f[]0'\LH`\NUHǐ6G c&E Npi$ ]>Tb}%ߒ +K%-tpa9? \vF ESe2⼄~=/6`] !G,k%( +&,eX˱|-7n',1#ÏCp8j</|iY?-Ïv(I5j @.sj6xb'mBB!##ji SrVPv/ȾD=jsFYj +BE}5ZA%\q -$|& W!ӿdSwh?b k"茕%Ek ͐* 8J`]4Q&Y д%|T*{dc(`XàI[Fڒ%є8 6̖7P݆KBfVOPcUe(;oD҂'+6''܀V&=/ӖjbSZHG+cTg*6y4*-^!7=9(7=)w? }kK]7W7ECvMMZLC8cOLaݶP+בx~_ D6D:/6@ 1h6濇sZ_}Ч:"RC&\0")@TAhxZ֥-5'z/rlܨ=Lj1 `uldn\ 8yE8ǧ K!M,Fuliދ:į'Ոc&6!}u]JlZ$o1 A.ڗ2IRBoN9 a6L$ ΉC7@W;M¶[0B'hDy@KhkjP,2nht`0#ofrB95AFS]WR@1}qto~ ͳ\2PW%xbUeœom5z;4w/3 '@iM{0FN }5x^+@__!(E-C1Wjsy+JYJ'X# +Ɯ>1cb૘:;8IL^U!!^ 0" V2wb2:2 157 DZzcq1z+cfuӉW;`OV*vpL )_$BA{(6Ɉ +2hߺ``Bc-Jptb^Aq%T@!l'0w +KGFʷ@^œׇϘX1?]يj.F˙g;a}sderSaʶ"ba!zF%_5= +6sX\AdY(_97~zɟD>ђ=vѠ9%*dI+֤;N`5oK_^:<0!ZP[n8ͧw~:м/,aap\-Hd(lXwc++x> LqZ??ϤrF`nu{0`A2?݌5Tk<a2$*_4X~۔L=IDž^JE2 .hQȷV伃 u e&8;c3 k-*ph_.vO]! ˘1/ȡ+:=s쓈?)؊R~ևp'S5Ĩ:E d4gg;hdP}A]!0=ˇy +tBҒ#ty 0Y iHaEҴK$ (j RÚQLI^4=Db,ľyTHڤ#- |7}̱5q IN*{T]z-{DEN(lMm&AӋB&<ن,r]ϔI8#ڹE?+ Yg^pT7n,K_[)NbWpe[<Ʌ$` +7l.dH 'Lj-n-v^m58(^GxsdJ$ 7%5^U.ʂOZ- ^t, .2{wRLsPXQ|FQ,`B}ZigepS+,e +>g }@_E>K(zHA-|&d[ayCRC E]Jo'TO`ia|ef:;ƈEt.k쮔 @%,?ĢGxNV,uVL֊5 lڑg8!yxY\ 9=gߠ@^щpNTqԢTcTKX髴3A̦ss6:^I&ל)t}h\@6Qwy'23T3 sYLf<ǩY-rqNt(t/o ;To=pp~+ 2#ƥ yA~3z9p!.ڲޓ#U+:xI KL/}GbY֪06g9+=S'\8ʼ~,MxeI̓JVVD|g\̓ZEu&(G;Vp| +JDaEV A`\xv'iy%Fj!\?""RzRfDGb4;epJ[4* $_:(Mu`B\1ěP%L*E6c"܌x[e=`R_Yo ]$]@*cf.3u> ++uhltd^09S/i?@!7ScޅK~s=;"{/-j_Ov4SpQ u*2^caD֕5‰LVM g=&Hr3̴,w1lj;`bNE<&~aS^o{d%M2OK[kb-T周 u}km˝׬ -z3xmxw@ ۶Gppp*%E-)D@]_CFpZh-&t:F'~C߱%eIΩt$ uV9 +'YʸDz]Η6vF^~Qr҅ӋU߱57AEBt}IoyHE: e4m|r5lM"ʌT~ٶ>r&1ڗ7s<gOjC_KFľud$\z]w9S^s/t& tNz<ko삾U|}\~1hY$Svkye($D`Q38`}Ie|@OOcӳ| MĄ֪9]_4!Π1 endstream endobj 62 0 obj <>stream +wFb$ /VX2lK +PMesjbл3+,r.e٤vYZ Rw cd af jeD7CJS`ٞ>͎ x'R \LsG՚C&q l`JC=G*=s,&[OPT + ܈VBe\YN$*c\hmE tݱ,7T(,l3Z& +J?cC" z(k#kg'v܇E J%YC%AObKWD<3ge34ᢾ(j9z"Q +%(I:?ؓnNH17Y1CFV^VD4$ j9Tyx.'?\+`A+*Hq>B!5`5\UO0weobXz'zL~葑|%(g˟rx-xbjؑj} +?n`͓@\3"G(ð֯uF>bO}ї!@VR7Ƴ#0߈f^^5I. F?l(TC<5ijw8EI;u WbxX Pg_{␘q7Òw%i۳cG1Fnʃ@7ط6ÂEgqᘙJ9;Q"&(![YgiEG"gC6Թ$rs-$ݹI4YmW:%mM=#C&Ԫn4AiҌ&GŪ`zR;%JHf"cqQW]*=⡄5_GzPna0Q$ Jr4*jna *ydd7Zg=i:3#TÞ; }_ FH'J\ib\LmBbQ⴬O׶. Į,F?7|x{Aş&nj~w;إ--GO׼DAkF4Ջ'PXcti x@W[UDr.lG]#L޳`:2bߡr,%xÌ¢G}"tю1 +P4㭥ռW.HǾ#j7$YPoڼO`K%)Ay(/ӂ($-_BTRiGykǝ&PThgN՝# +(b5;3bYyksUCꡃKyKu>NF* dtn*ȇo "aF? "{@GWn]𢡄#i\z^Kҍl)o6xLwU] dz݌NTkcQ5 9bx/̱I(I>V@Slܓ#<4b9ZK306#))^0G̃]EuB0FM5 +UV^lYQkI."81rʉk^ӈ,z7݁Vt÷,8J.lTOzd"g wֆHt孫!ھ |dbovٲ؈Z-q l| x_kL( -NjnA0 Ll,7 +&wu@7\Ճ@9iENDza,!F50F \b"=bP]A&gAa#;RL,2C{<3u QM 3KiӇUtco<9GhV5%2p8A(u8YC}3B(^}(-A}hp/[X#`ܦ@)ƃ3 3C,[ռޔ 2CwZB2zaeCFr1^rzny%ƅY љF$M +6#&L;:'E&&!q=>;$h>Xn_V̂  lfKt k.q<ގ?̉'<`] xD ;am9w@iPofolq>N(V +4v}'e ,zM腈^+EiA&?Tb=:~FۧyxSPzJD3YyBᐖ82:{+ͥzB"WHΏ!7]TGcbnfO%eY%b$o&6´!1t;_ KA ќe1^Y&3b4@]%!RP4ۘĬ g:\G1fL] 7&CtE\I$_./ޭ7ȹbgeLtjKƍ{9 M]YNG5#) W#Ɲoma%mn|!\st!Ea 0 ./~eފʠ U$ m&Eթ/c"uPoh?Bށt46-4xGJh0`4ր|8j>JZED=o;G)d"2oRt!0;<{b:&:Ul [fW%gVq+ I,6êهQ=HB+CrMؙG)[t J _jQ.]X>g#%d 9;x{C {6v(scFqP~"O𨾍x-Y?Xk +Թ'Iq哙)ϫ">ql̸r>-ar_ YN6}4z6Z0(a{Ԯx "?UNs9ǂΫ"8Ϭ qP> :Z絓&dxA(y^AticeT`=63A r&IX̡~P)FYOE Q'(/E3%?aGῈ6b͖*Ʒ(肬ؠ@wn5S8NT ~a"GB=u)Vl|3M ~9F-jpZJʣdH SM$`2gOm++1F$LK9%>/Y]Uhm5!y ]@0;Cr٭yklha {.%nk +x<ڽ5Q(!R?{Y-(_t,iKkVFHFt50{D 9ODBp!6k %BafG<% tAE$?b4."1l2`Ux l_^[0$=}୴QQ +z_ZޗDx;/7Ւduue-c rd^TX3h;YcU&T]P|v c Rp}TsPİ݀P:"P[|i^+ c2*@ +h:UǫAF%q}q|XTy?ƈ)l|c$_U_*kbf19 )Z/l|k0 %AvESE:6d$erB|DcSX`8K֐h ZJH3}+ulܵ<@ۗǣj<6#͢2Vl#qF_x{Uynb\XS& :i4ܯH7Jbc!kaD*#}-ǰtBĝBBg۪>*͟naFƃA7/1Be*W9U>Z  mu^0P6޴ɃYR(dAI!eM7Z68_=rѳvvw [쇼H%R&G9, &Mw$AmkϠuմ,P4l#B+ "r^vQSefP)\I ,*=#YZ34V=s/t㰺 +_Yߡ!8%{A @<ކ̓ l$UNLxjYW  Yqǣ;N.xb~uD˞0>dxYOC"* +qRhpPWJu:e=#dmNF"S;E~BiؚɆr谂E{qy)K);O]a*Wsҩe 77!_E8wSRD2v=/mRzMU/P^"rY_.Y&t+AX!.d(,ܵ5D:W`ii{M۽R6(2۝|ɲtb@,b0C zՐR{CmV ;RWf3<䷖4`KfI !DQ,aP,sar*l@LgTˈ\[AfEg!wTvs(w%Ut@Tי-DE34\!,,vUEdJf :H s++@B{-͒u-DeN(_Y3C4Gfʈīeyڥ2lL1\WcYsQ^ eݻ>+sCƒmJnwHFp=V_NM&4n,ִBs'aۆ`,t.;c.Zcz!e,Ś 5ކJM>.3&#3e;2zȹ,gW]CЯ7g: +y.;8 ߂Rzۜ i9qBZ9_&jV>Hnd9~x(\H֊fiδ$3{lH~&o%Oq9Q#WDfxdMhBGrc$qxUyC;1vXL.mRJ+S>G&\򇋷Ro%cPmϾ֛utJ%,ya1,cD>(B`g{urgtBސE>Doage@# px5qE`@_Iۼ-nebJuᏂFr rX.@teKN J=7"@9I~f8Ƨ 4Ul.M<~uh sf CשLp,A>;!J,:^GEWtfeڤs/k=8}tγW(€)R*UWTHk Yd`u-KA! rENݖфyEo,~z>6ƞ1H1)&yZQhQ]<)amg4X2TѠ4W{0gHF1=F#f}㡑 Vs9 ܸ6z)ٍXeiۛk8byZx7 }M5V=1%fyv!2!<ҲQQLk, R)%=H\d>m)txAњ GvZȞu{ȴf_bF8Y&sƠ,+#i0k>$[lm<&̈2!қshsӡ#47I26 2&&`Qam-&xn:u=nFϒ,dĤ +z,[2d>OHr2ms/+u7O.~eџ植._ zN 1!$Ml#^s?jP &G?;:kUv_lɭZ"4iAb0'ry*^# %b~O)C3S*Q09 kCl @_SOgGOuZŒp<½X޹b 4dBt~VSg,>mGv [j@Պ"jP__GzTQ3˱PWL KuFƁRGϛ0"[r:U$'! +绖G,yX8P(6P&DFGu ;zLSy{EV\oڏqхesuPT\Hn$6 h'g|b]1;81)Q4qI1yf1%?epaDp]q`]WH*,nba"oٮoT]!Tv8I| +K #bc +KjU˱^ +IQ s +?}p& Þߍ襩_]dYHBcJکSJeIGЁ~vq-miwUC c + H0\&0/3D|'D~ dLӅ|F 4Yb=c?nH'bATݗl`F$*,7oDFH6GCD+o,4#@T$GJϼf^A 5Y }4/Do Đl犕YX?k7' /&8|7 +gJ tAs| +Ǜ9l-"XHZW0e56K JK*I}Pчc#wC1|ՅIhljsGJ#eKݦ?VAx,m=?&7[IvQm +y47± +>b:jʱ_\ v%r$ + p ~71La ?@.CX]7DJ`Y|cH;kR\j61:qCu +$W\u8? dr!KC;MphZ╖{4ӹmAMy!鄬mzksZ >@Gv|'+P+[BḶ́n ECQn9uřLJX.RC+d#|]VrA<#l&x Yz"!ߢJ!GK.PkŁ׶P$3 Iek->ƶtqgfM@ TS~Zfh Qu\MlS$Zdm8*;JEaf#[pY+RuB(EKhs% C(<-yR^EyUڀjl(/9 kyD҆gӃp ]FqBMY:)*Wܼ q"+df{sieg\8q怳5S1d 04$cB砣BBTvuuxi*o&J.kj4?Q$g _kO0F4rx c*`bocދYTѷ&H ûx@Kt@ы!֖G5pQv&mYcoTLD񦚋ћJ?*k5~>=B,|7\A訏YwS>6ӎnFzy{VЯy,@ي~ c(3Èj$S'c̒ZDJ__S=)0ovl[ϒvqlo۪6e:\Vn燹^XJVliWqLdm0"bzJ BywiqŐ^%Qkd-9"^ 53)H.? *r:\X =bxyYoH= +\ "32C/78YIX29 +(\&;̣/DF8zeh@Tuj=Cݵmy.^p|kf@$@=Vr:lg@fP^/ +{hZ\d S9y_O#L9||$a +T9+yʐ$+dNZMf \#5 +;/Ʌ. G'HʖUw桠H:f1g~^vũP{p{~ iXcv`/G A+u +[W?q&7nH4r݁Xψ;gi˔E^i!_p35Nd6PR4s@/.id_ W V*}V2vZi&LIB"SWIVf/~=GuB5<ľȸ*a" iKZ۲1Wyo іXz<@TИ\fu /c6Fy"&a[2 (Ƞod5wY6rī%2m"3+<ឥ}1v#F|vMMo2`(pCw]t9_|1yQHjAN@ +>3ZiD_}c I2} t' ++22G2d + W~U]^PvOŴY7 +i]x1;-|ҢGɹL2%:MVzY ;ѤzQ=m +xwenDKEgX?UɲT=9>Νpʁ?Y?bC*{Ift176.p_Sv69yh=Yduf8΅f\vBrj㒍^FI_"1CN9)aWoJ*L6qPޯ~ߪo +4*mU@8m)2SPJ(A:IáKCdƼ|t0e=avnfvh 9gwBM6nGh\_F`" +oy!*]s]#;x M)}JdBERgtEȀf18ZZ*Q;Z+2`DQuؓ_l[K,?>>5RzzJLٲ*< ȳe #\|JnptP#2Q@RڣLZ*Cc#4,IZ:54U7JX}C>;c ɨC-Q p N[]YamgkQ_@{ek5!\){P{:_}~{U7}S6t)@⯊SJU-h2zgj{i34[#SK7Bl^P_„LȮ/SgJ둳W!tF.&oPXDXʟnq$ڎ+=ԐbV@Nhxq=m%t{O Ciʗ'f,he ə@]ML Oj40f @9D1y~ITL/4vj嬎k4NXU1cCa((:6{R  k/0s3`1m-nw+ClNa/~mG%@*g 7x>Q?R4py,5P3ZPAR r}uچNlbh6L۬/rQ ?[/v",a @"#Pɂa8sL55p7\4%qt .WO(fx N%!v2[^ +W +L%nݯ)ubSo +F (}J)mE Yl\\nFhNCs5L1S_ }8g/9}9GAS2VwpZ pqtE'doF{^a (uu~u5`$*Xɨ-*%k|w>^F󠌍S5A #v~`DɶNk@쟼@UʉiIY^[ŕ ƕ/ (SXEKuErʘBɋ繈wR>bE~uóIVv C6淓7!#D>")Pɟ /% W="`g$) .>\ XjҷnwےF,1w7L 2DMP~siNaaK64%'F',]qq=Q5Z}~iN8…D8&!eMq'9w\7(##hpnOO W zV6iKLMH e?pa+s -x ,Ӑ We[ \~CQE'?>jT8+,kZry%Q)jP\`w΋H|prhS +ùRR,η{@D :H=lxWF9cX)҇=W)D#Q`mi0,|I s*v +1 ^d)I5$aBRXp)~ 2GRGlvX[ڇGBCp݅uhE//ĢQ|B~kKJl\Uro +mt?ROZj4Dכ*YhXT<{Ad+>ƪyHg)YRk.U6GC1TC+FDVGI[Iu?;a"uSMZd7H|uS7ġxJ_ 1wKdW?u0Q `c}Ԡ9nR*/qĵjF'"pDj.i},r-Ô*s(<M 0U/6Λ:" v*mStna +#{ͅ]E6YȳH`.u&~^6ʘ=N& %v6^XiJC^R;0<˼:"Wt(<Ӧxܘ\\c~fiHU hӗbfS3$G7,QЀ?3ֳ˷fE@L&ql\g9µ +uRyoB&l-32@'Q$lryVőKn3,G_'ᛠeG5lGn8e0UTHKʜ߬r'Ii~lD1 +A[5ikN ׂR*^Łͣ +Z7E^;.T2*^ " Gڨ8@*!LE\a"hH|S;3&ơ{6/ZiXq*tY ϐIkw#)]3}V*캑1XHϒ"QSqgDj*O*Yt˿m"<:?g4MwΞӻ6ȇoiJu dJ/]c?*_{ԎÜ%^GO پi>}UǬt9'eN"ћ#٘㠂"ϗR}qIc^/ ؙ -1O^T k|} ltI d`)8~T &@T!D&!;S7 RHB[ iH0e6_ǁb9Al4JAi-uI 8< 88UOdln'ۅ-EPHб(% MM Y;o/b,L$0F?B + Ԗp N௨aDK} t^ȚQ;hҁ0LUvc]_ Qְ9n19"Swc +"tFLiObwkҜxgA- WxW UQz!u4yYB]YT(]~He[jqWDw[H1׃vymKY<4e?Q=FնPoHp}OD`M1阌C,fr: +`$f>oE`+*jF }8A"IP44!#p R$H0Eݴ 5~99XYtvrV륗*{l|НW|&N*bm u<ӕ\]X^\fV|vNyz@{3#4"=viZ0u[C>A{6?SN&U<'4lk)\mCk9`b01D(2X~9nzaH Z"ܸ4S0 B8 [56 "(8'_14*mFY?f[ ]qӞ=LgȆEۙ5 +&c};B$7hRr0d~#iT˗,tt{)[nG)(݅.xWwұa&F + +qkƯM Z֌cKҗ4vMJ 'WY#r$S +Y۸3S[ڶ`u[F[ضٳ-_QtvԷm{ښuaywNŰy{gBU?@`V;wh*hC!!)FU8atM3i3MQ0 'ՔbrRQYŇKK(E%FK *X`̀VFI,/E'H C|˶oŰnA=A7< +PmjK7hJ-STSM { )-M1E)VN*~iR #9ޑ#}\"wc İ]'ƂqN +M  +Qb GbQ(d/!`-sBgjB ā"`[Iv?ڏi YdzsBbX}69ƶr.e Y6>+GPoh' [7HDR ^%|/.YL. hiBd%}䑻ݯܝ}wgGޙ;}{ww.xt~xwN--# B`0 $5Y 0*22~j\HK)0@Ͱ!pq:W~w\\-{: bȈ)#-R|Puz="%?lU2 9f(ˎ ݽȫk5:2\5Ap\b +xae‰HooQ/n+(H˔ )J?UKZO +cfK*cl v4ڈ7:&F B\%tE#׵prղ E/n`ht^lc&h`^^X¶:*89ME<1_mϛzbk8LQ/k nH9U(@琱 #_m0L;naFE4fgÞ3ׄwԵJd&Ktۖµnk;f32/uM,R(#iC~fB"9@vӼW| bT#mʪD/H Z zD;SSKTEۨ~XG5M,=7gNKMBWVm^ h΍WD|4yrO.&Ia8w>uW&)ӉF˨[yT'&c5#%uv:O3VѰp .'Z,Np^ QV GZ"FF`>\KI*MU =5AZMl:F7!jlȠ2jx9x3"@!:L*~&}P3<}ţ{ظ VɆ=lx*^ш!K*drx[lc$ϦJU}%6i6nlK3Gg[朓?G9:\Gkuv6}fwaBR(bd#ׅ%E*hH%jl"bqc$ªjL '7`zf>JoZ5ft@BL 2#brKSp;vx,k*M**SHb͊I1  + + tٿHEh8 =2j[ᴑ0.d ɼ)CjCɐX0E3&PV:x&!+tio|2j%bijZی^1]p]H.2j%ٕ.y~<Z *DByk#=1ȼ~ 1.7p{ڢ+)C,F:F^.ŧ8߀MRYWl4c} 6D=}m5{4 A0f*@<]rJ +T5DHDBh1$b=^^gnc.AoPJdz%Ԛ^+Nh[C`,1n2\#ׄSJ! +4D4猯'wA#e "$/21fpd%EttL=Õ~~aH+L 9kYJ'ZXwhG}gA^ 03a(re<$H|G5E6e;=1] l"'L&l-#7A;]bk 8=uL(cGE]0ӿ?K쇑+p Ϳ<ܤ( }x7]myUur=tmFVbCX|ӮaqD!`J᱌g"ZkZFVWETnRN:O.,bFSϫaETTǮKCO)̘Lg(bFF3QGw\tP n'8fXnL+XыHX~WPGҽ޸AK"ǘQ% +JM[!3c; 'tI[d3O͝GPR1ҍ3Rtb OEcd<ʦ|lꨢ6E#^1p#.asz[Ynl@(}vGHI_ Bjd #1<$݊W_̲*%W=''uРމ$dLqaǝ1jhva3XIgPqVL!^J#P&fY294 < `:PG~ie H@):M )Ǭ._N|Qx{F Nua;.@NJˤ EsKF@ފD+z +XV؃I N4P"/&bq +S[ Z_sr3$~ y4j@@dmH +rQp$hhL(Mh󺀚,[|hգFt[sC,0EsF"5K=G(L ab!I\$rMda"{ۡZH73%Wk6Tۧ:Ek v9$v͏cًɧcP!i-w.[i%d' 7RڇVz^y8X!=c^5VQ[{q{3" Ζ[TtEMln.ֽEἷ/ƐwN+HVH T1eB>)U~Xa-9?hřvFSiؗs̫ـBgM,kS7;ccL +&I + C0N]NӦ(" #ýτM"%dž|=fғQDi[Ҋ\ofR@?IغpAM 3ή92WV"wd@}IM9#B-‹cxA-~k! PG-λ $mALfKc尃+7vHN e)To6Y5^3Zxhl ȰI\١wuGL]Z}܀g2֗@8)Ћ |(ufEA*nEN/d^"v 2?H(@P ͑\Vy+/R A1Y)Y +T4 7M_{, nX&˸>!W3µ(]&H' +h/j |q1'eHC):?"!P}̱͵Ae&PʙѠ{+Vրݾ'Qtv^t  Q!: W;͇m p`7OiJz..9~e)Z(s~`̓^O +YI[[>}]b$oRpt'Ȣ@:Y!TnF v,"&!B{4Iv1>u]db- +S$hYhT=!K`԰I0XEX|l5EdrAl=g gc{(u\%g{cOH+NmhO"@Z4Ж1a [,0>\h6G^wOXA?[tBCZrClY* [ڬ;$Ah *6DW˸N$VW?96v#1͹."^);h>clRgÊb*|ˬ-%bZ~h.4`Gx'(]%4,4'qMEFִnalRuN^FlωZ`2; +nb^G!][Z47\ .|B(Ŗ˨۟0YV\5$dm'YWp{Ԉ5ԅ̯g嵙`N*E+Mi^$`Gw :'a;X3pӟ0 +S T i.v1r T vh)8,f!;fb[`l3a 2MJesqdV*+"[q\Ln)VmHyhGmʂU!Z(M3 + R spF+7d GF &֏E7~[ͣ-;a+ }?=y74HV5>N&!\ P,7^fw3ߥOT6I:]TĐܗdgEős7ޟQ>`),XtS#p#g4= Kιi< +GH|qvcm(-o4#|ϰ:qLNT5yV3TdaR@A~+56۞d+\N$ d t@Ӝ(t 54tS@0SuuM~ee :AzIcν{#J=rc'A |r0[^Nl~x+xLٔea᩵fqm|0k,u93B-RԂEGWz! ZFZ) **idXPNE=D`1Ʈl~yҾ:rrtVZW#hZ22ᥔOքjr=S =duࠚ|o"n*qw+a@yX_w] 4bעH >bF͕X9>W᢯՘+xߑgΜȣq |RC80ĸ^9hOYcG1  Zӡ:(\p7QtYRu;' tؑv䖳dɦe}m̽}<%C ּpK_~HKOʟD[vo9Lt؊0#sZL `@y@7| +" bK!J*" cn-=:'4nZqw٫VJ$,C w(7unGo66H^B8`x1dH,Х5cthv{1lߐu%[I h49(2LvDne Dy=DPr\0soI}r5k)8 +4.Ѩ:V6' += +@#x36 6$Q ƿn 9"GHwN,3F+t۵p?x}vtd?"iϒ;4Q%R~t=si+*H܉v=d!V*˅$̞{3id BI O231ZfDK~'r b$:r#Ȭ&uΙƕ.ƛz#aؾl6 #Q.|"J鶋|X|&'{J|ȖsI/Pw+ #_=&y zP;;;-PާNnŪPY4LLλXUyVVUd'`喤rF:ID_ͤĀZQ~ /~hsi]-Keg3fG.>sΗzX>dJ4@Yy,;}U9l_NgəKHej7υ6&NJVm/iu"ωh,wٻ(Un$Igj:Ȕ吘1K;8RW(/[>Ťr2ѰͶq*?@sm([m-zho.(KXa$umZeOo2 JK! _< p ?NvKH-rsB{|«&zz*, ĵ3?NI0-tZ5^*}FYƀAlglݏsނ#3Sb5C iU5M @A›<XҦ +) Q6ާ~XOs|_P-mf^z! c϶EF#ak PK8>4x?/([6tfly hk}{e0_h4#r0J(]' b:OrCĕf̴ֳUQ <Ɓi\)𴙍TpT`,s=++X&ו NnؐKl񍥔pj*= V 'V%lZiC,(V./گ( +D +hd-#r íN*P{r.J<xV'H&scf ZBUƸ <}Cl#vv|2+Tp'H*ClV48ؐ̕$( i{쑎-;+ERf_YAė0t*%;7XCūVEu@~2Kb@`?vbmR^I\^K'Lh.h}(q@?8+S87)'Юg/>gwS_BzF $ZkC~bd\q29p|y7$|x]>v|'11-v^Q[r#պPr%#;At֠{#Iϯׁ C@€.=RIhĭ0|6R'݆ȭFNV9ԢYiމ@O[a` _+tv{~n-Q 6:6`ɨ&B]! +,T]e +)Tr QZ%vyQ-s_="| Q)s1uC]ebZL;ف .(7~>Li/ +r\ƊbĨncq_o_#'|BTF$ykK02^\}ƞս= qB]uXVʑ{Lռ'a񪇖+x'g<ذ lT% $ {o]jW}XYv]tfW7B#z+ZJvr?ue R ('Sziފ$u Az21^#Eqk5px- +LIVE61"89a0/aQҧ". hI:Q-tXc9FQLX_/U[Ȁ?hLoI8/;5qFT&QM-!jnYtM[/gY\I Iύ. \d4Yv_Oq@g`<]J(iꧣZ%}R:gtjPa5X*"2kOSu!F4/ W90ն]$Gh9ZfAQhٰ^Bs,{K5!% !R& GkuXko3qfXsXm X"8Me"꧉bl؈V}V')=Fuj$e}|`h)kB\нLތ .Op19Ϊ$k롛׫Y.Ku1J-aKQW?cﮡ.v&) >[]L I5]^0"q?DhLcXGj/.!V'T(T`&޼Dн@K2 5~WYX- +I -! ˧x._.ǀdF2TGYxFc,a[*wәKA;23 Ö Y\C/2p'T1ZK'87.,?Q8A?ӧd )DdMOVZk!B4oX0xeO)9.d/(Nmcz!_ uˣDA~%k~GO4Т6wQe*/ő9o0`%SD=3X­Mc +5h9T%rAgT:c>CFgDžФ72&[jǽM(~֔0.qtïH+eA4|:SЯ!bkVi0 vA K8+, xeR +ut(؇ڱ9_n" nz2?`$'VEظƈ>b ~DK/>Rpd& /g͒AQ?u&o/]-`*D_Y  !}_>xkK}?1}X3KfIS/_BPɱ7ٷ 5B8۹"e p`&MLtaf{sH2,|P4)b ~I%(AD_"{,Z0M$f 9ƫT-=tb`=%3VaWe&D s*N}.##@AFcPޡhp 5~؆vZy!snG` WT>{ .4qo{dqBft܊Οq]@veovpౣ/|4;a|3ۆ ~wEݚ 0iPxjmbצ4M#V{@W%Nzin}Eodcqf}I UbO9&$,.3s<^2CT@~ 5{ }`HI>@a3ÀYH)cvX + =h& M qYJtD +嘢jJݎOof}=A6K}8( =P6֧/^.xUNd3r;BIF /v{oiy$[px7#p0ZBlvj~,$~aOu5\ZRr%Vnot +_!Hc$F,-|S3 q`\G=%dL汩gϝN3]"ZCȌXK>oJN +UfC:/@ ؽ53 KNI}kIkq¯>FTv]eFr B3؊EsER1:x"R:℡V!Oxn)&C̮]SV842:g0jUJW31 .w]ܸdA+&K U h}12W'Y,oЏwsosG?Ia̡p‰.# @xS]0U2&3 +>F!@@_vr>ZQ6wh}{]Smb5HQ uA'@ 6,I:`zF]Ekx6yݘB|ܸKj-&CmYx?PH]K" b;+qt1ȵ>^\R{?[b!s?n$wwӽ!-w{$eI}LPj=JTfM-d?X M,ll.@? GEcwpAea +{ t g,mVŒaŧʞo5oM`  +F꒚_#J1i% rI=SDOҞXD!I`OF$=RP{vLmKj#UM;nƖYa==Bu}nZ@C:7~7{eS6#HT<;N 2YwG\ ^/drӉ7VϬHSbLʛwܣɕPsUHo)?Lji +Nuitw\%wǴqCQTm(EyZ=\BR6l{b: ׸ s,H _omEV~´-"] {[[^$ E!ôGr NSM|Q)UbH@`DA 3"& |*w@-V޻צDz!R-*n@H.< 4.@qE<0p^=um#BnSCh+f +qU731kEIF7VR>& \+}9jcrA4҄cݔ\Q +Dj˚|J u8,/g}*kv%YR*iT΢ PK%v +2.Y@A ̹l[lf + ýary fօVbIg, Kn$m5d^8>Yu9E"ymp܌QkCAF,<H !DVHb7H,AY60A< ku{ȟ Z܅XxƋ-8)XPtfS "a{-XxҍEl;@Dnhd9plLY㠆 _AW|}vK? ,Y/Fg{d/A}ɐǏĹ+2sP[p" ct\@aM]Ki\t8#d|S JhoEho%)L^JfIZגnM7{Ss vۀJpWMl{Awzr` +8m>BYn*NGb*3,9ҵ"Ea泱yCJ`Y)OfPv'm!c[y]Vf4" KbėE+ 3.5oO (Q: ^XƅNȽ=8B2e7ӎ,7 c]pjB2'=t[g؉OA2ǥ= y_OUlYT;KU +lt\Ц !I"rګiabSQRY{Vv'FF4%l= +;*I4}_ K><ħjA +N*}냖5a}}ͬj `dJO.5HNǖF v5*0ɒ(fEGu@R@U[=mX[ &_"Ǎt XFuxsb譃CEtp5%)! NyVDƗZ w*eYe q!9mQ.Nl?Px>H% jM@L5/w L7F.E%_󪱄,6!^н| }qCN g/po1[d^!U.6,*Ft"ŽPޖEv8TL@5]r;KŌ,}?v Y3!QI< ֻg\J ס݆@ Mɡ`As#@Ir,PҧU)@may^qy+kaڷʣnMX*R"t?N>T;J#)vSP|rySow0.K7R`'ё:4:(xc}x"ʅItMp7 @mFffj 0!0x2C: +-02x`~R.SE BGO4.c*R#f P0ZͲ;[m +>q蛕Ӽya&/da$ebaCpk ІC=ӥeF|n[Z `O|z!g9^TT*+(@!вn1MQԷ0Y\ f:I"Ʉiw6/M@l,rd-HT T:Q٫eE6"[xZ + +LZ !L15D"a8UfW;r z? 6~-t %^W +F!Y::Q9? L+esdEFWξ-4CgCP|lJC} Ȗ"nYG}4ۨG#`Tuy- +^of;PQg7͠١ @B\MZqm &vB +-ĄGT}&]a=jhݷ?y|= C:F廹q5IaOk7G*B+!):jG@ +cM*LۆŨdK lq[fk54JxUN<QFX@ k~2 qF;BF6Gfef +dCjM,.;kzf3{CfzxMk"`J6]ެuTl )mh*}`3R?7K?TP'-FQK+Tp`KЀa +q1IM(xe|%%U4#݂_rDU#ucs2*(+`H_vQd"h, +]@uINFm"GnBk\;F},}]\ +sD/"ѡ8u ҝUASsk-]5RMa#ɐaTC{AF9t தMSumY Ȅ +nzl[ɚ| 1Jε6PC>?p~BcR[ԕ=1>ևnݤ|Q-[Y|P +MRzx6@9LpTўB2ׇdUq"3 +CNѸ7%x{CxXiv{0L7)*dUę1C62HнHnވk#hSc0nAMƫ?'+O% ƀjjb< I6T"M>ҭIV+Vbo}U̦x%Ȼ30VtqYtu lt!B"fUr/ [[aN!^Ĉs0{3:sz xM}\xV D0j㲥Ұ"3Qv}cC %G&JQ42ғ f3DF"~c`4,r+T +gZ>te*(JVaRxyLN ˾޸8g0 :t[GXcE<"NJlBVU,@l_6t  +:)& 6+3~rsL[=aGK֦2Z n+W \kضٽʰT_l5f`O^|nz2j-ͭH1xZ2mem%n[amGUQ ۶іn*Jhmq DŽVF=[meTp*UrWnߩ k +3i:]1\ >QejLmRe.jR9V2tU-TΕCqAyzk "WtkY"iTD nJUj)5qw84W%ٻr%. [Rj%QZ4ڬ$U='ҙUy?-"6@0mNv XI/֜.I T%eM$'(I'٦&A%_|Q2MI`SD8sX!?zUλiXU"LFYU"J+_* J +0JUڙ^4dct :QeCyIxm, &`d3ʙ 3 ޯ3t>`D[dЉ*=b( 5 zkԄ Ԫk#iUXv5"Vo j2!Nuw  U QC% +!܄ɐp7 "@ d G7|&N8!X $,G4+/Xs0Kuom{;2pmeZ gW\s=Wўp"וJ(G_•<}ғ*TAB* aM}[8V_PE[V%KxU_ZTW54jW4Nti0 ~*h:16%?ڳY:ĩ )tE bNq*!?22Wʘ^M2n_$8 :ضe۶Eeeж`۶2Q@%QXsQimwҽ9ЙsN˩CⱧeo 3eq77:G3C'չjf֐qGCo@j1B9$faY4ͣ/4'G]gG-$[5r 2<~D_C7oLOsN9$/gG> '%e8yG"CҌGE"W5 ӑXJF)YCĤntNODj0ڸ]-Giݽ/W gez>l'G07xmNH(PpIw_Ds9K4]ҬgǎBQ'όf\U屪5t\gpeo!;BĵTnH\`%fpWC(*f&`Z^>&KCB`V@|oA͖iÄ>TU ewD\)X:yOd $Vb4"ef -j.24~0R r|Ś *fa+""E +ʰF5)CJD'dr̩9! +G?>V&1$ t!=b]p(Gx\s|ʅg3NSΊ^!t?L[:HSz`mxW5(ߣ1TYhg.fS  +r:%}U# rAGaxv1 +KK`7/f&T Q%f6c1EfE qqUm=nRBP CL&D'pșl@Q `y{wb&Q8.hhȹưei.5ߗ`M&#~WSǹw&ڊF4O_B=d o=3dD?Eu\p?-#EҔ-靧G +D'oJ*L] dn3wPd-rVec!VB}39 lNE‘L]#$2<)H caȐD3)QyD gJ^+8>Laa"Fvu#BP~R1ެN20̘ j}TSt ("*FtiIP[ߕ٩}k&2@bCpޟPˑ0r:~`^+a %SwcZ/h30r8OMHͻ쉳ұ/Sĩq)]KrE+0y3ͺ%/TԾMT5bN$ЊQ7oGJ]j?Y18N:5jc -d4P~]ߌAP֋BnȆpsLbr#Οҏ^7^ۨ/)hs8Dxm$[ 9'~n; Ah6O5Jm\ B{7Ҷj=V2}:DѢ9;ucb.S_v}򢤼 4@FV@j < sㄣ!)(<2  ೯A"[[-j5X=—} AW)9g MXhKc+td6Sł-f +wQ#zLfh - 4^hM";JΣc޳9&(ſC˓%$= + pÁ-~N9I|!d<ß7ocMơY8Ko۾#)اC \3(/<̳;Q?x3~ʘs2P9 +uLCI<2}kL^gdƜӔ )xF6Bf +9ۜ$٩$RBܢUqGIDf Bfݑ`gƋu_C +RՙZ*|UM2*\49beefVezLJ>b zNP#KKO}.e,I|q̠HERLq)VS7p+8zɁVW)hk r`Rp[C}z(n#%U! Aџ"D2 \tƋ2//3\rpI6(t4%Xj1Y&"BOC-Ff TLM)/ xmB 4BA0+J]dB>WLmd2dkBeIy*:Cro;7CA VZn2*s}x"HKVR+Hb|\&M +Qɸh?$Ԃ퓁af qHɔ<` +!h,s+{XleedFD}E~j&Sapv8E +WC ZSBWWu9 Nz G_p{h*rZi}󜲾hWdތO=%,=, {d0h +N#{ŏj] L@Y >7!m*JeX&r.t#A0ѧ\8qIjZ V M֩VWw5KxԹlܤ ٻuhA!fP8$7@<1b!D6 ;LV7װdǯub2p߰Еrrr ]Ge>R.Vv#?9v!&zFNN&4}AZsƨԿ:A~%^v$ވpC_CZ}^w% ) @^5[ܚl@9̇yhcfsM<2P+Bz< j.y,'}XD6ǘKNx +K2:Y=o`TrV}~Ș+Tۋn`&$_l8Bjl̰>+fG sam s5QoҬ#YbL}df "nEw2ٌ8ѬR̈́.un%/05[`)Ȱ8iApdWեۥpxL27(9T;Q>a#Hj })COg'ٛ+m'OEcUY8!f)ݚ"!D ">mlAF|1_l$wĚP[Q$T0FOPˈvH:`i2O&ˁ-g?zܚz#;תXޝrbUsny +:+BM&^eU7ͨ/"LVaGr^t3&1gֈ EJ?-GE( s bCQz;jIIK:e!0!ݛwnB#< 6PNP2mSxٕSѢD| }(=; ZRbTs=/ !mZ8cYRv[178=GMpb y2YW$@LG'c(Ҡ fM@&SDQU'Q25rأE3ѡ*ĊYcJe,;vIBPX#ޮ4 +v ZS-x HUajx;Z4=E"1(Ԁ NNX99>.pkLׇ~:2+h?G^O@;^sɉƎh5{hEd$;pޞ. s9A9RJXP)}FĶf聵nogЊd /DIu)9[}>;Esa.~A N*f YqBn$Y:a,5V4IoBWzrkus,.B:A $c|TQ֢A˟vz8@4jmlȐA,> 敉ׁ%xj!m96^9Sh'Cryn԰n"Hehׄ&= K:a ջP-,*Ʀн`(}nxÒ<4- zfU~*PKΣmbF0;) +ɣ/KiVUvq.kx1 tD.v1Qc -Q]bvi%<12;ܪ~틎B1P!"Tx#ptJ8#8ERI6P][X͒ue򑓑\j`d_<D_0-$z O,&k-Sq3ބ@`3L UPGS +>۞zNr/I peRտAG\i5gY+[޽W'9$Y|H?q9afy!;DŌ-UZE{ +)2kB/d͉Ky|5ݍ%<\2Md ǭ1JЊujbv U{X㡅}<^aRY;WfL*ǂֱYG܊OggL_+1Oǡt zBs7 %ɐ;70zuYX$=r3,Uqaf!e64XeU ^sD]oKT6ҟUFȥ@By. 讁P`Խ*57LzE :SKIhHbl20ޥYȟߣ$>6A ++OhʰS)6` :GdyL>ˌ};z""UvFNp :,&c{_<>$r t4'ꁁe hVb 圡] uնeKLZh=yDuCP\̅icZ5F ʋMVۄ5mxŐI4Zڃ O8ᵺ4]To"k<41njN(J?O82qj&k9Or}Tw/?[Zt{ +&`B'~#w=IMk7=Jkn#է6dXK%@(K !x8aJfF:M0uti74:TGrt}gwX>ԻE!0Şn 2vWBkwHg޸rɦ/Ph2[^u ,#ԔoBkCu,bK9Oh2i]Hg(ʬgg"Z=D,ndrO#QVy\&)@Ry5/ջ)%rv TX0?F$iDKF"rbGq!V"Bcm?6;Z IrL |\3 1hpdã:b},n6$S:DHs 0JEiuDe uGΰJ112h]7dB<),BiI@H9 +G%CcjcAf;G̈4s9)WkjR)C9IS;K$hQ-m8v&A(*$oh攉*%(҇ +(@I +xt|!3nk&@. +cQRreR9c_܎M; 0^9A= +s20Y4#$E\"uYXH;]b.E (?gJٸ&%,R=VIe^U#RaUfGrWzԄŭ[<|#<ٚ\@r+UH1"OL&~ jTPh{X>0xR͇YSom%.oylm#2ۗ Iqq*o#O$`}4|b" -yq{Q#hK!qdf]$| k̆tc(Ceˎ*smyXjTרۙFʟ^E؈~?_vx1UWh2@O+ z%E$}vpOIIBuބ( Pfb[B k$-ITg +^/, +_bv +Aݑa Kt$֮Gy %2Q0<6XtߕE˫W+܁ l:}񪨵ڠūcAI2@ M%9cIQUӋW1X <&ŏ"WGY{fˉ3 5 52=Lo=W澤{ƨM(imb^b!znٳ!XXr)àn,4}tmRw캅|K;,_תPa7:@=M[4b$.Sc2ðVq{[ΚQi<܃foةcg{T"@~䭲CRg].71p8bcXJ2cl>aA~P8t%v#z0yQԹ`Wt=΄Zti< +8T[ +Zq&=9D[fUC;#]pu̅X @>G{#+ [n)pRB*̼ wi,\:7?[syth˅␅U=@)GvP#eB!:EJuV<::[ru!+Rb4,=!NEk x,# ?ԥxaDI9acUC>c~D?%`;Cxn0 BBaȧ"I9-"Ԃ +@'A__'XJ*GoⅤ$'~I_-0iޫG/P}]IA.z^SČZCH/xK*@]4oT@v6-_h$A68ͺ2pqC=WTKv" xO) *qBJ Lj iD gQ*_śSUfbkmԑn\8I}c%[GnȾTg_R$#2F2avVFj#5 =̫2_T>Uw. \+D{e%$*JTJS!TK* ExɜTDӄ!uQU%+'N&O${K 07*lBF]F0LN%k7:a߁M@cĹтNdr=V$ϩ45^õҩY)*6.gƞ:UUr&(u*:};,Evȸ=d$S%}TDJTh!#Q!o|T+SA=p'#dftwpϜ=~)ZP2^fSU)B`쩎eK4cB&JGҠ+yN>0 O z׹EbZTa:<\4tYf(z=$zVb(KkZϔO iڏnӘeYeF3h! * .[GwbUb4L6Zh55lvLP~lz5Wƻh*>C\}j/Rw=l.8Ƞ|&oijD .dyɟ:Vd{YQ5hai? 0Rs!n BZQESu?W5~4(30Ai|F% CB{d҃[\hG0Y. T/|=!bM1%Ba˗,E&PVA*G/B7+#W/,z ^L@^pDҚr3[~_3,^&`+MωޭDB6L )Ɲg3A}ȦF ^27$nsSﺻ_4Ҍ2 [@L{'\luҬWh%_]mٱqQlK*_ڛa/~z*,1uX6Xl^S/H09f&l WojkllV&ŁI@+L ,}.(6w]&f3~*ƙ@*k,SӍ I dnhhʨ-XR= JS¨ۛU%: b$ސBTATDmsآO1/}KYr1 "N=ޡS_ζUqb'LU1yC/  ` 7S" (DN14pEC'ȑaƳo88r賑5ӥ(|hx{TGLȚhBHAj~&S1MMFp(fYr%:ם{Qa ; U8B$Vfu} +bS5 ts 1d9оn@14`&DDyin~G}@@R<۳X{WԄØr^its8WuAx:l=&a$z0Kox "6#, +ɒgv9Jnz_U!Y0Pr82SVToL>u `lMN2 +ŸP\(-`t=~Ʒ O@<쩉fOɪR$MK ۠Fc#(_v}СC]^d5 +FTk$T^M+ 69Zݟ {ݰ<0TD?tLs +ſQ6?Y5JoESr '$G9%f}S}Rghl3;75pAf#bj1ls+EYX| +}[~:G8>*eԣjC +>vߔ^= GÀz:%&\ +q@3L vO ( 8MzX5'{phuja5@foQ-d'Fr;)j?Z;TThq_D0[^q׺\_kT순c F+U CW?BTff^Ifhtd:,ѹDL^}ih3Y-o/@zMuPs1If3ӨmeNTiyKF!*ah7fa&hd>&Tħ$Ȧҷ <.R; 6ό,J F-EŇGD=3 뛼0(}eQv_RSnyG)/c5#ww\@ʁGL`Pq'ư|XxYfh?qoojgӵz%(Kӷ_@@b X~&KEuBsQX$lc#T4y`zqcG"CN/uy(P"aEY9V~w'[4'z|.ax9םʀ>bg`*O2?V7(#4e wxUF+* IGBi)j{;Iö\w_!'`4q#9HqVrxJ; SZRޒÄ.9o;YJ($"dX'9U3'!h\TgB4Ɗ31i%,QI9<K"B[_JbuI7яn̚) 9HLjy  l.F$//:x۱_sF~WsMGr\)a_En%cM"2k0sAmdfK׍2C[ Vx p2yqg7"`3x$;@q-=T. Ls(5r ]8/H-46@Eߔ+{<)_ٵ6P 7Qȭr[ t;#TMA4XvMLUv=QMWX߫! qM{.:y/_v!rLK +N}?Yb9K튑QBeHᳶDž^0V!2 cH*:迠ߵhe~S|S9cEKH;*}o G\8EN4d-՝WŃ"9N!Zq-WH F=%'F%v7hGm+aYYaittRsCSVVȿOHwU}VEN'F4}+bPǡ91q!p@6\]NTny 缯ubF9v|ǦBKBa䞹#wAP㖁nF:j +º3y!wv߀Xx遤[n6tga8u;pџ1#N7=~#P*̡eXC!_j(z\zࡵP 8N ~Wn\B5>>W@3|X%T1e I])\Oq" +gIԄS\u nǕ`}\{dlj"HoTY4o\ieI>DoaS__\^2&\$Lvvu~0'U>$g`<0Ĝ+?} >NxigT7Yg-|hGG-"d҈xGAH1u:^ iY%BT^t${H٦~O,Rj@TZ  kx1ĒV.9,݋pi'/ l@֧1=aLA%%5ТCj!P 6\INu7yOZ9;"H Alw1LS\)[?!攜Nf49Q1YxTԹkjdXr݋7s?4hUݫUE@旒q?hNdπ=޿^|  X5;g\ K y6\!E*F0m8^Cyh6 F~ Q|Dyc{>Ͻ\|=j7gܣ_nLeڭ(_(bp9(5 Ծ4'oGC"t'cj5oq |†mWo $jWkxr\#|5,"h_9B [$Ȱ{O kWa-Is [*jPzW7L˄`B9^mkMh*j4R>(X3ʗu!yoU.PdM9"^4WcFK顬Rgnqet}.|^cה^H MH#%urW.E*p+!k̶I|q9\7-JaƂcM$| kaO&'|Y d' R#| z81(8s n6ȸ+77 u#A1Okf@z_լՅi<|A,hu̬߷)[!4m5YN?o`0w: 8|EK| +9 옄 +&G/bkhv13P^Ȼ=Ҵ +J%H#B%͓F8TI`ծ1_P(3,!渹Υ4#@i]}pb rgW`~󲱢vge'RaA H۱aU5k;>C=LFjh:Dr՗.XH5rVIAb@c's|_]mW" + }PQgJ&\*r^FXQ(A꼂2ޕ&( :xZ8΁+\3U/f{NӟQ380 0<^X}e[Q.=L%DMnT]S#Y P ,ձYq, +a=)jz #] 3biA&TDusԤ^" jmGa8{0f_ \)0eBeЩ<~c~!N]n8UL`e`p+(us;@nɼ:xhUST?[&0#jIu0f?=7d7\WcĮr.ri8RyL9.#M7@ l +V ~K=6v%"kݣoBË6d81³n ߢ'͕~g6?NJnǽ]Sկ\/(_k n~2() +T%&EֱJS_V_n,-VV3n)㼃wvku+ , mɃ(2IS SUr>"CDҲd`BDu@{ +;2>Ԯoa(%4eJuP?s^zuhw + +"YWYeD.tSu[KDԥؗ)Ffa|N:LPJsW6gR@[tF'RCy40M/8buG0re˘u I.ka;! 1Ցm ©",5u'6y.;=W87$DŽjaSֶ9Jue5}tho'͉,'OLVbigcq:ai4Dv_<ᡛ2#5,.  Y}1z!CO9׽-J6P5jgK[^3b!~Hd& +WnBޭJ7 Gмi<=͙3unyʞ{zx\ëOY*Up ? ة.j@@GqcC9DՀՇUM =Q:19惮Aْ?rĴ}k?@҄[;si*B: }_-@$Kh.ߥJdZ^T[sqk7Lin\eAnH{zZ!z#rZ]njd"4^=܍ 6-"ۃ9A/'[{Jk:x]N,E$u^`>R`[g%f:,1{]L{&61\\=Qԥ#$^>og{bG= ++0 OAdua6#%KnL܃6sC{i7Œ .Q9RK8b' ⟉caww*qTRۆ0ݍb=ߘBMc;`eq;!Z?L\7áI919Srو ;V%Y psI5܎j,9 PT@r[ f"UMO}uaOy<ػ)~#<.Ǐ{cg>F>RvQP\tŖG'TW@"11_} ru8[ & 0+%EX2Fz $2ˈJCG#[2Iwsh1*&M܌8 L'vtN P5FKz=),)' A*Њ!1VT+ '>ntN3;OPD D J,RiG4dh?g@\":kY.$$m`j2Fp]Pb isKvVPUuu1yMX1>죰`pJJLQT0|B>?>x(!1'N?x1| 8܇pQ߆jJUC +TO۶'SSEA0:U1Ӥt[.sK7Kf/MYqd54 KꇐyuUcXATG>C}{Gt(ps !8̑h8M nBw!xX&NO>TOHɠWS9&337rjo(^# vC0hC 58 0(x' Սu "l[ݚ<܁Le"r R d0>^^Ǚ9̼L™y0_ +!P P\G o8~=숺kaPt9" Q+pth+\FGᲹC 쓇P N&&|6ÁDQGq3 GA1tf "/ӉyʡȚouG ؿAE؇! O[HvCH`4vXY֫dޑי#M_;[,U[}nz~S\4TkP,'K-ǚR3b9 Yi䲆Rhˤ̄|#AYL2&21!2>stream +lT,[֎4Q&td(P^BdlI-+tޤVEɲ)DJ捨QkrU>r o4bVтbgU|3:,#$C!#43+Jbr5 *oJd R +yUFR +uh=C<ZC8rWk PAPE1c(l5KYV4%u֣09=XƲevce]\f%˃re,`^.Ʋ˾rW6\5T.jd$) l$&/&$$<) +|-ecRdri?E&|Rr >`[\Ђ, NHYf^r"|R.za$dͲE~ XCPs44-)ǧ˯:"U' ނ҃A廚"nD * +Ռm<\$G^>>qVݫܒep:z90ԫvmn; (Ze|ePA5O|c\X4<_=@z6y7&řaRuϰb@=㾊EKb"7PΙDqȽ! KEw3֘5f̔\޼2f>}m8h!!cSe=i.9S@HOh3G +by1(W%S텓2@He̮D#5ТvL׬ xXrn!Ψh0 2˨M*QahfxJHEBz3P,!$S?řQМc (,c6 z6˥sH1lKgA1LP{>qMx G//x&EL9 :Bt`t[XW32yVb6란7/2{X7FDr-Y%3 SCFN."=:U^ mGwɖ4:ol B&bBC,dQ,"&ѽ!&u;,lDYJomA0lG74e,닇gt^e {??nΎ5GG7BT٫_FGwÆiDPL +Q#|.+j\'`N5g=Snu׾/XHegѝT{"+Fwݢ+'P#4Y˅뺌".jgL: $u讌mIGw&}lPHKphto\e8xɵq^0eCE-Qd9IZLY D\z=~>6B,ėb I{Jt$FV5qǑѭzVIgdLЀ@U+i:D$$OzhS̮^»YPAD7<ӓ[%67Ofe㣛DJ=ˌn7~4.ń]8": +}ъ(GB2+X-g)uG,#|\bA޿1gΪ$f/).+ף0SoW\#7V}~~V::+ ѩt\FT%"ho}6 ǯ)1 +y] +*ox~U>yM.$"; qukAN} |.B矽C)_@*&_4=YcD9BvDNvcGџfQ~;۠}Եv5r+ܫP3gb^Y@$?2񱛞u#In{5jX;1u"Jg7x$MqB UWLxibffCGd~;So֕(4.RXJd*TF]2keV\fÇY#ơ&he/c{8 +?HyUשÅh"r\*6҇krP#k`D3*J4dB$_9BDSl:ȞdHOL^,ғCk"Rܪj)\>|zdjASL$Ñ3Ѹ!ҍ~ +xp>! +fs"D2z iG.p?L#=Ɲ~IjէoDrd6Yl\LbHB0JɂսtinUHlm2"y#؜ N;*%acH#UJ$dY}䶫ςW/Ο ֑WSdvFD~{}- xgv}.D˒Yq%"?ƾGIa16g}]AkZmCIT֯Qjͽ9(GJ3늗m(hjR2L&hM(1vw\٩S'EW>j ӛfz|Dcp. z!O|xA(rZDbMWn!m:Lz]5D{N=fxZ›ո)_Rh|BDlcm!z1onمbvSG1- ~fν(ZtfQyk+ >6XuwcPU +r?&B!%6*y5ZYT>eOhzݘ Z vt8`rKMtQmTk@kAjb!o!PZ{xQ;}wk4xc-YvM& #x;tS1 6{Tн Oiǯg82QƊr3a#4%G*~;P;h>\~<h6JV@Be +8#ĕTfSSia=Dp*c` }ϘЗI̬di EGKr_a|uR%.Zp났Rty-]vaV2Zh:||b8lNOZ!?tnNUV3_1}-1$1j@ǻ 2F4:[}• 53m4{duoMfE1- ac& +joɝTCq`0-ISwfSm4<0ƭ(?2bzRa!ZtW{+N燹8m-.5;Mf8YLrKfbjNgF;As1Wu?F gSn{z_Z8ͪV"!w<+Ox:g"NCMOez7JRnI`Fa ޠ>]'q]-FGSOP g +QPK@TCyJB8)֟IUI21Ɉc$ ܬH|tW#B`"=#㧃⛇֢9xKBPrtƬ\?śI%CtwdWC ^AU"f +Ym.3Zt\3=8uK>Z(3T-d!>]hqZtk4 &@r.FR b#ȕjD$=vpzEj B1*bhZFrzK $xEVeA-Pzcd-^}K]+X(wu/"p !R 6*D2Wh!*5я#:i2Gg^rScmZ=HD_i}PU1tUX;4qѺeOĶ.U4,:h(e'n^EUc[*H^qYv-xX:h*&  *E ;J-Z  =ԧ/_-Z!J%DCF|8Mm\*:!ʝu#u W1u` j9 +H^^olsUdd=R (<|/#D.Z}6 V&1iAF![ f%^lW 3;?DQH,@p;d-(EaIj8);) ) +lع%')"3/+Yi OZ;"k2Qbq;RWDj6`[R*@Ⱥ5˼MJRP P)Z[?Y t(*+^)iܤTWU T}pԈeA Q`vCsgf^O${R*M ,)XcB- +j{::K5;"w8 sxQo}27S":_6"(> `g)U>C,m+/@#jsKv\m:T` D-R*BȭBzTRr܈w4O|"lYsLFrR䵞lG+fY@s4 +^;C p-lNrK"H5/(Iys +*Sq۰jNZh.t:3 u, k6PBuqSe7?Ag.d7BdF $ߴnxA +O'#J*4d@/QL# +-[Dvr7iZ.4|{1O҅z\12i*&P8ptHH݂l^*snQrCu*31`tj'3$ +t a,M] 1( bBuN8B^>M0p]ZuL[X/['QgFM- +){C?IzC-xRXTĦGA[T>+֓yg=f TI[Yv<)yXTIp`Q`XVaTME,ݱ.'LlQY&=̛(ĄⶨGW Y٦;VXT +f;({x.n,DZT%%&PJޡ7ʢ{ NE%r^/* 8XZ9' +dA`2ZVeb4IkX/*y w82xl+*SI  !:JEsR" pZT$ZkOzfRykH↧Rb+X! +1g0D%;0hC qPhأӪf4/:H5.dMSUh|q&/JW<O=\$O؝ovM*Vr8_w;cHs43'%{ٹ?6#b@ņTt4Th !w#QpsK<lVmU_œwMnonA8B݊{<oN鲂!fkTZFK A:}of@iMtł&ӣulj ĺI.LPXsO}s詘&}5cETچ!j[7&yR^29̖b񊀇>57l-s]T1yZ1Aq,1iŞ{R& $+stR õ@ $ qXovT`L@t9@884vYR_EW!pb.>"cqE]s*z*N&6WyRLIq1:7rDΌRMvya>ggN\}=q1cu¿ڸ `[}TL Nhzr a8҇n\:jChW'FgI xe G]aljN.2rXPLb}=ҝ$BDn6 w +4Y1Ntґnu@tsb mXt&34=K{n(]wnF +!z +R dD5c%]73]lQq nP[/ /$A($sG)51(J7Q +lc: +r+{15 (X2UB(뺄~+$+@ңt93 tK_&If-u[ ΐS f8!OI7RN MGևOT,V`vWsOwX!]H+Xmo(| "1"h ҍ0P(R}/60J#zJ~G˹A64ӵ+ >BmE<0={>`'!_Ĕn^MEJ^ op|OuV6 KVUEUHQr|rVU;F,T4ge{ (r( + QWBXdʅ~$&‚>P)Ǿ ?4"4rt 8Ps3Q0RcBLz; m=sX\d~uc\8hDzΏF 8v5d!`0G`vn\n,z[[]yDZWmO`8ǸV!7j-yت5CG^^ Ȣ9:0hqce|- }#fh2r\0͓YT[&i>gC)7xm!&j +qOJـ.*͸@ű"9u/V+!fL68Fʫ`'.aߝ7ZGCSt5 +LLp}betB} +Aр̰A ++NEG 0^"i_B**^r)1J uO)VИQB~s%}-qfXD7a +=2_F>FYJ!/ȧMХ&jתd`8X #VE Ұ@nN)nõ5 +Xh +z SHo*™ӶC[V܎[dA3dRUiLAJb?-)Ƶ 9;:G*e{H@9A^c1W!hKQzi|uy?Rl#ּrĠ:6s)WՁv)]6ybޚM^Pչ=HL$lEaW.63 -?Zt}lndstדgZ80pvϋݽ%ӞM@7+!kPyoTG6K %lvnYÈbPC(%,9Qú0K"._M샱ueC:jo#^# +& ;#`Aִ[(I.ol0.['BoVZ]S˽ +g! +Pe= (S$ٮWmRZ⮦6HQ"_=s.Ȃ䇇Xj罁Fc)κ6r h0F&=0|}kО +}S|u#Z!]BѸbvD# }ZZ N "/u&E(B .c9?ϕ6V[y +Q)OYb)C?u~G B5~?()yY +;(%\9zY9X \0_ԮDYG]3<Yz`_:r"٘D*H#dlڬ(ٸ/!ErA!Bjm:%1DI@bތ133Eao_,'T,[ǀƙ*zds&'hwu9ȷ p1 ZńtY`Z~%זiArHRNK7D=7]bld@*bW9^>FePE9ZЮt8%LpUVD,4)u# *bŤ厛0|8hŌeb>q,q좀8/&:V@~6"^!&?kUe ⹭5IVvq !b ÷y.L[nᒵ3}iNf6mi@ +=54 t1b_TdK\iГAN2~GB+2rjZ R( fAhA$-1{=PG岯l NOӊ$Q g2d5"aaC(C0Hk(¼lg7m? Bʮ*;5->(5.|@5~Xph#0K=4PlG !"F&ɦق۟=?N4V~ 䭀_P7BJgK7'z)s+tyGA! L0*Ud¢J @_pv\UT)d.T`Og GoE̡Z鋐lQu߼{Zę+2yݎ_-"hܔTKAV|$vq\ƘP%n8RvPa1Ԡ[m Қ{WPF_H3r2SyvY\򁌟[m>S%+lG^ln;N +F)"ShygK7|Hc9U&?/% +WygohbcNω؎mEOC>Kc]2 +eE*Ŷ"Y8tdۅHlθ$x%fh q#(7(NYx̼Jh=OZ HETd +k]"8!b3cl?Xοb,?%v4 &dvq pi-M|{?]N0;В9w@ό!Le d[:`l)u:r +W%Pr=[ֶacA23c*7q7bx;$H7cՈB}CA|ZEP3}LȽ3pjɵbǎ&h@6L烵$*OZŦ jL˛ܴF]dlW#!.=Gx5=TLU ugKDWe[ NZv4D3xz #s2$CH6!GVD4;MZ7l*ݺ CrԜ&~qx9s$߁m& |2)~]/L0 "$P$Bu4 GD&³ P"yw~tc֒x߾9JunDoOS|eC Q- (>[Q3NiSm=n"N }tKk*,{9 |^f2!on8$:_I&VyjPΰ$\H`[F+mҝ%sm\c 54qg.̒팃?pUEͬDS-cTZS Rn9%TD 0SP8l:8hރVDT3"gƏ>-ͥ9]TB<ЃAg A(E+PddML1>I U0T3P8; K{, +v: w_w: qcpw8^Y!bLguP;֑ n - @\ZgS?A@E8Ƃ)ɜ+sw8BxlW7zrڦpE)DWFe[GS9Z0[*'[ms=w'2eDP<x}x2od$J¾JP[2: ރ#/dX%G2hqlRgމ̻#FanGPhn'l({U I-k_)dH? %%\?vǃFyvJhq팲u73kԿ#L/EdHH eGСMUr]OmF*7v' mڸ=],u]mk|B |oBjDR2֓~i2\kO +\)tZ,lw )'XI9H_ /opeΘ0h05?,Flzf߄8{jA"Wv`s| f qXB{,fQ4R?Ð)T 쇋rq>'Q t-@%G]i.*:$:h q7D wȦװf(PjwXRaӕDܮJg먰LJt#-Z8\%"ԇXE+pU']AHg&imFͺON a'Ke}8UC7 I׼u/@eoL]ΈN³Ou.oK˼w|ch>TOvw8}XjwQ۞f@@S/X?eu.0#._gcW :} +8f olȻɁ%٥ZiohMO-8*Q"Wʴf94QPY۲a0K@:FXoZ%#V[d2IA&Zeu>n](2$ l='x`gK:kgdLn>a|9EK3mvr'@&}l8}'X\Y(O%OfE_WMڸI@z+YՖ۷]䳴%TJt<bo{ W,}et :ۦ 6=Gv>ԾAd C䀑7E/A\F-#ĶJ͇(n&9Z ey:;_ $/@1+ RXLdPnlXŵGkh@z& +2Or XEr"Vk荐1sy&8r,b,+5}Lz+JLW q<3 Jl֒eO0zNXdg1EboVRbD[F>UjK1x,~-Ր ˬFIDvHX,Yf7aԲlks|jQwݭ[nd*/_6_"tڦℤYH'Qo56aiwM} xRWkvqB$Eb?FANW[oо罐c_.$@ &:b4=uK&–| cq-]-#tnz\K@Nho=qem-wQdh҉-!~⦜w裆ֳ̈{^uHM6Eޕ3E WkzBm 砧F9 c-U4k  +%BKA]OB} Wϰƶb-axdIz2e33csxR6>y +)^ Ea4)/ 0kŋr6"]%p!X⢏`pT-b)fڐ&z+ $hB0tL?TmM`kr&ɀ-G&B +ޘQzT\;? J3ORð><{x"?s8c; ,c $ +IE5. 2!mۥwWnQHã*6]"fMVY[zH뷜"lN&th|Op bx6aZ2~xtDLSӚFFRbRm;5K$xF#2wa+qt/Bĺb"I -0`:F吴Rs"y0RuDWK[xv4ML S{\,| "=%?Rax_lK?/úNgn@cC)a"uZ^wԋu\C+Ip4?;uGM2`ot`ntjyhM0w"w|nPP(/@&.mTr>vzCB'Q;k!#Y;BtXyHK Bt&1=0pXPSė +J 0U(qRFf;Pî3S nRN'ہkB=6Ъqv9€A~I ;sƚ!ܢXVk\(B Ry%sA>3A?B?HVۀTzT@bgyJNYaQγo Yx^i% +tqަ_KFUg5ͪVʬE GO kOjqEuX3% 5?y~OG[7 e(p6x#rjXI-%vK6bm]_K;-8kU/8&q<\cd,M!6)~H"tU} kn!ChtD݀vI:ݷUpݫS<HS46#@j!¹({t%6y(Y쥿g$ YcHhmEaOL8 &w,G(nc{Axlf Uz/izNCSEW?%~K9d#2lXʯw/ݸmB+৪LJv\pZX20ߍCߕ(EGqW#=" ՗0\VO;O C\f|\VΘF<’(F@ zt ] "#.T6Jnu#{N.R⍁+`i|6^%w`jCEɦ1O%o&{(/? d0k3^ +xȘ@$ +P*i^,d>6l3+ q-3h$C,AU-F$ԓ 9 +Iu)er$덌7\is[h! ,y%@HqMQ:gئLf(;[G8!i2ۅ¼䴉Jyhk7MWKt"YIPv 2,mv,MxEĔ' +o>ua!* 7tT>Î^Al_sArHD+,f4uoҝ-rcكo" 0PcNs,\-FenZtQ6!^%=ifKHeN^Wmѳ%خnjUmdeܡVS=*QTU$"=-4w0?'g@[bvn{84ՆX(1UWfJ*.~pd?˄I +r ~$c>YΡ`Xl[n""vۜ!>*ކ@S)>o͡x *.j Ni-ŖnЫ$;8E ^EݫÐf(N ތ=׀>,"|1ҳ:z_p[MjVy_Y@;z%6mn'mMΨ|BN@;=f Y r6Waf=ۉ{e _4Ry+&$V/nSI#?k +BATDAI"q 丧G+!%[bwOˆ;k(233vg笝[9C;:ɂEBz(Zc m +/ڌ <9 +:B-9%ńZ݈R˦"{ !-aw[Ѷ@}J3wi3sB]n1r]b,~f?JbA>Gki +f`B5-jup X'kK̿sW  +N)3Pӛ|.QC&U'Dj}߭2'!>t|uۦ?QσP|er["Nڣ)EBc]dg_| xɱ$wU6nE{Il&u#Tpvuz{U-& >R٦‹UpNCֶ)0^&ϸ(Ң6%Vos3ɇ>٠q"?(*%>ԝg9[mkh!bKdoS- 1#>6։+4-o[(R'!ƦK-?>;)FKe&7J+N>sU2*;g(lԽ}T(Jc/W/eow(@|ʚy3eX=OLS 2Z}QE.KJ{\ѐ$?1o?叭n䦥E'v5G* ,.(}G +W0jF+=7 &CTa+\sNVG 8ٕ7T_OxbD['pJNzcX7HCD+$ 2Sϝ^M%ntuəU= =H wMCh%N7IcG|_oBsC BBPyLsOF#n`N8iz 5 eؤ#$^} u5o;bCu+N 눹 q]s^C(`FdCsBudG'8daI.4 +c<'  -0Ё@`j=kܖv[k9a" @upKw3_Rrz/H,1/`Jk!ߞKގ0W_lB Z)^ +]A*C&j0 +/ep]"nd=2 6WJ ~O)C]RVXYD ] +j%?{ "bK IOҺǼ:{w`eFCܙRY :d(cs9SJ`j"g3҈W**KI}hώT-agkG :zk0`'=pyv+ʼTgBHLB3,{ qc&$4IT /i0"Xx7lbgD: +!ptn;)]Y BFHQJC'whgP5\+Ցw04Ku$<%Kuĺv6 'HQ;&X U>,YshF^\vT^UM=؀ :=v~iKMm}Yp?MK QxP TRGq Ac%Nu >ke$ϔ_>@p<-"4i2 +>v]Z8ד x7–f|ѰJ^ľ \d@_8c2RM?2T,J\ꨦǢյD#W_Q1K5fe#4#4fa#SVoZ^♖(>]U4V Cƹ^Q.dCG LBH·}(:];V, mRøg'Kc7VU0/AS=vwQ':2;Z#H?jCzm-gMP,=35 sbz"tǀfezŶ 23ڠ$;L#NlxGj14S}wPF bU _41ݚU`tB( WZ˻\,@(fB?Ty8hؐ ^E*Jmmh?fKDWLG@dm:2oܨOuMNRG/>8[L襅el[ρ")"8'L$46HfǪu`AYMc|eߔ?!O*͠Kw3<@pt A|X5C.RHq +j{uI!ޑX#I CJ b.&vqM6g"h*v?,~Ûxy0E: zꏾ +K +>1 +C +8w{#?-$ +bݣ# 6|k!޴a$r0o_!_5DGbstRÕoBȗ~-|ECVR2Kx$/ YlQ&7 =Rz%iLSXZҬ(A[ur>!TьTC3(jI5w) k]n rD!f&#STQWU-S7E9f/7-0?u|yub(O¹grF^ƭf#@B]L$ GzC":QB+LqsW9eH8Bզ5L8Egn +9 "ХyXCP=q|RXKiq.pW'>2@_Exr$2?O"Ļeކ\dm] QxSfԮ& +uJ7}#ee _6s(U_XKdvbnR3M[UrIk6-zTI(d-כ듵gtjm(`tɓK .PJO%Ch7H6Xb(Ҥ1s +"plóMTw,-/D4|NV偸ʈg7"DĔ0#2|J'tއ1K Q'CE"-!Ah&6[|kNlIo8(5y-PkKtñ<2b ;krH"@q#SY -gR(,Fܽ\6{m6G>^f<.tWE!B:s|iuv_?*_bf]\ :M\%0Qbݭ("iFMR^W0茔 +' #bŸM*{դ/y1k2/QA̒)VW<ڽF#xxU(UN10he^vxΟMcoq_7)'7vFggmDDE1x80qT"Et`\%--v*^x0>'UDlEt+bwr^( + UXUX)UXVI4-t!! bBB,HMЄHhBHdEA?3</Uw8AuKD<ޜχ˻;Ļ;׻_Y|4yTu2vXn&g>veM|"%Y+I\F\ԸE0|ҵiP~n$j_}4GFhQ~{H4I,[$?z+Y”:qJ0UnzJLjb|\Ru)I~mS5/JI)w+\TJD~)X+QE|th zE%Y/;륪fPfl7jJ65"URfXFVY YϨ:]iMS0@AAAxp DHP,"(.P4,40@`!ƒBaA PP8 BBB@P8h0*pAрA,8 $è9GS#Ip)#c0,L>^F6P&%gRvFP*G;8'kO+ xd2fdJ<՝%U2\_hQ>( 9a2KJaO0>wE G0jʓ*nҬ\&L܁9A9Q;qU'B2N]aޞYRR 6G|.Ɯk d2,қH"\U>tsT=>k@zZ%x"~kQ,¸ԼFr9rZ80X |WI^h~Cm؃A'oZ3 eF2#hjbCwC*_T4YB` ȭ4B~C$̏z>2^?.8{e"T /^P y{6?#YF4Ӳ X%RNRme /F GͼRk]1B#W|#N`^`=AXDWCOOMY|Y/6R=2grhKG^0 /0O¹]%g +gtVGpԓ&PQxXjay!Pc#/X^?XePi"ɹt'>dl+Y!T^8Ydz.Y0zw"v;#jl|DR״&zș XIDH aO۽nO #jiMJ"CDݣ_L. yL +978釢5!kgw Ȧ|#qPt`dMKGu7S%Mx`K;(1tB5ي[0GGY`=edy= x:\| 2>#[nOA捒eHȜYJS jdY#}0EI!log&lZ`xd)Go}Hx\_Pzd{xD6ekWʢsNF6S۫*cǗ},fґrڍ k.cu_*'U.~K\eZ0e) CFp‘ž $O?;$wܣV>sQ17. He4Wn2xYh:82ԓrEdHLS!jʤ`DACbuhEªyxѣ<5zg8U5zੰ}+DAġi!jF1F^ "]QR5z. Fюe=ج[O: h=pkoZߌ5Mb}Rh(0z&h; BiFs573z8S1寄у'K(JX6=|0=@)e8G5 z%2zL{Di?cp@[Xa`; $ (%h5x BWReZw`tV;hMa=`uB=f|Ǚ P+B}eG}b jq&왯Di葕~ӏ?"=֞aAt_yWP1cg}q0<;1왺?9z8#\r$豉)Q>;"1Xhq^=˛c3|,rkT6CaB05\ĺJSX2mp0mѣse%1Ú:|eB{< K;EwXB0ȎY%r)/qd1QxP%g&$KB t.MEH8rZ~7`1c+ =h-ep!,X8121lN}w[wc͎(_>…6AΚR䥃 >gwpaB]Ӟ>'B.9ܽ2!g%{'ѫ#H&lɅ>t0[u;'+:0hkm8W "M.%d!L!J>D^k&63+qB챴2pS`j#[a~Jcz\hՐyfuSfzrttg@85N{;ZWX!$ҎD=hܻXc^硽gP=H^ǓfCeD5,MP4GtA7qeD92p/6 F:|{s`urQR4tQ6R$`_Gr`&w8ED%hOkn&g"~,S@V^uǼ+(qV,]ބ`R4Q}R |??;>[DSQFP۵>}~ ~-:A:jKO7.׆.(Lv(0Ht  \ Ð,0:6T4POVo |~Zv,owgg$1$OY<:>fg?=oKJ,CqPg&u>+PR{mI/:\|0% O΄,g;(Q a5A7*# 6F/> .)> -6W h}w4{[s lTh+w  Ei-HG:㒢y GSұB>O`M.>!oGzwT7wcx#uC^~\VAG&vA Pwn$-w# +SuD뾕'dP#Eaj;Gzq9*#cmifؼ5ڨ4GZ}9/2iރh\j6VZ +^!uvyշCy 2\@>|慊e*gid }!辆U!-`*%H/* (9<"䊩/ nЈ-YƆ&3\fR!Y +}rP, 8g5#? VZP!h(TBX[ڥBjT +ك6\`ͣBnL,Pf*B_钝*̈́} +)b4P [> 5 X@%IJOOa5ᨐGISƮB˾܆ݿWWm1r|JtHmҷ71[ʩ[5`EHKn'ͬ?F/" =⃸x8" +d +QePȦn.r!,rP/k ?uN|(Vh9&Y4 NuO5!aMV7 + AԽ?:mrE/VNpzBRM**k}j(ĸ(>}޴Xv~ \~ !'fBv_Z0C  6H 4E Q I#yS;T +Th̒Lj DFڞPXŸX2@[-hYièe8-I ȎҝYrw񢩣KM,_  {N])'D^/}Jʤ_&ֺ&r OJ12p`ǴsQ<\hLeāfCgkBG@HExJn=̺]ЌMj@UfS$\9,HcۋUв: +Hjqk&0-oչ\ߵ_+0e&#LmuN`C=XpB@Lgrͨ8QAz]{{-t Bdd9#y'Fu}]3$ Cʗل\4K>Lb:]ɂ'y^ )_xeܰ4x@ڊ=ltsn36 9Y8BHk뱠2ha:(/)>t6Dm?22q\@ +{.ZgY!Ie2r1Bje{$$׍ zK@5 zuseMݍ!]=OwKyD?ǣ׎P1ⲗ55fidt OlP$P9];o, +0 R牟}^Lᙪ1$Oiاl["֙c~=,?[f%b4A3d=\y}e +YVfͭO+⠉CpgB1'yFVQ/c\84,kuonV,Eh݉>gAck99ZqLveӾ`@O [rcY +\/4fLtnu񦽰LdDM -v1 [+S{(փpT?Rh^G|y73*!š* pje@2)2-fQLnB m-'e6skP05Av!l2§N|9r+E}zrT]ollz& S;IOC8}n-NkrTY]0ZP) q[ +M6xҗ(?vZHF[N7HU!r$p +/z2Imcrn#"10WAM'A/x+ sKuoH^[>Yi쫛j&fFFhas(wx%b1Z, ]-;QʐOHô^v0*W7y]P^H#?{~ L#:C+X@fB|/f$6 x2Q!gf)9F[r3'Q-ɱp B6gA D:v} Z哀-bGŗ%Hq,c7駕{!%")Py^bqGFdO{0 |rz;4ei᪨*{XcEETQwnzD|a/]T{;$?|xFEO0 M;[<ʿT߇"&\p#0})E"ՠXö>ǠGwtBW`ïDk^PIS.G;HMŢ6"_'XE-pET"o茳Sm *ȯFE\pprIYɇZϦ˕݆D66ōԈ8 VO,nI7qA'h 8\csQ36&@gF?YJ"Pz ٠D3/'`cqbFDpHN8直-oKcb:1I}#P3+//!鱩ms'KO+ּÎJl13,ݡP< |Ezh:m 8 )}a# wDL6PY S&ROGo F[da7}$w~5iCiÑĶ%K Ri{F $K923RDlbz /ͺPEakS8Xo},6%\8"PS= *q0V|FHL o5r]MFZKa,JP~d{/Q@KoC(֙ +ĺq@*WsMb 8_pr Fv`.6Ql_ FRk}vXi>Ť"3yz%NYJu +`RTE?-sR|i +Jq#ȱRܲ + HZNW9#,iyia&[ߨa?P)G1<̕V6ctaq2viDT7Ծ$D<9b^GdAKm3Y<3N^͵bnA:4 P '}XRѪkT15ABaV&|7BE6)荡!敥m1b '^5!ݱBw!;[1A'Դ4{T_É$ՖEV\8NPv&~hE#3.ɁHp:ּs !h# hUdnh&G[QZoZ1#5Ŏ|Vb2b4WBht'd$M 2+*?-X˲^_N"6(^BjPJep'mi22!`rVhroCd.JN~:MHi;s4H=P<䷛,EנPalr-ͣD}d@K%cǢφ^hޢ~ A4D%/ZXW1ѫ&5ܜaYU4@$@bhcd _l$"Y^Z`uMD# :LO-ö0bMnM%Ʋ +4>^닅ilMBY1nWq=-8QrM:Vf@Cs/l&voHnB0(w~>(h:XWm}e2.޹Gx'EQX<4|&#~|$, +)2s`b]&ry{V62"mCm vttBW]`F:kY|"<֓S!-=qZ &QƎu@s0\L"+O{&:bAWDyŅѾ ?c=,;RkMbXYVZ9hS#DVv>-ȣtX@3ޓ,lF׉f: +ڃW |SY#'/gU&4ckm{1`0ƝR4xD"e"0 ^Q +eZQdh'"ʜK0|7[t>mh1Ϙ̓ m.qx*#`HG" >Qxn Lk: @恠/v"~0r&G-`AB~u@cݹ81^14 9 !CF3ZjP$,ixQ~Bk0K](rоrjxsYBU|F[r&b(7آcɵ Lȏ{g4 ;«J_ +b{`{w .q㓵ۡ {by(xwHam)J*w%{UP0|tm&0$N) +(ťwfvX5W`K@n9wťeBG+i3=u}sf^&M}.Je5D/Px}u4D 4v(qZaq6 TL$|kyKEH z4۔A9 0>Qr-ј5#%wj8>I(D8YĬ\4^.DKZg{)I}J6f#P_s^Q9]0[lB_4c 4(/@!`-xB eIY #-2dL*h/_BފWV ţ/LxDpG7xj-39P]+O/C;,M)BrT0,{&HJr-vOcZ9f6R9a. QxR(L*;ըJ@ k#swR<v_wcĦIʯ fGSo6We}x/ąS}-oe!YNSrlWq:g'CZfD]9M)N\ꘝhf_"_P.qqsFeF*r Y+GZe&Oډ9 ?QCa18mO[>UE_p-ZgXr*qazoۻ\L + +绡9Hu+fI*. +n:HLYo|!T`Dp"E&Z?@ `ݖdDЎa5dQ"Y}%@7L =^w̓u,8 B܏\MxLtM7.X6qYJBnj ehH50*cq[Y;גHr3B 8 F=J͜sF2+b3ȩf~|FW/K1s|=]up5I) ~ ^AIIҬ!KV/N E57"P@rD\(hL.4!'b!>+!\Q֯~X(ߒsG|<@o׺BTZ!9CerGP +jyʉfUAH,(]$|饑0Uv;iΐЋK ym$ w/3]F&ǐ d3 qSȆ= +f(\]`XJA~^oq@eE"N(5w9[3Lww8/T5h镊= M%:f{ۑ#)T[yz£ĆrĎ Q,uiG )vweR *hX4hL <:'99gmJq +e@#+xv_UUu)I]ñ) +' +$7v͙5ka ~/xp0GІJ;A$ˈ5:S9$c$xa |*_qkzGII7HKR8HP2;S\Aqƒ9]яnLc4VĽ)*88 0v@\j^QphYJdPV +R4;<;ӡ:3ܲ2`Smg2M-~tu5MxPgnBԼ 8N[4g ސüΊCTUȎj#q_(O!Bcj+ er5PYPuQ:d-^a渔8Jͻ5 DB9tm>Ng8o29GPYvy^Lw"vw9&mC9'.e)8btZks5G4Լn$Լ~vJuӢ'l讲O{@UMufIj5&/a] @N1/tv`ԦrB Gɽ(hOY1Gʱ0̯/ endstream endobj 50 0 obj [/Indexed/DeviceRGB 255 64 0 R] endobj 64 0 obj <>stream +8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0 +b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup` +E1r!/,*0[*9.aFIR2&b-C#soRZ7Dl%MLY\.?d>Mn +6%Q2oYfNRF$$+ON<+]RUJmC0InDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j$XKrcYp0n+Xl_nU*O( +l[$6Nn+Z_Nq0]s7hs]`XX$6Ra!<<'!!!*'!!rrmPX()~> endstream endobj 47 0 obj <> endobj 45 0 obj [/ICCBased 66 0 R] endobj 65 0 obj <> endobj 67 0 obj <> endobj 68 0 obj <> endobj 69 0 obj <> endobj 70 0 obj <> endobj 71 0 obj <> endobj 72 0 obj <> endobj 73 0 obj <> endobj 74 0 obj <> endobj 66 0 obj <>stream +HyTSwoɞc [5laQIBHADED2mtFOE.c}08׎8GNg9w߽'0 ֠Jb  + 2y.-;!KZ ^i"L0- @8(r;q7Ly&Qq4j|9 +V)gB0iW8#8wթ8_٥ʨQQj@&A)/g>'Kt;\ ӥ$պFZUn(4T%)뫔0C&Zi8bxEB;Pӓ̹A om?W= +x-[0}y)7ta>jT7@tܛ`q2ʀ&6ZLĄ?_yxg)˔zçLU*uSkSeO4?׸c. R ߁-25 S>ӣVd`rn~Y&+`;A4 A9=-tl`;~p Gp| [`L`< "A YA+Cb(R,*T2B- +ꇆnQt}MA0alSx k&^>0|>_',G!"F$H:R!zFQd?r 9\A&G rQ hE]a4zBgE#H *B=0HIpp0MxJ$D1D, VĭKĻYdE"EI2EBGt4MzNr!YK ?%_&#(0J:EAiQ(()ӔWT6U@P+!~mD eԴ!hӦh/']B/ҏӿ?a0nhF!X8܌kc&5S6lIa2cKMA!E#ƒdV(kel }}Cq9 +N')].uJr + wG xR^[oƜchg`>b$*~ :Eb~,m,-ݖ,Y¬*6X[ݱF=3뭷Y~dó ti zf6~`{v.Ng#{}}jc1X6fm;'_9 r:8q:˜O:ϸ8uJqnv=MmR 4 +n3ܣkGݯz=[==<=GTB(/S,]6*-W:#7*e^YDY}UjAyT`#D="b{ų+ʯ:!kJ4Gmt}uC%K7YVfFY .=b?SƕƩȺy چ k5%4m7lqlioZlG+Zz͹mzy]?uuw|"űNwW&e֥ﺱ*|j5kyݭǯg^ykEklD_p߶7Dmo꿻1ml{Mś nLl<9O[$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! +zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Km endstream endobj 42 0 obj <> endobj 75 0 obj [/View/Design] endobj 76 0 obj <>>> endobj 46 0 obj <> endobj 5 0 obj <> endobj 39 0 obj [/View/Design] endobj 40 0 obj <>>> endobj 43 0 obj [42 0 R] endobj 77 0 obj <> endobj xref +0 78 +0000000004 65535 f +0000000016 00000 n +0000000159 00000 n +0000047101 00000 n +0000000000 00000 f +0000956356 00000 n +0000000000 00000 f +0000047152 00000 n +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000956426 00000 n +0000956457 00000 n +0000000000 00000 f +0000956056 00000 n +0000956542 00000 n +0000047575 00000 n +0000952172 00000 n +0000956243 00000 n +0000952029 00000 n +0000255161 00000 n +0000255642 00000 n +0000951455 00000 n +0000255716 00000 n +0000256091 00000 n +0000257526 00000 n +0000323114 00000 n +0000388702 00000 n +0000454290 00000 n +0000519878 00000 n +0000585466 00000 n +0000651054 00000 n +0000716642 00000 n +0000782230 00000 n +0000847818 00000 n +0000913406 00000 n +0000951503 00000 n +0000952207 00000 n +0000953399 00000 n +0000952441 00000 n +0000952563 00000 n +0000952684 00000 n +0000952799 00000 n +0000952914 00000 n +0000953034 00000 n +0000953155 00000 n +0000953277 00000 n +0000956127 00000 n +0000956158 00000 n +0000956567 00000 n +trailer <]>> startxref 956789 %%EOF \ No newline at end of file diff --git a/site/site/public/kitdigital/images/kitdigital.svg b/site/site/public/kitdigital/images/kitdigital.svg new file mode 100644 index 0000000..1cddc33 --- /dev/null +++ b/site/site/public/kitdigital/images/kitdigital.svg @@ -0,0 +1,4923 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/site/site/public/kitdigital/images/multiline-kitditial.svg b/site/site/public/kitdigital/images/multiline-kitditial.svg new file mode 100644 index 0000000..a57e66d --- /dev/null +++ b/site/site/public/kitdigital/images/multiline-kitditial.svg @@ -0,0 +1,4913 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/site/site/public/kitdigital/index.html b/site/site/public/kitdigital/index.html new file mode 100644 index 0000000..61a224a --- /dev/null +++ b/site/site/public/kitdigital/index.html @@ -0,0 +1,335 @@ + + + + + + Agente Digital - Kit Digital España + + + +

    +
    +
    +
    + + Kit Digital España + +
    +
    + + + +
    +
    + +
    +
    + +
    +
    +
    +

    Agente Digital Autorizado

    +

    Soluciones de Digitalización Kit Digital

    +

    + Ayudamos a pequeñas empresas, microempresas y autónomos + a digitalizar sus procesos mediante las ayudas del + programa + Kit Digital, financiado por los fondos + Next Generation EU. +

    + +
    +
    + +
    +
    +

    Soluciones de Digitalización

    +
    +
    +

    Sitio Web y Presencia en Internet

    +

    + Desarrollo de páginas web profesionales y + optimización de presencia digital. +

    +
    +
    +

    Gestión de Clientes (CRM)

    +

    + Sistemas para organizar y gestionar la relación + con tus clientes. +

    +
    +
    +

    Business Intelligence y Analítica: IA

    +

    + Herramientas de análisis de datos para tomar + mejores decisiones empresariales. +

    +
    +
    +

    Gestión de Procesos

    +

    + Automatización y digitalización de procesos + internos de la empresa. +

    +
    +
    +

    Facturación Electrónica

    +

    + Sistemas de facturación digital y gestión + documental electrónica. +

    +
    +
    +

    Comunicaciones

    +

    + Soluciones de comunicación empresarial y + colaboración en la nube. +

    +
    +
    +

    Ciberseguridad

    +

    + Protección y seguridad de sistemas informáticos + y datos empresariales. +

    +
    +
    +
    +
    + +
    +
    +

    Segmentos de Beneficiarios

    +
    +
    +

    Segmento I

    +

    + Empresas de 10 a 49 empleados +

    +

    Ayuda de hasta 12.000€

    +
      +
    • Sitio Web y Presencia en Internet
    • +
    • Gestión de Clientes
    • +
    • Business Intelligence y Analítica
    • +
    • Gestión de Procesos
    • +
    • Facturación Electrónica
    • +
    • Comunicaciones
    • +
    • Ciberseguridad
    • +
    +
    +
    +

    Segmento II

    +

    Empresas de 3 a 9 empleados

    +

    Ayuda de hasta 6.000€

    +
      +
    • Sitio Web y Presencia en Internet
    • +
    • Gestión de Clientes
    • +
    • Facturación Electrónica
    • +
    • Comunicaciones
    • +
    • Ciberseguridad
    • +
    +
    +
    +

    Segmento III

    +

    + Empresas de 0 a 2 empleados y + autónomos +

    +

    Ayuda de hasta 2.000€

    +
      +
    • Sitio Web y Presencia en Internet
    • +
    • Gestión de Clientes
    • +
    • Facturación Electrónica
    • +
    +
    +
    +
    +
    + +
    +
    +

    Sectores

    +
    +
    Comercio al por menor
    +
    Hostelería y turismo
    +
    Servicios profesionales
    +
    Salud y bienestar
    +
    Educación y formación
    +
    Construcción
    +
    Transporte y logística
    +
    + Agricultura y alimentación +
    +
    Industria manufacturera
    +
    Servicios financieros
    +
    +
    +
    + +
    +
    +

    Contacto

    +
    +
    +

    Solicitar Información

    +

    + Complete nuestro formulario para recibir + información personalizada sobre las soluciones + de digitalización disponibles para su empresa + mediante el programa + Kit Digital. +

    + Ir al formulario +
    +
    +

    Proceso de Solicitud

    +

    + Te asesoramos gratuitamente en todo el proceso + de solicitud y implementación de las soluciones + digitales más adecuadas para tu negocio. +

    +

    + Para consultas urgentes: + + contacto directo + +

    +
    +
    +
    +
    +
    + + + + + + diff --git a/site/site/public/kitdigital/styles.css b/site/site/public/kitdigital/styles.css new file mode 100644 index 0000000..e9f1b73 --- /dev/null +++ b/site/site/public/kitdigital/styles.css @@ -0,0 +1,890 @@ +/* Reset y configuración base */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html { + scroll-behavior: smooth; +} + +body { + font-family: "Arial", sans-serif; + line-height: 1.6; + color: #333; + background-color: #fff; +} + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 0 20px; +} + +/* Header */ +header { + background: #fff; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + position: fixed; + top: 0; + width: 100%; + z-index: 1000; +} + +header .container { + display: flex; + flex-direction: column; + align-items: center; + padding: 1rem 20px; + gap: 1rem; +} + +.logos-section { + background: #fafafa; + padding: 0.5rem 1rem; + border-radius: 8px; + display: flex; + justify-content: center; + align-items: center; + width: 100%; +} + +.logos-responsive { + display: none; +} + +.logos { + display: block; +} + +.logos-group-1, +.logos-group-2 { + display: flex; + align-items: center; + gap: 1rem; +} + +.eu-logo, +.gov-logo, +.redes-logo, +.recovery-logo, +.kit-digital-logo { + height: 5.5em; + width: auto; +} + +.navbar { + position: relative; +} + +.nav-toggle { + display: none; +} + +.hamburger { + display: none; + flex-direction: column; + cursor: pointer; + padding: 0; + width: 30px; + height: 30px; + justify-content: space-between; +} + +.hamburger span { + width: 100%; + height: 3px; + background-color: #333; + transition: all 0.3s ease; + display: block; +} + +.nav-toggle:checked + .hamburger span:nth-child(1) { + transform: rotate(45deg) translate(6px, 6px); +} + +.nav-toggle:checked + .hamburger span:nth-child(2) { + opacity: 0; +} + +.nav-toggle:checked + .hamburger span:nth-child(3) { + transform: rotate(-45deg) translate(7px, -6px); +} + +.nav-menu { + display: flex; + list-style: none; + gap: 2rem; + margin: 0; + padding: 0; +} + +.nav-link { + text-decoration: none; + color: #333; + font-weight: 500; + transition: color 0.3s; +} + +.nav-link:hover { + color: #0066cc; +} + +/* Secciones principales */ +main { + margin-top: 140px; +} + +section { + padding: 4rem 0; +} + +.hero { + background: linear-gradient(135deg, #0066cc 0%, #004499 100%); + color: white; + text-align: center; + padding: 6rem 0; +} + +.hero h1 { + font-size: 3rem; + margin-bottom: 1rem; + font-weight: bold; +} + +.hero h2 { + font-size: 1.8rem; + margin-bottom: 1.5rem; + font-weight: 300; +} + +.lead { + font-size: 1.2rem; + margin-bottom: 2rem; + max-width: 800px; + margin-left: auto; + margin-right: auto; +} +.lead a { + color: #c7c9ca; +} +.eu-funding a { + color: #fff; + text-decoration: none; +} + +.eu-funding { + background: rgba(255, 255, 255, 0.1); + padding: 1rem; + border-radius: 8px; + margin-top: 2rem; + display: inline-block; +} + +.eu-funding p { + font-size: 1rem; + margin: 0; +} + +/* Soluciones */ +.solutions { + background: #f8f9fa; +} + +.solutions h2 { + text-align: center; + font-size: 2.5rem; + margin-bottom: 3rem; + color: #0066cc; +} + +.solutions-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 2rem; +} + +.solution-card { + background: white; + padding: 2rem; + border-radius: 12px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); + transition: + transform 0.3s, + box-shadow 0.3s; +} + +.solution-card:hover { + transform: translateY(-5px); + box-shadow: 0 8px 30px rgba(0, 0, 0, 0.15); +} + +.solution-card h3 { + color: #0066cc; + margin-bottom: 1rem; + font-size: 1.3rem; +} + +.solution-card p { + color: #666; + line-height: 1.6; +} + +/* Segmentos */ +.segments { + background: white; +} + +.segments h2 { + text-align: center; + font-size: 2.5rem; + margin-bottom: 3rem; + color: #0066cc; +} + +.segments-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); + gap: 2rem; +} + +.segment-card { + background: #f8f9fa; + padding: 2.5rem; + border-radius: 12px; + border-left: 5px solid #0066cc; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); +} + +.segment-card h3 { + color: #0066cc; + font-size: 1.8rem; + margin-bottom: 1rem; +} + +.segment-card p { + margin-bottom: 1rem; + font-weight: 500; +} + +.segment-card ul { + list-style: none; + padding-left: 0; +} + +.segment-card li { + padding: 0.5rem 0; + border-bottom: 1px solid #e9ecef; + position: relative; + padding-left: 20px; +} + +.segment-card li:before { + content: "✓"; + color: #28a745; + font-weight: bold; + position: absolute; + left: 0; +} + +/* Sectores */ +.sectors { + background: #f8f9fa; +} + +.sectors h2 { + text-align: center; + font-size: 2.5rem; + margin-bottom: 3rem; + color: #0066cc; +} + +.sectors-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 1.5rem; +} + +.sector-item { + background: white; + padding: 1.5rem; + border-radius: 8px; + text-align: center; + font-weight: 500; + color: #0066cc; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + transition: transform 0.3s; +} + +.sector-item:hover { + transform: translateY(-3px); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); +} + +/* Contacto */ +.contact { + background: white; +} + +.contact h2 { + text-align: center; + font-size: 2.5rem; + margin-bottom: 3rem; + color: #0066cc; +} + +.contact-info { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 3rem; +} + +.contact-item { + text-align: center; + padding: 2rem; +} + +.contact-item h3 { + color: #0066cc; + margin-bottom: 1rem; + font-size: 1.5rem; +} + +.contact-item p { + color: #666; + line-height: 1.6; + font-size: 1.1rem; + margin-bottom: 1rem; +} + +.contact-form-btn { + display: inline-block; + background: linear-gradient(135deg, #0066cc 0%, #004499 100%); + color: white; + padding: 1rem 2rem; + text-decoration: none; + border-radius: 8px; + font-weight: 600; + font-size: 1rem; + transition: all 0.3s; + margin-top: 1rem; +} + +.contact-form-btn:hover { + transform: translateY(-2px); + box-shadow: 0 8px 25px rgba(0,102,204,0.3); + color: white; +} + +.contact-direct-link { + color: #0066cc; + text-decoration: underline; + font-weight: 500; +} + +.contact-direct-link:hover { + color: #004499; +} + +/* Footer */ +footer { + background: #f8f9fa; + padding: 1rem 0; +} + +.footer-content { + display: flex; + justify-content: space-between; + flex-wrap: wrap; + gap: 1rem; +} + +.footer-info { + text-align: left; +} + +.footer-info p, +.footer-legal p { + margin-bottom: 0.2rem; + font-size: 0.7rem; +} + +.footer-legal { + text-align: right; +} + +.footer-legal p { + font-size: 0.7rem; +} + +.footer-box { + display: flex; + justify-content: center; + align-items: center; + padding: 1rem 20px; + background: #f8f9fa; +} + +.footer-logo { + display: flex; + align-items: center; +} + +.footer-kit-logo { + height: 5.5em; + width: auto; +} + +/* Contact Form Styles */ +.contact-form { + background: #f8f9fa; + padding: 6rem 0 4rem 0; +} + +.contact-form h1 { + text-align: center; + font-size: 3rem; + margin-bottom: 1rem; + color: #0066cc; +} + +.contact-form h2 { + text-align: center; + font-size: 1.8rem; + margin-bottom: 2rem; + color: #333; + font-weight: 300; +} + +.form-intro { + text-align: center; + font-size: 1.1rem; + color: #666; + margin-bottom: 3rem; + max-width: 800px; + margin-left: auto; + margin-right: auto; +} + +.contact-form-container { + background: white; + padding: 3rem; + border-radius: 12px; + box-shadow: 0 4px 20px rgba(0,0,0,0.1); + max-width: 800px; + margin: 0 auto 3rem auto; +} + +.form-group { + margin-bottom: 2rem; +} + +.form-group label { + display: block; + margin-bottom: 0.5rem; + font-weight: 500; + color: #333; +} + +.form-group input, +.form-group select, +.form-group textarea { + width: 100%; + padding: 1rem; + border: 2px solid #e9ecef; + border-radius: 8px; + font-size: 1rem; + transition: border-color 0.3s; + font-family: inherit; +} + +.form-group input:focus, +.form-group select:focus, +.form-group textarea:focus { + outline: none; + border-color: #0066cc; +} + +/* Error Styles */ +.form-group.error input, +.form-group.error select, +.form-group.error textarea { + border-color: #dc3545; + background-color: #fff5f5; +} + +.form-group.error .checkbox-group, +.form-group.error .radio-group { + border: 2px solid #dc3545; + border-radius: 8px; + padding: 1rem; + background-color: #fff5f5; +} + +.error-message { + color: #dc3545; + font-size: 0.875rem; + margin-top: 0.5rem; + display: block; + font-weight: 500; +} + +.form-group.error label { + color: #dc3545; +} + +.form-group.error .checkmark, +.form-group.error .radio-mark { + border-color: #dc3545; +} + +/* Success styles for completed valid fields */ +.form-group.valid input, +.form-group.valid select, +.form-group.valid textarea { + border-color: #28a745; + background-color: #f8fff9; +} + +.form-group.valid .checkbox-group, +.form-group.valid .radio-group { + border-color: #28a745; + background-color: #f8fff9; +} + +.checkbox-group, +.radio-group { + display: grid; + gap: 0.8rem; + margin-top: 0.5rem; +} + +.checkbox-item, +.radio-item { + display: flex; + align-items: center; + cursor: pointer; + position: relative; + padding-left: 2rem; +} + +.checkbox-item input, +.radio-item input { + position: absolute; + opacity: 0; + cursor: pointer; + width: 0; + height: 0; +} + +.checkmark, +.radio-mark { + position: absolute; + left: 0; + top: 50%; + transform: translateY(-50%); + height: 20px; + width: 20px; + background-color: #fff; + border: 2px solid #e9ecef; + transition: all 0.3s; +} + +.checkmark { + border-radius: 4px; +} + +.radio-mark { + border-radius: 50%; +} + +.checkbox-item:hover .checkmark, +.radio-item:hover .radio-mark { + border-color: #0066cc; +} + +.checkbox-item input:checked ~ .checkmark, +.radio-item input:checked ~ .radio-mark { + background-color: #0066cc; + border-color: #0066cc; +} + +.checkmark:after, +.radio-mark:after { + content: ""; + position: absolute; + display: none; +} + +.checkbox-item input:checked ~ .checkmark:after { + display: block; + left: 6px; + top: 2px; + width: 6px; + height: 10px; + border: solid white; + border-width: 0 2px 2px 0; + transform: rotate(45deg); +} + +.radio-item input:checked ~ .radio-mark:after { + display: block; + top: 50%; + left: 50%; + width: 8px; + height: 8px; + border-radius: 50%; + background: white; + transform: translate(-50%, -50%); +} + +.privacy { + font-size: 0.9rem; + color: #666; +} + +.submit-btn { + background: linear-gradient(135deg, #0066cc 0%, #004499 100%); + color: white; + padding: 1rem 3rem; + border: none; + border-radius: 8px; + font-size: 1.1rem; + font-weight: 600; + cursor: pointer; + transition: all 0.3s; + width: 100%; + margin-top: 1rem; +} + +.submit-btn:hover { + transform: translateY(-2px); + box-shadow: 0 8px 25px rgba(0,102,204,0.3); +} + +.contact-info-additional { + max-width: 800px; + margin: 0 auto; +} + +.info-card { + background: white; + padding: 2rem; + border-radius: 12px; + box-shadow: 0 4px 20px rgba(0,0,0,0.1); + text-align: center; +} + +.info-card h3 { + color: #0066cc; + margin-bottom: 1rem; + font-size: 1.5rem; +} + +.info-card p { + color: #666; + margin-bottom: 1.5rem; + line-height: 1.6; +} + +.contact-direct { + display: inline-block; + background: #0066cc; + color: white; + padding: 0.8rem 2rem; + text-decoration: none; + border-radius: 6px; + font-weight: 500; + transition: background 0.3s; +} + +.contact-direct:hover { + background: #004499; +} + +/* Responsive */ +@media (max-width: 768px) { + .logos-responsive { + display: block; + } + + .logos { + display: none; + } + + .logos-section { + flex-direction: column; + gap: 1rem; + } + + .logos-group-1, + .logos-group-2 { + justify-content: center; + flex-wrap: wrap; + } + + .eu-logo, + .gov-logo, + .redes-logo, + .recovery-logo, + .kit-digital-logo { + height: 4em; + } + + .footer-kit-logo { + height: 4em; + } + .kit-digital-logo-responsive { + height: 8em; + } + + .hamburger { + display: flex; + } + + .nav-menu { + position: absolute; + top: 100%; + left: 0; + right: 0; + background: #fff; + width: 150px; + flex-direction: column; + gap: 0; + text-align: center; + padding: 0; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + border-radius: 8px; + margin: 0.5rem; + transform: translateY(-100%); + opacity: 0; + visibility: hidden; + transition: all 0.3s ease; + z-index: 1000; + } + + .nav-toggle:checked ~ .nav-menu { + transform: translateY(0); + opacity: 1; + visibility: visible; + } + + .nav-menu li { + border-bottom: 1px solid #f0f0f0; + } + + .nav-menu li:last-child { + border-bottom: none; + } + + .nav-link { + display: block; + padding: 1rem; + color: #004499 + text-decoration: none; + transition: background-color 0.3s; + } + + .nav-link:hover { + background-color: #f8f9fa; + color: #0066cc; + } + + .hero h1 { + font-size: 2rem; + } + + .hero h2 { + font-size: 1.4rem; + } + + .solutions-grid, + .segments-grid { + grid-template-columns: 1fr; + } + + .sectors-grid { + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + } + + .footer-content { + flex-direction: column; + text-align: center; + gap: 0.5rem; + } + + .footer-legal { + text-align: center; + } + + .footer-info p, + .footer-legal p { + font-size: 0.6rem; + } + /* Responsive Form Styles */ + .contact-form-container { + padding: 2rem 1.5rem; + margin: 0 1rem 2rem 1rem; + } + + .contact-form h1 { + font-size: 2.2rem; + } + + .contact-form h2 { + font-size: 1.4rem; + } + + .checkbox-group { + grid-template-columns: 1fr; + } +} + +@media (max-width: 480px) { + .container { + padding: 0 15px; + } + + section { + padding: 2rem 0; + } + + .hero { + padding: 4rem 0; + } + + .solution-card, + .segment-card { + padding: 1.5rem; + } + + .eu-logo, + .gov-logo, + .redes-logo, + .recovery-logo, + .kit-digital-logo { + height: 3em; + } + + .footer-kit-logo { + height: 3em; + } + .contact-form { + padding: 4rem 0 2rem 0; + } + + .contact-form-container { + padding: 1.5rem 1rem; + } + + .contact-form h1 { + font-size: 1.8rem; + } + + .form-group input, + .form-group select, + .form-group textarea { + padding: 0.8rem; + } +} diff --git a/site/site/public/logs.html b/site/site/public/logs.html new file mode 100644 index 0000000..49b171b --- /dev/null +++ b/site/site/public/logs.html @@ -0,0 +1,329 @@ + + + + + + Browser Console Logs Viewer + + + +
    +
    +

    🔍 Browser Console Logs

    +

    Real-time capture of browser console output

    +
    + +
    +
    Total Logs: 0
    +
    Errors: 0
    +
    Warnings: 0
    +
    Last Updated: Never
    +
    + +
    + + + +
    + + + + +
    +
    + +
    +
    +

    Waiting for console output...

    +

    To capture logs, visit another page on this site and use console.log(), console.error(), etc.

    + +
    +
    + + +
    + + + + diff --git a/site/site/public/robots.txt b/site/site/public/robots.txt new file mode 100644 index 0000000..25a33bd --- /dev/null +++ b/site/site/public/robots.txt @@ -0,0 +1,14 @@ +User-agent: * +Allow: / + +# Sitemap location +Sitemap: {{base_url}}/sitemap.xml + +# Crawl-delay (optional, in seconds) +# Crawl-delay: 1 + +# Disallow specific paths (customize as needed) +Disallow: /admin +Disallow: /.rustelo/ +Disallow: /target/ +Disallow: /node_modules/ \ No newline at end of file diff --git a/site/site/public/styles/app.min.css b/site/site/public/styles/app.min.css new file mode 100644 index 0000000..f76d59a --- /dev/null +++ b/site/site/public/styles/app.min.css @@ -0,0 +1 @@ +*,::before,::after{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-pan-x:;--un-pan-y:;--un-pinch-zoom:;--un-scroll-snap-strictness:proximity;--un-ordinal:;--un-slashed-zero:;--un-numeric-figure:;--un-numeric-spacing:;--un-numeric-fraction:;--un-border-spacing-x:0;--un-border-spacing-y:0;--un-ring-offset-shadow:0 0 rgb(0 0 0 / 0);--un-ring-shadow:0 0 rgb(0 0 0 / 0);--un-shadow-inset:;--un-shadow:0 0 rgb(0 0 0 / 0);--un-ring-inset:;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgb(147 197 253 / 0.5);--un-blur:;--un-brightness:;--un-contrast:;--un-drop-shadow:;--un-grayscale:;--un-hue-rotate:;--un-invert:;--un-saturate:;--un-sepia:;--un-backdrop-blur:;--un-backdrop-brightness:;--un-backdrop-contrast:;--un-backdrop-grayscale:;--un-backdrop-hue-rotate:;--un-backdrop-invert:;--un-backdrop-opacity:;--un-backdrop-saturate:;--un-backdrop-sepia:}::backdrop{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-pan-x:;--un-pan-y:;--un-pinch-zoom:;--un-scroll-snap-strictness:proximity;--un-ordinal:;--un-slashed-zero:;--un-numeric-figure:;--un-numeric-spacing:;--un-numeric-fraction:;--un-border-spacing-x:0;--un-border-spacing-y:0;--un-ring-offset-shadow:0 0 rgb(0 0 0 / 0);--un-ring-shadow:0 0 rgb(0 0 0 / 0);--un-shadow-inset:;--un-shadow:0 0 rgb(0 0 0 / 0);--un-ring-inset:;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgb(147 197 253 / 0.5);--un-blur:;--un-brightness:;--un-contrast:;--un-drop-shadow:;--un-grayscale:;--un-hue-rotate:;--un-invert:;--un-saturate:;--un-sepia:;--un-backdrop-blur:;--un-backdrop-brightness:;--un-backdrop-contrast:;--un-backdrop-grayscale:;--un-backdrop-hue-rotate:;--un-backdrop-invert:;--un-backdrop-opacity:;--un-backdrop-saturate:;--un-backdrop-sepia:}body{font-family:"Segoe UI",Tahoma,Geneva,Verdana,sans-serif}:root{--code-bg:#f8fafc;--code-border:#e2e8f0;--code-text:#334155;--code-comment:#64748b;--code-keyword:#0f172a;--code-string:#059669;--code-number:#dc2626;--code-function:#2563eb;--code-variable:#7c3aed;--inline-code-bg:#f1f5f9;--inline-code-text:#475569}.dark{--code-bg:#1e293b;--code-border:#334155;--code-text:#e2e8f0;--code-comment:#94a3b8;--code-keyword:#f8fafc;--code-string:#34d399;--code-number:#fca5a5;--code-function:#60a5fa;--code-variable:#a78bfa;--inline-code-bg:#374151;--inline-code-text:#d1d5db}pre{background-color:var(--code-bg) !important;border:1px solid var(--code-border);border-radius:var(--radius-md,0.375rem);padding:1.25rem 1.5rem;margin:1.5rem 0;overflow-x:auto;line-height:1.6;font-family:ui-monospace,SFMono-Regular,'SF Mono',Consolas,'Liberation Mono',Menlo,monospace;font-size:0.875rem;position:relative}pre code{background-color:transparent !important;padding:0 !important;border-radius:0;color:var(--code-text);font-size:inherit;font-weight:400}code:not(pre code){background-color:var(--inline-code-bg) !important;color:var(--inline-code-text) !important;padding:0.125rem 0.375rem;border-radius:var(--radius-sm,0.125rem);font-size:0.875em;font-weight:500;font-family:ui-monospace,SFMono-Regular,'SF Mono',Consolas,'Liberation Mono',Menlo,monospace;border:1px solid var(--code-border)}:is([prose=""],.prose):where(:not(pre)>code):not(:where(.not-prose,.not-prose *))::before,:is([prose=""],.prose):where(:not(pre)>code):not(:where(.not-prose,.not-prose *))::after{content:"" !important}.hljs{background:var(--code-bg) !important;color:var(--code-text) !important}pre::-webkit-scrollbar{height:8px}pre::-webkit-scrollbar-track{background:transparent}pre::-webkit-scrollbar-thumb{background-color:var(--code-border);border-radius:4px}pre::-webkit-scrollbar-thumb:hover{background-color:var(--code-comment)}.dark pre::-webkit-scrollbar-thumb{background-color:var(--color-neutral-600)}.dark pre::-webkit-scrollbar-thumb:hover{background-color:var(--color-neutral-500)}.prose pre,[prose=""] pre{margin:2rem 0 !important;line-height:1.7}.prose code,[prose=""] code{font-size:0.875em}@media (max-width:768px){pre{padding:1rem;margin:1rem -1rem;border-radius:0;border-left:none;border-right:none}}pre:focus-within{outline:2px solid var(--color-brand-primary);outline-offset:2px}code:focus{outline:2px solid var(--color-brand-primary);outline-offset:1px}pre code.hljs{background:transparent !important}.filter-selected{background-color:#2563eb !important;border-color:#2563eb !important;color:#ffffff !important}.filter-hidden{display:none !important}.i-carbon-moon{--un-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 32 32' width='1.2em' height='1.2em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M13.503 5.414a15.076 15.076 0 0 0 11.593 18.194a11.1 11.1 0 0 1-7.975 3.39c-.138 0-.278.005-.418 0a11.094 11.094 0 0 1-3.2-21.584M14.98 3a1 1 0 0 0-.175.016a13.096 13.096 0 0 0 1.825 25.981c.164.006.328 0 .49 0a13.07 13.07 0 0 0 10.703-5.555a1.01 1.01 0 0 0-.783-1.565A13.08 13.08 0 0 1 15.89 4.38A1.015 1.015 0 0 0 14.98 3'/%3E%3C/svg%3E");-webkit-mask:var(--un-icon) no-repeat;mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;mask-size:100% 100%;background-color:currentColor;color:inherit;width:1.2em;height:1.2em}.i-carbon-settings{--un-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 32 32' width='1.2em' height='1.2em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M27 16.76v-1.53l1.92-1.68A2 2 0 0 0 29.3 11l-2.36-4a2 2 0 0 0-1.73-1a2 2 0 0 0-.64.1l-2.43.82a11 11 0 0 0-1.31-.75l-.51-2.52a2 2 0 0 0-2-1.61h-4.68a2 2 0 0 0-2 1.61l-.51 2.52a11.5 11.5 0 0 0-1.32.75l-2.38-.86A2 2 0 0 0 6.79 6a2 2 0 0 0-1.73 1L2.7 11a2 2 0 0 0 .41 2.51L5 15.24v1.53l-1.89 1.68A2 2 0 0 0 2.7 21l2.36 4a2 2 0 0 0 1.73 1a2 2 0 0 0 .64-.1l2.43-.82a11 11 0 0 0 1.31.75l.51 2.52a2 2 0 0 0 2 1.61h4.72a2 2 0 0 0 2-1.61l.51-2.52a11.5 11.5 0 0 0 1.32-.75l2.42.82a2 2 0 0 0 .64.1a2 2 0 0 0 1.73-1l2.28-4a2 2 0 0 0-.41-2.51ZM25.21 24l-3.43-1.16a8.9 8.9 0 0 1-2.71 1.57L18.36 28h-4.72l-.71-3.55a9.4 9.4 0 0 1-2.7-1.57L6.79 24l-2.36-4l2.72-2.4a8.9 8.9 0 0 1 0-3.13L4.43 12l2.36-4l3.43 1.16a8.9 8.9 0 0 1 2.71-1.57L13.64 4h4.72l.71 3.55a9.4 9.4 0 0 1 2.7 1.57L25.21 8l2.36 4l-2.72 2.4a8.9 8.9 0 0 1 0 3.13L27.57 20Z'/%3E%3Cpath fill='currentColor' d='M16 22a6 6 0 1 1 6-6a5.94 5.94 0 0 1-6 6m0-10a3.91 3.91 0 0 0-4 4a3.91 3.91 0 0 0 4 4a3.91 3.91 0 0 0 4-4a3.91 3.91 0 0 0-4-4'/%3E%3C/svg%3E");-webkit-mask:var(--un-icon) no-repeat;mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;mask-size:100% 100%;background-color:currentColor;color:inherit;width:1.2em;height:1.2em}.i-carbon-sun{--un-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 32 32' width='1.2em' height='1.2em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M16 12.005a4 4 0 1 1-4 4a4.005 4.005 0 0 1 4-4m0-2a6 6 0 1 0 6 6a6 6 0 0 0-6-6M5.394 6.813L6.81 5.399l3.505 3.506L8.9 10.319zM2 15.005h5v2H2zm3.394 10.193L8.9 21.692l1.414 1.414l-3.505 3.506zM15 25.005h2v5h-2zm6.687-1.9l1.414-1.414l3.506 3.506l-1.414 1.414zm3.313-8.1h5v2h-5zm-3.313-6.101l3.506-3.506l1.414 1.414l-3.506 3.506zM15 2.005h2v5h-2z'/%3E%3C/svg%3E");-webkit-mask:var(--un-icon) no-repeat;mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;mask-size:100% 100%;background-color:currentColor;color:inherit;width:1.2em;height:1.2em}:is(.prose),:is([prose=""]){color:var(--un-prose-body);max-width:65ch;:where(p):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.25em;margin-bottom:1.25em}:where([class~="lead"]):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:var(--un-prose-lead);font-size:1.25em;line-height:1.6;margin-top:1.2em;margin-bottom:1.2em}:where(a):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:var(--un-prose-links);text-decoration:underline;font-weight:500}:where(strong):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:var(--un-prose-bold);font-weight:600}:where(a strong):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:inherit}:where(blockquote strong):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:inherit}:where(thead th strong):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:inherit}:where(ol):not(:where([class~="not-prose"],[class~="not-prose"] *)){list-style-type:decimal;margin-top:1.25em;margin-bottom:1.25em;padding-inline-start:1.625em}:where(ol[type="A"]):not(:where([class~="not-prose"],[class~="not-prose"] *)){list-style-type:upper-alpha}:where(ol[type="a"]):not(:where([class~="not-prose"],[class~="not-prose"] *)){list-style-type:lower-alpha}:where(ol[type="A" s]):not(:where([class~="not-prose"],[class~="not-prose"] *)){list-style-type:upper-alpha}:where(ol[type="a" s]):not(:where([class~="not-prose"],[class~="not-prose"] *)){list-style-type:lower-alpha}:where(ol[type="I"]):not(:where([class~="not-prose"],[class~="not-prose"] *)){list-style-type:upper-roman}:where(ol[type="i"]):not(:where([class~="not-prose"],[class~="not-prose"] *)){list-style-type:lower-roman}:where(ol[type="I" s]):not(:where([class~="not-prose"],[class~="not-prose"] *)){list-style-type:upper-roman}:where(ol[type="i" s]):not(:where([class~="not-prose"],[class~="not-prose"] *)){list-style-type:lower-roman}:where(ol[type="1"]):not(:where([class~="not-prose"],[class~="not-prose"] *)){list-style-type:decimal}:where(ul):not(:where([class~="not-prose"],[class~="not-prose"] *)){list-style-type:disc;margin-top:1.25em;margin-bottom:1.25em;padding-inline-start:1.625em}:where(ol>li::marker):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-weight:400;color:var(--un-prose-counters)}:where(ul>li::marker):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:var(--un-prose-bullets)}:where(dt):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:var(--un-prose-headings);font-weight:600;margin-top:1.25em}:where(hr):not(:where([class~="not-prose"],[class~="not-prose"] *)){border-color:var(--un-prose-hr);border-top-width:1px;margin-top:3em;margin-bottom:3em}:where(blockquote):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-weight:500;font-style:italic;color:var(--un-prose-quotes);border-inline-start-width:0.25rem;border-inline-start-color:var(--un-prose-quote-borders);quotes:"\201C""\201D""\2018""\2019";margin-top:1.6em;margin-bottom:1.6em;padding-inline-start:1em}:where(blockquote p:first-of-type::before):not(:where([class~="not-prose"],[class~="not-prose"] *)){content:open-quote}:where(blockquote p:last-of-type::after):not(:where([class~="not-prose"],[class~="not-prose"] *)){content:close-quote}:where(h1):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:var(--un-prose-headings);font-weight:800;font-size:2.25em;margin-top:0;margin-bottom:0.8888889em;line-height:1.1111111}:where(h1 strong):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-weight:900;color:inherit}:where(h2):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:var(--un-prose-headings);font-weight:700;font-size:1.5em;margin-top:2em;margin-bottom:1em;line-height:1.3333333}:where(h2 strong):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-weight:800;color:inherit}:where(h3):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:var(--un-prose-headings);font-weight:600;font-size:1.25em;margin-top:1.6em;margin-bottom:0.6em;line-height:1.6}:where(h3 strong):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-weight:700;color:inherit}:where(h4):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:var(--un-prose-headings);font-weight:600;margin-top:1.5em;margin-bottom:0.5em;line-height:1.5}:where(h4 strong):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-weight:700;color:inherit}:where(img):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:2em;margin-bottom:2em}:where(picture):not(:where([class~="not-prose"],[class~="not-prose"] *)){display:block;margin-top:2em;margin-bottom:2em}:where(video):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:2em;margin-bottom:2em}:where(kbd):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-weight:500;font-family:inherit;color:var(--un-prose-kbd);box-shadow:0 0 0 1px rgb(var(--un-prose-kbd-shadows) / 10%),0 3px 0 rgb(var(--un-prose-kbd-shadows) / 10%);font-size:0.875em;border-radius:0.3125rem;padding-top:0.1875em;padding-inline-end:0.375em;padding-bottom:0.1875em;padding-inline-start:0.375em}:where(code):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:var(--un-prose-code);font-weight:600;font-size:0.875em}:where(code::before):not(:where([class~="not-prose"],[class~="not-prose"] *)){content:"`"}:where(code::after):not(:where([class~="not-prose"],[class~="not-prose"] *)){content:"`"}:where(a code):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:inherit}:where(h1 code):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:inherit}:where(h2 code):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:inherit;font-size:0.875em}:where(h3 code):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:inherit;font-size:0.9em}:where(h4 code):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:inherit}:where(blockquote code):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:inherit}:where(thead th code):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:inherit}:where(pre):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:var(--un-prose-pre-code);background-color:var(--un-prose-pre-bg);overflow-x:auto;font-weight:400;font-size:0.875em;line-height:1.7142857;margin-top:1.7142857em;margin-bottom:1.7142857em;border-radius:0.375rem;padding-top:0.8571429em;padding-inline-end:1.1428571em;padding-bottom:0.8571429em;padding-inline-start:1.1428571em}:where(pre code):not(:where([class~="not-prose"],[class~="not-prose"] *)){background-color:transparent;border-width:0;border-radius:0;padding:0;font-weight:inherit;color:inherit;font-size:inherit;font-family:inherit;line-height:inherit}:where(pre code::before):not(:where([class~="not-prose"],[class~="not-prose"] *)){content:none}:where(pre code::after):not(:where([class~="not-prose"],[class~="not-prose"] *)){content:none}:where(table):not(:where([class~="not-prose"],[class~="not-prose"] *)){width:100%;table-layout:auto;margin-top:2em;margin-bottom:2em;font-size:0.875em;line-height:1.7142857}:where(thead):not(:where([class~="not-prose"],[class~="not-prose"] *)){border-bottom-width:1px;border-bottom-color:var(--un-prose-th-borders)}:where(thead th):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:var(--un-prose-headings);font-weight:600;vertical-align:bottom;padding-inline-end:0.5714286em;padding-bottom:0.5714286em;padding-inline-start:0.5714286em}:where(tbody tr):not(:where([class~="not-prose"],[class~="not-prose"] *)){border-bottom-width:1px;border-bottom-color:var(--un-prose-td-borders)}:where(tbody tr:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){border-bottom-width:0}:where(tbody td):not(:where([class~="not-prose"],[class~="not-prose"] *)){vertical-align:baseline}:where(tfoot):not(:where([class~="not-prose"],[class~="not-prose"] *)){border-top-width:1px;border-top-color:var(--un-prose-th-borders)}:where(tfoot td):not(:where([class~="not-prose"],[class~="not-prose"] *)){vertical-align:top}:where(th,td):not(:where([class~="not-prose"],[class~="not-prose"] *)){text-align:start}:where(figure>*):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0;margin-bottom:0}:where(figcaption):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:var(--un-prose-captions);font-size:0.875em;line-height:1.4285714;margin-top:0.8571429em}font-size:1rem;line-height:1.75;:where(picture>img):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0;margin-bottom:0}:where(li):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0.5em;margin-bottom:0.5em}:where(ol>li):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-inline-start:0.375em}:where(ul>li):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-inline-start:0.375em}:where(>ul>li p):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0.75em;margin-bottom:0.75em}:where(>ul>li>p:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.25em}:where(>ul>li>p:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-bottom:1.25em}:where(>ol>li>p:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.25em}:where(>ol>li>p:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-bottom:1.25em}:where(ul ul,ul ol,ol ul,ol ol):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0.75em;margin-bottom:0.75em}:where(dl):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.25em;margin-bottom:1.25em}:where(dd):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0.5em;padding-inline-start:1.625em}:where(hr+*):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0}:where(h2+*):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0}:where(h3+*):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0}:where(h4+*):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0}:where(thead th:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-inline-start:0}:where(thead th:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-inline-end:0}:where(tbody td,tfoot td):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-top:0.5714286em;padding-inline-end:0.5714286em;padding-bottom:0.5714286em;padding-inline-start:0.5714286em}:where(tbody td:first-child,tfoot td:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-inline-start:0}:where(tbody td:last-child,tfoot td:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-inline-end:0}:where(figure):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:2em;margin-bottom:2em}:where(>:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0}:where(>:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-bottom:0}--un-prose-body:#374151;--un-prose-invert-body:#d1d5db;--un-prose-headings:#111827;--un-prose-invert-headings:white;--un-prose-lead:#4b5563;--un-prose-invert-lead:#9ca3af;--un-prose-links:#111827;--un-prose-invert-links:white;--un-prose-bold:#111827;--un-prose-invert-bold:white;--un-prose-counters:#6b7280;--un-prose-invert-counters:#9ca3af;--un-prose-bullets:#d1d5db;--un-prose-invert-bullets:#4b5563;--un-prose-hr:#e5e7eb;--un-prose-invert-hr:#374151;--un-prose-quotes:#111827;--un-prose-invert-quotes:#f3f4f6;--un-prose-quote-borders:#e5e7eb;--un-prose-invert-quote-borders:#374151;--un-prose-captions:#6b7280;--un-prose-invert-captions:#9ca3af;--un-prose-kbd:#111827;--un-prose-invert-kbd:white;--un-prose-kbd-shadows:#111827;--un-prose-invert-kbd-shadows:white;--un-prose-code:#111827;--un-prose-invert-code:white;--un-prose-pre-code:#e5e7eb;--un-prose-invert-pre-code:#d1d5db;--un-prose-pre-bg:#1f2937;--un-prose-invert-pre-bg:rgb(0 0 0 / 50%);--un-prose-th-borders:#d1d5db;--un-prose-invert-th-borders:#4b5563;--un-prose-td-borders:#e5e7eb;--un-prose-invert-td-borders:#374151}:is(.prose-lg),:is([prose-lg=""]){font-size:1.125rem;line-height:1.7777778;:where(p):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.3333333em;margin-bottom:1.3333333em}:where([class~="lead"]):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-size:1.2222222em;line-height:1.4545455;margin-top:1.0909091em;margin-bottom:1.0909091em}:where(blockquote):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.6666667em;margin-bottom:1.6666667em;padding-inline-start:1em}:where(h1):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-size:2.6666667em;margin-top:0;margin-bottom:0.8333333em;line-height:1}:where(h2):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-size:1.6666667em;margin-top:1.8666667em;margin-bottom:1.0666667em;line-height:1.3333333}:where(h3):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-size:1.3333333em;margin-top:1.6666667em;margin-bottom:0.6666667em;line-height:1.5}:where(h4):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.7777778em;margin-bottom:0.4444444em;line-height:1.5555556}:where(img):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.7777778em;margin-bottom:1.7777778em}:where(picture):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.7777778em;margin-bottom:1.7777778em}:where(picture>img):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0;margin-bottom:0}:where(video):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.7777778em;margin-bottom:1.7777778em}:where(kbd):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-size:0.8888889em;border-radius:0.3125rem;padding-top:0.2222222em;padding-inline-end:0.4444444em;padding-bottom:0.2222222em;padding-inline-start:0.4444444em}:where(code):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-size:0.8888889em}:where(h2 code):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-size:0.8666667em}:where(h3 code):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-size:0.875em}:where(pre):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-size:0.8888889em;line-height:1.75;margin-top:2em;margin-bottom:2em;border-radius:0.375rem;padding-top:1em;padding-inline-end:1.5em;padding-bottom:1em;padding-inline-start:1.5em}:where(ol):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.3333333em;margin-bottom:1.3333333em;padding-inline-start:1.5555556em}:where(ul):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.3333333em;margin-bottom:1.3333333em;padding-inline-start:1.5555556em}:where(li):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0.6666667em;margin-bottom:0.6666667em}:where(ol>li):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-inline-start:0.4444444em}:where(ul>li):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-inline-start:0.4444444em}:where(>ul>li p):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0.8888889em;margin-bottom:0.8888889em}:where(>ul>li>p:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.3333333em}:where(>ul>li>p:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-bottom:1.3333333em}:where(>ol>li>p:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.3333333em}:where(>ol>li>p:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-bottom:1.3333333em}:where(ul ul,ul ol,ol ul,ol ol):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0.8888889em;margin-bottom:0.8888889em}:where(dl):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.3333333em;margin-bottom:1.3333333em}:where(dt):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.3333333em}:where(dd):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0.6666667em;padding-inline-start:1.5555556em}:where(hr):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:3.1111111em;margin-bottom:3.1111111em}:where(hr+*):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0}:where(h2+*):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0}:where(h3+*):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0}:where(h4+*):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0}:where(table):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-size:0.8888889em;line-height:1.5}:where(thead th):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-inline-end:0.75em;padding-bottom:0.75em;padding-inline-start:0.75em}:where(thead th:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-inline-start:0}:where(thead th:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-inline-end:0}:where(tbody td,tfoot td):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-top:0.75em;padding-inline-end:0.75em;padding-bottom:0.75em;padding-inline-start:0.75em}:where(tbody td:first-child,tfoot td:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-inline-start:0}:where(tbody td:last-child,tfoot td:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-inline-end:0}:where(figure):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.7777778em;margin-bottom:1.7777778em}:where(figure>*):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0;margin-bottom:0}:where(figcaption):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-size:0.8888889em;line-height:1.5;margin-top:1em}:where(>:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0}:where(>:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-bottom:0}}.dark:is(.dark\:prose-invert){--un-prose-body:var(--un-prose-invert-body);--un-prose-headings:var(--un-prose-invert-headings);--un-prose-lead:var(--un-prose-invert-lead);--un-prose-links:var(--un-prose-invert-links);--un-prose-bold:var(--un-prose-invert-bold);--un-prose-counters:var(--un-prose-invert-counters);--un-prose-bullets:var(--un-prose-invert-bullets);--un-prose-hr:var(--un-prose-invert-hr);--un-prose-quotes:var(--un-prose-invert-quotes);--un-prose-quote-borders:var(--un-prose-invert-quote-borders);--un-prose-captions:var(--un-prose-invert-captions);--un-prose-kbd:var(--un-prose-invert-kbd);--un-prose-kbd-shadows:var(--un-prose-invert-kbd-shadows);--un-prose-code:var(--un-prose-invert-code);--un-prose-pre-code:var(--un-prose-invert-pre-code);--un-prose-pre-bg:var(--un-prose-invert-pre-bg);--un-prose-th-borders:var(--un-prose-invert-th-borders);--un-prose-td-borders:var(--un-prose-invert-td-borders)}.-container{width:-100%}.container,[container=""]{width:100%}.btn:disabled{pointer-events:none;cursor:default;--un-bg-opacity:1;background-color:rgb(75 85 99 / var(--un-bg-opacity));opacity:0.5 !important}[btn=""]:disabled{pointer-events:none;cursor:default;--un-bg-opacity:1;background-color:rgb(75 85 99 / var(--un-bg-opacity));opacity:0.5 !important}.ds-btn:disabled{pointer-events:none;opacity:0.5}[ds-btn=""]:disabled{pointer-events:none;opacity:0.5}.ds-btn-content-category-filter:disabled{pointer-events:none;opacity:0.5}.ds-btn-ghost:disabled{pointer-events:none;opacity:0.5}[ds-btn-ghost=""]:disabled{pointer-events:none;opacity:0.5}.ds-btn-primary:disabled{pointer-events:none;opacity:0.5}[ds-btn-primary=""]:disabled{pointer-events:none;opacity:0.5}.ds-btn-secondary:disabled{pointer-events:none;opacity:0.5}[ds-btn-secondary=""]:disabled{pointer-events:none;opacity:0.5}.ds-btn-sm:disabled{pointer-events:none;opacity:0.5}[ds-btn-sm=""]:disabled{pointer-events:none;opacity:0.5}.ds-theme-toggle:disabled{pointer-events:none;opacity:0.5}[ds-theme-toggle=""]:disabled{pointer-events:none;opacity:0.5}.ds-card-text,[ds-card-text=""]{margin-left:auto;margin-right:auto;margin-bottom:var(--space-4);max-width:42rem;color:var(--color-neutral-600);color:var(--text-base);line-height:var(--leading-normal);line-height:var(--leading-relaxed)}.ds-container,[ds-container=""]{margin-left:auto;margin-right:auto;max-width:80rem;padding-left:var(--space-4);padding-right:var(--space-4)}.ds-container-lg{margin-left:auto;margin-right:auto;max-width:72rem;padding-left:var(--space-4);padding-right:var(--space-4)}.mx-auto,[mx-auto=""]{margin-left:auto;margin-right:auto}.ds-body,[ds-body=""]{margin-bottom:var(--space-4);color:var(--text-base);color:var(--color-neutral-600);line-height:var(--leading-normal);line-height:var(--leading-relaxed)}.ds-card-title,[ds-card-title=""]{margin-bottom:var(--space-3);margin-bottom:0.5rem;margin-bottom:var(--space-4,1rem);text-wrap:balance;color:var(--text-xl);color:var(--color-neutral-900);line-height:var(--leading-relaxed);font-family:var(--font-semibold)}.ds-heading-1{margin-bottom:var(--space-6);color:var(--text-4xl);color:var(--color-neutral-900);line-height:var(--leading-tight);font-family:var(--font-bold)}.ds-heading-2{margin-bottom:var(--space-4);color:var(--text-3xl);color:var(--color-neutral-900);line-height:var(--leading-tight);font-family:var(--font-semibold)}.ds-heading-3,[ds-heading-3=""]{margin-bottom:var(--space-4);color:var(--text-2xl);color:var(--color-neutral-900);line-height:var(--leading-tight);font-family:var(--font-semibold)}.ds-heading-4{margin-bottom:var(--space-3);color:var(--text-xl);color:var(--color-neutral-900);line-height:var(--leading-relaxed);font-family:var(--font-semibold)}.btn,[btn=""]{display:inline-block;cursor:pointer;border-radius:0.375rem;background-color:var(--c-primary);padding-left:1rem;padding-right:1rem;padding-top:0.25rem;padding-bottom:0.25rem;--un-text-opacity:1;color:rgb(255 255 255 / var(--un-text-opacity));letter-spacing:0.025em;opacity:0.9}.ds-input,[ds-input=""]{width:100%;border-width:1px;border-color:var(--color-neutral-300);border-radius:var(--radius-md);background-color:var(--color-neutral-50);padding-left:var(--space-3);padding-right:var(--space-3);padding-top:var(--space-2);padding-bottom:var(--space-2);color:var(--text-sm);transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms;transition-duration:200ms}.ds-select,[ds-select=""]{width:100%;cursor:pointer;border-width:1px;border-color:var(--color-neutral-300);border-radius:var(--radius-md);background-color:var(--color-neutral-50);padding-left:var(--space-3);padding-right:var(--space-3);padding-top:var(--space-2);padding-bottom:var(--space-2);color:var(--text-sm);transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms;transition-duration:200ms}.ds-textarea{width:100%;min-height:6rem;resize:vertical;border-width:1px;border-color:var(--color-neutral-300);border-radius:var(--radius-md);background-color:var(--color-neutral-50);padding-left:var(--space-3);padding-right:var(--space-3);padding-top:var(--space-2);padding-bottom:var(--space-2);color:var(--text-sm);transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms;transition-duration:200ms}.nav-link{display:flex;align-items:center;column-gap:var(--space-2);padding:var(--space-1);color:var(--text-sm);color:var(--color-neutral-600);font-family:var(--font-sans);text-decoration:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms;transition-duration:200ms}.ds-badge{display:inline-flex;align-items:center;border-radius:9999px;padding-left:0.625rem;padding-right:0.625rem;padding-top:0.125rem;padding-bottom:0.125rem;font-size:0.75rem;line-height:1rem;font-weight:500;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms}.ds-badge-ghost{display:inline-flex;align-items:center;border-radius:9999px;background-color:var(--color-neutral-100);padding-left:0.625rem;padding-right:0.625rem;padding-top:0.125rem;padding-bottom:0.125rem;font-size:0.75rem;line-height:1rem;color:var(--color-neutral-700);font-weight:500;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms}.ds-badge-outline{display:inline-flex;align-items:center;border-width:1px;border-color:var(--color-neutral-300);border-radius:9999px;background-color:transparent;padding-left:0.625rem;padding-right:0.625rem;padding-top:0.125rem;padding-bottom:0.125rem;font-size:0.75rem;line-height:1rem;color:var(--color-neutral-700);font-weight:500;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms}.ds-btn,[ds-btn=""]{display:inline-flex;cursor:pointer;align-items:center;justify-content:center;border-style:none;font-family:var(--font-medium);text-decoration:none;transition-property:all;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms;transition-duration:200ms}.ds-btn-content-category-filter,.ds-btn-ghost,[ds-btn-ghost=""]{display:inline-flex;cursor:pointer;align-items:center;justify-content:center;border-radius:var(--radius-md);border-style:none;background-color:transparent;padding-left:var(--space-4);padding-right:var(--space-4);padding-top:var(--space-2);padding-bottom:var(--space-2);color:var(--text-sm);color:var(--color-neutral-700);font-family:var(--font-medium);text-decoration:none;transition-property:all;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms;transition-duration:200ms}.ds-btn-primary,[ds-btn-primary=""]{display:inline-flex;cursor:pointer;align-items:center;justify-content:center;border-radius:var(--radius-md);border-style:none;background-color:var(--color-brand-primary);padding-left:var(--space-4);padding-right:var(--space-4);padding-top:var(--space-2);padding-bottom:var(--space-2);color:var(--text-sm);color:var(--color-neutral-50);font-family:var(--font-medium);text-decoration:none;transition-property:all;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms;transition-duration:200ms}.ds-btn-secondary,[ds-btn-secondary=""]{display:inline-flex;cursor:pointer;align-items:center;justify-content:center;border-radius:var(--radius-md);border-style:none;background-color:var(--color-neutral-200);padding-left:var(--space-4);padding-right:var(--space-4);padding-top:var(--space-2);padding-bottom:var(--space-2);color:var(--text-sm);color:var(--color-neutral-900);font-family:var(--font-medium);text-decoration:none;transition-property:all;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms;transition-duration:200ms}.ds-btn-sm,[ds-btn-sm=""]{display:inline-flex;cursor:pointer;align-items:center;justify-content:center;border-radius:var(--radius-md);border-style:none;padding-left:var(--space-3);padding-right:var(--space-3);padding-top:var(--space-1-5);padding-bottom:var(--space-1-5);color:var(--text-sm);font-family:var(--font-medium);text-decoration:none;transition-property:all;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms;transition-duration:200ms}.ds-card-featured,[ds-card-featured=""]{display:inline-flex;align-items:flex-end;border-radius:9999px;padding-left:0.625rem;padding-right:0.625rem;padding-top:0.125rem;padding-bottom:0.125rem;font-size:0.75rem;line-height:1rem;font-weight:500;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms}.ds-theme-toggle,[ds-theme-toggle=""]{display:inline-flex;cursor:pointer;align-items:center;justify-content:center;border-width:1px;border-color:var(--color-neutral-200);border-radius:var(--radius-lg);border-style:none;background-color:var(--color-neutral-50);padding:var(--space-2);color:var(--text-sm);color:var(--color-neutral-900);font-family:var(--font-medium);text-decoration:none;transition-property:all;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms;transition-duration:200ms}.hover\:ds-badge-accent:hover{display:inline-flex;align-items:center;border-radius:9999px;background-color:var(--color-success);padding-left:0.625rem;padding-right:0.625rem;padding-top:0.125rem;padding-bottom:0.125rem;font-size:0.75rem;line-height:1rem;--un-text-opacity:1;color:rgb(255 255 255 / var(--un-text-opacity));font-weight:500;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms}.ds-card,[ds-card=""]{border-width:1px;border-color:var(--color-neutral-200);border-radius:var(--radius-lg);background-color:var(--color-neutral-50);padding:var(--space-6);--un-shadow:var(--shadow-sm);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.dark .ds-badge-outline{border-color:var(--color-neutral-600);color:var(--color-neutral-300)}.dark .dark .dark\:ds-border,.dark .ds-border,.dark [ds-border=""]{border-color:var(--color-neutral-700)}.dark .dark\:ds-border,.ds-border,[ds-border=""]{border-color:var(--color-neutral-200)}.dark .ds-card,.dark .ds-input,.dark .ds-select,.dark .ds-textarea,.dark [ds-card=""],.dark [ds-input=""],.dark [ds-select=""]{border-color:var(--color-neutral-600);background-color:var(--color-neutral-800)}.dark .ds-theme-toggle,.dark [ds-theme-toggle=""]{border-color:var(--color-neutral-700);background-color:var(--color-neutral-900);color:var(--color-neutral-50)}.dark .hover\:ds-border:hover{border-color:var(--color-neutral-700)}.hover\:ds-border:hover{border-color:var(--color-neutral-200)}.ds-input:focus{border-color:var(--color-brand-primary);outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-color:var(--color-brand-primary)}[ds-input=""]:focus{border-color:var(--color-brand-primary);outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-color:var(--color-brand-primary)}.ds-select:focus{border-color:var(--color-brand-primary);outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-color:var(--color-brand-primary)}[ds-select=""]:focus{border-color:var(--color-brand-primary);outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-color:var(--color-brand-primary)}.ds-textarea:focus{border-color:var(--color-brand-primary);outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-color:var(--color-brand-primary)}.ds-rounded{border-radius:var(--radius-base)}.ds-rounded-lg,[ds-rounded-lg=""]{border-radius:var(--radius-lg)}.ds-rounded-md,[ds-rounded-md=""]{border-radius:var(--radius-md)}.ds-rounded-xl{border-radius:var(--radius-xl)}.round,.rounded-full{border-radius:9999px}.rounded,[rounded=""]{border-radius:0.375rem}.rounded-2xl{border-radius:1rem}.rounded-lg,[rounded-lg=""]{border-radius:0.5rem}.rounded-xl{border-radius:0.75rem}.dark .ds-badge-ghost{background-color:var(--color-neutral-800);color:var(--color-neutral-300)}.dark .ds-bg,.dark [ds-bg=""]{background-color:var(--color-neutral-950)}.ds-bg,[ds-bg=""]{background-color:var(--color-neutral-50)}.dark .ds-bg-page,.dark [ds-bg-page=""]{background-color:var(--color-neutral-900)}.ds-bg-page,.ds-bg-secondary,[ds-bg-page=""]{background-color:var(--color-neutral-100)}.dark .ds-bg-secondary{background-color:var(--color-neutral-800)}.hover\:ds-badge-accent:hover:hover{background-color:var(--color-success)}.dark .ds-badge-ghost:hover{background-color:var(--color-neutral-700)}.ds-badge-ghost:hover{background-color:var(--color-neutral-200)}.dark .ds-badge-outline:hover{background-color:var(--color-neutral-800)}.ds-badge-outline:hover{background-color:var(--color-neutral-100)}.hover\:ds-badge-primary-focus:hover{background-color:var(--color-brand-primary);--un-text-opacity:1;color:rgb(255 255 255 / var(--un-text-opacity))}.dark .hover\:ds-bg:hover{background-color:var(--color-neutral-950)}.dark [hover\:ds-bg=""]:hover{background-color:var(--color-neutral-950)}.hover\:ds-bg:hover{background-color:var(--color-neutral-50)}[hover\:ds-bg=""]:hover{background-color:var(--color-neutral-50)}.dark .ds-btn-content-category-filter:hover{background-color:var(--color-neutral-800)}.ds-btn-content-category-filter:hover{background-color:var(--color-neutral-100)}.dark .ds-btn-ghost:hover{background-color:var(--color-neutral-800)}.dark [ds-btn-ghost=""]:hover{background-color:var(--color-neutral-800)}.ds-btn-ghost:hover{background-color:var(--color-neutral-100)}[ds-btn-ghost=""]:hover{background-color:var(--color-neutral-100)}.ds-btn-primary:hover{background-color:var(--color-brand-primary)}[ds-btn-primary=""]:hover{background-color:var(--color-brand-primary)}.ds-btn-secondary:hover{background-color:var(--color-neutral-300)}[ds-btn-secondary=""]:hover{background-color:var(--color-neutral-300)}.dark .ds-theme-toggle:hover{background-color:var(--color-neutral-800)}.dark [ds-theme-toggle=""]:hover{background-color:var(--color-neutral-800)}.ds-theme-toggle:hover{background-color:var(--color-neutral-100)}[ds-theme-toggle=""]:hover{background-color:var(--color-neutral-100)}.ds-badge-sm{padding-left:0.5rem;padding-right:0.5rem;padding-top:0.125rem;padding-bottom:0.125rem;font-size:0.75rem;line-height:1rem}.text-center,[text-center=""]{text-align:center}.text-left,[text-left=""]{text-align:left}.text-right{text-align:right}.dark .ds-body,.dark .ds-card-text,.dark .ds-text-secondary,.dark .nav-link,.dark [ds-body=""],.dark [ds-card-text=""],.dark [ds-text-secondary=""]{color:var(--color-neutral-400)}.dark .ds-btn-content-category-filter,.dark .ds-btn-ghost,.dark [ds-btn-ghost=""]{color:var(--color-neutral-300)}.dark .ds-caption,.dark .ds-text-muted,.dark [ds-caption=""],.dark [ds-text-muted=""],.ds-text-muted,[ds-text-muted=""],.group:hover .dark .group-hover\:ds-text-muted,.group:hover .group-hover\:ds-text-muted{color:var(--color-neutral-500)}.ds-caption,[ds-caption=""]{color:var(--text-sm);color:var(--color-neutral-500);line-height:var(--leading-normal)}.dark .dark .dark\:ds-text,.dark .ds-card-title,.dark .ds-heading-1,.dark .ds-heading-2,.dark .ds-heading-3,.dark .ds-heading-4,.dark .ds-text,.dark [ds-card-title=""],.dark [ds-heading-3=""],.dark [ds-text=""]{color:var(--color-neutral-50)}.dark .dark\:ds-text,.ds-text,[ds-text=""]{color:var(--color-neutral-900)}.ds-text-secondary,[ds-text-secondary=""]{color:var(--color-neutral-600)}.dark .hover\:ds-text:hover{color:var(--color-neutral-50)}.hover\:ds-text:hover{color:var(--color-neutral-900)}.dark .hover\:ds-text-secondary:hover{color:var(--color-neutral-400)}.hover\:ds-text-secondary:hover{color:var(--color-neutral-600)}.nav-link:hover{color:var(--color-brand-primary)}.dark .placeholder-ds-text-muted::placeholder{color:var(--color-neutral-500)}.dark [placeholder-ds-text-muted=""]::placeholder{color:var(--color-neutral-500)}.placeholder-ds-text-muted::placeholder{color:var(--color-neutral-500)}[placeholder-ds-text-muted=""]::placeholder{color:var(--color-neutral-500)}.btn:hover{opacity:1}[btn=""]:hover{opacity:1}.ds-shadow-lg,[ds-shadow-lg=""]{--un-shadow:var(--shadow-lg);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.ds-shadow-md{--un-shadow:var(--shadow-md);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.ds-shadow-sm,[ds-shadow-sm=""]{--un-shadow:var(--shadow-sm);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.shadow,[shadow=""]{--un-shadow:var(--un-shadow-inset) 0 4px 6px -1px var(--un-shadow-color,rgb(0 0 0 / 0.1)),var(--un-shadow-inset) 0 2px 4px -2px var(--un-shadow-color,rgb(0 0 0 / 0.1));box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.shadow-lg,[shadow-lg=""]{--un-shadow:var(--un-shadow-inset) 0 10px 15px -3px var(--un-shadow-color,rgb(0 0 0 / 0.1)),var(--un-shadow-inset) 0 4px 6px -4px var(--un-shadow-color,rgb(0 0 0 / 0.1));box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.shadow-none{--un-shadow:0 0 var(--un-shadow-color,rgb(0 0 0 / 0));box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.shadow-sm{--un-shadow:var(--un-shadow-inset) 0 1px 2px 0 var(--un-shadow-color,rgb(0 0 0 / 0.05));box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.shadow-xl{--un-shadow:var(--un-shadow-inset) 0 20px 25px -5px var(--un-shadow-color,rgb(0 0 0 / 0.1)),var(--un-shadow-inset) 0 8px 10px -6px var(--un-shadow-color,rgb(0 0 0 / 0.1));box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.hover\:ds-shadow-md:hover{--un-shadow:var(--shadow-md);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.hover\:shadow-none:hover{--un-shadow:0 0 var(--un-shadow-color,rgb(0 0 0 / 0));box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.hover\:shadow-xl:hover{--un-shadow:var(--un-shadow-inset) 0 20px 25px -5px var(--un-shadow-color,rgb(0 0 0 / 0.1)),var(--un-shadow-inset) 0 8px 10px -6px var(--un-shadow-color,rgb(0 0 0 / 0.1));box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.disabled\:shadow-none:disabled{--un-shadow:0 0 var(--un-shadow-color,rgb(0 0 0 / 0));box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.ds-btn:focus{outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-offset-width:2px}[ds-btn=""]:focus{outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-offset-width:2px}.ds-btn-content-category-filter:focus{outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-offset-width:2px}.ds-btn-ghost:focus{outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-offset-width:2px}[ds-btn-ghost=""]:focus{outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-offset-width:2px}.ds-btn-primary:focus{outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-offset-width:2px;--un-ring-color:var(--color-brand-primary)}[ds-btn-primary=""]:focus{outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-offset-width:2px;--un-ring-color:var(--color-brand-primary)}.ds-btn-secondary:focus{outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-offset-width:2px;--un-ring-color:var(--color-neutral-200)}[ds-btn-secondary=""]:focus{outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-offset-width:2px;--un-ring-color:var(--color-neutral-200)}.ds-btn-sm:focus{outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-offset-width:2px}[ds-btn-sm=""]:focus{outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-offset-width:2px}.ds-theme-toggle:focus{outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-offset-width:2px;--un-ring-color:var(--color-brand-primary)}[ds-theme-toggle=""]:focus{outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-offset-width:2px;--un-ring-color:var(--color-brand-primary)}@media (min-width:640px){.-container{max-width:-640px}.container,[container=""]{max-width:640px}.sm\:ds-container,[sm\:ds-container=""]{margin-left:auto;margin-right:auto;max-width:80rem;padding-left:var(--space-4);padding-right:var(--space-4)}.ds-card-title,[ds-card-title=""]{margin-bottom:var(--space-4);color:var(--text-2xl);color:var(--color-neutral-900);line-height:var(--leading-tight);font-family:var(--font-semibold)}.dark .ds-card-title,.dark [ds-card-title=""]{color:var(--color-neutral-50)}}@media (min-width:768px){.-container{max-width:-768px}.container,[container=""]{max-width:768px}}@media (min-width:1024px){.-container{max-width:-1024px}.container,[container=""]{max-width:1024px}.lg\:mx-auto,[lg\:mx-auto=""]{margin-left:auto;margin-right:auto}.dark .lg\:ds-border{border-color:var(--color-neutral-700)}.lg\:ds-border{border-color:var(--color-neutral-200)}}@media (min-width:1280px){.-container{max-width:-1280px}.container,[container=""]{max-width:1280px}}@media (min-width:1536px){.-container{max-width:-1536px}.container,[container=""]{max-width:1536px}}:root,[data-theme]{background-color:hsl(var(--b1) / var(--un-bg-opacity,1));color:hsl(var(--bc) / var(--un-text-opacity,1))}html{-webkit-tap-highlight-color:transparent}.alert{display:grid;width:100%;grid-auto-flow:row;align-content:flex-start;align-items:center;justify-items:center;gap:1rem;text-align:center;border-width:1px;--un-border-opacity:1;border-color:hsl(var(--b2) / var(--un-border-opacity));padding:1rem;--un-text-opacity:1;color:hsl(var(--bc) / var(--un-text-opacity));border-radius:var(--rounded-box,1rem);--alert-bg:hsl(var(--b2));--alert-bg-mix:hsl(var(--b1));background-color:var(--alert-bg)}.alert{grid-auto-flow:column;grid-template-columns:auto minmax(auto,1fr);justify-items:start;text-align:left}.avatar{position:relative;display:inline-flex}.avatar>div{display:block;aspect-ratio:1 / 1;overflow:hidden}.avatar img{height:100%;width:100%;-o-object-fit:cover;object-fit:cover}.avatar.placeholder>div{display:flex;align-items:center;justify-content:center}.avatar.online:before{content:"";position:absolute;z-index:10;display:block;border-radius:9999px;--un-bg-opacity:1;background-color:hsl(var(--su) / var(--un-bg-opacity));outline-style:solid;outline-width:2px;outline-color:hsl(var(--b1) / 1);width:15%;height:15%;top:7%;right:7%}.avatar.offline:before{content:"";position:absolute;z-index:10;display:block;border-radius:9999px;--un-bg-opacity:1;background-color:hsl(var(--b3) / var(--un-bg-opacity));outline-style:solid;outline-width:2px;outline-color:hsl(var(--b1) / 1);width:15%;height:15%;top:7%;right:7%}.badge{display:inline-flex;align-items:center;justify-content:center;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-timing-function:cubic-bezier(0,0,0.2,1);transition-duration:200ms;height:1.25rem;font-size:0.875rem;line-height:1.25rem;width:-moz-fit-content;width:fit-content;padding-left:0.563rem;padding-right:0.563rem;border-width:1px;--un-border-opacity:1;border-color:hsl(var(--b2) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--b1) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--bc) / var(--un-text-opacity));border-radius:var(--rounded-badge,1.9rem)}.breadcrumbs{max-width:100%;overflow-x:auto;padding-top:0.5rem;padding-bottom:0.5rem}.breadcrumbs>ul,.breadcrumbs>ol{display:flex;align-items:center;white-space:nowrap;min-height:-moz-min-content;min-height:min-content}.breadcrumbs>ul>li,.breadcrumbs>ol>li{display:flex;align-items:center}.breadcrumbs>ul>li>a,.breadcrumbs>ol>li>a{display:flex;cursor:pointer;align-items:center}.breadcrumbs>ul>li>a:hover,.breadcrumbs>ol>li>a:hover{text-decoration-line:underline}.breadcrumbs>ul>li>a:focus,.breadcrumbs>ol>li>a:focus{outline:2px solid transparent;outline-offset:2px}.breadcrumbs>ul>li>a:focus-visible,.breadcrumbs>ol>li>a:focus-visible{outline:2px solid currentColor;outline-offset:2px}.breadcrumbs>ul>li+*:before,.breadcrumbs>ol>li+*:before{content:"";margin-left:0.5rem;margin-right:0.75rem;display:block;height:0.375rem;width:0.375rem;--un-rotate:45deg;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y));opacity:0.4;border-top:1px solid;border-right:1px solid;background-color:transparent}[dir="rtl"] .breadcrumbs>ul>li+*:before,[dir="rtl"] .breadcrumbs>ol>li+*:before{--un-rotate:-45deg}.link-hover:hover{text-decoration-line:underline}.link-primary:hover{--un-text-opacity:1;color:hsl(var(--pf) / var(--un-text-opacity))}.link-secondary:hover{--un-text-opacity:1;color:hsl(var(--sf) / var(--un-text-opacity))}.link-accent:hover{--un-text-opacity:1;color:hsl(var(--af) / var(--un-text-opacity))}.link-neutral:hover{--un-text-opacity:1;color:hsl(var(--nf) / var(--un-text-opacity))}.link-success:hover{--un-text-opacity:1;color:hsl(var(--su) / var(--un-text-opacity))}.link-info:hover{--un-text-opacity:1;color:hsl(var(--in) / var(--un-text-opacity))}.link-warning:hover{--un-text-opacity:1;color:hsl(var(--wa) / var(--un-text-opacity))}.link-error:hover{--un-text-opacity:1;color:hsl(var(--er) / var(--un-text-opacity))}.link{cursor:pointer;text-decoration-line:underline}.link-hover{text-decoration-line:none}.link-primary{--un-text-opacity:1;color:hsl(var(--p) / var(--un-text-opacity))}.link-secondary{--un-text-opacity:1;color:hsl(var(--s) / var(--un-text-opacity))}.link-accent{--un-text-opacity:1;color:hsl(var(--a) / var(--un-text-opacity))}.link-neutral{--un-text-opacity:1;color:hsl(var(--n) / var(--un-text-opacity))}.link-success{--un-text-opacity:1;color:hsl(var(--su) / var(--un-text-opacity))}.link-info{--un-text-opacity:1;color:hsl(var(--in) / var(--un-text-opacity))}.link-warning{--un-text-opacity:1;color:hsl(var(--wa) / var(--un-text-opacity))}.link-error{--un-text-opacity:1;color:hsl(var(--er) / var(--un-text-opacity))}.link:focus{outline:2px solid transparent;outline-offset:2px}.link:focus-visible{outline:2px solid currentColor;outline-offset:2px}.label a:hover{--un-text-opacity:1;color:hsl(var(--bc) / var(--un-text-opacity))}.label{display:flex;-webkit-user-select:none;-moz-user-select:none;user-select:none;align-items:center;justify-content:space-between;padding-left:0.25rem;padding-right:0.25rem;padding-top:0.5rem;padding-bottom:0.5rem}.menu li>*:not(ul):not(.menu-title):not(details):active,.menu li>*:not(ul):not(.menu-title):not(details).active,.menu li>details>summary:active{--un-bg-opacity:1;background-color:hsl(var(--n) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--nc) / var(--un-text-opacity))}:where(.menu li:not(.menu-title):not(.disabled)>*:not(ul):not(details):not(.menu-title)):not(.active):hover,:where(.menu li:not(.menu-title):not(.disabled)>details>summary:not(.menu-title)):not(.active):hover{cursor:pointer;background-color:hsl(var(--bc) / 0.1);--un-text-opacity:1;color:hsl(var(--bc) / var(--un-text-opacity));outline:2px solid transparent;outline-offset:2px}.menu{display:flex;flex-direction:column;flex-wrap:wrap;font-size:0.875rem;line-height:1.25rem;padding:0.5rem}.menu:where(li ul){position:relative;white-space:nowrap;margin-left:1rem;padding-left:0.5rem}.menu:where(li:not(.menu-title)>*:not(ul):not(details):not(.menu-title)),.menu:where(li:not(.menu-title)>details>summary:not(.menu-title)){display:grid;grid-auto-flow:column;align-content:flex-start;align-items:center;gap:0.5rem;grid-auto-columns:minmax(auto,max-content) auto max-content;-webkit-user-select:none;-moz-user-select:none;user-select:none}.menu li.disabled{cursor:not-allowed;-webkit-user-select:none;-moz-user-select:none;user-select:none;color:hsl(var(--bc) / 0.3)}.menu:where(li>.menu-dropdown:not(.menu-dropdown-show)){display:none}:where(.menu li){position:relative;display:flex;flex-shrink:0;flex-direction:column;flex-wrap:wrap;align-items:stretch}:where(.menu li) .badge{justify-self:end}:where(.menu li:empty){background-color:hsl(var(--bc) / 0.1);margin:0.5rem 1rem;height:1px}.menu:where(li ul):before{position:absolute;bottom:0.75rem;left:0px;top:0.75rem;width:1px;background-color:hsl(var(--bc) / 0.1);content:""}.menu:where(li:not(.menu-title)>*:not(ul):not(details):not(.menu-title)),.menu:where(li:not(.menu-title)>details>summary:not(.menu-title)){padding-left:1rem;padding-right:1rem;padding-top:0.5rem;padding-bottom:0.5rem;text-align:left;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-timing-function:cubic-bezier(0,0,0.2,1);transition-duration:200ms;border-radius:var(--rounded-btn,0.5rem);text-wrap:balance}:where(.menu li:not(.menu-title):not(.disabled)>*:not(ul):not(details):not(.menu-title)):not(summary):not(.active).focus,:where(.menu li:not(.menu-title):not(.disabled)>*:not(ul):not(details):not(.menu-title)):not(summary):not(.active):focus,:where(.menu li:not(.menu-title):not(.disabled)>*:not(ul):not(details):not(.menu-title)):is(summary):not(.active):focus-visible,:where(.menu li:not(.menu-title):not(.disabled)>details>summary:not(.menu-title)):not(summary):not(.active).focus,:where(.menu li:not(.menu-title):not(.disabled)>details>summary:not(.menu-title)):not(summary):not(.active):focus,:where(.menu li:not(.menu-title):not(.disabled)>details>summary:not(.menu-title)):is(summary):not(.active):focus-visible{cursor:pointer;background-color:hsl(var(--bc) / 0.1);--un-text-opacity:1;color:hsl(var(--bc) / var(--un-text-opacity));outline:2px solid transparent;outline-offset:2px}.menu li>*:not(ul):not(.menu-title):not(details):active,.menu li>*:not(ul):not(.menu-title):not(details).active,.menu li>details>summary:active{--un-bg-opacity:1;background-color:hsl(var(--n) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--nc) / var(--un-text-opacity))}.menu:where(li>details>summary)::-webkit-details-marker{display:none}.menu:where(li>details>summary):after,.menu:where(li>.menu-dropdown-toggle):after{justify-self:end;display:block;margin-top:-0.5rem;height:0.5rem;width:0.5rem;transform:rotate(45deg);transition-property:transform,margin-top;transition-duration:0.3s;transition-timing-function:cubic-bezier(0.4,0,0.2,1);content:"";transform-origin:75% 75%;box-shadow:2px 2px;pointer-events:none}.menu:where(li>details[open]>summary):after,.menu:where(li>.menu-dropdown-toggle.menu-dropdown-show):after{transform:rotate(225deg);margin-top:0}.tab:hover{--un-text-opacity:1}.tab[disabled],.tab[disabled]:hover{cursor:not-allowed;color:hsl(var(--bc) / var(--un-text-opacity));--un-text-opacity:0.2}.tab{position:relative;display:inline-flex;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;flex-wrap:wrap;align-items:center;justify-content:center;text-align:center;height:2rem;font-size:0.875rem;line-height:1.25rem;line-height:2;--tab-padding:1rem;--un-text-opacity:0.5;--tab-color:hsl(var(--bc) / var(--un-text-opacity,1));--tab-bg:hsl(var(--b1) / var(--un-bg-opacity,1));--tab-border-color:hsl(var(--b3) / var(--un-bg-opacity,1));color:var(--tab-color);padding-left:var(--tab-padding,1rem);padding-right:var(--tab-padding,1rem)}.tab.tab-active:not(.tab-disabled):not([disabled]){border-color:hsl(var(--bc) / var(--un-border-opacity));--un-border-opacity:1;--un-text-opacity:1}.tab:focus{outline:2px solid transparent;outline-offset:2px}.tab:focus-visible{outline:2px solid currentColor;outline-offset:-3px}.tab:focus-visible.tab-lifted{border-bottom-right-radius:var(--tab-radius,0.5rem);border-bottom-left-radius:var(--tab-radius,0.5rem)}.btn-circle{height:3rem;width:3rem;border-radius:9999px;padding:0px}.btn-circle:where(.btn-xs){height:1.5rem;width:1.5rem;border-radius:9999px;padding:0px}.btn-circle:where(.btn-sm){height:2rem;width:2rem;border-radius:9999px;padding:0px}.btn-circle:where(.btn-md){height:3rem;width:3rem;border-radius:9999px;padding:0px}.btn-circle:where(.btn-lg){height:4rem;width:4rem;border-radius:9999px;padding:0px}.card{position:relative;display:flex;flex-direction:column;border-radius:var(--rounded-box,1rem)}.card:focus{outline:2px solid transparent;outline-offset:2px}.card figure{display:flex;align-items:center;justify-content:center}.card.image-full{display:grid}.card.image-full:before{position:relative;content:"";z-index:10;--un-bg-opacity:1;background-color:hsl(var(--n) / var(--un-bg-opacity));opacity:0.75;border-radius:var(--rounded-box,1rem)}.card.image-full:before,.card.image-full>*{grid-column-start:1;grid-row-start:1}.card.image-full>figure img{height:100%;-o-object-fit:cover;object-fit:cover}.card.image-full>.card-body{position:relative;z-index:20;--un-text-opacity:1;color:hsl(var(--nc) / var(--un-text-opacity))}.card:where(figure:first-child){overflow:hidden;border-start-start-radius:inherit;border-start-end-radius:inherit;border-end-start-radius:unset;border-end-end-radius:unset}.card:where(figure:last-child){overflow:hidden;border-start-start-radius:unset;border-start-end-radius:unset;border-end-start-radius:inherit;border-end-end-radius:inherit}.card:focus-visible{outline:2px solid currentColor;outline-offset:2px}.card.bordered{border-width:1px;--un-border-opacity:1;border-color:hsl(var(--b2) / var(--un-border-opacity))}.card.compact .card-body{padding:1rem;font-size:0.875rem;line-height:1.25rem}.card.image-full:where(figure){overflow:hidden;border-radius:inherit}.chat{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));-moz-column-gap:0.75rem;column-gap:0.75rem;padding-top:0.25rem;padding-bottom:0.25rem}.checkbox{flex-shrink:0;--chkbg:var(--bc);--chkfg:var(--b1);height:1.5rem;width:1.5rem;cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-width:1px;border-color:hsl(var(--bc) / var(--un-border-opacity));--un-border-opacity:0.2;border-radius:var(--rounded-btn,0.5rem)}.checkbox:focus-visible{outline-style:solid;outline-width:2px;outline-offset:2px;outline-color:hsl(var(--bc) / 1)}.checkbox:checked,.checkbox[checked="true"],.checkbox[aria-checked="true"]{--un-bg-opacity:1;background-color:hsl(var(--bc) / var(--un-bg-opacity));background-repeat:no-repeat;animation:checkmark var(--animation-input,0.2s) ease-out;background-image:linear-gradient(-45deg,transparent 65%,hsl(var(--chkbg)) 65.99%),linear-gradient(45deg,transparent 75%,hsl(var(--chkbg)) 75.99%),linear-gradient(-45deg,hsl(var(--chkbg)) 40%,transparent 40.99%),linear-gradient( 45deg,hsl(var(--chkbg)) 30%,hsl(var(--chkfg)) 30.99%,hsl(var(--chkfg)) 40%,transparent 40.99% ),linear-gradient(-45deg,hsl(var(--chkfg)) 50%,hsl(var(--chkbg)) 50.99%)}.checkbox:indeterminate{--un-bg-opacity:1;background-color:hsl(var(--bc) / var(--un-bg-opacity));background-repeat:no-repeat;animation:checkmark var(--animation-input,0.2s) ease-out;background-image:linear-gradient(90deg,transparent 80%,hsl(var(--chkbg)) 80%),linear-gradient(-90deg,transparent 80%,hsl(var(--chkbg)) 80%),linear-gradient( 0deg,hsl(var(--chkbg)) 43%,hsl(var(--chkfg)) 43%,hsl(var(--chkfg)) 57%,hsl(var(--chkbg)) 57% )}.checkbox:disabled{cursor:not-allowed;border-color:transparent;--un-bg-opacity:1;background-color:hsl(var(--bc) / var(--un-bg-opacity));opacity:0.2}[dir="rtl"] .checkbox:checked,[dir="rtl"] .checkbox[checked="true"],[dir="rtl"] .checkbox[aria-checked="true"]{background-image:linear-gradient(45deg,transparent 65%,hsl(var(--chkbg)) 65.99%),linear-gradient(-45deg,transparent 75%,hsl(var(--chkbg)) 75.99%),linear-gradient(45deg,hsl(var(--chkbg)) 40%,transparent 40.99%),linear-gradient( -45deg,hsl(var(--chkbg)) 30%,hsl(var(--chkfg)) 30.99%,hsl(var(--chkfg)) 40%,transparent 40.99% ),linear-gradient(45deg,hsl(var(--chkfg)) 50%,hsl(var(--chkbg)) 50.99%)}.collapse:not(td):not(tr):not(colgroup){visibility:visible}.collapse{position:relative;display:grid;overflow:hidden;grid-template-rows:auto 0fr;transition:grid-template-rows 0.2s;width:100%;border-radius:var(--rounded-box,1rem)}.collapse>input[type="checkbox"],.collapse>input[type="radio"]{-webkit-appearance:none;-moz-appearance:none;appearance:none;opacity:0}.collapse[open],.collapse-open,.collapse:focus:not(.collapse-close){grid-template-rows:auto 1fr}.collapse:not(.collapse-close):has(>input[type="checkbox"]:checked),.collapse:not(.collapse-close):has(>input[type="radio"]:checked){grid-template-rows:auto 1fr}.collapse[open]>.collapse-content,.collapse-open>.collapse-content,.collapse:focus:not(.collapse-close)>.collapse-content,.collapse:not(.collapse-close)>input[type="checkbox"]:checked~.collapse-content,.collapse:not(.collapse-close)>input[type="radio"]:checked~.collapse-content{visibility:visible;min-height:-moz-fit-content;min-height:fit-content}.collapse:focus-visible{outline-style:solid;outline-width:2px;outline-offset:2px;outline-color:hsl(var(--bc) / 1)}.collapse:has(.collapse-title:focus-visible),.collapse:has(>input[type="checkbox"]:focus-visible),.collapse:has(>input[type="radio"]:focus-visible){outline-style:solid;outline-width:2px;outline-offset:2px;outline-color:hsl(var(--bc) / 1)}.collapse:not(.collapse-open):not(.collapse-close)>input[type="checkbox"],.collapse:not(.collapse-open):not(.collapse-close)>input[type="radio"]:not(:checked),.collapse:not(.collapse-open):not(.collapse-close)>.collapse-title{cursor:pointer}.collapse:focus:not(.collapse-open):not(.collapse-close):not(.collapse[open])>.collapse-title{cursor:unset}:where(.collapse>input[type="checkbox"]),:where(.collapse>input[type="radio"]){z-index:1}.collapse[open]>:where(.collapse-content),.collapse-open>:where(.collapse-content),.collapse:focus:not(.collapse-close)>:where(.collapse-content),.collapse:not(.collapse-close)>:where(input[type="checkbox"]:checked~.collapse-content),.collapse:not(.collapse-close)>:where(input[type="radio"]:checked~.collapse-content){padding-bottom:1rem;transition:padding 0.2s ease-out,background-color 0.2s ease-out}.collapse[open].collapse-arrow>.collapse-title:after,.collapse-open.collapse-arrow>.collapse-title:after,.collapse-arrow:focus:not(.collapse-close)>.collapse-title:after,.collapse-arrow:not(.collapse-close)>input[type="checkbox"]:checked~.collapse-title:after,.collapse-arrow:not(.collapse-close)>input[type="radio"]:checked~.collapse-title:after{--un-translate-y:-50%;--un-rotate:225deg;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}[dir="rtl"] .collapse[open].collapse-arrow>.collapse-title:after,[dir="rtl"] .collapse-open.collapse-arrow>.collapse-title:after,[dir="rtl"] .collapse-arrow:focus:not(.collapse-close) .collapse-title:after,[dir="rtl"] .collapse-arrow:not(.collapse-close) input[type="checkbox"]:checked~.collapse-title:after{--un-rotate:135deg}.collapse[open].collapse-plus>.collapse-title:after,.collapse-open.collapse-plus>.collapse-title:after,.collapse-plus:focus:not(.collapse-close)>.collapse-title:after,.collapse-plus:not(.collapse-close)>input[type="checkbox"]:checked~.collapse-title:after,.collapse-plus:not(.collapse-close)>input[type="radio"]:checked~.collapse-title:after{content:"−"}:root .countdown{line-height:1em}.countdown{display:inline-flex}.countdown>*{height:1em;display:inline-block;overflow-y:hidden}.countdown>*:before{position:relative;content:"00\A 01\A 02\A 03\A 04\A 05\A 06\A 07\A 08\A 09\A 10\A 11\A 12\A 13\A 14\A 15\A 16\A 17\A 18\A 19\A 20\A 21\A 22\A 23\A 24\A 25\A 26\A 27\A 28\A 29\A 30\A 31\A 32\A 33\A 34\A 35\A 36\A 37\A 38\A 39\A 40\A 41\A 42\A 43\A 44\A 45\A 46\A 47\A 48\A 49\A 50\A 51\A 52\A 53\A 54\A 55\A 56\A 57\A 58\A 59\A 60\A 61\A 62\A 63\A 64\A 65\A 66\A 67\A 68\A 69\A 70\A 71\A 72\A 73\A 74\A 75\A 76\A 77\A 78\A 79\A 80\A 81\A 82\A 83\A 84\A 85\A 86\A 87\A 88\A 89\A 90\A 91\A 92\A 93\A 94\A 95\A 96\A 97\A 98\A 99\A";white-space:pre;top:calc(var(--value) * -1em);text-align:center;transition:all 1s cubic-bezier(1,0,0,1)}.divider{display:flex;flex-direction:row;align-items:center;align-self:stretch;margin-top:1rem;margin-bottom:1rem;height:1rem;white-space:nowrap}.divider:before,.divider:after{content:"";flex-grow:1;height:0.125rem;width:100%}.divider:before{background-color:hsl(var(--bc) / var(--un-bg-opacity));--un-bg-opacity:0.1}.divider:after{background-color:hsl(var(--bc) / var(--un-bg-opacity));--un-bg-opacity:0.1}.divider:not(:empty){gap:1rem}.dropdown{position:relative;display:inline-block}.dropdown>*:not(summary):focus{outline:2px solid transparent;outline-offset:2px}.dropdown .dropdown-content{position:absolute}.dropdown:is(:not(details)) .dropdown-content{visibility:hidden;opacity:0;transform-origin:top;--un-scale-x:.95;--un-scale-y:.95;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y));transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-timing-function:cubic-bezier(0,0,0.2,1);transition-duration:200ms}.dropdown.dropdown-open .dropdown-content,.dropdown:not(.dropdown-hover):focus .dropdown-content,.dropdown:focus-within .dropdown-content{visibility:visible;opacity:1}.dropdown.dropdown-hover:hover .dropdown-content{visibility:visible;opacity:1}.dropdown.dropdown-hover:hover .dropdown-content{--un-scale-x:1;--un-scale-y:1;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.dropdown:is(details) summary::-webkit-details-marker{display:none}.dropdown.dropdown-open .dropdown-content,.dropdown:focus .dropdown-content,.dropdown:focus-within .dropdown-content{--un-scale-x:1;--un-scale-y:1;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.dropdown-end .dropdown-content{right:0px}.dropdown-end.dropdown-right .dropdown-content{bottom:0px;top:auto}.dropdown-end.dropdown-left .dropdown-content{bottom:0px;top:auto}.btn-primary:hover{--un-border-opacity:1;border-color:hsl(var(--pf) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--pf) / var(--un-bg-opacity))}.btn-primary{--un-border-opacity:1;border-color:hsl(var(--p) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--p) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--pc) / var(--un-text-opacity));outline-color:hsl(var(--p) / 1)}.btn-primary.btn-active{--un-border-opacity:1;border-color:hsl(var(--pf) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--pf) / var(--un-bg-opacity))}.btn-secondary:hover{--un-border-opacity:1;border-color:hsl(var(--sf) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--sf) / var(--un-bg-opacity))}.btn-secondary{--un-border-opacity:1;border-color:hsl(var(--s) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--s) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--sc) / var(--un-text-opacity));outline-color:hsl(var(--s) / 1)}.btn-secondary.btn-active{--un-border-opacity:1;border-color:hsl(var(--sf) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--sf) / var(--un-bg-opacity))}.btn-neutral:hover{--un-border-opacity:1;border-color:hsl(var(--nf) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--nf) / var(--un-bg-opacity))}.btn-neutral{--un-border-opacity:1;border-color:hsl(var(--n) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--n) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--nc) / var(--un-text-opacity));outline-color:hsl(var(--n) / 1)}.btn-neutral.btn-active{--un-border-opacity:1;border-color:hsl(var(--nf) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--nf) / var(--un-bg-opacity))}.btn-error:hover{--un-border-opacity:1;border-color:hsl(var(--er) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--er) / var(--un-bg-opacity))}.btn-error{--un-border-opacity:1;border-color:hsl(var(--er) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--er) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--erc) / var(--un-text-opacity));outline-color:hsl(var(--er) / 1)}.btn-error.btn-active{--un-border-opacity:1;border-color:hsl(var(--er) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--er) / var(--un-bg-opacity))}.btn-ghost:hover{--un-border-opacity:0;background-color:hsl(var(--bc) / var(--un-bg-opacity));--un-bg-opacity:0.2}.btn-ghost{border-width:1px;border-color:transparent;background-color:transparent;color:currentColor;--un-shadow:0 0 #0000;--un-shadow-colored:0 0 #0000;box-shadow:var(--un-ring-offset-shadow,0 0 #0000),var(--un-ring-shadow,0 0 #0000),var(--un-shadow);outline-color:currentColor}.btn-ghost.btn-active{--un-border-opacity:0;background-color:hsl(var(--bc) / var(--un-bg-opacity));--un-bg-opacity:0.2}.btn-outline:hover{--un-border-opacity:1;border-color:hsl(var(--bc) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--bc) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--b1) / var(--un-text-opacity))}.btn-outline.btn-primary:hover{--un-border-opacity:1;border-color:hsl(var(--pf) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--pf) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--pc) / var(--un-text-opacity))}.btn-outline.btn-secondary:hover{--un-border-opacity:1;border-color:hsl(var(--sf) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--sf) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--sc) / var(--un-text-opacity))}.btn-outline.btn-accent:hover{--un-border-opacity:1;border-color:hsl(var(--af) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--af) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--ac) / var(--un-text-opacity))}.btn-outline.btn-success:hover{--un-border-opacity:1;border-color:hsl(var(--su) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--su) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--suc) / var(--un-text-opacity))}.btn-outline.btn-info:hover{--un-border-opacity:1;border-color:hsl(var(--in) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--in) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--inc) / var(--un-text-opacity))}.btn-outline.btn-warning:hover{--un-border-opacity:1;border-color:hsl(var(--wa) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--wa) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--wac) / var(--un-text-opacity))}.btn-outline.btn-error:hover{--un-border-opacity:1;border-color:hsl(var(--er) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--er) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--erc) / var(--un-text-opacity))}.btn-outline{border-color:currentColor;background-color:transparent;--un-text-opacity:1;color:hsl(var(--bc) / var(--un-text-opacity));--un-shadow:0 0 #0000;--un-shadow-colored:0 0 #0000;box-shadow:var(--un-ring-offset-shadow,0 0 #0000),var(--un-ring-shadow,0 0 #0000),var(--un-shadow)}.btn-outline.btn-active{--un-border-opacity:1;border-color:hsl(var(--bc) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--bc) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--b1) / var(--un-text-opacity))}.btn-outline.btn-primary{--un-text-opacity:1;color:hsl(var(--p) / var(--un-text-opacity))}.btn-outline.btn-primary.btn-active{--un-border-opacity:1;border-color:hsl(var(--pf) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--pf) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--pc) / var(--un-text-opacity))}.btn-outline.btn-secondary{--un-text-opacity:1;color:hsl(var(--s) / var(--un-text-opacity))}.btn-outline.btn-secondary.btn-active{--un-border-opacity:1;border-color:hsl(var(--sf) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--sf) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--sc) / var(--un-text-opacity))}.btn-outline.btn-accent{--un-text-opacity:1;color:hsl(var(--a) / var(--un-text-opacity))}.btn-outline.btn-accent.btn-active{--un-border-opacity:1;border-color:hsl(var(--af) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--af) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--ac) / var(--un-text-opacity))}.btn-outline.btn-success{--un-text-opacity:1;color:hsl(var(--su) / var(--un-text-opacity))}.btn-outline.btn-success.btn-active{--un-border-opacity:1;border-color:hsl(var(--su) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--su) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--suc) / var(--un-text-opacity))}.btn-outline.btn-info{--un-text-opacity:1;color:hsl(var(--in) / var(--un-text-opacity))}.btn-outline.btn-info.btn-active{--un-border-opacity:1;border-color:hsl(var(--in) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--in) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--inc) / var(--un-text-opacity))}.btn-outline.btn-warning{--un-text-opacity:1;color:hsl(var(--wa) / var(--un-text-opacity))}.btn-outline.btn-warning.btn-active{--un-border-opacity:1;border-color:hsl(var(--wa) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--wa) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--wac) / var(--un-text-opacity))}.btn-outline.btn-error{--un-text-opacity:1;color:hsl(var(--er) / var(--un-text-opacity))}.btn-outline.btn-error.btn-active{--un-border-opacity:1;border-color:hsl(var(--er) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--er) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--erc) / var(--un-text-opacity))}.footer{display:grid;width:100%;grid-auto-flow:row;place-items:start;-moz-column-gap:1rem;column-gap:1rem;row-gap:2.5rem;font-size:0.875rem;line-height:1.25rem}.footer>*{display:grid;place-items:start;gap:0.5rem}.footer{grid-auto-flow:column}.hero{display:grid;width:100%;place-items:center;background-size:cover;background-position:center}.hero>*{grid-column-start:1;grid-row-start:1}.indicator{position:relative;display:inline-flex;width:-moz-max-content;width:max-content}.indicator:where(.indicator-item){z-index:1;position:absolute;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y));white-space:nowrap}.indicator:where(.indicator-item){bottom:auto;left:auto;right:0px;top:0px;--un-translate-y:-50%;--un-translate-x:50%;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.indicator:where(.indicator-item.indicator-start){left:0px;right:auto;--un-translate-x:-50%;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.indicator:where(.indicator-item.indicator-center){left:50%;right:50%;--un-translate-x:-50%;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.indicator:where(.indicator-item.indicator-end){left:auto;right:0px;--un-translate-x:50%;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.indicator:where(.indicator-item.indicator-bottom){bottom:0px;top:auto;--un-translate-y:50%;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.indicator:where(.indicator-item.indicator-middle){bottom:50%;top:50%;--un-translate-y:-50%;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.indicator:where(.indicator-item.indicator-top){bottom:auto;top:0px;--un-translate-y:-50%;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.input{flex-shrink:1;height:3rem;padding-left:1rem;padding-right:1rem;font-size:0.875rem;font-size:1rem;line-height:1.25rem;line-height:2;line-height:1.5rem;border-width:1px;border-color:hsl(var(--bc) / var(--un-border-opacity));--un-border-opacity:0;--un-bg-opacity:1;background-color:hsl(var(--b1) / var(--un-bg-opacity));border-radius:var(--rounded-btn,0.5rem)}.input input:focus{outline:2px solid transparent;outline-offset:2px}.input[list]::-webkit-calendar-picker-indicator{line-height:1em}.input:focus,.input:focus-within{outline-style:solid;outline-width:2px;outline-offset:2px;outline-color:hsl(var(--bc) / 0.2)}.kbd{display:inline-flex;align-items:center;justify-content:center;border-width:1px;border-color:hsl(var(--bc) / var(--un-border-opacity));--un-border-opacity:0.2;--un-bg-opacity:1;background-color:hsl(var(--b2) / var(--un-bg-opacity));padding-left:0.5rem;padding-right:0.5rem;border-radius:var(--rounded-btn,0.5rem);border-bottom-width:2px;min-height:2.2em;min-width:2.2em}.modal{pointer-events:none;position:fixed;inset:0px;margin:0px;display:grid;height:100%;max-height:none;width:100%;max-width:none;justify-items:center;padding:0px;opacity:0;overscroll-behavior:contain;overscroll-behavior:contain;z-index:999;background-color:transparent;color:inherit;transition-duration:200ms;transition-timing-function:cubic-bezier(0,0,0.2,1);transition-property:transform,opacity,visibility;overflow-y:hidden}:where(.modal){align-items:center}.modal-open,.modal:target,.modal-toggle:checked+.modal,.modal[open]{pointer-events:auto;visibility:visible;opacity:1}:root:has(:is(.modal-open,.modal:target,.modal-toggle:checked+.modal,.modal[open])){overflow:hidden}.modal:not(dialog:not(.modal-open)),.modal::backdrop{background-color:rgba(0,0,0,0.3);animation:modal-pop 0.2s ease-out}.modal-open .modal-box,.modal-toggle:checked+.modal .modal-box,.modal:target .modal-box,.modal[open] .modal-box{--un-translate-y:0px;--un-scale-x:1;--un-scale-y:1;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.progress{position:relative;width:100%;-webkit-appearance:none;-moz-appearance:none;appearance:none;overflow:hidden;height:0.5rem;background-color:hsl(var(--bc) / 0.2);border-radius:var(--rounded-box,1rem)}.progress::-moz-progress-bar{--un-bg-opacity:1;background-color:hsl(var(--bc) / var(--un-bg-opacity));border-radius:var(--rounded-box,1rem)}.progress:indeterminate{--progress-color:hsl(var(--bc));background-image:repeating-linear-gradient( 90deg,var(--progress-color) -1%,var(--progress-color) 10%,transparent 10%,transparent 90% );background-size:200%;background-position-x:15%;animation:progress-loading 5s ease-in-out infinite}.progress::-webkit-progress-bar{background-color:transparent;border-radius:var(--rounded-box,1rem)}.progress::-webkit-progress-value{--un-bg-opacity:1;background-color:hsl(var(--bc) / var(--un-bg-opacity));border-radius:var(--rounded-box,1rem)}.progress:indeterminate::-moz-progress-bar{background-color:transparent;background-image:repeating-linear-gradient( 90deg,var(--progress-color) -1%,var(--progress-color) 10%,transparent 10%,transparent 90% );background-size:200%;background-position-x:15%;animation:progress-loading 5s ease-in-out infinite}.radio{flex-shrink:0;--chkbg:var(--bc);height:1.5rem;width:1.5rem;cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:9999px;border-width:1px;border-color:hsl(var(--bc) / var(--un-border-opacity));--un-border-opacity:0.2}.radio:focus-visible{outline-style:solid;outline-width:2px;outline-offset:2px;outline-color:hsl(var(--bc) / 1)}.radio:checked,.radio[aria-checked="true"]{--un-bg-opacity:1;background-color:hsl(var(--bc) / var(--un-bg-opacity));animation:radiomark var(--animation-input,0.2s) ease-out;box-shadow:0 0 0 4px hsl(var(--b1)) inset,0 0 0 4px hsl(var(--b1)) inset}.radio:disabled{cursor:not-allowed;opacity:0.2}.range{height:1.5rem;width:100%;cursor:pointer;-moz-appearance:none;appearance:none;-webkit-appearance:none;--range-shdw:var(--bc);overflow:hidden;background-color:transparent;border-radius:var(--rounded-box,1rem)}.range:focus{outline:none}.range:focus-visible::-webkit-slider-thumb{--focus-shadow:0 0 0 6px hsl(var(--b1)) inset,0 0 0 2rem hsl(var(--range-shdw)) inset}.range:focus-visible::-moz-range-thumb{--focus-shadow:0 0 0 6px hsl(var(--b1)) inset,0 0 0 2rem hsl(var(--range-shdw)) inset}.range::-webkit-slider-runnable-track{height:0.5rem;width:100%;background-color:hsl(var(--bc) / 0.1);border-radius:var(--rounded-box,1rem)}.range::-moz-range-track{height:0.5rem;width:100%;background-color:hsl(var(--bc) / 0.1);border-radius:var(--rounded-box,1rem)}.range::-webkit-slider-thumb{position:relative;height:1.5rem;width:1.5rem;border-style:none;--un-bg-opacity:1;background-color:hsl(var(--b1) / var(--un-bg-opacity));border-radius:var(--rounded-box,1rem);appearance:none;-webkit-appearance:none;top:50%;color:hsl(var(--range-shdw));transform:translateY(-50%);--filler-size:100rem;--filler-offset:0.6rem;box-shadow:0 0 0 3px hsl(var(--range-shdw)) inset,var(--focus-shadow,0 0),calc(var(--filler-size) * -1 - var(--filler-offset)) 0 0 var(--filler-size)}.range::-moz-range-thumb{position:relative;height:1.5rem;width:1.5rem;border-style:none;--un-bg-opacity:1;background-color:hsl(var(--b1) / var(--un-bg-opacity));border-radius:var(--rounded-box,1rem);top:50%;color:hsl(var(--range-shdw));--filler-size:100rem;--filler-offset:0.5rem;box-shadow:0 0 0 3px hsl(var(--range-shdw)) inset,var(--focus-shadow,0 0),calc(var(--filler-size) * -1 - var(--filler-offset)) 0 0 var(--filler-size)}.select{display:inline-flex;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;-webkit-appearance:none;-moz-appearance:none;appearance:none;height:3rem;padding-left:1rem;padding-right:2.5rem;padding-right:2.5rem;font-size:0.875rem;line-height:1.25rem;line-height:2;min-height:3rem;border-width:1px;border-color:hsl(var(--bc) / var(--un-border-opacity));--un-border-opacity:0;--un-bg-opacity:1;background-color:hsl(var(--b1) / var(--un-bg-opacity));border-radius:var(--rounded-btn,0.5rem);background-image:linear-gradient(45deg,transparent 50%,currentColor 50%),linear-gradient(135deg,currentColor 50%,transparent 50%);background-position:calc(100% - 20px) calc(1px+50%),calc(100% - 16.1px) calc(1px+50%);background-size:4px 4px,4px 4px;background-repeat:no-repeat}.select[multiple]{height:auto}.select:focus{outline-style:solid;outline-width:2px;outline-offset:2px;outline-color:hsl(var(--bc) / 0.2)}[dir="rtl"] .select{background-position:calc(0%+12px) calc(1px+50%),calc(0%+16px) calc(1px+50%)}.stack{display:inline-grid;place-items:center;align-items:flex-end}.stack>*{grid-column-start:1;grid-row-start:1;transform:translateY(10%) scale(0.9);z-index:1;width:100%;opacity:0.6}.stack>*:nth-child(2){transform:translateY(5%) scale(0.95);z-index:2;opacity:0.8}.stack>*:nth-child(1){transform:translateY(0) scale(1);z-index:3;opacity:1}.stats{display:inline-grid;--un-bg-opacity:1;background-color:hsl(var(--b1) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--bc) / var(--un-text-opacity));border-radius:var(--rounded-box,1rem)}:where(.stats){grid-auto-flow:column;overflow-x:auto}:where(.stats)>:not([hidden])~:not([hidden]){--un-divide-x-reverse:0;border-right-width:calc(1px * var(--un-divide-x-reverse));border-left-width:calc(1px * calc(1 - var(--un-divide-x-reverse)));--un-divide-y-reverse:0;border-top-width:calc(0px * calc(1 - var(--un-divide-y-reverse)));border-bottom-width:calc(0px * var(--un-divide-y-reverse))}.stat{display:inline-grid;width:100%;grid-template-columns:repeat(1,1fr);-moz-column-gap:1rem;column-gap:1rem;border-color:hsl(var(--bc) / var(--un-border-opacity));--un-border-opacity:0.1;padding-left:1.5rem;padding-right:1.5rem;padding-top:1rem;padding-bottom:1rem}.steps{display:inline-grid;grid-auto-flow:column;overflow:hidden;overflow-x:auto;counter-reset:step;grid-auto-columns:1fr}.steps .step{display:grid;grid-template-columns:repeat(1,minmax(0,1fr));grid-template-columns:auto;grid-template-rows:repeat(2,minmax(0,1fr));grid-template-rows:40px 1fr;place-items:center;text-align:center;min-width:4rem}.steps .step:before{top:0px;grid-column-start:1;grid-row-start:1;height:0.5rem;width:100%;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y));--un-bg-opacity:1;background-color:hsl(var(--b3) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--bc) / var(--un-text-opacity));content:"";margin-left:-100%}.steps .step:after{content:counter(step);counter-increment:step;z-index:1;position:relative;grid-column-start:1;grid-row-start:1;display:grid;height:2rem;width:2rem;place-items:center;place-self:center;border-radius:9999px;--un-bg-opacity:1;background-color:hsl(var(--b3) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--bc) / var(--un-text-opacity))}.steps .step:first-child:before{content:none}.steps .step[data-content]:after{content:attr(data-content)}.steps .step-neutral+.step-neutral:before,.steps .step-neutral:after{--un-bg-opacity:1;background-color:hsl(var(--n) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--nc) / var(--un-text-opacity))}.steps .step-primary+.step-primary:before,.steps .step-primary:after{--un-bg-opacity:1;background-color:hsl(var(--p) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--pc) / var(--un-text-opacity))}.steps .step-secondary+.step-secondary:before,.steps .step-secondary:after{--un-bg-opacity:1;background-color:hsl(var(--s) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--sc) / var(--un-text-opacity))}.steps .step-accent+.step-accent:before,.steps .step-accent:after{--un-bg-opacity:1;background-color:hsl(var(--a) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--ac) / var(--un-text-opacity))}.steps .step-info+.step-info:before{--un-bg-opacity:1;background-color:hsl(var(--in) / var(--un-bg-opacity))}.steps .step-info:after{--un-bg-opacity:1;background-color:hsl(var(--in) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--inc) / var(--un-text-opacity))}.steps .step-success+.step-success:before{--un-bg-opacity:1;background-color:hsl(var(--su) / var(--un-bg-opacity))}.steps .step-success:after{--un-bg-opacity:1;background-color:hsl(var(--su) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--suc) / var(--un-text-opacity))}.steps .step-warning+.step-warning:before{--un-bg-opacity:1;background-color:hsl(var(--wa) / var(--un-bg-opacity))}.steps .step-warning:after{--un-bg-opacity:1;background-color:hsl(var(--wa) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--wac) / var(--un-text-opacity))}.steps .step-error+.step-error:before{--un-bg-opacity:1;background-color:hsl(var(--er) / var(--un-bg-opacity))}.steps .step-error:after{--un-bg-opacity:1;background-color:hsl(var(--er) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--erc) / var(--un-text-opacity))}.tabs{display:flex;flex-wrap:wrap;align-items:flex-end}.textarea{flex-shrink:1;min-height:3rem;padding-left:1rem;padding-right:1rem;padding-top:0.5rem;padding-bottom:0.5rem;font-size:0.875rem;line-height:1.25rem;line-height:2;border-width:1px;border-color:hsl(var(--bc) / var(--un-border-opacity));--un-border-opacity:0;--un-bg-opacity:1;background-color:hsl(var(--b1) / var(--un-bg-opacity));border-radius:var(--rounded-btn,0.5rem)}.textarea:focus{outline-style:solid;outline-width:2px;outline-offset:2px;outline-color:hsl(var(--bc) / 0.2)}.toast{position:fixed;display:flex;min-width:-moz-fit-content;min-width:fit-content;flex-direction:column;white-space:nowrap;gap:0.5rem;padding:1rem}.toast>*{animation:toast-pop 0.25s ease-out}:where(.toast){bottom:0px;left:auto;right:0px;top:auto;--un-translate-x:0px;--un-translate-y:0px;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.toast:where(.toast-start){left:0px;right:auto;--un-translate-x:0px;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.toast:where(.toast-center){left:50%;right:50%;--un-translate-x:-50%;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.toast:where(.toast-end){left:auto;right:0px;--un-translate-x:0px;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.toast:where(.toast-bottom){bottom:0px;top:auto;--un-translate-y:0px;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.toast:where(.toast-middle){bottom:auto;top:50%;--un-translate-y:-50%;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.toast:where(.toast-top){bottom:auto;top:0px;--un-translate-y:0px;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.toggle{flex-shrink:0;--tglbg:hsl(var(--b1));--handleoffset:1.5rem;--handleoffsetcalculator:calc(var(--handleoffset) * -1);--togglehandleborder:0 0;height:1.5rem;width:3rem;cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-width:1px;border-color:hsl(var(--bc) / var(--un-border-opacity));--un-border-opacity:0.2;background-color:hsl(var(--bc) / var(--un-bg-opacity));--un-bg-opacity:0.5;border-radius:var(--rounded-badge,1.9rem);transition:background,box-shadow var(--animation-input,0.2s) ease-out;box-shadow:var(--handleoffsetcalculator) 0 0 2px var(--tglbg) inset,0 0 0 2px var(--tglbg) inset,var(--togglehandleborder)}[dir="rtl"] .toggle{--handleoffsetcalculator:calc(var(--handleoffset) * 1)}.toggle:focus-visible{outline-style:solid;outline-width:2px;outline-offset:2px;outline-color:hsl(var(--bc) / 0.2)}.toggle:checked,.toggle[checked="true"],.toggle[aria-checked="true"]{--handleoffsetcalculator:var(--handleoffset);--un-border-opacity:1;--un-bg-opacity:1}[dir="rtl"] .toggle:checked,[dir="rtl"] .toggle[checked="true"],[dir="rtl"] .toggle[aria-checked="true"]{--handleoffsetcalculator:calc(var(--handleoffset) * -1)}.toggle:indeterminate{--un-border-opacity:1;--un-bg-opacity:1;box-shadow:calc(var(--handleoffset) / 2) 0 0 2px var(--tglbg) inset,calc(var(--handleoffset) / -2) 0 0 2px var(--tglbg) inset,0 0 0 2px var(--tglbg) inset}[dir="rtl"] .toggle:indeterminate{box-shadow:calc(var(--handleoffset) / 2) 0 0 2px var(--tglbg) inset,calc(var(--handleoffset) / -2) 0 0 2px var(--tglbg) inset,0 0 0 2px var(--tglbg) inset}.toggle:disabled{cursor:not-allowed;--un-border-opacity:1;border-color:hsl(var(--bc) / var(--un-border-opacity));background-color:transparent;opacity:0.3;--togglehandleborder:0 0 0 3px hsl(var(--bc)) inset,var(--handleoffsetcalculator) 0 0 3px hsl(var(--bc)) inset}.badge-primary{--un-border-opacity:1;border-color:hsl(var(--p) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--p) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--pc) / var(--un-text-opacity))}.input-bordered{--un-border-opacity:0.2}.loading{pointer-events:none;display:inline-block;aspect-ratio:1 / 1;width:1.5rem;background-color:currentColor;-webkit-mask-size:100%;mask-size:100%;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-position:center;mask-position:center;-webkit-mask-image:url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E");mask-image:url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E")}.menu-title{padding-left:1rem;padding-right:1rem;padding-top:0.5rem;padding-bottom:0.5rem;font-size:0.875rem;line-height:1.25rem;font-weight:700;color:hsl(var(--bc) / 0.4)}.rounded-box{border-radius:var(--rounded-box,1rem)}.badge-sm{height:1rem;font-size:0.75rem;line-height:1rem;padding-left:0.438rem;padding-right:0.438rem}.btn-xs{height:1.5rem;padding-left:0.5rem;padding-right:0.5rem;min-height:1.5rem;font-size:0.75rem}.btn-sm{height:2rem;padding-left:0.75rem;padding-right:0.75rem;min-height:2rem;font-size:0.875rem}.input-sm{height:2rem;padding-left:0.75rem;padding-right:0.75rem;font-size:0.875rem;line-height:2rem}.toggle-sm{--handleoffset:0.75rem;height:1.25rem;width:2rem}.menu-sm:where(li:not(.menu-title)>*:not(ul):not(details):not(.menu-title)),.menu-sm:where(li:not(.menu-title)>details>summary:not(.menu-title)){padding-left:0.75rem;padding-right:0.75rem;padding-top:0.25rem;padding-bottom:0.25rem;font-size:0.875rem;line-height:1.25rem;border-radius:var(--rounded-btn,0.5rem)}.menu-sm .menu-title{padding-left:0.75rem;padding-right:0.75rem;padding-top:0.5rem;padding-bottom:0.5rem}@keyframes button-pop{0%{transform:scale(var(--btn-focus-scale,0.98))}40%{transform:scale(1.02)}100%{transform:scale(1)}}@keyframes checkmark{0%{background-position-y:5px}50%{background-position-y:-2px}100%{background-position-y:0}}@keyframes modal-pop{0%{opacity:0}}@keyframes progress-loading{50%{background-position-x:-115%}}@keyframes radiomark{0%{box-shadow:0 0 0 12px hsl(var(--b1)) inset,0 0 0 12px hsl(var(--b1)) inset}50%{box-shadow:0 0 0 3px hsl(var(--b1)) inset,0 0 0 3px hsl(var(--b1)) inset}100%{box-shadow:0 0 0 4px hsl(var(--b1)) inset,0 0 0 4px hsl(var(--b1)) inset}}@keyframes rating-pop{0%{transform:translateY(-0.125em)}40%{transform:translateY(-0.125em)}100%{transform:translateY(0)}}@keyframes toast-pop{0%{transform:scale(0.9);opacity:0}100%{transform:scale(1);opacity:1}}:root{color-scheme:light;--pf:259 94% 44%;--sf:314 100% 40%;--af:174 75% 39%;--nf:214 20% 14%;--in:198 93% 60%;--su:158 64% 52%;--wa:43 96% 56%;--er:0 91% 71%;--inc:198 100% 12%;--suc:158 100% 10%;--wac:43 100% 11%;--erc:0 100% 14%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:259 94% 51%;--pc:259 96% 91%;--s:314 100% 47%;--sc:314 100% 91%;--a:174 75% 46%;--ac:174 75% 11%;--n:214 20% 21%;--nc:212 19% 87%;--b1:0 0% 100%;--b2:0 0% 95%;--b3:180 2% 90%;--bc:215 28% 17%}@media (prefers-color-scheme:dark){:root{color-scheme:dark;--pf:262 80% 43%;--sf:316 70% 43%;--af:175 70% 34%;--in:198 93% 60%;--su:158 64% 52%;--wa:43 96% 56%;--er:0 91% 71%;--inc:198 100% 12%;--suc:158 100% 10%;--wac:43 100% 11%;--erc:0 100% 14%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:262 80% 50%;--pc:0 0% 100%;--s:316 70% 50%;--sc:0 0% 100%;--a:175 70% 41%;--ac:0 0% 100%;--n:213 18% 20%;--nf:212 17% 17%;--nc:220 13% 69%;--b1:212 18% 14%;--b2:213 18% 12%;--b3:213 18% 10%;--bc:220 13% 69%}}[data-theme=light]{color-scheme:light;--pf:259 94% 44%;--sf:314 100% 40%;--af:174 75% 39%;--nf:214 20% 14%;--in:198 93% 60%;--su:158 64% 52%;--wa:43 96% 56%;--er:0 91% 71%;--inc:198 100% 12%;--suc:158 100% 10%;--wac:43 100% 11%;--erc:0 100% 14%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:259 94% 51%;--pc:259 96% 91%;--s:314 100% 47%;--sc:314 100% 91%;--a:174 75% 46%;--ac:174 75% 11%;--n:214 20% 21%;--nc:212 19% 87%;--b1:0 0% 100%;--b2:0 0% 95%;--b3:180 2% 90%;--bc:215 28% 17%}[data-theme=dark]{color-scheme:dark;--pf:262 80% 43%;--sf:316 70% 43%;--af:175 70% 34%;--in:198 93% 60%;--su:158 64% 52%;--wa:43 96% 56%;--er:0 91% 71%;--inc:198 100% 12%;--suc:158 100% 10%;--wac:43 100% 11%;--erc:0 100% 14%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:262 80% 50%;--pc:0 0% 100%;--s:316 70% 50%;--sc:0 0% 100%;--a:175 70% 41%;--ac:0 0% 100%;--n:213 18% 20%;--nf:212 17% 17%;--nc:220 13% 69%;--b1:212 18% 14%;--b2:213 18% 12%;--b3:213 18% 10%;--bc:220 13% 69%}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.disabled\:pointer-events-none:disabled{pointer-events:none}.visible{visibility:visible}.invisible{visibility:hidden}.absolute,[absolute=""]{position:absolute}.fixed{position:fixed}.relative,[relative=""]{position:relative}.root-relative:root{position:relative}.static,[static=""]{position:static}.inset-0{inset:0}.inset-y-0{top:0;bottom:0}.bottom-0{bottom:0}.bottom-4{bottom:1rem}.left-0{left:0}.right-0,[right-0=""]{right:0}.right-4{right:1rem}.top-0{top:0}.top-4{top:1rem}.top-full{top:100%}.line-clamp-3{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3;line-clamp:3}.z-\[1\]{z-index:1}.z-\[110\]{z-index:110}.z-\[80\]{z-index:80}.z-\[85\]{z-index:85}.z-10,[z-10=""]{z-index:10}.z-100{z-index:100}.z-120{z-index:120}.z-30{z-index:30}.z-50{z-index:50}.grid,[grid=""]{display:grid}.inline-grid{display:inline-grid}.col-span-1{grid-column:span 1/span 1}.grid-cols-1,[grid-cols-1=""]{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2,[grid-cols-2=""]{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-rows-2,[grid-rows-2=""]{grid-template-rows:repeat(2,minmax(0,1fr))}[rows~="\33 "]{grid-template-rows:repeat(3,minmax(0,1fr))}[rows~="\34 "]{grid-template-rows:repeat(4,minmax(0,1fr))}[rows~="\35 "]{grid-template-rows:repeat(5,minmax(0,1fr))}[rows~="\36 "]{grid-template-rows:repeat(6,minmax(0,1fr))}.m-1{margin:0.25rem}.m-2{margin:0.5rem}.m-3{margin:0.75rem}.m-4{margin:1rem}.m-6{margin:1.5rem}.m-8{margin:2rem}[m~="\30 \.45"]{margin:0.1125rem}[m~="\30 \.65"]{margin:0.1625rem}.-mx-1\.5{margin-left:-0.375rem;margin-right:-0.375rem}.-my-1\.5{margin-top:-0.375rem;margin-bottom:-0.375rem}.mx-2{margin-left:0.5rem;margin-right:0.5rem}.my{margin-top:1rem;margin-bottom:1rem}.my-0{margin-top:0;margin-bottom:0}.my-auto{margin-top:auto;margin-bottom:auto}.-ml-1{margin-left:-0.25rem}.mb-1{margin-bottom:0.25rem}.mb-12,[mb-12=""]{margin-bottom:3rem}.mb-2{margin-bottom:0.5rem}.mb-3,[mb-3=""]{margin-bottom:0.75rem}.mb-4,[mb-4=""]{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8,[mb-8=""]{margin-bottom:2rem}.mb-ds-12{margin-bottom:var(--space-12,3rem)}.mb-ds-4,[mb-ds-4=""]{margin-bottom:var(--space-4,1rem)}.mb-ds-6{margin-bottom:var(--space-6,1.5rem)}.mb-ds-8{margin-bottom:var(--space-8,2rem)}.me,[me=""]{margin-inline-end:1rem}.ml-1,[ml-1=""]{margin-left:0.25rem}.ml-2{margin-left:0.5rem}.ml-3,[ml-3=""]{margin-left:0.75rem}.ml-4,[ml-4=""]{margin-left:1rem}.ml-auto,[ml-auto=""]{margin-left:auto}.mr-1{margin-right:0.25rem}.mr-2{margin-right:0.5rem}.mr-3,[mr-3=""]{margin-right:0.75rem}.mr-4{margin-right:1rem}.ms,[ms=""]{margin-inline-start:1rem}.mt-1,[mt-1=""]{margin-top:0.25rem}.mt-10{margin-top:2.5rem}.mt-12,[mt-12=""]{margin-top:3rem}.mt-16{margin-top:4rem}.mt-2,[mt-2=""]{margin-top:0.5rem}.mt-3{margin-top:0.75rem}.mt-4{margin-top:1rem}.mt-6,[mt-6=""]{margin-top:1.5rem}.mt-8,[mt-8=""]{margin-top:2rem}.mt-auto{margin-top:auto}.mt-ds-3{margin-top:var(--space-3,0.75rem)}.mt-ds-6,[mt-ds-6=""]{margin-top:var(--space-6,1.5rem)}.mt-ds-8,[mt-ds-8=""]{margin-top:var(--space-8,2rem)}.inline{display:inline}.block,.dark .dark\:block,[block=""]{display:block}.empty\:block:empty{display:block}.inline-block,[inline-block=""]{display:inline-block}.contents,[contents=""]{display:contents}.dark .dark\:hidden,.hidden,[hidden=""]{display:none}.h-\[\{\}px\]{height:{}px}.h-10{height:2.5rem}.h-12,[h-12=""]{height:3rem}.h-15{height:3.75rem}.h-16{height:4rem}.h-2,.h2{height:0.5rem}.h-28,[h-28=""]{height:7rem}.h-3,.h3,[h3=""]{height:0.75rem}.h-32{height:8rem}.h-4,.h4,[h-4=""]{height:1rem}.h-5,.h5,[h-5=""],[h5=""]{height:1.25rem}.h-6,.h6,[h-6=""]{height:1.5rem}.h-64,[h-64=""]{height:16rem}.h-8,[h-8=""]{height:2rem}.h-9{height:2.25rem}.h-full,[h-full=""]{height:100%}.h1{height:0.25rem}.max-w-2xl,[max-w-2xl=""]{max-width:42rem}.max-w-3xl{max-width:48rem}.max-w-4xl,[max-w-4xl=""]{max-width:56rem}.max-w-6xl,[max-w-6xl=""]{max-width:72rem}.max-w-7xl,[max-w-7xl=""]{max-width:80rem}.max-w-lg{max-width:32rem}.max-w-md{max-width:28rem}.max-w-none,[max-w-none=""]{max-width:none}.max-w-sm{max-width:24rem}.max-w-xs{max-width:20rem}.min-h-\[34px\]{min-height:34px}.min-h-screen,[min-h-screen=""]{min-height:100vh}.min-w-\[34px\]{min-width:34px}.min-w-0{min-width:0}.min-w-32,[min-w-32=""]{min-width:8rem}.w-\[\{\}px\]{width:{}px}.w-1\/2{width:50%}.w-10{width:2.5rem}.w-12,[w-12=""]{width:3rem}.w-16{width:4rem}.w-24{width:6rem}.w-28,[w-28=""]{width:7rem}.w-3,[w-3=""]{width:0.75rem}.w-3\/4{width:75%}.w-32{width:8rem}.w-4,[w-4=""]{width:1rem}.w-48,[w-48=""]{width:12rem}.w-5,[w-5=""]{width:1.25rem}.w-52{width:13rem}.w-6,[w-6=""]{width:1.5rem}.w-64{width:16rem}.w-8{width:2rem}.w-9{width:2.25rem}.w-auto{width:auto}.w-full,[w-full=""]{width:100%}.w-min,[w-min=""]{width:min-content}[w-1=""]{width:0.25rem}[block~="a"]{block-size:auto}.flex,[flex=""]{display:flex}.inline-flex,[inline-flex=""]{display:inline-flex}.flex-1,[flex-1=""]{flex:1 1 0%}.flex-shrink{flex-shrink:1}.flex-shrink-0,.shrink-0,[flex-shrink-0=""]{flex-shrink:0}.flex-grow,[flex-grow=""]{flex-grow:1}.flex-col,[flex-col=""]{flex-direction:column}.flex-wrap,[flex-wrap=""]{flex-wrap:wrap}.table,[table=""]{display:table}.border-collapse{border-collapse:collapse}.hover\:scale-105:hover{--un-scale-x:1.05;--un-scale-y:1.05;transform:translateX(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotateZ(var(--un-rotate-z)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z))}[hover\:scale-105=""]:hover{--un-scale-x:1.05;--un-scale-y:1.05;transform:translateX(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotateZ(var(--un-rotate-z)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z))}.transform{transform:translateX(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotateZ(var(--un-rotate-z)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z))}@keyframes pulse{0%,100%{opacity:1}50%{opacity:.5}}@keyframes spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}.animate-pulse{animation:pulse 2s cubic-bezier(0.4,0,.6,1) infinite}.animate-spin{animation:spin 1s linear infinite}.cursor-pointer{cursor:pointer}.cursor-not-allowed,[cursor-not-allowed=""]{cursor:not-allowed}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.select-none{-webkit-user-select:none;user-select:none}.list-disc{list-style-type:disc}.list-inside{list-style-position:inside}.list-none{list-style-type:none}.place-items-center{place-items:center}.items-start,[items-start=""]{align-items:flex-start}.items-end{align-items:flex-end}.items-center,[items-center=""]{align-items:center}.items-stretch,[items-stretch=""]{align-items:stretch}.justify-end{justify-content:flex-end}.justify-center,[justify-center=""]{justify-content:center}.justify-between,[justify-between=""]{justify-content:space-between}.gap-1,[gap-1=""]{gap:0.25rem}.gap-12{gap:3rem}.gap-2,[gap-2=""]{gap:0.5rem}.gap-3,[gap-3=""]{gap:0.75rem}.gap-4,[gap-4=""]{gap:1rem}.gap-6,[gap-6=""]{gap:1.5rem}.gap-8{gap:2rem}.gap-ds-2{gap:var(--space-2,0.5rem)}.gap-ds-4{gap:var(--space-4,1rem)}.gap-ds-6{gap:var(--space-6,1.5rem)}.gap-ds-8{gap:var(--space-8,2rem)}.gap-x-6{column-gap:1.5rem}.space-x-1>:not([hidden])~:not([hidden]),[space-x-1=""]>:not([hidden])~:not([hidden]){--un-space-x-reverse:0;margin-left:calc(0.25rem * calc(1 - var(--un-space-x-reverse)));margin-right:calc(0.25rem * var(--un-space-x-reverse))}.space-x-2>:not([hidden])~:not([hidden]),[space-x-2=""]>:not([hidden])~:not([hidden]){--un-space-x-reverse:0;margin-left:calc(0.5rem * calc(1 - var(--un-space-x-reverse)));margin-right:calc(0.5rem * var(--un-space-x-reverse))}.space-x-3>:not([hidden])~:not([hidden]),[space-x-3=""]>:not([hidden])~:not([hidden]){--un-space-x-reverse:0;margin-left:calc(0.75rem * calc(1 - var(--un-space-x-reverse)));margin-right:calc(0.75rem * var(--un-space-x-reverse))}.space-x-4>:not([hidden])~:not([hidden]),[space-x-4=""]>:not([hidden])~:not([hidden]){--un-space-x-reverse:0;margin-left:calc(1rem * calc(1 - var(--un-space-x-reverse)));margin-right:calc(1rem * var(--un-space-x-reverse))}.space-x-6>:not([hidden])~:not([hidden]){--un-space-x-reverse:0;margin-left:calc(1.5rem * calc(1 - var(--un-space-x-reverse)));margin-right:calc(1.5rem * var(--un-space-x-reverse))}.space-y-1>:not([hidden])~:not([hidden]){--un-space-y-reverse:0;margin-top:calc(0.25rem * calc(1 - var(--un-space-y-reverse)));margin-bottom:calc(0.25rem * var(--un-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]),[space-y-2=""]>:not([hidden])~:not([hidden]){--un-space-y-reverse:0;margin-top:calc(0.5rem * calc(1 - var(--un-space-y-reverse)));margin-bottom:calc(0.5rem * var(--un-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--un-space-y-reverse:0;margin-top:calc(0.75rem * calc(1 - var(--un-space-y-reverse)));margin-bottom:calc(0.75rem * var(--un-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--un-space-y-reverse:0;margin-top:calc(1rem * calc(1 - var(--un-space-y-reverse)));margin-bottom:calc(1rem * var(--un-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--un-space-y-reverse:0;margin-top:calc(1.5rem * calc(1 - var(--un-space-y-reverse)));margin-bottom:calc(1.5rem * var(--un-space-y-reverse))}.space-y-8>:not([hidden])~:not([hidden]){--un-space-y-reverse:0;margin-top:calc(2rem * calc(1 - var(--un-space-y-reverse)));margin-bottom:calc(2rem * var(--un-space-y-reverse))}.space-y-ds-2>:not([hidden])~:not([hidden]){--un-space-y-reverse:0;margin-top:calc(var(--space-2,0.5rem) * calc(1 - var(--un-space-y-reverse)));margin-bottom:calc(var(--space-2,0.5rem) * var(--un-space-y-reverse))}.space-y-ds-4>:not([hidden])~:not([hidden]){--un-space-y-reverse:0;margin-top:calc(var(--space-4,1rem) * calc(1 - var(--un-space-y-reverse)));margin-bottom:calc(var(--space-4,1rem) * var(--un-space-y-reverse))}.space-y-ds-6>:not([hidden])~:not([hidden]){--un-space-y-reverse:0;margin-top:calc(var(--space-6,1.5rem) * calc(1 - var(--un-space-y-reverse)));margin-bottom:calc(var(--space-6,1.5rem) * var(--un-space-y-reverse))}.space-y-ds-8>:not([hidden])~:not([hidden]){--un-space-y-reverse:0;margin-top:calc(var(--space-8,2rem) * calc(1 - var(--un-space-y-reverse)));margin-bottom:calc(var(--space-8,2rem) * var(--un-space-y-reverse))}.overflow-hidden,[overflow-hidden=""]{overflow:hidden}.overflow-x-auto{overflow-x:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-nowrap{white-space:nowrap}.b,.border,[b=""],[border=""]{border-width:1px}.border-2,[border-2=""]{border-width:2px}.last\:border-0:last-child{border-width:0px}.border-b,[border-b=""]{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-r{border-right-width:1px}.border-t,[border-t=""]{border-top-width:1px}.border-t-2{border-top-width:2px}.last\:border-b-0:last-child{border-bottom-width:0px}[last\:border-b-0=""]:last-child{border-bottom-width:0px}.border-base-200{--un-border-opacity:1;border-color:hsl(var(--b2) / var(--un-border-opacity))}.border-base-300,[border-base-300=""]{--un-border-opacity:1;border-color:hsl(var(--b3) / var(--un-border-opacity))}.border-base-300\/50{border-color:hsl(var(--b3) / 0.5)}.border-base-content\/20{border-color:hsl(var(--bc) / 0.2)}.border-blue-200{--un-border-opacity:1;border-color:rgb(191 219 254 / var(--un-border-opacity))}.border-blue-500,[border-blue-500=""]{--un-border-opacity:1;border-color:rgb(59 130 246 / var(--un-border-opacity))}.border-blue-500\/20{border-color:rgb(59 130 246 / 0.2)}.border-blue-600{--un-border-opacity:1;border-color:rgb(37 99 235 / var(--un-border-opacity))}.border-error\/20{border-color:hsl(var(--er) / 0.2)}.border-gray-100{--un-border-opacity:1;border-color:rgb(243 244 246 / var(--un-border-opacity))}.border-gray-200,[border-gray-200=""]{--un-border-opacity:1;border-color:rgb(229 231 235 / var(--un-border-opacity))}.border-gray-300{--un-border-opacity:1;border-color:rgb(209 213 219 / var(--un-border-opacity))}.border-gray-400{--un-border-opacity:1;border-color:rgb(156 163 175 / var(--un-border-opacity))}.border-gray-500,[border-gray-500=""]{--un-border-opacity:1;border-color:rgb(107 114 128 / var(--un-border-opacity))}.border-green-200{--un-border-opacity:1;border-color:rgb(187 247 208 / var(--un-border-opacity))}.border-green-300{--un-border-opacity:1;border-color:rgb(134 239 172 / var(--un-border-opacity))}.border-green-400{--un-border-opacity:1;border-color:rgb(74 222 128 / var(--un-border-opacity))}.border-green-500,[border-green-500=""]{--un-border-opacity:1;border-color:rgb(34 197 94 / var(--un-border-opacity))}.border-purple-500,[border-purple-500=""]{--un-border-opacity:1;border-color:rgb(168 85 247 / var(--un-border-opacity))}.border-red-200,[border-red-200=""]{--un-border-opacity:1;border-color:rgb(254 202 202 / var(--un-border-opacity))}.border-red-300{--un-border-opacity:1;border-color:rgb(252 165 165 / var(--un-border-opacity))}.border-red-400{--un-border-opacity:1;border-color:rgb(248 113 113 / var(--un-border-opacity))}.border-transparent{border-color:transparent}.border-yellow-200{--un-border-opacity:1;border-color:rgb(254 240 138 / var(--un-border-opacity))}.border-yellow-500,[border-yellow-500=""]{--un-border-opacity:1;border-color:rgb(234 179 8 / var(--un-border-opacity))}.dark .dark\:border-gray-700{--un-border-opacity:1;border-color:rgb(55 65 81 / var(--un-border-opacity))}.hover\:border-blue-400:hover{--un-border-opacity:1;border-color:rgb(96 165 250 / var(--un-border-opacity))}.focus\:border-blue-500:focus{--un-border-opacity:1;border-color:rgb(59 130 246 / var(--un-border-opacity))}.focus\:border-transparent:focus{border-color:transparent}[focus\:border-blue-500=""]:focus{--un-border-opacity:1;border-color:rgb(59 130 246 / var(--un-border-opacity))}.rounded-md,[rounded-md=""]{border-radius:0.375rem}.rounded-l-md{border-top-left-radius:0.375rem;border-bottom-left-radius:0.375rem}.rounded-r-md,[rounded-r-md=""]{border-top-right-radius:0.375rem;border-bottom-right-radius:0.375rem}.border-dashed,[border-dashed=""]{border-style:dashed}.border-solid{border-style:solid}.bg-base-100{--un-bg-opacity:1;background-color:hsl(var(--b1) / var(--un-bg-opacity))}.bg-black{--un-bg-opacity:1;background-color:rgb(0 0 0 / var(--un-bg-opacity))}.bg-blue-100{--un-bg-opacity:1;background-color:rgb(219 234 254 / var(--un-bg-opacity))}.bg-blue-50,[bg-blue-50=""]{--un-bg-opacity:1;background-color:rgb(239 246 255 / var(--un-bg-opacity))}.bg-blue-500{--un-bg-opacity:1;background-color:rgb(59 130 246 / var(--un-bg-opacity))}.bg-blue-500\/10{background-color:rgb(59 130 246 / 0.1)}.bg-blue-600,[bg-blue-600=""]{--un-bg-opacity:1;background-color:rgb(37 99 235 / var(--un-bg-opacity))}.bg-ds-brand-primary{background-color:var(--color-brand-primary,#3b82f6)}.bg-gray-100,[bg-gray-100=""]{--un-bg-opacity:1;background-color:rgb(243 244 246 / var(--un-bg-opacity))}.bg-gray-200,[bg-gray-200=""]{--un-bg-opacity:1;background-color:rgb(229 231 235 / var(--un-bg-opacity))}.bg-gray-50,[bg-gray-50=""]{--un-bg-opacity:1;background-color:rgb(249 250 251 / var(--un-bg-opacity))}.bg-gray-600,.dark .dark\:bg-gray-600{--un-bg-opacity:1;background-color:rgb(75 85 99 / var(--un-bg-opacity))}.bg-gray-800,.dark .dark\:bg-gray-800{--un-bg-opacity:1;background-color:rgb(31 41 55 / var(--un-bg-opacity))}.bg-gray-900,.dark [dark\:bg-gray-900=""],[bg-gray-900=""]{--un-bg-opacity:1;background-color:rgb(17 24 39 / var(--un-bg-opacity))}.bg-green-100,[bg-green-100=""]{--un-bg-opacity:1;background-color:rgb(220 252 231 / var(--un-bg-opacity))}.bg-green-50,[bg-green-50=""]{--un-bg-opacity:1;background-color:rgb(240 253 244 / var(--un-bg-opacity))}.bg-green-500{--un-bg-opacity:1;background-color:rgb(34 197 94 / var(--un-bg-opacity))}.bg-indigo-100,[bg-indigo-100=""]{--un-bg-opacity:1;background-color:rgb(224 231 255 / var(--un-bg-opacity))}.bg-indigo-600{--un-bg-opacity:1;background-color:rgb(79 70 229 / var(--un-bg-opacity))}.bg-orange-500{--un-bg-opacity:1;background-color:rgb(249 115 22 / var(--un-bg-opacity))}.bg-primary{background-color:var(--c-primary)}.bg-purple-50,[bg-purple-50=""]{--un-bg-opacity:1;background-color:rgb(250 245 255 / var(--un-bg-opacity))}.bg-red-100{--un-bg-opacity:1;background-color:rgb(254 226 226 / var(--un-bg-opacity))}.bg-red-50,[bg-red-50=""]{--un-bg-opacity:1;background-color:rgb(254 242 242 / var(--un-bg-opacity))}.bg-red-500{--un-bg-opacity:1;background-color:rgb(239 68 68 / var(--un-bg-opacity))}.bg-slate-700{--un-bg-opacity:1;background-color:rgb(51 65 85 / var(--un-bg-opacity))}.bg-transparent{background-color:transparent}.bg-white{--un-bg-opacity:1;background-color:rgb(255 255 255 / var(--un-bg-opacity))}.bg-yellow-100,[bg-yellow-100=""]{--un-bg-opacity:1;background-color:rgb(254 249 195 / var(--un-bg-opacity))}.bg-yellow-50,[bg-yellow-50=""]{--un-bg-opacity:1;background-color:rgb(254 252 232 / var(--un-bg-opacity))}.bg-yellow-500{--un-bg-opacity:1;background-color:rgb(234 179 8 / var(--un-bg-opacity))}.dark .dark\:bg-blue-900\/20{background-color:rgb(30 58 138 / 0.2)}.dark .dark\:bg-gray-700{--un-bg-opacity:1;background-color:rgb(55 65 81 / var(--un-bg-opacity))}.dark .dark\:bg-gray-900\/50{background-color:rgb(17 24 39 / 0.5)}.dark .dark\:bg-green-900\/20{background-color:rgb(20 83 45 / 0.2)}.dark .dark\:bg-purple-900\/20{background-color:rgb(88 28 135 / 0.2)}.dark .dark\:bg-slate-400{--un-bg-opacity:1;background-color:rgb(148 163 184 / var(--un-bg-opacity))}.dark .dark\:bg-slate-900{--un-bg-opacity:1;background-color:rgb(15 23 42 / var(--un-bg-opacity))}.dark .dark\:bg-yellow-900\/20{background-color:rgb(113 63 18 / 0.2)}.dark [dark\:bg-blue-900=""]{--un-bg-opacity:1;background-color:rgb(30 58 138 / var(--un-bg-opacity))}.dark [dark\:bg-green-900=""]{--un-bg-opacity:1;background-color:rgb(20 83 45 / var(--un-bg-opacity))}.dark [dark\:bg-purple-900=""]{--un-bg-opacity:1;background-color:rgb(88 28 135 / var(--un-bg-opacity))}.dark [dark\:bg-yellow-900=""]{--un-bg-opacity:1;background-color:rgb(113 63 18 / var(--un-bg-opacity))}.dark .dark\:hover\:bg-gray-500:hover{--un-bg-opacity:1;background-color:rgb(107 114 128 / var(--un-bg-opacity))}.hover\:bg-base-content\/5:hover{background-color:hsl(var(--bc) / 0.05)}.hover\:bg-blue-700:hover{--un-bg-opacity:1;background-color:rgb(29 78 216 / var(--un-bg-opacity))}.hover\:bg-ds-brand-primary:hover{background-color:var(--color-brand-primary,#3b82f6)}.hover\:bg-gray-100:hover{--un-bg-opacity:1;background-color:rgb(243 244 246 / var(--un-bg-opacity))}.hover\:bg-gray-50:hover{--un-bg-opacity:1;background-color:rgb(249 250 251 / var(--un-bg-opacity))}.hover\:bg-gray-700:hover{--un-bg-opacity:1;background-color:rgb(55 65 81 / var(--un-bg-opacity))}.hover\:bg-primary\/80:hover{background-color:var(--c-primary)}.hover\:bg-red-100:hover{--un-bg-opacity:1;background-color:rgb(254 226 226 / var(--un-bg-opacity))}.hover\:bg-red-200:hover{--un-bg-opacity:1;background-color:rgb(254 202 202 / var(--un-bg-opacity))}.dark .dark\:from-gray-900{--un-gradient-from-position:0%;--un-gradient-from:rgb(17 24 39 / var(--un-from-opacity,1)) var(--un-gradient-from-position);--un-gradient-to-position:100%;--un-gradient-to:rgb(17 24 39 / 0) var(--un-gradient-to-position);--un-gradient-stops:var(--un-gradient-from),var(--un-gradient-to)}.from-blue-50{--un-gradient-from-position:0%;--un-gradient-from:rgb(239 246 255 / var(--un-from-opacity,1)) var(--un-gradient-from-position);--un-gradient-to-position:100%;--un-gradient-to:rgb(239 246 255 / 0) var(--un-gradient-to-position);--un-gradient-stops:var(--un-gradient-from),var(--un-gradient-to)}.from-slate-50{--un-gradient-from-position:0%;--un-gradient-from:rgb(248 250 252 / var(--un-from-opacity,1)) var(--un-gradient-from-position);--un-gradient-to-position:100%;--un-gradient-to:rgb(248 250 252 / 0) var(--un-gradient-to-position);--un-gradient-stops:var(--un-gradient-from),var(--un-gradient-to)}.dark .dark\:to-blue-900{--un-gradient-to-position:100%;--un-gradient-to:rgb(30 58 138 / var(--un-to-opacity,1)) var(--un-gradient-to-position)}.dark .dark\:to-slate-900{--un-gradient-to-position:100%;--un-gradient-to:rgb(15 23 42 / var(--un-to-opacity,1)) var(--un-gradient-to-position)}.to-gray-100{--un-gradient-to-position:100%;--un-gradient-to:rgb(243 244 246 / var(--un-to-opacity,1)) var(--un-gradient-to-position)}.to-indigo-100{--un-gradient-to-position:100%;--un-gradient-to:rgb(224 231 255 / var(--un-to-opacity,1)) var(--un-gradient-to-position)}.bg-gradient-to-br{--un-gradient-shape:to bottom right in oklch;--un-gradient:var(--un-gradient-shape),var(--un-gradient-stops);background-image:linear-gradient(var(--un-gradient))}[stroke-width~="\31 \.5"]{stroke-width:1.5px}[stroke-width~="\32 "]{stroke-width:2px}[stroke-width~="\34 "]{stroke-width:4px}.object-cover{object-fit:cover}.p-1{padding:0.25rem}.p-1\.5{padding:0.375rem}.p-2,[p-2=""]{padding:0.5rem}.p-3{padding:0.75rem}.p-4,[p-4=""]{padding:1rem}.p-6,[p-6=""]{padding:1.5rem}.p-8{padding:2rem}.p-ds-2{padding:var(--space-2,0.5rem)}.p-ds-4,[p-ds-4=""]{padding:var(--space-4,1rem)}.p-ds-6,[p-ds-6=""]{padding:var(--space-6,1.5rem)}.p-ds-8{padding:var(--space-8,2rem)}.px-1{padding-left:0.25rem;padding-right:0.25rem}.px-2,[px-2=""]{padding-left:0.5rem;padding-right:0.5rem}.px-2\.5{padding-left:0.625rem;padding-right:0.625rem}.px-3,[px-3=""]{padding-left:0.75rem;padding-right:0.75rem}.px-4,[px-4=""]{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-8{padding-left:2rem;padding-right:2rem}.px-ds-4{padding-left:var(--space-4,1rem);padding-right:var(--space-4,1rem)}.px-ds-8{padding-left:var(--space-8,2rem);padding-right:var(--space-8,2rem)}.py,.py-4{padding-top:1rem;padding-bottom:1rem}.py-0\.5{padding-top:0.125rem;padding-bottom:0.125rem}.py-1,[py-1=""]{padding-top:0.25rem;padding-bottom:0.25rem}.py-12,[py-12=""]{padding-top:3rem;padding-bottom:3rem}.py-16{padding-top:4rem;padding-bottom:4rem}.py-2,[py-2=""]{padding-top:0.5rem;padding-bottom:0.5rem}.py-20{padding-top:5rem;padding-bottom:5rem}.py-3{padding-top:0.75rem;padding-bottom:0.75rem}.py-5{padding-top:1.25rem;padding-bottom:1.25rem}.py-6,[py-6=""]{padding-top:1.5rem;padding-bottom:1.5rem}.py-8,[py-8=""]{padding-top:2rem;padding-bottom:2rem}.py-ds-12{padding-top:var(--space-12,3rem);padding-bottom:var(--space-12,3rem)}.py-ds-2{padding-top:var(--space-2,0.5rem);padding-bottom:var(--space-2,0.5rem)}.py-ds-4,[py-ds-4=""]{padding-top:var(--space-4,1rem);padding-bottom:var(--space-4,1rem)}.py-ds-6{padding-top:var(--space-6,1.5rem);padding-bottom:var(--space-6,1.5rem)}.py-ds-8{padding-top:var(--space-8,2rem);padding-bottom:var(--space-8,2rem)}.pb{padding-bottom:1rem}.pb-3,[pb-3=""]{padding-bottom:0.75rem}.pl{padding-left:1rem}.pl-3{padding-left:0.75rem}.pr-10,[pr-10=""]{padding-right:2.5rem}.pr-3{padding-right:0.75rem}.ps{padding-inline-start:1rem}.ps1{padding-inline-start:0.25rem}.pt{padding-top:1rem}.pt-2{padding-top:0.5rem}.pt-3,[pt-3=""]{padding-top:0.75rem}.pt-6{padding-top:1.5rem}.pt-ds-3{padding-top:var(--space-3,0.75rem)}.pt-ds-4{padding-top:var(--space-4,1rem)}.pt-ds-6{padding-top:var(--space-6,1.5rem)}.last\:pb-0:last-child{padding-bottom:0}.text-end,[text-end=""]{text-align:end}.text-balance,[text-balance=""]{text-wrap:balance}.align-middle{vertical-align:middle}.text-\[15px\]{font-size:15px}.text-2xl,[text-2xl=""]{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-4xl,[text-4xl=""]{font-size:2.25rem;line-height:2.5rem}.text-6xl{font-size:3.75rem;line-height:1}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm,[text-sm=""]{font-size:0.875rem;line-height:1.25rem}.text-xl,[text-xl=""]{font-size:1.25rem;line-height:1.75rem}.text-xs,[text-xs=""]{font-size:0.75rem;line-height:1rem}.dark .dark\:text-blue-400,.dark [dark\:text-blue-400=""],.text-blue-400{--un-text-opacity:1;color:rgb(96 165 250 / var(--un-text-opacity))}.dark .dark\:text-gray-300,.text-gray-300,[text-gray-300=""]{--un-text-opacity:1;color:rgb(209 213 219 / var(--un-text-opacity))}.dark .dark\:text-gray-400,.text-gray-400,[text-gray-400=""]{--un-text-opacity:1;color:rgb(156 163 175 / var(--un-text-opacity))}.dark .dark\:text-green-400,.dark [dark\:text-green-400=""]{--un-text-opacity:1;color:rgb(74 222 128 / var(--un-text-opacity))}.dark .dark\:text-purple-400,.dark [dark\:text-purple-400=""]{--un-text-opacity:1;color:rgb(192 132 252 / var(--un-text-opacity))}.dark .dark\:text-slate-900{--un-text-opacity:1;color:rgb(15 23 42 / var(--un-text-opacity))}.dark .dark\:text-yellow-400,.dark [dark\:text-yellow-400=""]{--un-text-opacity:1;color:rgb(250 204 21 / var(--un-text-opacity))}.text-base-content,[text-base-content=""]{--un-text-opacity:1;color:hsl(var(--bc) / var(--un-text-opacity))}.text-base-content\/50{color:hsl(var(--bc) / 0.5)}.text-base-content\/60{color:hsl(var(--bc) / 0.6)}.text-base-content\/70{color:hsl(var(--bc) / 0.7)}.text-black{--un-text-opacity:1;color:rgb(0 0 0 / var(--un-text-opacity))}.text-blue-100,[text-blue-100=""]{--un-text-opacity:1;color:rgb(219 234 254 / var(--un-text-opacity))}.text-blue-200{--un-text-opacity:1;color:rgb(191 219 254 / var(--un-text-opacity))}.text-blue-500,[text-blue-500=""]{--un-text-opacity:1;color:rgb(59 130 246 / var(--un-text-opacity))}.text-blue-600,[text-blue-600=""]{--un-text-opacity:1;color:rgb(37 99 235 / var(--un-text-opacity))}.text-blue-700,[text-blue-700=""]{--un-text-opacity:1;color:rgb(29 78 216 / var(--un-text-opacity))}.text-blue-800{--un-text-opacity:1;color:rgb(30 64 175 / var(--un-text-opacity))}.text-error{--un-text-opacity:1;color:hsl(var(--er) / var(--un-text-opacity))}.text-gray-500{--un-text-opacity:1;color:rgb(107 114 128 / var(--un-text-opacity))}.text-gray-600{--un-text-opacity:1;color:rgb(75 85 99 / var(--un-text-opacity))}.text-gray-700,[text-gray-700=""]{--un-text-opacity:1;color:rgb(55 65 81 / var(--un-text-opacity))}.text-gray-900,[text-gray-900=""]{--un-text-opacity:1;color:rgb(17 24 39 / var(--un-text-opacity))}.text-green-500{--un-text-opacity:1;color:rgb(34 197 94 / var(--un-text-opacity))}.text-green-600,[text-green-600=""]{--un-text-opacity:1;color:rgb(22 163 74 / var(--un-text-opacity))}.text-green-700,[text-green-700=""]{--un-text-opacity:1;color:rgb(21 128 61 / var(--un-text-opacity))}.text-green-800{--un-text-opacity:1;color:rgb(22 101 52 / var(--un-text-opacity))}.text-indigo-500{--un-text-opacity:1;color:rgb(99 102 241 / var(--un-text-opacity))}.text-indigo-700{--un-text-opacity:1;color:rgb(67 56 202 / var(--un-text-opacity))}.text-orange-600{--un-text-opacity:1;color:rgb(234 88 12 / var(--un-text-opacity))}.text-purple-700,[text-purple-700=""]{--un-text-opacity:1;color:rgb(126 34 206 / var(--un-text-opacity))}.text-red-400{--un-text-opacity:1;color:rgb(248 113 113 / var(--un-text-opacity))}.text-red-500{--un-text-opacity:1;color:rgb(239 68 68 / var(--un-text-opacity))}.text-red-600,[text-red-600=""]{--un-text-opacity:1;color:rgb(220 38 38 / var(--un-text-opacity))}.text-red-700{--un-text-opacity:1;color:rgb(185 28 28 / var(--un-text-opacity))}.text-success{--un-text-opacity:1;color:hsl(var(--su) / var(--un-text-opacity))}.text-warning{--un-text-opacity:1;color:hsl(var(--wa) / var(--un-text-opacity))}.dark .dark\:color-white,.text-white,[text-white=""]{--un-text-opacity:1;color:rgb(255 255 255 / var(--un-text-opacity))}.text-yellow-600,[text-yellow-600=""]{--un-text-opacity:1;color:rgb(202 138 4 / var(--un-text-opacity))}.text-yellow-700,[text-yellow-700=""]{--un-text-opacity:1;color:rgb(161 98 7 / var(--un-text-opacity))}.text-yellow-800{--un-text-opacity:1;color:rgb(133 77 14 / var(--un-text-opacity))}.dark .dark\:hover\:text-white:hover{--un-text-opacity:1;color:rgb(255 255 255 / var(--un-text-opacity))}.hover\:text-accent-content:hover{--un-text-opacity:1;color:hsl(var(--ac) / var(--un-text-opacity))}.hover\:text-blue-500:hover{--un-text-opacity:1;color:rgb(59 130 246 / var(--un-text-opacity))}.hover\:text-blue-600:hover{--un-text-opacity:1;color:rgb(37 99 235 / var(--un-text-opacity))}.hover\:text-blue-700:hover{--un-text-opacity:1;color:rgb(29 78 216 / var(--un-text-opacity))}.hover\:text-blue-800:hover{--un-text-opacity:1;color:rgb(30 64 175 / var(--un-text-opacity))}.hover\:text-gray-500:hover{--un-text-opacity:1;color:rgb(107 114 128 / var(--un-text-opacity))}.hover\:text-gray-900:hover{--un-text-opacity:1;color:rgb(17 24 39 / var(--un-text-opacity))}.hover\:text-green-800:hover{--un-text-opacity:1;color:rgb(22 101 52 / var(--un-text-opacity))}.hover\:text-primary:hover{color:var(--c-primary)}.hover\:text-red-800:hover{--un-text-opacity:1;color:rgb(153 27 27 / var(--un-text-opacity))}.hover\:text-white:hover{--un-text-opacity:1;color:rgb(255 255 255 / var(--un-text-opacity))}.hover\:text-yellow-800:hover{--un-text-opacity:1;color:rgb(133 77 14 / var(--un-text-opacity))}[hover\:text-blue-800=""]:hover{--un-text-opacity:1;color:rgb(30 64 175 / var(--un-text-opacity))}[hover\:text-primary=""]:hover{color:var(--c-primary)}[hover\:text-red-800=""]:hover{--un-text-opacity:1;color:rgb(153 27 27 / var(--un-text-opacity))}.text-opacity-70{--un-text-opacity:0.7}.font-bold,[font-bold=""]{font-weight:700}.font-medium,[font-medium=""]{font-weight:500}.font-semibold,[font-semibold=""]{font-weight:600}.leading-6{line-height:1.5rem}.leading-7,[leading-7=""]{line-height:1.75rem}.leading-8{line-height:2rem}.leading-relaxed{line-height:1.625}.tracking-tight,[tracking-tight=""]{letter-spacing:-0.025em}.tracking-widest{letter-spacing:0.1em}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}.font-sans{font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"}.uppercase,[uppercase=""]{text-transform:uppercase}.lowercase,[lowercase=""]{text-transform:lowercase}.capitalize{text-transform:capitalize}.italic{font-style:italic}.underline{text-decoration-line:underline}.hover\:underline:hover{text-decoration-line:underline}.no-underline,[no-underline=""]{text-decoration:none}.opacity-0,[opacity-0=""]{opacity:0}.opacity-100,[opacity-100=""]{opacity:1}.opacity-25{opacity:0.25}.opacity-50{opacity:0.5}.opacity-60{opacity:0.6}.opacity-75{opacity:0.75}.disabled\:opacity-40:disabled{opacity:0.4}.disabled\:opacity-50:disabled{opacity:0.5}.shadow-2xl{--un-shadow:var(--un-shadow-inset) 0 25px 50px -12px var(--un-shadow-color,rgb(0 0 0 / 0.25));box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.shadow-md{--un-shadow:var(--un-shadow-inset) 0 4px 6px -1px var(--un-shadow-color,rgb(0 0 0 / 0.1)),var(--un-shadow-inset) 0 2px 4px -2px var(--un-shadow-color,rgb(0 0 0 / 0.1));box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.outline{outline-style:solid}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}[focus\:outline-none=""]:focus{outline:2px solid transparent;outline-offset:2px}.ring-2,[ring-2=""]{--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.focus\:ring-2:focus{--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}[focus\:ring-2=""]:focus{--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.focus\:ring-offset-2:focus{--un-ring-offset-width:2px}.ring-blue-500{--un-ring-opacity:1;--un-ring-color:rgb(59 130 246 / var(--un-ring-opacity))}.focus\:ring-blue-500:focus{--un-ring-opacity:1;--un-ring-color:rgb(59 130 246 / var(--un-ring-opacity))}.focus\:ring-red-600:focus{--un-ring-opacity:1;--un-ring-color:rgb(220 38 38 / var(--un-ring-opacity))}.focus\:ring-white:focus{--un-ring-opacity:1;--un-ring-color:rgb(255 255 255 / var(--un-ring-opacity))}[focus\:ring-blue-500=""]:focus{--un-ring-opacity:1;--un-ring-color:rgb(59 130 246 / var(--un-ring-opacity))}.focus\:ring-offset-red-100:focus{--un-ring-offset-opacity:1;--un-ring-offset-color:rgb(254 226 226 / var(--un-ring-offset-opacity))}.focus\:ring-offset-red-50:focus{--un-ring-offset-opacity:1;--un-ring-offset-color:rgb(254 242 242 / var(--un-ring-offset-opacity))}.focus\:ring-inset:focus{--un-ring-inset:inset}[focus\:ring-inset=""]:focus{--un-ring-inset:inset}.backdrop-blur-sm{--un-backdrop-blur:blur(4px);-webkit-backdrop-filter:var(--un-backdrop-blur) var(--un-backdrop-brightness) var(--un-backdrop-contrast) var(--un-backdrop-grayscale) var(--un-backdrop-hue-rotate) var(--un-backdrop-invert) var(--un-backdrop-opacity) var(--un-backdrop-saturate) var(--un-backdrop-sepia);backdrop-filter:var(--un-backdrop-blur) var(--un-backdrop-brightness) var(--un-backdrop-contrast) var(--un-backdrop-grayscale) var(--un-backdrop-hue-rotate) var(--un-backdrop-invert) var(--un-backdrop-opacity) var(--un-backdrop-saturate) var(--un-backdrop-sepia)}.filter,[filter=""]{filter:var(--un-blur) var(--un-brightness) var(--un-contrast) var(--un-drop-shadow) var(--un-grayscale) var(--un-hue-rotate) var(--un-invert) var(--un-saturate) var(--un-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms}.transition-all,[transition-all=""]{transition-property:all;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms}.transition-shadow{transition-property:box-shadow;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms}.transition-transform,[transition-transform=""]{transition-property:transform;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms}.duration-150{transition-duration:150ms}.duration-200,[duration-200=""]{transition-duration:200ms}.duration-300,[duration-300=""]{transition-duration:300ms}.ease,.ease-in-out{transition-timing-function:cubic-bezier(0.4,0,0.2,1)}.ease-in{transition-timing-function:cubic-bezier(0.4,0,1,1)}@media (min-width:640px){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:ml-3,[sm\:ml-3=""]{margin-left:0.75rem}.sm\:mr-6{margin-right:1.5rem}.sm\:mt-1,[sm\:mt-1=""]{margin-top:0.25rem}.sm\:flex-row,[sm\:flex-row=""]{flex-direction:row}.sm\:flex-wrap{flex-wrap:wrap}.sm\:items-center{align-items:center}.sm\:truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sm\:p-6{padding:1.5rem}.sm\:px-6,[sm\:px-6=""]{padding-left:1.5rem;padding-right:1.5rem}.sm\:text-6xl{font-size:3.75rem;line-height:1}.sm\:leading-9,[sm\:leading-9=""]{line-height:2.25rem}}@media (min-width:768px){.md\:col-span-2{grid-column:span 2/span 2}.md\:grid-cols-2,[md\:grid-cols-2=""]{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3,[md\:grid-cols-3=""]{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-4,[md\:grid-cols-4=""]{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:ml-2{margin-left:0.5rem}.md\:ml-4{margin-left:1rem}.md\:ml-6{margin-left:1.5rem}.md\:mt-0,[md\:mt-0=""]{margin-top:0}.md\:hidden,[md\:hidden=""]{display:none}.md\:flex,[md\:flex=""]{display:flex}.md\:items-center,[md\:items-center=""]{align-items:center}.md\:justify-between,[md\:justify-between=""]{justify-content:space-between}.md\:space-x-2>:not([hidden])~:not([hidden]){--un-space-x-reverse:0;margin-left:calc(0.5rem * calc(1 - var(--un-space-x-reverse)));margin-right:calc(0.5rem * var(--un-space-x-reverse))}.md\:space-x-3>:not([hidden])~:not([hidden]){--un-space-x-reverse:0;margin-left:calc(0.75rem * calc(1 - var(--un-space-x-reverse)));margin-right:calc(0.75rem * var(--un-space-x-reverse))}.md\:space-x-8>:not([hidden])~:not([hidden]){--un-space-x-reverse:0;margin-left:calc(2rem * calc(1 - var(--un-space-x-reverse)));margin-right:calc(2rem * var(--un-space-x-reverse))}.md\:p-12{padding:3rem}.md\:text-5xl{font-size:3rem;line-height:1}}@media (min-width:1024px){.lg\:static{position:static}.lg\:inset-0{inset:0}.lg\:col-span-1{grid-column:span 1/span 1}.lg\:col-span-2{grid-column:span 2/span 2}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-3,[lg\:grid-cols-3=""]{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:ml-64{margin-left:16rem}.lg\:mt-0{margin-top:0}.lg\:block{display:block}.lg\:hidden{display:none}.lg\:h-20{height:5rem}.lg\:max-w-6xl,[lg\:max-w-6xl=""]{max-width:72rem}.lg\:w-1\/3{width:33.3333333333%}.lg\:w-40{width:10rem}[lg\:w-1=""]{width:0.25rem}.lg\:translate-x-0{--un-translate-x:0;transform:translateX(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotateZ(var(--un-rotate-z)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z))}.lg\:border-t,[lg\:border-t=""]{border-top-width:1px}.lg\:px-8,[lg\:px-8=""]{padding-left:2rem;padding-right:2rem}.lg\:pl-8,[lg\:pl-8=""]{padding-left:2rem}}*{margin:0;padding:0;box-sizing:border-box}body{font-family:"Segoe UI",Tahoma,Geneva,Verdana,sans-serif;line-height:1.6;color:#333;background-color:#f8f9fa}.container{max-width:1200px;margin:0 auto;padding:0 20px}h1,h2,h3,h4,h5,h6{margin-bottom:1rem;color:#2c3e50}h1{font-size:2.5rem;font-weight:700}h2{font-size:2rem;font-weight:600}h3{font-size:1.5rem;font-weight:500}p{margin-bottom:1rem}.btn-icon{padding:2px 4px !important}.btn{display:inline-block;font-size:1rem;font-weight:500;text-decoration:none;border:none;border-radius:6px;cursor:pointer;transition:all 0.3s ease}.btn-primary{background-color:#3498db;color:white}.btn-primary:hover{background-color:#2980b9;transform:translateY(-2px)}.btn-secondary{background-color:#6c757d;color:white}.btn-secondary:hover{background-color:#5a6268}.btn-success{background-color:#28a745;color:white}.btn-success:hover{background-color:#218838}.card{background:white;border-radius:8px;box-shadow:0 2px 10px rgba(0,0,0,0.1);padding:20px;margin-bottom:20px;transition:transform 0.3s ease,box-shadow 0.3s ease}.card:hover{transform:translateY(-5px);box-shadow:0 4px 20px rgba(0,0,0,0.15)}.card-title{color:#2c3e50;margin-bottom:10px}.card-text{color:#666;line-height:1.5}.navbar{background-color:#2c3e50;padding:1rem 0;box-shadow:0 2px 4px rgba(0,0,0,0.1)}.navbar-brand{color:white;font-size:1.5rem;font-weight:700;text-decoration:none}.navbar-nav{display:flex;list-style:none;gap:2rem;margin-left:auto}.nav-link{color:#ecf0f1;text-decoration:none;transition:color 0.3s ease}.nav-link:hover{color:#3498db}.row{display:flex;flex-wrap:wrap;margin:0 -15px}.col{flex:1;padding:0 15px}.col-1{flex:0 0 8.333333%}.col-2{flex:0 0 16.666667%}.col-3{flex:0 0 25%}.col-4{flex:0 0 33.333333%}.col-6{flex:0 0 50%}.col-8{flex:0 0 66.666667%}.col-12{flex:0 0 100%}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.mt-1{margin-top:0.25rem}.mt-2{margin-top:0.5rem}.mt-3{margin-top:1rem}.mt-4{margin-top:1.5rem}.mt-5{margin-top:3rem}.mb-1{margin-bottom:0.25rem}.mb-2{margin-bottom:0.5rem}.mb-3{margin-bottom:1rem}.mb-4{margin-bottom:1.5rem}.mb-5{margin-bottom:3rem}.p-1{padding:0.25rem}.p-2{padding:0.5rem}.p-3{padding:1rem}.p-4{padding:1.5rem}.p-5{padding:3rem}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:6px}.alert-info{color:#0c5460;background-color:#d1ecf1;border-color:#bee5eb}.alert-success{color:#155724;background-color:#d4edda;border-color:#c3e6cb}.alert-warning{color:#856404;background-color:#fff3cd;border-color:#ffeaa7}.alert-danger{color:#721c24;background-color:#f8d7da;border-color:#f5c6cb}.form-group{margin-bottom:1rem}.form-label{display:block;margin-bottom:0.5rem;font-weight:500;color:#333}.form-control{display:block;width:100%;padding:0.75rem;font-size:1rem;border:1px solid #ced4da;border-radius:6px;transition:border-color 0.3s ease,box-shadow 0.3s ease}.form-control:focus{outline:none;border-color:#3498db;box-shadow:0 0 0 3px rgba(52,152,219,0.1)}@media (max-width:768px){.container{padding:0 15px}.row{flex-direction:column}.col{flex:1;margin-bottom:1rem}.navbar-nav{flex-direction:column;gap:1rem}h1{font-size:2rem}h2{font-size:1.5rem}}.fade-in{animation:fadeIn 0.5s ease-in}@keyframes fadeIn{from{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.slide-up{animation:slideUp 0.6s ease-out}@keyframes slideUp{from{opacity:0;transform:translateY(30px)}to{opacity:1;transform:translateY(0)}}.static-file-badge{position:fixed;bottom:20px;right:20px;background-color:#28a745;color:white;padding:8px 12px;border-radius:20px;font-size:0.8rem;box-shadow:0 2px 10px rgba(0,0,0,0.2);z-index:1000}.static-file-badge:before{content:"📁 "}.contact-page-tailwind-style .tailwind-form-style{}.contact-page-tailwind-style .tailwind-form-style .unified-contact-form{background:transparent;border:none;padding:0;margin:0}.contact-page-tailwind-style .tailwind-form-style .form-header{display:none}.contact-page-tailwind-style .tailwind-form-style form{display:flex;flex-direction:column;gap:1.5rem}.contact-page-tailwind-style .tailwind-form-style .unified-form-field{margin-bottom:0}.contact-page-tailwind-style .tailwind-form-style label{display:block;font-size:0.875rem;font-weight:600;color:rgb(17 24 39);margin-bottom:0.5rem}.dark .contact-page-tailwind-style .tailwind-form-style label{color:rgb(249 250 251)}.contact-page-tailwind-style .tailwind-form-style input,.contact-page-tailwind-style .tailwind-form-style textarea{width:100%;padding:0.75rem 1rem;border:2px solid rgb(229 231 235);border-radius:0.5rem;background-color:rgb(255 255 255);color:rgb(17 24 39);outline:none;transition:all 0.2s ease-in-out;font-size:1rem}.dark .contact-page-tailwind-style .tailwind-form-style input,.dark .contact-page-tailwind-style .tailwind-form-style textarea{border-color:rgb(75 85 99);background-color:rgb(55 65 81);color:rgb(249 250 251)}.contact-page-tailwind-style .tailwind-form-style input:hover,.contact-page-tailwind-style .tailwind-form-style textarea:hover{border-color:rgb(209 213 219)}.dark .contact-page-tailwind-style .tailwind-form-style input:hover,.dark .contact-page-tailwind-style .tailwind-form-style textarea:hover{border-color:rgb(107 114 128)}.contact-page-tailwind-style .tailwind-form-style input:focus,.contact-page-tailwind-style .tailwind-form-style textarea:focus{border-color:rgb(59 130 246);box-shadow:0 0 0 2px rgb(59 130 246 / 0.2)}.contact-page-tailwind-style .tailwind-form-style textarea{resize:vertical;min-height:6rem}.contact-page-tailwind-style .tailwind-form-style .field-group-horizontal{display:grid;grid-template-columns:1fr;gap:1.5rem}@media (min-width:768px){.contact-page-tailwind-style .tailwind-form-style .field-group-horizontal{grid-template-columns:1fr 1fr}}.contact-page-tailwind-style .tailwind-form-style button[type="submit"]{background:linear-gradient(to right,rgb(37 99 235),rgb(147 51 234));color:white;padding:0.75rem 2rem;border-radius:0.5rem;font-weight:600;font-size:1.125rem;box-shadow:0 10px 15px -3px rgb(0 0 0 / 0.1),0 4px 6px -2px rgb(0 0 0 / 0.05);transition:all 0.3s ease;border:none;cursor:pointer;min-width:200px}.contact-page-tailwind-style .tailwind-form-style button[type="submit"]:hover{background:linear-gradient(to right,rgb(29 78 216),rgb(126 34 206));box-shadow:0 20px 25px -5px rgb(0 0 0 / 0.1),0 8px 10px -6px rgb(0 0 0 / 0.1);transform:scale(1.05)}.contact-page-tailwind-style .tailwind-form-style button[type="submit"]:focus{outline:2px solid transparent;outline-offset:2px;box-shadow:0 0 0 2px rgb(59 130 246)}.contact-page-tailwind-style .tailwind-form-style .submit-button-container{text-align:center;padding-top:1rem}.contact-page-tailwind-style .tailwind-form-style .field-error{color:rgb(239 68 68);font-size:0.875rem;margin-top:0.25rem}.contact-page-tailwind-style .tailwind-form-style .form-success{background-color:rgb(220 252 231);border:1px solid rgb(34 197 94);color:rgb(21 128 61);padding:1rem;border-radius:0.5rem;margin-bottom:1rem}.dark .contact-page-tailwind-style .tailwind-form-style .form-success{background-color:rgb(21 128 61 / 0.2);border-color:rgb(34 197 94);color:rgb(187 247 208)}.contact-page-tailwind-style .tailwind-form-style .form-submitting button[type="submit"]{opacity:0.7;cursor:not-allowed} \ No newline at end of file diff --git a/site/site/public/styles/custom.css b/site/site/public/styles/custom.css new file mode 100644 index 0000000..a2139c6 --- /dev/null +++ b/site/site/public/styles/custom.css @@ -0,0 +1,1269 @@ +/* Brand typography — Inter (UI) + JetBrains Mono (code/ids). @import must + precede every style rule (comments excepted), so it stays at the very top. + Mirrors the font stack of outreach/web so the site About matches it. */ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap'); + +/* Custom CSS file for static file serving example */ + +/* Reset and base styles */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; + line-height: 1.6; + color: #333; + background-color: #f8f9fa; +} + +/* Container */ +.container { + max-width: 1200px; + margin: 0 auto; + padding: 0 20px; +} + +/* Typography */ +h1, +h2, +h3, +h4, +h5, +h6 { + margin-bottom: 1rem; + color: #2c3e50; +} + +h1 { + font-size: 2.5rem; + font-weight: 700; +} + +h2 { + font-size: 2rem; + font-weight: 600; +} + +h3 { + font-size: 1.5rem; + font-weight: 500; +} + +p { + margin-bottom: 1rem; +} + +/* Buttons */ +.btn-icon { + padding: 2px 4px !important; +} + +.btn { + display: inline-block; + /* padding: 12px 24px; */ + font-size: 1rem; + font-weight: 500; + text-decoration: none; + border: none; + border-radius: 6px; + cursor: pointer; + transition: all 0.3s ease; +} + +.btn-primary { + background-color: #3498db; + color: white; +} + +.btn-primary:hover { + background-color: #2980b9; + transform: translateY(-2px); +} + +.btn-secondary { + background-color: #6c757d; + color: white; +} + +.btn-secondary:hover { + background-color: #5a6268; +} + +.btn-success { + background-color: #28a745; + color: white; +} + +.btn-success:hover { + background-color: #218838; +} + +/* Cards */ +.card { + background: white; + border-radius: 8px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + padding: 20px; + margin-bottom: 20px; + transition: + transform 0.3s ease, + box-shadow 0.3s ease; +} + +.card:hover { + transform: translateY(-5px); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); +} + +.card-title { + color: #2c3e50; + margin-bottom: 10px; +} + +.card-text { + color: #666; + line-height: 1.5; +} + +/* Navigation */ +.navbar { + background-color: #2c3e50; + padding: 1rem 0; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.navbar-brand { + color: white; + font-size: 1.5rem; + font-weight: 700; + text-decoration: none; +} + +.navbar-nav { + display: flex; + list-style: none; + gap: 2rem; + margin-left: auto; +} + +.nav-link { + color: #ecf0f1; + text-decoration: none; + transition: color 0.3s ease; +} + +.nav-link:hover { + color: #3498db; +} + +/* Grid system */ +.row { + display: flex; + flex-wrap: wrap; + margin: 0 -15px; +} + +.col { + flex: 1; + padding: 0 15px; +} + +.col-1 { + flex: 0 0 8.333333%; +} +.col-2 { + flex: 0 0 16.666667%; +} +.col-3 { + flex: 0 0 25%; +} +.col-4 { + flex: 0 0 33.333333%; +} +.col-6 { + flex: 0 0 50%; +} +.col-8 { + flex: 0 0 66.666667%; +} +.col-12 { + flex: 0 0 100%; +} + +/* Utilities */ +.text-center { + text-align: center; +} + +.text-left { + text-align: left; +} + +.text-right { + text-align: right; +} + +.mt-1 { + margin-top: 0.25rem; +} +.mt-2 { + margin-top: 0.5rem; +} +.mt-3 { + margin-top: 1rem; +} +.mt-4 { + margin-top: 1.5rem; +} +.mt-5 { + margin-top: 3rem; +} + +.mb-1 { + margin-bottom: 0.25rem; +} +.mb-2 { + margin-bottom: 0.5rem; +} +.mb-3 { + margin-bottom: 1rem; +} +.mb-4 { + margin-bottom: 1.5rem; +} +.mb-5 { + margin-bottom: 3rem; +} + +.p-1 { + padding: 0.25rem; +} +.p-2 { + padding: 0.5rem; +} +.p-3 { + padding: 1rem; +} +.p-4 { + padding: 1.5rem; +} +.p-5 { + padding: 3rem; +} + +/* Alerts */ +.alert { + padding: 15px; + margin-bottom: 20px; + border: 1px solid transparent; + border-radius: 6px; +} + +.alert-info { + color: #0c5460; + background-color: #d1ecf1; + border-color: #bee5eb; +} + +.alert-success { + color: #155724; + background-color: #d4edda; + border-color: #c3e6cb; +} + +.alert-warning { + color: #856404; + background-color: #fff3cd; + border-color: #ffeaa7; +} + +.alert-danger { + color: #721c24; + background-color: #f8d7da; + border-color: #f5c6cb; +} + +/* Forms */ +.form-group { + margin-bottom: 1rem; +} + +.form-label { + display: block; + margin-bottom: 0.5rem; + font-weight: 500; + color: #333; +} + +.form-control { + display: block; + width: 100%; + padding: 0.75rem; + font-size: 1rem; + border: 1px solid #ced4da; + border-radius: 6px; + transition: + border-color 0.3s ease, + box-shadow 0.3s ease; +} + +.form-control:focus { + outline: none; + border-color: #3498db; + box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1); +} + +/* Responsive design */ +@media (max-width: 768px) { + .container { + padding: 0 15px; + } + + .row { + flex-direction: column; + } + + .col { + flex: 1; + margin-bottom: 1rem; + } + + .navbar-nav { + flex-direction: column; + gap: 1rem; + } + + h1 { + font-size: 2rem; + } + + h2 { + font-size: 1.5rem; + } +} + +/* Animation utilities */ +.fade-in { + animation: fadeIn 0.5s ease-in; +} + +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.slide-up { + animation: slideUp 0.6s ease-out; +} + +@keyframes slideUp { + from { + opacity: 0; + transform: translateY(30px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* Static file serving indicator */ +.static-file-badge { + position: fixed; + bottom: 20px; + right: 20px; + background-color: #28a745; + color: white; + padding: 8px 12px; + border-radius: 20px; + font-size: 0.8rem; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); + z-index: 1000; +} + +.static-file-badge:before { + content: "📁 "; +} + +/* All images inside post body: never overflow the column. */ +.post-content-body img { + max-width: 100%; + height: auto; +} + +/* Post body: lead thumbnail image rendered from markdown (first paragraph, lone img) */ +.post-content-body > p:first-child > img:only-child { + width: 100%; + max-height: 24rem; + object-fit: contain; + border-radius: 0.5rem; + margin-bottom: 1rem; +} + +/* Hide duplicate H1 title from markdown body — already shown in the header section. + Covers two layouts: + - thumbnail-first:

    → adjacent sibling after first p + - no thumbnail:

    as first child */ +.post-content-body > p:first-child + h1, +.post-content-body > h1:first-child { + display: none; +} + +/* Content grid card thumbnail */ +.grid-post-figure { + max-height: 11rem; +} + +.grid-post-img { + height: 100%; + max-height: 11rem; + object-fit: contain; + margin: 0 auto; +} + +/* Theme-aware thumbnails: show light img on light theme, dark img on dark theme */ +.grid-post-img-dark { + display: none; +} + +[data-theme="dark"] .grid-post-img-light { + display: none; +} + +[data-theme="dark"] .grid-post-img-dark { + display: block; +} + +/* ── Content graph mini ────────────────────────────────────────────────────── */ + +/* Constrain graph to sidebar width; prevent overflow on mobile */ +.content-graph-mini-wrapper { + max-width: 280px; +} + +/* Preview: scales SVG to wrapper width, zoom-in cursor */ +.content-graph-mini-preview { + cursor: zoom-in; + border-radius: 0.5rem; + overflow: hidden; + transition: opacity 0.15s; +} + +.content-graph-mini-preview:hover { + opacity: 0.85; +} + +.content-graph-mini-preview svg { + width: 100%; + height: auto; + display: block; +} + +/* Dark-mode overrides for SVG CSS custom properties. + The SVG embeds + + diff --git a/site/site/templates/build-tools/README.md b/site/site/templates/build-tools/README.md new file mode 100644 index 0000000..d0bacb8 --- /dev/null +++ b/site/site/templates/build-tools/README.md @@ -0,0 +1,86 @@ +# Site-Info Documentation Templates + +This directory contains the Tera templates used by the site-info system to generate documentation from code analysis. + +## Structure + +``` +site-info/ +├── documentation/ # Main page templates +│ ├── base.tera # Base layout template +│ ├── summary.tera # Summary pages with metrics +│ ├── reference.tera # Detailed reference docs +│ ├── automation.tera # Automation guide +│ └── top_level.tera # Top-level overview +├── partials/ # Reusable components +│ ├── route_table.tera # Route information table +│ ├── component_card.tera # Component display card +│ ├── source_link.tera # Source reference links +│ ├── metrics_summary.tera # Statistics summary +│ └── nav_breadcrumbs.tera # Navigation breadcrumbs +├── i18n/ # Language-specific templates +│ ├── en/ # English templates +│ └── es/ # Spanish templates +└── README.md # This file +``` + +## Configuration + +Templates are configured in `config/site-info.toml`: + +```toml +[templates] +engine = "tera" +templates_path = "site/templates/site-info" +default_language = "en" +supported_languages = ["en", "es"] +``` + +## Internationalization + +Labels and text are loaded from FTL files in: +- `site/content/i18n/site-info/en/templates.ftl` (English) +- `site/content/i18n/site-info/es/templates.ftl` (Spanish) + +## Template Context + +Templates receive a `DocumentationContext` with: + +- `doc.routes` - API route information +- `doc.components` - Component definitions +- `doc.pages` - Page route configurations +- `i18n.labels` - Localized text labels +- `summary` - Statistical information +- `metadata` - Generation metadata +- `theme` - Display configuration + +## Custom Filters + +Available Tera filters: + +- `anchor` - Generate kebab-case anchors +- `source_link` - Format source reference links +- `pluralize` - Handle singular/plural forms +- `method_color` - HTTP method color coding + +## Custom Functions + +Available Tera functions: + +- `toc(items)` - Generate table of contents +- `format_number(number)` - Format numbers with commas + +## Environment Variables + +- `SITE_TEMPLATES_PATH` - Base templates directory (default: "site/templates") +- `SITE_I18N_PATH` - I18n content directory (default: "site/content") +- `SITE_CONFIG_PATH` - Configuration directory (default: "config") + +## Usage + +Templates are automatically loaded by the site-info build system. To customize: + +1. Edit templates in this directory +2. Update FTL files for text changes +3. Modify `config/site-info.toml` for settings +4. Rebuild the project to regenerate documentation \ No newline at end of file diff --git a/site/site/templates/build-tools/documentation/automation.tera b/site/site/templates/build-tools/documentation/automation.tera new file mode 100644 index 0000000..aa96c13 --- /dev/null +++ b/site/site/templates/build-tools/documentation/automation.tera @@ -0,0 +1,190 @@ +{% extends "documentation/base.tera" %} + +{% block title %}{{ i18n.labels.title_automation }}{% endblock title %} + +{% block toc %} +## {{ i18n.labels.table_of_contents }} + +- [{{ i18n.labels.overview }}](#overview) +- [Rustelo Manager Integration](#rustelo-manager) +- [MCP Server Integration](#mcp-server) +- [API Client Generation](#api-client) +- [Testing Automation](#testing) +- [Documentation Generation](#docs-generation) +- [CI/CD Integration](#ci-cd) + +{% endblock toc %} + +{% block content %} + +## {{ i18n.labels.overview }} {% raw %}{#overview}{% endraw %} + +The Rustelo route documentation system generates comprehensive metadata that powers numerous automation scenarios beyond basic API client generation. This system is designed to be the single source of truth for all routing, component, and page information in your application. + +## 🚀 Automation Possibilities + +### 1. Rustelo Manager Integration {% raw %}{#rustelo-manager}{% endraw %} + +The generated documentation directly powers the **Rustelo Manager** dashboard: + +- **Route Discovery**: Automatically populates available routes for navigation +- **Component Inspector**: Live component documentation and prop validation +- **Page Editor**: Dynamic form generation based on page route configurations +- **Feature Detection**: Shows which features are enabled for each route + +```rust +// Example: Manager uses generated data for route management +let routes = load_route_data("site/info/server/data.toml"); +manager.populate_routes(routes); +``` + +### 2. MCP Server Integration {% raw %}{#mcp-server}{% endraw %} + +Transform your Rustelo app into an **MCP (Model Context Protocol) Server**: + +- **Route Endpoints**: Expose API routes as MCP tools +- **Component Library**: Share component definitions with AI assistants +- **Page Templates**: Generate new pages using existing patterns +- **Real-time Updates**: Live documentation updates via MCP + +```rust +// Example: MCP tool for route information +#[mcp_tool] +fn get_route_info(path: &str) -> Result { + let routes = load_generated_routes()?; + routes.find_by_path(path).ok_or_else(|| McpError::NotFound) +} +``` + +### 3. API Client Generation {% raw %}{#api-client}{% endraw %} + +Generate type-safe API clients from route documentation: + +{% if doc.routes %} +**Available Routes for Client Generation:** + +{% for route in doc.routes %} +{% if route.methods %} +- `{{ route.path }}` - {{ route.handler }} +{% endif %} +{% endfor %} +{% endif %} + +```typescript +// Generated TypeScript client +export class RusteloApiClient { +{% for route in doc.routes %} +{% if route.methods %} + async {{ route.handler }}(): Promise<{{ route.response_type }}> { + return this.request('{{ route.path }}', 'GET'); + } +{% endif %} +{% endfor %} +} +``` + +### 4. Testing Automation {% raw %}{#testing}{% endraw %} + +Automated test generation from route specifications: + +```rust +// Generated integration tests +{% for route in doc.routes %} +{% if route %} +#[tokio::test] +async fn test_route_{{ loop.index }}() { + let app = create_test_app().await; + let response = app + .oneshot(Request::builder() + .uri("{{ route.path }}") + .method("{{ route.methods | first }}") + .body(Body::empty())?) + .await?; + + assert_eq!(response.status(), StatusCode::OK); +} +{% endif %} +{% endfor %} +``` + +### 5. Documentation Generation {% raw %}{#docs-generation}{% endraw %} + +Multi-format documentation from the same source: + +- **OpenAPI/Swagger**: Auto-generated API specs +- **Markdown**: Human-readable documentation (this file!) +- **JSON Schema**: Machine-readable specifications +- **Postman Collections**: Ready-to-use API collections + +### 6. CI/CD Integration {% raw %}{#ci-cd}{% endraw %} + +Automate deployment and validation: + +```yaml +# GitHub Actions integration +- name: Validate Route Documentation + run: | + cargo run --bin site-info-validator + +- name: Generate API Documentation + run: | + cargo run --bin docs-generator --format openapi + +- name: Update Postman Collection + run: | + cargo run --bin postman-generator +``` + +## 📊 Current System Statistics + +Based on the current route analysis: + +- **{{ summary.total_routes }}** total routes available for automation +- **{{ summary.api_routes }}** API routes for client generation +- **{{ summary.static_routes }}** static routes for optimization +- **{{ summary.total_components }}** components for template generation +- **{{ summary.total_pages }}** pages across **{{ summary.languages | length }}** languages + +## 🔧 Integration Examples + +### Route Validation Pipeline + +```rust +use site_info::route_analysis::RouteDocumentation; + +// Validate all routes are documented +fn validate_documentation_completeness() -> Result<(), ValidationError> { + let docs = RouteDocumentation::load_from_analysis()?; + + // Check all routes have descriptions + for route in &docs.api_routes { + if route.description.is_none() { + eprintln!("Warning: Route {} lacks description", route.path); + } + } + + // Validate auth requirements consistency + validate_auth_consistency(&docs.api_routes)?; + + Ok(()) +} +``` + +### Dynamic Menu Generation + +```rust +// Generate navigation menus from page routes +fn generate_navigation_menu(language: &str) -> Result { + let docs = RouteDocumentation::load_from_analysis()?; + let pages = docs.page_routes + .iter() + .filter(|p| p.language == language && p.enabled) + .collect(); + + NavMenu::from_pages(pages) +} +``` + +This automation system transforms static route definitions into a powerful foundation for development tooling, testing, and deployment automation. + +{% endblock content %} \ No newline at end of file diff --git a/site/site/templates/build-tools/documentation/base.tera b/site/site/templates/build-tools/documentation/base.tera new file mode 100644 index 0000000..731bc90 --- /dev/null +++ b/site/site/templates/build-tools/documentation/base.tera @@ -0,0 +1,11 @@ +{% include "partials/header.tera" %} + +# {% block title %}{{ i18n.labels.title }}{% endblock title %} + +{% if theme.show_toc %} +{% block toc %}{% endblock toc %} +{% endif %} + +{% block content %}{% endblock content %} + +{% include "partials/footer.tera" %} \ No newline at end of file diff --git a/site/site/templates/build-tools/documentation/reference.tera b/site/site/templates/build-tools/documentation/reference.tera new file mode 100644 index 0000000..0fadf95 --- /dev/null +++ b/site/site/templates/build-tools/documentation/reference.tera @@ -0,0 +1,125 @@ +{% extends "documentation/base.tera" %} + +{% block title %}{{ i18n.labels.title_server_reference }}{% endblock title %} + +{% block toc %} +## {{ i18n.labels.table_of_contents }} + +{% if doc.routes %} +- [{{ i18n.labels.routes }}](#routes) +{% for route in doc.routes %} + - [{{ route.path }}](#{{ route.anchor }}) +{% endfor %} +{% endif %} + +{% if doc.components %} +- [{{ i18n.labels.components }}](#components) +{% for component in doc.components %} + - [{{ component.name }}](#{{ component.anchor }}) +{% endfor %} +{% endif %} + +{% if doc.pages %} +- [{{ i18n.labels.pages }}](#pages) +{% for page in doc.pages %} + - [{{ page.path }}](#{{ page.anchor }}) +{% endfor %} +{% endif %} + +{% endblock toc %} + +{% block content %} + +{% if doc.routes %} +## {{ i18n.labels.routes }} {% raw %}{#routes}{% endraw %} + +{% for route in doc.routes %} +### {{ route.path }} {% raw %}{#{% endraw %}{{ route.anchor }}{% raw %}}{% endraw %}} + +{% if route.src_ref %} +{% include "partials/source_link.tera" %} +{% endif %} + +**{{ i18n.labels.methods }}:** {% for method in route.methods %}`{{ method }}`{% if not loop.last %}, {% endif %}{% endfor %} + +**{{ i18n.labels.handler }}:** `{{ route.handler }}` + +**{{ i18n.labels.module }}:** `{{ route.module }}` + +**{{ i18n.labels.response_type }}:** {{ route.response_type }} + +**{{ i18n.labels.requires_auth }}:** {{ route.requires_auth }} + +{% if route.middleware %} +**{{ i18n.labels.middleware }}:** {% for mw in route.middleware %}`{{ mw }}`{% if not loop.last %}, {% endif %}{% endfor %} +{% endif %} + +{% if route.parameters %} +**{{ i18n.labels.parameters }}:** + +| Name | Type | Source | Optional | +|---|---|---|---| +{% for param in route.parameters %} +| `{{ param.name }}` | `{{ param.param_type }}` | {{ param.source }} | {{ param.optional }} | +{% endfor %} +{% endif %} + +{% if route.description %} +**{{ i18n.labels.description }}:** {{ route.description }} +{% endif %} + +--- + +{% endfor %} +{% endif %} + +{% if doc.components %} +## {{ i18n.labels.components }} {% raw %}{#components}{% endraw %} + +{% for component in doc.components %} +{% include "partials/component_card.tera" %} +{% endfor %} +{% endif %} + +{% if doc.pages %} +## {{ i18n.labels.pages }} {% raw %}{#pages}{% endraw %} + +{% for page in doc.pages %} +#### {{ page.path }} {% raw %}{#{% endraw %}{{ page.anchor }}{% raw %}}{% endraw %}} + +{% if page.src_ref %} +{% include "partials/source_link.tera" %} +{% endif %} + +**Component:** `{{ page.component }}` + +**Page Component:** `{{ page.page_component }}` + +**Unified Component:** `{{ page.unified_component }}` + +{% if page.module_path %} +**{{ i18n.labels.module }}:** `{{ page.module_path }}` +{% endif %} + +**{{ i18n.labels.enabled }}:** {{ page.enabled }} + +**{{ i18n.labels.priority }}:** {{ page.priority }} + +{% if page.requires_auth is defined %} +**{{ i18n.labels.requires_auth }}:** {{ page.requires_auth }} +{% endif %} + +{% if page.menu_group %} +**Menu Group:** {{ page.menu_group }} +{% endif %} + +{% if page.keywords %} +**{{ i18n.labels.keywords }}:** {{ page.keywords | join(sep=", ") }} +{% endif %} + +--- + +{% endfor %} +{% endif %} + +{% endblock content %} \ No newline at end of file diff --git a/site/site/templates/build-tools/documentation/summary.tera b/site/site/templates/build-tools/documentation/summary.tera new file mode 100644 index 0000000..464caf5 --- /dev/null +++ b/site/site/templates/build-tools/documentation/summary.tera @@ -0,0 +1,71 @@ +{% extends "documentation/base.tera" %} + +{% block title %}{{ i18n.labels.title_server_summary }}{% endblock title %} + +{% block toc %} +## {{ i18n.labels.table_of_contents }} + +- [{{ i18n.labels.overview }}](#overview) +{% if doc.routes %} +- [{{ i18n.labels.routes }}](#routes) +{% endif %} +{% if doc.components %} +- [{{ i18n.labels.components }}](#components) +{% endif %} +{% if doc.pages %} +- [{{ i18n.labels.pages }}](#pages) +{% endif %} +- [{{ i18n.labels.summary }}](#statistics) + +{% endblock toc %} + +{% block content %} + +## {{ i18n.labels.overview }} {% raw %}{#overview}{% endraw %} + +{% include "partials/metrics_summary.tera" %} + +{% if doc.routes %} +## {{ i18n.labels.routes }} {% raw %}{#routes}{% endraw %} + +{% include "partials/route_table.tera" %} + +**Quick Navigation:** +{% for route in doc.routes %} +- [{{ route.path }}](reference.md#{{ route.anchor }}) +{% endfor %} +{% endif %} + +{% if doc.components %} +## {{ i18n.labels.components }} {% raw %}{#components}{% endraw %} + +| {{ i18n.labels.components }} | Type | {{ i18n.labels.module }} | +|---|---|---| +{% for component in doc.components %} +| [{{ component.name }}](reference.md#{{ component.anchor }}) | {{ component.component_type }} | `{{ component.module_path }}` | +{% endfor %} +{% endif %} + +{% if doc.pages %} +## {{ i18n.labels.pages }} {% raw %}{#pages}{% endraw %} + +### {{ i18n.labels.languages | upper }} + +{% for page in doc.pages %} +- [{{ page.path }}](reference.md#{{ page.anchor }}) ({{ page.language | upper }}) - {{ page.component }} +{% endfor %} +{% endif %} + +## {{ i18n.labels.summary }} {% raw %}{#statistics}{% endraw %} + +| Metric | Count | +|---|---| +| {{ i18n.labels.total_routes }} | {{ summary.total_routes }} | +| {{ i18n.labels.total_components }} | {{ summary.total_components }} | +| {{ i18n.labels.total_pages }} | {{ summary.total_pages }} | +| {{ i18n.labels.auth_required }} | {{ summary.auth_required_routes }} | +| {{ i18n.labels.static_routes }} | {{ summary.static_routes }} | +| {{ i18n.labels.api_routes }} | {{ summary.api_routes }} | +| {{ i18n.labels.languages }} | {{ summary.languages | join(sep=", ") }} | + +{% endblock content %} \ No newline at end of file diff --git a/site/site/templates/build-tools/documentation/top_level.tera b/site/site/templates/build-tools/documentation/top_level.tera new file mode 100644 index 0000000..1cb6db4 --- /dev/null +++ b/site/site/templates/build-tools/documentation/top_level.tera @@ -0,0 +1,144 @@ +{% extends "documentation/base.tera" %} + +{% block title %}{{ i18n.labels.title_top_level }}{% endblock title %} + +{% block toc %} +## {{ i18n.labels.table_of_contents }} + +- [{{ i18n.labels.overview }}](#overview) +- [🔧 Server Routes](#server-routes) +- [📄 Page Routes](#page-routes) +- [🧩 Components](#components) +- [🤖 Automation](#automation) +- [📊 Statistics](#statistics) + +{% endblock toc %} + +{% block content %} + +## {{ i18n.labels.overview }} {% raw %}{#overview}{% endraw %} + +This is a comprehensive documentation system for the **{{ metadata.project_name }}** web application. The documentation is automatically generated from code analysis and provides detailed information about routes, components, and pages. + +### Quick Navigation + +| Section | {{ i18n.labels.summary }} | Reference | Description | +|---------|---------|-----------|-------------| +| **🔧 Server** | [server/summary.md](server/summary.md) | [server/reference.md](server/reference.md) | API routes, handlers, and middleware | +| **📄 Pages** | [pages/summary.md](pages/summary.md) | [pages/reference.md](pages/reference.md) | Page routes and components | +| **🧩 Components** | [components/summary.md](components/summary.md) | [components/reference.md](components/reference.md) | Reusable UI components | + +--- + +## 🔧 Server Routes {% raw %}{#server-routes}{% endraw %} + +**{{ summary.total_routes }}** total routes | **{{ summary.api_routes }}** API routes | **{{ summary.static_routes }}** static routes + +### Route Categories + +{# Routes grouped by auth requirement #} + +- **🔒 Authentication Required**: {{ auth_routes | length }} routes +- **🌐 Public Access**: {{ public_routes | length }} routes +- **📁 Static Files**: {{ summary.static_routes }} routes + +### Popular Routes + +{% for route in doc.routes %} +- [`{{ route.path }}`](server/reference.md#{{ route.anchor }}) - {{ route.response_type }} +{% endfor %} + +[**View all server routes →**](server/reference.md) + +--- + +## 📄 Page Routes {% raw %}{#page-routes}{% endraw %} + +**{{ summary.total_pages }}** pages across **{{ summary.languages | length }}** languages + +### By Language + +**Multiple languages supported** + +### Recent Pages + +{% for page in doc.pages %} +- [`{{ page.path }}`](pages/reference.md#{{ page.anchor }}) ({{ page.language | upper }}) - {{ page.component }} +{% endfor %} + +[**View all page routes →**](pages/reference.md) + +--- + +## 🧩 Components {% raw %}{#components}{% endraw %} + +**{{ summary.total_components }}** reusable components + +### Component Types + +**Various component types available** + +### Key Components + +{% for component in doc.components %} +- [`{{ component.name }}`](components/reference.md#{{ component.anchor }}) - {{ component.component_type }} +{% endfor %} + +[**View all components →**](components/reference.md) + +--- + +## 🤖 Automation {% raw %}{#automation}{% endraw %} + +This documentation system powers several automation features: + +### Available Integrations + +- **🎛️ Rustelo Manager** - Live route management and editing +- **🔌 MCP Server** - AI assistant integration via Model Context Protocol +- **📝 API Clients** - Auto-generated TypeScript/Rust clients +- **🧪 Test Generation** - Automated integration tests +- **📋 OpenAPI Specs** - Swagger/Postman collections + +### Quick Actions + +```bash +# Generate API client +cargo run --bin api-client-generator --lang typescript + +# Export to OpenAPI +cargo run --bin openapi-exporter --output api-spec.yaml + +# Validate documentation +cargo run --bin docs-validator +``` + +[**View automation guide →**](automation/reference.md) + +--- + +## 📊 Statistics {% raw %}{#statistics}{% endraw %} + +### System Overview + +| Metric | Value | Notes | +|--------|-------|-------| +| **{{ i18n.labels.total_routes }}** | {{ summary.total_routes }} | Including API and static routes | +| **{{ i18n.labels.total_components }}** | {{ summary.total_components }} | Reusable UI components | +| **{{ i18n.labels.total_pages }}** | {{ summary.total_pages }} | Across all languages | +| **{{ i18n.labels.languages }}** | {{ summary.languages | join(sep=", ") }} | Supported languages | +| **{{ i18n.labels.auth_required }}** | {{ summary.auth_required_routes }} | Routes requiring authentication | + +### Documentation Coverage + +- ✅ **Route Documentation**: {{ (summary.total_routes / summary.total_routes * 100) | round }}% +- ✅ **Component Documentation**: {{ (summary.total_components / summary.total_components * 100) | round }}% +- ✅ **Page Documentation**: {{ (summary.total_pages / summary.total_pages * 100) | round }}% + +--- + +*Last updated: {{ metadata.generated_at }}* + +**Need help?** Check the [automation guide](automation/reference.md) for integration examples and advanced usage. + +{% endblock content %} \ No newline at end of file diff --git a/site/site/templates/build-tools/partials/component_card.tera b/site/site/templates/build-tools/partials/component_card.tera new file mode 100644 index 0000000..4d6999b --- /dev/null +++ b/site/site/templates/build-tools/partials/component_card.tera @@ -0,0 +1,26 @@ +### {{ component.name }} {% raw %}{#{% endraw %}{{ component.anchor }}{% raw %}}{% endraw %}} + +{% if component.src_ref %} +{% include "partials/source_link.tera" %} +{% endif %} + +**Type:** {{ component.component_type }} + +**{{ i18n.labels.module }}:** `{{ component.module_path }}` + +{% if component.props %} +**Props:** {% for prop in component.props %}`{{ prop }}`{% if not loop.last %}, {% endif %}{% endfor %} +{% endif %} + +{% if component.description %} +**{{ i18n.labels.description }}:** {{ component.description }} +{% endif %} + +{% if component.usage_example %} +**Usage Example:** +```rust +{{ component.usage_example }} +``` +{% endif %} + +--- \ No newline at end of file diff --git a/site/site/templates/build-tools/partials/footer.tera b/site/site/templates/build-tools/partials/footer.tera new file mode 100644 index 0000000..e1d3d57 --- /dev/null +++ b/site/site/templates/build-tools/partials/footer.tera @@ -0,0 +1,15 @@ +{# Site footer partial with Rustelo branding and generation info #} + +--- + +
    + +*{{ i18n.labels.generated_at }}: **{{ metadata.generated_at }}*** + +[![Rustelo Logo]({{ rustelo_logo_path | default(value="/logos/projects/rustelo_dev-logo-b-v.svg") }})]({{ rustelo_url | default(value="https://rustelo.dev") }}) + +**{{ i18n.labels.made_with }} [Rustelo]({{ rustelo_url | default(value="https://rustelo.dev") }})** - {{ i18n.labels.rustelo_tagline | default(value="Modern Rust Web Framework") }} + +*{{ i18n.labels.rustelo_description | default(value="Blazingly fast, type-safe web applications with Rust + Leptos + Axum") }}* + +
    \ No newline at end of file diff --git a/site/site/templates/build-tools/partials/header.tera b/site/site/templates/build-tools/partials/header.tera new file mode 100644 index 0000000..d4999f6 --- /dev/null +++ b/site/site/templates/build-tools/partials/header.tera @@ -0,0 +1,11 @@ +{# Site header partial with logo and branding #} +
    + +![Site Logo]({{ site_logo_path | default(value="/logos/jesusperez-logo-b.png") }}) + +# {{ site_name | default(value="Jesús Pérez") }} +{{ site_tagline | default(value="Professional Web Development & DevOps Solutions") }} + +--- + +
    \ No newline at end of file diff --git a/site/site/templates/build-tools/partials/metrics_summary.tera b/site/site/templates/build-tools/partials/metrics_summary.tera new file mode 100644 index 0000000..0ceee88 --- /dev/null +++ b/site/site/templates/build-tools/partials/metrics_summary.tera @@ -0,0 +1,11 @@ +### 📊 Quick Metrics + +| Metric | Count | Details | +|--------|-------|---------| +| 🌐 {{ i18n.labels.total_routes }} | **{{ summary.total_routes }}** | {{ summary.api_routes }} API + {{ summary.static_routes }} static | +| 🧩 {{ i18n.labels.total_components }} | **{{ summary.total_components }}** | Reusable UI components | +| 📄 {{ i18n.labels.total_pages }} | **{{ summary.total_pages }}** | Across {{ summary.languages | length }} languages | +| 🔒 {{ i18n.labels.auth_required }} | **{{ summary.auth_required_routes }}** | Secured endpoints | +| 🌍 {{ i18n.labels.languages }} | **{{ summary.languages | join(sep=", ") }}** | Supported locales | + +--- \ No newline at end of file diff --git a/site/site/templates/build-tools/partials/nav_breadcrumbs.tera b/site/site/templates/build-tools/partials/nav_breadcrumbs.tera new file mode 100644 index 0000000..68a7445 --- /dev/null +++ b/site/site/templates/build-tools/partials/nav_breadcrumbs.tera @@ -0,0 +1,5 @@ +{% if breadcrumbs %} +**Navigation:** {% for crumb in breadcrumbs %}[{{ crumb.title }}]({{ crumb.path }}){% if not loop.last %} → {% endif %}{% endfor %} + +--- +{% endif %} \ No newline at end of file diff --git a/site/site/templates/build-tools/partials/route_table.tera b/site/site/templates/build-tools/partials/route_table.tera new file mode 100644 index 0000000..53705a4 --- /dev/null +++ b/site/site/templates/build-tools/partials/route_table.tera @@ -0,0 +1,5 @@ +| Path | Methods | Handler | Auth | Type | +|------|---------|---------|------|------| +{% for route in doc.routes %} +| [`{{ route.path }}`](reference.md#{{ route.anchor }}) | {% for method in route.methods %}{{ method }}{% if not loop.last %} {% endif %}{% endfor %} | `{{ route.handler }}` | {% if route.requires_auth %}🔒{% else %}🌐{% endif %} | {{ route.response_type }} | +{% endfor %} \ No newline at end of file diff --git a/site/site/templates/build-tools/partials/source_link.tera b/site/site/templates/build-tools/partials/source_link.tera new file mode 100644 index 0000000..ea4c5ab --- /dev/null +++ b/site/site/templates/build-tools/partials/source_link.tera @@ -0,0 +1,9 @@ +{% if src_ref %} +{{ src_ref | source_link(item_name=item_name | default(value="item")) }} +{% elif route.src_ref %} +{{ route.src_ref | source_link(item_name=route.path | default(value="route")) }} +{% elif component.src_ref %} +{{ component.src_ref | source_link(item_name=component.name | default(value="component")) }} +{% elif page.src_ref %} +{{ page.src_ref | source_link(item_name=page.path | default(value="page")) }} +{% endif %} \ No newline at end of file diff --git a/site/site/templates/build-tools/setup.sh b/site/site/templates/build-tools/setup.sh new file mode 100755 index 0000000..639347e --- /dev/null +++ b/site/site/templates/build-tools/setup.sh @@ -0,0 +1,105 @@ +#!/bin/bash + +# Site-Info Template Setup Script +# This script initializes the site-info template system + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +TEMPLATES_DIR="$SCRIPT_DIR" + +echo "🎨 Site-Info Template System Setup" +echo "==================================" +echo "" + +# Check if templates already exist +if [ -f "$TEMPLATES_DIR/documentation/base.tera" ]; then + echo "✅ Templates are already set up at: $TEMPLATES_DIR" + echo "" + echo "📁 Template structure:" + find "$TEMPLATES_DIR" -name "*.tera" -o -name "*.md" | sort | sed 's|^| |' + echo "" + echo "ℹ️ Templates are ready for use. No action needed." + exit 0 +fi + +echo "🚨 WARNING: Templates directory exists but templates are missing!" +echo " Directory: $TEMPLATES_DIR" +echo "" +echo "This usually happens when:" +echo " - Templates were moved from source but files are missing" +echo " - Git didn't track the template files" +echo " - Template files were accidentally deleted" +echo "" + +# Check if we're in the right place +if [ ! -f "$SCRIPT_DIR/../../../crates/site-info/Cargo.toml" ]; then + echo "❌ Error: This script must be run from the correct project directory" + echo " Expected: site/templates/site-info/" + echo " Current: $SCRIPT_DIR" + exit 1 +fi + +echo "🔧 Template Recovery Options:" +echo "" +echo "1. Restore from crate source (if available):" +echo " cp -r ../../../crates/site-info/src/templates/* ." +echo "" +echo "2. Initialize from scratch:" +echo " Create templates manually using the documentation in README.md" +echo "" +echo "3. Check git history:" +echo " git log --follow -- site/templates/site-info/" +echo "" + +# Offer to restore from crate if source still exists +CRATE_TEMPLATES="../../../crates/site-info/src/templates" +if [ -d "$CRATE_TEMPLATES" ]; then + echo "✨ Found template source in crate directory!" + echo "" + read -p "Restore templates from crate source? [y/N]: " -n 1 -r + echo "" + + if [[ $REPLY =~ ^[Yy]$ ]]; then + echo "📋 Copying templates from crate source..." + + # Copy templates while preserving structure + if [ -d "$CRATE_TEMPLATES/documentation" ]; then + cp -r "$CRATE_TEMPLATES/documentation" "$TEMPLATES_DIR/" + echo " ✓ Copied documentation templates" + fi + + if [ -d "$CRATE_TEMPLATES/partials" ]; then + cp -r "$CRATE_TEMPLATES/partials" "$TEMPLATES_DIR/" + echo " ✓ Copied partial templates" + fi + + if [ -d "$CRATE_TEMPLATES/i18n" ]; then + cp -r "$CRATE_TEMPLATES/i18n" "$TEMPLATES_DIR/" + echo " ✓ Copied i18n template directories" + fi + + echo "" + echo "✅ Templates restored successfully!" + echo "" + echo "📁 Template structure:" + find "$TEMPLATES_DIR" -name "*.tera" | sort | sed 's|^| |' + echo "" + echo "🎯 Next steps:" + echo " 1. Test the build: cargo build" + echo " 2. Generate docs: cargo run --bin site-info-generator" + echo " 3. Commit templates: git add site/templates/site-info/" + + exit 0 + fi +fi + +echo "" +echo "📖 For manual template creation, see:" +echo " site/templates/site-info/README.md" +echo "" +echo "🔗 Template examples and documentation:" +echo " https://keats.github.io/tera/docs/" +echo "" + +exit 1 \ No newline at end of file diff --git a/site/site/templates/content/blog/example/_index.ncl b/site/site/templates/content/blog/example/_index.ncl new file mode 100644 index 0000000..1727409 --- /dev/null +++ b/site/site/templates/content/blog/example/_index.ncl @@ -0,0 +1,7 @@ +# AUTO-GENERATED — do not edit manually +# Scope: blog/{lang}/{category} +# Regen: nu scripts/content/generate-ncl-index.nu --type blog + +[ + import "./my-post-slug.ncl", +] diff --git a/site/site/templates/content/blog/example/post.md b/site/site/templates/content/blog/example/post.md new file mode 100644 index 0000000..c5c3ad9 --- /dev/null +++ b/site/site/templates/content/blog/example/post.md @@ -0,0 +1,13 @@ + + +# Post Title + +Introduction paragraph — state the problem this post addresses. + +## Section + +Content body. Code-first, prose second. + +```rust +fn example() {} +``` diff --git a/site/site/templates/content/blog/example/post.ncl b/site/site/templates/content/blog/example/post.ncl new file mode 100644 index 0000000..3848d17 --- /dev/null +++ b/site/site/templates/content/blog/example/post.ncl @@ -0,0 +1,18 @@ +let schema = import "content/metadata/post_metadata.ncl" in + +schema.make_post { + id = "my-post-slug", + title = "Post Title", + slug = "my-post-slug", + subtitle = "One-line description", + excerpt = "Two or three sentences for listings and SEO.", + + date = "2026-01-01", + featured = false, + + category = "category-slug", + tags = ["rust", "tag2"], + + read_time = "8 min read", + sort_order = 1, +} diff --git a/site/site/templates/email/html/form_submission.html b/site/site/templates/email/html/form_submission.html new file mode 100644 index 0000000..3b8af7a --- /dev/null +++ b/site/site/templates/email/html/form_submission.html @@ -0,0 +1,25 @@ + + + + + + Form Submission: {{ subject }} + + +

    New {{ form_type }} submission

    +

    {{ submitted_at }}

    + + + + + + + + + + +
    From{{ name }} <{{ email }}>
    Subject{{ subject }}
    + +
    {{ message }}
    + + diff --git a/site/site/templates/email/html/otp_code.html b/site/site/templates/email/html/otp_code.html new file mode 100644 index 0000000..3bdcfa2 --- /dev/null +++ b/site/site/templates/email/html/otp_code.html @@ -0,0 +1,27 @@ + + + + + + {{ title }} + + +

    {{ title }}

    +

    {{ subtitle }}

    +
    {{ code }}
    +

    + {{ expire_msg }}
    + {{ ignore_msg }} +

    + + diff --git a/site/site/templates/email/otp_code.html b/site/site/templates/email/otp_code.html new file mode 100644 index 0000000..7c612df --- /dev/null +++ b/site/site/templates/email/otp_code.html @@ -0,0 +1,27 @@ + + + + + + Your verification code + + +

    Your verification code

    +

    Use this code to sign in:

    +
    {{code}}
    +

    + This code expires in {{expiry_minutes}} minutes.
    + If you didn't request this, you can safely ignore this email. +

    + + diff --git a/site/site/templates/email/otp_code.txt b/site/site/templates/email/otp_code.txt new file mode 100644 index 0000000..f339c69 --- /dev/null +++ b/site/site/templates/email/otp_code.txt @@ -0,0 +1,4 @@ +Your verification code: {{code}} + +This code expires in {{expiry_minutes}} minutes. +If you didn't request this, you can safely ignore this email. diff --git a/site/site/templates/email/text/form_submission.txt b/site/site/templates/email/text/form_submission.txt new file mode 100644 index 0000000..2bce6e0 --- /dev/null +++ b/site/site/templates/email/text/form_submission.txt @@ -0,0 +1,8 @@ +New {{ form_type }} submission — {{ submitted_at }} + +From: {{ name }} <{{ email }}> +Subject: {{ subject }} + +--- + +{{ message }} diff --git a/site/site/templates/email/text/otp_code.txt b/site/site/templates/email/text/otp_code.txt new file mode 100644 index 0000000..6d38f8d --- /dev/null +++ b/site/site/templates/email/text/otp_code.txt @@ -0,0 +1,8 @@ +{{ title }} + +{{ subtitle }} + +{{ code }} + +{{ expire_msg }} +{{ ignore_msg }} diff --git a/site/site/templates/image-prompts/activities.j2 b/site/site/templates/image-prompts/activities.j2 new file mode 100644 index 0000000..0c105ec --- /dev/null +++ b/site/site/templates/image-prompts/activities.j2 @@ -0,0 +1,11 @@ +{% if image_type == "thumbnail" -%} +Square thumbnail for a {{ event_type }} titled "{{ title }}". +Category: {{ category }}. Topics: {{ tags }}. +{% else -%} +Wide hero image for a {{ event_type }} titled "{{ title }}". +Category: {{ category }}. Topics: {{ tags }}. +{% if subtitle %}Subtitle: {{ subtitle }}.{% endif %} +{% if excerpt %}Description: {{ excerpt }}.{% endif %} +{% endif -%} +{{ style }} +No embedded text. No typography overlays. No logos. diff --git a/site/site/templates/image-prompts/blog.j2 b/site/site/templates/image-prompts/blog.j2 new file mode 100644 index 0000000..edb6225 --- /dev/null +++ b/site/site/templates/image-prompts/blog.j2 @@ -0,0 +1,10 @@ +{% if image_type == "thumbnail" -%} +Square thumbnail for a technical blog post titled "{{ title }}". +Category: {{ category }}. Topics: {{ tags }}. +{% else -%} +Wide hero image for a technical blog post titled "{{ title }}". +Category: {{ category }}. Topics: {{ tags }}. +{% if excerpt %}Summary: {{ excerpt }}.{% endif %} +{% endif -%} +{{ style }} +No embedded text. No typography overlays. No logos. diff --git a/site/site/templates/image-prompts/projects.j2 b/site/site/templates/image-prompts/projects.j2 new file mode 100644 index 0000000..c5fd156 --- /dev/null +++ b/site/site/templates/image-prompts/projects.j2 @@ -0,0 +1,11 @@ +{% if image_type == "thumbnail" -%} +Square thumbnail for an open-source project titled "{{ title }}". +Topics: {{ tags }}. +{% else -%} +Wide hero image for an open-source project titled "{{ title }}". +Topics: {{ tags }}. +{% if subtitle %}Subtitle: {{ subtitle }}.{% endif %} +{% if excerpt %}Description: {{ excerpt }}.{% endif %} +{% endif -%} +{{ style }} +No embedded text. No typography overlays. No logos. diff --git a/site/site/templates/image-prompts/recipes.j2 b/site/site/templates/image-prompts/recipes.j2 new file mode 100644 index 0000000..2679cf5 --- /dev/null +++ b/site/site/templates/image-prompts/recipes.j2 @@ -0,0 +1,10 @@ +{% if image_type == "thumbnail" -%} +Square thumbnail for a technical recipe titled "{{ title }}". +Category: {{ category }}. Topics: {{ tags }}. +{% else -%} +Wide hero image for a technical recipe titled "{{ title }}". +Category: {{ category }}. Topics: {{ tags }}. +{% if excerpt %}Description: {{ excerpt }}.{% endif %} +{% endif -%} +{{ style }} +No embedded text. No typography overlays. No logos. diff --git a/site/update-content.sh b/site/update-content.sh new file mode 100755 index 0000000..154142b --- /dev/null +++ b/site/update-content.sh @@ -0,0 +1 @@ + nu ../../../website-htmx-rustelo/code/provisioning/content.nu sync --root "$(pwd)" # → snapshot al PV (/var/www/htmx-assets). Ya sin --all.