jacquardSnapshot

← snapshot

4067 bytes
/**
 * Screenshots the live orb, large, via Playwright. The in-editor browser
 * pane composites unreliably at small sizes; this renders the real page and
 * captures it faithfully.
 *
 * Subjects are reached by actually walking the UI, so what is captured is
 * what a visitor sees — not a synthetic harness.
 *
 *   node tour/orb-shot.mjs
 */

import { mkdir, rm } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { chromium } from "@playwright/test";

const HERE = path.dirname(fileURLToPath(import.meta.url));
const OUT = path.join(HERE, "orbs");
const BASE = process.env.TOUR_BASE ?? "http://localhost:3000";

async function main() {
  await rm(OUT, { recursive: true, force: true });
  await mkdir(OUT, { recursive: true });

  const browser = await chromium.launch();
  const page = await browser.newPage({
    viewport: { width: 900, height: 760 },
    deviceScaleFactor: 2,
    colorScheme: "dark",
  });
  await page.goto(BASE, { waitUntil: "networkidle" });
  await page.waitForSelector(".jac-orb-canvas", { timeout: 20000 });

  const hideChrome = () =>
    page.evaluate(() => {
      for (const sel of [
        ".jac-stage-say",
        ".jac-stage-card",
        ".jac-offers",
        ".jac-stage-controls",
        ".jac-seen",
        ".jac-alert",
        ".jac-stage-switch",
        ".jac-stage-top",
      ]) {
        for (const el of document.querySelectorAll(sel)) {
          el.style.visibility = "hidden";
        }
      }
      const orb = document.querySelector(".jac-orb");
      if (orb) {
        orb.style.setProperty(
          "transform",
          "translate(var(--orb-drift-x,0), var(--orb-drift-y,0)) scale(3.0)",
          "important",
        );
        orb.style.margin = "180px auto";
      }
    });

  const showChrome = () =>
    page.evaluate(() => {
      for (const sel of [
        ".jac-stage-say",
        ".jac-stage-card",
        ".jac-offers",
        ".jac-stage-controls",
        ".jac-seen",
        ".jac-alert",
        ".jac-stage-switch",
        ".jac-stage-top",
      ]) {
        for (const el of document.querySelectorAll(sel)) {
          el.style.visibility = "";
        }
      }
      const orb = document.querySelector(".jac-orb");
      if (orb) {
        orb.style.removeProperty("transform");
        orb.style.margin = "";
      }
    });

  const shot = async (name, opts = {}) => {
    await hideChrome();
    if (opts.hoverAt) {
      await page.mouse.move(opts.hoverAt[0], opts.hoverAt[1]);
      await page.waitForTimeout(750);
    } else {
      await page.mouse.move(450, 380);
      await page.waitForTimeout(600);
    }
    await page.waitForTimeout(700);
    const subject = await page.evaluate(
      () => document.querySelector(".jac-orb")?.dataset.subject ?? "?",
    );
    await page.screenshot({
      path: path.join(OUT, `${name}.png`),
      clip: { x: 200, y: 130, width: 500, height: 500 },
    });
    console.log(`  ${name.padEnd(18)} subject=${subject}`);
    await showChrome();
  };

  const goPlace = async (needle) => {
    const t = page
      .locator(".jac-offer, .jac-seen-chip")
      .filter({ hasText: needle })
      .first();
    await t.waitFor({ state: "visible", timeout: 15000 });
    await t.click();
    await page.waitForTimeout(900);
  };

  await shot("1-lobby");
  // Lean: pointer parked in the top-right corner.
  await shot("1-lobby-lean", { hoverAt: [830, 150] });

  const meridian = page.locator(".jac-seg button", { hasText: "Meridian" });
  if (await meridian.count()) {
    await meridian.first().click();
    await page.waitForTimeout(1100);
  }

  await goPlace("blocked promotion");
  await shot("2-gate");

  await goPlace("session.rs");
  await shot("3-file");

  await goPlace("session cache");
  await shot("4-decision");

  await goPlace("cloth so far");
  await shot("5-cloth");

  await goPlace("latest pick");
  await shot("6-snapshot");

  await browser.close();
  console.log(`-> ${OUT}`);
}

main().catch((e) => {
  console.error(e);
  process.exit(1);
});