1(() => { 2const fields={};['start_local','end_local','rsvp_closes_at'].forEach(id=>fields[id]=document.getElementById(id)); 3if(!fields.start_local)return; 4const shell=document.createElement('div');shell.innerHTML="<dialog id=\"fe-date-dialog\" aria-labelledby=\"title\"><div class=\"top\"><h2 id=\"title\">When\u2019s your event?</h2><button class=\"close\" aria-label=\"Close\">\u00d7</button></div><div class=\"tabs\"><button class=\"tab active\" data-side=\"start\"><small>START</small><span id=\"startSummary\">Choose a date & time</span></button><button class=\"tab\" data-side=\"end\"><small>END \u00b7 OPTIONAL</small><span id=\"endSummary\">Add an end time</span></button></div><div class=\"picker\"><div class=\"calendar\"><div class=\"month\"><button class=\"arrow\" id=\"prev\" aria-label=\"Previous month\">\u2039</button><strong id=\"month\"></strong><button class=\"arrow\" id=\"next\" aria-label=\"Next month\">\u203a</button></div><div class=\"week\"><span>Sun</span><span>Mon</span><span>Tue</span><span>Wed</span><span>Thu</span><span>Fri</span><span>Sat</span></div><div class=\"days\"></div></div><div class=\"times\"><h3 id=\"timeTitle\">Start time</h3><div class=\"list\"></div><div class=\"custom\"><label for=\"customTime\">Or enter a time</label><input id=\"customTime\" placeholder=\"e.g. 6:10 pm\" autocomplete=\"off\"></div></div></div><div class=\"zone\" style=\"display:none\"><label for=\"zone\">Time zone</label><select id=\"zone\"><option value=\"America/Chicago\">Central \u00b7 America/Chicago</option><option value=\"America/New_York\">Eastern \u00b7 America/New_York</option><option value=\"America/Denver\">Mountain \u00b7 America/Denver</option><option value=\"America/Los_Angeles\">Pacific \u00b7 America/Los_Angeles</option><option value=\"UTC\">UTC</option></select></div><div class=\"error\" role=\"status\"></div><div class=\"bottom\"><button class=\"clear\">Remove end time</button><button class=\"save\">Save date & time</button></div></dialog>";document.body.append(shell);const root=shell.querySelector('dialog');let deadline=false;let opener=null;root.addEventListener('close',()=>opener?.focus()); 5const triggers={}; 6function read(field){const m=(field?.value||'').match(/^(\d{4}-\d{2}-\d{2})[ T](\d{2}:\d{2})/);return m?{date:m[1],time:m[2]}:{date:'',time:''}} 7function write(field,v){if(!field)return;field.value=v.date&&v.time?v.date+' '+v.time:'';field.dispatchEvent(new Event('change',{bubbles:true}));refresh()} 8function refresh(){Object.entries(fields).forEach(([id,f])=>{if(f)triggers[id].textContent=summary(read(f))||(id==='start_local'?'Choose start date & time':id==='end_local'?'Add end date & time (optional)':'Choose RSVP deadline (optional)')})} 9function open(id){opener=triggers[id];deadline=id==='rsvp_closes_at';side=id==='end_local'?'end':'start';state=deadline?{start:read(fields.rsvp_closes_at),end:{date:'',time:''}}:{start:read(fields.start_local),end:read(fields.end_local)};if(state[side].date){const d=new Date(state[side].date+'T12:00:00');year=d.getFullYear();month=d.getMonth()}root.querySelector('#title').textContent=deadline?'When do RSVPs close?':'Whenâs your event?';root.querySelector('.tabs').hidden=deadline;const tz=fields.start_local.form?.querySelector('[name=timezone]')?.value||'America/Chicago';root.querySelector('.zone').style.display='flex';root.querySelector('.zone').textContent='Time zone: '+tz.replaceAll('_',' ');root.querySelector('.clear').textContent=deadline?'Remove deadline':'Remove end time';custom.value=state[side].time?labelTime(state[side].time):'';root.querySelector('.error').textContent='';render();dialog.showModal();scrollTime()} 10Object.entries(fields).forEach(([id,f])=>{if(!f)return;const trigger=document.createElement('button');trigger.type='button';trigger.className='fe-date-trigger';trigger.id=id+'-trigger';document.querySelectorAll('label[for="'+id+'"]').forEach(label=>label.htmlFor=trigger.id);trigger.setAttribute('aria-controls','fe-date-dialog');trigger.setAttribute('aria-haspopup','dialog');trigger.setAttribute('aria-label',id==='start_local'?'Choose start date and time':id==='end_local'?'Choose end date and time':'Choose RSVP deadline');f.type='hidden';f.required=false;f.after(trigger);triggers[id]=trigger;
10trigger.onclick=()=>open(id)}); 11fields.start_local.form?.addEventListener('submit',e=>{if(!fields.start_local.value){e.preventDefault();e.stopImmediatePropagation();open('start_local');root.querySelector('.error').textContent='Choose a start date and time.'}},true); 12 13const dialog=root,list=root.querySelector('.list'),custom=root.querySelector('#customTime');let state={start:{date:'',time:''},end:{date:'',time:''}},side='start',year=new Date().getFullYear(),month=new Date().getMonth(); 14function labelTime(t){let [h,m]=t.split(':').map(Number);return (h%12||12)+':'+String(m).padStart(2,'0')+' '+(h>=12?'PM':'AM')} 15function summary(v){return v.date?new Date(v.date+'T12:00:00').toLocaleDateString('en-US',{weekday:'short',month:'short',day:'numeric',year:'numeric'})+(v.time?' · '+labelTime(v.time):' · choose a time'):(v.time?labelTime(v.time)+' · choose a date':'')} 16function todayDate(){const parts=new Intl.DateTimeFormat('en-US',{timeZone:fields.start_local.form?.querySelector('[name=timezone]')?.value||Intl.DateTimeFormat().resolvedOptions().timeZone,year:'numeric',month:'2-digit',day:'2-digit'}).formatToParts(new Date());const get=t=>parts.find(p=>p.type===t).value;return get('year')+'-'+get('month')+'-'+get('day')} 17function render(){const today=todayDate();root.querySelector('#month').textContent=new Date(year,month,1).toLocaleDateString('en-US',{month:'long',year:'numeric'});const days=root.querySelector('.days');days.replaceChildren();for(let i=0;i<new Date(year,month,1).getDay();i++){let b=document.createElement('span');b.className='blank';days.append(b)}for(let n=1;n<=new Date(year,month+1,0).getDate();n++){const date=year+'-'+String(month+1).padStart(2,'0')+'-'+String(n).padStart(2,'0');let b=document.createElement('button');b.type='button';b.className='day'+(date===today?' today':'')+(state[side].date===date?' selected':'');if(date===today){b.setAttribute('aria-current','date');b.title='Today';}b.textContent=n;b.dataset.date=date;b.tabIndex=(state[side].date===date||(!state[side].date&&n===1))?0:-1;b.setAttribute('aria-label',new Date(date+'T12:00:00').toDateString()+(date===today?' · Today':''));b.setAttribute('aria-pressed',state[side].date===date);b.onclick=()=>{state[side].date=date;root.querySelector('.error').textContent='';render();root.querySelector('[data-date="'+date+'"]')?.focus()};days.append(b)}root.querySelectorAll('.time').forEach(b=>{b.classList.toggle('selected',b.dataset.time===state[side].time);b.setAttribute('aria-pressed',b.dataset.time===state[side].time)});root.querySelectorAll('.tab').forEach(b=>{b.classList.toggle('active',b.dataset.side===side);b.setAttribute('aria-pressed',b.dataset.side===side)});root.querySelector('#startSummary').textContent=summary(state.start)||'Choose a date & time';root.querySelector('#endSummary').textContent=summary(state.end)||'Add an end time';root.querySelector('#timeTitle').textContent=deadline?'RSVP deadline time':side==='start'?'Start time':'End time'} 18for(let i=0;i<96;i++){const t=String(Math.floor(i/4)).padStart(2,'0')+':'+String(i%4*15).padStart(2,'0');let b=document.createElement('button');b.type='button';b.className='time';b.dataset.time=t;b.textContent=labelTime(t);b.onclick=()=>{state[side].time=t;custom.value='';root.querySelector('.error').textContent='';render()};list.append(b)} 19function scrollTime(){const t=state[side].time||'16:00';const target=Array.from(list.children).find(b=>b.dataset.time>=t)||list.lastElementChild;list.scrollTop=Math.max(0,target.offsetTop-list.firstElementChild.offsetTop-list.clientHeight/2)} 20root.querySelector('.close').onclick=()=>dialog.close();root.querySelectorAll('.tab').forEach(b=>b.onclick=()=>{side=b.dataset.side;if(side==='end'&&!state.end.date&&state.start.date)state.end.date=state.start.date;if(state[side].date){const d=new Date(state[side].date+'T12:00:00');year=d.getFullYear();month=d.getMonth()}custom.value=state[side].time?labelTime(state[side].time):'';root.querySelector('.error').textContent='';render();scrollTime()});root.querySelector('#prev').onclick=()=>{month--;if(month<0){month=11;year--}render()};root.querySelector('#next').onclick=()=>{month++;if(month>11){month=0;year++}render()};custom.oninput=()=>{const m=custom.value.trim().match(/^(\d{1,2})(?::(\d{2}))?\s*(am|pm)?$/i);let valid=false;if(m){let h=+m[1],min=+(m[2]||0);if(min<60&&(m[3]?h>
20=1&&h<=12:h<=23)){h=m[3]?h%12+(m[3].toLowerCase()==='pm'?12:0):h;state[side].time=String(h).padStart(2,'0')+':'+String(min).padStart(2,'0');valid=true}}if(!valid)state[side].time='';root.querySelector('.error').textContent=valid?'':custom.value?'Enter a time such as 6:10 pm.':'';render()};root.querySelector('.clear').onclick=()=>{if(deadline){write(fields.rsvp_closes_at,{date:'',time:''});dialog.close();return}state.end={date:'',time:''};custom.value='';render()};root.querySelector('.save').onclick=()=>{const s=state.start,e=state.end;let msg='';if(!s.date||!s.time)msg=deadline?'Choose a deadline date and time.':'Choose a start date and time.';else if(!deadline&&(e.date||e.time)&&(!e.date||!e.time))msg='Complete the end date and time, or remove it.';else if(!deadline&&e.date&&e.date+' '+e.time<=s.date+' '+s.time)msg='End time must be after the start time.';root.querySelector('.error').textContent=msg;if(msg)return;if(deadline){write(fields.rsvp_closes_at,state.start)}else{write(fields.start_local,state.start);write(fields.end_local,state.end)}dialog.close()};render(); 21 22root.querySelector('.days').addEventListener('keydown',event=>{ 23 const button=event.target.closest('[data-date]');if(!button)return; 24 const offsets={ArrowLeft:-1,ArrowRight:1,ArrowUp:-7,ArrowDown:7}; 25 if(!(event.key in offsets)&&event.key!=='Home'&&event.key!=='End'&&event.key!=='PageUp'&&event.key!=='PageDown')return; 26 event.preventDefault();const date=new Date(button.dataset.date+'T12:00:00'); 27 if(event.key==='PageUp'||event.key==='PageDown'){const day=date.getDate();date.setDate(1);date.setMonth(date.getMonth()+(event.key==='PageUp'?-1:1));date.setDate(Math.min(day,new Date(date.getFullYear(),date.getMonth()+1,0).getDate()))} 28 else date.setDate(date.getDate()+(event.key==='Home'?-date.getDay():event.key==='End'?6-date.getDay():offsets[event.key])); 29 year=date.getFullYear();month=date.getMonth();render();const target=year+'-'+String(month+1).padStart(2,'0')+'-'+String(date.getDate()).padStart(2,'0'); 30 root.querySelectorAll('.day').forEach(b=>b.tabIndex=b.dataset.date===target?0:-1);root.querySelector('[data-date="'+target+'"]')?.focus(); 31}); 32refresh(); 33})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.