1/** 2 * Sigil 3 * 4 * Urbit's real idea is not its typography, it is that every identity carries a 5 * generated seal â a glyph you could not have drawn, derived from the name 6 * itself. Borrowed here because it is not decoration on this site, it is the 7 * thesis: a character has an identity, that identity is hers, and it travels 8 * with her when a player carries her into somebody else's world. 9 * 10 * So the sigil is derived from the persona document, deterministically. Change 11 * a name and you get a different seal; carry the same character into a second 12 * world and the seal is the one that arrived. It is the same argument the 13 * crossing scene makes, made in a form you can see before you read. 14 * 15 * Biomorphic rather than heraldic: the cells are grown from a wandering radius 16 * rather than stamped from a grid, so no two are the same shape and none of 17 * them is a circle. This is also the answer to the warm-cream-and-serif smell â 18 * a procedural mark is the one thing on a page that cannot have been picked 19 * from a template. 20 */ 21 22/** 23 * Two sources of randomness, on purpose. 24 * 25 * Identity seeds the STRUCTURE â how many membranes, how many lobes on each, 26 * how far off-centre the nucleus sits, how many filaments reach out. That part 27 * is derived from the persona document and never changes, so a character is 28 * recognisably the same organism wherever she appears, including after she has 29 * crossed into a world that never made her. 30 * 31 * The GROWTH is fresh every render. A living thing is never the same shape 32 * twice â a cell photographed an hour apart is the same cell and a different 33 * picture â so the wander that fleshes out each membrane is drawn from an 34 * unseeded source. No two sigils on this site are ever identical, including 35 * two of the same character, and that is the biologically honest version of 36 * what an identity mark is. 37 */ 38 39/** A small deterministic hash, so a name always seeds the same structure. */ 40const seedOf = (text) => { 41 let h = 2166136261; 42 for (let i = 0; i < text.length; i += 1) { 43 h ^= text.charCodeAt(i); 44 h = Math.imul(h, 16777619); 45 } 46 return h >>> 0; 47}; 48 49/** Mulberry32 â tiny, seedable, and good enough for a glyph. */ 50const rngOf = (seed) => () => { 51 seed |= 0; 52 seed = (seed + 0x6d2b79f5) | 0; 53 let t = Math.imul(seed ^ (seed >>> 15), 1 | seed); 54 t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; 55 return ((t ^ (t >>> 14)) >>> 0) / 4294967296; 56}; 57 58/** 59 * One membrane: a closed loop whose radius wanders, so it reads as something 60 * that grew into its shape rather than something struck from a die. 61 */ 62const cell = (rand, cx, cy, radius, lobes) => { 63 const points = []; 64 for (let i = 0; i < lobes; i += 1) { 65 const angle = (i / lobes) * Math.PI * 2; 66 const r = radius * (0.68 + rand() * 0.46); 67 points.push([cx + Math.cos(angle) * r, cy + Math.sin(angle) * r]); 68 } 69 // Catmull-Rom through the points, closed, so the wander stays smooth. 70 const d = points.map((p, i) => { 71 const p0 = points[(i - 1 + points.length) % points.length]; 72 const p1 = p; 73 const p2 = points[(i + 1) % points.length]; 74 const p3 = points[(i + 2) % points.length]; 75 const c1 = [p1[0] + (p2[0] - p0[0]) / 6, p1[1] + (p2[1] - p0[1]) / 6]; 76 const c2 = [p2[0] - (p3[0] - p1[0]) / 6, p2[1] - (p3[1] - p1[1]) / 6]; 77 const head = i === 0 ? `M${p1[0].toFixed(1)} ${p1[1].toFixed(1)}` : ''; 78 return `${head}C${c1[0].toFixed(1)} ${c1[1].toFixed(1)} ${c2[0].toFixed(1)} ${c2[1].toFixed(1)} ${p2[0].toFixed(1)} ${p2[1].toFixed(1)}`; 79 }).join(''); 80 return `${d}Z`; 81}; 82 83/** 84 * A chapter's mark, in the same system. 85 * 86 * The rune was sitting in a perfect circle with a hairline border â the 87 * constructed version of the exact form the sigil grows. One mark system now, 88 * two contents: a chapter's membrane holds its rune, a character's holds her 89 * own nucleus. Each chapter's membrane is seeded by its own rune, so no two 90 * chapters carry the same shape, the way no two characters do. 91 */ 92export const ChapterMark = ({ rune }) => { 93 const form = rngOf(seedOf(`chapter:${rune}`)); 94 const grow = Math.random; 95 const outer = cell(grow, 50, 50, 44, 7 + Math.floor(form() * 3)); 96 const inner = cell(grow, 50, 50, 33, 6 + Math.floor(form() * 3)); 97 return `<span class="chapter-mark" aria-hidden="true"> 98 <svg class="sigil chapter-mark-cell" viewBox="0 0 100 100" aria-hidden="true" focusable="false">
98<path d="${outer}" class="sigil-ring" style="--i:0"/><path d="${inner}" class="sigil-ring" style="--i:1"/></svg> 99 <span class="chapter-mark-rune">${rune}</span> 100 </span>`; 101}; 102 103export const Sigil = ({ name, size = 56, tone = 'honey' }) => { 104 const form = rngOf(seedOf(name)); // who she is 105 const grow = Math.random; // this particular growth of her 106 const rings = 2 + Math.floor(form() * 2); 107 const paths = []; 108 109 for (let i = 0; i < rings; i += 1) { 110 const radius = 42 - i * (9 + form() * 5); 111 paths.push(`<path d="${cell(grow, 50, 50, radius, 6 + Math.floor(form() * 4))}" class="sigil-ring" style="--i:${i}"/>`); 112 } 113 114 // The nucleus: off-centre, because a grown thing is not concentric. Where 115 // it sits is hers; the shape it takes is this morning's. 116 const nx = 50 + (form() - 0.5) * 14; 117 const ny = 50 + (form() - 0.5) * 14; 118 paths.push(`<path d="${cell(grow, nx, ny, 9 + form() * 5, 5 + Math.floor(form() * 3))}" class="sigil-core"/>`); 119 120 // Filaments reaching out of the membrane â what the character is bound to. 121 const strands = 2 + Math.floor(form() * 3); 122 for (let i = 0; i < strands; i += 1) { 123 const a = form() * Math.PI * 2; 124 const r1 = 18 + grow() * 10; 125 const r2 = 44 + grow() * 6; 126 paths.push(`<path d="M${(50 + Math.cos(a) * r1).toFixed(1)} ${(50 + Math.sin(a) * r1).toFixed(1)}L${(50 + Math.cos(a) * r2).toFixed(1)} ${(50 + Math.sin(a) * r2).toFixed(1)}" class="sigil-strand"/>`); 127 } 128 129 return `<svg class="sigil sigil-${tone}" viewBox="0 0 100 100" width="${size}" height="${size}" aria-hidden="true" focusable="false">${paths.join('')}</svg>`; 130};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.