vipcare 0.6.4 โ†’ 0.7.0

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/lib/card.js +110 -22
  2. package/package.json +1 -1
package/lib/card.js CHANGED
@@ -49,6 +49,25 @@ export function generateCards(profiles, outputPath) {
49
49
  if (noteLines.length) lastConnection = noteLines[noteLines.length - 1];
50
50
  }
51
51
 
52
+ // Extract Recent Activity section
53
+ const recentMatch = content.match(/## Recent Activity\n([\s\S]*?)(?=\n---|\n## [A-Z])/);
54
+ const recentActivity = recentMatch ? recentMatch[1].trim() : '';
55
+
56
+ // Extract Key Quotes section
57
+ const quotesMatch = content.match(/## Key Quotes\n([\s\S]*?)(?=\n---|\n## [A-Z])/);
58
+ const rawQuotes = quotesMatch ? quotesMatch[1].trim() : '';
59
+
60
+ // Extract Core Philosophy section
61
+ const philoMatch = content.match(/## Core Philosophy\n([\s\S]*?)(?=\n---|\n## [A-Z])/);
62
+ const rawPhilosophy = philoMatch ? philoMatch[1].trim() : '';
63
+
64
+ // Load raw Twitter data if available
65
+ const twitterRawFile = path.join(path.dirname(p.path), '.raw', p.slug, `twitter_${twitterHandle || p.slug}.md`);
66
+ let rawTweets = '';
67
+ if (fs.existsSync(twitterRawFile)) {
68
+ rawTweets = fs.readFileSync(twitterRawFile, 'utf-8');
69
+ }
70
+
52
71
  const data = extractVipData(content);
53
72
  if (!data) {
54
73
  const nameMatch = content.match(/^# (.+)$/m);
@@ -78,6 +97,10 @@ export function generateCards(profiles, outputPath) {
78
97
  updated: p.updated,
79
98
  annotations,
80
99
  last_connection: lastConnection,
100
+ recent_activity: recentActivity,
101
+ raw_quotes: rawQuotes,
102
+ raw_philosophy: rawPhilosophy,
103
+ raw_tweets: rawTweets,
81
104
  });
82
105
  continue;
83
106
  }
@@ -87,6 +110,10 @@ export function generateCards(profiles, outputPath) {
87
110
  data.twitter_handle = data.twitter_handle || twitterHandle;
88
111
  data.annotations = annotations;
89
112
  data.last_connection = lastConnection;
113
+ data.recent_activity = recentActivity;
114
+ data.raw_quotes = rawQuotes;
115
+ data.raw_philosophy = rawPhilosophy;
116
+ data.raw_tweets = rawTweets;
90
117
  cards.push(data);
91
118
  }
92
119
 
@@ -222,6 +249,16 @@ header .count { color: #94a3b8; font-size: 0.85em; }
222
249
  .panel p, .panel li { color: #475569; font-size: 0.85em; line-height: 1.6; }
223
250
  .panel ul { padding-left: 16px; }
224
251
  .panel blockquote { border-left: 3px solid #2563eb; padding: 4px 12px; margin: 6px 0; color: #475569; font-style: italic; font-size: 0.85em; }
252
+ .tabs { display: flex; border-bottom: 2px solid #e2e8f0; margin: 0 -24px 16px; padding: 0 24px; }
253
+ .tab { padding: 8px 16px; font-size: 0.85em; font-weight: 600; color: #94a3b8; cursor: pointer; border-bottom: 2px solid transparent; margin-bottom: -2px; transition: all 0.2s; }
254
+ .tab:hover { color: #475569; }
255
+ .tab.active { color: #2563eb; border-bottom-color: #2563eb; }
256
+ .tab-content { display: none; }
257
+ .tab-content.active { display: block; }
258
+ .tweet { background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 8px; padding: 10px 12px; margin: 8px 0; font-size: 0.8em; color: #475569; line-height: 1.5; }
259
+ .tweet-date { font-size: 0.7em; color: #94a3b8; margin-top: 4px; }
260
+ .activity-item { padding: 8px 0; border-bottom: 1px solid #f1f5f9; font-size: 0.85em; }
261
+ .activity-date { font-size: 0.75em; color: #94a3b8; }
225
262
  .radar { max-width: 100%; }
226
263
 
227
264
  /* Mobile */
@@ -346,13 +383,46 @@ function openPanel(index) {
346
383
  const panel = document.getElementById('panel-content');
347
384
  const avatarUrl = \`https://unavatar.io/twitter/\${card.twitter_handle || card.slug || 'unknown'}\`;
348
385
 
386
+ // Parse raw tweets into individual tweet objects
387
+ const tweets = [];
388
+ if (card.raw_tweets) {
389
+ const tweetBlocks = card.raw_tweets.split(/โ”€{5,}/);
390
+ for (const block of tweetBlocks) {
391
+ const textMatch = block.match(/:\n([\s\S]*?)(?=๐Ÿ“…|$)/);
392
+ const dateMatch = block.match(/๐Ÿ“…\s*(.+)/);
393
+ const linkMatch = block.match(/๐Ÿ”—\s*(https?:\/\/\S+)/);
394
+ if (textMatch && textMatch[1].trim()) {
395
+ tweets.push({
396
+ text: textMatch[1].trim().substring(0, 280),
397
+ date: dateMatch ? dateMatch[1].trim() : '',
398
+ link: linkMatch ? linkMatch[1] : '',
399
+ });
400
+ }
401
+ }
402
+ }
403
+
404
+ // Parse recent activity into items
405
+ const activityItems = [];
406
+ if (card.recent_activity) {
407
+ const lines = card.recent_activity.split('\n').filter(l => l.trim());
408
+ for (const line of lines) {
409
+ const dateMatch = line.match(/###?\s*(\d{4}-\d{2}-\d{2}|\w+\s+\d+)/);
410
+ if (dateMatch) {
411
+ activityItems.push({ date: dateMatch[1], text: '' });
412
+ } else if (activityItems.length) {
413
+ activityItems[activityItems.length - 1].text += line.replace(/^[-โ€ข*]\s*/, '').trim() + ' ';
414
+ } else {
415
+ activityItems.push({ date: '', text: line.replace(/^[-โ€ข*]\s*/, '').trim() });
416
+ }
417
+ }
418
+ }
419
+
349
420
  panel.innerHTML = \`
350
- <div style="display:flex;gap:14px;align-items:center;margin-bottom:16px">
421
+ <div style="display:flex;gap:14px;align-items:center;margin-bottom:12px">
351
422
  <img src="\${avatarUrl}" alt="" style="width:56px;height:56px;border-radius:50%;object-fit:cover;border:2px solid #e2e8f0" onerror="this.style.display='none'">
352
423
  <div>
353
424
  <div style="font-size:1.3em;font-weight:700;color:#0f172a">\${card.name}</div>
354
425
  <div style="font-size:0.85em;color:#64748b">\${card.title || ''}\${card.company ? ' @ ' + card.company : ''}</div>
355
- \${card.previous_role ? \`<div style="font-size:0.75em;color:#94a3b8">Previously: \${card.previous_role}</div>\` : ''}
356
426
  <div style="margin-top:4px">
357
427
  \${card.twitter_handle ? \`<a href="https://twitter.com/\${card.twitter_handle}" target="_blank" style="color:#2563eb;font-size:0.8em;text-decoration:none;margin-right:8px">@\${card.twitter_handle}</a>\` : ''}
358
428
  <span class="badge \${mbtiClass(card.mbti)}" title="\${card.mbti_reason || ''}">\${card.mbti || '?'}</span>
@@ -360,34 +430,45 @@ function openPanel(index) {
360
430
  </div>
361
431
  </div>
362
432
 
363
- \${card.latest_news ? \`<div class="news-chip" style="margin-bottom:12px">๐Ÿ“ฐ \${card.latest_news}</div>\` : ''}
433
+ <div class="tabs">
434
+ <div class="tab active" onclick="switchTab('recent',this)">Latest</div>
435
+ <div class="tab" onclick="switchTab('facts',this)">Facts & Approach</div>
436
+ </div>
437
+
438
+ <!-- RECENT TAB -->
439
+ <div class="tab-content active" id="tab-recent">
440
+ \${card.latest_news ? \`<div class="news-chip" style="margin-bottom:12px">๐Ÿ“ฐ \${card.latest_news}</div>\` : ''}
441
+
442
+ \${tweets.length ? \`<h2>Latest Tweets</h2>\${tweets.slice(0,5).map(t => \`<div class="tweet">\${t.text}\${t.date ? \`<div class="tweet-date">\${t.date}\${t.link ? \` ยท <a href="\${t.link}" target="_blank" style="color:#2563eb">link</a>\` : ''}</div>\` : ''}</div>\`).join('')}\` : ''}
443
+
444
+ \${activityItems.length ? \`<h2>Recent Moves</h2>\${activityItems.slice(0,8).map(a => \`<div class="activity-item">\${a.date ? \`<span class="activity-date">\${a.date}</span> ยท \` : ''}\${a.text.trim()}</div>\`).join('')}\` : ''}
364
445
 
365
- <h2>Focus & Goals</h2>
366
- \${card.current_focus ? \`<p>๐ŸŽฏ \${card.current_focus}</p>\` : ''}
367
- \${card.wants ? \`<p>๐Ÿ’Ž \${card.wants}</p>\` : ''}
446
+ \${card.key_quotes?.length ? \`<h2>In Their Words</h2>\${card.key_quotes.map(q => \`<blockquote>"\${q}"</blockquote>\`).join('')}\` : ''}
368
447
 
369
- \${card.philosophy?.length ? \`<h2>Philosophy</h2>\${card.philosophy.map(p => \`<blockquote>"\${p}"</blockquote>\`).join('')}\` : ''}
448
+ \${card.annotations?.length ? \`<h2>Your Notes</h2>\${card.annotations.map(a => \`<p style="font-size:0.8em;color:#64748b">๐Ÿ“ \${a}</p>\`).join('')}\` : ''}
449
+ </div>
370
450
 
371
- \${card.talking_points?.length ? \`<h2>Talking Points</h2><ul>\${card.talking_points.map(t => \`<li>\${t}</li>\`).join('')}</ul>\` : ''}
451
+ <!-- FACTS TAB -->
452
+ <div class="tab-content" id="tab-facts">
453
+ <h2>Focus & Goals</h2>
454
+ \${card.current_focus ? \`<p>๐ŸŽฏ \${card.current_focus}</p>\` : ''}
455
+ \${card.wants ? \`<p>๐Ÿ’Ž \${card.wants}</p>\` : ''}
372
456
 
373
- <h2>Approach</h2>
374
- \${card.last_connection ? \`<p>๐Ÿค <strong>Last:</strong> \${card.last_connection}</p>\` : '<p style="color:#94a3b8">No interactions yet</p>'}
375
- \${card.dos?.length || card.donts?.length ? \`<div class="do-dont">\${card.dos?.length ? \`<div class="do-box"><strong>โœ… Do</strong><br>\${card.dos.join('<br>')}</div>\` : ''}\${card.donts?.length ? \`<div class="dont-box"><strong>โŒ Don't</strong><br>\${card.donts.join('<br>')}</div>\` : ''}</div>\` : ''}
376
- \${card.gifts?.length ? \`<p style="font-size:0.85em">๐ŸŽ \${card.gifts.join(', ')}</p>\` : ''}
457
+ \${card.philosophy?.length ? \`<h2>Philosophy</h2>\${card.philosophy.map(p => \`<blockquote>"\${p}"</blockquote>\`).join('')}\` : ''}
377
458
 
378
- \${card.allies?.length || card.rivals?.length ? \`<h2>Relationships</h2>
379
- \${card.allies?.length ? \`<div style="margin-bottom:8px">\${card.allies.map(a => \`<p style="font-size:0.85em;color:#166534">๐Ÿ‘ <strong>\${a.name || a}</strong>\${a.reason ? \` โ€” \${a.reason}\` : ''}</p>\`).join('')}</div>\` : ''}
380
- \${card.rivals?.length ? \`<div>\${card.rivals.map(r => \`<p style="font-size:0.85em;color:#991b1b">๐Ÿ‘Ž <strong>\${r.name || r}</strong>\${r.reason ? \` โ€” \${r.reason}\` : ''}</p>\`).join('')}</div>\` : ''}
381
- \` : ''}
459
+ \${card.talking_points?.length ? \`<h2>Talking Points</h2><ul>\${card.talking_points.map(t => \`<li>\${t}</li>\`).join('')}</ul>\` : ''}
382
460
 
383
- \${card.key_quotes?.length ? \`<h2>Quotes</h2>\${card.key_quotes.slice(0,4).map(q => \`<p style="font-size:0.8em;color:#475569;margin:3px 0">"\${q}"</p>\`).join('')}\` : ''}
461
+ <h2>Approach</h2>
462
+ \${card.last_connection ? \`<p>๐Ÿค <strong>Last:</strong> \${card.last_connection}</p>\` : '<p style="color:#94a3b8">No interactions yet</p>'}
463
+ \${card.dos?.length || card.donts?.length ? \`<div class="do-dont">\${card.dos?.length ? \`<div class="do-box"><strong>โœ… Do</strong><br>\${card.dos.join('<br>')}</div>\` : ''}\${card.donts?.length ? \`<div class="dont-box"><strong>โŒ Don't</strong><br>\${card.donts.join('<br>')}</div>\` : ''}</div>\` : ''}
464
+ \${card.gifts?.length ? \`<p style="font-size:0.85em">๐ŸŽ \${card.gifts.join(', ')}</p>\` : ''}
384
465
 
385
- <h2>Personality</h2>
386
- <p style="font-size:0.85em"><strong>MBTI:</strong> \${card.mbti || '?'}\${card.mbti_reason ? \` โ€” \${card.mbti_reason}\` : ''}</p>
387
- \${card.superpower ? \`<p style="font-size:0.85em">โšก \${card.superpower}</p>\` : ''}
388
- <div style="display:flex;justify-content:center;margin:12px 0">\${radarSvg(s, 220)}</div>
466
+ \${card.allies?.length || card.rivals?.length ? \`<h2>Relationships</h2>
467
+ \${card.allies?.length ? \`<div style="margin-bottom:8px">\${card.allies.map(a => \`<p style="font-size:0.85em;color:#166534">๐Ÿ‘ <strong>\${a.name || a}</strong>\${a.reason ? \` โ€” \${a.reason}\` : ''}</p>\`).join('')}</div>\` : ''}
468
+ \${card.rivals?.length ? \`<div>\${card.rivals.map(r => \`<p style="font-size:0.85em;color:#991b1b">๐Ÿ‘Ž <strong>\${r.name || r}</strong>\${r.reason ? \` โ€” \${r.reason}\` : ''}</p>\`).join('')}</div>\` : ''}
469
+ \` : ''}
389
470
 
390
- \${card.annotations?.length ? \`<h2>Your Notes</h2>\${card.annotations.map(a => \`<p style="font-size:0.8em;color:#64748b">๐Ÿ“ \${a}</p>\`).join('')}\` : ''}
471
+ </div>
391
472
 
392
473
  <div style="display:flex;gap:8px;margin-top:20px">
393
474
  \${card.slug ? \`<a href="\${card.slug}.html" style="flex:1;text-align:center;color:#2563eb;text-decoration:none;padding:8px;border:1px solid #2563eb;border-radius:6px;font-size:0.85em">Full Profile</a>\` : ''}
@@ -399,6 +480,13 @@ function openPanel(index) {
399
480
  document.getElementById('main').classList.add('panel-open');
400
481
  }
401
482
 
483
+ function switchTab(tabId, el) {
484
+ document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
485
+ document.querySelectorAll('.tab-content').forEach(t => t.classList.remove('active'));
486
+ el.classList.add('active');
487
+ document.getElementById('tab-' + tabId).classList.add('active');
488
+ }
489
+
402
490
  function closePanel() {
403
491
  document.getElementById('panel').classList.remove('open');
404
492
  document.getElementById('main').classList.remove('panel-open');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vipcare",
3
- "version": "0.6.4",
3
+ "version": "0.7.0",
4
4
  "description": "Auto-build VIP person profiles from Twitter/LinkedIn public data",
5
5
  "type": "module",
6
6
  "bin": {