feat: visual polish & UX — fade-in, theme toggle, scroll-to-top, footer, cursor, badge
- Add FadeIn component (IntersectionObserver scroll-reveal + load animation) - Add dark/light theme toggle (useSyncExternalStore + localStorage) - Add scroll-to-top button (appears after 300px scroll) - Add configurable footer with social links (gallery.yaml) - Add crosshair cursor on photo grid items - Add 'X photos' count badge on subpage cover images - Add light theme color palette in tokens.css
This commit is contained in:
59
lib/urls.ts
59
lib/urls.ts
@@ -10,23 +10,26 @@ import type { ImmichAsset } from './immich';
|
||||
/**
|
||||
* Generate a public image proxy URL for an asset.
|
||||
*/
|
||||
export function imageUrl(assetId: string, size: 'thumbnail' | 'preview' | 'original' = 'preview'): string {
|
||||
return `/api/image/${encodeAssetId(assetId)}?size=${size}`;
|
||||
export function imageUrl(
|
||||
assetId: string,
|
||||
size: 'thumbnail' | 'preview' | 'original' = 'preview',
|
||||
): string {
|
||||
return `/api/image/${encodeAssetId(assetId)}?size=${size}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a public EXIF API URL for an asset.
|
||||
*/
|
||||
export function exifUrl(assetId: string): string {
|
||||
return `/api/exif/${encodeAssetId(assetId)}`;
|
||||
return `/api/exif/${encodeAssetId(assetId)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Placeholder data derived from an asset's ThumbHash.
|
||||
*/
|
||||
export interface PlaceholderData {
|
||||
blurDataURL: string;
|
||||
dominantColor: string;
|
||||
blurDataURL: string;
|
||||
dominantColor: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -34,24 +37,24 @@ export interface PlaceholderData {
|
||||
* Returns null if the asset has no ThumbHash.
|
||||
*/
|
||||
export function assetPlaceholder(asset: Pick<ImmichAsset, 'thumbhash'>): PlaceholderData | null {
|
||||
if (!asset.thumbhash) return null;
|
||||
try {
|
||||
return {
|
||||
blurDataURL: thumbHashToBlurDataUrl(asset.thumbhash),
|
||||
dominantColor: thumbHashToDominantHex(asset.thumbhash),
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
if (!asset.thumbhash) return null;
|
||||
try {
|
||||
return {
|
||||
blurDataURL: thumbHashToBlurDataUrl(asset.thumbhash),
|
||||
dominantColor: thumbHashToDominantHex(asset.thumbhash),
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compact EXIF summary for hover overlays.
|
||||
*/
|
||||
export interface ExifSummary {
|
||||
camera?: string;
|
||||
lens?: string;
|
||||
focalLength?: string;
|
||||
camera?: string;
|
||||
lens?: string;
|
||||
focalLength?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,15 +62,15 @@ export interface ExifSummary {
|
||||
* Returns undefined if no relevant EXIF data is available.
|
||||
*/
|
||||
export function assetExifSummary(asset: Pick<ImmichAsset, 'exifInfo'>): ExifSummary | undefined {
|
||||
const exif = asset.exifInfo;
|
||||
if (!exif) return undefined;
|
||||
const exif = asset.exifInfo;
|
||||
if (!exif) return undefined;
|
||||
|
||||
const camera = exif.model || undefined;
|
||||
const lens = exif.lensModel || undefined;
|
||||
const focalLength = exif.focalLength ? `${exif.focalLength}mm` : undefined;
|
||||
const camera = exif.model || undefined;
|
||||
const lens = exif.lensModel || undefined;
|
||||
const focalLength = exif.focalLength ? `${exif.focalLength}mm` : undefined;
|
||||
|
||||
if (!camera && !lens && !focalLength) return undefined;
|
||||
return { camera, lens, focalLength };
|
||||
if (!camera && !lens && !focalLength) return undefined;
|
||||
return { camera, lens, focalLength };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,8 +78,8 @@ export function assetExifSummary(asset: Pick<ImmichAsset, 'exifInfo'>): ExifSumm
|
||||
* Returns undefined if dimensions are not available.
|
||||
*/
|
||||
export function assetAspectRatio(asset: Pick<ImmichAsset, 'exifInfo'>): number | undefined {
|
||||
const w = asset.exifInfo?.exifImageWidth;
|
||||
const h = asset.exifInfo?.exifImageHeight;
|
||||
if (w && h && h > 0) return w / h;
|
||||
return undefined;
|
||||
const w = asset.exifInfo?.exifImageWidth;
|
||||
const h = asset.exifInfo?.exifImageHeight;
|
||||
if (w && h && h > 0) return w / h;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user