0x0187…01ca

All memos sent from and to 0x0187…01ca.

gardenconst baseGridDensity = 60; const baseWeaveSteps = 10; const baseWeaveSpeed = 1.0; const baseStrokeWidth = 1.0; let angleNoiseScale = 0.015; let sizeNoiseScale = 0.06; let recursionDepth = 3; let recursionFactor = 0.4; let grainAmount = 25; const gardenPalettes = { original: [ { h: [330, 360], s: [70, 90], b: [60, 80] }, { h: [190, 230], s: [80, 100], b: [70, 90] }, { h: [40, 60], s: [60, 80], b: [30, 50] }, { h: [260, 290], s: [70, 90], b: [40, 60] }, ], 'original-rose': [ { h: [330, 360], s: [70, 90], b: [60, 80] }, { h: [4, 22], s: [76, 94], b: [68, 94] }, { h: [34, 52], s: [66, 88], b: [56, 86] }, { h: [92, 124], s: [56, 78], b: [30, 62] }, { h: [260, 290], s: [50, 72], b: [48, 78] }, { h: [330, 360], s: [4, 14], b: [5, 20] }, ], 'original-water': [ { h: [190, 230], s: [80, 100], b: [70, 90] }, { h: [166, 194], s: [68, 90], b: [48, 80] }, { h: [92, 124], s: [54, 76], b: [30, 62] }, { h: [328, 350], s: [54, 76], b: [58, 88] }, { h: [38, 56], s: [64, 86], b: [54, 84] }, { h: [194, 230], s: [4, 14], b: [5, 20] }, ], 'original-gold': [ { h: [40, 60], s: [60, 80], b: [30, 50] }, ], 'original-violet': [ { h: [260, 290], s: [70, 90], b: [40, 60] }, ], 'night-bloom': [ { h: [285, 320], s: [70, 90], b: [60, 80] }, { h: [215, 245], s: [80, 100], b: [70, 90] }, { h: [165, 190], s: [60, 80], b: [30, 50] }, { h: [325, 350], s: [70, 90], b: [40, 60] }, ], 'sunlit-earth': [ { h: [5, 25], s: [70, 90], b: [60, 80] }, { h: [28, 48], s: [80, 100], b: [70, 90] }, { h: [50, 72], s: [60, 80], b: [30, 50] }, { h: [338, 358], s: [70, 90], b: [40, 60] }, ], 'pond-glass': [ { h: [145, 175], s: [70, 90], b: [60, 80] }, { h: [178, 205], s: [80, 100], b: [70, 90] }, { h: [205, 225], s: [60, 80], b: [30, 50] }, { h: [95, 125], s: [70, 90], b: [40, 60] }, ], 'orchard-dusk': [ { h: [345, 360], s: [70, 90], b: [60, 80] }, { h: [0, 18], s: [80, 100], b: [70, 90] }, { h: [72, 100], s: [60, 80], b: [30, 50] }, { h: [275, 305], s: [70, 90], b: [40, 60] }, ], 'spring-border': [ { h: [328, 350], s: [70, 90], b: [60, 80] }, { h: [42, 62], s: [80, 100], b: [70, 90] }, { h: [92, 126], s: [60, 80], b: [30, 50] }, { h: [182, 208], s: [70, 90], b: [40, 60] }, ], 'wildflower-meadow': [ { h: [0, 18], s: [70, 90], b: [60, 80] }, { h: [202, 232], s: [80, 100], b: [70, 90] }, { h: [42, 58], s: [60, 80], b: [30, 50] }, { h: [262, 294], s: [70, 90], b: [40, 60] }, ], 'herb-garden': [ { h: [92, 118], s: [70, 90], b: [60, 80] }, { h: [70, 96], s: [80, 100], b: [70, 90] }, { h: [154, 184], s: [60, 80], b: [30, 50] }, { h: [242, 270], s: [70, 90], b: [40, 60] }, ], 'kitchen-garden': [ { h: [0, 14], s: [70, 90], b: [60, 80] }, { h: [104, 136], s: [80, 100], b: [70, 90] }, { h: [42, 60], s: [60, 80], b: [30, 50] }, { h: [274, 302], s: [70, 90], b: [40, 60] }, ], 'water-garden': [ { h: [328, 350], s: [70, 90], b: [60, 80] }, { h: [180, 210], s: [80, 100], b: [70, 90] }, { h: [72, 104], s: [60, 80], b: [30, 50] }, { h: [246, 276], s: [70, 90], b: [40, 60] }, ], 'rose-arbor': [ { h: [342, 360], s: [70, 90], b: [60, 80] }, { h: [8, 28], s: [80, 100], b: [70, 90] }, { h: [304, 332], s: [60, 80], b: [30, 50] }, { h: [88, 120], s: [70, 90], b: [40, 60] }, ], 'blush-garden': [ { h: [342, 358], s: [22, 38], b: [80, 96] }, { h: [12, 28], s: [24, 42], b: [82, 98] }, { h: [88, 112], s: [18, 34], b: [72, 90] }, { h: [252, 276], s: [20, 38], b: [76, 94] }, { h: [110, 134], s: [58, 78], b: [28, 52] }, { h: [330, 360], s: [5, 16], b: [6, 24] }, ], 'morning-border': [ { h: [44, 58], s: [20, 36], b: [84, 98] }, { h: [194, 212], s: [18, 34], b: [80, 96] }, { h: [132, 154], s: [16, 32], b: [76, 92] }, { h: [326, 344], s: [20, 38], b: [82, 97] }, { h: [126, 150], s: [60, 80], b: [26, 50] }, { h: [198, 222], s: [6, 18], b: [6, 22] }, ], 'hydrangea-mist': [ { h: [218, 238], s: [24, 42], b: [78, 94] }, { h: [268, 290], s: [20, 38], b: [80, 96] }, { h: [194, 214], s: [18, 34], b: [82, 98] }, { h: [316, 338], s: [20, 36], b: [78, 95] }, { h: [98, 126], s: [56, 76], b: [26, 50] }, { h: [242, 270], s: [8, 20], b: [6, 22] }, ], 'cottage-pastel': [ { h: [4, 18], s: [28, 46], b: [80, 96] }, { h: [44, 58], s: [22, 40], b: [82, 98] }, { h: [90, 114], s: [18, 34], b: [72, 90] }, { h: [204, 226], s: [24, 42], b: [76, 94] }, { h: [88, 118], s: [58, 78], b: [28, 52] }, { h: [0, 20], s: [5, 16], b: [6, 22] }, ], 'pressed-flowers': [ { h: [336, 352], s: [24, 42], b: [66, 84] }, { h: [36, 52], s: [28, 46], b: [70, 88] }, { h: [80, 104], s: [18, 34], b: [62, 80] }, { h: [262, 284], s: [22, 40], b: [66, 84] }, { h: [68, 94], s: [46, 66], b: [24, 46] }, { h: [248, 280], s: [4, 14], b: [5, 20] }, ], 'glasshouse-light': [ { h: [124, 150], s: [18, 34], b: [78, 94] }, { h: [174, 196], s: [18, 32], b: [82, 98] }, { h: [14, 30], s: [22, 40], b: [82, 98] }, { h: [254, 278], s: [20, 36], b: [78, 95] }, { h: [142, 166], s: [62, 82], b: [24, 48] }, { h: [178, 210], s: [5, 16], b: [5, 21] }, ], }; const gardenPaletteNames = Object.keys(gardenPalettes); const softGardenPaletteNames = new Set([ 'blush-garden', 'morning-border', 'hydrangea-mist', 'cottage-pastel', 'pressed-flowers', 'glasshouse-light', ]); const mixedGardenPaletteNames = new Set([ 'original-rose', 'original-water', ]); let colorPalette; let paletteName; let softPaletteActive = false; let mixedPaletteActive = false; let lightVector; let seed; const FALLBACK_SEED = 949032; const MAX_SEED = 999999; const DEFAULT_SIZE = 1000; const RENDER_PIXEL_DENSITY = 2; let WIDTH = window.innerWidth; let HEIGHT = window.innerHeight; let DIM = Math.min(WIDTH, HEIGHT); let M = DIM / DEFAULT_SIZE; function readArtworkSeed() { const raw = window.ARTSCRIPT_SEED ?? window.__TOKEN_STUDIO_SEED ?? new URLSearchParams(window.location.search).get('seed'); if (raw === undefined || raw === null || !/^[0-9]+$/.test(String(raw).trim())) { return FALLBACK_SEED; } return Number(BigInt(String(raw).trim()) % BigInt(MAX_SEED + 1)); } function selectPaletteName(nextSeed) { const requestedName = new URLSearchParams(window.location.search).get('palette'); if (requestedName && Object.hasOwn(gardenPalettes, requestedName)) { return requestedName; } return gardenPaletteNames[nextSeed % gardenPaletteNames.length]; } function setup() { document.body.style.margin = '0'; document.body.style.padding = '0'; document.body.style.overflow = 'hidden'; document.body.style.backgroundColor = '#111'; document.body.style.display = 'flex'; document.body.style.justifyContent = 'center'; document.body.style.alignItems = 'center'; document.body.style.height = '100vh'; DIM = Math.min(WIDTH, HEIGHT); const canvasSize = Math.max(1, Math.floor(DIM)); createCanvas(canvasSize, canvasSize); pixelDensity(RENDER_PIXEL_DENSITY); colorMode(HSB, 360, 100, 100, 100); angleMode(RADIANS); startGeneration(readArtworkSeed()); noLoop(); } function startGeneration(nextSeed) { seed = Number.isInteger(nextSeed) ? constrain(nextSeed, 0, MAX_SEED) : floor(random(1000000)); randomSeed(seed); noiseSeed(seed); if (width <= 0 || height <= 0) return; const gridStep = (DIM / baseGridDensity); const weaveSteps = baseWeaveSteps; const weaveSpeed = baseWeaveSpeed * M; const scaledBaseStrokeWidth = baseStrokeWidth * M; lightVector = p5.Vector.fromAngle(random(TWO_PI)); lightVector.normalize(); paletteName = selectPaletteName(seed); colorPalette = gardenPalettes[paletteName]; softPaletteActive = softGardenPaletteNames.has(paletteName); mixedPaletteActive = mixedGardenPaletteNames.has(paletteName); document.body.dataset.artSeed = String(seed); document.body.dataset.palette = paletteName; document.body.dataset.colorStyle = softPaletteActive ? 'soft' : mixedPaletteActive ? 'mixed' : 'legacy'; if (softPaletteActive) { background(100, 12, 13); } else if (mixedPaletteActive) { background(100, 24, 9); } else { background(100, 40, 10); } for (let y = 0; y < height; y += gridStep) { for (let x = 0; x < width; x += gridStep) { let currentPos = createVector(x, y); for (let i = 0; i < weaveSteps; i++) { let virtualX = currentPos.x / M; let virtualY = currentPos.y / M; let angle = noise(virtualX * angleNoiseScale, virtualY * angleNoiseScale) * TWO_PI * 3; let sizeFactor = noise(virtualX * sizeNoiseScale, virtualY * sizeNoiseScale); let shapeSize = map(sizeFactor, 0, 1, 2 * M, gridStep * 2.5); drawRecursiveShape(currentPos.x, currentPos.y, shapeSize, angle, recursionDepth, scaledBaseStrokeWidth); currentPos.x += cos(angle) * weaveSpeed; currentPos.y += sin(angle) * weaveSpeed; } } } applyGrain(); const infoDiv = document.querySelector('.info'); if (infoDiv) { infoDiv.innerHTML = `Seed: ${seed} | Palette: ${paletteName} | Press 's' to save.`; } notifyRendered(); } function notifyRendered() { if (typeof window.__TOKEN_STUDIO_NOTIFY !== 'function') return; window.requestAnimationFrame(() => { window.__TOKEN_STUDIO_NOTIFY('token-art:rendered'); }); } function drawRecursiveShape(x, y, size, angle, level, scaledStrokeWidth) { if (level <= 0 || size < 0.5 * M) return; push(); translate(x, y); rotate(angle); let normalVector = p5.Vector.fromAngle(angle); let lightDot = normalVector.dot(lightVector); let paletteIndex = floor(map(angle % TWO_PI, 0, TWO_PI, 0, colorPalette.length)); paletteIndex = constrain(paletteIndex, 0, colorPalette.length - 1); let selectedPalette = colorPalette[paletteIndex]; let hue = map(x, 0, width, selectedPalette.h[0], selectedPalette.h[1]); let hueShift = softPaletteActive ? map(lightDot, -1, 1, -8, 8) : mixedPaletteActive ? map(lightDot, -1, 1, -12, 12) : map(lightDot, -1, 1, -20, 20); hue = (hue + hueShift + 360) % 360; let saturation = 95; let brightness = map(lightDot, -1, 1, 40, 100); if (softPaletteActive || mixedPaletteActive) { saturation = map(x, 0, width, selectedPalette.s[0], selectedPalette.s[1]); brightness = map(lightDot, -1, 1, selectedPalette.b[0], selectedPalette.b[1]); } let sw = map(level, recursionDepth, 1, scaledStrokeWidth, scaledStrokeWidth * 0.1); strokeWeight(sw); let alpha = softPaletteActive ? map(lightDot, -1, 1, 22, 58) : mixedPaletteActive ? map(lightDot, -1, 1, 26, 68) : map(lightDot, -1, 1, 25, 70); stroke(hue, saturation, brightness, alpha); noFill(); rectMode(CENTER); rect(0, 0, size * 1.8, size, size * 0.3); let newSize = size * recursionFactor; let angleOffset = map(sin(angle), -1, 1, -QUARTER_PI, QUARTER_PI); let newAngle = angle + angleOffset; drawRecursiveShape(0, 0, newSize, newAngle, level - 1, scaledStrokeWidth); pop(); } function applyGrain() { loadPixels(); for (let i = 0; i < pixels.length; i += 4) { let grain = random(-grainAmount, grainAmount); pixels[i] = pixels[i] + grain; pixels[i + 1] = pixels[i + 1] + grain; pixels[i + 2] = pixels[i + 2] + grain; } updatePixels(); } function keyPressed() { if (key === 's' || key === 'S') { saveCanvas('In the Garden - ' + seed, 'png'); } } function windowResized() { WIDTH = window.innerWidth; HEIGHT = window.innerHeight; DIM = Math.min(WIDTH, HEIGHT); M = DIM / DEFAULT_SIZE; const canvasSize = Math.max(1, Math.floor(DIM)); resizeCanvas(canvasSize, canvasSize); startGeneration(seed); }
Resonance drawingContext.lineJoin = 'round'; for (let i = 0; i < numStrokes; i++) { const tBase = (i + 0.3 + 0.4 * rnd()) / numStrokes; const s = tBase * totalLen; const { x, y, tx, ty } = sampleAt(s); const hairRoll = rnd(); const numHairs = hairRoll < 0.65 ? 1 : (hairRoll < 0.92 ? 2 : 3); for (let h = 0; h < numHairs; h++) { const strokeLen = baseWeight * (0.9 + 3.1 * rnd()); const baseAng = Math.atan2(ty, tx); const angJitter = (rnd() - 0.5) * 0.55; const a = baseAng + angJitter; const perpDist = (rnd() - 0.5) * (1.4 * M + baseWeight * 0.9); const px = -ty * perpDist; const py = tx * perpDist; const cosA = Math.cos(a), sinA = Math.sin(a); const cx = x + px; const cy = y + py; const x1 = cx - cosA * strokeLen / 2; const y1 = cy - sinA * strokeLen / 2; const x2 = cx + cosA * strokeLen / 2; const y2 = cy + sinA * strokeLen / 2; const variant = variants[Math.floor(rnd() * variants.length)]; const alpha = Math.floor((90 + rnd() * 130) * energyFactor); const w = baseWeight * (0.30 + 0.95 * Math.pow(rnd(), 1.35)); drawingContext.strokeStyle = `rgba(${variant.r},${variant.g},${variant.b},${alpha / 255})`; drawingContext.lineWidth = w; drawingContext.beginPath(); drawingContext.moveTo(x1, y1); drawingContext.lineTo(x2, y2); drawingContext.stroke(); } } pop(); } function drawSmoothCurve(pts) { beginShape(); curveVertex(pts[0].x, pts[0].y); for (const p of pts) curveVertex(p.x, p.y); curveVertex(pts[pts.length - 1].x, pts[pts.length - 1].y); endShape(); } function drawAnnotations(P) { const inkCol = color(P.ink); push(); stroke(red(inkCol), green(inkCol), blue(inkCol), 130); strokeWeight(0.6); drawingContext.lineCap = 'butt'; const margin = canvasSize * 0.058; const ruleY = margin * 0.6; const tickStart = margin, tickEnd = canvasSize - margin; line(tickStart, ruleY, tickEnd, ruleY); const numTicks = 24; for (let i = 0; i <= numTicks; i++) { const x = tickStart + (tickEnd - tickStart) * (i / numTicks); const tickLen = (i % 4 === 0) ? 6 : 3; line(x, ruleY - tickLen, x, ruleY); } const sbY = canvasSize - margin * 0.6; const sbX = canvasSize - margin - 80; line(sbX, sbY, sbX + 60, sbY); for (let i = 0; i <= 4; i++) { line(sbX + i * 15, sbY, sbX + i * 15, sbY - 5); } pop(); } function drawFrame(P) { const margin = canvasSize * 0.04; push(); noFill(); stroke(P.ink); strokeWeight(1); rect(margin, margin, canvasSize - 2 * margin, canvasSize - 2 * margin); stroke(red(color(P.ink)), green(color(P.ink)), blue(color(P.ink)), 90); strokeWeight(0.5); rect(margin + 6, margin + 6, canvasSize - 2 * margin - 12, canvasSize - 2 * margin - 12); pop(); } function drawInkSplatter(P) { const inkCol = color(P.ink); const r = red(inkCol), g = green(inkCol), b = blue(inkCol); push(); noStroke(); const numFine = 600; for (let i = 0; i < numFine; i++) { const u = rnd(), v = rnd(); const lf = sampleField(lowGrid, u, v); if (rnd() > Math.abs(lf) * 0.6 + 0.05) continue; const a = rrange(20, 90); fill(r, g, b, a); const sz = rrange(0.5, 1.6); circle(u * canvasSize, v * canvasSize, sz); } const numLarge = 12; for (let i = 0; i < numLarge; i++) { const u = rnd(), v = rnd(); fill(r, g, b, rrange(50, 100)); const sz = rrange(1.8, 4.5); circle(u * canvasSize, v * canvasSize, sz); if (rchance(0.6)) { fill(r, g, b, 25); circle(u * canvasSize, v * canvasSize, sz * 2.5); } } pop(); } function drawFinalOverlay(P) { const G = 384; const small = createImage(G, G); small.loadPixels(); for (let i = 0; i < G * G; i++) { const v = (rnd() - 0.5) * 12; const a = Math.abs(v); small.pixels[i * 4 + 0] = 0; small.pixels[i * 4 + 1] = 0; small.pixels[i * 4 + 2] = 0; small.pixels[i * 4 + 3] = a; } small.updatePixels(); push(); drawingContext.globalCompositeOperation = 'multiply'; drawingContext.imageSmoothingEnabled = false; image(small, 0, 0, canvasSize, canvasSize); drawingContext.imageSmoothingEnabled = true; drawingContext.globalCompositeOperation = 'source-over'; pop(); push(); const grd = drawingContext.createRadialGradient( canvasSize / 2, canvasSize / 2, canvasSize * 0.45, canvasSize / 2, canvasSize / 2, canvasSize * 0.78 ); grd.addColorStop(0, 'rgba(0,0,0,0)'); grd.addColorStop(1, 'rgba(0,0,0,0.10)'); drawingContext.fillStyle = grd; drawingContext.fillRect(0, 0, canvasSize, canvasSize); pop(); } function mousePressed() { if (seedLocked) return; // canonical on-chain piece — don't re-roll if (mouseX < 0 || mouseX > canvasSize || mouseY < 0 || mouseY > canvasSize) return; seed = Math.floor(Math.random() * 999999); location.hash = String(seed); generate(); } function keyPressed() { if (key === 's' || key === 'S') { saveCanvas('Resonance_' + seed, 'png'); } else if (key === 'p' || key === 'P') { exportPrintPng(); } else if (!seedLocked && keyCode === LEFT_ARROW) { seed = Math.max(1, seed - 1); location.hash = String(seed); generate(); } else if (!seedLocked && keyCode === RIGHT_ARROW) { seed = seed + 1; location.hash = String(seed); generate(); } } function exportPrintPng() { if (!composition) return; const screenSize = canvasSize; const screenM = M; const screenDensity = pixelDensity(); const t0 = performance.now(); console.log(`exporting ${PRINT_EXPORT_SIZE}x${PRINT_EXPORT_SIZE} print PNG...`); try { pixelDensity(PRINT_EXPORT_PIXEL_DENSITY); canvasSize = PRINT_EXPORT_SIZE; M = canvasSize / DEFAULT_SIZE; resizeCanvas(canvasSize, canvasSize); renderPaper(); renderArtwork(); saveCanvas('Resonance_' + seed, 'png'); } finally { pixelDensity(screenDensity || SCREEN_PIXEL_DENSITY); canvasSize = screenSize; M = screenM; resizeCanvas(canvasSize, canvasSize); renderPaper(); needsRender = true; redraw(); } const t1 = performance.now(); console.log(`print export ${(t1 - t0).toFixed(0)} ms`); } function windowResized() { const newSize = Math.floor(Math.min(window.innerWidth, window.innerHeight)); if (newSize !== canvasSize) { canvasSize = newSize; M = canvasSize / DEFAULT_SIZE; pixelDensity(SCREEN_PIXEL_DENSITY); resizeCanvas(canvasSize, canvasSize); if (composition) { renderPaper(); } needsRender = true; redraw(); } }
Resonance return (a * (1 - tx) + b * tx) * (1 - ty) + (c * (1 - tx) + d * tx) * ty; } function getDominantSource(x, y) { const fx = Math.max(0, Math.min(gridW - 1, Math.round(x * (gridW - 1)))); const fy = Math.max(0, Math.min(gridH - 1, Math.round(y * (gridH - 1)))); const v = domGrid[fy * gridW + fx]; return Math.round((v / 255) * (MAX_SOURCES - 1)); } function draw() { if (!composition || !needsRender) return; needsRender = false; const t0 = performance.now(); renderArtwork(); handleAutoSaveAfterRender(); const t1 = performance.now(); console.log(`render ${(t1 - t0).toFixed(0)} ms`); } function handleAutoSaveAfterRender() { if (!ifSave && !ifBulk) return; saveCanvas('Resonance_' + seed, 'png'); if (ifBulk) { setTimeout(() => { seed = Math.floor(Math.random() * 999999); location.hash = String(seed); generate(); }, 350); } } function renderArtwork() { const P = composition.palette; noStroke(); background(P.paper); if (paperImg) image(paperImg, 0, 0, canvasSize, canvasSize); setSeed(seed * 7 + 1); drawSubstrateGrain(P); setSeed(seed * 11 + 3); drawSubstrateGrid(P); setSeed(seed * 13 + 5); drawGapCircles(P); setSeed(seed * 17 + 7); drawContours(P); } function drawSubstrateGrain(P) { const G = 512; const img = createImage(G, G); img.loadPixels(); for (let i = 0; i < G * G; i++) { const v = rnd() * rnd(); if (v > 0.65) { const a = (v - 0.65) * 3.0; img.pixels[i * 4 + 0] = 0; img.pixels[i * 4 + 1] = 0; img.pixels[i * 4 + 2] = 0; img.pixels[i * 4 + 3] = Math.min(75, Math.floor(a * 110)); } else { img.pixels[i * 4 + 3] = 0; } } img.updatePixels(); push(); drawingContext.imageSmoothingEnabled = false; drawingContext.globalCompositeOperation = 'multiply'; image(img, 0, 0, canvasSize, canvasSize); drawingContext.globalCompositeOperation = 'source-over'; drawingContext.imageSmoothingEnabled = true; pop(); } function drawSubstrateGrid(P) { const inkCol = color(P.ink); const r = red(inkCol), g = green(inkCol), b = blue(inkCol); const minorCells = 200; const minorSpacing = canvasSize / minorCells; const tremor = 0.35 * M; push(); drawingContext.lineCap = 'butt'; drawingContext.strokeStyle = `rgba(${r},${g},${b},0.14)`; drawingContext.lineWidth = 0.34 * M; for (let n = 0; n <= minorCells; n++) { const x = n * minorSpacing; const ox = (Math.sin(n * 13.7 + seed * 0.011) + Math.sin(n * 41.3 + seed * 0.007) * 0.5) * tremor; drawingContext.beginPath(); drawingContext.moveTo(x + ox, 0); drawingContext.lineTo(x + ox, canvasSize); drawingContext.stroke(); } for (let n = 0; n <= minorCells; n++) { const y = n * minorSpacing; const oy = (Math.cos(n * 17.1 + seed * 0.013) + Math.cos(n * 39.7 + seed * 0.009) * 0.5) * tremor; drawingContext.beginPath(); drawingContext.moveTo(0, y + oy); drawingContext.lineTo(canvasSize, y + oy); drawingContext.stroke(); } pop(); } function drawWash(P) { const washImg = createImage(gridW, gridH); washImg.loadPixels(); const groundCol = color(P.ground); const accentCol = color(P.accent); const sec1Col = color(P.sec1); const gR = red(groundCol), gG = green(groundCol), gB = blue(groundCol); const aR = red(accentCol), aG = green(accentCol), aB = blue(accentCol); const sR = red(sec1Col), sG = green(sec1Col), sB = blue(sec1Col); for (let j = 0; j < gridH; j++) { for (let i = 0; i < gridW; i++) { const idx = (j * gridW + i) * 4; const lf = lowGrid[j * gridW + i]; const t = Math.max(0, lf); const tneg = Math.max(0, -lf) * 0.45; const alpha = Math.min(1, t * 0.55 + tneg * 0.45); const mix = tneg / (t + tneg + 0.0001); const sec1Mix = (1 - mix) * 0.25; let r = gR * (1 - mix - sec1Mix) + aR * mix + sR * sec1Mix; let g = gG * (1 - mix - sec1Mix) + aG * mix + sG * sec1Mix; let b = gB * (1 - mix - sec1Mix) + aB * mix + sB * sec1Mix; washImg.pixels[idx + 0] = r; washImg.pixels[idx + 1] = g; washImg.pixels[idx + 2] = b; washImg.pixels[idx + 3] = Math.floor(alpha * 110); } } washImg.updatePixels(); push(); drawingContext.imageSmoothingEnabled = true; image(washImg, 0, 0, canvasSize, canvasSize); pop(); } function drawRegionFills(P) { const palette = composition.palette; const colorOf = (key) => key ? color(palette[key]) : null; for (let s = 0; s < composition.numSources; s++) { const src = composition.sources[s]; if (src.fill === 'none') continue; const fillCol = colorOf(src.fillColor) || color(palette.ink); if (src.fill === 'stipple') drawStipple(s, fillCol); else if (src.fill === 'crosshatch') drawCrossHatch(s, fillCol); else if (src.fill === 'rings') drawInnerRings(s, src, fillCol); else if (src.fill === 'solid') drawSolidFill(s, fillCol); else if (src.fill === 'ribbon') drawRibbon(s, src, fillCol); else if (src.fill === 'dots-grid') drawDotsGrid(s, fillCol); } } function drawStipple(sourceIndex, fillCol) { push(); fill(red(fillCol), green(fillCol), blue(fillCol), 200); noStroke(); const step = canvasSize / 110; for (let y = 0; y < canvasSize; y += step) { for (let x = 0; x < canvasSize; x += step) { const u = (x + (rnd() - 0.5) * step) / canvasSize; const v = (y + (rnd() - 0.5) * step) / canvasSize; if (u < 0 || u > 1 || v < 0 || v > 1) continue; if (getDominantSource(u, v) !== sourceIndex) continue; const f = sampleField(fieldGrid, u, v); const lf = sampleField(lowGrid, u, v); if (lf < 0.15) continue; if (Math.abs(f) < 0.15) continue; const density = Math.abs(f) * 0.7 + lf * 0.4; if (rnd() > density * 1.6) continue; const r = step * 0.18 + Math.abs(f) * step * 0.15; circle(u * canvasSize, v * canvasSize, r); } } pop(); } function drawCrossHatch(sourceIndex, fillCol) { const baseAngle = composition.hatchAngle + (rnd() - 0.5) * 0.4; push(); drawingContext.lineCap = 'round'; for (let pass = 0; pass < 2; pass++) { const a = baseAngle + (pass === 0 ? -0.5 : 0.5); const cosA = Math.cos(a), sinA = Math.sin(a); const spacing = canvasSize / (110 + pass * 10); stroke(red(fillCol), green(fillCol), blue(fillCol), 130); strokeWeight(0.6); for (let s = -canvasSize; s < canvasSize * 2; s += spacing) { const samples = 220; let inSeg = false, segStart = null; for (let k = 0; k <= samples; k++) { const u = k / samples; const cx = canvasSize / 2 + (-sinA) * s + cosA * (u - 0.5) * canvasSize * 1.6; const cy = canvasSize / 2 + (cosA) * s + sinA * (u - 0.5) * canvasSize * 1.6; const ux = cx / canvasSize, uy = cy / canvasSize; let inRegion = false; if (ux >= 0 && ux <= 1 && uy >= 0 && uy <= 1 && getDominantSource(ux, uy) === sourceIndex) { const lf = sampleField(lowGrid, ux, uy); if (lf > 0.12) inRegion = true; } if (inRegion) { if (!inSeg) { segStart = { x: cx, y: cy }; inSeg = true; } } else { if (inSeg) { line(segStart.x, segStart.y, cx, cy); inSeg = false; } } } } } pop(); } function drawInnerRings(sourceIndex, src, fillCol) { push(); noFill(); stroke(red(fillCol), green(fillCol), blue(fillCol), 200); drawingContext.lineCap = 'round'; const cx = src.x * canvasSize, cy = src.y * canvasSize; for (let k = 1; k <= 18; k++) { const r = k * canvasSize * 0.012; strokeWeight(0.6 + (k % 3 === 0 ? 0.4 : 0)); const samples = 140; let prev = null; for (let s = 0; s <= samples; s++) { const t = s / samples; const a = t * Math.PI * 2; const px = cx + Math.cos(a) * r; const py = cy + Math.sin(a) * r; const ux = px / canvasSize, uy = py / canvasSize; if (ux < 0 || ux > 1 || uy < 0 || uy > 1) { prev = null; continue; } const inRegion = getDominantSource(ux, uy) === sourceIndex; if (inRegion && prev) line(prev.x, prev.y, px, py); prev = inRegion ? { x: px, y: py } : null; } } pop(); } function drawSolidFill(sourceIndex, fillCol) { const mw = 256, mh = 256; const img = createImage(mw, mh); img.loadPixels(); const fr = red(fillCol), fg = green(fillCol), fb = blue(fillCol); for (let j = 0; j < mh; j++) { for (let i = 0; i < mw; i++) { const u = i / (mw - 1), v = j / (mh - 1); const idx = (j * mw + i) * 4; const dom = getDominantSource(u, v); if (dom !== sourceIndex) { img.pixels[idx + 3] = 0; continue; } const lf = sampleField(lowGrid, u, v); if (lf < 0.08) { img.pixels[idx + 3] = 0; continue; } const a = Math.min(1, Math.max(0, lf * 1.4 - 0.05)); const dither = rnd() < a ? 1 : a * 0.4; img.pixels[idx + 0] = fr; img.pixels[idx + 1] = fg; img.pixels[idx + 2] = fb; img.pixels[idx + 3] = Math.floor(dither * 200); } } img.updatePixels(); push(); drawingContext.imageSmoothingEnabled = true; drawingContext.globalCompositeOperation = 'multiply'; image(img, 0, 0, canvasSize, canvasSize); drawingContext.globalCompositeOperation = 'source-over'; pop(); } function drawRibbon(sourceIndex, src, fillCol) { push(); drawingContext.lineCap = 'butt'; const cx = src.x * canvasSize, cy = src.y * canvasSize; for (let k = 0; k < 8; k++) { const baseR = k * canvasSize * 0.025 + canvasSize * 0.01; const w = canvasSize * 0.012; const a = 90 + (k % 2) * 70; stroke(red(fillCol), green(fillCol), blue(fillCol), a); strokeWeight(w); noFill(); const samples = 200; let prev = null; for (let s = 0; s <= samples; s++) { const t = s / samples; const ang = t * Math.PI * 2; const rr = baseR * (1 + 0.06 * Math.sin(ang * 5 + k * 0.7)); const px = cx + Math.cos(ang) * rr; const py = cy + Math.sin(ang) * rr; const ux = px / canvasSize, uy = py / canvasSize; if (ux < 0 || ux > 1 || uy < 0 || uy > 1) { prev = null; continue; } const inRegion = getDominantSource(ux, uy) === sourceIndex; if (inRegion && prev) line(prev.x, prev.y, px, py); prev = inRegion ? { x: px, y: py } : null; } } pop(); } function drawDotsGrid(sourceIndex, fillCol) { push(); noStroke(); fill(red(fillCol), green(fillCol), blue(fillCol), 200); const step = canvasSize / 60; for (let y = step / 2; y < canvasSize; y += step) { for (let x = step / 2; x < canvasSize; x += step) { const u = x / canvasSize, v = y / canvasSize; if (getDominantSource(u, v) !== sourceIndex) continue; const lf = sampleField(lowGrid, u, v); if (lf < 0.12) continue; const f = sampleField(fieldGrid, u, v); const r = step * 0.16 + Math.abs(f) * step * 0.12; circle(x, y, r); } } pop(); } function drawHatching(P) { const inkCol = color(P.ink); const angle = composition.hatchAngle; const spacing = canvasSize / 95; const cosA = Math.cos(angle), sinA = Math.sin(angle); push(); stroke(red(inkCol), green(inkCol), blue(inkCol), 50); strokeWeight(0.5); drawingContext.lineCap = 'round'; const hatchThreshold = -0.35; const len = canvasSize * 1.6; for (let s = -canvasSize; s < canvasSize * 2; s += spacing) { const samples = 280; let inSeg = false, segStart = null; for (let k = 0; k <= samples; k++) { const u = k / samples; const cx = canvasSize / 2 + (-sinA) * s + cosA * (u - 0.5) * len; const cy = canvasSize / 2 + (cosA) * s + sinA * (u - 0.5) * len; if (cx < 0 || cx >= canvasSize || cy < 0 || cy >= canvasSize) { if (inSeg) { line(segStart.x, segStart.y, cx, cy); inSeg = false; } continue; } const ux = cx / canvasSize, uy = cy / canvasSize; const v = sampleField(fieldGrid, ux, uy); if (v < hatchThreshold) { if (!inSeg) { segStart = { x: cx, y: cy }; inSeg = true; } } else { if (inSeg) { line(segStart.x, segStart.y, cx, cy); inSeg = false; } } } } pop(); } function drawGapCircles(P) { const inkCol = color(P.ink); const accentCol = color(P.accent); const inkR = red(inkCol), inkG = green(inkCol), inkB = blue(inkCol); const accR = red(accentCol), accG = green(accentCol), accB = blue(accentCol); const gw = gridW, gh = gridH; const gradMag = new Float32Array(gw * gh); for (let j = 1; j < gh - 1; j++) { for (let i = 1; i < gw - 1; i++) { const gx = fieldGrid[j * gw + (i + 1)] - fieldGrid[j * gw + (i - 1)]; const gy = fieldGrid[(j + 1) * gw + i] - fieldGrid[(j - 1) * gw + i]; gradMag[j * gw + i] = Math.hypot(gx, gy); } } const cd = composition.circleDensity != null ? composition.circleDensity : 0.7; const lerp = (a, b, t) => a + (b - a) * t; const gapThreshold = lerp(0.04, 0.10, cd); const cellsPerSide = lerp(70, 130, cd); const cellSizeNorm = 1 / cellsPerSide; const candidateCount = Math.max(1, Math.ceil(cellsPerSide - 0.5)); const placed = []; const HASH_CELL_NORM = 14 / DEFAULT_SIZE; const hashCols = Math.ceil(1 / HASH_CELL_NORM) + 1; const hashGrid = new Map(); const hashKey = (i, j) => i * hashCols + j; const hashAdd = (p) => { const i = Math.floor(p.u / HASH_CELL_NORM); const j = Math.floor(p.v / HASH_CELL_NORM); const k = hashKey(i, j); if (!hashGrid.has(k)) hashGrid.set(k, []); hashGrid.get(k).push(p); }; const placementCoef = lerp(0.7, 1.05, cd); const radiusBaseNorm = lerp(1.2, 1.8, cd) / DEFAULT_SIZE; const radiusRangeNorm = lerp(3.0, 5.0, cd) / DEFAULT_SIZE; const spacingBufferNorm = lerp(1.5, -2.5, cd) / DEFAULT_SIZE; const edgeClampNorm = 4 / DEFAULT_SIZE; for (let cy = 0; cy < candidateCount; cy++) { for (let cx = 0; cx < candidateCount; cx++) { const cu = (cx + 0.5) * cellSizeNorm; const cv = (cy + 0.5) * cellSizeNorm; const jU = cu + (rnd() - 0.5) * cellSizeNorm * 0.7; const jV = cv + (rnd() - 0.5) * cellSizeNorm * 0.7; if (jU < edgeClampNorm || jV < edgeClampNorm || jU > 1 - edgeClampNorm || jV > 1 - edgeClampNorm) continue; const jx = jU * canvasSize; const jy = jV * canvasSize; const gi = Math.max(1, Math.min(gw - 2, Math.round(jU * (gw - 1)))); const gj = Math.max(1, Math.min(gh - 2, Math.round(jV * (gh - 1)))); const grad = gradMag[gj * gw + gi]; if (grad > gapThreshold) continue; const placementProb = placementCoef * (1 - grad / gapThreshold); if (rnd() > placementProb) continue; const rRadiusNorm = radiusBaseNorm + radiusRangeNorm * (1 - grad / gapThreshold) * (0.5 + 0.5 * rnd()); const rRadius = rRadiusNorm * canvasSize; const hi = Math.floor(jU / HASH_CELL_NORM); const hj = Math.floor(jV / HASH_CELL_NORM); let tooClose = false; outer: for (let di = -1; di <= 1; di++) { for (let dj = -1; dj <= 1; dj++) { const bucket = hashGrid.get(hashKey(hi + di, hj + dj)); if (!bucket) continue; for (const p of bucket) { const du = p.u - jU, dv = p.v - jV; const minD = p.rNorm + rRadiusNorm + spacingBufferNorm; if (du * du + dv * dv < minD * minD) { tooClose = true; break outer; } } } } if (tooClose) continue; const newCircle = { u: jU, v: jV, rNorm: rRadiusNorm }; placed.push(newCircle); hashAdd(newCircle); } } push(); drawingContext.lineCap = 'round'; drawingContext.lineJoin = 'round'; for (const c of placed) { drawScribbledCircle(c.u * canvasSize, c.v * canvasSize, c.rNorm * canvasSize, inkR, inkG, inkB, accR, accG, accB); } pop(); } function drawScribbledCircle(cx, cy, radius, inkR, inkG, inkB, accR, accG, accB) { const numPasses = radius < 2.5 * M ? (1 + (rnd() < 0.4 ? 1 : 0)) : (2 + Math.floor(rnd() * 3)); const mixColors = rnd() < 0.4; const dominantIsAccent = rnd() < 0.18; for (let pass = 0; pass < numPasses; pass++) { const startA = rnd() * Math.PI * 2; const arcSpan = (0.7 + 1.6 * rnd()) * Math.PI * 2; const direction = rnd() < 0.5 ? 1 : -1; const segs = Math.max(8, Math.floor((radius / M) * 2.5 * (arcSpan / (Math.PI * 2)))); const noisePhase = rnd() * Math.PI * 2; const noiseFreq = 2 + rnd() * 4; const noiseAmp = radius * (0.12 + 0.20 * rnd()); let r0, g0, b0; if (mixColors) { if (rnd() < 0.35) { r0 = accR; g0 = accG; b0 = accB; } else { r0 = inkR; g0 = inkG; b0 = inkB; } } else { if (dominantIsAccent) { r0 = accR; g0 = accG; b0 = accB; } else { r0 = inkR; g0 = inkG; b0 = inkB; } } r0 += (rnd() - 0.5) * 18; g0 += (rnd() - 0.5) * 18; b0 += (rnd() - 0.5) * 18; r0 = Math.max(0, Math.min(255, r0)); g0 = Math.max(0, Math.min(255, g0)); b0 = Math.max(0, Math.min(255, b0)); const alpha = 0.30 + 0.30 * rnd(); const w = (0.5 + 0.6 * rnd()) * M; drawingContext.strokeStyle = `rgba(${r0 | 0},${g0 | 0},${b0 | 0},${alpha})`; drawingContext.lineWidth = w; drawingContext.beginPath(); for (let i = 0; i <= segs; i++) { const t = i / segs; const a = startA + direction * arcSpan * t; const wob = Math.sin(a * noiseFreq + noisePhase) * noiseAmp + Math.sin(a * (noiseFreq * 1.7) + noisePhase * 1.3) * (noiseAmp * 0.5) + (rnd() - 0.5) * radius * 0.10; const r = radius + wob; const px = cx + Math.cos(a) * r; const py = cy + Math.sin(a) * r; if (i === 0) drawingContext.moveTo(px, py); else drawingContext.lineTo(px, py); } drawingContext.stroke(); } } function drawContours(P) { const inkCol = color(P.ink); const primaryCol = color(P.primary); const accentCol = color(P.accent); const featureLevels = new Set(); const numLevels = composition.contourLevels.length; featureLevels.add(Math.floor(numLevels * 0.25)); featureLevels.add(Math.floor(numLevels * 0.75)); if (rnd() < 0.5) featureLevels.add(Math.floor(numLevels * 0.5)); for (let lvl = 0; lvl < numLevels; lvl++) { const t = composition.contourLevels[lvl]; const threshold = (t - 0.5) * 1.7; const polylines = marchingSquares(fieldGrid, gridW, gridH, threshold); let strokeCol, weight; const isFeature = featureLevels.has(lvl); const isCenter = Math.abs(t - 0.5) < 0.06; if (isCenter) { strokeCol = accentCol; weight = composition.baseWeight * 2.8 * M; } else if (isFeature) { strokeCol = primaryCol; weight = composition.baseWeight * 2.0 * M; } else { strokeCol = inkCol; weight = composition.baseWeight * (0.7 + 0.6 * Math.abs(t - 0.5) * 2) * M; } const levelWeightVariation = isCenter ? 0.92 + 0.36 * rnd() : (isFeature ? 0.80 + 0.55 * rnd() : 0.65 + 0.75 * rnd()); for (const poly of polylines) { if (poly.length < 4) continue; const polyWeightVariation = 0.82 + 0.36 * rnd(); drawPencilContour(poly, strokeCol, weight * levelWeightVariation * polyWeightVariation); } } } function drawPencilContour(polyline, col, baseWeight) { const pts = polyline.map(p => { const gx = Math.min(gridW - 1, Math.max(0, Math.round(p.x))); const gy = Math.min(gridH - 1, Math.max(0, Math.round(p.y))); return { x: (p.x / (gridW - 1)) * canvasSize, y: (p.y / (gridH - 1)) * canvasSize, gx, gy }; }); let energySum = 0; for (const p of pts) energySum += Math.abs(lowGrid[p.gy * gridW + p.gx]); const energyAvg = energySum / pts.length; const energyFactor = Math.min(1, 0.18 + energyAvg * 1.6); const tremor = Math.max(0.3 * M, 1.6 * M - baseWeight * 0.4); const tpts = pts.map((p, i) => { const dx = (Math.sin(i * 0.31 + seed * 0.013) + Math.sin(i * 0.117 + seed * 0.027) * 0.6) * tremor; const dy = (Math.cos(i * 0.27 + seed * 0.019) + Math.cos(i * 0.131 + seed * 0.033) * 0.6) * tremor; return { x: p.x + dx, y: p.y + dy }; }); const cumulative = [0]; for (let i = 1; i < tpts.length; i++) { const dx = tpts[i].x - tpts[i - 1].x; const dy = tpts[i].y - tpts[i - 1].y; cumulative.push(cumulative[i - 1] + Math.hypot(dx, dy)); } const totalLen = cumulative[cumulative.length - 1]; if (totalLen < 4 * M) return; const r = red(col), g = green(col), b = blue(col); if ((baseWeight / M) > composition.baseWeight * 1.4) { push(); noFill(); stroke(r, g, b, Math.floor(32 * energyFactor)); strokeWeight(baseWeight * 3.0); drawingContext.lineCap = 'round'; drawingContext.lineJoin = 'round'; drawSmoothCurve(tpts); pop(); } const variants = makeColorVariants(r, g, b); const unscaledWeight = baseWeight / M; const density = 0.38 + 0.82 * Math.min(1, unscaledWeight / (composition.baseWeight * 2.8)); const numStrokes = Math.max(2, Math.floor((totalLen / M) * density)); let lastIdx = 1; const sampleAt = (s) => { while (lastIdx > 1 && cumulative[lastIdx - 1] > s) lastIdx--; while (lastIdx < cumulative.length - 1 && cumulative[lastIdx] < s) lastIdx++; const i = lastIdx; const segStart = cumulative[i - 1]; const segLen = cumulative[i] - segStart; const t = segLen > 0 ? (s - segStart) / segLen : 0; const x = tpts[i - 1].x * (1 - t) + tpts[i].x * t; const y = tpts[i - 1].y * (1 - t) + tpts[i].y * t; let tx = tpts[i].x - tpts[i - 1].x; let ty = tpts[i].y - tpts[i - 1].y; const tlen = Math.hypot(tx, ty); if (tlen > 0) { tx /= tlen; ty /= tlen; } else { tx = 1; ty = 0; } return { x, y, tx, ty }; }; push(); drawingContext.lineCap = 'round';
Resonancelet RNG_STATE = 1; function setSeed(s) { RNG_STATE = (s >>> 0) || 1; } function rnd() { let t = RNG_STATE += 0x6D2B79F5; t = Math.imul(t ^ (t >>> 15), t | 1); t ^= t + Math.imul(t ^ (t >>> 7), t | 61); return ((t ^ (t >>> 14)) >>> 0) / 4294967296; } const rrange = (a, b) => a + (b - a) * rnd(); const rint = (a, b) => Math.floor(rrange(a, b + 1)); const rpick = (arr) => arr[Math.floor(rnd() * arr.length)]; const rchance = (p) => rnd() < p; const rgauss = () => { let u = 1 - rnd(), v = rnd(); return Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v); }; const rshuffle = (arr) => { const a = arr.slice(); for (let i = a.length - 1; i > 0; i--) { const j = Math.floor(rnd() * (i + 1)); [a[i], a[j]] = [a[j], a[i]]; } return a; }; function makeColorVariants(r, g, b) { const clamp = v => Math.max(0, Math.min(255, v | 0)); return [ { r: clamp(r), g: clamp(g), b: clamp(b) }, { r: clamp(r + 18), g: clamp(g + 15), b: clamp(b + 10) }, { r: clamp(r - 14), g: clamp(g - 12), b: clamp(b - 8) }, { r: clamp(r + 10), g: clamp(g + 5), b: clamp(b - 2) }, { r: clamp(r - 2), g: clamp(g + 4), b: clamp(b + 14) }, ]; } const PALETTES = [ { name: 'Cyanotype', paper: '#ecdfc8', ground: '#7a96b3', primary: '#1c3a5e', accent: '#c44536', ink: '#0d1b2a', sec1: '#a8b8c8', sec2: '#5d2a1a' }, { name: 'Bauhaus', paper: '#f0e7d2', ground: '#d6a24a', primary: '#2c2a4a', accent: '#a23216', ink: '#16161a', sec1: '#9a8268', sec2: '#3a4a6c' }, { name: 'Hokusai', paper: '#e8dcc4', ground: '#7d99b3', primary: '#1d3557', accent: '#bc4b51', ink: '#1d1d1f', sec1: '#a8a48f', sec2: '#5a4838' }, { name: 'Riso', paper: '#f3eee2', ground: '#7fb3a8', primary: '#e84a5f', accent: '#2b3a67', ink: '#101820', sec1: '#c8a87a', sec2: '#3a3a3a' }, { name: 'Clay', paper: '#ece2cf', ground: '#a78a6b', primary: '#5a3825', accent: '#7a4a31', ink: '#1f1a14', sec1: '#8a6e52', sec2: '#3a2a1a' }, { name: 'Verdigris', paper: '#ecdcb8', ground: '#8aa39b', primary: '#264027', accent: '#b8553a', ink: '#181818', sec1: '#6a7a5a', sec2: '#5a3a28' }, { name: 'Sumi', paper: '#ede4d0', ground: '#b9b1a3', primary: '#2a2a2a', accent: '#9c2a1a', ink: '#0e0e0e', sec1: '#7a7468', sec2: '#4a4438' }, { name: 'Cobalt', paper: '#e6d9b8', ground: '#9aa9b8', primary: '#1f3a8a', accent: '#e0a458', ink: '#0a0a0f', sec1: '#5a6a8a', sec2: '#7a4a28' }, { name: 'Iron Oxide', paper: '#e8d8b8', ground: '#a89478', primary: '#7a2a1d', accent: '#3a3a3a', ink: '#191512', sec1: '#bfa37e', sec2: '#5a342a' }, { name: 'Lichen', paper: '#e6dcc0', ground: '#7d8a6b', primary: '#3a2e22', accent: '#a8543a', ink: '#1a1814', sec1: '#a8aa8a', sec2: '#4a3a26' }, { name: 'Chartreuse', paper: '#ece8d6', ground: '#a8a89c', primary: '#1a1a1c', accent: '#d4c834', ink: '#0d0d10', sec1: '#7a7a72', sec2: '#3a3a3c' }, { name: 'Caution', paper: '#efeadc', ground: '#9c9a8e', primary: '#15151a', accent: '#f0c020', ink: '#08080c', sec1: '#6e6c64', sec2: '#3a3a3a' }, { name: 'Newsprint', paper: '#e6dab0', ground: '#9a8e72', primary: '#1d1a14', accent: '#c8a026', ink: '#13110c', sec1: '#b6a884', sec2: '#5a4f3c' }, { name: 'Brass', paper: '#ecdec0', ground: '#8e8a82', primary: '#2a2622', accent: '#e3a82a', ink: '#14120e', sec1: '#a8a094', sec2: '#4a463e' }, { name: 'Concrete', paper: '#e4e1d2', ground: '#828086', primary: '#1c1d22', accent: '#d8b62a', ink: '#0c0d12', sec1: '#a8a6a3', sec2: '#42434a' }, { name: 'Moss', paper: '#e6dcc0', ground: '#92a87a', primary: '#324326', accent: '#b86838', ink: '#161814', sec1: '#a8b58e', sec2: '#5a4a30' }, { name: 'Plum', paper: '#ecdfc8', ground: '#9a8aa0', primary: '#4a2a52', accent: '#c89a3a', ink: '#1a1218', sec1: '#a8a0aa', sec2: '#6a3a48' }, { name: 'Indigo', paper: '#e6dac4', ground: '#92a0b0', primary: '#1a2848', accent: '#c8b58a', ink: '#0c1020', sec1: '#7a8ca0', sec2: '#3a4a68' }, { name: 'Graphite', paper: '#e2e0d8', ground: '#9c9a96', primary: '#2a2c30', accent: '#5a5c60', ink: '#0a0b0e', sec1: '#bcbab4', sec2: '#46484c' }, { name: 'Oxblood', paper: '#ece0c8', ground: '#a0827a', primary: '#5a1f24', accent: '#d4a85a', ink: '#1a0e10', sec1: '#b09080', sec2: '#3a2a26' }, { name: 'Teal', paper: '#ece4ce', ground: '#7aa8a4', primary: '#1a4a52', accent: '#d8a836', ink: '#0e1a1c', sec1: '#a0bcb6', sec2: '#5a4a30' }, { name: 'Memphis', paper: '#f0e8d6', ground: '#3ec0b8', primary: '#e63a85', accent: '#2a8acc', ink: '#101015', sec1: '#f5c032', sec2: '#1a1a22' }, { name: 'Holi', paper: '#ecdfc2', ground: '#1ea58e', primary: '#e8602a', accent: '#d92766', ink: '#1a1010', sec1: '#f0b820', sec2: '#0d3a52' }, { name: 'Suprematist', paper: '#ede1c4', ground: '#a8a098', primary: '#d4321a', accent: '#1c40a8', ink: '#0a0a0e', sec1: '#e8b820', sec2: '#1a1a1a' }, { name: 'Acid', paper: '#ece6d8', ground: '#3acfc8', primary: '#ec3a98', accent: '#a8e030', ink: '#0c0a14', sec1: '#9a3ad8', sec2: '#2a8af0' }, { name: 'Loteria', paper: '#ecdfbe', ground: '#28a8b8', primary: '#1a8aa0', accent: '#d83a82', ink: '#0e1218', sec1: '#f0a820', sec2: '#1a48a0' }, { name: 'Pop', paper: '#f2ecda', ground: '#e8c828', primary: '#f0c020', accent: '#d8261a', ink: '#0a0a0e', sec1: '#1c50c8', sec2: '#e85a9a' }, ]; const FIELD_VERT = ` precision highp float; attribute vec3 aPosition; attribute vec2 aTexCoord; varying vec2 vUv; uniform mat4 uProjectionMatrix; uniform mat4 uModelViewMatrix; void main() { vUv = aTexCoord; gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aPosition, 1.0); } `; const MAX_SOURCES = 24; const FIELD_FRAG = ` precision highp float; varying vec2 vUv; uniform int uNumSources; uniform vec2 uPos[${MAX_SOURCES}]; uniform float uFreq[${MAX_SOURCES}]; uniform float uPhase[${MAX_SOURCES}]; uniform float uAmp[${MAX_SOURCES}]; uniform float uSign[${MAX_SOURCES}]; uniform float uDecay; uniform float uWarp; uniform float uWarpFreq; float hash(vec2 p){ return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); } float vnoise(vec2 p){ vec2 i = floor(p), f = fract(p); float a = hash(i); float b = hash(i + vec2(1.0, 0.0)); float c = hash(i + vec2(0.0, 1.0)); float d = hash(i + vec2(1.0, 1.0)); vec2 u = f*f*(3.0-2.0*f); return mix(a,b,u.x) + (c-a)*u.y*(1.0-u.x) + (d-b)*u.x*u.y; } float fbm(vec2 p){ float v = 0.0, a = 0.5; for (int i = 0; i < 4; i++) { v += a * vnoise(p); p *= 2.03; a *= 0.5; } return v; } void main() { vec2 uv = vUv; vec2 w = vec2(fbm(uv * uWarpFreq + 13.1), fbm(uv * uWarpFreq + 71.7)) - 0.5; vec2 p = uv + w * uWarp; float field = 0.0; float lowfreq = 0.0; float maxLocal = -1.0; float dominantId = 0.0; for (int i = 0; i < ${MAX_SOURCES}; i++) { if (i >= uNumSources) break; vec2 c = uPos[i]; float d = distance(p, c); float wave = cos(d * uFreq[i] - uPhase[i]) * uAmp[i] * uSign[i]; float falloff = 1.0 / (1.0 + uDecay * d * d); field += wave * falloff; lowfreq += uSign[i] * uAmp[i] * exp(-d * d * 6.0); float infl = uAmp[i] * exp(-d * d * 6.0); if (infl > maxLocal) { maxLocal = infl; dominantId = float(i) / float(${MAX_SOURCES} - 1); } } float fEnc = clamp(field * 0.5 + 0.5, 0.0, 1.0); float bEnc = clamp(lowfreq * 0.5 + 0.5, 0.0, 1.0); gl_FragColor = vec4(fEnc, dominantId, bEnc, 1.0); } `; const PAPER_FRAG = ` precision highp float; varying vec2 vUv; uniform float uSeed; uniform vec3 uPaperColor; float hash(vec2 p){ return fract(sin(dot(p, vec2(127.1, 311.7)) + uSeed) * 43758.5453); } float vnoise(vec2 p){ vec2 i = floor(p), f = fract(p); float a = hash(i); float b = hash(i + vec2(1.0, 0.0)); float c = hash(i + vec2(0.0, 1.0)); float d = hash(i + vec2(1.0, 1.0)); vec2 u = f*f*(3.0-2.0*f); return mix(a,b,u.x) + (c-a)*u.y*(1.0-u.x) + (d-b)*u.x*u.y; } float fbm(vec2 p){ float v = 0.0, a = 0.5; for (int i = 0; i < 5; i++) { v += a * vnoise(p); p *= 2.07; a *= 0.5; } return v; } void main() { vec2 uv = vUv; float macroTone = fbm(uv * 1.6 + vec2(7.3, 11.1)) * 2.0 - 0.5; macroTone = clamp(macroTone, -0.5, 1.0); float midTone = fbm(uv * 3.0) * 0.5 + 0.5; vec2 fdir = vec2(fbm(uv * 8.0 + 1.7), fbm(uv * 8.0 + 9.3)) - 0.5; vec2 anis = uv * vec2(180.0, 180.0) + fdir * 6.0; float fibers = vnoise(anis * vec2(1.0, 0.18)); float grain = hash(floor(uv * 1100.0)); vec3 warmTint = uPaperColor * vec3(0.97, 0.92, 0.84) * 0.93; vec3 coolTint = uPaperColor * vec3(0.95, 0.96, 0.99) * 0.97; float warmFactor = clamp(macroTone, 0.0, 1.0); float coolFactor = clamp(-macroTone, 0.0, 1.0); vec3 baseCol = uPaperColor; baseCol = mix(baseCol, warmTint, warmFactor * 0.65); baseCol = mix(baseCol, coolTint, coolFactor * 0.55); float dark = 1.0 - midTone * 0.05 - fibers * 0.08 - grain * 0.06; vec2 cv = uv - 0.5; float vig = 1.0 - dot(cv, cv) * 0.22; vec2 cornerPick = vec2( step(0.5, fract(uSeed * 0.71)) * 2.0 - 1.0, step(0.5, fract(uSeed * 0.31)) * 2.0 - 1.0 ); vec2 cornerPos = vec2(0.5, 0.5) + cornerPick * 0.5; float cornerDist = distance(uv, cornerPos); float cornerAge = smoothstep(0.7, 0.0, cornerDist) * 0.08; vig -= cornerAge; vec3 col = baseCol * dark * vig; gl_FragColor = vec4(col, 1.0); } `; let composition = null; function buildComposition() { const palette = rpick(PALETTES); const numSources = rint(5, 12); const sources = []; const gridN = Math.ceil(Math.sqrt(numSources * 1.6)); const cells = []; for (let i = 0; i < gridN; i++) for (let j = 0; j < gridN; j++) cells.push([i, j]); const shuffled = rshuffle(cells); const regime = rpick(['intricate', 'open', 'mixed', 'dense']); let baseFreq, freqSpread, ampDist; if (regime === 'intricate') { baseFreq = 90; freqSpread = 50; ampDist = 'flat'; } if (regime === 'open') { baseFreq = 38; freqSpread = 20; ampDist = 'tilted'; } if (regime === 'mixed') { baseFreq = 55; freqSpread = 70; ampDist = 'tilted'; } if (regime === 'dense') { baseFreq = 130; freqSpread = 70; ampDist = 'flat'; } for (let i = 0; i < numSources; i++) { const [ci, cj] = shuffled[i]; const cellSize = 1 / gridN; const x = (ci + 0.2 + 0.6 * rnd()) * cellSize; const y = (cj + 0.2 + 0.6 * rnd()) * cellSize; const freq = baseFreq + rgauss() * freqSpread * 0.4; const phase = rrange(0, Math.PI * 2); let amp; if (ampDist === 'flat') amp = rrange(0.7, 1.0); else amp = (i === 0) ? rrange(0.9, 1.1) : rrange(0.3, 0.8); const sign = rchance(0.5) ? 1 : -1; sources.push({ x, y, freq: Math.max(8, freq), phase, amp, sign }); } const targetFeatureCount = rint(2, 3); const featureIndices = []; const sourceOrder = rshuffle(Array.from({ length: numSources }, (_, i) => i)); for (const idx of sourceOrder) { if (featureIndices.length >= targetFeatureCount) break; const c = sources[idx]; let ok = true; for (const fi of featureIndices) { const fc = sources[fi]; const dx = c.x - fc.x, dy = c.y - fc.y; if (dx * dx + dy * dy < 0.18 * 0.18) { ok = false; break; } } if (ok) featureIndices.push(idx); } const featureSet = new Set(featureIndices); const allStyles = ['stipple', 'crosshatch', 'rings', 'solid', 'ribbon', 'dots-grid']; const stylePool = rshuffle(allStyles).slice(0, rint(2, 3)); const colorPool = rshuffle(['accent', 'primary', 'sec2']); for (let i = 0; i < numSources; i++) { if (featureSet.has(i)) { sources[i].fill = rpick(stylePool); const featPos = featureIndices.indexOf(i); sources[i].fillColor = colorPool[featPos % colorPool.length]; } else { sources[i].fill = 'none'; sources[i].fillColor = null; } } const densityMode = (() => { const r = rnd(); if (r < 0.25) return 'sparse'; if (r < 0.85) return 'balanced'; return 'dense'; })(); let numContours; if (densityMode === 'sparse') numContours = rint(12, 18); else if (densityMode === 'balanced') numContours = rint(24, 34); else numContours = rint(36, 48); const contourLevels = []; for (let i = 0; i < numContours; i++) contourLevels.push((i + 0.5) / numContours); const decay = rrange(0.8, 2.5); const warp = rrange(0.0, 0.05); const warpFreq = rrange(2, 5); const showHatch = rchance(0.7); const showFrame = rchance(0.7); const showAnnotations = rchance(0.55); const showSplatter = rchance(0.7); const baseWeight = rrange(0.6, 1.3); const hatchAngle = rnd() * Math.PI; const circleDensity = Math.sqrt(rnd()); return { palette, sources, numSources, contourLevels, decay, warp, warpFreq, showHatch, showFrame, showAnnotations, showSplatter, baseWeight, regime, hatchAngle, densityMode, circleDensity }; } function marchingSquares(field, w, h, threshold) { const segments = []; const get = (i, j) => field[j * w + i]; for (let j = 0; j < h - 1; j++) { for (let i = 0; i < w - 1; i++) { const a = get(i, j); const b = get(i + 1, j); const c = get(i + 1, j + 1); const d = get(i, j + 1); let code = 0; if (a > threshold) code |= 1; if (b > threshold) code |= 2; if (c > threshold) code |= 4; if (d > threshold) code |= 8; if (code === 0 || code === 15) continue; const top = () => ({ x: i + (threshold - a) / (b - a), y: j }); const right = () => ({ x: i + 1, y: j + (threshold - b) / (c - b) }); const bottom = () => ({ x: i + (threshold - d) / (c - d), y: j + 1 }); const left = () => ({ x: i, y: j + (threshold - a) / (d - a) }); switch (code) { case 1: case 14: segments.push([left(), top()]); break; case 2: case 13: segments.push([top(), right()]); break; case 3: case 12: segments.push([left(), right()]); break; case 4: case 11: segments.push([right(), bottom()]); break; case 6: case 9: segments.push([top(), bottom()]); break; case 7: case 8: segments.push([left(), bottom()]); break; case 5: { const center = (a + b + c + d) * 0.25; if (center > threshold) { segments.push([left(), top()]); segments.push([right(), bottom()]); } else { segments.push([left(), bottom()]); segments.push([top(), right()]); } break; } case 10: { const center = (a + b + c + d) * 0.25; if (center > threshold) { segments.push([top(), right()]); segments.push([left(), bottom()]); } else { segments.push([left(), top()]); segments.push([right(), bottom()]); } break; } } } } return stitchSegments(segments); } function stitchSegments(segments) { const PREC = 1000; const key = (p) => `${Math.round(p.x * PREC)}_${Math.round(p.y * PREC)}`; const map = new Map(); segments.forEach((s, idx) => { const k0 = key(s[0]); const k1 = key(s[1]); if (!map.has(k0)) map.set(k0, []); map.get(k0).push({ idx, end: 0 }); if (!map.has(k1)) map.set(k1, []); map.get(k1).push({ idx, end: 1 }); }); const used = new Array(segments.length).fill(false); const polylines = []; for (let start = 0; start < segments.length; start++) { if (used[start]) continue; used[start] = true; const poly = [segments[start][0], segments[start][1]]; let extending = true; while (extending) { extending = false; const tail = poly[poly.length - 1]; const list = map.get(key(tail)) || []; for (const { idx, end } of list) { if (used[idx]) continue; const seg = segments[idx]; const other = seg[1 - end]; poly.push(other); used[idx] = true; extending = true; break; } } extending = true; while (extending) { extending = false; const head = poly[0]; const list = map.get(key(head)) || []; for (const { idx, end } of list) { if (used[idx]) continue; const seg = segments[idx]; const other = seg[1 - end]; poly.unshift(other); used[idx] = true; extending = true; break; } } if (poly.length >= 3) polylines.push(poly); } return polylines; } const DEFAULT_SIZE = 1000; const SCREEN_PIXEL_DENSITY = 2; const PRINT_EXPORT_SIZE = 5000; const PRINT_EXPORT_PIXEL_DENSITY = 1; let ifSave = false; let ifBulk = false; let canvasSize = 800; let M = 1; let fieldFBO, fieldShader; let paperFBO, paperShader, paperImg; let fieldGrid, domGrid, lowGrid; let gridW = 0, gridH = 0; let mainCanvas; let seed = 1; let needsRender = false; let seedLocked = false; // --- On-chain seed ----------------------------------------------------- // The contract's tokenURI builds this page on-chain and injects the current // seed as window.ARTSCRIPT_SEED in a <script> before this sketch. When the // holder calls updateSeed(...), tokenURI regenerates with the new seed, so // the piece always reflects current on-chain state — no URL, no RPC. With no // injected seed the file runs in "studio mode": a random, explorable seed. // Reduce any uint256 to the 32-bit value the renderer's RNG uses. Small // curated seeds (e.g. 931222) are preserved exactly. function reduceSeed(big) { const MOD = 4294967296n; // 2^32 return Number(((big % MOD) + MOD) % MOD); } // Read the seed the contract injected into the page. The on-chain tokenURI // emits an inline script setting window.ARTSCRIPT_SEED before this sketch, // so the rendered piece always reflects current on-chain state. function readInjectedSeed() { if (typeof window.ARTSCRIPT_SEED === 'undefined' || window.ARTSCRIPT_SEED === null) return null; const raw = String(window.ARTSCRIPT_SEED).trim(); if (!/^[0-9]+$/.test(raw)) return null; try { return reduceSeed(BigInt(raw)); } catch (e) { return null; } } function setup() { // The on-chain HTML wrapper is minimal — ensure page styling + holder. document.documentElement.style.cssText = 'margin:0;padding:0;width:100%;height:100%'; document.body.style.cssText = 'margin:0;padding:0;width:100%;height:100%;background:#1a1a1a;overflow:hidden;display:flex;align-items:center;justify-content:center'; let holder = document.getElementById('canvas-holder'); if (!holder) { holder = document.createElement('div'); holder.id = 'canvas-holder'; holder.style.cssText = 'display:flex;align-items:center;justify-content:center;width:100vmin;height:100vmin'; document.body.appendChild(holder); } canvasSize = Math.floor(Math.min(window.innerWidth, window.innerHeight)); M = canvasSize / DEFAULT_SIZE; pixelDensity(SCREEN_PIXEL_DENSITY); mainCanvas = createCanvas(canvasSize, canvasSize); mainCanvas.parent(holder); const FBO_RES = 384; fieldFBO = createGraphics(FBO_RES, FBO_RES, WEBGL); fieldFBO.pixelDensity(1); fieldShader = fieldFBO.createShader(FIELD_VERT, FIELD_FRAG); fieldFBO.shader(fieldShader); const PAPER_RES = 1024; paperFBO = createGraphics(PAPER_RES, PAPER_RES, WEBGL); paperFBO.pixelDensity(1); paperShader = paperFBO.createShader(FIELD_VERT, PAPER_FRAG); const injected = readInjectedSeed(); if (injected !== null) { seed = injected; // seed delivered on-chain by the contract seedLocked = true; } else { seed = Math.floor(Math.random() * 999999); // studio mode (no injected seed) location.hash = String(seed); } generate(); noLoop(); } function uploadFieldUniforms() { const N = composition.sources.length; const posArr = []; const freqArr = []; const phaseArr = []; const ampArr = []; const signArr = []; for (let i = 0; i < MAX_SOURCES; i++) { if (i < N) { posArr.push(composition.sources[i].x, composition.sources[i].y); freqArr.push(composition.sources[i].freq); phaseArr.push(composition.sources[i].phase); ampArr.push(composition.sources[i].amp); signArr.push(composition.sources[i].sign); } else { posArr.push(0, 0); freqArr.push(0); phaseArr.push(0); ampArr.push(0); signArr.push(0); } } fieldShader.setUniform('uNumSources', N); fieldShader.setUniform('uPos', posArr); fieldShader.setUniform('uFreq', freqArr); fieldShader.setUniform('uPhase', phaseArr); fieldShader.setUniform('uAmp', ampArr); fieldShader.setUniform('uSign', signArr); fieldShader.setUniform('uDecay', composition.decay); fieldShader.setUniform('uWarp', composition.warp); fieldShader.setUniform('uWarpFreq', composition.warpFreq); } function renderFieldToGrid() { fieldFBO.noStroke(); fieldFBO.shader(fieldShader); fieldFBO.rect(-fieldFBO.width / 2, -fieldFBO.height / 2, fieldFBO.width, fieldFBO.height); fieldFBO.loadPixels(); gridW = fieldFBO.width; gridH = fieldFBO.height; fieldGrid = new Float32Array(gridW * gridH); domGrid = new Uint8Array(gridW * gridH); lowGrid = new Float32Array(gridW * gridH); const px = fieldFBO.pixels; for (let j = 0; j < gridH; j++) { for (let i = 0; i < gridW; i++) { const idx = (j * gridW + i) * 4; fieldGrid[j * gridW + i] = (px[idx + 0] / 255) * 2 - 1; domGrid[j * gridW + i] = px[idx + 1]; lowGrid[j * gridW + i] = (px[idx + 2] / 255) * 2 - 1; } } } function renderPaper() { const palette = composition.palette; const c = color(palette.paper); paperFBO.shader(paperShader); paperShader.setUniform('uSeed', seed % 1000); paperShader.setUniform('uPaperColor', [red(c) / 255, green(c) / 255, blue(c) / 255]); paperFBO.noStroke(); paperFBO.rect(-paperFBO.width / 2, -paperFBO.height / 2, paperFBO.width, paperFBO.height); paperImg = paperFBO.get(); } function generate() { setSeed(seed); composition = buildComposition(); uploadFieldUniforms(); renderFieldToGrid(); renderPaper(); needsRender = true; redraw(); } function sampleField(grid, x, y) { const fx = x * (gridW - 1); const fy = y * (gridH - 1); const i0 = Math.floor(fx), j0 = Math.floor(fy); const i1 = Math.min(gridW - 1, i0 + 1); const j1 = Math.min(gridH - 1, j0 + 1); const tx = fx - i0, ty = fy - j0; const a = grid[j0 * gridW + i0]; const b = grid[j0 * gridW + i1]; const c = grid[j1 * gridW + i0]; const d = grid[j1 * gridW + i1];
0x01876c26…01ca·#25,476,481·0x7540224f…3bb592
Pastelle Onchain: token frozen"attributes":[{"trait_type":"Seedata:application/json;utf8,qPastelle Onchain #`pPastelle Onchain: fee exceeds ca`DV[P_R_` R` `@_ T`@QPastelle Onchain: withdraw faile`DPastelle Onchain: zero addressPastelle Onchain: minting frozen`DPastelle Onchain: incorrect feePastelle Onchain: not token owne`Dwindow.ARTSCRIPT_SEED="Pastelle Onchain: invalid tag ty`D{\u00000123456789abcdefbtn