项目文件夹

文件
2026-05-10 15:45:54 +00:00

329 行
8.8 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Browser Harness Edge-case Benchmark</title>
<style>
:root {
color-scheme: light;
--bg: #f7f7fb;
--ink: #171923;
--muted: #596171;
--line: #cfd6e4;
--pass: #087f5b;
--card: #ffffff;
--accent: #5b46f6;
}
* { box-sizing: border-box; }
body {
margin: 0;
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: var(--bg);
color: var(--ink);
}
main {
max-width: 1120px;
margin: 0 auto;
padding: 32px 20px 56px;
}
header {
margin-bottom: 24px;
}
h1 {
margin: 0 0 8px;
font-size: 34px;
line-height: 1.1;
letter-spacing: 0;
}
p {
margin: 0;
color: var(--muted);
line-height: 1.5;
}
.grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16px;
}
.panel {
min-height: 280px;
background: var(--card);
border: 1px solid var(--line);
border-radius: 8px;
padding: 18px;
box-shadow: 0 1px 2px rgb(15 23 42 / 0.06);
}
.panel h2 {
margin: 0 0 12px;
font-size: 18px;
}
.status {
min-height: 28px;
margin-top: 12px;
font-weight: 700;
color: #8a4b00;
}
.status[data-pass="true"] {
color: var(--pass);
}
canvas {
display: block;
width: 100%;
height: 150px;
border: 2px dashed #8c97ac;
border-radius: 6px;
background: #fff;
touch-action: none;
}
.upload-row {
display: flex;
align-items: center;
gap: 12px;
min-height: 116px;
}
input[type="file"] {
max-width: 100%;
}
.drop-layout {
display: grid;
grid-template-columns: 160px 1fr;
gap: 18px;
align-items: center;
min-height: 150px;
}
#drag-card {
width: 150px;
height: 96px;
display: grid;
place-items: center;
border-radius: 8px;
background: #ebe9ff;
border: 2px solid var(--accent);
color: #2d218f;
font-weight: 800;
cursor: grab;
user-select: none;
}
#drop-zone {
min-height: 132px;
display: grid;
place-items: center;
border: 2px dashed #74809a;
border-radius: 8px;
background: #fbfcff;
color: var(--muted);
font-weight: 700;
}
#drop-zone.hot {
border-color: var(--accent);
background: #f1efff;
}
.coordinate-stage {
position: relative;
height: 168px;
border: 1px solid var(--line);
border-radius: 8px;
overflow: hidden;
background:
linear-gradient(90deg, rgb(15 23 42 / 0.06) 1px, transparent 1px),
linear-gradient(rgb(15 23 42 / 0.06) 1px, transparent 1px),
#ffffff;
background-size: 32px 32px;
}
#coordinate-target {
position: absolute;
left: 72%;
top: 58%;
width: 34px;
height: 34px;
border-radius: 50%;
border: 0;
background: #ff4d6d;
box-shadow: 0 0 0 8px rgb(255 77 109 / 0.16);
cursor: crosshair;
}
#coordinate-target span {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0 0 0 0);
}
.summary {
margin-top: 16px;
padding: 16px 18px;
border: 1px solid var(--line);
border-radius: 8px;
background: #fff;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
color: #202938;
white-space: pre-wrap;
}
@media (max-width: 760px) {
.grid { grid-template-columns: 1fr; }
.drop-layout { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<main>
<header>
<h1>Browser Harness Edge-case Benchmark</h1>
<p>Four browser mechanics that usually force an agent to inspect, patch, retry, and verify.</p>
</header>
<section class="grid" aria-label="Benchmark tasks">
<article class="panel" data-task="signature">
<h2>1. Canvas signature</h2>
<canvas id="signature-pad" width="520" height="150" aria-label="Signature pad"></canvas>
<div id="signature-status" class="status">Draw a stroke across the signature pad.</div>
</article>
<article class="panel" data-task="upload">
<h2>2. File upload</h2>
<div class="upload-row">
<input id="upload-input" type="file" aria-label="Upload benchmark file">
</div>
<div id="upload-status" class="status">Upload any local file.</div>
</article>
<article class="panel" data-task="drag">
<h2>3. Drag and drop</h2>
<div class="drop-layout">
<div id="drag-card" draggable="true">DRAG ME</div>
<div id="drop-zone">Drop here</div>
</div>
<div id="drag-status" class="status">Move the card into the drop zone.</div>
</article>
<article class="panel" data-task="coordinate">
<h2>4. Coordinate target</h2>
<div class="coordinate-stage" id="coordinate-stage">
<button id="coordinate-target" type="button"><span>Hit target</span></button>
</div>
<div id="coordinate-status" class="status">Click the red target by visible coordinates.</div>
</article>
</section>
<section class="summary" id="summary" aria-live="polite"></section>
</main>
<script>
const results = {
signature: false,
upload: false,
drag: false,
coordinate: false,
};
window.edgeBenchmark = {
results,
passed() {
return Object.values(results).filter(Boolean).length;
},
complete() {
return Object.values(results).every(Boolean);
},
};
function setPass(task, detail) {
results[task] = true;
const status = document.getElementById(`${task}-status`);
status.dataset.pass = "true";
status.textContent = `PASS: ${detail}`;
renderSummary();
}
function renderSummary() {
document.getElementById("summary").textContent =
`passed=${window.edgeBenchmark.passed()}/4\n` +
JSON.stringify(results, null, 2);
}
renderSummary();
const canvas = document.getElementById("signature-pad");
const ctx = canvas.getContext("2d");
ctx.lineWidth = 4;
ctx.lineCap = "round";
ctx.strokeStyle = "#171923";
let drawing = false;
let last = null;
let points = [];
function canvasPoint(event) {
const rect = canvas.getBoundingClientRect();
return {
x: (event.clientX - rect.left) * (canvas.width / rect.width),
y: (event.clientY - rect.top) * (canvas.height / rect.height),
};
}
canvas.addEventListener("pointerdown", (event) => {
drawing = true;
points = [canvasPoint(event)];
last = points[0];
canvas.setPointerCapture(event.pointerId);
});
canvas.addEventListener("pointermove", (event) => {
if (!drawing) return;
const next = canvasPoint(event);
ctx.beginPath();
ctx.moveTo(last.x, last.y);
ctx.lineTo(next.x, next.y);
ctx.stroke();
points.push(next);
last = next;
});
canvas.addEventListener("pointerup", () => {
drawing = false;
const distance = points.slice(1).reduce((total, point, index) => {
const prev = points[index];
return total + Math.hypot(point.x - prev.x, point.y - prev.y);
}, 0);
if (points.length >= 4 && distance > 80) {
setPass("signature", `${points.length} points, ${Math.round(distance)}px stroke`);
}
});
document.getElementById("upload-input").addEventListener("change", (event) => {
const file = event.target.files && event.target.files[0];
if (file) setPass("upload", file.name);
});
const dragCard = document.getElementById("drag-card");
const dropZone = document.getElementById("drop-zone");
dragCard.addEventListener("dragstart", (event) => {
event.dataTransfer.setData("text/plain", "browser-harness-edge-card");
event.dataTransfer.effectAllowed = "move";
});
dropZone.addEventListener("dragover", (event) => {
event.preventDefault();
dropZone.classList.add("hot");
});
dropZone.addEventListener("dragleave", () => {
dropZone.classList.remove("hot");
});
dropZone.addEventListener("drop", (event) => {
event.preventDefault();
dropZone.classList.remove("hot");
if (event.dataTransfer.getData("text/plain") === "browser-harness-edge-card") {
dropZone.textContent = "Dropped";
setPass("drag", "HTML5 DataTransfer accepted");
}
});
document.getElementById("coordinate-target").addEventListener("click", () => {
setPass("coordinate", "red target clicked");
});
</script>
</body>
</html>