'use strict'; const $=id=>document.getElementById(id); function show(id,message,error=false){const out=$('result-'+id);out.textContent=message;out.classList.toggle('error',error)} for(const b of document.querySelectorAll('[data-tool]'))b.addEventListener('click',()=>{for(const choice of document.querySelectorAll('[data-tool]'))choice.setAttribute('aria-pressed',String(choice===b));for(const panel of document.querySelectorAll('.tool-panel'))panel.hidden=panel.id!=='panel-'+b.dataset.tool}); function date(prefix){return {year:Number($(prefix+'-year').value),era:$(prefix+'-era').value,month:Number($(prefix+'-month').value),day:Number($(prefix+'-day').value),calendar:$(prefix+'-calendar').value}} async function request(kind,data){const response=await fetch('/tools/api/'+kind,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(data),signal:AbortSignal.timeout(12000)});const result=await response.json();if(!response.ok)throw Error(result.error||'This tool is temporarily unavailable.');return result} const format=n=>new Intl.NumberFormat(undefined,{maximumSignificantDigits:14}).format(n); for(const kind of ['calculator','weekday','calendar','roman','units'])$('form-'+kind).addEventListener('submit',async event=>{event.preventDefault();const submit=event.currentTarget.querySelector('[type=submit]');submit.disabled=true;show(kind,'Working…');try{let data; if(kind==='calculator')data={expression:$('expression').value}; if(kind==='weekday')data=date('weekday'); if(kind==='calendar')data={...date('calendar'),target:$('calendar-target').value}; if(kind==='roman')data={direction:$('roman-direction').value,value:$('roman-direction').value==='encode'?Number($('roman-value').value):$('roman-value').value}; if(kind==='units')data={group:$('unit-group').value,value:$('unit-value').value,source:$('unit-source').value,target:$('unit-target').value}; const result=await request(kind,data);let output=typeof result.result==='number'?format(result.result):result.result; if(kind==='weekday')output+='\n'+(result.calendar==='julian'?'Julian':'Gregorian')+' calendar · Julian day number '+result.jdn; if(kind==='calendar')output+='\n'+(result.calendar==='julian'?'Julian':'Gregorian')+' calendar · Julian day number '+result.jdn; if(kind==='units')output+=' '+result.unit; show(kind,output); }catch(e){show(kind,e.name==='TimeoutError'?'This tool took too long. Please try again.':e.message==='Failed to fetch'?'Tools are temporarily unavailable.':e.message,true)}finally{submit.disabled=false}}); const units={length:[['m','Metres'],['km','Kilometres'],['cm','Centimetres'],['mm','Millimetres'],['in','Inches'],['ft','Feet'],['yd','Yards'],['mi','Miles']],mass:[['kg','Kilograms'],['g','Grams'],['mg','Milligrams'],['lb','Pounds'],['oz','Ounces']],temperature:[['C','Celsius'],['F','Fahrenheit'],['K','Kelvin']]}; function unitChoices(){for(const id of ['unit-source','unit-target']){$(id).replaceChildren();for(const [value,label] of units[$('unit-group').value]){const option=document.createElement('option');option.value=value;option.textContent=label;$(id).append(option)}}$('unit-target').selectedIndex=1;show('units','')} $('unit-group').addEventListener('change',unitChoices);unitChoices(); const text=$('text-value'),maxBytes=2*1024*1024;let previous=null; function textCounts(){const value=text.value,words=value.trim()?value.trim().split(/\s+/u).length:0,size=new TextEncoder().encode(value).length;$('text-counts').textContent=words.toLocaleString()+' words · '+Array.from(value).length.toLocaleString()+' characters · '+(value?value.split(/\r\n|\r|\n/).length:0).toLocaleString()+' lines · '+size.toLocaleString()+' bytes';if(size>maxBytes)show('text','Text exceeds the 2 MiB limit.',true);else show('text','');$('text-download').disabled=size>maxBytes;return size<=maxBytes} text.addEventListener('input',textCounts);textCounts(); for(const button of document.querySelectorAll('[data-clean]'))button.addEventListener('click',()=>{if(!textCounts())return;previous=text.value;const lines=text.value.replace(/\r\n?/g,'\n').split('\n');if(button.dataset.clean==='trim')text.value=lines.map(x=>x.trim()).join('\n');if(button.dataset.clean==='spaces')text.value=lines.map(x=>x.replace(/[\t ]+/g,' ').trim()).join('\n');if(button.dataset.clean==='blank')text.value=lines.filter(x=>x.trim()).join('\n');if(button.dataset.clean==='dedupe')text.value=[...new Set(lines)].join('\n');$('text-undo').disabled=false;textCounts()}); $('text-undo').addEventListener('click',()=>{if(previous===null)return;const current=text.value;text.value=previous;previous=current;textCounts()}); $('text-open').addEventListener('click',()=>$('text-file').click());$('text-file').addEventListener('change',async()=>{const file=$('text-file').files[0];if(!file)return;if(file.size>maxBytes){show('text','Choose a text file smaller than 2 MiB.',true);return}try{const loaded=await file.text();previous=text.value;text.value=loaded;$('text-undo').disabled=false;textCounts()}catch(e){show('text','Could not read that text file.',true)}finally{$('text-file').value=''}}); $('text-download').addEventListener('click',()=>{if(!textCounts())return;const url=URL.createObjectURL(new Blob([text.value],{type:'text/plain;charset=utf-8'}));const link=document.createElement('a');link.href=url;link.download='hosaka-text.txt';link.hidden=true;document.body.append(link);link.click();link.remove();setTimeout(()=>URL.revokeObjectURL(url),60000)}); const theme=$('theme');function themeLabel(){theme.textContent=document.documentElement.classList.contains('dark')?'Light mode':'Dark mode'}themeLabel();theme.addEventListener('click',()=>{const dark=document.documentElement.classList.toggle('dark');try{localStorage.setItem('hosaka-theme',dark?'dark':'light')}catch(e){}themeLabel()});