web: pure/flat redesign — dotted rotating earth globe, PMM intro left/globe right, live server status (SZ 120.24.78.176 / HK 38.76.190.153) via status.php

这个提交包含在:
JGZYES
2026-09-01 21:16:26 +08:00
父节点 8453b9294e
当前提交 d31827fc19
修改 4 个文件,包含 310 行新增173 行删除
+119 -51
查看文件
@@ -1,4 +1,4 @@
/* PMM site — world map island glow + install tabs + copy buttons */ /* PMM site — rotating dotted Earth globe + live status + tabs + copy */
(function () { (function () {
"use strict"; "use strict";
@@ -32,8 +32,7 @@
var ta = document.createElement("textarea"); var ta = document.createElement("textarea");
ta.value = t; ta.style.position = "fixed"; ta.style.opacity = "0"; ta.value = t; ta.style.position = "fixed"; ta.style.opacity = "0";
document.body.appendChild(ta); ta.select(); document.body.appendChild(ta); ta.select();
try { document.execCommand("copy"); flash("已复制"); } try { document.execCommand("copy"); flash("已复制"); } catch (e) { flash("复制失败"); }
catch (e) { flash("复制失败"); }
document.body.removeChild(ta); document.body.removeChild(ta);
} }
function flash(msg) { function flash(msg) {
@@ -43,60 +42,129 @@
}); });
}); });
/* ---------- World map: flood-fill island glow ---------- */ /* ---------- Rotating dotted Earth globe ---------- */
var mapEl = document.getElementById("worldmap"); var cv = document.getElementById("globe");
if (!mapEl) return; if (cv && window.PMM_MAP && window.PMM_DIMS) {
var rows = mapEl.querySelectorAll(".row"); var ctx = cv.getContext("2d");
var grid = []; var R = window.PMM_DIMS.rows, C = window.PMM_DIMS.cols;
var R = grid.length, C = 0; var DG = Math.PI / 180;
rows.forEach(function (rowEl) { // map cell -> lat/lon
var arr = Array.prototype.slice.call(rowEl.children); var pts = window.PMM_MAP.map(function (p) {
grid.push(arr); return {
C = Math.max(C, arr.length); lat: (76 - p[0] * (152 / R)) * DG,
lon: (-180 + p[1] * (360 / C)) * DG
};
}); });
R = grid.length; // server locations (Shenzhen ~22.5N/114E, HK ~22.3N/114.2E)
var servers = [
{ lat: 22.54 * DG, lon: 114.06 * DG },
{ lat: 22.32 * DG, lon: 114.17 * DG }
];
function isLand(r, c) { var W, H, cx, cy, radius;
if (r < 0 || r >= R || c < 0 || c >= C) return false; function resize() {
var el = grid[r][c]; var dpr = Math.min(window.devicePixelRatio || 1, 2);
return el && el.classList.contains("land"); var s = cv.clientWidth || 320;
W = cv.width = s * dpr; H = cv.height = s * dpr;
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
cx = s / 2; cy = s / 2;
radius = s / 2 - 6;
}
resize();
window.addEventListener("resize", resize);
var theta = 0, rafId = null;
var ACC = "#3ea8ff", OKC = "#2dd4a7";
function draw(t) {
theta += 0.0035;
var cos = Math.cos, sin = Math.sin;
ctx.clearRect(0, 0, W, H);
// sphere disc
ctx.beginPath();
ctx.arc(cx, cy, radius, 0, 2 * Math.PI);
ctx.fillStyle = "rgba(62,168,255,0.045)";
ctx.fill();
ctx.strokeStyle = "rgba(62,168,255,0.20)";
ctx.lineWidth = 1;
ctx.stroke();
// faint equator + meridian rings for a globe feel
ctx.strokeStyle = "rgba(62,168,255,0.10)";
ctx.beginPath(); ctx.ellipse(cx, cy, radius, radius * 0.98, 0, 0, 2 * Math.PI); ctx.stroke();
function proj(lat, lon) {
var r = lon + theta;
var x = cos(lat) * sin(r);
var y = sin(lat);
var z = cos(lat) * cos(r);
return { x: x, y: y, z: z };
} }
function clearHot() { // land dot cloud — back pass (z<0) dim, front pass (z>0) bright
mapEl.querySelectorAll(".px.hot,.px.hot2").forEach(function (el) { for (var pass = 0; pass < 2; pass++) {
el.classList.remove("hot", "hot2"); var front = pass === 1;
el.style.transitionDelay = ""; for (var i = 0; i < pts.length; i++) {
var q = proj(pts[i].lat, pts[i].lon);
if ((q.z > 0) !== front) continue;
ctx.fillStyle = front ? "rgba(62,168,255,0.85)" : "rgba(62,168,255,0.18)";
ctx.fillRect(cx + q.x * radius, cy - q.y * radius, 1.6, 1.6);
}
}
// server location markers (pulse) — only if on the visible hemisphere
servers.forEach(function (s) {
var q = proj(s.lat, s.lon);
if (q.z <= 0) return;
var sx = cx + q.x * radius, sy = cy - q.y * radius;
var pulse = 0.6 + 0.4 * sin(t * 0.006);
ctx.beginPath();
ctx.arc(sx, sy, 3, 0, 2 * Math.PI);
ctx.fillStyle = OKC;
ctx.fill();
ctx.beginPath();
ctx.arc(sx, sy, 4 + (1 - pulse) * 4, 0, 2 * Math.PI);
ctx.strokeStyle = "rgba(45,212,167," + (0.5 * pulse + 0.15) + ")";
ctx.lineWidth = 1.5;
ctx.stroke();
}); });
rafId = requestAnimationFrame(draw);
} }
function light(r, c) { // only animate while visible
clearHot(); if ("IntersectionObserver" in window) {
if (!isLand(r, c)) return; var io = new IntersectionObserver(function (en) {
// BFS over the island (8-connectivity), staggered outward for a "flood" look en.forEach(function (e) {
var seen = {}, queue = [[r, c, 0]]; if (e.isIntersecting) { if (!rafId) rafId = requestAnimationFrame(draw); }
seen[r + "," + c] = 1; else if (rafId) { cancelAnimationFrame(rafId); rafId = null; }
var dirs = [[-1,-1],[-1,0],[-1,1],[0,-1],[0,1],[1,-1],[1,0],[1,1]];
while (queue.length) {
var cell = queue.shift();
var cr = cell[0], cc = cell[1], d = cell[2];
var el = grid[cr][cc];
var hot = d % 3 === 1 ? "hot2" : "hot";
el.style.transitionDelay = Math.min(d * 12, 320) + "ms";
el.classList.remove("hot", "hot2");
el.classList.add(hot);
for (var i = 0; i < dirs.length; i++) {
var nr = cr + dirs[i][0], nc = cc + dirs[i][1];
var k = nr + "," + nc;
if (isLand(nr, nc) && !seen[k]) { seen[k] = 1; queue.push([nr, nc, d + 1]); }
}
}
}
mapEl.addEventListener("mouseover", function (e) {
var el = e.target;
if (el && el.classList && el.classList.contains("land")) {
light(parseInt(el.getAttribute("data-r"), 10), parseInt(el.getAttribute("data-c"), 10));
}
}); });
mapEl.addEventListener("mouseleave", clearHot); }, { threshold: 0.1 });
io.observe(cv);
} else {
rafId = requestAnimationFrame(draw);
}
}
/* ---------- Live mirror status ---------- */
function loadStatus() {
fetch("status.php", { cache: "no-store" })
.then(function (r) { return r.json(); })
.then(function (data) {
if (!Array.isArray(data)) return;
data.forEach(function (s, i) {
var dot = document.getElementById("dot-" + i);
var meta = document.getElementById("meta-" + i);
if (!dot || !meta) return;
dot.className = "status-dot " + (s.online ? "up" : "down");
meta.innerHTML = s.online
? "在线 · <b>" + s.ms + "</b> ms · HTTP " + s.code
: "离线 / 不可达";
});
})
.catch(function () {});
}
loadStatus();
setInterval(loadStatus, 12000);
})(); })();
+72 -81
查看文件
@@ -1,17 +1,19 @@
/* ParlzPackageManager — dark, modern-minimal */ /* ParlzPackageManager — dark, pure flat solid colors (no gradients) */
:root{ :root{
--bg:#0b0e14; /* near-black */ --bg:#0b0e14;
--bg2:#10141f; --bg2:#0e131c;
--panel:#121826; --panel:#121821;
--panel2:#16202f; --panel2:#161d29;
--line:#1e2839; --line:#1d2734;
--text:#e6edf3; --text:#e8eef6;
--muted:#8a94a6; --muted:#93a1b3;
--dim:#5b6574; --dim:#5a6675;
--accent:#56d4ff; /* cyan */ --accent:#3ea8ff; /* solid blue */
--accent2:#8b5cf6; /* violet */ --accent-dim:#2b6fb0;
--ok:#34d399; --ok:#2dd4a7;
--warn:#f5b13d;
--bad:#f65e5e;
--radius:14px; --radius:14px;
--maxw:1120px; --maxw:1120px;
} }
@@ -19,106 +21,93 @@
*{box-sizing:border-box;margin:0;padding:0} *{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth} html{scroll-behavior:smooth}
body{ body{
background:var(--bg); background:var(--bg);color:var(--text);
color:var(--text);
font-family:Inter,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"PingFang SC","Microsoft YaHei",sans-serif; font-family:Inter,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"PingFang SC","Microsoft YaHei",sans-serif;
-webkit-font-smoothing:antialiased; -webkit-font-smoothing:antialiased;line-height:1.6;overflow-x:hidden;
line-height:1.6;
overflow-x:hidden;
} }
a{color:inherit;text-decoration:none} a{color:inherit;text-decoration:none}
code{font-family:"JetBrains Mono",ui-monospace,SFMono-Regular,Menlo,Consolas,"Courier New",monospace;font-size:.92em;color:var(--accent)} code{font-family:"JetBrains Mono",ui-monospace,SFMono-Regular,Menlo,Consolas,"Courier New",monospace;font-size:.92em;color:var(--accent)}
::selection{background:var(--accent);color:#04121a} ::selection{background:var(--accent);color:#04121a}
/* glow backdrop */
body::before{
content:"";position:fixed;inset:0;z-index:-1;pointer-events:none;
background:
radial-gradient(700px 420px at 78% -6%, rgba(139,92,246,.16), transparent 60%),
radial-gradient(720px 460px at 12% 4%, rgba(86,212,255,.13), transparent 60%);
}
/* nav */ /* nav */
.nav{position:sticky;top:0;z-index:50;backdrop-filter:blur(12px);background:rgba(11,14,20,.72); .nav{position:sticky;top:0;z-index:50;background:rgba(11,14,20,.9);border-bottom:1px solid var(--line);
border-bottom:1px solid var(--line)} display:flex;align-items:center;justify-content:space-between;max-width:var(--maxw);margin:0 auto;padding:14px 22px}
.nav{display:flex;align-items:center;justify-content:space-between;max-width:var(--maxw);margin:0 auto;
padding:14px 22px}
.brand{display:flex;align-items:center;gap:10px} .brand{display:flex;align-items:center;gap:10px}
.brand-mark{font-family:ui-monospace,monospace;font-weight:800;font-size:20px;color:#04121a; .brand-mark{font-family:ui-monospace,monospace;font-weight:800;font-size:20px;color:#04121a;background:var(--accent);
background:linear-gradient(135deg,var(--accent),var(--accent2));padding:2px 9px;border-radius:8px} padding:2px 9px;border-radius:8px}
.brand-name{font-weight:700;letter-spacing:.2px} .brand-name{font-weight:700}
.nav-links{display:flex;align-items:center;gap:22px;list-style:none} .nav-links{display:flex;align-items:center;gap:22px;list-style:none}
.nav-links a{color:var(--muted);font-size:14.5px;transition:color .2s} .nav-links a{color:var(--muted);font-size:14.5px;transition:color .2s}
.nav-links a:hover{color:var(--text)} .nav-links a:hover{color:var(--text)}
/* buttons */ /* buttons — solid, flat */
.btn{display:inline-block;padding:11px 20px;border-radius:11px;font-weight:600;font-size:14.5px; .btn{display:inline-block;padding:11px 20px;border-radius:11px;font-weight:600;font-size:14.5px;transition:transform .15s,box-shadow .2s;border:1px solid transparent;cursor:pointer}
transition:transform .15s,box-shadow .2s,background .2s;border:1px solid transparent} .btn-primary{background:var(--accent);color:#04121a}
.btn-primary{background:linear-gradient(135deg,var(--accent),var(--accent2));color:#04121a} .btn-primary:hover{transform:translateY(-2px);box-shadow:0 8px 22px -8px rgba(62,168,255,.6)}
.btn-primary:hover{transform:translateY(-2px);box-shadow:0 10px 30px -10px rgba(86,212,255,.5)}
.btn-ghost{background:transparent;border-color:var(--line);color:var(--text)} .btn-ghost{background:transparent;border-color:var(--line);color:var(--text)}
.btn-ghost:hover{border-color:var(--accent);color:var(--accent)} .btn-ghost:hover{border-color:var(--accent);color:var(--accent)}
.btn-sm{padding:8px 14px;font-size:13px} .btn-sm{padding:8px 14px;font-size:13px}
/* hero */ /* hero — two columns */
.hero{max-width:var(--maxw);margin:0 auto;padding:74px 22px 30px;text-align:center} .hero{max-width:var(--maxw);margin:0 auto;padding:70px 22px 40px}
.hero-badge{display:inline-block;font-size:12.5px;letter-spacing:.5px;color:var(--muted); .hero-inner{display:grid;grid-template-columns:1.05fr 0.95fr;gap:40px;align-items:center}
border:1px solid var(--line);border-radius:999px;padding:5px 14px;margin-bottom:20px} .hero-badge{display:inline-block;font-size:12.5px;letter-spacing:.4px;color:var(--muted);border:1px solid var(--line);border-radius:999px;padding:5px 14px;margin-bottom:20px}
.hero h1{font-size:clamp(34px,6vw,58px);font-weight:800;letter-spacing:-1px;line-height:1.08} .hero h1{font-size:clamp(32px,5vw,52px);font-weight:800;letter-spacing:-1px;line-height:1.08}
.accent{background:linear-gradient(135deg,var(--accent),var(--accent2));-webkit-background-clip:text; .accent{color:var(--accent)}
background-clip:text;color:transparent} .hero-tag{max-width:560px;margin:18px 0 0;color:var(--muted);font-size:17px}
.hero-tag{max-width:720px;margin:20px auto 0;color:var(--muted);font-size:17.5px} .hero-cta{display:flex;gap:14px;margin-top:28px;flex-wrap:wrap}
.hero-cta{display:flex;gap:14px;justify-content:center;margin-top:30px;flex-wrap:wrap} .hero-note{margin-top:24px;font-size:14px;color:var(--muted);display:flex;align-items:center;gap:8px}
.dot{width:9px;height:9px;border-radius:50%;display:inline-block;background:var(--dim)}
.dot.ok{background:var(--ok);box-shadow:0 0 0 3px rgba(45,212,167,.18)}
/* world map */ /* globe */
.map-section{padding:28px 22px 10px} .hero-globe{position:relative;display:flex;align-items:center;justify-content:center;
.map-wrap{max-width:var(--maxw);margin:0 auto;text-align:center} aspect-ratio:1;max-width:430px;margin:0 auto;width:100%}
.map-title{font-size:13px;letter-spacing:4px;text-transform:uppercase;color:var(--dim);margin-bottom:22px} #globe{width:100%;height:100%;display:block}
.worldmap{display:inline-block;font-family:"JetBrains Mono",ui-monospace,monospace; .globe-caption{position:absolute;bottom:-6px;left:0;right:0;text-align:center;font-size:13px;color:var(--dim);letter-spacing:.5px}
font-size:clamp(6px,1.05vw,11px);line-height:1;color:var(--dim);user-select:none}
.worldmap .row{display:flex}
.px{display:inline-flex;align-items:center;justify-content:center;width:1ch;height:1.15em}
.px.water{color:transparent}
.px.land{color:var(--dim);transition:color .1s,text-shadow .12s}
.worldmap:hover .px.land{color:#2b3749} /* dim until you engage */
.px.land.hot{color:var(--accent);text-shadow:0 0 9px rgba(86,212,255,.85),0 0 2px #fff}
.px.land.hot2{color:var(--accent2);text-shadow:0 0 9px rgba(139,92,246,.85)}
.map-hint{margin-top:16px;font-size:13px;color:var(--dim)}
/* sections */ /* status */
.section{padding:64px 22px} .section{padding:64px 22px}
.section.alt{background:linear-gradient(180deg,transparent,rgba(22,32,47,.55),transparent)} .section.alt{background:var(--bg2);border-top:1px solid var(--line);border-bottom:1px solid var(--line)}
.wrap{max-width:var(--maxw);margin:0 auto} .wrap{max-width:var(--maxw);margin:0 auto}
.section-title{text-align:center;font-size:clamp(26px,3.5vw,36px);font-weight:800;letter-spacing:-.5px} .section-title{text-align:center;font-size:clamp(26px,3.5vw,36px);font-weight:800;letter-spacing:-.5px}
.section-sub{text-align:center;color:var(--muted);max-width:640px;margin:12px auto 34px} .section-sub{text-align:center;color:var(--muted);max-width:640px;margin:12px auto 34px}
.status-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:18px;max-width:760px;margin:0 auto}
.status-card{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);padding:24px}
.status-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}
.pin{font-weight:800;color:var(--text);font-size:18px}
.status-dot{width:14px;height:14px;border-radius:50%;background:var(--dim)}
.status-dot.up{background:var(--ok);box-shadow:0 0 0 4px rgba(45,212,167,.16);animation:pulse 1.6s infinite}
.status-dot.down{background:var(--bad);box-shadow:0 0 0 4px rgba(246,94,94,.16)}
@keyframes pulse{0%{box-shadow:0 0 0 0 rgba(45,212,167,.4)}70%{box-shadow:0 0 0 8px rgba(45,212,167,0)}100%{box-shadow:0 0 0 0 rgba(45,212,167,0)}}
.status-ip{font-family:ui-monospace,monospace;font-size:20px;font-weight:700;color:var(--text)}
.status-host{font-family:ui-monospace,monospace;font-size:13px;color:var(--muted);margin-top:3px}
.status-meta{margin-top:14px;font-size:14px;color:var(--muted)}
.status-meta b{color:var(--text)}
/* grid cards */
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:18px} .grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:18px}
.card{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);padding:24px; .card{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);padding:24px;transition:transform .2s,border-color .2s}
transition:transform .2s,border-color .2s}
.card:hover{transform:translateY(-3px);border-color:var(--accent)} .card:hover{transform:translateY(-3px);border-color:var(--accent)}
.card h3{font-size:17px;margin-bottom:8px} .card h3{font-size:17px;margin-bottom:8px}
.card p{color:var(--muted);font-size:14.5px} .card p{color:var(--muted);font-size:14.5px}
/* install tabs */ /* tabs */
.tabs{display:inline-flex;gap:6px;background:var(--panel);border:1px solid var(--line); .tabs{display:flex;gap:6px;background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:5px;margin:0 auto 18px;width:max-content}
border-radius:12px;padding:5px;margin:0 auto 18px;display:flex} .tab{background:transparent;border:0;color:var(--muted);padding:8px 18px;border-radius:8px;cursor:pointer;font-size:14px;font-weight:600;transition:background .2s,color .2s}
.tab{background:transparent;border:0;color:var(--muted);padding:8px 18px;border-radius:8px;cursor:pointer; .tab.active{background:var(--accent);color:#04121a}
font-size:14px;font-weight:600;transition:background .2s,color .2s} .code-box{position:relative;max-width:820px;margin:0 auto;background:#0a0e15;border:1px solid var(--line);border-radius:var(--radius);padding:30px 24px 24px;text-align:left}
.tab.active{background:linear-gradient(135deg,var(--accent),var(--accent2));color:#04121a}
.code-box{position:relative;max-width:820px;margin:0 auto;background:#0a0d12;border:1px solid var(--line);
border-radius:var(--radius);padding:30px 24px 24px;text-align:left}
.code-box[data-pane]{display:none} .code-box[data-pane]{display:none}
.code-box[data-pane][data-active]{display:block} .code-box[data-pane][data-active]{display:block}
.code-box pre{overflow-x:auto} .code-box pre{overflow-x:auto}
.code-box code{color:#d7e3f4;font-size:14px;white-space:pre} .code-box code{color:#d7e3f4;font-size:14px;white-space:pre}
.copy-btn{position:absolute;top:12px;right:12px;background:var(--panel2);border:1px solid var(--line); .copy-btn{position:absolute;top:12px;right:12px;background:var(--panel2);border:1px solid var(--line);color:var(--muted);border-radius:8px;padding:6px 12px;font-size:12.5px;cursor:pointer;transition:all .15s}
color:var(--muted);border-radius:8px;padding:6px 12px;font-size:12.5px;cursor:pointer;transition:all .15s}
.copy-btn:hover{color:var(--accent);border-color:var(--accent)} .copy-btn:hover{color:var(--accent);border-color:var(--accent)}
.copy-btn.ok{color:var(--ok);border-color:var(--ok)} .copy-btn.ok{color:var(--ok);border-color:var(--ok)}
/* download */ /* download */
.dl-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:16px;max-width:920px;margin:0 auto} .dl-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:16px;max-width:920px;margin:0 auto}
.dl-card{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius); .dl-card{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);padding:22px;display:flex;flex-direction:column;gap:6px;transition:transform .2s,border-color .2s}
padding:22px;display:flex;flex-direction:column;gap:6px;transition:transform .2s,border-color .2s}
.dl-card:hover{transform:translateY(-3px);border-color:var(--accent)} .dl-card:hover{transform:translateY(-3px);border-color:var(--accent)}
.dl-os{font-size:13px;color:var(--accent);font-weight:700} .dl-os{font-size:13px;color:var(--accent);font-weight:700}
.dl-file{font-family:ui-monospace,monospace;color:var(--text);font-size:14px;word-break:break-all} .dl-file{font-family:ui-monospace,monospace;color:var(--text);font-size:14px;word-break:break-all}
@@ -126,13 +115,11 @@ body::before{
/* mirror */ /* mirror */
.mirror-list{max-width:820px;margin:0 auto 24px;display:grid;gap:12px} .mirror-list{max-width:820px;margin:0 auto 24px;display:grid;gap:12px}
.mirror-item{display:flex;align-items:center;justify-content:space-between;gap:14px;background:var(--panel); .mirror-item{display:flex;align-items:center;justify-content:space-between;gap:14px;background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:16px 18px;flex-wrap:wrap}
border:1px solid var(--line);border-radius:12px;padding:16px 18px;flex-wrap:wrap}
.mirror-tag{font-size:12.5px;color:var(--muted);border:1px solid var(--line);border-radius:999px;padding:4px 12px;white-space:nowrap} .mirror-tag{font-size:12.5px;color:var(--muted);border:1px solid var(--line);border-radius:999px;padding:4px 12px;white-space:nowrap}
.mirror-tag.default{color:var(--ok);border-color:var(--ok)} .mirror-tag.default{color:var(--ok);border-color:var(--ok)}
.mirror-item code{color:var(--text)} .mirror-item code{color:var(--text)}
.tips{text-align:center;color:var(--muted);font-size:14px;margin-top:16px} .tips{text-align:center;color:var(--muted);font-size:14px;margin-top:16px}
.section .btn-ghost{display:inline-block;margin:0 auto}
/* footer */ /* footer */
.footer{border-top:1px solid var(--line);padding:34px 22px} .footer{border-top:1px solid var(--line);padding:34px 22px}
@@ -142,7 +129,11 @@ body::before{
.footer-links a{color:var(--muted)}.footer-links a:hover{color:var(--accent)} .footer-links a{color:var(--muted)}.footer-links a:hover{color:var(--accent)}
.footer-note{text-align:center;color:var(--dim);font-size:12.5px} .footer-note{text-align:center;color:var(--dim);font-size:12.5px}
@media (max-width:760px){ @media (max-width:840px){
.hero-inner{grid-template-columns:1fr;text-align:center}
.hero-tag{margin-left:auto;margin-right:auto}
.hero-cta{justify-content:center}
.hero-note{justify-content:center}
.hero-globe{max-width:280px}
.nav-links{display:none} .nav-links{display:none}
.map-title{letter-spacing:2px}
} }
+46 -33
查看文件
@@ -1,14 +1,13 @@
<?php <?php
/* /*
* ParlzPackageManager (PMM) — official site * ParlzPackageManager (PMM) — official site (dark, pure flat colors).
* Dark, modern-minimal. Homepage shows a world map drawn with '+'/'-' * Hero: left = PMM intro, right = rotating dotted Earth globe (canvas).
* characters that lights up (island fill) on mouse hover. * A status panel monitors the SZ + HK mirror servers via status.php.
* Static PHP, no dependencies. Served from /web/ as the web root. * Static PHP, no dependencies.
*/ */
$VERSION = '0.2.6'; $VERSION = '0.2.6';
/* World map, drawn coarse with '#'=land, ' '=ocean. /* Coarse world map, '#'=land, ' '=ocean. Reused as the dot cloud on the globe. */
* Rendering turns '#' into '+'/'-' so the map is literally built from + and -. */
$map = [ $map = [
" ", " ",
" #################### ", " #################### ",
@@ -45,9 +44,14 @@ $map = [
" # # # # # ", " # # # # # ",
" ", " ",
]; ];
$ROWS = count($map); $ROWS = count($map);
$COLS = strlen($map[0]); $COLS = strlen($map[0]);
/* Land cell coordinates for the globe dot cloud */
$dots = array();
for ($r = 0; $r < $ROWS; $r++)
for ($c = 0; $c < $COLS; $c++)
if ($map[$r][$c] === '#') $dots[] = array($r, $c);
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="zh-CN"> <html lang="zh-CN">
@@ -55,8 +59,8 @@ $COLS = strlen($map[0]);
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ParlzPackageManager · 跨平台 C 语言包管理器</title> <title>ParlzPackageManager · 跨平台 C 语言包管理器</title>
<meta name="description" content="ParlzPackageManager (PMM) — 用 C 语言编写、跨 Windows/Linux/macOS 的包管理器,apt 式多镜像源、git release 安装、.pdm 打包格式、SHA-256/SHA-1 校验。"> <meta name="description" content="ParlzPackageManager (PMM) — 用 C 语言编写、跨 Windows/Linux/macOS 的包管理器,apt 式多镜像源、git release 安装、.pdm 打包格式、SHA-256 校验。">
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='7' fill='%230d1117'/%3E%3Ctext x='16' y='23' font-size='17' font-family='monospace' fill='%2356d4ff' text-anchor='middle'%3Epmm%3C/text%3E%3C/svg%3E"> <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='7' fill='%230b0e14'/%3E%3Ctext x='16' y='23' font-size='17' font-family='monospace' fill='%233ea8ff' text-anchor='middle'%3Epmm%3C/text%3E%3C/svg%3E">
<link rel="stylesheet" href="assets/style.css"> <link rel="stylesheet" href="assets/style.css">
</head> </head>
<body> <body>
@@ -64,16 +68,18 @@ $COLS = strlen($map[0]);
<nav class="nav"> <nav class="nav">
<a class="brand" href="#top"><span class="brand-mark">pmm</span><span class="brand-name">ParlzPackageManager</span></a> <a class="brand" href="#top"><span class="brand-mark">pmm</span><span class="brand-name">ParlzPackageManager</span></a>
<ul class="nav-links"> <ul class="nav-links">
<li><a href="#map">世界</a></li>
<li><a href="#features">特性</a></li> <li><a href="#features">特性</a></li>
<li><a href="#install">安装</a></li> <li><a href="#install">安装</a></li>
<li><a href="#download">下载</a></li> <li><a href="#download">下载</a></li>
<li><a href="#status">服务状态</a></li>
<li><a class="btn btn-sm" href="https://github.com/JGZYES/ParlzPackageManger" target="_blank" rel="noopener">GitHub ↗</a></li> <li><a class="btn btn-sm" href="https://github.com/JGZYES/ParlzPackageManger" target="_blank" rel="noopener">GitHub ↗</a></li>
</ul> </ul>
</nav> </nav>
<!-- Two-column hero: left intro / right dotted Earth globe -->
<header id="top" class="hero"> <header id="top" class="hero">
<div class="hero-inner"> <div class="hero-inner">
<div class="hero-copy">
<div class="hero-badge">v<?php echo $VERSION; ?> · C11 · Windows / Linux / macOS</div> <div class="hero-badge">v<?php echo $VERSION; ?> · C11 · Windows / Linux / macOS</div>
<h1>ParlzPackageManager <span class="accent">(PMM)</span></h1> <h1>ParlzPackageManager <span class="accent">(PMM)</span></h1>
<p class="hero-tag">一个用 <strong>C 语言</strong>从零编写的跨平台包管理器。像 apt 一样多用镜像源,也能直接从 <strong>GitHub / GitLab / Gitea / Forgejo</strong> Release 装包。</p> <p class="hero-tag">一个用 <strong>C 语言</strong>从零编写的跨平台包管理器。像 apt 一样多用镜像源,也能直接从 <strong>GitHub / GitLab / Gitea / Forgejo</strong> Release 装包。</p>
@@ -81,32 +87,35 @@ $COLS = strlen($map[0]);
<a class="btn btn-primary" href="#install">快速开始</a> <a class="btn btn-primary" href="#install">快速开始</a>
<a class="btn btn-ghost" href="https://github.com/JGZYES/ParlzPackageManger/releases/latest" target="_blank" rel="noopener">最新版 ↓</a> <a class="btn btn-ghost" href="https://github.com/JGZYES/ParlzPackageManger/releases/latest" target="_blank" rel="noopener">最新版 ↓</a>
</div> </div>
<div class="hero-note">
<span class="dot ok"></span> 双镜像 · 深圳 / 香港 实时可用
</div>
</div>
<div class="hero-globe">
<canvas id="globe" aria-label="PMM 服务分布地球"></canvas>
<div class="globe-caption">全球镜像 · 深圳 · 香港</div>
</div>
</div> </div>
</header> </header>
<!-- ASCII world map (+/-) with hover island glow --> <section id="status" class="section">
<section id="map" class="map-section"> <div class="wrap">
<div class="map-wrap"> <h2 class="section-title">服务 <span class="accent">状态</span></h2>
<div class="map-title">把世界装进一个包管理器</div> <p class="section-sub">实时监控 PMM 镜像服务器可用性与延迟(每 12 秒刷新)。</p>
<div id="worldmap" class="worldmap" data-rows="<?php echo $ROWS; ?>" data-cols="<?php echo $COLS; ?>"> <div class="status-grid" id="status-grid">
<?php <div class="status-card">
for ($r = 0; $r < $ROWS; $r++) { <div class="status-head"><span class="pin">深圳</span><span class="status-dot" id="dot-0"></span></div>
echo ' <div class="row">'; <div class="status-ip">120.24.78.176</div>
for ($c = 0; $c < $COLS; $c++) { <div class="status-host">sz.pmm.parlz.com</div>
$ch = $map[$r][$c]; <div class="status-meta" id="meta-0">检测中…</div>
if ($ch === '#') { </div>
/* alternate '+'/'-' by position for a textured +/- map */ <div class="status-card">
$g = (($r + $c) % 2 === 0) ? '+' : '-'; <div class="status-head"><span class="pin">香港</span><span class="status-dot" id="dot-1"></span></div>
echo '<span class="px land" data-r="' . $r . '" data-c="' . $c . '">' . $g . '</span>'; <div class="status-ip">38.76.190.153</div>
} else { <div class="status-host">pmm.parlz.com</div>
echo '<span class="px water"></span>'; <div class="status-meta" id="meta-1">检测中…</div>
} </div>
}
echo "</div>\n";
}
?>
</div> </div>
<div class="map-hint">将光标移到大陆上,点亮整个世界</div>
</div> </div>
</section> </section>
@@ -193,10 +202,14 @@ pmm -v</code></pre>
<a href="https://github.com/JGZYES/ParlzPackageManger/releases" target="_blank" rel="noopener">Releases</a> <a href="https://github.com/JGZYES/ParlzPackageManger/releases" target="_blank" rel="noopener">Releases</a>
<a href="#top">回到顶部</a> <a href="#top">回到顶部</a>
</div> </div>
<p class="footer-note">© 2026 ParlzPackageManager Project · Mirror: sz.pmm.parlz.com / pmm.parlz.com</p> <p class="footer-note">© 2026 ParlzPackageManager Project · 深圳 120.24.78.176 / 香港 38.76.190.153</p>
</div> </div>
</footer> </footer>
<script>
window.PMM_MAP = <?php echo json_encode($dots); ?>;
window.PMM_DIMS = { rows: <?php echo $ROWS; ?>, cols: <?php echo $COLS; ?> };
</script>
<script src="assets/main.js"></script> <script src="assets/main.js"></script>
</body> </body>
</html> </html>
+65
查看文件
@@ -0,0 +1,65 @@
<?php
/* Status probe for PMM mirror servers.
* Server-side GET (curl -k, since we hit IPs/hosts that may not match the
* browser's TLS expectations) -> returns JSON for the homepage status panel. */
header('Content-Type: application/json; charset=utf-8');
header('Cache-Control: no-store');
$servers = [
['name'=>'深圳', 'ip'=>'120.24.78.176', 'url'=>'https://sz.pmm.parlz.com/mirror/packages/pmm.json'],
['name'=>'香港', 'ip'=>'38.76.190.153', 'url'=>'https://pmm.parlz.com/mirror/packages/pmm.json'],
];
function probe($url) {
$t = microtime(true);
/* 1) PHP curl extension (preferred) */
if (function_exists('curl_init')) {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_NOBODY => true,
CURLOPT_CONNECTTIMEOUT => 3,
CURLOPT_TIMEOUT => 6,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_USERAGENT => 'pmm-status/1.0',
]);
curl_exec($ch);
$code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
$errno = curl_errno($ch);
curl_close($ch);
if ($errno === 0 && $code > 0)
return ['online'=>$code < 500, 'code'=>$code, 'ms'=>(int)round((microtime(true)-$t)*1000)];
}
/* 2) shell curl (pmm depends on the curl binary, so it is present) */
if (function_exists('shell_exec') && !in_array('shell_exec', array_map('trim', explode(',', ini_get('disable_functions'))))) {
$cmd = 'curl -k -s -o /dev/null -w "%{http_code}" --connect-timeout 3 --max-time 6 ' . escapeshellarg($url);
$out = trim((string)@shell_exec($cmd));
$code = (int)$out;
if ($code > 0)
return ['online'=>$code < 500, 'code'=>$code, 'ms'=>(int)round((microtime(true)-$t)*1000)];
}
/* 3) stream context fallback */
$ctx = stream_context_create([
'http' => ['method'=>'HEAD','timeout'=>6,'follow_location'=>1,'ignore_errors'=>true],
'ssl' => ['verify_peer'=>false,'verify_peer_name'=>false],
]);
$fp = @fopen($url, 'r', false, $ctx);
$ms = (int)round((microtime(true) - $t) * 1000);
if ($fp) { fclose($fp); return ['online'=>true,'code'=>200,'ms'=>$ms]; }
return ['online'=>false,'code'=>0,'ms'=>$ms];
}
$out = [];
foreach ($servers as $s) {
$st = probe($s['url']);
$out[] = [
'name' => $s['name'],
'ip' => $s['ip'],
'online' => $st['online'],
'code' => $st['code'],
'ms' => $st['ms'],
];
}
echo json_encode($out, JSON_UNESCAPED_UNICODE);