first commit

This commit is contained in:
hayato5246
2026-03-31 19:32:59 +09:00
commit d10b794c9f
78 changed files with 1671595 additions and 0 deletions

163
templates/layout/base.html Normal file
View File

@@ -0,0 +1,163 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ .Title }}</title>
<!-- Tailwind CSS CDN -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- TradingView Lightweight Charts v4 (v5는 API 호환성 문제로 고정) -->
<script src="https://unpkg.com/lightweight-charts@4.2.1/dist/lightweight-charts.standalone.production.js"></script>
<link rel="stylesheet" href="/static/css/custom.css">
<style>
/* 상승/하락 색상 (한국 주식 관행) */
.price-up { color: #ef4444; } /* 빨강 */
.price-down { color: #3b82f6; } /* 파랑 */
.price-flat { color: #6b7280; } /* 회색 */
/* 사이드바 토글 애니메이션 */
#sidebarWrapper {
display: flex;
overflow: hidden;
transition: width 0.25s ease, opacity 0.25s ease, margin 0.25s ease;
width: 240px;
opacity: 1;
}
#sidebarWrapper.sidebar-hidden {
width: 0;
opacity: 0;
margin-right: 0 !important;
}
</style>
</head>
<body class="bg-gray-50 min-h-screen">
<!-- 네비게이션 -->
<nav class="bg-white shadow-sm sticky top-0 z-50">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex items-center justify-between h-14">
<!-- 사이드바 토글 버튼 -->
<button id="sidebarToggle" title="사이드바 열기/닫기"
class="mr-2 p-1.5 rounded-lg text-gray-500 hover:bg-gray-100 transition-colors shrink-0">
<!-- 사이드바 열림: 왼쪽 화살표(닫기) -->
<svg id="sidebarIconOpen" xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7"/>
</svg>
<!-- 사이드바 닫힘: 오른쪽 화살표(열기) -->
<svg id="sidebarIconClose" xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 hidden" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7"/>
</svg>
</button>
<!-- 로고 -->
<a href="/" class="text-lg font-bold text-gray-800 hover:text-blue-600 shrink-0 mr-2">
📈 주식 시세
</a>
<!-- 네비 메뉴 -->
<nav class="flex items-center gap-0.5 shrink-0">
<a href="/" class="text-sm px-3 py-1.5 rounded-lg font-medium transition-colors
{{ if eq .ActiveMenu "시세" }}bg-blue-50 text-blue-600{{ else }}text-gray-500 hover:bg-gray-100 hover:text-gray-800{{ end }}">시세</a>
<a href="/theme" class="text-sm px-3 py-1.5 rounded-lg font-medium transition-colors
{{ if eq .ActiveMenu "테마" }}bg-blue-50 text-blue-600{{ else }}text-gray-500 hover:bg-gray-100 hover:text-gray-800{{ end }}">테마</a>
<a href="/kospi200" class="text-sm px-3 py-1.5 rounded-lg font-medium transition-colors
{{ if eq .ActiveMenu "코스피200" }}bg-blue-50 text-blue-600{{ else }}text-gray-500 hover:bg-gray-100 hover:text-gray-800{{ end }}">코스피200</a>
{{ if .LoggedIn }}
<a href="/asset" class="text-sm px-3 py-1.5 rounded-lg font-medium transition-colors
{{ if eq .ActiveMenu "자산" }}bg-blue-50 text-blue-600{{ else }}text-gray-500 hover:bg-gray-100 hover:text-gray-800{{ end }}">자산</a>
<a href="/autotrade" class="text-sm px-3 py-1.5 rounded-lg font-medium transition-colors
{{ if eq .ActiveMenu "자동매매" }}bg-blue-50 text-blue-600{{ else }}text-gray-500 hover:bg-gray-100 hover:text-gray-800{{ end }}">자동매매</a>
{{ end }}
</nav>
<!-- 검색창 -->
<div class="flex-1 max-w-md mx-4 relative">
<input
id="searchInput"
type="text"
placeholder="종목명 또는 코드 검색"
autocomplete="off"
class="w-full px-4 py-2 text-sm border border-gray-300 rounded-full focus:outline-none focus:ring-2 focus:ring-blue-400"
>
<!-- 자동완성 드롭다운 -->
<div id="searchDropdown" class="hidden absolute top-full left-0 right-0 mt-1 bg-white border border-gray-200 rounded-lg shadow-lg z-50 max-h-60 overflow-y-auto"></div>
</div>
<!-- 로그인/로그아웃 버튼 -->
{{ if .LoggedIn }}
<form method="POST" action="/logout" class="shrink-0">
<button type="submit"
class="text-xs px-3 py-1.5 rounded-lg text-gray-500 hover:bg-gray-100 hover:text-gray-800 font-medium transition-colors">
로그아웃
</button>
</form>
{{ else }}
<a href="/login" class="shrink-0 text-xs px-3 py-1.5 rounded-lg text-blue-600 hover:bg-blue-50 font-medium transition-colors">
로그인
</a>
{{ end }}
</div>
</div>
</nav>
<!-- 지수 티커 바 -->
<div class="bg-gray-800 text-white text-xs">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-8 flex items-center gap-6 overflow-x-auto" id="indexTicker">
<span class="text-gray-400 shrink-0">로딩 중...</span>
</div>
</div>
<!-- 메인 콘텐츠 -->
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6 flex gap-6 items-start">
<div id="sidebarWrapper">
{{ block "sidebar" . }}{{ end }}
</div>
<main class="flex-1 min-w-0">
{{ block "content" . }}{{ end }}
</main>
</div>
<!-- 공통 JavaScript -->
<script src="/static/js/websocket.js"></script>
<script src="/static/js/search.js"></script>
<script src="/static/js/indices.js"></script>
{{ block "scripts" . }}{{ end }}
<script>
(function () {
const wrapper = document.getElementById('sidebarWrapper');
const toggle = document.getElementById('sidebarToggle');
const iconOpen = document.getElementById('sidebarIconOpen');
const iconClose = document.getElementById('sidebarIconClose');
if (!wrapper || !toggle) return;
// 사이드바가 없는 페이지(sidebar 블록이 비어 있음)는 버튼 숨김
if (!wrapper.children.length) {
toggle.classList.add('hidden');
return;
}
const STORAGE_KEY = 'sidebar_visible';
function applyState(visible) {
if (visible) {
wrapper.classList.remove('sidebar-hidden');
iconOpen.classList.remove('hidden');
iconClose.classList.add('hidden');
} else {
wrapper.classList.add('sidebar-hidden');
iconOpen.classList.add('hidden');
iconClose.classList.remove('hidden');
}
localStorage.setItem(STORAGE_KEY, visible ? '1' : '0');
}
// 저장된 상태 복원 (기본값: 표시)
const saved = localStorage.getItem(STORAGE_KEY);
applyState(saved !== '0');
toggle.addEventListener('click', () => {
const isHidden = wrapper.classList.contains('sidebar-hidden');
applyState(isHidden); // 현재 숨겨져 있으면 보이게
});
})();
</script>
</body>
</html>