Files
Pier_v1/랜딩페이지/landing-video.ts
T
ypshimandClaude Haiku 5.5 ef596650cc feat(landing): 소개 영상 버튼과 재생 모달 추가
- 사용 가이드 옆 '▶ 소개 영상 1분' 버튼, 네이티브 dialog에서 재생
- 자체 호스팅 mp4(56초·1920×1080·H.264, 무음), 열 때만 로드(preload none), 무음 자동재생
- Esc·배경·닫기로 종료 시 일시정지·처음으로·포커스 복귀, 모션 감소 시 자동재생 안 함
- 자막 원본(.srt)은 보관만, 화면 미연결
- verify-intro-video.cjs: Chrome 17개 검사

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-11-09 18:01:12 +09:00

64 lines
3.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import './landing-video.css';
/** Self-hosted intro video (56 s, 1920×1080 H.264, no audio track). Fetched only when opened. */
export const introVideoUrl = new URL('./assets/pierzainer-intro.mp4', import.meta.url).href;
export const introVideoTriggerMarkup = `<button class="landing-video-trigger" type="button" aria-haspopup="dialog" aria-controls="landing-video"><span aria-hidden="true">▶</span>소개 영상 <span class="landing-video-trigger-time">1분</span></button>`;
/** Native dialog: Esc/backdrop close, pause + rewind on close, focus returns to the opener. */
export function createIntroVideo(root: HTMLElement, poster: string): { open: () => void; close: (restoreFocus?: boolean) => void } {
const trigger = root.querySelector<HTMLButtonElement>('.landing-video-trigger')!;
const dialog = document.createElement('dialog');
dialog.className = 'landing-video';
dialog.id = 'landing-video';
dialog.setAttribute('aria-labelledby', 'landing-video-title');
dialog.innerHTML = `
<div class="landing-video-head">
<div><span class="landing-video-kicker">PRODUCT TOUR / PIER &amp; ABUT</span><h2 id="landing-video-title">Pierzainer 소개 영상</h2></div>
<button type="button" class="landing-video-close" aria-label="소개 영상 닫기">×</button>
</div>
<div class="landing-video-frame">
<video controls playsinline muted preload="none" poster="${poster}" aria-describedby="landing-video-desc"></video>
</div>
<p id="landing-video-desc" class="landing-video-desc">설계정보 입력부터 3D 모델링, 철근배근, 도면작성, 수량산출까지의 흐름을 1분 동안 소개합니다. (음성 없음)</p>`;
root.append(dialog);
const video = dialog.querySelector('video')!;
const reducedMotion = window.matchMedia?.('(prefers-reduced-motion: reduce)');
const open = (): void => {
if (dialog.open) return;
if (!video.currentSrc && !video.querySelector('source')) {
const source = document.createElement('source');
source.src = introVideoUrl;
source.type = 'video/mp4';
video.append(source);
video.load();
}
dialog.showModal();
dialog.querySelector<HTMLButtonElement>('.landing-video-close')!.focus();
if (!reducedMotion?.matches) void video.play().catch(() => undefined);
};
const stop = (): void => {
video.pause();
if (video.readyState > 0) video.currentTime = 0;
};
const close = (restoreFocus = true): void => {
if (!dialog.open) return;
dialog.close();
if (restoreFocus && trigger.isConnected && !root.hidden) trigger.focus();
};
trigger.addEventListener('click', open);
dialog.querySelector('.landing-video-close')!.addEventListener('click', () => close());
dialog.addEventListener('close', stop);
dialog.addEventListener('cancel', (event) => { event.preventDefault(); close(); });
// Clicking the backdrop (outside the panel box) closes the dialog.
dialog.addEventListener('click', (event) => {
if (event.target !== dialog) return;
const box = dialog.getBoundingClientRect();
const inside = event.clientX >= box.left && event.clientX <= box.right && event.clientY >= box.top && event.clientY <= box.bottom;
if (!inside) close();
});
return { open, close };
}