From 2231ff14e1cd5be340942581babc0bd73290a61f Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 1 Feb 2026 15:25:59 +0000 Subject: [PATCH 1/2] Improve icon generator based on feedback Visual improvements: - Inner radius range extended to 0.75 for more dramatic perspective - LED color changed to yellow-green (#00e676, ~560nm) matching other tools - Added thick outline for arena outer edge, thin for inner edge (depth effect) - Added radial lines at gaps in partial arenas for clarity - Background options: dark, white, or transparent Orientation fix: - Respect column_order (cw/ccw) from arena config for proper symmetry - Matches arena editor behavior (cw: left of south CCW, ccw: right of south CW) UI updates: - Background selector dropdown (dark/white/transparent) - Inner radius slider max increased to 0.75 - Updated test files with new options https://claude.ai/code/session_0162zsZjsoDQdnYSLmhTG5sq --- icon_generator.html | 17 +++++-- js/icon-generator.js | 119 +++++++++++++++++++++++++++++++++++++------ test_quick.html | 12 +++-- 3 files changed, 126 insertions(+), 22 deletions(-) diff --git a/icon_generator.html b/icon_generator.html index 9d606c1..ef3d249 100644 --- a/icon_generator.html +++ b/icon_generator.html @@ -379,7 +379,16 @@

Pattern Icon Generator

- + +
+ +
+ +
@@ -609,13 +618,15 @@

Pattern Icon Generator

const mode = document.querySelector('input[name="icon-mode"]:checked').value; const size = parseInt(document.getElementById('size-select').value); const innerRadiusRatio = parseFloat(document.getElementById('radius-slider').value); + const background = document.getElementById('background-select').value; const options = { width: size, height: size, innerRadiusRatio: innerRadiusRatio, - backgroundColor: '#0f1419', - showGaps: true + backgroundColor: background, + showGaps: true, + showOutlines: true }; try { diff --git a/js/icon-generator.js b/js/icon-generator.js index 9b9f925..1fd2d86 100644 --- a/js/icon-generator.js +++ b/js/icon-generator.js @@ -21,8 +21,9 @@ function generatePatternIcon(patternData, arenaConfig, options = {}) { width: 256, height: 256, innerRadiusRatio: 0.2, // inner/outer radius (smaller = more perspective) - backgroundColor: '#0f1419', + backgroundColor: 'dark', // 'dark', 'white', or 'transparent' showGaps: true, // render missing panels as gaps + showOutlines: true, // show arena outlines for depth ...options }; @@ -56,8 +57,9 @@ function generateMotionIcon(patternData, arenaConfig, options = {}) { width: 256, height: 256, innerRadiusRatio: 0.2, - backgroundColor: '#0f1419', + backgroundColor: 'dark', // 'dark', 'white', or 'transparent' showGaps: true, + showOutlines: true, ...options }; @@ -171,14 +173,26 @@ function renderCylindricalIcon(frameData, patternData, arenaConfig, opts) { ctx.imageSmoothingEnabled = true; ctx.imageSmoothingQuality = 'high'; - // Fill background - ctx.fillStyle = opts.backgroundColor; - ctx.fillRect(0, 0, opts.width, opts.height); + // Determine background color + let bgColor; + if (opts.backgroundColor === 'transparent') { + bgColor = 'transparent'; + } else if (opts.backgroundColor === 'white') { + bgColor = '#ffffff'; + } else { + bgColor = '#0f1419'; // dark + } + + // Fill background (unless transparent) + if (bgColor !== 'transparent') { + ctx.fillStyle = bgColor; + ctx.fillRect(0, 0, opts.width, opts.height); + } // Calculate arena geometry const centerX = opts.width / 2; const centerY = opts.height / 2; - const outerRadius = Math.min(opts.width, opts.height) / 2 - 10; // padding + const outerRadius = Math.min(opts.width, opts.height) / 2 - 15; // padding for outlines const innerRadius = outerRadius * opts.innerRadiusRatio; // Get panel specs @@ -192,16 +206,29 @@ function renderCylindricalIcon(frameData, patternData, arenaConfig, opts) { const numRows = arenaConfig.num_rows; const columnsInstalled = arenaConfig.columns_installed || Array.from({ length: numCols }, (_, i) => i); + const columnOrder = arenaConfig.column_order || 'cw'; // Total pixels in arena const totalColPixels = numCols * pixelsPerPanel; const totalRowPixels = numRows * pixelsPerPanel; + // Base offset: -90° to start at south (-PI/2) + const BASE_OFFSET_RAD = -Math.PI / 2; + const alpha = (2 * Math.PI) / numCols; // angle per column + // Render each column for (const colIdx of columnsInstalled) { - // Calculate angular position for this column - const colStartAngle = (colIdx / numCols) * 2 * Math.PI; - const colEndAngle = ((colIdx + 1) / numCols) * 2 * Math.PI; + // Calculate angular position for this column based on column_order + // CW: c0 left of south, columns increase counter-clockwise (decreasing angle) + // CCW: c0 right of south, columns increase clockwise (increasing angle) + let colStartAngle, colEndAngle; + if (columnOrder === 'cw') { + colStartAngle = BASE_OFFSET_RAD - colIdx * alpha; + colEndAngle = BASE_OFFSET_RAD - (colIdx + 1) * alpha; + } else { + colStartAngle = BASE_OFFSET_RAD + colIdx * alpha; + colEndAngle = BASE_OFFSET_RAD + (colIdx + 1) * alpha; + } // Render each panel in this column for (let rowIdx = 0; rowIdx < numRows; rowIdx++) { @@ -238,17 +265,72 @@ function renderCylindricalIcon(frameData, patternData, arenaConfig, opts) { } // Draw inner circle to create donut shape - ctx.fillStyle = opts.backgroundColor; - ctx.beginPath(); - ctx.arc(centerX, centerY, innerRadius, 0, 2 * Math.PI); - ctx.fill(); + if (bgColor !== 'transparent') { + ctx.fillStyle = bgColor; + ctx.beginPath(); + ctx.arc(centerX, centerY, innerRadius, 0, 2 * Math.PI); + ctx.fill(); + } + + // Draw radial lines for gaps in partial arenas + if (opts.showGaps && columnsInstalled.length < numCols) { + const installedSet = new Set(columnsInstalled); + ctx.strokeStyle = '#2d3640'; // border color + ctx.lineWidth = 1; + + for (let colIdx = 0; colIdx < numCols; colIdx++) { + if (!installedSet.has(colIdx)) { + // Draw radial lines for missing columns + let angle1, angle2; + if (columnOrder === 'cw') { + angle1 = BASE_OFFSET_RAD - colIdx * alpha; + angle2 = BASE_OFFSET_RAD - (colIdx + 1) * alpha; + } else { + angle1 = BASE_OFFSET_RAD + colIdx * alpha; + angle2 = BASE_OFFSET_RAD + (colIdx + 1) * alpha; + } + + // Draw line at start of gap + ctx.beginPath(); + ctx.moveTo(centerX + innerRadius * Math.cos(angle1), + centerY + innerRadius * Math.sin(angle1)); + ctx.lineTo(centerX + outerRadius * Math.cos(angle1), + centerY + outerRadius * Math.sin(angle1)); + ctx.stroke(); + + // Draw line at end of gap + ctx.beginPath(); + ctx.moveTo(centerX + innerRadius * Math.cos(angle2), + centerY + innerRadius * Math.sin(angle2)); + ctx.lineTo(centerX + outerRadius * Math.cos(angle2), + centerY + outerRadius * Math.sin(angle2)); + ctx.stroke(); + } + } + } + + // Draw outlines for depth + if (opts.showOutlines) { + // Thick outline for outer edge + ctx.strokeStyle = '#2d3640'; + ctx.lineWidth = 3; + ctx.beginPath(); + ctx.arc(centerX, centerY, outerRadius, 0, 2 * Math.PI); + ctx.stroke(); + + // Thin outline for inner edge + ctx.lineWidth = 1.5; + ctx.beginPath(); + ctx.arc(centerX, centerY, innerRadius, 0, 2 * Math.PI); + ctx.stroke(); + } // Export as PNG return canvas.toDataURL('image/png'); } /** - * Convert brightness value to RGB color + * Convert brightness value to RGB color (LED green) */ function brightnessToRGB(brightness, grayscaleMode) { let normalized; @@ -264,8 +346,13 @@ function brightnessToRGB(brightness, grayscaleMode) { // Apply gamma correction for better visibility normalized = Math.pow(normalized, 0.8); - const value = Math.round(normalized * 255); - return `rgb(${value}, ${value}, ${value})`; + // LED green color: #00e676 (yellowish-green ~560nm) + // RGB: (0, 230, 118) + const r = Math.round(0 * normalized); + const g = Math.round(230 * normalized); + const b = Math.round(118 * normalized); + + return `rgb(${r}, ${g}, ${b})`; } /** diff --git a/test_quick.html b/test_quick.html index 0c105a9..94c6c97 100644 --- a/test_quick.html +++ b/test_quick.html @@ -127,7 +127,9 @@

Icon Generator Quick Test

const dataURL = IconGenerator.generatePatternIcon(patternData, arenaConfig, { width: 256, height: 256, - innerRadiusRatio: 0.2 + innerRadiusRatio: 0.2, + backgroundColor: 'dark', + showOutlines: true }); if (!dataURL || !dataURL.startsWith('data:image/png')) { @@ -175,7 +177,9 @@

Icon Generator Quick Test

const dataURLDramatic = IconGenerator.generatePatternIcon(patternData, arenaConfig, { width: 256, height: 256, - innerRadiusRatio: 0.1 // More dramatic + innerRadiusRatio: 0.1, // More dramatic + backgroundColor: 'dark', + showOutlines: true }); if (!dataURLDramatic || !dataURLDramatic.startsWith('data:image/png')) { @@ -230,7 +234,9 @@

Icon Generator Quick Test

innerRadiusRatio: 0.2, frameRange: [0, 9], maxFrames: 10, - weightingFunction: 'exponential' + weightingFunction: 'exponential', + backgroundColor: 'dark', + showOutlines: true }); if (!dataURL || !dataURL.startsWith('data:image/png')) { From 739c78a7fadac028c2bda2462bde294bc1535189 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 1 Feb 2026 15:30:42 +0000 Subject: [PATCH 2/2] Update versioning policy to require timestamps in ET Changes: - CLAUDE.md: Update versioning format to include HH:MM ET timestamp - icon_generator.html: Update to v0.2 with timestamp (2026-02-01 10:29 ET) - arena_editor.html: Add timestamp to existing version (15:00 ET) This helps distinguish multiple updates per day and makes it clear when pages were last modified. https://claude.ai/code/session_0162zsZjsoDQdnYSLmhTG5sq --- CLAUDE.md | 6 ++++-- arena_editor.html | 2 +- icon_generator.html | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 5b9b744..f4bfa19 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,9 +4,11 @@ Use simple two-digit versions for all web tools (e.g., `v1`, `v2`, `v6`). No semantic versioning (1.0.0) needed. -Format in footer: `Tool Name vX | YYYY-MM-DD` +Format in footer: `Tool Name vX | YYYY-MM-DD HH:MM ET` -Example: `Arena Editor v2 | 2026-01-16` +Example: `Arena Editor v2 | 2026-01-16 14:30 ET` + +**IMPORTANT**: Always include timestamp in Eastern Time (ET) to distinguish multiple updates per day. Update the timestamp whenever the page is modified. ## Design System diff --git a/arena_editor.html b/arena_editor.html index 7f0ceaf..80ce819 100644 --- a/arena_editor.html +++ b/arena_editor.html @@ -673,7 +673,7 @@

Arena Metrics

Reiser Lab | PanelDisplayTools

-

Arena Editor v3.1 | 2026-01-30

+

Arena Editor v3.1 | 2026-01-30 15:00 ET

diff --git a/icon_generator.html b/icon_generator.html index ef3d249..709789f 100644 --- a/icon_generator.html +++ b/icon_generator.html @@ -420,7 +420,7 @@

Pattern Icon Generator