283 lines
11 KiB
TypeScript
283 lines
11 KiB
TypeScript
import '../pierzainer_ground/ground-link';
|
|
import { loadSampleIntoApp } from './sample-loader';
|
|
import { createSampleExplorer, sampleExplorerMarkup, type Sample } from './sample-explorer';
|
|
import { createLandingGuide } from './landing-guide';
|
|
import { createHeroMotion, heroMotionMarkup } from './landing-motion';
|
|
import './landing-white.css';
|
|
import './landing-typography.css';
|
|
import { createDrawingPopup, drawingPopupMarkup } from './drawing-popup';
|
|
|
|
const appRoot = document.querySelector<HTMLDivElement>('.app');
|
|
const homeButton = document.querySelector<HTMLButtonElement>('#home-from-app');
|
|
const promoImageUrl = new URL('./assets/pierzainer-terrain-promo.png', import.meta.url).href;
|
|
const companyLogosUrl = new URL('./assets/family-company-logos.png', import.meta.url).href;
|
|
|
|
const landingOverlay = document.createElement('div');
|
|
landingOverlay.className = 'landing-overlay landing-white';
|
|
landingOverlay.setAttribute('role', 'dialog');
|
|
landingOverlay.setAttribute('aria-modal', 'true');
|
|
landingOverlay.setAttribute('aria-label', '프로그램 시작 선택');
|
|
|
|
const landingPanelMarkup = `
|
|
<div class="landing-shell">
|
|
<section class="landing-hero" aria-label="프로그램 홍보 영역">
|
|
<div class="landing-engineering-grid" aria-hidden="true"></div>
|
|
<div class="landing-light-stage" aria-hidden="true">
|
|
<div class="landing-glow"></div>
|
|
<div class="landing-light-ring"></div>
|
|
<div class="landing-orbit landing-orbit-one"></div>
|
|
<div class="landing-orbit landing-orbit-two"></div>
|
|
<div class="landing-orbit landing-orbit-three"></div>
|
|
<div class="landing-particles">
|
|
<i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i>
|
|
</div>
|
|
</div>
|
|
<div class="landing-hero-copy">
|
|
<div class="landing-title-group">
|
|
<h1 class="landing-title">PIER <span>&</span> ABUT</h1>
|
|
<h2 class="landing-headline">교량 하부구조 3D 통합모델링</h2>
|
|
</div>
|
|
<div class="landing-brand-meta">
|
|
<span class="landing-kicker"><span class="landing-status-light"></span> PIERZAINER <span class="landing-kicker-divider">/</span><span class="landing-kicker-detail">BRIDGE DESIGN STUDIO</span></span>
|
|
<p class="landing-subtitle">
|
|
설계정보 입력부터 3D 모델링, 구조검토, 철근배근, 도면작성 및 수량산출까지 하나의 플랫폼에서
|
|
</p>
|
|
<button class="landing-guide-trigger" type="button" aria-haspopup="dialog"><span aria-hidden="true">?</span>사용 가이드 <span class="landing-guide-trigger-arrow" aria-hidden="true">↗</span></button>
|
|
</div>
|
|
</div>
|
|
<div class="landing-hero-visual" aria-hidden="true">
|
|
<div class="landing-visual">
|
|
<div class="landing-artwork">
|
|
<img class="landing-promo-image" src="${promoImageUrl}" alt="" draggable="false" fetchpriority="high" />
|
|
${heroMotionMarkup(promoImageUrl)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<section class="landing-cards" aria-label="입력 방식 선택">
|
|
<article class="landing-card" data-card="excel">
|
|
<div>
|
|
<div class="landing-card-top"><div class="landing-card-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M14 3H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9zM14 3v6h6M8 13h8M8 17h8M12 13v4"/></svg></div><span class="landing-card-number">01 / IMPORT</span></div>
|
|
<span class="landing-card-eyebrow">Excel Import</span>
|
|
<h3>엑셀로 시작하기</h3>
|
|
<p>설계정보를 불러와 교각·교대 3D 모델을 자동 생성합니다.</p>
|
|
</div>
|
|
<div class="landing-card-actions">
|
|
<span class="landing-card-note">Excel · .xlsx</span>
|
|
<button class="landing-primary" type="button">Excel 파일 불러오기</button>
|
|
</div>
|
|
</article>
|
|
|
|
<article class="landing-card" data-card="drawing">
|
|
<div>
|
|
<div class="landing-card-top"><div class="landing-card-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 4h16v16H4zM4 9h7v11M11 4v5h9M15 13h2v3h-2M7 13v3"/></svg></div><span class="landing-card-number">02 / EXTRACT</span></div>
|
|
<span class="landing-card-eyebrow">Drawing Import</span>
|
|
<h3>도면으로 시작하기</h3>
|
|
<p>기존 도면에서 교각·교대 정보를 추출하여 3D 모델을 생성합니다.</p>
|
|
</div>
|
|
<div class="landing-card-actions">
|
|
<span class="landing-card-note" id="landing-drawing-status" role="status" aria-live="polite">프로그램 안에서 도면 추출</span>
|
|
<button class="landing-primary" type="button">도면 불러오기</button>
|
|
</div>
|
|
</article>
|
|
|
|
|
|
</section>
|
|
<footer class="landing-footer">
|
|
<div class="landing-footer-note" aria-hidden="true"><span>PRECISION IN EVERY DIMENSION</span><span>PIERZAINER · PIER & ABUT</span></div>
|
|
<div class="landing-company-logos"><img src="${companyLogosUrl}" width="9719" height="295" alt="한맥기술, 삼안, 장헌산업, 피티씨, 장헌, 한라산업개발, 바로컨설턴트 로고" draggable="false" /></div>
|
|
</footer>
|
|
</section>
|
|
|
|
${sampleExplorerMarkup}
|
|
<section id="landing-excel-panel" class="landing-panel" aria-label="엑셀 입력 패널">
|
|
<div class="landing-panel-head">
|
|
<h3>엑셀 입력</h3>
|
|
<button type="button" class="landing-close-button" data-close-panel="landing-excel-panel">닫기</button>
|
|
</div>
|
|
<div class="landing-panel-body">
|
|
<div class="landing-file-meta">
|
|
<span>선택 파일</span>
|
|
<strong id="landing-excel-name">선택된 파일이 없습니다.</strong>
|
|
</div>
|
|
<div class="landing-actions-row">
|
|
<input id="landing-excel-input" type="file" accept=".xlsx,.xls,.xlsm" hidden />
|
|
<button class="landing-ghost" type="button" data-pick-file="excel">파일 선택</button>
|
|
<button id="landing-excel-start" class="landing-primary" type="button" disabled>모델링 시작</button>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
${drawingPopupMarkup}
|
|
|
|
|
|
</div>
|
|
`;
|
|
|
|
landingOverlay.innerHTML = landingPanelMarkup;
|
|
document.body.prepend(landingOverlay);
|
|
const landingGuide = createLandingGuide(landingOverlay);
|
|
createHeroMotion(landingOverlay);
|
|
|
|
const selectPanel = (panelId: string): void => {
|
|
landingGuide.close(false);
|
|
document.querySelectorAll<HTMLElement>('.landing-panel').forEach((panel) => {
|
|
panel.classList.toggle('visible', panel.id === panelId);
|
|
});
|
|
const visible = document.getElementById(panelId);
|
|
if (visible) {
|
|
visible.scrollTo({ top: 0, behavior: 'auto' });
|
|
}
|
|
};
|
|
|
|
const closePanels = (): void => {
|
|
document.querySelectorAll<HTMLElement>('.landing-panel').forEach((panel) => panel.classList.remove('visible'));
|
|
};
|
|
|
|
const showApp = (): void => {
|
|
landingGuide.close(false);
|
|
if (appRoot) {
|
|
appRoot.hidden = false;
|
|
appRoot.setAttribute('aria-hidden', 'false');
|
|
}
|
|
if (homeButton) {
|
|
homeButton.hidden = false;
|
|
}
|
|
landingOverlay.hidden = true;
|
|
};
|
|
|
|
const showLanding = (): void => {
|
|
if (appRoot) {
|
|
appRoot.hidden = true;
|
|
appRoot.setAttribute('aria-hidden', 'true');
|
|
}
|
|
if (homeButton) {
|
|
homeButton.hidden = true;
|
|
}
|
|
landingOverlay.hidden = false;
|
|
landingOverlay.classList.remove('modal-open');
|
|
closePanels();
|
|
};
|
|
|
|
const selectFile = (input: HTMLInputElement): void => {
|
|
input.click();
|
|
};
|
|
|
|
const excelInput = document.querySelector<HTMLInputElement>('#landing-excel-input');
|
|
const excelName = document.querySelector<HTMLElement>('#landing-excel-name');
|
|
const excelStart = document.querySelector<HTMLButtonElement>('#landing-excel-start');
|
|
|
|
const ensureNoLegacyImportDialog = (): void => {
|
|
const dialog = document.querySelector<HTMLDialogElement>('#import-dialog');
|
|
if (dialog && dialog.open) dialog.close();
|
|
};
|
|
|
|
if (excelInput && excelName && excelStart) {
|
|
excelInput.addEventListener('change', () => {
|
|
const file = excelInput.files?.[0];
|
|
const name = file ? file.name : '선택된 파일이 없습니다.';
|
|
excelName.textContent = name;
|
|
excelStart.disabled = !file;
|
|
ensureNoLegacyImportDialog();
|
|
});
|
|
|
|
excelStart.addEventListener('click', () => {
|
|
if (!excelInput.files?.[0]) return;
|
|
ensureNoLegacyImportDialog();
|
|
showApp();
|
|
});
|
|
}
|
|
|
|
const bindFilePicker = (button: Element, targetId: string, nameTargetId: string): void => {
|
|
const input = document.querySelector<HTMLInputElement>(`#${targetId}`);
|
|
const output = document.querySelector<HTMLElement>(`#${nameTargetId}`);
|
|
if (!input || !output) return;
|
|
button.addEventListener('click', () => selectFile(input));
|
|
input.addEventListener('change', () => {
|
|
const file = input.files?.[0];
|
|
output.textContent = file ? file.name : '미선택';
|
|
});
|
|
};
|
|
|
|
const bindCard = (cardSelector: string, panelId: string, mode: 'panel' | 'picker' = 'panel'): void => {
|
|
const card = document.querySelector<HTMLElement>(cardSelector);
|
|
const trigger = card?.querySelector<HTMLButtonElement>('button');
|
|
if (!trigger) return;
|
|
trigger.addEventListener('click', () => {
|
|
landingGuide.dismissHint();
|
|
landingGuide.close(false);
|
|
if (mode === 'picker') {
|
|
if (cardSelector === '[data-card="excel"]') {
|
|
if (landingOverlay) {
|
|
landingOverlay.hidden = false;
|
|
landingOverlay.classList.add('modal-open');
|
|
}
|
|
const dialog = document.querySelector<HTMLDialogElement>('#import-dialog');
|
|
if (dialog && !dialog.open) {
|
|
dialog.showModal();
|
|
}
|
|
const projectName = document.querySelector<HTMLInputElement>('#project-name');
|
|
if (projectName) projectName.focus();
|
|
return;
|
|
}
|
|
}
|
|
selectPanel(panelId);
|
|
});
|
|
};
|
|
|
|
bindCard('[data-card="excel"]', 'landing-excel-panel', 'picker');
|
|
|
|
|
|
const drawingPopup = createDrawingPopup(landingOverlay, () => {
|
|
landingGuide.dismissHint();
|
|
landingGuide.close(false);
|
|
closePanels();
|
|
});
|
|
document.querySelector<HTMLButtonElement>('[data-card="drawing"] button')!.addEventListener('click', () => drawingPopup.open());
|
|
|
|
document.querySelectorAll<HTMLElement>('[data-close-panel]').forEach((button) => {
|
|
button.addEventListener('click', () => {
|
|
const panelId = button.getAttribute('data-close-panel');
|
|
if (!panelId) return;
|
|
const panel = document.getElementById(panelId);
|
|
panel?.classList.remove('visible');
|
|
});
|
|
});
|
|
|
|
document.querySelectorAll<HTMLButtonElement>('[data-pick-file]').forEach((button) => {
|
|
const kind = button.getAttribute('data-pick-file');
|
|
if (!kind) return;
|
|
|
|
const targetMap: Record<string, string> = {
|
|
excel: 'landing-excel-input',
|
|
};
|
|
|
|
const targetId = targetMap[kind];
|
|
if (!targetId) return;
|
|
|
|
const input = document.querySelector<HTMLInputElement>(`#${targetId}`);
|
|
if (!input) return;
|
|
button.addEventListener('click', () => selectFile(input));
|
|
});
|
|
|
|
bindFilePicker(document.querySelector('[data-pick-file="excel"]') as HTMLButtonElement, 'landing-excel-input', 'landing-excel-name');
|
|
|
|
createSampleExplorer(landingOverlay,(sample:Sample)=>loadSampleIntoApp(sample,showApp,showLanding));
|
|
|
|
if (homeButton) {
|
|
homeButton.addEventListener('click', () => {
|
|
showLanding();
|
|
});
|
|
}
|
|
|
|
if (appRoot) {
|
|
appRoot.hidden = true;
|
|
appRoot.setAttribute('aria-hidden', 'true');
|
|
}
|
|
if (homeButton) {
|
|
homeButton.hidden = true;
|
|
}
|
|
|
|
showLanding();
|
|
|