Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
| <div class="babel-library" style="width:100%;margin:0;aspect-ratio:2.5/1;min-height:300px;position:relative;overflow:hidden;cursor:grab;"></div> | |
| <script> | |
| (() => { | |
| const bootstrap = () => { | |
| const mount = document.currentScript ? document.currentScript.previousElementSibling : null; | |
| const container = (mount && mount.classList.contains('babel-library') && mount) || | |
| Array.from(document.querySelectorAll('.babel-library')).find(el => el.dataset.mounted !== 'true'); | |
| if (!container || container.dataset.mounted === 'true') return; | |
| container.dataset.mounted = 'true'; | |
| const canvas = document.createElement('canvas'); | |
| canvas.style.display = 'block'; | |
| canvas.style.width = '100%'; | |
| canvas.style.height = '100%'; | |
| container.appendChild(canvas); | |
| let ctx = canvas.getContext('2d'); | |
| // --- Constants --- | |
| const TOKENS_PER_BOOK = 125_000; | |
| const BOOKS_PER_SHELF = 250; | |
| const SHELVES_PER_WALL = 50; | |
| const WALLS_PER_CORRIDOR = 20; | |
| const TOTAL_TOKENS = 1_056_095_620_280; | |
| const TOTAL_BOOKS = Math.round(TOTAL_TOKENS / TOKENS_PER_BOOK); | |
| // Prompt labels and canonical mapping | |
| const PROMPT_LABELS = { | |
| article: 'Article', commentary: 'Commentary', discussion: 'Discussion', | |
| explanation: 'Explanation', faq: 'FAQ', math: 'Math', narrative: 'Narrative', | |
| table: 'Table', tutorial: 'Tutorial', distill: 'Distill', | |
| diverse_qa_pairs: 'Diverse QA', extract_knowledge: 'Extract Knowledge', | |
| knowledge_list: 'Knowledge List', wikipedia_style_rephrasing: 'Wikipedia Style', | |
| guided_rewrite_original: 'Guided Rewrite', guided_rewrite_improved: 'Guided Rewrite+', | |
| continue: 'Continue', summarize: 'Summarize' | |
| }; | |
| function toCanonical(promptPath) { | |
| let base = promptPath.split('/').pop().replace('.md', ''); | |
| // Strip model-specific suffixes like -1b-hq, -falcon3-1b-hq, -smollm2-1.7b-hq, etc. | |
| return base.replace(/-([\w.]+-)?\d+(\.\d+)?[bm]-\w+$/, ''); | |
| } | |
| // --- State --- | |
| let W = 800, H = 320, dpr = 1; | |
| let zoom = 0.0, targetZoom = 1.0; | |
| let autoPlaying = true, autoStartTime = 0, autoResumeZoom = 0; | |
| const AUTO_DURATION = 60000; | |
| let scrollResumeTimer = null; | |
| let promptDistribution = null; // [{name, label, tokens, fraction, color}] | |
| let shelfTextures = []; // offscreen canvases for shelf strips | |
| let isDark = document.documentElement.getAttribute('data-theme') === 'dark'; | |
| // Seeded RNG for deterministic book colors | |
| function mulberry32(a) { | |
| return () => { a |= 0; a = a + 0x6D2B79F5 | 0; | |
| let t = Math.imul(a ^ a >>> 15, 1 | a); | |
| t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t; | |
| return ((t ^ t >>> 14) >>> 0) / 4294967296; }; | |
| } | |
| // Tooltip | |
| const tip = document.createElement('div'); | |
| Object.assign(tip.style, { | |
| position: 'absolute', pointerEvents: 'none', padding: '8px 12px', borderRadius: '8px', | |
| fontSize: '12px', lineHeight: '1.4', border: '1px solid var(--border-color)', | |
| background: 'var(--surface-bg)', color: 'var(--text-color)', | |
| boxShadow: '0 4px 24px rgba(0,0,0,.18)', opacity: '0', transition: 'opacity .12s ease', | |
| whiteSpace: 'nowrap', zIndex: '10', fontFamily: "'Inter', system-ui, sans-serif" | |
| }); | |
| container.appendChild(tip); | |
| // --- Colors --- | |
| function getColors() { | |
| isDark = document.documentElement.getAttribute('data-theme') === 'dark'; | |
| // Curated palette that works in both light and dark mode | |
| return { | |
| bg: isDark ? '#1a1a2e' : '#faf9f6', | |
| shelfWood: isDark ? '#3d2b1f' : '#c8a882', | |
| shelfEdge: isDark ? '#2a1d14' : '#a08060', | |
| wallBg: isDark ? '#141424' : '#f0ece4', | |
| text: isDark ? 'rgba(255,255,255,0.9)' : 'rgba(0,0,0,0.85)', | |
| textMuted: isDark ? 'rgba(255,255,255,0.5)' : 'rgba(0,0,0,0.4)', | |
| shadow: isDark ? 'rgba(0,0,0,0.6)' : 'rgba(0,0,0,0.15)', | |
| vignette: isDark ? 'rgba(0,0,0,0.7)' : 'rgba(0,0,0,0.06)', | |
| }; | |
| } | |
| let colors = getColors(); | |
| const observer = new MutationObserver(() => { | |
| colors = getColors(); | |
| rebuildShelfTextures(); | |
| }); | |
| observer.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] }); | |
| // Prompt colors: use ColorPalettes if available, otherwise fall back | |
| function getPromptColors(n) { | |
| if (window.ColorPalettes) return window.ColorPalettes.getColors('categorical', n); | |
| // Tableau-inspired fallback | |
| return ['#4e79a7','#f28e2b','#e15759','#76b7b2','#59a14f','#edc949', | |
| '#af7aa1','#ff9da7','#9c755f','#bab0ab','#5fa2ce','#fc7d0b', | |
| '#a3cce9','#ffc428','#c85200','#8cd17d'].slice(0, n); | |
| } | |
| // --- Data loading --- | |
| const JSON_PATHS = ['/data/rephrasing_metadata.json', './assets/data/rephrasing_metadata.json', | |
| '../assets/data/rephrasing_metadata.json']; | |
| async function fetchFirst(paths) { | |
| for (const p of paths) { | |
| try { const r = await fetch(p, { cache: 'no-cache' }); if (r.ok) return r.json(); } catch(_) {} | |
| } | |
| throw new Error('Data not found'); | |
| } | |
| function buildDistribution(raw) { | |
| const byPrompt = {}; | |
| for (const entry of raw) { | |
| const key = toCanonical(entry.prompt); | |
| byPrompt[key] = (byPrompt[key] || 0) + entry.output_tokens; | |
| } | |
| const totalTokens = Object.values(byPrompt).reduce((a, b) => a + b, 0); | |
| const keys = Object.keys(byPrompt).sort((a, b) => byPrompt[b] - byPrompt[a]); | |
| const palette = getPromptColors(keys.length); | |
| return keys.map((key, i) => ({ | |
| name: key, | |
| label: PROMPT_LABELS[key] || key, | |
| tokens: byPrompt[key], | |
| fraction: byPrompt[key] / totalTokens, | |
| color: palette[i] | |
| })); | |
| } | |
| // Weighted random prompt selection using precomputed CDF | |
| let cdf = []; | |
| function buildCdf() { | |
| if (!promptDistribution) return; | |
| cdf = []; | |
| let acc = 0; | |
| for (const p of promptDistribution) { acc += p.fraction; cdf.push(acc); } | |
| } | |
| function randomPrompt(rand) { | |
| const r = rand(); | |
| for (let i = 0; i < cdf.length; i++) { if (r <= cdf[i]) return promptDistribution[i]; } | |
| return promptDistribution[promptDistribution.length - 1]; | |
| } | |
| // --- Shelf texture generation --- | |
| // Each shelf texture is a thin offscreen canvas filled with book spines | |
| function makeShelfTexture(shelfWidth, shelfHeight, seed) { | |
| const oc = document.createElement('canvas'); | |
| oc.width = shelfWidth; oc.height = shelfHeight; | |
| const octx = oc.getContext('2d'); | |
| const localRng = mulberry32(seed); | |
| let x = 0; | |
| while (x < shelfWidth) { | |
| const p = randomPrompt(localRng); | |
| const spineW = 2 + localRng() * 4; | |
| octx.fillStyle = p.color; | |
| octx.fillRect(x, 0, spineW, shelfHeight); | |
| // Subtle spine highlight | |
| octx.fillStyle = isDark ? 'rgba(255,255,255,0.08)' : 'rgba(255,255,255,0.25)'; | |
| octx.fillRect(x, 0, 1, shelfHeight); | |
| x += spineW + 0.5; | |
| } | |
| return oc; | |
| } | |
| function rebuildShelfTextures() { | |
| if (!promptDistribution) return; | |
| shelfTextures = []; | |
| for (let i = 0; i < 60; i++) { | |
| shelfTextures.push(makeShelfTexture(400, 20, 1000 + i)); | |
| } | |
| } | |
| // --- Zoom mapping --- | |
| // Each level has a core range and overlapping fade zones for crossfade transitions. | |
| // Format: [start, end, fadeWidth] | |
| const LEVEL_BOUNDS = [ | |
| { name: 'book', start: 0.00, end: 0.30, fade: 0.06 }, | |
| { name: 'shelf', start: 0.24, end: 0.52, fade: 0.06 }, | |
| { name: 'wall', start: 0.46, end: 0.72, fade: 0.06 }, | |
| { name: 'corridor', start: 0.66, end: 0.90, fade: 0.06 }, | |
| { name: 'library', start: 0.84, end: 1.00, fade: 0.06 }, | |
| ]; | |
| function getLevelAlpha(z, bound) { | |
| const fadeIn = Math.min(1, (z - bound.start) / bound.fade); | |
| // Last level (library) doesn't fade out; first level (book) doesn't fade in | |
| if (bound.end >= 1.0) return Math.max(0, Math.min(1, fadeIn)); | |
| const fadeOut = Math.min(1, (bound.end - z) / bound.fade); | |
| if (bound.start <= 0.0) return Math.max(0, Math.min(1, fadeOut)); | |
| return Math.max(0, Math.min(1, fadeIn, fadeOut)); | |
| } | |
| // Returns the primary level info (used for scale indicator, tooltips, etc.) | |
| function getScaleInfo(z) { | |
| if (z < 0.30) return { level: 'book', t: z / 0.30, booksVisible: Math.round(20 + (z / 0.30) * 480) }; | |
| if (z < 0.52) return { level: 'shelf', t: (z - 0.24) / 0.28, shelvesVisible: Math.round(1 + ((z - 0.24) / 0.28) * 49) }; | |
| if (z < 0.72) return { level: 'wall', t: (z - 0.46) / 0.26, wallsVisible: Math.round(2 + ((z - 0.46) / 0.26) * 18) }; | |
| if (z < 0.90) return { level: 'corridor', t: (z - 0.66) / 0.24, corridorsVisible: Math.round(2 + ((z - 0.66) / 0.24) * 14) }; | |
| return { level: 'library', t: (z - 0.84) / 0.16, corridorsVisible: 33 }; | |
| } | |
| // Build per-level info for crossfade rendering | |
| function getLevelInfoForBound(b, z) { | |
| const t = Math.max(0, Math.min(1, (z - b.start) / (b.end - b.start))); | |
| switch (b.name) { | |
| case 'book': return { level: 'book', t, booksVisible: Math.round(20 + t * 480) }; | |
| case 'shelf': return { level: 'shelf', t, shelvesVisible: Math.round(1 + t * 49) }; | |
| case 'wall': return { level: 'wall', t, wallsVisible: Math.round(2 + t * 18) }; | |
| case 'corridor': return { level: 'corridor', t, corridorsVisible: Math.round(2 + t * 14) }; | |
| case 'library': return { level: 'library', t, corridorsVisible: 33 }; | |
| } | |
| } | |
| // --- Drawing functions --- | |
| function drawBookLevel(info) { | |
| const n = info.booksVisible; | |
| const bookW = W / (n * 1.05); | |
| const bookH = H * 0.7; | |
| const startY = (H - bookH) / 2; | |
| const shelfY = startY + bookH; | |
| const localRng = mulberry32(7); | |
| // Shelf board | |
| ctx.fillStyle = colors.shelfWood; | |
| ctx.fillRect(0, shelfY, W, H * 0.06); | |
| ctx.fillStyle = colors.shelfEdge; | |
| ctx.fillRect(0, shelfY, W, 2); | |
| // Shadow under shelf | |
| const grad = ctx.createLinearGradient(0, shelfY + H * 0.06, 0, shelfY + H * 0.06 + 10); | |
| grad.addColorStop(0, colors.shadow); | |
| grad.addColorStop(1, 'transparent'); | |
| ctx.fillStyle = grad; | |
| ctx.fillRect(0, shelfY + H * 0.06, W, 10); | |
| for (let i = 0; i < n; i++) { | |
| const p = randomPrompt(localRng); | |
| const x = i * (W / n) + (W / n - bookW) / 2; | |
| const heightVar = 0.85 + localRng() * 0.15; | |
| const bh = bookH * heightVar; | |
| const by = shelfY - bh; | |
| const bw = bookW * (0.7 + localRng() * 0.3); | |
| ctx.fillStyle = p.color; | |
| ctx.fillRect(x, by, bw, bh); | |
| if (bw > 3) { | |
| // 3D edge highlight (left) | |
| ctx.fillStyle = isDark ? 'rgba(255,255,255,0.12)' : 'rgba(255,255,255,0.35)'; | |
| ctx.fillRect(x, by, Math.max(1, bw * 0.08), bh); | |
| // 3D shadow (right) | |
| ctx.fillStyle = isDark ? 'rgba(0,0,0,0.3)' : 'rgba(0,0,0,0.15)'; | |
| ctx.fillRect(x + bw - Math.max(1, bw * 0.06), by, Math.max(1, bw * 0.06), bh); | |
| // Top edge | |
| ctx.fillStyle = isDark ? 'rgba(255,255,255,0.06)' : 'rgba(255,255,255,0.2)'; | |
| ctx.fillRect(x, by, bw, Math.max(1, bh * 0.02)); | |
| } | |
| if (bw > 16) { | |
| ctx.save(); | |
| ctx.translate(x + bw / 2, by + bh / 2); | |
| ctx.rotate(-Math.PI / 2); | |
| ctx.fillStyle = isDark ? 'rgba(255,255,255,0.7)' : 'rgba(0,0,0,0.6)'; | |
| ctx.font = `600 ${Math.min(bw * 0.5, 11)}px 'Inter', system-ui, sans-serif`; | |
| ctx.textAlign = 'center'; | |
| ctx.textBaseline = 'middle'; | |
| ctx.fillText(p.label, 0, 0); | |
| ctx.restore(); | |
| } | |
| } | |
| } | |
| function drawShelfLevel(info) { | |
| const nShelves = info.shelvesVisible; | |
| const shelfH = H / (nShelves + 1); | |
| const bookAreaH = shelfH * 0.78; | |
| const woodH = shelfH * 0.08; | |
| const gapH = shelfH - bookAreaH - woodH; | |
| for (let s = 0; s < nShelves; s++) { | |
| const y = s * shelfH + gapH / 2; | |
| // Draw books as texture strip | |
| const texIdx = s % shelfTextures.length; | |
| if (shelfTextures[texIdx]) { | |
| ctx.drawImage(shelfTextures[texIdx], 0, 0, shelfTextures[texIdx].width, shelfTextures[texIdx].height, | |
| 0, y, W, bookAreaH); | |
| } | |
| // Shelf board | |
| ctx.fillStyle = colors.shelfWood; | |
| ctx.fillRect(0, y + bookAreaH, W, woodH); | |
| ctx.fillStyle = colors.shelfEdge; | |
| ctx.fillRect(0, y + bookAreaH, W, 1); | |
| } | |
| } | |
| function drawWallLevel(info) { | |
| const nWalls = info.wallsVisible; | |
| const wallW = W / Math.min(nWalls, 6); | |
| const wallH = H * 0.85; | |
| const startY = (H - wallH) / 2; | |
| const cols = Math.min(nWalls, 6); | |
| const rows = Math.ceil(nWalls / cols); | |
| for (let r = 0; r < rows; r++) { | |
| for (let c = 0; c < cols; c++) { | |
| const idx = r * cols + c; | |
| if (idx >= nWalls) break; | |
| const wx = c * wallW + 2; | |
| const wy = startY + r * (wallH / rows); | |
| const ww = wallW - 4; | |
| const wh = wallH / rows - 4; | |
| // Wall background | |
| ctx.fillStyle = colors.wallBg; | |
| ctx.fillRect(wx, wy, ww, wh); | |
| // Mini shelves as horizontal stripes (show ~12 visible, implying many more) | |
| const nStripes = 12; | |
| const stripeH = wh / nStripes; | |
| for (let s = 0; s < nStripes; s++) { | |
| const texIdx = (idx * nStripes + s) % shelfTextures.length; | |
| if (shelfTextures[texIdx]) { | |
| ctx.drawImage(shelfTextures[texIdx], 0, 0, shelfTextures[texIdx].width, shelfTextures[texIdx].height, | |
| wx, wy + s * stripeH, ww, stripeH * 0.85); | |
| } | |
| // Tiny shelf line | |
| ctx.fillStyle = colors.shelfEdge; | |
| ctx.fillRect(wx, wy + (s + 1) * stripeH - 1, ww, 1); | |
| } | |
| // Wall border | |
| ctx.strokeStyle = colors.shelfEdge; | |
| ctx.lineWidth = 1; | |
| ctx.strokeRect(wx, wy, ww, wh); | |
| } | |
| } | |
| } | |
| function drawCorridorLevel(info) { | |
| const nCorridors = info.corridorsVisible; | |
| const cols = Math.min(nCorridors, 8); | |
| const rows = Math.ceil(nCorridors / cols); | |
| const cellW = W / cols; | |
| const cellH = H / rows; | |
| for (let r = 0; r < rows; r++) { | |
| for (let c = 0; c < cols; c++) { | |
| const idx = r * cols + c; | |
| if (idx >= nCorridors) break; | |
| const cx_ = c * cellW; | |
| const cy = r * cellH; | |
| // Background | |
| ctx.fillStyle = colors.wallBg; | |
| ctx.fillRect(cx_, cy, cellW, cellH); | |
| // Perspective tunnel: nested rectangles with shelf textures on sides | |
| const depth = 5; | |
| const inset = Math.min(cellW, cellH) * 0.15; | |
| for (let d = depth; d >= 0; d--) { | |
| const t = d / depth; | |
| const dx = cx_ + inset * t; | |
| const dy = cy + inset * t * 0.5; | |
| const dw = cellW - inset * t * 2; | |
| const dh = cellH - inset * t; | |
| // Left wall strip with shelf texture | |
| const wallW = dw * 0.1; | |
| const texIdx = (idx * depth + d) % shelfTextures.length; | |
| if (shelfTextures[texIdx]) { | |
| ctx.globalAlpha = 0.25 + (1 - t) * 0.5; | |
| ctx.drawImage(shelfTextures[texIdx], 0, 0, shelfTextures[texIdx].width, shelfTextures[texIdx].height, | |
| dx, dy, wallW, dh); | |
| // Right wall | |
| ctx.drawImage(shelfTextures[(texIdx + 7) % shelfTextures.length], 0, 0, shelfTextures[0].width, shelfTextures[0].height, | |
| dx + dw - wallW, dy, wallW, dh); | |
| } | |
| ctx.globalAlpha = 1; | |
| } | |
| // Corridor border | |
| ctx.strokeStyle = isDark ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.06)'; | |
| ctx.lineWidth = 1; | |
| ctx.strokeRect(cx_, cy, cellW, cellH); | |
| } | |
| } | |
| } | |
| function drawLibraryLevel(info) { | |
| // Grand cathedral: one-point perspective with vanishing point | |
| const vpX = W / 2, vpY = H * 0.38; | |
| const localRng = mulberry32(2026); | |
| // Floor gradient | |
| const floorGrad = ctx.createLinearGradient(0, H * 0.5, 0, H); | |
| floorGrad.addColorStop(0, isDark ? '#1a1520' : '#e8e0d4'); | |
| floorGrad.addColorStop(1, isDark ? '#0d0a12' : '#d4cab8'); | |
| ctx.fillStyle = floorGrad; | |
| ctx.fillRect(0, H * 0.5, W, H * 0.5); | |
| // Ceiling | |
| const ceilGrad = ctx.createLinearGradient(0, 0, 0, H * 0.38); | |
| ceilGrad.addColorStop(0, isDark ? '#0d0a18' : '#f5f0e8'); | |
| ceilGrad.addColorStop(1, isDark ? '#151020' : '#ece4d8'); | |
| ctx.fillStyle = ceilGrad; | |
| ctx.fillRect(0, 0, W, H * 0.38); | |
| // Rows of shelving walls receding toward vanishing point | |
| const nRows = 12; | |
| for (let i = nRows; i >= 0; i--) { | |
| const t = i / nRows; // 0 = closest, 1 = farthest | |
| const perspective = 1 - t * 0.92; | |
| const rowW = W * perspective; | |
| const rowH = H * 0.7 * perspective; | |
| const rowX = vpX - rowW / 2; | |
| const rowY = vpY - rowH * 0.4 + (1 - perspective) * H * 0.1; | |
| // Left wall | |
| const wallW = rowW * 0.08; | |
| ctx.fillStyle = isDark ? `rgba(30,20,40,${0.3 + t * 0.5})` : `rgba(180,160,130,${0.2 + t * 0.4})`; | |
| ctx.fillRect(rowX, rowY, wallW, rowH); | |
| // Book colors on wall | |
| const stripes = 12; | |
| const sH = rowH / stripes; | |
| for (let s = 0; s < stripes; s++) { | |
| const p = randomPrompt(localRng); | |
| ctx.fillStyle = p.color; | |
| ctx.globalAlpha = 0.15 + perspective * 0.35; | |
| ctx.fillRect(rowX + 1, rowY + s * sH, wallW - 2, sH * 0.8); | |
| } | |
| ctx.globalAlpha = 1; | |
| // Right wall (mirror) | |
| const rwX = rowX + rowW - wallW; | |
| ctx.fillStyle = isDark ? `rgba(30,20,40,${0.3 + t * 0.5})` : `rgba(180,160,130,${0.2 + t * 0.4})`; | |
| ctx.fillRect(rwX, rowY, wallW, rowH); | |
| for (let s = 0; s < stripes; s++) { | |
| const p = randomPrompt(localRng); | |
| ctx.fillStyle = p.color; | |
| ctx.globalAlpha = 0.15 + perspective * 0.35; | |
| ctx.fillRect(rwX + 1, rowY + s * sH, wallW - 2, sH * 0.8); | |
| } | |
| ctx.globalAlpha = 1; | |
| // Floor tile line | |
| ctx.strokeStyle = isDark ? 'rgba(255,255,255,0.04)' : 'rgba(0,0,0,0.06)'; | |
| ctx.lineWidth = 1; | |
| const floorY = rowY + rowH; | |
| ctx.beginPath(); | |
| ctx.moveTo(rowX, floorY); | |
| ctx.lineTo(rowX + rowW, floorY); | |
| ctx.stroke(); | |
| } | |
| // Vanishing point glow | |
| const vpGrad = ctx.createRadialGradient(vpX, vpY, 0, vpX, vpY, H * 0.3); | |
| vpGrad.addColorStop(0, isDark ? 'rgba(180,160,220,0.25)' : 'rgba(255,240,200,0.4)'); | |
| vpGrad.addColorStop(1, 'transparent'); | |
| ctx.fillStyle = vpGrad; | |
| ctx.beginPath(); | |
| ctx.arc(vpX, vpY, H * 0.3, 0, Math.PI * 2); | |
| ctx.fill(); | |
| } | |
| const drawFns = { book: drawBookLevel, shelf: drawShelfLevel, wall: drawWallLevel, corridor: drawCorridorLevel, library: drawLibraryLevel }; | |
| // --- Scale indicator & annotations --- | |
| function drawScaleIndicator(z) { | |
| const info = getScaleInfo(z); | |
| let line1 = '', line2 = ''; | |
| if (info.level === 'book') { | |
| line1 = `~${info.booksVisible} books in view`; | |
| line2 = `1 book = ${(TOKENS_PER_BOOK).toLocaleString()} tokens`; | |
| } else if (info.level === 'shelf') { | |
| line1 = `~${info.shelvesVisible} shelves`; | |
| line2 = `1 shelf = ${BOOKS_PER_SHELF} books = ${(BOOKS_PER_SHELF * TOKENS_PER_BOOK / 1e6).toFixed(1)}M tokens`; | |
| } else if (info.level === 'wall') { | |
| line1 = `~${info.wallsVisible} walls`; | |
| line2 = `1 wall = ${SHELVES_PER_WALL} shelves = ${(SHELVES_PER_WALL * BOOKS_PER_SHELF * TOKENS_PER_BOOK / 1e9).toFixed(2)}B tokens`; | |
| } else if (info.level === 'corridor') { | |
| line1 = `~${info.corridorsVisible} corridors`; | |
| line2 = `1 corridor = ${WALLS_PER_CORRIDOR} walls = ${(WALLS_PER_CORRIDOR * SHELVES_PER_WALL * BOOKS_PER_SHELF * TOKENS_PER_BOOK / 1e9).toFixed(0)}B tokens`; | |
| } else { | |
| line1 = 'The Full Library'; | |
| line2 = `${TOTAL_BOOKS.toLocaleString()} books \u00B7 ${(TOTAL_TOKENS / 1e12).toFixed(2)}T tokens \u00B7 16 prompt formats`; | |
| } | |
| const fs1 = Math.max(10, Math.min(14, W * 0.014)); | |
| const fs2 = Math.max(9, Math.min(12, W * 0.012)); | |
| const pad = 10; | |
| ctx.textAlign = 'left'; | |
| ctx.textBaseline = 'top'; | |
| // Background pill | |
| ctx.font = `600 ${fs1}px 'Inter', system-ui, sans-serif`; | |
| const w1 = ctx.measureText(line1).width; | |
| ctx.font = `400 ${fs2}px 'Inter', system-ui, sans-serif`; | |
| const w2 = ctx.measureText(line2).width; | |
| const boxW = Math.max(w1, w2) + pad * 2; | |
| const boxH = fs1 + fs2 + 8 + pad * 2; | |
| ctx.fillStyle = isDark ? 'rgba(0,0,0,0.5)' : 'rgba(255,255,255,0.7)'; | |
| ctx.beginPath(); | |
| const bx = 8, by = 8, br = 6; | |
| ctx.moveTo(bx + br, by); | |
| ctx.lineTo(bx + boxW - br, by); | |
| ctx.quadraticCurveTo(bx + boxW, by, bx + boxW, by + br); | |
| ctx.lineTo(bx + boxW, by + boxH - br); | |
| ctx.quadraticCurveTo(bx + boxW, by + boxH, bx + boxW - br, by + boxH); | |
| ctx.lineTo(bx + br, by + boxH); | |
| ctx.quadraticCurveTo(bx, by + boxH, bx, by + boxH - br); | |
| ctx.lineTo(bx, by + br); | |
| ctx.quadraticCurveTo(bx, by, bx + br, by); | |
| ctx.fill(); | |
| ctx.font = `600 ${fs1}px 'Inter', system-ui, sans-serif`; | |
| ctx.fillStyle = colors.text; | |
| ctx.fillText(line1, bx + pad, by + pad); | |
| ctx.font = `400 ${fs2}px 'Inter', system-ui, sans-serif`; | |
| ctx.fillStyle = colors.textMuted; | |
| ctx.fillText(line2, bx + pad, by + pad + fs1 + 6); | |
| } | |
| function drawFullZoomAnnotation(z) { | |
| if (z < 0.8) return; | |
| const alpha = Math.min(1, (z - 0.8) / 0.2); | |
| ctx.globalAlpha = alpha; | |
| const bigFS = Math.min(W * 0.06, H * 0.14, 56); | |
| const subFS = Math.max(10, bigFS * 0.28); | |
| ctx.textAlign = 'center'; | |
| ctx.textBaseline = 'middle'; | |
| // Main number | |
| ctx.font = `900 ${bigFS}px 'Inter', system-ui, sans-serif`; | |
| ctx.fillStyle = colors.text; | |
| ctx.fillText('1,056,095,620,280 tokens', W / 2, H * 0.5); | |
| // Subtitle | |
| ctx.font = `500 ${subFS}px 'Inter', system-ui, sans-serif`; | |
| ctx.fillStyle = colors.textMuted; | |
| ctx.fillText(`${TOTAL_BOOKS.toLocaleString()} books across 16 prompt formats`, W / 2, H * 0.5 + bigFS * 0.75); | |
| ctx.globalAlpha = 1; | |
| } | |
| // Scroll hint at bottom | |
| function drawScrollHint(z) { | |
| if (z > 0.05 || !autoPlaying) return; | |
| const alpha = 1 - z / 0.05; | |
| ctx.globalAlpha = alpha * 0.4; | |
| const fs = Math.max(10, Math.min(12, W * 0.012)); | |
| ctx.font = `400 ${fs}px 'Inter', system-ui, sans-serif`; | |
| ctx.textAlign = 'center'; | |
| ctx.textBaseline = 'bottom'; | |
| ctx.fillStyle = colors.text; | |
| ctx.fillText('scroll to zoom out', W / 2, H - 8); | |
| ctx.globalAlpha = 1; | |
| } | |
| // Offscreen canvas for crossfade compositing | |
| const offCanvas = document.createElement('canvas'); | |
| const offCtx = offCanvas.getContext('2d'); | |
| // --- Main render --- | |
| function resize() { | |
| dpr = window.devicePixelRatio || 1; | |
| W = container.clientWidth || 800; | |
| H = container.clientHeight || 320; | |
| canvas.width = W * dpr; | |
| canvas.height = H * dpr; | |
| ctx.setTransform(dpr, 0, 0, dpr, 0, 0); | |
| offCanvas.width = canvas.width; | |
| offCanvas.height = canvas.height; | |
| } | |
| function render(timestamp) { | |
| if (!promptDistribution) { requestAnimationFrame(render); return; } | |
| // Auto-play animation (supports resuming from arbitrary zoom via autoResumeZoom) | |
| if (autoPlaying) { | |
| if (!autoStartTime) autoStartTime = timestamp; | |
| const elapsed = timestamp - autoStartTime; | |
| const remaining = 1.0 - autoResumeZoom; | |
| const duration = AUTO_DURATION * remaining; | |
| let t = duration > 0 ? Math.min(1, elapsed / duration) : 1; | |
| t = t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; | |
| zoom = autoResumeZoom + t * remaining; | |
| if (elapsed >= duration) { | |
| autoPlaying = false; | |
| zoom = 1.0; | |
| } | |
| } else { | |
| // Smooth interpolation toward target | |
| const diff = targetZoom - zoom; | |
| if (Math.abs(diff) > 0.0005) { | |
| zoom += diff * 0.02; | |
| } else { | |
| zoom = targetZoom; | |
| } | |
| } | |
| zoom = Math.max(0, Math.min(1, zoom)); | |
| // Clear | |
| ctx.fillStyle = colors.bg; | |
| ctx.fillRect(0, 0, W, H); | |
| // Draw all active levels with crossfade blending | |
| const info = getScaleInfo(zoom); | |
| for (const b of LEVEL_BOUNDS) { | |
| const alpha = getLevelAlpha(zoom, b); | |
| if (alpha <= 0) continue; | |
| if (alpha >= 0.999) { | |
| drawFns[b.name](getLevelInfoForBound(b, zoom)); | |
| } else { | |
| // Render to offscreen buffer, then composite with crossfade alpha | |
| offCtx.setTransform(dpr, 0, 0, dpr, 0, 0); | |
| offCtx.clearRect(0, 0, W, H); | |
| const savedCtx = ctx; | |
| ctx = offCtx; | |
| drawFns[b.name](getLevelInfoForBound(b, zoom)); | |
| ctx = savedCtx; | |
| ctx.globalAlpha = alpha; | |
| ctx.drawImage(offCanvas, 0, 0); | |
| ctx.globalAlpha = 1; | |
| } | |
| } | |
| // Vignette overlay | |
| const vigGrad = ctx.createRadialGradient(W/2, H/2, Math.min(W,H)*0.3, W/2, H/2, Math.max(W,H)*0.7); | |
| vigGrad.addColorStop(0, 'transparent'); | |
| vigGrad.addColorStop(1, colors.vignette); | |
| ctx.fillStyle = vigGrad; | |
| ctx.fillRect(0, 0, W, H); | |
| drawScaleIndicator(zoom); | |
| drawFullZoomAnnotation(zoom); | |
| drawScrollHint(zoom); | |
| requestAnimationFrame(render); | |
| } | |
| // --- Interaction --- | |
| function pauseAutoPlay() { | |
| if (autoPlaying) { | |
| autoPlaying = false; | |
| targetZoom = zoom; | |
| } | |
| clearTimeout(scrollResumeTimer); | |
| scrollResumeTimer = setTimeout(() => { | |
| if (!autoPlaying && zoom < 0.99) { | |
| autoPlaying = true; | |
| autoStartTime = 0; | |
| autoResumeZoom = zoom; | |
| } | |
| }, 10); | |
| } | |
| container.addEventListener('wheel', (e) => { | |
| e.preventDefault(); | |
| pauseAutoPlay(); | |
| const raw = e.deltaMode === 1 ? e.deltaY * 20 : e.deltaY; | |
| const delta = raw * 0.0002; | |
| targetZoom = Math.max(0, Math.min(1, targetZoom + delta)); | |
| }, { passive: false }); | |
| // Touch pinch zoom | |
| let lastTouchDist = 0; | |
| container.addEventListener('touchstart', (e) => { | |
| if (e.touches.length === 2) { | |
| const dx = e.touches[0].clientX - e.touches[1].clientX; | |
| const dy = e.touches[0].clientY - e.touches[1].clientY; | |
| lastTouchDist = Math.sqrt(dx * dx + dy * dy); | |
| pauseAutoPlay(); | |
| } | |
| }, { passive: true }); | |
| container.addEventListener('touchmove', (e) => { | |
| if (e.touches.length === 2) { | |
| e.preventDefault(); | |
| const dx = e.touches[0].clientX - e.touches[1].clientX; | |
| const dy = e.touches[0].clientY - e.touches[1].clientY; | |
| const dist = Math.sqrt(dx * dx + dy * dy); | |
| const delta = (lastTouchDist - dist) * 0.003; | |
| targetZoom = Math.max(0, Math.min(1, targetZoom + delta)); | |
| lastTouchDist = dist; | |
| } | |
| }, { passive: false }); | |
| // Click to restart auto-play from the beginning | |
| container.addEventListener('click', () => { | |
| clearTimeout(scrollResumeTimer); | |
| autoPlaying = true; | |
| autoStartTime = 0; | |
| autoResumeZoom = 0; | |
| zoom = 0; | |
| targetZoom = 1; | |
| }); | |
| // Cursor style | |
| container.addEventListener('mousedown', () => { container.style.cursor = 'grabbing'; }); | |
| container.addEventListener('mouseup', () => { container.style.cursor = 'grab'; }); | |
| // Hover tooltip for book level | |
| container.addEventListener('mousemove', (e) => { | |
| if (!promptDistribution) return; | |
| const info = getScaleInfo(zoom); | |
| if (info.level !== 'book') { | |
| tip.style.opacity = '0'; | |
| return; | |
| } | |
| const rect = container.getBoundingClientRect(); | |
| const mx = e.clientX - rect.left; | |
| const n = info.booksVisible; | |
| const bookIdx = Math.floor(mx / (W / n)); | |
| const localRng = mulberry32(7); | |
| let p = null; | |
| for (let i = 0; i <= bookIdx && i < n; i++) { | |
| p = randomPrompt(localRng); | |
| localRng(); localRng(); // consume height + width variance to stay in sync with drawBookLevel | |
| } | |
| if (p) { | |
| tip.innerHTML = `<strong>${p.label}</strong><br><span style="opacity:0.5">${(p.tokens / 1e9).toFixed(1)}B tokens total (${(p.fraction * 100).toFixed(1)}%)</span>`; | |
| tip.style.opacity = '1'; | |
| let tx = e.clientX - rect.left + 14; | |
| let ty = e.clientY - rect.top - 40; | |
| if (tx + 180 > W) tx = e.clientX - rect.left - 190; | |
| if (ty < 4) ty = 4; | |
| tip.style.left = tx + 'px'; | |
| tip.style.top = ty + 'px'; | |
| } | |
| }); | |
| container.addEventListener('mouseleave', () => { tip.style.opacity = '0'; }); | |
| // --- Init --- | |
| resize(); | |
| if (window.ResizeObserver) new ResizeObserver(resize).observe(container); | |
| else window.addEventListener('resize', resize); | |
| fetchFirst(JSON_PATHS).then(raw => { | |
| promptDistribution = buildDistribution(raw); | |
| buildCdf(); | |
| rebuildShelfTextures(); | |
| requestAnimationFrame(render); | |
| }).catch(err => { | |
| container.innerHTML = `<pre style="color:red;padding:12px">${err.message}</pre>`; | |
| }); | |
| }; | |
| if (document.readyState === 'loading') { | |
| document.addEventListener('DOMContentLoaded', bootstrap, { once: true }); | |
| } else { bootstrap(); } | |
| })(); | |
| </script> | |