| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271 |
- <!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>
|