opencrush 0.3.19 → 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 +55 -71
  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 > 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,84 +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 buildTextOverlay(data, bgR = 20, bgG = 20, bgB = 30) {
245657
+ function buildTextOverlay(data, overlayW) {
245658
245658
  const S2 = SCALE;
245659
- const textX = 44 * S2;
245660
- const panelW = 480 * S2;
245661
- const availW = panelW - textX - 30 * S2;
245659
+ const px = 70 * S2;
245660
+ const availW = overlayW - px - 60 * S2;
245662
245661
  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;
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));
245667
245665
  const nameY = y2;
245668
- y2 += 50 * S2;
245666
+ y2 += 72 * S2;
245669
245667
  const metaY = y2;
245670
- if (meta) y2 += 32 * S2;
245668
+ if (meta) y2 += 44 * S2;
245671
245669
  const jobY = y2;
245672
- if (data.job) y2 += 30 * S2;
245673
- y2 += 12 * S2;
245670
+ if (data.job) y2 += 40 * S2;
245671
+ y2 += 20 * S2;
245674
245672
  const tagStartY = y2;
245675
245673
  let inlineTags = "";
245676
- let offsetX = textX;
245674
+ let offsetX = px;
245677
245675
  let rowY = tagStartY;
245678
- const rowH = 28 * S2;
245676
+ const rowH = 38 * S2;
245679
245677
  let currentRow = 1;
245680
245678
  const maxRows = 2;
245681
- const charW = 6.2 * S2;
245682
- const padW = 16 * S2;
245679
+ const charW = 8 * S2;
245680
+ const padW = 24 * S2;
245683
245681
  for (const tag of data.tags) {
245684
245682
  const maxChars = Math.floor((availW - padW) / charW);
245685
245683
  const label = tag.length > maxChars ? tag.slice(0, maxChars - 1) + "\u2026" : tag;
245686
245684
  const w2 = Math.ceil(label.length * charW + padW);
245687
- if (offsetX + w2 > textX + availW) {
245685
+ if (offsetX + w2 > px + availW) {
245688
245686
  if (currentRow >= maxRows) break;
245689
245687
  currentRow++;
245690
- offsetX = textX;
245688
+ offsetX = px;
245691
245689
  rowY += rowH;
245692
245690
  }
245693
245691
  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>
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>
245696
245694
  `;
245697
- offsetX += w2 + 6 * S2;
245695
+ offsetX += w2 + 10 * S2;
245698
245696
  }
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));
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));
245702
245700
  const jobText = data.job.length > jobMax ? data.job.slice(0, jobMax - 1) + "\u2026" : data.job;
245703
245701
  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)" />
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)" />
245713
245705
 
245714
245706
  <!-- 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>
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>
245716
245708
 
245717
245709
  <!-- 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>` : ""}
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>` : ""}
245719
245711
 
245720
245712
  <!-- 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>` : ""}
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>` : ""}
245722
245714
 
245723
245715
  <!-- Tags -->
245724
245716
  ${inlineTags}
245725
245717
 
245726
245718
  <!-- 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>` : ""}
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>` : ""}
245728
245720
 
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>
245721
+ <!-- Branding -->
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>
245732
245724
  </svg>`;
245733
245725
  }
245734
245726
  async function generateCard(characterName) {
@@ -245748,20 +245740,13 @@ async function generateCard(characterName) {
245748
245740
  break;
245749
245741
  }
245750
245742
  }
245751
- if (!refImagePath) {
245752
- return generateFallbackCard(charDir, characterName, data);
245753
- }
245754
- const { data: colorData } = await sharp(refImagePath).resize(1, 1, { fit: "cover" }).raw().toBuffer({ resolveWithObject: true });
245755
- 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();
245743
+ if (!refImagePath) return generateFallbackCard(charDir, characterName, data);
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();
245763
245748
  const fadeMask = Buffer.from(
245764
- `<svg width="${portraitW}" height="${portraitH}">
245749
+ `<svg width="${portraitW}" height="${H2}">
245765
245750
  <defs>
245766
245751
  <linearGradient id="fade" x1="0" y1="0" x2="1" y2="0">
245767
245752
  <stop offset="0%" stop-color="white" />
@@ -245769,39 +245754,38 @@ async function generateCard(characterName) {
245769
245754
  <stop offset="100%" stop-color="black" />
245770
245755
  </linearGradient>
245771
245756
  </defs>
245772
- <rect width="${portraitW}" height="${portraitH}" fill="url(#fade)" />
245757
+ <rect width="${portraitW}" height="${H2}" fill="url(#fade)" />
245773
245758
  </svg>`
245774
245759
  );
245775
245760
  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([
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([
245780
245764
  { input: maskedPortrait, left: 0, top: 0 },
245781
- { input: textPanel, left: W2 - panelW, top: 0 }
245765
+ { input: textOverlay, left: W2 - overlayW, top: 0 }
245782
245766
  ]);
245783
245767
  const outputPath = (0, import_path12.join)(charDir, "card.png");
245784
245768
  await card.png({ quality: 95 }).toFile(outputPath);
245785
245769
  console.log(source_default.green(`
245786
245770
  Card generated: characters/${characterName}/card.png`));
245787
- console.log(source_default.gray(` ${W2}x${H2} PNG
245771
+ console.log(source_default.gray(` ${W2}x${H2} PNG (16:9)
245788
245772
  `));
245789
245773
  return outputPath;
245790
245774
  }
245791
245775
  async function generateFallbackCard(charDir, characterName, data) {
245792
245776
  const sharp = (await import("sharp")).default;
245793
245777
  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" />
245778
+ const svg = `<svg width="${W2}" height="${H2}" xmlns="http://www.w3.org/2000/svg">
245779
+ <defs><linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
245780
+ <stop offset="0%" stop-color="#1a1a2e" /><stop offset="100%" stop-color="#0f0f1a" />
245797
245781
  </linearGradient></defs>
245798
245782
  <rect width="${W2}" height="${H2}" fill="url(#bg)" />
245799
- <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>
245800
- <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>
245801
- <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>
245802
245786
  </svg>`;
245803
245787
  const outputPath = (0, import_path12.join)(charDir, "card.png");
245804
- await sharp(Buffer.from(gradient)).resize(W2, H2).png().toFile(outputPath);
245788
+ await sharp(Buffer.from(svg)).resize(W2, H2).png().toFile(outputPath);
245805
245789
  console.log(source_default.green(`
245806
245790
  Card generated: characters/${characterName}/card.png`));
245807
245791
  console.log(source_default.gray(` ${W2}x${H2} PNG (no reference image)
@@ -245816,8 +245800,8 @@ var init_card = __esm({
245816
245800
  import_gray_matter3 = __toESM(require_gray_matter());
245817
245801
  init_source();
245818
245802
  SCALE = 2;
245819
- W2 = 1200 * SCALE;
245820
- H2 = 960 * SCALE;
245803
+ W2 = 1920 * SCALE;
245804
+ H2 = 1080 * SCALE;
245821
245805
  }
245822
245806
  });
245823
245807
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencrush",
3
- "version": "0.3.19",
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"