<!doctype html>
<html lang="en">
  <head>
    <!-- BUILD_ID: 2026-04-09T04:38:14.727Z -->

    <meta charset="UTF-8" />
    <!-- Icon removed to prevent 404 -->
    <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
    <title>Chinese Track – Progressive Mandarin Course</title>
    <meta name="description" content="Learn Mandarin Chinese progressively with Chinese Track. Master HSK vocabulary, reading comprehension, and conversational skills through structured lessons and interactive exercises." />
    <!-- Performance: preconnect/dns-prefetch to same-origin (API) so staging/new/current all use current host -->
    <script>
      (function () {
        try {
          if (typeof window === 'undefined') return;
          var origin = window.location.origin;
          if (!origin) return;

          // preconnect
          var l1 = document.createElement('link');
          l1.rel = 'preconnect';
          l1.href = origin;
          l1.crossOrigin = 'anonymous';
          document.head.appendChild(l1);

          // dns-prefetch
          var l2 = document.createElement('link');
          l2.rel = 'dns-prefetch';
          l2.href = origin;
          document.head.appendChild(l2);
        } catch (e) {
          // no-op
        }
      })();
    </script>
    <!-- Google Fonts: Noto Sans Bopomofo for Zhuyin (注音) support -->
    <!-- Build marker: 2025-01-15-zhuyin-font-fix -->
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+Bopomofo&display=optional" rel="stylesheet" />
    <script type="module" crossorigin src="/current/assets/index-BQ4F623x-debug-v4.js"></script>
    <link rel="stylesheet" crossorigin href="/current/assets/index-G8EJ-VBw-debug-v4.css">
  </head>
  <body>
    <div id="root"></div>
    <script>
      // Canonical redirect: SPA runs under /current/. If loaded at / with hash router (#/...), redirect to /current/#/...
      (function () {
        var pathname = location.pathname || '/';
        var isCurrent = pathname.startsWith('/current');
        var hasSpaHash = (location.hash || '').startsWith('#/');
        if (!isCurrent && hasSpaHash) {
          location.replace('/current/' + location.hash + location.search);
        }
      })();
    </script>
    <script>
      // Boot watchdog: show fallback overlay if React never mounts or top-level error occurs.
      // Chunk-load recovery: one auto-reload per tab when we detect deploy-related load failures.
      window.__CT_BOOT = { startedAt: Date.now(), mounted: false, errors: [] };
      window.addEventListener('error', function (e) {
        window.__CT_BOOT.errors.push({ name: (e.error && e.error.name) || 'Error', message: (e.error && e.error.message) || e.message });
      });
      window.addEventListener('unhandledrejection', function (e) {
        var msg = (e.reason && (e.reason.message || String(e.reason))) || String(e.reason);
        window.__CT_BOOT.errors.push({ name: 'UnhandledRejection', message: msg });
      });
      function isChunkLoadLike(err) {
        if (!err || !err.message) return false;
        var m = String(err.message);
        return m.indexOf('Failed to fetch dynamically imported module') !== -1 ||
          m.indexOf('Importing a module script failed') !== -1 ||
          m.indexOf('ChunkLoadError') !== -1 ||
          m.indexOf('Loading chunk') !== -1 ||
          (err.name && String(err.name).indexOf('ChunkLoadError') !== -1) ||
          /404|Not Found|Failed to load|Loading CSS chunk/i.test(m);
      }
      function getBuildIdFromPage() {
        try {
          var raw = document.documentElement && document.documentElement.innerHTML ? document.documentElement.innerHTML : '';
          var match = raw.match(/BUILD_ID:\s*([^<\s]+)/);
          return match ? match[1] : '';
        } catch (e) { return ''; }
      }
      var CHUNK_RELOAD_KEY = 'ct_chunk_reload_done';
      setTimeout(function () {
        if (window.__CT_BOOT && !window.__CT_BOOT.mounted) {
          var last3 = (window.__CT_BOOT.errors || []).slice(-3);
          var hasChunkLike = last3.some(isChunkLoadLike);
          var alreadyReloaded = false;
          try { alreadyReloaded = sessionStorage.getItem(CHUNK_RELOAD_KEY) === '1'; } catch (e) {}
          var buildId = getBuildIdFromPage();
          if (hasChunkLike && !alreadyReloaded) {
            try { sessionStorage.setItem(CHUNK_RELOAD_KEY, '1'); } catch (e) {}
            console.warn('[CT_BOOT] Chunk/load error detected; auto-reload once. url=' + (location.href || '') + ' buildId=' + (buildId || 'n/a'));
            window.location.reload();
            return;
          }
          var el = document.createElement('div');
          var allTransient = last3.length > 0 && last3.every(function (e) {
            var n = (e && e.name) || '';
            var m = (e && e.message) ? String(e.message) : '';
            return n === 'CircuitOpenError' || n === 'AbortError' || n === 'TimeoutError' ||
              (n === 'UnhandledRejection' && (m.indexOf('timeout') !== -1 || m.indexOf('Abort') !== -1 || m.indexOf('circuit') !== -1));
          });
          if (allTransient) {
            el.id = 'ct-boot-fallback';
            el.style.cssText = 'position:fixed;inset:0;background:#1a1a1a;color:#eee;font-family:system-ui,sans-serif;padding:24px;display:flex;align-items:center;justify-content:center;z-index:999999;';
            el.innerHTML = '<div style="text-align:center;max-width:400px;">' +
              '<h2 style="margin:0 0 12px;font-size:1.25rem;">Backend temporarily unavailable</h2>' +
              '<p style="margin:0 0 20px;color:#94a3b8;">Retrying in 30s… or use the buttons below.</p>' +
              '<p style="margin:0 0 16px;">' +
              '<button onclick="window.location.reload()" style="margin-right:8px;background:#F37021;color:#fff;border:none;padding:10px 18px;border-radius:8px;font-weight:600;cursor:pointer;">Retry now</button>' +
              '<a href="/current/#/" style="color:#7dd3fc;">Go home</a>' +
              '</p></div>';
            document.body.appendChild(el);
            window.setTimeout(function () { window.location.reload(); }, 35000);
          } else {
            if (hasChunkLike) {
              console.warn('[CT_BOOT] Startup failed after one auto-reload. url=' + (location.href || '') + ' buildId=' + (buildId || 'n/a') + ' autoReloadAttempted=true');
            }
            var errHtml = last3.length ? last3.map(function (err) { return '<div style="margin:8px 0;font-size:14px;"><strong>' + (err.name || '') + '</strong>: ' + (String(err.message || '').substring(0, 200)) + '</div>'; }).join('') : '<div style="margin:8px 0;">(no errors captured)</div>';
            var buildLine = buildId ? '<p style="margin:8px 0;"><strong>Build</strong>: <code style="background:#333;padding:2px 6px;">' + buildId + '</code></p>' : '';
            var reloadHint = hasChunkLike && alreadyReloaded ? '<p style="margin:8px 0 16px;color:#94a3b8;">We already tried reloading once. Please reload the page to get the latest version.</p>' : '';
            el.id = 'ct-boot-fallback';
            el.style.cssText = 'position:fixed;inset:0;background:#1a1a1a;color:#eee;font-family:system-ui,sans-serif;padding:24px;overflow:auto;z-index:999999;';
            el.innerHTML = '<h2 style="margin:0 0 16px;">App failed to start</h2>' +
              '<p style="margin:8px 0;"><strong>URL</strong>: <code style="background:#333;padding:2px 6px;">' + (location.href || '') + '</code></p>' +
              buildLine +
              reloadHint +
              '<p style="margin:12px 0 4px;"><strong>Last errors</strong>:</p>' + errHtml +
              '<p style="margin:16px 0 8px;"><strong>Links</strong>:</p>' +
              '<p style="margin:4px 0;"><button onclick="window.location.reload()" style="background:#F37021;color:#fff;border:none;padding:10px 18px;border-radius:8px;font-weight:600;cursor:pointer;">Reload page</button></p>' +
              '<p style="margin:8px 0;"><a href="/current/" target="_blank" rel="noopener" style="color:#7dd3fc;">/current/</a> · <a href="/wp-json/" target="_blank" rel="noopener" style="color:#7dd3fc;">/wp-json/</a></p>';
            document.body.appendChild(el);
          }
        }
      }, 3000);
    </script>
    <script>
      // Pre-React hash rewrite for legacy course outline URLs
      (function () {
        const m = location.hash.match(/^#\/course-outline-level-(\d+)(\/.*)?$/);
        if (m) {
          console.log('Legacy URL detected, rewriting:', location.hash, '->', `#/course-outline/level/${m[1]}`);
          location.hash = `#/course-outline/level/${m[1]}`;
        }
      })();
    </script>
    <!-- <script src="/api/chatgpt-analysis.js"></script> -->
    <!-- HanziWriter removed from global CDN - now loaded as npm dependency only where needed -->
    <!-- Chinese Track GA4 -->
    <script async src="https://www.googletagmanager.com/gtag/js?id=G-44YCC5M7KB"></script>
    <script>
      window.dataLayer = window.dataLayer || [];
      function gtag(){dataLayer.push(arguments);}
      window.gtag = window.gtag || gtag;

      gtag('js', new Date());
      gtag('config', 'G-44YCC5M7KB', { send_page_view: false });

      (function () {
        function getPagePath() {
          return window.location.pathname + window.location.search + window.location.hash;
        }
        var lastPath = null;

        function sendPageView() {
          var path = getPagePath();
          if (path === lastPath) return;
          lastPath = path;

          gtag('event', 'page_view', {
            page_path: path,
            page_location: window.location.href,
            page_title: document.title
          });
        }

        sendPageView();
        window.addEventListener('hashchange', sendPageView);
        window.addEventListener('popstate', sendPageView);
        setInterval(sendPageView, 3000);
      })();
    </script>
    <!-- /Chinese Track GA4 -->
<script>
  (function () {
    function trackClickyPage() {
      if (window.clicky && typeof window.clicky.log === 'function') {
        var path = window.location.pathname + window.location.search + window.location.hash;
        window.clicky.log(path, 'pageview');
      }
    }

    // Track initial load
    trackClickyPage();

    // Track SPA hash changes
    window.addEventListener('hashchange', trackClickyPage);
  })();
</script>
    <script async data-id="101504067" src="//static.getclicky.com/js"></script>
  </body>
</html>
