import {addUnique,assessConstraints,linearGrams,gsmFromLinear,gsmFromOzYd2,ozYd2FromGsm,areaMass,makeBrief,safeTabularCell} from './logic.mjs'; import {PREFIX,DraftStore,strictNumber,validateTargets,normalisePublicIds,toMetres,convertUnit,trialText} from './state.mjs'; const $=s=>document.querySelector(s), $$=s=>[...document.querySelectorAll(s)]; const feedback=$('#feedback');let feedbackTimer; function say(message){feedback.textContent=message;clearTimeout(feedbackTimer);feedbackTimer=setTimeout(()=>{feedback.textContent='';},14000);} function el(tag,text,cls){const x=document.createElement(tag);if(text!==undefined)x.textContent=text;if(cls)x.className=cls;return x;} function event(name,properties={}){document.dispatchEvent(new CustomEvent('melton:event',{detail:{name,page_id:document.body.dataset.pageId,locale:document.body.dataset.locale||'en',...properties}}));} let session,local;try{session=sessionStorage;}catch{session={getItem:()=>null,setItem:()=>{throw Error();},removeItem:()=>{throw Error();}};}try{local=localStorage;}catch{local={getItem:()=>null,setItem:()=>{throw Error();},removeItem:()=>{throw Error();}};} const store=new DraftStore(session);let saved=store.read().fields;let references=[]; try{references=await (await fetch(new URL('./references.json',import.meta.url))).json();}catch{say('Reference data is unavailable. You can still prepare an enquiry from your own project.');} const ref=id=>references.find(r=>r.id===id); function ids(type){try{return normalisePublicIds(local.getItem(PREFIX+type),type==='compare'?3:12);}catch{return [];}} function setIds(type,a){try{local.setItem(PREFIX+type,JSON.stringify(a));return true;}catch{say('Reference storage is unavailable. Keep the reference name in your enquiry.');return false;}} function formData(form){return Object.fromEntries(new FormData(form));} function saveForm(form,prefix=''){const fields=formData(form);if(prefix){store.write({[prefix]:fields});}else{saved={...saved,...fields};store.write(fields);}if(store.error)say(store.error);return fields;} function restore(form,values){if(!form)return;for(const [name,value] of Object.entries(values||{})){const input=form.elements.namedItem(name);if(input&&typeof value==='string')input.value=value;}} function saveSnapshots(){const snapshots=ids('shortlist').map(id=>{const r=ref(id);const old=(saved.referenceSnapshots||[]).find(r=>r.id===id);return r?{id:r.id,name:r.name,kind:r.kind,article_code:r.article_code}:old||{id,name:id,kind:'unavailable'};});saved.referenceSnapshots=snapshots;store.write({referenceSnapshots:snapshots});} function shortlist(){for(const host of $$('[data-shortlist]')){host.replaceChildren();const a=ids('shortlist');if(!a.length){host.append(el('p','You have not selected a reference. You can prepare an enquiry from your own fabric or garment idea.','small'));continue;}for(const id of a){const r=ref(id),old=(saved.referenceSnapshots||[]).find(x=>x.id===id);const item=el('div',undefined,'list-item'),text=el('div',r?.name||old?.name||id);text.append(el('small',r?'Development direction · composition, GSM and width need confirmation':'Unavailable in the current catalogue · ask Fred to reconfirm'));const b=el('button','Remove','outline');b.type='button';b.addEventListener('click',()=>{setIds('shortlist',ids('shortlist').filter(x=>x!==id));saveSnapshots();shortlist();event('shortlist_remove',{reference_id:id});say('Reference removed from shortlist.');});item.append(text,b);host.append(item);}}} $$('[data-add]').forEach(b=>b.addEventListener('click',()=>{const id=b.dataset.add;if(!ref(id)){say('Reference is not available. Ask Fred to confirm.');return;}const result=addUnique(ids('shortlist'),id,12);if(result.status==='added'&&setIds('shortlist',result.ids)){saveSnapshots();shortlist();event('shortlist_add',{reference_id:id});}say(({added:'Added to your shortlist.',already_added:'Already added.',limit_reached:'Shortlist limit is 12. Remove a reference before adding another.'})[result.status]);})); $$('[data-clear-shortlist]').forEach(b=>b.addEventListener('click',()=>{setIds('shortlist',[]);saveSnapshots();shortlist();say('Shortlist cleared.');})); function compare(){const host=$('#compare-table');if(!host)return;host.replaceChildren();const a=ids('compare');if(a.length<2)host.append(el('p','Add another reference for a side-by-side comparison. The initial catalogue currently has one sourced direction. A single reference can still be included in your brief.','draft-note'));if(!a.length)return;const wrap=el('div',undefined,'table-wrap');wrap.tabIndex=0;wrap.setAttribute('aria-label','Scrollable reference comparison');const table=el('table'),thead=el('thead'),heading=el('tr');heading.append(el('th','Field'));for(const id of a){const cell=el('th',ref(id)?.name||id);cell.scope='col';const remove=el('button','Remove','text-button');remove.type='button';remove.addEventListener('click',()=>{setIds('compare',ids('compare').filter(x=>x!==id));compare();say('Reference removed from comparison.');});cell.append(remove);heading.append(cell);}thead.append(heading);const body=el('tbody');for(const [label,key] of [['Record type','kind'],['Factory article code','article_code'],['Composition','wool_pct'],['GSM','gsm'],['Usable width','usable_width_cm'],['Total width','total_width_cm'],['Source status','source']]){const row=el('tr');const title=el('th',label);title.scope='row';row.append(title);for(const id of a){const r=ref(id);let value='Unavailable / ask Fred';if(r){value=key==='kind'?'Development direction':key==='article_code'?(r.article_code||'Not assigned'):key==='source'?'Public collection direction; article details unconfirmed':r.fields[key]?.value!=null&&r.fields[key]?.evidence_status==='confirmed'?`${r.fields[key].value} ${r.fields[key].unit}`:'To be confirmed';}row.append(el('td',value));}body.append(row);}table.append(thead,body);wrap.append(table);host.append(wrap);event('compare_open');} $$('[data-compare]').forEach(b=>b.addEventListener('click',()=>{const id=b.dataset.compare;if(!ref(id))return;const result=addUnique(ids('compare'),id,3);if(result.status==='added')setIds('compare',result.ids);compare();say(({added:'Added to comparison.',already_added:'Already in comparison.',limit_reached:'Remove one reference to compare another. Maximum three.'})[result.status]);})); shortlist();compare(); function download(text,name,type='text/plain;charset=utf-8'){const blob=new Blob([text],{type});const url=URL.createObjectURL(blob);const a=el('a');a.href=url;a.download=name;document.body.append(a);a.click();a.remove();setTimeout(()=>URL.revokeObjectURL(url),1000);} const filename=stem=>stem+'_'+new Date().toISOString().replace(/[^0-9]/g,'').slice(0,14)+'.txt'; function printText(text){const node=el('pre',text,'print-document'),main=$('main');const hidden=[...document.body.children].filter(x=>x!==node);for(const x of hidden)x.hidden=true;document.body.append(node);const cleanup=()=>{node.remove();for(const x of hidden)x.hidden=false;window.removeEventListener('afterprint',cleanup);};window.addEventListener('afterprint',cleanup);window.print();setTimeout(cleanup,3000);} function buildBrief(d){validateTargets(d);const current=ids('shortlist').map(id=>{const r=ref(id);const old=(saved.referenceSnapshots||[]).find(x=>x.id===id);return r||{id,name:(old?.name||id)+' — unavailable; ask Fred to reconfirm',kind:old?.kind||'unavailable'};});const hard=[d.compositionHard==='yes'?d.composition:'',d.wool&&d.compositionHard==='yes'?`${d.wool}% wool; other fibre ${d.otherFibre||'not specified'}%`:'',d.care?`Care requirement: ${d.care}`:'',d.minGsm?`Minimum GSM: ${d.minGsm}`:'',d.maxGsm?`Maximum GSM: ${d.maxGsm}`:''].filter(Boolean).join('; ');const garmentLabels={tailored_coat:'Tailored coat',relaxed_coat:'Relaxed or belted coat',pea_coat:'Pea coat',short_jacket:'Short jacket',cape:'Cape',duffle_style:'Duffle-style coat',uniform_style:'Uniform-style coat',other:'Other'};const sampleLabels={advice:'Advice on the next sample',swatch:'Swatches',larger_cutting:'Larger cutting',metreage:'Sample metreage'};const formatted={...d,endUse:garmentLabels[d.endUse]||d.endUse,sampleRequirement:sampleLabels[d.sampleRequirement]||d.sampleRequirement,references:current,hardRequirements:hard,targetSpecification:[d.weight?`${d.weight} ${d.weightUnit}`:'',d.width?`${d.width} ${d.widthUnit||'cm'} usable width`:'',d.calculation||''].filter(Boolean).join('; '),quantityColours:[d.quantity?`${d.quantity} ${d.quantityUnit}`:'',d.byColour?`Per colour: ${d.byColour}`:'',d.colour?`Colour: ${d.colour}`:''].filter(Boolean).join('; '),targetSchedule:d.schedule,openQuestions:d.notes};return makeBrief(formatted)+'\n\nAdditional buyer targets:\n'+[`Composition preference: ${d.composition||'Open'}`,`Shape / surface: ${[d.shape,d.surface,d.surfaceNote].filter(Boolean).join('; ')||'Open'}`,`Reply name: ${d.name||'Optional'}`,`Company: ${d.company||'Optional'}`,`Reply contact: ${d.reply||'Optional'}`,d.trialRecord?`\nGarment trial record:\n${d.trialRecord}`:''].filter(Boolean).join('\n');} const enquiry=$('#enquiry-form');restore(enquiry,saved);let fullBrief=''; if(enquiry){enquiry.addEventListener('input',()=>{saveForm(enquiry);const count=[...enquiry.querySelectorAll('details input,details textarea')].filter(x=>x.value.trim()).length;$('#detail-count').textContent=count?`(${count} filled)`:'';});enquiry.addEventListener('change',()=>saveForm(enquiry));enquiry.addEventListener('submit',e=>{e.preventDefault();try{const d={...saved,...saveForm(enquiry)};fullBrief=buildBrief(d);$('#brief-output').value=fullBrief;$('#brief-review').hidden=false;const projectLabel=d.endUse?enquiry.elements.endUse.selectedOptions[0].textContent:'Fabric development';const subject='MeltonFabric sample enquiry — '+projectLabel;const summary=`Source: meltonfabric.com\nProject: ${projectLabel}\nPlease see my complete sample brief, which I will paste or attach.`;const mail='mailto:mrkcal@fredtextile.com?subject='+encodeURIComponent(subject)+'&body='+encodeURIComponent(fullBrief);$('#email-brief').href='mailto:mrkcal@fredtextile.com?subject='+encodeURIComponent(subject)+'&body='+encodeURIComponent(summary);$('#wa-brief').href='https://wa.me/18182620958?text='+encodeURIComponent(summary);$('#brief-review').scrollIntoView({block:'start'});say(mail.length>1800?'Complete brief ready. Copy the full text or attach the TXT to the short email summary.':'Brief ready. Copy the full text or attach the TXT when sending your message.');}catch(error){say(error.message);}});} $$('[data-brief]').forEach(b=>b.addEventListener('click',async()=>{if(!fullBrief)return;const type=b.dataset.brief;if(type==='copy'){try{await navigator.clipboard.writeText(fullBrief);say('Brief copied.');event('brief_copy');}catch{$('#brief-output').focus();$('#brief-output').select();say('Copying was unavailable. The complete brief is selected; copy it manually.');}}if(type==='download'){download(fullBrief,filename('MeltonFabric_brief'));say('Complete UTF-8 brief downloaded.');event('brief_download');}if(type==='print')printText(fullBrief);})); document.addEventListener('click',e=>{const a=e.target.closest?.('a[href]');if(!a)return;const href=a.getAttribute('href');if(href.startsWith('mailto:'))event('contact_intent',{channel:'email'});else if(href.startsWith('https://wa.me/18182620958'))event('contact_intent',{channel:'whatsapp'});else if(href==='https://fredtextile.com/contact/')event('contact_intent',{channel:'fred_contact'});}); if(document.body.dataset.pageId==='MF-008')event('reference_view',{reference_id:'MF-DIR-CLASSIC-01'}); $$('[data-clear-draft]').forEach(b=>b.addEventListener('click',()=>{const existing=$('#clear-confirm');if(existing)return;const box=el('div',undefined,'draft-note');box.id='clear-confirm';box.append(el('p','Clear the information saved by this site in this tab? Download your brief first if you want to keep a copy.'));const yes=el('button','Clear saved draft'),no=el('button','Keep draft','outline');yes.type=no.type='button';yes.addEventListener('click',()=>{if(store.clear()){saved={};enquiry?.reset();fullBrief='';if($('#brief-review'))$('#brief-review').hidden=true;say('This site’s tab draft cleared.');box.remove();}else say(store.error);});no.addEventListener('click',()=>box.remove());box.append(yes,no);b.parentElement.after(box);})); const finder=$('#finder-form');let step=0;restore(finder,saved); function showStep(){if(!finder)return;$$('[data-step]').forEach(x=>x.hidden=Number(x.dataset.step)!==step);$$('[data-step-mark]').forEach(x=>x.classList.toggle('active',Number(x.dataset.stepMark)===step));$('#finder-back').hidden=step===0;$('#finder-next').hidden=step===5;$('#finder-complete').hidden=step!==5;} if(finder){showStep();finder.addEventListener('input',()=>saveForm(finder));$('#finder-next').addEventListener('click',()=>{try{const d=saveForm(finder);if(step===0&&!d.endUse&&!d.description.trim())throw new Error('Choose a garment or add a project description.');if(step>=3)validateTargets(d);step=Math.min(5,step+1);showStep();}catch(e){say(e.message);}});$('#finder-back').addEventListener('click',()=>{saveForm(finder);step=Math.max(0,step-1);showStep();});finder.addEventListener('submit',e=>{e.preventDefault();try{const d=saveForm(finder),v=validateTargets(d);const constraints=[];if(d.compositionHard==='yes'&&v.wool!=null)constraints.push({field:'wool_pct',op:'eq',value:v.wool});if(v.min!=null)constraints.push({field:'gsm',op:'gte',value:v.min});if(v.max!=null)constraints.push({field:'gsm',op:'lte',value:v.max});if(d.care)constraints.push({field:'care',op:'eq',value:d.care});if(d.width)constraints.push({field:'usable_width_cm',op:'gte',value:Number(d.width)});const candidates=references.filter(r=>r.uses?.some(x=>x.tag==='outerwear'));const host=$('#finder-result');host.hidden=false;host.replaceChildren(el('h2','Discuss a suitable reference with Fred'));let visible=0;for(const r of candidates){const a=assessConstraints(r,constraints);if(a.status==='does_not_meet')continue;if(visible++>=3)break;const card=el('article',undefined,'result');card.append(el('h3',r.name),el('p',constraints.length||d.compositionHard==='yes'?'Needs confirmation: current article-specific evidence is required for your hard requirements.':'No hard constraints assessed. This is a sourced direction for an outerwear discussion, not a confirmed match.'),el('p','Confirm composition, weight, usable width and sample availability. Try the important garment details with the actual support materials.'));const add=el('button','Add to shortlist');add.type='button';add.addEventListener('click',()=>{const result=addUnique(ids('shortlist'),r.id,12);if(result.status==='added')setIds('shortlist',result.ids);saveSnapshots();say(result.status==='limit_reached'?'Shortlist limit reached. Remove one reference first.':result.status==='already_added'?'Already added.':'Added to shortlist.');});card.append(add);host.append(card);}if(!visible)host.append(el('p','No applicable reference is available for this project. Describe it to Fred for advice.'));const next=el('a','Prepare your sample enquiry','button');next.href='/request-samples/';host.append(next);event('finder_complete',{tool_id:'coat-finder'});host.scrollIntoView({block:'start'});}catch(error){say(error.message);}});} const weightForm=$('#weight-form');let calculation=''; if(weightForm){const widthUnit=weightForm.elements.measuredWidthUnit,lengthUnit=weightForm.elements.lengthUnit;let previousWidth=widthUnit.value,previousLength=lengthUnit.value;const origins=[];for(const [selector,input,previous] of [[widthUnit,weightForm.elements.measuredWidth,'width'],[lengthUnit,weightForm.elements.length,'length']])selector.addEventListener('change',()=>{try{const before=previous==='width'?previousWidth:previousLength;if(input.value){const n=strictNumber(input.value,input.name);origins.push(`${input.name}: ${input.value} ${before}`);input.value=String(Number(convertUnit(n,before,selector.value).toPrecision(12)));say('Value converted to the selected unit; the original input is retained with the result.');}if(previous==='width')previousWidth=selector.value;else previousLength=selector.value;}catch(error){selector.value=previous==='width'?previousWidth:previousLength;say(error.message);}});weightForm.addEventListener('submit',e=>{e.preventDefault();try{const d=formData(weightForm),n=strictNumber(d.massValue,'Weight');let output,formula,basis='';const round=x=>Number(x.toPrecision(10));if(d.mode==='oz'){output=round(gsmFromOzYd2(n))+' GSM';formula=`${n} oz/yd² × (28.349523125 ÷ 0.9144²)`;}else if(d.mode==='fromoz'){output=round(ozYd2FromGsm(n))+' oz/yd²';formula=`${n} GSM ÷ (28.349523125 ÷ 0.9144²)`;}else{const width=toMetres(strictNumber(d.measuredWidth,'Measured width'),d.measuredWidthUnit);if(d.mode==='linear'){output=round(linearGrams(n,width))+' g/linear m';formula=`${n} GSM × ${round(width)} m measured width`;}else if(d.mode==='gsm'){output=round(gsmFromLinear(n,width))+' GSM';formula=`${n} g/linear m ÷ ${round(width)} m measured width`;}else{const length=toMetres(strictNumber(d.length,'Length'),d.lengthUnit);const result=areaMass(n,width,length,d.widthBasis);output=round(result.kg)+' kg';basis=result.label;formula=`${n} GSM × ${round(width)} m × ${round(length)} m ÷ 1000`;}}const result=$('#weight-result');result.hidden=false;result.replaceChildren(el('strong',output),el('p',formula),el('p',basis||'The width corresponds to the material described by the input weight.'));if(origins.length)result.append(el('p','Original input before unit conversion: '+origins.join('; '),'small'));calculation=[output,formula,basis,origins.length?'Original input: '+origins.join('; '):''].filter(Boolean).join('\n');$('#weight-add').hidden=false;}catch(error){$('#weight-result').hidden=true;$('#weight-add').hidden=true;say(error.message);}});$('#weight-add').addEventListener('click',()=>{store.write({calculation});say('Calculation saved in this tab’s enquiry draft.');});} const trial=$('#trial-form');let trialRecord='';restore(trial,saved.trial||{}); if(trial){trial.addEventListener('input',()=>saveForm(trial,'trial'));trial.addEventListener('submit',e=>{e.preventDefault();try{const d=saveForm(trial,'trial');trialRecord=trialText(d);$('#trial-output').value=trialRecord;$('#trial-review').hidden=false;say('Trial record ready for buyer discussion.');}catch(error){say(error.message);}});$('#trial-download').addEventListener('click',()=>{download(trialRecord,filename('MeltonFabric_trial'));event('trial_export',{tool_id:'garment-trial'});});$('#trial-tsv').addEventListener('click',()=>{const d=formData(trial);download(['Field\tBuyer entry',...Object.entries(d).map(([k,v])=>safeTabularCell(k)+'\t'+safeTabularCell(v))].join('\r\n'),filename('MeltonFabric_trial').replace('.txt','.tsv'),'text/tab-separated-values;charset=utf-8');});$('#trial-to-brief').addEventListener('click',()=>{store.write({trialRecord});say('Trial record added to the enquiry draft in this tab.');});$('#trial-blank').addEventListener('click',()=>printText('MeltonFabric coat detail trial worksheet\n\nReference:\nDetail:\nGoal:\nFace fabric:\nFacing:\nInterfacing:\nLining:\nPattern version:\nConditions:\nObservation after trial:\nOpen questions:\n\nBuyer record; not factory approval.'));}