<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Pramuditha Nadun</title>
    <description>The latest articles on DEV Community by Pramuditha Nadun (@pramudithan).</description>
    <link>https://dev.to/pramudithan</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4108692%2F2a7383ab-ffaa-487a-9fd4-5e1efda04795.jpg</url>
      <title>DEV Community: Pramuditha Nadun</title>
      <link>https://dev.to/pramudithan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pramudithan"/>
    <language>en</language>
    <item>
      <title>I built a pixel-art whale that grows with your GitHub commits (Rust, SDFs, Bayer dithering)</title>
      <dc:creator>Pramuditha Nadun</dc:creator>
      <pubDate>Sun, 01 Nov 2026 16:01:50 +0000</pubDate>
      <link>https://dev.to/pramudithan/i-built-a-pixel-art-whale-that-grows-with-your-github-commits-rust-sdfs-bayer-dithering-32p7</link>
      <guid>https://dev.to/pramudithan/i-built-a-pixel-art-whale-that-grows-with-your-github-commits-rust-sdfs-bayer-dithering-32p7</guid>
      <description>&lt;p&gt;Your GitHub profile README is the one page you fully control. Most of us fill it with badges and stat cards. I wanted something alive, so I built &lt;strong&gt;GitTulkun&lt;/strong&gt;: a pixel-art Tulkun (the giant whale-like creatures from Avatar's Pandora) that is generated from your real GitHub activity, swims in a seamless looping GIF, and grows up as your year goes on.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/PramudithaN" rel="noopener noreferrer"&gt;
        PramudithaN
      &lt;/a&gt; / &lt;a href="https://github.com/PramudithaN/git-tulkun" rel="noopener noreferrer"&gt;
        git-tulkun
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Procedural pixel-art Tulkun whale generated from your GitHub contributions and language colors as a looping GIF for your profile README.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;git-tulkun&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A procedural pixel-art &lt;strong&gt;Tulkun&lt;/strong&gt; leviathan for your GitHub profile &lt;code&gt;README.md&lt;/code&gt; that resets to a baby calf every January 1st, grows on a logarithmic scale into an armored adult bull from your contributions, and takes its colors from your top programming languages.&lt;/p&gt;
&lt;p&gt;
  &lt;a href="https://github.com/PramudithaN/git-tulkun" rel="noopener noreferrer"&gt;
    &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2FPramudithaN%2Fgit-tulkun%2FHEAD%2Ftulkun%2Ftulkun.gif%3Fv%3D9" width="640" alt="Live git-tulkun rendered from @PramudithaN's GitHub activity"&gt;
  &lt;/a&gt;
  &lt;br&gt;
  &lt;em&gt;Live &lt;code&gt;git-tulkun&lt;/code&gt; generated from &lt;a href="https://github.com/PramudithaN" rel="noopener noreferrer"&gt;@PramudithaN&lt;/a&gt;'s current-year GitHub activity (1,072 contributions - Stage 3: Adolescent Tulkun)&lt;/em&gt;
&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Quick Start (1-Step Setup)&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;1.&lt;/strong&gt; Add this embed snippet to your profile &lt;code&gt;README.md&lt;/code&gt; (replace &lt;code&gt;YOUR_USERNAME&lt;/code&gt; with your GitHub username):&lt;/p&gt;

&lt;div class="highlight highlight-text-md notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&amp;lt;&lt;span class="pl-ent"&gt;p&lt;/span&gt; &lt;span class="pl-e"&gt;align&lt;/span&gt;=&lt;span class="pl-s"&gt;"&lt;/span&gt;&lt;span class="pl-s"&gt;center&lt;/span&gt;&lt;span class="pl-s"&gt;"&lt;/span&gt;&amp;gt;
  &amp;lt;&lt;span class="pl-ent"&gt;a&lt;/span&gt; &lt;span class="pl-e"&gt;href&lt;/span&gt;=&lt;span class="pl-s"&gt;"&lt;/span&gt;&lt;span class="pl-s"&gt;https://github.com/PramudithaN/git-tulkun&lt;/span&gt;&lt;span class="pl-s"&gt;"&lt;/span&gt;&amp;gt;
    &lt;span class="pl-c1"&gt;&amp;lt;img src="https://raw.githubusercontent.com/YOUR_USERNAME/YOUR_USERNAME/output/tulkun/tulkun.gif" width="640" alt="git-tulkun swimming" /&amp;gt;&lt;/span&gt;
  &amp;lt;/&lt;span class="pl-ent"&gt;a&lt;/span&gt;&amp;gt;
&amp;lt;/&lt;span class="pl-ent"&gt;p&lt;/span&gt;&amp;gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;2.&lt;/strong&gt; Create &lt;code&gt;.github/workflows/tulkun.yml&lt;/code&gt; in the same repository (the action pushes &lt;code&gt;tulkun.gif&lt;/code&gt;, &lt;code&gt;tulkun.png&lt;/code&gt;, and &lt;code&gt;share-card.png&lt;/code&gt; to an orphan &lt;code&gt;output&lt;/code&gt; branch automatically so daily updates never bloat your &lt;code&gt;main&lt;/code&gt; commit history):&lt;/p&gt;
&lt;div class="highlight highlight-source-yaml notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-ent"&gt;name&lt;/span&gt;: &lt;span class="pl-s"&gt;Update Tulkun&lt;/span&gt;
&lt;span class="pl-ent"&gt;on&lt;/span&gt;:
  &lt;span class="pl-ent"&gt;schedule&lt;/span&gt;: &lt;span class="pl-s"&gt;[{ cron: "0 0 * *&lt;/span&gt;&lt;/pre&gt;…
&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/PramudithaN/git-tulkun" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F86py5yxxsltxn4x0huvt.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F86py5yxxsltxn4x0huvt.gif" alt="A fully grown Tulkun swimming" width="640" height="280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;That is my own whale, generated live from my own contributions.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Reads your current-year contributions and top repository languages from the GitHub GraphQL API&lt;/li&gt;
&lt;li&gt;Maps your contribution count to one of five life stages, from Baby Calf to Adult Male Bull&lt;/li&gt;
&lt;li&gt;Resets every January 1, so everyone starts the year as a calf&lt;/li&gt;
&lt;li&gt;Tints the whale with the official GitHub colors of your top three languages&lt;/li&gt;
&lt;li&gt;Renders a 48-frame, 15 FPS loop that updates daily through a GitHub Action&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The five stages
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;th&gt;Contributions this year&lt;/th&gt;
&lt;th&gt;What changes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1. Baby Calf&lt;/td&gt;
&lt;td&gt;[range]&lt;/td&gt;
&lt;td&gt;Short, stubby body, blunt snout, cream belly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2. Yearling Calf&lt;/td&gt;
&lt;td&gt;[range]&lt;/td&gt;
&lt;td&gt;Longer, slimmer body; kuru whips sprout&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3. Adolescent&lt;/td&gt;
&lt;td&gt;[range]&lt;/td&gt;
&lt;td&gt;Sensor crest rises; six flippers elongate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4. Adult Female&lt;/td&gt;
&lt;td&gt;[range]&lt;/td&gt;
&lt;td&gt;Dark carapace armor, sloped crest, serrated jaw&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5. Adult Male Bull&lt;/td&gt;
&lt;td&gt;[range]&lt;/td&gt;
&lt;td&gt;Maximum length, hooked hammer-crest, battle scars&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu02lr4zli89bl9e9nu3x.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu02lr4zli89bl9e9nu3x.gif" alt="Stage 1: Baby Calf" width="640" height="280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxcig4q6gn2wrljxj63ni.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxcig4q6gn2wrljxj63ni.gif" alt="Stage 5: Adult Male Bull" width="640" height="280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How the renderer works
&lt;/h2&gt;

&lt;p&gt;There are no spritesheets. Every frame is computed from a handful of parameters, so one code path draws all five stages and three camera angles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Shape.&lt;/strong&gt; The body is a spline, with signed distance fields (SDFs) for the head crest, flippers, whips, jaw stripes and flukes. A stage change just moves parameters, such as the spine growing from 64 px to 186 px.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Lighting.&lt;/strong&gt; For each pixel the renderer builds a surface normal from the body's cylindrical profile and lights it with a fixed directional light:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;n = (nx, ny, sqrt(1 - nx^2 - ny^2))
light = [-0.30, -0.62, 0.72]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Pixel-art look.&lt;/strong&gt; Brightness is quantized to a 4-shade ramp with a 4x4 ordered Bayer dither matrix, which gives the classic crosshatch of old pixel art. A 1 px dark outline (#0f121a) keeps the whale readable on both light and dark GitHub themes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Language colors.&lt;/strong&gt; Each top language's hex color becomes a 4-shade ramp (shadow, mid dark, mid light, highlight). Your top language paints the belly, skin and carapace, the second tints the sensor crest, and the third lights its tip. A TypeScript-heavy profile gets a different whale than a Rust-heavy one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Swimming.&lt;/strong&gt; The loop is 48 frames at 15 FPS (3.2 seconds). A traveling S-wave bends the torso and drives the tail, while the flippers lag behind in phase:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dy(u, t) = A_bob * sin(w*t) * (1 - u)^1.3 + A_tail * sin(2.35*pi*u - w*t) * u^0.92
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here u runs along the body from head (0) to tail (1).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. No jitter.&lt;/strong&gt; The camera box is precomputed across the whole stroke, so the whale never clips or bounces the frame. Output is upscaled 2x with nearest-neighbor into a 640x280 GIF, plus a PNG and an HTML preview.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Rust and a GitHub Action
&lt;/h2&gt;

&lt;p&gt;The whole thing is one Rust binary with five dependencies (&lt;code&gt;chrono&lt;/code&gt;, &lt;code&gt;gif&lt;/code&gt;, &lt;code&gt;png&lt;/code&gt;, &lt;code&gt;serde&lt;/code&gt;, &lt;code&gt;serde_json&lt;/code&gt;). The composite GitHub Action downloads a prebuilt binary when one exists and compiles from source otherwise. Data collection shells out to &lt;code&gt;gh api graphql&lt;/code&gt;, so the built-in workflow token is enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add your own whale
&lt;/h2&gt;

&lt;p&gt;Create &lt;code&gt;.github/workflows/tulkun.yml&lt;/code&gt; in your profile repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Update Tulkun&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;17&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;tulkun&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PramudithaN/git-tulkun@[latest tag, e.g. v1]&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;view&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;side&lt;/span&gt;
          &lt;span class="na"&gt;canon&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;git config user.name "github-actions[bot]"&lt;/span&gt;
          &lt;span class="s"&gt;git config user.email "41898282+github-actions[bot]@users.noreply.github.com"&lt;/span&gt;
          &lt;span class="s"&gt;git add tulkun/tulkun.gif tulkun/tulkun.png&lt;/span&gt;
          &lt;span class="s"&gt;git diff --cached --quiet || git commit -m "chore: update Tulkun"&lt;/span&gt;
          &lt;span class="s"&gt;git push&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;[If you shipped a commit input or an output-branch option, show the shorter workflow here instead.]&lt;/p&gt;

&lt;p&gt;Then embed it in your README:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"tulkun/tulkun.gif"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"640"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"My Tulkun"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Want a preview without any setup? Run the demo mode locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo run &lt;span class="nt"&gt;--release&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; demo &lt;span class="nt"&gt;--commits&lt;/span&gt; 145 &lt;span class="nt"&gt;--view&lt;/span&gt; dive &lt;span class="nt"&gt;--output-dir&lt;/span&gt; tulkun-demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;[Add 2 or 3 honest lessons here, for example: keeping five stages visually consistent, getting a seamless loop, or the repo bloat from daily GIF commits and how you fixed it. Real stories earn more comments than feature lists.]&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would love feedback on
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Should the stages scale differently, or use a rolling 365 days instead of a calendar year?&lt;/li&gt;
&lt;li&gt;What else should shape the whale: pull requests, reviews, streaks?&lt;/li&gt;
&lt;li&gt;Which camera angle do you like most?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Drop your whale in the comments, I would love to see them.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/PramudithaN/git-tulkun" rel="noopener noreferrer"&gt;https://github.com/PramudithaN/git-tulkun&lt;/a&gt; (if you like it, a star helps others find it).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;GitTulkun is an unofficial fan project. Tulkun and Avatar belong to their respective owners, and this project is not affiliated with or endorsed by them.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>rust</category>
      <category>github</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
