- Harness: kintex-mobile-dev agent + kintex-mobile-orchestrator skill (WISE mobile ref, Stitch-first design rule, dual app targets B2B/B2C) - design.md v2.1: full 84-screen inventory (web 51 / admin 10 / public 8 / mobile 15) with Stitch prompts incl. ticketing (SCR-P7/P8, M14/M15) - PLANNING v3.1: unified account + split signup tracks (2FA required for staff, light signup/guest for visitors), one codebase / two app targets - Deliverables: dev plan (21s), user/operator/developer guides (17/14/15s), program spec (44s, 65 programs, 8 flowcharts), DA (DB design 14s + table spec xlsx 35 tables/299 cols) - Benchmark: ticketing-app-benchmark.md (7 apps) -> IMPLEMENTATION_BACKLOG Phase F (14 items) - Stitch: 23 generated screens saved (mobile 10, admin 6, web core 5, ticket 2) - mobile/: Expo scaffold (SDK 51, expo-router, secure store JWT) - frontend: SCR-13~17 QA fixes, icons.tsx, kintexEvents, V10 seed migration - ci/: KINTEX CI logo assets Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
410 lines
20 KiB
HTML
410 lines
20 KiB
HTML
<!DOCTYPE html>
|
|
|
|
<html class="light" lang="ko"><head>
|
|
<meta charset="utf-8"/>
|
|
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"/>
|
|
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Geist:wght@500;600&display=swap" rel="stylesheet"/>
|
|
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
|
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
|
<script id="tailwind-config">
|
|
tailwind.config = {
|
|
darkMode: "class",
|
|
theme: {
|
|
extend: {
|
|
"colors": {
|
|
"surface": "#f9f9ff",
|
|
"error-container": "#ffdad6",
|
|
"surface-bright": "#f9f9ff",
|
|
"primary-fixed": "#d3e4ff",
|
|
"on-primary-container": "#d2e3ff",
|
|
"secondary-fixed-dim": "#c9bfff",
|
|
"on-primary-fixed-variant": "#004881",
|
|
"surface-variant": "#dbe2f9",
|
|
"inverse-primary": "#a2c9ff",
|
|
"on-secondary-fixed": "#1b0063",
|
|
"surface-container-lowest": "#ffffff",
|
|
"on-surface": "#141b2c",
|
|
"on-tertiary-fixed-variant": "#005236",
|
|
"on-primary-fixed": "#001c38",
|
|
"background": "#f9f9ff",
|
|
"surface-dim": "#d2daf0",
|
|
"inverse-on-surface": "#edf0ff",
|
|
"on-secondary-fixed-variant": "#4500d8",
|
|
"inverse-surface": "#293041",
|
|
"on-primary": "#ffffff",
|
|
"tertiary-fixed": "#8ef7c4",
|
|
"tertiary-container": "#00734e",
|
|
"primary-container": "#0066b3",
|
|
"outline-variant": "#c1c7d3",
|
|
"error": "#ba1a1a",
|
|
"surface-container-low": "#f1f3ff",
|
|
"on-secondary": "#ffffff",
|
|
"on-secondary-container": "#f3edff",
|
|
"on-error-container": "#93000a",
|
|
"on-tertiary-fixed": "#002113",
|
|
"on-background": "#141b2c",
|
|
"surface-container-high": "#e0e8ff",
|
|
"primary-fixed-dim": "#a2c9ff",
|
|
"on-surface-variant": "#414751",
|
|
"tertiary-fixed-dim": "#72daa9",
|
|
"secondary-fixed": "#e5deff",
|
|
"surface-container": "#e9edff",
|
|
"secondary-container": "#6d4aff",
|
|
"on-error": "#ffffff",
|
|
"surface-container-highest": "#dbe2f9",
|
|
"on-tertiary-container": "#8ef7c3",
|
|
"secondary": "#5427e6",
|
|
"outline": "#717782",
|
|
"tertiary": "#00583b",
|
|
"surface-tint": "#0060a9",
|
|
"on-tertiary": "#ffffff",
|
|
"primary": "#004e8b"
|
|
},
|
|
"borderRadius": {
|
|
"DEFAULT": "0.125rem",
|
|
"lg": "0.25rem",
|
|
"xl": "0.5rem",
|
|
"full": "0.75rem"
|
|
},
|
|
"spacing": {
|
|
"stack-md": "16px",
|
|
"stack-lg": "24px",
|
|
"stack-sm": "8px",
|
|
"gutter": "16px",
|
|
"container-padding": "24px",
|
|
"base": "4px"
|
|
},
|
|
"fontFamily": {
|
|
"body-base": ["Inter"],
|
|
"display-lg": ["Inter"],
|
|
"title-sm": ["Inter"],
|
|
"headline-md": ["Inter"],
|
|
"label-md": ["Geist"],
|
|
"label-xs": ["Geist"],
|
|
"display-lg-mobile": ["Inter"],
|
|
"body-sm": ["Inter"]
|
|
},
|
|
"fontSize": {
|
|
"body-base": ["14px", {"lineHeight": "20px", "fontWeight": "400"}],
|
|
"display-lg": ["36px", {"lineHeight": "44px", "letterSpacing": "-0.02em", "fontWeight": "700"}],
|
|
"title-sm": ["18px", {"lineHeight": "28px", "fontWeight": "600"}],
|
|
"headline-md": ["24px", {"lineHeight": "32px", "fontWeight": "600"}],
|
|
"label-md": ["12px", {"lineHeight": "16px", "letterSpacing": "0.02em", "fontWeight": "600"}],
|
|
"label-xs": ["11px", {"lineHeight": "14px", "fontWeight": "500"}],
|
|
"display-lg-mobile": ["28px", {"lineHeight": "36px", "fontWeight": "700"}],
|
|
"body-sm": ["13px", {"lineHeight": "18px", "fontWeight": "400"}]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
<style>
|
|
.material-symbols-outlined {
|
|
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
|
|
display: inline-block;
|
|
line-height: 1;
|
|
text-transform: none;
|
|
letter-spacing: normal;
|
|
word-wrap: normal;
|
|
white-space: nowrap;
|
|
direction: ltr;
|
|
}
|
|
|
|
.map-canvas {
|
|
background-color: #f1f3ff;
|
|
background-image:
|
|
linear-gradient(45deg, #e0e8ff 25%, transparent 25%),
|
|
linear-gradient(-45deg, #e0e8ff 25%, transparent 25%),
|
|
linear-gradient(45deg, transparent 75%, #e0e8ff 75%),
|
|
linear-gradient(-45deg, transparent 75%, #e0e8ff 75%);
|
|
background-size: 40px 40px;
|
|
background-position: 0 0, 0 20px, 20px -20px, -20px 0px;
|
|
}
|
|
|
|
.booth-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(6, 1fr);
|
|
grid-template-rows: repeat(8, 1fr);
|
|
gap: 4px;
|
|
width: 1000px;
|
|
height: 1400px;
|
|
padding: 40px;
|
|
}
|
|
|
|
@keyframes pulse-blue {
|
|
0% { transform: scale(1); opacity: 1; }
|
|
50% { transform: scale(1.5); opacity: 0.4; }
|
|
100% { transform: scale(1); opacity: 1; }
|
|
}
|
|
|
|
.blue-dot-pulse {
|
|
animation: pulse-blue 2s infinite ease-in-out;
|
|
}
|
|
|
|
.route-line {
|
|
stroke-dasharray: 10;
|
|
animation: dash 20s linear infinite;
|
|
}
|
|
|
|
@keyframes dash {
|
|
to { stroke-dashoffset: -1000; }
|
|
}
|
|
|
|
.bottom-sheet {
|
|
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
</style>
|
|
<style>
|
|
body {
|
|
min-height: max(884px, 100dvh);
|
|
}
|
|
</style>
|
|
</head>
|
|
<body class="bg-surface text-on-surface font-body-base overflow-hidden select-none">
|
|
<!-- Top Navigation Bar -->
|
|
<header class="bg-surface dark:bg-inverse-surface w-full top-0 sticky z-40 transition-colors duration-200 ease-in-out">
|
|
<div class="flex justify-between items-center px-container-padding h-16 w-full">
|
|
<button class="text-primary dark:text-primary-fixed-dim hover:bg-surface-container transition-colors p-2 rounded-full">
|
|
<span class="material-symbols-outlined">menu</span>
|
|
</button>
|
|
<div class="flex-1 mx-4">
|
|
<div class="relative group">
|
|
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
|
<span class="material-symbols-outlined text-outline">search</span>
|
|
</div>
|
|
<input class="block w-full pl-10 pr-3 py-2 border border-outline-variant bg-surface-container-lowest rounded-full text-body-sm focus:ring-2 focus:ring-primary focus:border-primary transition-all outline-none" placeholder="부스 · 화장실 · 비상구 · 시설 검색" type="text"/>
|
|
</div>
|
|
</div>
|
|
<button class="text-primary dark:text-primary-fixed-dim hover:bg-surface-container transition-colors p-2 rounded-full">
|
|
<span class="material-symbols-outlined">qr_code_scanner</span>
|
|
</button>
|
|
</div>
|
|
</header>
|
|
<!-- Interactive Map Area -->
|
|
<main class="relative h-[calc(100vh-64px)] w-full overflow-auto cursor-grab active:cursor-grabbing" id="map-container">
|
|
<div class="map-canvas relative" style="width: 1200px; height: 1600px;">
|
|
<!-- Hall Layout Decorative Elements -->
|
|
<div class="absolute inset-0 p-12">
|
|
<div class="w-full h-full border-[12px] border-outline-variant rounded-xl relative overflow-hidden bg-white/50 backdrop-blur-sm">
|
|
<!-- Hall Name Overlay -->
|
|
<div class="absolute top-8 left-8">
|
|
<h2 class="font-headline-md text-headline-md text-outline-variant opacity-30 select-none">KINTEX HALL 1</h2>
|
|
</div>
|
|
<!-- Booth Grid Visualization -->
|
|
<div class="booth-grid">
|
|
<!-- Iterative Booth Generation (Mockup) -->
|
|
<div class="bg-surface-container border border-outline-variant rounded flex items-center justify-center font-label-md text-on-surface-variant">A-101</div>
|
|
<div class="bg-primary-container/10 border-2 border-primary rounded flex flex-col items-center justify-center font-label-md text-primary ring-4 ring-primary/20" id="target-booth">
|
|
<span class="text-[10px] opacity-70">A-102</span>
|
|
<span class="font-bold">한빛로보틱스</span>
|
|
</div>
|
|
<div class="bg-surface-container border border-outline-variant rounded flex items-center justify-center font-label-md text-on-surface-variant">A-103</div>
|
|
<div class="bg-surface-container border border-outline-variant rounded flex items-center justify-center font-label-md text-on-surface-variant">A-104</div>
|
|
<div class="col-span-2 bg-surface-variant/30 border border-dashed border-outline rounded flex items-center justify-center text-outline italic text-label-xs">준비중</div>
|
|
<div class="bg-surface-container border border-outline-variant rounded flex items-center justify-center font-label-md text-on-surface-variant">B-101</div>
|
|
<div class="bg-surface-container border border-outline-variant rounded flex items-center justify-center font-label-md text-on-surface-variant">B-102</div>
|
|
<div class="bg-surface-container border border-outline-variant rounded flex items-center justify-center font-label-md text-on-surface-variant">B-103</div>
|
|
<div class="bg-surface-container border border-outline-variant rounded flex items-center justify-center font-label-md text-on-surface-variant">B-104</div>
|
|
<div class="bg-surface-container border border-outline-variant rounded flex items-center justify-center font-label-md text-on-surface-variant">B-105</div>
|
|
<div class="bg-surface-container border border-outline-variant rounded flex items-center justify-center font-label-md text-on-surface-variant">B-106</div>
|
|
<!-- Restroom Area -->
|
|
<div class="col-span-6 h-12 flex justify-end gap-4 pr-8">
|
|
<div class="flex items-center gap-2 text-primary font-label-md">
|
|
<span class="material-symbols-outlined text-[20px]">wc</span>
|
|
<span>화장실</span>
|
|
</div>
|
|
<div class="flex items-center gap-2 text-error font-label-md">
|
|
<span class="material-symbols-outlined text-[20px]">emergency</span>
|
|
<span>비상구</span>
|
|
</div>
|
|
</div>
|
|
<!-- More Booths -->
|
|
<div class="bg-surface-container border border-outline-variant rounded flex items-center justify-center font-label-md text-on-surface-variant">C-101</div>
|
|
<div class="bg-surface-container border border-outline-variant rounded flex items-center justify-center font-label-md text-on-surface-variant">C-102</div>
|
|
<div class="bg-surface-container border border-outline-variant rounded flex items-center justify-center font-label-md text-on-surface-variant">C-103</div>
|
|
<div class="bg-surface-container border border-outline-variant rounded flex items-center justify-center font-label-md text-on-surface-variant">C-104</div>
|
|
<div class="bg-surface-container border border-outline-variant rounded flex items-center justify-center font-label-md text-on-surface-variant">C-105</div>
|
|
<div class="bg-surface-container border border-outline-variant rounded flex items-center justify-center font-label-md text-on-surface-variant">C-106</div>
|
|
</div>
|
|
<!-- SVG Route Layer -->
|
|
<svg class="absolute inset-0 pointer-events-none w-full h-full" xmlns="http://www.w3.org/2000/svg">
|
|
<defs>
|
|
<marker id="arrowhead" markerheight="7" markerwidth="10" orient="auto" refx="0" refy="3.5">
|
|
<polygon fill="#004e8b" points="0 0, 10 3.5, 0 7"></polygon>
|
|
</marker>
|
|
</defs>
|
|
<!-- Start: 200, 1200 | End: 340, 180 -->
|
|
<path class="route-line" d="M 200 1200 L 200 600 L 500 600 L 500 200 L 340 200" fill="none" marker-end="url(#arrowhead)" stroke="#004e8b" stroke-linecap="round" stroke-width="6"></path>
|
|
</svg>
|
|
<!-- User Blue Dot Marker -->
|
|
<div class="absolute left-[190px] top-[1190px] z-20">
|
|
<div class="relative">
|
|
<div class="absolute inset-0 bg-primary rounded-full blue-dot-pulse opacity-50 scale-150"></div>
|
|
<div class="w-5 h-5 bg-primary border-2 border-white rounded-full shadow-lg relative z-10 flex items-center justify-center">
|
|
<div class="w-1.5 h-1.5 bg-white rounded-full"></div>
|
|
</div>
|
|
</div>
|
|
<div class="absolute top-8 left-1/2 -translate-x-1/2 whitespace-nowrap bg-white/90 px-2 py-1 rounded shadow-sm text-label-xs border border-outline-variant">
|
|
내 위치
|
|
</div>
|
|
</div>
|
|
<!-- Destination Pin -->
|
|
<div class="absolute left-[330px] top-[180px] z-20">
|
|
<span class="material-symbols-outlined text-primary text-[32px]" style="font-variation-settings: 'FILL' 1;">location_on</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Float Map Controls -->
|
|
<div class="fixed right-6 bottom-48 flex flex-col gap-2 z-30">
|
|
<button class="w-12 h-12 bg-surface-container-lowest rounded-full shadow-lg flex items-center justify-center text-primary hover:bg-surface-container transition-all">
|
|
<span class="material-symbols-outlined">add</span>
|
|
</button>
|
|
<button class="w-12 h-12 bg-surface-container-lowest rounded-full shadow-lg flex items-center justify-center text-primary hover:bg-surface-container transition-all">
|
|
<span class="material-symbols-outlined">remove</span>
|
|
</button>
|
|
<button class="w-12 h-12 bg-primary text-on-primary rounded-full shadow-lg flex items-center justify-center hover:bg-primary-container transition-all">
|
|
<span class="material-symbols-outlined">my_location</span>
|
|
</button>
|
|
</div>
|
|
<!-- Zone Warning Note -->
|
|
<div class="fixed bottom-40 left-1/2 -translate-x-1/2 z-30 pointer-events-none">
|
|
<div class="bg-on-surface/80 text-white px-4 py-1.5 rounded-full text-label-xs backdrop-blur-sm shadow-md flex items-center gap-2">
|
|
<span class="material-symbols-outlined text-[14px]">info</span>
|
|
존 레벨 안내 (정밀 측위 미지원 시)
|
|
</div>
|
|
</div>
|
|
</main>
|
|
<!-- Bottom Sheet Navigation Panel -->
|
|
<section class="fixed bottom-0 w-full z-50 bg-surface-container-lowest rounded-t-[24px] shadow-[0_-8px_24px_rgba(0,0,0,0.08)] bottom-sheet">
|
|
<!-- Drag Handle -->
|
|
<div class="flex justify-center pt-3 pb-2">
|
|
<div class="w-10 h-1 bg-outline-variant rounded-full"></div>
|
|
</div>
|
|
<div class="px-container-padding pb-8 pt-2">
|
|
<div class="flex justify-between items-start mb-6">
|
|
<div>
|
|
<div class="flex items-center gap-2 mb-1">
|
|
<span class="px-2 py-0.5 bg-primary/10 text-primary rounded text-label-xs font-bold">도착지</span>
|
|
<h3 class="font-title-sm text-title-sm text-on-surface">A-102 · 한빛로보틱스</h3>
|
|
</div>
|
|
<div class="flex items-center gap-4 text-on-surface-variant">
|
|
<div class="flex items-center gap-1">
|
|
<span class="material-symbols-outlined text-[18px]">directions_walk</span>
|
|
<span class="font-bold text-primary">도보 3분</span>
|
|
</div>
|
|
<span class="text-outline-variant">|</span>
|
|
<span class="text-body-sm">약 140m</span>
|
|
</div>
|
|
</div>
|
|
<button class="text-outline hover:text-on-surface transition-colors">
|
|
<span class="material-symbols-outlined">close</span>
|
|
</button>
|
|
</div>
|
|
<div class="grid grid-cols-2 gap-3 mb-6">
|
|
<div class="bg-surface-container-low p-3 rounded-xl border border-outline-variant/30">
|
|
<span class="text-label-xs text-on-surface-variant block mb-1">가장 가까운 입구</span>
|
|
<span class="text-body-sm font-semibold">1홀 정문</span>
|
|
</div>
|
|
<div class="bg-surface-container-low p-3 rounded-xl border border-outline-variant/30">
|
|
<span class="text-label-xs text-on-surface-variant block mb-1">근처 편의시설</span>
|
|
<span class="text-body-sm font-semibold">동측 화장실 (20m)</span>
|
|
</div>
|
|
</div>
|
|
<div class="flex gap-3">
|
|
<button class="flex-1 bg-primary text-on-primary h-14 rounded-xl font-title-sm flex items-center justify-center gap-2 hover:bg-primary-container active:scale-95 transition-all">
|
|
<span class="material-symbols-outlined">navigation</span>
|
|
경로 시작
|
|
</button>
|
|
<button class="w-14 h-14 border border-outline-variant rounded-xl flex items-center justify-center text-primary hover:bg-surface-container transition-colors">
|
|
<span class="material-symbols-outlined">share</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
<!-- Bottom Navigation (Implicit from Shared Components) -->
|
|
<nav class="fixed bottom-0 w-full flex justify-around items-center px-4 py-2 bg-surface-container dark:bg-inverse-surface border-t border-outline-variant z-[60] h-[72px]">
|
|
<a class="flex flex-col items-center justify-center text-on-surface-variant px-5 py-1" href="#">
|
|
<span class="material-symbols-outlined">home</span>
|
|
<span class="font-label-md text-label-md">홈</span>
|
|
</a>
|
|
<a class="flex flex-col items-center justify-center bg-secondary-container dark:bg-secondary text-on-secondary-container rounded-full px-5 py-1" href="#">
|
|
<span class="material-symbols-outlined" style="font-variation-settings: 'FILL' 1;">map</span>
|
|
<span class="font-label-md text-label-md">지도</span>
|
|
</a>
|
|
<a class="flex flex-col items-center justify-center text-on-surface-variant px-5 py-1" href="#">
|
|
<span class="material-symbols-outlined">auto_awesome</span>
|
|
<span class="font-label-md text-label-md">매칭</span>
|
|
</a>
|
|
<a class="flex flex-col items-center justify-center text-on-surface-variant px-5 py-1" href="#">
|
|
<span class="material-symbols-outlined">person</span>
|
|
<span class="font-label-md text-label-md">마이</span>
|
|
</a>
|
|
</nav>
|
|
<script>
|
|
// Simple Map Panning Logic
|
|
const container = document.getElementById('map-container');
|
|
let isDown = false;
|
|
let startX;
|
|
let startY;
|
|
let scrollLeft;
|
|
let scrollTop;
|
|
|
|
// Center on the route path initially
|
|
window.addEventListener('load', () => {
|
|
container.scrollLeft = 0;
|
|
container.scrollTop = 150;
|
|
});
|
|
|
|
container.addEventListener('mousedown', (e) => {
|
|
isDown = true;
|
|
startX = e.pageX - container.offsetLeft;
|
|
startY = e.pageY - container.offsetTop;
|
|
scrollLeft = container.scrollLeft;
|
|
scrollTop = container.scrollTop;
|
|
});
|
|
|
|
container.addEventListener('mouseleave', () => {
|
|
isDown = false;
|
|
});
|
|
|
|
container.addEventListener('mouseup', () => {
|
|
isDown = false;
|
|
});
|
|
|
|
container.addEventListener('mousemove', (e) => {
|
|
if (!isDown) return;
|
|
e.preventDefault();
|
|
const x = e.pageX - container.offsetLeft;
|
|
const y = e.pageY - container.offsetTop;
|
|
const walkX = (x - startX) * 1.5;
|
|
const walkY = (y - startY) * 1.5;
|
|
container.scrollLeft = scrollLeft - walkX;
|
|
container.scrollTop = scrollTop - walkY;
|
|
});
|
|
|
|
// Touch support
|
|
container.addEventListener('touchstart', (e) => {
|
|
isDown = true;
|
|
startX = e.touches[0].pageX - container.offsetLeft;
|
|
startY = e.touches[0].pageY - container.offsetTop;
|
|
scrollLeft = container.scrollLeft;
|
|
scrollTop = container.scrollTop;
|
|
});
|
|
|
|
container.addEventListener('touchend', () => {
|
|
isDown = false;
|
|
});
|
|
|
|
container.addEventListener('touchmove', (e) => {
|
|
if (!isDown) return;
|
|
const x = e.touches[0].pageX - container.offsetLeft;
|
|
const y = e.touches[0].pageY - container.offsetTop;
|
|
const walkX = (x - startX) * 1.5;
|
|
const walkY = (y - startY) * 1.5;
|
|
container.scrollLeft = scrollLeft - walkX;
|
|
container.scrollTop = scrollTop - walkY;
|
|
});
|
|
</script>
|
|
</body></html> |