Skip to content
Buse Turgay Sönmez

Design Engineer, Belgrade

Shipping Record

Every developer portfolio has a green grid, and it hides the one thing worth knowing. A day with three commits and a day with 182 look the same on it. So I counted my own git history instead: 2170 commits over 86 of 105 days, drawn in ink, with the real number in every cell.

  • 2026
  • Data
  • CSS
  • Pointer

In short

Window
2026-07-07 to 2026-10-13, 15 weeks
Commits
2170, counted once each
Days worked
86 of 105
Busiest day
182 commits
Repositories
At least 73
When a scan finds less
The day keeps its earlier count
What leaves the machine
Day counts. No repository names, no commit messages, no branches
What it measures
Days something shipped, not lines typed. Most of this code was written by agents

What a commit here means

These commits are in my name. Most were written by AI agents working to rules and tests I set. The grid is not a count of lines I typed, and it was never going to be. It records days when something was decided, reviewed and shipped. That part of the work is mine.

Why not GitHub's

I checked mine in September, before building this. GitHub showed 72 active days against the 65 I could count then, so on presence it was roughly right. What it cannot show is volume: its darkest square means four or more, so a day of 182 commits and a day of 4 are the same square. My repositories are also private, which leaves the profile looking empty, and opening them would publish repository names that carry client names.

Counting honestly

A scan of a working disk overcounts in two ways, and I ran into both. Other people's commits sit in the same repositories, so the scan matches on my own addresses only: without that filter the total came to 3781, most of it colleagues' work. The same history also exists more than once on disk, as four checked out branches of one client repository and as the v1 and v2 of a site, so every commit is keyed by its hash and counted once. That took 2255 down to 1961, the first total this page showed.

A scan can also undercount, because it only sees what is on the disk that day. In October the same folder held 77 repositories where it had held 82, and the days up to 19 September added up to 531 fewer commits than the record already had. Those commits had still shipped. So a day keeps the highest count it has been given, and a new scan can only raise a day or add one. The repository figure is the most found in a single scan, for the same reason.

The window starts on 7 July 2026 because that is when most of these repositories were initialised, not when the work started. Some of what they hold was built before and squashed into a first commit. The grid is a record of 15 weeks, and it does not claim to be a career.

What the shading means

The five steps are quartiles of my own active days, not fixed numbers, so the scale describes this record rather than an average developer's. Turn the counts on and the grid stops being a mood and starts being data. The busiest day carries the one accent.

Controls

Speed
1.00×

Origin

The grid is GitHub's idea. The data is not theirs: it is scanned from the repositories on my own machine by scripts/katki.mjs, which writes out day counts and nothing else.

Source

piece.tsx97 lines
import type { DomPieceProps } from "@/entries/types";
import { cn } from "@/lib/utils";
import { shipping as data } from "./data";
import styles from "./shipping-record.module.css";

/**
 * My own git history as a contribution grid: weeks across, weekdays down.
 *
 * The data is counts and nothing else (scripts/katki.mjs), so this file cannot leak a repository
 * name even by accident. Everything below is derived from those counts at module level: the numbers
 * are fixed at build time, and render stays a pure map over them.
 */
const { counts, totals, from } = data;
const WEEKS = counts.length / 7;

/**
 * Five steps, cut at the quartiles of the days that have commits.
 *
 * A linear scale to the busiest day would sink a typical day of 30 into the same near-empty shade
 * as a day of 3, because one day of 182 sets the top. Quartiles describe this record instead: each
 * step holds a quarter of the days worked.
 */
const worked = counts.filter((n) => n > 0).sort((a, b) => a - b);
const CUTS = [0.25, 0.5, 0.75].map((q) => worked[Math.floor(worked.length * q)]);
const WEIGHTS = [0.22, 0.42, 0.66, 1];

function weight(count: number): number {
  if (count === 0) return 0;
  return WEIGHTS[CUTS.filter((cut) => count > cut).length];
}

/** The Monday each week starts on, for the month labels under the grid. */
const MONTHS = Array.from({ length: WEEKS }, (_, week) => {
  const day = new Date(`${from}T00:00:00Z`);
  day.setUTCDate(day.getUTCDate() + week * 7);
  return day.toLocaleString("en-GB", { month: "short", timeZone: "UTC" });
});
/** One label per month, over the first week that falls in it. */
const AXIS = MONTHS.map((month, week) => (week === 0 || month !== MONTHS[week - 1] ? month : ""));

export default function ShippingRecord({ values, paused }: DomPieceProps) {
  const speed = typeof values.speed === "number" && values.speed > 0 ? values.speed : 1;
  const numbers = values.numbers !== false;
  const still = paused || (typeof values.speed === "number" && values.speed <= 0);
  // One walk across the grid takes a second a week at speed 1, however many weeks there are.
  const duration = `${WEEKS / speed}s`;

  return (
    <div aria-hidden="true" className={cn(styles.root, still && styles.paused)}>
      <div className={styles.frame}>
        <div className={styles.sheet} style={{ "--weeks": WEEKS } as React.CSSProperties}>
          <div className={styles.grid}>
            {Array.from({ length: WEEKS }, (_, week) => (
              <div key={week} className={styles.week}>
                {counts.slice(week * 7, week * 7 + 7).map((count, day) => (
                  <div
                    key={day}
                    className={cn(styles.cell, !numbers && styles.hidden)}
                    data-empty={count === 0 ? "" : undefined}
                    data-peak={count === totals.busiest ? "" : undefined}
                    style={{ "--w": weight(count) } as React.CSSProperties}
                  >
                    {count || ""}
                  </div>
                ))}
                {/* Walks the weeks left to right. Invisible at rest, so the frozen grid is the data. */}
                <div className={styles.sweep} style={{ "--dur": duration, "--delay": `${week / speed}s` } as React.CSSProperties} />
              </div>
            ))}
          </div>
          <div className={styles.axis}>
            {AXIS.map((month, week) => (
              <span key={week}>{month}</span>
            ))}
          </div>
          <p className={styles.totals}>
            <span>
              <b>{totals.commits}</b> commits
            </span>
            <span>
              <b>
                {totals.activeDays} of {totals.windowDays}
              </b>{" "}
              days
            </span>
            <span>
              <b>{totals.repos}+</b> repositories
            </span>
            <span>
              busiest <b>{totals.busiest}</b>
            </span>
          </p>
        </div>
      </div>
    </div>
  );
}
shipping-record.module.css156 lines
/*
  The shipping record: weeks as columns, weekdays as rows. The number of weeks comes from the data
  (--weeks, set inline by the piece), so the drawing keeps fitting as the record grows.

  The grid is always complete. Cells carry their real weight from the first frame, so a visitor who
  has motion turned off, or who pauses the stage, sees the whole record and loses nothing. The only
  thing that moves is a highlight that walks the columns, and a highlight that is not there takes
  nothing away.
*/
.root {
  container-type: size;
  width: 100%;
  height: 100%;
}

.frame {
  box-sizing: border-box;
  display: grid;
  width: 100%;
  height: 100%;
  padding: 16px 16px 76px;
  place-items: center;
  font-family: var(--font-mono);
}

.sheet {
  display: flex;
  flex-direction: column;
  /*
    One unit drives the whole drawing, so the grid scales as one thing. It is bound by both axes:
    the weeks across the width, seven rows plus the labels down the height. The tall stage is
    width bound and the phone stage is height bound, and min() picks the right one without a query.

    Across, the columns and the gaps between them (0.13 of a cell each) share 81.2% of the stage,
    the width eleven weeks took at 6.6cqw a cell, which is what this was drawn at first.
  */
  --cell: clamp(10px, min(calc(81.2cqw / (var(--weeks) * 1.13 - 0.13)), 6.2cqh), 56px);
  --gap: calc(var(--cell) * 0.13);
  gap: calc(var(--cell) * 0.5);
}

.grid {
  display: grid;
  grid-auto-flow: column;
  grid-template-rows: repeat(7, var(--cell));
  gap: var(--gap);
}

/* A week. Its own stacking context, so the highlight can sit over the cells it owns. */
.week {
  position: relative;
  display: grid;
  grid-row: 1 / -1;
  grid-template-rows: subgrid;
  gap: var(--gap);
}

.cell {
  border-radius: calc(var(--cell) * 0.22);
  /* --w is the day's weight, 0 to 1, set inline from the real count. */
  background: color-mix(in oklab, var(--ink) calc(var(--w) * 100%), var(--surface));
  display: grid;
  place-items: center;
  font-size: calc(var(--cell) * 0.3);
  line-height: 1;
  font-variant-numeric: tabular-nums;
  /* Ink cells need light numerals, empty ones need dark: the flip follows the same weight. */
  color: color-mix(in oklab, var(--ground) calc(var(--w) * 100%), var(--faint));
}

/* An empty day is a dent in the surface, not a hole in the page. */
.cell[data-empty] {
  background: color-mix(in oklab, var(--ink) 7%, var(--surface));
  color: transparent;
}

/* The one accent in the piece: the busiest day. */
.cell[data-peak] {
  background: var(--accent);
  color: var(--ground);
}

.hidden {
  color: transparent;
}

/*
  The highlight. Base state is invisible, so the frozen grid is exactly the data. It brightens one
  week at a time, left to right, on a delay set inline per week.
*/
.sweep {
  position: absolute;
  inset: calc(var(--gap) * -1);
  border-radius: calc(var(--cell) * 0.3);
  background: var(--ink);
  opacity: 0;
  animation: sr-sweep var(--dur) linear infinite;
  animation-delay: var(--delay);
  pointer-events: none;
}

@keyframes sr-sweep {
  0%,
  12%,
  100% {
    opacity: 0;
  }
  4% {
    opacity: 0.09;
  }
}

/* The same columns as the grid, so a month label sits over the week it starts in. */
.axis {
  display: grid;
  grid-template-columns: repeat(var(--weeks), var(--cell));
  gap: var(--gap);
  font-size: calc(var(--cell) * 0.3);
  color: var(--faint);
}

.totals {
  display: flex;
  flex-wrap: wrap;
  gap: calc(var(--cell) * 0.55);
  font-size: calc(var(--cell) * 0.32);
  color: var(--faint);
}

.totals b {
  font-weight: 400;
  color: var(--ink);
}

/* The stage's pause button. The grid keeps its data; only the walk stops. */
.paused .sweep {
  animation-play-state: paused;
}

/* A phone stage is short and wide: the counts no longer fit in a cell. */
@container (max-height: 559px) {
  /*
    The busiest day keeps its number, because `.cell[data-peak]` outranks this rule. That is wanted:
    if only one number survives on a small screen it should be the one the piece is arguing about.
  */
  .cell {
    color: transparent;
  }

  /* A month label is wider than a cell here, so it would collide with the next one. */
  .axis {
    grid-template-columns: repeat(var(--weeks), var(--cell));
    overflow: visible;
    white-space: nowrap;
  }
}
--ground
oklch(0.985 0.003 85)
--surface
oklch(0.958 0.005 85)
--raised
oklch(1 0 0)
--ink
oklch(0.2 0.006 85)
--muted
oklch(0.43 0.008 85)
--faint
oklch(0.5 0.008 85)
--line
oklch(0.9 0.004 85)
--line-strong
oklch(0.6 0.006 85)
--accent
oklch(0.66 0.22 38)
--accent-ink
oklch(0.52 0.19 38)
--stage
oklch(0.152 0.004 85)
--radius-stage
36px
--radius-stage-sm
28px
--radius-media
24px
--radius-panel
16px
--radius-field
10px
--ease-out-expo
cubic-bezier(0.16, 1, 0.3, 1)

This week · W39One WebGL context, three leaksGeometry · 2026