Download app.js from IntelligenceLab/Terminal-Data-Website: direct link, hf CLI and curl.
- Browser
- Download file 25.2 kB
-
https://huggingface.co/spaces/IntelligenceLab/Terminal-Data-Website/resolve/main/app.js
- Command line
-
hf download hf://spaces/IntelligenceLab/Terminal-Data-Website/app.js
-
curl -L -o app.js https://huggingface.co/spaces/IntelligenceLab/Terminal-Data-Website/resolve/main/app.js
25.2 kB
| (() => { | |
| 'use strict'; | |
| // Where "Email us" and "Contact" links send inquiries. Replace before publishing. | |
| const CONTACT_EMAIL = 'contact@example.com'; | |
| const $ = (sel, root = document) => root.querySelector(sel); | |
| const $$ = (sel, root = document) => Array.from(root.querySelectorAll(sel)); | |
| const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; | |
| const clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v)); | |
| const rand = (lo, hi) => lo + Math.random() * (hi - lo); | |
| function initContact() { | |
| $$('[data-contact]').forEach((link) => { | |
| const subject = encodeURIComponent(link.dataset.subject || 'Hello'); | |
| link.href = `mailto:${CONTACT_EMAIL}?subject=${subject}`; | |
| }); | |
| $$('[data-year]').forEach((el) => { | |
| el.textContent = String(new Date().getFullYear()); | |
| }); | |
| } | |
| function initNav() { | |
| const nav = $('.nav'); | |
| const onScroll = () => nav.classList.toggle('is-scrolled', window.scrollY > 8); | |
| onScroll(); | |
| window.addEventListener('scroll', onScroll, { passive: true }); | |
| const toggle = $('.nav-toggle'); | |
| const menu = $('#mobile-menu'); | |
| const setOpen = (open) => { | |
| toggle.setAttribute('aria-expanded', String(open)); | |
| toggle.setAttribute('aria-label', open ? 'Close menu' : 'Open menu'); | |
| menu.hidden = !open; | |
| document.body.classList.toggle('menu-open', open); | |
| }; | |
| toggle.addEventListener('click', () => setOpen(toggle.getAttribute('aria-expanded') !== 'true')); | |
| $$('a', menu).forEach((a) => a.addEventListener('click', () => setOpen(false))); | |
| window.addEventListener('keydown', (e) => { | |
| if (e.key === 'Escape') setOpen(false); | |
| }); | |
| window.matchMedia('(min-width: 1101px)').addEventListener('change', (e) => { | |
| if (e.matches) setOpen(false); | |
| }); | |
| if (!('IntersectionObserver' in window)) return; | |
| const links = new Map($$('.nav-links a').map((a) => [a.getAttribute('href').slice(1), a])); | |
| const observer = new IntersectionObserver((entries) => { | |
| entries.forEach((entry) => { | |
| if (!entry.isIntersecting) return; | |
| links.forEach((a) => a.classList.remove('is-active')); | |
| const active = links.get(entry.target.dataset.nav || entry.target.id); | |
| if (active) active.classList.add('is-active'); | |
| }); | |
| }, { rootMargin: '-45% 0px -50% 0px' }); | |
| $$('main > section[id]').forEach((section) => observer.observe(section)); | |
| } | |
| function initReveal() { | |
| const items = $$('.reveal'); | |
| if (reduceMotion || !('IntersectionObserver' in window)) { | |
| items.forEach((el) => el.classList.add('is-visible')); | |
| return; | |
| } | |
| const observer = new IntersectionObserver((entries) => { | |
| entries.forEach((entry) => { | |
| if (!entry.isIntersecting) return; | |
| entry.target.classList.add('is-visible'); | |
| observer.unobserve(entry.target); | |
| }); | |
| }, { rootMargin: '0px 0px -8% 0px', threshold: 0.12 }); | |
| items.forEach((el) => observer.observe(el)); | |
| } | |
| function initTabs() { | |
| const tabs = $$('[role="tab"]'); | |
| if (!tabs.length) return; | |
| const panels = tabs.map((tab) => document.getElementById(tab.getAttribute('aria-controls'))); | |
| const select = (index, focus) => { | |
| tabs.forEach((tab, i) => { | |
| const on = i === index; | |
| tab.setAttribute('aria-selected', String(on)); | |
| tab.tabIndex = on ? 0 : -1; | |
| panels[i].hidden = !on; | |
| panels[i].classList.toggle('is-active', on); | |
| }); | |
| if (focus) tabs[index].focus(); | |
| tabs[index].scrollIntoView({ block: 'nearest', inline: 'nearest', behavior: reduceMotion ? 'auto' : 'smooth' }); | |
| }; | |
| tabs.forEach((tab, i) => { | |
| tab.addEventListener('click', () => select(i, false)); | |
| tab.addEventListener('keydown', (e) => { | |
| const keys = { ArrowRight: 1, ArrowDown: 1, ArrowLeft: -1, ArrowUp: -1 }; | |
| if (e.key in keys) { | |
| e.preventDefault(); | |
| select((i + keys[e.key] + tabs.length) % tabs.length, true); | |
| } else if (e.key === 'Home' || e.key === 'End') { | |
| e.preventDefault(); | |
| select(e.key === 'Home' ? 0 : tabs.length - 1, true); | |
| } | |
| }); | |
| }); | |
| } | |
| function initExamples() { | |
| const grid = $('#ex-grid'); | |
| const modal = $('#ex-modal'); | |
| const source = $('#examples-data'); | |
| if (!grid || !modal || !source) return; | |
| let examples; | |
| try { | |
| examples = JSON.parse(source.textContent); | |
| } catch (err) { | |
| return; | |
| } | |
| const cards = $$('.ex-card', grid); | |
| const more = $('#ex-more'); | |
| const extras = cards.filter((card) => card.classList.contains('is-extra')); | |
| if (more) { | |
| if (!extras.length) more.hidden = true; | |
| more.textContent = `Show ${extras.length} more examples`; | |
| more.addEventListener('click', () => { | |
| grid.classList.add('show-all'); | |
| more.setAttribute('aria-expanded', 'true'); | |
| if (extras[0]) extras[0].focus({ preventScroll: true }); | |
| }); | |
| } | |
| const sheet = $('.ex-sheet', modal); | |
| const closeButton = $('.icon-btn[data-ex-close]', modal); | |
| const field = (id) => document.getElementById(id); | |
| const fields = { | |
| domain: field('exm-domain'), | |
| title: field('exm-title'), | |
| tracks: field('exm-tracks'), | |
| img: field('exm-img'), | |
| desc: field('exm-desc'), | |
| software: field('exm-software'), | |
| artifacts: field('exm-artifacts'), | |
| axes: field('exm-axes'), | |
| outputs: field('exm-outputs'), | |
| cta: field('exm-cta'), | |
| live: field('exm-live'), | |
| }; | |
| let current = 0; | |
| let lastFocus = null; | |
| let liveView = null; | |
| const mountLive = (ex) => { | |
| if (liveView) { | |
| liveView.dispose(); | |
| liveView = null; | |
| } | |
| const live = window.ILLive; | |
| if (!fields.live || !live || !live.ready) { | |
| if (fields.live) fields.live.hidden = true; | |
| return; | |
| } | |
| fields.live.hidden = false; | |
| fields.live.style.setProperty('--dc', ex.color); | |
| liveView = live.mount(fields.live, ex.kind, { color: ex.color, hero: true }); | |
| if (!liveView) fields.live.hidden = true; | |
| }; | |
| const listItems = (ul, items) => { | |
| ul.replaceChildren(...items.map(({ text, cls }) => { | |
| const li = document.createElement('li'); | |
| li.textContent = text; | |
| if (cls) li.className = cls; | |
| return li; | |
| })); | |
| }; | |
| const plain = (items) => items.map((text) => ({ text })); | |
| const render = (index) => { | |
| current = (index + examples.length) % examples.length; | |
| const ex = examples[current]; | |
| sheet.style.setProperty('--dc', ex.color); | |
| fields.domain.textContent = ex.domain; | |
| fields.title.textContent = ex.title; | |
| const tracks = ex.tracks.map((t) => ({ text: t, cls: `trk trk-${t.toLowerCase()}` })); | |
| if (ex.modality) tracks.push({ text: ex.modality, cls: 'trk trk-mod' }); | |
| listItems(fields.tracks, tracks); | |
| fields.img.src = ex.img; | |
| fields.img.alt = ex.alt; | |
| fields.desc.textContent = ex.description; | |
| listItems(fields.software, plain(ex.software)); | |
| listItems(fields.artifacts, plain(ex.artifacts)); | |
| listItems(fields.axes, plain(ex.axes)); | |
| listItems(fields.outputs, plain(ex.outputs)); | |
| fields.cta.href = `mailto:${CONTACT_EMAIL}?subject=${encodeURIComponent(`Sample request: ${ex.title}`)}`; | |
| sheet.scrollTop = 0; | |
| if (!modal.hidden) mountLive(ex); | |
| }; | |
| const open = (index) => { | |
| lastFocus = document.activeElement; | |
| render(index); | |
| modal.hidden = false; | |
| mountLive(examples[current]); | |
| document.body.classList.add('modal-open'); | |
| closeButton.focus(); | |
| }; | |
| const close = () => { | |
| modal.hidden = true; | |
| if (liveView) { | |
| liveView.dispose(); | |
| liveView = null; | |
| } | |
| document.body.classList.remove('modal-open'); | |
| if (lastFocus) lastFocus.focus({ preventScroll: true }); | |
| }; | |
| document.addEventListener('il-live-ready', () => { | |
| if (!modal.hidden) mountLive(examples[current]); | |
| }); | |
| cards.forEach((card) => { | |
| const index = Number(card.dataset.index); | |
| card.addEventListener('click', () => open(index)); | |
| card.addEventListener('keydown', (e) => { | |
| if (e.key === 'Enter' || e.key === ' ') { | |
| e.preventDefault(); | |
| open(index); | |
| } | |
| }); | |
| }); | |
| $$('[data-ex-close]', modal).forEach((el) => el.addEventListener('click', close)); | |
| $('[data-ex-prev]', modal).addEventListener('click', () => render(current - 1)); | |
| $('[data-ex-next]', modal).addEventListener('click', () => render(current + 1)); | |
| document.addEventListener('keydown', (e) => { | |
| if (modal.hidden) return; | |
| if (e.key === 'Escape') { | |
| e.preventDefault(); | |
| close(); | |
| } else if (e.key === 'ArrowRight') { | |
| render(current + 1); | |
| } else if (e.key === 'ArrowLeft') { | |
| render(current - 1); | |
| } else if (e.key === 'Tab') { | |
| const focusables = $$('button, a[href]', sheet); | |
| const first = focusables[0]; | |
| const last = focusables[focusables.length - 1]; | |
| if (e.shiftKey && document.activeElement === first) { | |
| e.preventDefault(); | |
| last.focus(); | |
| } else if (!e.shiftKey && document.activeElement === last) { | |
| e.preventDefault(); | |
| first.focus(); | |
| } | |
| } | |
| }); | |
| } | |
| function initWaves() { | |
| $$('path[data-wave]').forEach((path) => { | |
| const [amp, decay, freq, phase] = path.dataset.wave.split(/\s+/).map(Number); | |
| const points = []; | |
| for (let i = 0; i <= 120; i += 1) { | |
| const t = (3 * i) / 120; | |
| const x = 40 + (296 * i) / 120; | |
| const y = 104 - amp * Math.exp(-decay * t) * Math.cos(freq * t + phase); | |
| points.push(`${x.toFixed(1)},${y.toFixed(1)}`); | |
| } | |
| path.setAttribute('d', `M${points.join('L')}`); | |
| if (path.classList.contains('wave-recon')) path.setAttribute('pathLength', '1'); | |
| }); | |
| } | |
| function initLadder() { | |
| const svg = $('#ladder-chart'); | |
| const slider = $('#ladder-strength'); | |
| const readout = $('#ladder-readout'); | |
| const label = $('#ladder-strength-label'); | |
| if (!svg || !slider || !readout || !label) return; | |
| const NS = 'http://www.w3.org/2000/svg'; | |
| // Reference-solver measurements (pass@4) by synthesis round. | |
| const ANCHORS = [[1, 0.9], [6, 0.2], [15, 0.025]]; | |
| const ROUNDS = 15; | |
| const LOW = 0.1; | |
| const HIGH = 0.5; | |
| const logit = (p) => Math.log(p / (1 - p)); | |
| const sigmoid = (z) => 1 / (1 + Math.exp(-z)); | |
| const baseLogit = (r) => { | |
| for (let i = 0; i < ANCHORS.length - 1; i += 1) { | |
| const [r0, p0] = ANCHORS[i]; | |
| const [r1, p1] = ANCHORS[i + 1]; | |
| if (r <= r1) return logit(p0) + ((r - r0) / (r1 - r0)) * (logit(p1) - logit(p0)); | |
| } | |
| return logit(ANCHORS[ANCHORS.length - 1][1]); | |
| }; | |
| const W = 640; | |
| const H = 330; | |
| const m = { l: 46, r: 14, t: 18, b: 46 }; | |
| const pw = W - m.l - m.r; | |
| const ph = H - m.t - m.b; | |
| const xOf = (r) => m.l + ((r - 0.5) / ROUNDS) * pw; | |
| const yOf = (p) => m.t + (1 - p) * ph; | |
| const barW = (pw / ROUNDS) * 0.6; | |
| svg.setAttribute('viewBox', `0 0 ${W} ${H}`); | |
| const add = (tag, attrs, text) => { | |
| const node = document.createElementNS(NS, tag); | |
| Object.entries(attrs).forEach(([k, v]) => node.setAttribute(k, String(v))); | |
| if (text != null) node.textContent = text; | |
| svg.appendChild(node); | |
| return node; | |
| }; | |
| [0, 0.25, 0.5, 0.75, 1].forEach((v) => { | |
| add('line', { x1: m.l, x2: m.l + pw, y1: yOf(v), y2: yOf(v), class: 'grid' }); | |
| add('text', { x: m.l - 10, y: yOf(v) + 4, class: 'tick', 'text-anchor': 'end' }, `${v * 100}%`); | |
| }); | |
| add('rect', { x: m.l, y: yOf(HIGH), width: pw, height: yOf(LOW) - yOf(HIGH), class: 'zone' }); | |
| add('line', { x1: m.l, x2: m.l + pw, y1: yOf(HIGH), y2: yOf(HIGH), class: 'zone-edge' }); | |
| add('line', { x1: m.l, x2: m.l + pw, y1: yOf(LOW), y2: yOf(LOW), class: 'zone-edge' }); | |
| const bars = []; | |
| for (let r = 1; r <= ROUNDS; r += 1) { | |
| bars.push(add('rect', { x: xOf(r) - barW / 2, y: yOf(0), width: barW, height: 0, rx: 3, class: 'bar' })); | |
| add('text', { x: xOf(r), y: H - m.b + 18, class: 'tick', 'text-anchor': 'middle' }, String(r)); | |
| } | |
| add('text', { x: m.l + pw / 2, y: H - 6, class: 'axis-label', 'text-anchor': 'middle' }, 'Synthesis round (harder →)'); | |
| let d = ''; | |
| for (let r = 1; r <= ROUNDS + 1e-9; r += 0.2) { | |
| d += `${d ? 'L' : 'M'}${xOf(r).toFixed(1)},${yOf(sigmoid(baseLogit(r))).toFixed(1)}`; | |
| } | |
| add('path', { d, class: 'ref-line' }); | |
| ANCHORS.forEach(([r, p]) => { | |
| add('circle', { cx: xOf(r), cy: yOf(p), r: 5, class: 'anchor' }); | |
| const lastRound = r === ROUNDS; | |
| add('text', { | |
| x: xOf(r) + (lastRound ? -10 : 10), | |
| y: yOf(p) - 10, | |
| class: 'anchor-label', | |
| 'text-anchor': lastRound ? 'end' : 'start', | |
| }, `${+(p * 100).toFixed(1)}%`); | |
| }); | |
| const current = bars.map(() => 0); | |
| let target = bars.map(() => 0); | |
| let frame = 0; | |
| let started = false; | |
| const drawBars = (values) => { | |
| values.forEach((p, i) => { | |
| const top = yOf(p); | |
| bars[i].setAttribute('y', top.toFixed(2)); | |
| bars[i].setAttribute('height', Math.max(0, yOf(0) - top).toFixed(2)); | |
| }); | |
| }; | |
| const tick = () => { | |
| let moving = false; | |
| for (let i = 0; i < current.length; i += 1) { | |
| const delta = target[i] - current[i]; | |
| if (Math.abs(delta) > 0.0005) { | |
| current[i] += delta * 0.2; | |
| moving = true; | |
| } else { | |
| current[i] = target[i]; | |
| } | |
| } | |
| drawBars(current); | |
| frame = moving ? requestAnimationFrame(tick) : 0; | |
| }; | |
| const kick = () => { | |
| if (reduceMotion) { | |
| target.forEach((p, i) => { current[i] = p; }); | |
| drawBars(current); | |
| } else if (!frame) { | |
| frame = requestAnimationFrame(tick); | |
| } | |
| }; | |
| const update = () => { | |
| const shift = Number(slider.value); | |
| target = bars.map((_, i) => sigmoid(baseLogit(i + 1) + shift)); | |
| const zone = []; | |
| target.forEach((p, i) => { | |
| const inZone = p >= LOW && p <= HIGH; | |
| bars[i].classList.toggle('in-zone', inZone); | |
| if (inZone) zone.push(i + 1); | |
| }); | |
| if (Math.abs(shift) < 0.05) label.textContent = 'Reference solver'; | |
| else label.textContent = shift > 0 ? 'Stronger than reference' : 'Weaker than reference'; | |
| if (zone.length) { | |
| const span = zone.length === 1 ? `round ${zone[0]}` : `rounds ${zone[0]}–${zone[zone.length - 1]}`; | |
| readout.innerHTML = `Next batch: <strong>${span}</strong>, where this model succeeds 10–50% of the time.`; | |
| } else if (target[ROUNDS - 1] > HIGH) { | |
| readout.innerHTML = 'This model has outgrown the ladder: <strong>we synthesize further rounds</strong>.'; | |
| } else { | |
| readout.innerHTML = '<strong>Start with warm-up tiers</strong> and calibrate upward.'; | |
| } | |
| if (started) kick(); | |
| }; | |
| slider.addEventListener('input', update); | |
| update(); | |
| const begin = () => { | |
| started = true; | |
| kick(); | |
| }; | |
| if (reduceMotion || !('IntersectionObserver' in window)) { | |
| begin(); | |
| } else { | |
| const observer = new IntersectionObserver((entries) => { | |
| if (!entries.some((entry) => entry.isIntersecting)) return; | |
| observer.disconnect(); | |
| begin(); | |
| }, { threshold: 0.3 }); | |
| observer.observe(svg); | |
| } | |
| } | |
| function initHero() { | |
| const hero = $('.hero'); | |
| const canvas = hero && $('.hero-canvas', hero); | |
| if (!canvas || !canvas.getContext) return; | |
| const ctx = canvas.getContext('2d'); | |
| const copy = $('.hero-copy', hero); | |
| const badges = $$('.tier-badges li', hero); | |
| const TIER_RGB = [[20, 184, 138], [13, 165, 160], [37, 140, 214], [88, 101, 226], [124, 92, 220]]; | |
| const IDLE_RGB = [100, 116, 139]; | |
| const REJECT_RGB = [242, 107, 91]; | |
| const GATE_LABELS = ['Execute', 'Verify', 'Audit']; | |
| const REJECT_RATE = [0.14, 0.1, 0.06]; | |
| let W = 0; | |
| let H = 0; | |
| let copyRight = 0; | |
| let flowMode = true; | |
| let gates = []; | |
| let targets = []; | |
| const particles = []; | |
| let raf = 0; | |
| let last = 0; | |
| let onScreen = true; | |
| const mouse = { x: -1e4, y: -1e4 }; | |
| const glowing = new Set(); | |
| const smooth = (k) => k * k * (3 - 2 * k); | |
| function spawn(scatter) { | |
| const x = scatter ? rand(-40, W) : rand(-90, -10); | |
| let y; | |
| let tier; | |
| if (flowMode || !targets.length) { | |
| y = rand(H * 0.08, H * 0.92); | |
| tier = Math.floor(Math.random() * TIER_RGB.length); | |
| } else { | |
| // Lanes start near the badge they feed, so streams fan in instead of crossing. | |
| const top = targets[0].y; | |
| const bottom = targets[targets.length - 1].y; | |
| y = clamp(rand(top - 90, bottom + 90), H * 0.06, H * 0.94); | |
| const slot = ((y - top) / (bottom - top)) * (targets.length - 1) + rand(-0.6, 0.6); | |
| tier = clamp(Math.round(slot), 0, targets.length - 1); | |
| } | |
| return { | |
| x, y, px: x, py: y, baseY: y, | |
| speed: rand(0.45, 1.05), | |
| phase: rand(0, Math.PI * 2), | |
| amp: rand(4, 16), | |
| tier, | |
| gate: 0, | |
| rejected: false, | |
| alpha: 1, | |
| size: rand(1, 2.3), | |
| }; | |
| } | |
| function measure() { | |
| const rect = hero.getBoundingClientRect(); | |
| W = rect.width; | |
| H = rect.height; | |
| const dpr = Math.min(window.devicePixelRatio || 1, 2); | |
| canvas.width = Math.round(W * dpr); | |
| canvas.height = Math.round(H * dpr); | |
| ctx.setTransform(dpr, 0, 0, dpr, 0, 0); | |
| copyRight = copy ? copy.getBoundingClientRect().right - rect.left : W * 0.5; | |
| targets = badges.map((badge) => { | |
| const r = badge.getBoundingClientRect(); | |
| return { x: r.left - rect.left - 6, y: r.top - rect.top + r.height / 2, ok: r.width > 0 }; | |
| }); | |
| flowMode = !(targets.length && targets.every((t) => t.ok)); | |
| gates = []; | |
| if (!flowMode) { | |
| const start = copyRight + 56; | |
| const end = Math.min(...targets.map((t) => t.x)) - 60; | |
| if (end - start > 200) { | |
| gates = GATE_LABELS.map((_, i) => start + ((end - start) * i) / (GATE_LABELS.length - 1)); | |
| } else { | |
| flowMode = true; | |
| } | |
| } | |
| const count = Math.round(clamp((W * H) / (flowMode ? 4200 : 1500), 110, 760)); | |
| while (particles.length < count) particles.push(spawn(true)); | |
| particles.length = count; | |
| particles.forEach((p) => { | |
| if (p.baseY > H || p.y > H) Object.assign(p, spawn(true)); | |
| }); | |
| } | |
| function arrive(i) { | |
| if (glowing.has(i) || Math.random() > 0.05) return; | |
| const badge = badges[i]; | |
| if (!badge) return; | |
| glowing.add(i); | |
| badge.classList.add('glow'); | |
| setTimeout(() => { | |
| badge.classList.remove('glow'); | |
| glowing.delete(i); | |
| }, 700); | |
| } | |
| function update(p, dt, t) { | |
| p.px = p.x; | |
| p.py = p.y; | |
| if (p.rejected) { | |
| p.x += p.speed * 0.45 * dt; | |
| p.y += 0.8 * dt; | |
| p.alpha -= 0.024 * dt; | |
| if (p.alpha <= 0) Object.assign(p, spawn(false)); | |
| return; | |
| } | |
| while (p.gate < gates.length && p.x >= gates[p.gate]) { | |
| if (Math.random() < REJECT_RATE[p.gate]) { | |
| p.rejected = true; | |
| return; | |
| } | |
| p.gate += 1; | |
| } | |
| const target = flowMode ? null : targets[p.tier]; | |
| let k = 0; | |
| let laneY = p.baseY; | |
| if (target) { | |
| const x0 = gates[0] - 80; | |
| k = clamp((p.x - x0) / (target.x - x0), 0, 1); | |
| laneY = p.baseY + (target.y - p.baseY) * smooth(k); | |
| } | |
| const wave = Math.sin(t * 0.0011 + p.phase + p.x * 0.0045) * p.amp * (1 - 0.9 * k); | |
| p.x += p.speed * (0.8 + W / 1800) * dt; | |
| p.y += (laneY + wave - p.y) * Math.min(1, 0.085 * dt); | |
| const dx = p.x - mouse.x; | |
| const dy = p.y - mouse.y; | |
| const d2 = dx * dx + dy * dy; | |
| if (d2 < 14400) { | |
| const dist = Math.sqrt(d2) || 1; | |
| const force = (1 - dist / 120) * 2.4 * dt; | |
| p.x += (dx / dist) * force * 0.35; | |
| p.y += (dy / dist) * force; | |
| } | |
| if (target && p.x >= target.x) { | |
| arrive(p.tier); | |
| Object.assign(p, spawn(false)); | |
| } else if (p.x > W + 24) { | |
| Object.assign(p, spawn(false)); | |
| } | |
| } | |
| function roundRect(x, y, w, h, r) { | |
| ctx.beginPath(); | |
| if (ctx.roundRect) { | |
| ctx.roundRect(x, y, w, h, r); | |
| return; | |
| } | |
| ctx.moveTo(x + r, y); | |
| ctx.arcTo(x + w, y, x + w, y + h, r); | |
| ctx.arcTo(x + w, y + h, x, y + h, r); | |
| ctx.arcTo(x, y + h, x, y, r); | |
| ctx.arcTo(x, y, x + w, y, r); | |
| ctx.closePath(); | |
| } | |
| function drawGates() { | |
| const top = H * 0.16; | |
| const bottom = H * 0.86; | |
| ctx.save(); | |
| ctx.font = '600 11px Inter, ui-sans-serif, system-ui, sans-serif'; | |
| ctx.textAlign = 'center'; | |
| ctx.textBaseline = 'middle'; | |
| gates.forEach((gx, i) => { | |
| const grad = ctx.createLinearGradient(0, top, 0, bottom); | |
| grad.addColorStop(0, 'rgba(15,23,42,0)'); | |
| grad.addColorStop(0.5, 'rgba(15,23,42,0.18)'); | |
| grad.addColorStop(1, 'rgba(15,23,42,0)'); | |
| ctx.strokeStyle = grad; | |
| ctx.lineWidth = 1; | |
| ctx.setLineDash([3, 6]); | |
| ctx.beginPath(); | |
| ctx.moveTo(gx, top); | |
| ctx.lineTo(gx, bottom); | |
| ctx.stroke(); | |
| ctx.setLineDash([]); | |
| const text = GATE_LABELS[i]; | |
| const w = ctx.measureText(text).width + 22; | |
| const h = 24; | |
| const y = top - 16; | |
| ctx.fillStyle = 'rgba(255,255,255,0.92)'; | |
| ctx.strokeStyle = 'rgba(15,23,42,0.12)'; | |
| roundRect(gx - w / 2, y - h / 2, w, h, 12); | |
| ctx.fill(); | |
| ctx.stroke(); | |
| ctx.fillStyle = '#334155'; | |
| ctx.fillText(text, gx, y + 0.5); | |
| }); | |
| ctx.restore(); | |
| } | |
| function drawBadgeGlow() { | |
| targets.forEach((t, i) => { | |
| const [r, g, b] = TIER_RGB[i]; | |
| const glow = ctx.createRadialGradient(t.x, t.y, 0, t.x, t.y, 72); | |
| glow.addColorStop(0, `rgba(${r},${g},${b},0.18)`); | |
| glow.addColorStop(1, `rgba(${r},${g},${b},0)`); | |
| ctx.fillStyle = glow; | |
| ctx.fillRect(t.x - 72, t.y - 72, 144, 144); | |
| }); | |
| } | |
| function draw() { | |
| ctx.clearRect(0, 0, W, H); | |
| if (gates.length) { | |
| drawGates(); | |
| drawBadgeGlow(); | |
| } | |
| ctx.lineCap = 'round'; | |
| for (const p of particles) { | |
| let rgb; | |
| let a; | |
| if (p.rejected) { | |
| rgb = REJECT_RGB; | |
| a = 0.75 * p.alpha; | |
| } else if (flowMode) { | |
| rgb = TIER_RGB[p.tier]; | |
| a = 0.4; | |
| } else if (p.gate === 0) { | |
| rgb = IDLE_RGB; | |
| a = p.x < copyRight ? 0.2 : 0.36; | |
| } else { | |
| rgb = TIER_RGB[p.tier]; | |
| a = 0.5 + 0.15 * p.gate; | |
| } | |
| let sx = (p.x - p.px) * 3; | |
| let sy = (p.y - p.py) * 3; | |
| const len = Math.hypot(sx, sy); | |
| if (len > 16) { | |
| sx *= 16 / len; | |
| sy *= 16 / len; | |
| } | |
| ctx.strokeStyle = `rgba(${rgb[0]},${rgb[1]},${rgb[2]},${a.toFixed(3)})`; | |
| ctx.lineWidth = p.size; | |
| ctx.beginPath(); | |
| ctx.moveTo(p.x - sx, p.y - sy); | |
| ctx.lineTo(p.x + 0.01, p.y); | |
| ctx.stroke(); | |
| } | |
| } | |
| function loop(now) { | |
| const dt = clamp((now - last) / 16.67, 0.2, 3); | |
| last = now; | |
| for (const p of particles) update(p, dt, now); | |
| draw(); | |
| raf = requestAnimationFrame(loop); | |
| } | |
| const start = () => { | |
| if (raf || reduceMotion || !onScreen || document.hidden) return; | |
| last = performance.now(); | |
| raf = requestAnimationFrame(loop); | |
| }; | |
| const stop = () => { | |
| if (raf) cancelAnimationFrame(raf); | |
| raf = 0; | |
| }; | |
| const renderStill = () => { | |
| for (let i = 0; i < 260; i += 1) particles.forEach((p) => update(p, 1, i * 16)); | |
| draw(); | |
| }; | |
| measure(); | |
| if (reduceMotion) renderStill(); | |
| else start(); | |
| let resizeTimer = 0; | |
| window.addEventListener('resize', () => { | |
| clearTimeout(resizeTimer); | |
| resizeTimer = setTimeout(() => { | |
| measure(); | |
| if (reduceMotion) renderStill(); | |
| }, 120); | |
| }); | |
| if (document.fonts && document.fonts.ready) { | |
| document.fonts.ready.then(() => { | |
| measure(); | |
| if (reduceMotion) renderStill(); | |
| }); | |
| } | |
| hero.addEventListener('pointermove', (e) => { | |
| const r = hero.getBoundingClientRect(); | |
| mouse.x = e.clientX - r.left; | |
| mouse.y = e.clientY - r.top; | |
| }); | |
| hero.addEventListener('pointerleave', () => { | |
| mouse.x = -1e4; | |
| mouse.y = -1e4; | |
| }); | |
| if ('IntersectionObserver' in window) { | |
| new IntersectionObserver((entries) => { | |
| onScreen = entries.some((entry) => entry.isIntersecting); | |
| if (onScreen) start(); | |
| else stop(); | |
| }).observe(hero); | |
| } | |
| document.addEventListener('visibilitychange', () => (document.hidden ? stop() : start())); | |
| } | |
| initContact(); | |
| initNav(); | |
| initReveal(); | |
| initTabs(); | |
| initExamples(); | |
| initWaves(); | |
| initLadder(); | |
| initHero(); | |
| })(); | |