| | <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:center; flex-wrap:wrap; margin:14px 0; } |
| | .image-comparison .controls label { font-size:14px; color: var(--text-color); display:flex; align-items:center; justify-content:center; gap:10px; font-weight:600; } |
| | .image-comparison .controls select { |
| | font-size: 14px; |
| | padding: 8px 32px 8px 12px; |
| | border: 1px solid var(--border-color); |
| | border-radius: 10px; |
| | 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; |
| | box-shadow: 0 1px 2px rgba(0,0,0,.04); |
| | } |
| | [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; } |
| | |
| | |
| | @media (max-width: 520px) { |
| | .image-comparison .controls label { flex-direction: column; gap: 6px; } |
| | } |
| | |
| | .image-comparison .grid { display:grid; grid-template-columns: repeat(4, 1fr); gap: 12px; width:100%; align-items: start; } |
| | |
| | @media (max-width: 1100px) { .image-comparison .grid { grid-template-columns: repeat(2, 1fr); } } |
| | @media (max-width: 680px) { .image-comparison .grid { grid-template-columns: 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 THIS_SCRIPT = document.currentScript; |
| | const bootstrap = () => { |
| | const scriptEl = THIS_SCRIPT; |
| | const host = scriptEl && scriptEl.parentElement; |
| | let container = null; |
| | if (host && host.querySelector) { |
| | container = host.querySelector('.image-comparison'); |
| | } |
| | if (!container) { |
| | let sib = scriptEl && scriptEl.previousElementSibling; |
| | while (sib && !(sib.classList && sib.classList.contains('image-comparison'))) { |
| | sib = sib.previousElementSibling; |
| | } |
| | container = sib || 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': { query: 'id_1_query.png', 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': { query: 'id_2_query.png', 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': { query: 'id_3_query.png', 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.query, files[1], files[2], files[3]]; |
| | grid.innerHTML = ''; |
| | ordered.forEach((fname, idx) => { |
| | const { sim } = parseInfo(fname); |
| | const isQuery = idx === 0; |
| | 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} ${isQuery ? 'query' : `match ${idx}`}`; img.loading = 'lazy'; img.src = basePath + fname; media.appendChild(img); |
| | const meta = document.createElement('div'); meta.className = 'meta'; |
| | |
| | if (isQuery) { |
| | const label = document.createElement('span'); label.className = 'value'; label.textContent = 'Query'; |
| | meta.appendChild(label); |
| | } else { |
| | const content = document.createElement('span'); |
| | content.innerHTML = `<span class="value">Match ${idx}</span><br><span class="label">Similarity: ${formatSim(sim)}</span>`; |
| | meta.appendChild(content); |
| | } |
| | |
| | card.appendChild(media); card.appendChild(meta); grid.appendChild(card); |
| | }); |
| | }; |
| | |
| | (async () => { |
| | |
| | basePath = await resolveBase(CANDIDATE_BASES, FILES['1'].query); |
| | render('1'); |
| | })(); |
| | |
| | select.addEventListener('change', () => render(select.value)); |
| | }; |
| | |
| | if (document.readyState === 'loading') { |
| | document.addEventListener('DOMContentLoaded', bootstrap, { once: true }); |
| | } else { bootstrap(); } |
| | })(); |
| | </script> |
| |
|
| |
|
| |
|