initial import: random background api

This commit is contained in:
shumengya
2026-03-29 10:58:33 +08:00
commit f68e19a3bd
62 changed files with 985 additions and 0 deletions

390
index.html Normal file
View File

@@ -0,0 +1,390 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>随机背景 API</title>
<meta name="description" content="Cloudflare Pages 随机背景 API 文档与在线预览。">
<style>
:root {
--bg: #f6f7fb;
--panel: #fff;
--text: #172033;
--muted: #5b657a;
--line: #e3e7ef;
--soft: #f3f5f9;
--accent: #2563eb;
--code: #0b1220;
--shadow: 0 10px 28px rgba(15, 23, 42, 0.06);
}
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
background: linear-gradient(180deg, #fff 0%, var(--bg) 100%);
color: var(--text);
font: 14px/1.55 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
}
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
.wrap {
max-width: 1200px;
margin: 0 auto;
padding: 14px;
}
.top {
display: grid;
gap: 10px;
grid-template-columns: minmax(0, 1fr) 250px;
align-items: start;
}
.main {
display: grid;
gap: 10px;
grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr);
margin-top: 10px;
}
.card {
background: var(--panel);
border: 1px solid var(--line);
border-radius: 14px;
box-shadow: var(--shadow);
overflow: hidden;
}
.pad { padding: 12px; }
h1, h2, p { margin: 0; }
h1 {
font-size: clamp(24px, 3vw, 34px);
line-height: 1.1;
letter-spacing: -0.03em;
}
h2 {
font-size: 15px;
margin-bottom: 6px;
}
.lead {
margin-top: 6px;
color: var(--muted);
}
.meta {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: 10px;
}
.tag {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 5px 9px;
border-radius: 999px;
border: 1px solid var(--line);
background: var(--soft);
font-size: 11px;
color: var(--text);
}
.links {
display: grid;
gap: 6px;
font-size: 13px;
}
.section {
display: grid;
gap: 8px;
}
.grid-2 {
display: grid;
gap: 8px;
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.endpoint {
padding: 10px;
border-radius: 12px;
border: 1px solid var(--line);
background: #fff;
}
.endpoint strong {
display: block;
margin-bottom: 4px;
font-size: 12px;
}
.code {
margin: 0;
padding: 9px 10px;
border-radius: 10px;
border: 1px solid var(--line);
background: var(--code);
color: #e7eefc;
overflow-x: auto;
font: 11px/1.5 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
label {
display: block;
margin-bottom: 4px;
font-size: 11px;
color: var(--muted);
}
select, button, input {
width: 100%;
padding: 9px 10px;
border-radius: 10px;
border: 1px solid var(--line);
background: #fff;
color: var(--text);
font: inherit;
}
button {
cursor: pointer;
border-color: var(--accent);
background: var(--accent);
color: #fff;
}
.row {
display: grid;
gap: 8px;
grid-template-columns: repeat(3, minmax(0, 1fr)) 100px;
}
.preview-grid {
display: grid;
gap: 8px;
grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr);
align-items: start;
}
.preview-box {
aspect-ratio: 16 / 10;
border-radius: 12px;
overflow: hidden;
border: 1px solid var(--line);
background: #eef2f7;
}
.preview-box img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.small {
color: var(--muted);
font-size: 11px;
}
.footer {
margin-top: 10px;
color: var(--muted);
font-size: 11px;
}
@media (max-width: 900px) {
.top, .main, .grid-2, .preview-grid, .row {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<main class="wrap">
<section class="top">
<div class="card pad">
<h1>随机背景 API</h1>
<p class="lead">Cloudflare Pages 随机背景接口。上传图片后先用 `build.py` 生成序号 `.webp`,然后可通过 `/api/random` 和 `/api/list` 调用。</p>
<div class="meta">
<span class="tag">竖屏mobile-image</span>
<span class="tag">横屏desketop-image</span>
<span class="tag">返回302 / JSON</span>
<span class="tag">组件widget.js</span>
</div>
</div>
<div class="card pad">
<h2>快速链接</h2>
<div class="links">
<a href="/api/list" target="_blank" rel="noreferrer">/api/list</a>
<a href="/manifest.json" target="_blank" rel="noreferrer">manifest.json</a>
<a href="/mobile-image/1.webp" target="_blank" rel="noreferrer">mobile-image/1.webp</a>
</div>
</div>
</section>
<section class="main">
<div class="card pad section" id="api">
<h2>API 调用</h2>
<div class="grid-2">
<div class="endpoint">
<strong>随机图片</strong>
<div class="small">默认 302 跳转到随机图片。</div>
<pre class="code">GET /api/random</pre>
</div>
<div class="endpoint">
<strong>返回 JSON</strong>
<div class="small">适合前端自己处理图片地址。</div>
<pre class="code">GET /api/random?format=json</pre>
</div>
</div>
<div class="grid-2">
<div class="endpoint">
<strong>强制竖屏</strong>
<pre class="code">GET /api/random?mode=mobile</pre>
</div>
<div class="endpoint">
<strong>强制横屏</strong>
<pre class="code">GET /api/random?mode=desktop</pre>
</div>
</div>
<p class="small">支持参数:`mode=auto|mobile|desktop`、`orientation=portrait|landscape`、`device=mobile|desktop`、`format=redirect|json`。</p>
</div>
<div class="card pad section" id="preview">
<h2>在线预览</h2>
<div class="row">
<div>
<label for="modeSelect">mode</label>
<select id="modeSelect">
<option value="auto">auto</option>
<option value="mobile">mobile</option>
<option value="desktop">desktop</option>
</select>
</div>
<div>
<label for="orientationSelect">orientation</label>
<select id="orientationSelect">
<option value="">auto</option>
<option value="portrait">portrait</option>
<option value="landscape">landscape</option>
</select>
</div>
<div>
<label for="formatSelect">format</label>
<select id="formatSelect">
<option value="json">json</option>
<option value="redirect">redirect</option>
</select>
</div>
<button id="refreshBtn" type="button">刷新</button>
</div>
<div class="preview-grid">
<div>
<label for="apiUrl">当前地址</label>
<input id="apiUrl" readonly value="">
<div class="preview-box" style="margin-top:8px">
<img id="previewImg" alt="随机背景预览" src="">
</div>
</div>
<div>
<label>返回结果</label>
<pre class="code" id="jsonOutput">{}</pre>
</div>
</div>
</div>
</section>
<section class="card pad section" style="margin-top:10px">
<h2>嵌入组件</h2>
<div class="grid-2">
<div class="endpoint">
<strong>直接引用</strong>
<pre class="code">&lt;script src="https://random-background-api.pages.dev/widget.js"&gt;&lt;/script&gt;
&lt;random-background-widget
src="https://random-background-api.pages.dev"
blur="12"
radius="16px"
height="260px"&gt;
&lt;/random-background-widget&gt;</pre>
</div>
<div class="endpoint">
<strong>说明</strong>
<div class="small">组件只读静态 `manifest.json` 和图片,不消耗 Functions 额度;`blur` 控制高斯模糊像素值。</div>
</div>
</div>
</section>
<div class="footer">
构建命令:<code>python3 build.py</code>。这里保留 Pages Functions所以可以直接提供 HTTP 随机 API。
</div>
</main>
<script>
const modeSelect = document.getElementById('modeSelect');
const orientationSelect = document.getElementById('orientationSelect');
const formatSelect = document.getElementById('formatSelect');
const refreshBtn = document.getElementById('refreshBtn');
const apiUrl = document.getElementById('apiUrl');
const previewImg = document.getElementById('previewImg');
const jsonOutput = document.getElementById('jsonOutput');
function detectOrientation() {
return window.matchMedia('(orientation: portrait)').matches ? 'portrait' : 'landscape';
}
function detectDevice() {
if (navigator.userAgentData && typeof navigator.userAgentData.mobile === 'boolean') {
return navigator.userAgentData.mobile ? 'mobile' : 'desktop';
}
return /Mobi|Android|iPhone|iPad|iPod/i.test(navigator.userAgent) ? 'mobile' : 'desktop';
}
function buildApiPath() {
const params = new URLSearchParams();
params.set('mode', modeSelect.value);
params.set('device', detectDevice());
params.set('format', formatSelect.value);
const orientation = orientationSelect.value || detectOrientation();
params.set('orientation', orientation);
return `/api/random?${params.toString()}`;
}
async function refresh() {
const url = buildApiPath();
apiUrl.value = url;
previewImg.style.opacity = '0.72';
if (formatSelect.value === 'json') {
const response = await fetch(url);
const data = await response.json();
jsonOutput.textContent = JSON.stringify(data, null, 2);
previewImg.src = data.url || '';
} else {
jsonOutput.textContent = '{ "format": "redirect" }';
previewImg.src = url;
}
}
previewImg.addEventListener('load', () => {
previewImg.style.opacity = '1';
});
refreshBtn.addEventListener('click', refresh);
modeSelect.addEventListener('change', refresh);
orientationSelect.addEventListener('change', refresh);
formatSelect.addEventListener('change', refresh);
refresh();
</script>
</body>
</html>