enprot 0.5.61

Engyon Protected Text (EPT) — confidentiality processor and capability ledger
---
import Base from "../layouts/Base.astro";
---

<Base title="Search · enprot">
  <section class="max-w-2xl">
    <h1 class="text-3xl font-bold mb-2">Search</h1>
    <p class="text-enprot-muted mb-4">
      Full-text search across every page of the site.
    </p>
    <input
      id="search-input"
      type="search"
      placeholder="Search the docs…"
      autocomplete="off"
      class="w-full rounded border border-enprot-ink/20 bg-white px-4 py-2
             font-mono text-sm focus:border-enprot-accent focus:outline-none"
      aria-label="Search the documentation"
    />
    <div id="search-status" class="mt-2 text-sm text-enprot-muted" aria-live="polite"></div>
    <ul id="search-results" class="mt-4 space-y-4 list-none p-0"></ul>
  </section>

  <script>
    // pagefind's index is generated AFTER the astro build (see the
    // `build` script in package.json), so the module only exists at
    // runtime — @vite-ignore keeps the bundler from trying to
    // resolve it at build time.
    let pagefind: any;

    async function ensureIndex(): Promise<any> {
      if (!pagefind) {
        pagefind = await import(/* @vite-ignore */ "/pagefind/pagefind.js");
      }
      return pagefind;
    }

    const input = document.getElementById("search-input") as HTMLInputElement;
    const status = document.getElementById("search-status")!;
    const resultsEl = document.getElementById("search-results")!;

    let timer: ReturnType<typeof setTimeout> | undefined;
    input.addEventListener("input", () => {
      clearTimeout(timer);
      timer = setTimeout(runSearch, 150);
    });

    async function runSearch(): Promise<void> {
      const q = input.value.trim();
      resultsEl.replaceChildren();
      if (q.length < 2) {
        status.textContent = "";
        return;
      }
      status.textContent = "Searching…";
      const index = await ensureIndex();
      const search = await index.search(q);
      const shown = search.results.slice(0, 10);
      for (const result of shown) {
        const data = await result.data();
        const li = document.createElement("li");
        const a = document.createElement("a");
        a.href = data.url;
        a.className = "block rounded border border-enprot-ink/10 bg-white px-4 py-3 hover:border-enprot-accent";
        const title = document.createElement("div");
        title.className = "font-semibold";
        title.textContent = data.meta.title ?? data.url;
        const excerpt = document.createElement("div");
        excerpt.className = "text-sm text-enprot-muted mt-1";
        excerpt.innerHTML = data.excerpt;
        a.append(title, excerpt);
        li.append(a);
        resultsEl.append(li);
      }
      status.textContent =
        search.results.length === 0
          ? `No results for “${q}”.`
          : `${search.results.length} result${search.results.length === 1 ? "" : "s"}${shown.length < search.results.length ? ` (showing ${shown.length})` : ""}.`;
    }
  </script>
</Base>