you-wrapped 0.3.0 → 0.3.1
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/SKILL.md +8 -2
- package/bin/cli.mjs +33 -7
- package/package.json +1 -1
- package/src/render.mjs +45 -13
package/SKILL.md
CHANGED
|
@@ -85,8 +85,14 @@ Write the portrait as JSON matching the schema in `prompt.md`, then:
|
|
|
85
85
|
npx you-wrapped --render portrait.json --out ./wrapped
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
Writes `wrapped.html` **and** the redacted `wrapped.share.html`, then opens the first in
|
|
89
|
+
their browser automatically. Pass `--no-open` to suppress that.
|
|
90
|
+
|
|
91
|
+
Tell them to read the share copy before posting it — they should verify their own scrub.
|
|
92
|
+
Redaction removes emails, phones, addresses, links, tokens, coordinates, and every name
|
|
93
|
+
harvested from their contacts. It does **not** catch place names or company names you
|
|
94
|
+
wrote into the prose yourself — so keep those out of the portrait text if they'd identify
|
|
95
|
+
someone, or tell the user which ones remain.
|
|
90
96
|
|
|
91
97
|
## Phase 5 — offer to go deeper (every machine is different)
|
|
92
98
|
|
package/bin/cli.mjs
CHANGED
|
@@ -34,6 +34,7 @@ ${c.b}you-wrapped${c.x} — a portrait of your year, from the record your Mac al
|
|
|
34
34
|
--days N how far back to read (default 400)
|
|
35
35
|
--out DIR where to write (default ./you-wrapped-out)
|
|
36
36
|
--no-share skip the redacted share copy
|
|
37
|
+
--no-open don't open the result in your browser
|
|
37
38
|
--yes skip prompts
|
|
38
39
|
--no-launch don't hand off to Claude Code when done
|
|
39
40
|
|
|
@@ -78,17 +79,42 @@ if (has('--install-skill')) {
|
|
|
78
79
|
process.exit(0);
|
|
79
80
|
}
|
|
80
81
|
|
|
81
|
-
// ── render-only path
|
|
82
|
+
// ── render-only path (this is what the Claude Code skill calls)
|
|
82
83
|
if (has('--render')) {
|
|
83
84
|
const p = JSON.parse(readFileSync(resolve(val('--render')), 'utf8'));
|
|
84
|
-
|
|
85
|
-
|
|
85
|
+
mkdirSync(OUT, { recursive: true });
|
|
86
|
+
|
|
87
|
+
// Pull signals from alongside the portrait so charts and the stat line are real.
|
|
88
|
+
let src = p.__signals;
|
|
89
|
+
if (!src) {
|
|
86
90
|
const sp = join(OUT, 'signals.json');
|
|
87
|
-
if (existsSync(sp)) { try {
|
|
91
|
+
if (existsSync(sp)) { try { src = JSON.parse(readFileSync(sp, 'utf8')).sources; } catch {} }
|
|
88
92
|
}
|
|
89
|
-
|
|
90
|
-
|
|
93
|
+
p.__signals = src || {};
|
|
94
|
+
const sm = p.__signals.messages || {};
|
|
95
|
+
const line = [
|
|
96
|
+
sm.sent && `${sm.sent.toLocaleString()} messages you sent`,
|
|
97
|
+
p.__signals.notes?.count && `${p.__signals.notes.count.toLocaleString()} notes`,
|
|
98
|
+
p.__signals.browser && `${((p.__signals.browser.desktopQueries || 0) + (p.__signals.browser.mobileQueries || 0)).toLocaleString()} searches`,
|
|
99
|
+
p.__signals.photos?.geotagged && `${p.__signals.photos.geotagged.toLocaleString()} geotagged frames`,
|
|
100
|
+
].filter(Boolean).join(' · ');
|
|
101
|
+
const stats = { line, footer: line ? `Assembled ${new Date().toISOString().slice(0, 10)} from ${line}.` : '' };
|
|
102
|
+
|
|
103
|
+
writeFileSync(join(OUT, 'wrapped.html'), render(p, { stats }));
|
|
91
104
|
log(`${c.g}✓${c.x} ${join(OUT, 'wrapped.html')}`);
|
|
105
|
+
|
|
106
|
+
// The share copy is promised everywhere else — produce it here too.
|
|
107
|
+
if (!has('--no-share')) {
|
|
108
|
+
const names = harvestNames(null, sm.topPeople, sm.topChats);
|
|
109
|
+
const shared = { ...scrubDeep(p, names), __signals: p.__signals };
|
|
110
|
+
writeFileSync(join(OUT, 'wrapped.share.html'), render(shared, { shareMode: true, stats }));
|
|
111
|
+
log(`${c.g}✓${c.x} ${join(OUT, 'wrapped.share.html')} ${c.d}(redacted)${c.x}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!has('--no-open')) {
|
|
115
|
+
try { execFileSync('open', [join(OUT, 'wrapped.html')]); log(`${c.d} opened in your browser${c.x}`); }
|
|
116
|
+
catch { log(`${c.d} open it: ${join(OUT, 'wrapped.html')}${c.x}`); }
|
|
117
|
+
}
|
|
92
118
|
process.exit(0);
|
|
93
119
|
}
|
|
94
120
|
|
|
@@ -231,7 +257,7 @@ if (portrait) {
|
|
|
231
257
|
const shared = { ...scrubDeep(portrait, names), __signals: profile.sources };
|
|
232
258
|
writeFileSync(join(OUT, 'wrapped.share.html'), render(shared, { shareMode: true, stats: { line: statLine, footer: stats.footer } }));
|
|
233
259
|
}
|
|
234
|
-
try { execFileSync('open', [join(OUT, 'wrapped.html')]); } catch {}
|
|
260
|
+
if (!has('--no-open')) { try { execFileSync('open', [join(OUT, 'wrapped.html')]); } catch {} }
|
|
235
261
|
}
|
|
236
262
|
|
|
237
263
|
// ── done
|
package/package.json
CHANGED
package/src/render.mjs
CHANGED
|
@@ -234,7 +234,7 @@ export function render(p, { shareMode = false, stats = {} } = {}) {
|
|
|
234
234
|
|
|
235
235
|
const slides = cards.map((c, i) => {
|
|
236
236
|
const t = THEMES[i % THEMES.length];
|
|
237
|
-
return `<section class="card
|
|
237
|
+
return `<section class="card k-${c.kind}" data-i="${i}"
|
|
238
238
|
style="--bg:${t.bg};--fg:${t.fg};--ac:${t.ac}">
|
|
239
239
|
<div class="inner">${c.html}</div></section>`;
|
|
240
240
|
}).join('\n');
|
|
@@ -265,15 +265,18 @@ body{font-family:var(--sans);-webkit-font-smoothing:antialiased;overscroll-behav
|
|
|
265
265
|
}
|
|
266
266
|
|
|
267
267
|
.card{position:absolute;inset:0;background:var(--bg);color:var(--fg);
|
|
268
|
-
|
|
268
|
+
overflow-y:auto;overflow-x:hidden;-webkit-overflow-scrolling:touch;
|
|
269
|
+
overscroll-behavior:contain;scrollbar-width:none;
|
|
269
270
|
opacity:0;pointer-events:none;transform:scale(1.03);
|
|
270
271
|
transition:opacity .42s ease,transform .52s cubic-bezier(.2,.75,.3,1)}
|
|
272
|
+
.card::-webkit-scrollbar{display:none}
|
|
271
273
|
.card.on{opacity:1;pointer-events:auto;transform:none}
|
|
272
274
|
/* paper grain — keeps flat color from looking like a CSS default */
|
|
273
|
-
.
|
|
275
|
+
.inner::after{content:"";position:absolute;inset:0;pointer-events:none;opacity:.055;z-index:-1;
|
|
274
276
|
background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='120' height='120'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/></filter><rect width='120' height='120' filter='url(%23n)'/></svg>")}
|
|
275
|
-
.inner{position:relative;z-index:1;width:100%;
|
|
276
|
-
|
|
277
|
+
.inner{position:relative;z-index:1;width:100%;min-height:100%;
|
|
278
|
+
display:flex;flex-direction:column;justify-content:center;
|
|
279
|
+
padding:calc(var(--pad) + 34px) var(--pad) calc(var(--pad) + 26px)}
|
|
277
280
|
|
|
278
281
|
.eyebrow{font-family:var(--mono);font-size:11px;letter-spacing:.18em;text-transform:uppercase;
|
|
279
282
|
opacity:.62;margin-bottom:20px}
|
|
@@ -289,6 +292,14 @@ blockquote::before{content:"“"}blockquote::after{content:"”"}
|
|
|
289
292
|
.sub{font-family:var(--mono);font-size:12.5px;line-height:1.7;margin-top:26px;opacity:.72;max-width:32ch}
|
|
290
293
|
.small{font-size:14.5px;line-height:1.55;margin-top:16px;opacity:.72;max-width:38ch}
|
|
291
294
|
.cite{font-family:var(--mono);font-size:11.5px;margin-top:18px;opacity:.6}
|
|
295
|
+
#deck[data-more="1"]::after{content:"";position:absolute;left:0;right:0;bottom:0;height:78px;
|
|
296
|
+
z-index:6;pointer-events:none;
|
|
297
|
+
background:linear-gradient(to top,var(--fade) 10%,color-mix(in srgb,var(--fade) 65%,transparent) 55%,transparent)}
|
|
298
|
+
.more{position:absolute;right:20px;bottom:14px;z-index:7;color:var(--fadefg);
|
|
299
|
+
font-family:var(--mono);font-size:10px;letter-spacing:.14em;text-transform:uppercase;
|
|
300
|
+
opacity:0;transition:opacity .25s;pointer-events:none;display:flex;align-items:center;gap:6px}
|
|
301
|
+
#deck[data-more="1"] .more{opacity:.75;animation:bob 1.8s ease-in-out infinite}
|
|
302
|
+
@keyframes bob{0%,100%{transform:translateY(0)}50%{transform:translateY(3px)}}
|
|
292
303
|
.hint{position:absolute;left:var(--pad);bottom:calc(var(--pad) + 4px);font-family:var(--mono);font-size:11px;
|
|
293
304
|
letter-spacing:.16em;text-transform:uppercase;opacity:.5;animation:pulse 2.4s ease-in-out infinite}
|
|
294
305
|
@keyframes pulse{0%,100%{opacity:.28}50%{opacity:.72}}
|
|
@@ -326,8 +337,8 @@ blockquote::before{content:"“"}blockquote::after{content:"”"}
|
|
|
326
337
|
#deck.dark #bars i{background:rgba(0,0,0,.22)}
|
|
327
338
|
#deck.dark #bars i.done,#deck.dark #bars i.now{background:rgba(0,0,0,.82)}
|
|
328
339
|
|
|
329
|
-
.tapzone{position:absolute;top:0;bottom:0;width:
|
|
330
|
-
.tapzone.l{left:0}.tapzone.r{right:0;width:
|
|
340
|
+
.tapzone{position:absolute;top:0;bottom:0;width:20%;z-index:8;cursor:pointer}
|
|
341
|
+
.tapzone.l{left:0}.tapzone.r{right:0;width:22%}
|
|
331
342
|
#badge{position:absolute;bottom:10px;left:0;right:0;text-align:center;z-index:9;
|
|
332
343
|
font-family:var(--mono);font-size:10px;letter-spacing:.14em;text-transform:uppercase;
|
|
333
344
|
color:rgba(255,255,255,.4)}
|
|
@@ -342,7 +353,7 @@ blockquote::before{content:"“"}blockquote::after{content:"”"}
|
|
|
342
353
|
.evq::before{content:"“"}.evq::after{content:"”"}
|
|
343
354
|
.evq cite{display:block;font-family:var(--mono);font-size:10px;font-style:normal;font-weight:400;
|
|
344
355
|
letter-spacing:.06em;opacity:.55;margin-top:7px}
|
|
345
|
-
.card.chart
|
|
356
|
+
.card.k-chart .inner,.card.k-data .inner{padding-top:4px}
|
|
346
357
|
${CHART_CSS}
|
|
347
358
|
|
|
348
359
|
/* ── long-read mode ── */
|
|
@@ -409,6 +420,7 @@ body.reading #stage,body.reading #badge{display:none}
|
|
|
409
420
|
<div id="bars">${bars}</div>
|
|
410
421
|
${slides}
|
|
411
422
|
<div class="hint" id="hint">tap or press → to begin</div>
|
|
423
|
+
<div class="more" id="more">scroll ↓</div>
|
|
412
424
|
<div class="tapzone l" data-nav="-1" aria-hidden="true"></div>
|
|
413
425
|
<div class="tapzone r" data-nav="1" aria-hidden="true"></div>
|
|
414
426
|
</div></div>
|
|
@@ -425,12 +437,26 @@ const LIGHT=new Set(cards.map((c,k)=>{
|
|
|
425
437
|
const v=parseInt(h.slice(0,2),16)*.299+parseInt(h.slice(2,4),16)*.587+parseInt(h.slice(4,6),16)*.114;
|
|
426
438
|
return v>140?k:-1;
|
|
427
439
|
}).filter(k=>k>=0));
|
|
440
|
+
function measure(){
|
|
441
|
+
cards.forEach(c=>{ c.dataset.more = c.scrollHeight > c.clientHeight + 4 ? '1' : '0'; });
|
|
442
|
+
}
|
|
443
|
+
function syncMore(){
|
|
444
|
+
const c=cards[i];
|
|
445
|
+
const more = c.dataset.more==='1' && c.scrollTop + c.clientHeight < c.scrollHeight - 6;
|
|
446
|
+
deck.dataset.more = more ? '1' : '0';
|
|
447
|
+
// fade + cue inherit the active card's colours
|
|
448
|
+
const cs=getComputedStyle(c);
|
|
449
|
+
deck.style.setProperty('--fade', cs.backgroundColor);
|
|
450
|
+
deck.style.setProperty('--fadefg', cs.color);
|
|
451
|
+
}
|
|
428
452
|
function show(k){
|
|
429
453
|
const h=document.getElementById('hint'); if(h) h.style.display = k===0 ? '' : 'none';
|
|
430
454
|
i=Math.max(0,Math.min(N-1,k));
|
|
431
455
|
cards.forEach((c,x)=>c.classList.toggle('on',x===i));
|
|
432
456
|
bars.forEach((b,x)=>{b.classList.toggle('done',x<i);b.classList.toggle('now',x===i)});
|
|
433
457
|
deck.classList.toggle('dark',LIGHT.has(i));
|
|
458
|
+
cards[i].scrollTop=0;
|
|
459
|
+
requestAnimationFrame(syncMore);
|
|
434
460
|
}
|
|
435
461
|
document.querySelectorAll('[data-nav]').forEach(z=>
|
|
436
462
|
z.addEventListener('click',()=>show(i+ +z.dataset.nav)));
|
|
@@ -451,12 +477,18 @@ addEventListener('keydown',e=>{
|
|
|
451
477
|
if(e.key==='ArrowRight'||e.key===' ') {e.preventDefault();show(i+1)}
|
|
452
478
|
if(e.key==='ArrowLeft') {e.preventDefault();show(i-1)}
|
|
453
479
|
});
|
|
454
|
-
let x0=null;
|
|
455
|
-
deck.addEventListener('touchstart',e=>x0=e.touches[0].clientX,{passive:true});
|
|
480
|
+
let x0=null,y0=null;
|
|
481
|
+
deck.addEventListener('touchstart',e=>{x0=e.touches[0].clientX;y0=e.touches[0].clientY},{passive:true});
|
|
456
482
|
deck.addEventListener('touchend',e=>{
|
|
457
|
-
if(x0===null)return;
|
|
458
|
-
|
|
483
|
+
if(x0===null)return;
|
|
484
|
+
const dx=e.changedTouches[0].clientX-x0, dy=e.changedTouches[0].clientY-y0;
|
|
485
|
+
// only treat it as a page flip if the gesture is clearly horizontal
|
|
486
|
+
if(Math.abs(dx)>44 && Math.abs(dx)>Math.abs(dy)*1.5) show(i+(dx<0?1:-1));
|
|
487
|
+
x0=y0=null;
|
|
459
488
|
},{passive:true});
|
|
460
|
-
|
|
489
|
+
cards.forEach(c=>c.addEventListener('scroll',syncMore,{passive:true}));
|
|
490
|
+
addEventListener('resize',()=>{measure();syncMore()});
|
|
491
|
+
if(document.fonts?.ready) document.fonts.ready.then(()=>{measure();syncMore()});
|
|
492
|
+
measure(); show(0);
|
|
461
493
|
</script></body></html>`;
|
|
462
494
|
}
|