<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>피싱 모의훈련 안내 패널 시안</title>
<link rel="stylesheet" as="style" crossorigin
href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<style>
:root{
--brand-blue: #0A6CBE; /* 홈페이지 핵심 진한 하늘색 (중앙 콘텐츠용으로만 사용) */
--brand-blue-deep: #063F73;
--brand-blue-mist: #EAF3FB;
--drill-red: #B0202E; /* 모의훈련 배너 주색 */
--drill-red-deep: #601018;
--drill-amber: #E8A93C; /* "실제 위협 아님"을 신호하는 보조색 */
--ink: #0B1B2B;
--ink-soft: #4B6178;
--paper: #FFFFFF;
--line: #D8E6F2;
}
*{ box-sizing:border-box; }
body{
margin:0;
background:var(--paper);
font-family:'Pretendard', -apple-system, sans-serif;
color:var(--ink);
display:flex;
justify-content:center;
min-height:100vh;
}
.page{
display:flex;
align-items:flex-start;
gap:0;
}
/* ================= 데모용 중앙 콘텐츠 (실제 홈페이지 자리) ================= */
.demo-center{
width:520px;
padding:64px 40px;
color:var(--ink-soft);
}
.demo-center h1{ color:var(--ink); font-size:22px; margin:0 0 8px; }
.demo-center p{ font-size:14px; line-height:1.7; }
.demo-note{
margin-top:24px;
padding:14px 16px;
background:var(--brand-blue-mist);
border-left:3px solid var(--brand-blue);
font-size:12.5px;
color:var(--brand-blue-deep);
line-height:1.6;
}
/* ================= 좌측: 모의훈련 안내 존 (300px) ================= */
.drill-zone{
width:300px;
min-height:100vh;
background:linear-gradient(180deg, var(--drill-red-deep) 0%, var(--drill-red) 55%, var(--drill-red) 100%);
color:#fff;
padding:48px 28px 32px;
display:flex;
flex-direction:column;
align-items:center;
text-align:center;
position:relative;
overflow:hidden;
}
/* 완만하게 도는 표적 링 — 급박한 경고가 아니라 "진행 중인 점검"을 표현 */
.radar{
width:120px; height:120px;
position:relative;
margin-bottom:24px;
display:flex; align-items:center; justify-content:center;
}
.radar-ring{
position:absolute; inset:0;
border-radius:50%;
border:1.5px solid rgba(232,169,60,0.55);
}
.radar-ring.r2{ inset:14px; border-color:rgba(232,169,60,0.4); }
.radar-ring.r3{ inset:28px; border-color:rgba(232,169,60,0.3); }
.radar-sweep{
position:absolute; inset:0;
border-radius:50%;
background:conic-gradient(from 0deg, rgba(232,169,60,0) 0deg, rgba(232,169,60,0.5) 28deg, rgba(232,169,60,0) 65deg);
animation:sweep 5.5s linear infinite;
}
.radar-core{
width:14px; height:14px;
border-radius:50%;
background:var(--drill-amber);
z-index:2;
}
@keyframes sweep{ to{ transform:rotate(360deg); } }
.drill-badge{
display:inline-block;
font-size:11px;
font-weight:700;
letter-spacing:0.3px;
color:var(--drill-red-deep);
background:var(--drill-amber);
padding:4px 10px;
border-radius:2px;
margin-bottom:14px;
}
.drill-eyebrow{
font-size:13px;
color:#FBE3C0;
margin-bottom:10px;
letter-spacing:0.2px;
}
.drill-headline{
font-size:26px;
font-weight:700;
line-height:1.4;
margin:0 0 18px;
}
.drill-headline em{
font-style:normal;
color:var(--drill-amber);
}
.drill-desc{
font-size:13.5px;
line-height:1.7;
color:#F3D8CE;
margin:0 0 26px;
}
.drill-count{
display:flex;
align-items:baseline;
gap:8px;
padding:16px 22px;
background:rgba(255,255,255,0.12);
border:1px solid rgba(255,255,255,0.22);
border-radius:4px;
margin-bottom:26px;
}
.drill-count .num{
font-family:'JetBrains Mono', monospace;
font-size:34px;
font-weight:600;
color:var(--drill-amber);
line-height:1;
}
.drill-count .unit{ font-size:13px; color:#F3D8CE; }
.drill-cta{
appearance:none;
border:1px solid rgba(255,255,255,0.5);
background:transparent;
color:#fff;
font-family:inherit;
font-size:13.5px;
font-weight:600;
padding:12px 22px;
border-radius:2px;
cursor:pointer;
transition:background .15s ease;
}
.drill-cta:hover{ background:rgba(255,255,255,0.14); }
.drill-cta:focus-visible{ outline:2px solid #fff; outline-offset:2px; }
.drill-foot{
margin-top:auto;
padding-top:28px;
font-size:11.5px;
color:#E9BFAE;
line-height:1.6;
}
/* ================= 우측: 훈련 메일 로그 존 (300px) ================= */
.feed-zone{
width:300px;
min-height:100vh;
background:var(--paper);
border-left:1px solid var(--line);
padding:40px 22px 32px;
}
.feed-head{
display:flex;
align-items:center;
gap:8px;
margin-bottom:6px;
}
.live-dot{
width:8px; height:8px;
border-radius:50%;
background:var(--drill-red);
animation:blink 1.6s ease-in-out infinite;
flex-shrink:0;
}
@keyframes blink{ 0%,100%{ opacity:1; } 50%{ opacity:.25; } }
.feed-head h2{
font-size:15.5px;
font-weight:700;
margin:0;
color:var(--ink);
}
.feed-sub{
font-size:12px;
color:var(--ink-soft);
margin:0 0 20px;
font-family:'JetBrains Mono', monospace;
}
.feed-list{
display:flex;
flex-direction:column;
gap:10px;
}
.feed-card{
border:1px solid var(--line);
border-left:3px solid var(--drill-red);
background:#FBEDEC;
padding:12px 13px;
border-radius:2px;
}
.feed-card[data-severity="high"]{ border-left-color:var(--drill-red-deep); background:#F7DEDC; }
.feed-type{
display:inline-block;
font-size:10.5px;
font-weight:600;
color:var(--drill-red-deep);
background:rgba(176,32,46,0.1);
padding:2px 7px;
border-radius:2px;
margin-bottom:7px;
}
.feed-subject{
font-size:13px;
font-weight:600;
color:var(--ink);
line-height:1.4;
margin:0 0 6px;
/* 긴 제목 잘림 처리 */
display:-webkit-box;
-webkit-line-clamp:2;
-webkit-box-orient:vertical;
overflow:hidden;
}
.feed-meta{
font-family:'JetBrains Mono', monospace;
font-size:11px;
color:var(--ink-soft);
display:flex;
flex-direction:column;
gap:2px;
}
.feed-meta b{ color:var(--ink-soft); font-weight:500; }
.feed-empty{
font-size:12.5px;
color:var(--ink-soft);
text-align:center;
padding:32px 0;
}
@media (prefers-reduced-motion: reduce){
.radar-sweep, .live-dot{ animation:none; }
}
</style>
</head>
<body>
<div class="page">
<!-- ================= 좌측: 모의훈련 안내 존 ================= -->
<aside class="drill-zone" id="drillZone">
<div class="radar" aria-hidden="true">
<div class="radar-ring"></div>
<div class="radar-ring r2"></div>
<div class="radar-ring r3"></div>
<div class="radar-sweep"></div>
<div class="radar-core"></div>
</div>
<span class="drill-badge">모의훈련 · 실제 위협 아님</span>
<p class="drill-eyebrow">보안팀 모의훈련 안내</p>
<h2 class="drill-headline">지금은 <em>피싱 모의훈련</em><br>기간입니다</h2>
<p class="drill-desc">실제 공격이 아닌 대응 역량 점검을 위한 훈련입니다.<br>의심 메일을 받으면 평소처럼 신고 절차를 따라주세요.</p>
<div class="drill-count">
<span class="num" id="drillDay">3</span>
<span class="unit">일차 진행 중</span>
</div>
<button class="drill-cta" type="button">훈련 안내 보기</button>
<p class="drill-foot"><span id="drillPeriod">9.1 ~ 9.5</span> 진행 · 문의: 보안팀 내선 1234</p>
</aside>
<!-- ================= 중앙: 데모용 (실제 배포 시 제외) ================= -->
<main class="demo-center">
<h1>홈페이지 본문 영역</h1>
<p>이 영역은 실제 홈페이지 콘텐츠가 들어갈 자리입니다. 좌우 300px 패널이 본문을 감싸는 배치를 확인하기 위한 데모용 블록입니다.</p>
<div class="demo-note">
좌측은 모의훈련 기간임을 각인시키는 포스터형 구성, 우측은 훈련에 사용된 모의 피싱 메일 정보를 스캔하기 쉬운 로그형 구성으로 나눴습니다.
우측은 <code>window.updateDrillFeed(list)</code>, 좌측 일차/기간 표기는 <code>window.updateDrillInfo(data)</code> 호출로 갱신됩니다. 아래 스크립트 예시를 참고해 주세요.
</div>
</main>
<!-- ================= 우측: 훈련 메일 로그 존 ================= -->
<aside class="feed-zone">
<div class="feed-head">
<span class="live-dot" aria-hidden="true"></span>
<h2>모의훈련 메일 로그</h2>
</div>
<p class="feed-sub">phishing_drill // sent_feed</p>
<div class="feed-list" id="feedList">
<!-- JS로 렌더링됨. 아래는 초기 더미 데이터 예시 -->
</div>
</aside>
</div>
<script>
// ============================================================
// 데모용 더미 데이터 — 실제로는 이미 작성하신 코드에서
// window.updateDrillFeed(list) 를 호출해 주시면 됩니다.
// list 항목 형식: { subject, sender, type, severity }
// severity: "high" (진한 강조) | "normal" (기본)
// ============================================================
const DUMMY_DATA = [
{ subject: "[긴급] 급여명세서 확인 요청드립니다", sender: "hr-noti@company-hr.co", type: "인사팀 사칭", severity: "high" },
{ subject: "세금계산서 발행 안내 (미확인 시 연체)", sender: "billing@invoice-secure.net", type: "인보이스 사칭", severity: "normal" },
{ subject: "택배 배송 지연 안내 - 주소 재확인 필요", sender: "delivery@parcel-track.info", type: "택배 사칭", severity: "normal" },
{ subject: "대표님 지시사항 - 긴급 송금 요청", sender: "ceo.office@corp-mail.top", type: "임원 사칭", severity: "high" },
];
function renderFeed(list){
const el = document.getElementById('feedList');
if(!list || list.length === 0){
el.innerHTML = '<p class="feed-empty">이번 훈련에 발송된 메일이 아직 없습니다.</p>';
return;
}
el.innerHTML = list.map(item => `
<div class="feed-card" data-severity="${item.severity === 'high' ? 'high' : 'normal'}">
<span class="feed-type">${escapeHtml(item.type)}</span>
<p class="feed-subject">${escapeHtml(item.subject)}</p>
<div class="feed-meta">
<span><b>보낸사람</b> ${escapeHtml(item.sender)}</span>
</div>
</div>
`).join('');
}
function escapeHtml(str){
return String(str).replace(/[&<>"']/g, c => ({
'&':'&','<':'<','>':'>','"':'"',"'":'''
}[c]));
}
// 외부(기존 코드)에서 호출할 공개 API
window.updateDrillFeed = function(list){
renderFeed(Array.isArray(list) ? list : []);
};
// 좌측 배너의 일차/기간 갱신 — data 예시: { day: 3, period: "9.1 ~ 9.5" }
window.updateDrillInfo = function(data){
if(!data) return;
if(data.day != null) document.getElementById('drillDay').textContent = data.day;
if(data.period) document.getElementById('drillPeriod').textContent = data.period;
};
// 초기 데모 렌더
renderFeed(DUMMY_DATA);
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>피싱 모의훈련 안내 패널 시안</title>
<link rel="stylesheet" as="style" crossorigin
href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<style>
:root{
--brand-blue: #0A6CBE; /* 홈페이지 핵심 진한 하늘색 (중앙 콘텐츠용으로만 사용) */
--brand-blue-deep: #063F73;
--brand-blue-mist: #EAF3FB;
--drill-red: #B0202E; /* 모의훈련 배너 주색 */
--drill-red-deep: #601018;
--drill-amber: #E8A93C; /* "실제 위협 아님"을 신호하는 보조색 */
--ink: #0B1B2B;
--ink-soft: #4B6178;
--paper: #FFFFFF;
--line: #D8E6F2;
}
*{ box-sizing:border-box; }
body{
margin:0;
background:var(--paper);
font-family:'Pretendard', -apple-system, sans-serif;
color:var(--ink);
display:flex;
justify-content:center;
min-height:100vh;
}
.page{
display:flex;
align-items:flex-start;
gap:0;
}
/* ================= 데모용 중앙 콘텐츠 (실제 홈페이지 자리) ================= */
.demo-center{
width:520px;
padding:64px 40px;
color:var(--ink-soft);
}
.demo-center h1{ color:var(--ink); font-size:22px; margin:0 0 8px; }
.demo-center p{ font-size:14px; line-height:1.7; }
.demo-note{
margin-top:24px;
padding:14px 16px;
background:var(--brand-blue-mist);
border-left:3px solid var(--brand-blue);
font-size:12.5px;
color:var(--brand-blue-deep);
line-height:1.6;
}
/* ================= 좌측: 모의훈련 안내 존 (300px) ================= */
.drill-zone{
width:300px;
min-height:100vh;
background:linear-gradient(180deg, var(--drill-red-deep) 0%, var(--drill-red) 55%, var(--drill-red) 100%);
color:#fff;
padding:48px 28px 32px;
display:flex;
flex-direction:column;
align-items:center;
text-align:center;
position:relative;
overflow:hidden;
}
/* 완만하게 도는 표적 링 — 급박한 경고가 아니라 "진행 중인 점검"을 표현 */
.radar{
width:120px; height:120px;
position:relative;
margin-bottom:24px;
display:flex; align-items:center; justify-content:center;
}
.radar-ring{
position:absolute; inset:0;
border-radius:50%;
border:1.5px solid rgba(232,169,60,0.55);
}
.radar-ring.r2{ inset:14px; border-color:rgba(232,169,60,0.4); }
.radar-ring.r3{ inset:28px; border-color:rgba(232,169,60,0.3); }
.radar-sweep{
position:absolute; inset:0;
border-radius:50%;
background:conic-gradient(from 0deg, rgba(232,169,60,0) 0deg, rgba(232,169,60,0.5) 28deg, rgba(232,169,60,0) 65deg);
animation:sweep 5.5s linear infinite;
}
.radar-core{
width:14px; height:14px;
border-radius:50%;
background:var(--drill-amber);
z-index:2;
}
@keyframes sweep{ to{ transform:rotate(360deg); } }
.drill-badge{
display:inline-block;
font-size:11px;
font-weight:700;
letter-spacing:0.3px;
color:var(--drill-red-deep);
background:var(--drill-amber);
padding:4px 10px;
border-radius:2px;
margin-bottom:14px;
}
.drill-eyebrow{
font-size:13px;
color:#FBE3C0;
margin-bottom:10px;
letter-spacing:0.2px;
}
.drill-headline{
font-size:26px;
font-weight:700;
line-height:1.4;
margin:0 0 18px;
}
.drill-headline em{
font-style:normal;
color:var(--drill-amber);
}
.drill-desc{
font-size:13.5px;
line-height:1.7;
color:#F3D8CE;
margin:0 0 26px;
}
.drill-count{
display:flex;
align-items:baseline;
gap:8px;
padding:16px 22px;
background:rgba(255,255,255,0.12);
border:1px solid rgba(255,255,255,0.22);
border-radius:4px;
margin-bottom:26px;
}
.drill-count .num{
font-family:'JetBrains Mono', monospace;
font-size:34px;
font-weight:600;
color:var(--drill-amber);
line-height:1;
}
.drill-count .unit{ font-size:13px; color:#F3D8CE; }
.drill-cta{
appearance:none;
border:1px solid rgba(255,255,255,0.5);
background:transparent;
color:#fff;
font-family:inherit;
font-size:13.5px;
font-weight:600;
padding:12px 22px;
border-radius:2px;
cursor:pointer;
transition:background .15s ease;
}
.drill-cta:hover{ background:rgba(255,255,255,0.14); }
.drill-cta:focus-visible{ outline:2px solid #fff; outline-offset:2px; }
.drill-foot{
margin-top:auto;
padding-top:28px;
font-size:11.5px;
color:#E9BFAE;
line-height:1.6;
}
/* ================= 우측: 훈련 메일 로그 존 (300px) ================= */
.feed-zone{
width:300px;
min-height:100vh;
background:var(--paper);
border-left:1px solid var(--line);
padding:40px 22px 32px;
}
.feed-head{
display:flex;
align-items:center;
gap:8px;
margin-bottom:6px;
}
.live-dot{
width:8px; height:8px;
border-radius:50%;
background:var(--drill-red);
animation:blink 1.6s ease-in-out infinite;
flex-shrink:0;
}
@keyframes blink{ 0%,100%{ opacity:1; } 50%{ opacity:.25; } }
.feed-head h2{
font-size:15.5px;
font-weight:700;
margin:0;
color:var(--ink);
}
.feed-sub{
font-size:12px;
color:var(--ink-soft);
margin:0 0 20px;
font-family:'JetBrains Mono', monospace;
}
.feed-list{
display:flex;
flex-direction:column;
gap:10px;
}
.feed-card{
border:1px solid var(--line);
border-left:3px solid var(--drill-red);
background:#FBEDEC;
padding:12px 13px;
border-radius:2px;
}
.feed-card[data-severity="high"]{ border-left-color:var(--drill-red-deep); background:#F7DEDC; }
.feed-type{
display:inline-block;
font-size:10.5px;
font-weight:600;
color:var(--drill-red-deep);
background:rgba(176,32,46,0.1);
padding:2px 7px;
border-radius:2px;
margin-bottom:7px;
}
.feed-subject{
font-size:13px;
font-weight:600;
color:var(--ink);
line-height:1.4;
margin:0 0 6px;
/* 긴 제목 잘림 처리 */
display:-webkit-box;
-webkit-line-clamp:2;
-webkit-box-orient:vertical;
overflow:hidden;
}
.feed-meta{
font-family:'JetBrains Mono', monospace;
font-size:11px;
color:var(--ink-soft);
display:flex;
flex-direction:column;
gap:2px;
}
.feed-meta b{ color:var(--ink-soft); font-weight:500; }
.feed-empty{
font-size:12.5px;
color:var(--ink-soft);
text-align:center;
padding:32px 0;
}
@media (prefers-reduced-motion: reduce){
.radar-sweep, .live-dot{ animation:none; }
}
</style>
</head>
<body>
<div class="page">
<!-- ================= 좌측: 모의훈련 안내 존 ================= -->
<aside class="drill-zone" id="drillZone">
<div class="radar" aria-hidden="true">
<div class="radar-ring"></div>
<div class="radar-ring r2"></div>
<div class="radar-ring r3"></div>
<div class="radar-sweep"></div>
<div class="radar-core"></div>
</div>
<span class="drill-badge">모의훈련 · 실제 위협 아님</span>
<p class="drill-eyebrow">보안팀 모의훈련 안내</p>
<h2 class="drill-headline">지금은 <em>피싱 모의훈련</em><br>기간입니다</h2>
<p class="drill-desc">실제 공격이 아닌 대응 역량 점검을 위한 훈련입니다.<br>의심 메일을 받으면 평소처럼 신고 절차를 따라주세요.</p>
<div class="drill-count">
<span class="num" id="drillDay">3</span>
<span class="unit">일차 진행 중</span>
</div>
<button class="drill-cta" type="button">훈련 안내 보기</button>
<p class="drill-foot"><span id="drillPeriod">9.1 ~ 9.5</span> 진행 · 문의: 보안팀 내선 1234</p>
</aside>
<!-- ================= 중앙: 데모용 (실제 배포 시 제외) ================= -->
<main class="demo-center">
<h1>홈페이지 본문 영역</h1>
<p>이 영역은 실제 홈페이지 콘텐츠가 들어갈 자리입니다. 좌우 300px 패널이 본문을 감싸는 배치를 확인하기 위한 데모용 블록입니다.</p>
<div class="demo-note">
좌측은 모의훈련 기간임을 각인시키는 포스터형 구성, 우측은 훈련에 사용된 모의 피싱 메일 정보를 스캔하기 쉬운 로그형 구성으로 나눴습니다.
우측은 <code>window.updateDrillFeed(list)</code>, 좌측 일차/기간 표기는 <code>window.updateDrillInfo(data)</code> 호출로 갱신됩니다. 아래 스크립트 예시를 참고해 주세요.
</div>
</main>
<!-- ================= 우측: 훈련 메일 로그 존 ================= -->
<aside class="feed-zone">
<div class="feed-head">
<span class="live-dot" aria-hidden="true"></span>
<h2>모의훈련 메일 로그</h2>
</div>
<p class="feed-sub">phishing_drill // sent_feed</p>
<div class="feed-list" id="feedList">
<!-- JS로 렌더링됨. 아래는 초기 더미 데이터 예시 -->
</div>
</aside>
</div>
<script>
// ============================================================
// 데모용 더미 데이터 — 실제로는 이미 작성하신 코드에서
// window.updateDrillFeed(list) 를 호출해 주시면 됩니다.
// list 항목 형식: { subject, sender, type, severity }
// severity: "high" (진한 강조) | "normal" (기본)
// ============================================================
const DUMMY_DATA = [
{ subject: "[긴급] 급여명세서 확인 요청드립니다", sender: "hr-noti@company-hr.co", type: "인사팀 사칭", severity: "high" },
{ subject: "세금계산서 발행 안내 (미확인 시 연체)", sender: "billing@invoice-secure.net", type: "인보이스 사칭", severity: "normal" },
{ subject: "택배 배송 지연 안내 - 주소 재확인 필요", sender: "delivery@parcel-track.info", type: "택배 사칭", severity: "normal" },
{ subject: "대표님 지시사항 - 긴급 송금 요청", sender: "ceo.office@corp-mail.top", type: "임원 사칭", severity: "high" },
];
function renderFeed(list){
const el = document.getElementById('feedList');
if(!list || list.length === 0){
el.innerHTML = '<p class="feed-empty">이번 훈련에 발송된 메일이 아직 없습니다.</p>';
return;
}
el.innerHTML = list.map(item => `
<div class="feed-card" data-severity="${item.severity === 'high' ? 'high' : 'normal'}">
<span class="feed-type">${escapeHtml(item.type)}</span>
<p class="feed-subject">${escapeHtml(item.subject)}</p>
<div class="feed-meta">
<span><b>보낸사람</b> ${escapeHtml(item.sender)}</span>
</div>
</div>
`).join('');
}
function escapeHtml(str){
return String(str).replace(/[&<>"']/g, c => ({
'&':'&','<':'<','>':'>','"':'"',"'":'''
}[c]));
}
// 외부(기존 코드)에서 호출할 공개 API
window.updateDrillFeed = function(list){
renderFeed(Array.isArray(list) ? list : []);
};
// 좌측 배너의 일차/기간 갱신 — data 예시: { day: 3, period: "9.1 ~ 9.5" }
window.updateDrillInfo = function(data){
if(!data) return;
if(data.day != null) document.getElementById('drillDay').textContent = data.day;
if(data.period) document.getElementById('drillPeriod').textContent = data.period;
};
// 초기 데모 렌더
renderFeed(DUMMY_DATA);
</script>
</body>
</html>