1→// OVE — app identity + version stamp. 2→// 3→// Single source of truth for the product name and version shown in the UI 4→// (header, footer, document title). Bump APP_VERSION whenever we ship a 5→// large change so the running build can always be identified at a glance — 6→// useful both for the z.ai Version History and for naming local backups 7→// (v6.0 = rename to Open Visualization Engine + version indicator). 8→// 9→// STANDING RULE (since v7.6): every APP_VERSION bump MUST come with a 10→// matching entry at the top of RELEASE_NOTES in src/lib/release-notes.ts — 11→// that list powers the header's "What's New!" dialog. A dev-mode guard 12→// warns in the console when the two drift apart. 13→// 14→// History: 15→// 7.14 — Arctic Rainbow re-imagined (user request): the old stand-in was 16→// plain white columns of rain dots sweeping upward. Rebuilt as a 17→// music-driven water spray, stateless projectile physics in the 18→// [point] program: each of up to 120 droplets is a deterministic 19→// parabola (own apex height 1.26..1.88, own flight time 1.9..3.4 s, 20→// phase-staggered clock; every splash cycle re-rolls its origin, 21→// drift and arch) -- launch from below the bottom edge, slow rise, 22→// hang, arch left or right, fall back off-screen. Drop count 23→// follows the music (vol_att/bass_att envelope, fast attack 6/s, 24→// slow release 1.2/s: silence = 0 drops, loud = all 120). Each 25→// drop owns a hue that shifts by its own per-drop amount on every 26→// detected beat, plus a subtle gradient along its tail. The tail 27→// is 12 explicit trail samples (0.014..0.44 s back along the arc) 28→// drawn as a tapered polyline in lines mode, gated by smoothstep 29→// on the fall speed: nothing while rising (crisp dot), fading in 30→// as the arch turns downward -- no persistence smear (persist 31→// 0.12). Slot map through cell centers (the v7.13.1 lesson), 14 32→// slots per drop (separator + head + 12 trail), n = 1680. 33→// EMBEDDED_SEED_VERSION 7.13.1 -> 7.14 re-seeds the library. 34→// 7.13.1 — Beat Matrix endpoint fix (user report): the rightmost column 35→// of dots sat one row short -- its bottom dot missing -- while a 36→// phantom dot spilled one column past the wall's right edge at 37→// the top. Root cause: the engine's point position i spans 0..1 38→// INCLUSIVE (classic AVS Superscope convention, engine.ts: i = 39→// k/(n-1)), but Beat Matrix's grid math assumed exclusive i = k/n: 40→// floor(i*cols) turned the last point into col 16 (one past the 41→// 16-column wall) at row 0, and column 15 was left with nine 42→// dots (rows 0-8, no bottom). Fix: the script now maps through 43→// cell centers -- cell = i*(cols*rows-1)+0.5, col = 44→// floor(cell/rows), row = floor(cell-col*rows) -- which puts 45→// every floor() half a cell away from an integer boundary, so 46→// the endpoint can never spill and float dust can never shuffle 47→// a dot. Full 16x10 bijection verified headlessly (all 160 48→// points in 160 distinct cells, col<=15, row<=9, bottom-right 49→// cell present). Sweep of the other 100 presets: every other 50→// floor(i*M) use is benign -- angular presets close their loop 51→// (bar=bars lands exactly on bar 0's angle), Aurora Veil's 52→// phantom lands off-screen (x=1.19), the megabuf particle 53→// presets draw their phantom slot at alpha 0, and the converted 54→// Winamp classics rendered identically under original AVS 55→// (which also uses inclusive i). EMBEDDED_SEED_VERSION 7.12 -> 56→// 7.13.1 re-seeds the library so every visitor gets the fixed 57→// Beat Matrix. 58→// 7.13 — one build, every feature: the parallel development lines are 59→// merged. This tree now carries BOTH the community OVE-Directory 60→// (v7.11 here) AND the parallel line's additions — the spectrum 61→// 16 kHz cap (its 7.10.1), the screen wake lock + respectful 62→// Demo button + one-click Save (its 7.11, folded into this 63→// history as 7.10.2 to keep one clean timeline), and the 64→// AVS-style background movement family (bgrot / bgx / bgy / 65→// bgzoom) with the eleventh Zai-Magic preset "osc-rotations" 66→// (its 7.12). Git three-way merge against the shared v7.10 67→// ancestor (identical tree hashes verified on both sides); the 68→// only hand-merged surface was the Script tab toolbar, which 69→// now carries the one-click Save and "Upload to OVE-Directory" 70→// side by side, and the two changelogs (here + release-notes). 71→// 7.12 — background movement + osc-rotations. Scripts can now move the 72→// whole framebuffer every frame, the way the classic Winamp AVS 73→// plugins did: bgrot (radians/frame, + clockwise on screen), 74→// bgx / bgy (per-frame drift in the -1..1 screen convention, y 75→// up), bgzoom (< 1 draw-in, > 1 blow-out) -- read after [frame]/ 76→// [beat] so this frame's values apply this frame. The engine 77→// snapshots the canvas into an offscreen buffer, wipes, and 78→// draws the snapshot back through the transform, so the 79→// persistence fade blends the moving trails: "speed accelerates 80→// to the beat, a hard hit flips the spin, fade to black stays 81→// slow" is pure script (a beatage envelope drives the rate; a 82→// [beat] section flips a dir variable on hard hits via bass vs 83→// bass_att). Neutral readings (0/0/0/1) skip the pass entirely 84→// (zero cost for every preset that doesn't move); non-finite 85→// values fall back to neutral (a NaN in a canvas transform 86→// matrix would black the screen with no error); per-frame 87→// clamps: rot ±0.6 rad, drift ±0.25, zoom 0.5..2. New 11th 88→// Zai-Magic preset "osc-rotations" (user-requested): a 89→// stationary centered oscilloscope (time-domain v) whose color 90→// is mirror-symmetric -- points equidistant left/right of 91→// center shift together, the center points hold their own hue 92→// -- over a background rotating cw/ccw to the music that 93→// temporarily accelerates on the beat; hard hits 94→// (bass > 1.25 × bass_att) flip the spin; a whisper of draw-in 95→// (bgzoom ~0.999) spirals the trails inward; persist 0.965 96→// keeps the fade slow so colors blend. Help panel: bg* variable 97→// rows + three new example formulas. EMBEDDED_SEED_VERSION 98→// 7.10 -> 7.12 (library re-seed adds the preset; the share 99→// codec's dictionary stays frozen per its compatibility 100→// contract -- bg* identifiers ride the pair packing like any 101→// custom variable). Headless validation of the preset's 102→// compiled programs: quiet 0.004 rad/frame, hard beat 0.026 103→// (6.5×) + direction flip, soft beat no flip, mirrored points 104→// share hue / center distinct 105→// 7.11 — the community OVE-Directory. A sixth tab (Directory) lists every 106→// script published to the community directory — a separate VPS at 107→// s3374dir.openvisengine.com, reached ONLY through this server's 108→// backend proxy (/api/ove-directory/*), which holds the API key in 109→// env and re-validates every upload (the share string must decode 110→// AND compile). Publishing: the Script tab gains "Upload to 111→// OVE-Directory" (enabled only while the editor shows ✔ Compiled OK 112→// with no runtime error); the script travels as its Script⇢Hash 113→// string, so the directory stores a fraction of the source. The 114→// listing carries each entry's name, type and date, a 1–5 star 115→// widget (the voter id is an HttpOnly cookie minted by the proxy; 116→// one vote per script per visitor, enforced upstream), the live 117→// "Average: #.#" to the right of the stars, and sorting by Name / 118→// Type / Date / Rating (done upstream, both directions). "Get" 119→// fetches the full record, decodes the hash back into the exact 120→// script, files it in this browser's preset tree under 121→// root/user created/OVE-Directory (content-deduped, names numbered 122→// "(1) (2) (3)…" on collision) and offers "Open in editor". 123→// Duplicate uploads resolve as success (409 DUPLICATE_HASH carries 124→// the existing row); duplicate votes show the stored aggregates. 125→// 7.10.2 — wake lock, a respectful Demo button, one-click save. While any 126→// audio source is live the site holds a Screen Wake Lock so phone 127→// and tablet screens stay on (released the moment the engine goes 128→// idle so the normal inactivity dimming returns; browsers silently 129→// drop the sentinel on hide, so a visibilitychange watcher 130→// re-acquires it on return while music is still going; 131→// unsupported browsers degrade silently). The Demo Track button no 132→// longer force-engages the Sequence checkbox: a visitor who loaded 133→// a preset (tree double-click / Load / Copy to my presets / created 134→// one), brought their own script material into the editor (typed, 135→// pasted, Hash⇢Script, file), saved their own default startup 136→// script, or runs their own Random/Sequence mix keeps their 137→// selection -- Demo just plays the track (the showcase's tree 138→// re-scope, 20 s interval and retire-on-end all skip too; a new 139→// per-visit manualChoice flag + a boot-script snapshot decide, and 140→// ensureDefaultPreset gained the same guard so mic/file/link never 141→// clobber a custom draft either; the showcase now only starts for 142→// a visit that never picked or typed anything). Script tab: a valid 143→// script shows the Save button with NO save target needed -- one 144→// click creates a preset under "user created", auto-named from the 145→// script's name: header (a hash import of "Spectrum Bars:[▼…]" 146→// saves as "Spectrum Bars"), "Empty Name" when nameless, " (1)"/ 147→// " (2)"… suffixes on conflicts, and whenever the name had to be 148→// invented a "Give This Preset a Name" dialog opens to rename the 149→// just-saved preset in one step (the preset is always saved FIRST, 150→// renamed second, so nothing is ever lost to a naming problem). 151→// Hash⇢Script imports clear the stale save target (the editor now 152→// holds foreign content) and pass the decoded label through as the 153→// save-name fallback when the script has no name: header 154→// 7.10.1 — the frozen right edge of spectrum visuals. getspec()/sv mapped 155→// the bar field across the whole FFT (bin 2 ... Nyquist, ~12 Hz to 156→// 24 kHz), but music carries almost nothing above ~16 kHz and every 157→// lossy source low-passes even lower (192 kbps MP3 at ~18.6 kHz, 158→// YouTube AAC at ~15.5 kHz; measured across the demo collection: 159→// 0.000% of total magnitude above 18.6 kHz), so the rightmost ~20% 160→// of every spectrum visual read a permanent zero -- bars that never 161→// moved. getSpectrum() now tops the field out at 16 kHz 162→// (SPEC_TOP_HZ, matching the spectral-flux cap in getBands()), so 163→// the right edge lands on the 12-16 kHz "air" band where cymbals, 164→// hats and synth sparkle live. rust-agent's spectrum_at() mirrors 165→// the change, and the help panel's sv entry now states the span. 166→// 7.10 — ten demo tracks + first-person changelog + declutter. The demo 167→// source is now a COLLECTION: ten mastered tracks in 168→// public/demo-tracks/ (demo-01.mp3 … demo-10.mp3, filename order 169→// 1→10, replacing the single Neural Mecha Pulse file). Each Demo 170→// click plays the visitor's PLACE in the collection once through; 171→// the place advances when a pass truly ends — natural finish or 172→// the Stop button ('ended'/'stop' teardown reasons, a new 173→// onDemoEnded signature) — and wraps 10→1; switching to another 174→// source keeps the place. The place persists per visitor in 175→// localStorage (ove.demoTrack.next), so returning picks up where 176→// they left off. The engine caches one decoded buffer at a time, 177→// keyed by track index (~50–60 MB PCM each — all ten would be half 178→// a gigabyte). The header chip + footer SRC label show the current 179→// track's name (Demo Track N). RELEASE_NOTES rewritten in the 180→// operator's first-person voice (no more third-person marketing 181→// copy or addressing the visitor), and every em dash in the 182→// interface replaced with a plain double minus ( -- ) — including 183→// the embedded preset library's descriptions and script comments 184→// (EMBEDDED_SEED_VERSION 7.01 → 7.10 re-seed). Declutter: the 185→// landing menu's source list line, the presets panel's two 186→// read-only paragraphs, the Random/Sequence hint line, the empty 187→// user-folder note reworded, the Settings theme/highlight/Native 188→// paragraphs, the link dialog's privacy footnote and the footer's 189→// engine-ok dot are all gone — compile errors still toast, so the 190→// always-green badge was noise 191→// 7.9 — footer credit + What's New polish. The footer's blank middle now 192→// carries the maker's signature: "© {year} · Site Created & 193→// Deployed by Sum'ting White" (year generated from the clock, 194→// hydration-guarded) with a "View my portfolio at zcreations.dev" 195→// link that opens zcreations.dev in a new tab — centered in the 196→// blank area from xl (mx-auto between the stats and the version 197→// stamp), dropping to its own centered row on narrower screens so 198→// nothing ever crowds. The What's New dialog got a typographic 199→// tidy-up: a hairline divider above every release box 200→// (border-zinc-800 — a ramp step, so it matches every base theme 201→// through the globals.css remaps), and each change row became an 202→// invisible two-column grid — the kind tag top-right justified 203→// against the column boundary, the description top-left with word 204→// wrap — so all descriptions start on the same pixel column 205→// 7.8 — share strings 2.0: ~72% shorter than the script (was ~49%). The 206→// base-62 idea, cranked to 11,172… 32,164: after the symbol 207→// dictionary (which grew ~170 data-mined entries — every function 208→// WITH its open paren, every engine variable, the AVS idioms 209→// "hue = hue + dt * " and "t = t + dt * ", " * 0.", ") * ", 210→// blank-line+section skeletons, "linewidth: " / "blend: additive" 211→// headers, ~70 number literals, and the library's common 212→// variable names incl. chroma) the whole body is read as ONE BIG 213→// NUMBER and rewritten in base 32,164 — one digit per character 214→// from the CJK + Hangul blocks, ~15 bits a character vs base-62's 215→// 5.95 (so "1400" costs ~0.85 of a character). The decoder gets 216→// the body's alphabet back from a ~32-character bitmap header 217→// instead of ~80 literal symbols. Short scripts where the header 218→// wouldn't pay keep the v7.7 symbol form (encoder picks whichever 219→// is shorter); ▲ strings from v7.7 still decode; still lossless 220→// for ANY text, still copy-safe, still self-checking. Corpus 221→// measure: median share string is 27% of the script's length 222→// (best 22%), e.g. Bass Bloom 1341 → 326 characters 223→// 7.7 — script sharing + settings declutter. Script⇢Hash / Hash⇢Script: 224→// two buttons in the Script tab turn the editor's script into ONE 225→// short shareable Unicode string ("Bass Bloom:[▲…①①①①①①①①]") and 226→// paste one back — the script returns exactly as written (lossless 227→// for ANY text, compiling NOT required, so WIP scripts can be 228→// shared for debugging). The string is built from built-in symbol 229→// references (♤ = line return, ♡ = beats, ♠ = beatage, ♦ = space, 230→// ♩♪♫♬ = the four sections, one symbol per function/variable, plus 231→// a pair-packing layer that folds leftover identifier/number text 232→// two chars per CJK code point) — ~50% shorter than the script on 233→// real presets. Copy-safe by construction (no raw whitespace or 234→// control chars; platform-injected variation selectors / zero- 235→// widths stripped on paste) and self-checking: 8 check digits (an 236→// FNV-1a digest rendered as ①–⑯) reject strings that were cut off 237→// by a character limit or altered in transit, instead of decoding 238→// into garbage. Also: the Music Services log in / "log out of all" 239→// section in Settings is parked (MUSIC_SERVICES_UI flag) — the 240→// built-in house account already powers Stream-a-Link for 241→// everyone; the section returns reworked in a later update 242→// 7.6.1 — header declutter: the Set Beat button is gone from the header 243→// (tap-tempo lives in the Beat tab, and the Space shortcut works 244→// anywhere — both unchanged). The beat indicator dot stays. 245→// 7.6 — "What's New!" dialog: a Sparkles button in the header (left of 246→// the theme switch) opens the visitor-facing changelog — every 247→// release from v4.0 on, newest first, each with one-line Added / 248→// Improved / Fixed / Removed notes (src/lib/release-notes.ts, 249→// color-coded tags, the running version badged "current", 250→// scrollable with the thin scrollbar). A small emerald dot on the 251→// button marks updates the visitor hasn't seen yet — per visitor 252→// in localStorage, cleared on open (mount-guarded, no hydration 253→// mismatch). Standing rule added: version bumps ship with their 254→// release note; a dev-mode guard warns when they drift 255→// 7.5.1 — Link dialog width fix: text no longer escapes the dialog box. 256→// Root cause: Radix dialogs lay their children in an implicit CSS 257→// grid whose single column sizes to the widest child's min-content; 258→// the RECENT LINKS rows carry truncate spans (white-space: nowrap), 259→// and Chromium measures a nowrap flex item's full text width for 260→// that measurement — so a long saved title (e.g. "Bobby Foxx — This 261→// Is Why We Can't Have Nice Things (Official Music Video)") blew 262→// the column ~40 px past the 448 px dialog, and EVERY child (title, 263→// description, input + Load, rows, footer) laid out beyond the 264→// right border. Fix: the shared Dialog/AlertDialog content now pins 265→// the column to minmax(0,1fr) — capped at the available width, so 266→// no nowrap content can ever stretch a dialog again (covers every 267→// dialog app-wide) — the Link dialog widened to max-w-lg for 268→// comfier wrapping, and min-w-0 + break-words added on the 269→// recents/playlist containers and error text as belt-and-braces 270→// 7.5 — yt-dlp compatibility: single tracks resolve by DOWNLOAD + CONVERT 271→// instead of proxying the extractor's stream URL. The old stream 272→// URLs were IP-locked (googlevideo bakes in the resolver's IP), 273→// expired within hours, and arrived as webm/opus — a container 274→// Safari & co. refuse ("The media resource … was not suitable"). 275→// The server now runs the operator's pipeline per track: 276→// yt-dlp -f bestaudio/best -x --audio-format mp3 277→// --audio-quality 0 -o "