The engineering notebook

A picture backed
by an accounting.

What is stored. How it becomes a pixel. Why time moves this way. The implementation and the tradeoffs behind the published film.

EDITION / BLOCKS 0–966,360 · DATA CUTOFF / 2026-09-10 · SOURCE / C++17 + JAVASCRIPT

01 / Pipeline

One chain. Two views.

A node-local C++ preprocessor reads blocks through Bitcoin Core's REST API and records UTXO creation and spending. The same change stream drives both the film and the historical index used by the explorer.

Pipeline from Bitcoin Core through the change stream, parallel video and history production, to R2 and a Cloudflare Worker

The renderer produces raw RGB24 frames over TCP. FFmpeg encodes them into HEVC. Separately, utxo_history builds creation/spend records. The cloud publisher packages the video as HLS and the history as row-indexed shards. A Worker serves the UI, video and queries from private R2 storage. Public viewing does not require the rendering Mac or the Bitcoin node to be online.

Sources: preprocessor · renderer · cloud service

02 / Data formats

Keep what can be verified.

ArtifactStructure and purposeThis edition
changes.blk1Sequential records: 4-byte BLK\x02 marker, uint32 block height, uint32 payload size, then encoded block metadata and amount/creation-height changes. Record lengths allow structural scans without decoding every change.18.30 GB
0–966,360
checkpoint_v3.utxoUTX3 header: last height, exact BLK byte length, final record offset and hash, and entry count. Entries store txid prefixes, original creation heights and packed output index/amount values. Written through a temporary file and rename.10.45 GB
Matching node state
utxo_history.bin64-byte BUVHIST1 header with section offsets, uint32 block timestamps, uint64 creation-height index, and 16-byte records: uint32 creation height, uint32 spend height, int64 satoshis. UINT32_MAX denotes unspent at the cutoff.58.49 GB
3,654,431,611 records
Cloud history shards512 creation blocks per shard; records sorted by exact Y row. A 32-byte header and 2,073 row offsets let a query read just the selected row. Later spends can be applied as patches indexed by stable record ordinal.2,072 plot rows
16-byte records

The incremental history updater may place its small index arrays after the enlarged record region. Readers use the header offsets; they must not assume a fixed section order. History records retain amounts and lifecycle heights, not full outpoints. Transaction lookup is a separate, potentially ambiguous operation.

Checkpoint accuracy: v3 preserves creation heights and zero-satoshi outputs and validates the matching BLK tail before resuming. The old checkpoint caveat about lost creation heights applies to legacy formats. A node checkpoint is not a renderer checkpoint.

File sizes are measured decimal GB from the September 10 release. Record count means historical outputs, including spent ones, rather than currently unspent outputs. Zero-value outputs are retained by checkpoint accounting but have no positive logarithmic Y coordinate in the plot.

Sources: BLK framing · UTX3 · history layout · shard reader

03 / Rendering model

Position, density, change.

Four choices for the time axis

ModeBehaviourConsequence
linearMaps the full configured block range linearly.Easy to compare intervals; recent blocks have little space.
epochLogThe current epoch gets half the width; earlier epochs receive geometrically smaller shares.More detail for recent history; boundaries change the layout.
normalizedGeometric publishedReserves epochRatio for the current epoch. When compression is needed, older epochs divide the remaining width with weights 1, ½, ¼…Stable positions within an epoch, with optional smooth transitions.
continuousLogA power law, approximately x = (h/N)^k × W, periodically resampled as the range grows.Continuous recency emphasis with a configurable resampling cost.

The published profile uses 105,000-block epochs and a 0.5 current share. Two epochs fit before compression is necessary. At each subsequent boundary, 120 blocks interpolate the old and new maps with smoothstep t²(3−2t). Coordinates are clamped and truncated to integer pixels; the renderer, HUD and explorer inverse mapping must agree.

The Y axis has three zones

Values increase upward. The central 100-satoshi to 10,000-BTC region uses a logarithmic scale. The 1–100 satoshi zone takes one third of its ordinary logarithmic height. The 10,000–100,000 BTC zone takes 15% of a central decade's height. Values at or above 100,000 BTC share the top row. The plot is [0,10,3720,2072] inside a 3840×2160 frame.

Persistent density and exact rebuilding

Persistent weight is max(1, |amount| / 5 BTC) with amount weighting enabled. A 100 BTC output contributes 20; a small output contributes 1. Double-precision pixel densities are updated when outputs appear or are spent. In epoch modes, an alive ledger grouped by creation height and Y row lets the renderer rebuild every surviving contribution at the exact destination pixel. This avoids the old mismatch between spreading density across columns and later removing it from a single column.

Every frame of a smooth transition rebuilds from that ledger. Diagnostics count missing ledger entries and dropped decrements. Successful release runs reported zero for both. This is accounting consistency of the rendered density; it is separate from lossy video compression and transaction-identification ambiguity.

Colour and transients

DensityToImage maps log(density + 30) between density 1 and 500 onto palette indices 0–255. Non-positive density becomes black. The base palette is turbo; rows at or above the 10 BTC threshold use a tail blended toward warm white. Both palettes share their lower entries. The optional amount colour floor is disabled in this edition.

Development comparison of global and row-selective white-hot palettes at the 10 BTC boundary
Development comparison: global white-hot at left, split palette at right.

Flash emphasis is based on the actual total BTC changed at a pixel, normalized to 5 BTC, with no minimum per output. This prevents huge numbers of tiny outputs from looking like a high-value transfer. Distant activity uses larger marks and longer fades; amount-weighted old spends can become stars. Near the creation edge, highlights remain small to protect the new-column detail. Flash pixels follow their creation column during a slide.

Orange flow marks summarize a rolling ten-block creation window, separated from the current column by 15 pixels. Large creations receive a square-root value boost when weighting is enabled. These marks are drawn and restored without changing stored density. The HUD stays at the left and synchronizes its axis mapper every frame.

Optional experiments

The denomination filter selects a fixed list of common CoinJoin-like amounts; it is a heuristic, not proof that a transaction is a CoinJoin. Audio synthesis converts spending activity into mono float32 impulses across five age-related frequency bands, with at most 64 concurrent events. Both are disabled in the published film.

Sources: axis mapper · density and ledger · colour transfer · flash history

04 / Encoding

Keep the small points.

Published masterValue
Image / time3840×2160 · 60 fps · one frame per block
CodecHEVC / libx265 · superfast · CRF 21 · yuv444p · hvc1 tag
Count / duration966,661 frames, including a 300-frame ending · 4:28:31.016
Measured size84,150,399,131 bytes

4:4:4 retains a chroma sample at every pixel, which matters for isolated coloured points. It is still lossy HEVC; neither the master nor the preview images are exact RGB data. 4:2:0 is easier to decode on many devices but reduces colour resolution. The guide uses short 1080p H.264/VP9 previews for lighter loading.

Exact-RGB lossless experiments compared decoded frame hashes and pixels but produced a roughly 312 GB full-chain file. That oversized rendition was retired; CRF 21 is the retained delivery choice. Native 8K configurations exist, but this edition is 4K. An 8K RGB24 frame is about 99.5 MB; copies, TCP throughput, encoding and storage all matter. Apple VideoToolbox H.264 could not open the tested 8K session, and HEVC VideoToolbox did not provide the required native 4:4:4 path.

Long renders record into MKV, then remux into MP4 with faststart. A normal MP4 interrupted before its trailer can lack the moov index. MKV reduces that operational risk. Remuxing changes the container without re-encoding the pictures.

Evidence: retained master · measured release report · retired artifacts

05 / Cloud delivery

The film lives in the cloud.

The production explorer at utxo.aiception.ai reaches a Cloudflare proxy, which forwards to the video Worker. The Worker serves the UI and allowlisted media from the private utxo-video R2 bucket. Viewing does not involve a local tunnel.

Browseraiception proxyVideo WorkerPrivate R2PUBLIC UI / HTTPSALLOWLIST + EDGE CACHEIMMUTABLE ASSETS

The original single 84 GB R2 object was uploaded as a multipart object. Measured ranged reads were too slow for reliable seeking. The production stream instead uses ten-second fragmented MP4 HLS segments, uploaded individually and cached independently at the edge. Segment packaging copies the existing HEVC bytes. This is not a new render or a transcode.

Video, history routing, UI assets and metadata switch together through an immutable release descriptor. Existing HLS segments and base history shards can be shared between releases. The latest stream is /hls/v3/media.m3u8; the legacy /video.mp4 aliases remain compatibility routes. A UI-only release can advance the site namespace without changing video or history.

The Worker validates single HTTP byte ranges, supports GET/HEAD and CORS preflight, adds CSP and content-type protections, and uses versioned cache keys for query results. Its in-memory per-isolate token buckets limit pixel queries (8/s, burst 20), txid resolution (0.5/s, burst 2), and range queries (30/s, burst 60). They are best-effort abuse controls, not globally coordinated quotas. R2 object names stay private behind an allowlist.

The public guide itself is plain static HTML, CSS, JavaScript and curated media served by GitHub Pages. Fonts are self-hosted. It does not fetch the full video or history index. The full explorer targets desktop displays; HEVC 4:4:4 support depends on the browser and platform. A mobile rendition experiment was stopped and is not a supported playback path.

Sources: request handling · release descriptor · public-domain proxy

06 / Explorer & read API

A pixel is a query.

Coordinates are native image pixels, not scaled screen coordinates. The explorer accounts for letterboxing, inverts the active X/Y maps, and asks for outputs in that creation-height and amount range at the selected block. It can also show previously spent outputs and their later fate.

GET /api/info

Frame geometry, fps, block coverage, palettes, video URL, source version and data cutoff.

GET /api/pixel

?block=314000&x=3000&y=1525
Ranges, unspent/past counts, satoshi totals, lifecycle chart bins and output samples. truncated and pastTruncated indicate limited returned lists.

GET /api/ranges

Same block/x/y parameters. Returns coordinate bounds and dates for the hover readout; future-space queries are marked.

GET /api/date

?d=2014-08-04
Maps a UTC date into the timestamp table. Block timestamps are not perfectly ordered; lookup uses a bounded local correction, not a calendar-time consensus guarantee.

GET /api/txid

?height=274388&satoshi=21236
Uses public Bitcoin block data to find candidate transaction IDs/output indices. Returns matches and ambiguous.

Share a view

/?block=314000&x=3000&y=1525
Opens the selected block and optional pixel. Arrow keys aim; brackets step blocks; Space toggles playback.

What “exact” means here: counts and amounts come from the indexed history. A screen pixel aggregates multiple outputs; the compact index does not preserve txid/vout identity. Outputs with equal amounts in one creation block may have multiple transaction matches. “Still unspent” is evaluated at the published cutoff, not against the live chain.

Paused seeks target the midpoint of a frame to avoid a native-player rounding edge that could display the preceding frame at a near-boundary timestamp. The ending's 300 hold/fade frames do not extend the blockchain's height range.

Sources: queries · txid matching · explorer UI

07 / Incremental updates

Keep the past. Append the future.

The September 10 update extended the release from block 964,388 to 966,360: 1,972 new blocks. Encoded frames through 964,379 were preserved. The nine-block incomplete GOP was encoded again with the new tail and a replacement 300-frame ending: 2,281 encoded tail frames in total.

The existing compressed prefix was packet-copied into the new master. HLS reused 1,607 older segments and uploaded only five replacement/new segments. Cloud history reused base shards with spend patches and five full new/replaced shards. Video, history and metadata were checked together before cutover.

Measured stageTimeResult
Node v3 load, update and save5m 46.55sMatching BLK/checkpoint through 966,360
Old-prefix SHA-256 check1m 29.49s18.22 GB prefix unchanged
Transfer change suffix17.31s75,512,433 bytes; verified continuity
Local history update3m 50s15,338,763 new records; zero unmatched spends
Replay, pre-roll and tail encode50m 54.42s2,281 frames encoded; zero ledger misses/decrements dropped
Encoding within that stage1m 10.09s32.54 fps
Cloud history delta10m 19.30s330.5 MB uploaded
Master splice and faststart3m 47.53s84.15 GB; 966,661 packets
HLS tail packaging0.63sExisting encoded payloads preserved
Total operational wall time102m 24sIncludes overlapping stages and delivery troubleshooting

These timings are observations from one run, not performance promises; overlapping rows must not be added. The complete report records uploads, rollback, timestamp correction, verification and network failures. Roughly 49 minutes still went into reconstructing renderer state, including 1,080 historical slide rebuilds. Skipping invisible slides or adding a renderer checkpoint could reduce this cost; neither optimization is implemented in this edition.

The append scripts are anchored to that verified run's filenames, ranges and manifest. A future update must prepare fresh values and compute its closed-GOP and HLS boundaries. They are not a general unattended “update to tip” command.

The final blocks and hold/fade ending of the published edition.

Evidence: complete timed append report · retained source files and next-update procedure

08 / Decisions & tradeoffs

Every choice has a cost.

Chosen behaviourAlternativeWhy / cost
v3 checkpoint resumeReplay from genesisPreserves creation heights and skips extraction work; requires a consistent checkpoint/BLK pair and hash checks.
Node-local REST extractionSSH-forwarded RESTA measured 13.8 MB block took 0.156s locally vs 21.8s over a tunnel. Node-local work consumes the node's CPU and RAM.
105k epochs / 50% newest210k halving epochs or linear timeMore frequent recency emphasis; unequal horizontal distances cannot be read as equal elapsed time.
120-block smoothstep slideOne-frame cutPositions remain visually traceable; rebuilding the ledger on every transition frame is expensive.
Exact alive-ledger remapping in epoch modesRedistribute the old rasterSpends subtract at the same mapped location as survivors; additional memory and rebuild work.
Amount-weighted densityOne vote per outputSparse valuable outputs remain visible; colours no longer mean a raw output count.
White-hot only ≥10 BTCGlobal white-hot / unmodified turboBright valuable rows without whitening ordinary bands; amount changes the colour interpretation.
Amount colour floor disabledFloor colour by amountPreserves density as the primary colour input; some sparse rows stay subtle.
Compressed low and top bandsUniform logarithmic decadesMore space for the middle range; extreme bands lose vertical detail, and ≥100k BTC shares one row.
Actual-value flash weightingMinimum flash weight per outputTiny-output bursts do not mimic whale spends; low-value changes create less prominent flashes.
4K HEVC 4:4:4 CRF 214:2:0, lossless RGB or native 8KPreserves more coloured point detail at manageable size; lossy and less broadly decodable than H.264 4:2:0.
HLS fMP4 segmentsOne multipart MP4 objectMeasured seeking and edge caching improved; manifests and continuous timestamps add operational complexity.
R2 shards and spend patchesOne memory-mapped cloud history fileFits Worker constraints; oldest compressed columns can require more shard reads.
Compact lifecycle recordsFull txid/vout per recordKeeps the index smaller; exact outpoint identity is unavailable and txid lookup can be ambiguous.
Append with a GOP overlapRe-encode the whole filmPreserves the encoded prefix; a short overlap is encoded again, and old renderer state must still be replayed.
MKV then remuxLong direct MP4 recordingMore tolerant of interruption; remux needs time and temporary disk headroom.
allowBlkFileTruncate=falseImplicit overwrite on failureProtects multi-GB inputs; deliberate rebuilds need explicit configuration and a fresh path.
Mixed txid-prefix hashesUse raw prefix bits as map hashesAvoids observed robin-hood hash-map overflow; compact-prefix identity still differs from full 256-bit txid storage.
Desktop explorer focusAdaptive/mobile renditionKeeps high-detail inspection central; the stopped mobile experiment is not supported.
Cloud-only public viewingServe through the rendering MacNo home-machine dependency; requires cloud storage, delivery and release maintenance.
09 / Complete configuration reference

The settings behind the image.

Every field read by Cfg.cpp is listed below. “Required” means the JSON parser has no fallback, even when a particular task does not use that field. Production values are from the published render profile; omitted values use the parser fallback. File paths are shown as portable roles rather than a maintainer's volume path.

42 settings shown

SettingParser defaultPublished renderMeaning
bitcoinRpcUrlRequired"http://127.0.0.1:18332"Bitcoin Core REST base URL; despite the name, this is not a JSON-RPC endpoint.
blkFileRequired"path/to/changes.blk1"Change-stream input for rendering/history, output for extraction. Adapt the path.
utxoToChangeNumThreadsRequired12Concurrent block fetching/parsing workers.
utxoToChangeNumResourcesRequired24Reusable HTTP/parser resources; normally at least the worker count.
imageWidthRequired3840Frame width in pixels; must match the encoder input.
imageHeightRequired2160Frame height in pixels; must match the encoder input.
graphRectRequired[0, 10, 3720, 2072]Plot rectangle [x, y, width, height] inside the image.
minSatoshiRequired1Lower amount bound in satoshis; positive for logarithmic mapping.
maxSatoshiRequired10000000000000Upper amount bound in satoshis.
startShowAtBlockHeightRequired0First emitted block; earlier history is still replayed.
endShowAtBlockHeight00Final block in the requested range; 0 means no explicit limit.
skipBlocksRequired00 or 1 emits all blocks; values greater than 1 emit at modulo-N intervals.
repeatLastBlockTimesRequired300Extra frames after the last block, allowing activity to fade.
connectionIpAddrRequired"127.0.0.1"TCP address where FFmpeg listens for raw RGB24 frames.
connectionSocketRequired12987TCP port for raw frames.
colorUpperValueLimitRequired500Density mapped to the top of the logarithmic colour transfer.
colorMapRequired"turbo"Base palette: viridis, magma, parula, turbo or spacious.
colorHighlightRGBRequired[255, 255, 255]Base flash highlight colour, three integer RGB channels.
colorBackgroundRGBRequired[0, 0, 0]Background colour, three integer RGB channels.
checkpointFile""""Extraction checkpoint path; empty disables checkpoint writes.
checkpointIntervalBlocks100000Periodic checkpoint interval; 0 disables periodic saves.
allowBlkFileTruncatefalsefalse (fallback)Permit deliberate replacement of existing BLK data; keep false for updates.
xAxisMode"linear""normalizedGeometric"linear, epochLog, normalizedGeometric or continuousLog. Unknown strings currently fall through to epochLog.
epochBlocks25000105000Number of blocks per layout epoch; published value is half a subsidy-halving interval.
epochRatio0.50.5Width reserved for the current epoch in normalizedGeometric mode.
epochTransitionBlocks0120Smoothstep slide length at an epoch boundary; must be smaller than epochBlocks. 0 gives a cut.
logCompressionFactor4.854.85Power exponent for continuousLog; unused by the published epoch mode.
resampleEveryNBlocks100100Rebuild interval for continuousLog; unused by the published epoch mode.
compressLowSatoshifalsetrueReduce the 1–100 satoshi band to one third of its normal log height.
compressTopSatoshifalsetrueAdd the compressed 10k–100k BTC band at 15% of a middle decade height.
amountColorFloorfalsefalseExperimental amount-dependent minimum palette index for rows at or above 0.1 BTC.
amountWeightedDensityfalsetrueWeight persistent density by max(1, amount/5 BTC); use actual value for flashes.
whiteHotTailfalsetrueBlend the upper palette toward warm white.
whiteHotTailMinSatoshi01000000000Apply white-hot only above this nonnegative amount; 0 means all rows. Ignored if whiteHotTail is false.
coinjoinFilterfalsefalse (fallback)Keep only configured denomination patterns; heuristic, not transaction identification.
audioEnabledfalsefalse (fallback)Write experimental mono float32 spending sonification.
audioOutputFile"""" (fallback)Raw audio output path; used when audio is enabled.
audioSampleRate4800048000 (fallback)Audio samples per second.
audioSamplesPerBlock800800 (fallback)Audio samples per block; 800 at 48 kHz corresponds to 60 frames/s.
historyFile"""" (fallback)Historical output index path for history/explorer tasks.
explorerVideoFile"""" (fallback)Local MP4 served by the native explorer.
explorerPort1298812988 (fallback)Local explorer HTTP port.

The explorer uses its own matching profile for historyFile, explorerVideoFile, explicit end block and geometry. skipBlocks=0 and 1 both emit every block; only values greater than 1 reduce frame emission. Optional-field parsing currently catches load/type errors and falls back, so validate edited JSON and test a short range rather than assuming malformed values will always be rejected.

Authoritative: parser · types · published render profile · explorer profile

10 / Build & run

Reproduce your own edition.

Use a C++17 compiler, CMake ≥3.13, OpenCV and TBB development libraries. Dependencies are pinned Git submodules. FFmpeg is needed for video, Python 3 for the configuration wizard and publishing tools.

git clone --recurse-submodules https://github.com/nostitos/utxo-timelapse.git
cd utxo-timelapse
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --parallel
./build/buv

# Targeted checks that run on the current macOS build too:
./build/buv -ns '-tc=density_palette,epoch_transition_mapping,checkpoint_v3'

Ubuntu/Debian packages: build-essential cmake libopencv-dev libtbb-dev. macOS Homebrew: cmake opencv tbb. Current AppleClang may need the documented warning compatibility flags and the TBB link path in the operations index. On macOS the broad legacy unit suite is omitted by CMake; a bare “SUCCESS” with zero cases is not a regression test. The targeted checks above run explicitly.

1. Extract or update

Use a synchronized Bitcoin Core with rest=1 and txindex=1, preferably on the same node as the preprocessor. Adapt the checked-in paths to your machine. Read the v3 resume requirements before pointing an updater at existing data.

./build/buv -ns -tc=utxo_to_change -cfg=configs/buv_update.json

2. Configure and encode

Start with a copy of configs/buv_render_full_weighted.json for the published look. The wizard explains its supported settings, but newer rendering options are set directly in JSON. Start FFmpeg first; then run the visualizer in another terminal. This sample preserves the source's 4:4:4 colour resolution.

ffmpeg -n -f rawvideo -pixel_format rgb24 \
  -video_size 3840x2160 -framerate 60 \
  -i 'tcp://127.0.0.1:12987?listen' \
  -c:v libx265 -preset superfast -crf 21 -pix_fmt yuv444p output.mkv

./build/buv -ns -tc=visualizer -cfg=configs/my-render.json

ffmpeg -n -i output.mkv -c copy -tag:v hvc1 \
  -movflags +faststart output.mp4

The example assumes my-render.json is your adapted copy with 3840×2160 geometry and valid input/output paths. Set a short meaningful block range before a full run. A later startShowAtBlockHeight still replays earlier changes to reconstruct density; it does not make the historical state free.

3. Build history and inspect locally

./build/buv -ns -tc=utxo_history -cfg=configs/my-explorer.json
./build/buv -ns -tc=utxo_explorer -cfg=configs/my-explorer.json
# Open http://127.0.0.1:12988/

Copy configs/buv_explorer.json and set its BLK, history and MP4 paths plus matching geometry and block range. After a compatible BLK append, utxo_history_update extends an existing index. Cloud publishing is covered in the Worker README. Build Docker from the repository root with docker build -t buv .; the container entry point is already buv.

4. Maintain the guide

Edit site/ directly. No bundler or framework is required. GitHub Actions validates and publishes this directory. scripts/build_site_media.py extracts curated frame/clip previews from an existing master; the full master and history stay outside Git. Media provenance is recorded in the asset manifest.

11 / Verification & limits

Evidence before “done.”

  1. Extraction: validate the BLK marker, contiguous heights, last record hash and exact EOF against the matching v3 checkpoint and node.
  2. Rendering: require renderer/FFmpeg success, the final encoder summary, zero ledger misses and dropped decrements, and the expected frame count.
  3. Video: inspect dimensions, fps, codec, chroma and duration with ffprobe; decode meaningful early, boundary and recent frames.
  4. Append: verify old-prefix bytes and packet/timestamp samples, matching codec configuration, the full splice decode, and continuous HLS timestamps.
  5. History: compare cloud pixel counts and satoshi sums against the local index, including old outputs spent after the previous cutoff.
  6. Delivery: verify HTTP Range responses, actual playback and paused seeks in the browser, plus shared pixel links.
  7. Guide: check every local asset and link target, responsive layouts, keyboard access, image lightboxes and the time-axis demo against the production mapper.

The release report records eight cloud/local pixel comparisons and multi-era playback checks. The guide's frame captions are tied to the block timestamp table; stills are extracted by an integer-second seek followed by a frame-index selection to avoid fractional timestamp rounding. Preview compression may hide individual points, and neither images nor denomination patterns identify a person or establish transaction intent.

Evidence: September 10 report · transition checks · palette checks

12 / Credits & source material

Built on shared work.

Martinus's BitcoinUtxoVisualizer is the original concept and code foundation. UTXO Timelapse is maintained by nostitos, with substantial changes to time mapping, accounting, visual encoding, inspection and delivery. The original MIT copyright and license remain intact.

The native program uses OpenCV, fmt, simdjson, doctest, robin-hood-hashing, cpp-httplib, indicators and Howard Hinnant's date library. FFmpeg performs encoding and packaging. Cloud delivery uses Cloudflare Workers and R2; the explorer vendors hls.js 1.7.2 with its license. The site uses Instrument Serif and IBM Plex Sans/Mono under the SIL Open Font License, with fonts hosted here.

Frames, clips and screenshots in this guide come from this project's render and interface. The explicitly labelled original comparison image and animation come from Martinus's repository. The palette comparisons are labelled development experiments. No generated illustration is presented as blockchain evidence.

MIT license · native dependency sources · hls.js license · GitHub Pages deployment reference