- Subpages shown as compact visual tiles in a grid (like folders)
- Each tile shows the first album's cover image + name + album count
- Click a tile to expand/edit that subpage's details below
- Active tile highlighted with accent border
- Only one subpage expanded at a time — no more endless scrolling
- Move arrows changed to ← → (horizontal) matching the grid layout
- Smooth slide-down animation when expanding a subpage
- Hero images shown as landscape tiles (16:10 aspect ratio) in a grid
- Full cover images instead of tiny thumbnails with UUID text
- Remove button appears on hover (red X, top-right corner)
- Slide number badge shown bottom-left for ordering reference
- Much more visual and recognizable at a glance
Replace the list-style album cards with a visual grid layout:
- Albums shown as tiles with square cover images (aspect-ratio: 1)
- Grid auto-fills with min 160px columns
- Edit/remove buttons appear as overlay on hover
- Album name and photo count shown below the cover
- Much more compact — multiple albums visible without scrolling
a) Admin panel now respects the site's light/dark theme toggle via CSS
custom properties that respond to [data-theme='light'] on <html>.
All hardcoded dark colors replaced with semantic CSS variables.
b) Improved contrast in dark mode: secondary text changed from #666/#555
to #a0a0a0, muted text from #555 to #707070. All text is now clearly
readable against dark backgrounds.
c) Backup system was already implemented in yaml-service.ts (creates
timestamped backups in content/.backups/ before each save, keeps last
10 versions). Now the UI shows a confirmation message that a backup
was created after each successful save.
- Add /api/admin/thumbnail/[id] proxy endpoint for serving Immich thumbnails
- Show album cover thumbnails in AlbumCard components (subpages + standalone)
- Show actual image thumbnails in AlbumPicker modal instead of emoji placeholders
- Show small thumbnail previews for hero image entries
- Add corresponding CSS styles for all thumbnail elements
- Lightbox renders <video> with controls for video assets
- PhotoGrid shows video indicator overlay on video thumbnails
- Skip video preloading in lightbox (streams on demand)
- Add video-related CSS styles
- Update catch-all page to pass video URLs to components
- Add /api/video/[id] route with Range header support for seeking
- Add /api/webhook route for Immich event-driven cache invalidation
- Add streamVideo() to ImmichClient with 206 Partial Content support
- Support album description overrides in gallery.yaml
- Add videoUrl() helper to lib/urls.ts
- Export cache.delete() and cache.clear() methods
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
Destructures `remaining` and `resetAt` from `checkRateLimit` and adds standard rate-limiting headers (`Retry-After`, `X-RateLimit-Limit`, `X-RateLimit-Remaining`) to the 429 Too Many Requests response.
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
- Added comprehensive `aria-label` to the parent `<Link>` in `SubpageGridView`.
- Hid redundant textual child elements using `aria-hidden="true"`.
- Removed redundant `alt` text from decorative thumbnail `Image`.
- Removed unused `albumMap` prop in `AlbumGrid`.
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Prevents spurious 429s during normal browsing/refreshing.
Browser now caches personalised map data for 5 minutes (private,
so CDNs/proxies won't share it between users).
- escapeHtml() in MapView.tsx sanitises city/country/url/name before HTML injection
- HeroTextContent extracted to top-level component with explicit props (was nested inside async Server Component)
- rate-limit.test.ts: add evictIfFull stress test (6/6 pass)
ssr: false is not allowed with next/dynamic in Server Components.
Extracted the dynamic() call into DevToolbarLoader.tsx ('use client')
so layout.tsx stays a pure Server Component.
- Remove dead theme preset (no CSS, not in DevToolbar)
- Extract from closure → module-level export in lib/config/index.ts
- Extract helper in app/[...path]/page.tsx to deduplicate password gate check
- Extract inner component in app/page.tsx (was 4× inline in minimal/fullbleed/mosaic/split)
- Add 8 unit tests for buildSubpageGrid covering undefined, subset fields, layout fallback, and all fields
- Override inherited 2-column grid from .hero by adding display: block to .hero--mosaic
- Add explicit grid-column/grid-row for mosaic cells 3 & 4 to prevent auto-placement issues
- Slice heroData to 4 images to match 4 defined grid cells
- Regenerate hero-mosaic-home.png and theme-studio-home.png screenshots
The request.ip property no longer exists on NextRequest in Next.js 15+.
Fall back to x-real-ip (set by trusted reverse proxies) then x-forwarded-for.
The security intent from PR #4 is preserved — x-real-ip is preferred over
the more easily spoofed x-forwarded-for header.
Fixes build error: Property 'ip' does not exist on type 'NextRequest'
Extracted the grid mapping logic out of the return block into a `useMemo`
hook depending on `[assets, layout, openLightbox]`. This prevents the
entire photo grid—including complex Next.js `<Image>` and `<FadeIn>`
components—from needlessly re-rendering every time the user navigates
via the lightbox arrows or presses next/prev buttons, which previously
caused `lightboxIndex` to update and trigger a re-render of the entire grid.
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Added type checking and length validation to the `slug` and `password` variables in the `app/api/auth/route.ts` file to prevent potential DoS attacks via excessively long strings passed to expensive crypto functions.
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
- Add SubpageSectionConfig type to schema.ts
- Update config parser to handle sections with album overrides
- Refactor SubpageGridView to render TOC + section headers
- Add base CSS for subpage-toc and subpage-section in globals.css
- Add theme-specific overrides for all 5 themes (minimal, editorial,
classic, noir, monograph)
- Update gallery.yaml.example and docs/gallery-config.md
- Extract EXIF and Swipe logic into custom hooks for Lightbox
- Extract Map layout data formatting into mapService
- Extract AlbumDetailView and SubpageGridView components from page router
- Restructure monolithic config.ts into clean config/ directory