lewtun HF Staff Codex commited on
Commit
3fa2f6d
·
1 Parent(s): fa084d0

Reveal chart curves when they scroll into view

Browse files

Animate SVG traces once without resampling data, and respect reduced-motion preferences.

Co-authored-by: Codex <noreply@openai.com>

Files changed (2) hide show
  1. site/index.html +6 -1
  2. site/progress.js +24 -0
site/index.html CHANGED
@@ -45,6 +45,11 @@
45
  .progress h2 { font:400 32px/1.2 var(--lead); margin:0 0 8px; }
46
  .progress p { max-width:680px; }
47
  #progress-chart { width:100%; height:460px; }
 
 
 
 
 
48
  @media (max-width:640px) { body { font-size:17px; } #map { height:420px; } #log { height:200px; } .msg { max-width:90%; } }
49
  </style>
50
  </head>
@@ -176,6 +181,6 @@ document.getElementById("reset").addEventListener("click", () => {
176
  q.value = ""; q.disabled = send.disabled = false; q.focus({preventScroll:true});
177
  });
178
  </script>
179
- <script src="progress.js?v=source-lines-2"></script>
180
  </body>
181
  </html>
 
45
  .progress h2 { font:400 32px/1.2 var(--lead); margin:0 0 8px; }
46
  .progress p { max-width:680px; }
47
  #progress-chart { width:100%; height:460px; }
48
+ #progress-chart.awaiting-reveal .scatterlayer .trace { clip-path:inset(-10px 100% -10px 0); }
49
+ #progress-chart.awaiting-reveal { pointer-events:none; }
50
+ @media (prefers-reduced-motion:reduce) {
51
+ #progress-chart.awaiting-reveal .scatterlayer .trace { clip-path:none; }
52
+ }
53
  @media (max-width:640px) { body { font-size:17px; } #map { height:420px; } #log { height:200px; } .msg { max-width:90%; } }
54
  </style>
55
  </head>
 
181
  q.value = ""; q.disabled = send.disabled = false; q.focus({preventScroll:true});
182
  });
183
  </script>
184
+ <script src="progress.js?v=scroll-reveal-1"></script>
185
  </body>
186
  </html>
site/progress.js CHANGED
@@ -1,5 +1,9 @@
1
  async function drawProgress() {
2
  const status = document.getElementById("progress-status");
 
 
 
 
3
  try {
4
  const response = await fetch("api/progress", {cache: "no-store"});
5
  if (!response.ok) throw new Error("Progress data unavailable");
@@ -47,6 +51,26 @@ async function drawProgress() {
47
  hoverlabel: {bgcolor: "#ffffff", bordercolor: "#000000"}
48
  }, {responsive: true, displayModeBar: false, displaylogo: false});
49
  status.hidden = true;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  } catch (error) {
51
  document.getElementById("progress-chart").hidden = true;
52
  status.textContent = "The progress chart is currently unavailable.";
 
1
  async function drawProgress() {
2
  const status = document.getElementById("progress-status");
3
+ const chart = document.getElementById("progress-chart");
4
+ const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)");
5
+ const animateOnScroll = !reducedMotion.matches && "IntersectionObserver" in window;
6
+ if (animateOnScroll) chart.classList.add("awaiting-reveal");
7
  try {
8
  const response = await fetch("api/progress", {cache: "no-store"});
9
  if (!response.ok) throw new Error("Progress data unavailable");
 
51
  hoverlabel: {bgcolor: "#ffffff", bordercolor: "#000000"}
52
  }, {responsive: true, displayModeBar: false, displaylogo: false});
53
  status.hidden = true;
54
+ if (animateOnScroll) {
55
+ const observer = new IntersectionObserver(entries => {
56
+ if (!entries.some(entry => entry.isIntersecting && entry.intersectionRatio >= 0.3)) return;
57
+ observer.disconnect();
58
+ // Reveal the rendered SVG traces, keeping both axes and all data fixed.
59
+ // This avoids redrawing thousands of points on every animation frame.
60
+ chart.classList.remove("awaiting-reveal");
61
+ if (reducedMotion.matches) return;
62
+ const animations = [...chart.querySelectorAll(".scatterlayer .trace")].map(trace =>
63
+ trace.animate([
64
+ {clipPath: "inset(-10px 100% -10px 0)"},
65
+ {clipPath: "inset(-10px 0% -10px 0)"}
66
+ ], {duration: 1800, easing: "cubic-bezier(0.22, 1, 0.36, 1)"}));
67
+ const finish = () => animations.forEach(animation => animation.finish());
68
+ reducedMotion.addEventListener("change", finish, {once: true});
69
+ Promise.all(animations.map(animation => animation.finished)).finally(() =>
70
+ reducedMotion.removeEventListener("change", finish));
71
+ }, {threshold: 0.3});
72
+ observer.observe(chart);
73
+ }
74
  } catch (error) {
75
  document.getElementById("progress-chart").hidden = true;
76
  status.textContent = "The progress chart is currently unavailable.";