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.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 drawingFormats=root.querySelector('#landing-drawing-longitudinal-input')?.accept||'.dwg,.dxf,.pdf,.png,.jpg,.jpeg'; const titles=['시작 방식 선택','프로젝트 데이터 준비','프로젝트 시작']; const descriptions: Record={ excel:['작성한 교각 설계정보가 있다면 엑셀 불러오기를 선택하세요. 원본 시트의 인식 결과를 확인하며 시작할 수 있습니다.', '프로젝트명을 입력하고 Excel 파일을 선택하세요. 인식된 시트와 교각을 확인한 뒤 사용할 항목을 고릅니다.', '가져오기 창에서 선택한 교각의 형상 생성 버튼을 누르면 기존 3D 형상 화면으로 이동합니다. 이후 기능은 프로그램에서 제공하는 범위 안에서 사용할 수 있습니다.'], drawing:['도면 파일을 준비했다면 도면 불러오기를 선택하세요. 현재는 도면 선택과 기존 화면으로의 이동 흐름을 제공합니다.', '종평면도·교량받침배치도·ELEVATION 산출근거도를 각각 선택합니다. 세 파일을 모두 고르면 다음 버튼이 활성화됩니다.', '세 파일을 선택한 뒤 도면 분석 시작 버튼으로 기존 프로그램 화면에 진입합니다. 자동 도면 분석·제원 추출 기능은 아직 이 화면에 연결되지 않았습니다.'], sample:['파일 준비 없이 실제 교각과 지반 모델을 살펴보려면 샘플 프로젝트를 선택하세요.', '샘플 선택 패널에서 산외교·운곡교 등 12개 교량의 이름과 교각 수를 확인하고 원하는 교량을 선택합니다.', `현재 ${sampleProjects.filter(s=>!s.blockReason).length}개 교량은 교각과 원도면 지반면, 위성사진, 2D 지도를 함께 엽니다. 확인이 필요한 샘플은 이유를 안내합니다. 설계 지반고는 Excel 원래 값을 유지하며, 기존 검증 알림은 확인이 필요합니다.`] }; const facts: Record={ excel:['선택 가이드 · 교각 설계정보가 담긴 Excel 파일이 있을 때',`현재 주 가져오기 창의 지원 확장자: ${excelFormats.replaceAll(',', ' · ')}`,'확인할 내용 · 시트 인식 결과, 선택 교각, 프로젝트명'], drawing:['지원 범위 · 파일 선택 흐름 제공 / 자동 분석 연결 준비 중',`현재 파일 선택 확장자: ${drawingFormats.replaceAll(',', ' · ')}`,'안내 · 구조해석·자동설계가 완료된 기능이라는 의미는 아닙니다.'], 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);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}; }