termlings 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/termlings.js CHANGED
@@ -377,11 +377,13 @@ function getTraitColors(traits2, bw2 = false) {
377
377
  if (bw2) {
378
378
  const fg = hueToGray(traits2.faceHue);
379
379
  const dg = Math.round(fg * 0.55);
380
+ const bg = Math.round(fg * 0.18);
380
381
  const hg = hueToGray(traits2.hatHue);
381
382
  return {
382
383
  faceRgb: [fg, fg, fg],
383
384
  darkRgb: [dg, dg, dg],
384
- hatRgb: [hg, hg, hg]
385
+ hatRgb: [hg, hg, hg],
386
+ bgRgb: [bg, bg, bg]
385
387
  };
386
388
  }
387
389
  const faceHueDeg = traits2.faceHue * 30;
@@ -389,30 +391,35 @@ function getTraitColors(traits2, bw2 = false) {
389
391
  return {
390
392
  faceRgb: hslToRgb(faceHueDeg, 0.5, 0.5),
391
393
  darkRgb: hslToRgb(faceHueDeg, 0.5, 0.28),
392
- hatRgb: hslToRgb(hatHueDeg, 0.5, 0.5)
394
+ hatRgb: hslToRgb(hatHueDeg, 0.5, 0.5),
395
+ bgRgb: hslToRgb(faceHueDeg, 0.5, 0.1)
393
396
  };
394
397
  }
395
- function renderSVG(dna2, pixelSize = 10, frame = 0, background = "#000", padding = 1, bw2 = false) {
398
+ function renderSVG(dna2, pixelSize = 10, frame = 0, background = "auto", padding = 1, bw2 = false) {
396
399
  const traits2 = decodeDNA(dna2);
397
400
  const grid = generateGrid(traits2, frame);
398
- const { faceRgb: faceRgb2, darkRgb: darkRgb2, hatRgb: hatRgb2 } = getTraitColors(traits2, bw2);
401
+ const { faceRgb: faceRgb2, darkRgb: darkRgb2, hatRgb: hatRgb2, bgRgb: bgRgb2 } = getTraitColors(traits2, bw2);
399
402
  const toHex = (r, g, b) => `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
400
403
  const faceHex = toHex(...faceRgb2);
401
404
  const darkHex = toHex(...darkRgb2);
402
405
  const hatHex = toHex(...hatRgb2);
406
+ const resolvedBg = background === "auto" ? toHex(...bgRgb2) : background;
403
407
  const cols = 9;
404
408
  const rows = grid.length;
405
409
  const pad = padding;
406
- const w = (cols + pad * 2) * pixelSize;
407
- const h = (rows + pad * 2) * pixelSize;
410
+ const side = Math.max(cols, rows) + pad * 2;
411
+ const w = side * pixelSize;
412
+ const h = side * pixelSize;
413
+ const ox = Math.floor((side - cols) / 2);
414
+ const oy = Math.floor((side - rows) / 2);
408
415
  const half = Math.round(pixelSize / 2);
409
416
  const quarter = Math.round(pixelSize / 4);
410
417
  const rects = [];
411
418
  for (let y = 0; y < rows; y++) {
412
419
  for (let x = 0; x < cols; x++) {
413
420
  const cell = grid[y][x];
414
- const rx = (x + pad) * pixelSize;
415
- const ry = (y + pad) * pixelSize;
421
+ const rx = (x + ox) * pixelSize;
422
+ const ry = (y + oy) * pixelSize;
416
423
  if (cell === "f") {
417
424
  rects.push(`<rect x="${rx}" y="${ry}" width="${pixelSize}" height="${pixelSize}" fill="${faceHex}"/>`);
418
425
  } else if (cell === "l") {
@@ -443,7 +450,7 @@ function renderSVG(dna2, pixelSize = 10, frame = 0, background = "#000", padding
443
450
  }
444
451
  }
445
452
  }
446
- const bg = background ? `<rect width="${w}" height="${h}" fill="${background}"/>
453
+ const bg = resolvedBg ? `<rect width="${w}" height="${h}" fill="${resolvedBg}"/>
447
454
  ` : "";
448
455
  return `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}" shape-rendering="crispEdges">
449
456
  ${bg}${rects.join("\n")}
@@ -492,13 +499,14 @@ function renderTerminal(dna2, frame = 0, bw2 = false) {
492
499
  }
493
500
  return lines.join("\n");
494
501
  }
495
- function renderLayeredSVG(dna2, pixelSize = 10, bw2 = false) {
502
+ function renderLayeredSVG(dna2, pixelSize = 10, bw2 = false, padding = 0) {
496
503
  const traits2 = decodeDNA(dna2);
497
- const { faceRgb: faceRgb2, darkRgb: darkRgb2, hatRgb: hatRgb2 } = getTraitColors(traits2, bw2);
504
+ const { faceRgb: faceRgb2, darkRgb: darkRgb2, hatRgb: hatRgb2, bgRgb: bgRgb2 } = getTraitColors(traits2, bw2);
498
505
  const toHex = (r, g, b) => `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
499
506
  const faceHex = toHex(...faceRgb2);
500
507
  const darkHex = toHex(...darkRgb2);
501
508
  const hatHex = toHex(...hatRgb2);
509
+ const bgHex = toHex(...bgRgb2);
502
510
  const half = Math.round(pixelSize / 2);
503
511
  const quarter = Math.round(pixelSize / 4);
504
512
  const cols = 9;
@@ -511,8 +519,12 @@ function renderLayeredSVG(dna2, pixelSize = 10, bw2 = false) {
511
519
  const legVariant = LEGS[traits2.legs];
512
520
  const legFrameCount2 = legVariant.length;
513
521
  const totalRows = hatRows.length + 1 + 1 + 2 + 2 + 1;
514
- const w = cols * pixelSize;
515
- const h = totalRows * pixelSize;
522
+ const pad = padding;
523
+ const side = Math.max(cols, totalRows) + pad * 2;
524
+ const w = side * pixelSize;
525
+ const h = side * pixelSize;
526
+ const ox = Math.floor((side - cols) / 2);
527
+ const oy = Math.floor((side - totalRows) / 2);
516
528
  function px(cell, rx, ry) {
517
529
  if (cell === "_") return "";
518
530
  if (cell === "f") return `<rect x="${rx}" y="${ry}" width="${pixelSize}" height="${pixelSize}" fill="${faceHex}"/>`;
@@ -531,7 +543,7 @@ function renderLayeredSVG(dna2, pixelSize = 10, bw2 = false) {
531
543
  let out = "";
532
544
  for (let y = 0; y < rows.length; y++) {
533
545
  for (let x = 0; x < cols; x++) {
534
- out += px(rows[y][x], x * pixelSize, (startY + y) * pixelSize);
546
+ out += px(rows[y][x], (x + ox) * pixelSize, (startY + y + oy) * pixelSize);
535
547
  }
536
548
  }
537
549
  return out;
@@ -550,7 +562,7 @@ function renderLayeredSVG(dna2, pixelSize = 10, bw2 = false) {
550
562
  const legs2 = legFrameCount2 > 2 ? renderRows([legVariant[2]], lY) : "";
551
563
  const legs3 = legFrameCount2 > 3 ? renderRows([legVariant[3]], lY) : "";
552
564
  const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}" shape-rendering="crispEdges"><g class="tg-bob">${staticRects}<g class="tg-mouth-0">${mouth0}</g><g class="tg-mouth-1">${mouth1}</g><g class="tg-body-0">${body0}</g><g class="tg-body-1">${body1}</g><g class="tg-body-2">${body2}</g></g><g class="tg-legs-0">${legs0}</g>` + (legs1 ? `<g class="tg-legs-1">${legs1}</g>` : "") + (legs2 ? `<g class="tg-legs-2">${legs2}</g>` : "") + (legs3 ? `<g class="tg-legs-3">${legs3}</g>` : "") + `</svg>`;
553
- return { svg, legFrames: legFrameCount2, rows: totalRows };
565
+ return { svg, legFrames: legFrameCount2, rows: totalRows, bgHex };
554
566
  }
555
567
  function getAvatarCSS() {
556
568
  return `
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "termlings",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Open-source pixel creatures for web and the terminal.",
package/src/index.ts CHANGED
@@ -443,15 +443,18 @@ export function getTraitColors(traits: DecodedDNA, bw = false): {
443
443
  faceRgb: [number, number, number];
444
444
  darkRgb: [number, number, number];
445
445
  hatRgb: [number, number, number];
446
+ bgRgb: [number, number, number];
446
447
  } {
447
448
  if (bw) {
448
449
  const fg = hueToGray(traits.faceHue);
449
450
  const dg = Math.round(fg * 0.55);
451
+ const bg = Math.round(fg * 0.18);
450
452
  const hg = hueToGray(traits.hatHue);
451
453
  return {
452
454
  faceRgb: [fg, fg, fg],
453
455
  darkRgb: [dg, dg, dg],
454
456
  hatRgb: [hg, hg, hg],
457
+ bgRgb: [bg, bg, bg],
455
458
  };
456
459
  }
457
460
  const faceHueDeg = traits.faceHue * 30;
@@ -460,6 +463,7 @@ export function getTraitColors(traits: DecodedDNA, bw = false): {
460
463
  faceRgb: hslToRgb(faceHueDeg, 0.5, 0.5),
461
464
  darkRgb: hslToRgb(faceHueDeg, 0.5, 0.28),
462
465
  hatRgb: hslToRgb(hatHueDeg, 0.5, 0.5),
466
+ bgRgb: hslToRgb(faceHueDeg, 0.5, 0.1),
463
467
  };
464
468
  }
465
469
 
@@ -467,11 +471,11 @@ export function getTraitColors(traits: DecodedDNA, bw = false): {
467
471
  * Render a DNA string as an SVG string with transparent background.
468
472
  * Each pixel is rendered as a square rect. 1-cell padding around the grid.
469
473
  */
470
- export function renderSVG(dna: string, pixelSize = 10, frame = 0, background: string | null = '#000', padding = 1, bw = false): string {
474
+ export function renderSVG(dna: string, pixelSize = 10, frame = 0, background: string | null = 'auto', padding = 1, bw = false): string {
471
475
  const traits = decodeDNA(dna);
472
476
  const grid = generateGrid(traits, frame);
473
477
 
474
- const { faceRgb, darkRgb, hatRgb } = getTraitColors(traits, bw);
478
+ const { faceRgb, darkRgb, hatRgb, bgRgb } = getTraitColors(traits, bw);
475
479
 
476
480
  const toHex = (r: number, g: number, b: number) =>
477
481
  `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
@@ -479,12 +483,16 @@ export function renderSVG(dna: string, pixelSize = 10, frame = 0, background: st
479
483
  const faceHex = toHex(...faceRgb);
480
484
  const darkHex = toHex(...darkRgb);
481
485
  const hatHex = toHex(...hatRgb);
486
+ const resolvedBg = background === 'auto' ? toHex(...bgRgb) : background;
482
487
 
483
488
  const cols = 9;
484
489
  const rows = grid.length;
485
490
  const pad = padding;
486
- const w = (cols + pad * 2) * pixelSize;
487
- const h = (rows + pad * 2) * pixelSize;
491
+ const side = Math.max(cols, rows) + pad * 2;
492
+ const w = side * pixelSize;
493
+ const h = side * pixelSize;
494
+ const ox = Math.floor((side - cols) / 2);
495
+ const oy = Math.floor((side - rows) / 2);
488
496
 
489
497
  const half = Math.round(pixelSize / 2);
490
498
  const quarter = Math.round(pixelSize / 4);
@@ -492,8 +500,8 @@ export function renderSVG(dna: string, pixelSize = 10, frame = 0, background: st
492
500
  for (let y = 0; y < rows; y++) {
493
501
  for (let x = 0; x < cols; x++) {
494
502
  const cell = grid[y][x];
495
- const rx = (x + pad) * pixelSize;
496
- const ry = (y + pad) * pixelSize;
503
+ const rx = (x + ox) * pixelSize;
504
+ const ry = (y + oy) * pixelSize;
497
505
  if (cell === "f") {
498
506
  rects.push(`<rect x="${rx}" y="${ry}" width="${pixelSize}" height="${pixelSize}" fill="${faceHex}"/>`);
499
507
  } else if (cell === "l") {
@@ -530,7 +538,7 @@ export function renderSVG(dna: string, pixelSize = 10, frame = 0, background: st
530
538
  }
531
539
  }
532
540
 
533
- const bg = background ? `<rect width="${w}" height="${h}" fill="${background}"/>\n` : '';
541
+ const bg = resolvedBg ? `<rect width="${w}" height="${h}" fill="${resolvedBg}"/>\n` : '';
534
542
  return `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}" shape-rendering="crispEdges">\n${bg}${rects.join("\n")}\n</svg>`;
535
543
  }
536
544
 
@@ -596,14 +604,15 @@ export function renderTerminal(dna: string, frame = 0, bw = false): string {
596
604
  * Returns the SVG string, number of leg frames, and total row count.
597
605
  * Used by framework components for CSS-only animation (no JS timers).
598
606
  */
599
- export function renderLayeredSVG(dna: string, pixelSize = 10, bw = false): {
607
+ export function renderLayeredSVG(dna: string, pixelSize = 10, bw = false, padding = 0): {
600
608
  svg: string;
601
609
  legFrames: number;
602
610
  rows: number;
611
+ bgHex: string;
603
612
  } {
604
613
  const traits = decodeDNA(dna);
605
614
 
606
- const { faceRgb, darkRgb, hatRgb } = getTraitColors(traits, bw);
615
+ const { faceRgb, darkRgb, hatRgb, bgRgb } = getTraitColors(traits, bw);
607
616
 
608
617
  const toHex = (r: number, g: number, b: number) =>
609
618
  `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
@@ -611,6 +620,7 @@ export function renderLayeredSVG(dna: string, pixelSize = 10, bw = false): {
611
620
  const faceHex = toHex(...faceRgb);
612
621
  const darkHex = toHex(...darkRgb);
613
622
  const hatHex = toHex(...hatRgb);
623
+ const bgHex = toHex(...bgRgb);
614
624
 
615
625
  const half = Math.round(pixelSize / 2);
616
626
  const quarter = Math.round(pixelSize / 4);
@@ -627,8 +637,12 @@ export function renderLayeredSVG(dna: string, pixelSize = 10, bw = false): {
627
637
 
628
638
  // hat + face + eyes + 2 mouth + 2 body + 1 legs
629
639
  const totalRows = hatRows.length + 1 + 1 + 2 + 2 + 1;
630
- const w = cols * pixelSize;
631
- const h = totalRows * pixelSize;
640
+ const pad = padding;
641
+ const side = Math.max(cols, totalRows) + pad * 2;
642
+ const w = side * pixelSize;
643
+ const h = side * pixelSize;
644
+ const ox = Math.floor((side - cols) / 2);
645
+ const oy = Math.floor((side - totalRows) / 2);
632
646
 
633
647
  function px(cell: Pixel, rx: number, ry: number): string {
634
648
  if (cell === "_") return "";
@@ -649,7 +663,7 @@ export function renderLayeredSVG(dna: string, pixelSize = 10, bw = false): {
649
663
  let out = "";
650
664
  for (let y = 0; y < rows.length; y++) {
651
665
  for (let x = 0; x < cols; x++) {
652
- out += px(rows[y][x], x * pixelSize, (startY + y) * pixelSize);
666
+ out += px(rows[y][x], (x + ox) * pixelSize, (startY + y + oy) * pixelSize);
653
667
  }
654
668
  }
655
669
  return out;
@@ -691,7 +705,7 @@ export function renderLayeredSVG(dna: string, pixelSize = 10, bw = false): {
691
705
  (legs3 ? `<g class="tg-legs-3">${legs3}</g>` : "") +
692
706
  `</svg>`;
693
707
 
694
- return { svg, legFrames: legFrameCount, rows: totalRows };
708
+ return { svg, legFrames: legFrameCount, rows: totalRows, bgHex };
695
709
  }
696
710
 
697
711
  /**