firobeid commited on
Commit
b22e88e
·
verified ·
1 Parent(s): 79030e9

Upload index.html

Browse files
Files changed (1) hide show
  1. index.html +405 -18
index.html CHANGED
@@ -1,19 +1,406 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
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>RoPE Full Rotation Matrix</title>
7
+ <style>
8
+ :root {
9
+ --color-background-primary: #ffffff;
10
+ --color-background-secondary: #f5f4f0;
11
+ --color-background-tertiary: #eeecea;
12
+ --color-background-info: #e6f1fb;
13
+ --color-text-primary: #1a1a18;
14
+ --color-text-secondary: #73726c;
15
+ --color-text-info: #185fa5;
16
+ --color-border-tertiary: rgba(0,0,0,0.12);
17
+ --color-border-secondary: rgba(0,0,0,0.22);
18
+ --color-border-info: #185fa5;
19
+ --font-sans: "Anthropic Sans", system-ui, sans-serif;
20
+ }
21
+ @media (prefers-color-scheme: dark) {
22
+ :root {
23
+ --color-background-primary: #1e1e1c;
24
+ --color-background-secondary: #2a2a27;
25
+ --color-background-tertiary: #222220;
26
+ --color-background-info: #0c2a42;
27
+ --color-text-primary: #e8e6de;
28
+ --color-text-secondary: #9c9a92;
29
+ --color-text-info: #85b7eb;
30
+ --color-border-tertiary: rgba(255,255,255,0.12);
31
+ --color-border-secondary: rgba(255,255,255,0.22);
32
+ --color-border-info: #378add;
33
+ }
34
+ }
35
+ * { box-sizing: border-box; margin: 0; padding: 0; }
36
+ body { font-family: var(--font-sans); background: var(--color-background-primary); color: var(--color-text-primary); padding: 1.5rem; }
37
+ .pe-wrap { padding: 0.5rem 0 1rem; }
38
+ .pe-title { font-size: 15px; font-weight: 500; color: var(--color-text-primary); margin-bottom: 4px; }
39
+ .pe-sub { font-size: 12px; color: var(--color-text-secondary); margin-bottom: 14px; line-height: 1.6; }
40
+ .pe-axis-label { font-size: 12px; color: var(--color-text-secondary); text-align: center; }
41
+ .pe-y-label { writing-mode: vertical-rl; transform: rotate(180deg); font-size: 12px; color: var(--color-text-secondary); display: flex; align-items: center; justify-content: center; min-width: 20px; }
42
+ .pe-row { display: flex; align-items: stretch; gap: 8px; }
43
+ .pe-legend { display: flex; align-items: center; gap: 8px; margin-top: 8px; font-size: 11px; color: var(--color-text-secondary); }
44
+ .annotation-box { background: var(--color-background-secondary); border: 0.5px solid var(--color-border-tertiary); border-radius: 8px; padding: 10px 14px; margin-top: 12px; font-size: 12px; color: var(--color-text-secondary); line-height: 1.6; }
45
+ .ann-row { display: flex; gap: 16px; }
46
+ .ann-col { flex: 1; }
47
+ .ann-head { font-weight: 500; font-size: 12px; color: var(--color-text-primary); margin-bottom: 3px; }
48
+ .controls-row { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 8px; align-items: center; }
49
+ .pe-btn { background: var(--color-background-secondary); border: 0.5px solid var(--color-border-secondary); border-radius: 6px; padding: 4px 11px; font-size: 12px; cursor: pointer; color: var(--color-text-primary); }
50
+ .pe-btn.active { background: var(--color-background-info); color: var(--color-text-info); border-color: var(--color-border-info); }
51
+ .tooltip { position: absolute; background: var(--color-background-primary); border: 0.5px solid var(--color-border-secondary); border-radius: 6px; padding: 6px 10px; font-size: 11px; color: var(--color-text-primary); pointer-events: none; opacity: 0; transition: opacity 0.15s; white-space: nowrap; z-index: 10; line-height: 1.6; }
52
+ .badge { display: inline-block; font-size: 10px; padding: 2px 6px; border-radius: 4px; background: var(--color-background-info); color: var(--color-text-info); border: 0.5px solid var(--color-border-info); margin-left: 6px; }
53
+ .divider { border: none; border-top: 0.5px solid var(--color-border-tertiary); margin: 8px 0; }
54
+ .dual-wrap { display: flex; gap: 6px; flex: 1; }
55
+ .matrix-panel { flex: 1; position: relative; }
56
+ </style>
57
+ </head>
58
+ <body>
59
+ <div class="pe-wrap">
60
+ <div class="pe-title">RoPE — Full Rotation Matrix <span class="badge">Rotary Position Encoding</span></div>
61
+ <div class="pe-sub">
62
+ RoPE rotates each dimension pair (q<sub>2i</sub>, q<sub>2i+1</sub>) by angle <strong>pos · θ<sub>i</sub></strong> using a 2×2 rotation matrix — requiring both <strong style="color:#0F6E56">cos</strong> and <strong style="color:#BA7517">sin</strong>. Toggle between three views of the same underlying data.
63
+ </div>
64
+
65
+ <div class="controls-row">
66
+ <span style="font-size:12px;color:var(--color-text-secondary)">View:</span>
67
+ <button class="pe-btn active" id="btn-view-sidebyside" onclick="setView('sidebyside')">cos | sin side by side</button>
68
+ <button class="pe-btn" id="btn-view-interleaved" onclick="setView('interleaved')">interleaved (as in code)</button>
69
+ <button class="pe-btn" id="btn-view-angle" onclick="setView('angle')">rotation angle φ = pos·θ</button>
70
+ </div>
71
+ <div class="controls-row" style="margin-bottom:10px">
72
+ <span style="font-size:12px;color:var(--color-text-secondary)">Hover mode:</span>
73
+ <button class="pe-btn active" id="btn-cell" onclick="setMode('cell')">Cell value</button>
74
+ <button class="pe-btn" id="btn-col" onclick="setMode('col')">Column (dim pair)</button>
75
+ <button class="pe-btn" id="btn-row" onclick="setMode('row')">Row (position)</button>
76
+ </div>
77
+
78
+ <!-- Side-by-side view -->
79
+ <div id="view-sidebyside">
80
+ <div style="display:flex;gap:6px;margin-left:28px;margin-bottom:3px">
81
+ <div style="flex:1;text-align:center;font-size:11px;font-weight:500;color:#0F6E56;background:rgba(29,158,117,0.1);border-radius:4px;padding:2px 0">cos(pos · θ<sub>i</sub>)</div>
82
+ <div style="flex:1;text-align:center;font-size:11px;font-weight:500;color:#854F0B;background:rgba(186,117,23,0.1);border-radius:4px;padding:2px 0">sin(pos · θ<sub>i</sub>)</div>
83
+ </div>
84
+ <div class="pe-row">
85
+ <div class="pe-y-label">Sequence position <em>(pos ↓)</em></div>
86
+ <div class="dual-wrap">
87
+ <div class="matrix-panel">
88
+ <canvas id="pe-canvas-cos" style="width:100%;display:block;cursor:crosshair;"></canvas>
89
+ <div class="tooltip" id="tt-cos"></div>
90
+ </div>
91
+ <div style="width:1px;background:var(--color-border-tertiary);margin:0 1px;flex-shrink:0"></div>
92
+ <div class="matrix-panel">
93
+ <canvas id="pe-canvas-sin" style="width:100%;display:block;cursor:crosshair;"></canvas>
94
+ <div class="tooltip" id="tt-sin"></div>
95
+ </div>
96
+ </div>
97
+ </div>
98
+ <div style="margin-left:28px">
99
+ <div class="pe-axis-label">Dimension pair index <em>(i →)</em></div>
100
+ <div style="display:flex;gap:6px;margin-top:2px">
101
+ <div style="flex:1;display:flex;justify-content:space-between;font-size:10px;color:var(--color-text-secondary)"><span>0 (fast)</span><span>64</span><span>127 (slow)</span></div>
102
+ <div style="width:3px;flex-shrink:0"></div>
103
+ <div style="flex:1;display:flex;justify-content:space-between;font-size:10px;color:var(--color-text-secondary)"><span>0 (fast)</span><span>64</span><span>127 (slow)</span></div>
104
+ </div>
105
+ </div>
106
+ <div class="pe-legend" style="margin-left:28px">
107
+ <span>−1</span>
108
+ <canvas id="legend-bar-sbs" height="12" style="flex:1;border-radius:3px;"></canvas>
109
+ <span>+1</span>
110
+ </div>
111
+ </div>
112
+
113
+ <!-- Interleaved view -->
114
+ <div id="view-interleaved" style="display:none">
115
+ <div style="margin-left:28px;margin-bottom:3px;font-size:11px;color:var(--color-text-secondary)">
116
+ Columns alternate <strong style="color:#0F6E56">cos</strong>, <strong style="color:#854F0B">sin</strong>, <strong style="color:#0F6E56">cos</strong>, <strong style="color:#854F0B">sin</strong>… matching how <code>cos_pos</code> and <code>sin_pos</code> are broadcast in the code via <code>repeat(..., rep=2)</code>.
117
+ </div>
118
+ <div class="pe-row">
119
+ <div class="pe-y-label">Sequence position <em>(pos ↓)</em></div>
120
+ <div style="flex:1;position:relative;">
121
+ <canvas id="pe-canvas-interleaved" style="width:100%;display:block;cursor:crosshair;"></canvas>
122
+ <div class="tooltip" id="tt-il"></div>
123
+ </div>
124
+ </div>
125
+ <div style="margin-left:28px">
126
+ <div class="pe-axis-label">Embedding dimension d (pairs of columns = one rotation)</div>
127
+ <div style="display:flex;justify-content:space-between;font-size:10px;color:var(--color-text-secondary);margin-top:2px"><span>d=0 cos</span><span>d=128</span><span>d=255 sin</span></div>
128
+ </div>
129
+ <div class="pe-legend" style="margin-left:28px">
130
+ <span>−1</span>
131
+ <canvas id="legend-bar-il" height="12" style="flex:1;border-radius:3px;"></canvas>
132
+ <span>+1</span>
133
+ </div>
134
+ </div>
135
+
136
+ <!-- Angle view -->
137
+ <div id="view-angle" style="display:none">
138
+ <div style="margin-left:28px;margin-bottom:3px;font-size:11px;color:var(--color-text-secondary)">
139
+ The raw rotation angle φ = pos · θ<sub>i</sub> in radians. Both cos and sin are derived from this single scalar. Colour = angle magnitude (dark = 0, bright = max).
140
+ </div>
141
+ <div class="pe-row">
142
+ <div class="pe-y-label">Sequence position <em>(pos ↓)</em></div>
143
+ <div style="flex:1;position:relative;">
144
+ <canvas id="pe-canvas-angle" style="width:100%;display:block;cursor:crosshair;"></canvas>
145
+ <div class="tooltip" id="tt-ang"></div>
146
+ </div>
147
+ </div>
148
+ <div style="margin-left:28px">
149
+ <div class="pe-axis-label">Dimension pair index <em>(i →)</em></div>
150
+ <div style="display:flex;justify-content:space-between;font-size:10px;color:var(--color-text-secondary);margin-top:2px"><span>0 (fast, large θ)</span><span>64</span><span>127 (slow, tiny θ)</span></div>
151
+ </div>
152
+ <div class="pe-legend" style="margin-left:28px">
153
+ <span>0 rad</span>
154
+ <canvas id="legend-bar-ang" height="12" style="flex:1;border-radius:3px;"></canvas>
155
+ <span id="ang-max-label">max rad</span>
156
+ </div>
157
+ </div>
158
+
159
+ <div class="annotation-box" id="ann-box"></div>
160
+ </div>
161
+
162
+ <script>
163
+ const ROWS = 50, COLS = 128, D_MODEL = 256;
164
+ const isDark = matchMedia('(prefers-color-scheme: dark)').matches;
165
+
166
+ function theta(i) { return Math.pow(10000, -(2 * i) / D_MODEL); }
167
+
168
+ const cosMatrix = [], sinMatrix = [], ilMatrix = [], angMatrix = [];
169
+ const maxAngle = (ROWS - 1) * theta(0);
170
+
171
+ for (let r = 0; r < ROWS; r++) {
172
+ cosMatrix[r] = []; sinMatrix[r] = []; ilMatrix[r] = []; angMatrix[r] = [];
173
+ for (let c = 0; c < COLS; c++) {
174
+ const phi = r * theta(c);
175
+ cosMatrix[r][c] = Math.cos(phi);
176
+ sinMatrix[r][c] = Math.sin(phi);
177
+ angMatrix[r][c] = phi;
178
+ }
179
+ for (let d = 0; d < 256; d++) {
180
+ const pair = Math.floor(d / 2), phi = r * theta(pair);
181
+ ilMatrix[r][d] = (d % 2 === 0) ? Math.cos(phi) : Math.sin(phi);
182
+ }
183
+ }
184
+
185
+ // Diverging color for trig values [-1, 1]: green for positive, purple for negative
186
+ function trig2color(v, alpha) {
187
+ const t = (v + 1) / 2;
188
+ if (isDark) {
189
+ if (t > 0.5) {
190
+ const s = (t - 0.5) * 2;
191
+ return `rgba(${Math.round(20+s*10)},${Math.round(60+s*150)},${Math.round(70+s*60)},${alpha})`;
192
+ } else {
193
+ const s = (0.5 - t) * 2;
194
+ return `rgba(${Math.round(40+s*120)},${Math.round(20+s*20)},${Math.round(70+s*150)},${alpha})`;
195
+ }
196
+ } else {
197
+ if (t > 0.5) {
198
+ const s = (t - 0.5) * 2;
199
+ return `rgba(${Math.round(245-s*200)},${Math.round(252-s*70)},${Math.round(240-s*170)},${alpha})`;
200
+ } else {
201
+ const s = (0.5 - t) * 2;
202
+ return `rgba(${Math.round(235-s*50)},${Math.round(225-s*170)},${Math.round(252-s*30)},${alpha})`;
203
+ }
204
+ }
205
+ }
206
+
207
+ // Sequential amber for angle values [0, maxAngle]
208
+ function angle2color(v, alpha) {
209
+ const t = Math.min(v / maxAngle, 1);
210
+ if (isDark) {
211
+ return `rgba(${Math.round(15+t*230)},${Math.round(15+t*150)},${Math.round(15+t*10)},${alpha})`;
212
+ } else {
213
+ return `rgba(${Math.round(252-t*30)},${Math.round(252-t*145)},${Math.round(252-t*235)},${alpha})`;
214
+ }
215
+ }
216
+
217
+ function drawCanvas(canvas, matrix, colorFn, cols, hc, hr) {
218
+ const w = canvas.offsetWidth;
219
+ if (!w) return;
220
+ const rows = matrix.length;
221
+ const h = Math.round(w * (rows / cols) * 1.4);
222
+ const dpr = window.devicePixelRatio || 1;
223
+ canvas.width = w * dpr; canvas.height = h * dpr;
224
+ canvas.style.height = h + 'px';
225
+ const ctx = canvas.getContext('2d');
226
+ ctx.scale(dpr, dpr);
227
+ const cw = w / cols, ch = h / rows;
228
+ const dim = hc >= 0 || hr >= 0;
229
+ for (let r = 0; r < rows; r++) {
230
+ for (let c = 0; c < cols; c++) {
231
+ const isHL = (hc >= 0 && c === hc) || (hr >= 0 && r === hr);
232
+ ctx.fillStyle = colorFn(matrix[r][c], dim && !isHL ? 0.28 : 1);
233
+ ctx.fillRect(c * cw, r * ch, cw + 0.5, ch + 0.5);
234
+ }
235
+ }
236
+ if (hc >= 0) { ctx.strokeStyle='#EF9F27'; ctx.lineWidth=2; ctx.strokeRect(hc*cw+1,1,cw-1,rows*ch-2); }
237
+ if (hr >= 0) { ctx.strokeStyle='#1D9E75'; ctx.lineWidth=2; ctx.strokeRect(1,hr*ch+1,cols*cw-2,ch-1); }
238
+ }
239
+
240
+ function drawLegend(id, colorFn, minV, maxV) {
241
+ const lb = document.getElementById(id); if (!lb) return;
242
+ const lctx = lb.getContext('2d');
243
+ const w = lb.offsetWidth || 300, dpr = window.devicePixelRatio || 1;
244
+ lb.width = w * dpr; lb.style.width = '100%';
245
+ for (let x = 0; x < w; x++) {
246
+ lctx.fillStyle = colorFn(minV + (x / w) * (maxV - minV), 1);
247
+ lctx.fillRect(x * dpr, 0, dpr, 12);
248
+ }
249
+ }
250
+
251
+ let currentView = 'sidebyside', mode = 'cell';
252
+ let hlCol = -1, hlRow = -1;
253
+
254
+ function redraw(hc, hr) {
255
+ if (hc !== undefined) { hlCol = hc; hlRow = hr; }
256
+ const hc_ = hlCol, hr_ = hlRow;
257
+ if (currentView === 'sidebyside') {
258
+ drawCanvas(document.getElementById('pe-canvas-cos'), cosMatrix, trig2color, COLS, hc_, hr_);
259
+ drawCanvas(document.getElementById('pe-canvas-sin'), sinMatrix, trig2color, COLS, hc_, hr_);
260
+ drawLegend('legend-bar-sbs', trig2color, -1, 1);
261
+ } else if (currentView === 'interleaved') {
262
+ const ilHc = hc_ >= 0 ? hc_ * 2 : -1;
263
+ drawCanvas(document.getElementById('pe-canvas-interleaved'), ilMatrix, trig2color, 256, ilHc, hr_);
264
+ drawLegend('legend-bar-il', trig2color, -1, 1);
265
+ } else {
266
+ drawCanvas(document.getElementById('pe-canvas-angle'), angMatrix, angle2color, COLS, hc_, hr_);
267
+ drawLegend('legend-bar-ang', angle2color, 0, maxAngle);
268
+ document.getElementById('ang-max-label').textContent = maxAngle.toFixed(1) + ' rad';
269
+ }
270
+ }
271
+
272
+ function clearHL() { hlCol = -1; hlRow = -1; redraw(); }
273
+
274
+ function setView(v) {
275
+ currentView = v;
276
+ ['sidebyside','interleaved','angle'].forEach(k => {
277
+ document.getElementById('view-'+k).style.display = k===v ? '' : 'none';
278
+ document.getElementById('btn-view-'+k).classList.toggle('active', k===v);
279
+ });
280
+ clearHL(); updateAnnotation();
281
+ }
282
+
283
+ function setMode(m) {
284
+ mode = m;
285
+ ['cell','col','row'].forEach(k => document.getElementById('btn-'+k).classList.toggle('active', k===m));
286
+ clearHL();
287
+ }
288
+
289
+ function attachHover(canvasId, ttId, colsOverride, getInfo) {
290
+ const canvas = document.getElementById(canvasId);
291
+ const tt = document.getElementById(ttId);
292
+ canvas.addEventListener('mousemove', function(e) {
293
+ const rect = canvas.getBoundingClientRect();
294
+ const x = e.clientX - rect.left, y = e.clientY - rect.top;
295
+ const cols = colsOverride || COLS;
296
+ const col = Math.floor(x / (rect.width / cols));
297
+ const row = Math.floor(y / (rect.height / ROWS));
298
+ if (col < 0 || col >= cols || row < 0 || row >= ROWS) return;
299
+ const info = getInfo(row, col);
300
+ if (mode === 'cell') { redraw(-1, -1); tt.innerHTML = info.cell; }
301
+ else if (mode === 'col') { redraw(info.pairCol, -1); tt.innerHTML = info.colTip; }
302
+ else { redraw(-1, row); tt.innerHTML = info.rowTip; }
303
+ tt.style.left = Math.min(x+10, rect.width-250)+'px';
304
+ tt.style.top = Math.max(y-44,0)+'px';
305
+ tt.style.opacity = '1';
306
+ });
307
+ canvas.addEventListener('mouseleave', () => { tt.style.opacity='0'; clearHL(); });
308
+ }
309
+
310
+ function pairInfo(row, col) {
311
+ const phi = (row * theta(col)).toFixed(4);
312
+ const cv = Math.cos(row*theta(col)).toFixed(4);
313
+ const sv = Math.sin(row*theta(col)).toFixed(4);
314
+ const th = theta(col).toExponential(3);
315
+ const speed = col < 10 ? 'fast' : col < 60 ? 'moderate' : 'slow';
316
+ return { phi, cv, sv, th, speed };
317
+ }
318
+
319
+ attachHover('pe-canvas-cos', 'tt-cos', null, (row, col) => {
320
+ const { phi, cv, sv, th, speed } = pairInfo(row, col);
321
+ return {
322
+ pairCol: col,
323
+ cell: `<b>pos=${row}, pair i=${col}</b><br>cos(φ) = ${cv}<br>sin(φ) = ${sv}<br>φ = ${phi} rad`,
324
+ colTip: `<b>Pair i=${col}</b> (${speed}) · θ = ${th}<br>cos col highlighted left, sin col right`,
325
+ rowTip: `<b>pos=${row}</b> · full rotation vector (all pairs)`
326
+ };
327
+ });
328
+
329
+ attachHover('pe-canvas-sin', 'tt-sin', null, (row, col) => {
330
+ const { phi, cv, sv, th, speed } = pairInfo(row, col);
331
+ return {
332
+ pairCol: col,
333
+ cell: `<b>pos=${row}, pair i=${col}</b><br>sin(φ) = ${sv}<br>cos(φ) = ${cv}<br>φ = ${phi} rad`,
334
+ colTip: `<b>Pair i=${col}</b> (${speed}) · θ = ${th}`,
335
+ rowTip: `<b>pos=${row}</b> · full rotation vector (all pairs)`
336
+ };
337
+ });
338
+
339
+ attachHover('pe-canvas-interleaved', 'tt-il', 256, (row, d) => {
340
+ const pair = Math.floor(d / 2), isCos = d % 2 === 0;
341
+ const { phi, cv, sv, th, speed } = pairInfo(row, pair);
342
+ return {
343
+ pairCol: d,
344
+ cell: `<b>pos=${row}, d=${d}</b> (pair ${pair}, ${isCos?'cos':'sin'})<br>value = ${isCos?cv:sv}<br>φ = ${phi} rad`,
345
+ colTip: `<b>d=${d}</b> · pair ${pair} · ${isCos?'<b style="color:#1D9E75">cos</b>':'<b style="color:#BA7517">sin</b>'} channel`,
346
+ rowTip: `<b>pos=${row}</b> · interleaved rotation vector (256 dims)`
347
+ };
348
+ });
349
+
350
+ attachHover('pe-canvas-angle', 'tt-ang', null, (row, col) => {
351
+ const { phi, cv, sv, th, speed } = pairInfo(row, col);
352
+ return {
353
+ pairCol: col,
354
+ cell: `<b>pos=${row}, pair i=${col}</b><br>φ = ${phi} rad<br>→ cos = ${cv}, sin = ${sv}`,
355
+ colTip: `<b>Pair i=${col}</b> (${speed}) · θ = ${th}<br>angle grows by θ per position step`,
356
+ rowTip: `<b>pos=${row}</b> · angle = pos × θ<sub>i</sub> for each pair`
357
+ };
358
+ });
359
+
360
+ function updateAnnotation() {
361
+ const ann = document.getElementById('ann-box');
362
+ if (currentView === 'sidebyside') {
363
+ ann.innerHTML = `<div class="ann-row">
364
+ <div class="ann-col">
365
+ <div class="ann-head" style="color:#0F6E56">cos matrix (left) — the "stay" component</div>
366
+ cos(pos·θ<sub>i</sub>) scales how much of the original vector survives. At φ=0 it is 1 (identity). At φ=π it is −1 (full flip). Fast pairs (left columns) cycle rapidly; slow pairs (right) stay near 1 across all 50 positions.
367
+ </div>
368
+ <div class="ann-col">
369
+ <div class="ann-head" style="color:#854F0B">sin matrix (right) — the "spin" component</div>
370
+ sin(pos·θ<sub>i</sub>) scales the 90°-rotated copy <code>qw2 = [−q<sub>odd</sub>, q<sub>even</sub>]</code>. At φ=0 it is 0 (no spin); at φ=π/2 it is 1 (maximum rotation contribution). Together cos² + sin² = 1, preserving vector length exactly.
371
+ </div>
372
+ </div>
373
+ <hr class="divider">
374
+ <div style="font-size:11px"><b>Full rotation formula:</b> q<sub>new</sub> = q · cos_pos + [−q<sub>odd</sub>, q<sub>even</sub>] · sin_pos</div>`;
375
+ } else if (currentView === 'interleaved') {
376
+ ann.innerHTML = `<div class="ann-row">
377
+ <div class="ann-col">
378
+ <div class="ann-head">How the code broadcasts</div>
379
+ <code>cos_pos = repeat(sinusoidal[..., 1::2], rep=2)</code> duplicates each cos value twice, so both dims of a pair share the same scalar.<br>
380
+ <code>sin_pos = repeat(sinusoidal[..., ::2], rep=2)</code> does the same for sin.<br><br>
381
+ Each consecutive pair of columns (d, d+1) gets the same rotation angle — only the trig function differs.
382
+ </div>
383
+ <div class="ann-col">
384
+ <div class="ann-head">What to notice</div>
385
+ Adjacent columns are always identical in value (the repeat). Visually this makes each pair look like a 2-pixel-wide stripe. Fast pairs (left) stripe rapidly across positions; slow pairs (right) are nearly uniform. The pattern <em>is</em> the side-by-side view — just interleaved instead of separated.
386
+ </div>
387
+ </div>`;
388
+ } else {
389
+ ann.innerHTML = `<div class="ann-row">
390
+ <div class="ann-col">
391
+ <div class="ann-head">The underlying clock: φ = pos · θ<sub>i</sub></div>
392
+ Both cos and sin are simply read off the unit circle at this angle. The left column (pair 0) reaches ${maxAngle.toFixed(1)} rad by pos=49 — many full revolutions. The right column barely ticks above 0. This triangular ramp is the speed spectrum that gives RoPE its multi-scale position sensitivity.
393
+ </div>
394
+ <div class="ann-col">
395
+ <div class="ann-head">Why the gradient is triangular</div>
396
+ φ = pos × θ<sub>i</sub>, where θ<sub>i</sub> = 10000<sup>−2i/d</sup> decreases geometrically rightward and pos increases linearly downward. The brightest cell is bottom-left (pos=49, pair 0). Moving right, the brightness drops exponentially; moving up, it drops linearly. This is the "clock" RoPE winds at different speeds per pair.
397
+ </div>
398
+ </div>`;
399
+ }
400
+ }
401
+
402
+ setTimeout(() => { redraw(); updateAnnotation(); }, 60);
403
+ window.addEventListener('resize', () => redraw());
404
+ </script>
405
+ </body>
406
  </html>