document.addEventListener("DOMContentLoaded", function () { const sourceMap = captureSourceMap(); let currentLocale = getInitialLocale(); let heroRotationTimer = null; const copyMessages = { zh: { copy: "复制", copied: "已复制", failed: "复制失败" }, en: { copy: "Copy", copied: "Copied", failed: "Copy failed" } }; const enTranslations = { "common.nav.home": "Home", "common.nav.docs": "Docs", "common.nav.skills": "Skills", "common.nav.console": "Console", "common.nav.pricing": "Pricing", "common.login": "Sign in", "common.logout": "Sign out", "index.meta.title": "Popi.art SkillHub", "index.meta.description": "Static bilingual prototype for Popi.art SkillHub, written from popiartcli and Popiart_skillhub.", "index.hero.catalogCard": `
The public registry comes from Popiart_skillhub. It currently exposes 59 skills across image, video, and audio.
text2image / img2img / image2video
`, "index.hero.mcpCard": `list_skills / run_skill / get_job / pull_artifact
`, "index.hero.showcaseCard": `The character-consistency showcase skill is already visible in the public catalog.
`, "index.hero.copy": `
popiartcli owns the unified CLI entrypoint, auth, skill discovery, run, jobs, artifacts, and MCP discoverability; Popiart_skillhub owns the public catalog and skill descriptions.
The homepage is organized around real command surfaces, catalog facts, and execution boundaries instead of vague product copy.
`, "index.surfaces.metricTotal": "public skills", "index.surfaces.metricAudio": "audio skills", "index.surfaces.metricImage": "image skills", "index.surfaces.metricVideo": "video skills", "index.scenarios.heading": `These names stay close to the real skill ids, categories, and capabilities in Popiart_skillhub.
Built around popiskill-image-text2image-* for prompt-driven image generation, character sheets, three-view, and stylized artwork.
Includes baseline image editing, localized retouching, and the PopiStudio Alice consistency showcase workflow.
View img2img `, "index.scenarios.card3": `One of the official runtime baselines, ideal for turning still frames into short teasers and motion clips.
View video skills `, "index.scenarios.card4": `The catalog already includes text2video definitions across kling, sora2pro, and studio-oriented variants.
View text2video `, "index.scenarios.card5": `Covers doubao, ima-studio, multimodel, and voice-engineer style speech output workflows.
View audio skills `, "index.scenarios.card6": `The catalog already includes local speech recognition, video transcription, translation, and localization-related skills.
View STT & dubbing `, "index.scenarios.card7": `popiart mcp serve / print-config / doctor already forms a practical tool surface for agent integration.
The CLI generates discoverability assets and merges bundled seed skills into the query path.
View bootstrap `, "index.flow.heading": `No invented commands. The first-screen product story follows the commands already implemented in popiartcli.
Supports Homebrew, install.sh, PowerShell, and source build. The main distribution path is the Go CLI.
`, "index.flow.step2": `Use bootstrap --discoverable to generate shell completion, MCP config snippets, and seed skill profile assets.
Sign in with the product-layer key, then use skills list/get/schema to discover merged remote and local skills.
Tasks always return a job_id, long jobs are polled, and outputs are recovered through artifacts pull.
popiartcli and Popiart_skillhub, with layout references from open-claw.",
"docs.meta.title": "Popi.art Docs",
"docs.meta.description": "Bilingual static documentation prototype for Popi.art CLI and SkillHub.",
"docs.heading": `
This page is sourced from the popiartcli README, current status notes, and command tree. It does not invent future commands.
The current popiartcli README explicitly lists Homebrew, curl/install.sh, PowerShell, and source build as supported install paths.
brew tap wtgoku-create/popi
brew install wtgoku-create/popi/popiart
popiart bootstrap --agent codex --discoverable
git clone https://github.com/wtgoku-create/popiartcli.git
cd popiartcli
go install ./cmd/popiart
popiart --help
The smallest closed loop is: install the CLI, sign in, discover a skill, run a task, poll jobs, and pull artifacts.
popiart auth login --key pk-...
popiart skills list --tag image
popiart run popiskill-image-text2image-basic-v1 --input @params.json --wait
popiart jobs get job_xxx
popiart artifacts pull art_xxx
The repo already implements popiart mcp serve / print-config / doctor and exposes a practical agent-facing tool surface.
popiart mcp serve --describe
popiart mcp print-config
popiart mcp doctor
popiart bootstrap --agent codex --discoverable
The current status notes for popiartcli explicitly treat only three skills as the official runtime baseline. They are the best candidates for homepage and console defaults.
popiartcli README, command tree, and current-status notes.",
"login.meta.title": "Popi.art Login",
"login.meta.description": "Static bilingual login prototype for Popi.art.",
"login.form": `
The real auth model in popiartcli is a PopiArt product-layer key, not a raw OpenAI, Gemini, Sora, or other provider key pasted into the client.
popiartcli is the local entrypoint, popiartServer is the product backend, and PopiNewAPI is the model gateway. Provider keys do not enter the CLI.
Saved keys go into ~/.popiart/config.json, and can be overridden by POPIART_KEY or POPIART_TOKEN.
popiart auth login --key instead of inventing a separate account system.",
"pricing.meta.title": "Popi.art Pricing",
"pricing.meta.description": "Static bilingual pricing prototype for Popi.art credits and usage.",
"pricing.heading": `
The static pricing page follows the credits structure of open-claw, while capability names come from popiartcli and Popiart_skillhub.
This static prototype only demonstrates the information architecture. Real pricing, recharge, balance, and billing should come from popiartServer.
Capability names and categories follow the current catalog. Credit numbers are placeholders for the static prototype and should later come from the real billing table.
The static prototype assumes packs can stack into the same product-layer account. Real validity and stacking rules should come from the billing service.
`, "pricing.faq2": `No. The current CLI status notes explicitly treat only 3 skills as the official runtime baseline. Everything else still depends on server-side registration and routing.
`, "pricing.faq3": `The static prototype currently assumes you need to purchase another credits pack. If monthly plans or bundled quotas are added later, the billing layer should define the rule.
`, "pricing.faq4": `popiartcli should not hold raw upstream provider keys. Those keys belong behind the gateway and product backend boundary.
The directory structure comes from index.json, and detail structure comes from each skills/*/SKILL.md instead of a separate hand-written layer.
One of the official runtime baselines and the most natural default capability to highlight on the homepage and in the console.
`, "skills.card2": `The baseline image-editing path and a key entrypoint from the static catalog into actual editable workflows.
`, "skills.card3": `The strongest public example skill on the current video path for a runnable product showcase.
`, "skills.card4": `A fixed-reference Alice consistency showcase workflow, suitable for demos and proof frames.
`, "skills.card5": `Extends the Alice showcase from a single frame into a teaser-style motion clip, which makes it ideal for homepage marketing.
`, "skills.card6": `The presence of many audio skills makes it clear the site cannot be designed only around images and video.
`, "skills.detail": `From SKILL.md you can reliably extract frontmatter descriptions, recommended usage, discouraged usage, required inputs, workflow, command patterns, payload templates, and artifact handling.
popiart run popiskill-image-img2img-popistudio-alice-showcase-v1 \
--input @params.json \
--wait
index.json and SKILL.md, and can later connect directly to the remote registry.",
"console.meta.title": "Popi.art Console",
"console.meta.description": "Static bilingual console prototype for Popi.art.",
"console.heading": `
Manage product-layer keys, inspect credit usage, and install the CLI quickly.
`, "console.stat1": `brew tap wtgoku-create/popi
brew install wtgoku-create/popi/popiart
export POPIART_KEY="pk_559196_demo"
export POPIART_ENDPOINT="https://api.creatoragentos.io/v1"
popiart bootstrap --agent codex --discoverable
`,
"console.footer.title": "Popi.art Console Prototype",
"console.footer.subtitle": "The console layout is heavily inspired by open-claw, while config fields and install commands are rewritten from popiartcli."
};
bindLocaleToggles();
applyLocale(currentLocale);
function captureSourceMap() {
const map = {};
document.querySelectorAll("[data-i18n]").forEach(function (node) {
map[node.getAttribute("data-i18n")] = node.textContent;
});
document.querySelectorAll("[data-i18n-html]").forEach(function (node) {
map[node.getAttribute("data-i18n-html")] = node.innerHTML.trim();
});
document.querySelectorAll("[data-i18n-content]").forEach(function (node) {
map[node.getAttribute("data-i18n-content")] = node.getAttribute("content") || "";
});
return map;
}
function getInitialLocale() {
try {
const url = new URL(window.location.href);
const param = url.searchParams.get("lang");
if (param === "en" || param === "zh") {
return param;
}
} catch (error) {
// Ignore URL parsing failures for unusual environments.
}
try {
const stored = window.localStorage.getItem("popiart_locale");
if (stored === "en" || stored === "zh") {
return stored;
}
} catch (error) {
// Ignore storage failures.
}
return "zh";
}
function resolveTranslation(key, locale) {
if (locale === "en" && Object.prototype.hasOwnProperty.call(enTranslations, key)) {
return enTranslations[key];
}
return sourceMap[key];
}
function applyLocale(locale) {
currentLocale = locale;
document.documentElement.lang = locale === "en" ? "en" : "zh-CN";
try {
window.localStorage.setItem("popiart_locale", locale);
} catch (error) {
// Ignore storage failures.
}
try {
const url = new URL(window.location.href);
url.searchParams.set("lang", locale);
window.history.replaceState({}, "", url.toString());
} catch (error) {
// Ignore history update failures.
}
document.querySelectorAll("[data-i18n]").forEach(function (node) {
const value = resolveTranslation(node.getAttribute("data-i18n"), locale);
if (typeof value === "string") {
node.textContent = value;
}
});
document.querySelectorAll("[data-i18n-html]").forEach(function (node) {
const value = resolveTranslation(node.getAttribute("data-i18n-html"), locale);
if (typeof value === "string") {
node.innerHTML = value;
}
});
document.querySelectorAll("[data-i18n-content]").forEach(function (node) {
const value = resolveTranslation(node.getAttribute("data-i18n-content"), locale);
if (typeof value === "string") {
node.setAttribute("content", value);
}
});
document.querySelectorAll("[data-locale-toggle]").forEach(function (node) {
node.textContent = locale === "zh" ? "EN" : "中文";
node.setAttribute("aria-label", locale === "zh" ? "Switch to English" : "切换到中文");
});
bindCopyButtons();
initRotatingHero();
}
function bindLocaleToggles() {
document.querySelectorAll("[data-locale-toggle]").forEach(function (node) {
node.onclick = function (event) {
event.preventDefault();
applyLocale(currentLocale === "zh" ? "en" : "zh");
};
});
}
function bindCopyButtons() {
const messages = copyMessages[currentLocale] || copyMessages.zh;
document.querySelectorAll("[data-copy-target]").forEach(function (button) {
button.onclick = async function () {
const targetId = button.getAttribute("data-copy-target");
const target = targetId ? document.getElementById(targetId) : null;
if (!target) {
return;
}
const original = button.textContent || messages.copy;
try {
await navigator.clipboard.writeText(target.textContent || "");
button.textContent = messages.copied;
} catch (error) {
button.textContent = messages.failed;
}
window.setTimeout(function () {
button.textContent = original;
}, 1400);
};
});
}
function initRotatingHero() {
if (heroRotationTimer) {
window.clearInterval(heroRotationTimer);
heroRotationTimer = null;
}
const rotating = document.querySelector("[data-hero-phrases]");
if (!rotating) {
return;
}
let phrases = [];
try {
phrases = JSON.parse(rotating.getAttribute("data-hero-phrases") || "[]");
} catch (error) {
phrases = [];
}
if (phrases.length > 1) {
let index = 0;
heroRotationTimer = window.setInterval(function () {
index = (index + 1) % phrases.length;
rotating.textContent = phrases[index];
}, 2400);
}
}
});