| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| export const NEURAL_NETWORK_BANNER_HTML = `<div class="d3-banner-nn" aria-label="Neural network banner"></div> |
| <style> |
| .d3-banner-nn { |
| width: 100%; |
| height: 100%; |
| overflow: hidden; |
| position: relative; |
| /* Transparent on purpose: embeds (banner included) inherit the page |
| background, per the "no background by default" convention. */ |
| background: transparent; |
| } |
| .d3-banner-nn svg { |
| width: 100%; |
| height: 100%; |
| display: block; |
| } |
| .d3-banner-nn .nn-link { |
| /* Hardcoded translucent white, deliberately decoupled from the |
| host theme. The hero surface that sits behind this banner is |
| always tinted (primary-colored / dark), regardless of whether |
| the editor body itself is in light or dark mode, so a fixed |
| light stroke is the most reliable readability target. Binding |
| the stroke to --text-color flipped between near-black and |
| near-white per theme and vanished whenever the iframe theme |
| did not match the host hero (e.g. light editor + dark hero). */ |
| stroke: rgba(255, 255, 255, 0.4); |
| stroke-width: 1; |
| vector-effect: non-scaling-stroke; |
| fill: none; |
| } |
| .d3-banner-nn .nn-node { |
| fill: var(--primary-color, #4f46e5); |
| opacity: 0.85; |
| } |
| .d3-banner-nn .nn-node-ring { |
| fill: none; |
| stroke: color-mix(in oklab, var(--primary-color, #4f46e5) 60%, transparent); |
| stroke-width: 1.2; |
| vector-effect: non-scaling-stroke; |
| } |
| .d3-banner-nn .nn-pulse { |
| fill: var(--primary-color, #4f46e5); |
| } |
| </style> |
| <script> |
| (() => { |
| const bootstrap = () => { |
| const all = Array.from(document.querySelectorAll('.d3-banner-nn')); |
| const container = all.find((el) => el.dataset.mounted !== 'true'); |
| if (!container) return; |
| container.dataset.mounted = 'true'; |
| |
| const NS = 'http://www.w3.org/2000/svg'; |
| const W = 980; |
| const H = 392; |
| |
| const svg = document.createElementNS(NS, 'svg'); |
| svg.setAttribute('viewBox', '0 0 ' + W + ' ' + H); |
| svg.setAttribute('preserveAspectRatio', 'xMidYMid meet'); |
| container.appendChild(svg); |
| |
| // Layer sizes: input -> 3 hidden layers -> output. |
| const layers = [6, 10, 14, 10, 6]; |
| const padX = 110; |
| const padY = 70; |
| |
| const xs = layers.map((_, i) => |
| padX + (i * (W - padX * 2)) / (layers.length - 1) |
| ); |
| |
| const nodes = []; |
| layers.forEach((count, li) => { |
| const usable = H - padY * 2; |
| const step = count > 1 ? usable / (count - 1) : 0; |
| for (let i = 0; i < count; i++) { |
| nodes.push({ |
| layer: li, |
| idx: i, |
| x: xs[li], |
| y: padY + (count > 1 ? i * step : usable / 2), |
| }); |
| } |
| }); |
| |
| const links = []; |
| for (let li = 0; li < layers.length - 1; li++) { |
| const a = nodes.filter((n) => n.layer === li); |
| const b = nodes.filter((n) => n.layer === li + 1); |
| a.forEach((na) => b.forEach((nb) => links.push([na, nb]))); |
| } |
| |
| // Trim each link so its endpoints sit just OUTSIDE the node ring |
| // (radius 7 in viewBox units, plus a small margin). This guarantees |
| // the lines never poke through the inside of the ring, which used |
| // to make them read as if they sat on top of the nodes even though |
| // gLinks is appended before gNodes in DOM order. With trimming the |
| // visual layering is unambiguous: the ring contains a clean gap on |
| // every spoke. We do this analytically rather than relying on a |
| // background fill, which would fight with the transparent banner. |
| const NODE_R = 7; |
| const LINK_GAP = 1.5; |
| const trim = NODE_R + LINK_GAP; |
| const gLinks = document.createElementNS(NS, 'g'); |
| links.forEach(([a, b]) => { |
| const dx = b.x - a.x; |
| const dy = b.y - a.y; |
| const dist = Math.hypot(dx, dy) || 1; |
| const nx = dx / dist; |
| const ny = dy / dist; |
| const line = document.createElementNS(NS, 'line'); |
| line.setAttribute('class', 'nn-link'); |
| line.setAttribute('x1', String(a.x + nx * trim)); |
| line.setAttribute('y1', String(a.y + ny * trim)); |
| line.setAttribute('x2', String(b.x - nx * trim)); |
| line.setAttribute('y2', String(b.y - ny * trim)); |
| gLinks.appendChild(line); |
| }); |
| svg.appendChild(gLinks); |
| |
| const gNodes = document.createElementNS(NS, 'g'); |
| nodes.forEach((n) => { |
| const ring = document.createElementNS(NS, 'circle'); |
| ring.setAttribute('class', 'nn-node-ring'); |
| ring.setAttribute('cx', String(n.x)); |
| ring.setAttribute('cy', String(n.y)); |
| ring.setAttribute('r', '7'); |
| gNodes.appendChild(ring); |
| |
| const c = document.createElementNS(NS, 'circle'); |
| c.setAttribute('class', 'nn-node'); |
| c.setAttribute('cx', String(n.x)); |
| c.setAttribute('cy', String(n.y)); |
| c.setAttribute('r', '3.2'); |
| gNodes.appendChild(c); |
| }); |
| svg.appendChild(gNodes); |
| |
| const gPulses = document.createElementNS(NS, 'g'); |
| svg.appendChild(gPulses); |
| |
| const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches; |
| if (reduced) return; |
| |
| let stopped = false; |
| container.__demoCleanup = () => { stopped = true; }; |
| |
| const fire = () => { |
| if (stopped || links.length === 0) return; |
| const [a, b] = links[Math.floor(Math.random() * links.length)]; |
| const c = document.createElementNS(NS, 'circle'); |
| c.setAttribute('class', 'nn-pulse'); |
| c.setAttribute('r', '2.6'); |
| c.setAttribute('cx', String(a.x)); |
| c.setAttribute('cy', String(a.y)); |
| c.style.opacity = '0.9'; |
| gPulses.appendChild(c); |
| |
| const start = performance.now(); |
| const dur = 700 + Math.random() * 700; |
| const tick = (now) => { |
| if (stopped) { c.remove(); return; } |
| const t = Math.min(1, (now - start) / dur); |
| const x = a.x + (b.x - a.x) * t; |
| const y = a.y + (b.y - a.y) * t; |
| c.setAttribute('cx', String(x)); |
| c.setAttribute('cy', String(y)); |
| c.style.opacity = String(0.9 * (1 - t)); |
| if (t < 1) requestAnimationFrame(tick); |
| else c.remove(); |
| }; |
| requestAnimationFrame(tick); |
| }; |
| |
| const loop = () => { |
| if (stopped) return; |
| const burst = 2 + Math.floor(Math.random() * 3); |
| for (let i = 0; i < burst; i++) fire(); |
| setTimeout(loop, 160 + Math.random() * 180); |
| }; |
| loop(); |
| }; |
| |
| if (document.readyState === 'loading') { |
| document.addEventListener('DOMContentLoaded', bootstrap, { once: true }); |
| } else { |
| bootstrap(); |
| } |
| })(); |
| </script>`; |
|
|
| |
| |
| |
| |
| |
| |
| |
|
|
| export const MODEL_ACCURACY_CSV = `epoch,baseline,transformer,mixture-of-experts |
| 1,0.62,0.71,0.74 |
| 2,0.68,0.78,0.81 |
| 3,0.71,0.83,0.86 |
| 4,0.73,0.86,0.89 |
| 5,0.74,0.88,0.91 |
| 6,0.75,0.89,0.92 |
| 7,0.75,0.90,0.93 |
| 8,0.76,0.90,0.93`; |
|
|
| export const MODEL_ACCURACY_CSV_META = { |
| name: "model-accuracy.csv", |
| ext: "csv", |
| columns: ["epoch", "baseline", "transformer", "mixture-of-experts"], |
| rowCount: 8, |
| }; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| export const ARCHITECTURE_JSON = `{ |
| "name": "transformer-block", |
| "layers": [ |
| { "name": "tokens", "kind": "input", "units": 6 }, |
| { "name": "embedding", "kind": "projection","units": 10 }, |
| { "name": "attention", "kind": "self-attn", "units": 14 }, |
| { "name": "values", "kind": "projection","units": 10 }, |
| { "name": "logits", "kind": "output", "units": 6 } |
| ], |
| "connectivity": "fully-connected", |
| "activation": "softmax" |
| }`; |
|
|
| export const ARCHITECTURE_JSON_META = { |
| name: "architecture.json", |
| ext: "json", |
| columns: ["name", "kind", "units"], |
| rowCount: 5, |
| }; |
|
|
| |
| |
| |
| |
| |
| |
| export const MODEL_ACCURACY_CHART_HTML = `<div class="d3-accuracy" aria-label="Model accuracy over epochs"></div> |
| <style> |
| .d3-accuracy { |
| width: 100%; |
| background: transparent; |
| font-family: ui-sans-serif, system-ui, sans-serif; |
| } |
| .d3-accuracy svg { width: 100%; height: auto; display: block; } |
| .d3-accuracy .axis path, |
| .d3-accuracy .axis line { |
| stroke: var(--axis-color, #94a3b8); |
| stroke-opacity: 0.55; |
| } |
| .d3-accuracy .axis text { |
| fill: var(--muted-color, #64748b); |
| font-size: 11px; |
| } |
| .d3-accuracy .grid line { |
| stroke: var(--grid-color, #e2e8f0); |
| stroke-opacity: 0.4; |
| } |
| .d3-accuracy .series { fill: none; stroke-width: 2.2; } |
| .d3-accuracy .series--baseline { stroke: color-mix(in oklab, var(--muted-color, #64748b) 75%, transparent); stroke-dasharray: 4 3; } |
| .d3-accuracy .series--transformer { stroke: var(--primary-color, #4f46e5); } |
| .d3-accuracy .series--moe { stroke: color-mix(in oklab, var(--primary-color, #4f46e5) 60%, #10b981); } |
| .d3-accuracy .dot { stroke: var(--surface-bg, #fff); stroke-width: 1.5; } |
| .d3-accuracy .legend { |
| display: flex; gap: 14px; flex-wrap: wrap; |
| padding: 6px 4px 10px; |
| font-size: 12px; |
| color: var(--text-color, #0f172a); |
| } |
| .d3-accuracy .legend .swatch { |
| display: inline-block; width: 14px; height: 14px; |
| border-radius: 3px; margin-right: 6px; vertical-align: middle; |
| } |
| </style> |
| <script> |
| (() => { |
| const ensureD3 = (cb) => { |
| if (window.d3 && typeof window.d3.select === 'function') return cb(); |
| let s = document.getElementById('d3-cdn-script'); |
| 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); |
| } |
| s.addEventListener('load', cb, { once: true }); |
| if (window.d3) cb(); |
| }; |
| |
| const DATA = [ |
| { epoch: 1, baseline: 0.62, transformer: 0.71, moe: 0.74 }, |
| { epoch: 2, baseline: 0.68, transformer: 0.78, moe: 0.81 }, |
| { epoch: 3, baseline: 0.71, transformer: 0.83, moe: 0.86 }, |
| { epoch: 4, baseline: 0.73, transformer: 0.86, moe: 0.89 }, |
| { epoch: 5, baseline: 0.74, transformer: 0.88, moe: 0.91 }, |
| { epoch: 6, baseline: 0.75, transformer: 0.89, moe: 0.92 }, |
| { epoch: 7, baseline: 0.75, transformer: 0.90, moe: 0.93 }, |
| { epoch: 8, baseline: 0.76, transformer: 0.90, moe: 0.93 }, |
| ]; |
| const SERIES = [ |
| { key: 'baseline', cls: 'series--baseline', label: 'Baseline' }, |
| { key: 'transformer', cls: 'series--transformer', label: 'Transformer' }, |
| { key: 'moe', cls: 'series--moe', label: 'Mixture of Experts' }, |
| ]; |
| |
| const bootstrap = () => { |
| const scriptEl = document.currentScript; |
| let container = scriptEl ? scriptEl.previousElementSibling : null; |
| if (!(container && container.classList.contains('d3-accuracy'))) { |
| const cs = Array.from(document.querySelectorAll('.d3-accuracy')) |
| .filter(el => el.dataset.mounted !== 'true'); |
| container = cs[cs.length - 1] || null; |
| } |
| if (!container) return; |
| if (container.dataset.mounted === 'true') return; |
| container.dataset.mounted = 'true'; |
| |
| const d3 = window.d3; |
| const legend = document.createElement('div'); |
| legend.className = 'legend'; |
| legend.innerHTML = SERIES |
| .map(s => '<span><span class="swatch" style="background:' + |
| (s.cls === 'series--transformer' ? 'var(--primary-color,#4f46e5)' : |
| s.cls === 'series--moe' ? 'color-mix(in oklab, var(--primary-color,#4f46e5) 60%, #10b981)' : |
| 'color-mix(in oklab, var(--muted-color,#64748b) 75%, transparent)') |
| + '"></span>' + s.label + '</span>') |
| .join(''); |
| container.appendChild(legend); |
| |
| const svg = d3.select(container).append('svg'); |
| |
| const render = () => { |
| const w = container.clientWidth || 640; |
| const h = Math.max(240, Math.round(w / 2.6)); |
| const margin = { top: 10, right: 16, bottom: 28, left: 34 }; |
| svg.attr('viewBox', '0 0 ' + w + ' ' + h) |
| .attr('width', w).attr('height', h); |
| svg.selectAll('*').remove(); |
| |
| const x = d3.scaleLinear() |
| .domain(d3.extent(DATA, d => d.epoch)) |
| .range([margin.left, w - margin.right]); |
| const y = d3.scaleLinear() |
| .domain([0.55, 1]).nice() |
| .range([h - margin.bottom, margin.top]); |
| |
| const g = svg.append('g'); |
| g.append('g') |
| .attr('class', 'grid') |
| .attr('transform', 'translate(' + margin.left + ',0)') |
| .call(d3.axisLeft(y).ticks(5).tickSize(-(w - margin.left - margin.right)).tickFormat(() => '')); |
| |
| g.append('g') |
| .attr('class', 'axis') |
| .attr('transform', 'translate(0,' + (h - margin.bottom) + ')') |
| .call(d3.axisBottom(x).ticks(DATA.length).tickFormat(d3.format('d'))); |
| |
| g.append('g') |
| .attr('class', 'axis') |
| .attr('transform', 'translate(' + margin.left + ',0)') |
| .call(d3.axisLeft(y).ticks(5).tickFormat(d3.format('.0%'))); |
| |
| const line = d3.line().x(d => x(d.epoch)).y(d => y(d.v)).curve(d3.curveMonotoneX); |
| SERIES.forEach(s => { |
| const rows = DATA.map(d => ({ epoch: d.epoch, v: d[s.key] })); |
| g.append('path') |
| .datum(rows) |
| .attr('class', 'series ' + s.cls) |
| .attr('d', line); |
| g.selectAll('.dot-' + s.key) |
| .data(rows) |
| .join('circle') |
| .attr('class', 'dot') |
| .attr('r', 3) |
| .attr('cx', d => x(d.epoch)) |
| .attr('cy', d => y(d.v)) |
| .attr('fill', |
| s.cls === 'series--transformer' ? 'var(--primary-color,#4f46e5)' : |
| s.cls === 'series--moe' ? 'color-mix(in oklab, var(--primary-color,#4f46e5) 60%, #10b981)' : |
| 'var(--muted-color,#64748b)'); |
| }); |
| }; |
| |
| ensureD3(() => { |
| render(); |
| if (window.ResizeObserver) new ResizeObserver(render).observe(container); |
| }); |
| }; |
| |
| if (document.readyState === 'loading') { |
| document.addEventListener('DOMContentLoaded', bootstrap, { once: true }); |
| } else { |
| bootstrap(); |
| } |
| })(); |
| </script>`; |
|
|
|
|