Skip to content
quarry Artifact [833e68dcad]
Login

Artifact [833e68dcad]

Artifact 833e68dcada8284a44a54b5a15f8e1d6b427e6f067c738995b9b65e2178bd483:


// Package idn is quarry's identity mark: one hue and one small grid per
// person, derived from the name alone.
//
// It lives on its own because THREE packages need the same answer and a
// person must look the same in all of them. web draws the mark beside a
// byline, api hands it to the markup resolver so an @mention carries it,
// and both hash the name to the same hue. When userHue was written twice —
// once in web, once in api — nothing checked that the two agreed; they did,
// by luck, and a one-character edit in either would have given the same
// person two colours on the same page.
//
// Colours are OKLCH: its hue wheel is perceptually even and every person
// sits at the same lightness, so no hue reads louder than another. Hue and
// chroma are identity and never move; lightness is the context axis, set by
// the theme token --idn-l and nudged by callers for shade steps.
package idn

import (
	"fmt"
	"hash/fnv"
	"strings"
	"sync"
)

func sum(name string) uint64 {
	h := fnv.New64a()
	h.Write([]byte(name))
	return h.Sum64()
}

// Hue is the person's colour, 0-359. One hue per person keeps the mark
// recognisable at a glance: the colour IS the identity as much as the
// pattern is.
func Hue(name string) int {
	return int(sum(name) % 360)
}

// Chroma is the person's chroma band: 0 vivid, 1 soft. A second fixed
// identity dimension so two neighbouring hues still read as two people.
func Chroma(name string) int {
	return int(sum(name) >> 40 & 1)
}

// Fill is the person's colour at the given lightness expression, e.g.
// "var(--idn-l)" or "calc(var(--idn-l) + 8%)". Chroma comes from the theme
// token for the person's band so themes can retune it in one place.
func Fill(name, lightness string) string {
	return fmt.Sprintf("oklch(%s var(--idn-c%d) %d)", lightness, Chroma(name), Hue(name))
}

// Tone is the custom-property pair a stylesheet needs to paint a person:
// `--uh: H; --uc: var(--idn-cN)`, for a style attribute.
func Tone(name string) string {
	return fmt.Sprintf("--uh: %d; --uc: var(--idn-c%d)", Hue(name), Chroma(name))
}

var memo sync.Map // name -> string

// Mark is the person's identicon as an inline SVG: a 5x3 half-grid
// mirrored into a 5x5 face, in two lightness steps of their hue, on a wash
// of the same hue. Fills go in style= because a presentation attribute
// cannot hold var().
//
// Memoised because a busy page draws the same handful of people dozens of
// times, and the answer never changes.
func Mark(name string) string {
	if v, ok := memo.Load(name); ok {
		return v.(string)
	}
	v := sum(name)
	cells := v >> 9   // 15 bits: which of the 5x3 half-grid cells are on
	shades := v >> 24 // 15 more: which on-cells take the deeper shade
	if cells&0x7FFF == 0 {
		cells = 0x1084 // a degenerate hash still gets a face: centre column
	}
	lit := Fill(name, "var(--idn-l)")
	deep := Fill(name, "calc(var(--idn-l) - 12%)")
	var b strings.Builder
	fmt.Fprintf(&b, `<svg class="idn" viewBox="-0.6 -0.6 6.2 6.2" aria-hidden="true">`)
	fmt.Fprintf(&b, `<rect x="-0.6" y="-0.6" width="6.2" height="6.2" rx="1" style="fill:%s" fill-opacity="0.14"/>`, lit)
	cell := func(x, y int, fill string) {
		fmt.Fprintf(&b, `<rect x="%d" y="%d" width="1" height="1" rx="0.22" style="fill:%s"/>`, x, y, fill)
	}
	for row := 0; row < 5; row++ {
		for col := 0; col < 3; col++ {
			i := uint(row*3 + col)
			if cells>>i&1 == 1 {
				fill := lit
				if shades>>i&1 == 1 {
					fill = deep
				}
				cell(col, row, fill)
				if col < 2 {
					cell(4-col, row, fill)
				}
			}
		}
	}
	b.WriteString(`</svg>`)
	out := b.String()
	memo.Store(name, out)
	return out
}