<?php declare(strict_types=1);
$__platformDir = __DIR__ . '/android_cast/examples/platform';
if (!is_file($__platformDir . '/footer.php')) {
    $__platformDir = dirname(__DIR__) . '/platform';
}
require_once $__platformDir . '/footer.php';
$__sessionFile = $__platformDir . '/shared_session.php';
$hubCrashesBase = '/app/androidcast_project/crashes';
$hubUsername = '';
if (is_file($__sessionFile) && function_exists('session_start')) {
    require_once $__sessionFile;
    platform_start_session('ac_crash_sess', '/app/androidcast_project');
    $hubUser = $_SESSION['user'] ?? null;
    if (is_array($hubUser)) {
        $hubUsername = trim((string) ($hubUser['username'] ?? ''));
    }
}
unset($__platformDir, $__sessionFile);

function hub_h(string $s): string {
    return htmlspecialchars($s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
?>
<!DOCTYPE html>
<html lang="en" data-theme="dark" data-clock-font="mixed">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Android Cast — Landing</title>
  <script>
    (function () {
      var t = localStorage.getItem('crash_console_theme');
      if (t === 'light' || t === 'dark') document.documentElement.setAttribute('data-theme', t);
      var l = localStorage.getItem('crash_console_lang');
      if (l === 'en' || l === 'ru') document.documentElement.setAttribute('lang', l);
    })();
  </script>
  <link rel="stylesheet" href="/app/androidcast_project/crashes/assets/css/app.css">
  <link rel="stylesheet" href="hub.css?v=20260610overlay">
  <link rel="stylesheet" href="hub-logo-layers.css?v=20260610overlay">
  <link rel="stylesheet" href="hub-landing.css?v=20260613layout">
  <script type="importmap">
  {
    "imports": {
      "three": "https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js"
    }
  }
  </script>
  <script src="/app/androidcast_project/crashes/assets/js/nav_shell.js" defer></script>
  <script src="assets/hub-i18n.js" defer></script>
  <script src="assets/hub-landing.js" defer></script>
  <script src="assets/analytics.config.js"></script>
  <script src="/app/androidcast_project/crashes/assets/js/analytics.js" defer></script>
</head>
<body class="hub-page hub-landing">
  <div id="lcd-alphabet-host" hidden aria-hidden="true"></div>
  <div id="overtime-alphabet-host" hidden aria-hidden="true"></div>
  <div id="quartz-alphabet-host" hidden aria-hidden="true"></div>
  <div id="hub-logo-stage" class="hub-logo-stage" hidden aria-hidden="true"></div>
  <section class="hub-clock-widget" id="hub-clock-widget" aria-label="World clock widget">
    <div class="cw-clock-inner">
      <div class="cw-primary-block">
        <div class="cw-line cw-row cw-row-clock cw-line-time">
          <strong class="cw-time-main">
            <span id="cw-hm-lcd" class="ot-lcd-line ot-lcd-bold cw-col-hm" aria-label="Hours and minutes"></span>
            <span id="cw-colon-lcd" class="ot-lcd-line ot-lcd-colon cw-colon-blink" aria-label="Separator"></span>
            <span id="cw-ss-lcd" class="ot-lcd-line ot-lcd-sec cw-col-ss" aria-label="Seconds"></span>
            <span id="cw-tz-text" class="ot-lcd-line ot-lcd-tz cw-col-tz" aria-label="Timezone offset"></span>
          </strong>
        </div>
        <div class="cw-line cw-row cw-row-tz cw-line-region ot-lcd-subline" id="cw-region">EUROPE/WARSAW</div>
        <div class="cw-line cw-row cw-row-date cw-line-date ot-lcd-subline" id="cw-date-line">WED 27 MAY 2026</div>
      </div>
      <div class="cw-separator" aria-hidden="true"></div>
      <div class="cw-world-section" id="cw-world-row"></div>
    </div>
  </section>
  <div id="hub-docs-modal" class="hub-docs-modal" hidden aria-hidden="true">
    <div class="hub-docs-backdrop" data-close-docs></div>
    <section class="hub-docs-panel card card--lift" role="dialog" aria-modal="true" aria-labelledby="hub-docs-title">
      <header class="hub-docs-header">
        <h2 id="hub-docs-title">Deployment model</h2>
        <button type="button" class="hub-docs-close" data-close-docs aria-label="Close">x</button>
      </header>
      <div class="hub-docs-layout">
        <aside class="hub-docs-toc detail-tree" id="hub-docs-toc" aria-label="Table of contents">
          <p class="muted hub-docs-toc-title">Table of contents</p>
        </aside>
        <div class="hub-docs-panels" id="hub-docs-panels"></div>
      </div>
    </section>
  </div>
  <header class="landing-top" id="landing-top">
    <a class="landing-top-logo" href="?page=1" data-goto-page="1">
      <img src="assets/hub-logo-transparent.svg" alt="" width="36" height="27" decoding="async">
      <strong>Android Cast</strong>
    </a>
    <nav class="landing-top-nav" aria-label="Landing menu">
      <div class="landing-menu-item">
        <button type="button" class="landing-menu-btn" aria-haspopup="true" aria-expanded="false" data-goto-page="2" data-i18n="nav.androidcast">AndroidCast</button>
        <div class="landing-dropdown" role="menu">
          <a href="#" role="menuitem" data-goto-page="3" data-i18n="nav.about">About</a>
          <a href="#" role="menuitem" data-goto-page="4" data-i18n="nav.why">Why</a>
          <a href="#" role="menuitem" data-goto-page="5" data-i18n="nav.tech">Technologies</a>
        </div>
      </div>
      <div class="landing-menu-item">
        <button type="button" class="landing-menu-btn" aria-haspopup="true" aria-expanded="false" data-goto-page="6" data-i18n="nav.resources">Resources</button>
        <div class="landing-dropdown" role="menu">
          <a href="#" role="menuitem" data-goto-page="7" data-i18n="nav.people">People</a>
          <a href="#" role="menuitem" data-goto-page="8" data-i18n="nav.culture">Culture</a>
        </div>
      </div>
      <a class="landing-menu-link" href="#" data-goto-page="9" data-i18n="nav.pricing">Pricing</a>
      <a class="landing-menu-link" href="#" data-goto-page="10" data-i18n="nav.contact">Contact us</a>
    </nav>
    <div class="landing-top-tools">
      <label class="toolbar-select hub-theme">
        <span data-i18n="theme.label">Theme</span>
        <select id="theme-select" aria-label="UI theme">
          <option value="dark">Dark</option>
          <option value="light">Light</option>
        </select>
      </label>
      <label class="toolbar-select locale-toolbar-select hub-locale" data-i18n-title="lang.label" title="Language">
        <span class="locale-flag" id="locale-flag" aria-hidden="true">🇬🇧</span>
        <select class="lang-select" id="lang-select" data-i18n-aria="lang.label" aria-label="Language">
          <option value="en">EN</option>
          <option value="ru">RU</option>
        </select>
      </label>
    </div>
  </header>

  <div class="landing-frame">
    <nav class="nav-pane landing-left" id="landing-nav-pane" aria-label="Android Cast apps">
      <button type="button" class="nav-handle" id="landing-nav-handle" data-i18n-aria="nav.toggle" data-i18n-title="nav.toggle" aria-label="Navigation" title="Navigation">
        <span class="nav-icon nav-icon--menu" aria-hidden="true"></span>
      </button>
      <ul class="nav-list">
        <li><a href="crashes/?view=home" class="nav-link" data-i18n-title="nav.console" title="Console"><span class="nav-icon nav-icon--home"></span><span class="nav-text"><span class="nav-label" data-i18n="nav.console">Console</span><span class="nav-desc" data-i18n="nav.console_desc">Crash triage home and workspace cards.</span></span></a></li>
        <li><a href="git/" class="nav-link" data-i18n-title="nav.git" title="Git"><span class="nav-icon nav-icon--git"></span><span class="nav-text"><span class="nav-label" data-i18n="nav.git">Git</span><span class="nav-desc" data-i18n="nav.git_desc">Browse repos, branches, and tags.</span></span></a></li>
        <li><a href="crashes/?view=reports" class="nav-link" data-i18n-title="nav.issues" title="Issues"><span class="nav-icon nav-icon--reports"></span><span class="nav-text"><span class="nav-label" data-i18n="nav.issues">Issues</span><span class="nav-desc" data-i18n="nav.issues_desc">Browse and triage crash reports.</span></span></a></li>
        <li><a href="crashes/?view=tickets" class="nav-link" data-i18n-title="nav.tickets" title="Tickets"><span class="nav-icon nav-icon--tickets"></span><span class="nav-text"><span class="nav-label" data-i18n="nav.tickets">Tickets</span><span class="nav-desc" data-i18n="nav.tickets_desc">Roadmap tasks, QA items, and tags.</span></span></a></li>
        <li><a href="graphs/" class="nav-link" data-i18n-title="nav.graphs" title="Graphs"><span class="nav-icon nav-icon--graphs"></span><span class="nav-text"><span class="nav-label" data-i18n="nav.graphs">Graphs</span><span class="nav-desc" data-i18n="nav.graphs_desc">Sessions, crashes, and device activity.</span></span></a></li>
        <li><a href="crashes/?view=remote_access" class="nav-link" data-i18n-title="nav.remote" title="Remote access"><span class="nav-icon nav-icon--remote"></span><span class="nav-text"><span class="nav-label" data-i18n="nav.remote">Remote access</span><span class="nav-desc" data-i18n="nav.remote_desc">WireGuard sessions and device reachability.</span></span></a></li>
        <li><a href="crashes/?view=short_links" class="nav-link" data-i18n-title="nav.short_links" title="Short links"><span class="nav-icon nav-icon--link"></span><span class="nav-text"><span class="nav-label" data-i18n="nav.short_links">Short links</span><span class="nav-desc" data-i18n="nav.short_links_desc">Mint bearer tokens and shorten URLs for s.f0xx.org.</span></span></a></li>
        <li><a href="build/" class="nav-link" data-i18n-title="nav.builder" title="Builder"><span class="nav-icon nav-icon--builder"></span><span class="nav-text"><span class="nav-label" data-i18n="nav.builder">Builder</span><span class="nav-desc" data-i18n="nav.builder_desc">Docker CI for APK baking and OTA artifacts.</span></span></a></li>
      </ul>
      <div class="nav-user">
        <?php if ($hubUsername !== ''): ?>
        <span class="nav-user-name" title="<?= hub_h($hubUsername) ?>">
          <span class="nav-icon nav-icon--user" aria-hidden="true"></span>
          <span class="nav-label"><?= hub_h($hubUsername) ?></span>
        </span>
        <a href="<?= hub_h($hubCrashesBase) ?>/account-security"
           class="nav-link"
           data-i18n-aria="nav.security" data-i18n-title="nav.security"
           aria-label="Security" title="Security">
          <span class="nav-icon nav-icon--user" aria-hidden="true"></span>
          <span class="nav-label" data-i18n="nav.security">Security</span>
        </a>
        <a href="<?= hub_h($hubCrashesBase) ?>/logout"
           class="nav-link nav-link--logout"
           data-i18n-aria="nav.logout" data-i18n-title="nav.logout"
           aria-label="Logout" title="Logout">
          <span class="nav-icon nav-icon--logout" aria-hidden="true"></span>
          <span class="nav-label" data-i18n="nav.logout">Logout</span>
        </a>
        <?php else: ?>
        <a href="<?= hub_h($hubCrashesBase) ?>/login"
           class="nav-link"
           data-i18n-aria="nav.sign_in" data-i18n-title="nav.sign_in"
           aria-label="Sign in" title="Sign in">
          <span class="nav-icon nav-icon--user" aria-hidden="true"></span>
          <span class="nav-label" data-i18n="nav.sign_in">Sign in</span>
        </a>
        <?php endif; ?>
      </div>
    </nav>

    <main class="landing-scroll" id="landing-scroll" tabindex="-1">
      <?php require __DIR__ . '/landing-pages.inc.php'; ?>
    </main>
  </div>

  <aside class="landing-rail" id="landing-rail" aria-label="Page navigation">
    <button type="button" class="landing-rail-handle" id="landing-rail-handle" data-i18n-aria="rail.handle" aria-label="Show page navigation"></button>
    <nav class="landing-rail-dots" aria-label="Pages">
      <?php for ($p = 1; $p <= 10; $p++): ?>
      <button type="button" class="landing-rail-dot<?= $p === 1 ? ' is-active' : '' ?>" data-page="<?= $p ?>" aria-label="Page <?= $p ?>"<?= $p === 1 ? ' aria-current="true"' : '' ?>></button>
      <?php endfor; ?>
    </nav>
  </aside>

  <?php platform_render_footer([
    'use_i18n' => false,
    'extra_class' => 'bottom-bar',
  ]); ?>
  <script>
    (function () {
      var footerLeft = document.querySelector('.platform-footer__left');
      if (footerLeft) {
        footerLeft.setAttribute('data-i18n', 'footer.copyright');
        footerLeft.setAttribute('data-holder', 'Anton Afanaasyeu');
        footerLeft.setAttribute('data-year', '2026 - current');
      }
    })();
  </script>
  <script>
    (function () {
      var sel = document.getElementById('theme-select');
      if (sel) {
        var cur = document.documentElement.getAttribute('data-theme') || 'dark';
        sel.value = cur;
        sel.addEventListener('change', function () {
          var v = sel.value;
          document.documentElement.setAttribute('data-theme', v);
          try { localStorage.setItem('crash_console_theme', v); } catch (e) {}
        });
      }

      var stage = document.getElementById('hub-logo-stage');

      var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
      var supportsSvg = !!(svg && svg.createSVGRect);
      var supportsLayout = !!(window.CSS && CSS.supports && CSS.supports('object-fit', 'cover'));
      var logoEnabled = !!(stage && supportsSvg && supportsLayout);

      var logoBuild = '20260606globe14';

      function hubAsset(rel) {
        var link = document.querySelector('link[href*="hub.css"]');
        if (link) {
          var href = link.getAttribute('href') || '';
          return href.replace(/[^/]*$/, '') + rel;
        }
        return rel;
      }

      var LCD_FONT_CATALOG = {
        'dseg14-classic': {
          url: 'assets/dseg14-classic-alphabet.svg',
          prefix: 'd14c',
          css: '"DSEG14 Classic", "Consolas", monospace'
        },
        'dseg14-modern': {
          url: 'assets/dseg14-modern-alphabet.svg',
          prefix: 'd14m',
          css: '"DSEG14 Modern", "Consolas", monospace'
        },
        'ds-digital': {
          url: 'assets/ds-digital-alphabet.svg',
          prefix: 'dsd',
          css: '"DS-Digital", "Consolas", monospace'
        },
        'digital-7-mono-italic': {
          url: 'assets/digital-7-mono-italic-alphabet.svg',
          prefix: 'd7mi',
          css: '"Digital-7 Mono Italic", "Consolas", monospace'
        }
      };

      var clockFontParams = new URLSearchParams(window.location.search);
      var clockFontKey = (clockFontParams.get('clockFont') || 'digital-7-mono-italic').toLowerCase();
      if (!LCD_FONT_CATALOG[clockFontKey]) clockFontKey = 'digital-7-mono-italic';
      var lcdFontReady = false;
      var lcdSymbolIds = {};
      var lcdFontPrefix = LCD_FONT_CATALOG[clockFontKey].prefix;
      var fragments = {
        space: 'assets/hub-logo-fragment-space.svg',
        globe: 'assets/hub-logo-fragment-globe.svg',
        continents: 'assets/hub-logo-continents-mercator-globe.svg',
        gridOverlay: 'assets/hub-logo-fragment-globe-grid-overlay.svg',
        compass: 'assets/hub-logo-fragment-compass.svg'
      };
      var parts = {
        full: ['space', 'globe', 'continents', 'gridOverlay', 'compass'],
        space: ['space'],
        globe: ['globe', 'continents', 'gridOverlay', 'compass']
      };

      var params = new URLSearchParams(window.location.search);
      var part = (params.get('logoPart') || 'full').toLowerCase();
      var grid = (params.get('grid') || '3').toLowerCase();
      var list = parts[part] || parts.full;

      function fragmentSrc(name) {
        var base = fragments[name];
        if (name === 'globe' && grid === '4') {
          base = 'assets/hub-logo-fragment-globe-4x4.svg';
        }
        return base + '?v=' + logoBuild;
      }

      var useGlobe3d = logoEnabled && (params.get('globe3d') || '1') !== '0';
      var globe3dLayers = { globe: true, continents: true, gridOverlay: true };

      function appendStaticLayers() {
        list.forEach(function (name) {
          if (useGlobe3d && globe3dLayers[name]) return;
          var img = document.createElement('img');
          img.className = 'hub-logo-layer hub-logo-layer--' + name;
          img.src = fragmentSrc(name);
          img.alt = '';
          img.decoding = 'async';
          img.loading = 'lazy';
          if (name === 'compass') {
            img.id = 'hub-compass-layer';
            img.setAttribute('role', 'button');
            img.setAttribute('tabindex', '0');
            img.setAttribute('aria-label', 'Open deployment model');
          }
          stage.appendChild(img);
        });
      }

      if (logoEnabled) {
        appendStaticLayers();

        var logoOpacity = params.get('logoOpacity');
        if (logoOpacity) {
          stage.style.opacity = logoOpacity;
        }

        stage.hidden = false;
        stage.setAttribute('aria-hidden', 'true');

        if (useGlobe3d) {
          var reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
          var globeYawDeg = parseFloat(params.get('globeYawDeg'));
          var globeOpts = {
            build: logoBuild,
            reducedMotion: reducedMotion
          };
          if (isFinite(globeYawDeg)) {
            globeOpts.initialRotationY = globeYawDeg * Math.PI / 180;
          }
          var globeModuleUrl = new URL('assets/hub-globe.js', window.location.href);
          globeModuleUrl.searchParams.set('v', logoBuild);
          import(globeModuleUrl.href).then(function (mod) {
            return mod.mountHubGlobe(stage, globeOpts);
          }).then(function (handle) {
            if (handle) {
              stage.dataset.globe3d = 'active';
            }
            if (!handle) {
              ['globe', 'continents', 'gridOverlay'].forEach(function (name) {
                if (list.indexOf(name) === -1) return;
                var img = document.createElement('img');
                img.className = 'hub-logo-layer hub-logo-layer--' + name;
                img.src = fragmentSrc(name);
                img.alt = '';
                img.decoding = 'async';
                stage.appendChild(img);
              });
            }
          }).catch(function (err) {
            console.warn('HubGlobe 3D unavailable, using flat layers', err);
            ['globe', 'continents', 'gridOverlay'].forEach(function (name) {
              if (list.indexOf(name) === -1) return;
              var img = document.createElement('img');
              img.className = 'hub-logo-layer hub-logo-layer--' + name;
              img.src = fragmentSrc(name);
              img.alt = '';
              stage.appendChild(img);
            });
          });
        }
      }

      /* Nav dock overlaps globe left hemisphere; stack top → bottom. */
      var GLOBE_VIEW = { imgW: 1920, imgH: 1080, cx: 720, cy: 560, r: 330 };
      function globeCoverRect() {
        var vw = window.innerWidth;
        var vh = window.innerHeight;
        var scale = Math.max(vw / GLOBE_VIEW.imgW, vh / GLOBE_VIEW.imgH);
        var drawW = GLOBE_VIEW.imgW * scale;
        var drawH = GLOBE_VIEW.imgH * scale;
        var offX = (vw - drawW) / 2;
        var offY = (vh - drawH) / 2;
        var left = offX + (GLOBE_VIEW.cx - GLOBE_VIEW.r) * scale;
        var top = offY + (GLOBE_VIEW.cy - GLOBE_VIEW.r) * scale;
        var bottom = offY + (GLOBE_VIEW.cy + GLOBE_VIEW.r) * scale;
        var right = offX + (GLOBE_VIEW.cx + GLOBE_VIEW.r) * scale;
        return { left: left, top: top, bottom: bottom, right: right, width: right - left, height: bottom - top };
      }
      function layoutHubNavDock() {
        var dock = document.getElementById('hub-nav-dock');
        if (!dock) return;
        var globe = globeCoverRect();
        var dockW = Math.min(300, Math.max(210, globe.width * 0.38));
        dock.style.width = dockW + 'px';
        dock.style.left = globe.left + 'px';
        dock.style.top = globe.top + 'px';
        dock.style.height = globe.height + 'px';
        dock.style.right = 'auto';
        dock.style.bottom = 'auto';
        dock.style.display = 'flex';
        dock.style.flexDirection = 'column';
        dock.style.flexWrap = 'nowrap';
        dock.style.alignItems = 'stretch';
        dock.style.gap = '5px';
        dock.style.justifyContent = 'flex-end';
      }
      layoutHubNavDock();
      window.addEventListener('resize', layoutHubNavDock);

      var docsModal = document.getElementById('hub-docs-modal');
      var docsToc = document.getElementById('hub-docs-toc');
      var docsPanels = document.getElementById('hub-docs-panels');
      var compassLayer = document.getElementById('hub-compass-layer');
      var docsApi = '/app/androidcast_project/crashes/api/hub_deploy_docs.php';
      var docsQueue = [];
      var docsLoading = false;
      var docsCache = {};
      var docsPanelsById = {};

      function closeDocs() {
        if (!docsModal) return;
        docsModal.hidden = true;
        docsModal.setAttribute('aria-hidden', 'true');
      }

      closeDocs();

      function openDocs() {
        docsModal.hidden = false;
        docsModal.setAttribute('aria-hidden', 'false');
      }

      function xhrJson(url, onOk, onErr) {
        var xhr = new XMLHttpRequest();
        xhr.open('GET', url, true);
        xhr.setRequestHeader('Accept', 'application/json');
        xhr.onreadystatechange = function () {
          if (xhr.readyState !== 4) return;
          if (xhr.status < 200 || xhr.status >= 300) {
            if (onErr) onErr();
            return;
          }
          try {
            onOk(JSON.parse(xhr.responseText));
          } catch (e) {
            if (onErr) onErr();
          }
        };
        xhr.send();
      }

      function ensureDocsPanel(sectionId, title) {
        if (docsPanelsById[sectionId]) return docsPanelsById[sectionId];
        var item = document.createElement('div');
        item.className = 'detail-tree-item hub-docs-section';
        item.dataset.section = sectionId;
        var panelId = 'hub-doc-panel-' + sectionId;
        item.innerHTML =
          '<div class="detail-tree-row report-row" role="button" tabindex="0" data-tree-toggle data-controls="' + panelId + '">' +
            '<span class="report-tree-cell">' +
              '<button type="button" class="report-tree-toggle" aria-expanded="false" aria-controls="' + panelId + '" tabindex="-1">' +
                '<span class="report-tree-arrow" aria-hidden="true"></span>' +
              '</button>' +
            '</span>' +
            '<span class="detail-tree-caption">' + title + '</span>' +
          '</div>' +
          '<div class="detail-tree-body" id="' + panelId + '" hidden><p class="muted">Loading...</p></div>';
        docsPanels.appendChild(item);
        docsPanelsById[sectionId] = item;
        bindDocsTree(item);
        return item;
      }

      function bindDocsTree(root) {
        root.querySelectorAll('[data-tree-toggle]').forEach(function (row) {
          var panelId = row.getAttribute('data-controls');
          var panel = panelId ? document.getElementById(panelId) : null;
          var btn = row.querySelector('.report-tree-toggle');
          if (!panel) return;
          var toggle = function () {
            var open = panel.hidden;
            panel.hidden = !open;
            if (btn) btn.setAttribute('aria-expanded', open ? 'true' : 'false');
            row.classList.toggle('report-row--open', open);
          };
          if (btn) {
            btn.addEventListener('click', function (e) {
              e.stopPropagation();
              toggle();
            });
          }
          row.addEventListener('click', toggle);
          row.addEventListener('keydown', function (e) {
            if (e.key === 'Enter' || e.key === ' ') {
              e.preventDefault();
              toggle();
            }
          });
        });
      }

      function renderSectionHtml(sectionId, html) {
        var item = docsPanelsById[sectionId];
        if (!item) return;
        var body = item.querySelector('.detail-tree-body');
        var row = item.querySelector('.detail-tree-row');
        var btn = item.querySelector('.report-tree-toggle');
        if (body) body.innerHTML = html || '<p class="muted">No content.</p>';
        if (body && row && body.hidden) {
          body.hidden = false;
          row.classList.add('report-row--open');
          if (btn) btn.setAttribute('aria-expanded', 'true');
        }
      }

      function pumpDocsQueue() {
        if (docsLoading || docsQueue.length === 0) return;
        docsLoading = true;
        var sectionId = docsQueue.shift();
        if (docsCache[sectionId]) {
          renderSectionHtml(sectionId, docsCache[sectionId]);
          docsLoading = false;
          pumpDocsQueue();
          return;
        }
        xhrJson(docsApi + '?section=' + encodeURIComponent(sectionId), function (payload) {
          if (payload && payload.ok) {
            docsCache[sectionId] = payload.html || '';
            renderSectionHtml(sectionId, docsCache[sectionId]);
          } else {
            renderSectionHtml(sectionId, '<p class="muted">Failed to load section.</p>');
          }
          docsLoading = false;
          pumpDocsQueue();
        }, function () {
          renderSectionHtml(sectionId, '<p class="muted">Network error while loading section.</p>');
          docsLoading = false;
          pumpDocsQueue();
        });
      }

      function queueSection(sectionId, front) {
        if (!sectionId) return;
        if (front) docsQueue.unshift(sectionId);
        else docsQueue.push(sectionId);
        pumpDocsQueue();
      }

      function buildTocNode(node, parentEl) {
        var children = node.children || [];
        if (children.length) {
          children.forEach(function (child) {
            ensureDocsPanel(child.id, child.title);
            queueSection(child.id, false);
          });
          return;
        }
        ensureDocsPanel(node.id, node.title);
        queueSection(node.id, false);
      }

      function openDeploymentModel(prioritySection) {
        openDocs();
        docsPanels.innerHTML = '';
        docsPanelsById = {};
        docsCache = {};
        docsQueue = [];
        docsLoading = false;
        docsToc.innerHTML = '<p class="muted hub-docs-toc-title">Table of contents</p>';

        xhrJson(docsApi + '?action=toc', function (payload) {
          if (!payload || !payload.ok || !payload.sections) {
            docsPanels.innerHTML = '<p class="muted">Failed to load table of contents.</p>';
            return;
          }
          payload.sections.forEach(function (node) {
            var group = document.createElement('div');
            group.className = 'hub-docs-toc-group';
            var head = document.createElement('button');
            head.type = 'button';
            head.className = 'hub-docs-toc-head';
            head.textContent = node.title;
            head.dataset.section = node.id;
            head.addEventListener('click', function () {
              var kids = node.children || [];
              if (kids.length) queueSection(kids[0].id, true);
              else queueSection(node.id, true);
            });
            group.appendChild(head);
            (node.children || []).forEach(function (child) {
              var leaf = document.createElement('button');
              leaf.type = 'button';
              leaf.className = 'hub-docs-toc-leaf';
              leaf.textContent = child.title;
              leaf.dataset.section = child.id;
              leaf.addEventListener('click', function () { queueSection(child.id, true); });
              group.appendChild(leaf);
            });
            docsToc.appendChild(group);
            buildTocNode(node, docsPanels);
          });
          if (prioritySection) queueSection(prioritySection, true);
          else if (payload.default_section) queueSection(payload.default_section, true);
        }, function () {
          docsPanels.innerHTML = '<p class="muted">Network error while loading deployment model.</p>';
        });
      }

      function bindCompassOpen() {
        if (!logoEnabled || !compassLayer) return;
        var open = function () { openDeploymentModel(); };
        compassLayer.addEventListener('click', open);
        compassLayer.addEventListener('keydown', function (ev) {
          if (ev.key === 'Enter' || ev.key === ' ') {
            ev.preventDefault();
            open();
          }
        });
      }
      bindCompassOpen();

      docsModal.addEventListener('click', function (ev) {
        var target = ev.target;
        if (!(target instanceof Element)) return;
        if (target.hasAttribute('data-close-docs')) closeDocs();
      });
      document.addEventListener('keydown', function (ev) {
        if (ev.key === 'Escape' && !docsModal.hidden) closeDocs();
      });

      var cityLabels = {
        'America/New_York': 'NEW YORK',
        'Europe/Moscow': 'MOSCOW',
        'Asia/Shanghai': 'BEIJING',
        'Africa/Johannesburg': 'CAPETOWN'
      };
      var roman = ['XII', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X', 'XI'];

      function pad2(v) { return String(v).padStart(2, '0'); }
      function systemZone() { return Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC'; }

      function secondaryZones(primary) {
        var preferred = ['America/New_York', 'Europe/Moscow', 'Asia/Shanghai'];
        var list = preferred.filter(function (z) { return z !== primary; });
        while (list.length < 3) {
          if (!list.includes('Africa/Johannesburg') && primary !== 'Africa/Johannesburg') list.push('Africa/Johannesburg');
          else list.push('UTC');
        }
        return list.slice(0, 3);
      }

      function timeParts(date, zone) {
        var parts = new Intl.DateTimeFormat('en-US', {
          timeZone: zone, hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit'
        }).formatToParts(date);
        var map = {};
        parts.forEach(function (p) { if (p.type !== 'literal') map[p.type] = p.value; });
        return { hh: map.hour || '00', mm: map.minute || '00', ss: map.second || '00' };
      }

      function formatDate(date, zone) {
        var parts = new Intl.DateTimeFormat('en-US', {
          timeZone: zone, weekday: 'short', day: '2-digit', month: 'short', year: 'numeric'
        }).formatToParts(date);
        var map = {};
        parts.forEach(function (p) { if (p.type !== 'literal') map[p.type] = p.value; });
        return (map.weekday || '---').slice(0, 3).toUpperCase() + ' '
          + (map.day || '00') + ' '
          + (map.month || '---').slice(0, 3).toUpperCase() + ' '
          + (map.year || '0000');
      }

      function buildWorldClock(tz) {
        var cell = document.createElement('div');
        cell.className = 'cw-cell cw-world-cell';
        var stack = document.createElement('div');
        stack.className = 'cw-world-stack';

        var label = document.createElement('div');
        label.className = 'cw-world-label';
        label.textContent = cityLabels[tz] || tz.toUpperCase();
        stack.appendChild(label);

        var dial = document.createElement('div');
        dial.className = 'cw-dial';
        for (var i = 0; i < 12; i++) {
          var n = document.createElement('div');
          n.className = 'cw-roman';
          if (i === 0 || i === 3 || i === 6 || i === 9) n.className += ' cw-roman-major';
          else n.className += ' cw-roman-minor';
          n.style.setProperty('--i', String(i));
          n.textContent = roman[i];
          dial.appendChild(n);
        }

        var hour = document.createElement('div');
        hour.className = 'cw-hand cw-hour';
        var minute = document.createElement('div');
        minute.className = 'cw-hand cw-minute';
        var second = document.createElement('div');
        second.className = 'cw-hand cw-second';
        var pin = document.createElement('div');
        pin.className = 'cw-pin';
        dial.appendChild(hour);
        dial.appendChild(minute);
        dial.appendChild(second);
        dial.appendChild(pin);
        stack.appendChild(dial);
        cell.appendChild(stack);

        return { tz: tz, hour: hour, minute: minute, second: second, root: cell };
      }

      var overtimeReady = false;
      var overtimeSymbolIds = {};
      var quartzReady = false;
      var quartzSymbolIds = {};

      function overtimeSymbolId(ch) {
        if (ch >= '0' && ch <= '9') return 'ot-' + ch;
        if (ch === ':') return 'ot-colon';
        if (ch === '+') return 'ot-plus';
        if (ch === '-') return 'ot-minus';
        if (ch === '/') return 'ot-slash';
        if (ch === ' ') return 'ot-space';
        if (ch >= 'A' && ch <= 'Z') return 'ot-' + ch;
        return null;
      }

      function loadOvertimeAlphabet(done) {
        var host = document.getElementById('overtime-alphabet-host');
        if (!host) { done(false); return; }
        fetch('assets/overtime-01-alphabet.svg?v=' + logoBuild)
          .then(function (res) { return res.text(); })
          .then(function (svgText) {
            host.innerHTML = svgText;
            host.querySelectorAll('symbol').forEach(function (sym) {
              if (sym.id) overtimeSymbolIds[sym.id] = sym;
            });
            overtimeReady = true;
            done(true);
          })
          .catch(function () { done(false); });
      }

      function quartzSymbolId(ch) {
        if (ch >= '0' && ch <= '9') return 'qz-' + ch;
        if (ch === ':') return 'qz-colon';
        if (ch === '+') return 'qz-plus';
        if (ch === '-') return 'qz-minus';
        if (ch === '/') return 'qz-slash';
        if (ch === ' ') return 'qz-space';
        if (ch >= 'A' && ch <= 'Z') return 'qz-' + ch;
        return null;
      }

      function lcdSymbolId(ch) {
        var p = lcdFontPrefix;
        if (ch >= '0' && ch <= '9') return p + '-' + ch;
        if (ch === ':') return p + '-colon';
        if (ch === '+') return p + '-plus';
        if (ch === '-') return p + '-minus';
        if (ch === '/') return p + '-slash';
        if (ch === '.') return p + '-dot';
        if (ch === ' ') return p + '-space';
        if (ch >= 'A' && ch <= 'Z') return p + '-' + ch;
        if (ch >= 'a' && ch <= 'z') return p + '-' + ch.toUpperCase();
        return null;
      }

      function loadLcdAlphabet(done) {
        var host = document.getElementById('lcd-alphabet-host');
        var spec = LCD_FONT_CATALOG[clockFontKey];
        if (!host || !spec) { done(false); return; }
        fetch(hubAsset(spec.url) + '?v=' + logoBuild)
          .then(function (res) {
            if (!res.ok) throw new Error('alphabet fetch failed');
            return res.text();
          })
          .then(function (svgText) {
            host.replaceChildren();
            var parser = new DOMParser();
            var doc = parser.parseFromString(svgText, 'image/svg+xml');
            var sprite = doc.documentElement;
            if (sprite.querySelector('parsererror')) throw new Error('alphabet parse failed');
            sprite.setAttribute('aria-hidden', 'true');
            sprite.style.cssText = 'position:absolute;width:0;height:0;overflow:hidden;pointer-events:none';
            host.appendChild(sprite);
            lcdSymbolIds = {};
            sprite.querySelectorAll('symbol').forEach(function (sym) {
              if (sym.id) lcdSymbolIds[sym.id] = sym;
            });
            lcdFontReady = Object.keys(lcdSymbolIds).length > 0;
            document.documentElement.setAttribute('data-clock-font', 'mixed');
            var widget = document.getElementById('hub-clock-widget');
            if (widget) widget.setAttribute('data-lcd-mode', lcdFontReady ? 'svg' : 'text');
            done(lcdFontReady);
          })
          .catch(function () {
            lcdFontReady = false;
            var widget = document.getElementById('hub-clock-widget');
            if (widget) widget.setAttribute('data-lcd-mode', 'text');
            done(false);
          });
      }

      function cloneLcdSymbolContent(svg, sym) {
        sym.childNodes.forEach(function (node) {
          if (node.nodeType !== 1) return;
          svg.appendChild(node.cloneNode(true));
        });
      }

      function renderLcdText(el, text, opts) {
        opts = opts || {};
        if (!el) return;
        el.textContent = text;
        el.classList.add('cw-lcd-text');
        if (opts.blinkColon) el.classList.add('ot-colon-blink');
      }

      function loadQuartzAlphabet(done) {
        var host = document.getElementById('quartz-alphabet-host');
        if (!host) { done(false); return; }
        fetch('assets/quartz-alphabet.svg?v=' + logoBuild)
          .then(function (res) { return res.text(); })
          .then(function (svgText) {
            host.innerHTML = svgText;
            host.querySelectorAll('symbol').forEach(function (sym) {
              if (sym.id) quartzSymbolIds[sym.id] = sym;
            });
            quartzReady = true;
            done(true);
          })
          .catch(function () { done(false); });
      }

      var OVERTIME_SEG_GRID = [
        [3, 1, 14, 3], [1, 4, 3, 11], [16, 4, 3, 11],
        [3, 15, 14, 3], [1, 17, 3, 11], [16, 17, 3, 11],
        [3, 28, 14, 3]
      ];

      function parseOvertimeRects(sym) {
        var rects = [];
        sym.querySelectorAll('rect').forEach(function (node) {
          rects.push({
            x: parseFloat(node.getAttribute('x') || 0),
            y: parseFloat(node.getAttribute('y') || 0),
            w: parseFloat(node.getAttribute('width') || 0),
            h: parseFloat(node.getAttribute('height') || 0)
          });
        });
        return rects;
      }

      function overtimeSegMatchesRect(seg, rect) {
        var eps = 1.25;
        return Math.abs(rect.x - seg[0]) <= eps
          && Math.abs(rect.y - seg[1]) <= eps
          && Math.abs(rect.w - seg[2]) <= eps
          && Math.abs(rect.h - seg[3]) <= eps;
      }

      function overtimeSegIsLit(seg, activeRects) {
        for (var i = 0; i < activeRects.length; i++) {
          if (overtimeSegMatchesRect(seg, activeRects[i])) return true;
        }
        return false;
      }

      function buildOvertimeInactiveGrid(svg, sym, glyphW) {
        if (glyphW === 12 || glyphW === 8 || glyphW === 16) return;
        if (glyphW !== 20 && glyphW < 22) return;
        var activeRects = parseOvertimeRects(sym);
        var seg = document.createElementNS('http://www.w3.org/2000/svg', 'g');
        seg.setAttribute('class', 'ot-grid');
        OVERTIME_SEG_GRID.forEach(function (d) {
          if (overtimeSegIsLit(d, activeRects)) return;
          var r = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
          r.setAttribute('x', String(d[0]));
          r.setAttribute('y', String(d[1]));
          r.setAttribute('width', String(d[2]));
          r.setAttribute('height', String(d[3]));
          r.setAttribute('rx', '1');
          seg.appendChild(r);
        });
        if (seg.childNodes.length) svg.appendChild(seg);
      }

      function cloneOvertimeSymbol(svg, sym) {
        sym.querySelectorAll('rect').forEach(function (node) {
          var rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
          ['x', 'y', 'width', 'height', 'rx', 'transform'].forEach(function (attr) {
            var val = node.getAttribute(attr);
            if (val != null) rect.setAttribute(attr, val);
          });
          rect.setAttribute('class', 'ot-active');
          svg.appendChild(rect);
        });
      }

      function renderOvertimeText(el, text, opts) {
        opts = opts || {};
        if (!el) return;
        el.innerHTML = '';
        if (!overtimeReady) {
          el.textContent = text;
          return;
        }
        String(text).toUpperCase().split('').forEach(function (ch) {
          if (opts.forceFallback) {
            var forced = document.createElement('span');
            forced.className = 'ot-fallback-char' + (opts.bold ? ' ot-fallback-char--bold' : '');
            forced.textContent = ch === ' ' ? '\u00A0' : ch;
            el.appendChild(forced);
            return;
          }
          var sid = overtimeSymbolId(ch);
          if (!sid || !overtimeSymbolIds[sid]) {
            var fb = document.createElement('span');
            fb.className = 'ot-fallback-char' + (opts.bold ? ' ot-fallback-char--bold' : '');
            fb.textContent = ch === ' ' ? '\u00A0' : ch;
            el.appendChild(fb);
            return;
          }
          var wrap = document.createElement('span');
          wrap.className = 'ot-glyph-wrap' + (opts.bold ? ' ot-glyph-wrap--bold' : '');
          if (opts.blinkColon && ch === ':') wrap.className += ' ot-colon-blink';
          var sym = overtimeSymbolIds[sid];
          var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
          var viewBox = sym.getAttribute('viewBox') || '0 0 20 32';
          svg.setAttribute('viewBox', viewBox);
          svg.setAttribute('class', 'ot-glyph');
          svg.setAttribute('aria-hidden', 'true');
          var vb = viewBox.split(/\s+/).map(function (v) { return Number(v) || 0; });
          var glyphW = vb[2] || 20;
          if (opts.ghost !== false) buildOvertimeInactiveGrid(svg, sym, glyphW);
          cloneOvertimeSymbol(svg, sym);
          wrap.appendChild(svg);
          el.appendChild(wrap);
        });
      }

      /* Clock digits: original Overtime path (<use> only, no ghost grid). */
      function renderOvertimeDigits(el, text, blinkColon, bold) {
        if (!el) return;
        el.innerHTML = '';
        if (!overtimeReady) {
          el.textContent = text;
          return;
        }
        text.split('').forEach(function (ch) {
          var sid = overtimeSymbolId(ch);
          if (!sid || !overtimeSymbolIds[sid]) {
            el.appendChild(document.createTextNode(ch));
            return;
          }
          var wrap = document.createElement('span');
          wrap.className = 'ot-glyph-wrap' + (bold ? ' ot-glyph-wrap--bold' : '');
          if (blinkColon && ch === ':') wrap.className += ' ot-colon-blink';
          var sym = overtimeSymbolIds[sid];
          var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
          svg.setAttribute('viewBox', sym.getAttribute('viewBox') || '0 0 20 32');
          svg.setAttribute('class', 'ot-glyph');
          var use = document.createElementNS('http://www.w3.org/2000/svg', 'use');
          use.setAttribute('href', '#' + sid);
          svg.appendChild(use);
          wrap.appendChild(svg);
          el.appendChild(wrap);
        });
      }

      function renderQuartzText(el, text, opts) {
        opts = opts || {};
        if (!el) return;
        el.innerHTML = '';
        if (!quartzReady) {
          el.textContent = text;
          return;
        }
        String(text).toUpperCase().split('').forEach(function (ch) {
          var sid = quartzSymbolId(ch);
          if (!sid || !quartzSymbolIds[sid]) {
            var fb = document.createElement('span');
            fb.className = 'qz-fallback-char' + (opts.bold ? ' qz-fallback-char--bold' : '');
            fb.textContent = ch === ' ' ? '\u00A0' : ch;
            el.appendChild(fb);
            return;
          }
          var wrap = document.createElement('span');
          wrap.className = 'qz-glyph-wrap' + (opts.bold ? ' qz-glyph-wrap--bold' : '');
          if (opts.blinkColon && ch === ':') wrap.className += ' qz-colon-blink';
          var sym = quartzSymbolIds[sid];
          var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
          var viewBox = sym.getAttribute('viewBox') || '0 0 20 32';
          svg.setAttribute('viewBox', viewBox);
          svg.setAttribute('class', 'qz-glyph');
          var vb = viewBox.split(/\s+/).map(function (v) { return Number(v) || 0; });
          var use = document.createElementNS('http://www.w3.org/2000/svg', 'use');
          use.setAttribute('href', '#' + sid);
          use.setAttribute('class', 'qz-active');
          svg.appendChild(use);
          wrap.appendChild(svg);
          el.appendChild(wrap);
        });
      }

      function renderQuartzDigits(el, text, blinkColon, bold) {
        renderQuartzText(el, text, { blinkColon: blinkColon, bold: bold });
      }

      function regionLabel(tz) {
        var parts = tz.split('/');
        if (parts.length >= 2) {
          return parts[0].replace(/_/g, ' ').toUpperCase() + '/' + parts[parts.length - 1].replace(/_/g, ' ').toUpperCase();
        }
        return tz.toUpperCase();
      }

      function gmtOffsetLabel(date, tz) {
        try {
          var parts = new Intl.DateTimeFormat('en-US', { timeZone: tz, timeZoneName: 'shortOffset' }).formatToParts(date);
          var name = parts.find(function (p) { return p.type === 'timeZoneName'; });
          if (name && name.value) {
            return name.value.replace('GMT', 'GMT').replace('UTC', 'GMT').toUpperCase();
          }
        } catch (e) {}
        return 'GMT+0';
      }

      function formatDateLine(date, zone) {
        var parts = new Intl.DateTimeFormat('en-US', {
          timeZone: zone, weekday: 'short', day: '2-digit', month: 'short', year: 'numeric'
        }).formatToParts(date);
        var map = {};
        parts.forEach(function (p) { if (p.type !== 'literal') map[p.type] = p.value; });
        return (map.weekday || '---').slice(0, 3).toUpperCase() + ' '
          + String(Number(map.day || '0')).padStart(2, '0') + ' '
          + (map.month || '---').slice(0, 3).toUpperCase() + ' '
          + (map.year || '0000');
      }

      var primaryTz = systemZone();
      var worldRow = document.getElementById('cw-world-row');
      var secondary = secondaryZones(primaryTz).map(function (tz) {
        var model = buildWorldClock(tz);
        worldRow.appendChild(model.root);
        return model;
      });

      function setClockopiaText(el, text) {
        renderLcdText(el, text, {});
      }

      function updateClockWidget() {
        var now = new Date();
        var p = timeParts(now, primaryTz);
        renderLcdText(document.getElementById('cw-hm-lcd'), p.hh + ':' + p.mm, { bold: true });
        renderLcdText(document.getElementById('cw-colon-lcd'), ':', { blinkColon: true });
        renderLcdText(document.getElementById('cw-ss-lcd'), p.ss, {});
        renderLcdText(document.getElementById('cw-tz-text'), gmtOffsetLabel(now, primaryTz), {});
        renderLcdText(document.getElementById('cw-region'), regionLabel(primaryTz), {});
        renderLcdText(document.getElementById('cw-date-line'), formatDateLine(now, primaryTz), {});

        secondary.forEach(function (clock) {
          var t = timeParts(now, clock.tz);
          var h = Number(t.hh), m = Number(t.mm), s = Number(t.ss);
          var hDeg = ((h % 12) + (m / 60) + (s / 3600)) * 30;
          var mDeg = (m + (s / 60)) * 6;
          var sDeg = s * 6;
          clock.hour.style.transform = 'translate(-50%, -100%) rotate(' + hDeg + 'deg)';
          clock.minute.style.transform = 'translate(-50%, -100%) rotate(' + mDeg + 'deg)';
          clock.second.style.transform = 'translate(-50%, -100%) rotate(' + sDeg + 'deg)';
        });
      }

      loadLcdAlphabet(function () {
        updateClockWidget();
        setInterval(updateClockWidget, 1000);
      });

      /* Resize handles: hidden by default (CSS). Skip red debug boxes entirely. */
      (function makeClockWidgetInteractive() {
        var widget = document.getElementById('hub-clock-widget');
        if (!widget) return;
        var drag = null;
        widget.addEventListener('pointerdown', function (ev) {
          var target = ev.target;
          if (!(target instanceof Element)) return;
          var box = widget.getBoundingClientRect();
          var handle = target.closest('[data-resize]');
          var resizeDir = handle ? handle.getAttribute('data-resize') : null;
          if (resizeDir) {
            drag = {
              mode: 'resize',
              dir: resizeDir,
              x: ev.clientX, y: ev.clientY,
              left: box.left, top: box.top, width: box.width, height: box.height
            };
          } else {
            drag = { mode: 'move', dx: ev.clientX - box.left, dy: ev.clientY - box.top };
          }
          widget.setPointerCapture(ev.pointerId);
          widget.classList.add('is-dragging');
          ev.preventDefault();
        });

        widget.addEventListener('pointermove', function (ev) {
          if (!drag) return;
          if (drag.mode === 'move') {
            widget.style.left = Math.max(0, ev.clientX - drag.dx) + 'px';
            widget.style.top = Math.max(0, ev.clientY - drag.dy) + 'px';
          } else {
            var minW = 280, minH = 210;
            var dx = ev.clientX - drag.x;
            var dy = ev.clientY - drag.y;
            var left = drag.left, top = drag.top, width = drag.width, height = drag.height;
            if (drag.dir.indexOf('e') !== -1) width = Math.max(minW, drag.width + dx);
            if (drag.dir.indexOf('s') !== -1) height = Math.max(minH, drag.height + dy);
            if (drag.dir.indexOf('w') !== -1) {
              width = Math.max(minW, drag.width - dx);
              left = drag.left + (drag.width - width);
            }
            if (drag.dir.indexOf('n') !== -1) {
              height = Math.max(minH, drag.height - dy);
              top = drag.top + (drag.height - height);
            }
            widget.style.left = Math.max(0, left) + 'px';
            widget.style.top = Math.max(0, top) + 'px';
            widget.style.width = width + 'px';
            widget.style.minWidth = width + 'px';
            widget.style.transform = 'none';
          }
        });

        function endDrag(ev) {
          if (!drag) return;
          drag = null;
          widget.classList.remove('is-dragging');
          widget.releasePointerCapture(ev.pointerId);
        }

        widget.addEventListener('pointerup', endDrag);
        widget.addEventListener('pointercancel', endDrag);
      })();
    })();
  </script>
</body>
</html>
