joelniklaus HF Staff commited on
Commit
95045da
Β·
1 Parent(s): 0f45b18

replace the banner with Thibaud's design

Browse files
Files changed (1) hide show
  1. app/src/content/embeds/banner.html +300 -212
app/src/content/embeds/banner.html CHANGED
@@ -1,15 +1,10 @@
1
- <div class="d3-galaxy" style="width:100%;margin:10px 0;aspect-ratio:3/1;min-height:260px;"></div>
2
  <script>
3
  (() => {
4
  const ensureD3 = (cb) => {
5
  if (window.d3 && typeof window.d3.select === 'function') return cb();
6
  let s = document.getElementById('d3-cdn-script');
7
- if (!s) {
8
- s = document.createElement('script');
9
- s.id = 'd3-cdn-script';
10
- s.src = 'https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js';
11
- document.head.appendChild(s);
12
- }
13
  const onReady = () => { if (window.d3 && typeof window.d3.select === 'function') cb(); };
14
  s.addEventListener('load', onReady, { once: true });
15
  if (window.d3) onReady();
@@ -17,234 +12,327 @@
17
 
18
  const bootstrap = () => {
19
  const mount = document.currentScript ? document.currentScript.previousElementSibling : null;
20
- const container = (mount && mount.querySelector && mount.querySelector('.d3-galaxy')) || document.querySelector('.d3-galaxy');
 
21
  if (!container) return;
22
- if (container.dataset) {
23
  if (container.dataset.mounted === 'true') return;
24
  container.dataset.mounted = 'true';
25
- }
26
- // Scene params (match previous Plotly ranges)
27
- const cx = 1.5, cy = 0.5;
28
- const a = 1.3, b = 0.45;
29
- const numPoints = 3000;
30
- const numArms = 3;
31
- const numTurns = 2.1;
32
- const angleJitter = 0.12;
33
- const posNoise = 0.015;
34
 
35
- // Circle size settings
36
- const minCircleSize = 4; // minimum diameter in pixels
37
- const maxCircleSize = 12; // maximum diameter in pixels
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
 
39
- // Generate spiral + bulge
40
- const twoPi = Math.PI * 2;
41
- const t = Float64Array.from({ length: numPoints }, () => Math.random() * (twoPi * numTurns));
42
- const armIndices = Int16Array.from({ length: numPoints }, () => Math.floor(Math.random() * numArms));
43
- const armOffsets = Float64Array.from(armIndices, (k) => k * (twoPi / numArms));
44
- const theta = Float64Array.from(t, (tv, i) => tv + armOffsets[i] + d3.randomNormal.source(Math.random)(0, angleJitter)());
45
- const rNorm = Float64Array.from(t, (tv) => Math.pow(tv / (twoPi * numTurns), 0.9));
46
- const noiseScale = (rn) => posNoise * (0.8 + 0.6 * rn);
47
- const noiseX = Float64Array.from(rNorm, (rn) => d3.randomNormal.source(Math.random)(0, noiseScale(rn))());
48
- const noiseY = Float64Array.from(rNorm, (rn) => d3.randomNormal.source(Math.random)(0, noiseScale(rn))());
49
 
50
- const xSpiral = Float64Array.from(theta, (th, i) => cx + a * rNorm[i] * Math.cos(th) + noiseX[i]);
51
- const ySpiral = Float64Array.from(theta, (th, i) => cy + b * rNorm[i] * Math.sin(th) + noiseY[i]);
 
 
 
52
 
53
- const bulgePoints = Math.floor(0.18 * numPoints);
54
- const phiB = Float64Array.from({ length: bulgePoints }, () => twoPi * Math.random());
55
- const rB = Float64Array.from({ length: bulgePoints }, () => Math.pow(Math.random(), 2.2) * 0.22);
56
- const noiseXB = Float64Array.from({ length: bulgePoints }, () => d3.randomNormal.source(Math.random)(0, posNoise * 0.6)());
57
- const noiseYB = Float64Array.from({ length: bulgePoints }, () => d3.randomNormal.source(Math.random)(0, posNoise * 0.6)());
58
- const xBulge = Float64Array.from(phiB, (ph, i) => cx + a * rB[i] * Math.cos(ph) + noiseXB[i]);
59
- const yBulge = Float64Array.from(phiB, (ph, i) => cy + b * rB[i] * Math.sin(ph) + noiseYB[i]);
60
 
61
- // Concatenate
62
- const X = Array.from(xSpiral).concat(Array.from(xBulge));
63
- const Y = Array.from(ySpiral).concat(Array.from(yBulge));
64
- const lenSpiral = xSpiral.length;
 
 
 
 
 
 
65
 
66
- const zSpiral = Array.from(rNorm, (rn) => 1 - rn);
67
- const maxRB = rB && rB.length ? (window.d3 && d3.max ? d3.max(rB) : Math.max.apply(null, Array.from(rB))) : 1;
68
- const zBulge = Array.from(rB, (rb) => 1 - (maxRB ? rb / maxRB : 0));
69
- const Zraw = zSpiral.concat(zBulge);
70
- const sizesPx = Zraw.map((z) => minCircleSize + z * (maxCircleSize - minCircleSize)); // diameter in pixels
 
 
71
 
72
- // Labels (same categories as Python version)
73
- const labelOf = (i) => {
74
- const z = Zraw[i];
75
- if (z < 0.25) return 'tiny star';
76
- if (z < 0.5) return 'small star';
77
- if (z < 0.75) return 'medium star';
78
- return 'large star';
79
- };
80
 
81
- // Sort by size ascending for z-index: small first, big last
82
- const idx = d3.range(X.length).sort((i, j) => sizesPx[i] - sizesPx[j]);
 
 
 
 
 
83
 
84
- // Colors: piecewise gradient [0 -> 0.5 -> 1]
85
- const c0 = d3.rgb(78, 165, 183); // rgb(78, 165, 183)
86
- const c1 = d3.rgb(206, 192, 250); // rgb(206, 192, 250)
87
- const c2 = d3.rgb(232, 137, 171); // rgb(232, 137, 171)
88
- const interp01 = d3.interpolateRgb(c0, c1);
89
- const interp12 = d3.interpolateRgb(c1, c2);
90
- const colorFor = (v) => {
91
- const t = Math.max(0, Math.min(1, v));
92
- return t <= 0.5 ? interp01(t / 0.5) : interp12((t - 0.5) / 0.5);
93
- };
94
 
95
- // Create SVG
96
- const svg = d3.select(container).append('svg')
97
- .attr('width', '100%')
98
- .style('display', 'block')
99
- .style('cursor', 'crosshair');
100
 
101
- const render = () => {
102
- const width = container.clientWidth || 800;
103
- const height = Math.max(260, Math.round(width / 3)); // keep ~3:1, min height
104
- svg.attr('width', width).attr('height', height);
105
 
106
- const xScale = d3.scaleLinear().domain([0, 3]).range([0, width]);
107
- const yScale = d3.scaleLinear().domain([0, 1]).range([height, 0]);
 
 
 
 
 
108
 
109
- // Subtle stroke color depending on theme
110
- const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
111
- const strokeColor = isDark ? 'rgba(255,255,255,0.18)' : 'rgba(0,0,0,0.12)';
112
- const glowColor = isDark ? 'rgba(255,255,255,0.35)' : 'rgba(0,0,0,0.25)';
113
 
 
 
114
 
115
- // Group for points (no blend mode for better print/PDF visibility)
116
- const g = svg.selectAll('g.points').data([0]).join('g').attr('class', 'points');
 
 
 
 
 
 
 
 
117
 
118
- // Ensure container can host an absolute tooltip
119
- container.style.position = container.style.position || 'relative';
120
- let tip = container.querySelector('.d3-tooltip');
121
- let tipInner;
122
- if (!tip) {
123
- tip = document.createElement('div');
124
- tip.className = 'd3-tooltip';
125
- Object.assign(tip.style, {
126
- position: 'absolute',
127
- top: '0px',
128
- left: '0px',
129
- transform: 'translate(-9999px, -9999px)',
130
- pointerEvents: 'none',
131
- padding: '10px 12px',
132
- borderRadius: '12px',
133
- fontSize: '12px',
134
- lineHeight: '1.35',
135
- border: '1px solid var(--border-color)',
136
- background: 'var(--surface-bg)',
137
- color: 'var(--text-color)',
138
- boxShadow: '0 8px 32px rgba(0,0,0,.28), 0 2px 8px rgba(0,0,0,.12)',
139
- opacity: '0',
140
- transition: 'opacity .12s ease',
141
- backdropFilter: 'saturate(1.12) blur(8px)',
142
- zIndex: '20'
143
- });
144
- tipInner = document.createElement('div');
145
- tipInner.className = 'd3-tooltip__inner';
146
- Object.assign(tipInner.style, {
147
- textAlign: 'left',
148
- display: 'flex',
149
- flexDirection: 'column',
150
- gap: '6px',
151
- minWidth: '220px'
152
- });
153
- tip.appendChild(tipInner);
154
- container.appendChild(tip);
155
- } else {
156
- tipInner = tip.querySelector('.d3-tooltip__inner') || tip;
157
- }
158
 
159
- // Final filter: remove small dots very close to the galaxy center (after placement)
160
- const centerHoleRadius = 0.48; // elliptical radius threshold
161
- const smallSizeThreshold = 7.5; // same notion as Python size cut
162
- const rTotal = idx.map((i) => Math.sqrt(((X[i] - cx) / a) ** 2 + ((Y[i] - cy) / b) ** 2));
163
- const idxFiltered = idx.filter((i, k) => !(rTotal[k] <= centerHoleRadius && sizesPx[i] < smallSizeThreshold));
 
 
 
 
 
 
 
 
164
 
165
- const sel = g.selectAll('circle').data(idxFiltered, (i) => i);
166
- sel.join(
167
- (enter) => enter.append('circle')
168
- .attr('cx', (i) => xScale(X[i]))
169
- .attr('cy', (i) => yScale(Y[i]))
170
- .attr('r', (i) => sizesPx[i] / 2)
171
- .attr('fill', (i) => colorFor(Zraw[i]))
172
- .attr('fill-opacity', 0.9)
173
- .on('mouseenter', function (ev, i) {
174
- d3.select(this).raise()
175
- .style('filter', `drop-shadow(0 0 8px ${glowColor})`)
176
- .transition().duration(120).ease(d3.easeCubicOut)
177
- .attr('r', (sizesPx[i] / 2) * 1.25)
178
- .attr('fill-opacity', 1);
179
- const r = Math.sqrt(((X[i] - cx) / a) ** 2 + ((Y[i] - cy) / b) ** 2);
180
- const type = i < lenSpiral ? 'spiral' : 'bulge';
181
- const arm = i < lenSpiral ? (armIndices[i] + 1) : null;
182
- tipInner.innerHTML =
183
- `<div style="font-weight:800;letter-spacing:.1px;"><strong>${labelOf(i)}</strong></div>` +
184
- `<div style="font-size:11px;color:var(--muted-color);margin-top:-4px;margin-bottom:2px;letter-spacing:.1px;"><strong>Type</strong> ${type}${arm ? ` (Arm ${arm})` : ''}</div>` +
185
- `<div style="padding-top:6px;border-top:1px solid var(--border-color);"><strong>Position</strong> X ${X[i].toFixed(2)} Β· <strong>Y</strong> ${Y[i].toFixed(2)}</div>` +
186
- `<div><strong>Distance</strong> Radius ${r.toFixed(3)} Β· <strong>Z</strong> ${Zraw[i].toFixed(3)}</div>` +
187
- `<div><strong>Size</strong> ${sizesPx[i].toFixed(1)} px</div>`;
188
- tip.style.opacity = '1';
189
- })
190
- .on('mousemove', (ev, i) => {
191
- const [mx, my] = d3.pointer(ev, container);
192
- const offsetX = 10, offsetY = 12;
193
- tip.style.transform = `translate(${Math.round(mx + offsetX)}px, ${Math.round(my + offsetY)}px)`;
194
- })
195
- .on('mouseleave', function () {
196
- tip.style.opacity = '0';
197
- tip.style.transform = 'translate(-9999px, -9999px)';
198
- d3.select(this)
199
- .style('filter', null)
200
- .transition().duration(120).ease(d3.easeCubicOut)
201
- .attr('r', (i2) => sizesPx[i2] / 2)
202
- .attr('fill-opacity', 0.9);
203
- }),
204
- (update) => update
205
- .attr('cx', (i) => xScale(X[i]))
206
- .attr('cy', (i) => yScale(Y[i]))
207
- .attr('r', (i) => sizesPx[i] / 2)
208
- .attr('fill', (i) => colorFor(Zraw[i]))
209
- .attr('fill-opacity', 0.9)
210
- .on('mouseenter', function (ev, i) {
211
- d3.select(this).raise()
212
- .style('filter', `drop-shadow(0 0 8px ${glowColor})`)
213
- .transition().duration(120).ease(d3.easeCubicOut)
214
- .attr('r', (sizesPx[i] / 2) * 1.25)
215
- .attr('fill-opacity', 1);
216
- const r = Math.sqrt(((X[i] - cx) / a) ** 2 + ((Y[i] - cy) / b) ** 2);
217
- const type = i < lenSpiral ? 'spiral' : 'bulge';
218
- const arm = i < lenSpiral ? (armIndices[i] + 1) : null;
219
- tipInner.innerHTML =
220
- `<div style="font-weight:800;letter-spacing:.1px;"><strong>${labelOf(i)}</strong></div>` +
221
- `<div style="font-size:11px;color:var(--muted-color);margin-top:-4px;margin-bottom:2px;letter-spacing:.1px;"><strong>Type</strong> ${type}${arm ? ` (Arm ${arm})` : ''}</div>` +
222
- `<div style="padding-top:6px;border-top:1px solid var(--border-color);"><strong>Position</strong> X ${X[i].toFixed(2)} Β· <strong>Y</strong> ${Y[i].toFixed(2)}</div>` +
223
- `<div><strong>Distance</strong> Radius ${r.toFixed(3)} Β· <strong>Z</strong> ${Zraw[i].toFixed(3)}</div>` +
224
- `<div><strong>Size</strong> ${sizesPx[i].toFixed(1)} px</div>`;
225
- tip.style.opacity = '1';
226
- })
227
- .on('mousemove', (ev, i) => {
228
- const [mx, my] = d3.pointer(ev, container);
229
- const offsetX = 10, offsetY = 12;
230
- tip.style.transform = `translate(${Math.round(mx + offsetX)}px, ${Math.round(my + offsetY)}px)`;
231
- })
232
- .on('mouseleave', function () {
233
- tip.style.opacity = '0';
234
- tip.style.transform = 'translate(-9999px, -9999px)';
235
- d3.select(this)
236
- .style('filter', null)
237
- .transition().duration(120).ease(d3.easeCubicOut)
238
- .attr('r', (i2) => sizesPx[i2] / 2)
239
- .attr('fill-opacity', 0.9);
240
- })
241
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
242
  };
243
 
244
- // First render + resize
245
  if (window.ResizeObserver) {
246
- const ro = new ResizeObserver(() => render());
247
- ro.observe(container);
248
  } else {
249
  window.addEventListener('resize', render);
250
  }
@@ -255,4 +343,4 @@
255
  document.addEventListener('DOMContentLoaded', () => ensureD3(bootstrap), { once: true });
256
  } else { ensureD3(bootstrap); }
257
  })();
258
- </script>
 
1
+ <div class="d3-synth-scale" style="width:100%;margin:0;aspect-ratio:2.5/1;min-height:300px;"></div>
2
  <script>
3
  (() => {
4
  const ensureD3 = (cb) => {
5
  if (window.d3 && typeof window.d3.select === 'function') return cb();
6
  let s = document.getElementById('d3-cdn-script');
7
+ if (!s) { s = document.createElement('script'); s.id = 'd3-cdn-script'; s.src = 'https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js'; document.head.appendChild(s); }
 
 
 
 
 
8
  const onReady = () => { if (window.d3 && typeof window.d3.select === 'function') cb(); };
9
  s.addEventListener('load', onReady, { once: true });
10
  if (window.d3) onReady();
 
12
 
13
  const bootstrap = () => {
14
  const mount = document.currentScript ? document.currentScript.previousElementSibling : null;
15
+ const container = (mount && mount.querySelector && mount.querySelector('.d3-synth-scale')) ||
16
+ Array.from(document.querySelectorAll('.d3-synth-scale')).find(el => el.dataset.mounted !== 'true');
17
  if (!container) return;
 
18
  if (container.dataset.mounted === 'true') return;
19
  container.dataset.mounted = 'true';
20
+ container.style.background = 'transparent';
21
+ container.style.overflow = 'visible';
22
+ container.style.position = 'relative';
 
 
 
 
 
 
23
 
24
+ const raw = [
25
+ ["Faq","Format","FW-Edu HQ","gemma-3-1b-it","Gemma",17.674,22.215,20.15,0.711,-0.346],
26
+ ["Guided Rewrite","REWIRE","FW-Edu HQ","gemma-3-1b-it","Gemma",8.690,10.303,20.15,0.278,-1.012],
27
+ ["Guided Rewrite","REWIRE","FW-Edu HQ","gemma-3-27b-it","Gemma",8.429,8.644,19.95,0.546,-1.228],
28
+ ["Faq","Format","FW-Edu HQ","Llama-3.2-1B-Instruct","Llama",6.290,11.620,20.15,0.735,-0.375],
29
+ ["Tutorial","Format","FW-Edu HQ","gemma-3-12b-it","Gemma",5.326,6.568,20.15,0.398,-0.077],
30
+ ["Tutorial","Format","FW-Edu HQ","Falcon3-1B-Instruct","Falcon",4.965,7.578,20.15,0.447,-0.145],
31
+ ["Tutorial","Format","FW-Edu HQ","SmolLM2-135M-Instruct","SmolLM2",4.510,6.987,20.15,0.423,-0.572],
32
+ ["Article","Format","FW-Edu HQ","gemma-3-1b-it","Gemma",3.999,5.659,19.74,-0.018,-0.432],
33
+ ["Discussion","Format","FW-Edu HQ","gemma-3-1b-it","Gemma",3.958,6.248,20.15,0.036,-0.574],
34
+ ["Tutorial","Format","FW-Edu HQ","Qwen3-1.7B","Qwen",3.555,5.695,20.15,0.281,0.056],
35
+ ["Tutorial","Format","FW-Edu HQ","granite-3.1-1b-a400m-instruct","Granite",3.512,6.417,20.15,0.449,-0.072],
36
+ ["Wikipedia Style","Nemotron","FW-Edu HQ","gemma-3-1b-it","Gemma",3.426,8.366,20.15,0.297,-0.193],
37
+ ["Faq","Format","FW-Edu HQ","Falcon3-1B-Instruct","Falcon",3.412,4.842,20.15,0.691,-0.249],
38
+ ["Faq","Format","FW-Edu HQ","gemma-3-12b-it","Gemma",3.220,4.062,20.15,0.712,-0.295],
39
+ ["Tutorial","Format","FW-Edu HQ","SmolLM2-360M-Instruct","SmolLM2",3.145,6.521,20.15,0.441,-0.565],
40
+ ["Tutorial","Format","FW-Edu HQ","Qwen2-1.5B-Instruct","Qwen",3.093,8.000,20.15,0.527,-0.010],
41
+ ["Knowledge List","Nemotron","FW-Edu HQ","gemma-3-1b-it","Gemma",2.901,9.154,20.15,0.589,-0.193],
42
+ ["Discussion","Format","FW-Edu HQ","Qwen3-1.7B","Qwen",2.896,7.202,20.15,0.715,-0.122],
43
+ ["Faq","Format","FW-Edu HQ","Qwen3-1.7B","Qwen",2.880,4.775,20.15,0.632,-0.219],
44
+ ["Faq","Format","FW-Edu HQ","granite-3.1-1b-a400m-instruct","Granite",2.554,4.173,20.15,0.680,-0.166],
45
+ ["Tutorial","Format","FW-Edu HQ","gemma-3-27b-it","Gemma",2.545,3.289,20.15,0.485,-0.057],
46
+ ["Math","Format","FW-Edu HQ","gemma-3-12b-it","Gemma",2.506,5.926,20.15,0.755,-0.370],
47
+ ["Math","Format","FW-Edu HQ","gemma-3-1b-it","Gemma",2.455,5.081,20.15,0.620,-0.174],
48
+ ["Extract Knowledge","Nemotron","FW-Edu HQ","gemma-3-1b-it","Gemma",2.412,6.592,20.15,0.254,0.025],
49
+ ["Tutorial","Format","FW-Edu HQ","Llama-3.2-1B-Instruct","Llama",2.361,4.884,20.15,0.557,-0.031],
50
+ ["Tutorial","Format","FW-Edu LQ","gemma-3-12b-it","Gemma",2.352,2.574,32.89,0.189,0.379],
51
+ ["Tutorial","Format","FW-Edu HQ","Qwen2.5-1.5B-Instruct","Qwen",2.269,4.874,20.15,0.576,-0.083],
52
+ ["Tutorial","Format","FW-Edu HQ","Qwen1.5-1.8B-Chat","Qwen",2.222,2.404,20.15,0.647,0.072],
53
+ ["Distill","Nemotron","FW-Edu HQ","gemma-3-1b-it","Gemma",2.115,5.375,20.15,0.331,-0.132],
54
+ ["Diverse QA","Nemotron","FW-Edu HQ","gemma-3-1b-it","Gemma",1.973,7.013,20.15,0.779,-0.208],
55
+ ["Faq","Format","FW-Edu LQ","gemma-3-12b-it","Gemma",1.909,1.902,32.89,0.799,0.357],
56
+ ["Tutorial","Format","FW-Edu LQ","gemma-3-1b-it","Gemma",1.903,2.513,32.89,0.172,0.413],
57
+ ["Faq","Format","FW-Edu LQ","gemma-3-1b-it","Gemma",1.838,1.747,32.89,0.690,0.369],
58
+ ["Guided Rewrite","REWIRE","FW-Edu HQ","gemma-3-12b-it","Gemma",1.776,1.718,20.15,0.607,-1.214],
59
+ ["Table","Format","FW-Edu HQ","gemma-3-1b-it","Gemma",1.682,5.619,20.15,0.715,-0.776],
60
+ ["Tutorial","Format","DCLM","gemma-3-1b-it","Gemma",1.668,3.305,29.48,0.371,0.528],
61
+ ["Tutorial","Format","FW-Edu HQ","gemma-3-270m-it","Gemma",1.639,1.709,20.15,0.514,-0.337],
62
+ ["Commentary","Format","FW-Edu HQ","Qwen3-1.7B","Qwen",1.636,5.944,20.15,0.630,-0.229],
63
+ ["Guided Rewrite+","REWIRE","FW-Edu HQ","gemma-3-1b-it","Gemma",1.634,3.421,20.15,0.097,-0.428],
64
+ ["Math","Format","FW-Edu HQ","granite-3.1-1b-a400m-instruct","Granite",1.575,3.347,20.15,0.698,-0.324],
65
+ ["Tutorial","Format","FW-Edu HQ","SmolLM2-1.7B-Instruct","SmolLM2",1.521,3.380,20.15,0.439,-0.244],
66
+ ["Faq","Format","DCLM","gemma-3-1b-it","Gemma",1.519,2.084,29.48,0.709,0.346],
67
+ ["Tutorial","Format","FW-Edu HQ","gemma-3-1b-it","Gemma",1.507,2.705,19.37,0.074,-0.077],
68
+ ["Tutorial","Format","FW-Edu HQ","gemma-3-4b-it","Gemma",1.496,2.021,20.15,0.404,-0.049],
69
+ ["Commentary","Format","DCLM","gemma-3-1b-it","Gemma",1.456,5.523,29.48,0.202,0.359],
70
+ ["Guided Rewrite+","REWIRE","FW-Edu HQ","gemma-3-12b-it","Gemma",1.449,2.139,20.15,0.222,-0.102],
71
+ ["Table","Format","FW-Edu HQ","granite-3.1-1b-a400m-instruct","Granite",1.440,3.898,20.15,0.726,-0.597],
72
+ ["Math","Format","FW-Edu HQ","Falcon3-1B-Instruct","Falcon",1.422,2.166,20.15,0.704,-0.156],
73
+ ["Faq","Format","Cosmopedia","gemma-3-1b-it","Gemma",1.328,1.314,31.06,0.329,-0.264],
74
+ ["Math","Format","FW-Edu HQ","gemma-3-270m-it","Gemma",1.209,1.331,20.15,0.600,-0.406],
75
+ ["Math","Format","FW-Edu HQ","gemma-3-27b-it","Gemma",1.052,2.621,20.15,0.754,-0.429],
76
+ ["Tutorial","Format","Cosmopedia","gemma-3-1b-it","Gemma",1.016,1.468,31.06,-0.049,-0.080],
77
+ ["Guided Rewrite","REWIRE","FW-Edu HQ","gemma-3-4b-it","Gemma",0.911,1.097,20.15,0.436,-0.919],
78
+ ["Faq","Format","FW-Edu HQ","SmolLM2-1.7B-Instruct","SmolLM2",0.886,2.002,20.15,0.648,-0.406],
79
+ ["Table","Format","FW-Edu HQ","Llama-3.2-1B-Instruct","Llama",0.877,2.734,20.15,0.772,-0.815],
80
+ ["Math","Format","FW-Edu HQ","Llama-3.2-1B-Instruct","Llama",0.864,2.170,20.15,0.737,-0.384],
81
+ ["Table","Format","FW-Edu HQ","Qwen3-1.7B","Qwen",0.796,2.692,20.15,0.751,-0.638],
82
+ ["Guided Rewrite","REWIRE","FW-Edu HQ","gemma-3-270m-it","Gemma",0.775,1.708,20.15,0.755,-2.407],
83
+ ["Table","Format","FW-Edu HQ","gemma-3-12b-it","Gemma",0.764,2.491,20.15,0.719,-0.774],
84
+ ["Math","Format","FW-Edu HQ","gemma-3-4b-it","Gemma",0.676,1.749,20.15,0.743,-0.335],
85
+ ["Table","Format","FW-Edu HQ","Falcon3-1B-Instruct","Falcon",0.655,2.000,20.15,0.722,-0.748],
86
+ ["Math","Format","FW-Edu HQ","SmolLM2-1.7B-Instruct","SmolLM2",0.642,2.277,20.15,0.657,-0.669],
87
+ ["Commentary","Format","FW-Edu HQ","gemma-3-1b-it","Gemma",0.640,2.417,20.15,0.055,-0.517],
88
+ ["Math","Format","FW-Edu HQ","Qwen3-1.7B","Qwen",0.560,1.997,20.15,0.742,-0.310],
89
+ ["Table","Format","FW-Edu HQ","SmolLM2-1.7B-Instruct","SmolLM2",0.407,1.520,20.15,0.668,-0.943]
90
+ ];
91
 
92
+ let animFrame = null;
 
 
 
 
 
 
 
 
 
93
 
94
+ const render = () => {
95
+ const W = container.clientWidth || 900;
96
+ const H = container.clientHeight || 360;
97
+ if (animFrame) { cancelAnimationFrame(animFrame); animFrame = null; }
98
+ container.innerHTML = '';
99
 
100
+ const canvas = d3.select(container).append('svg')
101
+ .attr('width', W).attr('height', H)
102
+ .style('font-family', "'Inter', system-ui, -apple-system, sans-serif");
 
 
 
 
103
 
104
+ // Color by model family β€” more meaningful for readers
105
+ const col = {
106
+ Gemma: '#5b9bd5',
107
+ Qwen: '#e07b54',
108
+ Llama: '#8bc474',
109
+ Falcon: '#c9a046',
110
+ Granite: '#9a8ec2',
111
+ SmolLM2: '#e06b9e'
112
+ };
113
+ const fillAlpha = 0.22;
114
 
115
+ const data = raw.map((d, i) => ({
116
+ id: i, prompt: d[0], cat: d[1], source: d[2],
117
+ model: d[3], family: d[4],
118
+ compB: d[5], promptB: d[6], docsM: d[7],
119
+ dclm: d[8], edu: d[9],
120
+ phase: (i * 2.399) % (Math.PI * 2)
121
+ }));
122
 
123
+ // ─── LAYOUT β€” unified composition centered at one point ───
124
+ const cx = W / 2;
 
 
 
 
 
 
125
 
126
+ // Sizing β€” slightly bigger
127
+ const packR = Math.min(W * 0.28, H * 0.34);
128
+ const maxR = Math.min(packR * 0.37, 56);
129
+ const minR = Math.max(4, W * 0.005);
130
+ const rScale = d3.scaleSqrt()
131
+ .domain([0, d3.max(data, d => d.compB)])
132
+ .range([minR, maxR]);
133
 
134
+ // Mega text metrics
135
+ const megaFS = Math.min(W * 0.15, H * 0.24, 150);
136
+ const subFS = Math.max(8, megaFS * 0.1);
137
+ // Mega number is the anchor β€” slightly below center
138
+ // Bubbles gravitate around it, subtitle sits below
139
+ const megaY = H * 0.48;
140
+ const subY = H * 0.87;
141
+ const packCY = megaY;
 
 
142
 
143
+ data.forEach(d => {
144
+ d.r = rScale(d.compB);
145
+ d.x = cx;
146
+ d.y = packCY;
147
+ });
148
 
149
+ // Two focal points for oval spread β€” wider
150
+ const spread = Math.min(W * 0.13, 110);
151
+ const focalL = cx - spread;
152
+ const focalR = cx + spread;
153
 
154
+ const sim = d3.forceSimulation(data)
155
+ .alphaDecay(0.012)
156
+ .velocityDecay(0.32)
157
+ .force('x', d3.forceX(d => (d.id % 2 === 0) ? focalL : focalR).strength(0.02))
158
+ .force('y', d3.forceY(packCY).strength(0.05))
159
+ .force('collide', d3.forceCollide(d => d.r + 1.5).strength(0.8).iterations(3))
160
+ .stop();
161
 
162
+ for (let i = 0; i < 250; i++) sim.tick();
 
 
 
163
 
164
+ // ─── Now render with already-settled positions ───
165
+ const gB = canvas.append('g');
166
 
167
+ const circles = gB.selectAll('circle')
168
+ .data(data).join('circle')
169
+ .attr('cx', d => d.x).attr('cy', d => d.y)
170
+ .attr('r', d => d.r)
171
+ .attr('fill', d => col[d.family])
172
+ .attr('fill-opacity', fillAlpha)
173
+ .attr('stroke', d => col[d.family])
174
+ .attr('stroke-width', 0.7)
175
+ .attr('stroke-opacity', 0.12)
176
+ .style('cursor', 'pointer');
177
 
178
+ // Resume remaining simulation live
179
+ sim.on('tick', () => {
180
+ circles
181
+ .attr('cx', d => d.x)
182
+ .attr('cy', d => d.y);
183
+ })
184
+ .restart();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185
 
186
+ // After sim settles, start gentle float
187
+ sim.on('end', () => {
188
+ data.forEach(d => { d.ox = d.x; d.oy = d.y; });
189
+ const drift = () => {
190
+ const t = Date.now() * 0.001;
191
+ circles.each(function(d) {
192
+ const dy = Math.sin(t * 0.22 + d.phase) * 1.2;
193
+ d3.select(this).attr('cy', d.oy + dy);
194
+ });
195
+ animFrame = requestAnimationFrame(drift);
196
+ };
197
+ drift();
198
+ });
199
 
200
+ // ─── MEGA NUMBER β€” centered on same X as pack ───
201
+ canvas.append('text')
202
+ .attr('x', cx).attr('y', megaY)
203
+ .attr('text-anchor', 'middle').attr('dominant-baseline', 'middle')
204
+ .attr('fill', '#1a1a1a')
205
+ .attr('font-size', megaFS)
206
+ .attr('font-weight', 900)
207
+ .attr('letter-spacing', '-0.04em')
208
+ .text('164.8B');
209
+
210
+ // "tokens generated" right below the number
211
+ const labelFS = Math.max(10, megaFS * 0.15);
212
+ canvas.append('text')
213
+ .attr('x', cx).attr('y', megaY + megaFS * 0.44)
214
+ .attr('text-anchor', 'middle').attr('dominant-baseline', 'middle')
215
+ .attr('fill', '#999')
216
+ .attr('font-size', labelFS)
217
+ .attr('font-weight', 600)
218
+ .attr('letter-spacing', '0.18em')
219
+ .text('TOKENS GENERATED');
220
+
221
+ // Bottom stats line
222
+ const subText = '65 EXPERIMENTS \u00B7 1.41B DOCUMENTS';
223
+ const subEl = canvas.append('text')
224
+ .attr('x', cx).attr('y', subY)
225
+ .attr('text-anchor', 'middle').attr('dominant-baseline', 'middle')
226
+ .attr('fill', '#555')
227
+ .attr('font-size', subFS)
228
+ .attr('font-weight', 500)
229
+ .attr('letter-spacing', '0.14em')
230
+ .text(subText);
231
+ // ─── LEGEND β€” below subtitle ───
232
+ const legFS = Math.max(7, subFS * 0.95);
233
+ const dotR = Math.max(2.5, legFS * 0.38);
234
+ const legY = subY + subFS * 2.6;
235
+ const familyCounts = {};
236
+ data.forEach(d => { familyCounts[d.family] = (familyCounts[d.family] || 0) + 1; });
237
+ const families = Object.entries(familyCounts)
238
+ .sort((a, b) => b[1] - a[1])
239
+ .map(([n, c]) => ({ n, c }));
240
+
241
+ const legG = canvas.append('g');
242
+ let tw = 0;
243
+ families.forEach(it => { tw += dotR * 2 + 4 + (it.n.length + 4) * legFS * 0.55 + 12; });
244
+ tw -= 12;
245
+ let lx = cx - tw / 2;
246
+ families.forEach(it => {
247
+ legG.append('circle')
248
+ .attr('cx', lx + dotR).attr('cy', legY)
249
+ .attr('r', dotR).attr('fill', col[it.n]).attr('fill-opacity', 0.5);
250
+ const t = legG.append('text')
251
+ .attr('x', lx + dotR * 2 + 4).attr('y', legY)
252
+ .attr('dominant-baseline', 'middle')
253
+ .attr('fill', '#555').attr('font-size', legFS)
254
+ .attr('font-weight', 500)
255
+ .text(`${it.n} (${it.c})`);
256
+ lx += dotR * 2 + 4 + t.node().getComputedTextLength() + 12;
257
+ });
258
+
259
+ // Background pill behind subtitle (per line)
260
+ const subBBox = subEl.node().getBBox();
261
+ canvas.insert('rect', 'text:first-of-type')
262
+ .attr('x', subBBox.x - 8).attr('y', subBBox.y - 3)
263
+ .attr('width', subBBox.width + 16).attr('height', subBBox.height + 6)
264
+ .attr('rx', 4).attr('ry', 4)
265
+ .attr('fill', 'white').attr('fill-opacity', 0.75);
266
+ // Background pill behind legend (per line)
267
+ const legBBox = legG.node().getBBox();
268
+ canvas.insert('rect', 'text:first-of-type')
269
+ .attr('x', legBBox.x - 8).attr('y', legBBox.y - 3)
270
+ .attr('width', legBBox.width + 16).attr('height', legBBox.height + 6)
271
+ .attr('rx', 4).attr('ry', 4)
272
+ .attr('fill', 'white').attr('fill-opacity', 0.75);
273
+
274
+ // ─── TOOLTIP ───
275
+ const tip = d3.select(container).append('div')
276
+ .style('position', 'absolute').style('pointer-events', 'none')
277
+ .style('background', 'rgba(255,255,255,0.96)')
278
+ .style('backdrop-filter', 'blur(10px)')
279
+ .style('color', '#2c3e50')
280
+ .style('border', '1px solid rgba(0,0,0,0.08)')
281
+ .style('border-radius', '10px').style('padding', '12px 16px')
282
+ .style('font-size', Math.max(10, W * 0.011) + 'px')
283
+ .style('font-family', "'Inter', system-ui, sans-serif")
284
+ .style('line-height', '1.55').style('white-space', 'nowrap')
285
+ .style('opacity', 0).style('transition', 'opacity .1s ease')
286
+ .style('z-index', 10)
287
+ .style('box-shadow', '0 4px 24px rgba(0,0,0,0.08)');
288
+
289
+ circles
290
+ .on('mouseenter', function(event, d) {
291
+ d3.select(this)
292
+ .transition().duration(80)
293
+ .attr('fill-opacity', 0.55)
294
+ .attr('stroke-opacity', 0.5)
295
+ .attr('stroke-width', 1.5);
296
+ gB.selectAll('circle')
297
+ .filter(o => o.id !== d.id)
298
+ .transition().duration(80)
299
+ .attr('fill-opacity', 0.06).attr('stroke-opacity', 0.03);
300
+ const c = col[d.family];
301
+ const dc = d.dclm >= 0 ? '#16a34a' : '#dc2626';
302
+ const ec = d.edu >= 0 ? '#16a34a' : '#dc2626';
303
+ tip.html(
304
+ `<div style="display:flex;align-items:center;gap:6px;margin-bottom:4px">` +
305
+ `<span style="width:8px;height:8px;border-radius:50%;background:${c};opacity:.6;display:inline-block"></span>` +
306
+ `<span style="font-weight:700;font-size:1.05em;color:#111">${d.model}</span></div>` +
307
+ `<div style="opacity:.4;font-size:.88em;margin-bottom:7px">${d.prompt} Β· ${d.cat} Β· ${d.source}</div>` +
308
+ `<div style="display:grid;grid-template-columns:auto 1fr;gap:3px 14px;font-size:.9em">` +
309
+ `<span style="opacity:.35">Output</span><span style="font-weight:600;color:#111">${d.compB.toFixed(2)}B tokens</span>` +
310
+ `<span style="opacity:.35">Input</span><span>${d.promptB.toFixed(2)}B</span>` +
311
+ `<span style="opacity:.35">Docs</span><span>${d.docsM.toFixed(1)}M</span>` +
312
+ `<span style="opacity:.35">DCLM</span><span style="color:${dc}">${d.dclm >= 0 ? '+' : ''}${d.dclm.toFixed(3)}</span>` +
313
+ `<span style="opacity:.35">Edu</span><span style="color:${ec}">${d.edu >= 0 ? '+' : ''}${d.edu.toFixed(3)}</span></div>`
314
+ ).style('opacity', 1);
315
+ })
316
+ .on('mousemove', function(event) {
317
+ const r = container.getBoundingClientRect();
318
+ let tx = event.clientX - r.left + 14;
319
+ let ty = event.clientY - r.top - 10;
320
+ if (tx + 260 > W) tx = event.clientX - r.left - 270;
321
+ if (ty < 8) ty = 8;
322
+ tip.style('left', tx + 'px').style('top', ty + 'px');
323
+ })
324
+ .on('mouseleave', function() {
325
+ gB.selectAll('circle')
326
+ .transition().duration(160)
327
+ .attr('fill-opacity', fillAlpha)
328
+ .attr('stroke-opacity', 0.12)
329
+ .attr('stroke-width', 0.7);
330
+ tip.style('opacity', 0);
331
+ });
332
  };
333
 
 
334
  if (window.ResizeObserver) {
335
+ new ResizeObserver(() => render()).observe(container);
 
336
  } else {
337
  window.addEventListener('resize', render);
338
  }
 
343
  document.addEventListener('DOMContentLoaded', () => ensureD3(bootstrap), { once: true });
344
  } else { ensureD3(bootstrap); }
345
  })();
346
+ </script>