镜像自地址
https://github.com/JGZYES/ParlzPackageManger.git
已同步 2026-09-11 08:52:45 +08:00
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
这个提交包含在:
+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);
|
||||||
在新工单中引用
屏蔽一个用户