feat: show image dimensions + lock horizontal scroll on mobile

- Add width/height fields to ImageResult model
- Extract dimensions from DuckDuckGo and Bing engine responses
- Show size (e.g. 1920×1080) on card hover overlay and in lightbox details
- Prevent horizontal scrolling/panning on mobile (overflow-x: hidden + overscroll-behavior)
- Allow pinch-to-zoom via viewport meta (user-scalable=yes, maximum-scale=5)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-02-23 18:39:51 +11:00
co-authored by Copilot
parent 70b62065bd
commit 1b85dbc80a
7 changed files with 30 additions and 2 deletions
+2
View File
@@ -213,6 +213,8 @@ class BingEngine(SearchEngine):
thumbnail_src=thumb_url or img_url,
source="Bing",
engine=self.name,
width=int(data.get("mw", 0) or 0),
height=int(data.get("mh", 0) or 0),
)
)
except _json.JSONDecodeError:
+2
View File
@@ -104,6 +104,8 @@ class DuckDuckGoEngine(SearchEngine):
thumbnail_src=item.get("thumbnail", ""),
source=item.get("source", ""),
engine=self.name,
width=int(item.get("width", 0) or 0),
height=int(item.get("height", 0) or 0),
))
return results
+2
View File
@@ -28,6 +28,8 @@ class ImageResult(BaseModel):
source: str = ""
engine: str = ""
rank: int = 0
width: int = 0
height: int = 0
class EngineError(BaseModel):
+1 -1
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=yes" />
<title>Hey Search</title>
</head>
<body>
+16 -1
View File
@@ -2,6 +2,11 @@ import { useState, useEffect, useCallback, type MouseEvent } from "react";
import type { ImageResult } from "@/lib/api";
import { X, ChevronLeft, ChevronRight } from "lucide-react";
function formatSize(w: number, h: number): string | null {
if (w > 0 && h > 0) return `${w}×${h}`;
return null;
}
interface ImageResultsProps {
results: ImageResult[];
}
@@ -71,7 +76,14 @@ export function ImageResults({ results }: ImageResultsProps) {
{/* Hover overlay with title */}
<div className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/70 to-transparent p-2 opacity-0 transition-opacity group-hover:opacity-100">
<p className="truncate text-xs text-white">{img.title}</p>
<p className="truncate text-[10px] text-white/60">{img.source}</p>
<div className="flex items-center gap-1">
<p className="truncate text-[10px] text-white/60">{img.source}</p>
{formatSize(img.width, img.height) && (
<span className="ml-auto shrink-0 text-[10px] tabular-nums text-white/60">
{formatSize(img.width, img.height)}
</span>
)}
</div>
</div>
</a>
))}
@@ -127,6 +139,9 @@ export function ImageResults({ results }: ImageResultsProps) {
<h3 className="font-medium">{selected.title}</h3>
<p className="mt-1 text-sm text-muted-foreground">
Source: {selected.source} Engine: {selected.engine}
{formatSize(selected.width, selected.height) && (
<> {formatSize(selected.width, selected.height)}px</>
)}
<span className="ml-2 tabular-nums opacity-60">
{selectedIndex + 1} / {results.length}
</span>
+5
View File
@@ -2,6 +2,11 @@
@custom-variant dark (&:is(.dark *));
html, body {
overflow-x: hidden;
overscroll-behavior-x: none;
}
:root {
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
+2
View File
@@ -18,6 +18,8 @@ export interface ImageResult {
source: string;
engine: string;
rank: number;
width: number;
height: number;
}
export interface EngineError {