// Static display build driven by DISPLAY_CONFIG (function () { const { COLS, ROWS, CELL, BASE, BACKGROUND_COLOR, DEFAULT_BACKGROUND_STROKE, getRowSegments, computeCanvasSize, PADDING_PX } = window.SHARED; let containerEl; let configs; let slideIdx = 0; let config; let initialFirstSlideDone = false; // after we leave slide 0 once // Animation timing const PH1_WAVE_MS = 700; // wave from center to outer const PH1_FADE_MS = 300; // per-segment fade duration const PH2_FADE_MS = 500; // style overlay fade const PH2_STAGGER_MS = 5; // faster succession between styled cells const PH3_FADE_MS = 500; // big blocks fade // Connectors const CONNECTOR_DRAW_MS = 2000; // line draw duration const CONNECTOR_FADE_MS = 400; // fade-out duration (separate) const CONNECTOR_STAGGER_MS = 500; // delay between connectors // Overlaps & staggers const PH2_OVERLAP_MS = 300; // start styled slightly before base finishes (first slide) const PH3_OVERLAP_MS = 300; // start blocks slightly before styled finishes const BLOCK_STAGGER_MS = 200; // short interval so blocks don't appear all at once let startMs = 0; let segmentsMeta = null; // [{row,col,span,distNorm,overlayIndex}] new p5((p) => { p.setup = function () { containerEl = document.getElementById('sketch-container'); const single = window.DISPLAY_CONFIG; const multi = window.DISPLAY_CONFIGS; configs = Array.isArray(multi) && multi.length ? multi : [ single || { background: [], blocks: [], connectors: [] } ]; slideIdx = 0; config = configs[slideIdx]; const dpr = (p.displayDensity && p.displayDensity()) || (window.devicePixelRatio || 1); p.pixelDensity(Math.max(1, Math.round(dpr))); const dims = window.SHARED.computeCanvasDims(containerEl); const canvas = p.createCanvas(dims.width, dims.height); canvas.parent(containerEl); startMs = p.millis(); p.loop(); p.textFont('Inter'); p.textSize(12); }; p.windowResized = function () { if (!containerEl) return; const dims = window.SHARED.computeCanvasDims(containerEl); p.resizeCanvas(dims.width, dims.height); p.redraw(); }; p.draw = function () { p.background(BACKGROUND_COLOR); const innerW = p.width - PADDING_PX * 2; const cellSize = innerW / COLS; if (!segmentsMeta) segmentsMeta = buildSegmentsMeta(); const now = p.millis(); const t = now - startMs; const phase2Start = (slideIdx === 0 && !initialFirstSlideDone) ? Math.max(0, PH1_WAVE_MS + PH1_FADE_MS - PH2_OVERLAP_MS) : 0; // on subsequent slides, start styled immediately const overlayTotal = (segmentsMeta && segmentsMeta.length > 0) ? ((segmentsMeta.length - 1) * PH2_STAGGER_MS + PH2_FADE_MS) : PH2_FADE_MS; const phase3Start = Math.max(0, phase2Start + overlayTotal - PH3_OVERLAP_MS); const connectorsInStart = phase3Start + PH3_FADE_MS; const connectorsCount = config.connectors ? config.connectors.length : 0; const connectorsInTotal = (connectorsCount > 0) ? (CONNECTOR_DRAW_MS + (connectorsCount - 1) * CONNECTOR_STAGGER_MS) : 0; const waitStart = connectorsInStart + connectorsInTotal; const WAIT_MS = 3000; const overlayOutStart = waitStart + WAIT_MS; const overlayOutTotal = overlayTotal; const blocksOutStart = overlayOutStart; const connectorsOutStart = overlayOutStart; const slideEnd = overlayOutStart + Math.max(overlayOutTotal, PH3_FADE_MS, CONNECTOR_FADE_MS); p.push(); p.translate(PADDING_PX, PADDING_PX); drawBackgroundPhased(p, cellSize, config.background, t, phase2Start, overlayOutStart); drawBlocksPhased(p, cellSize, config.blocks, t, phase3Start, blocksOutStart); drawConnectorsPhased(p, cellSize, config.blocks, config.connectors, t, connectorsInStart, connectorsOutStart); p.pop(); if (t > slideEnd + 50) { const prevIdx = slideIdx; slideIdx = (slideIdx + 1) % configs.length; config = configs[slideIdx]; startMs = now; if (prevIdx === 0) initialFirstSlideDone = true; p.loop(); } }; function drawBackgroundPhased(p, cellSize, overrides, t, phase2Start, overlayOutStart) { const overrideKey = new Map(); for (const o of overrides || []) overrideKey.set(`${o.row}:${o.col}`, o); // PASS 1: draw all unstyled bases first for (const meta of segmentsMeta) { const x = meta.col * cellSize; const y = meta.row * cellSize; const w = meta.span * cellSize; const h = cellSize; const radius = cellSize / 2; const o = overrideKey.get(`${meta.row}:${meta.col}`); // Only fade-in base on the very first slide (initial load) const fadeBase = (slideIdx === 0 && !initialFirstSlideDone); const segStart = meta.distNorm * PH1_WAVE_MS; const segProgress = fadeBase ? Math.max(0, Math.min(1, (t - segStart) / PH1_FADE_MS)) : 1; if (segProgress <= 0) continue; const segStyleAlphaIn = Math.max(0, Math.min(1, (t - (phase2Start + (meta.overlayIndex || 0) * PH2_STAGGER_MS)) / PH2_FADE_MS)); p.noFill(); p.strokeWeight(1); let baseAlpha = Math.round(255 * segProgress); if (o && o.style === 'invisible') baseAlpha = Math.round(baseAlpha * (1 - segStyleAlphaIn)); const c = p.color(DEFAULT_BACKGROUND_STROKE); p.stroke(p.red(c), p.green(c), p.blue(c), baseAlpha); p.rect(x, y, w, h, radius); } // PASS 2: draw all styled overlays on top for (const meta of segmentsMeta) { const o = overrideKey.get(`${meta.row}:${meta.col}`); if (!o) continue; if (o.style === 'invisible') continue; const x = meta.col * cellSize; const y = meta.row * cellSize; const w = meta.span * cellSize; const h = cellSize; const radius = cellSize / 2; const fadeBase = (slideIdx === 0 && !initialFirstSlideDone); const segStart = meta.distNorm * PH1_WAVE_MS; const segProgress = fadeBase ? Math.max(0, Math.min(1, (t - segStart) / PH1_FADE_MS)) : 1; if (segProgress <= 0) continue; const segStyleAlphaIn = Math.max(0, Math.min(1, (t - (phase2Start + (meta.overlayIndex || 0) * PH2_STAGGER_MS)) / PH2_FADE_MS)); const segStyleAlphaOut = Math.max(0, Math.min(1, (t - (overlayOutStart + (meta.overlayIndex || 0) * PH2_STAGGER_MS)) / PH2_FADE_MS)); const overlayAlpha = Math.round(255 * Math.min(1, segProgress) * Math.max(0, Math.min(1, segStyleAlphaIn * (1 - segStyleAlphaOut)))); if (overlayAlpha <= 0) continue; if (o.style === 'fillNeutral') { p.fill(221, 215, 211, overlayAlpha); p.noStroke(); p.rect(x, y, w, h, radius); } else if (o.style === 'fillColor' && o.color) { const cc = p.color(o.color); p.fill(p.red(cc), p.green(cc), p.blue(cc), overlayAlpha); p.noStroke(); p.rect(x, y, w, h, radius); } else if (o.style === 'strokeColor' && o.color) { const cc = p.color(o.color); p.stroke(p.red(cc), p.green(cc), p.blue(cc), overlayAlpha); p.strokeWeight(2); p.noFill(); p.rect(x, y, w, h, radius); } } } function drawBlocksPhased(p, cellSize, blocks, t, phase3Start, blocksOutStart) { if (!blocks || blocks.length === 0) return; const outAlpha = 1 - Math.max(0, Math.min(1, (t - blocksOutStart) / PH3_FADE_MS)); if (outAlpha <= 0) return; p.noStroke(); p.textAlign(p.CENTER, p.CENTER); const labelSizePx = Math.max(8, Math.floor(cellSize * 0.32)); for (let i = 0; i < blocks.length; i++) { const b = blocks[i]; const inAlpha = Math.max(0, Math.min(1, (t - (phase3Start + i * BLOCK_STAGGER_MS)) / PH3_FADE_MS)); const a = Math.round(255 * Math.min(1, inAlpha * outAlpha)); if (a <= 0) continue; const x = b.x * cellSize; const y = b.y * cellSize; const w = b.w * cellSize; const h = b.h * cellSize; const r = cellSize / 2; const c = p.color(b.color || '#014AA5'); p.fill(p.red(c), p.green(c), p.blue(c), a); p.rect(x, y, w, h, r); if (b.label) { p.textSize(labelSizePx); p.fill(255, a); p.text(b.label, x + w / 2, y + h / 2); } } } function drawConnectorsPhased(p, cellSize, blocks, connectors, t, connectorsInStart, connectorsOutStart) { const idToBlock = new Map(); for (const b of blocks || []) idToBlock.set(b.id, b); const ctx = p.drawingContext; (connectors || []).forEach((c, i) => { // Render path-only connectors; skip if no valid path if (!c.path || c.path.length < 2) return; const path = c.path; const inT = (t - (connectorsInStart + i * CONNECTOR_STAGGER_MS)) / CONNECTOR_DRAW_MS; const progIn = Math.max(0, Math.min(1, inT)); const alphaOut = 1 - Math.max(0, Math.min(1, (t - connectorsOutStart) / CONNECTOR_FADE_MS)); if (progIn <= 0) return; const prevAlpha = ctx.globalAlpha; ctx.globalAlpha = Math.max(0.001, Math.min(1, alphaOut)); if (progIn < 1) { window.SHARED.drawConnectorPathAnimated(ctx, cellSize, path, progIn); } else { window.SHARED.drawConnectorPath(ctx, cellSize, path); } ctx.globalAlpha = prevAlpha; }); } function buildSegmentsMeta() { const cx = COLS / 2; // 8 const cy = ROWS / 2; // 8 const metas = []; let maxD = 0; for (let r = 0; r < ROWS; r++) { const segments = getRowSegments(r); for (const seg of segments) { const sx = seg.col + seg.span / 2; const sy = r + 0.5; const dx = sx - cx + 0.0; const dy = sy - cy + 0.0; const d = Math.hypot(dx, dy); metas.push({ row: r, col: seg.col, span: seg.span, d }); if (d > maxD) maxD = d; } } for (const m of metas) { m.distNorm = maxD > 0 ? m.d / maxD : 0; } // Assign overlay order from center outward for per-cell stagger const sorted = metas.slice().sort((a, b) => a.d - b.d); for (let i = 0; i < sorted.length; i++) { sorted[i].overlayIndex = i; } return metas; } }); })();