---
/**
 * Interactive, lazily-loaded KiCad Gerber viewer embedded in a blog post.
 *
 * Nothing heavy loads on page view: the ~22 MB (gzipped) WASM is fetched only
 * when the reader activates the viewer, which mounts an <iframe> pointing at the
 * self-contained boot page (public/gerber-demo/index.html).
 *
 * The live viewer needs cross-origin isolation (SharedArrayBuffer + pthreads)
 * and WebGL2. We feature-detect and degrade gracefully: capable browsers get the
 * live viewer; everything else gets a static poster + a short note. COOP/COEP are
 * applied to this route (dev: astro.config.mjs; prod: public/_headers); because
 * Astro's <ClientRouter/> can deliver this page via a soft SPA swap (which is
 * NOT cross-origin isolated), the client script does one guarded reload so the
 * route headers take effect.
 */
const poster = "/gerber-demo/poster.png"; // captured screenshot; optional fallback art
const caption =
  "KiCad's Gerber viewer compiled to WebAssembly, running the tiny_tapeout demo board ";
---

<figure class="gerber-demo not-prose" data-state="idle">
  <div class="gerber-demo__frame">
    <!-- Poster / launch surface. The href is a real fallback: with JS off (or if
         we choose to), it opens the standalone demo page. JS intercepts it to
         embed inline. -->
    <a
      class="gerber-demo__launch"
      href="/gerber-demo/index.html"
      style={`background-image:url(${poster})`}
    >
      <span class="gerber-demo__play" aria-hidden="true">▶</span>
      <span class="gerber-demo__cta">Launch Gerber viewer</span>
      <span class="gerber-demo__sub">~22 MB · loads on demand · runs locally in your browser</span>
    </a>

    <!-- Shown only when the browser can't run the live viewer. -->
    <div class="gerber-demo__fallback" hidden>
      <p class="gerber-demo__fallback-title">Live demo unavailable in this browser</p>
      <p class="gerber-demo__sub">
        The in-browser viewer needs a recent desktop <strong>Chrome</strong>,
        <strong>Firefox</strong>, or <strong>Safari 16+</strong> (it relies on
        SharedArrayBuffer + WebGL2). Here's a screenshot of what it renders.
      </p>
    </div>

    <!-- Status line while the WASM downloads/boots. -->
    <div class="gerber-demo__status" hidden>
      <span class="gerber-demo__spinner" aria-hidden="true"></span>
      <span class="gerber-demo__status-text">Loading the viewer…</span>
    </div>
  </div>
  <figcaption class="gerber-demo__caption">{caption}</figcaption>
</figure>

<script>
  const BOOT_SRC = "/gerber-demo/index.html";
  const RELOAD_KEY = "gerber-demo-coi-reload";
  const RELOAD_DEBOUNCE_MS = 10000;

  function hasWebGL2(): boolean {
    try {
      return !!document.createElement("canvas").getContext("webgl2");
    } catch {
      return false;
    }
  }

  // Best path: embed the viewer inline (only when this page is cross-origin isolated).
  function launchInline(root: HTMLElement): void {
    if (root.dataset.state === "loading" || root.dataset.state === "ready") return;
    root.dataset.state = "loading";

    const status = root.querySelector<HTMLElement>(".gerber-demo__status");
    if (status) status.hidden = false;

    const iframe = document.createElement("iframe");
    iframe.src = BOOT_SRC;
    iframe.title = "KiCad Gerber viewer";
    // Delegate cross-origin isolation to the (same-origin) iframe so it can use
    // SharedArrayBuffer / pthreads.
    iframe.allow = "cross-origin-isolated; fullscreen";
    iframe.setAttribute("allowfullscreen", "");
    iframe.className = "gerber-demo__iframe";
    // Size via INLINE styles, not the scoped stylesheet: this element is created
    // in JS so it lacks Astro's data-astro-cid scope attribute, and the scoped
    // `.gerber-demo__iframe` rule would not match it (it would fall back to the
    // 300x150 iframe default, shrinking the whole viewer).
    iframe.style.cssText =
      "position:absolute;inset:0;width:100%;height:100%;border:0;display:block;";
    iframe.addEventListener("load", () => {
      root.dataset.state = "ready";
      if (status) status.hidden = true;
    });

    const frame = root.querySelector(".gerber-demo__frame");
    frame?.appendChild(iframe);
  }

  // Genuinely incapable browser: cross-origin isolated, yet no SharedArrayBuffer
  // or WebGL2. The only true dead-end — show the poster + the "recent browser"
  // note. (We can ONLY assert this when isolated; on a non-isolated page
  // SharedArrayBuffer is always undefined regardless of the browser's support.)
  function showUnsupported(root: HTMLElement): void {
    root.dataset.state = "unsupported";
    const launchEl = root.querySelector<HTMLElement>(".gerber-demo__launch");
    launchEl?.querySelector(".gerber-demo__play")?.setAttribute("hidden", "");
    launchEl?.querySelector(".gerber-demo__cta")?.setAttribute("hidden", "");
    launchEl?.querySelector(".gerber-demo__sub")?.setAttribute("hidden", "");
    if (launchEl) {
      launchEl.removeAttribute("href");
      launchEl.style.cursor = "default";
    }
    const fallback = root.querySelector<HTMLElement>(".gerber-demo__fallback");
    if (fallback) fallback.hidden = false;
  }

  function initDemo(): void {
    const root = document.querySelector<HTMLElement>(".gerber-demo");
    if (!root) return; // page without the demo
    const launchEl = root.querySelector<HTMLAnchorElement>(".gerber-demo__launch");

    if (window.crossOriginIsolated === true) {
      sessionStorage.removeItem(RELOAD_KEY);
      if (typeof SharedArrayBuffer !== "undefined" && hasWebGL2()) {
        root.dataset.state = "idle";
        launchEl?.addEventListener("click", (e) => {
          e.preventDefault(); // intercept the href; embed inline instead
          launchInline(root);
        });
      } else {
        showUnsupported(root);
      }
      return;
    }

    // NOT cross-origin isolated — usually because a soft SPA navigation delivered
    // this page (the route's COOP/COEP only take effect on a real load). Reload
    // ONCE to get the isolated version so we can embed inline. Guard with a
    // TIMESTAMP, not a sticky flag: a stale flag from an earlier visit must not
    // permanently strand the demo, but two reloads within the debounce window
    // would be a loop — so we only suppress a reload we *just* did.
    const last = Number(sessionStorage.getItem(RELOAD_KEY) || "0");
    if (Date.now() - last > RELOAD_DEBOUNCE_MS) {
      sessionStorage.setItem(RELOAD_KEY, String(Date.now()));
      location.reload();
      return;
    }

    // Reloaded moments ago and STILL not isolated (stale cache, missing headers,
    // a preview without _headers…). Don't dead-end and don't blame the
    // browser: the standalone viewer page is isolated on its own, so let the
    // button open it in a new tab — capable browsers get the full viewer there.
    if (launchEl) {
      launchEl.target = "_blank";
      launchEl.rel = "noopener";
      root.dataset.state = "external";
    }
  }

  // Run on first load and on every SPA navigation (ClientRouter).
  document.addEventListener("astro:page-load", initDemo);
</script>

<style>
  .gerber-demo {
    margin: 2rem 0;
  }
  .gerber-demo__frame {
    position: relative;
    width: 100%;
    aspect-ratio: 16 / 10;
    min-height: 360px;
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    overflow: hidden;
    background: var(--bg-soft);
  }
  /* Launch surface (poster + CTA). */
  .gerber-demo__launch {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    text-decoration: none;
    color: var(--fg);
    background-size: cover;
    background-position: center;
    background-color: var(--bg-soft);
    transition: filter var(--dur) var(--ease);
  }
  .gerber-demo__launch::before {
    /* Dim the poster so the CTA reads clearly. */
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(
      180deg,
      rgba(11, 16, 32, 0.45),
      rgba(11, 16, 32, 0.7)
    );
  }
  .gerber-demo__launch:hover {
    filter: brightness(1.06);
    text-decoration: none;
  }
  .gerber-demo__play {
    position: relative;
    display: grid;
    place-items: center;
    width: 64px;
    height: 64px;
    border-radius: 999px;
    background: var(--brand-600);
    color: #fff;
    font-size: 1.5rem;
    padding-left: 4px;
    box-shadow: 0 6px 24px rgba(0, 0, 0, 0.35);
  }
  .gerber-demo__cta {
    position: relative;
    font-weight: 600;
    font-size: 1.05rem;
  }
  .gerber-demo__sub {
    position: relative;
    font-family: var(--font-mono);
    font-size: 0.78rem;
    color: var(--fg-muted);
    text-align: center;
    max-width: 32rem;
    padding: 0 1rem;
  }
  .gerber-demo__iframe {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    border: 0;
    display: block;
  }
  .gerber-demo__status {
    position: absolute;
    left: 50%;
    bottom: 1rem;
    transform: translateX(-50%);
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-family: var(--font-mono);
    font-size: 0.8rem;
    color: var(--fg);
    background: rgba(11, 16, 32, 0.85);
    border: 1px solid var(--border);
    padding: 0.4rem 0.7rem;
    border-radius: var(--radius);
  }
  .gerber-demo__spinner {
    width: 12px;
    height: 12px;
    border: 2px solid var(--border);
    border-top-color: var(--accent);
    border-radius: 999px;
    animation: gerber-spin 0.8s linear infinite;
  }
  @keyframes gerber-spin {
    to {
      transform: rotate(360deg);
    }
  }
  .gerber-demo__fallback {
    position: absolute;
    inset: auto 0 0 0;
    padding: 1rem 1.25rem;
    background: rgba(11, 16, 32, 0.9);
    border-top: 1px solid var(--border);
    text-align: center;
  }
  .gerber-demo__fallback-title {
    margin: 0 0 0.25rem;
    font-weight: 600;
  }
  .gerber-demo__fallback .gerber-demo__sub {
    position: static;
    margin: 0 auto;
  }
  .gerber-demo__caption {
    margin-top: 0.75rem;
    font-size: 0.85rem;
    color: var(--fg-muted);
    line-height: 1.5;
  }
  @media (prefers-reduced-motion: reduce) {
    .gerber-demo__spinner {
      animation: none;
    }
    .gerber-demo__launch {
      transition: none;
    }
  }
</style>
