1
0

chart-ratchet-en.html 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>Ratchet Mechanism</title>
  7. <style>
  8. @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800&display=swap');
  9. * { margin: 0; padding: 0; box-sizing: border-box; }
  10. body {
  11. width: 1200px;
  12. height: 450px;
  13. background: #111111;
  14. font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
  15. display: flex;
  16. flex-direction: column;
  17. overflow: hidden;
  18. position: relative;
  19. }
  20. .header {
  21. padding: 28px 60px 0;
  22. display: flex;
  23. align-items: baseline;
  24. gap: 16px;
  25. }
  26. .label {
  27. color: #D4532B;
  28. font-size: 13px;
  29. font-weight: 800;
  30. letter-spacing: 2px;
  31. text-transform: uppercase;
  32. }
  33. .subtitle {
  34. color: #555;
  35. font-size: 12px;
  36. font-weight: 600;
  37. letter-spacing: 1px;
  38. }
  39. .chart-area {
  40. flex: 1;
  41. display: flex;
  42. align-items: flex-end;
  43. justify-content: center;
  44. padding: 0 60px 60px;
  45. gap: 0;
  46. position: relative;
  47. }
  48. .bars-wrapper {
  49. display: flex;
  50. align-items: flex-end;
  51. gap: 40px;
  52. position: relative;
  53. width: 100%;
  54. justify-content: center;
  55. }
  56. .bar-group {
  57. display: flex;
  58. flex-direction: column;
  59. align-items: center;
  60. position: relative;
  61. width: 80px;
  62. }
  63. .score {
  64. font-size: 36px;
  65. font-weight: 800;
  66. color: #ffffff;
  67. margin-bottom: 10px;
  68. line-height: 1;
  69. }
  70. .score.rollback {
  71. color: #C92A2A;
  72. text-decoration: line-through;
  73. text-decoration-thickness: 3px;
  74. }
  75. .bar {
  76. width: 80px;
  77. border-radius: 4px 4px 0 0;
  78. position: relative;
  79. }
  80. .bar.baseline {
  81. background: #444444;
  82. }
  83. .bar.retained {
  84. background: #ffffff;
  85. }
  86. .bar.rollback-bar {
  87. background: transparent;
  88. border: 2px dashed #C92A2A;
  89. border-bottom: none;
  90. }
  91. .bar.highlight {
  92. background: #D4532B;
  93. }
  94. .round-label {
  95. margin-top: 12px;
  96. color: #666666;
  97. font-size: 12px;
  98. font-weight: 600;
  99. letter-spacing: 0.5px;
  100. white-space: nowrap;
  101. }
  102. /* SVG overlay for arrows and ratchet line */
  103. .svg-overlay {
  104. position: absolute;
  105. top: 0;
  106. left: 0;
  107. width: 100%;
  108. height: 100%;
  109. pointer-events: none;
  110. }
  111. </style>
  112. </head>
  113. <body>
  114. <div class="header">
  115. <div class="label">RATCHET MECHANISM</div>
  116. <div class="subtitle">— effective baseline only moves up</div>
  117. </div>
  118. <div class="chart-area" id="chartArea">
  119. <div class="bars-wrapper" id="barsWrapper">
  120. <!-- bars will be injected by JS -->
  121. </div>
  122. <svg class="svg-overlay" id="svgOverlay"></svg>
  123. </div>
  124. <script>
  125. const scores = [72, 78, 75, 84, 87];
  126. const types = ['baseline', 'retained', 'rollback', 'retained', 'highlight'];
  127. const rounds = ['Round 0', 'Round 1', 'Round 2', 'Round 3', 'Round 4'];
  128. // Effective baseline sequence (ratchet): 72, 78, 78, 84, 87
  129. const effectiveBaseline = [72, 78, 78, 84, 87];
  130. const maxScore = 90;
  131. const minScore = 60;
  132. const chartHeight = 270; // px available for bars
  133. function barHeight(score) {
  134. return Math.round((score - minScore) / (maxScore - minScore) * chartHeight);
  135. }
  136. const wrapper = document.getElementById('barsWrapper');
  137. scores.forEach((score, i) => {
  138. const group = document.createElement('div');
  139. group.className = 'bar-group';
  140. group.id = `group-${i}`;
  141. const scoreEl = document.createElement('div');
  142. scoreEl.className = 'score' + (types[i] === 'rollback' ? ' rollback' : '');
  143. scoreEl.textContent = score;
  144. const bar = document.createElement('div');
  145. bar.className = 'bar ' + (types[i] === 'rollback' ? 'rollback-bar' : types[i]);
  146. const h = barHeight(score);
  147. bar.style.height = h + 'px';
  148. const label = document.createElement('div');
  149. label.className = 'round-label';
  150. label.textContent = rounds[i];
  151. group.appendChild(scoreEl);
  152. group.appendChild(bar);
  153. group.appendChild(label);
  154. wrapper.appendChild(group);
  155. });
  156. // Draw arrows and ratchet line after layout
  157. requestAnimationFrame(() => {
  158. requestAnimationFrame(() => {
  159. const svg = document.getElementById('svgOverlay');
  160. const chartArea = document.getElementById('chartArea');
  161. const chartRect = chartArea.getBoundingClientRect();
  162. // Collect bar group positions
  163. const groups = [];
  164. for (let i = 0; i < 5; i++) {
  165. const g = document.getElementById(`group-${i}`);
  166. const rect = g.getBoundingClientRect();
  167. // top of the bar (not the score label)
  168. const bar = g.querySelector('.bar');
  169. const barRect = bar.getBoundingClientRect();
  170. groups.push({
  171. cx: rect.left - chartRect.left + rect.width / 2,
  172. barTop: barRect.top - chartRect.top,
  173. barBottom: barRect.bottom - chartRect.top,
  174. });
  175. }
  176. // Arrow heads: connect bar top centers (exclude rollback from arrows, draw arrow anyway between all)
  177. const arrowColor = '#D4532B';
  178. const arrowGap = 8;
  179. let svgContent = `
  180. <defs>
  181. <marker id="arrow" markerWidth="8" markerHeight="8" refX="6" refY="3" orient="auto">
  182. <path d="M0,0 L0,6 L8,3 z" fill="${arrowColor}" />
  183. </marker>
  184. </defs>
  185. `;
  186. // Draw horizontal arrows between consecutive bar centers at mid-height of the lower bar
  187. for (let i = 0; i < 4; i++) {
  188. const fromX = groups[i].cx + 40 + arrowGap;
  189. const toX = groups[i+1].cx - 40 - arrowGap - 8;
  190. const higherBarTop = Math.min(groups[i].barTop, groups[i+1].barTop);
  191. const lowerBarTop = Math.max(groups[i].barTop, groups[i+1].barTop);
  192. const lowerBarBottom = Math.max(groups[i].barBottom, groups[i+1].barBottom);
  193. const y = lowerBarTop + (lowerBarBottom - lowerBarTop) * 0.5;
  194. // Use a slightly raised y to look cleaner
  195. const arrowY = Math.min(groups[i].barTop, groups[i+1].barTop) - 18;
  196. const clampedY = Math.max(arrowY, 40);
  197. svgContent += `<line x1="${fromX}" y1="${clampedY}" x2="${toX}" y2="${clampedY}"
  198. stroke="${arrowColor}" stroke-width="2" marker-end="url(#arrow)" opacity="0.7"/>`;
  199. }
  200. // Ratchet line: connects effective baseline tops
  201. const effectiveHeights = effectiveBaseline.map(s => barHeight(s));
  202. const baselineBottom = groups[0].barBottom; // all bars share same bottom
  203. // Points for ratchet line
  204. const points = groups.map((g, i) => {
  205. const effH = effectiveHeights[i];
  206. const y = baselineBottom - effH;
  207. return { x: g.cx, y };
  208. });
  209. // Draw dashed orange line through effective baseline tops
  210. let pathD = `M ${points[0].x} ${points[0].y}`;
  211. for (let i = 1; i < points.length; i++) {
  212. pathD += ` L ${points[i].x} ${points[i].y}`;
  213. }
  214. svgContent += `<path d="${pathD}" fill="none" stroke="${arrowColor}" stroke-width="2"
  215. stroke-dasharray="6,4" opacity="0.9"/>`;
  216. // Dots at each effective baseline point
  217. points.forEach((p, i) => {
  218. svgContent += `<circle cx="${p.x}" cy="${p.y}" r="4" fill="${arrowColor}" opacity="0.9"/>`;
  219. });
  220. // Label for the ratchet line
  221. svgContent += `<text x="${points[4].x + 10}" y="${points[4].y - 6}" fill="${arrowColor}"
  222. font-family="Inter, sans-serif" font-size="11" font-weight="700" letter-spacing="0.5">EFFECTIVE FLOOR</text>`;
  223. svg.innerHTML = svgContent;
  224. });
  225. });
  226. </script>
  227. </body>
  228. </html>