| | <div class="image-comparison" style="width:100%;margin:10px 0;"></div> |
| | <style> |
| | .image-comparison { position: relative; } |
| | .image-comparison .controls { display:flex; align-items:center; gap:16px; justify-content:flex-start; margin:12px 0; } |
| | .image-comparison .controls label { font-size:12px; color: var(--muted-color); display:flex; align-items:center; gap:8px; } |
| | .image-comparison .controls select { |
| | font-size: 12px; |
| | padding: 8px 28px 8px 10px; |
| | border: 1px solid var(--border-color); |
| | border-radius: 8px; |
| | background-color: var(--surface-bg); |
| | color: var(--text-color); |
| | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%230f1115' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E"); |
| | background-repeat: no-repeat; |
| | background-position: right 8px center; |
| | background-size: 12px; |
| | -webkit-appearance: none; appearance: none; cursor: pointer; |
| | transition: border-color .15s ease, box-shadow .15s ease; |
| | } |
| | [data-theme="dark"] .image-comparison .controls select { |
| | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E"); |
| | } |
| | .image-comparison .controls select:hover { border-color: var(--primary-color); } |
| | .image-comparison .controls select:focus { border-color: var(--primary-color); box-shadow: 0 0 0 3px rgba(232,137,171,.25); outline: none; } |
| | |
| | .image-comparison .grid { display:grid; grid-template-columns: repeat(4, 1fr); gap: 12px; width:100%; align-items: start; } |
| | @media (max-width: 980px) { .image-comparison .grid { grid-template-columns: repeat(2, 1fr); } } |
| | |
| | .image-comparison .card { position: relative; border:1px solid var(--border-color); border-radius:10px; overflow:hidden; background: var(--surface-bg); display:flex; flex-direction:column; } |
| | .image-comparison .card .media { position: relative; width:100%; height: 200px; background: var(--surface-2, var(--surface-bg)); display:block; } |
| | .image-comparison .card .media img { width:100%; height:100%; object-fit: contain; display:block; } |
| | .image-comparison .badge { position:absolute; top:8px; left:8px; font-size:11px; padding:3px 6px; border-radius:6px; background: var(--surface-bg); color: var(--text-color); border:1px solid var(--border-color); } |
| | .image-comparison .meta { padding:8px 10px; border-top:1px solid var(--border-color); font-size:12px; display:flex; height: 55px; align-items:start; justify-content:space-between; gap:8px; } |
| | .image-comparison .meta .label { color: var(--muted-color); } |
| | .image-comparison .meta .value { font-weight:600; } |
| | </style> |
| | <script> |
| | (() => { |
| | const bootstrap = () => { |
| | const mount = document.currentScript ? document.currentScript.previousElementSibling : null; |
| | const container = (mount && mount.querySelector && mount.querySelector('.image-comparison')) || document.querySelector('.image-comparison'); |
| | if (!container) return; |
| | if (container.dataset && container.dataset.mounted === 'true') return; if (container.dataset) container.dataset.mounted = 'true'; |
| | |
| | |
| | const FILES = { |
| | '1': { 1: 'id_1_rank_1_sim_1.000.png', 2: 'id_1_rank_2_sim_0.165.png', 3: 'id_1_rank_3_sim_0.143.png' }, |
| | '2': { 1: 'id_2_rank_1_sim_1.000.png', 2: 'id_2_rank_2_sim_0.978.png', 3: 'id_2_rank_3_sim_0.975.png' }, |
| | '3': { 1: 'id_3_rank_1_sim_0.936.png', 2: 'id_3_rank_2_sim_0.686.png', 3: 'id_3_rank_3_sim_0.676.png' }, |
| | }; |
| | |
| | |
| | const CANDIDATE_BASES = [ '/data/comparison/' ]; |
| | |
| | const resolveBase = (candidates, filename) => new Promise((resolve) => { |
| | let idx = 0; const tryNext = () => { |
| | if (idx >= candidates.length) return resolve(candidates[0]); |
| | const img = new Image(); |
| | img.onload = () => resolve(candidates[idx]); |
| | img.onerror = () => { idx += 1; tryNext(); }; |
| | img.src = candidates[idx] + filename; |
| | }; tryNext(); |
| | }); |
| | |
| | |
| | const controls = document.createElement('div'); controls.className = 'controls'; |
| | const label = document.createElement('label'); label.textContent = 'Example'; |
| | const select = document.createElement('select'); |
| | const EXAMPLE_LABELS = { '1': 'photo', '2': 'chart', '3': 'drawing' }; |
| | ['1','2','3'].forEach((id)=>{ const o=document.createElement('option'); o.value=id; o.textContent=EXAMPLE_LABELS[id]; select.appendChild(o); }); |
| | label.appendChild(select); controls.appendChild(label); container.appendChild(controls); |
| | |
| | |
| | const grid = document.createElement('div'); grid.className = 'grid'; container.appendChild(grid); |
| | |
| | let basePath = CANDIDATE_BASES[0]; |
| | |
| | const parseInfo = (filename) => { |
| | const rankMatch = filename.match(/rank_(\d+)/i); const rank = rankMatch ? rankMatch[1] : ''; |
| | const simMatch = filename.match(/sim_([0-9.]+)/i); const sim = simMatch ? simMatch[1] : ''; |
| | return { rank, sim }; |
| | }; |
| | |
| | const formatSim = (val) => { |
| | if (val == null || val === '') return '—'; |
| | return String(val).replace(/\.$/, ''); |
| | }; |
| | |
| | const render = (id) => { |
| | const files = FILES[id]; if (!files) return; |
| | const ordered = [files[1], files[1], files[2], files[3]]; |
| | grid.innerHTML = ''; |
| | ordered.forEach((fname, idx) => { |
| | const { sim } = parseInfo(fname); |
| | const isFirst = idx === 0; |
| | const isDuplicateOfFirst = idx === 1; |
| | const card = document.createElement('div'); card.className = 'card'; |
| | const media = document.createElement('div'); media.className = 'media'; |
| | const img = document.createElement('img'); img.alt = `example ${id} image ${idx+1}${isDuplicateOfFirst ? ' identical' : ''}`; img.loading = 'lazy'; img.src = basePath + fname; media.appendChild(img); |
| | const meta = document.createElement('div'); meta.className = 'meta'; |
| | const left = document.createElement('span'); left.className = 'label'; left.textContent = isFirst ? 'Query' : 'Similarity'; |
| | meta.appendChild(left); |
| | if (!isFirst) { |
| | const right = document.createElement('span'); right.className = 'value'; right.textContent = isDuplicateOfFirst ? '1.000 identical' : formatSim(sim); |
| | meta.appendChild(right); |
| | } |
| | card.appendChild(media); card.appendChild(meta); grid.appendChild(card); |
| | }); |
| | }; |
| | |
| | (async () => { |
| | |
| | basePath = await resolveBase(CANDIDATE_BASES, FILES['1'][1]); |
| | render('1'); |
| | })(); |
| | |
| | select.addEventListener('change', () => render(select.value)); |
| | }; |
| | |
| | if (document.readyState === 'loading') { |
| | document.addEventListener('DOMContentLoaded', bootstrap, { once: true }); |
| | } else { bootstrap(); } |
| | })(); |
| | </script> |
| |
|
| |
|
| |
|