CLAUDE.md — Pixel on Kaspa
Project website for PIXEL on Kaspa, an audio-visual art and NFT project on the Kaspa blockchain. Live at pixel-on-kaspa.fyi.
Tech Stack
| Layer | Technology |
|---|---|
| Static generator | Jekyll 3.9.3+ (via Beautiful Jekyll v6.0.1 theme) |
| Markup | Markdown (Kramdown, GFM) |
| CSS | Bootstrap 4.4.1, Font Awesome 6.5.2, custom CSS |
| JavaScript | Vanilla JS, p5.js, Three.js/WebGL, Web Audio API |
| Fonts | Google Fonts — Lora, Open Sans |
| Hosting | GitHub Pages (custom domain via CNAME) |
| CI/CD | GitHub Actions |
| Package manager | Bundler (Ruby only — no npm/yarn) |
No Node.js build pipeline. No webpack, Vite, or similar tools.
Repository Structure
Pixel-on-Kaspa.github.io/
├── _config.yml # Jekyll site configuration
├── _layouts/ # Jekyll HTML layout templates (6 files)
├── _includes/ # Jekyll partials / components (29 files)
├── _posts/ # Blog posts in Markdown
├── _data/ # Data files (ui-text.yml — 40+ language strings)
├── assets/
│ ├── css/ # Stylesheets (beautifuljekyll.css + custom-styles.css)
│ ├── img/ # Image assets (~47 MB, NFT and artwork images)
│ ├── js/ # JS utilities
│ └── data/ # searchcorpus.json (search index)
├── js/ # Root-level JS generator scripts
├── synthi/ # Audio synthesizer sub-application
│ ├── index.html # SYNTHI main interface
│ ├── index.js # Web Audio API implementation (~36 KB)
│ ├── generator.html # Generator UI
│ ├── rec.html # Recording UI
│ └── js/synthi-audio.js
├── index.html # Homepage (2348 lines)
├── viewer.html # NFT collection viewer
├── pixel-p5.html # Interactive p5.js sketch generator
├── yohei-glsl.html # WebGL GLSL shader visualizer (fixed motifs)
├── glsl-lab.html # Modular GLSL shader lab (build your own motif)
├── artists.html # Artist/creator gallery
├── about.md # About the PIXEL project
├── collections.md # NFT collections listing
├── CNAME # Custom domain: pixel-on-kaspa.fyi
└── .github/workflows/ci.yml # Build & deploy pipeline
Build & Deployment
Local development
bundle install
bundle exec jekyll serve
# Site available at http://localhost:4000
CI/CD
GitHub Actions builds on every push and PR:
bundle exec appraisal jekyll build --future --config _config_ci.yml,_config.yml
Deployment is via GitHub Pages artifact upload. No manual deployment step needed — merge to master triggers a build.
Jekyll configuration
Key settings in _config.yml:
- Theme: Beautiful Jekyll v6.0.1
- Paginate: 5 posts per page
- Timezone: Europe/Prague
- Permalink:
/:year-:month-:day-:title/ - Plugins:
jekyll-paginate,jekyll-sitemap - Custom CSS:
/assets/css/custom-styles.css - Social: Twitter
@PixelonKas, email, Telegram - Navigation: NFT Viewer, Collections, About Pixel
Content
Blog posts (_posts/)
Markdown files with YAML front matter. Required front matter fields:
---
layout: post
title: "Post title"
cover-img: /assets/img/some-image.jpg
thumbnail-img: /assets/img/thumbnail.jpg
tags: [kaspa, nft]
---
Pages
about.md,collections.md— Markdown pages rendered by Jekyllviewer.html,pixel-p5.html,yohei-glsl.html,glsl-lab.html,artists.html— standalone HTML pages with embedded JS/CSS
NFT Collections
Two collections referenced throughout the site:
- PIXELONKAS — primary pixel art NFT collection
- SYKORA — collection inspired by Zdeněk Sýkora’s computer art legacy, interpreted by David Vrbík
Minting links point to kaspa.com/nft/collections/.
Interactive Features
SYNTHI — Audio Synthesizer (synthi/)
Full Web Audio API drum machine and synthesizer. Key files:
synthi/index.js— core engine (~36 KB): oscillators, drum synthesis, sequencersynthi/js/synthi-audio.js— audio processing modulesynthi/generator.html— parameter generator UIsynthi/rec.html— recording interface
Sound transformed into visual forms using oscilloscope techniques — this is the core artistic concept of the project.
p5.js Generator (pixel-p5.html)
Interactive sketch generator with user-controllable speed and quality parameters. Source sketch logic in js/pixel-p5.js.
WebGL / GLSL Visualizer (yohei-glsl.html)
Real-time GLSL shader-based visual renderer. Two fixed shaders (a raymarcher and the “Yohei Tweet” fold), parameterized by sliders — the motif itself is fixed.
GLSL Lab (glsl-lab.html)
Modular shader lab: one fragment shader assembled from swappable modules, so the motif is chosen by the user rather than baked in.
scene coords → DOMAIN → WARP → FIELD (2D) / SDF + MARCH (3D) → SHADE → PALETTE
→ exposure, written LINEAR and UNCLAMPED into an RGBA16F texture
bright scene → threshold on luminance, downsampled to ¼
blur separable 9-tap gaussian, horizontal then vertical
post scene + bloom → RGB split → GRADE (tone map, gamma, contrast, sat,
vignette, grain, posterize+dither) → scanlines → trails
The grade lives in GRADE, a string shared by the scene shader (when it is the
only pass) and the post shader (when effects are on). Tone mapping must come
after bloom — the first version graded inside the scene shader, so bloom read
an already-clamped image and did visibly nothing. The chain only runs when an
effect is switched on; otherwise it is a single pass as before. Without
EXT_color_buffer_float the targets fall back to RGBA8 and bloom is weaker.
Trails are the one effect that does not loop: they carry state across frames, so a clip with trails on will not close seamlessly.
js/glsl-lab-shader.js— the module library and the source builder. Structural choices (which module, iteration counts) are#define-injected;GLSLLabShader.build(cfg)returns the fragment source,key(cfg)is the program-cache key. Module name tables (DOMAINS,FIELDS,SDFS, …) are exported and drive the UI.js/glsl-lab.js— engine and UI. A singleSCHEMAarray is the one source of truth for every control: default, range, visibility (when), whether it is structural (struct→ recompile, cached) and what goes in the URL. Presets, the dice roller and the exports live here too.- WebGL2 required (no WebGL1 fallback).
Design notes worth keeping:
- Panel contrast. The control panel has its own lighter colour scale
(
--panelBg,--grpBg,--grpHead,--label) — the first version reused the page’s dark tokens and the rows were unreadable dark-on-dark. - The fold family is the form engine.
Fold engine(2D field) andKleinian fold(3D SDF) implement the shape of Yohei Nishitsuji’s tweet shader —e = intensity/dot(p,p),p = offset - abs(abs(p)*e - params)— with the same control names as the Yohei page (Fold X/Y/Z, Param A/B/C, Intensity, Scale).params.yfeeds back one, and Morph drive walks the params around a circle over one loop, so the geometry itself animates rather than just rotating.foldRotadds a rotation between iterations, which is also what rescues Menger / Mandelbox / Apollonian from looking regular. Its DE is steep: keep Step scale ≈ 0.2–0.35 and Glow falloff in the hundreds to low thousands, or the volume washes out to fog. - Angularity. Round, regular output was the first complaint. The levers
against it:
noiseShape(smooth / ridged / billow octave folding) withroughness+lacunarity, theAngular fold2D warp (abs-fold + rotate, no inversion → straight edges), the Mandelbox SDF, and a bounding box rather than only a sphere. Kaleidoscope/polar domains impose the regularity —Planeis the way out. - Palette input is squashed through
palIn(v) = v/(1+|v|)before the ramp, but only where the input is a raw field value (TINT 1 and 2) — shading masks are already 0..1 and stay linear. Without this,Ramp spreaddid nothing on one motif and strobed on the next, since every field has its own range. - Frame budget is held by the render scale, not by the parameters. These
shaders cost whatever the user asks;
scale: autoshrinks the buffer when frames pass ~42 ms and grows it back below ~19 ms, capped at the display’s own pixel ratio. Awebglcontextlosthandler recovers (and explains) a GPU reset instead of freezing silently. - Never write the URL on every input event.
persist()is debounced 300 ms:history.replaceStateat slider rate trips Chrome’s navigation throttle and stalls the tab. Slider drags also skip the full panel re-sync — visibility only ever keys off selects and toggles.
Notes:
- Seamless loops. All animation is driven by
PH = TAU*(uTime/uPeriod + phase)and every time-dependent term advances by a whole number of turns per loop, so frame 0 and frame N are identical. Rates are rounded to integers whileloopis on. Verified: a full phase turn leaves every preset pixel-identical. - Exports. PNG up to 4K, and a clip: WebCodecs H.264 → MP4 via
mp4-muxer(CDN, optional) with a MediaRecorder fallback. Frames are rendered at a fixed dt, so a clip is frame-exact rather than dependent on real-time frame rate. Downloads go through blobs — Chrome refuses multi-megabyte data-URL downloads. window.GLSLLabAppexposesrender(size),patch(obj),preset(i)for headless/automated checks;window.__glslLabSweep()compiles every module variant and reports failures.
NFT Viewer (viewer.html)
Grid/card layout for browsing both NFT collections. Includes filtering and minting links.
Styling
Design system — dark theme with glassmorphism:
- Background: radial gradient
#121623→#050608 - Accent: cyan
#00c4ff - Text: light gray
#f5f5f5 - Glassmorphic panels:
backdrop-filter: blur(...), semi-transparent backgrounds
CSS layers (load order):
- Bootstrap 4.4.1
beautifuljekyll.css(theme base)bootstrap-social.csspygment_highlights.css(code blocks)/assets/css/custom-styles.css(project-specific overrides)
Custom styles include:
- Page title sizing (
3rem) - Italic centered tagline
- Card hover effects (
4px border-radius) - Post preview max-width (
900px)
Multilingual Support
_data/ui-text.yml provides UI strings in 40+ languages (for comment system labels, buttons, etc.). Controlled by the lang front matter field on pages/posts.
Notable Constraints
.envexists at repo root — holds X API credentials and OpenSea API key for local tooling only. Never commit it (gitignored). See.env.examplefor required keys.- No npm/Node.js — adding JS dependencies requires either bundling manually or loading via CDN and updating the HTML directly.
- Image assets are large (~47 MB of images in
assets/img/). Avoid committing uncompressed images. - Beautiful Jekyll upstream — the repo has
https://github.com/daattali/beautiful-jekyll.gitset as upstream. Theme changes should be reconciled carefully to avoid merge conflicts. oldviewer.html.BACKUP— stale backup file in root; not served by Jekyll but present in the repo.
X Posting Commands
Slash commands in .claude/commands/:
/post --artist <name>— pick random media from~/Desktop/pixel-exports/{artist}/, generate posts for all three profiles, post after approval/mint-post --collection <name>— NFT mint announcement post
Profiles
| Handle | Voice |
|——–|——-|
| @PixelonKas | Project voice — clean, direct, EN |
| @marekozor | Personal voice — reflective, first person, EN only |
| @synthicoin | Punk experimental — raw, poetic, never promotional |
All posts require per-profile approval before sending. All posts include pixel-on-kaspa.fyi.
API credentials (.env, never commit)
PIXELONKAS_,MAREKOZOR_,SYNTHICOIN_prefixes for X API (API_KEY, API_SECRET, ACCESS_TOKEN, ACCESS_TOKEN_SECRET, BEARER_TOKEN)OPENSEA_API_KEY— used by/post --artist marekozorto fetch NFT images
Media folders
| Folder | Source |
|——–|——–|
| ~/Desktop/pixel-exports/yohei/ | GLSL shader exports |
| ~/Desktop/pixel-exports/koma/ | Koma exports |
| ~/Desktop/pixel-exports/sykora/ | Sykora exports |
| ~/Desktop/pixel-exports/marekozor/ | Fetched live from OpenSea API |
For --artist marekozor, images are fetched from OpenSea collections: deepmemory (Polygon), sphericalharmony (Polygon), angryheadsv2 (Ethereum).
NFT Collections
kaspa.com/nft/collections/PIXELONKAS— primary pixel art collection (342 max)kaspa.com/nft/collections/SYKORA— generative art by David Vrbík (2518 max); Ice NFTs are special/rare (Style trait = “Ice”)
Rewards Tracker
- File:
admin/rewards-tracker.html— password:pixel2025 - Loads PIXELONKAS and SYKORA NFT data from
mainnet.krc721.streamAPI - ICE badge — SYK rows show a cyan ICE badge (auto-set for Ice trait, toggleable per row, persisted in localStorage under
pxr_ice_SYKORA_{id}) - Mint date column — fetched via two-step API chain:
GET /api/v1/krc721/mainnet/history/{tick}/{id}?direction=forward&limit=1→opScoreModGET /api/v1/krc721/mainnet/ops/score/{opScoreMod}→mtsAdd(Unix ms timestamp)
Git & GitHub
- Remote:
https://github.com/Pixel-on-Kaspa/Pixel-on-Kaspa.github.io.git - Branch:
master(default and deployment branch) - Upstream: Beautiful Jekyll theme repo (for theme updates)
- Comments system (
staticman.yml) is configured but currently disabled.