Agent 2.0 exdev 서버 배포 스택

- server-dev start/stop/deploy 및 Gitea push 자동 배포
- local-dev 로컬 개발 환경

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Macbook
2026-07-21 22:57:30 +09:00
commit 4b86b2a660
344 changed files with 45787 additions and 0 deletions
@@ -0,0 +1,97 @@
.minwon-page .partcptn-header {
justify-content: space-between;
}
.minwon-page .partcptn-logo {
text-decoration: none;
}
.minwon-back-link {
font-size: 13px;
font-weight: 600;
color: #333;
text-decoration: none;
}
.minwon-back-link:hover {
color: #005bac;
}
.minwon-main {
flex: 1;
padding: 24px 20px 32px;
max-width: 480px;
margin: 0 auto;
width: 100%;
}
.minwon-title {
margin: 0 0 10px;
font-size: 24px;
font-weight: 800;
letter-spacing: -0.03em;
color: #111;
}
.minwon-lead {
margin: 0 0 24px;
font-size: 14px;
line-height: 1.65;
color: #444;
}
.minwon-cards {
display: flex;
flex-direction: column;
gap: 12px;
}
.minwon-card {
padding: 18px 16px;
border-radius: 14px;
background: rgba(255, 255, 255, 0.92);
border: 1px solid rgba(0, 0, 0, 0.06);
box-shadow: 0 2px 8px rgba(15, 23, 42, 0.04);
}
.minwon-card h2 {
margin: 0 0 8px;
font-size: 16px;
font-weight: 700;
color: #111;
}
.minwon-card p {
margin: 0 0 14px;
font-size: 13px;
line-height: 1.55;
color: #555;
}
.minwon-card-btn {
border: 1px solid #246beb;
background: #fff;
color: #246beb;
border-radius: 999px;
padding: 8px 16px;
font-size: 13px;
font-weight: 700;
font-family: inherit;
cursor: pointer;
}
.minwon-card-btn:hover {
background: #eef4ff;
}
.minwon-note {
margin: 24px 0 0;
font-size: 12px;
line-height: 1.6;
color: #666;
}
.minwon-note a {
color: #005bac;
font-weight: 600;
}
@@ -0,0 +1,306 @@
/* 국민참여로 (ex.co.kr/partcptn) 샘플 — 모바일 퍼스트 */
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body {
margin: 0;
padding: 0;
font-family: "Pretendard", "Malgun Gothic", "Apple SD Gothic Neo", sans-serif;
color: #111;
background: #dfe8f0;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
button {
font-family: inherit;
}
.partcptn-page {
min-height: 100vh;
min-height: 100dvh;
display: flex;
flex-direction: column;
max-width: 480px;
margin: 0 auto;
background: #e8eef4;
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.04);
}
.partcptn-header {
position: relative;
z-index: 10;
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 18px;
background: rgba(255, 255, 255, 0.92);
}
.partcptn-logo {
display: flex;
align-items: center;
gap: 6px;
font-size: 0;
}
.partcptn-logo-ex {
font-size: 22px;
font-weight: 900;
letter-spacing: -0.04em;
background: linear-gradient(90deg, #e60012 0%, #005bac 100%);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.partcptn-logo-text {
font-size: 17px;
font-weight: 800;
color: #111;
letter-spacing: -0.03em;
}
.partcptn-menu-btn {
display: flex;
align-items: center;
gap: 6px;
border: none;
background: transparent;
font-size: 14px;
font-weight: 600;
color: #333;
cursor: pointer;
padding: 4px 0;
}
.partcptn-menu-icon {
display: flex;
flex-direction: column;
gap: 4px;
width: 18px;
}
.partcptn-menu-icon span {
display: block;
height: 2px;
background: #333;
border-radius: 1px;
}
.partcptn-hero {
position: relative;
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
overflow: visible;
}
.partcptn-hero-bg {
position: absolute;
inset: 0;
background:
linear-gradient(180deg, rgba(232, 238, 244, 0.2) 0%, rgba(210, 220, 232, 0.45) 100%),
radial-gradient(ellipse 70% 50% at 50% 55%, rgba(255, 220, 150, 0.35) 0%, transparent 55%),
radial-gradient(ellipse 90% 60% at 50% 40%, rgba(200, 215, 235, 0.9) 0%, rgba(180, 195, 215, 0.6) 100%);
pointer-events: none;
}
.partcptn-hero-bg::after {
content: "";
position: absolute;
left: 50%;
top: 42%;
transform: translate(-50%, -50%);
width: 120px;
height: 160px;
background: radial-gradient(ellipse, rgba(255, 240, 200, 0.5) 0%, transparent 70%);
filter: blur(8px);
}
.partcptn-hero-content {
position: relative;
z-index: 2;
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
padding: 28px 20px 24px;
overflow: visible;
}
.partcptn-hero-title {
margin: 0 0 16px;
font-size: clamp(22px, 5.5vw, 28px);
font-weight: 800;
line-height: 1.35;
letter-spacing: -0.03em;
color: #111;
}
.partcptn-hero-sub {
margin: 0 0 28px;
font-size: 14px;
line-height: 1.65;
color: #333;
max-width: 320px;
}
.partcptn-search-slot {
width: 100%;
max-width: 340px;
background: transparent;
line-height: 0;
overflow: visible;
position: relative;
z-index: 30;
}
.partcptn-search-slot iframe {
display: block;
width: 100%;
border: 0;
min-height: 48px;
background: transparent !important;
overflow: visible;
vertical-align: top;
}
.partcptn-search-slot.is-expanded {
margin-bottom: 4px;
}
.partcptn-page--search-open .partcptn-hero {
flex: 1 1 auto;
min-height: min(72vh, 640px);
}
.partcptn-bottom-dock {
position: relative;
z-index: 5;
flex-shrink: 0;
display: flex;
align-items: stretch;
justify-content: space-around;
gap: 0;
margin-top: auto;
padding: 18px 12px calc(18px + env(safe-area-inset-bottom, 0px));
background: rgba(58, 54, 50, 0.62);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border-top: 1px solid rgba(255, 255, 255, 0.06);
}
.partcptn-dock-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
gap: 10px;
min-width: 0;
padding: 4px 8px 2px;
border: none;
background: transparent;
color: #fff;
cursor: pointer;
text-align: center;
transition: opacity 0.15s;
}
.partcptn-dock-item:hover {
opacity: 0.88;
}
.partcptn-dock-item:active {
opacity: 0.75;
}
.partcptn-dock-icon {
width: 44px;
height: 44px;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
flex-shrink: 0;
}
.partcptn-dock-icon svg {
width: 44px;
height: 44px;
display: block;
}
.partcptn-dock-label {
display: block;
font-size: 12px;
font-weight: 500;
line-height: 1.35;
letter-spacing: -0.02em;
color: rgba(255, 255, 255, 0.96);
word-break: keep-all;
}
.sample-chat-overlay {
position: fixed;
inset: 0;
z-index: 9999;
display: none;
align-items: center;
justify-content: center;
padding: 16px;
background: rgba(15, 23, 42, 0.45);
}
.sample-chat-overlay.is-open {
display: flex;
}
.sample-chat-panel {
position: relative;
width: min(100%, 420px);
height: min(88vh, 720px);
border-radius: 16px;
overflow: hidden;
background: #fff;
box-shadow: 0 24px 48px rgba(15, 23, 42, 0.2);
}
.sample-chat-panel iframe {
display: block;
width: 100%;
height: 100%;
border: 0;
}
.sample-chat-close {
position: absolute;
top: 10px;
right: 10px;
z-index: 2;
width: 36px;
height: 36px;
border: none;
border-radius: 50%;
background: rgba(255, 255, 255, 0.95);
color: #333;
font-size: 22px;
line-height: 1;
cursor: pointer;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
}
@media (min-width: 481px) {
.partcptn-page {
min-height: 100vh;
border-left: 1px solid rgba(0, 0, 0, 0.06);
border-right: 1px solid rgba(0, 0, 0, 0.06);
}
}
@@ -0,0 +1,244 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<title>국민참여로 - 국민과 함께, 열린 도로, 열린 소통</title>
<meta name="description" content="한국도로공사 국민참여로 샘플 — AI 검색 배너 iframe 연동" />
<link rel="stylesheet" href="/chatbot/sample/css/partcptn-sample.css" />
</head>
<body>
<div class="partcptn-page">
<header class="partcptn-header">
<div class="partcptn-logo" aria-label="ex 국민참여로">
<span class="partcptn-logo-ex">ex</span>
<span class="partcptn-logo-text">국민참여로</span>
</div>
<button type="button" class="partcptn-menu-btn" aria-label="전체메뉴">
<span class="partcptn-menu-icon" aria-hidden="true">
<span></span><span></span><span></span>
</span>
전체메뉴
</button>
</header>
<main class="partcptn-hero">
<div class="partcptn-hero-bg" aria-hidden="true"></div>
<div class="partcptn-hero-content">
<h1 class="partcptn-hero-title">국민과 함께, 열린 도로, 열린 소통</h1>
<p class="partcptn-hero-sub">
국민의 작은 목소리도 정책의 시작이 됩니다.<br />
소통하고, 함께 만들어가는 스마트 국민 소통 플랫폼
</p>
<div class="partcptn-search-slot" id="partcptn-search-slot">
<iframe
id="chatbot-banner-frame"
src="/chatbot/banner/?embed=1&theme=partcptn&minwonUrl=%2Fchatbot%2Fsample%2Fminwon%2F&placeholder=%EA%B2%80%EC%83%89%EC%96%B4%EB%A5%BC%20%EC%9E%85%EB%A0%A5%ED%95%B4%20%EC%A3%BC%EC%84%B8%EC%9A%94"
title="AI 검색"
loading="lazy"
allowtransparency="true"
style="height: 48px"
></iframe>
</div>
</div>
</main>
<nav class="partcptn-bottom-dock" aria-label="챗봇 바로가기">
<button
type="button"
class="partcptn-dock-item"
data-chatbot-url="/chatbot/app/"
data-chatbot-title="카톡챗봇"
>
<span class="partcptn-dock-icon" aria-hidden="true">
<svg viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M14 10h20a4 4 0 0 1 4 4v10a4 4 0 0 1-4 4H20l-6 4v-4h-4a4 4 0 0 1-4-4V14a4 4 0 0 1 4-4z"
stroke="currentColor"
stroke-width="1.6"
stroke-linejoin="round"
/>
<circle cx="18" cy="19" r="1.2" fill="currentColor" />
<circle cx="24" cy="19" r="1.2" fill="currentColor" />
<circle cx="30" cy="19" r="1.2" fill="currentColor" />
</svg>
</span>
<span class="partcptn-dock-label">카톡챗봇</span>
</button>
<button
type="button"
class="partcptn-dock-item"
data-chatbot-url="/chatbot/ai/"
data-chatbot-title="AI챗봇"
>
<span class="partcptn-dock-icon" aria-hidden="true">
<svg viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="12" y="14" width="24" height="20" rx="3" stroke="currentColor" stroke-width="1.6" />
<path d="M20 22h8M20 26h5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" />
<path d="M18 10h12" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" />
<circle cx="18" cy="10" r="1.5" fill="currentColor" />
<circle cx="30" cy="10" r="1.5" fill="currentColor" />
<path d="M16 38h16" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" />
</svg>
</span>
<span class="partcptn-dock-label">AI챗봇</span>
</button>
<button
type="button"
class="partcptn-dock-item"
data-chatbot-url="/chatbot/voicebot/"
data-chatbot-title="음성챗봇"
>
<span class="partcptn-dock-icon" aria-hidden="true">
<svg viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="20" y="12" width="8" height="14" rx="4" stroke="currentColor" stroke-width="1.6" />
<path
d="M16 24a8 8 0 0 0 16 0"
stroke="currentColor"
stroke-width="1.6"
stroke-linecap="round"
/>
<path d="M24 32v4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" />
<path d="M20 36h8" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" />
</svg>
</span>
<span class="partcptn-dock-label">음성챗봇</span>
</button>
</nav>
</div>
<div
id="sample-chat-overlay"
class="sample-chat-overlay"
role="dialog"
aria-modal="true"
aria-hidden="true"
aria-label="챗봇"
>
<div class="sample-chat-panel">
<iframe
id="sample-chat-frame"
title="챗봇"
src="about:blank"
allow="microphone *; autoplay *"
></iframe>
</div>
</div>
<script>
(function () {
var iframe = document.getElementById("chatbot-banner-frame");
var searchSlot = document.getElementById("partcptn-search-slot");
var page = document.querySelector(".partcptn-page");
if (iframe) {
var minH = 48;
function maxH() {
return Math.min(Math.round(window.innerHeight * 0.72), 640);
}
window.addEventListener("message", function (ev) {
if (!ev.data || ev.data.type !== "ex-chatbot-banner-resize") return;
if (ev.source !== iframe.contentWindow) return;
var h = Number(ev.data.height);
if (h > 0) {
var next = Math.min(Math.max(h, minH), maxH());
iframe.style.height = next + "px";
var expanded = next > minH + 8;
if (searchSlot) {
searchSlot.classList.toggle("is-expanded", expanded);
}
if (page) {
page.classList.toggle("partcptn-page--search-open", expanded);
}
}
});
}
var overlay = document.getElementById("sample-chat-overlay");
var chatFrame = document.getElementById("sample-chat-frame");
function withEmbed(url) {
if (!url) return url;
return url + (url.indexOf("?") >= 0 ? "&" : "?") + "embed=1";
}
function openChatbot(url, title) {
if (!overlay || !chatFrame) return;
var isVoicebot = url.indexOf("/voicebot") !== -1;
var frameUrl = withEmbed(url);
function showFrame() {
chatFrame.src = frameUrl;
chatFrame.title = title || "챗봇";
overlay.classList.add("is-open");
overlay.setAttribute("aria-hidden", "false");
document.body.style.overflow = "hidden";
if (isVoicebot) {
chatFrame.onload = function () {
try {
chatFrame.contentWindow.postMessage(
{ type: "ex-voicebot-autostart" },
window.location.origin
);
} catch (_) {}
};
} else {
chatFrame.onload = null;
}
}
if (isVoicebot && navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
navigator.mediaDevices
.getUserMedia({ audio: true, video: false })
.then(function (stream) {
stream.getTracks().forEach(function (track) {
track.stop();
});
showFrame();
})
.catch(function () {
showFrame();
});
return;
}
showFrame();
}
function closeChatbot() {
if (!overlay || !chatFrame) return;
overlay.classList.remove("is-open");
overlay.setAttribute("aria-hidden", "true");
chatFrame.src = "about:blank";
document.body.style.overflow = "";
}
document.querySelectorAll("[data-chatbot-url]").forEach(function (btn) {
btn.addEventListener("click", function () {
openChatbot(btn.getAttribute("data-chatbot-url"), btn.getAttribute("data-chatbot-title"));
});
});
window.addEventListener("message", function (ev) {
if (!ev.data || ev.data.type !== "ex-chatbot-close") return;
if (chatFrame && ev.source === chatFrame.contentWindow) {
closeChatbot();
}
});
if (overlay) {
overlay.addEventListener("click", function (e) {
if (e.target === overlay) closeChatbot();
});
}
document.addEventListener("keydown", function (e) {
if (e.key === "Escape" && overlay && overlay.classList.contains("is-open")) {
closeChatbot();
}
});
})();
</script>
</body>
</html>
@@ -0,0 +1,52 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<title>민원신청 - 국민참여로</title>
<link rel="stylesheet" href="/chatbot/sample/css/partcptn-sample.css" />
<link rel="stylesheet" href="/chatbot/sample/css/minwon-sample.css" />
</head>
<body>
<div class="partcptn-page minwon-page">
<header class="partcptn-header">
<a href="/chatbot/sample/" class="partcptn-logo" aria-label="국민참여로 홈">
<span class="partcptn-logo-ex">ex</span>
<span class="partcptn-logo-text">국민참여로</span>
</a>
<a href="/chatbot/sample/" class="minwon-back-link">← 샘플 홈</a>
</header>
<main class="minwon-main">
<h1 class="minwon-title">민원신청</h1>
<p class="minwon-lead">
고속도로 이용과 관련된 민원을 온라인으로 접수·조회하실 수 있습니다.
</p>
<section class="minwon-cards" aria-label="민원 유형">
<article class="minwon-card">
<h2>포장 파손 피해 배상</h2>
<p>고속도로 포장 파손으로 인한 차량 피해 배상을 신청합니다.</p>
<button type="button" class="minwon-card-btn">신청하기</button>
</article>
<article class="minwon-card">
<h2>도로 점용 허가</h2>
<p>고속도로 구간 내 점용·굴착 등 허가를 신청합니다.</p>
<button type="button" class="minwon-card-btn">신청하기</button>
</article>
<article class="minwon-card">
<h2>일반 민원 접수</h2>
<p>기타 고속도로 관련 민원을 접수합니다.</p>
<button type="button" class="minwon-card-btn">신청하기</button>
</article>
</section>
<p class="minwon-note">
본 페이지는 국민참여로 샘플 연동용 데모 화면입니다. 실제 민원 접수는
<a href="https://www.ex.co.kr/partcptn/" target="_blank" rel="noopener noreferrer">한국도로공사 국민참여로</a>
를 이용해 주세요.
</p>
</main>
</div>
</body>
</html>