feat(ui): roll out light/dark theme to board, publish, rework, discover, etc.
- board: tokenized kanban columns/cards; page-header + 사용법 modal (drag guide) - publish: tokenized table/tabs; accent tab-active; page-header + 사용법 modal - rework: tokenized the many dark-assuming inline styles so the editor renders correctly in the light theme - discover/channels/production/channel_detail: tokenized dark-assuming colors (white text, translucent-white surfaces, #1e1e2d) for light-theme readability Verified board/publish/rework/discover render cleanly in light. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
616003479b
commit
52c6b51e61
@ -8,29 +8,51 @@
|
||||
|
||||
<body>
|
||||
<div layout:fragment="content">
|
||||
<header class="mb-4 flex items-center justify-between">
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold mb-1">작업 보드</h1>
|
||||
<p class="text-muted">카드를 드래그해 단계를 옮기세요. 수집 → 검토 → 작업대상 → 완료.</p>
|
||||
<h1>칸반 보드</h1>
|
||||
<p class="sub">카드를 드래그해 단계를 옮기세요. 수집 → 검토 → 작업대상 → 완료.</p>
|
||||
</div>
|
||||
<button class="btn btn-secondary px-4 py-2 flex items-center gap-1" onclick="loadBoard()">
|
||||
<i data-lucide="refresh-cw" style="width:16px;"></i> 새로고침
|
||||
</button>
|
||||
</header>
|
||||
<div class="actions">
|
||||
<button class="btn btn-secondary" onclick="openHelp()"><i data-lucide="help-circle" style="width:15px;"></i> 사용법</button>
|
||||
<button class="btn btn-secondary" onclick="loadBoard()"><i data-lucide="refresh-cw" style="width:15px;"></i> 새로고침</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 사용법 모달 -->
|
||||
<div id="helpModal" class="modal-overlay" onclick="if(event.target===this) closeHelp()">
|
||||
<div class="modal-card">
|
||||
<div class="modal-head"><h3>📖 칸반 보드 사용법</h3><button class="modal-close" onclick="closeHelp()">×</button></div>
|
||||
<div class="modal-body">
|
||||
<div class="help-item">
|
||||
<div class="hi-ic"><i data-lucide="columns-3"></i></div>
|
||||
<div><div class="hi-t">단계(컬럼)</div><div class="hi-d"><b>수집됨 → 검토중 → 작업대상 → 완료</b> 순으로 영상의 진행 상태를 나타냅니다. <b>제외</b>는 작업하지 않을 영상입니다.</div></div>
|
||||
</div>
|
||||
<div class="help-item">
|
||||
<div class="hi-ic"><i data-lucide="move"></i></div>
|
||||
<div><div class="hi-t">드래그로 단계 이동</div><div class="hi-d">카드를 <b>끌어다 다른 컬럼에 놓으면</b> 상태가 바로 바뀝니다(저장됨). 상단 숫자는 컬럼별 카드 수입니다.</div></div>
|
||||
</div>
|
||||
<div class="help-item">
|
||||
<div class="hi-ic"><i data-lucide="wand-2"></i></div>
|
||||
<div><div class="hi-t">카드</div><div class="hi-d">썸네일·제목·지표(조회·시간당·길이)와 배율을 보여줍니다. <b>🪄 아이콘</b>으로 재가공 에디터로 이동합니다.</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="board" style="display:grid; grid-template-columns: repeat(5, minmax(220px, 1fr)); gap:1rem; align-items:start; overflow-x:auto;">
|
||||
<!-- columns injected -->
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.kb-col { background: rgba(255,255,255,0.02); border:1px solid var(--glass-border); border-radius: var(--radius-md); display:flex; flex-direction:column; min-height:200px; }
|
||||
.kb-col-head { padding:12px; border-bottom:1px solid var(--glass-border); display:flex; align-items:center; justify-content:space-between; position:sticky; top:0; }
|
||||
.kb-col { background: var(--surface-2); border:1px solid var(--border); border-radius: var(--r); display:flex; flex-direction:column; min-height:200px; }
|
||||
.kb-col-head { padding:12px; border-bottom:1px solid var(--border); display:flex; align-items:center; justify-content:space-between; position:sticky; top:0; background:var(--surface-2); border-radius:var(--r) var(--r) 0 0; }
|
||||
.kb-body { padding:10px; display:flex; flex-direction:column; gap:8px; min-height:120px; flex:1; transition: background 0.15s; }
|
||||
.kb-body.drag-over { background: rgba(124,58,237,0.12); }
|
||||
.kb-card { background: var(--glass-bg,#1e1e2d); border:1px solid var(--glass-border); border-radius:8px; padding:8px; cursor:grab; transition: transform 0.1s, border-color 0.15s; }
|
||||
.kb-card:hover { border-color:#7C3AED88; }
|
||||
.kb-body.drag-over { background: var(--accent-soft); }
|
||||
.kb-card { background: var(--surface); border:1px solid var(--border); border-radius:9px; padding:9px; cursor:grab; box-shadow:var(--shadow); transition: transform 0.1s, border-color 0.15s; }
|
||||
.kb-card:hover { border-color: var(--accent); }
|
||||
.kb-card.dragging { opacity:0.5; }
|
||||
.kb-count { font-size:0.75rem; background:rgba(255,255,255,0.08); padding:2px 8px; border-radius:999px; }
|
||||
.kb-count { font-size:0.75rem; font-weight:600; background:var(--surface); border:1px solid var(--border); color:var(--text-2); padding:2px 9px; border-radius:999px; }
|
||||
</style>
|
||||
|
||||
<script th:inline="javascript">
|
||||
@ -46,6 +68,14 @@
|
||||
let dragId = null;
|
||||
|
||||
function esc(s){ return (s==null?'':String(s)).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); }
|
||||
function fmt(n){ return (n==null)?'-':Number(n).toLocaleString(); }
|
||||
function durBadge(v){
|
||||
if(v.isShorts) return 'Shorts';
|
||||
const s = v.durationSec;
|
||||
if(s==null || s<=0) return '';
|
||||
const m = Math.floor(s/60), sec = s%60;
|
||||
return m+':'+String(sec).padStart(2,'0');
|
||||
}
|
||||
async function api(url, opts){
|
||||
const res = await fetch(url, opts);
|
||||
const json = await res.json().catch(()=>({}));
|
||||
@ -56,6 +86,12 @@
|
||||
function cardHtml(v){
|
||||
const ratio = v.viewsPerSubRatio!=null ? Number(v.viewsPerSubRatio).toFixed(1)+'x' : '';
|
||||
const ratioColor = (v.viewsPerSubRatio>=10)?'#ef4444':((v.viewsPerSubRatio>=2)?'#f59e0b':'#10b981');
|
||||
const db = durBadge(v);
|
||||
const metrics = [
|
||||
v.viewCount!=null ? '👁 '+fmt(v.viewCount) : '',
|
||||
v.viewsPerHour!=null ? '⚡ '+fmt(Math.round(v.viewsPerHour))+'/h' : '',
|
||||
db ? '⏱ '+db : ''
|
||||
].filter(Boolean).join(' · ');
|
||||
return `<div class="kb-card" draggable="true" data-id="${v.id}">
|
||||
<div style="display:flex; gap:8px;">
|
||||
<img src="${esc(v.thumbnailUrl)}" style="width:64px; height:36px; object-fit:cover; border-radius:4px; flex-shrink:0;">
|
||||
@ -63,6 +99,7 @@
|
||||
<div class="text-sm font-bold" style="display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; line-height:1.3;">${esc(v.title)}</div>
|
||||
</div>
|
||||
</div>
|
||||
${metrics?`<div class="text-muted mt-2" style="font-size:0.68rem; line-height:1.4;">${metrics}</div>`:''}
|
||||
<div class="flex items-center justify-between mt-2">
|
||||
<span class="text-muted" style="font-size:0.7rem;">${esc(v.channelTitle||'')}</span>
|
||||
<div class="flex items-center gap-2">
|
||||
@ -145,6 +182,10 @@
|
||||
});
|
||||
}
|
||||
|
||||
function openHelp(){ document.getElementById('helpModal').classList.add('open'); }
|
||||
function closeHelp(){ document.getElementById('helpModal').classList.remove('open'); }
|
||||
document.addEventListener('keydown', e => { if(e.key === 'Escape') closeHelp(); });
|
||||
|
||||
loadBoard();
|
||||
/*]]>*/
|
||||
</script>
|
||||
|
||||
@ -70,7 +70,7 @@
|
||||
<!-- Video List Table -->
|
||||
<div class="card p-0" style="overflow-x: auto;">
|
||||
<table class="w-full" style="border-collapse: collapse; text-align: left;">
|
||||
<thead style="background: rgba(255,255,255,0.02); border-bottom: 1px solid var(--glass-border);">
|
||||
<thead style="background: var(--surface-2); border-bottom: 1px solid var(--glass-border);">
|
||||
<tr>
|
||||
<th class="p-4 text-sm font-bold text-muted">Video</th>
|
||||
<th class="p-4 text-sm font-bold text-muted text-center">Script</th>
|
||||
@ -315,10 +315,10 @@
|
||||
options: {
|
||||
responsive: true, maintainAspectRatio: false,
|
||||
interaction: { mode: 'index', intersect: false },
|
||||
plugins: { legend: { labels: { color: '#cbd5e1' } } },
|
||||
plugins: { legend: { labels: { color: 'var(--text-2)' } } },
|
||||
scales: {
|
||||
x: { ticks: { color: '#94a3b8' }, grid: { color: 'rgba(255,255,255,0.05)' } },
|
||||
y: { position: 'left', ticks: { color: '#a78bfa' }, grid: { color: 'rgba(255,255,255,0.05)' }, title: { display: true, text: '구독자', color: '#a78bfa' } },
|
||||
x: { ticks: { color: '#94a3b8' }, grid: { color: 'var(--surface-2)' } },
|
||||
y: { position: 'left', ticks: { color: '#a78bfa' }, grid: { color: 'var(--surface-2)' }, title: { display: true, text: '구독자', color: '#a78bfa' } },
|
||||
y1: { position: 'right', ticks: { color: '#34d399' }, grid: { drawOnChartArea: false }, title: { display: true, text: '조회수', color: '#34d399' } }
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,13 +40,13 @@
|
||||
onchange="updateSelectedCount(); event.stopPropagation();">
|
||||
<div class="custom-checkbox flex items-center justify-center" style="
|
||||
width: 24px; height: 24px;
|
||||
border: 2px solid rgba(255,255,255,0.2);
|
||||
border: 2px solid var(--border-strong);
|
||||
border-radius: 6px;
|
||||
background: rgba(255,255,255,0.05);
|
||||
background: var(--surface-2);
|
||||
transition: all 0.2s ease;
|
||||
pointer-events: none;
|
||||
">
|
||||
<i data-lucide="check" style="width: 16px; height: 16px; color: white; display: none;"></i>
|
||||
<i data-lucide="check" style="width: 16px; height: 16px; color: var(--text); display: none;"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -75,13 +75,13 @@
|
||||
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-bottom: 1.5rem;">
|
||||
<div class="p-4"
|
||||
style="background: rgba(255,255,255,0.03); border-radius: 8px; text-align: center;">
|
||||
style="background: var(--surface-2); border-radius: 8px; text-align: center;">
|
||||
<div class="text-muted mb-4" style="font-size: 0.75rem">Videos</div>
|
||||
<div class="font-bold" th:text="${#numbers.formatInteger(channel.videoCount ?: 0, 0, 'COMMA')}">450
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-4"
|
||||
style="background: rgba(255,255,255,0.03); border-radius: 8px; text-align: center;">
|
||||
style="background: var(--surface-2); border-radius: 8px; text-align: center;">
|
||||
<div class="text-muted mb-4" style="font-size: 0.75rem">Total Views</div>
|
||||
<div class="font-bold" th:text="${#numbers.formatInteger(channel.viewCount ?: 0, 0, 'COMMA')}">45K
|
||||
</div>
|
||||
@ -224,8 +224,8 @@
|
||||
} else {
|
||||
card.style.borderColor = 'var(--glass-border)';
|
||||
card.style.background = 'var(--glass-bg)';
|
||||
customCb.style.background = 'rgba(255,255,255,0.05)';
|
||||
customCb.style.borderColor = 'rgba(255,255,255,0.2)';
|
||||
customCb.style.background = 'var(--surface-2)';
|
||||
customCb.style.borderColor = 'var(--border-strong)';
|
||||
checkIcon.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
@ -67,7 +67,7 @@
|
||||
<!-- 결과 테이블 -->
|
||||
<div class="card p-0" style="overflow-x:auto;">
|
||||
<table class="w-full" style="border-collapse:collapse; text-align:left;">
|
||||
<thead style="background:rgba(255,255,255,0.02); border-bottom:1px solid var(--glass-border);">
|
||||
<thead style="background:var(--surface-2); border-bottom:1px solid var(--glass-border);">
|
||||
<tr>
|
||||
<th class="p-3 text-sm font-bold text-muted">썸네일</th>
|
||||
<th class="p-3 text-sm font-bold text-muted">제목</th>
|
||||
@ -89,7 +89,7 @@
|
||||
|
||||
<!-- 영상 모달 -->
|
||||
<div id="videoModal" style="display:none; position:fixed; z-index:1000; left:0; top:0; width:100%; height:100%; background:rgba(0,0,0,0.85); align-items:center; justify-content:center;" onclick="if(event.target===this) closeVideoModal()">
|
||||
<div style="position:relative; width:95%; max-width:900px; background:var(--glass-bg,#1e1e2d); border-radius:12px; padding:16px; border:1px solid var(--glass-border);">
|
||||
<div style="position:relative; width:95%; max-width:900px; background:var(--glass-bg,var(--surface)); border-radius:12px; padding:16px; border:1px solid var(--glass-border);">
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:12px;">
|
||||
<h3 class="text-lg font-bold truncate pr-4" id="modalTitle">Video</h3>
|
||||
<button onclick="closeVideoModal()" class="text-muted hover:text-white" style="font-size:28px; line-height:1; background:none; border:none; cursor:pointer;">×</button>
|
||||
@ -99,8 +99,8 @@
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.row-sel { padding:4px 6px; background:rgba(255,255,255,0.05); border:1px solid var(--glass-border); border-radius:6px; color:white; font-size:0.8rem; outline:none; }
|
||||
.row-sel option { background:#1e1e2d; color:white; }
|
||||
.row-sel { padding:4px 6px; background:var(--surface-2); border:1px solid var(--glass-border); border-radius:6px; color:var(--text); font-size:0.8rem; outline:none; }
|
||||
.row-sel option { background:var(--surface); color:var(--text); }
|
||||
</style>
|
||||
|
||||
<script th:inline="javascript">
|
||||
@ -172,11 +172,11 @@
|
||||
</td>
|
||||
<td class="p-3" style="max-width:280px;">
|
||||
<div class="font-bold text-sm" style="display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;">
|
||||
<a href="https://www.youtube.com/watch?v=${v.videoId}" target="_blank" class="hover:underline text-white">${esc(v.title)}</a>${shortsBadge}
|
||||
<a href="https://www.youtube.com/watch?v=${v.videoId}" target="_blank" class="hover:underline">${esc(v.title)}</a>${shortsBadge}
|
||||
</div>
|
||||
</td>
|
||||
<td class="p-3 text-sm text-muted" style="max-width:130px;">
|
||||
<a href="${v.ytChannelId?('https://www.youtube.com/channel/'+v.ytChannelId):'#'}" target="_blank" class="hover:underline truncate" style="color:#e2e8f0;">${esc(v.channelTitle||'-')}</a>
|
||||
<a href="${v.ytChannelId?('https://www.youtube.com/channel/'+v.ytChannelId):'#'}" target="_blank" class="hover:underline truncate" style="color:var(--text-2);">${esc(v.channelTitle||'-')}</a>
|
||||
</td>
|
||||
<td class="p-3 text-sm">${fmt(v.subscriberCount)}</td>
|
||||
<td class="p-3 text-sm">${fmt(v.viewCount)}</td>
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
<div class="card p-0" style="overflow-x: auto;">
|
||||
<h3 class="p-4 text-lg font-bold border-b border-[var(--glass-border)]">Crawl History</h3>
|
||||
<table class="w-full" style="border-collapse: collapse; text-align: left;">
|
||||
<thead style="background: rgba(255,255,255,0.02); border-bottom: 1px solid var(--glass-border);">
|
||||
<thead style="background: var(--surface-2); border-bottom: 1px solid var(--glass-border);">
|
||||
<tr>
|
||||
<th class="p-4 text-sm font-bold text-muted">ID</th>
|
||||
<th class="p-4 text-sm font-bold text-muted">Crawled Date</th>
|
||||
@ -32,7 +32,7 @@
|
||||
<tr th:each="history : ${historyList}"
|
||||
th:onclick="'location.href=\'/production/' + ${history.id} + '\''"
|
||||
style="border-bottom: 1px solid var(--glass-border); cursor: pointer; transition: background 0.2s;"
|
||||
onmouseover="this.style.background='rgba(255,255,255,0.03)'"
|
||||
onmouseover="this.style.background='var(--surface-2)'"
|
||||
onmouseout="this.style.background='transparent'">
|
||||
<td class="p-4 text-sm" th:text="${history.id}">1</td>
|
||||
<td class="p-4 text-sm text-muted"
|
||||
|
||||
@ -8,29 +8,55 @@
|
||||
|
||||
<body>
|
||||
<div layout:fragment="content">
|
||||
<header class="mb-4">
|
||||
<h1 class="text-2xl font-bold mb-1">발행 큐</h1>
|
||||
<p class="text-muted">재가공한 영상의 발행 패키지를 단계별로 관리합니다. (실제 업로드는 수동 — 여기서 준비·추적)</p>
|
||||
</header>
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h1>발행 큐</h1>
|
||||
<p class="sub">재가공한 영상의 발행 패키지를 단계별로 관리합니다. (실제 업로드는 수동 — 여기서 준비·추적)</p>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button class="btn btn-secondary" onclick="openHelp()"><i data-lucide="help-circle" style="width:15px;"></i> 사용법</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 mb-4" id="tabs">
|
||||
<button class="btn btn-secondary px-4 py-2 tab active" data-status="" onclick="setTab(this,'')">전체</button>
|
||||
<button class="btn btn-secondary px-4 py-2 tab" data-status="DRAFT" onclick="setTab(this,'DRAFT')">작성중</button>
|
||||
<button class="btn btn-secondary px-4 py-2 tab" data-status="READY" onclick="setTab(this,'READY')">발행대기</button>
|
||||
<button class="btn btn-secondary px-4 py-2 tab" data-status="PUBLISHED" onclick="setTab(this,'PUBLISHED')">발행완료</button>
|
||||
<span id="cnt" class="text-sm text-muted" style="margin-left:auto; align-self:center;"></span>
|
||||
<!-- 사용법 모달 -->
|
||||
<div id="helpModal" class="modal-overlay" onclick="if(event.target===this) closeHelp()">
|
||||
<div class="modal-card">
|
||||
<div class="modal-head"><h3>📖 발행 큐 사용법</h3><button class="modal-close" onclick="closeHelp()">×</button></div>
|
||||
<div class="modal-body">
|
||||
<div class="help-item">
|
||||
<div class="hi-ic"><i data-lucide="list-checks"></i></div>
|
||||
<div><div class="hi-t">상태 탭</div><div class="hi-d"><b>작성중 → 발행대기 → 발행완료</b>로 발행 준비 단계를 거릅니다. 탭으로 상태별로 필터합니다.</div></div>
|
||||
</div>
|
||||
<div class="help-item">
|
||||
<div class="hi-ic"><i data-lucide="pencil"></i></div>
|
||||
<div><div class="hi-t">발행안 만들기</div><div class="hi-d">발행 패키지는 <b>재가공 화면 하단</b>에서 제목·설명·해시태그·플랫폼·예약을 저장하면 생성됩니다. 표의 <b>✏️</b>로 다시 편집합니다.</div></div>
|
||||
</div>
|
||||
<div class="help-item">
|
||||
<div class="hi-ic"><i data-lucide="upload-cloud"></i></div>
|
||||
<div><div class="hi-t">업로드 & 기록</div><div class="hi-d">실제 업로드는 <b>플랫폼에서 수동</b>으로 합니다. 업로드 후 URL을 기록하면 ‘발행완료’로 추적됩니다. <b>📋</b>로 설명+해시태그를 복사하세요.</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 mb-4" id="tabs" style="flex-wrap:wrap; align-items:center;">
|
||||
<button class="btn btn-secondary tab active" data-status="" onclick="setTab(this,'')">전체</button>
|
||||
<button class="btn btn-secondary tab" data-status="DRAFT" onclick="setTab(this,'DRAFT')">작성중</button>
|
||||
<button class="btn btn-secondary tab" data-status="READY" onclick="setTab(this,'READY')">발행대기</button>
|
||||
<button class="btn btn-secondary tab" data-status="PUBLISHED" onclick="setTab(this,'PUBLISHED')">발행완료</button>
|
||||
<span id="cnt" class="badge badge-muted" style="margin-left:auto;"></span>
|
||||
</div>
|
||||
|
||||
<div class="card p-0" style="overflow-x:auto;">
|
||||
<table class="w-full" style="border-collapse:collapse; text-align:left;">
|
||||
<thead style="background:rgba(255,255,255,0.02); border-bottom:1px solid var(--glass-border);">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="p-3 text-sm font-bold text-muted">상태</th>
|
||||
<th class="p-3 text-sm font-bold text-muted">플랫폼</th>
|
||||
<th class="p-3 text-sm font-bold text-muted">제목</th>
|
||||
<th class="p-3 text-sm font-bold text-muted">예약</th>
|
||||
<th class="p-3 text-sm font-bold text-muted">발행 URL</th>
|
||||
<th class="p-3 text-sm font-bold text-muted">관리</th>
|
||||
<th>상태</th>
|
||||
<th>플랫폼</th>
|
||||
<th>제목</th>
|
||||
<th>예약</th>
|
||||
<th>발행 URL</th>
|
||||
<th>관리</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="body">
|
||||
@ -40,7 +66,8 @@
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.tab.active { border-color:#7C3AED; color:#fff; }
|
||||
.tab { padding:0.5rem 1rem; }
|
||||
.tab.active { background:var(--accent-soft); border-color:var(--accent); color:var(--accent); }
|
||||
</style>
|
||||
|
||||
<script th:inline="javascript">
|
||||
@ -74,7 +101,7 @@
|
||||
body.innerHTML = data.map(p=>{
|
||||
const st = ST[p.status] || {t:p.status,cls:'badge-muted'};
|
||||
const sched = p.scheduledAt ? String(p.scheduledAt).substring(0,16).replace('T',' ') : '-';
|
||||
const urlCell = p.publishedUrl ? `<a href="${esc(p.publishedUrl)}" target="_blank" class="hover:underline" style="color:#60a5fa;">열기</a>` : '-';
|
||||
const urlCell = p.publishedUrl ? `<a href="${esc(p.publishedUrl)}" target="_blank" class="hover:underline" style="color:var(--accent);">열기</a>` : '-';
|
||||
return `<tr style="border-bottom:1px solid var(--glass-border);">
|
||||
<td class="p-3"><span class="badge ${st.cls}">${st.t}</span></td>
|
||||
<td class="p-3 text-sm">${esc(p.platform||'')}</td>
|
||||
@ -96,6 +123,10 @@
|
||||
navigator.clipboard.writeText(text).then(()=>alert('설명+해시태그가 복사되었습니다.'));
|
||||
}
|
||||
|
||||
function openHelp(){ document.getElementById('helpModal').classList.add('open'); }
|
||||
function closeHelp(){ document.getElementById('helpModal').classList.remove('open'); }
|
||||
document.addEventListener('keydown', e => { if(e.key === 'Escape') closeHelp(); });
|
||||
|
||||
load();
|
||||
/*]]>*/
|
||||
</script>
|
||||
|
||||
@ -60,32 +60,32 @@
|
||||
<div id="transcribeStatus" class="text-sm mb-2" style="display:none;"></div>
|
||||
|
||||
<!-- 영상 싱크 세그먼트 리스트 (CapCut형) -->
|
||||
<div id="segmentList" style="display:none; max-height:340px; overflow:auto; border:1px solid var(--glass-border); border-radius:var(--radius-md); background:rgba(255,255,255,0.02);"></div>
|
||||
<div id="segmentList" style="display:none; max-height:340px; overflow:auto; border:1px solid var(--glass-border); border-radius:var(--radius-md); background:var(--surface-2);"></div>
|
||||
|
||||
<!-- 평문 폴백 (URL 자막 추출 시) -->
|
||||
<textarea id="transcript" readonly placeholder="‘영상 업로드·전사’로 영상에서 싱크된 자막을 추출하거나, ‘URL자막’으로 YouTube 자막을 가져오세요."
|
||||
style="width:100%; min-height:140px; resize:vertical; padding:12px; background:rgba(255,255,255,0.03); border:1px solid var(--glass-border); border-radius:var(--radius-md); color:#cbd5e1; outline:none; font-size:0.9rem; line-height:1.6;"></textarea>
|
||||
style="width:100%; min-height:140px; resize:vertical; padding:12px; background:var(--surface-2); border:1px solid var(--glass-border); border-radius:var(--radius-md); color:var(--text-2); outline:none; font-size:0.9rem; line-height:1.6;"></textarea>
|
||||
|
||||
<!-- 내보내기 (배속 + 말 없는 구간 제거 + SRT/영상) -->
|
||||
<div id="exportBar" style="display:none; margin-top:14px; padding-top:12px; border-top:1px solid var(--glass-border);">
|
||||
<div class="flex items-center gap-2 mb-3" style="flex-wrap:wrap;">
|
||||
<label class="text-sm text-muted">배속</label>
|
||||
<input id="srtSpeed" type="number" value="1.0" step="0.1" min="0.1" max="2.0"
|
||||
style="width:64px; padding:7px; background:rgba(255,255,255,0.05); border:1px solid var(--glass-border); border-radius:var(--radius-md); color:white; outline:none; font-size:0.9rem;">
|
||||
style="width:64px; padding:7px; background:var(--surface-2); border:1px solid var(--glass-border); border-radius:var(--radius-md); color:var(--text); outline:none; font-size:0.9rem;">
|
||||
<span class="text-xs text-muted">배속 적용 시 타임스탬프 자동 보정 (0.5~2.0)</span>
|
||||
</div>
|
||||
|
||||
<div style="background:rgba(255,255,255,0.03); border:1px solid var(--glass-border); border-radius:var(--radius-md); padding:10px 12px;">
|
||||
<div style="background:var(--surface-2); border:1px solid var(--glass-border); border-radius:var(--radius-md); padding:10px 12px;">
|
||||
<div class="flex items-center gap-2 mb-2" style="flex-wrap:wrap;">
|
||||
<label class="flex items-center gap-1 text-sm" style="cursor:pointer;">
|
||||
<input type="checkbox" id="trimOn" onchange="onTrimToggle()"> 말 없는 구간 제거
|
||||
</label>
|
||||
<span class="text-xs text-muted">앞뒤 여백</span>
|
||||
<input id="trimPad" type="number" value="0.15" step="0.05" min="0" onchange="if(trimApplied)previewTrim()"
|
||||
style="width:60px; padding:5px; background:rgba(255,255,255,0.05); border:1px solid var(--glass-border); border-radius:6px; color:white; font-size:0.85rem;">
|
||||
style="width:60px; padding:5px; background:var(--surface-2); border:1px solid var(--glass-border); border-radius:6px; color:var(--text); font-size:0.85rem;">
|
||||
<span class="text-xs text-muted">최소 간격</span>
|
||||
<input id="trimGap" type="number" value="0.3" step="0.1" min="0" onchange="if(trimApplied)previewTrim()"
|
||||
style="width:60px; padding:5px; background:rgba(255,255,255,0.05); border:1px solid var(--glass-border); border-radius:6px; color:white; font-size:0.85rem;">
|
||||
style="width:60px; padding:5px; background:var(--surface-2); border:1px solid var(--glass-border); border-radius:6px; color:var(--text); font-size:0.85rem;">
|
||||
</div>
|
||||
<div id="trimInfo" class="text-xs text-muted"></div>
|
||||
</div>
|
||||
@ -104,11 +104,11 @@
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.seg-row { display:flex; gap:10px; padding:7px 12px; cursor:pointer; border-bottom:1px solid rgba(255,255,255,0.05); font-size:0.9rem; line-height:1.5; }
|
||||
.seg-row:hover { background:rgba(255,255,255,0.05); }
|
||||
.seg-row { display:flex; gap:10px; padding:7px 12px; cursor:pointer; border-bottom:1px solid var(--surface-2); font-size:0.9rem; line-height:1.5; }
|
||||
.seg-row:hover { background:var(--surface-2); }
|
||||
.seg-row.active { background:rgba(96,165,250,0.18); }
|
||||
.seg-time { color:#60a5fa; font-variant-numeric:tabular-nums; flex-shrink:0; font-size:0.82rem; padding-top:1px; }
|
||||
.seg-text { color:#e2e8f0; }
|
||||
.seg-text { color:var(--text-2); }
|
||||
</style>
|
||||
|
||||
<!-- 재작성 에디터 -->
|
||||
@ -120,7 +120,7 @@
|
||||
</button>
|
||||
</div>
|
||||
<textarea id="reworkText" placeholder="원본을 참고해 각색/수정한 스크립트를 여기에 작성하세요. 저장하면 상태가 TARGET(작업대상)으로 바뀝니다."
|
||||
style="width:100%; min-height:280px; resize:vertical; padding:12px; background:rgba(255,255,255,0.05); border:1px solid var(--glass-border); border-radius:var(--radius-md); color:white; outline:none; font-size:0.95rem; line-height:1.7;"></textarea>
|
||||
style="width:100%; min-height:280px; resize:vertical; padding:12px; background:var(--surface-2); border:1px solid var(--glass-border); border-radius:var(--radius-md); color:var(--text); outline:none; font-size:0.95rem; line-height:1.7;"></textarea>
|
||||
<div class="text-sm text-muted mt-2" id="saveInfo"></div>
|
||||
</div>
|
||||
|
||||
@ -181,8 +181,8 @@
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.pub-in { width:100%; padding:9px; background:rgba(255,255,255,0.05); border:1px solid var(--glass-border); border-radius:var(--radius-md); color:white; outline:none; font-size:0.9rem; }
|
||||
.pub-in option { background:#1e1e2d; color:white; }
|
||||
.pub-in { width:100%; padding:9px; background:var(--surface-2); border:1px solid var(--glass-border); border-radius:var(--radius-md); color:var(--text); outline:none; font-size:0.9rem; }
|
||||
.pub-in option { background:var(--surface); color:var(--text); }
|
||||
</style>
|
||||
|
||||
<script th:inline="javascript">
|
||||
@ -303,7 +303,7 @@
|
||||
document.getElementById('trimOn').checked = true;
|
||||
const sp = curSpeed();
|
||||
const kept = (p.keptDuration||0) / sp;
|
||||
info.style.color = '#cbd5e1';
|
||||
info.style.color = 'var(--text-2)';
|
||||
info.innerHTML = '제거 ' + (p.removedCount||0) + '구간 · 결과 길이 ≈ <b>' + kept.toFixed(1) + 's</b>'
|
||||
+ (sp!==1 ? (' (배속 ' + sp + 'x 포함)') : '')
|
||||
+ ' · SRT/영상이 이 타임라인으로 출력됩니다';
|
||||
|
||||
Loading…
Reference in New Issue
Block a user