|
|
|
@@ -68,11 +68,28 @@ export function buildCybersigil(opts: SigilOptions = {}): string {
|
|
|
|
|
const emit = (d: string, cls: string) => {
|
|
|
|
|
if (strokeCount >= MAX_PATHS) return;
|
|
|
|
|
parts.push(
|
|
|
|
|
`<path class="${cls}" d="${d}" pathLength="1" style="--i:${strokeCount % 16}"/>`,
|
|
|
|
|
`<path class="${cls}" d="${d}" pathLength="1" style="--i:${strokeCount % 16}" filter="url(#cs-erosion)"/>`,
|
|
|
|
|
);
|
|
|
|
|
strokeCount++;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const emitText = (x: number, y: number, txt: string, cls: string) => {
|
|
|
|
|
parts.push(
|
|
|
|
|
`<text x="${n(x)}" y="${n(y)}" class="${cls}" style="--i:${strokeCount % 16}">${txt}</text>`,
|
|
|
|
|
);
|
|
|
|
|
strokeCount++;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const microFilaments = (at: Pt, ang: number) => {
|
|
|
|
|
const num = 5 + Math.floor(rng() * 5);
|
|
|
|
|
for (let i = 0; i < num; i++) {
|
|
|
|
|
const a = ang + rnd(-0.5, 0.5);
|
|
|
|
|
const l = rnd(2, 8);
|
|
|
|
|
const tip: Pt = [at[0] + Math.cos(a) * l, at[1] + Math.sin(a) * l];
|
|
|
|
|
emit(`M${n(at[0])} ${n(at[1])} L${n(tip[0])} ${n(tip[1])}`, 'cs-sig-micro');
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Catmull-Rom → cubic Bézier through an ordered point list (organic sweep).
|
|
|
|
|
const spline = (pts: Pt[], tension = 6): string => {
|
|
|
|
|
if (pts.length < 2) return '';
|
|
|
|
@@ -146,8 +163,19 @@ export function buildCybersigil(opts: SigilOptions = {}): string {
|
|
|
|
|
const pts: Pt[] = [[ox, oy], mid, peak, hook, tail];
|
|
|
|
|
emit(spline(pts, 5.5), 'cs-sig-main');
|
|
|
|
|
|
|
|
|
|
// Bifurcation: split at the mid point with a secondary branch
|
|
|
|
|
if (depth > 0 && rng() < 0.45) {
|
|
|
|
|
const bAng = ang + (rng() < 0.5 ? 0.8 : -0.8) + rnd(-0.3, 0.3);
|
|
|
|
|
const bL = L * rnd(0.4, 0.7);
|
|
|
|
|
const bPeak: Pt = [mid[0] + Math.cos(bAng) * bL, mid[1] + Math.sin(bAng) * bL];
|
|
|
|
|
const bPts: Pt[] = [mid, bPeak, [bPeak[0] + rnd(-10, 10), bPeak[1] + rnd(10, 20)]];
|
|
|
|
|
emit(spline(bPts, 4), 'cs-sig-main');
|
|
|
|
|
if (rng() < 0.3) ornament(bPeak, bAng, 0.6);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// terminal spike off the outermost point
|
|
|
|
|
barb(peak, ang + rnd(-0.4, 0.4), scale * rnd(10, 22));
|
|
|
|
|
if (rng() < 0.3) microFilaments(peak, ang);
|
|
|
|
|
|
|
|
|
|
// denser filament shadows trailing the main sweep
|
|
|
|
|
const numFilaments = rng() < 0.5 ? 2 : 1;
|
|
|
|
@@ -198,15 +226,31 @@ export function buildCybersigil(opts: SigilOptions = {}): string {
|
|
|
|
|
if (depth === 0 && rng() < 0.35) ornament(peak, ang, 0.9);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ── Wavering spine: a curve from top to bottom, gently bowing in +x.
|
|
|
|
|
const spineNodes = 6 + Math.floor(rng() * 3);
|
|
|
|
|
// ── Harmonic spine: a weighted wave from top to bottom
|
|
|
|
|
const spineNodes = 8 + Math.floor(rng() * 4);
|
|
|
|
|
const spinePts: Pt[] = [];
|
|
|
|
|
const sFreq = rnd(0.8, 1.8);
|
|
|
|
|
const sAmp = rnd(8, 15);
|
|
|
|
|
for (let i = 0; i <= spineNodes; i++) {
|
|
|
|
|
const y = (H * i) / spineNodes;
|
|
|
|
|
const x = i === 0 || i === spineNodes ? 0 : rnd(0, 14);
|
|
|
|
|
const t = i / spineNodes;
|
|
|
|
|
const y = H * t;
|
|
|
|
|
const x = (i === 0 || i === spineNodes) ? 0 :
|
|
|
|
|
Math.sin(t * Math.PI * sFreq) * sAmp +
|
|
|
|
|
Math.sin(t * Math.PI * sFreq * 2.3) * (sAmp * 0.4);
|
|
|
|
|
spinePts.push([x, y]);
|
|
|
|
|
}
|
|
|
|
|
emit(spline(spinePts, 5), 'cs-sig-main cs-sig-spine');
|
|
|
|
|
|
|
|
|
|
// ── Vascular Filaments: high-frequency "shiver" paths tracking the spine
|
|
|
|
|
for (let j = 0; j < 2; j++) {
|
|
|
|
|
const vPts = spinePts.map(([x, y], i) => {
|
|
|
|
|
const t = i / spineNodes;
|
|
|
|
|
const off = Math.sin(t * 22 + rng() * 6) * 1.8;
|
|
|
|
|
return [x + off + (j === 0 ? -2.5 : 2.5), y] as Pt;
|
|
|
|
|
});
|
|
|
|
|
emit(spline(vPts, 6.5), 'cs-sig-vessel');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Chromatic Aberration: two faint, offset echoes of the spine
|
|
|
|
|
emit(spline(spinePts.map(([x, y]) => [x - 1.5, y] as Pt), 5), 'cs-sig-spine-ab cs-sig-spine-ab--1');
|
|
|
|
|
emit(spline(spinePts.map(([x, y]) => [x + 1.5, y] as Pt), 5), 'cs-sig-spine-ab cs-sig-spine-ab--2');
|
|
|
|
@@ -256,6 +300,14 @@ export function buildCybersigil(opts: SigilOptions = {}): string {
|
|
|
|
|
emit(`M${n(n1[0])} ${n(n1[1])} L${n(n2[0])} ${n(n2[1])}`, 'cs-sig-connect');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Ghost Symbols: tiny technical fragments
|
|
|
|
|
const symbols = ['0x00', 'NULL', 'VOID', 'ERR', 'INIT', 'HALT', 'RECLAIM', 'DEAD'];
|
|
|
|
|
const numSymbols = 2 + Math.floor(rng() * 3);
|
|
|
|
|
for (let i = 0; i < numSymbols; i++) {
|
|
|
|
|
const pt = pick(nodePoints);
|
|
|
|
|
emitText(pt[0] + rnd(4, 12), pt[1] + rnd(-4, 4), pick(symbols), 'cs-sig-text');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const half = parts.join('');
|
|
|
|
|
const minX = -(maxX + PAD);
|
|
|
|
|
const vbW = 2 * (maxX + PAD);
|
|
|
|
@@ -263,6 +315,12 @@ export function buildCybersigil(opts: SigilOptions = {}): string {
|
|
|
|
|
`<svg class="cs-sigil" viewBox="${n(minX)} ${-PAD} ${n(vbW)} ${H + 2 * PAD}" ` +
|
|
|
|
|
`preserveAspectRatio="xMidYMid meet" aria-hidden="true" focusable="false" ` +
|
|
|
|
|
`xmlns="http://www.w3.org/2000/svg">` +
|
|
|
|
|
`<defs>` +
|
|
|
|
|
`<filter id="cs-erosion" x="-20%" y="-20%" width="140%" height="140%">` +
|
|
|
|
|
`<feTurbulence type="fractalNoise" baseFrequency="0.6" numOctaves="3" result="noise" />` +
|
|
|
|
|
`<feDisplacementMap in="SourceGraphic" in2="noise" scale="1.5" xChannelSelector="R" yChannelSelector="G" />` +
|
|
|
|
|
`</filter>` +
|
|
|
|
|
`</defs>` +
|
|
|
|
|
`<g class="cs-sig-half">${half}</g>` +
|
|
|
|
|
`<g class="cs-sig-half" transform="scale(-1 1)">${half}</g>` +
|
|
|
|
|
`</svg>`
|
|
|
|
|