fix(comment-cards): UI/UX 리뷰 반영(접근성·대비·성능·상태표시)
- 토글(모자이크/모서리/단어가리기) ON 상태 accent로 시각화 + aria-pressed - 검색·좋아요 필터 200ms 디바운스(대량 카드 렉 완화), 아바타 loading=lazy - 수집 중 스피너, prefers-reduced-motion 대응 - 작은 회색 글자 대비 상향(text-3→text-2), 숫자 tabular-nums - 아이콘 버튼/셀렉트 터치 타깃 확대, 입력 16px(iOS 확대 방지) - 월별 댓글량 값 상시 노출(hover 의존 제거) - 카드 키보드 접근(role/tabindex/Enter·Space 복사), focus-visible 링 - 섹션 아이콘 이모지→Lucide 통일(카드 내 👍💬는 export용 유지) - 입력 aria-label, 결과 URL ?v= 딥링크(공유·새로고침 유지) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
af53219dcc
commit
f109d08539
@ -126,7 +126,7 @@
|
||||
const head = document.createElement('div');
|
||||
head.className = 'cc-head';
|
||||
head.innerHTML =
|
||||
'<img class="cc-avatar" crossorigin="anonymous" alt="" src="' + avatarSrc + '">' +
|
||||
'<img class="cc-avatar" crossorigin="anonymous" loading="lazy" decoding="async" alt="" src="' + avatarSrc + '">' +
|
||||
'<div class="cc-meta">' +
|
||||
'<div><span class="cc-author"></span><span class="cc-time"></span></div>' +
|
||||
'<div class="cc-text"></div>' +
|
||||
@ -139,7 +139,14 @@
|
||||
|
||||
// 단어 가리기 ON: 카드 클릭 복사 잠금(단어 클릭으로 가림). OFF: 카드 클릭=복사.
|
||||
card.title = '단어 클릭 = 가리기 · 토글을 끄면 카드 클릭 = 복사';
|
||||
card.setAttribute('role', 'button');
|
||||
card.setAttribute('tabindex', '0');
|
||||
card.setAttribute('aria-label', (c.authorName || '댓글') + ' 카드 · 복사');
|
||||
card.addEventListener('click', () => { if (wordMaskOn) return; window.copyCard(card); });
|
||||
card.addEventListener('keydown', (e) => { // 키보드 접근: Enter/Space로 복사
|
||||
if (wordMaskOn) return;
|
||||
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); window.copyCard(card); }
|
||||
});
|
||||
|
||||
// 저장(다운로드) 버튼 — cc-noexport라 캡처 이미지엔 안 찍힘. 캡컷엔 이 파일을 불러오기.
|
||||
const actions = document.createElement('div');
|
||||
@ -147,7 +154,8 @@
|
||||
const saveBtn = document.createElement('button');
|
||||
saveBtn.type = 'button';
|
||||
saveBtn.className = 'cc-iconbtn';
|
||||
saveBtn.textContent = '⬇ 저장';
|
||||
saveBtn.innerHTML = '<i data-lucide="download" style="width:13px;height:13px;"></i> 저장';
|
||||
saveBtn.setAttribute('aria-label', 'PNG 파일로 저장');
|
||||
saveBtn.title = 'PNG 파일로 저장 (캡컷에서 불러오기)';
|
||||
saveBtn.addEventListener('click', (e) => { e.stopPropagation(); window.downloadCard(card, c); });
|
||||
actions.appendChild(saveBtn);
|
||||
@ -276,10 +284,10 @@
|
||||
const words = computeWords(all, weighted);
|
||||
const vid = window.__videoId;
|
||||
|
||||
let html = '<div class="cc-an-head"><b>📊 분석</b> '
|
||||
let html = '<div class="cc-an-head"><b><i data-lucide="bar-chart-3"></i> 분석</b> '
|
||||
+ '<span class="cc-an-sub">' + all.length.toLocaleString() + '개 댓글 기준'
|
||||
+ (exTerms.length ? ' · 제외어 적용' : '') + '</span>'
|
||||
+ '<button type="button" class="cc-iconbtn" id="ccAnToggle">접기</button></div>'
|
||||
+ '<button type="button" class="cc-iconbtn" id="ccAnToggle" aria-expanded="true">접기</button></div>'
|
||||
+ '<div id="ccAnBody">';
|
||||
|
||||
html += '<div class="cc-tiles">'
|
||||
@ -290,7 +298,7 @@
|
||||
+ '</div>';
|
||||
|
||||
// 타임라인
|
||||
html += '<div class="cc-an-sec"><div class="cc-an-sechead"><h4>⏱ 많이 언급된 타임라인</h4>'
|
||||
html += '<div class="cc-an-sec"><div class="cc-an-sechead"><h4><i data-lucide="clock"></i> 많이 언급된 타임라인</h4>'
|
||||
+ '<select id="ccTlSort" class="cc-mini-select">'
|
||||
+ '<option value="count"' + (tlSort === 'count' ? ' selected' : '') + '>언급순</option>'
|
||||
+ '<option value="time"' + (tlSort === 'time' ? ' selected' : '') + '>시간순</option>'
|
||||
@ -309,7 +317,7 @@
|
||||
html += '</div>';
|
||||
|
||||
// 단어
|
||||
html += '<div class="cc-an-sec"><div class="cc-an-sechead"><h4>🔤 자주 나온 단어</h4>'
|
||||
html += '<div class="cc-an-sec"><div class="cc-an-sechead"><h4><i data-lucide="type"></i> 자주 나온 단어</h4>'
|
||||
+ '<label class="cc-wlabel"><input type="checkbox" id="ccWeighted"' + (weighted ? ' checked' : '') + '> 좋아요 가중</label></div>';
|
||||
if (words.length) {
|
||||
const mx = words[0][1];
|
||||
@ -321,7 +329,7 @@
|
||||
|
||||
// 최다 좋아요 댓글
|
||||
if (stats.max) {
|
||||
html += '<div class="cc-an-sec"><h4>🏆 최다 좋아요 댓글</h4>'
|
||||
html += '<div class="cc-an-sec"><h4><i data-lucide="award"></i> 최다 좋아요 댓글</h4>'
|
||||
+ '<div class="cc-top-comment"><b>' + esc(stats.max.authorName) + '</b> · 👍 '
|
||||
+ (stats.max.likeCount || 0).toLocaleString()
|
||||
+ '<div class="cc-top-text">' + esc(toPlainText(stats.max.text).slice(0, 200)) + '</div></div></div>';
|
||||
@ -330,9 +338,10 @@
|
||||
// 월별 댓글량
|
||||
if (stats.months.length > 1) {
|
||||
const mx = Math.max(...stats.months.map(m => m[1]));
|
||||
html += '<div class="cc-an-sec"><h4>📅 월별 댓글량</h4><div class="cc-months">'
|
||||
html += '<div class="cc-an-sec"><h4><i data-lucide="calendar"></i> 월별 댓글량</h4><div class="cc-months">'
|
||||
+ stats.months.slice(-18).map(([mo, n]) =>
|
||||
'<div class="cc-mo" title="' + esc(mo) + ': ' + n + '">'
|
||||
+ '<span class="cc-mo-val">' + n.toLocaleString() + '</span>' // 값 상시 노출
|
||||
+ '<span class="cc-mo-bar" style="height:' + Math.max(6, Math.round(n / mx * 70)) + 'px"></span>'
|
||||
+ '<span class="cc-mo-lb">' + esc(mo.slice(2)) + '</span></div>').join('')
|
||||
+ '</div></div>';
|
||||
@ -341,6 +350,7 @@
|
||||
html += '</div>'; // #ccAnBody
|
||||
box.innerHTML = html;
|
||||
box.style.display = 'block';
|
||||
if (window.lucide) window.lucide.createIcons(); // 섹션 헤더 아이콘(이모지 대체) 렌더
|
||||
|
||||
const tgl = $('ccAnToggle');
|
||||
if (tgl) tgl.addEventListener('click', () => {
|
||||
@ -348,6 +358,7 @@
|
||||
const hidden = b.style.display === 'none';
|
||||
b.style.display = hidden ? '' : 'none';
|
||||
tgl.textContent = hidden ? '접기' : '펼치기';
|
||||
tgl.setAttribute('aria-expanded', String(hidden));
|
||||
});
|
||||
const wt = $('ccWeighted');
|
||||
if (wt) wt.addEventListener('change', () => renderAnalytics(window.__all));
|
||||
@ -445,7 +456,8 @@
|
||||
window.__videoId = parseVideoId(url);
|
||||
const btn = $('ccFetch');
|
||||
btn.disabled = true; btn.textContent = '수집 중…';
|
||||
$('ccEmpty').textContent = '전체 댓글을 모으는 중입니다. 댓글이 많으면 시간이 걸릴 수 있어요…';
|
||||
$('ccEmpty').innerHTML = '<span class="cc-spinner" role="status" aria-label="수집 중"></span>'
|
||||
+ '전체 댓글을 모으는 중입니다. 댓글이 많으면 시간이 걸릴 수 있어요…';
|
||||
try {
|
||||
const res = await fetch('/api/comment-cards/fetch', {
|
||||
method: 'POST',
|
||||
@ -456,6 +468,8 @@
|
||||
if (!json.success) throw new Error(json.message || '가져오기 실패');
|
||||
window.__all = json.data || [];
|
||||
window.__cards = window.__all;
|
||||
// 딥링크: 공유·새로고침 시 결과 유지되도록 URL에 videoId 반영
|
||||
if (window.__videoId) history.replaceState(null, '', '?v=' + encodeURIComponent(window.__videoId));
|
||||
$('ccFilters').style.display = window.__all.length ? 'flex' : 'none';
|
||||
$('ccEmpty').textContent = window.__all.length ? '' : '댓글이 없습니다.';
|
||||
renderAnalytics(window.__all);
|
||||
@ -470,12 +484,22 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 토글 버튼 공통: 상태 클래스(.active)·aria-pressed 갱신
|
||||
function setToggle(btn, on) {
|
||||
btn.classList.toggle('active', on);
|
||||
btn.setAttribute('aria-pressed', String(on));
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
$('ccFetch').addEventListener('click', fetchComments);
|
||||
$('ccUrl').addEventListener('keydown', (e) => { if (e.key === 'Enter') fetchComments(); });
|
||||
['ccSearch', 'ccSort', 'ccMinLikes', 'ccRepliesOnly'].forEach(id =>
|
||||
// 즉시 반영해도 가벼운 것(정렬·체크박스)
|
||||
['ccSort', 'ccRepliesOnly'].forEach(id =>
|
||||
$(id).addEventListener('input', () => window.renderCards()));
|
||||
// 제외 단어는 카드+분석 둘 다 갱신. 대량 댓글 대비 디바운스.
|
||||
// 대량 카드(최대 2,000장) 대비 검색·좋아요 필터는 디바운스
|
||||
const debouncedRender = debounce(() => window.renderCards(), 200);
|
||||
['ccSearch', 'ccMinLikes'].forEach(id => $(id).addEventListener('input', debouncedRender));
|
||||
// 제외 단어는 카드+분석 둘 다 갱신.
|
||||
$('ccExclude').addEventListener('input', debounce(() => {
|
||||
window.renderCards();
|
||||
renderAnalytics(window.__all);
|
||||
@ -483,17 +507,25 @@
|
||||
$('ccMosaic').addEventListener('click', () => {
|
||||
mosaicOn = !mosaicOn;
|
||||
$('ccMosaic').textContent = mosaicOn ? '모자이크 해제' : '전체 모자이크';
|
||||
setToggle($('ccMosaic'), mosaicOn);
|
||||
document.querySelectorAll('.comment-card').forEach(el => el.classList.toggle('mosaic', mosaicOn));
|
||||
});
|
||||
$('ccRounded').addEventListener('click', () => {
|
||||
roundedOn = !roundedOn;
|
||||
$('ccRounded').textContent = roundedOn ? '모서리 각지게' : '모서리 둥글게';
|
||||
setToggle($('ccRounded'), roundedOn);
|
||||
document.querySelectorAll('.comment-card').forEach(el => el.classList.toggle('rounded', roundedOn));
|
||||
});
|
||||
$('ccWordMask').addEventListener('click', () => {
|
||||
wordMaskOn = !wordMaskOn;
|
||||
$('ccWordMask').textContent = wordMaskOn ? '🖍 단어 가리기: 켜짐' : '🖍 단어 가리기: 꺼짐(복사)';
|
||||
$('ccWordMask').textContent = wordMaskOn ? '단어 가리기: 켜짐' : '단어 가리기: 꺼짐(복사)';
|
||||
setToggle($('ccWordMask'), wordMaskOn);
|
||||
$('cardGrid').classList.toggle('wordmask', wordMaskOn);
|
||||
});
|
||||
|
||||
// 딥링크: URL ?v=<id>가 있으면 자동 조회(공유·새로고침 시 결과 유지)
|
||||
const params = new URLSearchParams(location.search);
|
||||
const v = params.get('v');
|
||||
if (v) { $('ccUrl').value = v; fetchComments(); }
|
||||
});
|
||||
})();
|
||||
|
||||
@ -7,12 +7,26 @@
|
||||
.cc-toolbar { display:flex; flex-wrap:wrap; gap:.6rem; align-items:center; margin-bottom:1rem; }
|
||||
.cc-toolbar input[type=text], .cc-toolbar select, .cc-toolbar input[type=number] {
|
||||
background:var(--surface-2); color:var(--text); border:1px solid var(--border);
|
||||
border-radius:8px; padding:.5rem .7rem; font-size:14px;
|
||||
border-radius:8px; padding:.5rem .7rem; font-size:16px; /* 16px: iOS 포커스 자동확대 방지 */
|
||||
}
|
||||
.cc-url { flex:1; min-width:240px; }
|
||||
.cc-btn { background:var(--primary-gradient); color:#fff; border:none; border-radius:8px;
|
||||
padding:.55rem 1rem; font-weight:600; cursor:pointer; font-size:14px; }
|
||||
padding:.55rem 1rem; font-weight:600; cursor:pointer; font-size:14px; min-height:40px; }
|
||||
.cc-btn.secondary { background:var(--surface-2); color:var(--text); border:1px solid var(--border); }
|
||||
/* 토글 ON 상태: accent로 구분 */
|
||||
.cc-btn.secondary.active { background:var(--accent-soft); border-color:var(--primary); color:var(--primary); }
|
||||
/* 키보드 포커스 링(모든 상호작용 요소) */
|
||||
.cc-btn:focus-visible, .cc-iconbtn:focus-visible, .cc-mini-select:focus-visible,
|
||||
.cc-toolbar input:focus-visible, .comment-card:focus-visible, .cc-tl-link:focus-visible {
|
||||
outline:2px solid var(--primary); outline-offset:2px; }
|
||||
/* 수집 중 스피너 */
|
||||
.cc-spinner { display:inline-block; width:14px; height:14px; border:2px solid var(--border);
|
||||
border-top-color:var(--primary); border-radius:50%; animation:cc-spin .7s linear infinite;
|
||||
vertical-align:-2px; margin-right:.45rem; }
|
||||
@keyframes cc-spin { to { transform:rotate(360deg); } }
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.cc-spinner { animation:none; } .cc-toast { transition:none; }
|
||||
}
|
||||
/* 메이슨리(다단): 카드가 내용 높이에 딱 맞게 쌓여 짧은 글 아래 흰 여백이 안 생김 */
|
||||
.cc-grid { column-width:320px; column-gap:1rem; }
|
||||
/* 카드: 영상 위에 얹을 소스 → 투명 캡처 기준. 미리보기는 surface 배경. */
|
||||
@ -25,14 +39,15 @@
|
||||
.cc-avatar { width:40px; height:40px; border-radius:50%; flex-shrink:0; object-fit:cover; background:var(--surface-2); }
|
||||
.cc-meta { flex:1; min-width:0; }
|
||||
.cc-author { font-weight:600; font-size:13.5px; color:var(--text); }
|
||||
.cc-time { font-size:11.5px; color:var(--text-3); margin-left:.4rem; }
|
||||
.cc-text { font-size:14px; color:var(--text); margin-top:.35rem; white-space:pre-wrap; word-break:break-word; line-height:1.45; }
|
||||
.cc-stats { font-size:12px; color:var(--text-3); margin-top:.5rem; display:flex; gap:1rem; }
|
||||
.cc-time { font-size:11.5px; color:var(--text-2); margin-left:.4rem; }
|
||||
.cc-text { font-size:14px; color:var(--text); margin-top:.35rem; white-space:pre-wrap; word-break:break-word; line-height:1.5; }
|
||||
.cc-stats { font-size:12px; color:var(--text-2); margin-top:.5rem; display:flex; gap:1rem; font-variant-numeric:tabular-nums; }
|
||||
.comment-card { cursor:pointer; }
|
||||
/* 저장 버튼 — cc-noexport라 캡처 이미지에서 제외됨 */
|
||||
.cc-actions { position:absolute; top:.5rem; right:.5rem; display:flex; gap:.3rem; }
|
||||
.cc-iconbtn { background:var(--surface-2); color:var(--text-2); border:1px solid var(--border);
|
||||
border-radius:6px; font-size:11.5px; font-weight:600; padding:.25rem .5rem; cursor:pointer; line-height:1; }
|
||||
border-radius:6px; font-size:12px; font-weight:600; padding:.4rem .6rem; cursor:pointer;
|
||||
line-height:1; min-height:32px; display:inline-flex; align-items:center; gap:.25rem; }
|
||||
.cc-iconbtn:hover { color:var(--text); border-color:var(--primary, #6366f1); }
|
||||
/* 복사 완료 토스트 — 카드 밖(body)에 떠서 캡처 이미지에 안 찍힘 */
|
||||
.cc-toast { position:fixed; left:50%; bottom:2rem; transform:translateX(-50%) translateY(1rem);
|
||||
@ -53,34 +68,36 @@
|
||||
.cc-analytics { background:var(--surface); border:1px solid var(--border); border-radius:12px;
|
||||
padding:1rem 1.1rem; margin-bottom:1.2rem; }
|
||||
.cc-an-head { display:flex; align-items:center; gap:.6rem; margin-bottom:.9rem; font-size:15px; }
|
||||
.cc-an-sub { color:var(--text-3); font-size:12.5px; font-weight:400; }
|
||||
.cc-an-sub { color:var(--text-2); font-size:12.5px; font-weight:400; }
|
||||
.cc-an-head .cc-iconbtn { margin-left:auto; }
|
||||
.cc-an-head [data-lucide], .cc-an-sec h4 [data-lucide] { width:16px; height:16px; vertical-align:-3px; }
|
||||
.cc-tiles { display:grid; grid-template-columns:repeat(auto-fit,minmax(120px,1fr)); gap:.6rem; margin-bottom:1rem; }
|
||||
.cc-tile { background:var(--surface-2); border:1px solid var(--border); border-radius:10px; padding:.6rem .8rem; }
|
||||
.cc-tile-v { font-size:1.25rem; font-weight:700; color:var(--text); line-height:1.2; }
|
||||
.cc-tile-l { font-size:11.5px; color:var(--text-3); margin-top:.15rem; }
|
||||
.cc-tile-v { font-size:1.25rem; font-weight:700; color:var(--text); line-height:1.2; font-variant-numeric:tabular-nums; }
|
||||
.cc-tile-l { font-size:12px; color:var(--text-2); margin-top:.15rem; }
|
||||
.cc-an-sec { margin-top:1rem; }
|
||||
.cc-an-sec h4 { font-size:13.5px; font-weight:600; color:var(--text-2); margin:0 0 .5rem; }
|
||||
.cc-an-sechead { display:flex; align-items:center; justify-content:space-between; gap:.6rem; }
|
||||
.cc-wlabel { font-size:12px; color:var(--text-2); display:flex; align-items:center; gap:.3rem; cursor:pointer; }
|
||||
.cc-mini-select { background:var(--surface-2); color:var(--text); border:1px solid var(--border);
|
||||
border-radius:8px; padding:.25rem .5rem; font-size:12px; cursor:pointer; }
|
||||
border-radius:8px; padding:.35rem .6rem; font-size:13px; cursor:pointer; min-height:32px; }
|
||||
.cc-an-empty { color:var(--text-3); font-size:13px; margin:.2rem 0; }
|
||||
.cc-bars { display:flex; flex-direction:column; gap:.35rem; }
|
||||
.cc-bar-row { display:grid; grid-template-columns:76px 1fr 52px; align-items:center; gap:.5rem; font-size:12.5px; }
|
||||
.cc-bar-label { color:var(--text); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
||||
.cc-bar-track { background:var(--surface-2); border-radius:999px; height:9px; overflow:hidden; }
|
||||
.cc-bar-fill { display:block; height:100%; background:var(--primary-gradient); border-radius:999px; }
|
||||
.cc-bar-val { color:var(--text-3); text-align:right; }
|
||||
.cc-bar-val { color:var(--text-2); text-align:right; font-variant-numeric:tabular-nums; }
|
||||
a.cc-tl-link { text-decoration:none; }
|
||||
a.cc-tl-link:hover .cc-bar-label { text-decoration:underline; color:var(--primary,#6366f1); }
|
||||
.cc-top-comment { background:var(--surface-2); border:1px solid var(--border); border-radius:10px;
|
||||
padding:.6rem .8rem; font-size:12.5px; color:var(--text-2); }
|
||||
.cc-top-text { color:var(--text); margin-top:.3rem; white-space:pre-wrap; word-break:break-word; }
|
||||
.cc-months { display:flex; align-items:flex-end; gap:.35rem; height:92px; overflow-x:auto; padding-top:.3rem; }
|
||||
.cc-mo { display:flex; flex-direction:column; align-items:center; justify-content:flex-end; gap:.25rem; min-width:26px; }
|
||||
.cc-months { display:flex; align-items:flex-end; gap:.35rem; height:108px; overflow-x:auto; padding-top:.3rem; }
|
||||
.cc-mo { display:flex; flex-direction:column; align-items:center; justify-content:flex-end; gap:.2rem; min-width:28px; }
|
||||
.cc-mo-val { font-size:10px; color:var(--text-2); font-variant-numeric:tabular-nums; } /* 값 상시 노출(hover 의존 X) */
|
||||
.cc-mo-bar { display:block; width:16px; background:var(--primary-gradient); border-radius:4px 4px 0 0; }
|
||||
.cc-mo-lb { font-size:10px; color:var(--text-3); white-space:nowrap; }
|
||||
.cc-mo-lb { font-size:11px; color:var(--text-2); white-space:nowrap; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@ -88,30 +105,31 @@
|
||||
<h1 style="font-size:1.5rem;font-weight:700;margin-bottom:.3rem;">댓글 카드</h1>
|
||||
<p style="color:var(--text-3);margin-bottom:1.2rem;font-size:14px;">
|
||||
유튜브 링크의 <b>전체 댓글</b>을 가져와 분석(많이 언급된 타임라인·자주 나온 단어·요약 통계)하고 카드로 만듭니다.
|
||||
모자이크는 기본 켜짐, <b>모서리 둥글게</b>로 각진/라운드 전환. 기본은 <b>단어 클릭=가리기</b>(블러), <b>🖍 단어 가리기</b>를 끄면 <b>카드 클릭=이미지 복사</b>. <b>⬇ 저장=PNG 파일 다운로드</b>(캡컷은 붙여넣기가 안 되니 저장한 파일을 "불러오기" 하세요).
|
||||
모자이크는 기본 켜짐, <b>모서리 둥글게</b>로 각진/라운드 전환. 기본은 <b>단어 클릭=가리기</b>(블러), <b>단어 가리기</b> 토글을 끄면 <b>카드 클릭=이미지 복사</b>. <b>저장 버튼=PNG 파일 다운로드</b>(캡컷은 붙여넣기가 안 되니 저장한 파일을 "불러오기" 하세요).
|
||||
</p>
|
||||
|
||||
<div class="cc-toolbar">
|
||||
<input type="text" id="ccUrl" class="cc-url" placeholder="유튜브 링크 또는 영상 ID 붙여넣기" />
|
||||
<input type="text" id="ccUrl" class="cc-url" placeholder="유튜브 링크 또는 영상 ID 붙여넣기"
|
||||
aria-label="유튜브 링크 또는 영상 ID" />
|
||||
<button class="cc-btn" id="ccFetch">가져오기</button>
|
||||
</div>
|
||||
|
||||
<div class="cc-toolbar" id="ccFilters" style="display:none;">
|
||||
<input type="text" id="ccSearch" placeholder="🔍 댓글 내용 검색" style="min-width:180px;" />
|
||||
<input type="text" id="ccExclude" placeholder="🚫 제외 단어 (쉼표로 구분)" style="min-width:180px;" />
|
||||
<select id="ccSort">
|
||||
<input type="text" id="ccSearch" placeholder="댓글 내용 검색" aria-label="댓글 내용 검색" style="min-width:180px;" />
|
||||
<input type="text" id="ccExclude" placeholder="제외 단어 (쉼표로 구분)" aria-label="제외 단어 (쉼표로 구분)" style="min-width:180px;" />
|
||||
<select id="ccSort" aria-label="정렬 기준">
|
||||
<option value="likes">좋아요순</option>
|
||||
<option value="replies">답글순</option>
|
||||
<option value="latest">최신순</option>
|
||||
</select>
|
||||
<label style="font-size:13px;color:var(--text-2);">좋아요
|
||||
<input type="number" id="ccMinLikes" value="0" min="0" style="width:90px;" /> 이상</label>
|
||||
<input type="number" id="ccMinLikes" value="0" min="0" style="width:90px;" aria-label="최소 좋아요 수" /> 이상</label>
|
||||
<label style="font-size:13px;color:var(--text-2);display:flex;align-items:center;gap:.3rem;">
|
||||
<input type="checkbox" id="ccRepliesOnly" /> 답글 있는 것만</label>
|
||||
<button class="cc-btn secondary" id="ccMosaic">모자이크 해제</button>
|
||||
<button class="cc-btn secondary" id="ccRounded">모서리 둥글게</button>
|
||||
<button class="cc-btn secondary" id="ccWordMask">🖍 단어 가리기: 켜짐</button>
|
||||
<span id="ccCount" style="font-size:12px;color:var(--text-3);"></span>
|
||||
<button class="cc-btn secondary active" id="ccMosaic" aria-pressed="true">모자이크 해제</button>
|
||||
<button class="cc-btn secondary" id="ccRounded" aria-pressed="false">모서리 둥글게</button>
|
||||
<button class="cc-btn secondary active" id="ccWordMask" aria-pressed="true">단어 가리기: 켜짐</button>
|
||||
<span id="ccCount" style="font-size:12px;color:var(--text-2);"></span>
|
||||
</div>
|
||||
|
||||
<div class="cc-analytics" id="ccAnalytics" style="display:none;"></div>
|
||||
@ -123,7 +141,7 @@
|
||||
<th:block layout:fragment="script">
|
||||
<!-- DOM→이미지 캡처 (Task 5에서 사용). blur 필터 지원 위해 modern-screenshot 사용. -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/modern-screenshot@4/dist/index.js"></script>
|
||||
<script th:src="@{/js/comment-cards.js(v=20260708d)}"></script>
|
||||
<script th:src="@{/js/comment-cards.js(v=20260710)}"></script>
|
||||
</th:block>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user