opencrush 0.3.20 → 0.3.21

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 -55
  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, 6);
245630
+ const tags = splitRespectingParens(hobbiesRaw).map((t2) => t2.trim()).filter(Boolean).map((t2) => t2.length > 35 ? 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)) {
@@ -245651,68 +245651,76 @@ function splitRespectingParens(str2) {
245651
245651
  if (current) result.push(current);
245652
245652
  return result;
245653
245653
  }
245654
- function escapeXml(str2) {
245654
+ function esc(str2) {
245655
245655
  return str2.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
245656
245656
  }
245657
- function buildTextPanel(data, panelH, bgR, bgG, bgB) {
245657
+ function buildTextOverlay(data, overlayW) {
245658
245658
  const S2 = SCALE;
245659
- const padX = 48 * S2;
245660
- const availW = W2 - padX * 2;
245661
- let y2 = 50 * S2;
245662
- const nameY = y2;
245663
- y2 += 56 * S2;
245659
+ const px = 70 * S2;
245660
+ const availW = overlayW - px - 60 * S2;
245664
245661
  const meta = [data.age, data.location].filter(Boolean).join(" \xB7 ");
245662
+ const lines = 1 + (meta ? 1 : 0) + (data.job ? 1 : 0) + (data.tags.length > 0 ? 2 : 0) + (data.vibe ? 1 : 0);
245663
+ const blockH = lines * 44 * S2;
245664
+ let y2 = Math.max(70 * S2, Math.round((H2 - blockH) / 2));
245665
+ const nameY = y2;
245666
+ y2 += 72 * S2;
245665
245667
  const metaY = y2;
245666
- if (meta) y2 += 36 * S2;
245668
+ if (meta) y2 += 44 * S2;
245667
245669
  const jobY = y2;
245668
- if (data.job) y2 += 34 * S2;
245669
- y2 += 16 * S2;
245670
+ if (data.job) y2 += 40 * S2;
245671
+ y2 += 20 * S2;
245670
245672
  const tagStartY = y2;
245671
245673
  let inlineTags = "";
245672
- let offsetX = padX;
245674
+ let offsetX = px;
245673
245675
  let rowY = tagStartY;
245674
- const rowH = 32 * S2;
245676
+ const rowH = 38 * S2;
245675
245677
  let currentRow = 1;
245676
245678
  const maxRows = 2;
245677
- const charW = 6.5 * S2;
245678
- const padW = 18 * S2;
245679
+ const charW = 8 * S2;
245680
+ const padW = 24 * S2;
245679
245681
  for (const tag of data.tags) {
245680
245682
  const maxChars = Math.floor((availW - padW) / charW);
245681
245683
  const label = tag.length > maxChars ? tag.slice(0, maxChars - 1) + "\u2026" : tag;
245682
245684
  const w2 = Math.ceil(label.length * charW + padW);
245683
- if (offsetX + w2 > padX + availW) {
245685
+ if (offsetX + w2 > px + availW) {
245684
245686
  if (currentRow >= maxRows) break;
245685
245687
  currentRow++;
245686
- offsetX = padX;
245688
+ offsetX = px;
245687
245689
  rowY += rowH;
245688
245690
  }
245689
245691
  inlineTags += `
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>
245692
+ <rect x="${offsetX}" y="${rowY - 18 * S2}" width="${w2}" height="${32 * S2}" rx="${16 * S2}" fill="rgba(255,255,255,0.10)" />
245693
+ <text x="${offsetX + 12 * S2}" y="${rowY + 4 * S2}" font-family="system-ui, -apple-system, sans-serif" font-size="${16 * S2}" fill="rgba(255,255,255,0.8)">${esc(label)}</text>
245692
245694
  `;
245693
- offsetX += w2 + 7 * S2;
245695
+ offsetX += w2 + 10 * S2;
245694
245696
  }
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));
245697
+ const vibeY = tagStartY + currentRow * rowH + 32 * S2;
245698
+ const jobMax = Math.floor(availW / (9 * S2));
245699
+ const vibeMax = Math.floor(availW / (8.5 * S2));
245698
245700
  const jobText = data.job.length > jobMax ? data.job.slice(0, jobMax - 1) + "\u2026" : data.job;
245699
245701
  const vibeText = data.vibe.length > vibeMax ? data.vibe.slice(0, vibeMax - 1) + "\u2026" : data.vibe;
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
+ return `<svg width="${overlayW}" height="${H2}" xmlns="http://www.w3.org/2000/svg">
245703
+ <!-- Semi-transparent dark overlay for readability -->
245704
+ <rect width="${overlayW}" height="${H2}" fill="rgba(0,0,0,0.55)" />
245702
245705
 
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>
245706
+ <!-- Name -->
245707
+ <text x="${px}" y="${nameY}" font-family="system-ui, -apple-system, sans-serif" font-size="${60 * S2}" font-weight="bold" fill="white">${esc(data.name)}</text>
245704
245708
 
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>` : ""}
245709
+ <!-- Meta -->
245710
+ ${meta ? `<text x="${px}" y="${metaY}" font-family="system-ui, -apple-system, sans-serif" font-size="${22 * S2}" fill="rgba(255,255,255,0.65)">${esc(meta)}</text>` : ""}
245706
245711
 
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>` : ""}
245712
+ <!-- Job -->
245713
+ ${data.job ? `<text x="${px}" y="${jobY}" font-family="system-ui, -apple-system, sans-serif" font-size="${18 * S2}" fill="rgba(255,255,255,0.45)">${esc(jobText)}</text>` : ""}
245708
245714
 
245715
+ <!-- Tags -->
245709
245716
  ${inlineTags}
245710
245717
 
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>` : ""}
245718
+ <!-- Vibe -->
245719
+ ${data.vibe ? `<text x="${px}" y="${vibeY}" font-family="system-ui, -apple-system, sans-serif" font-size="${18 * S2}" fill="rgba(255,255,255,0.50)" font-style="italic">${esc(vibeText)}</text>` : ""}
245712
245720
 
245713
245721
  <!-- 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>
245722
+ <text x="${px}" y="${H2 - 44 * S2}" font-family="system-ui, -apple-system, sans-serif" font-size="${18 * S2}" font-weight="bold" fill="#ff69b4">Opencrush</text>
245723
+ <text x="${overlayW - 60 * S2}" y="${H2 - 44 * S2}" font-family="system-ui, -apple-system, sans-serif" font-size="${13 * S2}" fill="rgba(255,255,255,0.2)" text-anchor="end">github.com/heloraai/Opencrush</text>
245716
245724
  </svg>`;
245717
245725
  }
245718
245726
  async function generateCard(characterName) {
@@ -245733,39 +245741,34 @@ async function generateCard(characterName) {
245733
245741
  }
245734
245742
  }
245735
245743
  if (!refImagePath) return generateFallbackCard(charDir, characterName, data);
245736
- const { data: colorData } = await sharp(refImagePath).resize(1, 1, { fit: "cover" }).raw().toBuffer({ resolveWithObject: true });
245737
- const [cr2, cg, cb] = [colorData[0], colorData[1], colorData[2]];
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();
245744
+ const portraitW = Math.round(W2 * 0.55);
245745
+ const overlayW = Math.round(W2 * 0.5);
245746
+ const bgBlur = await sharp(refImagePath).resize(W2, H2, { fit: "cover", position: "center" }).blur(50).modulate({ brightness: 0.3, saturation: 0.5 }).png().toBuffer();
245747
+ const portrait = await sharp(refImagePath).resize(portraitW, H2, { fit: "cover", position: "top" }).png().toBuffer();
245742
245748
  const fadeMask = Buffer.from(
245743
- `<svg width="${W2}" height="${portraitH}">
245749
+ `<svg width="${portraitW}" height="${H2}">
245744
245750
  <defs>
245745
- <linearGradient id="fade" x1="0" y1="0" x2="0" y2="1">
245751
+ <linearGradient id="fade" x1="0" y1="0" x2="1" y2="0">
245746
245752
  <stop offset="0%" stop-color="white" />
245747
- <stop offset="80%" stop-color="white" />
245753
+ <stop offset="65%" stop-color="white" />
245748
245754
  <stop offset="100%" stop-color="black" />
245749
245755
  </linearGradient>
245750
245756
  </defs>
245751
- <rect width="${W2}" height="${portraitH}" fill="url(#fade)" />
245757
+ <rect width="${portraitW}" height="${H2}" fill="url(#fade)" />
245752
245758
  </svg>`
245753
245759
  );
245754
245760
  const maskedPortrait = await sharp(portrait).composite([{ input: fadeMask, blend: "dest-in" }]).png().toBuffer();
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([
245761
+ const textSvg = buildTextOverlay(data, overlayW);
245762
+ const textOverlay = await sharp(Buffer.from(textSvg)).resize(overlayW, H2).png().toBuffer();
245763
+ const card = sharp(bgBlur).composite([
245761
245764
  { input: maskedPortrait, left: 0, top: 0 },
245762
- { input: textPanel, left: 0, top: portraitH }
245765
+ { input: textOverlay, left: W2 - overlayW, top: 0 }
245763
245766
  ]);
245764
245767
  const outputPath = (0, import_path12.join)(charDir, "card.png");
245765
245768
  await card.png({ quality: 95 }).toFile(outputPath);
245766
245769
  console.log(source_default.green(`
245767
245770
  Card generated: characters/${characterName}/card.png`));
245768
- console.log(source_default.gray(` ${W2}x${H2} PNG (9:16)
245771
+ console.log(source_default.gray(` ${W2}x${H2} PNG (16:9)
245769
245772
  `));
245770
245773
  return outputPath;
245771
245774
  }
@@ -245773,13 +245776,13 @@ async function generateFallbackCard(charDir, characterName, data) {
245773
245776
  const sharp = (await import("sharp")).default;
245774
245777
  const S2 = SCALE;
245775
245778
  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">
245779
+ <defs><linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
245777
245780
  <stop offset="0%" stop-color="#1a1a2e" /><stop offset="100%" stop-color="#0f0f1a" />
245778
245781
  </linearGradient></defs>
245779
245782
  <rect width="${W2}" height="${H2}" fill="url(#bg)" />
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>
245781
- <text x="${W2 / 2}" y="${H2 / 2 + 30 * S2}" font-family="system-ui" font-size="${16 * S2}" fill="#999" text-anchor="middle">${escapeXml([data.age, data.location, data.job].filter(Boolean).join(" \xB7 "))}</text>
245782
- <text x="${36 * S2}" y="${H2 - 20 * S2}" font-family="system-ui" font-size="${14 * S2}" font-weight="bold" fill="#ff69b4">Opencrush</text>
245783
+ <text x="${W2 / 2}" y="${H2 / 2 - 20 * S2}" font-family="system-ui" font-size="${60 * S2}" font-weight="bold" fill="white" text-anchor="middle">${esc(data.name)}</text>
245784
+ <text x="${W2 / 2}" y="${H2 / 2 + 50 * S2}" font-family="system-ui" font-size="${20 * S2}" fill="#999" text-anchor="middle">${esc([data.age, data.location, data.job].filter(Boolean).join(" \xB7 "))}</text>
245785
+ <text x="${40 * S2}" y="${H2 - 30 * S2}" font-family="system-ui" font-size="${18 * S2}" font-weight="bold" fill="#ff69b4">Opencrush</text>
245783
245786
  </svg>`;
245784
245787
  const outputPath = (0, import_path12.join)(charDir, "card.png");
245785
245788
  await sharp(Buffer.from(svg)).resize(W2, H2).png().toFile(outputPath);
@@ -245797,8 +245800,8 @@ var init_card = __esm({
245797
245800
  import_gray_matter3 = __toESM(require_gray_matter());
245798
245801
  init_source();
245799
245802
  SCALE = 2;
245800
- W2 = 1080 * SCALE;
245801
- H2 = 1920 * SCALE;
245803
+ W2 = 1920 * SCALE;
245804
+ H2 = 1080 * SCALE;
245802
245805
  }
245803
245806
  });
245804
245807
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencrush",
3
- "version": "0.3.20",
3
+ "version": "0.3.21",
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"