---
title: Seedmoji
canonical: "https://presque.cool/projects/seedmoji/"
description: "Un avatar ASCII unique pour chaque mot, chaque email, chaque pseudo. Même entrée, même visage — toujours."
---

<div data-lang="fr">

## Le pitch

Seedmoji transforme n'importe quelle chaîne de caractère — email, pseudo, UUID — en un avatar ASCII unique et déterministe. Même entrée, même visage. Toujours. Zéro API, zéro stockage, 100% client.

**Essayer** : [presque.cool/seedmoji/](https://presque.cool/seedmoji/)

<div class="quick-overview">
  <div class="overview-item">
    <span class="overview-label">Durée</span>
    <span class="overview-value">~1 jour</span>
  </div>
  <div class="overview-item">
    <span class="overview-label">Période</span>
    <span class="overview-value">Fév 2026</span>
  </div>
  <div class="overview-item">
    <span class="overview-label">Stack</span>
    <div class="tech-tags">
      <span class="tech-tag">React</span>
      <span class="tech-tag">TypeScript</span>
      <span class="tech-tag">PWA</span>
    </div>
  </div>
  <div class="overview-item">
    <span class="overview-label">Statut</span>
    <span class="overview-value accent">✓ En production</span>
  </div>
</div>

---

## Pourquoi ce projet

Il existe de nombreux générateurs d'avatars par seed — Identicon, MonsterID, Wavatars, Retro (by Gravatar), RoboHash... Mais aucun n'utilise des caractères ASCII pour composer des visages. En découvrant [Facehash](https://facehash.dev) par cossistant, j'ai eu envie de faire ma propre version, en poussant plus loin la variabilité : plus d'éléments, plus de combinaisons, plus d'expressivité.

Le résultat : des petits visages en glyphs qui sont souvent drôles, parfois étranges, toujours uniques.

Un jour de développement, du plaisir créatif pur.

---

## Le dictionnaire de glyphs

Le cœur de Seedmoji, c'est son dictionnaire de caractères. J'ai écumé des sites de référence Unicode et des tables de glyphs pour constituer un catalogue trié sur le volet :

- **109 paires d'yeux** — du classique ASCII (`o,o` `^,^` `x,x`) aux formes géométriques (`◉,◉` `★,☆`) en passant par les kaomoji (`ಠ,ಠ` `ꖘ,ꖘ`) et les symboles monétaires (`€,€` `¥,¥`). Symétriques et asymétriques.
- **41 bouches** — lignes horizontales (`─` `━`), courbes kaomoji (`ω` `Д` `∀`), formes expressives (`▽` `益`).
- **15 nez** — affichés uniquement avec les bouches horizontales pour éviter les chevauchements visuels.
- **32 cadres** — des crochets classiques (`[ ]`) aux cadres CJK (`「  」` `【  】`) en passant par les décoratifs (`╭ ╮` `╔ ╗`).
- **16 couleurs de fond** — une palette vibrante récupérée sur un site d'inspiration graphique.

Le tout donne environ **34 millions de combinaisons uniques** (sans compter les 3 formes de découpe). Chaque seed produit un visage différent et reproductible.

---

## Le cœur technique : un hash déterministe

L'algorithme repose sur un hash simple, variante du `String.hashCode()` de Java (style djb2) :

```text
seed → hash 32 bits → indices déterministes → composants du visage
```

**Pourquoi pas SHA-256 ou MD5 ?**

1. **Pas de sécurité requise** — on génère des avatars, pas des tokens d'authentification
2. **Performance** — djb2 fait ~10 opérations par caractère vs ~1000+ pour SHA-256. L'avatar se recalcule à chaque frappe clavier, la différence est perceptible
3. **Synchronicité** — `crypto.subtle.digest()` est asynchrone, ça compliquerait inutilement le code
4. **Bundle size** — le hash fait 8 lignes. Zéro dépendance, zéro polyfill

Un détail de design intéressant : le **premier caractère du seed détermine la bouche**. Taper `a` produit un avatar avec `a` comme bouche. Cela crée un lien visuel immédiat entre l'entrée et le résultat.

Le contraste texte/fond est calculé automatiquement via la luminance perçue (formule 0.299R + 0.587G + 0.114B) : fond clair → texte noir, fond foncé → texte blanc.

---

## L'API d'embedding

Seedmoji inclut un endpoint `/api/` qui génère du SVG brut via query params :

```text
/seedmoji/api/?seed=tristan&shape=squircle&size=128
```

Pas de serveur — c'est la même app React qui détecte le pathname et rend un SVG pur au lieu de l'interface. Le SVG utilise des `clipPath` pour les formes (carré, squircle, rond), là où le composant React utilise `borderRadius` en CSS.

Pas encore utilisé en production, mais prêt pour une intégration dans d'autres projets.

---

## Stack technique

<ul class="tech-stack">
  <li><strong>React</strong> + <strong>TypeScript</strong> — Composants fonctionnels, typage strict</li>
  <li><strong>Vite</strong> + <strong>SWC</strong> — Build rapide</li>
  <li><strong>Tailwind CSS</strong> — Couleurs OKLch, dark mode</li>
  <li><strong>Zustand</strong> — State management, persistance localStorage</li>
  <li><strong>PWA</strong> — installable, offline, Workbox</li>
  <li><strong>i18n</strong> maison — Français/anglais, détection automatique du navigateur</li>
  <li><strong>Export</strong> SVG + PNG (512, 720, 1024px) via Canvas API</li>
<li><strong>Compression</strong> — Brotli + Gzip sur les assets</li>
<li><strong>CSP</strong> — Content-Security-Policy avec hashes auto-générés</li>
</ul>

---

## Évolutions

| Date     | Version | Description                                        |
| -------- | ------- | -------------------------------------------------- |
| Fév 2026 | 1.0     | Version initiale — Dictionnaire, hash, export, PWA |

---

## Bilan

**Durée** : 1 jour (février 2026).

**Ce que j'ai appris** :

- Qu'un dictionnaire de caractères Unicode soigneusement curé peut produire des résultats étonnamment expressifs — la contrainte ASCII force la créativité
- Qu'un hash de 8 lignes suffit quand on n'a pas besoin de sécurité cryptographique

**Ce que je referais pareil** :

- Partir d'un constat simple ("aucun générateur ASCII par seed n'existe") et livrer en un jour
- Le premier caractère du seed comme bouche — c'est le détail qui rend l'avatar personnel

**Ce que je changerais** :

- Explorer les animations SVG pour donner vie aux avatars — un clin d'œil, une bouche qui bouge
- Intégrer l'API dans d'autres projets pour tester son utilité réelle

</div>

<div data-lang="en">

## The pitch

Seedmoji turns any string — email, username, UUID — into a unique, deterministic ASCII avatar. Same input, same face. Always. Zero APIs, zero storage, 100% client-side.

**Try it**: [presque.cool/seedmoji/](https://presque.cool/seedmoji/)

<div class="quick-overview">
  <div class="overview-item">
    <span class="overview-label">Duration</span>
    <span class="overview-value">~1 day</span>
  </div>
  <div class="overview-item">
    <span class="overview-label">Period</span>
    <span class="overview-value">Feb 2026</span>
  </div>
  <div class="overview-item">
    <span class="overview-label">Stack</span>
    <div class="tech-tags">
      <span class="tech-tag">React</span>
      <span class="tech-tag">TypeScript</span>
      <span class="tech-tag">PWA</span>
    </div>
  </div>
  <div class="overview-item">
    <span class="overview-label">Status</span>
    <span class="overview-value accent">✓ In production</span>
  </div>
</div>

---

## Why this project

There are many seed-based avatar generators — Identicon, MonsterID, Wavatars, Retro (by Gravatar), RoboHash... But none use ASCII characters to compose faces. After discovering [Facehash](https://facehash.dev) by cossistant, I wanted to build my own version with more variability: more elements, more combinations, more expressiveness.

The result: little glyph faces that are often funny, sometimes weird, always unique.

One day of development, pure creative fun.

---

## The glyph dictionary

The heart of Seedmoji is its character dictionary. I scoured Unicode reference sites and glyph tables to build a hand-picked catalogue:

- **109 eye pairs** — from classic ASCII (`o,o` `^,^` `x,x`) to geometric shapes (`◉,◉` `★,☆`), kaomoji (`ಠ,ಠ` `ꖘ,ꖘ`), and currency symbols (`€,€` `¥,¥`). Both symmetric and asymmetric.
- **41 mouths** — horizontal lines (`─` `━`), kaomoji curves (`ω` `Д` `∀`), expressive shapes (`▽` `益`).
- **15 noses** — displayed only with horizontal mouths to avoid visual overlap.
- **32 frames** — from classic brackets (`[ ]`) to CJK frames (`「  」` `【  】`) and decorative ones (`╭ ╮` `╔ ╗`).
- **16 background colors** — a vibrant palette sourced from a graphic inspiration site.

This yields roughly **34 million unique combinations** (not counting the 3 clipping shapes). Each seed produces a different, reproducible face.

---

## The technical core: a deterministic hash

The algorithm relies on a simple hash, a variant of Java's `String.hashCode()` (djb2-style):

```text
seed → 32-bit hash → deterministic indices → face components
```

**Why not SHA-256 or MD5?**

1. **No security needed** — we're generating avatars, not authentication tokens
2. **Performance** — djb2 runs ~10 operations per character vs ~1000+ for SHA-256. The avatar recalculates on every keystroke, the difference is noticeable
3. **Synchronicity** — `crypto.subtle.digest()` is async. That would needlessly complicate the code
4. **Bundle size** — the hash is 8 lines. Zero dependencies, zero polyfills

An interesting design detail: the **first character of the seed determines the mouth**. Typing `a` produces an avatar with `a` as its mouth. This creates an immediate visual link between input and output.

Text/background contrast is calculated automatically using perceived luminance (formula 0.299R + 0.587G + 0.114B): light background → black text, dark background → white text.

---

## The embedding API

Seedmoji includes an `/api/` endpoint that generates raw SVG via query params:

```text
/seedmoji/api/?seed=tristan&shape=squircle&size=128
```

No server — the same React app detects the pathname and renders pure SVG instead of the interface. The SVG uses `clipPath` for shapes (square, squircle, round), while the React component uses CSS `borderRadius`.

Not yet used in production, but ready for integration into other projects.

---

## Tech stack

<ul class="tech-stack">
  <li><strong>React</strong> + <strong>TypeScript</strong> — Functional components, strict typing</li>
  <li><strong>Vite</strong> + <strong>SWC</strong> — Fast builds</li>
  <li><strong>Tailwind CSS</strong> — OKLch colors, dark mode</li>
  <li><strong>Zustand</strong> — State management, localStorage persistence</li>
  <li><strong>PWA</strong> — installable, offline, Workbox</li>
  <li><strong>i18n</strong> custom — French/English, automatic browser detection</li>
  <li><strong>Export</strong> SVG + PNG (512, 720, 1024px) via Canvas API</li>
<li><strong>Compression</strong> — Brotli + Gzip on assets</li>
<li><strong>CSP</strong> — Content-Security-Policy with auto-generated hashes</li>
</ul>

---

## Timeline

| Date     | Version | Description                                     |
| -------- | ------- | ----------------------------------------------- |
| Feb 2026 | 1.0     | Initial version — Dictionary, hash, export, PWA |

---

## Takeaways

**Duration**: 1 day (February 2026).

**What I learned**:

- That a carefully curated Unicode character dictionary can produce surprisingly expressive results — the ASCII constraint forces creativity
- That an 8-line hash function is enough when you don't need cryptographic security

**What I'd do the same**:

- Start from a simple observation ("no ASCII seed-based generator exists") and ship in one day
- The first character of the seed as the mouth — it's the detail that makes the avatar feel personal

**What I'd change**:

- Explore SVG animations to bring the avatars to life — a wink, a moving mouth
- Integrate the API into other projects to test its real-world usefulness

</div>
