usehour 0.2.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/README.md +3 -3
- package/hour.mjs +142 -51
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
Your year of building with AI, as Wrapped-style cards.
|
|
4
4
|
|
|
5
5
|
```
|
|
6
|
-
npx usehour@0.
|
|
6
|
+
npx usehour@0.3.1
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
- Asks before it reads anything, and lists the folders it will read.
|
|
10
10
|
- Reads your Claude Code and Codex session logs on your machine, read only.
|
|
11
|
-
- Counts your habits, and
|
|
11
|
+
- Counts your habits, and finds a few of your best prompts (anything that looks like a key, password, email or home folder already hidden). You can pick some for your profile and black out words, but not add or change any.
|
|
12
12
|
- Opens your cards in your browser. Save any card as an image to post it.
|
|
13
|
-
- Optional:
|
|
13
|
+
- Optional: claim usehour.com/@you from the cards page. You see exactly what is sent first. The script itself never connects to the internet.
|
|
14
14
|
- No dependencies, nothing installed.
|
|
15
15
|
|
|
16
16
|
It's one file: read [hour.mjs](https://usehour.com/hour.mjs) before you run it.
|
package/hour.mjs
CHANGED
|
@@ -30,7 +30,7 @@ import { spawn } from "node:child_process";
|
|
|
30
30
|
import { Worker, isMainThread, parentPort, workerData } from "node:worker_threads";
|
|
31
31
|
import { fileURLToPath } from "node:url";
|
|
32
32
|
|
|
33
|
-
const VERSION = "0.
|
|
33
|
+
const VERSION = "0.3.1";
|
|
34
34
|
const API_DEFAULT = "https://usehour.com/api";
|
|
35
35
|
|
|
36
36
|
/* ------------------------------------------------------------------ patterns */
|
|
@@ -376,7 +376,7 @@ function cards(m, name) {
|
|
|
376
376
|
if (m.proof.codingSessions >= 5) C.push({ id: "edge", bg: "#F7F3EC", fg: "#0B0B0A", dot: "#F2601F", pat: "ring", pct: tested, ask: tested < 0.75 ? "Your growth edge" : "Your strength", big: `${pct(tested)}%`, line: tested < 0.75 ? "of coding sessions ran a test. Push that higher." : "of coding sessions ran a test. Keep it that way.", bs: "30cqw" });
|
|
377
377
|
if (m.promptProfile && m.promptProfile.length >= 3) {
|
|
378
378
|
const hp = m.promptProfile, best = [...hp].sort((a, b) => b.value / b.notable - a.value / a.notable)[0];
|
|
379
|
-
C.push({ id: "prompting", bg: "#F7F3EC", fg: "#0B0B0A", bs: "
|
|
379
|
+
C.push({ id: "prompting", bg: "#F7F3EC", fg: "#0B0B0A", bs: "8.6cqw", rows: hp.map((h) => [h.label, habitFill(h), `${pct(h.value)}%`]), ask: "How you prompt", big: best.label, line: `Your strongest habit.` });
|
|
380
380
|
}
|
|
381
381
|
C.push({ id: "summary", bg: "#0B0B0A", fg: "#F4F1EA", summary: true, who: name || "", period: `${monthYear(m.window.first)} – ${monthYear(m.window.last)}`, type: type.name,
|
|
382
382
|
stats: [["Sessions", m.sessions.total.toLocaleString("en")], ["Busiest hour", hourLabel(m.busiestHour)], ["Go-to prompt", m.goToPrompt ? `“${m.goToPrompt.text}”` : "–"], ["Agents at once", String(m.parallelMax)], ["Made it prove it", `${pct(verify)}%`], ["Model", top ? top.name.replace(/^Claude /, "") : "–"]] });
|
|
@@ -389,9 +389,10 @@ function cards(m, name) {
|
|
|
389
389
|
a button: "Continue" checks that your handle is free (sends the handle
|
|
390
390
|
only), "Create" sends the profile you saw in step 3. */
|
|
391
391
|
const CLAIM_CSS = String.raw`
|
|
392
|
+
|
|
392
393
|
.claimc .cdots{margin-top:4cqw;height:14cqw;background-image:radial-gradient(circle,rgba(244,241,234,.95) 0 .95cqw,transparent 1.05cqw);background-size:3.6cqw 3.6cqw;-webkit-mask-image:linear-gradient(115deg,transparent 8%,#000 55%,transparent 96%);mask-image:linear-gradient(115deg,transparent 8%,#000 55%,transparent 96%)}
|
|
393
394
|
.claimc .pill{margin-top:4cqw;align-self:flex-start;padding:2cqw 3.6cqw;border-radius:999px;background:rgba(244,241,234,.1);box-shadow:inset 0 0 0 .3cqw rgba(244,241,234,.22);font:4.2cqw var(--mono)}.claimc .pill span{opacity:.6}.claimc .pill b{font-weight:500}
|
|
394
|
-
.claimc .cta{position:relative;z-index:5;margin-top:5cqw;height:
|
|
395
|
+
.claimc .cta{position:relative;z-index:5;margin-top:5cqw;min-height:15cqw;padding:3.4cqw 0;border-radius:999px;border:0;background:#F4F1EA;color:#0B0B0A;font:600 5.4cqw var(--sans);cursor:pointer}
|
|
395
396
|
.claimbar{margin-top:28px;display:flex;justify-content:space-between;align-items:center;gap:16px;flex-wrap:wrap;background:var(--ink);color:#F4F1EA;border-radius:22px;padding:22px 24px}.claimbar b{display:block;font-size:20px;letter-spacing:-.02em}.claimbar span{font-size:14px;opacity:.8}
|
|
396
397
|
.claimbar button{height:48px;padding:0 22px;border-radius:999px;border:0;background:#F4F1EA;color:var(--ink);font:600 15px var(--sans);cursor:pointer}
|
|
397
398
|
.cl{position:fixed;inset:0;z-index:50;background:rgba(11,11,10,.55);display:flex;align-items:center;justify-content:center;padding:16px}.cl[hidden]{display:none}
|
|
@@ -442,11 +443,20 @@ const CLAIM_CSS = String.raw`
|
|
|
442
443
|
.cl-done p{position:relative;margin:14px 0 0;font-size:14px;line-height:1.45;opacity:.8}
|
|
443
444
|
.cl-row{flex:none;display:flex;gap:10px;margin-top:16px}.cl-row a,.cl-row button{flex:1;display:flex;align-items:center;justify-content:center;height:52px;border-radius:999px;font:600 16px var(--sans);text-decoration:none;cursor:pointer;border:0}
|
|
444
445
|
.cl-row a{background:var(--ink);color:var(--paper)}.cl-row button{background:#fff;color:var(--ink);box-shadow:inset 0 0 0 1px rgba(11,11,10,.14)}
|
|
446
|
+
.cl-g2{margin-top:22px}.cl-g2>*+*{margin-top:14px}
|
|
447
|
+
.cl-foot{flex:none;display:flex;flex-direction:column-reverse;gap:8px;margin-top:14px}.cl-foot .cl-fine{margin:0;text-align:center}
|
|
448
|
+
.cl-tip{margin-top:10px;font-size:12.5px;font-weight:600;opacity:.8}
|
|
449
|
+
.cl-toks{margin-top:8px;font-size:15.5px;line-height:1.6;max-height:300px;overflow:auto;white-space:pre-wrap;overflow-wrap:anywhere;background:rgba(255,255,255,.95);color:var(--ink);border-radius:12px;padding:10px 12px;cursor:auto}
|
|
450
|
+
.tok{font:inherit;color:inherit;background:none;border:0;padding:0 1px;margin:0;border-radius:4px;cursor:pointer}.tok:hover{background:rgba(11,11,10,.1)}.tok.on{background:var(--ink);color:var(--ink)}
|
|
451
|
+
.cl-pc.ed{cursor:default}.cl-narrow{width:100%;max-width:560px;margin:0 auto}
|
|
452
|
+
@media(min-width:900px){.cl-sheet{max-width:1040px;height:min(820px,calc(100svh - 48px));padding:26px 40px 30px}.cl-h{font-size:44px}
|
|
453
|
+
.cl-g2{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:28px;align-items:start}.cl-g2>*+*{margin-top:0}.cl-g2>.cl-hcard{margin-top:0}
|
|
454
|
+
.cl-list{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));align-content:start;align-items:start}
|
|
455
|
+
.cl-foot{flex-direction:row;justify-content:space-between;align-items:center;gap:20px}.cl-foot .cl-fine{text-align:left}.cl-foot .cl-go{width:auto;min-width:300px;padding:0 34px}.cl-foot .cl-skip{width:auto}}
|
|
445
456
|
`;
|
|
446
|
-
const CLAIM_HTML = `<div class="
|
|
447
|
-
<div class="cl" id="cl" hidden role="dialog" aria-modal="true" aria-label="Claim your handle"><div class="cl-sheet" id="cls"></div></div>`;
|
|
457
|
+
const CLAIM_HTML = `<div class="cl" id="cl" hidden role="dialog" aria-modal="true" aria-label="Claim your handle"><div class="cl-sheet" id="cls"></div></div>`;
|
|
448
458
|
const CLAIM_JS = String.raw`
|
|
449
|
-
var CS={step:1,handle:D.suggest||"",name:"",email:"",picked:[],texts:(D.examples||[]).map(function(e){return e.text}),editing:-1,pub:false,roles:false,feature:false,busy:false,err:"",data:false,done:null};
|
|
459
|
+
var CS={hide:(D.examples||[]).map(function(){return new Set()}),step:1,handle:D.suggest||"",name:"",email:"",picked:[],texts:(D.examples||[]).map(function(e){return e.text}),editing:-1,pub:false,roles:false,feature:false,busy:false,err:"",data:false,done:null};
|
|
450
460
|
var HANDLE_RE=/^[a-z0-9](?:[a-z0-9]|-(?=[a-z0-9])){2,23}$/,EMAIL_RE=/^[^\s@<>"]+@[^\s@<>"]+\.[^\s@<>"]+$/;
|
|
451
461
|
var PCOL=[["#DBA004","#0B0B0A"],["#8B5FD9","#FFFFFF"],["#2B5FE3","#FFFFFF"]];
|
|
452
462
|
function hidHTML(t){return esc(t).replace(/\[hidden\]/g,'<span class="hid">hidden</span>')}
|
|
@@ -455,34 +465,41 @@ var h=CS.handle||"you";d.innerHTML='<div class="in"><div class="ask">One more th
|
|
|
455
465
|
d.querySelector(".cta").onclick=function(e){e.stopPropagation();openClaim()};return d}
|
|
456
466
|
function openClaim(){document.getElementById("cl").hidden=false;document.body.style.overflow="hidden";renderClaim()}
|
|
457
467
|
function closeClaim(){document.getElementById("cl").hidden=true;document.body.style.overflow=""}
|
|
458
|
-
function payload(){return{v:2,handle:CS.handle,name:CS.name.trim(),email:CS.email.trim(),linkedin:"",public:CS.pub,roles:CS.roles,feature:CS.feature,prompts:CS.picked.map(function(i){return{text:
|
|
468
|
+
function payload(){return{v:2,handle:CS.handle,name:CS.name.trim(),email:CS.email.trim(),linkedin:"",public:CS.pub,roles:CS.roles,feature:CS.feature,prompts:CS.picked.map(function(i){return{text:promptOut(i),date:D.examples[i].date,hidden:CS.hide[i].size}}).filter(function(p){return p.text&&p.text!=="[hidden]"}),wrapped:D.m}}
|
|
459
469
|
function prog(n){return'<div class="cl-top"><button type="button" class="cl-back" data-a="back">'+(n===1?"Close":"Back")+'</button><span>'+n+' of 3</span></div><div class="cl-prog">'+[1,2,3].map(function(i){return'<i'+(i<=n?' class="on"':'')+'></i>'}).join("")+'</div>'}
|
|
460
470
|
function handleNote(){var h=CS.handle;if(!h)return"Letters, numbers and dashes.";if(!HANDLE_RE.test(h))return"3 to 24 letters, numbers or single dashes.";return"Press Continue to check it's free."}
|
|
471
|
+
function toks(i){return CS.texts[i].split(/(\s+)/)}
|
|
472
|
+
function isAuto(w){return w.indexOf("[hidden]")>=0}
|
|
473
|
+
function promptOut(i){var h=CS.hide[i];return toks(i).map(function(w,k){return h.has(k)?"[hidden]":w}).join("").replace(/\[hidden\](\s+\[hidden\])+/g,"[hidden]").trim()}
|
|
474
|
+
function hidCount(i){return (CS.texts[i].match(/\[hidden\]/g)||[]).length+CS.hide[i].size}
|
|
475
|
+
function viewHTML(i){var h=CS.hide[i];return toks(i).map(function(w,k){if(/^\s+$/.test(w))return w;if(h.has(k))return'<span class="hid">'+esc(w)+'</span>';return hidHTML(w)}).join("")}
|
|
476
|
+
function editHTML(i){var h=CS.hide[i];return toks(i).map(function(w,k){if(/^\s+$/.test(w))return w;if(isAuto(w))return hidHTML(w);return'<button type="button" class="tok'+(h.has(k)?" on":"")+'" data-a="tok" data-i="'+i+'" data-k="'+k+'">'+esc(w)+'</button>'}).join("")}
|
|
461
477
|
function renderClaim(){var s=document.getElementById("cls"),H="",n=CS.step;
|
|
462
478
|
if(n===1){H=prog(1)+'<h2 class="cl-h">Pick your handle.</h2><p class="cl-p">It’s the address of your profile. Once it’s yours, nobody else can have it.</p>'
|
|
463
|
-
+'<div class="cl-hcard"><label for="clh">usehour.com/</label><div class="cl-hrow"><span class="grad">@</span><input id="clh" autocomplete="off" autocapitalize="off" spellcheck="false" maxlength="24" value="'+esc(CS.handle)+'"></div><div class="cl-hst" id="clhst">'+esc(handleNote())+'</div></div>'
|
|
464
|
-
+'<label class="cl-f">Your name<input id="cln" autocomplete="name" maxlength="80" value="'+esc(CS.name)+'"></label>'
|
|
479
|
+
+'<div class="cl-g2"><div class="cl-hcard"><label for="clh">usehour.com/</label><div class="cl-hrow"><span class="grad">@</span><input id="clh" autocomplete="off" autocapitalize="off" spellcheck="false" maxlength="24" value="'+esc(CS.handle)+'"></div><div class="cl-hst" id="clhst">'+esc(handleNote())+'</div></div>'
|
|
480
|
+
+'<div><label class="cl-f" style="margin-top:0">Your name<input id="cln" autocomplete="name" maxlength="80" value="'+esc(CS.name)+'"></label>'
|
|
465
481
|
+'<label class="cl-f">Email, to keep your handle<input id="cle" type="email" autocomplete="email" maxlength="200" value="'+esc(CS.email)+'"></label>'
|
|
466
|
-
+'<p class="cl-fine">We email you a link to your profile. That’s how you get back in. No password.</p
|
|
467
|
-
+'<
|
|
468
|
-
|
|
469
|
-
+'<
|
|
470
|
-
|
|
471
|
-
+(ed?'
|
|
472
|
-
+'<
|
|
473
|
-
+'<
|
|
474
|
-
+(k?'<button type="button" class="cl-skip" data-a="skip2">Skip, don’t share any prompts</button>':'')}
|
|
482
|
+
+'<p class="cl-fine">We email you a link to your profile. That’s how you get back in. No password.</p></div></div>'
|
|
483
|
+
+'<div class="cl-err">'+esc(CS.err)+'</div><div class="cl-sp"></div>'
|
|
484
|
+
+'<div class="cl-foot"><p class="cl-fine">Continue checks the name with usehour.com. Nothing else is sent.</p><button type="button" class="cl-go" data-a="next1"'+(CS.busy?" disabled":"")+'>'+(CS.busy?"Checking…":"Continue")+'</button></div>'}
|
|
485
|
+
else if(n===2){var k=CS.picked.length;H=prog(2)+'<h2 class="cl-h">Show how you brief an agent.</h2><p class="cl-p">Your strongest prompts, found in your logs. Pick up to 3. You can black out words, but not add or change any.</p>'
|
|
486
|
+
+'<div class="cl-lw"><div class="cl-list" id="cll">'+D.examples.map(function(e,i){var at=CS.picked.indexOf(i),on=at>=0,c=on?PCOL[at%3]:null,ed=CS.editing===i,w=CS.texts[i].split(/\s+/).filter(Boolean).length,hn=hidCount(i);
|
|
487
|
+
return'<div class="cl-pc'+(on?" on":"")+(ed?" ed":"")+'" data-a="pick" data-i="'+i+'"'+(c?' style="background:'+c[0]+';color:'+c[1]+'"':'')+'><div class="cl-pch"><span>'+esc(e.date?new Date(e.date+"T12:00:00Z").toLocaleString("en",{month:"short",day:"numeric",timeZone:"UTC"})+" · ":"")+w+' words'+(hn?" · "+hn+" hidden":"")+'</span><span class="cl-pick">'+(on?"✓ Picked":"Pick")+'</span></div>'
|
|
488
|
+
+(ed?'<div class="cl-tip">Tap words to black them out. Tap again to bring them back.</div><div class="cl-toks">'+editHTML(i)+'</div>':'<p>“'+viewHTML(i)+'”</p>')
|
|
489
|
+
+'<button type="button" class="cl-edit" data-a="edit" data-i="'+i+'">'+(ed?"Done":"Black out words")+'</button></div>'}).join("")+'</div><div class="cl-more" id="clm" hidden><span>'+(D.examples.length>4?"More prompts below":"More below")+'</span></div></div>'
|
|
490
|
+
+'<div class="cl-err">'+esc(CS.err)+'</div><div class="cl-foot" style="margin-top:14px">'+(k?'<button type="button" class="cl-skip" data-a="skip2">Skip, don’t share any prompts</button>':'<span></span>')+'<button type="button" class="cl-go" data-a="next2">'+(k?"Continue with "+k+" prompt"+(k>1?"s":""):"Continue without prompts")+'</button></div>'}
|
|
475
491
|
else if(n===3){var p=payload(),hh=esc(CS.handle);H=prog(3)+'<h2 class="cl-h">Make it work for you.</h2><p class="cl-p">All optional. Change any of it later from your profile.</p>'
|
|
476
|
-
+'<
|
|
492
|
+
+'<div class="cl-g2"><div>'
|
|
493
|
+
+'<label class="cl-opt dk" style="margin-top:0"><span class="cl-oh"><span>Make <span class="grad">@'+hh+'</span> public</span><input type="checkbox" data-o="pub"'+(CS.pub?" checked":"")+'></span><span class="cl-ot">A link for your LinkedIn, GitHub and email signature. It shows how you work, which a CV can’t.</span><span class="cl-ot s">Off: your handle stays reserved, and only people with your private link can see the profile.</span></label>'
|
|
477
494
|
+'<label class="cl-opt"><span class="cl-oh"><span>Let companies come to you</span><input type="checkbox" data-o="roles"'+(CS.roles?" checked":"")+'></span><ul class="cl-ul" style="--dot:#2B5FE3"><li>Teams hiring AI builders see your profile and ask for an intro. No applying, no cover letters.</li><li>You say yes or no to each one. Your email stays hidden until you say yes.</li></ul></label>'
|
|
478
495
|
+'<label class="cl-opt"><span class="cl-oh"><span>Get featured by Hour</span><input type="checkbox" data-o="feature"'+(CS.feature?" checked":"")+'></span><ul class="cl-ul" style="--dot:#F2601F"><li>We post standout cards in our “builders to watch” series, tagged to you.</li><li>Your work in front of founders and hiring teams who follow Hour.</li></ul></label>'
|
|
479
|
-
+'
|
|
480
|
-
+'<button type="button" class="cl-link" data-a="data">'+(CS.data?"Hide the exact data":"See the exact data")+'</button>'+(CS.data?'<pre>'+esc(JSON.stringify(p,null,2))+'</pre>':'')
|
|
481
|
-
+'<div class="cl-err">'+esc(CS.err)+'</div><div class="cl-sp"></div><button type="button" class="cl-go" data-a="create"'+(CS.busy?" disabled":"")+'>'+(CS.busy?"Creating…":"Create @"+hh)+'</button
|
|
482
|
-
else{var d=CS.done,u=d.publicUrl||d.url;H='<div class="cl-top"><span></span><button type="button" class="cl-back" data-a="close">Close</button></div><div class="cl-done"><h2>@'+esc(d.handle)+'<br>is yours.</h2><a class="u" href="'+esc(u)+'" target="_blank" rel="noopener">'+esc(u.replace("https://",""))+'</a><p>'+(d.publicUrl?"Your profile is public. Put the link anywhere you like.":"Your profile is private. Only people you send this link to can see it.")+'</p></div>'
|
|
483
|
-
+'<div class="cl-row"><a href="'+esc(u)+'" target="_blank" rel="noopener">Open my profile</a><button type="button" data-a="copy">Copy link</button></div><p class="cl-fine c">We emailed you this link, plus a private link to change your settings or delete your profile.</p>'}
|
|
496
|
+
+'</div><div><h3 class="cl-h3" style="margin-top:0">What goes to Hour</h3><div class="cl-two"><div><b><i style="background:#2B5FE3"></i>Sent</b><span>Your card numbers</span><span>'+(p.prompts.length?p.prompts.length+" prompt"+(p.prompts.length>1?"s":"")+" you picked":"No prompts")+'</span><span>Handle, name, email</span></div><div><b><i style="background:#F2601F"></i>Stays with you</b><span>Your logs and code</span><span>Every other prompt</span><span>File and project names</span></div></div>'
|
|
497
|
+
+'<button type="button" class="cl-link" data-a="data">'+(CS.data?"Hide the exact data":"See the exact data")+'</button>'+(CS.data?'<pre>'+esc(JSON.stringify(p,null,2))+'</pre>':'')+'</div></div>'
|
|
498
|
+
+'<div class="cl-err">'+esc(CS.err)+'</div><div class="cl-sp"></div><div class="cl-foot"><p class="cl-fine">Delete it any time, with one link.</p><button type="button" class="cl-go" data-a="create"'+(CS.busy?" disabled":"")+'>'+(CS.busy?"Creating…":"Create @"+hh)+'</button></div>'}
|
|
499
|
+
else{var d=CS.done,u=d.publicUrl||d.url;H='<div class="cl-top"><span></span><button type="button" class="cl-back" data-a="close">Close</button></div><div class="cl-narrow"><div class="cl-done"><h2>@'+esc(d.handle)+'<br>is yours.</h2><a class="u" href="'+esc(u)+'" target="_blank" rel="noopener">'+esc(u.replace("https://",""))+'</a><p>'+(d.publicUrl?"Your profile is public. Put the link anywhere you like.":"Your profile is private. Only people you send this link to can see it.")+'</p></div>'
|
|
500
|
+
+'<div class="cl-row"><a href="'+esc(u)+'" target="_blank" rel="noopener">Open my profile</a><button type="button" data-a="copy">Copy link</button></div><p class="cl-fine c">We emailed you this link, plus a private link to change your settings or delete your profile.</p></div>'}
|
|
484
501
|
s.innerHTML=H;s.scrollTop=0;
|
|
485
|
-
if(n===2){var L=document.getElementById("cll"),M=document.getElementById("clm"),f=function(){M.hidden=L.scrollHeight-L.clientHeight-L.scrollTop<24};L.addEventListener("scroll",f);f()
|
|
502
|
+
if(n===2){var L=document.getElementById("cll"),M=document.getElementById("clm"),f=function(){M.hidden=L.scrollHeight-L.clientHeight-L.scrollTop<24};L.addEventListener("scroll",f);f()}}
|
|
486
503
|
document.getElementById("cls").addEventListener("input",function(e){var t=e.target;
|
|
487
504
|
if(t.id==="clh"){var v=t.value.toLowerCase().replace(/[^a-z0-9-]/g,"");if(v!==t.value)t.value=v;CS.handle=v;CS.err="";document.getElementById("clhst").textContent=handleNote()}
|
|
488
505
|
else if(t.id==="cln")CS.name=t.value;else if(t.id==="cle")CS.email=t.value;
|
|
@@ -493,18 +510,20 @@ if(a==="back"){CS.err="";if(CS.step===1)closeClaim();else{CS.step=CS.step===3&&!
|
|
|
493
510
|
else if(a==="close"){closeClaim()}
|
|
494
511
|
else if(a==="next1"){if(CS.busy)return;if(!HANDLE_RE.test(CS.handle)){CS.err="Handles are 3 to 24 letters, numbers or single dashes.";return renderClaim()}if(!CS.name.trim()){CS.err="Add your name.";return renderClaim()}if(!EMAIL_RE.test(CS.email.trim())){CS.err="Add an email address, so the handle stays yours.";return renderClaim()}
|
|
495
512
|
CS.busy=true;CS.err="";renderClaim();fetch(D.api+"/handles/"+encodeURIComponent(CS.handle)).then(function(r){return r.json()}).then(function(j){CS.busy=false;if(j.available){CS.step=D.examples.length?2:3}else CS.err="@"+CS.handle+(j.reason==="already taken"?" is taken. Try another.":": "+j.reason+".");renderClaim()}).catch(function(){CS.busy=false;CS.err="Couldn’t reach usehour.com. Check your connection and try again.";renderClaim()})}
|
|
496
|
-
else if(a==="edit"){e.stopPropagation();CS.editing=CS.editing===i?-1:i;renderClaim()}
|
|
497
|
-
else if(a==="
|
|
513
|
+
else if(a==="edit"){e.stopPropagation();CS.editing=CS.editing===i?-1:i;var y0=document.getElementById("cll").scrollTop;renderClaim();document.getElementById("cll").scrollTop=y0}
|
|
514
|
+
else if(a==="tok"){e.stopPropagation();var kk=+b.getAttribute("data-k"),hs=CS.hide[i];if(hs.has(kk))hs.delete(kk);else hs.add(kk);b.classList.toggle("on");var mt=b.closest(".cl-pc").querySelector(".cl-pch span");mt.textContent=mt.textContent.replace(/ \u00B7 \d+ hidden$/,"")+(hidCount(i)?" \u00B7 "+hidCount(i)+" hidden":"")}
|
|
515
|
+
else if(a==="pick"){if(CS.editing===i)return;var at=CS.picked.indexOf(i);CS.err="";if(at>=0)CS.picked.splice(at,1);else if(CS.picked.length>=3)CS.err="Pick up to 3. Untick one first.";else CS.picked.push(i);var y=document.getElementById("cll").scrollTop;renderClaim();document.getElementById("cll").scrollTop=y}
|
|
498
516
|
else if(a==="next2"){CS.err="";CS.editing=-1;CS.step=3;renderClaim()}
|
|
499
517
|
else if(a==="skip2"){CS.picked=[];CS.err="";CS.editing=-1;CS.step=3;renderClaim()}
|
|
500
518
|
else if(a==="data"){CS.data=!CS.data;renderClaim()}
|
|
501
519
|
else if(a==="copy"){var u=CS.done.publicUrl||CS.done.url;(navigator.clipboard?navigator.clipboard.writeText(u):Promise.reject()).then(function(){b.textContent="Copied"},function(){window.prompt("Copy this link",u)})}
|
|
502
520
|
else if(a==="create"){if(CS.busy)return;CS.busy=true;CS.err="";renderClaim();
|
|
503
521
|
fetch(D.api+"/profiles",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(payload())}).then(function(r){return r.json().catch(function(){return{}}).then(function(j){return{s:r.status,j:j}})}).then(function(x){CS.busy=false;
|
|
504
|
-
if(x.s===201&&x.j.url){CS.done=x.j;CS.step=4;document.
|
|
522
|
+
if(x.s===201&&x.j.url){CS.done=x.j;CS.step=4;document.getElementById("topnote").textContent="Your profile is on usehour.com. Your logs and other prompts stayed on this computer.";document.getElementById("claimbar").hidden=true}
|
|
505
523
|
else if(x.s===409){CS.step=1;CS.err="Someone just took @"+CS.handle+". Pick another."}
|
|
506
524
|
else CS.err="That didn’t go through ("+(x.j.error||"error "+x.s)+"). Nothing was saved. Try again in a minute.";renderClaim()}).catch(function(){CS.busy=false;CS.err="Couldn’t reach usehour.com. Nothing was saved. Try again.";renderClaim()})}});
|
|
507
525
|
document.getElementById("claimb").onclick=openClaim;
|
|
526
|
+
(function(){var h=document.getElementById("clhandle"),b=document.getElementById("claimb");if(CS.handle){h.textContent=CS.handle;b.textContent="Claim @"+CS.handle}})();
|
|
508
527
|
`;
|
|
509
528
|
|
|
510
529
|
/* --------------------------------------------------------------- the page */
|
|
@@ -514,6 +533,10 @@ function pageHTML(m, C, o = {}) {
|
|
|
514
533
|
/* The page may talk to exactly one place: Hour's profile endpoint, and only
|
|
515
534
|
when you press the button. With --no-share it may talk to nothing. */
|
|
516
535
|
const connect = api ? new URL(api).origin : "'none'";
|
|
536
|
+
const escH = (t) => String(t).replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ }[c]));
|
|
537
|
+
const meta = { period: `${monthYear(m.window.first)} – ${monthYear(m.window.last)}`, sessions: m.sessions.total.toLocaleString("en"), prompts: m.prompts.total.toLocaleString("en") };
|
|
538
|
+
const LOCK = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5" y="11" width="14" height="9" rx="2"/><path d="M8 11V8a4 4 0 0 1 8 0v3"/></svg>';
|
|
539
|
+
const DL = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 4v11M7 10l5 5 5-5M5 20h14"/></svg>';
|
|
517
540
|
const csp = `default-src 'none'; img-src data: blob:; font-src data:; style-src 'unsafe-inline'; script-src 'unsafe-inline'; connect-src ${connect}; form-action 'none'; base-uri 'none'`;
|
|
518
541
|
return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
519
542
|
<meta http-equiv="Content-Security-Policy" content="${csp}">
|
|
@@ -522,17 +545,50 @@ function pageHTML(m, C, o = {}) {
|
|
|
522
545
|
@font-face{font-family:"Geist";src:url(data:font/woff2;base64,${FONT_GEIST}) format("woff2");font-weight:100 900}
|
|
523
546
|
:root{--paper:#F7F3EC;--ink:#0B0B0A;--ink-3:#6C6A64;--hair:rgba(11,11,10,.1);--surface:#fff;--sans:"Geist",ui-sans-serif,system-ui,-apple-system,"Helvetica Neue",Arial,sans-serif;--mono:"Geist Mono",ui-monospace,"SF Mono",Menlo,monospace}
|
|
524
547
|
*{box-sizing:border-box}body{background:var(--paper);color:var(--ink);font-family:var(--sans);margin:0;-webkit-font-smoothing:antialiased}
|
|
525
|
-
.wrap{max-width:1180px;margin:0 auto;padding:24px clamp(16px,4vw,
|
|
526
|
-
.top{display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap
|
|
527
|
-
.brand{display:flex;align-items:center;gap:9px;font-weight:600}.brand svg{width:22px;height:22px}
|
|
528
|
-
.note{font-size:14px;color:var(--ink-3);
|
|
529
|
-
.
|
|
530
|
-
.
|
|
531
|
-
.
|
|
532
|
-
.
|
|
533
|
-
|
|
534
|
-
.
|
|
535
|
-
.
|
|
548
|
+
.wrap{max-width:1180px;margin:0 auto;padding:24px clamp(16px,4vw,48px) 64px}
|
|
549
|
+
.top{display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap}
|
|
550
|
+
.brand{display:flex;align-items:center;gap:9px;font-weight:600;font-size:17px}.brand svg{width:22px;height:22px}
|
|
551
|
+
.note{display:flex;align-items:center;gap:8px;font-size:14px;color:var(--ink-3);line-height:1.5}.note svg{flex:none;width:15px;height:15px}
|
|
552
|
+
.btn{display:inline-flex;align-items:center;justify-content:center;gap:9px;height:52px;padding:0 22px;border-radius:999px;border:0;background:var(--ink);color:var(--paper);font:600 16px var(--sans);cursor:pointer;text-decoration:none}
|
|
553
|
+
.btn.ghost{background:#fff;color:var(--ink);font-weight:500;box-shadow:inset 0 0 0 1px rgba(11,11,10,.14)}.btn svg{width:17px;height:17px;flex:none}.btn[disabled]{opacity:.6;cursor:wait}
|
|
554
|
+
.hero{margin-top:40px;display:grid;grid-template-columns:minmax(0,420px) minmax(0,1fr);gap:clamp(28px,5vw,56px);align-items:stretch}
|
|
555
|
+
.hero-r{display:flex;flex-direction:column}
|
|
556
|
+
.hero h1{margin:4px 0 0;font-size:clamp(48px,6.4vw,76px);line-height:.9;letter-spacing:-.06em;font-weight:650}
|
|
557
|
+
.hero p.sub{margin:16px 0 0;font-size:19px;line-height:1.4;color:#3A3935}
|
|
558
|
+
.acts{margin-top:22px;display:flex;gap:10px;flex-wrap:wrap}
|
|
559
|
+
.claimbox{margin-top:auto;border-radius:26px;background:#8B5FD9;color:#fff;padding:28px 28px 26px;position:relative;overflow:hidden}
|
|
560
|
+
.claimbox:before{content:"";position:absolute;right:-20px;top:-20px;width:300px;height:190px;background-image:radial-gradient(circle,rgba(255,255,255,.9) 0 3px,transparent 3.4px);background-size:14px 14px;-webkit-mask-image:radial-gradient(circle at 80% 20%,#000 10%,transparent 68%);mask-image:radial-gradient(circle at 80% 20%,#000 10%,transparent 68%)}
|
|
561
|
+
.claimbox>*{position:relative}.claimbox .k{font-size:15px;font-weight:500;opacity:.9}.claimbox .h{margin-top:8px;font-size:clamp(32px,3.6vw,44px);line-height:.95;letter-spacing:-.05em;font-weight:650}
|
|
562
|
+
.claimbox .pill{display:inline-flex;margin-top:14px;padding:9px 15px;border-radius:999px;background:rgba(11,11,10,.22);font:16px var(--mono)}.claimbox .pill span{opacity:.7}
|
|
563
|
+
.claimbox p{margin:12px 0 0;max-width:460px;font-size:15.5px;line-height:1.42}
|
|
564
|
+
.claimbox button{margin-top:18px;height:52px;padding:0 24px;border-radius:999px;border:0;background:#fff;color:var(--ink);font:600 16px var(--sans);cursor:pointer}
|
|
565
|
+
h2.sec{margin:64px 0 18px;font-size:30px;letter-spacing:-.04em;font-weight:650}
|
|
566
|
+
.grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:16px}@media(max-width:1000px){.grid{grid-template-columns:repeat(3,minmax(0,1fr))}}@media(max-width:640px){.grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}}
|
|
567
|
+
.grid .card{cursor:pointer;transition:transform .15s}.grid .card:hover{transform:translateY(-3px)}
|
|
568
|
+
.save{display:flex;align-items:center;justify-content:center;gap:7px;width:100%;height:40px;margin-top:6px;border:0;background:none;border-radius:999px;font:500 13px var(--sans);color:#3A3935;cursor:pointer}.save:hover{background:rgba(11,11,10,.05)}.save svg{width:14px;height:14px}
|
|
569
|
+
.tips{margin-top:36px;display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px}.tips div{border-radius:18px;background:#fff;box-shadow:inset 0 0 0 1px rgba(11,11,10,.08);padding:20px 22px}.tips b{display:block;font-size:16px}.tips span{display:block;margin-top:4px;font-size:14px;line-height:1.45;color:#3A3935}
|
|
570
|
+
@media(max-width:760px){.hero{grid-template-columns:1fr}.hero-r{order:-1}.claimbox{margin-top:24px}.tips{grid-template-columns:1fr}h2.sec{margin-top:48px;font-size:24px}}
|
|
571
|
+
.intro{position:fixed;inset:0;z-index:40;background:var(--ink);color:#F4F1EA;display:flex;flex-direction:column;padding:26px clamp(20px,4vw,48px);overflow:auto}.intro[hidden],.story[hidden]{display:none}
|
|
572
|
+
.intro:before{content:"";position:absolute;inset:0;background-image:radial-gradient(circle,rgba(244,241,234,.85) 0 3px,transparent 3.4px);background-size:18px 18px;-webkit-mask-image:radial-gradient(ellipse at 78% 40%,#000 0%,transparent 55%);mask-image:radial-gradient(ellipse at 78% 40%,#000 0%,transparent 55%);pointer-events:none}
|
|
573
|
+
.intro>*{position:relative}.intro .top .note{color:#F4F1EA;opacity:.75}
|
|
574
|
+
.intro .mid{margin:auto 0;max-width:720px;padding:40px 0}.intro .meta{font-size:clamp(15px,1.6vw,18px);font-weight:500;opacity:.8}
|
|
575
|
+
.intro h1{margin:18px 0 0;font-size:clamp(56px,9vw,112px);line-height:.88;letter-spacing:-.065em;font-weight:650}
|
|
576
|
+
.intro p{margin:24px 0 0;font-size:clamp(18px,2vw,22px);line-height:1.35;opacity:.9;max-width:560px}
|
|
577
|
+
.intro .go{margin-top:34px;display:flex;gap:12px;flex-wrap:wrap;align-items:center}
|
|
578
|
+
.intro .play{display:inline-flex;align-items:center;gap:12px;height:62px;padding:0 30px 0 24px;border-radius:999px;border:0;background:#F4F1EA;color:var(--ink);font:600 19px var(--sans);cursor:pointer}.intro .play svg{width:20px;height:20px}
|
|
579
|
+
.intro .skip{height:62px;padding:0 20px;border:0;background:none;color:#F4F1EA;opacity:.8;font:500 16px var(--sans);cursor:pointer}
|
|
580
|
+
.intro .tip{font-size:13px;opacity:.55}
|
|
581
|
+
.story{position:fixed;inset:0;z-index:40;background:#161614;color:#F4F1EA;display:flex;flex-direction:column;align-items:center;padding:18px clamp(12px,3vw,32px) 20px}
|
|
582
|
+
.story .top{align-self:stretch}.story .cnt{display:flex;align-items:center;gap:14px;font-size:14px}.story .cnt span{opacity:.6}
|
|
583
|
+
.rb{display:inline-flex;align-items:center;justify-content:center;width:44px;height:44px;border-radius:50%;border:0;background:rgba(244,241,234,.1);color:#F4F1EA;cursor:pointer;flex:none}.rb svg{width:18px;height:18px}
|
|
584
|
+
.st-row{flex:1;min-height:0;width:100%;display:flex;align-items:center;justify-content:center;gap:clamp(8px,3vw,36px)}
|
|
585
|
+
.st-col{--w:min(480px,88vw,calc((100svh - 190px) * .8));width:var(--w);display:flex;flex-direction:column;gap:10px}
|
|
586
|
+
.st-bars{display:flex;gap:4px}.st-bars i{flex:1;height:3px;border-radius:2px;background:rgba(244,241,234,.25);overflow:hidden}.st-bars i b{display:block;height:100%;width:0;background:#F4F1EA}
|
|
587
|
+
.st-slot{position:relative}.st-slot .card{box-shadow:0 40px 100px rgba(0,0,0,.5);border-radius:28px/22px}
|
|
588
|
+
.st-hit{position:absolute;top:0;bottom:0;z-index:4;background:transparent;border:0;cursor:pointer}.st-hit.l{left:0;width:32%}.st-hit.r{right:0;width:68%}
|
|
589
|
+
.nb{width:56px;height:56px}.nb.fw{background:#F4F1EA;color:var(--ink)}.nb svg{width:22px;height:22px}
|
|
590
|
+
@media(max-width:640px){.nb{display:none}}
|
|
591
|
+
.st-acts{margin-top:14px;display:flex;gap:10px}.st-acts button{display:inline-flex;align-items:center;gap:8px;height:44px;padding:0 18px;border-radius:999px;border:0;background:rgba(244,241,234,.1);color:#F4F1EA;font:500 14px var(--sans);cursor:pointer}.st-acts svg{width:16px;height:16px}
|
|
536
592
|
.card{container-type:inline-size;aspect-ratio:4/5;width:100%;border-radius:5.5%/4.4%;overflow:hidden;position:relative;background:var(--bg);color:var(--fg);isolation:isolate}
|
|
537
593
|
.card canvas{position:absolute;inset:0;width:100%;height:100%}
|
|
538
594
|
.card .in{position:absolute;inset:0;padding:9cqw 8cqw 7cqw;display:flex;flex-direction:column;z-index:1}
|
|
@@ -542,17 +598,35 @@ h2{font-size:24px;letter-spacing:-.03em;margin:0 0 14px}
|
|
|
542
598
|
.ft{display:flex;justify-content:space-between;align-items:center;margin-top:6cqw;font-size:3.4cqw;opacity:.8}.ft .b{display:flex;align-items:center;gap:1.6cqw;font-weight:600}.ft svg{width:4.6cqw;height:4.6cqw}.ft .u{font-family:var(--mono)}
|
|
543
599
|
.sum{margin-top:auto;display:grid;grid-template-columns:1fr 1fr;gap:4.5cqw 5cqw}.sum div{border-top:.35cqw solid currentColor;padding-top:2cqw}.sum small{display:block;font-size:3.4cqw;opacity:.75;margin-bottom:.6cqw}.sum b{font-size:6.2cqw;letter-spacing:-.035em;font-weight:650;line-height:1}
|
|
544
600
|
.arch{font-size:11.5cqw;line-height:.92;letter-spacing:-.05em;font-weight:650;margin:2cqw 0 6cqw}
|
|
545
|
-
.rows{margin-top:
|
|
601
|
+
.rows{margin-top:6cqw;display:grid;gap:3.2cqw}.rows div{display:grid;grid-template-columns:minmax(0,1fr) auto 9cqw;align-items:center;gap:2.6cqw;font-size:4cqw;font-weight:500;white-space:nowrap}.rows div>span{overflow:hidden;text-overflow:ellipsis}.rows em{display:flex;gap:.9cqw}.rows u{width:2.3cqw;height:2.3cqw;border-radius:50%;background:currentColor;opacity:.14}.rows u.on{opacity:1;background:#8B5FD9}.rows b{text-align:right;font-weight:600}
|
|
546
602
|
${api ? CLAIM_CSS : ""}
|
|
547
603
|
.grad{background:linear-gradient(100deg,#4F88FF 0%,#B0ACFE 38%,#FB65C2 72%,#FD2980 100%);-webkit-background-clip:text;background-clip:text;color:transparent}
|
|
548
604
|
</style></head><body>
|
|
549
605
|
<svg width="0" height="0" style="position:absolute" aria-hidden="true"><defs><linearGradient id="ob" x1="0" y1="0" x2="1" y2="1"><stop offset="0%" stop-color="#4F88FF"/><stop offset="38%" stop-color="#B0ACFE"/><stop offset="72%" stop-color="#FB65C2"/><stop offset="100%" stop-color="#FD2980"/></linearGradient><symbol id="mark" viewBox="0 0 64 64"><path fill="url(#ob)" d="${MARK_PATH}"/></symbol></defs></svg>
|
|
606
|
+
<div class="intro" id="intro">
|
|
607
|
+
<div class="top"><div class="brand"><svg viewBox="0 0 64 64"><use href="#mark"/></svg>Hour</div><div class="note">${LOCK}Made on this computer. Nothing has been sent.</div></div>
|
|
608
|
+
<div class="mid"><div class="meta">${escH(meta.period)} · ${meta.sessions} sessions · ${meta.prompts} prompts</div>
|
|
609
|
+
<h1>Your year with <span class="grad">AI</span> is ready.</h1>
|
|
610
|
+
<p>${C.length} cards about how you build: your type, your hours, how you brief the agent. About a minute.</p>
|
|
611
|
+
<div class="go"><button type="button" class="play" id="play"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M7 4.5v15l13-7.5z"/></svg>Play my year</button><button type="button" class="skip" id="skip">Skip to all cards</button></div></div>
|
|
612
|
+
<div class="tip">Tip: use the arrow keys, or tap the left and right of a card. Space pauses.</div>
|
|
613
|
+
</div>
|
|
614
|
+
<div class="story" id="story" hidden role="dialog" aria-modal="true" aria-label="Your year with AI">
|
|
615
|
+
<div class="top"><div class="brand"><svg viewBox="0 0 64 64"><use href="#mark"/></svg>Your year with AI</div><div class="cnt"><span id="stn"></span><button type="button" class="rb" id="stx" aria-label="Close"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><path d="M6 6l12 12M18 6L6 18"/></svg></button></div></div>
|
|
616
|
+
<div class="st-row"><button type="button" class="rb nb" id="stp" aria-label="Previous card"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 5l-7 7 7 7"/></svg></button>
|
|
617
|
+
<div class="st-col"><div class="st-bars" id="bars"></div><div class="st-slot"><div id="slot"></div><button type="button" class="st-hit l" id="hl" aria-label="Previous"></button><button type="button" class="st-hit r" id="hr" aria-label="Next"></button></div></div>
|
|
618
|
+
<button type="button" class="rb nb fw" id="stf" aria-label="Next card"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 5l7 7-7 7"/></svg></button></div>
|
|
619
|
+
<div class="st-acts" id="sta"><button type="button" id="sts">${DL}Save this card</button><button type="button" id="stz"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><rect x="6" y="5" width="4" height="14" rx="1"/><rect x="14" y="5" width="4" height="14" rx="1"/></svg><span>Pause</span></button></div>
|
|
620
|
+
</div>
|
|
550
621
|
<div class="wrap">
|
|
551
|
-
<div class="top"><div class="brand"><svg viewBox="0 0 64 64"><use href="#mark"/></svg>Hour</div><div class="note"
|
|
552
|
-
<
|
|
553
|
-
<
|
|
554
|
-
<
|
|
555
|
-
|
|
622
|
+
<div class="top"><div class="brand"><svg viewBox="0 0 64 64"><use href="#mark"/></svg>Hour</div><div class="note" id="topnote">${LOCK}Made on this computer. Nothing has been sent.</div></div>
|
|
623
|
+
<section class="hero"><div id="sumcard"></div><div class="hero-r">
|
|
624
|
+
<h1>Your year with AI</h1><p class="sub">${C.length} cards from ${meta.sessions} sessions. Save the ones you like and post them.</p>
|
|
625
|
+
<div class="acts"><button type="button" class="btn" id="saveall">${DL}Save all cards</button><button type="button" class="btn ghost" id="replay"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M7 4.5v15l13-7.5z"/></svg>Play again</button></div>
|
|
626
|
+
${api ? `<div class="claimbox" id="claimbar"><div class="k">Next: make it a profile</div><div class="h">Claim your handle.</div><div class="pill"><span>usehour.com/</span>@<b id="clhandle">you</b></div><p>A profile companies hiring AI builders actually want to read. First come, first served. Private until you say otherwise.</p><button type="button" id="claimb">Claim your handle</button></div>` : ""}
|
|
627
|
+
</div></section>
|
|
628
|
+
<h2 class="sec">All your cards</h2><div class="grid" id="grid"></div>
|
|
629
|
+
<div class="tips"><div><b>What to post</b><span>Your summary card or your builder type, with one line on what surprised you.</span></div><div><b>Where they go</b><span>Saved cards land in your Downloads, sized for LinkedIn and X.</span></div><div><b>Come back any time</b><span>This page lives in the hour-wrapped folder where you ran the command. The numbers are in wrapped.json.</span></div></div>
|
|
556
630
|
${api ? CLAIM_HTML : ""}
|
|
557
631
|
</div>
|
|
558
632
|
<script>
|
|
@@ -588,13 +662,30 @@ g.font="650 "+W*.115+"px "+F;g.fillStyle=grad();g.fillText(c.type,P,W*.33);g.fil
|
|
|
588
662
|
g.font="450 "+W*.056+"px "+F;const L=wrapText(g,c.line,(W-2*P)*.92);L.reverse().forEach((l,i)=>g.fillText(l,P,y-i*W*.07));y-=L.length*W*.07+W*.034;
|
|
589
663
|
let bs=parseFloat(c.bs||"19")/100*W;g.font="650 "+bs+"px "+F;let Bl=wrapText(g,c.big,W-2*P);while(Bl.length>1&&Bl[Bl.length-1].length<=2&&bs>W*.08){bs*=.92;g.font="650 "+bs+"px "+F;Bl=wrapText(g,c.big,W-2*P)}g.fillStyle=c.grad?grad():c.fg;Bl.reverse().forEach((l,i)=>g.fillText(l,P,y-i*bs*.9));
|
|
590
664
|
g.fillStyle=c.fg;g.globalAlpha=.85;g.font="500 "+W*.044+"px "+F;wrapText(g,c.ask,(W-2*P)*.62).forEach((l,i)=>g.fillText(l,P,W*.16+i*W*.055));g.globalAlpha=1;
|
|
591
|
-
if(c.rows){const d=W*.
|
|
665
|
+
if(c.rows){const d=W*.023,gap=W*.009,pw=W*.135,dx=W-P-pw-10*d-9*gap;c.rows.forEach((w,k)=>{const yy=W*.29+k*W*.083;g.fillStyle=c.fg;g.font="500 "+W*.04+"px "+F;g.fillText(w[0],P,yy);g.font="600 "+W*.04+"px "+F;g.textAlign="right";g.fillText(w[2],W-P,yy);g.textAlign="left";for(let q=0;q<10;q++){const on=q<Math.round(w[1]*10);g.globalAlpha=on?1:.14;g.fillStyle=on?"#8B5FD9":c.fg;g.beginPath();g.arc(dx+q*(d+gap)+d/2,yy-W*.015,d/2,0,7);g.fill()}g.globalAlpha=1})}}
|
|
592
666
|
async function save(c){try{await document.fonts.ready}catch(e){}const cv=document.createElement("canvas");cv.width=1080;cv.height=1350;paint(cv.getContext("2d"),1080,1350,c);const a=document.createElement("a");a.download="hour-"+c.id+".png";a.href=cv.toDataURL("image/png");document.body.appendChild(a);a.click();a.remove()}
|
|
593
|
-
let cur=0
|
|
594
|
-
const
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
667
|
+
let cur=0,timer=null,t0=0,paused=false;const DUR=6000;
|
|
668
|
+
const $=id=>document.getElementById(id),slot=$("slot"),bars=$("bars"),grid=$("grid");
|
|
669
|
+
const N=D.cards.length+(D.api?1:0);for(let k=0;k<N;k++){const i=document.createElement("i");i.appendChild(document.createElement("b"));bars.appendChild(i)}
|
|
670
|
+
function fill(){[...bars.children].forEach((b,k)=>{b.firstChild.style.width=k<cur?"100%":k>cur?"0":b.firstChild.style.width})}
|
|
671
|
+
function tick(){if(paused||$("story").hidden)return;const p=Math.min(1,(performance.now()-t0)/DUR);bars.children[cur].firstChild.style.width=(p*100)+"%";if(p>=1){go(cur+1);return}timer=requestAnimationFrame(tick)}
|
|
672
|
+
function show(n){cancelAnimationFrame(timer);cur=Math.max(0,n);const cl=cur>=D.cards.length;slot.innerHTML="";slot.appendChild(cl?claimEl():el(D.cards[cur]));
|
|
673
|
+
$("hl").style.display=$("hr").style.display=cl?"none":"";$("sts").style.visibility=cl?"hidden":"";$("stz").style.visibility=cl?"hidden":"";
|
|
674
|
+
$("stn").textContent=(cur+1)+" of "+N;[...bars.children].forEach((b,k)=>{b.firstChild.style.width=k<cur?"100%":"0"});
|
|
675
|
+
if(cl){bars.children[cur].firstChild.style.width="100%";return}t0=performance.now();if(!paused)timer=requestAnimationFrame(tick)}
|
|
676
|
+
function go(n){if(n>=N){closeStory();return}show(n)}
|
|
677
|
+
function openStory(n){$("intro").hidden=true;$("story").hidden=false;document.body.style.overflow="hidden";paused=false;setPause();show(n||0)}
|
|
678
|
+
function closeStory(){cancelAnimationFrame(timer);$("story").hidden=true;$("intro").hidden=true;document.body.style.overflow=""}
|
|
679
|
+
function setPause(){$("stz").querySelector("span").textContent=paused?"Play":"Pause"}
|
|
680
|
+
function togglePause(){if(cur>=D.cards.length)return;paused=!paused;setPause();if(!paused){const w=parseFloat(bars.children[cur].firstChild.style.width)||0;t0=performance.now()-w/100*DUR;timer=requestAnimationFrame(tick)}else cancelAnimationFrame(timer)}
|
|
681
|
+
$("play").onclick=()=>openStory(0);$("skip").onclick=closeStory;$("replay").onclick=()=>openStory(0);$("stx").onclick=closeStory;
|
|
682
|
+
$("stp").onclick=$("hl").onclick=()=>show(cur-1);$("stf").onclick=$("hr").onclick=()=>go(cur+1);$("stz").onclick=togglePause;$("sts").onclick=()=>save(D.cards[cur]);
|
|
683
|
+
addEventListener("keydown",e=>{const o=$("cl");if(o&&!o.hidden)return;if($("story").hidden){if(!$("intro").hidden&&(e.key==="Enter"||e.key===" ")){e.preventDefault();openStory(0)}return}
|
|
684
|
+
if(e.key==="ArrowRight")go(cur+1);else if(e.key==="ArrowLeft")show(cur-1);else if(e.key===" "){e.preventDefault();togglePause()}else if(e.key==="Escape")closeStory()});
|
|
685
|
+
const SUM=D.cards.find(c=>c.summary);if(SUM){const e=el(SUM);e.style.cursor="pointer";e.onclick=()=>openStory(D.cards.indexOf(SUM));$("sumcard").appendChild(e)}
|
|
686
|
+
const DLI='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 4v11M7 10l5 5 5-5M5 20h14"/></svg>';
|
|
687
|
+
D.cards.forEach((c,k)=>{const w=document.createElement("div");const e=el(c);e.onclick=()=>openStory(k);const s=document.createElement("button");s.type="button";s.className="save";s.innerHTML=DLI+"Save";s.onclick=()=>save(c);w.append(e,s);grid.appendChild(w)});
|
|
688
|
+
$("saveall").onclick=async()=>{const b=$("saveall"),t=b.innerHTML;b.disabled=true;for(let i=0;i<D.cards.length;i++){b.textContent="Saving "+(i+1)+" of "+D.cards.length+"…";await save(D.cards[i]);await new Promise(r=>setTimeout(r,350))}b.innerHTML=t;b.disabled=false};
|
|
598
689
|
${api ? CLAIM_JS : ""}
|
|
599
690
|
</script></body></html>`;
|
|
600
691
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "usehour",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Your year of building with AI, as Wrapped-style cards, and an optional private profile. Reads your Claude Code and Codex logs locally.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": { "usehour": "hour.mjs" },
|