Correctness: - Make the local-artifact audit test skip on fresh clones (data/ is gitignored), so the suite passes outside this workstation - Drop the transform from the viewRise entrance animation: an animated transform made .view.active a containing block for 320ms and threw the fixed decision panel off-screen on every workbench entry - Collapse the queue toolbar at 1380px instead of 1180px; 1280x800 laptops no longer get a horizontal scrollbar (verified live) - Serve .woff2 as font/woff2 with an immutable cache header so the 2MB bundled font is fetched once, not per page load (with test) - Clip overflow on top-bar status chips (long apiError strings spilled over neighbors at 981-1180px) - Give queue-row selection a selector that outranks the even-row zebra stripe (selection background was parity-dependent) Cleanup: - Replace the stale old-palette focus ring and ::selection literals with color-mix over var(--teal) - Delete dead tokens: unused back-compat aliases (the comment claiming they were referenced was false), --rail-bot, --ochre-deep, and --font-stamp (identical to --font-ui since the Pretendard switch) - Tokenize scattered raw colors: rail ink scale, soft tint levels, inset-well and bevel shadows, naver/internal source-chip triplets - Remove the asset-preload div and three orphan SVGs nothing renders; tests now reject reintroducing them Verified: 359 tests pass; Playwright audit at 1440/1280/390 shows zero horizontal overflow on all views, Pretendard active, decision panel fixed at the viewport corner mid-animation.
463 lines
23 KiB
HTML
463 lines
23 KiB
HTML
<!doctype html>
|
|
<html lang="ko">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>권리 검수 콘솔</title>
|
|
<link rel="preload" href="assets/fonts/PretendardVariable.woff2" as="font" type="font/woff2" crossorigin>
|
|
<link rel="stylesheet" href="styles.css">
|
|
<script src="operator-labels.js" defer></script>
|
|
<script src="submission-import.js" defer></script>
|
|
<script src="evidence-guidance.js" defer></script>
|
|
<script src="operator-search.js" defer></script>
|
|
<script src="app.js" defer></script>
|
|
</head>
|
|
<body data-internal-only="true">
|
|
<div class="app-shell">
|
|
<nav class="nav-rail" aria-label="내부 운영 콘솔">
|
|
<div class="brand-block">
|
|
<span class="brand-mark" aria-hidden="true">CR</span>
|
|
<div>
|
|
<strong>권리 검수기</strong>
|
|
<span>권리 심사 콘솔</span>
|
|
</div>
|
|
</div>
|
|
|
|
<button class="nav-button active" type="button" data-view="queue" aria-current="page">
|
|
<span aria-hidden="true">Q</span>
|
|
<span>심사 큐</span>
|
|
</button>
|
|
<button class="nav-button" type="button" data-view="workbench">
|
|
<span aria-hidden="true">C</span>
|
|
<span>케이스 심사</span>
|
|
</button>
|
|
<button class="nav-button" type="button" data-view="knowledge">
|
|
<span aria-hidden="true">K</span>
|
|
<span>기준 DB</span>
|
|
</button>
|
|
<button class="nav-button" type="button" data-view="providers">
|
|
<span aria-hidden="true">P</span>
|
|
<span>외부 검색 tool 활용</span>
|
|
</button>
|
|
<button class="nav-button" type="button" data-view="audit">
|
|
<span aria-hidden="true">A</span>
|
|
<span>감사 로그</span>
|
|
</button>
|
|
|
|
<div class="nav-footer">
|
|
<span class="status-dot ok" aria-hidden="true"></span>
|
|
<span>내부 전용</span>
|
|
</div>
|
|
</nav>
|
|
|
|
<div class="workspace">
|
|
<header class="top-bar">
|
|
<div class="product-purpose" aria-label="제품 목적">
|
|
<strong>이미지 저작권 위험 심사</strong>
|
|
<span>제출 이미지, 외부 검색 근거, 내부 기준 DB를 한 화면에서 검토합니다.</span>
|
|
</div>
|
|
<div class="queue-health" id="queue-health" aria-live="polite"></div>
|
|
<div class="provider-pulse" id="provider-pulse" aria-label="외부 검색 tool 활용 상태"></div>
|
|
<div class="coverage-tabs" id="coverage-tabs" aria-label="검색 근거 필터" aria-live="polite"></div>
|
|
</header>
|
|
|
|
<main class="main-surface" id="app-main">
|
|
<section class="view active" id="queue-view" data-view-panel="queue" aria-labelledby="queue-title">
|
|
<div class="section-header">
|
|
<div>
|
|
<p class="eyebrow">심사 대기열</p>
|
|
<h1 id="queue-title">권리 위험 심사 큐</h1>
|
|
<p class="queue-intent" id="queue-upload-guidance">사진을 추가하면 현재 큐에 새 심사 건으로 들어가고, 바로 케이스 심사 화면에서 확인할 수 있습니다.</p>
|
|
</div>
|
|
<div class="queue-actions">
|
|
<label class="queue-folder-input" for="submission-folder">
|
|
<span>제출 폴더</span>
|
|
<input id="submission-folder" type="text" placeholder="비워두면 현재 큐 재확인">
|
|
</label>
|
|
<label class="queue-file-input" for="submission-image">
|
|
<span>사진 추가</span>
|
|
<span id="submission-image-name">선택된 파일 없음</span>
|
|
<input id="submission-image" type="file" accept="image/*">
|
|
</label>
|
|
<button class="secondary-action" type="button" id="upload-submission-image">사진 넣기</button>
|
|
<button class="primary-action" type="button" id="reload-submissions">새 제출 불러오기</button>
|
|
<button class="secondary-action" type="button" id="bulk-rerun">선택 재분석</button>
|
|
</div>
|
|
</div>
|
|
<div class="import-status" id="submission-import-status" aria-live="polite"></div>
|
|
|
|
<section class="operator-workflow" aria-label="운영 흐름">
|
|
<article>
|
|
<span>1</span>
|
|
<strong>심사 건 추가</strong>
|
|
<p>사진 추가 또는 제출 폴더 불러오기로 검토할 이미지를 큐에 올립니다.</p>
|
|
</article>
|
|
<article>
|
|
<span>2</span>
|
|
<strong>근거 보강</strong>
|
|
<p>근거 부족 사유와 추천 쿼리로 외부 검색 결과를 보강합니다.</p>
|
|
</article>
|
|
<article>
|
|
<span>3</span>
|
|
<strong>운영 결정</strong>
|
|
<p>승인·보류·반려 결정을 남기고 필요한 경우 기준 DB에 반영합니다.</p>
|
|
</article>
|
|
</section>
|
|
|
|
<div class="toolbar" aria-label="큐 필터">
|
|
<div class="segmented" id="risk-filter" aria-label="위험도">
|
|
<button class="active" type="button" data-risk="all">전체</button>
|
|
<button type="button" data-risk="high">높음</button>
|
|
<button type="button" data-risk="medium">중간</button>
|
|
<button type="button" data-risk="low">낮음</button>
|
|
<button type="button" data-risk="failed">실패</button>
|
|
<button type="button" data-risk="pending">대기</button>
|
|
</div>
|
|
<label>
|
|
<span>증거 출처</span>
|
|
<select id="source-filter">
|
|
<option value="all">전체</option>
|
|
<option value="naver">네이버 근거</option>
|
|
<option value="google">구글 근거</option>
|
|
<option value="fingerprint">이미지 유사도</option>
|
|
<option value="face">얼굴/인물 감지</option>
|
|
<option value="llm">내부 요약</option>
|
|
<option value="failure">외부 검색 tool 실패</option>
|
|
</select>
|
|
</label>
|
|
<label>
|
|
<span>결정 상태</span>
|
|
<select id="decision-filter">
|
|
<option value="all">전체</option>
|
|
<option value="unreviewed">미심사</option>
|
|
<option value="held">보류</option>
|
|
<option value="rejected">반려</option>
|
|
<option value="approved">승인</option>
|
|
<option value="corrected">보정됨</option>
|
|
</select>
|
|
</label>
|
|
<label>
|
|
<span>정렬</span>
|
|
<select id="queue-sort">
|
|
<option value="risk">위험도순</option>
|
|
<option value="newest">최신순</option>
|
|
<option value="oldest">오래된순</option>
|
|
<option value="analysis_failure">분석 실패</option>
|
|
<option value="provider_failure">외부 검색 tool 실패</option>
|
|
</select>
|
|
</label>
|
|
<label class="filter-search">
|
|
<span>큐 검색</span>
|
|
<input id="queue-search" type="search" autocomplete="off" placeholder="SUB-1002, 아이돌, toon...">
|
|
</label>
|
|
</div>
|
|
|
|
<div class="table-shell queue-grid-shell">
|
|
<table class="queue-table queue-grid">
|
|
<thead>
|
|
<tr>
|
|
<th scope="col"><span class="sr-only">선택</span></th>
|
|
<th scope="col">이미지</th>
|
|
<th scope="col">제출</th>
|
|
<th scope="col">위험</th>
|
|
<th scope="col">상위 근거</th>
|
|
<th scope="col">외부 검색 tool 활용</th>
|
|
<th scope="col">지원자 상태</th>
|
|
<th scope="col">운영 결정</th>
|
|
<th scope="col">시간</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="queue-body"></tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="view" id="workbench-view" data-view-panel="workbench" aria-labelledby="case-title" hidden>
|
|
<div class="section-header">
|
|
<div>
|
|
<p class="eyebrow">케이스 심사</p>
|
|
<h1 id="case-title">케이스 심사</h1>
|
|
</div>
|
|
<div class="case-actions">
|
|
<button class="secondary-action" type="button" id="rerun-enrichment">증거 재수집</button>
|
|
<button class="secondary-action" type="button" id="open-correction">보정 열기</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="workbench-tabs" role="tablist" aria-label="케이스 워크벤치">
|
|
<button class="active" type="button" data-workbench-tab="evidence">근거 및 판단</button>
|
|
<button type="button" data-workbench-tab="queries">검색 이력</button>
|
|
</div>
|
|
|
|
<section class="workbench-panel active" data-workbench-panel="evidence">
|
|
<div class="case-layout evidence-layout">
|
|
<aside class="pane decision-pane floating-decision-panel" aria-labelledby="decision-pane-title">
|
|
<div class="pane-heading floating-decision-head">
|
|
<div>
|
|
<p class="eyebrow">운영자 판정</p>
|
|
<h2 id="decision-pane-title">판정</h2>
|
|
</div>
|
|
<span class="score-pill risk-pending" id="floating-case-score">대기</span>
|
|
</div>
|
|
<div class="recommendation-box" id="recommendation-box"></div>
|
|
<label class="memo-field" for="decision-memo">
|
|
<span>결정 메모</span>
|
|
<textarea id="decision-memo" rows="3" placeholder="반려 또는 보정 시 메모 필수"></textarea>
|
|
</label>
|
|
<p class="form-error" id="memo-error" aria-live="assertive"></p>
|
|
<div class="decision-actions">
|
|
<button class="approve-action" type="button" data-decision="approved">승인</button>
|
|
<button class="hold-action" type="button" data-decision="held">보류</button>
|
|
<button class="reject-action" type="button" data-decision="rejected" data-requires-memo="true">반려</button>
|
|
</div>
|
|
<details class="decision-secondary">
|
|
<summary>보조 작업</summary>
|
|
<div class="derived-preview" id="derived-preview"></div>
|
|
<div class="quick-actions">
|
|
<button type="button" id="add-from-case">주의 후보 보기</button>
|
|
<button type="button" id="mark-irrelevant">증거 미사용 처리</button>
|
|
<button type="button" id="disable-derived">자동 항목 비활성</button>
|
|
</div>
|
|
</details>
|
|
</aside>
|
|
<section class="pane image-pane" aria-labelledby="image-pane-title">
|
|
<div class="pane-heading">
|
|
<h2 id="image-pane-title">제출 이미지</h2>
|
|
<div class="icon-actions" aria-label="이미지 보기 제어">
|
|
<button type="button" id="fit-image" title="맞춤">맞춤</button>
|
|
<button type="button" id="actual-image" title="실제 크기">1:1</button>
|
|
<button type="button" id="rotate-image" title="회전">회전</button>
|
|
</div>
|
|
</div>
|
|
<figure class="review-image-frame">
|
|
<img id="case-image" src="assets/case-portrait.svg" alt="심사 대상 제출 이미지">
|
|
<figcaption id="image-derivative-note"></figcaption>
|
|
</figure>
|
|
<div class="fact-grid" id="file-facts"></div>
|
|
<div class="similar-strip" id="similar-strip" aria-label="유사 이미지"></div>
|
|
</section>
|
|
|
|
<section class="pane evidence-pane" aria-labelledby="evidence-pane-title">
|
|
<div class="pane-heading">
|
|
<h2 id="evidence-pane-title">증거와 판단 근거</h2>
|
|
<span class="score-pill" id="case-score"></span>
|
|
</div>
|
|
<div id="case-reasons" class="reason-list"></div>
|
|
<div id="evidence-next-actions" class="evidence-next-actions"></div>
|
|
<div id="evidence-groups" class="evidence-groups"></div>
|
|
</section>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="workbench-panel" data-workbench-panel="queries" aria-labelledby="evidence-title" hidden>
|
|
<div class="section-header">
|
|
<div>
|
|
<p class="eyebrow">근거 검색</p>
|
|
<h1 id="evidence-title">검색 증거 재현</h1>
|
|
</div>
|
|
</div>
|
|
<div class="split-workspace">
|
|
<section class="pane">
|
|
<div class="pane-heading">
|
|
<h2>쿼리 기록</h2>
|
|
<span id="query-case-label"></span>
|
|
</div>
|
|
<div id="query-history" class="stack-list"></div>
|
|
</section>
|
|
<section class="pane">
|
|
<div class="pane-heading">
|
|
<h2>수동 텍스트 쿼리</h2>
|
|
</div>
|
|
<form id="manual-query-form" class="manual-query-form">
|
|
<label for="manual-query-provider">
|
|
<span>외부 검색 tool 활용</span>
|
|
<select id="manual-query-provider">
|
|
<option value="naver">네이버 검색</option>
|
|
</select>
|
|
</label>
|
|
<label for="manual-query">
|
|
<span>쿼리</span>
|
|
<input id="manual-query" type="text" autocomplete="off" placeholder="작품명, 인물명, 키워드">
|
|
</label>
|
|
<button class="primary-action" type="submit">실행</button>
|
|
</form>
|
|
<div id="manual-query-status" class="inline-status" aria-live="polite"></div>
|
|
<div id="search-results" class="stack-list"></div>
|
|
</section>
|
|
</div>
|
|
</section>
|
|
</section>
|
|
|
|
<section class="view" id="knowledge-view" data-view-panel="knowledge" aria-labelledby="knowledge-title" hidden>
|
|
<div class="section-header">
|
|
<div>
|
|
<p class="eyebrow">기준 데이터베이스</p>
|
|
<h1 id="knowledge-title">기준 데이터베이스</h1>
|
|
</div>
|
|
</div>
|
|
<div class="knowledge-tabs" role="tablist" aria-label="기준 데이터베이스">
|
|
<button class="active" type="button" data-knowledge-tab="collect">후보 수집</button>
|
|
<button type="button" data-knowledge-tab="registered">등록된 기준</button>
|
|
<button type="button" data-knowledge-tab="manual">수동 등록</button>
|
|
<button type="button" data-knowledge-tab="corrections">보정 기록</button>
|
|
</div>
|
|
<section class="knowledge-panel active" data-knowledge-panel="collect">
|
|
<section class="pane">
|
|
<div class="pane-heading">
|
|
<h2>키워드 후보 수집</h2>
|
|
</div>
|
|
<form id="candidate-collection-form" class="candidate-collection-form">
|
|
<label for="collection-provider">
|
|
<span>외부 검색 tool 활용</span>
|
|
<select id="collection-provider">
|
|
<option value="naver">네이버 검색</option>
|
|
</select>
|
|
</label>
|
|
<label for="collection-query">
|
|
<span>키워드</span>
|
|
<input id="collection-query" type="text" autocomplete="off" placeholder="인물명, 그룹명, 작품명">
|
|
</label>
|
|
<button class="primary-action" type="submit">후보 수집</button>
|
|
</form>
|
|
<div id="collection-status" class="inline-status" aria-live="polite"></div>
|
|
<div class="candidate-selection-actions">
|
|
<button class="secondary-action" type="button" id="select-all-candidates">전체 선택</button>
|
|
<button class="secondary-action" type="button" id="clear-selected-candidates">전체 선택 취소</button>
|
|
</div>
|
|
<div id="collection-candidates" class="collection-candidates"></div>
|
|
</section>
|
|
</section>
|
|
<section class="knowledge-panel" data-knowledge-panel="registered" hidden>
|
|
<section class="pane">
|
|
<div class="pane-heading">
|
|
<h2>등록된 기준</h2>
|
|
</div>
|
|
<div id="knowledge-list" class="stack-list"></div>
|
|
</section>
|
|
</section>
|
|
<section class="knowledge-panel" data-knowledge-panel="manual" hidden>
|
|
<section class="pane">
|
|
<div class="pane-heading compact-heading">
|
|
<h2>선택 후보 묶어서 편입</h2>
|
|
</div>
|
|
<form id="collection-promotion-form" class="collection-promotion-form">
|
|
<label>
|
|
<span>편입 이름</span>
|
|
<input id="collection-promotion-name" type="text" autocomplete="off" placeholder="인물명, 작품명, 캐릭터명">
|
|
</label>
|
|
<label>
|
|
<span>유형</span>
|
|
<select id="collection-promotion-type">
|
|
<option value="public_figure">연예인/유명인</option>
|
|
<option value="work">작품</option>
|
|
<option value="character">캐릭터</option>
|
|
<option value="game">게임</option>
|
|
<option value="rejected_reference">반려 이미지 참조</option>
|
|
</select>
|
|
</label>
|
|
<label>
|
|
<span>별칭</span>
|
|
<input id="collection-promotion-aliases" type="text" autocomplete="off" placeholder="쉼표로 구분">
|
|
</label>
|
|
<label>
|
|
<span>검색 키워드</span>
|
|
<input id="collection-promotion-keywords" type="text" autocomplete="off" placeholder="쉼표로 구분">
|
|
</label>
|
|
<label class="wide-field">
|
|
<span>판단 메모</span>
|
|
<textarea id="collection-promotion-memo" rows="3" placeholder="후보를 같은 대상의 샘플로 묶은 이유"></textarea>
|
|
</label>
|
|
<button class="primary-action" type="submit" id="promote-selected-candidates">선택 후보 묶어서 편입</button>
|
|
</form>
|
|
<div class="section-divider" aria-hidden="true"></div>
|
|
<div class="pane-heading">
|
|
<h2>수동 기준 등록</h2>
|
|
</div>
|
|
<form id="knowledge-form" class="knowledge-form">
|
|
<label>
|
|
<span>이름</span>
|
|
<input id="knowledge-name" type="text" autocomplete="off" required>
|
|
</label>
|
|
<label>
|
|
<span>유형</span>
|
|
<select id="knowledge-type">
|
|
<option value="public_figure">연예인/유명인</option>
|
|
<option value="work">작품</option>
|
|
<option value="character">캐릭터</option>
|
|
<option value="game">게임</option>
|
|
<option value="rejected_reference">반려 이미지 참조</option>
|
|
</select>
|
|
</label>
|
|
<label>
|
|
<span>별칭</span>
|
|
<input id="knowledge-aliases" type="text" autocomplete="off" placeholder="쉼표로 구분">
|
|
</label>
|
|
<label>
|
|
<span>검색 키워드</span>
|
|
<input id="knowledge-keywords" type="text" autocomplete="off" placeholder="쉼표로 구분">
|
|
</label>
|
|
<label class="file-picker" for="knowledge-image">
|
|
<span>참조 이미지</span>
|
|
<span class="file-picker-control">
|
|
<span class="file-picker-button">이미지 선택</span>
|
|
<span id="knowledge-image-name">선택된 파일 없음</span>
|
|
</span>
|
|
<input id="knowledge-image" class="file-picker-input" type="file" accept="image/*">
|
|
</label>
|
|
<label>
|
|
<span>정책 메모</span>
|
|
<textarea id="knowledge-memo" rows="5" required></textarea>
|
|
</label>
|
|
<button class="primary-action" type="submit">등록</button>
|
|
</form>
|
|
<div id="knowledge-entry-status" class="inline-status" aria-live="polite"></div>
|
|
</section>
|
|
</section>
|
|
<section class="knowledge-panel" data-knowledge-panel="corrections" hidden>
|
|
<div class="pane">
|
|
<div class="pane-heading">
|
|
<h2>오판 보정 기록</h2>
|
|
</div>
|
|
<div id="corrections-list" class="stack-list"></div>
|
|
</div>
|
|
</section>
|
|
</section>
|
|
|
|
<section class="view" id="providers-view" data-view-panel="providers" aria-labelledby="providers-title" hidden>
|
|
<div class="section-header">
|
|
<div>
|
|
<p class="eyebrow">외부 검색 tool 활용</p>
|
|
<h1 id="providers-title">외부 검색 tool 활용</h1>
|
|
</div>
|
|
<button class="danger-outline" type="button" id="emergency-disable">외부 검색 tool 즉시 중지</button>
|
|
</div>
|
|
<div id="providers-list" class="provider-list"></div>
|
|
</section>
|
|
|
|
<section class="view" id="audit-view" data-view-panel="audit" aria-labelledby="audit-title" hidden>
|
|
<div class="section-header">
|
|
<div>
|
|
<p class="eyebrow">감사 로그</p>
|
|
<h1 id="audit-title">감사 로그</h1>
|
|
</div>
|
|
</div>
|
|
<div class="table-shell">
|
|
<table class="audit-table">
|
|
<thead>
|
|
<tr>
|
|
<th scope="col">시간</th>
|
|
<th scope="col">행위자</th>
|
|
<th scope="col">이벤트</th>
|
|
<th scope="col">대상</th>
|
|
<th scope="col">변경</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="audit-body"></tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>
|