Files
playbook/darwin-skill/assets/chart-ratchet-en.html
T
2026-08-12 08:38:22 +08:00

272 lines
7.3 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ratchet Mechanism</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800&display=swap');
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
width: 1200px;
height: 450px;
background: #111111;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
display: flex;
flex-direction: column;
overflow: hidden;
position: relative;
}
.header {
padding: 28px 60px 0;
display: flex;
align-items: baseline;
gap: 16px;
}
.label {
color: #D4532B;
font-size: 13px;
font-weight: 800;
letter-spacing: 2px;
text-transform: uppercase;
}
.subtitle {
color: #555;
font-size: 12px;
font-weight: 600;
letter-spacing: 1px;
}
.chart-area {
flex: 1;
display: flex;
align-items: flex-end;
justify-content: center;
padding: 0 60px 60px;
gap: 0;
position: relative;
}
.bars-wrapper {
display: flex;
align-items: flex-end;
gap: 40px;
position: relative;
width: 100%;
justify-content: center;
}
.bar-group {
display: flex;
flex-direction: column;
align-items: center;
position: relative;
width: 80px;
}
.score {
font-size: 36px;
font-weight: 800;
color: #ffffff;
margin-bottom: 10px;
line-height: 1;
}
.score.rollback {
color: #C92A2A;
text-decoration: line-through;
text-decoration-thickness: 3px;
}
.bar {
width: 80px;
border-radius: 4px 4px 0 0;
position: relative;
}
.bar.baseline {
background: #444444;
}
.bar.retained {
background: #ffffff;
}
.bar.rollback-bar {
background: transparent;
border: 2px dashed #C92A2A;
border-bottom: none;
}
.bar.highlight {
background: #D4532B;
}
.round-label {
margin-top: 12px;
color: #666666;
font-size: 12px;
font-weight: 600;
letter-spacing: 0.5px;
white-space: nowrap;
}
/* SVG overlay for arrows and ratchet line */
.svg-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
</style>
</head>
<body>
<div class="header">
<div class="label">RATCHET MECHANISM</div>
<div class="subtitle">— effective baseline only moves up</div>
</div>
<div class="chart-area" id="chartArea">
<div class="bars-wrapper" id="barsWrapper">
<!-- bars will be injected by JS -->
</div>
<svg class="svg-overlay" id="svgOverlay"></svg>
</div>
<script>
const scores = [72, 78, 75, 84, 87];
const types = ['baseline', 'retained', 'rollback', 'retained', 'highlight'];
const rounds = ['Round 0', 'Round 1', 'Round 2', 'Round 3', 'Round 4'];
// Effective baseline sequence (ratchet): 72, 78, 78, 84, 87
const effectiveBaseline = [72, 78, 78, 84, 87];
const maxScore = 90;
const minScore = 60;
const chartHeight = 270; // px available for bars
function barHeight(score) {
return Math.round((score - minScore) / (maxScore - minScore) * chartHeight);
}
const wrapper = document.getElementById('barsWrapper');
scores.forEach((score, i) => {
const group = document.createElement('div');
group.className = 'bar-group';
group.id = `group-${i}`;
const scoreEl = document.createElement('div');
scoreEl.className = 'score' + (types[i] === 'rollback' ? ' rollback' : '');
scoreEl.textContent = score;
const bar = document.createElement('div');
bar.className = 'bar ' + (types[i] === 'rollback' ? 'rollback-bar' : types[i]);
const h = barHeight(score);
bar.style.height = h + 'px';
const label = document.createElement('div');
label.className = 'round-label';
label.textContent = rounds[i];
group.appendChild(scoreEl);
group.appendChild(bar);
group.appendChild(label);
wrapper.appendChild(group);
});
// Draw arrows and ratchet line after layout
requestAnimationFrame(() => {
requestAnimationFrame(() => {
const svg = document.getElementById('svgOverlay');
const chartArea = document.getElementById('chartArea');
const chartRect = chartArea.getBoundingClientRect();
// Collect bar group positions
const groups = [];
for (let i = 0; i < 5; i++) {
const g = document.getElementById(`group-${i}`);
const rect = g.getBoundingClientRect();
// top of the bar (not the score label)
const bar = g.querySelector('.bar');
const barRect = bar.getBoundingClientRect();
groups.push({
cx: rect.left - chartRect.left + rect.width / 2,
barTop: barRect.top - chartRect.top,
barBottom: barRect.bottom - chartRect.top,
});
}
// Arrow heads: connect bar top centers (exclude rollback from arrows, draw arrow anyway between all)
const arrowColor = '#D4532B';
const arrowGap = 8;
let svgContent = `
<defs>
<marker id="arrow" markerWidth="8" markerHeight="8" refX="6" refY="3" orient="auto">
<path d="M0,0 L0,6 L8,3 z" fill="${arrowColor}" />
</marker>
</defs>
`;
// Draw horizontal arrows between consecutive bar centers at mid-height of the lower bar
for (let i = 0; i < 4; i++) {
const fromX = groups[i].cx + 40 + arrowGap;
const toX = groups[i+1].cx - 40 - arrowGap - 8;
const higherBarTop = Math.min(groups[i].barTop, groups[i+1].barTop);
const lowerBarTop = Math.max(groups[i].barTop, groups[i+1].barTop);
const lowerBarBottom = Math.max(groups[i].barBottom, groups[i+1].barBottom);
const y = lowerBarTop + (lowerBarBottom - lowerBarTop) * 0.5;
// Use a slightly raised y to look cleaner
const arrowY = Math.min(groups[i].barTop, groups[i+1].barTop) - 18;
const clampedY = Math.max(arrowY, 40);
svgContent += `<line x1="${fromX}" y1="${clampedY}" x2="${toX}" y2="${clampedY}"
stroke="${arrowColor}" stroke-width="2" marker-end="url(#arrow)" opacity="0.7"/>`;
}
// Ratchet line: connects effective baseline tops
const effectiveHeights = effectiveBaseline.map(s => barHeight(s));
const baselineBottom = groups[0].barBottom; // all bars share same bottom
// Points for ratchet line
const points = groups.map((g, i) => {
const effH = effectiveHeights[i];
const y = baselineBottom - effH;
return { x: g.cx, y };
});
// Draw dashed orange line through effective baseline tops
let pathD = `M ${points[0].x} ${points[0].y}`;
for (let i = 1; i < points.length; i++) {
pathD += ` L ${points[i].x} ${points[i].y}`;
}
svgContent += `<path d="${pathD}" fill="none" stroke="${arrowColor}" stroke-width="2"
stroke-dasharray="6,4" opacity="0.9"/>`;
// Dots at each effective baseline point
points.forEach((p, i) => {
svgContent += `<circle cx="${p.x}" cy="${p.y}" r="4" fill="${arrowColor}" opacity="0.9"/>`;
});
// Label for the ratchet line
svgContent += `<text x="${points[4].x + 10}" y="${points[4].y - 6}" fill="${arrowColor}"
font-family="Inter, sans-serif" font-size="11" font-weight="700" letter-spacing="0.5">EFFECTIVE FLOOR</text>`;
svg.innerHTML = svgContent;
});
});
</script>
</body>
</html>