// Tiny shared harness for challenge pages. // Each page calls Challenge.init({id, instructions}) then Challenge.pass()/fail() // based on its own listeners. (function () { const events = []; const state = { id: null, verdict: "PENDING", reason: [], events, details: [], thresholds: {}, }; function render() { const el = document.getElementById("__verdict"); if (!el) return; el.textContent = state.verdict; el.dataset.verdict = state.verdict; el.style.background = state.verdict === "PASS" ? "#1f7a1f" : state.verdict === "FAIL" ? "#a11" : state.verdict === "SKIP" ? "#76520b" : state.verdict === "WARN" ? "#6b5d00" : "#444"; el.style.color = "#fff"; const r = document.getElementById("__reason"); if (r) r.textContent = state.reason.join("\n"); } function log(name, detail) { events.push({ t: performance.now(), name, ...detail }); if (events.length > 500) events.shift(); } const Challenge = { init({ id, instructions, thresholds = {} }) { state.id = id; state.thresholds = parseThresholds(thresholds); document.title = `[${state.verdict}] ${id}`; const root = document.body; const bar = document.createElement("div"); bar.style.cssText = "position:sticky;top:0;background:#111;color:#eee;padding:8px 12px;font:13px monospace;border-bottom:1px solid #333;z-index:9999"; bar.innerHTML = ` ${id} PENDING ${instructions}

      `;
      root.insertBefore(bar, root.firstChild);
      window.__challenge = id;
      window.__verdict = state.verdict;
      window.__reason = state.reason;
      window.__events = state.events;
      render();
    },
    pass(...reasons) {
      if (state.verdict === "FAIL") return; // sticky
      state.verdict = "PASS";
      state.reason.push(...reasons.map((r) => "✓ " + r));
      window.__verdict = state.verdict;
      document.title = `[PASS] ${state.id}`;
      persist(); render();
    },
    fail(...reasons) {
      state.verdict = "FAIL";
      state.reason.push(...reasons.map((r) => "✗ " + r));
      window.__verdict = state.verdict;
      document.title = `[FAIL] ${state.id}`;
      persist(); render();
    },
    skip(...reasons) {
      if (state.verdict === "FAIL" || state.verdict === "PASS") return;
      state.verdict = "SKIP";
      state.reason.push(...reasons.map((r) => "↷ " + r));
      window.__verdict = state.verdict;
      document.title = `[SKIP] ${state.id}`;
      persist(); render();
    },
    warn(...reasons) {
      if (state.verdict === "FAIL" || state.verdict === "PASS") return;
      state.verdict = "WARN";
      state.reason.push(...reasons.map((r) => "! " + r));
      window.__verdict = state.verdict;
      document.title = `[WARN] ${state.id}`;
      persist(); render();
    },
    partial({ name, pass, reason, data }) {
      state.details.push({ name, pass: !!pass, reason: reason || "", data });
      log("partial", { name, pass: !!pass, reason, data });
      persist(); render();
      return !!pass;
    },
    finishPartials() {
      const failed = state.details.filter(d => !d.pass);
      if (failed.length) Challenge.fail(...failed.map(d => `${d.name}: ${d.reason || "failed"}`));
      else Challenge.pass(...state.details.map(d => `${d.name}: ok`));
    },
    getThreshold(name, fallback) {
      return Object.prototype.hasOwnProperty.call(state.thresholds, name) ? state.thresholds[name] : fallback;
    },
    log,
    state,
  };

  function persist() {
    try {
      localStorage.setItem(
        "pi-chrome-suite:" + state.id,
        JSON.stringify({ id: state.id, verdict: state.verdict, reason: state.reason, details: state.details, thresholds: state.thresholds, events: state.events.slice(-50), ts: Date.now() })
      );
    } catch {}
  }

  function parseThresholds(defaults) {
    const out = { ...defaults };
    try {
      const qs = new URLSearchParams(location.search);
      for (const [k, v] of qs) {
        if (!k.startsWith("threshold.")) continue;
        const key = k.slice("threshold.".length);
        const num = Number(v);
        out[key] = Number.isFinite(num) ? num : v;
      }
    } catch {}
    return out;
  }

  window.Challenge = Challenge;
})();