play.wenpai.net/play/index.html
elementary-qa 0153aaa951 refactor: 插件数据外置到 plugins.json + 移除 Google Fonts 依赖
- 插件注册表从 index.html 硬编码提取到独立 plugins.json,fetch 动态加载
- 新增产品只需编辑 plugins.json,无需改 index.html
- 移除 Google Fonts (Inter) 外部依赖,使用系统字体栈
- 减少 3 次外部网络请求(2 preconnect + 1 stylesheet)
- 新增 docs/improvement-plan.md 记录改进路线图

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-21 11:09:59 +08:00

418 lines
14 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文派体验场 — play.wenpai.net</title>
<meta name="description" content="在浏览器中即时体验 WordPress 插件与主题,无需安装任何软件">
<meta property="og:title" content="文派体验场">
<meta property="og:description" content="在浏览器中即时体验 WordPress 插件与主题">
<meta property="og:url" content="https://play.wenpai.net/">
<style>
:root {
--wp-blue: #3858e9;
--wp-blue-hover: #1d35b4;
--wp-dark: #1e1e1e;
--wp-gray-900: #1e1e1e;
--wp-gray-700: #757575;
--wp-gray-600: #949494;
--wp-gray-300: #ddd;
--wp-gray-100: #f0f0f0;
--wp-gray-050: #f6f7f7;
--wp-white: #ffffff;
--wp-alert-bg: #fcf9e8;
--wp-alert-border: #f0c33c;
--wp-alert-text: #50401e;
--wp-radius: 2px;
--wp-font: -apple-system, 'PingFang SC',
'Microsoft YaHei', 'Noto Sans SC', sans-serif;
--wp-max-width: 960px;
--wp-transition: 120ms ease;
}
*, *::before, *::after {
margin: 0; padding: 0; box-sizing: border-box;
}
body {
font-family: var(--wp-font);
background: var(--wp-white);
color: var(--wp-dark);
min-height: 100vh;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
</style>
</head>
<body>
<!-- Hero -->
<header class="hero">
<div class="hero-pattern"></div>
<div class="hero-content">
<div class="hero-wp-mark" aria-hidden="true">
<svg viewBox="0 0 122.5 122.5" xmlns="http://www.w3.org/2000/svg">
<path d="M8.7 61.3c0 20.8 12.1 38.7 29.6 47.3L13 39.6a52.3
52.3 0 0 0-4.3 21.7zm88.6-2.7c0-6.5-2.3-11-4.3-14.5-2.7
-4.3-5.2-8-5.2-12.3 0-4.8 3.7-9.3 8.9-9.3h.7a52.4 52.4
0 0 0-79.4 1.4h4c6.5 0 16.6-.8 16.6-.8 3.4-.2 3.8 4.7.4
5.1 0 0-3.4.4-7.1.6l22.5 67 13.5-40.6-9.6-26.4c-3.4-.2
-6.6-.6-6.6-.6-3.4-.2-3-5.3.4-5.1 0 0 10.3.8 16.4.8 6.5
0 16.6-.8 16.6-.8 3.4-.2 3.8 4.7.4 5.1 0 0-3.4.4-7.2
.6l22.4 66.5 6.2-20.6c2.7-8.6 4.7-14.7 4.7-20zm-37.8
7.9L40.4 113a52.6 52.6 0 0 0 32.3 1 4.7 4.7 0 0 1-.4
-.7zm47.8-32.4c.2 1.7.4 3.6.4 5.6 0 5.5-1 11.7-4.2
19.4l-16.8 48.4c16.3-9.5 27.3-27.2 27.3-47.4 0-9.6-2.5
-18.6-6.7-26zM61.3 0a61.3 61.3 0 1 0 0 122.5A61.3 61.3
0 0 0 61.3 0zm0 119.7a58.5 58.5 0 1 1 0-117 58.5 58.5
0 0 1 0 117z" fill="currentColor"/>
</svg>
</div>
<h1 class="hero-title">文派体验场</h1>
<p class="hero-subtitle">在浏览器中即时体验 WordPress 插件与主题</p>
<p class="hero-desc">基于 WordPress Playground所有环境运行在浏览器沙盒中无需服务器</p>
</div>
</header>
<!-- Main -->
<main class="main">
<div class="container">
<div class="notice" role="note">
<svg class="notice-icon" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="1.5">
<circle cx="12" cy="12" r="10"/>
<line x1="12" y1="8" x2="12" y2="12"/>
<line x1="12" y1="16" x2="12.01" y2="16"/>
</svg>
<p>所有体验环境均为临时沙盒关闭页面后数据不会保留。AI 相关功能需配置 API Key 后使用。</p>
</div>
<div class="grid" id="plugins"></div>
</div>
</main>
<!-- Footer -->
<footer class="footer">
<div class="container">
<div class="footer-inner">
<span>Powered by
<a href="https://wordpress.github.io/wordpress-playground/">WordPress Playground</a>
</span>
<span class="footer-sep" aria-hidden="true"></span>
<span><a href="https://wenpai.net">文派</a></span>
</div>
</div>
</footer>
<style>
.hero {
position: relative;
background: var(--wp-gray-900);
color: var(--wp-white);
padding: 5rem 1.5rem 4.5rem;
text-align: center;
overflow: hidden;
}
.hero-pattern {
position: absolute; inset: 0; opacity: 0.04;
background-image:
linear-gradient(45deg, currentColor 25%, transparent 25%),
linear-gradient(-45deg, currentColor 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, currentColor 75%),
linear-gradient(-45deg, transparent 75%, currentColor 75%);
background-size: 30px 30px;
background-position: 0 0, 0 15px, 15px -15px, -15px 0;
animation: drift 20s linear infinite;
}
@keyframes drift {
to { background-position: 30px 0, 30px 15px, 45px -15px, -15px 0; }
}
.hero-content {
position: relative;
max-width: var(--wp-max-width);
margin: 0 auto;
animation: fadeUp 0.6s ease both;
}
@keyframes fadeUp {
from { opacity: 0; transform: translateY(12px); }
to { opacity: 1; transform: translateY(0); }
}
.hero-wp-mark {
width: 48px; height: 48px;
margin: 0 auto 1.75rem;
color: var(--wp-blue); opacity: 0.85;
}
.hero-wp-mark svg { width: 100%; height: 100%; }
.hero-title {
font-size: 2.25rem; font-weight: 600;
letter-spacing: -0.025em; line-height: 1.2;
margin-bottom: 0.75rem;
}
.hero-subtitle {
font-size: 1.05rem; font-weight: 400;
color: rgba(255,255,255,0.75); line-height: 1.6;
margin-bottom: 0.375rem;
}
.hero-desc {
font-size: 0.8125rem;
color: rgba(255,255,255,0.4); line-height: 1.6;
}
.main {
padding: 3rem 0 4rem;
background: var(--wp-gray-050);
min-height: 50vh;
}
.container {
max-width: var(--wp-max-width);
margin: 0 auto; padding: 0 1.5rem;
}
.notice {
display: flex; align-items: flex-start; gap: 0.625rem;
background: var(--wp-alert-bg);
border-left: 3px solid var(--wp-alert-border);
border-radius: 0 var(--wp-radius) var(--wp-radius) 0;
padding: 0.875rem 1rem; margin-bottom: 2rem;
font-size: 0.8125rem; color: var(--wp-alert-text);
line-height: 1.6;
animation: fadeUp 0.6s 0.15s ease both;
}
.notice-icon {
flex-shrink: 0; width: 18px; height: 18px;
margin-top: 1px; color: var(--wp-alert-border);
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1.25rem;
}
.card {
background: var(--wp-white);
border: 1px solid var(--wp-gray-300);
border-radius: var(--wp-radius);
padding: 1.5rem;
transition: border-color var(--wp-transition),
box-shadow var(--wp-transition);
animation: fadeUp 0.6s ease both;
}
.card:hover {
border-color: var(--wp-blue);
box-shadow: 0 0 0 1px var(--wp-blue);
}
.card-header {
display: flex; align-items: center;
justify-content: space-between;
margin-bottom: 0.75rem;
}
.card-tag {
display: inline-block;
font-size: 0.6875rem; font-weight: 500;
letter-spacing: 0.03em; text-transform: uppercase;
padding: 0.125rem 0.5rem;
border-radius: var(--wp-radius);
background: var(--wp-blue); color: var(--wp-white);
}
.card-tag.core { background: var(--wp-gray-700); }
.card-version {
font-size: 0.75rem; color: var(--wp-gray-600);
font-variant-numeric: tabular-nums;
}
.card-name {
font-size: 1.125rem; font-weight: 600;
letter-spacing: -0.01em; line-height: 1.3;
margin-bottom: 0.5rem;
}
.card-desc {
font-size: 0.8125rem; color: var(--wp-gray-700);
line-height: 1.7; margin-bottom: 1.25rem;
}
.card-actions {
display: flex; flex-wrap: wrap; gap: 0.5rem;
}
.btn {
display: inline-flex; align-items: center; gap: 0.375rem;
padding: 0.5rem 1rem; border-radius: var(--wp-radius);
font-family: var(--wp-font);
font-size: 0.8125rem; font-weight: 500;
text-decoration: none; line-height: 1;
transition: background var(--wp-transition),
color var(--wp-transition),
box-shadow var(--wp-transition);
cursor: pointer; border: none;
}
.btn svg { width: 14px; height: 14px; flex-shrink: 0; }
.btn-primary {
background: var(--wp-blue); color: var(--wp-white);
}
.btn-primary:hover { background: var(--wp-blue-hover); }
.btn-secondary {
background: transparent; color: var(--wp-blue);
box-shadow: inset 0 0 0 1px var(--wp-gray-300);
}
.btn-secondary:hover {
box-shadow: inset 0 0 0 1px var(--wp-blue);
}
.btn-tertiary {
background: transparent; color: var(--wp-gray-700);
padding: 0.5rem 0.625rem;
}
.btn-tertiary:hover { color: var(--wp-blue); }
.footer {
padding: 2rem 0;
border-top: 1px solid var(--wp-gray-300);
background: var(--wp-white);
}
.footer-inner {
display: flex; align-items: center;
justify-content: center; gap: 0.75rem;
font-size: 0.8125rem; color: var(--wp-gray-600);
}
.footer a {
color: var(--wp-gray-700); text-decoration: none;
transition: color var(--wp-transition);
}
.footer a:hover { color: var(--wp-blue); }
.footer-sep {
width: 3px; height: 3px;
border-radius: 50%; background: var(--wp-gray-300);
}
@media (max-width: 640px) {
.hero { padding: 3.5rem 1.25rem 3rem; }
.hero-title { font-size: 1.75rem; }
.hero-subtitle { font-size: 0.9375rem; }
.grid { grid-template-columns: 1fr; }
.card-actions { flex-direction: column; }
.card-actions .btn { justify-content: center; }
}
.card:nth-child(1) { animation-delay: 0.2s; }
.card:nth-child(2) { animation-delay: 0.3s; }
.card:nth-child(3) { animation-delay: 0.4s; }
.card:nth-child(4) { animation-delay: 0.5s; }
.card:nth-child(5) { animation-delay: 0.6s; }
.card:nth-child(6) { animation-delay: 0.7s; }
</style>
<script>
// ── 插件注册表 — 从 plugins.json 动态加载 ──
var PLAYGROUND_BASE = window.location.origin;
function playUrl(bp) {
var origin = window.location.origin;
return origin + '/playground.html?blueprint-url=' + origin + bp;
}
function playUrlRemote(bp) {
return 'https://playground.wordpress.net/?blueprint-url=' + PLAYGROUND_BASE + bp;
}
// ── DOM 构建(无 innerHTML──
function createSvg(pathMarkup) {
var t = document.createElement('template');
t.innerHTML = pathMarkup.trim();
return t.content.firstChild;
}
function makeIcon(type) {
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('viewBox', '0 0 24 24');
svg.setAttribute('fill', 'none');
svg.setAttribute('stroke', 'currentColor');
svg.setAttribute('stroke-width', '2');
svg.setAttribute('stroke-linecap', 'round');
svg.setAttribute('stroke-linejoin', 'round');
if (type === 'play') {
var poly = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');
poly.setAttribute('points', '5 3 19 12 5 21 5 3');
svg.appendChild(poly);
} else if (type === 'globe') {
var c = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
c.setAttribute('cx', '12'); c.setAttribute('cy', '12'); c.setAttribute('r', '10');
svg.appendChild(c);
var l = document.createElementNS('http://www.w3.org/2000/svg', 'line');
l.setAttribute('x1', '2'); l.setAttribute('y1', '12');
l.setAttribute('x2', '22'); l.setAttribute('y2', '12');
svg.appendChild(l);
var p = document.createElementNS('http://www.w3.org/2000/svg', 'path');
p.setAttribute('d', 'M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z');
svg.appendChild(p);
} else if (type === 'code') {
var p1 = document.createElementNS('http://www.w3.org/2000/svg', 'polyline');
p1.setAttribute('points', '16 18 22 12 16 6');
svg.appendChild(p1);
var p2 = document.createElementNS('http://www.w3.org/2000/svg', 'polyline');
p2.setAttribute('points', '8 6 2 12 8 18');
svg.appendChild(p2);
}
return svg;
}
function makeBtn(cls, href, iconType, label) {
var a = document.createElement('a');
a.className = 'btn ' + cls;
a.href = href;
a.target = '_blank';
a.rel = 'noopener';
a.appendChild(makeIcon(iconType));
a.appendChild(document.createTextNode(' ' + label));
return a;
}
var grid = document.getElementById('plugins');
function renderPlugins(plugins) {
var frag = document.createDocumentFragment();
plugins.forEach(function(p) {
var card = document.createElement('div');
card.className = 'card';
var header = document.createElement('div');
header.className = 'card-header';
var tag = document.createElement('span');
tag.className = 'card-tag' + (p.tagClass ? ' ' + p.tagClass : '');
tag.textContent = p.tag;
var ver = document.createElement('span');
ver.className = 'card-version';
ver.textContent = p.version;
header.appendChild(tag);
header.appendChild(ver);
var name = document.createElement('h2');
name.className = 'card-name';
name.textContent = p.name;
var desc = document.createElement('p');
desc.className = 'card-desc';
desc.textContent = p.desc;
var actions = document.createElement('div');
actions.className = 'card-actions';
actions.appendChild(makeBtn('btn-primary', playUrl(p.blueprint), 'play', '立即体验'));
actions.appendChild(makeBtn('btn-secondary', playUrlRemote(p.blueprint), 'globe', '国际线路'));
if (p.repo) {
actions.appendChild(makeBtn('btn-tertiary', p.repo, 'code', '源码'));
}
card.appendChild(header);
card.appendChild(name);
card.appendChild(desc);
card.appendChild(actions);
frag.appendChild(card);
});
grid.appendChild(frag);
}
fetch('/plugins.json')
.then(function(r) { return r.json(); })
.then(renderPlugins)
.catch(function(e) {
grid.textContent = '加载插件列表失败,请刷新重试。';
console.error('plugins.json load error:', e);
});
</script>
</body>
</html>