pretext

Build creative browser demos with DOM-free text layout.

  • creative-coding
  • typography
  • pretext
  • ascii-art
  • canvas
  • generative
  • text-layout
  • kinetic-typography

Declared platforms: linux · macos · windows

Install
npx skills add 'https://github.com/NousResearch/hermes-agent/tree/main/optional-skills/creative/pretext'
Download bundle ↓
main · 24fd22bScanned 2026-09-15

Contributors

GitHub-linked commit authors for this SKILL.md at the saved revision. Co-authors and history before file renames are not included.

File history ↗

templates/donut-orbit.html

templates/donut-orbit.htmlBrowse 4 files
View on GitHub
← Back to SKILL.md
<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no" /><title>NOUS · pretext</title><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Geist+Mono:wght@400;500&display=block" rel="stylesheet"><style>  :root { color-scheme: dark; }   :root {    --background:       color-mix(in srgb, #041C1C 100%, transparent);    --background-base:  #041C1C;    --background-alpha: 1;    --background-blend: difference;    --midground:        color-mix(in srgb, #ffe6cb 100%, transparent);    --midground-base:   #ffe6cb;    --midground-alpha:  1;    --foreground:       color-mix(in srgb, #ffffff 0%, transparent);    --foreground-base:  #ffffff;    --foreground-alpha: 0;    --foreground-blend: difference;  }   html, body {    margin: 0;    width: 100%;    min-height: 100vh;    overflow: hidden;    background: #000;  }   /* literally center the composition in 100vh */  body {    display: flex;    align-items: center;    justify-content: center;  }   #stage {    position: relative;    z-index: 2;    width: min(1240px, 100vw);    height: min(720px, 100vh);    cursor: grab;    overflow: visible;  }  #stage:active { cursor: grabbing; }   #c,  #orbCanvas {    display: block;    position: absolute;    left: calc((100vw - min(1240px, 100vw)) / -2);    top: calc((100vh - min(720px, 100vh)) / -2);    width: 100vw;    height: 100vh;    pointer-events: none;  }  #c { z-index: 3; }  #orbCanvas {    z-index: 4;    opacity: 1;  }  #noiseCanvas {    display: block;    position: absolute;    inset: 0;    width: 100%;    height: 100%;    pointer-events: none;  }  #textLayer {    position: absolute;    inset: 0;    z-index: 2;    color: var(--midground-base);    font: 400 10px/15px "Geist Mono", "JetBrains Mono", "SF Mono", SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;    pointer-events: auto;    user-select: text;    -webkit-user-select: text;  }  #textLayer *::selection {    background: var(--selection-bg, var(--midground));    color: var(--background-base);  }  .flow-line {    position: absolute;    height: 20px;    white-space: pre;    overflow: visible;    color: var(--midground-base);  }  #annotationLayer {    position: absolute;    inset: 0;    z-index: 4;    pointer-events: none;    overflow: visible;  }  .annot {    position: absolute;    pointer-events: none;    will-change: opacity, transform;  }  .tok-keyword { color: color-mix(in srgb, var(--midground-base) 54%, #ff174d); }  .tok-string  { color: color-mix(in srgb, var(--midground-base) 52%, #ff6b3d); }  .tok-comment { color: color-mix(in srgb, var(--midground-base) 42%, #7f1d1d); }  .tok-number  { color: color-mix(in srgb, var(--midground-base) 50%, #ff2a6d); }  .tok-const   { color: color-mix(in srgb, var(--midground-base) 48%, #ff003c); }   .layer { position: fixed; inset: 0; pointer-events: none; }  .bg-lens   { z-index: 1; background-color: var(--background); mix-blend-mode: var(--background-blend); }  .vignette  { z-index: 99; background: radial-gradient(ellipse at 0% 0%, rgba(255,189,56,0) 60%, rgba(255,189,56,0.35) 100%); mix-blend-mode: lighten; opacity: 0.22; }  .fg-lens   { z-index: 100; background-color: var(--foreground); mix-blend-mode: var(--foreground-blend); }  .noise {    z-index: 101;    mix-blend-mode: difference;  }  #noiseCanvas {    width: 100%;    height: 100%;    display: block;  }  .terminal-cursor {    position: absolute;    width: 0.6em;    height: 1em;    background: #fff;    z-index: 6;    pointer-events: none;    mix-blend-mode: difference;    animation: term-blink 1s steps(1, end) infinite;  }  @keyframes term-blink {    0%, 60% { opacity: 1; }    60.01%, 100% { opacity: 0; }  }  .cursor-anchor {    display: inline-block;    width: 0;    height: 1em;    vertical-align: text-top;    pointer-events: none;  }   @font-face {    font-family: "Mondwest";    src: url("https://esm.sh/@nous-research/ui@0.4.0/dist/fonts/Mondwest-Regular.woff2") format("woff2");    font-weight: 400; font-style: normal; font-display: block;  }  @font-face {    font-family: "Geist Mono";    src: url("https://cdn.jsdelivr.net/npm/@fontsource/geist-mono@5.2.5/files/geist-mono-latin-400-normal.woff2") format("woff2");    font-weight: 400; font-style: normal; font-display: block;  }  @font-face {    font-family: "JetBrains Mono";    src: url("https://cdn.jsdelivr.net/npm/@fontsource/jetbrains-mono@5.2.5/files/jetbrains-mono-latin-400-normal.woff2") format("woff2");    font-weight: 400; font-style: normal; font-display: block;  }   .font-primer { position: fixed; left: -9999px; top: -9999px; font: 400 26px "Mondwest", serif; }</style></head><body><div id="stage"><div id="textLayer" aria-label="self-learning source stream"></div><div id="terminalCursor" class="terminal-cursor" hidden></div><canvas id="c"></canvas><canvas id="orbCanvas"></canvas></div><div class="layer bg-lens"></div><div class="layer vignette"></div><div class="layer fg-lens"></div><div class="layer noise"><canvas id="noiseCanvas"></canvas></div><div class="font-primer" aria-hidden="true">Aa Mondwest priming glyphs 0123456789</div> <script type="module">import {  prepareWithSegments,  layoutNextLineRange,  materializeLineRange,} from "https://esm.sh/@chenglou/pretext@0.0.6";import gsap from "https://esm.sh/gsap@3.12.5";import GUI from "https://esm.sh/lil-gui@0.19.2"; const SELF_LEARNING_SOURCE = String.raw`type ToolId=u16; type FeatureId=u32; type W=f32;struct Obs{tool:ToolId,reward:f32,lat:u32,feat:&'static[FeatureId],tok:u16}struct Belief{prior:W,conf:W,last:u64,uses:u32} #[inline] fn sig(x:f32)->f32{1.0/(1.0+(-x).exp())}#[inline] fn decay(now:u64,last:u64)->f32{1.0-((now-last)as f32/900.0).min(1.0)*0.22}#[inline] fn clamp8(x:f32)->f32{x.clamp(-8.0,8.0)} fn score(theta:&[W],b:&[Belief],o:&Obs)->f32{    let sparse=o.feat.iter().fold(0.0,|a,id|a+theta[*id as usize]);    let belief=b[o.tool as usize].prior*b[o.tool as usize].conf.max(0.15);    sparse+belief-(o.lat as f32*0.00072)-(o.tok as f32*0.00009)} fn update(theta:&mut[W],b:&mut[Belief],o:&Obs,now:u64,eta:f32){    let p=sig(score(theta,b,o));    let e=(o.reward-p).clamp(-1.0,1.0);    let step=eta*e/(o.feat.len().max(1)as f32).sqrt();     for id in o.feat{        let w=&mut theta[*id as usize];        *w=clamp8(*w+step-0.0003*w.signum());    }     let slot=&mut b[o.tool as usize];    let d=decay(now,slot.last);    let r=(o.reward-o.lat as f32*0.00035).clamp(-1.0,1.0);    slot.prior=slot.prior*d+r*0.14+e*0.06;    slot.conf=(slot.conf*0.93+r.abs()*0.07).min(1.0);    slot.uses=slot.uses.saturating_add(1);    slot.last=now;} fn consolidate(trace:&[Obs],theta:&mut[W],b:&mut[Belief],now:u64){    let eta=0.042/(1.0+(trace.len()as f32*0.003));    let mut credit=1.0;    for o in trace.iter().rev(){        update(theta,b,o,now,eta*credit);        credit*=0.985;    }} fn choose<'a>(theta:&[W],b:&[Belief],xs:&'a[Obs],temp:f32)->&'a Obs{    xs.iter().max_by(|a,bx|{        let sa=score(theta,b,a)/temp.max(0.05);        let sb=score(theta,b,bx)/temp.max(0.05);        sa.partial_cmp(&sb).unwrap()    }).unwrap()}`;const CODE_CHUNK = SELF_LEARNING_SOURCE  .trim()  .replace(/\t/g, "  ")  .replace(/\n{3,}/g, "\n\n");const STREAM_REPEAT_COUNT = 6;const streamCorpus = Array.from({ length: STREAM_REPEAT_COUNT }, () => CODE_CHUNK).join("\n\n"); const stage = document.getElementById("stage");const textLayer = document.getElementById("textLayer");const canvas = document.getElementById("c");const orbCanvas = document.getElementById("orbCanvas");const ctx = canvas.getContext("2d", { alpha: true });const orbCtx = orbCanvas.getContext("2d", { alpha: true });let W = 0, H = 0, DPR = 1;let canvasLeft = 0, canvasTop = 0, canvasW = 0, canvasH = 0; let BODY_FONT = '400 10px "Geist Mono", "JetBrains Mono", "SF Mono", SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace';let BODY_LINE_H = 15;let ASCII_FONT = '8px monospace';let CELL_W = 4.8;let CELL_H = 8;const CHARS = ' ·:;+=░▒▓█';const EMPTY = 0;const CHARS_LAST = CHARS.length - 1; const LOGO = ['N', 'O', 'U', 'S'].map((letter, i) => ({  letter,  rows: [    i === 0      ? '██░░░██,██░░░██,███░░██,█░██░██,█░░████,█░░░███,██░░░██,██░░░██,██░░░██'      : i === 1        ? '░█████░,██░░░██,██░░░██,██░░░██,██░░░██,██░░░██,██░░░██,██░░░██,░█████░'        : i === 2          ? '██░░░██,██░░░██,██░░░██,██░░░██,██░░░██,██░░░██,██░░░██,██░░░██,░█████░'          : '░█████░,██░░░██,██░░░░░,░████░░,░░░░██░,░░░░░██,██░░░██,██░░░██,░█████░'  ][0].split(',')})); function hash(x, y) {  const n = Math.sin(x * 127.1 + y * 311.7) * 43758.5453;  return n - Math.floor(n);}function charAt(t) {  return CHARS[Math.min(CHARS.length - 1, Math.max(0, Math.floor(t * CHARS.length)))];}function gridDist(v, step) {  return Math.abs(v / step - Math.round(v / step)) * step;}function rot(a, b, angle) {  const c = Math.cos(angle), s = Math.sin(angle);  return [a * c - b * s, a * s + b * c];}function hexToRGB(h) {  const v = parseInt(h.slice(1), 16);  return [(v >> 16) & 255, (v >> 8) & 255, v & 255];} let fontsReady = false;let bodyPrepared = null;function rebuildLayouts() {  bodyPrepared = prepareWithSegments(streamCorpus, BODY_FONT, { whiteSpace: "pre-wrap" });} const LENS_0 = { bgBlend: "difference", bgColor: "#041C1C", bgOpacity: 1, mgColor: "#ffe6cb", mgOpacity: 1, fgBlend: "difference", fgColor: "#FFFFFF", fgOpacity: 0 };const LENS_5I = { bgBlend: "multiply", bgColor: "#ffffff", bgOpacity: 1, mgColor: "#fffff5", mgOpacity: 1, fgBlend: "difference", fgColor: "#041a1f", fgOpacity: 1 };const LENS = { current: null, mgBase: null, depthColors: null };function colorMix(color, alpha) { return `color-mix(in srgb, ${color} ${Math.round(alpha * 100)}%, transparent)`; }function precomputeDepthColors(mgColor) {  const [r, g, b] = hexToRGB(mgColor);  return Array.from({ length: 64 }, (_, i) => {    const depth = i / 63;    const m = 0.4 + depth * 0.6;    return `rgba(${(r * m) | 0},${(g * m) | 0},${(b * m) | 0},${depth})`;  });}function applyLens(preset) {  LENS.current = preset;  LENS.mgBase = preset.mgColor;  LENS.depthColors = precomputeDepthColors(preset.mgColor);  const s = document.documentElement.style;  for (const [name, color, alpha] of [["foreground", preset.fgColor, preset.fgOpacity], ["midground", preset.mgColor, preset.mgOpacity], ["background", preset.bgColor, preset.bgOpacity]]) {    s.setProperty(`--${name}`, colorMix(color, alpha));    s.setProperty(`--${name}-base`, color);    s.setProperty(`--${name}-alpha`, `${alpha}`);  }  s.setProperty("--background-blend", preset.bgBlend);  s.setProperty("--foreground-blend", preset.fgBlend);}applyLens(LENS_0);addEventListener("keydown", e => { if (e.key === "x" || e.key === "X") applyLens(LENS.current === LENS_0 ? LENS_5I : LENS_0); });const SELECTION_COLORS = [  "oklch(85% 0.12 330)",  "oklch(85% 0.12 300)",  "oklch(85% 0.12 270)",  "oklch(85% 0.12 230)",  "oklch(85% 0.12 180)",  "oklch(85% 0.12 150)",  "oklch(85% 0.12 120)",  "oklch(85% 0.12 90)",  "oklch(85% 0.12 60)",  "oklch(85% 0.12 30)",  "oklch(88% 0.10 80)",];let selectionIdx = 0;function cycleSelectionColor() {  requestAnimationFrame(() => {    document.documentElement.style.setProperty("--selection-bg", SELECTION_COLORS[(selectionIdx = (selectionIdx + 1) % SELECTION_COLORS.length)]);  });}document.addEventListener("selectstart", cycleSelectionColor);addEventListener("keydown", e => {  if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "a") {    cycleSelectionColor();    e.preventDefault();    const selection = getSelection();    const range = document.createRange();    range.selectNodeContents(textLayer);    selection.removeAllRanges();    selection.addRange(range);  }}); let asciiMask, asciiChar, asciiGlyph, orbMask, orbChar, orbGlyph, obstacleRows, intervals;let cols = 0, rows = 0;let canvasCols = 0, canvasRows = 0;let lastFrameTime = 0;const drawnLines = { x: [], y: [], w: [], text: [], count: 0 };let lineNodes = [];let terminalAnchorEl = null;let terminalLineEl = null;let orbShapeControl = null;let autoCubeCall = null;let lastTextSignature = "";const ORB = {  panX: 0, panY: 0,  orbitX: -1.42, orbitY: 0,  radius: 0.135,  zoom: 3,  cubeScale: 1.8,  autoSpin: 0.18,  lat: 19,  lon: 30,  wire: 0.2,  shape: "sphere",  shapeMix: 0,  drag: null,  positioned: false,  staged: false,};const SCENE = {  orbBuildDuration: 1.45,}; let nousStartTime = null;const AUTO_CUBE_AFTER_NOUS_RESOLVE = 0.0;function setOrbShape(shape, { reveal = false, syncControl = true } = {}) {  ORB.shape = shape;  if (syncControl) {    ctrls.orbShape = shape;    orbShapeControl?.updateDisplay();  }  if (reveal) {    gsap.killTweensOf(orbCanvas);    orbCanvas.style.opacity = "1";  }  gsap.killTweensOf(ORB, "shapeMix");  gsap.to(ORB, {    shapeMix: shape === "cube" ? 1 : 0,    duration: Math.max(0.01, ctrls.shapeMorphDuration ?? 0.65),    ease: "power2.inOut",    onUpdate: () => { lastTextSignature = ""; },  });  lastTextSignature = "";}function sceneTimeNow() {  return (typeof performance !== "undefined" ? performance.now() : Date.now()) * 0.001 - sceneEpoch;}function armNous(atTime = sceneTimeNow()) {  if (nousStartTime !== null) return;  const startAt = Math.max(0, atTime);  nousStartTime = startAt;  const delay = Math.max(0, startAt - sceneTimeNow()) + Math.max(0, ctrls.orbContainNous ?? 1.0);  gsap.killTweensOf(orbCanvas);  gsap.to(orbCanvas, {    opacity: 0,    duration: Math.max(0.05, ctrls.orbFadeDuration ?? 0.7),    delay,    ease: "power2.out",  });  autoCubeCall?.kill();  const nousResolveAt = startAt + (LOGO.length - 1) * 0.4 + 1.0;  const cubeAt = nousResolveAt + AUTO_CUBE_AFTER_NOUS_RESOLVE;  autoCubeCall = gsap.delayedCall(Math.max(0, cubeAt - sceneTimeNow()), () => {    setOrbShape("cube", { reveal: true, syncControl: false });  });}function orbBuildStartTime() {  return Math.max(0, ctrls.orbLead ?? 0);}function codeTypingStartTime() {  return Math.max(0, orbBuildStartTime() + SCENE.orbBuildDuration * 0.62 - 0.3);} function logoCenterPan() {  const aspect = cols / rows / 2;  const h = 0.38;  const w = h * 2.6 * 0.55;  const halfStageX = (w / 2 / aspect) * W;  const centerX = W * 0.5;  const centerY = H * 0.5;  return {    panX: centerX / W - 0.5,    panY: centerY / H - 0.5,    left: centerX - halfStageX,    right: centerX + halfStageX,  };} function startSceneTimeline() {  if (ORB.staged || !cols || !rows || !W || !H) return;  ORB.staged = true;  const now = sceneTimeNow();  const startAt = Math.max(0, orbBuildStartTime() + SCENE.orbBuildDuration * 0.72 - now);  gsap.timeline({ delay: startAt })    .to(ORB, {      orbitX: 0.55,      orbitY: `+=${Math.PI * 1.85}`,      duration: 2.4,      ease: "power3.inOut",      onUpdate: () => { lastTextSignature = ""; },    }, "<")    .to(ORB, {      radius: 0.125,      duration: 2.4,      ease: "power3.inOut",      onUpdate: () => { lastTextSignature = ""; },    }, "<")    .to(ORB, {      wire: ctrls.orbWireLow,      duration: ctrls.wireDropDuration,      ease: "power2.inOut",      onUpdate: () => { lastTextSignature = ""; },      onComplete: () => {        armNous(sceneTimeNow() + Math.max(0, ctrls.nousAfterWire));      },    });} function resize() {  const r = stage.getBoundingClientRect();  W = Math.max(1, Math.floor(r.width));  H = Math.max(1, Math.floor(r.height));  canvasLeft = r.left;  canvasTop = r.top;  canvasW = Math.max(1, window.innerWidth);  canvasH = Math.max(1, window.innerHeight);  DPR = Math.min(window.devicePixelRatio || 1, 2);  canvas.width = Math.floor(canvasW * DPR);  canvas.height = Math.floor(canvasH * DPR);  orbCanvas.width = Math.floor(canvasW * DPR);  orbCanvas.height = Math.floor(canvasH * DPR);  ctx.setTransform(DPR, 0, 0, DPR, 0, 0);  orbCtx.setTransform(DPR, 0, 0, DPR, 0, 0);  ctx.font = ASCII_FONT;  orbCtx.font = ASCII_FONT;  CELL_W = ctx.measureText("M").width || CELL_W;  cols = Math.ceil(W / CELL_W);  rows = Math.ceil(H / CELL_H);  canvasCols = Math.ceil(canvasW / CELL_W);  canvasRows = Math.ceil(canvasH / CELL_H);  asciiMask = new Uint8Array(canvasCols * canvasRows);  asciiChar = new Uint8Array(canvasCols * canvasRows);  asciiGlyph = new Array(canvasCols * canvasRows);  orbMask = new Uint8Array(canvasCols * canvasRows);  orbChar = new Uint8Array(canvasCols * canvasRows);  orbGlyph = new Array(canvasCols * canvasRows);  obstacleRows = Array.from({ length: rows }, () => []);  intervals = new Float32Array(64);  if (!ORB.positioned && W > 100 && H > 100) {    ORB.panX = 0;    ORB.panY = 0;    ORB.positioned = true;  }  startSceneTimeline();}addEventListener("resize", () => { lastTextSignature = ""; resize(); });function orbCenter() {  const rNorm = ORB.radius * ORB.zoom;  return {    x: (0.5 + ORB.panX) * W,    y: (0.5 + ORB.panY) * H,    canvasX: (0.5 + ORB.panX) * W + canvasLeft,    canvasY: (0.5 + ORB.panY) * H + canvasTop,    r: rNorm * H,    rNorm,  };} stage.addEventListener("pointerdown", e => {  const rect = stage.getBoundingClientRect();  const center = orbCenter();  const hitScale = ORB.shape === "cube" ? ORB.cubeScale : 1;  const dx = e.clientX - rect.left - center.x;  const dy = e.clientY - rect.top - center.y;  if (Math.hypot(dx, dy) > center.r * 1.35 * hitScale) return;  e.preventDefault();  stage.setPointerCapture(e.pointerId);  ORB.drag = {    id: e.pointerId,    mode: (e.ctrlKey || e.metaKey) ? "rotate" : "move",    dx,    dy,    lastX: e.clientX,    lastY: e.clientY,  };});stage.addEventListener("pointermove", e => {  if (!ORB.drag || ORB.drag.id !== e.pointerId) return;  if (ORB.drag.mode === "rotate" || e.ctrlKey || e.metaKey) {    const deltaX = e.clientX - ORB.drag.lastX;    const deltaY = e.clientY - ORB.drag.lastY;    ORB.orbitY += (deltaX / Math.max(1, W)) * Math.PI * 2;    ORB.orbitX = Math.max(-1.5, Math.min(1.5, ORB.orbitX + (deltaY / Math.max(1, H)) * 4));    ORB.drag.mode = "rotate";    ORB.drag.lastX = e.clientX;    ORB.drag.lastY = e.clientY;    return;  }  const rect = stage.getBoundingClientRect();  const cx = e.clientX - rect.left - ORB.drag.dx;  const cy = e.clientY - rect.top - ORB.drag.dy;  ORB.panX = cx / W - 0.5;  ORB.panY = cy / H - 0.5;  lastTextSignature = "";});stage.addEventListener("wheel", e => {  const rect = stage.getBoundingClientRect();  const center = orbCenter();  const hitScale = ORB.shape === "cube" ? ORB.cubeScale : 1;  const dx = e.clientX - rect.left - center.x;  const dy = e.clientY - rect.top - center.y;  if (Math.hypot(dx, dy) > center.r * 1.5 * hitScale) return;  e.preventDefault();  ORB.zoom *= Math.exp(-e.deltaY * 0.0012);  ORB.zoom = Math.max(0.25, Math.min(12, ORB.zoom));  lastTextSignature = "";}, { passive: false });stage.addEventListener("pointerup", e => {  if (ORB.drag?.id === e.pointerId) ORB.drag = null;});stage.addEventListener("pointercancel", e => {  if (ORB.drag?.id === e.pointerId) ORB.drag = null;});stage.addEventListener("dblclick", e => {  const rect = stage.getBoundingClientRect();  const center = orbCenter();  const hitScale = ORB.shape === "cube" ? ORB.cubeScale : 1;  const dx = e.clientX - rect.left - center.x;  const dy = e.clientY - rect.top - center.y;  if (Math.hypot(dx, dy) > center.r * 1.45 * hitScale) return;  e.preventDefault();  setOrbShape(ORB.shape === "sphere" ? "cube" : "sphere", { reveal: true });}); function rasterizeNOUS(time) {  asciiMask.fill(0);  asciiGlyph.fill("");  for (const r of obstacleRows) r.length = 0;   // NOUS uses the same cell-shader/reveal language as bb-ascii's source logo.  if (nousStartTime === null || time < nousStartTime) {    mergeObstacleRows();    return;  }  const t = Math.max(0, time - nousStartTime);  const aspect = cols / rows / 2;  const h = 0.38;  const baseW = h * 2.6 * 0.55;  const letterWidth = 7;  const baseLetterPitch = 7.6;  const letterGap = Math.max(0, ctrls.nousLetterSpacing ?? 0);  const letterAdvance = baseLetterPitch + letterGap;  const baseLogoUnits = LOGO.length * baseLetterPitch - 0.6;  const logoUnits = LOGO.length * baseLetterPitch + (LOGO.length - 1) * letterGap - 0.6;  const w = baseW * (logoUnits / baseLogoUnits);  const nousRowMin = new Float32Array(rows);  const nousRowMax = new Float32Array(rows);  nousRowMin.fill(Infinity);  nousRowMax.fill(-Infinity);   for (let y = 0; y < rows; y++) {    const ny = y / rows - 0.5;    if (Math.abs(ny) > h / 2) continue;     for (let x = 0; x < cols; x++) {      const nx = (x / cols - 0.5) * aspect;      if (Math.abs(nx) > w / 2) continue;       const lx = (nx + w / 2) / w;      const ly = (ny + h / 2) / h;      const tx = lx * logoUnits;      const idx = Math.floor(tx / letterAdvance);      const cx = Math.floor(tx - idx * letterAdvance);      const cy = Math.floor(ly * 9);       if (idx < 0 || idx >= LOGO.length || cx >= letterWidth) continue;       const { letter, rows: glyphRows } = LOGO[idx];      const row = glyphRows[cy];      if (!row || cx >= row.length || row[cx] !== '█') continue;       const localT = Math.max(0, t - idx * 0.4);      const phase1 = Math.min(1, localT);      const phase2 = Math.min(1, Math.max(0, localT - 1));      const reveal = phase1 * 1.5 - hash(cx, cy + idx * 10) * 0.5;       if (phase1 < 1 && reveal <= 0) continue;       const morph = phase2 + hash(cx, cy) * 0.3;      const ch = phase1 < 1        ? (reveal >= 1 ? letter : charAt(reveal))        : charAt(morph);      const drawC = Math.floor((canvasLeft + x * CELL_W + CELL_W * 0.5) / CELL_W);      const drawR = Math.floor((canvasTop + y * CELL_H + CELL_H * 0.5) / CELL_H);      let cellIdx = -1;      if (drawR >= 0 && drawR < canvasRows && drawC >= 0 && drawC < canvasCols) {        cellIdx = drawR * canvasCols + drawC;        asciiMask[cellIdx] = 1;        asciiChar[cellIdx] = Math.max(0, CHARS.indexOf(ch));        if (CHARS.indexOf(ch) < 0) asciiGlyph[cellIdx] = ch;      }      const x0 = x * CELL_W;      const x1 = x0 + CELL_W;      if (x0 < nousRowMin[y]) nousRowMin[y] = x0;      if (x1 > nousRowMax[y]) nousRowMax[y] = x1;      // bb-ascii turns late morph cells into solid dark cell backgrounds. In      // this transparent/lensed canvas, a full-block char carries that punch.      if (cellIdx >= 0 && phase1 >= 1 && morph > 0.8) asciiChar[cellIdx] = CHARS.length - 1;    }  }   // Slightly dilate/smooth the measured NOUS silhouette so surrounding text  // does not visually "kiss" sharp letter corners (notably on the S).  const smoothMin = new Float32Array(rows);  const smoothMax = new Float32Array(rows);  smoothMin.fill(Infinity);  smoothMax.fill(-Infinity);  for (let y = 0; y < rows; y++) {    if (!Number.isFinite(nousRowMin[y])) continue;    for (let oy = -1; oy <= 1; oy++) {      const yy = y + oy;      if (yy < 0 || yy >= rows) continue;      if (!Number.isFinite(nousRowMin[yy])) continue;      if (nousRowMin[yy] < smoothMin[y]) smoothMin[y] = nousRowMin[yy];      if (nousRowMax[yy] > smoothMax[y]) smoothMax[y] = nousRowMax[yy];    }  }   const pad = Math.max(4, CELL_W * 1.25);  const bleedRows = 2;  for (let y = 0; y < rows; y++) {    if (!Number.isFinite(smoothMin[y])) continue;    for (let oy = -bleedRows; oy <= bleedRows; oy++) {      const row = y + oy;      if (row < 0 || row >= rows) continue;      const bleedPad = pad + Math.abs(oy) * CELL_W * 0.28;      obstacleRows[row].push([        Math.max(0, smoothMin[y] - bleedPad),        Math.min(W, smoothMax[y] + bleedPad),      ]);    }  }   mergeObstacleRows();} function mergeObstacleRows() {  for (const spans of obstacleRows) {    if (spans.length < 2) continue;    spans.sort((a, b) => a[0] - b[0]);    let w = 0;    for (let i = 1; i < spans.length; i++) {      const last = spans[w], cur = spans[i];      if (cur[0] <= last[1] + CELL_W * 3.1) last[1] = Math.max(last[1], cur[1]);      else spans[++w] = cur;    }    spans.length = w + 1;  }} const CUBE_VERTS = [  [-1, -1, -1], [1, -1, -1], [1, 1, -1], [-1, 1, -1],  [-1, -1, 1], [1, -1, 1], [1, 1, 1], [-1, 1, 1],];const CUBE_EDGES = [  [0, 1], [1, 2], [2, 3], [3, 0],  [4, 5], [5, 6], [6, 7], [7, 4],  [0, 4], [1, 5], [2, 6], [3, 7],];function projectCubeVerts(center, radiusPx, orbitX, orbitY, scale = 1) {  const camZ = 3.3;  const projScale = radiusPx * 1.34 * scale;  return CUBE_VERTS.map(([vx, vy, vz]) => {    const [rx, ry, rz] = rotateOrbVec(vx, vy, vz, orbitX, orbitY);    const inv = 1 / (camZ + rz);    return {      x: center.canvasX + rx * projScale * inv,      y: center.canvasY + ry * projScale * inv,      d: (rz + 1) * 0.5,    };  });}function convexHull(points) {  if (points.length <= 2) return points.slice();  const pts = points    .map(p => ({ x: p.x, y: p.y }))    .sort((a, b) => (a.x - b.x) || (a.y - b.y));  const cross = (o, a, b) => (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);  const lower = [];  for (const p of pts) {    while (lower.length >= 2 && cross(lower[lower.length - 2], lower[lower.length - 1], p) <= 0) lower.pop();    lower.push(p);  }  const upper = [];  for (let i = pts.length - 1; i >= 0; i--) {    const p = pts[i];    while (upper.length >= 2 && cross(upper[upper.length - 2], upper[upper.length - 1], p) <= 0) upper.pop();    upper.push(p);  }  lower.pop();  upper.pop();  return lower.concat(upper);}function makeEmptySpanRows() {  return Array.from({ length: rows }, () => ({ left: Infinity, right: -Infinity }));}function addSpan(spanRows, row, left, right) {  if (row < 0 || row >= rows || right <= left) return;  if (left < spanRows[row].left) spanRows[row].left = left;  if (right > spanRows[row].right) spanRows[row].right = right;}function collectCubeObstacleRows(center, radiusPx, orbitX, orbitY, obstacleScale) {  const spanRows = makeEmptySpanRows();  if (obstacleScale <= 0.0001) return spanRows;  const projected = projectCubeVerts(center, radiusPx, orbitX, orbitY, ORB.cubeScale * obstacleScale)    .map(p => ({ x: p.x - canvasLeft, y: p.y - canvasTop }));  const hull = convexHull(projected);  if (hull.length < 3) return spanRows;   let yMin = Infinity;  let yMax = -Infinity;  for (const p of hull) {    if (p.y < yMin) yMin = p.y;    if (p.y > yMax) yMax = p.y;  }  const r0 = Math.max(0, Math.floor((yMin - CELL_H) / CELL_H));  const r1 = Math.min(rows - 1, Math.ceil((yMax + CELL_H) / CELL_H));  const pad = Math.max(4, CELL_W * 1.6);   for (let row = r0; row <= r1; row++) {    const scanY = row * CELL_H + CELL_H * 0.5;    const xs = [];    for (let i = 0; i < hull.length; i++) {      const a = hull[i];      const b = hull[(i + 1) % hull.length];      const y0 = a.y;      const y1 = b.y;      if ((y0 <= scanY && scanY < y1) || (y1 <= scanY && scanY < y0)) {        const t = (scanY - y0) / (y1 - y0);        xs.push(a.x + (b.x - a.x) * t);      }    }    if (xs.length < 2) continue;    xs.sort((a, b) => a - b);    for (let i = 0; i + 1 < xs.length; i += 2) {      const left = Math.max(0, xs[i] - pad);      const right = Math.min(W, xs[i + 1] + pad);      if (right - left > CELL_W) addSpan(spanRows, row, left, right);    }  }  return spanRows;}function collectSphereObstacleRows(center, radiusNorm, obstacleScale, rPad) {  const spanRows = makeEmptySpanRows();  const obstacleRadiusNorm = radiusNorm * obstacleScale;  const obstaclePadNorm = (rPad / H) * obstacleScale;  if (obstacleRadiusNorm <= 0.0001) return spanRows;   const radiusPx = radiusNorm * H;  const rMin = Math.floor((center.canvasY - radiusPx - rPad) / CELL_H);  const rMax = Math.ceil((center.canvasY + radiusPx + rPad) / CELL_H);  for (let r = rMin; r <= rMax; r++) {    const y = r * CELL_H + CELL_H * 0.5;    const stageY = y - canvasTop;    const pyForObstacle = stageY / H - 0.5 - ORB.panY;    if (Math.abs(pyForObstacle) > obstacleRadiusNorm + obstaclePadNorm) continue;    const halfNorm = Math.sqrt(Math.max(0, (obstacleRadiusNorm + obstaclePadNorm) ** 2 - pyForObstacle * pyForObstacle));    const halfPx = halfNorm * H;    const stageRow = Math.floor(stageY / CELL_H);    addSpan(spanRows, stageRow, center.x - halfPx, center.x + halfPx);  }  return spanRows;}function pushMorphedObstacleRows(sphereRows, cubeRows, mix) {  const centerX = W * (0.5 + ORB.panX);  for (let row = 0; row < rows; row++) {    const sphereValid = Number.isFinite(sphereRows[row].left);    const cubeValid = Number.isFinite(cubeRows[row].left);    if (!sphereValid && !cubeValid) continue;    const sLeft = sphereValid ? sphereRows[row].left : centerX;    const sRight = sphereValid ? sphereRows[row].right : centerX;    const cLeft = cubeValid ? cubeRows[row].left : centerX;    const cRight = cubeValid ? cubeRows[row].right : centerX;    const left = sLeft + (cLeft - sLeft) * mix;    const right = sRight + (cRight - sRight) * mix;    if (right - left > CELL_W) obstacleRows[row].push([Math.max(0, left), Math.min(W, right)]);  }}function rotateOrbVec(vx, vy, vz, orbitX, orbitY) {  let a = rot(vy, vz, orbitX); vy = a[0]; vz = a[1];  a = rot(vx, vz, orbitY); vx = a[0]; vz = a[1];  return [vx, vy, vz];}function rasterizeCubeWire(center, radiusPx, buildPhase, orbitX, orbitY, weight = 1) {  const unit = Math.max(1, Math.min(CELL_W, CELL_H));  const wirePx = Math.max(1, ORB.wire * radiusPx * 0.17);  const brush = Math.max(0, Math.ceil((wirePx - unit * 0.45) / unit));  const projected = projectCubeVerts(center, radiusPx, orbitX, orbitY, ORB.cubeScale);   for (let ei = 0; ei < CUBE_EDGES.length; ei++) {    const [a, b] = CUBE_EDGES[ei];    const p0 = projected[a];    const p1 = projected[b];    const dx = p1.x - p0.x;    const dy = p1.y - p0.y;    const len = Math.hypot(dx, dy);    const steps = Math.max(2, Math.ceil(len / Math.max(1, unit * 0.45)));    for (let s = 0; s <= steps; s++) {      const t = s / steps;      const px = p0.x + dx * t;      const py = p0.y + dy * t;      const depth = p0.d + (p1.d - p0.d) * t;      const c = Math.floor(px / CELL_W);      const r = Math.floor(py / CELL_H);      for (let oy = -brush; oy <= brush; oy++) {        for (let ox = -brush; ox <= brush; ox++) {          if (brush > 0 && ox * ox + oy * oy > brush * brush + 0.25) continue;          const cc = c + ox;          const rr = r + oy;          if (rr < 0 || rr >= canvasRows || cc < 0 || cc >= canvasCols) continue;          const reveal = buildPhase * 1.45 - hash(cc + ei * 13, rr + ei * 17) * 0.5;          if (buildPhase < 1 && reveal <= 0) continue;          const idx = rr * canvasCols + cc;          const val = buildPhase < 1            ? Math.min(CHARS_LAST, Math.floor(reveal * CHARS_LAST))            : Math.min(CHARS_LAST, Math.floor((0.45 + depth * 0.55) * CHARS_LAST));          const weightedVal = Math.min(CHARS_LAST, Math.floor(val * weight));          if (weightedVal <= 0) continue;          orbMask[idx] = 1;          orbGlyph[idx] = "";          if (weightedVal > orbChar[idx]) orbChar[idx] = weightedVal;        }      }    }  }} function rasterizeOrb(time) {  orbMask.fill(0);  orbChar.fill(0);  orbGlyph.fill("");  const buildStart = orbBuildStartTime();  if (time < buildStart) return;  const shapeMix = Math.max(0, Math.min(1, ORB.shapeMix ?? (ORB.shape === "cube" ? 1 : 0)));  const sphereWeight = 1 - shapeMix;  const cubeWeight = shapeMix;  const sphereOrbitX = ORB.orbitX;  const sphereOrbitY = ORB.orbitY + time * ORB.autoSpin;  const cubeOrbitX = ORB.orbitX + time * ORB.autoSpin * 0.72;  const cubeOrbitY = ORB.orbitY + time * ORB.autoSpin * 1.35;  const latStep = Math.PI / ORB.lat;  const lonStep = (Math.PI * 2) / ORB.lon;  const rPad = 15.5;  const center = orbCenter();  const radiusPx = center.r;  const radiusNorm = center.rNorm;  const buildPhase = Math.min(1, Math.max(0, (time - buildStart) / SCENE.orbBuildDuration));  const spacePhase = Math.min(1, Math.max(0, (time - buildStart) / Math.max(0.001, ctrls.orbSpaceDuration)));  const spaceGrow = Math.pow(spacePhase, Math.max(0.01, ctrls.orbSpaceEase));  const spaceStart = Math.max(0, Math.min(1, ctrls.orbSpaceStart));  const obstacleScale = spaceStart + (1 - spaceStart) * spaceGrow;  const rMin = Math.floor((center.canvasY - radiusPx - rPad) / CELL_H);  const rMax = Math.ceil((center.canvasY + radiusPx + rPad) / CELL_H);   const sphereObstacleRows = collectSphereObstacleRows(center, radiusNorm, obstacleScale, rPad);  const cubeObstacleRows = collectCubeObstacleRows(center, radiusPx, cubeOrbitX, cubeOrbitY, obstacleScale);  pushMorphedObstacleRows(sphereObstacleRows, cubeObstacleRows, shapeMix);   if (cubeWeight > 0.001) {    rasterizeCubeWire(center, radiusPx, buildPhase, cubeOrbitX, cubeOrbitY, cubeWeight);  }  if (sphereWeight <= 0.001) {    mergeObstacleRows();    return;  }   const cMin = Math.floor((center.canvasX - radiusPx - rPad) / CELL_W);  const cMax = Math.ceil((center.canvasX + radiusPx + rPad) / CELL_W);  for (let r = rMin; r <= rMax; r++) {    const y = r * CELL_H + CELL_H * 0.5;    const stageY = y - canvasTop;    for (let c = cMin; c <= cMax; c++) {      // Direct port of bb-ascii/app/nous/sphere/page.tsx:      // px uses width/height normalization, py is row-normalized, then both      // are offset by pan. This is what makes the sphere circular on screen.      const stageX = c * CELL_W + CELL_W * 0.5 - canvasLeft;      const px = (stageX / W - 0.5 - ORB.panX) * (W / H);      const py = stageY / H - 0.5 - ORB.panY;      const d2 = px * px + py * py;      if (d2 > radiusNorm * radiusNorm) continue;       const pz = Math.sqrt(radiusNorm * radiusNorm - d2);      const depth = (pz / radiusNorm) * 0.5 + 0.5;       let sx = px / radiusNorm, sy = py / radiusNorm, sz = pz / radiusNorm;      let a = rot(sy, sz, sphereOrbitX); sy = a[0]; sz = a[1];      a = rot(sx, sz, sphereOrbitY); sx = a[0]; sz = a[1];       const latD = gridDist(Math.asin(Math.max(-1, Math.min(1, sy))) + Math.PI / 2, latStep);      const lonD = gridDist(Math.atan2(sz, sx) + Math.PI, lonStep);      const wire = ORB.wire * (0.3 + depth * 0.7);      const minD = Math.min(latD, lonD);      if (minD > wire) continue;       const edge = 1 - minD / wire;      if (r >= 0 && r < canvasRows && c >= 0 && c < canvasCols) {        const reveal = buildPhase * 1.5 - hash(c, r) * 0.5;        if (buildPhase < 1 && reveal <= 0) continue;        const idx = r * canvasCols + c;        const val = buildPhase < 1          ? Math.min(CHARS_LAST, Math.floor(reveal * CHARS_LAST))          : Math.min(CHARS_LAST, Math.floor(edge * (0.4 + depth * 0.6) * CHARS.length));        const weightedVal = Math.min(CHARS_LAST, Math.floor(val * sphereWeight));        if (weightedVal <= 0) continue;        orbMask[idx] = 1;        orbGlyph[idx] = "";        if (weightedVal > orbChar[idx]) orbChar[idx] = weightedVal;      }    }  }   mergeObstacleRows();} let typedChars = 0;let typedPrepared = null;let typedDirty = true;let typedBudget = 0; function tickTyping(time, dt) {  if (!bodyPrepared) return;  if (time < codeTypingStartTime()) return;  const cps = Math.max(1, ctrls.typeSpeed ?? 42);  typedBudget += dt * cps;  const steps = Math.min(240, Math.floor(typedBudget));  if (steps > 0) {    typedBudget -= steps;    typedChars = Math.min(streamCorpus.length, typedChars + steps);    typedDirty = true;  }} function getTypedPrepared() {  if (!typedDirty && typedPrepared) return typedPrepared;  const cap = Math.max(0, Math.floor(typedChars));  const visible = streamCorpus.slice(0, cap);  typedPrepared = prepareWithSegments(visible, BODY_FONT, { whiteSpace: "pre-wrap" });  typedDirty = false;  return typedPrepared;} function layoutParagraphs() {  drawnLines.count = 0;  if (typedChars <= 0) return;  const prepared = getTypedPrepared();  ctx.font = BODY_FONT;  const MARGIN_X = Math.max(32, Math.min(72, W * 0.06));  const MIN_LINE_W = 120;  const yStart = Math.max(32, H * 0.08);  const yEnd = H - 32;  let cursor = { segmentIndex: 0, graphemeIndex: 0 };   for (let y = yStart; y < yEnd; y += BODY_LINE_H) {    const row = Math.max(0, Math.min(rows - 1, Math.floor(y / CELL_H)));    const spans = obstacleRows[row] || [];    let n = 0;    let x = MARGIN_X;     for (const [a, b] of spans) {      const left = Math.max(MARGIN_X, a);      const right = Math.min(W - MARGIN_X, b);      if (left - x >= MIN_LINE_W) { intervals[n++] = x; intervals[n++] = left - 10; }      x = Math.max(x, right + 10);    }    if (W - MARGIN_X - x >= MIN_LINE_W) { intervals[n++] = x; intervals[n++] = W - MARGIN_X; }    if (!n) { intervals[n++] = MARGIN_X; intervals[n++] = W - MARGIN_X; }     for (let k = 0; k < n; k += 2) {      const ix0 = intervals[k], ix1 = intervals[k + 1];      const range = layoutNextLineRange(prepared, cursor, ix1 - ix0);      if (!range) return;      const line = materializeLineRange(prepared, range);      const i = drawnLines.count++;      drawnLines.x[i] = ix0;      drawnLines.y[i] = y;      drawnLines.w[i] = ix1 - ix0;      drawnLines.text[i] = line.text;      cursor = range.end;    }  }} function escapeHTML(text) {  return text    .replaceAll("&", "&amp;")    .replaceAll("<", "&lt;")    .replaceAll(">", "&gt;");} function highlightSource(text) {  const token = /("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\/\/[^\n]*|\b(?:as|break|const|else|enum|false|fn|for|if|impl|in|let|match|mut|pub|return|self|static|struct|true|type|use|where|while)\b|\b[A-Z_]{2,}\b|\b\d+(?:\.\d+)?\b)/g;  let out = "";  let last = 0;  for (const match of text.matchAll(token)) {    const value = match[0];    out += escapeHTML(text.slice(last, match.index));    const cls = value.startsWith("#")      ? "tok-comment"      : value.startsWith('"') || value.startsWith("'")        ? "tok-string"        : /^\d/.test(value)          ? "tok-number"          : /^[A-Z_]{2,}$/.test(value)            ? "tok-const"            : "tok-keyword";    out += `<span class="${cls}">${escapeHTML(value)}</span>`;    last = match.index + value.length;  }  out += escapeHTML(text.slice(last));  return out;} function renderTextLayer() {  const parts = [LENS.mgBase, drawnLines.count];  for (let i = 0; i < drawnLines.count; i++) {    parts.push(drawnLines.x[i] | 0, drawnLines.y[i] | 0, drawnLines.w[i] | 0, drawnLines.text[i]);  }  const signature = parts.join("|");  if (signature === lastTextSignature) return;  lastTextSignature = signature;   ctx.font = BODY_FONT;  textLayer.style.color = LENS.mgBase;  textLayer.style.setProperty("--midground-base", LENS.mgBase);   while (lineNodes.length < drawnLines.count) {    const el = document.createElement("div");    el.className = "flow-line";    textLayer.appendChild(el);    lineNodes.push(el);  }  terminalAnchorEl = null;  terminalLineEl = null;  for (let i = 0; i < lineNodes.length; i++) {    const el = lineNodes[i];    if (i >= drawnLines.count) {      el.hidden = true;      continue;    }    const text = drawnLines.text[i];    const isTail = i === drawnLines.count - 1;    el.hidden = false;    el.innerHTML = isTail      ? `${highlightSource(text)}<span class="cursor-anchor" aria-hidden="true"></span>`      : highlightSource(text);    el.style.left = `${drawnLines.x[i]}px`;    el.style.top = `${drawnLines.y[i] - BODY_LINE_H + 4}px`;    el.style.width = `${drawnLines.w[i]}px`;    el.style.textAlign = "left";    el.style.wordSpacing = "0px";    if (isTail) {      terminalAnchorEl = el.querySelector(".cursor-anchor");      terminalLineEl = el;    }  }}  let sceneEpoch = 0; function restartScene() {  gsap.killTweensOf(ORB);  gsap.killTweensOf(orbCanvas);  autoCubeCall?.kill();  autoCubeCall = null;  ORB.panX = 0;  ORB.panY = 0;  ORB.zoom = ctrls.orbZoom;  ORB.cubeScale = ctrls.cubeScale;  ORB.shape = ctrls.orbShape;  ORB.shapeMix = ctrls.orbShape === "cube" ? 1 : 0;  ORB.orbitX = -1.42;  ORB.orbitY = 0;  ORB.wire = ctrls.orbWire;  orbCanvas.style.opacity = "1";  ORB.staged = false;  ORB.positioned = true;  typedChars = 0;  typedBudget = 0;  typedPrepared = null;  typedDirty = true;  nousStartTime = null;  sceneEpoch = (typeof performance !== "undefined" ? performance.now() : Date.now()) * 0.001;  lastTextSignature = "";  startSceneTimeline();} function applyFontSizes() {  const bodyPx = ctrls.bodyFontSize;  const asciiPx = ctrls.asciiFontSize;  BODY_FONT = `400 ${bodyPx}px "JetBrains Mono", "SF Mono", SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace`;  BODY_LINE_H = Math.round(bodyPx * 1.45);  ASCII_FONT = `${asciiPx}px monospace`;  CELL_H = asciiPx;  textLayer.style.font = `400 ${bodyPx}px/${BODY_LINE_H}px "JetBrains Mono", "SF Mono", SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace`;  rebuildLayouts();  typedPrepared = null;  typedDirty = true;  resize();  lastTextSignature = "";} function applyColors() {  if (!LENS.current) return;  LENS.current.bgColor = ctrls.bgColor;  LENS.current.mgColor = ctrls.mgColor;  LENS.current.fgColor = ctrls.fgColor;  applyLens(LENS.current);  lastTextSignature = "";} const ctrls = {  bodyFontSize: 10,  asciiFontSize: 8,  autoSpin: 0.18,  orbShape: "sphere",  shapeMorphDuration: 0.65,  cubeScale: 1.8,  orbLead: 0.35,  orbZoom: 3,  orbRadius: 0.135,  orbWire: 0.2,  orbSpaceStart: 0.82,  orbSpaceDuration: 0.7,  orbSpaceEase: 1.1,  orbWireLow: 0.01,  wireDropDuration: 1.2,  orbFadeDuration: 0.85,  orbContainNous: 1.0,  nousAfterWire: 0,  nousLetterSpacing: 2.7,  typeSpeed: 816,  bgColor: "#041C1C",  mgColor: "#ffe6cb",  fgColor: "#FFFFFF",  noiseEnabled: true,  noiseColor: "#eaeaea",  noiseDensity: 0.11,  noiseOpacity: 0.25,  noiseSize: 1,  noiseBlend: "difference",  restart: () => restartScene(),}; const DEV_MODE = new URLSearchParams(window.location.search).has("dev");if (DEV_MODE) {  const gui = new GUI({ title: "controls" });  gui.add(ctrls, "bodyFontSize", 10, 22, 1).name("body font").onChange(applyFontSizes);  gui.add(ctrls, "asciiFontSize", 8, 20, 1).name("ascii font").onChange(applyFontSizes);  gui.add(ctrls, "autoSpin", 0, 0.6, 0.01).name("auto-spin").onChange(v => { ORB.autoSpin = v; });  orbShapeControl = gui.add(ctrls, "orbShape", ["sphere", "cube"]).name("orb shape").onChange(v => {    setOrbShape(v);  });  gui.add(ctrls, "shapeMorphDuration", 0.05, 2, 0.01).name("shape morph (s)");  gui.add(ctrls, "orbLead", 0, 6, 0.05).name("orb start (s)").onFinishChange(() => restartScene());  gui.add(ctrls, "orbZoom", 0.25, 12, 0.01).name("orb zoom").onChange(v => { ORB.zoom = v; });  gui.add(ctrls, "cubeScale", 0.8, 3.5, 0.01).name("cube scale").onChange(v => { ORB.cubeScale = v; });  gui.add(ctrls, "orbRadius", 0.08, 0.4, 0.005).name("orb radius").onChange(v => { ORB.radius = v; });  gui.add(ctrls, "orbSpaceStart", 0.4, 1, 0.01).name("space start");  gui.add(ctrls, "orbSpaceDuration", 0.2, 4, 0.05).name("space grow (s)");  gui.add(ctrls, "orbSpaceEase", 0.4, 3, 0.05).name("space ease");  gui.add(ctrls, "orbWire", 0.01, 0.2, 0.005).name("wire start").onChange(v => { ORB.wire = v; });  gui.add(ctrls, "orbWireLow", 0.005, 0.12, 0.001).name("wire low").onFinishChange(() => restartScene());  gui.add(ctrls, "wireDropDuration", 0.4, 6, 0.05).name("wire drop (s)").onFinishChange(() => restartScene());  gui.add(ctrls, "orbContainNous", 0, 3, 0.05).name("orb contain NOUS");  gui.add(ctrls, "orbFadeDuration", 0.1, 3, 0.05).name("orb fade on NOUS");  gui.add(ctrls, "nousAfterWire", 0, 4, 0.05).name("NOUS after wire").onFinishChange(() => restartScene());  gui.add(ctrls, "nousLetterSpacing", 0, 4, 0.05).name("NOUS spacing");  gui.add(ctrls, "typeSpeed", 0, 4000, 10).name("type speed");  gui.addColor(ctrls, "bgColor").name("background").onChange(applyColors);  gui.addColor(ctrls, "mgColor").name("midground").onChange(applyColors);  gui.addColor(ctrls, "fgColor").name("foreground").onChange(applyColors);  const noiseFolder = gui.addFolder("noise");  noiseFolder.add(ctrls, "noiseEnabled").name("on");  noiseFolder.addColor(ctrls, "noiseColor").name("color");  noiseFolder.add(ctrls, "noiseDensity", 0, 1, 0.01).name("density");  noiseFolder.add(ctrls, "noiseOpacity", 0, 1, 0.01).name("opacity");  noiseFolder.add(ctrls, "noiseSize", 0.1, 10, 0.1).name("size");  noiseFolder.add(ctrls, "noiseBlend", [    "normal", "multiply", "screen", "difference", "exclusion",    "color-dodge", "color-burn", "hard-light", "soft-light", "overlay", "lighten",  ]).name("blend").onChange(v => { document.querySelector(".noise").style.mixBlendMode = v; });  gui.add(ctrls, "restart").name("restart anim");} const terminalCursorEl = document.getElementById("terminalCursor");function getLastCharRect(lineEl) {  if (!lineEl) return null;  const walker = document.createTreeWalker(lineEl, NodeFilter.SHOW_TEXT);  let lastTextNode = null;  let node = walker.nextNode();  while (node) {    if (node.nodeValue && node.nodeValue.length) lastTextNode = node;    node = walker.nextNode();  }  if (!lastTextNode) return null;  const end = lastTextNode.nodeValue.length;  if (!end) return null;  const range = document.createRange();  range.setStart(lastTextNode, end - 1);  range.setEnd(lastTextNode, end);  const rects = range.getClientRects();  const rect = rects.length ? rects[rects.length - 1] : range.getBoundingClientRect();  return rect && (rect.width || rect.height) ? rect : null;}function updateTerminalCursor() {  if (!drawnLines.count || !terminalAnchorEl || !terminalLineEl) {    terminalCursorEl.hidden = true;    return;  }  const stageRect = stage.getBoundingClientRect();  const charRect = getLastCharRect(terminalLineEl);  const anchorRect = terminalAnchorEl.getBoundingClientRect();  if (!anchorRect.width && !anchorRect.height) {    terminalCursorEl.hidden = true;    return;  }  const fontSize = parseInt(BODY_FONT.match(/(\d+)px/)?.[1] || "12", 10);  const fallbackW = Math.max(6, Math.round(fontSize * 0.6));  const leftPx = (charRect?.left ?? (anchorRect.left - fallbackW)) - stageRect.left;  const topPx = (charRect?.top ?? anchorRect.top) - stageRect.top;  const widthPx = Math.max(1, Math.round(charRect?.width || fallbackW));  const heightPx = Math.max(1, Math.round(charRect?.height || (BODY_LINE_H - 4)));  const left = Math.round(leftPx);  const top = Math.round(topPx);  terminalCursorEl.style.left = `${left}px`;  terminalCursorEl.style.top = `${top}px`;  terminalCursorEl.style.height = `${heightPx}px`;  terminalCursorEl.style.width = `${widthPx}px`;  terminalCursorEl.style.fontSize = `${fontSize}px`;  terminalCursorEl.hidden = false;} const noiseCanvas = document.getElementById("noiseCanvas");const noiseGL = noiseCanvas.getContext("webgl", { alpha: true, premultipliedAlpha: true })  || noiseCanvas.getContext("experimental-webgl", { alpha: true, premultipliedAlpha: true });let noiseProgram = null;let noiseLocs = null;function initNoise() {  if (!noiseGL) return;  const vertSrc = `    attribute vec2 aPos;    varying vec2 vUv;    void main() {      vUv = aPos * 0.5 + 0.5;      gl_Position = vec4(aPos, 0.0, 1.0);    }  `;  const fragSrc = `    precision mediump float;    varying vec2 vUv;    uniform vec2 uRes;    uniform float uDpr;    uniform float uSize;    uniform float uDensity;    uniform float uOpacity;    uniform vec3 uColor;    float hash(vec2 p) {      vec3 p3 = fract(vec3(p.xyx) * 0.1031);      p3 += dot(p3, p3.yzx + 33.33);      return fract((p3.x + p3.y) * p3.z);    }    void main() {      float n = hash(floor(vUv * uRes / (uSize * uDpr)));      gl_FragColor = vec4(uColor, step(1.0 - uDensity, n)) * uOpacity;    }  `;  function compile(type, src) {    const sh = noiseGL.createShader(type);    noiseGL.shaderSource(sh, src);    noiseGL.compileShader(sh);    return sh;  }  const vs = compile(noiseGL.VERTEX_SHADER, vertSrc);  const fs = compile(noiseGL.FRAGMENT_SHADER, fragSrc);  noiseProgram = noiseGL.createProgram();  noiseGL.attachShader(noiseProgram, vs);  noiseGL.attachShader(noiseProgram, fs);  noiseGL.linkProgram(noiseProgram);  noiseGL.useProgram(noiseProgram);   const buf = noiseGL.createBuffer();  noiseGL.bindBuffer(noiseGL.ARRAY_BUFFER, buf);  noiseGL.bufferData(noiseGL.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), noiseGL.STATIC_DRAW);  const aPos = noiseGL.getAttribLocation(noiseProgram, "aPos");  noiseGL.enableVertexAttribArray(aPos);  noiseGL.vertexAttribPointer(aPos, 2, noiseGL.FLOAT, false, 0, 0);   noiseLocs = {    uRes: noiseGL.getUniformLocation(noiseProgram, "uRes"),    uDpr: noiseGL.getUniformLocation(noiseProgram, "uDpr"),    uSize: noiseGL.getUniformLocation(noiseProgram, "uSize"),    uDensity: noiseGL.getUniformLocation(noiseProgram, "uDensity"),    uOpacity: noiseGL.getUniformLocation(noiseProgram, "uOpacity"),    uColor: noiseGL.getUniformLocation(noiseProgram, "uColor"),  };   noiseGL.enable(noiseGL.BLEND);  noiseGL.blendFunc(noiseGL.ONE, noiseGL.ONE_MINUS_SRC_ALPHA);  noiseGL.clearColor(0, 0, 0, 0);} function resizeNoise() {  if (!noiseGL) return;  const dpr = Math.min(window.devicePixelRatio || 1, 2);  noiseCanvas.width = innerWidth * dpr;  noiseCanvas.height = innerHeight * dpr;  noiseGL.viewport(0, 0, noiseCanvas.width, noiseCanvas.height);  if (noiseLocs) {    noiseGL.uniform2f(noiseLocs.uRes, noiseCanvas.width, noiseCanvas.height);    noiseGL.uniform1f(noiseLocs.uDpr, dpr);  }}addEventListener("resize", resizeNoise);initNoise();resizeNoise(); function drawNoiseFrame() {  if (!noiseGL || !noiseProgram) return;  noiseGL.clear(noiseGL.COLOR_BUFFER_BIT);  if (!ctrls.noiseEnabled) return;  noiseGL.useProgram(noiseProgram);  noiseGL.uniform1f(noiseLocs.uSize, ctrls.noiseSize);  noiseGL.uniform1f(noiseLocs.uDensity, ctrls.noiseDensity);  noiseGL.uniform1f(noiseLocs.uOpacity, ctrls.noiseOpacity);  const [r, g, b] = hexToRGB(ctrls.noiseColor);  noiseGL.uniform3f(noiseLocs.uColor, r / 255, g / 255, b / 255);  noiseGL.drawArrays(noiseGL.TRIANGLE_STRIP, 0, 4);} function draw(time) {  if (!fontsReady || !bodyPrepared) { requestAnimationFrame(draw); return; }  const t = time * 0.001 - sceneEpoch;  const dt = lastFrameTime ? Math.min(0.05, (time - lastFrameTime) / 1000) : 0.016;  lastFrameTime = time;  ctx.clearRect(0, 0, canvasW, canvasH);  orbCtx.clearRect(0, 0, canvasW, canvasH);   rasterizeNOUS(t);  rasterizeOrb(t);  tickTyping(t, dt);  layoutParagraphs();  renderTextLayer();  updateTerminalCursor();  drawNoiseFrame();   ctx.font = ASCII_FONT;  ctx.textBaseline = "middle";  ctx.textAlign = "center";  ctx.fillStyle = LENS.mgBase;  orbCtx.font = ASCII_FONT;  orbCtx.textBaseline = "middle";  orbCtx.textAlign = "center";  orbCtx.fillStyle = LENS.mgBase;  for (let r = 0; r < canvasRows; r++) {    const base = r * canvasCols;    for (let c = 0; c < canvasCols; c++) {      const idx = base + c;      if (asciiMask[idx] === EMPTY) continue;      ctx.fillText(asciiGlyph[idx] || CHARS[asciiChar[idx]], c * CELL_W + CELL_W * 0.5, r * CELL_H + CELL_H * 0.5);    }  }  for (let r = 0; r < canvasRows; r++) {    const base = r * canvasCols;    for (let c = 0; c < canvasCols; c++) {      const idx = base + c;      if (orbMask[idx] === EMPTY) continue;      orbCtx.fillText(orbGlyph[idx] || CHARS[orbChar[idx]], c * CELL_W + CELL_W * 0.5, r * CELL_H + CELL_H * 0.5);    }  }   requestAnimationFrame(draw);} resize();const DS_CDN = "https://esm.sh/@nous-research/ui@0.4.0/dist/fonts";const FACES = [new FontFace("Mondwest", `url(${DS_CDN}/Mondwest-Regular.woff2) format("woff2")`, { weight: "400", display: "block" })];(async () => {  try {    const loaded = await Promise.all(FACES.map(f => f.load()));    for (const f of loaded) document.fonts.add(f);    await document.fonts.load(BODY_FONT, "Aa");    await document.fonts.load(ASCII_FONT, "Aa");  } catch (err) {    console.warn("DS font load failed, using fallbacks:", err);  }  rebuildLayouts();  fontsReady = true;  requestAnimationFrame(draw);})();</script></body></html> 
Referenced from SKILL.md