<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Bundled Page</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { background: #16181d; display: flex; align-items: center; justify-content: center; min-height: 100vh; font-family: -apple-system, BlinkMacSystemFont, sans-serif; }
    #__bundler_loading { position: fixed; bottom: 20px; right: 20px; font: 13px/1.4 -apple-system, BlinkMacSystemFont, sans-serif; color: #666; background: #fff; padding: 8px 14px; border-radius: 8px; box-shadow: 0 1px 4px rgba(0,0,0,0.12); z-index: 10000; }
    #__bundler_thumbnail { position: fixed; inset: 0; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; background: #16181d; z-index: 9999; }
    #__bundler_thumbnail svg { width: 100%; height: 100%; object-fit: contain; }
    #__bundler_placeholder { color: #999; font-size: 14px; }
  </style>
  <noscript>
    <style>#__bundler_loading { display: none; }</style>
    <div style="position:fixed;bottom:12px;left:12px;font:13px/1.4 -apple-system,BlinkMacSystemFont,sans-serif;color:#999;background:rgba(255,255,255,0.9);padding:6px 12px;border-radius:6px;box-shadow:0 1px 4px rgba(0,0,0,0.08);z-index:10000;">
      This page requires JavaScript to display.
    </div>
  </noscript>
</head>
<body>
  <div id="__bundler_thumbnail">
  <svg viewBox="0 0 1200 800" xmlns="http://www.w3.org/2000/svg">
    <rect width="1200" height="800" fill="#16181d"></rect>
    <rect x="430" y="250" width="340" height="300" rx="28" fill="#1f5fdb"></rect>
    <text x="600" y="470" font-family="monospace" font-size="220" font-weight="700" fill="#ffffff" text-anchor="middle">τ</text>
    <circle cx="360" cy="300" r="22" fill="#0f9d8a"></circle>
    <circle cx="840" cy="300" r="22" fill="#e0892e"></circle>
    <circle cx="360" cy="500" r="22" fill="#c0476b"></circle>
    <circle cx="840" cy="500" r="22" fill="#6b59c9"></circle>
    <line x1="382" y1="300" x2="430" y2="330" stroke="#3a4256" stroke-width="6"></line>
    <line x1="818" y1="300" x2="770" y2="330" stroke="#3a4256" stroke-width="6"></line>
    <line x1="382" y1="500" x2="430" y2="470" stroke="#3a4256" stroke-width="6"></line>
    <line x1="818" y1="500" x2="770" y2="470" stroke="#3a4256" stroke-width="6"></line>
  </svg>
</div>
  <div id="__bundler_loading">Unpacking...</div>

  <script>
    
document.addEventListener('DOMContentLoaded', async function() {
  const loading = document.getElementById('__bundler_loading');
  function setStatus(msg) { if (loading) loading.textContent = msg; }

  // Error sink persists across replaceWith since it's on window, not the DOM.
  window.addEventListener('error', function(e) {
    var p = document.body || document.documentElement;
    var d = document.getElementById('__bundler_err') || p.appendChild(document.createElement('div'));
    d.id = '__bundler_err';
    d.style.cssText = 'position:fixed;bottom:12px;left:12px;right:12px;font:12px/1.4 ui-monospace,monospace;background:#2a1215;color:#ff8a80;padding:10px 14px;border-radius:8px;border:1px solid #5c2b2e;z-index:99999;white-space:pre-wrap;max-height:40vh;overflow:auto';
    d.textContent = (d.textContent ? d.textContent + String.fromCharCode(10) : '') +
      '[bundle] ' + (e.message || e.type) +
      (e.filename ? ' (' + e.filename.slice(0, 60) + ':' + e.lineno + ')' : '');
  }, true);

  try {
    const manifestEl = document.querySelector('script[type="__bundler/manifest"]');
    const templateEl = document.querySelector('script[type="__bundler/template"]');
    if (!manifestEl || !templateEl) {
      setStatus('Error: missing bundle data');
      console.error('[bundler] Missing script tags — manifestEl:', !!manifestEl, 'templateEl:', !!templateEl);
      return;
    }

    const manifest = JSON.parse(manifestEl.textContent);
    let template = JSON.parse(templateEl.textContent);

    const uuids = Object.keys(manifest);
    setStatus('Unpacking ' + uuids.length + ' assets...');

    const blobUrls = {};
    await Promise.all(uuids.map(async (uuid) => {
      const entry = manifest[uuid];
      try {
        const binaryStr = atob(entry.data);
        const bytes = new Uint8Array(binaryStr.length);
        for (let i = 0; i < binaryStr.length; i++) bytes[i] = binaryStr.charCodeAt(i);

        let finalBytes = bytes;
        if (entry.compressed) {
          if (typeof DecompressionStream !== 'undefined') {
            const ds = new DecompressionStream('gzip');
            const writer = ds.writable.getWriter();
            const reader = ds.readable.getReader();
            writer.write(bytes);
            writer.close();
            const chunks = [];
            let totalLen = 0;
            while (true) {
              const { done, value } = await reader.read();
              if (done) break;
              chunks.push(value);
              totalLen += value.length;
            }
            finalBytes = new Uint8Array(totalLen);
            let offset = 0;
            for (const chunk of chunks) { finalBytes.set(chunk, offset); offset += chunk.length; }
          } else {
            console.warn('DecompressionStream not available, asset ' + uuid + ' may not render');
          }
        }

        blobUrls[uuid] = URL.createObjectURL(new Blob([finalBytes], { type: entry.mime }));
      } catch (err) {
        console.error('Failed to decode asset ' + uuid + ':', err);
        blobUrls[uuid] = URL.createObjectURL(new Blob([], { type: entry.mime }));
      }
    }));

    const extResEl = document.querySelector('script[type="__bundler/ext_resources"]');
    const extResources = extResEl ? JSON.parse(extResEl.textContent) : [];
    const resourceMap = {};
    for (const entry of extResources) {
      if (blobUrls[entry.uuid]) resourceMap[entry.id] = blobUrls[entry.uuid];
    }

    setStatus('Rendering...');
    for (const uuid of uuids) template = template.split(uuid).join(blobUrls[uuid]);

    // Strip integrity + crossorigin — blob URLs from a file:// document inherit
    // a null origin, so crossorigin forces a CORS fetch that SRI then rejects.
    // The manifest bytes are ours; SRI protects against CDN compromise, not this.
    template = template.replace(/\s+integrity="[^"]*"/gi, '').replace(/\s+crossorigin="[^"]*"/gi, '');

    const resourceScript = '<script>window.__resources = ' +
      JSON.stringify(resourceMap).split('</' + 'script>').join('<\\/' + 'script>') +
      ';</' + 'script>';
    // Inject after <head> so the DOCTYPE stays first; prepending the script
    // would push the parser into quirks mode. DOMParser always emits a <head>
    // (synthesizing one if the source HTML omitted it) but may carry
    // attributes through, so match the full opening tag. slice() rather than
    // replace() keeps us clear of $-pattern substitution in resourceScript.
    const headOpen = template.match(/<head[^>]*>/i);
    if (headOpen) {
      const i = headOpen.index + headOpen[0].length;
      template = template.slice(0, i) + resourceScript + template.slice(i);
    }

    // Parse the template and swap the root element. Scripts inserted via
    // DOMParser/replaceWith are inert per spec — re-create each with
    // createElement so they execute, awaiting onload for src scripts to
    // preserve ordering (React before ReactDOM before Babel before text/babel).
    const doc = new DOMParser().parseFromString(template, 'text/html');
    document.documentElement.replaceWith(doc.documentElement);
    const dead = Array.from(document.scripts);
    for (const old of dead) {
      const s = document.createElement('script');
      for (const a of old.attributes) s.setAttribute(a.name, a.value);
      s.textContent = old.textContent;
      // text/babel scripts with a src: fetch and inline. transformScriptTags
      // does XHR against the src, but blob:null/ from a file:// origin is
      // silently dropped. Inlining makes it a plain inline babel script,
      // which transformScriptTags handles unconditionally.
      if ((s.type === 'text/babel' || s.type === 'text/jsx') && s.src) {
        const r = await fetch(s.src);
        s.textContent = await r.text();
        s.removeAttribute('src');
      }
      const p = s.src ? new Promise(function(r) { s.onload = s.onerror = r; }) : null;
      old.replaceWith(s);
      if (p) await p;
    }
    // Babel standalone auto-transforms type=text/babel on DOMContentLoaded,
    // which fired before we swapped the document. Trigger manually if present.
    if (window.Babel && typeof window.Babel.transformScriptTags === 'function') {
      window.Babel.transformScriptTags();
    }
  } catch (err) {
    setStatus('Error unpacking: ' + err.message);
    console.error('Bundle unpack error:', err);
  }
});

  </script>

  <script type="__bundler/manifest">
{"9bf6b9f6-b7a1-43e2-9abb-328ee0e0045d":{"mime":"text/javascript","compressed":true,"data":"[encoded data, 19740 characters]"},"46f40e85-dfa5-4a06-97fd-5c53fdcdec88":{"mime":"font/woff2","compressed":false,"data":"[encoded data, 9216 characters]"},"52dedb9b-d9e8-4616-a704-b002b8bface8":{"mime":"font/woff2","compressed":false,"data":"[encoded data, 11144 characters]"},"ca334b0a-fa05-43ad-9816-b9a19f7bb44f":{"mime":"font/woff2","compressed":false,"data":"[encoded data, 7824 characters]"},"5e851c14-6be5-44c7-b888-2fef7dc3e577":{"mime":"font/woff2","compressed":false,"data":"[encoded data, 17800 characters]"},"8d2d699f-0425-4b6f-9b24-760a45d20a8f":{"mime":"font/woff2","compressed":false,"data":"[encoded data, 19612 characters]"},"cee9095e-2431-4934-8090-64d9e093e15a":{"mime":"font/woff2","compressed":false,"data":"[encoded data, 9296 characters]"},"b79de338-829d-4744-86b7-773becfa3032":{"mime":"font/woff2","compressed":false,"data":"[encoded data, 11280 characters]"},"b5224a32-0b1a-4d3c-96ff-010083bd7f5d":{"mime":"font/woff2","compressed":false,"data":"[encoded data, 8056 characters]"},"001ab721-0dee-40e2-893e-d0319c54d51d":{"mime":"font/woff2","compressed":false,"data":"[encoded data, 17912 characters]"},"b9939325-baa2-428a-8275-55d5966a3b33":{"mime":"font/woff2","compressed":false,"data":"[encoded data, 19852 characters]"},"bacf4651-a86e-43b2-b23d-67f63eec2599":{"mime":"font/woff2","compressed":false,"data":"[encoded data, 10256 characters]"},"01ee5390-e1b7-47de-baac-49da33a3f006":{"mime":"font/woff2","compressed":false,"data":"[encoded data, 12472 characters]"},"7b774497-d09d-4ad6-abb7-6783f747038c":{"mime":"font/woff2","compressed":false,"data":"[encoded data, 9244 characters]"},"92f00237-1453-4b10-87ec-e72c017f7842":{"mime":"font/woff2","compressed":false,"data":"[encoded data, 19104 characters]"},"b892f3a3-59ef-4687-9741-76f1cd8dfdb2":{"mime":"font/woff2","compressed":false,"data":"[encoded data, 20828 characters]"},"43171bb5-60e2-449c-a711-60dd741423b5":{"mime":"font/woff2","compressed":false,"data":"[encoded data, 31424 characters]"},"34ad020c-def6-418c-b649-43ec4dabe439":{"mime":"font/woff2","compressed":false,"data":"[encoded data, 39352 characters]"},"32b8f24b-898e-41c8-8f24-0958238573fd":{"mime":"font/woff2","compressed":false,"data":"[encoded data, 26000 characters]"},"6618a068-d912-416f-9e32-f2a36dc74b23":{"mime":"font/woff2","compressed":false,"data":"[encoded data, 17548 characters]"},"b23d4ade-678c-4beb-8e29-2fce9dd11237":{"mime":"font/woff2","compressed":false,"data":"[encoded data, 41288 characters]"},"957c2e38-f64d-48b9-876e-d7abb78a0feb":{"mime":"font/woff2","compressed":false,"data":"[encoded data, 60952 characters]"}}
  </script>

  <script type="__bundler/ext_resources">
[]
  </script>

  <script type="__bundler/template">
"<!DOCTYPE html>\n<html><head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<script src=\"9bf6b9f6-b7a1-43e2-9abb-328ee0e0045d\"><\u002Fscript>\n<\u002Fhead>\n<body>\n\n<x-dc>\n<helmet>\n<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n<link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin=\"\">\n<style>/* cyrillic-ext */\n@font-face {\n  font-family: 'IBM Plex Mono';\n  font-style: normal;\n  font-weight: 400;\n  font-display: swap;\n  src: url(\"46f40e85-dfa5-4a06-97fd-5c53fdcdec88\") format('woff2');\n  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'IBM Plex Mono';\n  font-style: normal;\n  font-weight: 400;\n  font-display: swap;\n  src: url(\"52dedb9b-d9e8-4616-a704-b002b8bface8\") format('woff2');\n  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* vietnamese */\n@font-face {\n  font-family: 'IBM Plex Mono';\n  font-style: normal;\n  font-weight: 400;\n  font-display: swap;\n  src: url(\"ca334b0a-fa05-43ad-9816-b9a19f7bb44f\") format('woff2');\n  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'IBM Plex Mono';\n  font-style: normal;\n  font-weight: 400;\n  font-display: swap;\n  src: url(\"5e851c14-6be5-44c7-b888-2fef7dc3e577\") format('woff2');\n  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'IBM Plex Mono';\n  font-style: normal;\n  font-weight: 400;\n  font-display: swap;\n  src: url(\"8d2d699f-0425-4b6f-9b24-760a45d20a8f\") format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'IBM Plex Mono';\n  font-style: normal;\n  font-weight: 500;\n  font-display: swap;\n  src: url(\"cee9095e-2431-4934-8090-64d9e093e15a\") format('woff2');\n  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'IBM Plex Mono';\n  font-style: normal;\n  font-weight: 500;\n  font-display: swap;\n  src: url(\"b79de338-829d-4744-86b7-773becfa3032\") format('woff2');\n  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* vietnamese */\n@font-face {\n  font-family: 'IBM Plex Mono';\n  font-style: normal;\n  font-weight: 500;\n  font-display: swap;\n  src: url(\"b5224a32-0b1a-4d3c-96ff-010083bd7f5d\") format('woff2');\n  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'IBM Plex Mono';\n  font-style: normal;\n  font-weight: 500;\n  font-display: swap;\n  src: url(\"001ab721-0dee-40e2-893e-d0319c54d51d\") format('woff2');\n  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'IBM Plex Mono';\n  font-style: normal;\n  font-weight: 500;\n  font-display: swap;\n  src: url(\"b9939325-baa2-428a-8275-55d5966a3b33\") format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'IBM Plex Mono';\n  font-style: normal;\n  font-weight: 600;\n  font-display: swap;\n  src: url(\"bacf4651-a86e-43b2-b23d-67f63eec2599\") format('woff2');\n  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'IBM Plex Mono';\n  font-style: normal;\n  font-weight: 600;\n  font-display: swap;\n  src: url(\"01ee5390-e1b7-47de-baac-49da33a3f006\") format('woff2');\n  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* vietnamese */\n@font-face {\n  font-family: 'IBM Plex Mono';\n  font-style: normal;\n  font-weight: 600;\n  font-display: swap;\n  src: url(\"7b774497-d09d-4ad6-abb7-6783f747038c\") format('woff2');\n  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'IBM Plex Mono';\n  font-style: normal;\n  font-weight: 600;\n  font-display: swap;\n  src: url(\"92f00237-1453-4b10-87ec-e72c017f7842\") format('woff2');\n  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'IBM Plex Mono';\n  font-style: normal;\n  font-weight: 600;\n  font-display: swap;\n  src: url(\"b892f3a3-59ef-4687-9741-76f1cd8dfdb2\") format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'IBM Plex Sans';\n  font-style: normal;\n  font-weight: 400;\n  font-stretch: 100%;\n  font-display: swap;\n  src: url(\"43171bb5-60e2-449c-a711-60dd741423b5\") format('woff2');\n  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'IBM Plex Sans';\n  font-style: normal;\n  font-weight: 400;\n  font-stretch: 100%;\n  font-display: swap;\n  src: url(\"34ad020c-def6-418c-b649-43ec4dabe439\") format('woff2');\n  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* greek */\n@font-face {\n  font-family: 'IBM Plex Sans';\n  font-style: normal;\n  font-weight: 400;\n  font-stretch: 100%;\n  font-display: swap;\n  src: url(\"32b8f24b-898e-41c8-8f24-0958238573fd\") format('woff2');\n  unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;\n}\n/* vietnamese */\n@font-face {\n  font-family: 'IBM Plex Sans';\n  font-style: normal;\n  font-weight: 400;\n  font-stretch: 100%;\n  font-display: swap;\n  src: url(\"6618a068-d912-416f-9e32-f2a36dc74b23\") format('woff2');\n  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'IBM Plex Sans';\n  font-style: normal;\n  font-weight: 400;\n  font-stretch: 100%;\n  font-display: swap;\n  src: url(\"b23d4ade-678c-4beb-8e29-2fce9dd11237\") format('woff2');\n  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'IBM Plex Sans';\n  font-style: normal;\n  font-weight: 400;\n  font-stretch: 100%;\n  font-display: swap;\n  src: url(\"957c2e38-f64d-48b9-876e-d7abb78a0feb\") format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'IBM Plex Sans';\n  font-style: normal;\n  font-weight: 500;\n  font-stretch: 100%;\n  font-display: swap;\n  src: url(\"43171bb5-60e2-449c-a711-60dd741423b5\") format('woff2');\n  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'IBM Plex Sans';\n  font-style: normal;\n  font-weight: 500;\n  font-stretch: 100%;\n  font-display: swap;\n  src: url(\"34ad020c-def6-418c-b649-43ec4dabe439\") format('woff2');\n  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* greek */\n@font-face {\n  font-family: 'IBM Plex Sans';\n  font-style: normal;\n  font-weight: 500;\n  font-stretch: 100%;\n  font-display: swap;\n  src: url(\"32b8f24b-898e-41c8-8f24-0958238573fd\") format('woff2');\n  unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;\n}\n/* vietnamese */\n@font-face {\n  font-family: 'IBM Plex Sans';\n  font-style: normal;\n  font-weight: 500;\n  font-stretch: 100%;\n  font-display: swap;\n  src: url(\"6618a068-d912-416f-9e32-f2a36dc74b23\") format('woff2');\n  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'IBM Plex Sans';\n  font-style: normal;\n  font-weight: 500;\n  font-stretch: 100%;\n  font-display: swap;\n  src: url(\"b23d4ade-678c-4beb-8e29-2fce9dd11237\") format('woff2');\n  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'IBM Plex Sans';\n  font-style: normal;\n  font-weight: 500;\n  font-stretch: 100%;\n  font-display: swap;\n  src: url(\"957c2e38-f64d-48b9-876e-d7abb78a0feb\") format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'IBM Plex Sans';\n  font-style: normal;\n  font-weight: 600;\n  font-stretch: 100%;\n  font-display: swap;\n  src: url(\"43171bb5-60e2-449c-a711-60dd741423b5\") format('woff2');\n  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'IBM Plex Sans';\n  font-style: normal;\n  font-weight: 600;\n  font-stretch: 100%;\n  font-display: swap;\n  src: url(\"34ad020c-def6-418c-b649-43ec4dabe439\") format('woff2');\n  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* greek */\n@font-face {\n  font-family: 'IBM Plex Sans';\n  font-style: normal;\n  font-weight: 600;\n  font-stretch: 100%;\n  font-display: swap;\n  src: url(\"32b8f24b-898e-41c8-8f24-0958238573fd\") format('woff2');\n  unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;\n}\n/* vietnamese */\n@font-face {\n  font-family: 'IBM Plex Sans';\n  font-style: normal;\n  font-weight: 600;\n  font-stretch: 100%;\n  font-display: swap;\n  src: url(\"6618a068-d912-416f-9e32-f2a36dc74b23\") format('woff2');\n  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'IBM Plex Sans';\n  font-style: normal;\n  font-weight: 600;\n  font-stretch: 100%;\n  font-display: swap;\n  src: url(\"b23d4ade-678c-4beb-8e29-2fce9dd11237\") format('woff2');\n  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'IBM Plex Sans';\n  font-style: normal;\n  font-weight: 600;\n  font-stretch: 100%;\n  font-display: swap;\n  src: url(\"957c2e38-f64d-48b9-876e-d7abb78a0feb\") format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'IBM Plex Sans';\n  font-style: normal;\n  font-weight: 700;\n  font-stretch: 100%;\n  font-display: swap;\n  src: url(\"43171bb5-60e2-449c-a711-60dd741423b5\") format('woff2');\n  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'IBM Plex Sans';\n  font-style: normal;\n  font-weight: 700;\n  font-stretch: 100%;\n  font-display: swap;\n  src: url(\"34ad020c-def6-418c-b649-43ec4dabe439\") format('woff2');\n  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* greek */\n@font-face {\n  font-family: 'IBM Plex Sans';\n  font-style: normal;\n  font-weight: 700;\n  font-stretch: 100%;\n  font-display: swap;\n  src: url(\"32b8f24b-898e-41c8-8f24-0958238573fd\") format('woff2');\n  unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;\n}\n/* vietnamese */\n@font-face {\n  font-family: 'IBM Plex Sans';\n  font-style: normal;\n  font-weight: 700;\n  font-stretch: 100%;\n  font-display: swap;\n  src: url(\"6618a068-d912-416f-9e32-f2a36dc74b23\") format('woff2');\n  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'IBM Plex Sans';\n  font-style: normal;\n  font-weight: 700;\n  font-stretch: 100%;\n  font-display: swap;\n  src: url(\"b23d4ade-678c-4beb-8e29-2fce9dd11237\") format('woff2');\n  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'IBM Plex Sans';\n  font-style: normal;\n  font-weight: 700;\n  font-stretch: 100%;\n  font-display: swap;\n  src: url(\"957c2e38-f64d-48b9-876e-d7abb78a0feb\") format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;\n}\n<\u002Fstyle>\n<style>\n  *{box-sizing:border-box}\n  html,body{margin:0;padding:0}\n  body{font-family:\"IBM Plex Sans\",system-ui,sans-serif;color:#16181d;background:#eef0f2;-webkit-font-smoothing:antialiased}\n  ::-webkit-scrollbar{width:10px;height:10px}\n  ::-webkit-scrollbar-thumb{background:#cfd4da;border-radius:6px;border:2px solid #eef0f2}\n  ::-webkit-scrollbar-track{background:transparent}\n<\u002Fstyle>\n<\u002Fhelmet>\n\n<div style=\"display:flex;flex-direction:column;height:100vh;width:100%;overflow:hidden;background:#eef0f2\">\n\n  <!-- header -->\n  <header style=\"display:flex;align-items:center;gap:14px;height:56px;flex:none;padding:0 22px;background:#fff;border-bottom:1px solid #e2e5ea\">\n    <div style=\"display:flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:7px;background:#16181d;color:#fff;font-family:'IBM Plex Mono',monospace;font-weight:600;font-size:13px\">τ<\u002Fdiv>\n    <div style=\"display:flex;flex-direction:column;line-height:1.25;white-space:nowrap\">\n      <span style=\"font-size:14.5px;font-weight:600;letter-spacing:-.01em\">Tribology Measurement Ontology<\u002Fspan>\n      <span style=\"font-size:11.5px;color:#838a95\">Laboratory measurement-result registry<\u002Fspan>\n    <\u002Fdiv>\n    <div style=\"margin-left:auto;display:flex;align-items:center;gap:8px\">\n      <span style=\"font-family:'IBM Plex Mono',monospace;font-size:11px;color:#838a95;background:#f3f5f7;border:1px solid #e6e8ec;padding:4px 10px;border-radius:6px\">PostgreSQL · 6 relations<\u002Fspan>\n      <span style=\"display:inline-flex;align-items:center;gap:6px;font-size:12px;color:#0f7a45;background:#e7f6ee;padding:4px 10px;border-radius:6px;font-weight:600\"><span style=\"width:7px;height:7px;border-radius:7px;background:#18a05a\"><\u002Fspan>Schema v1.0<\u002Fspan>\n    <\u002Fdiv>\n  <\u002Fheader>\n\n  <div style=\"display:flex;flex:1;min-height:0\">\n\n    <!-- sidebar -->\n    <nav style=\"width:248px;flex:none;background:#fff;border-right:1px solid #e2e5ea;padding:14px 12px;display:flex;flex-direction:column;overflow-y:auto\">\n      <div style=\"font-family:'IBM Plex Mono',monospace;font-size:10px;letter-spacing:.1em;color:#a4abb4;padding:6px 11px 8px\">NAVIGATE<\u002Fdiv>\n      <sc-for list=\"{{ navItems }}\" as=\"item\" hint-placeholder-count=\"6\">\n        <button style=\"{{ item.style }}\" onclick=\"{{ item.onClick }}\">\n          <span style=\"{{ item.tagStyle }}\">{{ item.tag }}<\u002Fspan>\n          <span style=\"flex:1\">{{ item.label }}<\u002Fspan>\n          <sc-if value=\"{{ item.hasCount }}\" hint-placeholder-val=\"{{ true }}\"><span style=\"{{ item.countStyle }}\">{{ item.count }}<\u002Fspan><\u002Fsc-if>\n        <\u002Fbutton>\n      <\u002Fsc-for>\n      <div style=\"margin-top:auto;padding:14px 11px 6px;border-top:1px solid #eef0f2\">\n        <div style=\"font-family:'IBM Plex Mono',monospace;font-size:10px;letter-spacing:.08em;color:#a4abb4;margin-bottom:8px\">DATA MODEL<\u002Fdiv>\n        <div style=\"font-size:12px;color:#6b727c;line-height:1.6\">Experiment → Test → Result<br>linked to Instrument, Condition<br>and Measurement-Value dictionary.<\u002Fdiv>\n      <\u002Fdiv>\n    <\u002Fnav>\n\n    <!-- content -->\n    <main style=\"flex:1;min-width:0;overflow-y:auto;padding:26px 30px 60px\">\n\n      <!-- ============ DASHBOARD ============ -->\n      <sc-if value=\"{{ isDashboard }}\" hint-placeholder-val=\"{{ true }}\">\n      <div>\n        <div style=\"display:flex;align-items:flex-end;gap:16px;margin-bottom:20px\">\n          <div>\n            <h1 style=\"margin:0;font-size:21px;font-weight:600;letter-spacing:-.02em\">Laboratory Overview<\u002Fh1>\n            <p style=\"margin:4px 0 0;font-size:13px;color:#838a95\">Simulated tribology measurement results across all active experiments.<\u002Fp>\n          <\u002Fdiv>\n          <div style=\"margin-left:auto;display:flex\">\n            <button style=\"{{ segA }}\" onclick=\"{{ onAnalytical }}\">Analytical<\u002Fbutton>\n            <button style=\"{{ segB }}\" onclick=\"{{ onOperational }}\">Operational<\u002Fbutton>\n          <\u002Fdiv>\n        <\u002Fdiv>\n\n        <!-- KPI row -->\n        <div style=\"display:grid;grid-template-columns:repeat(5,1fr);gap:14px;margin-bottom:18px\">\n          <sc-for list=\"{{ kpis }}\" as=\"k\" hint-placeholder-count=\"5\">\n            <div style=\"background:#fff;border:1px solid #e6e8ec;border-radius:10px;padding:15px 16px\">\n              <div style=\"font-size:11.5px;color:#838a95;font-weight:500;text-transform:uppercase;letter-spacing:.04em\">{{ k.label }}<\u002Fdiv>\n              <div style=\"font-family:'IBM Plex Mono',monospace;font-size:27px;font-weight:600;color:#16181d;margin-top:7px;line-height:1\">{{ k.value }}<\u002Fdiv>\n              <div style=\"font-size:11.5px;color:#a4abb4;margin-top:5px\">{{ k.sub }}<\u002Fdiv>\n            <\u002Fdiv>\n          <\u002Fsc-for>\n        <\u002Fdiv>\n\n        <!-- ANALYTICAL -->\n        <sc-if value=\"{{ dashAnalytical }}\" hint-placeholder-val=\"{{ true }}\">\n        <div>\n          <div style=\"display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:14px\">\n            <!-- scatter -->\n            <div style=\"background:#fff;border:1px solid #e6e8ec;border-radius:10px;padding:16px 18px\">\n              <div style=\"display:flex;align-items:center;justify-content:space-between;margin-bottom:4px\">\n                <h3 style=\"margin:0;font-size:14px;font-weight:600\">Coefficient of friction vs. normal load<\u002Fh3>\n              <\u002Fdiv>\n              <p style=\"margin:0 0 6px;font-size:11.5px;color:#a4abb4\">Every COF record, coloured by contact regime<\u002Fp>\n              <div style=\"display:flex;gap:14px;flex-wrap:wrap;margin-bottom:6px\">\n                <sc-for list=\"{{ scatterLegend }}\" as=\"g\" hint-placeholder-count=\"4\">\n                  <span style=\"display:inline-flex;align-items:center;gap:6px;font-size:11px;color:#5b6472\"><span style=\"{{ g.dot }}\"><\u002Fspan>{{ g.label }}<\u002Fspan>\n                <\u002Fsc-for>\n              <\u002Fdiv>\n              <svg viewBox=\"0 0 560 240\" style=\"width:100%;height:auto;display:block\">\n                <line x1=\"46\" y1=\"202\" x2=\"546\" y2=\"202\" stroke=\"#d8dce1\" stroke-width=\"1\"><\u002Fline>\n                <line x1=\"46\" y1=\"14\" x2=\"46\" y2=\"202\" stroke=\"#d8dce1\" stroke-width=\"1\"><\u002Fline>\n                <sc-for list=\"{{ scatter.yticks }}\" as=\"t\" hint-placeholder-count=\"5\">\n                  <line x1=\"46\" y1=\"{{ t.y }}\" x2=\"546\" y2=\"{{ t.y }}\" stroke=\"#eef0f2\" stroke-width=\"1\"><\u002Fline>\n                  <text x=\"40\" y=\"{{ t.y }}\" text-anchor=\"end\" dominant-baseline=\"middle\" font-family=\"IBM Plex Mono, monospace\" font-size=\"9\" fill=\"#a4abb4\">{{ t.label }}<\u002Ftext>\n                <\u002Fsc-for>\n                <sc-for list=\"{{ scatter.xticks }}\" as=\"t\" hint-placeholder-count=\"5\">\n                  <text x=\"{{ t.x }}\" y=\"216\" text-anchor=\"middle\" font-family=\"IBM Plex Mono, monospace\" font-size=\"9\" fill=\"#a4abb4\">{{ t.label }}<\u002Ftext>\n                <\u002Fsc-for>\n                <text x=\"296\" y=\"234\" text-anchor=\"middle\" font-size=\"10\" fill=\"#838a95\">Normal load (N)<\u002Ftext>\n                <sc-for list=\"{{ scatter.points }}\" as=\"p\" hint-placeholder-count=\"20\">\n                  <circle cx=\"{{ p.cx }}\" cy=\"{{ p.cy }}\" r=\"4\" fill=\"{{ p.color }}\" fill-opacity=\"0.78\" stroke=\"#fff\" stroke-width=\"0.8\"><\u002Fcircle>\n                <\u002Fsc-for>\n              <\u002Fsvg>\n            <\u002Fdiv>\n            <!-- wear bars -->\n            <div style=\"background:#fff;border:1px solid #e6e8ec;border-radius:10px;padding:16px 18px\">\n              <h3 style=\"margin:0;font-size:14px;font-weight:600\">Mean specific wear rate by experiment<\u002Fh3>\n              <p style=\"margin:2px 0 10px;font-size:11.5px;color:#a4abb4\">Logarithmic scale · mm³/N·m<\u002Fp>\n              <svg viewBox=\"0 0 560 240\" style=\"width:100%;height:auto;display:block\">\n                <line x1=\"46\" y1=\"202\" x2=\"546\" y2=\"202\" stroke=\"#d8dce1\" stroke-width=\"1\"><\u002Fline>\n                <line x1=\"46\" y1=\"14\" x2=\"46\" y2=\"202\" stroke=\"#d8dce1\" stroke-width=\"1\"><\u002Fline>\n                <sc-for list=\"{{ wear.yticks }}\" as=\"t\" hint-placeholder-count=\"4\">\n                  <line x1=\"46\" y1=\"{{ t.y }}\" x2=\"546\" y2=\"{{ t.y }}\" stroke=\"#eef0f2\" stroke-width=\"1\"><\u002Fline>\n                  <text x=\"40\" y=\"{{ t.y }}\" text-anchor=\"end\" dominant-baseline=\"middle\" font-family=\"IBM Plex Mono, monospace\" font-size=\"9\" fill=\"#a4abb4\">{{ t.label }}<\u002Ftext>\n                <\u002Fsc-for>\n                <sc-for list=\"{{ wear.bars }}\" as=\"b\" hint-placeholder-count=\"8\">\n                  <rect x=\"{{ b.x }}\" y=\"{{ b.y }}\" width=\"{{ b.w }}\" height=\"{{ b.h }}\" rx=\"2.5\" fill=\"#1f5fdb\" fill-opacity=\"0.85\"><\u002Frect>\n                  <text x=\"{{ b.lx }}\" y=\"216\" text-anchor=\"middle\" font-family=\"IBM Plex Mono, monospace\" font-size=\"8.5\" fill=\"#838a95\">{{ b.code }}<\u002Ftext>\n                <\u002Fsc-for>\n              <\u002Fsvg>\n            <\u002Fdiv>\n          <\u002Fdiv>\n          <!-- utilization -->\n          <div style=\"background:#fff;border:1px solid #e6e8ec;border-radius:10px;padding:16px 18px\">\n            <h3 style=\"margin:0 0 12px;font-size:14px;font-weight:600\">Instrument utilisation · records captured<\u002Fh3>\n            <div style=\"display:flex;flex-direction:column;gap:11px\">\n              <sc-for list=\"{{ util }}\" as=\"u\" hint-placeholder-count=\"7\">\n                <div style=\"display:flex;align-items:center;gap:14px\">\n                  <span style=\"width:150px;flex:none;font-size:12.5px;color:#3d424b\">{{ u.short }}<\u002Fspan>\n                  <span style=\"font-family:'IBM Plex Mono',monospace;font-size:10px;color:#a4abb4;width:48px;flex:none\">{{ u.id }}<\u002Fspan>\n                  <div style=\"flex:1;background:#f1f3f6;border-radius:6px;overflow:hidden\"><div style=\"{{ u.barStyle }}\"><\u002Fdiv><\u002Fdiv>\n                  <span style=\"font-family:'IBM Plex Mono',monospace;font-size:12px;color:#16181d;width:34px;text-align:right;flex:none\">{{ u.count }}<\u002Fspan>\n                <\u002Fdiv>\n              <\u002Fsc-for>\n            <\u002Fdiv>\n          <\u002Fdiv>\n        <\u002Fdiv>\n        <\u002Fsc-if>\n\n        <!-- OPERATIONAL -->\n        <sc-if value=\"{{ dashOperational }}\" hint-placeholder-val=\"{{ true }}\">\n        <div style=\"display:grid;grid-template-columns:1.4fr 1fr;gap:14px\">\n          <div style=\"background:#fff;border:1px solid #e6e8ec;border-radius:10px;padding:16px 18px\">\n            <h3 style=\"margin:0 0 12px;font-size:14px;font-weight:600\">Latest measurement records<\u002Fh3>\n            <div style=\"display:flex;flex-direction:column\">\n              <div style=\"display:grid;grid-template-columns:78px 1fr 96px 70px;gap:10px;padding:0 0 8px;border-bottom:1px solid #eef0f2;font-size:10.5px;text-transform:uppercase;letter-spacing:.05em;color:#a4abb4;font-weight:600\">\n                <span>Record<\u002Fspan><span>Measurement<\u002Fspan><span>Value<\u002Fspan><span>Time<\u002Fspan>\n              <\u002Fdiv>\n              <sc-for list=\"{{ recent }}\" as=\"r\" hint-placeholder-count=\"9\">\n                <div style=\"display:grid;grid-template-columns:78px 1fr 96px 70px;gap:10px;align-items:center;padding:9px 0;border-bottom:1px solid #f4f5f7\">\n                  <span style=\"font-family:'IBM Plex Mono',monospace;font-size:11px;color:#838a95\">{{ r.id }}<\u002Fspan>\n                  <span style=\"display:flex;align-items:center;gap:8px;font-size:12.5px;color:#2b3038\"><span style=\"{{ r.dot }}\"><\u002Fspan>{{ r.measName }}<\u002Fspan>\n                  <span style=\"font-family:'IBM Plex Mono',monospace;font-size:12.5px;font-weight:600;color:#16181d\">{{ r.valStr }} {{ r.unit }}<\u002Fspan>\n                  <span style=\"font-family:'IBM Plex Mono',monospace;font-size:10.5px;color:#a4abb4\">{{ r.time }}<\u002Fspan>\n                <\u002Fdiv>\n              <\u002Fsc-for>\n            <\u002Fdiv>\n          <\u002Fdiv>\n          <div style=\"display:flex;flex-direction:column;gap:14px\">\n            <div style=\"background:#fff;border:1px solid #e6e8ec;border-radius:10px;padding:16px 18px\">\n              <h3 style=\"margin:0 0 12px;font-size:14px;font-weight:600\">Instrument calibration status<\u002Fh3>\n              <div style=\"display:flex;flex-direction:column;gap:9px\">\n                <sc-for list=\"{{ calib }}\" as=\"c\" hint-placeholder-count=\"7\">\n                  <div style=\"display:flex;align-items:center;gap:10px\">\n                    <span style=\"font-family:'IBM Plex Mono',monospace;font-size:10px;color:#a4abb4;width:46px;flex:none\">{{ c.id }}<\u002Fspan>\n                    <span style=\"font-size:12.5px;color:#3d424b;flex:1\">{{ c.short }}<\u002Fspan>\n                    <span style=\"{{ c.statusStyle }}\">{{ c.status }}<\u002Fspan>\n                  <\u002Fdiv>\n                <\u002Fsc-for>\n              <\u002Fdiv>\n            <\u002Fdiv>\n            <div style=\"background:#fff;border:1px solid #e6e8ec;border-radius:10px;padding:16px 18px\">\n              <h3 style=\"margin:0 0 12px;font-size:14px;font-weight:600\">Experiment progress<\u002Fh3>\n              <div style=\"display:flex;flex-direction:column;gap:12px\">\n                <sc-for list=\"{{ activeExp }}\" as=\"e\" hint-placeholder-count=\"8\">\n                  <div>\n                    <div style=\"display:flex;align-items:center;gap:8px;margin-bottom:5px\">\n                      <span style=\"font-family:'IBM Plex Mono',monospace;font-size:11px;color:#5b6472\">{{ e.code }}<\u002Fspan>\n                      <span style=\"font-size:12px;color:#3d424b;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis\">{{ e.name }}<\u002Fspan>\n                      <span style=\"font-family:'IBM Plex Mono',monospace;font-size:11px;color:#a4abb4\">{{ e.rcount }}<\u002Fspan>\n                    <\u002Fdiv>\n                    <div style=\"background:#f1f3f6;border-radius:6px;overflow:hidden\"><div style=\"{{ e.barStyle }}\"><\u002Fdiv><\u002Fdiv>\n                  <\u002Fdiv>\n                <\u002Fsc-for>\n              <\u002Fdiv>\n            <\u002Fdiv>\n          <\u002Fdiv>\n        <\u002Fdiv>\n        <\u002Fsc-if>\n      <\u002Fdiv>\n      <\u002Fsc-if>\n\n      <!-- ============ EXPERIMENTS (data browser) ============ -->\n      <sc-if value=\"{{ isProjects }}\" hint-placeholder-val=\"{{ true }}\">\n      <div>\n\n        <!-- grid -->\n        <sc-if value=\"{{ projGrid }}\" hint-placeholder-val=\"{{ true }}\">\n        <div>\n          <h1 style=\"margin:0;font-size:21px;font-weight:600;letter-spacing:-.02em\">Experiments<\u002Fh1>\n          <p style=\"margin:4px 0 20px;font-size:13px;color:#838a95\">Each experiment groups multiple tests; open one to drill into tests and measurement records.<\u002Fp>\n          <div style=\"display:grid;grid-template-columns:repeat(2,1fr);gap:14px\">\n            <sc-for list=\"{{ gridProjects }}\" as=\"p\" hint-placeholder-count=\"8\">\n              <button onclick=\"{{ p.onClick }}\" style=\"text-align:left;font:inherit;cursor:pointer;background:#fff;border:1px solid #e6e8ec;border-radius:11px;padding:18px 19px;display:flex;flex-direction:column;gap:10px\" style-hover=\"border-color:#1f5fdb;box-shadow:0 2px 10px rgba(31,95,219,.08)\">\n                <div style=\"display:flex;align-items:center;gap:10px\">\n                  <span style=\"font-family:'IBM Plex Mono',monospace;font-size:11px;font-weight:600;color:#1f5fdb;background:#eaf1fe;padding:3px 9px;border-radius:6px\">{{ p.code }}<\u002Fspan>\n                  <span style=\"{{ p.statusStyle }}\">{{ p.status }}<\u002Fspan>\n                <\u002Fdiv>\n                <div style=\"font-size:15.5px;font-weight:600;color:#16181d;line-height:1.25\">{{ p.name }}<\u002Fdiv>\n                <div style=\"font-size:12.5px;color:#838a95;line-height:1.5;text-wrap:pretty\">{{ p.objective }}<\u002Fdiv>\n                <div style=\"display:flex;align-items:center;gap:18px;margin-top:2px;padding-top:11px;border-top:1px solid #f1f3f6\">\n                  <span style=\"font-size:12px;color:#5b6472\"><b style=\"font-family:'IBM Plex Mono',monospace;color:#16181d\">{{ p.tests }}<\u002Fb> tests<\u002Fspan>\n                  <span style=\"font-size:12px;color:#5b6472\"><b style=\"font-family:'IBM Plex Mono',monospace;color:#16181d\">{{ p.rcount }}<\u002Fb> records<\u002Fspan>\n                  <span style=\"margin-left:auto;font-size:12px;color:#a4abb4\">{{ p.lead }}<\u002Fspan>\n                <\u002Fdiv>\n              <\u002Fbutton>\n            <\u002Fsc-for>\n          <\u002Fdiv>\n        <\u002Fdiv>\n        <\u002Fsc-if>\n\n        <!-- project detail -->\n        <sc-if value=\"{{ projDetail }}\" hint-placeholder-val=\"{{ true }}\">\n        <div>\n          <button onclick=\"{{ onGrid }}\" style=\"font:inherit;font-size:12.5px;color:#1f5fdb;background:none;border:none;cursor:pointer;padding:0;margin-bottom:14px;display:inline-flex;align-items:center;gap:6px\">← All experiments<\u002Fbutton>\n          <div style=\"display:flex;align-items:center;gap:11px;margin-bottom:5px\">\n            <span style=\"font-family:'IBM Plex Mono',monospace;font-size:12px;font-weight:600;color:#1f5fdb;background:#eaf1fe;padding:3px 10px;border-radius:6px\">{{ cur.code }}<\u002Fspan>\n            <span style=\"{{ cur.statusStyle }}\">{{ cur.status }}<\u002Fspan>\n          <\u002Fdiv>\n          <h1 style=\"margin:0;font-size:21px;font-weight:600;letter-spacing:-.02em\">{{ cur.name }}<\u002Fh1>\n          <p style=\"margin:7px 0 16px;font-size:13px;color:#5b6472;max-width:720px;line-height:1.55;text-wrap:pretty\">{{ cur.objective }}<\u002Fp>\n          <div style=\"display:flex;gap:26px;margin-bottom:22px;padding:13px 18px;background:#fff;border:1px solid #e6e8ec;border-radius:10px\">\n            <div><div style=\"font-size:11px;color:#a4abb4;text-transform:uppercase;letter-spacing:.04em\">Lead<\u002Fdiv><div style=\"font-size:13px;color:#2b3038;margin-top:3px\">{{ cur.lead }}<\u002Fdiv><\u002Fdiv>\n            <div><div style=\"font-size:11px;color:#a4abb4;text-transform:uppercase;letter-spacing:.04em\">Start<\u002Fdiv><div style=\"font-family:'IBM Plex Mono',monospace;font-size:13px;color:#2b3038;margin-top:3px\">{{ cur.start }}<\u002Fdiv><\u002Fdiv>\n            <div><div style=\"font-size:11px;color:#a4abb4;text-transform:uppercase;letter-spacing:.04em\">Records<\u002Fdiv><div style=\"font-family:'IBM Plex Mono',monospace;font-size:13px;color:#2b3038;margin-top:3px\">{{ cur.rcount }}<\u002Fdiv><\u002Fdiv>\n          <\u002Fdiv>\n          <h3 style=\"margin:0 0 11px;font-size:13px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:#838a95\">Tests<\u002Fh3>\n          <div style=\"display:flex;flex-direction:column;gap:10px\">\n            <sc-for list=\"{{ cur.tests }}\" as=\"t\" hint-placeholder-count=\"2\">\n              <button onclick=\"{{ t.onClick }}\" style=\"text-align:left;font:inherit;cursor:pointer;background:#fff;border:1px solid #e6e8ec;border-radius:10px;padding:15px 18px;display:flex;align-items:center;gap:16px\" style-hover=\"border-color:#1f5fdb;box-shadow:0 2px 10px rgba(31,95,219,.07)\">\n                <span style=\"{{ t.dot }}\"><\u002Fspan>\n                <div style=\"flex:1;min-width:0\">\n                  <div style=\"font-size:14.5px;font-weight:600;color:#16181d\">{{ t.name }}<\u002Fdiv>\n                  <div style=\"font-size:12px;color:#838a95;margin-top:3px\">{{ t.pair }} · {{ t.lube }}<\u002Fdiv>\n                <\u002Fdiv>\n                <div style=\"text-align:right;flex:none\">\n                  <div style=\"font-family:'IBM Plex Mono',monospace;font-size:11px;color:#5b6472\">{{ t.std }}<\u002Fdiv>\n                  <div style=\"font-size:11.5px;color:#a4abb4;margin-top:3px\">{{ t.rcount }} records · {{ t.date }}<\u002Fdiv>\n                <\u002Fdiv>\n                <span style=\"color:#c2c8d0;font-size:16px\">→<\u002Fspan>\n              <\u002Fbutton>\n            <\u002Fsc-for>\n          <\u002Fdiv>\n        <\u002Fdiv>\n        <\u002Fsc-if>\n\n        <!-- test detail -->\n        <sc-if value=\"{{ testDetail }}\" hint-placeholder-val=\"{{ true }}\">\n        <div>\n          <div style=\"display:flex;align-items:center;gap:8px;margin-bottom:14px;font-size:12.5px\">\n            <button onclick=\"{{ onGrid }}\" style=\"font:inherit;font-size:12.5px;color:#1f5fdb;background:none;border:none;cursor:pointer;padding:0\">Experiments<\u002Fbutton>\n            <span style=\"color:#c2c8d0\">/<\u002Fspan>\n            <button onclick=\"{{ onProj }}\" style=\"font:inherit;font-size:12.5px;color:#1f5fdb;background:none;border:none;cursor:pointer;padding:0\">{{ cur.code }}<\u002Fbutton>\n            <span style=\"color:#c2c8d0\">/<\u002Fspan>\n            <span style=\"color:#838a95\">{{ curTest.id }}<\u002Fspan>\n          <\u002Fdiv>\n          <h1 style=\"margin:0;font-size:20px;font-weight:600;letter-spacing:-.02em\">{{ curTest.name }}<\u002Fh1>\n          <p style=\"margin:5px 0 18px;font-size:13px;color:#838a95\">{{ cur.name }}<\u002Fp>\n\n          <div style=\"background:#fff;border:1px solid #e6e8ec;border-radius:10px;padding:16px 18px;margin-bottom:16px\">\n            <div style=\"display:grid;grid-template-columns:repeat(3,1fr);gap:16px 26px\">\n              <sc-for list=\"{{ curTest.meta }}\" as=\"m\" hint-placeholder-count=\"6\">\n                <div>\n                  <div style=\"font-size:11px;color:#a4abb4;text-transform:uppercase;letter-spacing:.04em\">{{ m.k }}<\u002Fdiv>\n                  <div style=\"font-size:13px;color:#2b3038;margin-top:4px\">{{ m.v }}<\u002Fdiv>\n                <\u002Fdiv>\n              <\u002Fsc-for>\n            <\u002Fdiv>\n          <\u002Fdiv>\n\n          <h3 style=\"margin:0 0 9px;font-size:13px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:#838a95\">Test conditions<\u002Fh3>\n          <div style=\"background:#fff;border:1px solid #e6e8ec;border-radius:10px;overflow:hidden;margin-bottom:18px\">\n            <div style=\"display:grid;grid-template-columns:90px 1fr 1fr 1fr 1fr 1fr 1.2fr;gap:10px;padding:10px 16px;background:#f7f8fa;border-bottom:1px solid #eef0f2;font-size:10.5px;text-transform:uppercase;letter-spacing:.04em;color:#838a95;font-weight:600\">\n              <span>Cond. ID<\u002Fspan><span>Load<\u002Fspan><span>Speed<\u002Fspan><span>Temp<\u002Fspan><span>Freq<\u002Fspan><span>Duration<\u002Fspan><span>Atmosphere<\u002Fspan>\n            <\u002Fdiv>\n            <sc-for list=\"{{ curTest.conds }}\" as=\"c\" hint-placeholder-count=\"2\">\n              <div style=\"display:grid;grid-template-columns:90px 1fr 1fr 1fr 1fr 1fr 1.2fr;gap:10px;padding:11px 16px;border-bottom:1px solid #f4f5f7;font-size:12.5px;color:#3d424b;align-items:center\">\n                <span style=\"font-family:'IBM Plex Mono',monospace;font-size:11px;color:#1f5fdb\">{{ c.id }}<\u002Fspan>\n                <span style=\"font-family:'IBM Plex Mono',monospace\">{{ c.load }}<\u002Fspan>\n                <span style=\"font-family:'IBM Plex Mono',monospace\">{{ c.speed }}<\u002Fspan>\n                <span style=\"font-family:'IBM Plex Mono',monospace\">{{ c.temp }}<\u002Fspan>\n                <span style=\"font-family:'IBM Plex Mono',monospace\">{{ c.freq }}<\u002Fspan>\n                <span style=\"font-family:'IBM Plex Mono',monospace\">{{ c.dur }}<\u002Fspan>\n                <span>{{ c.atm }}<\u002Fspan>\n              <\u002Fdiv>\n            <\u002Fsc-for>\n          <\u002Fdiv>\n\n          <h3 style=\"margin:0 0 9px;font-size:13px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:#838a95\">Measurement records<\u002Fh3>\n          <div style=\"background:#fff;border:1px solid #e6e8ec;border-radius:10px;overflow:hidden\">\n            <div style=\"display:grid;grid-template-columns:78px 1.5fr 1.2fr 64px 1.1fr 1.2fr 1fr;gap:10px;padding:10px 16px;background:#f7f8fa;border-bottom:1px solid #eef0f2;font-size:10.5px;text-transform:uppercase;letter-spacing:.04em;color:#838a95;font-weight:600\">\n              <span>Record<\u002Fspan><span>Measurement<\u002Fspan><span>Value<\u002Fspan><span>Cond.<\u002Fspan><span>Instrument<\u002Fspan><span>Time<\u002Fspan><span>Operator<\u002Fspan>\n            <\u002Fdiv>\n            <sc-for list=\"{{ curTest.results }}\" as=\"r\" hint-placeholder-count=\"6\">\n              <div style=\"display:grid;grid-template-columns:78px 1.5fr 1.2fr 64px 1.1fr 1.2fr 1fr;gap:10px;padding:11px 16px;border-bottom:1px solid #f4f5f7;font-size:12.5px;color:#3d424b;align-items:center\">\n                <span style=\"font-family:'IBM Plex Mono',monospace;font-size:11px;color:#838a95\">{{ r.id }}<\u002Fspan>\n                <span style=\"display:flex;align-items:center;gap:8px\"><span style=\"{{ r.dot }}\"><\u002Fspan>{{ r.measName }}<\u002Fspan>\n                <span><span style=\"font-family:'IBM Plex Mono',monospace;font-weight:600;color:#16181d\">{{ r.valStr }}<\u002Fspan> <span style=\"color:#a4abb4;font-size:11px\">{{ r.unit }}<\u002Fspan><br><span style=\"font-family:'IBM Plex Mono',monospace;font-size:10.5px;color:#b4bac2\">{{ r.unc }}<\u002Fspan><\u002Fspan>\n                <span style=\"font-family:'IBM Plex Mono',monospace;font-size:11.5px\">{{ r.cond }}<\u002Fspan>\n                <span style=\"font-size:12px\">{{ r.inst }}<\u002Fspan>\n                <span style=\"font-family:'IBM Plex Mono',monospace;font-size:10.5px;color:#838a95\">{{ r.time }}<\u002Fspan>\n                <span style=\"font-size:12px;color:#5b6472\">{{ r.op }}<\u002Fspan>\n              <\u002Fdiv>\n            <\u002Fsc-for>\n          <\u002Fdiv>\n        <\u002Fdiv>\n        <\u002Fsc-if>\n\n      <\u002Fdiv>\n      <\u002Fsc-if>\n\n      <!-- ============ INSTRUMENTS ============ -->\n      <sc-if value=\"{{ isInstruments }}\" hint-placeholder-val=\"{{ true }}\">\n      <div>\n        <h1 style=\"margin:0;font-size:21px;font-weight:600;letter-spacing:-.02em\">Instrument Registry<\u002Fh1>\n        <p style=\"margin:4px 0 20px;font-size:13px;color:#838a95\">Scientific instruments available in the laboratory, with their measurement purpose.<\u002Fp>\n        <div style=\"display:grid;grid-template-columns:repeat(2,1fr);gap:14px\">\n          <sc-for list=\"{{ instrumentsView }}\" as=\"i\" hint-placeholder-count=\"7\">\n            <div style=\"background:#fff;border:1px solid #e6e8ec;border-radius:11px;padding:18px 19px\">\n              <div style=\"display:flex;align-items:flex-start;gap:12px;margin-bottom:11px\">\n                <div style=\"font-family:'IBM Plex Mono',monospace;font-size:11px;font-weight:600;color:#16181d;background:#f1f3f6;border:1px solid #e6e8ec;padding:4px 9px;border-radius:7px;flex:none\">{{ i.id }}<\u002Fdiv>\n                <div style=\"flex:1;min-width:0\">\n                  <div style=\"font-size:15px;font-weight:600;color:#16181d\">{{ i.name }}<\u002Fdiv>\n                  <div style=\"font-size:12px;color:#838a95;margin-top:2px\">{{ i.mfr }} · {{ i.model }} · {{ i.type }}<\u002Fdiv>\n                <\u002Fdiv>\n                <span style=\"{{ i.statusStyle }}\">{{ i.status }}<\u002Fspan>\n              <\u002Fdiv>\n              <p style=\"margin:0 0 13px;font-size:12.5px;color:#5b6472;line-height:1.55;text-wrap:pretty\">{{ i.purpose }}<\u002Fp>\n              <div style=\"display:flex;flex-wrap:wrap;gap:7px;margin-bottom:13px\">\n                <sc-for list=\"{{ i.chips }}\" as=\"c\" hint-placeholder-count=\"3\">\n                  <span style=\"{{ c.style }}\"><b style=\"font-family:'IBM Plex Mono',monospace;color:#16181d\">{{ c.sym }}<\u002Fb>{{ c.name }}<\u002Fspan>\n                <\u002Fsc-for>\n              <\u002Fdiv>\n              <div style=\"display:flex;align-items:center;gap:16px;padding-top:12px;border-top:1px solid #f1f3f6\">\n                <span style=\"font-size:11.5px;color:#838a95\">📍 {{ i.loc }}<\u002Fspan>\n                <span style=\"{{ i.calStyle }}\">Cal: {{ i.calNote }}<\u002Fspan>\n                <span style=\"margin-left:auto;font-size:11.5px;color:#838a95\"><b style=\"font-family:'IBM Plex Mono',monospace;color:#16181d\">{{ i.used }}<\u002Fb> records<\u002Fspan>\n              <\u002Fdiv>\n            <\u002Fdiv>\n          <\u002Fsc-for>\n        <\u002Fdiv>\n      <\u002Fdiv>\n      <\u002Fsc-if>\n\n      <!-- ============ MEASUREMENT VALUES (dictionary) ============ -->\n      <sc-if value=\"{{ isMeasurements }}\" hint-placeholder-val=\"{{ true }}\">\n      <div>\n        <h1 style=\"margin:0;font-size:21px;font-weight:600;letter-spacing:-.02em\">Measurement-Value Dictionary<\u002Fh1>\n        <p style=\"margin:4px 0 20px;font-size:13px;color:#838a95\">Controlled vocabulary of measured quantities — each result references one entry.<\u002Fp>\n        <div style=\"background:#fff;border:1px solid #e6e8ec;border-radius:11px;overflow:hidden\">\n          <div style=\"display:grid;grid-template-columns:96px 1.5fr 64px 1fr 1.1fr 1fr 70px;gap:12px;padding:12px 18px;background:#f7f8fa;border-bottom:1px solid #eef0f2;font-size:10.5px;text-transform:uppercase;letter-spacing:.04em;color:#838a95;font-weight:600\">\n            <span>ID<\u002Fspan><span>Quantity<\u002Fspan><span>Symbol<\u002Fspan><span>Unit<\u002Fspan><span>Typical range<\u002Fspan><span>Standard<\u002Fspan><span>Records<\u002Fspan>\n          <\u002Fdiv>\n          <sc-for list=\"{{ measView }}\" as=\"m\" hint-placeholder-count=\"8\">\n            <div style=\"display:grid;grid-template-columns:96px 1.5fr 64px 1fr 1.1fr 1fr 70px;gap:12px;padding:14px 18px;border-bottom:1px solid #f4f5f7;align-items:center\">\n              <span style=\"font-family:'IBM Plex Mono',monospace;font-size:11.5px;color:#1f5fdb\">{{ m.id }}<\u002Fspan>\n              <span style=\"display:flex;align-items:center;gap:9px\"><span style=\"{{ m.dot }}\"><\u002Fspan><span><span style=\"font-size:13px;font-weight:600;color:#16181d\">{{ m.name }}<\u002Fspan><br><span style=\"font-size:11px;color:#a4abb4\">{{ m.desc }}<\u002Fspan><\u002Fspan><\u002Fspan>\n              <span style=\"font-family:'IBM Plex Mono',monospace;font-size:13px;color:#3d424b\">{{ m.sym }}<\u002Fspan>\n              <span style=\"font-family:'IBM Plex Mono',monospace;font-size:12px;color:#3d424b\">{{ m.unit }}<\u002Fspan>\n              <span style=\"font-family:'IBM Plex Mono',monospace;font-size:11.5px;color:#5b6472\">{{ m.range }}<\u002Fspan>\n              <span style=\"font-family:'IBM Plex Mono',monospace;font-size:11.5px;color:#838a95\">{{ m.std }}<\u002Fspan>\n              <span style=\"font-family:'IBM Plex Mono',monospace;font-size:13px;color:#16181d\">{{ m.count }}<\u002Fspan>\n            <\u002Fdiv>\n          <\u002Fsc-for>\n        <\u002Fdiv>\n      <\u002Fdiv>\n      <\u002Fsc-if>\n\n      <!-- ============ RELATIONAL SCHEMA (ERD) ============ -->\n      <sc-if value=\"{{ isSchema }}\" hint-placeholder-val=\"{{ true }}\">\n      <div>\n        <h1 style=\"margin:0;font-size:21px;font-weight:600;letter-spacing:-.02em\">Relational Schema<\u002Fh1>\n        <p style=\"margin:4px 0 20px;font-size:13px;color:#838a95\">Six relations organise the ontology. PK = primary key, FK = foreign key. Lines show 1 → ∞ relationships.<\u002Fp>\n        <div style=\"background:#fff;border:1px solid #e6e8ec;border-radius:11px;padding:8px\">\n          <div style=\"position:relative;width:1180px;height:660px;margin:0 auto\">\n            <svg viewBox=\"0 0 1180 660\" style=\"position:absolute;inset:0;width:1180px;height:660px;pointer-events:none\">\n              <!-- projects -> tests -->\n              <path d=\"M250 96 H300\" stroke=\"#c5cdd8\" stroke-width=\"1.5\" fill=\"none\"><\u002Fpath>\n              <!-- tests -> test_conditions -->\n              <path d=\"M375 250 V360\" stroke=\"#c5cdd8\" stroke-width=\"1.5\" fill=\"none\"><\u002Fpath>\n              <!-- tests -> test_results -->\n              <path d=\"M530 150 H560 V300 H620\" stroke=\"#c5cdd8\" stroke-width=\"1.5\" fill=\"none\"><\u002Fpath>\n              <!-- test_conditions -> test_results -->\n              <path d=\"M530 430 H575 V330 H620\" stroke=\"#c5cdd8\" stroke-width=\"1.5\" fill=\"none\"><\u002Fpath>\n              <!-- instruments -> test_results -->\n              <path d=\"M930 150 H895 V285 H850\" stroke=\"#c5cdd8\" stroke-width=\"1.5\" fill=\"none\"><\u002Fpath>\n              <!-- measurement_types -> test_results -->\n              <path d=\"M930 470 H895 V345 H850\" stroke=\"#c5cdd8\" stroke-width=\"1.5\" fill=\"none\"><\u002Fpath>\n              <!-- cardinality dots -->\n              <circle cx=\"250\" cy=\"96\" r=\"3.5\" fill=\"#1f5fdb\"><\u002Fcircle><circle cx=\"375\" cy=\"250\" r=\"3.5\" fill=\"#1f5fdb\"><\u002Fcircle>\n              <circle cx=\"530\" cy=\"150\" r=\"3.5\" fill=\"#1f5fdb\"><\u002Fcircle><circle cx=\"530\" cy=\"430\" r=\"3.5\" fill=\"#1f5fdb\"><\u002Fcircle>\n              <circle cx=\"930\" cy=\"150\" r=\"3.5\" fill=\"#1f5fdb\"><\u002Fcircle><circle cx=\"930\" cy=\"470\" r=\"3.5\" fill=\"#1f5fdb\"><\u002Fcircle>\n              <text x=\"262\" y=\"90\" font-family=\"IBM Plex Mono, monospace\" font-size=\"11\" fill=\"#1f5fdb\">1<\u002Ftext>\n              <text x=\"608\" y=\"296\" font-family=\"IBM Plex Mono, monospace\" font-size=\"11\" fill=\"#9aa1ab\">∞<\u002Ftext>\n              <text x=\"608\" y=\"326\" font-family=\"IBM Plex Mono, monospace\" font-size=\"11\" fill=\"#9aa1ab\">∞<\u002Ftext>\n              <text x=\"862\" y=\"282\" font-family=\"IBM Plex Mono, monospace\" font-size=\"11\" fill=\"#9aa1ab\">∞<\u002Ftext>\n              <text x=\"862\" y=\"342\" font-family=\"IBM Plex Mono, monospace\" font-size=\"11\" fill=\"#9aa1ab\">∞<\u002Ftext>\n              <text x=\"383\" y=\"345\" font-family=\"IBM Plex Mono, monospace\" font-size=\"11\" fill=\"#9aa1ab\">∞<\u002Ftext>\n            <\u002Fsvg>\n\n            <!-- projects -->\n            <div style=\"position:absolute;left:20px;top:30px;width:230px;background:#fff;border:1px solid #d4dae2;border-radius:9px;box-shadow:0 1px 4px rgba(20,30,50,.05);overflow:hidden\">\n              <div style=\"background:#16181d;color:#fff;padding:9px 13px;font-family:'IBM Plex Mono',monospace;font-size:12px;font-weight:600;display:flex;justify-content:space-between\"><span>projects<\u002Fspan><span style=\"color:#7e8794\">experiment<\u002Fspan><\u002Fdiv>\n              <div style=\"font-size:11.5px;color:#3d424b\">\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px;background:#eaf1fe\"><span style=\"font-family:'IBM Plex Mono',monospace\">project_id<\u002Fspan><span style=\"font-family:'IBM Plex Mono',monospace;color:#1f5fdb;font-weight:600\">PK<\u002Fspan><\u002Fdiv>\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px\"><span style=\"font-family:'IBM Plex Mono',monospace\">code<\u002Fspan><span style=\"color:#a4abb4\">varchar<\u002Fspan><\u002Fdiv>\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px\"><span style=\"font-family:'IBM Plex Mono',monospace\">name<\u002Fspan><span style=\"color:#a4abb4\">varchar<\u002Fspan><\u002Fdiv>\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px\"><span style=\"font-family:'IBM Plex Mono',monospace\">objective<\u002Fspan><span style=\"color:#a4abb4\">text<\u002Fspan><\u002Fdiv>\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px\"><span style=\"font-family:'IBM Plex Mono',monospace\">lead_researcher<\u002Fspan><span style=\"color:#a4abb4\">varchar<\u002Fspan><\u002Fdiv>\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px\"><span style=\"font-family:'IBM Plex Mono',monospace\">start_date<\u002Fspan><span style=\"color:#a4abb4\">date<\u002Fspan><\u002Fdiv>\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px\"><span style=\"font-family:'IBM Plex Mono',monospace\">status<\u002Fspan><span style=\"color:#a4abb4\">enum<\u002Fspan><\u002Fdiv>\n              <\u002Fdiv>\n            <\u002Fdiv>\n\n            <!-- tests -->\n            <div style=\"position:absolute;left:300px;top:30px;width:230px;background:#fff;border:1px solid #d4dae2;border-radius:9px;box-shadow:0 1px 4px rgba(20,30,50,.05);overflow:hidden\">\n              <div style=\"background:#16181d;color:#fff;padding:9px 13px;font-family:'IBM Plex Mono',monospace;font-size:12px;font-weight:600;display:flex;justify-content:space-between\"><span>tests<\u002Fspan><span style=\"color:#7e8794\">method run<\u002Fspan><\u002Fdiv>\n              <div style=\"font-size:11.5px;color:#3d424b\">\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px;background:#eaf1fe\"><span style=\"font-family:'IBM Plex Mono',monospace\">test_id<\u002Fspan><span style=\"font-family:'IBM Plex Mono',monospace;color:#1f5fdb;font-weight:600\">PK<\u002Fspan><\u002Fdiv>\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px;background:#fbf2e8\"><span style=\"font-family:'IBM Plex Mono',monospace\">project_id<\u002Fspan><span style=\"font-family:'IBM Plex Mono',monospace;color:#c08428;font-weight:600\">FK<\u002Fspan><\u002Fdiv>\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px\"><span style=\"font-family:'IBM Plex Mono',monospace\">name<\u002Fspan><span style=\"color:#a4abb4\">varchar<\u002Fspan><\u002Fdiv>\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px\"><span style=\"font-family:'IBM Plex Mono',monospace\">material_pair<\u002Fspan><span style=\"color:#a4abb4\">varchar<\u002Fspan><\u002Fdiv>\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px\"><span style=\"font-family:'IBM Plex Mono',monospace\">lubricant<\u002Fspan><span style=\"color:#a4abb4\">varchar<\u002Fspan><\u002Fdiv>\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px\"><span style=\"font-family:'IBM Plex Mono',monospace\">standard<\u002Fspan><span style=\"color:#a4abb4\">varchar<\u002Fspan><\u002Fdiv>\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px\"><span style=\"font-family:'IBM Plex Mono',monospace\">test_date<\u002Fspan><span style=\"color:#a4abb4\">date<\u002Fspan><\u002Fdiv>\n              <\u002Fdiv>\n            <\u002Fdiv>\n\n            <!-- test_conditions -->\n            <div style=\"position:absolute;left:300px;top:360px;width:230px;background:#fff;border:1px solid #d4dae2;border-radius:9px;box-shadow:0 1px 4px rgba(20,30,50,.05);overflow:hidden\">\n              <div style=\"background:#16181d;color:#fff;padding:9px 13px;font-family:'IBM Plex Mono',monospace;font-size:12px;font-weight:600;display:flex;justify-content:space-between\"><span>test_conditions<\u002Fspan><\u002Fdiv>\n              <div style=\"font-size:11.5px;color:#3d424b\">\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px;background:#eaf1fe\"><span style=\"font-family:'IBM Plex Mono',monospace\">condition_id<\u002Fspan><span style=\"font-family:'IBM Plex Mono',monospace;color:#1f5fdb;font-weight:600\">PK<\u002Fspan><\u002Fdiv>\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px;background:#fbf2e8\"><span style=\"font-family:'IBM Plex Mono',monospace\">test_id<\u002Fspan><span style=\"font-family:'IBM Plex Mono',monospace;color:#c08428;font-weight:600\">FK<\u002Fspan><\u002Fdiv>\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px\"><span style=\"font-family:'IBM Plex Mono',monospace\">normal_load_N<\u002Fspan><span style=\"color:#a4abb4\">numeric<\u002Fspan><\u002Fdiv>\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px\"><span style=\"font-family:'IBM Plex Mono',monospace\">sliding_speed_ms<\u002Fspan><span style=\"color:#a4abb4\">numeric<\u002Fspan><\u002Fdiv>\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px\"><span style=\"font-family:'IBM Plex Mono',monospace\">temperature_C<\u002Fspan><span style=\"color:#a4abb4\">numeric<\u002Fspan><\u002Fdiv>\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px\"><span style=\"font-family:'IBM Plex Mono',monospace\">frequency_Hz<\u002Fspan><span style=\"color:#a4abb4\">numeric<\u002Fspan><\u002Fdiv>\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px\"><span style=\"font-family:'IBM Plex Mono',monospace\">atmosphere<\u002Fspan><span style=\"color:#a4abb4\">varchar<\u002Fspan><\u002Fdiv>\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px\"><span style=\"font-family:'IBM Plex Mono',monospace\">humidity_pct<\u002Fspan><span style=\"color:#a4abb4\">numeric<\u002Fspan><\u002Fdiv>\n              <\u002Fdiv>\n            <\u002Fdiv>\n\n            <!-- test_results -->\n            <div style=\"position:absolute;left:620px;top:170px;width:230px;background:#fff;border:2px solid #1f5fdb;border-radius:9px;box-shadow:0 3px 12px rgba(31,95,219,.12);overflow:hidden\">\n              <div style=\"background:#1f5fdb;color:#fff;padding:9px 13px;font-family:'IBM Plex Mono',monospace;font-size:12px;font-weight:600;display:flex;justify-content:space-between\"><span>test_results<\u002Fspan><span style=\"color:#bcd2f7\">fact<\u002Fspan><\u002Fdiv>\n              <div style=\"font-size:11.5px;color:#3d424b\">\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px;background:#eaf1fe\"><span style=\"font-family:'IBM Plex Mono',monospace\">result_id<\u002Fspan><span style=\"font-family:'IBM Plex Mono',monospace;color:#1f5fdb;font-weight:600\">PK<\u002Fspan><\u002Fdiv>\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px;background:#fbf2e8\"><span style=\"font-family:'IBM Plex Mono',monospace\">test_id<\u002Fspan><span style=\"font-family:'IBM Plex Mono',monospace;color:#c08428;font-weight:600\">FK<\u002Fspan><\u002Fdiv>\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px;background:#fbf2e8\"><span style=\"font-family:'IBM Plex Mono',monospace\">condition_id<\u002Fspan><span style=\"font-family:'IBM Plex Mono',monospace;color:#c08428;font-weight:600\">FK<\u002Fspan><\u002Fdiv>\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px;background:#fbf2e8\"><span style=\"font-family:'IBM Plex Mono',monospace\">instrument_id<\u002Fspan><span style=\"font-family:'IBM Plex Mono',monospace;color:#c08428;font-weight:600\">FK<\u002Fspan><\u002Fdiv>\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px;background:#fbf2e8\"><span style=\"font-family:'IBM Plex Mono',monospace\">measurement_id<\u002Fspan><span style=\"font-family:'IBM Plex Mono',monospace;color:#c08428;font-weight:600\">FK<\u002Fspan><\u002Fdiv>\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px\"><span style=\"font-family:'IBM Plex Mono',monospace\">value<\u002Fspan><span style=\"color:#a4abb4\">numeric<\u002Fspan><\u002Fdiv>\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px\"><span style=\"font-family:'IBM Plex Mono',monospace\">uncertainty<\u002Fspan><span style=\"color:#a4abb4\">numeric<\u002Fspan><\u002Fdiv>\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px\"><span style=\"font-family:'IBM Plex Mono',monospace\">measured_at<\u002Fspan><span style=\"color:#a4abb4\">timestamp<\u002Fspan><\u002Fdiv>\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px\"><span style=\"font-family:'IBM Plex Mono',monospace\">operator<\u002Fspan><span style=\"color:#a4abb4\">varchar<\u002Fspan><\u002Fdiv>\n              <\u002Fdiv>\n            <\u002Fdiv>\n\n            <!-- instruments -->\n            <div style=\"position:absolute;left:930px;top:30px;width:230px;background:#fff;border:1px solid #d4dae2;border-radius:9px;box-shadow:0 1px 4px rgba(20,30,50,.05);overflow:hidden\">\n              <div style=\"background:#16181d;color:#fff;padding:9px 13px;font-family:'IBM Plex Mono',monospace;font-size:12px;font-weight:600;display:flex;justify-content:space-between\"><span>instruments<\u002Fspan><span style=\"color:#7e8794\">registry<\u002Fspan><\u002Fdiv>\n              <div style=\"font-size:11.5px;color:#3d424b\">\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px;background:#eaf1fe\"><span style=\"font-family:'IBM Plex Mono',monospace\">instrument_id<\u002Fspan><span style=\"font-family:'IBM Plex Mono',monospace;color:#1f5fdb;font-weight:600\">PK<\u002Fspan><\u002Fdiv>\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px\"><span style=\"font-family:'IBM Plex Mono',monospace\">name<\u002Fspan><span style=\"color:#a4abb4\">varchar<\u002Fspan><\u002Fdiv>\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px\"><span style=\"font-family:'IBM Plex Mono',monospace\">type<\u002Fspan><span style=\"color:#a4abb4\">varchar<\u002Fspan><\u002Fdiv>\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px\"><span style=\"font-family:'IBM Plex Mono',monospace\">manufacturer<\u002Fspan><span style=\"color:#a4abb4\">varchar<\u002Fspan><\u002Fdiv>\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px\"><span style=\"font-family:'IBM Plex Mono',monospace\">model<\u002Fspan><span style=\"color:#a4abb4\">varchar<\u002Fspan><\u002Fdiv>\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px\"><span style=\"font-family:'IBM Plex Mono',monospace\">purpose<\u002Fspan><span style=\"color:#a4abb4\">text<\u002Fspan><\u002Fdiv>\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px\"><span style=\"font-family:'IBM Plex Mono',monospace\">calibration_date<\u002Fspan><span style=\"color:#a4abb4\">date<\u002Fspan><\u002Fdiv>\n              <\u002Fdiv>\n            <\u002Fdiv>\n\n            <!-- measurement_types -->\n            <div style=\"position:absolute;left:930px;top:380px;width:230px;background:#fff;border:1px solid #d4dae2;border-radius:9px;box-shadow:0 1px 4px rgba(20,30,50,.05);overflow:hidden\">\n              <div style=\"background:#16181d;color:#fff;padding:9px 13px;font-family:'IBM Plex Mono',monospace;font-size:12px;font-weight:600;display:flex;justify-content:space-between\"><span>measurement_types<\u002Fspan><\u002Fdiv>\n              <div style=\"font-size:11.5px;color:#3d424b\">\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px;background:#eaf1fe\"><span style=\"font-family:'IBM Plex Mono',monospace\">measurement_id<\u002Fspan><span style=\"font-family:'IBM Plex Mono',monospace;color:#1f5fdb;font-weight:600\">PK<\u002Fspan><\u002Fdiv>\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px\"><span style=\"font-family:'IBM Plex Mono',monospace\">name<\u002Fspan><span style=\"color:#a4abb4\">varchar<\u002Fspan><\u002Fdiv>\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px\"><span style=\"font-family:'IBM Plex Mono',monospace\">symbol<\u002Fspan><span style=\"color:#a4abb4\">varchar<\u002Fspan><\u002Fdiv>\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px\"><span style=\"font-family:'IBM Plex Mono',monospace\">unit<\u002Fspan><span style=\"color:#a4abb4\">varchar<\u002Fspan><\u002Fdiv>\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px\"><span style=\"font-family:'IBM Plex Mono',monospace\">method_standard<\u002Fspan><span style=\"color:#a4abb4\">varchar<\u002Fspan><\u002Fdiv>\n                <div style=\"display:flex;justify-content:space-between;padding:6px 13px\"><span style=\"font-family:'IBM Plex Mono',monospace\">typical_range<\u002Fspan><span style=\"color:#a4abb4\">varchar<\u002Fspan><\u002Fdiv>\n              <\u002Fdiv>\n            <\u002Fdiv>\n          <\u002Fdiv>\n        <\u002Fdiv>\n      <\u002Fdiv>\n      <\u002Fsc-if>\n\n      <!-- ============ ONTOLOGY GRAPH ============ -->\n      <sc-if value=\"{{ isOntology }}\" hint-placeholder-val=\"{{ true }}\">\n      <div>\n        <h1 style=\"margin:0;font-size:21px;font-weight:600;letter-spacing:-.02em\">Ontology Graph<\u002Fh1>\n        <p style=\"margin:4px 0 20px;font-size:13px;color:#838a95\">Entity classes and the relationships that connect a measurement result to its full context.<\u002Fp>\n        <div style=\"background:#fff;border:1px solid #e6e8ec;border-radius:11px;padding:10px\">\n          <svg viewBox=\"0 0 1080 580\" style=\"width:100%;height:auto;display:block;font-family:'IBM Plex Sans',sans-serif\">\n            <defs>\n              <marker id=\"arr\" markerWidth=\"9\" markerHeight=\"9\" refX=\"7\" refY=\"3\" orient=\"auto\" markerUnits=\"strokeWidth\"><path d=\"M0,0 L7,3 L0,6 z\" fill=\"#aeb6c1\"><\u002Fpath><\u002Fmarker>\n            <\u002Fdefs>\n            <!-- edges -->\n            <g stroke=\"#c8cfd9\" stroke-width=\"1.6\" fill=\"none\">\n              <path d=\"M205 110 H300\" marker-end=\"url(#arr)\"><\u002Fpath>\n              <path d=\"M400 120 C460 150 470 200 470 250\" marker-end=\"url(#arr)\"><\u002Fpath>\n              <path d=\"M360 165 C360 280 360 360 360 430\" marker-end=\"url(#arr)\"><\u002Fpath>\n              <path d=\"M540 470 C600 440 630 360 645 310\" marker-end=\"url(#arr)\"><\u002Fpath>\n              <path d=\"M700 250 C760 200 800 170 830 150\" marker-end=\"url(#arr)\"><\u002Fpath>\n              <path d=\"M715 290 H820\" marker-end=\"url(#arr)\"><\u002Fpath>\n              <path d=\"M660 340 C700 410 740 440 790 460\" marker-end=\"url(#arr)\"><\u002Fpath>\n              <path d=\"M460 300 C400 320 360 330 320 340\" marker-end=\"url(#arr)\"><\u002Fpath>\n            <\u002Fg>\n            <!-- edge labels -->\n            <g font-family=\"IBM Plex Mono, monospace\" font-size=\"10.5\" fill=\"#8a909b\">\n              <rect x=\"228\" y=\"92\" width=\"58\" height=\"16\" rx=\"3\" fill=\"#fff\"><\u002Frect><text x=\"232\" y=\"104\">comprises<\u002Ftext>\n              <rect x=\"430\" y=\"178\" width=\"70\" height=\"16\" rx=\"3\" fill=\"#fff\"><\u002Frect><text x=\"434\" y=\"190\">configured by<\u002Ftext>\n              <rect x=\"298\" y=\"290\" width=\"58\" height=\"16\" rx=\"3\" fill=\"#fff\"><\u002Frect><text x=\"302\" y=\"302\">produces<\u002Ftext>\n              <rect x=\"560\" y=\"392\" width=\"62\" height=\"16\" rx=\"3\" fill=\"#fff\"><\u002Frect><text x=\"564\" y=\"404\">applies to<\u002Ftext>\n              <rect x=\"752\" y=\"186\" width=\"68\" height=\"16\" rx=\"3\" fill=\"#fff\"><\u002Frect><text x=\"756\" y=\"198\">measured by<\u002Ftext>\n              <rect x=\"742\" y=\"272\" width=\"62\" height=\"16\" rx=\"3\" fill=\"#fff\"><\u002Frect><text x=\"746\" y=\"284\">quantifies<\u002Ftext>\n              <rect x=\"700\" y=\"410\" width=\"66\" height=\"16\" rx=\"3\" fill=\"#fff\"><\u002Frect><text x=\"704\" y=\"422\">recorded by<\u002Ftext>\n              <rect x=\"360\" y=\"318\" width=\"40\" height=\"16\" rx=\"3\" fill=\"#fff\"><\u002Frect><text x=\"364\" y=\"330\">tests<\u002Ftext>\n            <\u002Fg>\n            <!-- nodes -->\n            <g>\n              <!-- Experiment -->\n              <rect x=\"60\" y=\"86\" width=\"146\" height=\"48\" rx=\"10\" fill=\"#eaf1fe\" stroke=\"#1f5fdb\" stroke-width=\"1.5\"><\u002Frect>\n              <text x=\"133\" y=\"108\" text-anchor=\"middle\" font-size=\"13.5\" font-weight=\"600\" fill=\"#16181d\">Experiment<\u002Ftext>\n              <text x=\"133\" y=\"124\" text-anchor=\"middle\" font-size=\"10\" fill=\"#5e7fb8\" font-family=\"IBM Plex Mono, monospace\">projects<\u002Ftext>\n              <!-- Test -->\n              <rect x=\"292\" y=\"96\" width=\"130\" height=\"48\" rx=\"10\" fill=\"#eaf1fe\" stroke=\"#1f5fdb\" stroke-width=\"1.5\"><\u002Frect>\n              <text x=\"357\" y=\"118\" text-anchor=\"middle\" font-size=\"13.5\" font-weight=\"600\" fill=\"#16181d\">Test<\u002Ftext>\n              <text x=\"357\" y=\"134\" text-anchor=\"middle\" font-size=\"10\" fill=\"#5e7fb8\" font-family=\"IBM Plex Mono, monospace\">tests<\u002Ftext>\n              <!-- TestCondition -->\n              <rect x=\"285\" y=\"430\" width=\"150\" height=\"48\" rx=\"10\" fill=\"#fff5e9\" stroke=\"#e0892e\" stroke-width=\"1.5\"><\u002Frect>\n              <text x=\"360\" y=\"452\" text-anchor=\"middle\" font-size=\"13\" font-weight=\"600\" fill=\"#16181d\">Test Condition<\u002Ftext>\n              <text x=\"360\" y=\"468\" text-anchor=\"middle\" font-size=\"10\" fill=\"#bd7b2a\" font-family=\"IBM Plex Mono, monospace\">test_conditions<\u002Ftext>\n              <!-- TestResult (hub) -->\n              <rect x=\"560\" y=\"248\" width=\"156\" height=\"56\" rx=\"12\" fill=\"#1f5fdb\" stroke=\"#1647a8\" stroke-width=\"1.5\"><\u002Frect>\n              <text x=\"638\" y=\"272\" text-anchor=\"middle\" font-size=\"14.5\" font-weight=\"700\" fill=\"#fff\">Test Result<\u002Ftext>\n              <text x=\"638\" y=\"290\" text-anchor=\"middle\" font-size=\"10\" fill=\"#cadcfb\" font-family=\"IBM Plex Mono, monospace\">test_results · fact<\u002Ftext>\n              <!-- Instrument -->\n              <rect x=\"828\" y=\"118\" width=\"150\" height=\"48\" rx=\"10\" fill=\"#e9f6f1\" stroke=\"#0f9d8a\" stroke-width=\"1.5\"><\u002Frect>\n              <text x=\"903\" y=\"140\" text-anchor=\"middle\" font-size=\"13.5\" font-weight=\"600\" fill=\"#16181d\">Instrument<\u002Ftext>\n              <text x=\"903\" y=\"156\" text-anchor=\"middle\" font-size=\"10\" fill=\"#0c8676\" font-family=\"IBM Plex Mono, monospace\">instruments<\u002Ftext>\n              <!-- MeasurementType -->\n              <rect x=\"820\" y=\"266\" width=\"166\" height=\"48\" rx=\"10\" fill=\"#efeafb\" stroke=\"#6b59c9\" stroke-width=\"1.5\"><\u002Frect>\n              <text x=\"903\" y=\"288\" text-anchor=\"middle\" font-size=\"13\" font-weight=\"600\" fill=\"#16181d\">Measurement Value<\u002Ftext>\n              <text x=\"903\" y=\"304\" text-anchor=\"middle\" font-size=\"10\" fill=\"#5d4cb5\" font-family=\"IBM Plex Mono, monospace\">measurement_types<\u002Ftext>\n              <!-- Operator -->\n              <rect x=\"790\" y=\"438\" width=\"130\" height=\"46\" rx=\"10\" fill=\"#f3f4f6\" stroke=\"#9aa1ab\" stroke-width=\"1.5\"><\u002Frect>\n              <text x=\"855\" y=\"466\" text-anchor=\"middle\" font-size=\"13\" font-weight=\"600\" fill=\"#16181d\">Operator<\u002Ftext>\n            <\u002Fg>\n          <\u002Fsvg>\n        <\u002Fdiv>\n      <\u002Fdiv>\n      <\u002Fsc-if>\n\n    <\u002Fmain>\n  <\u002Fdiv>\n<\u002Fdiv>\n<\u002Fx-dc>\n<script type=\"text/x-dc\" data-dc-script=\"\" data-props=\"{&quot;$preview&quot;:{&quot;width&quot;:1280,&quot;height&quot;:840}}\">\nclass Component extends DCLogic {\n  constructor(props){\n    super(props);\n    this.state = { section:'dashboard', dashView:'analytical', proj:null, test:null };\n    this._build();\n  }\n\n  // ---------- helpers ----------\n  sup(n){ const m={'-':'⁻','0':'⁰','1':'¹','2':'²','3':'³','4':'⁴','5':'⁵','6':'⁶','7':'⁷','8':'⁸','9':'⁹'}; return String(n).split('').map(c=>m[c]||c).join(''); }\n  fmtSci(v){ const e=Math.floor(Math.log10(v)); const m=v/Math.pow(10,e); return m.toFixed(1)+' × 10'+this.sup(e); }\n  fmtVal(mid,v){ const M=this.M[mid]; if(M.sci) return this.fmtSci(v); return v.toFixed(M.dec); }\n  fmtDate(d){ return d.getFullYear()+'-'+String(d.getMonth()+1).padStart(2,'0')+'-'+String(d.getDate()).padStart(2,'0'); }\n  fmtTime(d){ return this.fmtDate(d)+' '+String(d.getHours()).padStart(2,'0')+':'+String(d.getMinutes()).padStart(2,'0'); }\n  statusStyle(s){\n    const map={\n      'Operational':['#0f7a45','#e7f6ee'],'Active':['#1a52bd','#eaf1fe'],'Completed':['#5b6472','#eef0f2'],\n      'In maintenance':['#9a6a12','#fbf0d9'],'Calibration due':['#b23b4e','#fbe9ec'],'Planned':['#5b6472','#eef0f2']\n    };\n    const c=map[s]||['#5b6472','#eef0f2'];\n    return 'display:inline-flex;align-items:center;gap:5px;padding:2px 9px;border-radius:20px;font-size:11px;font-weight:600;letter-spacing:.01em;color:'+c[0]+';background:'+c[1];\n  }\n\n  // ---------- data ----------\n  _build(){\n    const mulberry=(a)=>()=>{a|=0;a=a+0x6D2B79F5|0;let t=Math.imul(a^a>>>15,1|a);t=t+Math.imul(t^t>>>7,61|t)^t;return((t^t>>>14)>>>0)/4294967296;};\n    const today=new Date('2026-06-19'); this.today=today;\n\n    this.instruments=[\n      {id:'INS-01',name:'Pin-on-Disk Tribometer',short:'Pin-on-Disk',type:'Tribometer',mfr:'Anton Paar',model:'TRB³',purpose:'Friction and wear of dry or lubricated rotating pin / ball-on-disk contacts.',measures:['MEA-COF','MEA-TEMP','MEA-WR'],loc:'Bay A-1',cal:'2026-04-02',status:'Operational'},\n      {id:'INS-02',name:'Reciprocating Tribometer (SRV)',short:'Reciprocating SRV',type:'Tribometer',mfr:'Optimol',model:'SRV®5',purpose:'Oscillating / fretting friction, EP and anti-wear film performance under high contact pressure.',measures:['MEA-COF','MEA-TEMP','MEA-WR'],loc:'Bay A-2',cal:'2025-12-18',status:'In maintenance'},\n      {id:'INS-03',name:'Surface Roughness Tester',short:'Roughness Tester',type:'Profilometry',mfr:'Mitutoyo',model:'Surftest SJ-410',purpose:'Stylus 2D roughness (Ra, Rz) of wear tracks and reference substrates.',measures:['MEA-RA'],loc:'Metrology',cal:'2026-05-20',status:'Operational'},\n      {id:'INS-04',name:'Microhardness Tester',short:'Microhardness',type:'Mechanical',mfr:'Struers',model:'DuraScan 70',purpose:'Vickers / Knoop micro-indentation hardness of coatings and substrates.',measures:['MEA-HV'],loc:'Metrology',cal:'2025-06-10',status:'Calibration due'},\n      {id:'INS-05',name:'Digital Optical Microscope',short:'Optical Microscope',type:'Imaging',mfr:'Keyence',model:'VHX-7000',purpose:'High-magnification imaging and wear-scar metrology of contact surfaces.',measures:['MEA-WS'],loc:'Imaging Lab',cal:'2026-01-15',status:'Operational'},\n      {id:'INS-06',name:'Modular Rheometer',short:'Rheometer',type:'Rheology',mfr:'Anton Paar',model:'MCR 302',purpose:'Dynamic viscosity and rheology of lubricants across temperature and shear rate.',measures:['MEA-VIS'],loc:'Fluids Lab',cal:'2026-02-28',status:'Operational'},\n      {id:'INS-07',name:'3D Optical Profilometer',short:'3D Profilometer',type:'Profilometry',mfr:'Bruker',model:'ContourGT-K',purpose:'Areal surface topography, wear-volume reconstruction and lubricant film thickness.',measures:['MEA-WR','MEA-FILM','MEA-RA'],loc:'Metrology',cal:'2025-11-05',status:'Operational'}\n    ];\n    this.instName={}; this.instShort={};\n    this.instruments.forEach(i=>{ this.instName[i.id]=i.name; this.instShort[i.id]=i.short; });\n\n    this.M={\n      'MEA-COF':{name:'Coefficient of Friction',sym:'μ',unit:'—',dec:3,inst:'tribo',std:'ASTM G99',range:'0.01 – 1.2',desc:'Ratio of tangential friction force to applied normal load.'},\n      'MEA-WR':{name:'Specific Wear Rate',sym:'k',unit:'mm³/N·m',sci:true,inst:'INS-07',std:'ASTM G99',range:'1×10⁻⁷ – 5×10⁻⁵',desc:'Worn volume normalised by load and total sliding distance.'},\n      'MEA-WS':{name:'Wear Scar Diameter',sym:'d',unit:'mm',dec:3,inst:'INS-05',std:'ASTM D4172',range:'0.2 – 2.0',desc:'Mean diameter of the wear scar on the stationary specimen.'},\n      'MEA-RA':{name:'Surface Roughness (Ra)',sym:'Ra',unit:'µm',dec:3,inst:'INS-03',std:'ISO 21920',range:'0.01 – 1.0',desc:'Arithmetic mean deviation of the assessed surface profile.'},\n      'MEA-HV':{name:'Vickers Hardness',sym:'HV',unit:'HV',dec:0,inst:'INS-04',std:'ISO 6507',range:'150 – 3500',desc:'Micro-indentation hardness of coating or substrate.'},\n      'MEA-VIS':{name:'Dynamic Viscosity',sym:'η',unit:'mPa·s',dec:1,inst:'INS-06',std:'ASTM D7042',range:'2 – 400',desc:'Lubricant resistance to shear at a defined temperature.'},\n      'MEA-TEMP':{name:'Contact Temperature',sym:'T',unit:'°C',dec:1,inst:'tribo',std:'in-situ',range:'20 – 300',desc:'Near-contact temperature recorded during sliding.'},\n      'MEA-FILM':{name:'Lubricant Film Thickness',sym:'h',unit:'nm',dec:0,inst:'INS-07',std:'EHD',range:'2 – 400',desc:'Thickness of the separating lubricant film in the contact.'}\n    };\n    this.measOrder=['MEA-COF','MEA-WR','MEA-WS','MEA-RA','MEA-HV','MEA-VIS','MEA-TEMP','MEA-FILM'];\n    this.MC={'MEA-COF':'#1f5fdb','MEA-WR':'#c0476b','MEA-WS':'#e0892e','MEA-RA':'#0f9d8a','MEA-HV':'#6b59c9','MEA-VIS':'#2f9e7a','MEA-TEMP':'#d4572a','MEA-FILM':'#3b7bd6'};\n    this.RC={lubricated:'#1f5fdb',dry:'#c0476b',coating:'#0f9d8a',brake:'#e0892e',biomed:'#6b59c9',ehd:'#3b7bd6'};\n    this.RLAB={lubricated:'Lubricated',dry:'Dry / fretting',coating:'Coating',brake:'Brake',biomed:'Biomedical',ehd:'EHD / film'};\n\n    const P=[\n      {id:'PRJ-01',code:'EVDL-24',name:'EV Drivetrain Lubricant Screening',objective:'Screen low-viscosity e-fluids for friction and wear in electric-motor bearing contacts.',lead:'Dr. L. Hoffmann',start:'2025-09-02',status:'Active',tests:[\n        {id:'TST-0101',name:'PAO-6 Baseline Friction Map',pair:'AISI 52100 ball / GCr15 disc',lube:'PAO-6 (non-additised)',std:'ASTM G99',regime:'lubricated',tribo:'INS-01',loads:[5,10,20],speed:0.10,temp:80,atm:'Dry N₂',hum:8,date:'2025-09-14',measures:['MEA-COF','MEA-TEMP','MEA-WR','MEA-WS']},\n        {id:'TST-0102',name:'e-Fluid A Film & Viscosity',pair:'Cu coupon / AISI 1045',lube:'EV e-fluid A',std:'ASTM D7042',regime:'ehd',tribo:'INS-01',loads:[10,20],speed:0.05,temp:40,atm:'Laboratory air',date:'2025-10-01',measures:['MEA-VIS','MEA-FILM','MEA-COF'],vis:[14,34]}\n      ]},\n      {id:'PRJ-02',code:'BIOX-25',name:'Bio-Based Engine Oil Wear Study',objective:'Evaluate anti-wear behaviour of ester base oils versus mineral references under soot loading.',lead:'Dr. M. Costa',start:'2025-07-10',status:'Completed',tests:[\n        {id:'TST-0201',name:'Ester Base-Oil Anti-Wear',pair:'AISI 52100 four-ball',lube:'TMP-trioleate ester',std:'ASTM D4172',regime:'lubricated',tribo:'INS-02',loads:[40,80],speed:0.02,temp:75,freq:50,date:'2025-07-22',measures:['MEA-COF','MEA-WS','MEA-WR']},\n        {id:'TST-0202',name:'Soot-Laden Oil Wear',pair:'AISI 52100 / cast iron',lube:'Ester + 3% carbon black',std:'ASTM D6079',regime:'lubricated',tribo:'INS-02',loads:[40,80],temp:100,freq:50,date:'2025-08-05',measures:['MEA-WS','MEA-WR','MEA-RA']}\n      ]},\n      {id:'PRJ-03',code:'DLCD-25',name:'DLC Coating Durability Evaluation',objective:'Compare a-C:H and ta-C diamond-like coatings for low-friction, high-load durability.',lead:'Dr. A. Singh',start:'2025-10-15',status:'Active',tests:[\n        {id:'TST-0301',name:'a-C:H vs ta-C Friction',pair:'DLC pin / 100Cr6 disc',lube:'Dry',std:'ASTM G99',regime:'coating',tribo:'INS-01',loads:[5,10],speed:0.20,temp:25,hum:40,date:'2025-10-28',measures:['MEA-COF','MEA-WR','MEA-HV'],hv:[1800,2600],cof:[0.06,0.14]},\n        {id:'TST-0302',name:'High-Load Scuffing Limit',pair:'ta-C / bearing steel',lube:'Dry',std:'ASTM G99',regime:'coating',tribo:'INS-01',loads:[20,40],speed:0.20,temp:25,date:'2025-11-12',measures:['MEA-COF','MEA-WS','MEA-TEMP'],cof:[0.09,0.20]}\n      ]},\n      {id:'PRJ-04',code:'AERO-24',name:'Aerospace Bearing Steel Fretting',objective:'Characterise fretting wear of M50 bearing steel across amplitude and temperature.',lead:'Dr. R. Novák',start:'2024-11-04',status:'Completed',tests:[\n        {id:'TST-0401',name:'Fretting Amplitude Sweep',pair:'M50 / M50 steel',lube:'Dry',std:'ASTM G204',regime:'dry',tribo:'INS-02',loads:[10,20],freq:30,temp:25,date:'2024-11-20',measures:['MEA-COF','MEA-WS','MEA-WR']},\n        {id:'TST-0402',name:'Elevated-Temperature Fretting',pair:'M50 / M50 steel',lube:'Dry',std:'ASTM G204',regime:'dry',tribo:'INS-02',loads:[20],freq:30,temp:200,date:'2024-12-09',measures:['MEA-COF','MEA-TEMP','MEA-WR']}\n      ]},\n      {id:'PRJ-05',code:'BRKE-25',name:'Brake Friction Stability',objective:'Assess friction stability and wear of NAO brake pads through fade cycling.',lead:'Dr. S. Park',start:'2025-02-03',status:'Active',tests:[\n        {id:'TST-0501',name:'Fade-Cycle Friction',pair:'NAO pad / grey cast iron',lube:'Dry',std:'SAE J2522',regime:'brake',tribo:'INS-02',loads:[30,60],speed:0.50,freq:5,temp:150,date:'2025-02-17',measures:['MEA-COF','MEA-TEMP','MEA-WR']},\n        {id:'TST-0502',name:'Roughness Evolution',pair:'NAO pad / grey cast iron',lube:'Dry',std:'SAE J2522',regime:'brake',tribo:'INS-02',loads:[30,60],temp:150,date:'2025-03-04',measures:['MEA-RA','MEA-WS','MEA-COF']}\n      ]},\n      {id:'PRJ-06',code:'BIOM-25',name:'Hip Implant UHMWPE Wear',objective:'Quantify wear of CoCrMo against virgin and cross-linked UHMWPE in serum lubricant.',lead:'Dr. E. Romano',start:'2025-05-12',status:'Active',tests:[\n        {id:'TST-0601',name:'CoCr-on-UHMWPE Wear',pair:'CoCrMo / UHMWPE',lube:'25% bovine serum',std:'ISO 14243',regime:'biomed',tribo:'INS-02',loads:[10,20],freq:1,temp:37,date:'2025-05-26',measures:['MEA-COF','MEA-WR','MEA-WS']},\n        {id:'TST-0602',name:'Cross-Linked vs Virgin PE',pair:'CoCrMo / XLPE',lube:'25% bovine serum',std:'ISO 14243',regime:'biomed',tribo:'INS-02',loads:[10,20],freq:1,temp:37,date:'2025-06-10',measures:['MEA-WR','MEA-WS','MEA-RA']}\n      ]},\n      {id:'PRJ-07',code:'WIND-24',name:'Wind Turbine Gear Oil Additives',objective:'Test EP additive packages for micropitting protection in wind gearbox oils.',lead:'Dr. K. Berg',start:'2024-09-18',status:'Completed',tests:[\n        {id:'TST-0701',name:'Micropitting EP Additive',pair:'16MnCr5 gear / disc',lube:'PAG + 1.2% EP',std:'FVA 54',regime:'lubricated',tribo:'INS-01',loads:[20,40],speed:0.80,temp:90,date:'2024-10-02',measures:['MEA-COF','MEA-WR','MEA-RA']},\n        {id:'TST-0702',name:'Gear-Oil Viscosity vs Temperature',pair:'— (fluid only)',lube:'PAG ISO VG 320',std:'ASTM D7042',regime:'lubricated',tribo:'INS-06',loads:[20],temp:40,date:'2024-10-20',measures:['MEA-VIS'],vis:[60,330]}\n      ]},\n      {id:'PRJ-08',code:'CUTL-25',name:'Cutting Tool Coating Tribology',objective:'Benchmark TiAlN and AlCrN tool coatings for friction and high-temperature wear.',lead:'Dr. T. Yamada',start:'2025-03-22',status:'Active',tests:[\n        {id:'TST-0801',name:'TiAlN vs AlCrN Friction',pair:'TiAlN / Inconel 718',lube:'Dry',std:'ASTM G99',regime:'coating',tribo:'INS-01',loads:[5,10],speed:0.30,temp:25,date:'2025-04-05',measures:['MEA-COF','MEA-HV','MEA-WR'],hv:[2800,3400],cof:[0.30,0.52]},\n        {id:'TST-0802',name:'High-Temperature Wear',pair:'AlCrN / Ti-6Al-4V',lube:'Dry',std:'ASTM G99',regime:'coating',tribo:'INS-01',loads:[10],speed:0.30,temp:220,date:'2025-04-21',measures:['MEA-COF','MEA-WS','MEA-TEMP'],cof:[0.35,0.60]}\n      ]}\n    ];\n\n    const REG={lubricated:[0.05,0.12],dry:[0.45,0.72],coating:[0.08,0.18],brake:[0.32,0.46],biomed:[0.04,0.09],ehd:[0.04,0.10]};\n    const ops=['A. Meyer','J. Wu','P. Sørensen','N. Patel','C. Rossi','F. Khan'];\n    const perCond={'MEA-COF':1,'MEA-WR':1,'MEA-WS':1,'MEA-TEMP':1,'MEA-FILM':1};\n    const clamp=(x,a,b)=>Math.max(a,Math.min(b,x));\n    const lerp=(a,b,t)=>a+(b-a)*t;\n    let rc=0; const all=[];\n    P.forEach((pr,pi)=>{\n      pr.tests.forEach((t,ti)=>{\n        const rnd=mulberry((pi+1)*131+(ti+1)*17+7);\n        const loads=t.loads||[10];\n        t.lmin=Math.min.apply(null,loads); t.lmax=Math.max.apply(null,loads);\n        t.conds=loads.map((ld,ci)=>({id:'CND-'+(pi+1)+''+(ti+1)+''+(ci+1),load:ld,speed:t.speed||0.10,temp:t.temp||25,freq:t.freq||null,dur:t.dur||3600,atm:t.atm||'Laboratory air',hum:(t.hum!=null?t.hum:45)}));\n        t.results=[];\n        const base=new Date(t.date+'T08:00:00'); let mins=0;\n        t.measures.forEach(m=>{\n          const cset=perCond[m]?t.conds:[t.conds[0]];\n          cset.forEach(cond=>{\n            const frac=(cond.load-t.lmin)/((t.lmax-t.lmin)||1);\n            const r=rnd();\n            let v;\n            if(m==='MEA-COF'){ const rg=t.cof||REG[t.regime]; v=clamp(rg[0]+(rg[1]-rg[0])*(0.3+0.55*frac)+(r-0.5)*(rg[1]-rg[0])*0.3, rg[0]*0.7, rg[1]*1.15); }\n            else if(m==='MEA-WR'){ v=Math.pow(10, lerp(-6.6,-4.7, 0.25+0.5*frac+0.25*(r-0.5))); }\n            else if(m==='MEA-WS'){ v=(0.45+0.7*frac)*(0.9+0.25*r); }\n            else if(m==='MEA-RA'){ v=0.04+0.5*r; }\n            else if(m==='MEA-HV'){ const rg=t.hv||[620,920]; v=rg[0]+(rg[1]-rg[0])*r; }\n            else if(m==='MEA-VIS'){ const rg=t.vis||[20,200]; v=rg[0]+(rg[1]-rg[0])*r; }\n            else if(m==='MEA-TEMP'){ v=(cond.temp||25)+12+frac*45+r*10; }\n            else if(m==='MEA-FILM'){ v=(180-110*frac)*(0.85+0.25*r); }\n            else v=r;\n            mins+=8+Math.floor(rnd()*26);\n            const inst=this.M[m].inst==='tribo'?t.tribo:this.M[m].inst;\n            all.push({id:'RES-'+String(++rc).padStart(4,'0'),pid:pr.id,pcode:pr.code,tid:t.id,mid:m,cond:cond.id,load:cond.load,inst,value:v,unc:v*(0.03+0.03*rnd()),time:new Date(base.getTime()+mins*60000),op:ops[Math.floor(rnd()*ops.length)],regime:t.regime});\n          });\n        });\n      });\n    });\n    P.forEach(pr=>{ pr.tests.forEach(t=>{ t.results=all.filter(r=>r.tid===t.id); t.rcount=t.results.length; }); pr.rcount=pr.tests.reduce((a,t)=>a+t.rcount,0); });\n    this.projects=P; this.results=all;\n\n    // ----- aggregates -----\n    this.kpis=[\n      {label:'Experiments',value:String(P.length),sub:'projects'},\n      {label:'Tests',value:String(P.reduce((a,p)=>a+p.tests.length,0)),sub:'method runs'},\n      {label:'Result records',value:String(all.length),sub:'measured values'},\n      {label:'Instruments',value:String(this.instruments.length),sub:'registered'},\n      {label:'Measurement types',value:String(this.measOrder.length),sub:'dictionary'}\n    ];\n\n    // scatter: COF vs load\n    const cof=all.filter(r=>r.mid==='MEA-COF');\n    const maxLoad=Math.ceil(Math.max.apply(null,cof.map(r=>r.load))/10)*10;\n    const maxCof=Math.ceil(Math.max.apply(null,cof.map(r=>r.value))*10)/10;\n    const SL=46,SR=14,ST=14,SB=38,SW=560,SH=240; const pw=SW-SL-SR, ph=SH-ST-SB;\n    this.scatter={\n      L:SL,R:SR,T:ST,B:SB,W:SW,H:SH,x0:SL,x1:SW-SR,y0:ST,y1:SH-SB,\n      points:cof.map(r=>({cx:(SL+(r.load/maxLoad)*pw).toFixed(1),cy:(ST+ph-(r.value/maxCof)*ph).toFixed(1),color:this.RC[r.regime]})),\n      xticks:[0,1,2,3,4].map(i=>{const val=maxLoad*i/4;return{x:(SL+(val/maxLoad)*pw).toFixed(1),label:String(Math.round(val))};}),\n      yticks:[0,1,2,3,4].map(i=>{const val=maxCof*i/4;return{y:(ST+ph-(val/maxCof)*ph).toFixed(1),label:val.toFixed(2)};})\n    };\n    this.scatterLegend=Object.keys(this.RLAB).filter(k=>cof.some(r=>r.regime===k)).map(k=>({label:this.RLAB[k],dot:'width:9px;height:9px;border-radius:9px;background:'+this.RC[k]}));\n\n    // wear bars by experiment\n    const lmin=-7,lmax=-4;\n    const wbars=P.map(p=>{ const w=all.filter(r=>r.pid===p.id&&r.mid==='MEA-WR'); if(!w.length)return null; const mean=w.reduce((a,r)=>a+r.value,0)/w.length; return {code:p.code,mean}; }).filter(Boolean);\n    const N=wbars.length, slot=pw/N, gap=Math.min(26,slot*0.42);\n    this.wear={\n      L:SL,R:SR,T:ST,B:SB,W:SW,H:SH,x0:SL,x1:SW-SR,y0:ST,y1:SH-SB,\n      bars:wbars.map((b,i)=>{const h=clamp((Math.log10(b.mean)-lmin)/(lmax-lmin),0,1)*ph; return {x:(SL+i*slot+gap/2).toFixed(1),y:(ST+ph-h).toFixed(1),w:(slot-gap).toFixed(1),h:h.toFixed(1),code:b.code,valStr:this.fmtSci(b.mean),lx:(SL+i*slot+slot/2).toFixed(1),vy:(ST+ph-h-7).toFixed(1)};}),\n      yticks:[-7,-6,-5,-4].map(e=>({y:(ST+ph-((e-lmin)/(lmax-lmin))*ph).toFixed(1),label:'10'+this.sup(e)}))\n    };\n\n    // instrument utilization\n    const util=this.instruments.map(i=>({id:i.id,short:i.short,count:all.filter(r=>r.inst===i.id).length})).sort((a,b)=>b.count-a.count);\n    const umax=Math.max.apply(null,util.map(u=>u.count))||1;\n    this.util=util.map(u=>({id:u.id,short:u.short,count:u.count,barStyle:'height:8px;border-radius:6px;background:#1f5fdb;width:'+Math.round(u.count/umax*100)+'%'}));\n\n    // instruments view\n    this.instrumentsView=this.instruments.map(i=>{\n      const cal=new Date(i.cal+'T00:00:00'); const days=Math.round((today-cal)/86400000);\n      const due=days>365;\n      return {id:i.id,name:i.name,type:i.type,mfr:i.mfr,model:i.model,purpose:i.purpose,loc:i.loc,cal:i.cal,status:i.status,\n        statusStyle:this.statusStyle(i.status),\n        chips:i.measures.map(m=>({sym:this.M[m].sym,name:this.M[m].name,style:'display:inline-flex;align-items:center;gap:5px;padding:2px 8px;border-radius:6px;font-size:11px;background:#f1f3f6;color:#444b56;border:1px solid #e6e8ec'})),\n        used:all.filter(r=>r.inst===i.id).length,\n        calNote:due?(Math.round(days/30)+' mo ago · overdue'):('valid · '+i.cal),\n        calStyle:'font-family:\\\"IBM Plex Mono\\\",monospace;font-size:11px;color:'+(due?'#b23b4e':'#7a818c')};\n    });\n\n    // measurements view\n    this.measView=this.measOrder.map(m=>{ const M=this.M[m]; const insts=this.instruments.filter(i=>i.measures.indexOf(m)>-1).map(i=>i.id);\n      return {id:m,name:M.name,sym:M.sym,unit:M.unit,std:M.std,range:M.range,desc:M.desc,\n        color:this.MC[m],dot:'width:10px;height:10px;border-radius:10px;flex:none;background:'+this.MC[m],\n        count:all.filter(r=>r.mid===m).length, inst:insts.join(', ')};\n    });\n\n    // grid projects\n    this.gridProjectsBase=P.map(p=>({id:p.id,code:p.code,name:p.name,objective:p.objective,lead:p.lead,start:p.start,status:p.status,statusStyle:this.statusStyle(p.status),tests:p.tests.length,rcount:p.rcount}));\n\n    // recent\n    this.recentAll=all.slice().sort((a,b)=>b.time-a.time).slice(0,9).map(r=>this._row(r));\n\n    // calibration list\n    this.calib=this.instrumentsView.map(i=>({id:i.id,short:this.instShort[i.id],status:i.status,statusStyle:i.statusStyle,note:i.calNote,noteStyle:i.calStyle}));\n\n    // active experiments (operational)\n    const rmax=Math.max.apply(null,P.map(p=>p.rcount));\n    this.activeExp=P.map(p=>({code:p.code,name:p.name,status:p.status,statusStyle:this.statusStyle(p.status),tests:p.tests.length,rcount:p.rcount,barStyle:'height:6px;border-radius:6px;background:'+(p.status==='Completed'?'#9aa6b6':'#1f5fdb')+';width:'+Math.round(p.rcount/rmax*100)+'%'}));\n  }\n\n  _row(r){\n    const M=this.M[r.mid];\n    return {id:r.id,measName:M.name,sym:M.sym,inst:this.instShort[r.inst],instId:r.inst,cond:r.load+' N',valStr:this.fmtVal(r.mid,r.value),unit:M.unit,time:this.fmtTime(r.time),op:r.op,unc:'± '+this.fmtVal(r.mid,r.unc),\n      dot:'width:9px;height:9px;border-radius:9px;flex:none;background:'+this.MC[r.mid]};\n  }\n\n  // ---------- nav ----------\n  go(s){ this.setState({section:s,proj:null,test:null}); }\n  openProject(id){ this.setState({section:'projects',proj:id,test:null}); }\n  openTest(id){ this.setState({test:id}); }\n  backGrid(){ this.setState({proj:null,test:null}); }\n  backProj(){ this.setState({test:null}); }\n  setDash(v){ this.setState({dashView:v}); }\n\n  renderVals(){\n    const sec=this.state.section;\n    const navDef=[\n      {key:'dashboard',label:'Dashboard',tag:'DB'},\n      {key:'projects',label:'Experiments',tag:'EX',count:String(this.projects.length)},\n      {key:'instruments',label:'Instruments',tag:'IN',count:String(this.instruments.length)},\n      {key:'measurements',label:'Measurement Values',tag:'MV',count:String(this.measOrder.length)},\n      {key:'schema',label:'Relational Schema',tag:'DB'},\n      {key:'ontology',label:'Ontology Graph',tag:'OG'}\n    ];\n    const navItems=navDef.map(n=>{\n      const active=sec===n.key;\n      return {key:n.key,label:n.label,tag:n.tag,count:n.count,hasCount:n.count!=null,\n        onClick:()=>this.go(n.key),\n        style:'display:flex;align-items:center;gap:11px;width:100%;padding:8px 11px;border:none;border-radius:8px;cursor:pointer;text-align:left;font:inherit;font-size:13.5px;margin:1px 0;'+(active?'background:#eaf1fe;color:#16181d;font-weight:600;':'background:transparent;color:#475059;font-weight:500;'),\n        tagStyle:'width:24px;height:19px;border-radius:5px;display:flex;align-items:center;justify-content:center;font-family:\\\"IBM Plex Mono\\\",monospace;font-size:9px;font-weight:600;flex:none;'+(active?'background:#1f5fdb;color:#fff;':'background:#eceef1;color:#8a909b;'),\n        countStyle:'margin-left:auto;font-family:\\\"IBM Plex Mono\\\",monospace;font-size:11px;color:'+(active?'#5e7fb8':'#aab0ba')};\n    });\n\n    // experiments mode\n    const proj=this.projects.find(p=>p.id===this.state.proj)||null;\n    const test=proj?proj.tests.find(t=>t.id===this.state.test):null;\n    let cur=null, curTest=null;\n    if(proj){\n      cur={id:proj.id,code:proj.code,name:proj.name,objective:proj.objective,lead:proj.lead,start:proj.start,status:proj.status,statusStyle:this.statusStyle(proj.status),rcount:proj.rcount,\n        tests:proj.tests.map(t=>({id:t.id,name:t.name,pair:t.pair,lube:t.lube,std:t.std,date:t.date,rcount:t.rcount,regime:this.RLAB[t.regime],onClick:()=>this.openTest(t.id),\n          dot:'width:8px;height:8px;border-radius:8px;flex:none;background:'+this.RC[t.regime]}))};\n    }\n    if(test){\n      curTest={id:test.id,name:test.name,pair:test.pair,lube:test.lube,std:test.std,date:test.date,regime:this.RLAB[test.regime],\n        meta:[\n          {k:'Material pair',v:test.pair},{k:'Lubricant / medium',v:test.lube},{k:'Test standard',v:test.std},\n          {k:'Contact regime',v:this.RLAB[test.regime]},{k:'Tribometer',v:this.instShort[test.tribo]},{k:'Date',v:test.date}\n        ],\n        conds:test.conds.map(c=>({id:c.id,load:c.load+' N',speed:c.speed+' m/s',temp:c.temp+' °C',freq:c.freq?c.freq+' Hz':'—',dur:c.dur+' s',atm:c.atm,hum:c.hum!=null?c.hum+' %':'—'})),\n        results:test.results.map(r=>this._row(r))};\n    }\n\n    const segBase='padding:6px 14px;border:1px solid #d8dce1;background:#fff;cursor:pointer;font:inherit;font-size:12.5px;font-weight:500;color:#5b6472;';\n    const segOn='padding:6px 14px;border:1px solid #1f5fdb;background:#1f5fdb;cursor:pointer;font:inherit;font-size:12.5px;font-weight:600;color:#fff;';\n\n    return {\n      navItems,\n      isDashboard:sec==='dashboard', isProjects:sec==='projects', isInstruments:sec==='instruments',\n      isMeasurements:sec==='measurements', isSchema:sec==='schema', isOntology:sec==='ontology',\n      dashAnalytical:this.state.dashView==='analytical', dashOperational:this.state.dashView==='operational',\n      onAnalytical:()=>this.setDash('analytical'), onOperational:()=>this.setDash('operational'),\n      segA:(this.state.dashView==='analytical'?segOn:segBase)+'border-radius:7px 0 0 7px;',\n      segB:(this.state.dashView==='operational'?segOn:segBase)+'border-radius:0 7px 7px 0;border-left:none;',\n      kpis:this.kpis, scatter:this.scatter, scatterLegend:this.scatterLegend, wear:this.wear, util:this.util,\n      recent:this.recentAll, calib:this.calib, activeExp:this.activeExp,\n      instrumentsView:this.instrumentsView, measView:this.measView,\n      gridProjects:this.gridProjectsBase.map(p=>Object.assign({},p,{onClick:()=>this.openProject(p.id)})),\n      projGrid:!proj, projDetail:!!proj&&!test, testDetail:!!test,\n      cur, curTest,\n      onGrid:()=>this.backGrid(), onProj:()=>this.backProj(),\n      openProject:(id)=>this.openProject(id)\n    };\n  }\n}\n<\u002Fscript>\n\n\n<\u002Fbody><\u002Fhtml>"
  </script>
</body>
</html>