opencrush 0.3.19 → 0.3.20

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.
Files changed (2) hide show
  1. package/dist/index.js +58 -77
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -245627,7 +245627,7 @@ function parseIdentity(identityPath) {
245627
245627
  const job = jobRaw.split(/[—–\.\+]/).map((s2) => s2.trim()).filter(Boolean)[0] ?? "";
245628
245628
  const hobbiesMatch = content.match(/\*\*Hobbies:\*\*\s*(.+)/i);
245629
245629
  const hobbiesRaw = (hobbiesMatch == null ? void 0 : hobbiesMatch[1]) ?? "";
245630
- const tags = splitRespectingParens(hobbiesRaw).map((t2) => t2.trim()).filter(Boolean).map((t2) => t2.length > 30 ? t2.split(/[,(]/)[0].trim() : t2).filter((t2) => t2.length > 1).slice(0, 5);
245630
+ const tags = splitRespectingParens(hobbiesRaw).map((t2) => t2.trim()).filter(Boolean).map((t2) => t2.length > 30 ? t2.split(/[,(]/)[0].trim() : t2).filter((t2) => t2.length > 1).slice(0, 6);
245631
245631
  let vibe = "";
245632
245632
  const soulPath = identityPath.replace("IDENTITY.md", "SOUL.md");
245633
245633
  if ((0, import_fs18.existsSync)(soulPath)) {
@@ -245654,81 +245654,65 @@ function splitRespectingParens(str2) {
245654
245654
  function escapeXml(str2) {
245655
245655
  return str2.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
245656
245656
  }
245657
- function buildTextOverlay(data, bgR = 20, bgG = 20, bgB = 30) {
245657
+ function buildTextPanel(data, panelH, bgR, bgG, bgB) {
245658
245658
  const S2 = SCALE;
245659
- const textX = 44 * S2;
245660
- const panelW = 480 * S2;
245661
- const availW = panelW - textX - 30 * S2;
245662
- const meta = [data.age, data.location].filter(Boolean).join(" \xB7 ");
245663
- const lineCount = 1 + (meta ? 1 : 0) + (data.job ? 1 : 0) + 1 + (data.tags.length > 0 ? 1 : 0) + (data.vibe ? 1 : 0);
245664
- const blockH = lineCount * 32 * S2;
245665
- const startY = Math.max(50 * S2, Math.round((H2 - blockH) / 2) - 20 * S2);
245666
- let y2 = startY;
245659
+ const padX = 48 * S2;
245660
+ const availW = W2 - padX * 2;
245661
+ let y2 = 50 * S2;
245667
245662
  const nameY = y2;
245668
- y2 += 50 * S2;
245663
+ y2 += 56 * S2;
245664
+ const meta = [data.age, data.location].filter(Boolean).join(" \xB7 ");
245669
245665
  const metaY = y2;
245670
- if (meta) y2 += 32 * S2;
245666
+ if (meta) y2 += 36 * S2;
245671
245667
  const jobY = y2;
245672
- if (data.job) y2 += 30 * S2;
245673
- y2 += 12 * S2;
245668
+ if (data.job) y2 += 34 * S2;
245669
+ y2 += 16 * S2;
245674
245670
  const tagStartY = y2;
245675
245671
  let inlineTags = "";
245676
- let offsetX = textX;
245672
+ let offsetX = padX;
245677
245673
  let rowY = tagStartY;
245678
- const rowH = 28 * S2;
245674
+ const rowH = 32 * S2;
245679
245675
  let currentRow = 1;
245680
245676
  const maxRows = 2;
245681
- const charW = 6.2 * S2;
245682
- const padW = 16 * S2;
245677
+ const charW = 6.5 * S2;
245678
+ const padW = 18 * S2;
245683
245679
  for (const tag of data.tags) {
245684
245680
  const maxChars = Math.floor((availW - padW) / charW);
245685
245681
  const label = tag.length > maxChars ? tag.slice(0, maxChars - 1) + "\u2026" : tag;
245686
245682
  const w2 = Math.ceil(label.length * charW + padW);
245687
- if (offsetX + w2 > textX + availW) {
245683
+ if (offsetX + w2 > padX + availW) {
245688
245684
  if (currentRow >= maxRows) break;
245689
245685
  currentRow++;
245690
- offsetX = textX;
245686
+ offsetX = padX;
245691
245687
  rowY += rowH;
245692
245688
  }
245693
245689
  inlineTags += `
245694
- <rect x="${offsetX}" y="${rowY - 13 * S2}" width="${w2}" height="${24 * S2}" rx="${12 * S2}" fill="rgba(255,255,255,0.12)" />
245695
- <text x="${offsetX + 8 * S2}" y="${rowY + 3 * S2}" font-family="system-ui, -apple-system, sans-serif" font-size="${11 * S2}" fill="#ddd">${escapeXml(label)}</text>
245690
+ <rect x="${offsetX}" y="${rowY - 14 * S2}" width="${w2}" height="${26 * S2}" rx="${13 * S2}" fill="rgba(255,255,255,0.10)" />
245691
+ <text x="${offsetX + 9 * S2}" y="${rowY + 3 * S2}" font-family="system-ui, -apple-system, sans-serif" font-size="${12 * S2}" fill="#ccc">${escapeXml(label)}</text>
245696
245692
  `;
245697
- offsetX += w2 + 6 * S2;
245693
+ offsetX += w2 + 7 * S2;
245698
245694
  }
245699
- const vibeY = tagStartY + currentRow * rowH + 20 * S2;
245700
- const jobMax = Math.floor(availW / (7 * S2));
245701
- const vibeMax = Math.floor(availW / (6.5 * S2));
245695
+ const vibeY = tagStartY + currentRow * rowH + 24 * S2;
245696
+ const jobMax = Math.floor(availW / (7.5 * S2));
245697
+ const vibeMax = Math.floor(availW / (7 * S2));
245702
245698
  const jobText = data.job.length > jobMax ? data.job.slice(0, jobMax - 1) + "\u2026" : data.job;
245703
245699
  const vibeText = data.vibe.length > vibeMax ? data.vibe.slice(0, vibeMax - 1) + "\u2026" : data.vibe;
245704
- return `<svg width="${panelW}" height="${H2}" xmlns="http://www.w3.org/2000/svg">
245705
- <!-- Panel background using sampled color -->
245706
- <defs>
245707
- <linearGradient id="panel" x1="0" y1="0" x2="1" y2="0">
245708
- <stop offset="0%" stop-color="rgba(${bgR},${bgG},${bgB},0.92)" />
245709
- <stop offset="100%" stop-color="rgba(${bgR},${bgG},${bgB},0.80)" />
245710
- </linearGradient>
245711
- </defs>
245712
- <rect width="${panelW}" height="${H2}" fill="url(#panel)" />
245713
-
245714
- <!-- Name -->
245715
- <text x="${textX}" y="${nameY}" font-family="system-ui, -apple-system, sans-serif" font-size="${42 * S2}" font-weight="bold" fill="white">${escapeXml(data.name)}</text>
245716
-
245717
- <!-- Meta -->
245718
- ${meta ? `<text x="${textX}" y="${metaY}" font-family="system-ui, -apple-system, sans-serif" font-size="${16 * S2}" fill="rgba(255,255,255,0.7)">${escapeXml(meta)}</text>` : ""}
245719
-
245720
- <!-- Job -->
245721
- ${data.job ? `<text x="${textX}" y="${jobY}" font-family="system-ui, -apple-system, sans-serif" font-size="${13 * S2}" fill="rgba(255,255,255,0.5)">${escapeXml(jobText)}</text>` : ""}
245722
-
245723
- <!-- Tags -->
245700
+ return `<svg width="${W2}" height="${panelH}" xmlns="http://www.w3.org/2000/svg">
245701
+ <rect width="${W2}" height="${panelH}" fill="rgb(${bgR},${bgG},${bgB})" />
245702
+
245703
+ <text x="${padX}" y="${nameY}" font-family="system-ui, -apple-system, sans-serif" font-size="${48 * S2}" font-weight="bold" fill="white">${escapeXml(data.name)}</text>
245704
+
245705
+ ${meta ? `<text x="${padX}" y="${metaY}" font-family="system-ui, -apple-system, sans-serif" font-size="${17 * S2}" fill="rgba(255,255,255,0.65)">${escapeXml(meta)}</text>` : ""}
245706
+
245707
+ ${data.job ? `<text x="${padX}" y="${jobY}" font-family="system-ui, -apple-system, sans-serif" font-size="${14 * S2}" fill="rgba(255,255,255,0.45)">${escapeXml(jobText)}</text>` : ""}
245708
+
245724
245709
  ${inlineTags}
245725
245710
 
245726
- <!-- Vibe -->
245727
- ${data.vibe ? `<text x="${textX}" y="${vibeY}" font-family="system-ui, -apple-system, sans-serif" font-size="${13 * S2}" fill="rgba(255,255,255,0.55)" font-style="italic">${escapeXml(vibeText)}</text>` : ""}
245711
+ ${data.vibe ? `<text x="${padX}" y="${vibeY}" font-family="system-ui, -apple-system, sans-serif" font-size="${14 * S2}" fill="rgba(255,255,255,0.50)" font-style="italic">${escapeXml(vibeText)}</text>` : ""}
245728
245712
 
245729
- <!-- Bottom branding -->
245730
- <text x="${textX}" y="${H2 - 28 * S2}" font-family="system-ui, -apple-system, sans-serif" font-size="${14 * S2}" font-weight="bold" fill="#ff69b4">Opencrush</text>
245731
- <text x="${panelW - 30 * S2}" y="${H2 - 28 * S2}" font-family="system-ui, -apple-system, sans-serif" font-size="${10 * S2}" fill="rgba(255,255,255,0.3)" text-anchor="end">github.com/heloraai/Opencrush</text>
245713
+ <!-- Branding -->
245714
+ <text x="${padX}" y="${panelH - 30 * S2}" font-family="system-ui, -apple-system, sans-serif" font-size="${15 * S2}" font-weight="bold" fill="#ff69b4">Opencrush</text>
245715
+ <text x="${W2 - padX}" y="${panelH - 30 * S2}" font-family="system-ui, -apple-system, sans-serif" font-size="${11 * S2}" fill="rgba(255,255,255,0.25)" text-anchor="end">github.com/heloraai/Opencrush</text>
245732
245716
  </svg>`;
245733
245717
  }
245734
245718
  async function generateCard(characterName) {
@@ -245748,52 +245732,49 @@ async function generateCard(characterName) {
245748
245732
  break;
245749
245733
  }
245750
245734
  }
245751
- if (!refImagePath) {
245752
- return generateFallbackCard(charDir, characterName, data);
245753
- }
245735
+ if (!refImagePath) return generateFallbackCard(charDir, characterName, data);
245754
245736
  const { data: colorData } = await sharp(refImagePath).resize(1, 1, { fit: "cover" }).raw().toBuffer({ resolveWithObject: true });
245755
245737
  const [cr2, cg, cb] = [colorData[0], colorData[1], colorData[2]];
245756
- const bgR = Math.round(cr2 * 0.18), bgG = Math.round(cg * 0.18), bgB = Math.round(cb * 0.18);
245757
- const bgSolid = await sharp({
245758
- create: { width: W2, height: H2, channels: 3, background: { r: bgR, g: bgG, b: bgB } }
245759
- }).png().toBuffer();
245760
- const portraitH = H2;
245761
- const portraitW = Math.round(portraitH * 4 / 5);
245762
- const portrait = await sharp(refImagePath).resize(portraitW, portraitH, { fit: "cover", position: "top" }).png().toBuffer();
245738
+ const bgR = Math.round(cr2 * 0.15), bgG = Math.round(cg * 0.15), bgB = Math.round(cb * 0.15);
245739
+ const portraitH = Math.round(W2 * 5 / 4);
245740
+ const panelH = H2 - portraitH;
245741
+ const portrait = await sharp(refImagePath).resize(W2, portraitH, { fit: "cover", position: "top" }).png().toBuffer();
245763
245742
  const fadeMask = Buffer.from(
245764
- `<svg width="${portraitW}" height="${portraitH}">
245743
+ `<svg width="${W2}" height="${portraitH}">
245765
245744
  <defs>
245766
- <linearGradient id="fade" x1="0" y1="0" x2="1" y2="0">
245745
+ <linearGradient id="fade" x1="0" y1="0" x2="0" y2="1">
245767
245746
  <stop offset="0%" stop-color="white" />
245768
- <stop offset="65%" stop-color="white" />
245747
+ <stop offset="80%" stop-color="white" />
245769
245748
  <stop offset="100%" stop-color="black" />
245770
245749
  </linearGradient>
245771
245750
  </defs>
245772
- <rect width="${portraitW}" height="${portraitH}" fill="url(#fade)" />
245751
+ <rect width="${W2}" height="${portraitH}" fill="url(#fade)" />
245773
245752
  </svg>`
245774
245753
  );
245775
245754
  const maskedPortrait = await sharp(portrait).composite([{ input: fadeMask, blend: "dest-in" }]).png().toBuffer();
245776
- const panelW = Math.round(480 * SCALE);
245777
- const textSvg = buildTextOverlay(data, bgR, bgG, bgB);
245778
- const textPanel = await sharp(Buffer.from(textSvg)).resize(panelW, H2).png().toBuffer();
245779
- const card = sharp(bgSolid).composite([
245755
+ const textSvg = buildTextPanel(data, panelH, bgR, bgG, bgB);
245756
+ const textPanel = await sharp(Buffer.from(textSvg)).resize(W2, panelH).png().toBuffer();
245757
+ const bg = await sharp({
245758
+ create: { width: W2, height: H2, channels: 3, background: { r: bgR, g: bgG, b: bgB } }
245759
+ }).png().toBuffer();
245760
+ const card = sharp(bg).composite([
245780
245761
  { input: maskedPortrait, left: 0, top: 0 },
245781
- { input: textPanel, left: W2 - panelW, top: 0 }
245762
+ { input: textPanel, left: 0, top: portraitH }
245782
245763
  ]);
245783
245764
  const outputPath = (0, import_path12.join)(charDir, "card.png");
245784
245765
  await card.png({ quality: 95 }).toFile(outputPath);
245785
245766
  console.log(source_default.green(`
245786
245767
  Card generated: characters/${characterName}/card.png`));
245787
- console.log(source_default.gray(` ${W2}x${H2} PNG
245768
+ console.log(source_default.gray(` ${W2}x${H2} PNG (9:16)
245788
245769
  `));
245789
245770
  return outputPath;
245790
245771
  }
245791
245772
  async function generateFallbackCard(charDir, characterName, data) {
245792
245773
  const sharp = (await import("sharp")).default;
245793
245774
  const S2 = SCALE;
245794
- const gradient = `<svg width="${W2}" height="${H2}">
245795
- <defs><linearGradient id="bg" x1="0" y1="0" x2="0.5" y2="1">
245796
- <stop offset="0%" stop-color="#1a1a2e" /><stop offset="100%" stop-color="#16213e" />
245775
+ const svg = `<svg width="${W2}" height="${H2}" xmlns="http://www.w3.org/2000/svg">
245776
+ <defs><linearGradient id="bg" x1="0" y1="0" x2="0" y2="1">
245777
+ <stop offset="0%" stop-color="#1a1a2e" /><stop offset="100%" stop-color="#0f0f1a" />
245797
245778
  </linearGradient></defs>
245798
245779
  <rect width="${W2}" height="${H2}" fill="url(#bg)" />
245799
245780
  <text x="${W2 / 2}" y="${H2 / 2 - 20 * S2}" font-family="system-ui" font-size="${48 * S2}" font-weight="bold" fill="white" text-anchor="middle">${escapeXml(data.name)}</text>
@@ -245801,7 +245782,7 @@ async function generateFallbackCard(charDir, characterName, data) {
245801
245782
  <text x="${36 * S2}" y="${H2 - 20 * S2}" font-family="system-ui" font-size="${14 * S2}" font-weight="bold" fill="#ff69b4">Opencrush</text>
245802
245783
  </svg>`;
245803
245784
  const outputPath = (0, import_path12.join)(charDir, "card.png");
245804
- await sharp(Buffer.from(gradient)).resize(W2, H2).png().toFile(outputPath);
245785
+ await sharp(Buffer.from(svg)).resize(W2, H2).png().toFile(outputPath);
245805
245786
  console.log(source_default.green(`
245806
245787
  Card generated: characters/${characterName}/card.png`));
245807
245788
  console.log(source_default.gray(` ${W2}x${H2} PNG (no reference image)
@@ -245816,8 +245797,8 @@ var init_card = __esm({
245816
245797
  import_gray_matter3 = __toESM(require_gray_matter());
245817
245798
  init_source();
245818
245799
  SCALE = 2;
245819
- W2 = 1200 * SCALE;
245820
- H2 = 960 * SCALE;
245800
+ W2 = 1080 * SCALE;
245801
+ H2 = 1920 * SCALE;
245821
245802
  }
245822
245803
  });
245823
245804
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencrush",
3
- "version": "0.3.19",
3
+ "version": "0.3.20",
4
4
  "description": "Your AI companion lives on your device. She watches dramas, listens to music, and thinks of you.",
5
5
  "bin": {
6
6
  "opencrush": "dist/index.js"