im recording a video for developers and I want to use the github contribution graph as a fun flair element. I want you to write some javascript to control it. can you use your chrome skill to figure out how to do that? we're basically going to alternate the green graph to make an animation. lets start by seeing if you can find the graph on github.com/aarondfrancis
GitHub Contribution Graph Waveform
Turned a static GitHub contribution graph into a beat-synced waveform visualizer — Claude inspected the DOM via Chrome, built a reusable animator with wave/pulse/rain effects, then synced it to audio beats for a developer video intro.
- Stats
- Prompts
- 25
- Tool calls
- 42
- Messages
- 196
- Assets
- 6
- Meta
- Author
- @aarondfrancis
- Created At
- February 08, 2026
- Source
- Claude
- Share
Thought 1 time. Used 0 tool calls. +0 -0 [Expand]
Thinking
The user wants me to help create a JavaScript animation for the GitHub contribution graph. They want to use it as a visual flair element in a developer video. Let me start by checking the browser context and navigating to the GitHub profile to inspect the contribution graph.
First, I should get the browser tab context.
I'll check the browser context and navigate to that GitHub profile to inspect the contribution graph structure.
Thought 4 times. Used 4 tool calls. +0 -0 [Expand]
Tool: mcp__claude-in-chrome__tabs_context_mcp
mcp__claude-in-chrome__tabs_context_mcp
{
"createIfEmpty": true
}{"availableTabs":[{"tabId":1857700089,"title":"New Tab","url":"chrome://newtab"}],"tabGroupId":1913878167}Thinking
Good, I have a new tab available. Let me navigate to the GitHub profile page to see the contribution graph.
Tool: mcp__claude-in-chrome__navigate
mcp__claude-in-chrome__navigate
{
"url": "https://github.com/aarondfrancis",
"tabId": 1857700089
}Navigated to https://github.com/aarondfrancisThinking
Let me wait a moment for the page to load and then take a screenshot to see the contribution graph.
Tool: mcp__claude-in-chrome__computer
mcp__claude-in-chrome__computer
{
"action": "wait",
"duration": 2,
"tabId": 1857700089
}Waited for 2 secondsThinking
Good, the page loaded. Now let me take a screenshot to see the contribution graph and then inspect the DOM structure to understand how to manipulate it.
Tool: mcp__claude-in-chrome__computer
mcp__claude-in-chrome__computer
{
"action": "screenshot",
"tabId": 1857700089
}Successfully captured screenshot (1584x776, jpeg) - ID: ss_7715dtw5mThinking
I can see the GitHub contribution graph. It shows "3,662 contributions in the last year" with the green squares. Now I need to inspect the DOM structure to understand how the graph is built so I can manipulate it with JavaScript.
Let me use the read_page tool to find the contribution graph elements, or I can use JavaScript to inspect it directly. Let me try finding the contribution graph structure.
I can see the contribution graph. Now let me inspect the DOM structure to understand how it's built.
Thought 1 time. Used 1 tool call. +0 -0 [Expand]
Tool: mcp__claude-in-chrome__javascript_tool
mcp__claude-in-chrome__javascript_tool
{
"action": "javascript_exec",
"tabId": 1857700089,
"text": "\n// Find the contribution graph and inspect its structure\nconst graph = document.querySelector('.js-calendar-graph');\nconst cells = document.querySelectorAll('.ContributionCalendar-day');\n\n// Get sample cell structure\nconst sampleCell = cells[10];\nconst cellInfo = {\n totalCells: cells.length,\n graphFound: !!graph,\n sampleCell: sampleCell ? {\n tagName: sampleCell.tagName,\n className: sampleCell.className,\n attributes: Array.from(sampleCell.attributes).map(a => ({name: a.name, value: a.value})),\n style: sampleCell.getAttribute('style'),\n innerHTML: sampleCell.innerHTML.substring(0, 200)\n } : null,\n // Check if they use data attributes or fill colors\n fills: Array.from(new Set(Array.from(cells).map(c => c.getAttribute('data-level')))).slice(0, 10)\n};\n\nJSON.stringify(cellInfo, null, 2);\n"
}{
"totalCells": 372,
"graphFound": true,
"sampleCell": {
"tagName": "TD",
"className": "ContributionCalendar-day",
"attributes": [
{
"name": "tabindex",
"value": "-1"
},
{
"name": "data-ix",
"value": "10"
},
{
"name": "aria-selected",
"value": "false"
},
{
"name": "aria-describedby",
"value": "contribution-graph-legend-level-1"
},
{
"name": "style",
"value": "width: 10px"
},
{
"name": "data-date",
"value": "2025-03-23"
},
{
"name": "id",
"value": "contribution-day-component-0-10"
},
{
"name": "data-level",
"value": "1"
},
{
"name": "role",
"value": "gridcell"
},
{
"name": "data-view-component",
"value": "true"
},
{
"name": "class",
"value": "ContributionCalendar-day"
[TRUNCATED]Thinking
Excellent! I found the structure. The contribution graph uses:
-
tdelements with classContributionCalendar-day - A
data-levelattribute (0-4) that controls the color intensity - There are 372 cells total
The data-level attribute values likely map to:
- 0 = no contributions (gray/empty)
- 1 = light green
- 2 = medium green
- 3 = darker green
- 4 = darkest green
Let me verify the level values and then create an animation. First, let me see all the unique levels.
Found it. The graph uses data-level attributes (0-4) to control the green intensity. Let me verify the levels and see how the colors work.
Thought 1 time. Used 1 tool call. +0 -0 [Expand]
Tool: mcp__claude-in-chrome__javascript_tool
mcp__claude-in-chrome__javascript_tool
{
"action": "javascript_exec",
"tabId": 1857700089,
"text": "\n// Get all unique levels and count cells by level\nconst cells = document.querySelectorAll('.ContributionCalendar-day');\nconst levelCounts = {};\ncells.forEach(cell => {\n const level = cell.getAttribute('data-level');\n levelCounts[level] = (levelCounts[level] || 0) + 1;\n});\n\n// Get the computed styles for different levels\nconst sampleCells = {};\ncells.forEach(cell => {\n const level = cell.getAttribute('data-level');\n if (!sampleCells[level]) {\n const style = window.getComputedStyle(cell);\n sampleCells[level] = {\n backgroundColor: style.backgroundColor,\n level: level\n };\n }\n});\n\nJSON.stringify({ levelCounts, sampleCells }, null, 2);\n"
}{
"levelCounts": {
"0": 4,
"1": 338,
"2": 21,
"3": 7,
"4": 2
},
"sampleCells": {
"0": {
"backgroundColor": "rgb(239, 242, 245)",
"level": "0"
},
"1": {
"backgroundColor": "rgb(172, 238, 187)",
"level": "1"
},
"2": {
"backgroundColor": "rgb(74, 194, 107)",
"level": "2"
},
"3": {
"backgroundColor": "rgb(45, 164, 78)",
"level": "3"
},
"4": {
"backgroundColor": "rgb(17, 99, 41)",
"level": "4"
}
}
}Thinking
Now I have the full picture:
- Level 0 = gray (no contributions)
- Level 1-4 = increasingly dark green
The cells use data-level to control the color via CSS. I can create animations by changing the data-level attribute on each cell.
Let me create a simple wave animation as a proof of concept - alternating the levels to create a visual effect.
Now I understand the structure. Let me create a simple wave animation as a proof of concept.
Thought 2 times. Used 2 tool calls. +0 -0 [Expand]
Tool: mcp__claude-in-chrome__javascript_tool
mcp__claude-in-chrome__javascript_tool
{
"action": "javascript_exec",
"tabId": 1857700089,
"text": "\n// Save original levels so we can restore later\nconst cells = Array.from(document.querySelectorAll('.ContributionCalendar-day'));\nwindow.originalLevels = cells.map(c => c.getAttribute('data-level'));\n\n// Create a wave animation\nlet frame = 0;\nwindow.waveAnimation = setInterval(() => {\n cells.forEach((cell, i) => {\n // Get column index for wave effect\n const ix = parseInt(cell.getAttribute('data-ix') || i % 53);\n // Create wave based on column position and frame\n const wave = Math.sin((ix + frame) * 0.3);\n // Map wave (-1 to 1) to level (0 to 4)\n const level = Math.round((wave + 1) * 2);\n cell.setAttribute('data-level', level);\n });\n frame++;\n}, 100);\n\n\"Wave animation started! Run: clearInterval(window.waveAnimation) to stop\";\n"
}Wave animation started! Run: clearInterval(window.waveAnimation) to stopThinking
The animation should be running now. Let me take a screenshot to show the user what it looks like.
Tool: mcp__claude-in-chrome__computer
mcp__claude-in-chrome__computer
{
"action": "screenshot",
"tabId": 1857700089
}Successfully captured screenshot (1584x776, jpeg) - ID: ss_551035bubThinking
You can see the wave animation is running - the green squares are now showing a wave pattern moving across the graph. The screenshot captures one frame of the animation. Let me stop the animation and give the user the code they can use.
The wave animation is running. You can see the pattern has changed from the original - it's now showing alternating bands of green intensity moving across the graph.
Let me stop this and give you the clean code you can use:
Thought 1 time. Used 1 tool call. +0 -0 [Expand]
Tool: mcp__claude-in-chrome__javascript_tool
mcp__claude-in-chrome__javascript_tool
{
"action": "javascript_exec",
"tabId": 1857700089,
"text": "\n// Stop and restore original\nclearInterval(window.waveAnimation);\n\n// Restore original levels\nconst cells = Array.from(document.querySelectorAll('.ContributionCalendar-day'));\nif (window.originalLevels) {\n cells.forEach((cell, i) => {\n cell.setAttribute('data-level', window.originalLevels[i]);\n });\n}\n\n\"Animation stopped, original graph restored\";\n"
}Animation stopped, original graph restoredThinking
I've successfully found and manipulated the GitHub contribution graph. Now I can provide the user with a clean code snippet they can use. The key findings are:
- Cells are
td.ContributionCalendar-dayelements - Color is controlled by
data-levelattribute (0-4) - 0 = gray, 1-4 = light to dark green
- There are ~372 cells
- Each cell has a
data-ixattribute for column position
I should give the user a nice reusable code snippet they can paste into the console.
Here's what I found:
Key structure:
- Cells:
td.ContributionCalendar-dayelements (372 total) - Color control:
data-levelattribute (0=gray, 1-4=light→dark green) - Column position:
data-ixattribute (useful for wave effects)
Here's clean, reusable code you can paste in the console:
// GitHub Contribution Graph Animator
const ContribAnim = {
cells: [],
original: [],
interval: null,
init() {
this.cells = Array.from(document.querySelectorAll('.ContributionCalendar-day'));
this.original = this.cells.map(c => c.getAttribute('data-level'));
return this;
},
// Wave animation
wave(speed = 100) {
let frame = 0;
this.interval = setInterval(() => {
this.cells.forEach((cell, i) => {
const ix = parseInt(cell.getAttribute('data-ix') || i % 53);
const level = Math.round((Math.sin((ix + frame) * 0.3) + 1) * 2);
cell.setAttribute('data-level', level);
});
frame++;
}, speed);
},
// Pulse all cells
pulse(speed = 200) {
let frame = 0;
this.interval = setInterval(() => {
const level = Math.round((Math.sin(frame * 0.5) + 1) * 2);
this.cells.forEach(cell => cell.setAttribute('data-level', level));
frame++;
}, speed);
},
// Random sparkle
sparkle(speed = 50) {
this.interval = setInterval(() => {
this.cells.forEach(cell => {
cell.setAttribute('data-level', Math.floor(Math.random() * 5));
});
}, speed);
},
stop() {
clearInterval(this.interval);
this.cells.forEach((c, i) => c.setAttribute('data-level', this.original[i]));
}
};
// Usage:
ContribAnim.init().wave(); // Start wave
// ContribAnim.init().pulse(); // Or pulse
// ContribAnim.init().sparkle(); // Or random sparkle
// ContribAnim.stop(); // Stop and restore
Want me to demo any other animation styles, or tweak the speed/pattern?
Let me examine that audio file to understand its characteristics.
Thought 0 times. Used 1 tool call. +0 -0 [Expand]
$ file "~/Desktop/4k.m4a" && afinfo "~/Desktop/4k.m4a"~/Desktop/4k.m4a: ISO Media, Apple iTunes ALAC/AAC-LC (.M4A) Audio
File: ~/Desktop/4k.m4a
File type ID: m4af
Num Tracks: 1
----
Data format: 2 ch, 48000 Hz, aac (0x00000000) 0 bits/channel, 0 bytes/packet, 1024 frames/packet, 0 bytes/frame
no channel layout.
estimated duration: 8.199958 sec
audio bytes: 259323
audio packets: 387
bit rate: 251281 bits per second
packet size upper bound: 1350
maximum packet size: 1350
audio data file offset: 61440
optimized
audio 393598 valid frames + 2112 priming + 578 remainder = 396288
format list:
[ 0] format: 2 ch, 48000 Hz, aac (0x00000000) 0 bits/channel, 0 bytes/packet, 1024 frames/packet, 0 bytes/frame
Channel layout: Stereo (L R)
----Let me try using ffmpeg or sox to analyze the audio and potentially extract beat information.
8.2 second audio file. Let me analyze it for beats/rhythm - I'll try to extract some tempo information.