52386efb83
Build & Deploy to K8s / build-and-deploy (push) Has been cancelled
운영 도메인이 실제로는 v1.invyone.com / solution.invyone.com / api.invyone.com 인데 코드/문서 곳곳에 v1.invion.com / api.invion.com 등 미존재 도메인이 박혀 있어 정리. 변경 파일 (21): - frontend lib/api/client.ts, lib/utils/apiUrl.ts: hostname 체크 endsWith(\".invyone.com\") 일반화 - frontend lib/api/dashboard.ts, file.ts, flow.ts, FileViewerModal*2.tsx: 도메인 치환 - frontend invion-layout-v5.html: 시안 내 placeholder 도메인 정리 - backend-spring SecurityConfig.java: CORS 주석 예시 정리 - docker/deploy/docker-compose.yml, k8s/traefik-dynamic.yaml: traefik Host 라벨 정리 - scripts/prod/deploy.sh: 안내 메시지 정리 - .cursor/rules/api-client-usage.mdc, project-conventions.mdc: AI 가이드 정리 - docs/* 4개: 아키텍처/플로우 문서 도메인 정리 - notes/gbpark/* 3개: 과거 메모 정리 신규: - docs/DOMAIN_MAPPING.md: 운영/개발/폐기 도메인 영구 기록. AI 에이전트와 신규 개발자가 헷갈리지 않도록 단일 진실 출처. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
974 lines
60 KiB
HTML
974 lines
60 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="ko">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>INVION - Layout v5</title>
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet">
|
|
<style>
|
|
:root {
|
|
--bg:#fafaff; --bg-subtle:#f3f2fa; --surface:rgba(255,255,255,0.55); --surface-solid:#ffffff;
|
|
--surface-hover:rgba(255,255,255,0.7); --text:#0f0e1a; --text-sec:#6b6a80; --text-muted:#9998ad;
|
|
--primary:#6c5ce7; --primary-light:#a29bfe; --primary-glow:rgba(108,92,231,0.25);
|
|
--cyan:#00cec9; --cyan-glow:rgba(0,206,201,0.2); --pink:#fd79a8; --pink-glow:rgba(253,121,168,0.15);
|
|
--red:#ff4757; --green:#00b894; --amber:#fdcb6e;
|
|
--border:rgba(108,92,231,0.12); --border-subtle:rgba(0,0,0,0.05);
|
|
--glass:rgba(255,255,255,0.45); --glass-strong:rgba(255,255,255,0.65);
|
|
--glass-border:rgba(108,92,231,0.12);
|
|
--glow-sm:0 0 20px rgba(108,92,231,0.12); --glow-md:0 0 40px rgba(108,92,231,0.2);
|
|
--glow-lg:0 0 80px rgba(108,92,231,0.25);
|
|
--sidebar-w:220px;
|
|
}
|
|
.dark {
|
|
--bg:#06050e; --bg-subtle:#0c0b18; --surface:rgba(17,16,42,0.5); --surface-solid:#11102a;
|
|
--surface-hover:rgba(25,24,64,0.6); --text:#eae8f4; --text-sec:#8d8ba8; --text-muted:#5a587a;
|
|
--primary:#a29bfe; --primary-light:#c8c4ff; --primary-glow:rgba(162,155,254,0.25);
|
|
--cyan:#55efc4; --cyan-glow:rgba(85,239,196,0.15); --pink:#fd79a8; --red:#ff6b6b;
|
|
--green:#55efc4; --amber:#ffeaa7;
|
|
--border:rgba(162,155,254,0.1); --border-subtle:rgba(255,255,255,0.04);
|
|
--glass:rgba(17,16,42,0.45); --glass-strong:rgba(17,16,42,0.65);
|
|
--glass-border:rgba(162,155,254,0.12);
|
|
--glow-sm:0 0 20px rgba(162,155,254,0.1); --glow-md:0 0 40px rgba(162,155,254,0.18);
|
|
--glow-lg:0 0 80px rgba(162,155,254,0.22);
|
|
}
|
|
|
|
*{margin:0;padding:0;box-sizing:border-box;}
|
|
html,body{height:100%;overflow:hidden;}
|
|
body{font-family:'Inter',system-ui,sans-serif;background:var(--bg);color:var(--text);transition:background .5s,color .4s;}
|
|
::-webkit-scrollbar{width:5px;} ::-webkit-scrollbar-track{background:transparent;}
|
|
::-webkit-scrollbar-thumb{background:rgba(108,92,231,0.2);border-radius:3px;}
|
|
|
|
/* ===== COSMIC BACKGROUND ===== */
|
|
.cosmos{position:fixed;inset:0;pointer-events:none;z-index:0;overflow:hidden;}
|
|
.star{position:absolute;width:2px;height:2px;background:white;border-radius:50%;
|
|
animation:twinkle var(--d,3s) ease-in-out infinite alternate;animation-delay:var(--dl,0s);opacity:0;}
|
|
.star.c{width:3px;height:3px;background:var(--sc);}
|
|
@keyframes twinkle{0%{opacity:0;transform:scale(.5)}100%{opacity:var(--mo,.7);transform:scale(1)}}
|
|
html:not(.dark) .star{display:none;}
|
|
html:not(.dark) .shooting-star{display:none;}
|
|
html:not(.dark) .particle{display:none;}
|
|
|
|
.neb{position:absolute;border-radius:50%;filter:blur(140px);animation:drift 16s ease-in-out infinite alternate;}
|
|
.neb-1{width:700px;height:700px;top:-20%;right:-15%;background:radial-gradient(circle,var(--primary-glow),transparent 70%);animation-duration:18s;}
|
|
.neb-2{width:600px;height:600px;bottom:-25%;left:-10%;background:radial-gradient(circle,var(--cyan-glow),transparent 70%);animation-duration:14s;animation-delay:-4s;}
|
|
.neb-3{width:450px;height:450px;top:35%;left:40%;background:radial-gradient(circle,var(--pink-glow),transparent 70%);animation-duration:12s;animation-delay:-8s;}
|
|
.neb-4{width:350px;height:350px;top:60%;right:25%;background:radial-gradient(circle,rgba(108,92,231,0.08),transparent 70%);animation-duration:20s;animation-delay:-2s;}
|
|
@keyframes drift{0%{transform:translate(0,0) scale(1)}100%{transform:translate(30px,-25px) scale(1.1)}}
|
|
|
|
html:not(.dark) .cosmos{background:linear-gradient(180deg,#e8e4ff 0%,#f0edff 30%,#fafaff 60%,#f5f0ff 100%);}
|
|
html:not(.dark) .neb{filter:blur(100px);}
|
|
html:not(.dark) .neb-1{width:1200px;height:500px;top:auto;bottom:-10%;right:-15%;border-radius:50%;
|
|
background:radial-gradient(ellipse,rgba(255,255,255,0.9),rgba(230,225,255,0.5),transparent 70%);animation-duration:25s;}
|
|
html:not(.dark) .neb-2{width:1000px;height:400px;top:auto;bottom:-5%;left:-10%;
|
|
background:radial-gradient(ellipse,rgba(255,255,255,0.85),rgba(200,240,255,0.4),transparent 70%);animation-duration:20s;}
|
|
html:not(.dark) .neb-3{width:800px;height:350px;top:auto;bottom:5%;left:30%;
|
|
background:radial-gradient(ellipse,rgba(255,255,255,0.8),rgba(240,220,255,0.3),transparent 70%);animation-duration:22s;}
|
|
html:not(.dark) .neb-4{width:600px;height:600px;top:-10%;right:20%;bottom:auto;
|
|
background:radial-gradient(circle,rgba(108,92,231,0.08),rgba(0,206,201,0.04),transparent 70%);}
|
|
|
|
.shooting-star{position:absolute;width:80px;height:1px;
|
|
background:linear-gradient(90deg,rgba(255,255,255,0.6),transparent);
|
|
transform:rotate(35deg);animation:shoot 5s ease-in-out infinite;opacity:0;}
|
|
.shooting-star:nth-child(6){animation-delay:3s;top:25%;left:65%;width:55px;transform:rotate(40deg);}
|
|
@keyframes shoot{0%{opacity:0;transform:rotate(35deg) translateX(0)}3%{opacity:0.7}12%{opacity:0;transform:rotate(35deg) translateX(-350px)}100%{opacity:0}}
|
|
|
|
.particle{position:absolute;width:var(--sz,4px);height:var(--sz,4px);background:var(--pc,var(--primary));
|
|
border-radius:50%;opacity:0;animation:floatup var(--fd,9s) ease-in-out infinite;animation-delay:var(--fdl,0s);}
|
|
@keyframes floatup{0%{opacity:0;transform:translateY(100vh) scale(0)}10%{opacity:.4}90%{opacity:.4}100%{opacity:0;transform:translateY(-80px) scale(1)}}
|
|
|
|
/* ===== LAYOUT SHELL ===== */
|
|
.shell{display:flex;flex-direction:column;height:100vh;position:relative;z-index:1;}
|
|
|
|
/* --- Header --- */
|
|
.hdr{height:50px;display:flex;align-items:center;justify-content:space-between;padding:0 1.25rem;
|
|
background:var(--glass);backdrop-filter:blur(20px) saturate(1.4);-webkit-backdrop-filter:blur(20px) saturate(1.4);
|
|
border-bottom:1px solid var(--glass-border);position:relative;z-index:20;flex-shrink:0;}
|
|
.hdr-l{display:flex;align-items:center;gap:1rem;}
|
|
.hdr-logo{font-size:1.05rem;font-weight:900;letter-spacing:-.03em;
|
|
background:linear-gradient(135deg,var(--primary),var(--cyan));-webkit-background-clip:text;
|
|
-webkit-text-fill-color:transparent;background-clip:text;cursor:default;}
|
|
.hdr-bc{font-size:.72rem;color:var(--text-muted);}
|
|
.hdr-bc b{color:var(--text);font-weight:600;}
|
|
.hdr-r{display:flex;align-items:center;gap:.65rem;}
|
|
|
|
/* Theme pill */
|
|
.pill{display:flex;background:var(--surface);backdrop-filter:blur(8px);border:1px solid var(--glass-border);
|
|
border-radius:999px;padding:2px;}
|
|
.pill button{padding:.22rem .65rem;border-radius:999px;border:none;background:transparent;
|
|
color:var(--text-muted);cursor:pointer;font-size:.6rem;font-weight:600;font-family:inherit;
|
|
transition:all .3s cubic-bezier(.4,0,.2,1);}
|
|
.pill button.on{background:var(--primary);color:white;box-shadow:var(--glow-sm);}
|
|
|
|
/* Bell */
|
|
.bell{position:relative;width:32px;height:32px;border-radius:10px;border:1px solid var(--glass-border);
|
|
background:var(--surface);backdrop-filter:blur(8px);color:var(--text-muted);cursor:pointer;
|
|
display:flex;align-items:center;justify-content:center;transition:all .2s;}
|
|
.bell:hover{border-color:var(--primary);color:var(--primary);box-shadow:var(--glow-sm);}
|
|
.bell-dot{position:absolute;top:5px;right:5px;width:7px;height:7px;background:var(--red);border-radius:50%;animation:pdot 2s infinite;}
|
|
@keyframes pdot{0%,100%{box-shadow:0 0 0 0 rgba(255,71,87,.4)}50%{box-shadow:0 0 0 5px rgba(255,71,87,0)}}
|
|
|
|
/* Admin button */
|
|
.admin-btn{position:relative;width:32px;height:32px;border-radius:10px;border:1px solid var(--glass-border);
|
|
background:var(--surface);backdrop-filter:blur(8px);color:var(--text-muted);cursor:pointer;
|
|
display:flex;align-items:center;justify-content:center;transition:all .25s;}
|
|
.admin-btn:hover{border-color:var(--primary);color:var(--primary);box-shadow:var(--glow-sm);transform:scale(1.1);}
|
|
.admin-btn .admin-label{position:absolute;top:110%;left:50%;transform:translateX(-50%);
|
|
font-size:.52rem;font-weight:600;color:var(--primary);white-space:nowrap;
|
|
opacity:0;transition:opacity .2s,color .2s;pointer-events:none;}
|
|
.admin-btn:hover .admin-label{opacity:1;}
|
|
.admin-mode .admin-btn .admin-label{color:var(--cyan);}
|
|
|
|
/* Avatar */
|
|
.avatar-w{position:relative;}
|
|
.avatar{width:32px;height:32px;border-radius:50%;background:linear-gradient(135deg,var(--primary),var(--cyan));
|
|
display:flex;align-items:center;justify-content:center;font-size:.7rem;font-weight:700;color:white;
|
|
cursor:pointer;transition:transform .2s,box-shadow .3s;}
|
|
.avatar:hover{transform:scale(1.1);box-shadow:var(--glow-sm);}
|
|
|
|
/* Avatar dropdown */
|
|
.avatar-dd{position:absolute;top:calc(100% + 10px);right:0;width:220px;
|
|
background:var(--glass-strong);backdrop-filter:blur(20px) saturate(1.4);-webkit-backdrop-filter:blur(20px) saturate(1.4);
|
|
border:1px solid var(--glass-border);border-radius:16px;padding:.5rem;
|
|
box-shadow:0 12px 40px rgba(0,0,0,0.12),var(--glow-sm);
|
|
opacity:0;transform:translateY(-8px) scale(.96);pointer-events:none;
|
|
transition:all .25s cubic-bezier(.16,1,.3,1);z-index:100;}
|
|
.dark .avatar-dd{box-shadow:0 12px 40px rgba(0,0,0,0.5),var(--glow-md);}
|
|
.avatar-dd.open{opacity:1;transform:none;pointer-events:auto;}
|
|
.avatar-dd .av-profile{display:flex;align-items:center;gap:.6rem;padding:.55rem .6rem;
|
|
border-bottom:1px solid var(--border-subtle);margin-bottom:.35rem;}
|
|
.avatar-dd .av-avatar{width:36px;height:36px;border-radius:50%;background:linear-gradient(135deg,var(--primary),var(--cyan));
|
|
display:flex;align-items:center;justify-content:center;font-size:.8rem;font-weight:700;color:white;flex-shrink:0;}
|
|
.avatar-dd .av-name{font-size:.78rem;font-weight:700;color:var(--text);}
|
|
.avatar-dd .av-email{font-size:.6rem;color:var(--text-muted);margin-top:.1rem;}
|
|
.avatar-dd .av-item{display:flex;align-items:center;gap:.5rem;padding:.45rem .6rem;
|
|
border-radius:10px;font-size:.72rem;font-weight:500;color:var(--text-sec);
|
|
cursor:pointer;transition:all .15s;}
|
|
.avatar-dd .av-item:hover{background:var(--surface-hover);color:var(--text);transform:translateX(2px);}
|
|
.avatar-dd .av-item .av-ic{width:16px;height:16px;display:flex;align-items:center;justify-content:center;opacity:.6;flex-shrink:0;}
|
|
.avatar-dd .av-item:hover .av-ic{opacity:1;}
|
|
.avatar-dd .av-divider{height:1px;background:var(--border-subtle);margin:.3rem .6rem;}
|
|
.avatar-dd .av-item.danger{color:var(--red);}
|
|
.avatar-dd .av-item.danger:hover{background:rgba(255,71,87,.08);}
|
|
|
|
/* Notification panel */
|
|
.noti-panel{position:absolute;top:calc(100% + 10px);right:0;width:300px;max-height:400px;
|
|
background:var(--glass-strong);backdrop-filter:blur(20px) saturate(1.4);-webkit-backdrop-filter:blur(20px) saturate(1.4);
|
|
border:1px solid var(--glass-border);border-radius:16px;
|
|
box-shadow:0 12px 40px rgba(0,0,0,0.12),var(--glow-sm);
|
|
opacity:0;transform:translateY(-8px) scale(.96);pointer-events:none;
|
|
transition:all .25s cubic-bezier(.16,1,.3,1);z-index:100;overflow:hidden;}
|
|
.dark .noti-panel{box-shadow:0 12px 40px rgba(0,0,0,0.5),var(--glow-md);}
|
|
.noti-panel.open{opacity:1;transform:none;pointer-events:auto;}
|
|
.noti-head{display:flex;align-items:center;justify-content:space-between;padding:.7rem .85rem;
|
|
border-bottom:1px solid var(--border-subtle);}
|
|
.noti-head .noti-title{font-size:.78rem;font-weight:700;color:var(--text);}
|
|
.noti-head .noti-clear{font-size:.58rem;font-weight:600;color:var(--primary);cursor:pointer;
|
|
border:none;background:none;font-family:inherit;transition:color .2s;}
|
|
.noti-head .noti-clear:hover{color:var(--cyan);}
|
|
.noti-list{overflow-y:auto;max-height:330px;padding:.35rem;}
|
|
.noti-item{display:flex;gap:.55rem;padding:.55rem .5rem;border-radius:10px;cursor:pointer;transition:all .15s;}
|
|
.noti-item:hover{background:var(--surface-hover);}
|
|
.noti-item.unread{background:linear-gradient(135deg,rgba(108,92,231,.05),rgba(108,92,231,.02));}
|
|
.dark .noti-item.unread{background:linear-gradient(135deg,rgba(162,155,254,.06),rgba(162,155,254,.02));}
|
|
.noti-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0;margin-top:.3rem;}
|
|
.noti-dot.info{background:var(--primary);}
|
|
.noti-dot.warn{background:var(--amber);}
|
|
.noti-dot.err{background:var(--red);box-shadow:0 0 6px rgba(255,71,87,.4);}
|
|
.noti-dot.ok{background:var(--green);}
|
|
.noti-body{flex:1;min-width:0;}
|
|
.noti-msg{font-size:.7rem;font-weight:500;color:var(--text);line-height:1.35;}
|
|
.noti-msg b{font-weight:600;}
|
|
.noti-time{font-size:.55rem;color:var(--text-muted);margin-top:.15rem;}
|
|
.noti-empty{text-align:center;padding:2rem;color:var(--text-muted);font-size:.75rem;}
|
|
|
|
/* ===== RESPONSIVE ===== */
|
|
/* Mobile menu toggle */
|
|
.mobile-toggle{display:none;width:36px;height:36px;border-radius:10px;border:1px solid var(--glass-border);
|
|
background:var(--surface);backdrop-filter:blur(8px);color:var(--text-muted);cursor:pointer;
|
|
align-items:center;justify-content:center;transition:all .2s;flex-shrink:0;}
|
|
.mobile-toggle:hover{border-color:var(--primary);color:var(--primary);}
|
|
|
|
/* Mobile overlay */
|
|
.side-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,0.4);z-index:25;
|
|
opacity:0;transition:opacity .3s;pointer-events:none;}
|
|
.side-overlay.open{opacity:1;pointer-events:auto;}
|
|
|
|
@media(max-width:768px){
|
|
.mobile-toggle{display:flex;}
|
|
.side{position:fixed;left:0;top:0;bottom:0;z-index:30;
|
|
transform:translateX(-100%);transition:transform .35s cubic-bezier(.4,0,.2,1);
|
|
width:260px;padding-top:60px;}
|
|
.side.mobile-open{transform:none;}
|
|
.side-overlay{display:block;}
|
|
.hdr-bc{display:none;}
|
|
.admin-badge{display:none !important;}
|
|
.hdr-logo{font-size:.9rem;}
|
|
.tabs{padding:0 .3rem;}
|
|
.tab{padding:0 .6rem;font-size:.65rem;}
|
|
.tab-toggle{display:none;}
|
|
.content{padding:.75rem;}
|
|
.admin-label{display:none !important;}
|
|
.pill{display:none;}
|
|
}
|
|
@media(min-width:769px) and (max-width:1024px){
|
|
:root{--sidebar-w:180px;}
|
|
.si{font-size:.72rem;padding:.45rem .6rem;}
|
|
.content{padding:1rem;}
|
|
}
|
|
|
|
/* --- Tabs --- */
|
|
.tabs{height:36px;display:flex;align-items:stretch;padding:0 .5rem;gap:1px;overflow-x:auto;
|
|
background:var(--glass);backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);
|
|
border-bottom:1px solid var(--glass-border);position:relative;z-index:15;flex-shrink:0;}
|
|
.tab{display:flex;align-items:center;gap:.4rem;padding:0 .85rem;font-size:.7rem;font-weight:500;
|
|
color:var(--text-muted);cursor:pointer;border-bottom:2px solid transparent;white-space:nowrap;transition:all .25s;}
|
|
.tab:hover{color:var(--text-sec);background:var(--surface-hover);}
|
|
.tab.on{color:var(--primary);font-weight:600;border-bottom-color:var(--primary);background:var(--surface);}
|
|
.tab-x{width:14px;height:14px;border-radius:3px;border:none;background:transparent;color:var(--text-muted);
|
|
font-size:.6rem;cursor:pointer;display:flex;align-items:center;justify-content:center;opacity:0;transition:all .15s;}
|
|
.tab:hover .tab-x{opacity:1;}.tab-x:hover{background:rgba(255,71,87,.15);color:var(--red);}
|
|
|
|
/* --- Body (sidebar + content) --- */
|
|
.body{display:flex;flex:1;overflow:hidden;position:relative;z-index:5;}
|
|
|
|
/* --- Sidebar --- */
|
|
.side{width:var(--sidebar-w);background:var(--glass);backdrop-filter:blur(20px) saturate(1.3);
|
|
-webkit-backdrop-filter:blur(20px) saturate(1.3);border-right:1px solid var(--glass-border);
|
|
padding:.85rem .6rem;overflow-y:auto;display:flex;flex-direction:column;gap:1px;flex-shrink:0;}
|
|
.side-sec{font-size:.55rem;font-weight:700;text-transform:uppercase;letter-spacing:.12em;
|
|
color:var(--text-muted);padding:1rem .65rem .35rem;}
|
|
.side-sec:first-child{padding-top:.25rem;}
|
|
.si{padding:.5rem .7rem;border-radius:10px;font-size:.77rem;color:var(--text-sec);cursor:pointer;
|
|
transition:all .25s cubic-bezier(.4,0,.2,1);font-weight:450;display:flex;align-items:center;gap:.6rem;
|
|
position:relative;overflow:hidden;height:auto;}
|
|
.si .ic{width:16px;height:16px;display:flex;align-items:center;justify-content:center;opacity:.65;flex-shrink:0;}
|
|
.si:hover{background:var(--surface-hover);color:var(--text);transform:translateX(2px);}
|
|
.si.on{background:linear-gradient(135deg,rgba(108,92,231,.12),rgba(108,92,231,.05));
|
|
color:var(--primary);font-weight:600;border:1px solid rgba(108,92,231,.15);box-shadow:var(--glow-sm);}
|
|
.si.on .ic{opacity:1;}
|
|
.dark .si.on{background:linear-gradient(135deg,rgba(162,155,254,.14),rgba(162,155,254,.05));border-color:rgba(162,155,254,.15);}
|
|
.si::before{content:'';position:absolute;left:0;top:0;width:3px;height:100%;background:var(--primary);
|
|
border-radius:0 2px 2px 0;transform:scaleY(0);transition:transform .2s cubic-bezier(.4,0,.2,1);}
|
|
.si.on::before{transform:scaleY(1);}
|
|
.side-anim .si{animation:slideR .4s cubic-bezier(.16,1,.3,1) both;}
|
|
@keyframes slideR{from{opacity:0;transform:translateX(-16px)}to{opacity:1;transform:none}}
|
|
|
|
/* ===== SIDEBAR COLLAPSE ===== */
|
|
/* Toggle button at bottom of sidebar */
|
|
.side-toggle{margin-top:auto;padding:.5rem .7rem;border-radius:10px;border:none;
|
|
background:var(--surface);backdrop-filter:blur(8px);color:var(--text-muted);cursor:pointer;
|
|
display:flex;align-items:center;gap:.6rem;font-size:.7rem;font-weight:500;font-family:inherit;
|
|
transition:all .25s;flex-shrink:0;}
|
|
.side-toggle:hover{background:var(--surface-hover);color:var(--primary);}
|
|
.side-toggle svg{transition:transform .3s cubic-bezier(.4,0,.2,1);}
|
|
|
|
/* Collapsed sidebar */
|
|
.side.collapsed{width:56px;padding:.85rem .4rem;overflow:visible;z-index:30;}
|
|
.side.collapsed .si{justify-content:center;padding:.55rem;border-radius:10px;gap:0;}
|
|
.side.collapsed .si span:not(.ic){width:0;overflow:hidden;opacity:0;transition:width .25s,opacity .2s;}
|
|
.side.collapsed .si .ic{opacity:.7;margin:0;transition:opacity .2s;}
|
|
.side.collapsed .si.on .ic{opacity:1;}
|
|
.side.collapsed .si:hover{transform:none;}
|
|
.side.collapsed .side-sec{height:0;overflow:hidden;padding:0;margin:0;opacity:0;transition:all .25s;}
|
|
|
|
/* Expand: text & section animate back */
|
|
.side:not(.collapsed) .si span:not(.ic){opacity:1;transition:opacity .3s .15s;}
|
|
.side:not(.collapsed) .side-sec{opacity:1;transition:opacity .3s .1s,height .3s,padding .3s;}
|
|
|
|
/* Collapsed: show category groups */
|
|
.side-group{display:contents;}
|
|
.side.collapsed .side-group{display:flex;flex-direction:column;gap:1px;position:relative;}
|
|
|
|
/* Category header in collapsed mode */
|
|
.side-cat{height:0;overflow:hidden;opacity:0;padding:0;margin:0;pointer-events:none;
|
|
display:flex;flex-direction:column;align-items:center;justify-content:center;
|
|
border-radius:10px;cursor:pointer;position:relative;color:var(--text-muted);
|
|
transition:height .25s,opacity .2s,padding .25s,margin .25s;}
|
|
.side.collapsed .side-cat{height:auto;overflow:visible;opacity:1;pointer-events:auto;
|
|
padding:.55rem;margin-top:.4rem;transition:height .3s .05s,opacity .3s .1s,padding .3s .05s,margin .3s .05s;}
|
|
.side.collapsed .side-cat:first-child{margin-top:0;}
|
|
.side.collapsed .side-cat:hover{background:var(--surface-hover);color:var(--primary);}
|
|
.side.collapsed .side-cat.open{background:linear-gradient(135deg,rgba(108,92,231,.1),rgba(108,92,231,.04));
|
|
color:var(--primary);}
|
|
.dark .side.collapsed .side-cat.open{background:linear-gradient(135deg,rgba(162,155,254,.1),rgba(162,155,254,.04));}
|
|
.side-cat .cat-label{font-size:.48rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;
|
|
margin-top:.15rem;text-align:center;line-height:1;}
|
|
/* Cat appear animation when collapsing */
|
|
.side.collapsed .side-cat{animation:catIn .3s cubic-bezier(.16,1,.3,1) both;}
|
|
.side.collapsed .side-group:nth-child(1) .side-cat{animation-delay:.05s;}
|
|
.side.collapsed .side-group:nth-child(2) .side-cat{animation-delay:.1s;}
|
|
.side.collapsed .side-group:nth-child(3) .side-cat{animation-delay:.15s;}
|
|
@keyframes catIn{from{opacity:0;transform:scale(.7)}to{opacity:1;transform:none}}
|
|
|
|
/* Hide menu items in collapsed mode (shown in flyout) */
|
|
.side.collapsed .side-group .si{height:0;padding:0;margin:0;overflow:hidden;opacity:0;
|
|
transition:height .25s,padding .25s,opacity .15s,margin .25s;}
|
|
.side.collapsed .side-group .side-sec{height:0;padding:0;margin:0;overflow:hidden;opacity:0;}
|
|
/* Expand: items animate back */
|
|
.side:not(.collapsed) .side-group .si{transition:height .3s .1s,padding .3s .1s,opacity .3s .15s,margin .3s .1s;}
|
|
|
|
/* Flyout panel */
|
|
.side-flyout{position:absolute;left:calc(100% + 8px);top:0;width:170px;
|
|
background:var(--glass-strong);backdrop-filter:blur(20px) saturate(1.4);-webkit-backdrop-filter:blur(20px) saturate(1.4);
|
|
border:1px solid var(--glass-border);border-radius:14px;padding:.4rem;
|
|
box-shadow:0 12px 40px rgba(0,0,0,0.15),var(--glow-sm);
|
|
opacity:0;transform:translateX(-8px) scale(.96);pointer-events:none;
|
|
transition:all .25s cubic-bezier(.16,1,.3,1);z-index:100;}
|
|
.dark .side-flyout{box-shadow:0 12px 40px rgba(0,0,0,0.5),var(--glow-md);}
|
|
.side-flyout.open{opacity:1;transform:none;pointer-events:auto;}
|
|
.side-flyout .fly-title{font-size:.58rem;font-weight:700;color:var(--text-muted);
|
|
text-transform:uppercase;letter-spacing:.08em;padding:.3rem .6rem .45rem;}
|
|
.side-flyout .fly-item{display:flex;align-items:center;gap:.5rem;padding:.45rem .6rem;
|
|
border-radius:10px;font-size:.72rem;font-weight:500;color:var(--text-sec);
|
|
cursor:pointer;transition:all .15s;}
|
|
.side-flyout .fly-item:hover{background:var(--surface-hover);color:var(--text);transform:translateX(2px);}
|
|
.side-flyout .fly-item.on{color:var(--primary);font-weight:600;
|
|
background:linear-gradient(135deg,rgba(108,92,231,.1),rgba(108,92,231,.04));}
|
|
.side-flyout .fly-item .ic{width:14px;height:14px;display:flex;align-items:center;justify-content:center;opacity:.6;flex-shrink:0;}
|
|
.side-flyout .fly-item.on .ic{opacity:1;}
|
|
|
|
/* Collapsed toggle — just show icon */
|
|
.side.collapsed .side-toggle span{width:0;overflow:hidden;opacity:0;transition:width .2s,opacity .15s;}
|
|
.side.collapsed .side-toggle{justify-content:center;padding:.55rem;}
|
|
.side.collapsed .side-toggle svg{transform:rotate(180deg);}
|
|
.side:not(.collapsed) .side-toggle span{opacity:1;transition:opacity .3s .2s;}
|
|
|
|
/* --- Content placeholder --- */
|
|
.content{flex:1;overflow-y:auto;padding:1.25rem;display:flex;flex-direction:column;gap:1rem;}
|
|
|
|
/* Placeholder card */
|
|
.placeholder{
|
|
flex:1;display:flex;align-items:center;justify-content:center;
|
|
border:2px dashed var(--border);border-radius:16px;
|
|
background:var(--glass);backdrop-filter:blur(12px);
|
|
color:var(--text-muted);font-size:.85rem;font-weight:500;
|
|
min-height:300px;
|
|
}
|
|
.placeholder .ph-inner{text-align:center;}
|
|
.placeholder .ph-icon{font-size:2.5rem;margin-bottom:.75rem;opacity:.3;}
|
|
.placeholder .ph-title{font-size:.9rem;font-weight:700;color:var(--text-sec);margin-bottom:.3rem;}
|
|
.placeholder .ph-desc{font-size:.7rem;color:var(--text-muted);}
|
|
|
|
/* ===== ADMIN MODE ===== */
|
|
/* Mode transition overlay */
|
|
.mode-fade{position:fixed;inset:0;z-index:9998;pointer-events:none;opacity:0;
|
|
background:radial-gradient(ellipse at center,var(--primary-glow),transparent 70%);
|
|
transition:opacity .5s cubic-bezier(.4,0,.2,1);}
|
|
.mode-fade.in{opacity:1;}
|
|
|
|
/* Shell transition */
|
|
.shell{transition:opacity .3s,transform .3s;}
|
|
|
|
/* Admin mode header accent */
|
|
.admin-mode .hdr{border-bottom-color:var(--primary);}
|
|
.admin-mode .hdr::after{content:'';position:absolute;bottom:-1px;left:0;right:0;height:2px;
|
|
background:linear-gradient(90deg,var(--primary),var(--cyan));animation:glowLine .6s ease-out both;}
|
|
@keyframes glowLine{from{transform:scaleX(0);opacity:0}to{transform:scaleX(1);opacity:1}}
|
|
|
|
/* Admin badge in header */
|
|
.admin-badge{display:none;align-items:center;gap:.4rem;padding:.2rem .6rem;border-radius:999px;
|
|
background:linear-gradient(135deg,rgba(108,92,231,.12),rgba(0,206,201,.08));
|
|
border:1px solid rgba(108,92,231,.2);font-size:.58rem;font-weight:700;color:var(--primary);
|
|
animation:badgeIn .4s cubic-bezier(.16,1,.3,1) both;}
|
|
.dark .admin-badge{background:linear-gradient(135deg,rgba(162,155,254,.12),rgba(85,239,196,.08));
|
|
border-color:rgba(162,155,254,.2);color:var(--primary-light);}
|
|
.admin-mode .admin-badge{display:flex;}
|
|
@keyframes badgeIn{from{opacity:0;transform:scale(.8) translateX(-10px)}to{opacity:1;transform:none}}
|
|
.admin-badge .badge-dot{width:6px;height:6px;border-radius:50%;background:var(--cyan);
|
|
box-shadow:0 0 8px var(--cyan-glow);animation:bdPulse 2s infinite;}
|
|
@keyframes bdPulse{0%,100%{box-shadow:0 0 4px var(--cyan-glow)}50%{box-shadow:0 0 12px var(--cyan-glow)}}
|
|
|
|
/* Admin button icon swap */
|
|
.admin-btn .ic-gear{display:block;}
|
|
.admin-btn .ic-home{display:none;}
|
|
.admin-mode .admin-btn .ic-gear{display:none;}
|
|
.admin-mode .admin-btn .ic-home{display:block;}
|
|
.admin-mode .admin-btn{border-color:var(--cyan);color:var(--cyan);background:rgba(0,206,201,.08);
|
|
box-shadow:0 0 15px var(--cyan-glow);}
|
|
.admin-mode .admin-btn:hover{color:var(--cyan);border-color:var(--cyan);}
|
|
|
|
/* Admin sidebar — different accent */
|
|
.admin-side .si.on{background:linear-gradient(135deg,rgba(0,206,201,.12),rgba(0,206,201,.05));
|
|
color:var(--cyan);border-color:rgba(0,206,201,.2);}
|
|
.admin-side .si.on .ic{opacity:1;}
|
|
.admin-side .si::before{background:var(--cyan);}
|
|
.dark .admin-side .si.on{background:linear-gradient(135deg,rgba(85,239,196,.12),rgba(85,239,196,.05));
|
|
border-color:rgba(85,239,196,.15);}
|
|
.admin-side .si:hover{color:var(--text);}
|
|
|
|
/* Sidebar swap animation — merge with collapse transition */
|
|
.side{transition:width .4s cubic-bezier(.4,0,.2,1),padding .4s,transform .35s cubic-bezier(.16,1,.3,1),opacity .25s;}
|
|
.side.slide-out{transform:translateX(-100%);opacity:0;}
|
|
.side.slide-in{animation:sideSlideIn .4s cubic-bezier(.16,1,.3,1) both;}
|
|
@keyframes sideSlideIn{from{transform:translateX(-30px);opacity:0}to{transform:none;opacity:1}}
|
|
|
|
/* Tabs swap animation */
|
|
.tabs{transition:opacity .2s;}
|
|
.tabs.fade-out{opacity:0;}
|
|
.tabs.fade-in{animation:tabsFadeIn .3s ease-out both;}
|
|
@keyframes tabsFadeIn{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}
|
|
|
|
/* ===== TAB COLLAPSE ===== */
|
|
/* Toggle button — sits at left edge of tab bar area */
|
|
.tab-toggle{width:28px;height:28px;border-radius:8px;border:1px solid var(--glass-border);
|
|
background:var(--surface);backdrop-filter:blur(8px);color:var(--text-muted);cursor:pointer;
|
|
display:flex;align-items:center;justify-content:center;transition:all .25s;flex-shrink:0;margin-right:.35rem;}
|
|
.tab-toggle:hover{border-color:var(--primary);color:var(--primary);box-shadow:var(--glow-sm);}
|
|
.tab-toggle svg{transition:transform .3s cubic-bezier(.4,0,.2,1);}
|
|
.tabs-collapsed .tab-toggle svg{transform:rotate(180deg);}
|
|
|
|
/* Collapsed state — hide tab items, shrink bar */
|
|
.tabs.tabs-collapsed{height:0;padding:0;border:none;overflow:hidden;transition:height .3s cubic-bezier(.4,0,.2,1),padding .3s,border-width .3s;}
|
|
.tabs:not(.tabs-collapsed){transition:height .3s cubic-bezier(.16,1,.3,1),padding .3s;}
|
|
|
|
/* Mini tab icon — appears in header when tabs collapsed */
|
|
.tab-mini{position:relative;display:none;width:32px;height:32px;border-radius:10px;border:1px solid var(--glass-border);
|
|
background:var(--surface);backdrop-filter:blur(8px);color:var(--text-muted);cursor:pointer;
|
|
align-items:center;justify-content:center;transition:all .25s;}
|
|
.tab-mini:hover{border-color:var(--primary);color:var(--primary);box-shadow:var(--glow-sm);}
|
|
.tab-mini.visible{display:flex;animation:miniIn .3s cubic-bezier(.16,1,.3,1) both;}
|
|
@keyframes miniIn{from{opacity:0;transform:scale(.7)}to{opacity:1;transform:none}}
|
|
.tab-mini .tab-count{position:absolute;top:3px;right:3px;width:14px;height:14px;border-radius:50%;
|
|
background:var(--primary);color:white;font-size:.5rem;font-weight:700;
|
|
display:flex;align-items:center;justify-content:center;line-height:1;}
|
|
|
|
/* Tab dropdown — opens from mini icon */
|
|
.tab-dropdown{position:absolute;top:calc(100% + 8px);right:0;width:220px;
|
|
background:var(--glass-strong);backdrop-filter:blur(20px) saturate(1.4);-webkit-backdrop-filter:blur(20px) saturate(1.4);
|
|
border:1px solid var(--glass-border);border-radius:14px;padding:.4rem;
|
|
box-shadow:0 12px 40px rgba(0,0,0,0.12),var(--glow-sm);
|
|
opacity:0;transform:translateY(-8px) scale(.96);pointer-events:none;
|
|
transition:all .25s cubic-bezier(.16,1,.3,1);z-index:100;}
|
|
.dark .tab-dropdown{box-shadow:0 12px 40px rgba(0,0,0,0.5),var(--glow-md);}
|
|
.tab-dropdown.open{opacity:1;transform:none;pointer-events:auto;}
|
|
|
|
.tab-dropdown .td-item{display:flex;align-items:center;justify-content:space-between;
|
|
padding:.45rem .65rem;border-radius:10px;font-size:.72rem;font-weight:500;
|
|
color:var(--text-sec);cursor:pointer;transition:all .15s;gap:.4rem;}
|
|
.tab-dropdown .td-item:hover{background:var(--surface-hover);color:var(--text);transform:translateX(2px);}
|
|
.tab-dropdown .td-item.on{color:var(--primary);font-weight:600;
|
|
background:linear-gradient(135deg,rgba(108,92,231,.1),rgba(108,92,231,.04));}
|
|
.tab-dropdown .td-item .td-close{width:16px;height:16px;border-radius:4px;border:none;
|
|
background:transparent;color:var(--text-muted);cursor:pointer;display:flex;
|
|
align-items:center;justify-content:center;font-size:.55rem;opacity:0;transition:all .15s;flex-shrink:0;}
|
|
.tab-dropdown .td-item:hover .td-close{opacity:1;}
|
|
.tab-dropdown .td-close:hover{background:rgba(255,71,87,.12);color:var(--red);}
|
|
|
|
.tab-dropdown .td-head{display:flex;align-items:center;justify-content:space-between;
|
|
padding:.3rem .65rem .5rem;border-bottom:1px solid var(--border-subtle);margin-bottom:.25rem;}
|
|
.tab-dropdown .td-title{font-size:.6rem;font-weight:700;color:var(--text-muted);text-transform:uppercase;letter-spacing:.08em;}
|
|
.tab-dropdown .td-expand{font-size:.55rem;font-weight:600;color:var(--primary);cursor:pointer;border:none;background:none;font-family:inherit;transition:color .2s;}
|
|
.tab-dropdown .td-expand:hover{color:var(--cyan);}
|
|
|
|
/* --- Theme fade overlay --- */
|
|
.theme-fade{position:fixed;inset:0;z-index:9999;pointer-events:none;opacity:0;
|
|
transition:opacity .4s cubic-bezier(.4,0,.2,1);}
|
|
.theme-fade.in{opacity:1;}
|
|
|
|
/* --- Preview tag --- */
|
|
.preview-tag{position:fixed;top:0;left:50%;transform:translateX(-50%);z-index:10000;
|
|
background:linear-gradient(135deg,var(--primary),var(--cyan));color:white;font-size:.55rem;
|
|
font-weight:700;padding:.2rem 1.2rem;border-radius:0 0 10px 10px;letter-spacing:.06em;
|
|
box-shadow:0 4px 15px var(--primary-glow);}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="preview-tag">INVION v5 — LAYOUT SHELL</div>
|
|
<div class="theme-fade" id="theme-fade"></div>
|
|
<div class="mode-fade" id="mode-fade"></div>
|
|
|
|
<!-- Cosmic background -->
|
|
<div class="cosmos" id="cosmos">
|
|
<div class="neb neb-1"></div><div class="neb neb-2"></div><div class="neb neb-3"></div><div class="neb neb-4"></div>
|
|
<div class="shooting-star" style="top:12%;left:70%"></div>
|
|
<div class="shooting-star" style="top:35%;left:55%"></div>
|
|
</div>
|
|
|
|
<!-- Layout Shell -->
|
|
<div class="shell">
|
|
<!-- Header -->
|
|
<header class="hdr">
|
|
<div class="hdr-l">
|
|
<button class="mobile-toggle" onclick="toggleMobileSide()">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
|
|
</button>
|
|
<div class="hdr-logo">INVION</div>
|
|
<div class="hdr-bc" id="breadcrumb">홈 › <b>대시보드</b></div>
|
|
<div class="admin-badge"><div class="badge-dot"></div>관리자 모드</div>
|
|
</div>
|
|
<div class="hdr-r">
|
|
<div class="pill">
|
|
<button class="on" onclick="setTheme('light')">Light</button>
|
|
<button onclick="setTheme('dark')">Dark</button>
|
|
</div>
|
|
<div class="tab-mini" id="tab-mini" onclick="toggleTabDropdown()">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="9" y1="3" x2="9" y2="9"/></svg>
|
|
<div class="tab-count" id="tab-count">4</div>
|
|
<div class="tab-dropdown" id="tab-dropdown"></div>
|
|
</div>
|
|
<div style="position:relative">
|
|
<button class="bell" onclick="toggleNoti()">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>
|
|
<div class="bell-dot"></div>
|
|
</button>
|
|
<div class="noti-panel" id="noti-panel">
|
|
<div class="noti-head">
|
|
<span class="noti-title">알림</span>
|
|
<button class="noti-clear" onclick="document.getElementById('noti-panel').classList.remove('open')">모두 읽음</button>
|
|
</div>
|
|
<div class="noti-list">
|
|
<div class="noti-item unread"><div class="noti-dot err"></div><div class="noti-body"><div class="noti-msg"><b>DTG-078</b> 통신 오류 발생</div><div class="noti-time">2분 전</div></div></div>
|
|
<div class="noti-item unread"><div class="noti-dot warn"></div><div class="noti-body"><div class="noti-msg"><b>3월 정산</b> 보고서 검토 요청</div><div class="noti-time">14분 전</div></div></div>
|
|
<div class="noti-item unread"><div class="noti-dot info"></div><div class="noti-body"><div class="noti-msg"><b>김대리</b>가 DTG-003 설치 완료</div><div class="noti-time">30분 전</div></div></div>
|
|
<div class="noti-item"><div class="noti-dot ok"></div><div class="noti-body"><div class="noti-msg">시스템 자동 백업 완료 <b>(2.3GB)</b></div><div class="noti-time">1시간 전</div></div></div>
|
|
<div class="noti-item"><div class="noti-dot info"></div><div class="noti-body"><div class="noti-msg"><b>최부장</b>이 결재 3건 승인</div><div class="noti-time">2시간 전</div></div></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<button class="admin-btn" id="admin-toggle" title="관리자" onclick="switchMode(document.querySelector('.shell').classList.contains('admin-mode')?'main':'admin')">
|
|
<svg class="ic-gear" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
|
|
<svg class="ic-home" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
|
|
<span class="admin-label">관리자</span>
|
|
</button>
|
|
<div class="avatar-w">
|
|
<div class="avatar" onclick="toggleAvatarDD()">P</div>
|
|
<div class="avatar-dd" id="avatar-dd">
|
|
<div class="av-profile">
|
|
<div class="av-avatar">P</div>
|
|
<div><div class="av-name">Park님</div><div class="av-email">park@invyone.com</div></div>
|
|
</div>
|
|
<div class="av-item"><span class="av-ic"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg></span>내 정보</div>
|
|
<div class="av-item"><span class="av-ic"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg></span>비밀번호 변경</div>
|
|
<div class="av-item"><span class="av-ic"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg></span>환경설정</div>
|
|
<div class="av-divider"></div>
|
|
<div class="av-item danger"><span class="av-ic"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg></span>로그아웃</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- Main Tabs -->
|
|
<div class="tabs" id="main-tabs">
|
|
<button class="tab-toggle" onclick="collapseTab()" title="탭 접기"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="18 15 12 9 6 15"/></svg></button>
|
|
<div class="tab on"><span>대시보드</span><button class="tab-x">×</button></div>
|
|
<div class="tab"><span>DTG 관리</span><button class="tab-x">×</button></div>
|
|
<div class="tab"><span>사용자 관리</span><button class="tab-x">×</button></div>
|
|
<div class="tab"><span>정산</span><button class="tab-x">×</button></div>
|
|
</div>
|
|
|
|
<!-- Admin Tabs (hidden by default) -->
|
|
<div class="tabs" id="admin-tabs" style="display:none">
|
|
<div class="tab on"><span>메뉴 관리</span><button class="tab-x">×</button></div>
|
|
<div class="tab"><span>사용자 관리</span><button class="tab-x">×</button></div>
|
|
<div class="tab"><span>테이블 관리</span><button class="tab-x">×</button></div>
|
|
</div>
|
|
|
|
<!-- Mobile overlay -->
|
|
<div class="side-overlay" id="side-overlay" onclick="closeMobileSide()"></div>
|
|
|
|
<!-- Body -->
|
|
<div class="body">
|
|
<!-- Sidebar -->
|
|
<nav class="side side-anim" id="side">
|
|
<!-- 관리 그룹 -->
|
|
<div class="side-group" data-cat="관리">
|
|
<div class="side-cat" onclick="toggleFlyout(this)">
|
|
<div><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="9"/><rect x="14" y="3" width="7" height="5"/><rect x="14" y="12" width="7" height="9"/><rect x="3" y="16" width="7" height="5"/></svg>
|
|
<div class="cat-label">관리</div></div>
|
|
<div class="side-flyout"></div>
|
|
</div>
|
|
<div class="side-sec">관리</div>
|
|
<div class="si on" data-name="대시보드"><span class="ic"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg></span><span>대시보드</span></div>
|
|
<div class="si" data-name="DTG 관리"><span class="ic"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg></span><span>DTG 관리</span></div>
|
|
<div class="si" data-name="정산"><span class="ic"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg></span><span>정산</span></div>
|
|
<div class="si" data-name="물류"><span class="ic"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="1" y="3" width="15" height="13"/><polygon points="16 8 20 8 23 11 23 16 16 16 16 8"/><circle cx="5.5" cy="18.5" r="2.5"/><circle cx="18.5" cy="18.5" r="2.5"/></svg></span><span>물류</span></div>
|
|
</div>
|
|
<!-- 설정 그룹 -->
|
|
<div class="side-group" data-cat="설정">
|
|
<div class="side-cat" onclick="toggleFlyout(this)">
|
|
<div><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
|
|
<div class="cat-label">설정</div></div>
|
|
<div class="side-flyout"></div>
|
|
</div>
|
|
<div class="side-sec">설정</div>
|
|
<div class="si" data-name="메뉴 관리"><span class="ic"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg></span><span>메뉴 관리</span></div>
|
|
<div class="si" data-name="사용자 관리"><span class="ic"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/></svg></span><span>사용자 관리</span></div>
|
|
<div class="si" data-name="화면 빌더"><span class="ic"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></svg></span><span>화면 빌더</span></div>
|
|
</div>
|
|
<!-- 분석 그룹 -->
|
|
<div class="side-group" data-cat="분석">
|
|
<div class="side-cat" onclick="toggleFlyout(this)">
|
|
<div><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/></svg>
|
|
<div class="cat-label">분석</div></div>
|
|
<div class="side-flyout"></div>
|
|
</div>
|
|
<div class="side-sec">분석</div>
|
|
<div class="si" data-name="리포트"><span class="ic"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/></svg></span><span>리포트</span></div>
|
|
<div class="si" data-name="AI 어시스턴트"><span class="ic"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/></svg></span><span>AI 어시스턴트</span></div>
|
|
</div>
|
|
<!-- Toggle button -->
|
|
<button class="side-toggle" onclick="toggleSidebar()">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="15 18 9 12 15 6"/></svg>
|
|
<span>접기</span>
|
|
</button>
|
|
</nav>
|
|
|
|
<!-- Admin Sidebar (hidden by default) -->
|
|
<nav class="side admin-side side-anim" id="admin-side" style="display:none">
|
|
<!-- 시스템 그룹 -->
|
|
<div class="side-group" data-cat="시스템">
|
|
<div class="side-cat" onclick="toggleFlyout(this)">
|
|
<div><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
|
|
<div class="cat-label">시스템</div></div>
|
|
<div class="side-flyout"></div>
|
|
</div>
|
|
<div class="side-sec">시스템</div>
|
|
<div class="si on" data-name="메뉴 관리"><span class="ic"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg></span><span>메뉴 관리</span></div>
|
|
<div class="si" data-name="사용자 관리"><span class="ic"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg></span><span>사용자 관리</span></div>
|
|
<div class="si" data-name="권한 관리"><span class="ic"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg></span><span>권한 관리</span></div>
|
|
</div>
|
|
<!-- 데이터 그룹 -->
|
|
<div class="side-group" data-cat="데이터">
|
|
<div class="side-cat" onclick="toggleFlyout(this)">
|
|
<div><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/></svg>
|
|
<div class="cat-label">데이터</div></div>
|
|
<div class="side-flyout"></div>
|
|
</div>
|
|
<div class="side-sec">데이터</div>
|
|
<div class="si" data-name="테이블 관리"><span class="ic"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/></svg></span><span>테이블 관리</span></div>
|
|
<div class="si" data-name="코드 관리"><span class="ic"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg></span><span>코드 관리</span></div>
|
|
<div class="si" data-name="카테고리 관리"><span class="ic"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg></span><span>카테고리 관리</span></div>
|
|
</div>
|
|
<!-- 화면 그룹 -->
|
|
<div class="side-group" data-cat="화면">
|
|
<div class="side-cat" onclick="toggleFlyout(this)">
|
|
<div><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
|
|
<div class="cat-label">화면</div></div>
|
|
<div class="side-flyout"></div>
|
|
</div>
|
|
<div class="side-sec">화면</div>
|
|
<div class="si" data-name="화면 빌더"><span class="ic"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></svg></span><span>화면 빌더</span></div>
|
|
<div class="si" data-name="화면 그룹"><span class="ic"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg></span><span>화면 그룹</span></div>
|
|
</div>
|
|
<!-- 운영 그룹 -->
|
|
<div class="side-group" data-cat="운영">
|
|
<div class="side-cat" onclick="toggleFlyout(this)">
|
|
<div><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
|
|
<div class="cat-label">운영</div></div>
|
|
<div class="side-flyout"></div>
|
|
</div>
|
|
<div class="side-sec">운영</div>
|
|
<div class="si" data-name="배치 관리"><span class="ic"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg></span><span>배치 관리</span></div>
|
|
<div class="si" data-name="감사 로그"><span class="ic"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></svg></span><span>감사 로그</span></div>
|
|
</div>
|
|
<!-- Toggle button -->
|
|
<button class="side-toggle" onclick="toggleAdminSidebar()">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="15 18 9 12 15 6"/></svg>
|
|
<span>접기</span>
|
|
</button>
|
|
</nav>
|
|
|
|
<!-- Content area — placeholder -->
|
|
<main class="content">
|
|
<div class="placeholder">
|
|
<div class="ph-inner">
|
|
<div class="ph-icon">⚙</div>
|
|
<div class="ph-title">콘텐츠 영역</div>
|
|
<div class="ph-desc">이 영역에 대시보드, 관리 페이지 등 실제 콘텐츠가 렌더링됩니다</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Stars + particles
|
|
(()=>{
|
|
const co=document.getElementById('cosmos'),cs=['rgba(162,155,254,.8)','rgba(85,239,196,.7)','rgba(253,121,168,.7)'];
|
|
for(let i=0;i<150;i++){const s=document.createElement('div');s.className='star'+(Math.random()>.83?' c':'');
|
|
if(s.classList.contains('c'))s.style.setProperty('--sc',cs[Math.random()*3|0]);
|
|
s.style.left=Math.random()*100+'%';s.style.top=Math.random()*100+'%';
|
|
s.style.setProperty('--d',(2+Math.random()*5)+'s');s.style.setProperty('--dl',Math.random()*5+'s');
|
|
s.style.setProperty('--mo',(0.3+Math.random()*.7)+'');co.appendChild(s);}
|
|
const pc=['var(--primary)','var(--cyan)','var(--pink)'];
|
|
for(let i=0;i<20;i++){const p=document.createElement('div');p.className='particle';
|
|
p.style.left=Math.random()*100+'%';p.style.setProperty('--sz',(2+Math.random()*4)+'px');
|
|
p.style.setProperty('--pc',pc[Math.random()*3|0]);p.style.setProperty('--fd',(7+Math.random()*12)+'s');
|
|
p.style.setProperty('--fdl',Math.random()*10+'s');co.appendChild(p);}
|
|
})();
|
|
|
|
// Theme toggle
|
|
function setTheme(t){
|
|
if(window._themeSwitching)return;
|
|
const cur=document.documentElement.classList.contains('dark')?'dark':'light';
|
|
if(cur===t)return;
|
|
window._themeSwitching=true;
|
|
const fade=document.getElementById('theme-fade');
|
|
fade.style.background=t==='dark'
|
|
?'radial-gradient(ellipse at center,#0c0b18,#06050e)'
|
|
:'radial-gradient(ellipse at center,#f3f2fa,#fafaff)';
|
|
fade.classList.add('in');
|
|
setTimeout(()=>{
|
|
document.documentElement.classList.toggle('dark',t==='dark');
|
|
document.querySelectorAll('.pill button').forEach(b=>b.classList.toggle('on',(t==='dark'&&b.textContent==='Dark')||(t==='light'&&b.textContent==='Light')));
|
|
setTimeout(()=>{fade.classList.remove('in');window._themeSwitching=false;},50);
|
|
},420);
|
|
}
|
|
|
|
// ===== Mode switch (main ↔ admin) =====
|
|
function switchMode(mode){
|
|
if(window._modeSwitching)return;
|
|
const shell=document.querySelector('.shell');
|
|
const isAdmin=shell.classList.contains('admin-mode');
|
|
if((mode==='admin'&&isAdmin)||(mode==='main'&&!isAdmin))return;
|
|
window._modeSwitching=true;
|
|
|
|
const mainSide=document.getElementById('side');
|
|
const adminSide=document.getElementById('admin-side');
|
|
const mainTabs=document.getElementById('main-tabs');
|
|
const adminTabs=document.getElementById('admin-tabs');
|
|
const bc=document.getElementById('breadcrumb');
|
|
const modeFade=document.getElementById('mode-fade');
|
|
|
|
// Phase 1: Fade overlay + slide out current sidebar
|
|
modeFade.classList.add('in');
|
|
const curSide=mode==='admin'?mainSide:adminSide;
|
|
const newSide=mode==='admin'?adminSide:mainSide;
|
|
const curTabs=mode==='admin'?mainTabs:adminTabs;
|
|
const newTabs=mode==='admin'?adminTabs:mainTabs;
|
|
|
|
curSide.classList.add('slide-out');
|
|
curTabs.classList.add('fade-out');
|
|
|
|
setTimeout(()=>{
|
|
// Phase 2: Swap visibility
|
|
curSide.style.display='none';
|
|
curSide.classList.remove('slide-out');
|
|
newSide.style.display='';
|
|
newSide.classList.add('slide-in');
|
|
|
|
curTabs.style.display='none';
|
|
curTabs.classList.remove('fade-out');
|
|
newTabs.style.display='';
|
|
newTabs.classList.add('fade-in');
|
|
|
|
// Toggle admin mode class
|
|
shell.classList.toggle('admin-mode',mode==='admin');
|
|
bc.innerHTML=mode==='admin'?'관리자 › <b>메뉴 관리</b>':'홈 › <b>대시보드</b>';
|
|
document.querySelector('.admin-label').textContent=mode==='admin'?'홈으로':'관리자';
|
|
|
|
// Re-apply sidebar animation delays
|
|
newSide.querySelectorAll('.si').forEach((n,i)=>{
|
|
n.style.animationDelay=(.03+i*.03)+'s';
|
|
n.classList.remove('slide-in');void n.offsetWidth;
|
|
});
|
|
|
|
// Phase 3: Fade out overlay
|
|
setTimeout(()=>{
|
|
modeFade.classList.remove('in');
|
|
newSide.classList.remove('slide-in');
|
|
newTabs.classList.remove('fade-in');
|
|
window._modeSwitching=false;
|
|
},300);
|
|
},300);
|
|
}
|
|
|
|
// Sidebar click (delegated for both main + admin)
|
|
document.addEventListener('click',function(e){
|
|
const si=e.target.closest('.si');
|
|
if(!si)return;
|
|
const side=si.closest('.side');
|
|
if(!side||side.classList.contains('collapsed'))return;
|
|
side.querySelectorAll('.si').forEach(i=>i.classList.remove('on'));
|
|
si.classList.add('on');
|
|
const name=si.dataset.name||si.textContent.trim();
|
|
const bc=document.getElementById('breadcrumb');
|
|
const isAdmin=document.querySelector('.shell').classList.contains('admin-mode');
|
|
bc.innerHTML=(isAdmin?'관리자':'홈')+' › <b>'+name+'</b>';
|
|
});
|
|
|
|
// Tab click + close (delegated)
|
|
document.addEventListener('click',function(e){
|
|
const tabX=e.target.closest('.tab-x');
|
|
if(tabX){
|
|
e.stopPropagation();const tab=tabX.closest('.tab');if(!tab)return;
|
|
const wasOn=tab.classList.contains('on');const parent=tab.parentElement;tab.remove();
|
|
if(wasOn){const first=parent.querySelector('.tab');if(first)first.classList.add('on');}
|
|
return;
|
|
}
|
|
const tab=e.target.closest('.tab');
|
|
if(!tab)return;
|
|
const parent=tab.parentElement;
|
|
parent.querySelectorAll('.tab').forEach(i=>i.classList.remove('on'));tab.classList.add('on');
|
|
});
|
|
|
|
// ===== Tab collapse =====
|
|
function collapseTab(){
|
|
const tabs=document.getElementById('main-tabs');
|
|
const mini=document.getElementById('tab-mini');
|
|
tabs.classList.add('tabs-collapsed');
|
|
mini.classList.add('visible');
|
|
updateTabCount();
|
|
}
|
|
function expandTab(){
|
|
const tabs=document.getElementById('main-tabs');
|
|
const mini=document.getElementById('tab-mini');
|
|
const dd=document.getElementById('tab-dropdown');
|
|
tabs.classList.remove('tabs-collapsed');
|
|
mini.classList.remove('visible');
|
|
dd.classList.remove('open');
|
|
}
|
|
function toggleTabDropdown(){
|
|
const dd=document.getElementById('tab-dropdown');
|
|
if(dd.classList.contains('open')){dd.classList.remove('open');return;}
|
|
// Build dropdown content from current tabs
|
|
const tabs=document.getElementById('main-tabs').querySelectorAll('.tab');
|
|
let html='<div class="td-head"><span class="td-title">열린 탭 ('+tabs.length+')</span><button class="td-expand" onclick="expandTab()">펼치기</button></div>';
|
|
tabs.forEach((t,i)=>{
|
|
const name=t.querySelector('span')?.textContent||'';
|
|
const isOn=t.classList.contains('on');
|
|
html+='<div class="td-item'+(isOn?' on':'')+'" data-idx="'+i+'" onclick="selectTabFromDD(this)"><span>'+name+'</span><button class="td-close" onclick="event.stopPropagation();closeTabFromDD('+i+')">×</button></div>';
|
|
});
|
|
dd.innerHTML=html;
|
|
dd.classList.add('open');
|
|
}
|
|
function selectTabFromDD(el){
|
|
const idx=parseInt(el.dataset.idx);
|
|
const tabs=document.getElementById('main-tabs').querySelectorAll('.tab');
|
|
tabs.forEach(t=>t.classList.remove('on'));
|
|
if(tabs[idx])tabs[idx].classList.add('on');
|
|
document.getElementById('tab-dropdown').classList.remove('open');
|
|
// Update breadcrumb
|
|
const name=tabs[idx]?.querySelector('span')?.textContent||'';
|
|
document.getElementById('breadcrumb').innerHTML='홈 › <b>'+name+'</b>';
|
|
}
|
|
function closeTabFromDD(idx){
|
|
const tabs=document.getElementById('main-tabs').querySelectorAll('.tab');
|
|
const tab=tabs[idx];if(!tab)return;
|
|
const wasOn=tab.classList.contains('on');tab.remove();
|
|
if(wasOn){const first=document.querySelector('#main-tabs .tab');if(first)first.classList.add('on');}
|
|
updateTabCount();
|
|
toggleTabDropdown();// Refresh dropdown
|
|
}
|
|
function updateTabCount(){
|
|
const count=document.getElementById('main-tabs').querySelectorAll('.tab').length;
|
|
document.getElementById('tab-count').textContent=count;
|
|
}
|
|
// Close dropdown on outside click
|
|
document.addEventListener('click',function(e){
|
|
if(!e.target.closest('.tab-mini')){
|
|
const dd=document.getElementById('tab-dropdown');
|
|
if(dd)dd.classList.remove('open');
|
|
}
|
|
});
|
|
|
|
// ===== Avatar dropdown =====
|
|
function toggleAvatarDD(){
|
|
const dd=document.getElementById('avatar-dd');
|
|
closeAllPanels('avatar-dd');
|
|
dd.classList.toggle('open');
|
|
}
|
|
|
|
// ===== Notification panel =====
|
|
function toggleNoti(){
|
|
const panel=document.getElementById('noti-panel');
|
|
closeAllPanels('noti-panel');
|
|
panel.classList.toggle('open');
|
|
}
|
|
|
|
// ===== Close all floating panels =====
|
|
function closeAllPanels(except){
|
|
['avatar-dd','noti-panel','tab-dropdown'].forEach(id=>{
|
|
if(id!==except){const el=document.getElementById(id);if(el)el.classList.remove('open');}
|
|
});
|
|
}
|
|
document.addEventListener('click',function(e){
|
|
if(!e.target.closest('.avatar-w')&&!e.target.closest('.bell')&&!e.target.closest('.noti-panel')
|
|
&&!e.target.closest('.tab-mini')){
|
|
closeAllPanels();
|
|
}
|
|
});
|
|
|
|
// ===== Mobile sidebar =====
|
|
function toggleMobileSide(){
|
|
const side=document.getElementById('side');
|
|
const overlay=document.getElementById('side-overlay');
|
|
side.classList.toggle('mobile-open');
|
|
overlay.classList.toggle('open');
|
|
}
|
|
function closeMobileSide(){
|
|
document.getElementById('side').classList.remove('mobile-open');
|
|
document.getElementById('side-overlay').classList.remove('open');
|
|
}
|
|
|
|
// ===== Sidebar collapse =====
|
|
function toggleSidebar(){
|
|
const side=document.getElementById('side');
|
|
closeFlyouts();
|
|
side.classList.toggle('collapsed');
|
|
}
|
|
function toggleAdminSidebar(){
|
|
const side=document.getElementById('admin-side');
|
|
closeFlyouts();
|
|
side.classList.toggle('collapsed');
|
|
}
|
|
|
|
function toggleFlyout(catEl){
|
|
const side=document.getElementById('side');
|
|
if(!side.classList.contains('collapsed'))return;
|
|
const flyout=catEl.querySelector('.side-flyout');
|
|
const wasOpen=flyout.classList.contains('open');
|
|
closeFlyouts();
|
|
if(wasOpen)return;
|
|
|
|
// Build flyout items from the group's .si items
|
|
const group=catEl.closest('.side-group');
|
|
const catName=group.dataset.cat;
|
|
const items=group.querySelectorAll('.si');
|
|
let html='<div class="fly-title">'+catName+'</div>';
|
|
items.forEach(si=>{
|
|
const name=si.dataset.name||si.textContent.trim();
|
|
const isOn=si.classList.contains('on');
|
|
const iconHtml=si.querySelector('.ic')?.innerHTML||'';
|
|
html+='<div class="fly-item'+(isOn?' on':'')+'" data-name="'+name+'" onclick="selectFromFlyout(this)"><span class="ic">'+iconHtml+'</span><span>'+name+'</span></div>';
|
|
});
|
|
flyout.innerHTML=html;
|
|
catEl.classList.add('open');
|
|
flyout.style.top='0';
|
|
requestAnimationFrame(()=>flyout.classList.add('open'));
|
|
}
|
|
|
|
function selectFromFlyout(flyItem){
|
|
const name=flyItem.dataset.name;
|
|
const side=flyItem.closest('.side');
|
|
const sideId=side?side.id:'side';
|
|
// Update active state
|
|
side.querySelectorAll('.si').forEach(si=>si.classList.remove('on'));
|
|
const target=side.querySelector('.si[data-name="'+name+'"]');
|
|
if(target)target.classList.add('on');
|
|
// Update breadcrumb
|
|
const isAdmin=document.querySelector('.shell').classList.contains('admin-mode');
|
|
document.getElementById('breadcrumb').innerHTML=(isAdmin?'관리자':'홈')+' › <b>'+name+'</b>';
|
|
closeFlyouts();
|
|
}
|
|
|
|
function closeFlyouts(){
|
|
document.querySelectorAll('.side-flyout').forEach(f=>f.classList.remove('open'));
|
|
document.querySelectorAll('.side-cat').forEach(c=>c.classList.remove('open'));
|
|
}
|
|
|
|
// Close flyouts on outside click
|
|
document.addEventListener('click',function(e){
|
|
if(!e.target.closest('.side-cat')&&!e.target.closest('.side-flyout')){
|
|
closeFlyouts();
|
|
}
|
|
});
|
|
|
|
// Sidebar animate delays
|
|
document.querySelectorAll('#side .si').forEach((n,i)=>{n.style.animationDelay=(.05+i*.04)+'s';});
|
|
document.querySelectorAll('#admin-side .si').forEach((n,i)=>{n.style.animationDelay=(.05+i*.04)+'s';});
|
|
document.querySelectorAll('#admin-side .side-cat').forEach((n,i)=>{n.style.animationDelay=(.05+i*.05)+'s';});
|
|
</script>
|
|
</body>
|
|
</html>
|