import { sampleProjects } from './sample-projects'; type StartMode = 'excel' | 'drawing' | 'sample'; const modeNames: Record = {excel:'엑셀 불러오기',drawing:'도면 불러오기',sample:'기설계 교량'}; /** A native dialog keeps keyboard focus and upload dialogs from competing. */ export function createLandingGuide(root: HTMLElement): {close:(restoreFocus?:boolean)=>void;dismissHint:()=>void} { const trigger=root.querySelector('.landing-guide-trigger')!; const hero=root.querySelector('.landing-hero')!; const hint=document.createElement('aside'); hint.className='landing-guide-hint';hint.setAttribute('aria-label','처음 사용자 안내'); hint.innerHTML=`
처음 사용하시나요?

사용 가이드로 시작해 보세요.

`; const hintKey='pierzainer:landing:guide-hint-dismissed'; try {hint.hidden=sessionStorage.getItem(hintKey)==='1';} catch { /* In-memory dismissal still works without browser storage. */ } hero.append(hint); const dismissHint=():void=>{hint.hidden=true;try{sessionStorage.setItem(hintKey,'1');}catch{/* Storage may be blocked. */}}; const dialog=document.createElement('dialog'); dialog.className='landing-guide';dialog.id='landing-guide';dialog.setAttribute('aria-labelledby','landing-guide-title'); dialog.innerHTML=`
GET STARTED / PIER & ABUT

하나의 시작, 세 가지 방법

입력 카드와 우측 샘플 패널은 서로 다른 시작 방식입니다.
준비된 자료에 맞는 시작 방식 하나를 선택하세요.

${(['excel','drawing','sample'] as StartMode[]).map(mode=>``).join('')}

`; root.append(dialog); trigger.setAttribute('aria-controls',dialog.id); trigger.setAttribute('aria-expanded','false'); let mode: StartMode='excel'; let step=0; let opener: HTMLElement=trigger; const highlight=():void=>{ root.querySelector('[data-guide-target=sample]')?.classList.toggle('is-guided',dialog.open&&mode==='sample'); root.querySelectorAll('[data-card]').forEach(card=>card.classList.toggle('is-guided',dialog.open&&card.dataset.card===mode)); }; const setText=(selector:string,text:string):void=>{dialog.querySelector(selector)!.textContent=text;}; const render=():void=>{ const excelFormats=document.querySelector('#file')?.accept||'.xlsx'; const titles=['시작 방식 선택','프로젝트 데이터 준비','프로젝트 시작']; const descriptions: Record={ excel:['작성한 교각 설계정보가 있다면 엑셀 불러오기를 선택하세요. 원본 시트의 인식 결과를 확인하며 시작할 수 있습니다.', '프로젝트명을 입력하고 Excel 파일을 선택하세요. 인식된 시트와 교각을 확인한 뒤 사용할 항목을 고릅니다.', '가져오기 창에서 선택한 교각의 형상 생성 버튼을 누르면 기존 3D 형상 화면으로 이동합니다. 이후 기능은 프로그램에서 제공하는 범위 안에서 사용할 수 있습니다.'], drawing:['도면 불러오기를 누르면 프로그램 안에 도면추출 팝업이 열립니다.', '종평면도·받침배치도·ELEVATION DWG가 들어 있는 폴더를 선택하고 자동 추출 시작을 누르세요. 진행 내용이 팝업에 표시됩니다.', '결과 목록의 교각 불러오기를 눌러 Excel 인식 결과를 확인하고 형상을 생성합니다. Excel 저장으로 파일만 받을 수도 있습니다.'], sample:['파일 준비 없이 실제 교각과 지반 모델을 살펴보려면 샘플 프로젝트를 선택하세요.', '설계 라이브러리 팝업에서 산외교·운곡교 등 12개 교량의 이름과 교각 수를 확인하고 원하는 교량을 선택합니다.', `현재 ${sampleProjects.filter(s=>s.pierCount>0).length}개 교량은 교각과 원도면 지반면, 위성사진, 2D 지도를 함께 엽니다. 교각 데이터가 없는 승천천교는 지반·지도 자료를 표시합니다. 설계 지반고는 Excel 원래 값을 유지하며, 기존 검증 알림은 확인이 필요합니다.`] }; const facts: Record={ excel:['선택 가이드 · 교각 설계정보가 담긴 Excel 파일이 있을 때',`현재 주 가져오기 창의 지원 확장자: ${excelFormats.replaceAll(',', ' · ')}`,'확인할 내용 · 시트 인식 결과, 선택 교각, 프로젝트명'], drawing:['실행 방식 · 프로그램 내부 팝업 / 기존 추출 엔진','입력 · DWG가 들어 있는 폴더 / AI 연결 없음','결과 · 프로젝트의 도면추출/output에 저장 / 미확정 항목은 검토 필요'], sample:['시작 방식 · 실제 교량 12개 중 선택','포함 자료 · 교각 + 지반면 + 위성사진 + 2D 지도','확인 사항 · 원본 CRS 확인이 필요한 추정 좌표계 포함'] }; setText('.landing-guide-step-label',`STEP 0${step+1} / ${modeNames[mode]}`); setText('.landing-guide-step-title',titles[step]); setText('.landing-guide-step-description',descriptions[mode][step]); setText('.landing-guide-fact',facts[mode][step]); setText('.landing-guide-page',`${step+1} / 3`); setText('.landing-guide-illustration-label',['CHOOSE YOUR START','PREPARE YOUR INPUT','OPEN YOUR PROJECT'][step]); const icon=root.querySelector(`[data-card="${mode}"] .landing-card-icon svg`); dialog.querySelector('.landing-guide-icon')!.replaceChildren(...(icon?[icon.cloneNode(true)]:[])); dialog.dataset.step=String(step); dialog.querySelector('.landing-guide-prev')!.disabled=step===0; setText('.landing-guide-next',step===2?`${modeNames[mode]}로 시작`:'다음 →'); dialog.querySelectorAll('[data-guide-mode]').forEach(button=>button.setAttribute('aria-pressed',String(button.dataset.guideMode===mode))); dialog.querySelectorAll('[data-guide-step]').forEach(button=>{ if(Number(button.dataset.guideStep)===step)button.setAttribute('aria-current','step');else button.removeAttribute('aria-current'); }); highlight(); }; const finishClose=():void=>{ root.classList.remove('guide-open');trigger.setAttribute('aria-expanded','false');highlight(); }; const close=(restoreFocus=true):void=>{ if(!dialog.open)return; dialog.close();finishClose(); if(restoreFocus&&opener.isConnected&&!root.hidden)opener.focus(); }; const open=(source:HTMLElement):void=>{ if(root.hidden||document.querySelector('#import-dialog[open]')||root.querySelector('.landing-panel.visible'))return; dismissHint();opener=source;step=0; if(!dialog.open)dialog.showModal(); root.classList.add('guide-open');trigger.setAttribute('aria-expanded','true');render(); }; trigger.addEventListener('click',()=>open(trigger)); hint.querySelector('.landing-hint-open')!.addEventListener('click',()=>open(trigger)); hint.querySelector('.landing-hint-close')!.addEventListener('click',()=>{dismissHint();trigger.focus();}); dialog.querySelector('.landing-guide-close')!.addEventListener('click',()=>close()); dialog.addEventListener('close',finishClose); dialog.addEventListener('cancel',(event)=>{event.preventDefault();close();}); dialog.addEventListener('keydown',(event)=>{ if(event.key!=='Tab')return; const buttons=Array.from(dialog.querySelectorAll('button:not(:disabled)')).filter(button=>button.getClientRects().length>0); const first=buttons[0],last=buttons[buttons.length-1]; if(!first||!last)return; if(event.shiftKey&&document.activeElement===first){event.preventDefault();last.focus();} else if(!event.shiftKey&&document.activeElement===last){event.preventDefault();first.focus();} }); dialog.addEventListener('click',(event)=>{ if(event.target!==dialog)return; const bounds=dialog.getBoundingClientRect(); if(event.clientXbounds.right||event.clientYbounds.bottom)close(); }); dialog.querySelectorAll('[data-guide-mode]').forEach(button=>button.addEventListener('click',()=>{mode=button.dataset.guideMode as StartMode;render();})); dialog.querySelectorAll('[data-guide-step]').forEach(button=>button.addEventListener('click',()=>{step=Number(button.dataset.guideStep);render();})); dialog.querySelector('.landing-guide-prev')!.addEventListener('click',()=>{step=Math.max(0,step-1);render();if(step===0)dialog.querySelector('[data-guide-step="0"]')!.focus();}); dialog.querySelector('.landing-guide-next')!.addEventListener('click',()=>{ if(step<2){step++;render();return;} close(false);if(mode==='sample'){root.querySelector('[data-card=sample] button')?.click();return;}root.querySelector(`[data-card="${mode}"] button`)?.click(); }); // Main-app functions may hide this overlay without calling landing functions. new MutationObserver(()=>{if(root.hidden)close(false);}).observe(root,{attributes:true,attributeFilter:['hidden']}); return {close,dismissHint}; }