Add lightbox to all three frontends — click photo to view full size

Click any result image to open it in a dark overlay. Click anywhere or
press Escape to close. Score colour matches each frontend's accent colour.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-19 15:15:52 +02:00
parent 048309da8a
commit 054b678dae
3 changed files with 162 additions and 3 deletions
+54 -1
View File
@@ -106,6 +106,37 @@
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.empty { text-align: center; color: #999; margin-top: 3rem; font-size: 1rem; } .empty { text-align: center; color: #999; margin-top: 3rem; font-size: 1rem; }
.card img { cursor: pointer; }
.card img:hover { opacity: 0.85; }
.lightbox {
display: none;
position: fixed;
inset: 0;
background: rgba(0,0,0,0.85);
z-index: 100;
align-items: center;
justify-content: center;
flex-direction: column;
gap: 0.8rem;
}
.lightbox.open { display: flex; }
.lightbox img {
max-width: 90vw;
max-height: 80vh;
object-fit: contain;
border-radius: 4px;
box-shadow: 0 4px 32px rgba(0,0,0,0.6);
}
.lightbox-info { color: white; font-size: 0.95rem; text-align: center; }
.lightbox-info .lb-score { color: #cba6f7; font-weight: 700; }
.lightbox-close {
position: fixed;
top: 1rem; right: 1.2rem;
color: white; font-size: 2rem;
cursor: pointer; line-height: 1;
}
</style> </style>
</head> </head>
<body> <body>
@@ -134,6 +165,14 @@
<p class="stats" id="stats"></p> <p class="stats" id="stats"></p>
<div class="grid" id="grid"><p class="empty">Enter a search term above.</p></div> <div class="grid" id="grid"><p class="empty">Enter a search term above.</p></div>
<div class="lightbox" id="lightbox" onclick="closeLightbox()">
<span class="lightbox-close" onclick="closeLightbox()"></span>
<img id="lb-img" src="" alt="" />
<div class="lightbox-info">
<span id="lb-name"></span> &nbsp;·&nbsp; <span class="lb-score" id="lb-score"></span>
</div>
</div>
<script> <script>
const API = "http://localhost:8002"; const API = "http://localhost:8002";
@@ -166,7 +205,8 @@
} }
grid.innerHTML = results.map(r => ` grid.innerHTML = results.map(r => `
<div class="card"> <div class="card">
<img src="${API}/photos/${encodeURIComponent(r.filename)}" alt="${r.filename}" loading="lazy" /> <img src="${API}/photos/${encodeURIComponent(r.filename)}" alt="${r.filename}" loading="lazy"
onclick="openLightbox('${encodeURIComponent(r.filename)}','${r.filename}','${(r.score*100).toFixed(1)}%')" />
<div class="card-info"> <div class="card-info">
<div class="score">${(r.score * 100).toFixed(1)}% match</div> <div class="score">${(r.score * 100).toFixed(1)}% match</div>
<div class="name">${r.filename}</div> <div class="name">${r.filename}</div>
@@ -174,6 +214,19 @@
</div> </div>
`).join(""); `).join("");
} }
function openLightbox(encoded, name, score) {
document.getElementById("lb-img").src = `${API}/photos/${encoded}`;
document.getElementById("lb-name").textContent = name;
document.getElementById("lb-score").textContent = score + " match";
document.getElementById("lightbox").classList.add("open");
}
function closeLightbox() {
document.getElementById("lightbox").classList.remove("open");
}
document.addEventListener("keydown", e => { if (e.key === "Escape") closeLightbox(); });
</script> </script>
</body> </body>
</html> </html>
+54 -1
View File
@@ -106,6 +106,37 @@
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.empty { text-align: center; color: #999; margin-top: 3rem; font-size: 1rem; } .empty { text-align: center; color: #999; margin-top: 3rem; font-size: 1rem; }
.card img { cursor: pointer; }
.card img:hover { opacity: 0.85; }
.lightbox {
display: none;
position: fixed;
inset: 0;
background: rgba(0,0,0,0.85);
z-index: 100;
align-items: center;
justify-content: center;
flex-direction: column;
gap: 0.8rem;
}
.lightbox.open { display: flex; }
.lightbox img {
max-width: 90vw;
max-height: 80vh;
object-fit: contain;
border-radius: 4px;
box-shadow: 0 4px 32px rgba(0,0,0,0.6);
}
.lightbox-info { color: white; font-size: 0.95rem; text-align: center; }
.lightbox-info .lb-score { color: #f38ba8; font-weight: 700; }
.lightbox-close {
position: fixed;
top: 1rem; right: 1.2rem;
color: white; font-size: 2rem;
cursor: pointer; line-height: 1;
}
</style> </style>
</head> </head>
<body> <body>
@@ -134,6 +165,14 @@
<p class="stats" id="stats"></p> <p class="stats" id="stats"></p>
<div class="grid" id="grid"><p class="empty">Enter a search term above.</p></div> <div class="grid" id="grid"><p class="empty">Enter a search term above.</p></div>
<div class="lightbox" id="lightbox" onclick="closeLightbox()">
<span class="lightbox-close" onclick="closeLightbox()"></span>
<img id="lb-img" src="" alt="" />
<div class="lightbox-info">
<span id="lb-name"></span> &nbsp;·&nbsp; <span class="lb-score" id="lb-score"></span>
</div>
</div>
<script> <script>
const API = "http://localhost:8001"; const API = "http://localhost:8001";
@@ -166,7 +205,8 @@
} }
grid.innerHTML = results.map(r => ` grid.innerHTML = results.map(r => `
<div class="card"> <div class="card">
<img src="${API}/photos/${encodeURIComponent(r.filename)}" alt="${r.filename}" loading="lazy" /> <img src="${API}/photos/${encodeURIComponent(r.filename)}" alt="${r.filename}" loading="lazy"
onclick="openLightbox('${encodeURIComponent(r.filename)}','${r.filename}','${(r.score*100).toFixed(1)}%')" />
<div class="card-info"> <div class="card-info">
<div class="score">${(r.score * 100).toFixed(1)}% match</div> <div class="score">${(r.score * 100).toFixed(1)}% match</div>
<div class="name">${r.filename}</div> <div class="name">${r.filename}</div>
@@ -174,6 +214,19 @@
</div> </div>
`).join(""); `).join("");
} }
function openLightbox(encoded, name, score) {
document.getElementById("lb-img").src = `${API}/photos/${encoded}`;
document.getElementById("lb-name").textContent = name;
document.getElementById("lb-score").textContent = score + " match";
document.getElementById("lightbox").classList.add("open");
}
function closeLightbox() {
document.getElementById("lightbox").classList.remove("open");
}
document.addEventListener("keydown", e => { if (e.key === "Escape") closeLightbox(); });
</script> </script>
</body> </body>
</html> </html>
+54 -1
View File
@@ -106,6 +106,37 @@
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.empty { text-align: center; color: #999; margin-top: 3rem; font-size: 1rem; } .empty { text-align: center; color: #999; margin-top: 3rem; font-size: 1rem; }
.card img { cursor: pointer; }
.card img:hover { opacity: 0.85; }
.lightbox {
display: none;
position: fixed;
inset: 0;
background: rgba(0,0,0,0.85);
z-index: 100;
align-items: center;
justify-content: center;
flex-direction: column;
gap: 0.8rem;
}
.lightbox.open { display: flex; }
.lightbox img {
max-width: 90vw;
max-height: 80vh;
object-fit: contain;
border-radius: 4px;
box-shadow: 0 4px 32px rgba(0,0,0,0.6);
}
.lightbox-info { color: white; font-size: 0.95rem; text-align: center; }
.lightbox-info .lb-score { color: #89b4fa; font-weight: 700; }
.lightbox-close {
position: fixed;
top: 1rem; right: 1.2rem;
color: white; font-size: 2rem;
cursor: pointer; line-height: 1;
}
</style> </style>
</head> </head>
<body> <body>
@@ -134,6 +165,14 @@
<p class="stats" id="stats"></p> <p class="stats" id="stats"></p>
<div class="grid" id="grid"><p class="empty">Enter a search term above.</p></div> <div class="grid" id="grid"><p class="empty">Enter a search term above.</p></div>
<div class="lightbox" id="lightbox" onclick="closeLightbox()">
<span class="lightbox-close" onclick="closeLightbox()"></span>
<img id="lb-img" src="" alt="" />
<div class="lightbox-info">
<span id="lb-name"></span> &nbsp;·&nbsp; <span class="lb-score" id="lb-score"></span>
</div>
</div>
<script> <script>
const API = "http://localhost:8000"; const API = "http://localhost:8000";
@@ -166,7 +205,8 @@
} }
grid.innerHTML = results.map(r => ` grid.innerHTML = results.map(r => `
<div class="card"> <div class="card">
<img src="${API}/photos/${encodeURIComponent(r.filename)}" alt="${r.filename}" loading="lazy" /> <img src="${API}/photos/${encodeURIComponent(r.filename)}" alt="${r.filename}" loading="lazy"
onclick="openLightbox('${encodeURIComponent(r.filename)}','${r.filename}','${(r.score*100).toFixed(1)}%')" />
<div class="card-info"> <div class="card-info">
<div class="score">${(r.score * 100).toFixed(1)}% match</div> <div class="score">${(r.score * 100).toFixed(1)}% match</div>
<div class="name">${r.filename}</div> <div class="name">${r.filename}</div>
@@ -174,6 +214,19 @@
</div> </div>
`).join(""); `).join("");
} }
function openLightbox(encoded, name, score) {
document.getElementById("lb-img").src = `${API}/photos/${encoded}`;
document.getElementById("lb-name").textContent = name;
document.getElementById("lb-score").textContent = score + " match";
document.getElementById("lightbox").classList.add("open");
}
function closeLightbox() {
document.getElementById("lightbox").classList.remove("open");
}
document.addEventListener("keydown", e => { if (e.key === "Escape") closeLightbox(); });
</script> </script>
</body> </body>
</html> </html>