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 / c o n v e r s a t i o n s \. j s o n $ / i. test ( n ) ; } ) [ 0 ]
561+ || Object . keys ( files ) . filter ( function ( n ) { return / \. j s o n $ / 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 = / \. ( z i p | d m s ) $ / . 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