🎨 Birthday Color Cafein
Behind the scenes 11 min read

Inside the BirthdayColor Algorithm: How Every Date Becomes a Color

A complete walkthrough of the JavaScript that decides which of 64 traditional Chinese colors gets attached to a given birthday — the actual code from the site, worked examples for a few real dates, and the reasoning behind each line.

Run the algorithm on your own birthday
Pick a date and let the function do its thing — same math we'll be reading through below.

The 30-second version

Two steps, both pure functions:

  1. Figure out which season the date falls in. Four buckets, decided by Chinese solar terms.
  2. Run the date through a small hash and take the result mod 16. That's your index into a 16-color pool for that season.

Total palette: 64 colors (16 × 4 seasons). Same date in, same color out. No randomness, no year argument, no server call. The whole thing lives in palette.js on the site.

Step 1 — From a date to a season

The site doesn't use Western quarter-year seasons. The boundaries follow the four "Beginnings" of the Chinese solar terms — 立春, 立夏, 立秋, 立冬 — which fall on roughly Feb 4, May 5, Aug 7, and Nov 7. In code, that's compressed into a small integer trick:

window.seasonOf = function(m, d){
  const mmdd = m*100 + d;
  if (mmdd >= 204  && mmdd <= 504)  return "spring";
  if (mmdd >= 505  && mmdd <= 806)  return "summer";
  if (mmdd >= 807  && mmdd <= 1106) return "autumn";
  return "winter";
};

Reading that line by line:

mmdd rangeSeasonSolar term boundary
204 → 504Spring 春立春 → 立夏 − 1
505 → 806Summer 夏立夏 → 立秋 − 1
807 → 1106Autumn 秋立秋 → 立冬 − 1
1107 → 1231 & 0101 → 0203Winter 冬立冬 → 立春 − 1

The cutoffs are fixed across years. Real solar terms drift by roughly a day depending on the astronomical calendar, but a fixed table keeps the function pure: no year argument required, no leap-year edge case, no API call. Same date, same season, every year.

Step 2 — Picking a color inside the season

Once the season is known, the site has a fixed pool of 16 colors for it. The full code is just six lines:

window.colorOf = function(m, d){
  const s = seasonOf(m, d);
  const pool = PALETTE[s];
  const hash = (m*31 + d*7 + m*d + 3*d*d) >>> 0;
  return { season: s, ...pool[hash % pool.length] };
};

Everything interesting happens in the hash line:

const hash = (m*31 + d*7 + m*d + 3*d*d) >>> 0;

Four small terms, each doing a specific job:

The >>> 0 at the end is a JavaScript-specific quirk — it coerces the result to an unsigned 32-bit integer. The arithmetic above only ever produces small positive numbers, so this is mostly defensive: it guarantees the modulo on the next line stays non-negative under any input.

Then:

hash % pool.length    // pool.length is 16, always

…hands you a number between 0 and 15. That's your index into the season's color array. Done.

Worked examples

Let's run a few real birthdays through the function on paper.

March 8 — spring

So March 8 picks up PALETTE.spring[13].

July 4 — summer

December 25 — winter

February 29 — spring (leap-day birthday)

The site treats February as 29 days year-round, so leap-day babies always land on the same color.

Want to verify it yourself? Open the home page, open your browser's DevTools console, and run colorOf(3, 8) for any month/day. The function is exposed on window. There's no server call — the math is happening in your browser, on your CPU, in well under a millisecond.

The companion palette: a second use of the same hash

Each birthday color also comes with a 3-color companion palette — the small "colors that go with yours" set on the result page. Same season, same hash, but offset:

window.harmony = function(m, d){
  const s = seasonOf(m, d);
  const pool = PALETTE[s];
  const hash = (m*31 + d*7 + m*d + 3*d*d) >>> 0;
  const idx = hash % pool.length;
  const offsets = [3, 7, 11];
  return offsets.map(off =>
    ({ season: s, ...pool[(idx + off) % pool.length] })
  );
};

The offsets [3, 7, 11] are intentionally uneven inside a 16-slot pool:

The four-color group (your color plus three companions) has an irregular, slightly off-balance rhythm — which is what designers usually want when they pull together a moodboard, instead of perfectly evenly-spaced hues that all cancel each other out.

Why a hash function and not a hand-curated table?

The original Japanese cafein.jp site does it the other way: a person sat down and assigned a specific color to each of 366 days by hand. There's a real charm to that approach — it's a personal artwork.

The hash approach trades editorial intimacy for three different qualities:

  1. Reproducibility. The function lives in palette.js, runs in your browser, and produces the same result on any day in any year. There's no hidden lookup table.
  2. Cultural depth per color. With only 64 colors in play, every one of them gets a fully-developed entry — a Chinese name, a poem citation, a personality reading, a hex value calibrated against historical samples. A 366-color hand-table would dilute that depth.
  3. A built-in "we" signal. Roughly five or six birthdays share each color. Some people find that disappointing at first — until they realize it's what makes phrases like "we're both Vermilion-East" possible.

How "fair" is the distribution?

A reasonable question: with this hash, are the 16 colors in each season equally likely? The honest answer is "close but not perfectly." Each season has roughly 92 days mapping into 16 buckets, so a perfectly uniform distribution would put about 5.75 days on each color. The actual counts come out close to that — within a small range — but not exactly equal.

I tested several variants of the hash function and picked one whose distribution looked close to uniform and whose calendar-neighbors usually landed on different colors. Those two goals pull against each other: maximizing one tends to weaken the other. The current function is a compromise that does both reasonably well.

Run the math on your birthday
Pick a date and watch the algorithm pick your color (in roughly half a millisecond).

Reading the source

If you'd like to inspect the live code, open palette.js. The relevant section is short — the four functions above (seasonOf, colorOf, harmony, plus a small daysInMonth helper) sit together in the file under the comment /* ---------- 核心函数 ---------- */. Everything else in palette.js is data: the 64 colors and their metadata.

If you want to argue with a design choice, fork it. The whole site is small enough to read in 30 minutes, and the math is small enough to redo in a notebook.

Where to go next