- 插件注册表从 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>
418 lines
14 KiB
HTML
418 lines
14 KiB
HTML
<!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>
|