mesmertech's picture
Deploy static space build
27ec081 verified
Raw
History Blame Contribute Delete
6.66 kB
import { createSpace, el } from "./shared/ui.js";
import { callRest } from "./shared/api-client.js";
import { ENDPOINTS, siteUrl } from "./shared/config.js";
const TOOL_KEY = "screenshot";
const API_PATH = ENDPOINTS[TOOL_KEY].apiPath; // "/api/v1/screenshot"
const FULL_TOOL_URL = siteUrl(ENDPOINTS[TOOL_KEY].fullToolPath); // mesmer.tools/api-tools/screenshot
// Allowed dimension ranges (validated client-side before the API call).
const WIDTH = { min: 320, max: 1920, default: 1280 };
const HEIGHT = { min: 240, max: 1080, default: 720 };
const space = createSpace({
toolKey: TOOL_KEY,
emoji: "📸",
title: "Free Website Screenshot API",
subtitle: "Capture a screenshot of any website URL as an image. Paste a link, pick a size, get a JPG — free, no signup.",
intro: "Turn any URL into an image right from your browser. Great for previews, thumbnails, and link cards.",
});
/* --- Form ----------------------------------------------------------------- */
const urlInput = el("input", {
className: "ms-input",
type: "url",
id: "url",
name: "url",
required: "",
placeholder: "https://example.com",
autocomplete: "off",
autocapitalize: "off",
spellcheck: "false",
});
const widthInput = el("input", {
className: "ms-input",
type: "number",
id: "width",
name: "width",
inputmode: "numeric",
min: String(WIDTH.min),
max: String(WIDTH.max),
step: "1",
placeholder: String(WIDTH.default),
});
const heightInput = el("input", {
className: "ms-input",
type: "number",
id: "height",
name: "height",
inputmode: "numeric",
min: String(HEIGHT.min),
max: String(HEIGHT.max),
step: "1",
placeholder: String(HEIGHT.default),
});
const widthHint = el("span", { className: "ms-hint", text: `Optional · ${WIDTH.min}${WIDTH.max}, default ${WIDTH.default}` });
const heightHint = el("span", { className: "ms-hint", text: `Optional · ${HEIGHT.min}${HEIGHT.max}, default ${HEIGHT.default}` });
function preset(label, w, h) {
return el("button", {
className: "ms-btn-ghost",
type: "button",
onClick: () => {
widthInput.value = String(w);
heightInput.value = String(h);
validate(widthInput, widthHint, WIDTH);
validate(heightInput, heightHint, HEIGHT);
},
}, label);
}
const submitBtn = el("button", { className: "ms-btn", type: "submit" }, "Capture screenshot");
space.form.append(
el("div", { className: "ms-field" },
el("label", { for: "url", text: "Website URL" }),
urlInput,
el("span", { className: "ms-hint", text: "Any public page, e.g. https://github.com" }),
),
el("div", { className: "ms-row" },
el("div", { className: "ms-field" },
el("label", { for: "width", text: "Width (px)" }),
widthInput,
widthHint,
),
el("div", { className: "ms-field" },
el("label", { for: "height", text: "Height (px)" }),
heightInput,
heightHint,
),
),
el("div", { className: "ms-field" },
el("label", { text: "Size presets" }),
el("div", { className: "ms-presets" },
preset("Desktop 1280×720", 1280, 720),
preset("Wide 1920×1080", 1920, 1080),
preset("Mobile 390×844", 390, 844),
),
),
submitBtn,
el("p", { className: "ms-api-note" },
"Free demo: ",
String(ENDPOINTS[TOOL_KEY].freeLimitPerHour),
" screenshots/hour per visitor. Need more? ",
el("a", { href: FULL_TOOL_URL, target: "_blank", rel: "noopener", text: "Higher limits on mesmer.tools →" }),
),
);
// Live-validate dimension fields as the user edits them.
widthInput.addEventListener("input", () => validate(widthInput, widthHint, WIDTH));
heightInput.addEventListener("input", () => validate(heightInput, heightHint, HEIGHT));
/* --- Validation ----------------------------------------------------------- */
/**
* Validates a dimension input. Empty means "use the default".
* Returns { value, ok }; on failure marks the hint red and value is null.
*/
function validate(input, hint, range) {
const raw = input.value.trim();
if (raw === "") {
resetHint(hint, range, "ok");
return { value: range.default, ok: true };
}
const n = Number(raw);
if (!Number.isInteger(n)) {
setHint(hint, "Enter a whole number of pixels.", true);
return { value: null, ok: false };
}
if (n < range.min || n > range.max) {
setHint(hint, `Must be between ${range.min} and ${range.max}px.`, true);
return { value: null, ok: false };
}
resetHint(hint, range, "ok");
return { value: n, ok: true };
}
function resetHint(hint, range, _state) {
const min = range === WIDTH ? WIDTH.min : HEIGHT.min;
const max = range === WIDTH ? WIDTH.max : HEIGHT.max;
setHint(hint, `Optional · ${min}${max}, default ${range.default}`, false);
}
function setHint(hint, text, isError) {
hint.textContent = text;
hint.style.color = isError ? "#fca5a5" : "";
}
/* --- Submit --------------------------------------------------------------- */
space.form.addEventListener("submit", async (e) => {
e.preventDefault();
const url = urlInput.value.trim();
if (!url) {
urlInput.focus();
return;
}
const w = validate(widthInput, widthHint, WIDTH);
const h = validate(heightInput, heightHint, HEIGHT);
if (!w.ok || !h.ok) {
(!w.ok ? widthInput : heightInput).focus();
return;
}
space.clearOutput();
space.setLoading(true, "Capturing screenshot…");
submitBtn.disabled = true;
try {
const data = await callRest(API_PATH, {
params: { url, width: w.value, height: h.value },
});
space.clearStatus();
renderResult(data);
} catch (err) {
space.clearOutput();
space.showError(err);
} finally {
submitBtn.disabled = false;
}
});
/* --- Result --------------------------------------------------------------- */
function renderResult(data) {
// Success shape: { url, width, height, cached }
const imgUrl = data.url;
const width = data.width;
const height = data.height;
const filename = `screenshot-${width}x${height}.jpg`;
space.output.append(
el("div", { className: "ms-result-frame" },
el("img", {
src: imgUrl,
alt: `Screenshot of ${urlInput.value.trim()} at ${width}×${height}`,
loading: "lazy",
}),
),
el("div", { className: "ms-result-actions" },
el("a", { className: "ms-btn-ghost", href: imgUrl, target: "_blank", rel: "noopener", text: "Open full size ↗" }),
el("a", { className: "ms-btn-ghost", href: imgUrl, download: filename, text: "Download" }),
),
el("p", { className: "ms-meta",
text: `${width}×${height} · ${data.cached ? "served from cache" : "freshly captured"}` }),
);
}