---
/**
 * 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: vercel.json); 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 (gerbview), compiled to WebAssembly, running the tiny_tapeout demo board entirely in your browser.";
---

<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";

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

  function launch(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);
  }

  function showFallback(root: HTMLElement): void {
    root.dataset.state = "unsupported";
    const launchEl = root.querySelector<HTMLElement>(".gerber-demo__launch");
    const fallback = root.querySelector<HTMLElement>(".gerber-demo__fallback");
    // Keep the poster (it's the <a> background); hide the play affordance, show note.
    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"); // not clickable
      launchEl.style.cursor = "default";
    }
    if (fallback) fallback.hidden = false;
  }

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

    const isolated = window.crossOriginIsolated === true;

    // Soft SPA navigation lands here NOT cross-origin isolated; force one real
    // load so the route's COOP/COEP headers apply. Guard against reload loops.
    if (!isolated) {
      if (!sessionStorage.getItem(RELOAD_KEY)) {
        sessionStorage.setItem(RELOAD_KEY, "1");
        location.reload();
        return;
      }
      // Already retried and still not isolated → genuinely unsupported (or no
      // headers, e.g. a preview without vercel.json). Fall back.
    } else {
      sessionStorage.removeItem(RELOAD_KEY);
    }

    const supported =
      isolated && typeof SharedArrayBuffer !== "undefined" && hasWebGL2();

    if (!supported) {
      showFallback(root);
      return;
    }

    const launchEl = root.querySelector<HTMLElement>(".gerber-demo__launch");
    launchEl?.addEventListener("click", (e) => {
      e.preventDefault(); // intercept the href; embed inline instead
      launch(root);
    });
  }

  // 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>
