// Plant markers are a solid circle in the plant's color with a 1–2 letter // monogram — the design's replacement for emoji icons. The letters are derived // from the name, never stored, so a renamed plant re-letters itself. // // Rule: the initial of the species (the part before a " — Variety" suffix). // Within one catalog, plants that share an initial are told apart by a second, // lowercase letter — Marigold → Ma, Mint → Mi. The bare initial goes to the // plant that entered the catalog first (lowest id): the built-ins are seeded // roughly commonest-first, so Tomato keeps T over Thyme, and a variety you add // later ("Tomato — Cherokee Purple") gets To rather than displacing it. The // collision set is the whole catalog, so a plant's letters are the same on // every screen. /** "Tomato — Cherokee Purple" → "Tomato"; "Melon (Hale's Best)" → "Melon". */ export function speciesName(name: string): string { const cut = name.split(/\s[—–-]\s|\s\(/)[0] ?? name return cut.trim() || name.trim() } function letters(name: string): string[] { // Letters only; a leading digit or punctuation would make an unreadable mark. return speciesName(name) .normalize('NFD') .replace(/[̀-ͯ]/g, '') .replace(/[^A-Za-z]/g, '') .split('') } /** The monogram for every plant in the catalog, keyed by id. */ export function monogramMap

(plants: readonly P[]): Map { const out = new Map() const taken = new Set() const sorted = [...plants].sort((a, b) => a.id - b.id || a.name.localeCompare(b.name)) for (const p of sorted) { const ls = letters(p.name) if (ls.length === 0) { out.set(p.id, '?') continue } const initial = ls[0].toUpperCase() // Prefer the bare initial, then initial + each following letter in turn — // skipping a repeat of the initial ("Pp" is not a mark anyone can read) — // then give up on uniqueness rather than grow past two letters. const candidates = [ initial, ...ls .slice(1) .filter((l) => l.toUpperCase() !== initial) .map((l) => initial + l.toLowerCase()), ] const pick = candidates.find((c) => !taken.has(c)) ?? candidates[candidates.length - 1] taken.add(pick) out.set(p.id, pick) } return out } /** A single plant's monogram when the catalog isn't at hand (a fallback, not the * collision-aware mark — prefer monogramMap where a list exists). */ export function monogramFor(name: string): string { const ls = letters(name) return ls.length ? ls[0].toUpperCase() : '?' }