Skip to content

Commit 9b43d4e

Browse files
feat: accept .zip/.dms export directly + class tie-break (iteration 2)
- web path now accepts the raw claude.ai export .zip (or .dms) and finds conversations.json inside it client-side via vendored fflate — no manual unzip - tied top axes now render an honest hybrid class ("A / B") instead of silently picking one by axis order - title + demo card refreshed Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 9e34b46 commit 9b43d4e

2 files changed

Lines changed: 58 additions & 28 deletions

File tree

index.html

Lines changed: 57 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33
<head>
44
<meta charset="UTF-8">
55
<meta name="viewport" content="width=device-width, initial-scale=1">
6-
<title>Claude Status Check</title>
6+
<title>Claude Character Sheet — your Claude usage as an MMORPG stat card</title>
77
<meta name="description" content="เช็คสเตตัสการใช้ Claude ของคุณ — radar 6 สาย + class + level สไตล์ MMORPG · ข้อมูลไม่ออกจากเครื่อง">
88
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Crect width='100' height='100' rx='20' fill='%230a120e'/%3E%3Cpolygon points='50,15 80,35 72,75 28,75 20,35' fill='none' stroke='%2334d399' stroke-width='6'/%3E%3C/svg%3E">
99
<link href="https://fonts.googleapis.com/css2?family=Kanit:wght@300;400;600;800&family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet">
1010
<script src="lib/chart.umd.min.js"></script>
1111
<script src="lib/html-to-image.min.js"></script>
12+
<script src="lib/fflate.min.js"></script>
1213
<script src="lib/scoring.js"></script>
1314
<style>
1415
* { margin: 0; padding: 0; box-sizing: border-box; }
@@ -179,13 +180,13 @@ <h2><span id="entry2Title">สาย claude.ai</span> <span class="badge">BETA</
179180
<div class="big" id="dropBig">วางไฟล์ conversations.json ที่นี่</div>
180181
<div class="hint" id="dropHint">หรือคลิกเพื่อเลือกไฟล์</div>
181182
</div>
182-
<input type="file" id="fileInput" accept=".json,application/json" class="hidden">
183+
<input type="file" id="fileInput" accept=".zip,.dms,.json,application/json,application/zip" class="hidden">
183184
<div class="steps" id="steps">ขอไฟล์: Settings → Privacy → Export data → ไฟล์จะส่งมาทางอีเมล แตก zip แล้วเลือก <code>conversations.json</code></div>
184185
<div class="privacy"><b>Privacy:</b> <span id="privacy2Text">ไฟล์ถูกอ่านในเครื่องเท่านั้น ไม่มีการอัปโหลด</span></div>
185186
</div>
186187
</div>
187188
<div class="demo-row">
188-
<a id="demoLink" href="#s=eyJ2IjoxLCJzcmMiOiJjbGkiLCJzdGF0cyI6eyJhdXRvbWF0b3IiOjkxLCJyZXNlYXJjaGVyIjo5MywiY29kZXIiOjkzLCJpbnRlZ3JhdG9yIjo3MSwid3JpdGVyIjo4OCwicGlsb3QiOjQ1fSwidG90YWxzIjp7InNlc3Npb25zIjo0MjYsInRvb2xDYWxscyI6NTc0MzcsInVzZXJNc2dzIjo1ODk3OH19">ดูการ์ดตัวอย่างก่อน →</a>
189+
<a id="demoLink" href="#s=eyJ2IjoxLCJzcmMiOiJjbGkiLCJzdGF0cyI6eyJhdXRvbWF0b3IiOjkxLCJyZXNlYXJjaGVyIjo5NCwiY29kZXIiOjkzLCJpbnRlZ3JhdG9yIjo3MCwid3JpdGVyIjo4OCwicGlsb3QiOjUxfSwidG90YWxzIjp7InNlc3Npb25zIjo0MzQsInRvb2xDYWxscyI6NTc2MTQsInVzZXJNc2dzIjo2ODQ5MX19">ดูการ์ดตัวอย่างก่อน →</a>
189190
</div>
190191
</div>
191192

@@ -242,16 +243,17 @@ <h2><span id="entry2Title">สาย claude.ai</span> <span class="badge">BETA</
242243
privacy1Text: 'ไฟล์ในเครื่องถูกอ่านในเครื่องเท่านั้น ไม่อัปโหลด · ลิงก์แชร์มีแค่คะแนนสรุป',
243244
entry2Title: 'สาย claude.ai',
244245
entry2Sub: 'ใช้ไฟล์ export จาก claude.ai — วิเคราะห์ในเบราว์เซอร์นี้ทั้งหมด',
245-
dropBig: 'วางไฟล์ conversations.json ที่นี่',
246-
dropHint: 'หรือคลิกเพื่อเลือกไฟล์',
247-
steps: 'ขอไฟล์: Settings → Privacy → Export data → ไฟล์จะส่งมาทางอีเมล แตก zip แล้วเลือก ',
246+
dropBig: 'วางไฟล์ export ที่นี่ (.zip ได้เลย)',
247+
dropHint: 'หรือคลิกเพื่อเลือกไฟล์ — ไม่ต้องแตก zip',
248+
steps: 'ขอไฟล์: Settings → Privacy → Export data → ไฟล์ zip มาทางอีเมล ลากทั้งไฟล์มาวางได้เลย (หรือไฟล์ ',
248249
privacy2Text: 'ไฟล์ถูกอ่านในเครื่องเท่านั้น ไม่มีการอัปโหลด',
249250
demoLink: 'ดูการ์ดตัวอย่างก่อน →',
250251
copyCmdBtn: 'คัดลอก',
251252
pngBtn: 'ดาวน์โหลดการ์ด PNG',
252253
copyLinkBtn: 'คัดลอกลิงก์แชร์',
253254
resetBtn: 'สร้างการ์ดของคุณ →',
254255
webEstimate: '⚠ ประเมินคร่าวๆ จากข้อความ · เทียบกับการ์ดสาย CLI ไม่ได้',
256+
hybridNote: 'สายผสม — คะแนนสูงสุดเสมอกันสองด้าน',
255257
badLink: 'ลิงก์ไม่ถูกต้องหรือเสียหาย — กลับสู่หน้าแรก',
256258
copiedCmd: 'คัดลอกคำสั่งแล้ว — ไปวางใน terminal ได้เลย',
257259
copiedLink: 'คัดลอกลิงก์แล้ว — ในลิงก์มีแค่คะแนน 6 ค่า + ยอดรวม 3 ตัว (session · tool call · ข้อความ)',
@@ -274,16 +276,17 @@ <h2><span id="entry2Title">สาย claude.ai</span> <span class="badge">BETA</
274276
privacy1Text: 'Local files are read on your machine and never uploaded · the share link carries only summary scores',
275277
entry2Title: 'claude.ai users',
276278
entry2Sub: 'Use your claude.ai data export — analyzed entirely in this browser.',
277-
dropBig: 'Drop conversations.json here',
278-
dropHint: 'or click to choose a file',
279-
steps: 'Get the file: Settings → Privacy → Export data → it arrives by email; unzip and pick ',
279+
dropBig: 'Drop your export here (.zip works)',
280+
dropHint: 'or click to choose — no need to unzip',
281+
steps: 'Get the file: Settings → Privacy → Export data → a .zip arrives by email; drop the whole zip here (or the ',
280282
privacy2Text: 'The file is read locally — nothing is uploaded',
281283
demoLink: 'See a sample card first →',
282284
copyCmdBtn: 'Copy',
283285
pngBtn: 'Download card PNG',
284286
copyLinkBtn: 'Copy share link',
285287
resetBtn: 'Make your own →',
286288
webEstimate: '⚠ Rough estimate inferred from text · not comparable to CLI cards',
289+
hybridNote: 'Hybrid build — your top two axes are tied',
287290
badLink: 'Invalid or corrupted link — back to home',
288291
copiedCmd: 'Command copied — paste it in your terminal',
289292
copiedLink: 'Link copied — it carries only the 6 scores + 3 totals (sessions · tool calls · messages)',
@@ -324,6 +327,7 @@ <h2><span id="entry2Title">สาย claude.ai</span> <span class="badge">BETA</
324327
var code = document.createElement('code');
325328
code.textContent = 'conversations.json';
326329
steps.appendChild(code);
330+
steps.appendChild(document.createTextNode(lang === 'th' ? ' ที่อยู่ข้างใน)' : ' inside it)'));
327331
document.getElementById('langBtn').textContent = lang === 'th' ? 'EN' : 'ไทย';
328332
if (current) renderCard(current);
329333
}
@@ -392,8 +396,16 @@ <h2><span id="entry2Title">สาย claude.ai</span> <span class="badge">BETA</
392396
var effort = isWeb ? (Number(totals.userMsgs) || 0) * 4 : (Number(totals.toolCalls) || 0);
393397
var lv = S.level(effort);
394398

395-
document.getElementById('className').textContent = cls[0];
396-
document.getElementById('classSub').textContent = isWeb ? t('webEstimate') : ((lang === 'th' ? cls[1] : cls[2]) || cls[1]);
399+
var topScore = stats[topKey];
400+
var tied = S.AXES.filter(function (k) { return stats[k] === topScore; });
401+
var nameText = cls[0];
402+
if (tied.length > 1) {
403+
nameText = tied.slice(0, 2).map(function (k) { return S.CLASSES[k][0]; }).join(' / ');
404+
}
405+
document.getElementById('className').textContent = nameText;
406+
document.getElementById('classSub').textContent = isWeb
407+
? t('webEstimate')
408+
: (tied.length > 1 ? t('hybridNote') : ((lang === 'th' ? cls[1] : cls[2]) || cls[1]));
397409
document.getElementById('lv').textContent = String(lv);
398410
document.getElementById('srcBadge').textContent = isWeb ? 'WEB · BETA' : 'CLI';
399411

@@ -528,27 +540,44 @@ <h2><span id="entry2Title">สาย claude.ai</span> <span class="badge">BETA</
528540
};
529541
}
530542

543+
function finishParse(parsed) {
544+
if (!parsed) { toast(t('badFormat'), true); return; }
545+
var stats = S.scoreAxes(parsed.raw, 'web');
546+
var encoded = S.encodeShare({ v: 1, src: 'web', stats: stats, totals: parsed.totals });
547+
window.location.hash = '#s=' + encoded;
548+
}
549+
550+
function parseJsonText(text) {
551+
try { return parseClaudeExport(JSON.parse(text)); } catch (e) { return null; }
552+
}
553+
554+
// claude.ai exports arrive as a .zip (sometimes a .dms = renamed .zip). Accept them
555+
// directly so the user never has to unzip — find conversations.json inside, in-browser.
556+
function handleZip(buf) {
557+
if (!window.fflate || !fflate.unzipSync) { toast(t('badFormat'), true); return; }
558+
var files;
559+
try { files = fflate.unzipSync(new Uint8Array(buf)); } catch (e) { toast(t('badFormat'), true); return; }
560+
var key = Object.keys(files).filter(function (n) { return /conversations\.json$/i.test(n); })[0]
561+
|| Object.keys(files).filter(function (n) { return /\.json$/i.test(n); })[0];
562+
if (!key) { toast(t('badFormat'), true); return; }
563+
var text;
564+
try { text = fflate.strFromU8(files[key]); } catch (e) { toast(t('badFormat'), true); return; }
565+
finishParse(parseJsonText(text));
566+
}
567+
531568
function handleFile(file) {
532569
if (!file) return;
570+
var name = (file.name || '').toLowerCase();
571+
var isZip = /\.(zip|dms)$/.test(name);
533572
var reader = new FileReader();
534573
reader.onerror = function () { toast(t('readFail')); };
535-
reader.onload = function () {
536-
var parsed = null;
537-
try {
538-
var json = JSON.parse(String(reader.result));
539-
parsed = parseClaudeExport(json);
540-
} catch (e) {
541-
parsed = null;
542-
}
543-
if (!parsed) {
544-
toast(t('badFormat'), true);
545-
return;
546-
}
547-
var stats = S.scoreAxes(parsed.raw, 'web');
548-
var encoded = S.encodeShare({ v: 1, src: 'web', stats: stats, totals: parsed.totals });
549-
window.location.hash = '#s=' + encoded;
550-
};
551-
reader.readAsText(file, 'utf-8');
574+
if (isZip) {
575+
reader.onload = function () { handleZip(reader.result); };
576+
reader.readAsArrayBuffer(file);
577+
} else {
578+
reader.onload = function () { finishParse(parseJsonText(String(reader.result))); };
579+
reader.readAsText(file, 'utf-8');
580+
}
552581
}
553582

554583
/* ---------- wire up ---------- */

0 commit comments

Comments
 (0)