发布作者: 笒鬼鬼
百度收录: 正在检测是否收录...
作品采用: 《 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 》许可协议授权

<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>笒鬼鬼</title>
<style>
:root {
--scale: 1;
--user-photo: radial-gradient(
120% 120% at 20% 10%,
#ead7dd 0%,
#8f667a 45%,
#35212f 100%
);
--dot: rgba(199, 66, 78, 0.55);
}
html,
body {
height: 100%;
}
body {
margin: 0;
background: #000;
font-family: system-ui, -apple-system, "Segoe UI", Roboto, Arial,
"PingFang SC", "Microsoft YaHei", sans-serif;
}
.viewport {
width: 100vw;
height: 100vh;
position: relative;
overflow: hidden;
}
.canvas {
width: 1267px;
height: 932px;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%) scale(var(--scale));
transform-origin: center;
border-radius: 6px;
overflow: hidden;
isolation: isolate;
box-shadow: 0 28px 90px rgba(0, 0, 0, 0.6);
background:
radial-gradient(120% 120% at 0% 0%, #d7c8c4 0%, rgba(215, 200, 196, 0) 55%),
linear-gradient(90deg, #d1c6c2 0%, #cbb6bb 34%, #5a3047 58%, #2a0e1f 100%);
}
.noise {
position: absolute;
inset: 0;
background-image: repeating-linear-gradient(
0deg,
rgba(255, 255, 255, 0.03) 0 1px,
rgba(255, 255, 255, 0) 1px 4px
);
opacity: 0.22;
pointer-events: none;
mix-blend-mode: overlay;
z-index: 2;
}
.title {
position: absolute;
left: 114px;
top: 42px;
color: rgba(0, 0, 0, 0.78);
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.45);
user-select: none;
z-index: 6;
}
.title h1 {
margin: 0;
font-family: "Times New Roman", Times, serif;
font-weight: 700;
font-size: 40px;
letter-spacing: 0.08em;
}
.title p {
margin: 4px 0 0;
font-size: 14px;
line-height: 1.25;
opacity: 0.9;
}
.mood {
position: absolute;
left: 671px;
top: 58px;
transform: translateX(-50%);
color: rgba(255, 255, 255, 0.86);
font-weight: 650;
font-size: 22px;
letter-spacing: 0.02em;
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.45);
user-select: none;
z-index: 6;
}
.mood::after {
content: "";
display: inline-block;
width: 10px;
height: 10px;
margin-left: 10px;
transform: rotate(45deg);
background: radial-gradient(circle at 30% 30%, #ffd1e0 0 35%, #e06585 70% 100%);
border-radius: 2px;
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.18), 0 10px 30px rgba(0, 0, 0, 0.25);
}
.photo {
position: absolute;
overflow: hidden;
border-radius: 18px;
background: rgba(255, 255, 255, 0.04);
z-index: 1;
}
.photo::before {
content: "";
position: absolute;
inset: -24%;
background-image: var(--user-photo);
background-size: cover;
background-position: var(--pos, 50% 50%);
transform: translate(var(--tx, 0), var(--ty, 0)) rotate(var(--rot, 0deg))
scale(var(--zoom, 1));
filter: saturate(0.95) contrast(1.06) brightness(1.02);
}
.photo::after {
content: "";
position: absolute;
inset: 0;
background-image: radial-gradient(var(--dot) 1px, transparent 1.8px);
background-size: 18px 18px;
opacity: 0.38;
mix-blend-mode: multiply;
pointer-events: none;
}
.photo--main {
left: 532px;
top: -37px;
width: 811px;
height: 1025px;
border-radius: 0;
--rot: 0deg;
--zoom: 1.05;
--pos: 52% 46%;
z-index: 1;
}
.photo--main::after {
opacity: 0.34;
background-size: 19px 19px;
}
.cute {
position: absolute;
right: 120px;
top: 512px;
color: rgba(255, 255, 255, 0.9);
font-family: "Times New Roman", Times, serif;
font-weight: 700;
font-size: 30px;
letter-spacing: 0.01em;
text-shadow: 0 2px 16px rgba(0, 0, 0, 0.45);
user-select: none;
z-index: 7;
}
.widget {
position: absolute;
left: 61px;
top: 145px;
width: 641px;
height: 303px;
border-radius: 26px;
background: rgba(166, 166, 168, 0.92);
box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.12),
0 30px 60px rgba(0, 0, 0, 0.35);
overflow: hidden;
display: flex;
gap: 18px;
padding: 18px;
z-index: 8;
}
.widget__thumb {
width: 43%;
height: 100%;
border-radius: 18px;
position: relative;
overflow: hidden;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.22);
}
.widget__thumb.photo {
position: relative;
inset: auto;
border-radius: 18px;
--rot: 0deg;
--zoom: 1.12;
--pos: 52% 52%;
z-index: auto;
}
.player {
flex: 1;
height: 100%;
border-radius: 18px;
background: rgba(131, 131, 133, 0.92);
position: relative;
box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.08);
overflow: hidden;
color: rgba(255, 255, 255, 0.84);
}
.player__msg {
position: absolute;
left: 16px;
right: 16px;
bottom: 56px;
font-size: 11px;
text-align: center;
opacity: 0.86;
display: none;
}
.player.has-error .player__msg {
display: block;
}
.player__bunnies {
position: absolute;
left: 16px;
right: 16px;
top: 14px;
height: 54px;
display: grid;
place-items: center;
opacity: 0.95;
}
.player__bunnies svg {
width: 120px;
height: 54px;
filter: drop-shadow(0 8px 12px rgba(0, 0, 0, 0.22));
}
.player__timeline {
position: absolute;
left: 16px;
right: 16px;
top: 78px;
display: grid;
gap: 8px;
font-size: 11px;
opacity: 0.9;
}
.timeline__row {
display: flex;
justify-content: space-between;
align-items: center;
}
.bar {
position: relative;
height: 6px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.22);
overflow: hidden;
}
.bar > i {
position: absolute;
inset: 0;
width: 0%;
background: rgba(255, 255, 255, 0.68);
border-radius: inherit;
}
.bar--seek,
.bar--vol {
cursor: pointer;
}
.player__controls {
position: absolute;
left: 18px;
right: 18px;
top: 120px;
display: grid;
place-items: center;
}
.controls {
display: flex;
gap: 34px;
align-items: center;
}
.btn {
width: 42px;
height: 42px;
border: 0;
border-radius: 999px;
background: rgba(255, 255, 255, 0.09);
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.18);
display: grid;
place-items: center;
cursor: pointer;
}
.btn--play {
width: 54px;
height: 54px;
background: rgba(255, 255, 255, 0.14);
}
.btn svg {
width: 20px;
height: 20px;
fill: rgba(255, 255, 255, 0.82);
}
.btn--play svg {
width: 22px;
height: 22px;
}
.icon--play {
display: none;
margin-left: 2px;
}
.player.is-paused .icon--play {
display: inline;
}
.player.is-paused .icon--pause {
display: none;
}
.player__volume {
position: absolute;
left: 18px;
right: 18px;
bottom: 22px;
display: grid;
gap: 10px;
}
.homebar {
position: absolute;
left: 50%;
bottom: 12px;
transform: translateX(-50%);
width: 44%;
height: 5px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.28);
opacity: 0.7;
}
.quote {
position: absolute;
left: 142px;
top: 461px;
width: 507px;
color: rgba(0, 0, 0, 0.8);
font-family: "Times New Roman", Times, serif;
font-weight: 700;
font-size: 12px;
letter-spacing: 0.03em;
text-transform: uppercase;
text-align: center;
user-select: none;
z-index: 7;
}
.circleFrame {
position: absolute;
left: 111px;
top: 560px;
width: 355px;
height: 355px;
border-radius: 999px;
background: rgba(166, 166, 168, 0.8);
box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.12),
0 24px 48px rgba(0, 0, 0, 0.28);
display: grid;
place-items: center;
overflow: hidden;
z-index: 8;
}
.circleInner {
width: 82%;
height: 82%;
border-radius: 999px;
overflow: hidden;
position: relative;
box-shadow: 0 14px 26px rgba(0, 0, 0, 0.2);
animation: spin 10s linear infinite;
animation-play-state: paused;
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
html.is-playing .circleInner {
animation-play-state: running;
}
@media (prefers-reduced-motion: reduce) {
.circleInner {
animation: none;
}
}
.circleInner.photo {
position: relative;
inset: auto;
border-radius: 999px;
--rot: 0deg;
--zoom: 1.2;
--pos: 52% 40%;
z-index: auto;
}
.vertical {
position: absolute;
left: 555px;
top: 535px;
color: rgba(255, 255, 255, 0.92);
font-family: "STKaiti", "KaiTi", "Kaiti SC", "Songti SC", "STSong", "SimSun",
serif;
font-weight: 700;
font-size: 40px;
writing-mode: vertical-rl;
text-orientation: upright;
letter-spacing: 0.05em;
text-shadow: 0 2px 18px rgba(0, 0, 0, 0.5);
user-select: none;
z-index: 9;
}
.decor {
position: absolute;
pointer-events: none;
user-select: none;
filter: drop-shadow(0 14px 20px rgba(0, 0, 0, 0.25));
opacity: 0.95;
z-index: 10;
}
.bus {
right: 15px;
top: 15px;
width: 86px;
}
.bus2 {
left: 697px;
bottom: 56px;
width: 72px;
transform: rotate(-8deg);
}
.castle {
right: 43px;
bottom: 88px;
width: 52px;
opacity: 0.9;
}
.uploader {
position: absolute;
inset: 0;
display: grid;
place-items: center;
background: radial-gradient(
70% 70% at 50% 35%,
rgba(0, 0, 0, 0.35),
rgba(0, 0, 0, 0)
);
pointer-events: none;
z-index: 30;
}
.uploader[hidden] {
display: none;
}
.panel {
text-align: center;
color: rgba(255, 255, 255, 0.92);
background: rgba(0, 0, 0, 0.28);
border: 1px solid rgba(255, 255, 255, 0.16);
border-radius: 14px;
padding: 14px 14px 12px;
backdrop-filter: blur(10px);
pointer-events: auto;
width: 520px;
max-width: 82%;
}
.panel h2 {
margin: 0;
font-size: 16px;
letter-spacing: 0.02em;
}
.panel p {
margin: 6px 0 0;
font-size: 12px;
opacity: 0.88;
line-height: 1.55;
}
.panel code {
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono",
monospace;
background: rgba(255, 255, 255, 0.12);
padding: 2px 6px;
border-radius: 8px;
}
.pick {
margin-top: 10px;
display: inline-flex;
gap: 10px;
align-items: center;
justify-content: center;
}
.pick label {
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 10px;
padding: 10px 14px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.16);
border: 1px solid rgba(255, 255, 255, 0.22);
font-weight: 650;
}
.pick small {
opacity: 0.8;
}
#file {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
}
</style>
</head>
<body>
<main class="viewport" aria-label="参考图模板">
<div class="canvas" id="canvas" aria-label="画布">
<div class="noise" aria-hidden="true"></div>
<section class="title" aria-label="标题区域">
<h1>MAKE YOUR OWN</h1>
<p>I' ll always be you. Joyly present</p>
<p>I'm confident will change you're someone special</p>
</section>
<div class="mood" aria-hidden="true">in the mood</div>
<div class="photo photo--main" aria-label="主照片区域"></div>
<div class="cute" aria-hidden="true">#cute!!</div>
<section class="widget" aria-label="左侧卡片">
<div class="widget__thumb photo" aria-label="小照片"></div>
<div class="player is-paused" id="player" aria-label="播放器卡片">
<div class="player__bunnies" aria-hidden="true">
<svg viewBox="0 0 210 100" role="img" aria-label="bunnies">
<defs>
<linearGradient id="g" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#ffffff" stop-opacity="0.95" />
<stop offset="1" stop-color="#f3f3f3" stop-opacity="0.88" />
</linearGradient>
</defs>
<g fill="url(#g)" stroke="rgba(0,0,0,0.25)" stroke-width="2">
<g transform="translate(20,34)">
<ellipse cx="25" cy="38" rx="28" ry="22" />
<ellipse cx="13" cy="9" rx="9" ry="20" />
<ellipse cx="35" cy="9" rx="9" ry="20" />
<circle cx="16" cy="34" r="3" fill="#333" stroke="none" />
<circle cx="34" cy="34" r="3" fill="#333" stroke="none" />
<circle cx="25" cy="42" r="2.6" fill="#ff8aa8" stroke="none" />
</g>
<g transform="translate(78,30)">
<ellipse cx="25" cy="42" rx="30" ry="24" />
<ellipse cx="12" cy="10" rx="10" ry="22" />
<ellipse cx="38" cy="10" rx="10" ry="22" />
<circle cx="16" cy="38" r="3" fill="#333" stroke="none" />
<circle cx="34" cy="38" r="3" fill="#333" stroke="none" />
<circle cx="25" cy="47" r="2.6" fill="#ff8aa8" stroke="none" />
</g>
<g transform="translate(140,34)">
<ellipse cx="25" cy="38" rx="28" ry="22" />
<ellipse cx="13" cy="9" rx="9" ry="20" />
<ellipse cx="35" cy="9" rx="9" ry="20" />
<circle cx="16" cy="34" r="3" fill="#333" stroke="none" />
<circle cx="34" cy="34" r="3" fill="#333" stroke="none" />
<circle cx="25" cy="42" r="2.6" fill="#ff8aa8" stroke="none" />
</g>
</g>
<g fill="#ff7aa6" opacity="0.9">
<path
d="M107 20c-5-10 9-18 15-9 6-9 20-1 15 9-4 8-14 13-15 14-1-1-11-6-15-14z"
/>
</g>
</svg>
</div>
<div class="player__timeline" aria-hidden="true">
<div class="timeline__row">
<span id="timeNow">0:00</span>
<span id="timeTotal">0:00</span>
</div>
<div class="bar bar--seek" id="seekBar" aria-label="播放进度">
<i id="seekFill"></i>
</div>
</div>
<div class="player__controls" aria-hidden="true">
<div class="controls">
<button class="btn" type="button" id="prevBtn" aria-label="上一段(后退10秒)">
<svg viewBox="0 0 24 24">
<path d="M7 5h2v14H7V5Zm4.2 7 9-7v14l-9-7Z" />
</svg>
</button>
<button class="btn btn--play" type="button" id="playBtn" aria-label="播放/暂停">
<svg class="icon--pause" viewBox="0 0 24 24">
<path d="M7 6h4v12H7V6Zm6 0h4v12h-4V6Z" />
</svg>
<svg class="icon--play" viewBox="0 0 24 24">
<path d="M8 5v14l11-7L8 5Z" />
</svg>
</button>
<button class="btn" type="button" id="nextBtn" aria-label="下一段(前进10秒)">
<svg viewBox="0 0 24 24">
<path d="M15 5h2v14h-2V5ZM12.8 12l-9 7V5l9 7Z" />
</svg>
</button>
</div>
</div>
<div class="player__volume" aria-hidden="true">
<div class="bar bar--vol" id="volBar" style="height: 4px">
<i id="volFill" style="width: 64%"></i>
</div>
<div class="homebar" aria-hidden="true"></div>
</div>
<div class="player__msg" id="audioMsg" aria-hidden="true"></div>
</div>
</section>
<div class="quote" aria-hidden="true">
I LOVE ALL THE STARS IN THE SKY, BUT THEY ARE NOTHING COMPARED<br />
TO THE ONES IN YOUR EYES<br />
I ONLY WISH TO FACE THE SEA,<br />
WITH SPRING FLOWER BLOSSOMING.
</div>
<section class="circleFrame" aria-label="圆形照片">
<div class="circleInner photo" aria-hidden="true"></div>
</section>
<div class="vertical" aria-hidden="true">人间忽晚,山河已秋!</div>
<svg class="decor bus" viewBox="0 0 160 90" aria-hidden="true">
<g>
<rect
x="18"
y="18"
width="124"
height="48"
rx="10"
fill="#ffcc3d"
stroke="rgba(0,0,0,0.25)"
stroke-width="4"
/>
<rect x="30" y="28" width="78" height="20" rx="6" fill="#ffe9a8" />
<rect x="114" y="28" width="18" height="20" rx="6" fill="#ffe9a8" />
<circle cx="48" cy="70" r="10" fill="#2d2d2d" />
<circle cx="116" cy="70" r="10" fill="#2d2d2d" />
<circle cx="48" cy="70" r="5" fill="#9aa0a6" />
<circle cx="116" cy="70" r="5" fill="#9aa0a6" />
</g>
</svg>
<svg class="decor bus2" viewBox="0 0 160 90" aria-hidden="true">
<g>
<rect
x="18"
y="18"
width="124"
height="48"
rx="10"
fill="#ffcc3d"
stroke="rgba(0,0,0,0.25)"
stroke-width="4"
/>
<rect x="30" y="28" width="78" height="20" rx="6" fill="#ffe9a8" />
<rect x="114" y="28" width="18" height="20" rx="6" fill="#ffe9a8" />
<circle cx="48" cy="70" r="10" fill="#2d2d2d" />
<circle cx="116" cy="70" r="10" fill="#2d2d2d" />
<circle cx="48" cy="70" r="5" fill="#9aa0a6" />
<circle cx="116" cy="70" r="5" fill="#9aa0a6" />
</g>
</svg>
<svg class="decor castle" viewBox="0 0 120 120" aria-hidden="true">
<g fill="#b9b2a6" stroke="rgba(0,0,0,0.22)" stroke-width="4">
<path d="M22 96V54l12-8v10l14-10 14 10V46l14 10 14-10v10l12 8v42H22Z" />
<path d="M46 96V70h28v26H46Z" fill="#a59d92" />
<path d="M55 80h10v16H55V80Z" fill="#d7d0c5" stroke="none" />
</g>
</svg>
<section class="uploader" id="uploader" aria-label="上传照片">
<div class="panel">
<h2>选择一张照片,自动裁剪进模板</h2>
<p>
也可以把图片放到同目录命名为 <code>photo.jpg</code>(或
<code>photo.png</code>/<code>photo.webp</code>),打开页面即可。
</p>
<div class="pick">
<label for="file">选择照片</label>
<small>支持拖拽</small>
</div>
</div>
<input id="file" type="file" accept="image/*" />
</section>
<audio id="audio" preload="metadata" hidden></audio>
</div>
</main>
<script>
(() => {
const BASE_W = 1267;
const BASE_H = 932;
const root = document.documentElement;
const uploader = document.getElementById("uploader");
const input = document.getElementById("file");
const updateScale = () => {
const scale = Math.min(window.innerWidth / BASE_W, window.innerHeight / BASE_H);
root.style.setProperty("--scale", String(Math.max(0.1, scale)));
};
window.addEventListener("resize", updateScale, { passive: true });
updateScale();
let lastObjectUrl = null;
const setPhoto = (url) => {
root.style.setProperty("--user-photo", `url("${url}")`);
uploader.hidden = true;
};
const setFile = (file) => {
if (!file) return;
if (lastObjectUrl) URL.revokeObjectURL(lastObjectUrl);
lastObjectUrl = URL.createObjectURL(file);
setPhoto(lastObjectUrl);
};
input.addEventListener("change", () => setFile(input.files && input.files[0]));
const tryLocal = (names, i = 0) => {
if (i >= names.length) return;
const img = new Image();
img.onload = () => setPhoto(names[i]);
img.onerror = () => tryLocal(names, i + 1);
img.src = names[i];
};
tryLocal(["photo.jpg", "photo.jpeg", "photo.png", "photo.webp"]);
const stop = (e) => {
e.preventDefault();
e.stopPropagation();
};
const onDrop = (e) => {
stop(e);
const file =
e.dataTransfer &&
e.dataTransfer.files &&
e.dataTransfer.files[0] &&
e.dataTransfer.files[0].type.startsWith("image/")
? e.dataTransfer.files[0]
: null;
setFile(file);
};
window.addEventListener("dragenter", stop);
window.addEventListener("dragover", stop);
window.addEventListener("drop", onDrop);
// --- Music player (知我) ---
const audio = document.getElementById("audio");
const player = document.getElementById("player");
const playBtn = document.getElementById("playBtn");
const prevBtn = document.getElementById("prevBtn");
const nextBtn = document.getElementById("nextBtn");
const seekBar = document.getElementById("seekBar");
const seekFill = document.getElementById("seekFill");
const timeNow = document.getElementById("timeNow");
const timeTotal = document.getElementById("timeTotal");
const volBar = document.getElementById("volBar");
const volFill = document.getElementById("volFill");
const audioMsg = document.getElementById("audioMsg");
const clamp01 = (n) => Math.max(0, Math.min(1, n));
const fmtTime = (sec) => {
if (!Number.isFinite(sec) || sec < 0) return "0:00";
const m = Math.floor(sec / 60);
const s = Math.floor(sec % 60);
return `${m}:${String(s).padStart(2, "0")}`;
};
const syncPlayer = () => {
if (!audio) return;
if (timeNow) timeNow.textContent = fmtTime(audio.currentTime);
if (timeTotal) timeTotal.textContent = fmtTime(audio.duration);
const p = audio.duration ? clamp01(audio.currentTime / audio.duration) : 0;
if (seekFill) seekFill.style.width = `${p * 100}%`;
if (volFill) volFill.style.width = `${clamp01(audio.volume) * 100}%`;
};
const syncPaused = () => {
if (!audio) return;
const paused = audio.paused;
document.documentElement.classList.toggle("is-playing", !paused);
if (player) {
if (paused) player.classList.add("is-paused");
else player.classList.remove("is-paused");
}
};
if (audio) {
audio.volume = 0.64;
audio.preload = "metadata";
audio.addEventListener("loadedmetadata", () => {
if (player) player.classList.remove("has-error");
if (audioMsg) audioMsg.textContent = "";
syncPlayer();
});
audio.addEventListener("durationchange", syncPlayer);
audio.addEventListener("timeupdate", syncPlayer);
audio.addEventListener("volumechange", syncPlayer);
audio.addEventListener("play", syncPaused);
audio.addEventListener("pause", syncPaused);
audio.addEventListener("ended", syncPaused);
audio.addEventListener("error", () => {
if (player) player.classList.add("has-error");
if (audioMsg) {
audioMsg.textContent =
"音频加载失败(可能跨域/链接失效)。可把MP3放同目录命名为 song.mp3 再刷新。";
}
syncPaused();
});
}
if (playBtn && audio) {
playBtn.addEventListener("click", async () => {
try {
if (audio.paused) await audio.play();
else audio.pause();
} catch (_) {}
});
}
if (prevBtn && audio) {
prevBtn.addEventListener("click", () => {
audio.currentTime = Math.max(0, audio.currentTime - 10);
});
}
if (nextBtn && audio) {
nextBtn.addEventListener("click", () => {
const limit = Number.isFinite(audio.duration) ? audio.duration : Infinity;
audio.currentTime = Math.min(limit, audio.currentTime + 10);
});
}
if (seekBar && audio) {
seekBar.addEventListener("click", (e) => {
if (!Number.isFinite(audio.duration) || audio.duration <= 0) return;
const rect = seekBar.getBoundingClientRect();
const ratio = clamp01((e.clientX - rect.left) / rect.width);
audio.currentTime = ratio * audio.duration;
});
}
if (volBar && audio) {
volBar.addEventListener("click", (e) => {
const rect = volBar.getBoundingClientRect();
const ratio = clamp01((e.clientX - rect.left) / rect.width);
audio.volume = ratio;
});
}
const SONG_API =
"https://musicapi.cenguigui.cn/?input=1394167216&type=netease&filter=id&format=json";
const setAudioSrc = (src, title = "知我 - 国风堂") => {
if (!audio) return;
audio.src = src;
audio.load();
if (player) player.title = title;
syncPaused();
syncPlayer();
};
const probeAudio = (src) =>
new Promise((resolve, reject) => {
const test = new Audio();
test.preload = "metadata";
test.addEventListener("loadedmetadata", () => resolve(src), { once: true });
test.addEventListener("error", () => reject(new Error("load fail")), {
once: true,
});
test.src = src;
test.load();
});
const loadSong = async () => {
if (!audio) return;
try {
const res = await fetch(SONG_API, { cache: "no-store" });
const text = await res.text();
const data = JSON.parse(text);
if (data && typeof data.url === "string" && data.url) {
setAudioSrc(data.url, `${data.title || "知我"} - ${data.author || "国风堂"}`);
return;
}
} catch (_) {}
try {
await probeAudio("song.mp3");
setAudioSrc("song.mp3", data.author);
return;
} catch (_) {}
setAudioSrc(data.author);
};
loadSong();
})();
</script>
</body>
</html>
—— 评论区 ——