Spaces:
Running
Running
| <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; } | |
| /* Responsive: empiler label et select proprement sur petits écrans */ | |
| @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; } | |
| /* Large → 4 colonnes; Medium → 2 colonnes; Mobile → 1 colonne */ | |
| @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'; | |
| // Known filenames in /public/data/comparison | |
| 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' }, | |
| }; | |
| // Images served from [domain]/public/data/comparison/*.png → path is /data/comparison/ | |
| 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(); | |
| }); | |
| // Controls | |
| 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); | |
| // Grid | |
| 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]]; // query, then matches 1, 2, 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 () => { | |
| // Resolve a working base then initial render | |
| 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> | |