Add content-based hashing to recipe images for proper cache invalidation
while maintaining graceful degradation through dual file storage.
Changes:
- Add imageHash utility with SHA-256 content hashing (8-char)
- Update Recipe model to store hashed filenames in images[0].mediapath
- Modify image upload endpoint to save both hashed and unhashed versions
- Update frontend components to use images[0].mediapath with fallback
- Add migration endpoint to hash existing images (production-only)
- Update image delete/rename endpoints to handle both file versions
Images are now stored as:
- recipe.a1b2c3d4.webp (hashed, cached forever)
- recipe.webp (unhashed, graceful degradation fallback)
Database stores hashed filename for cache busting, while unhashed
version remains on disk for backward compatibility and manual uploads.
Replace id="image" with class="image" in both Card and TitleImgParallax
components to prevent duplicate IDs when multiple instances appear on the
same page. Update TitleImgParallax to use Svelte 5 $props() and $state()
runes instead of legacy export let syntax, and modernize event handlers
to use onload/onclick attributes.
Use $props(), $state(), and $derived() to make image references properly
reactive. This fixes the issue where recipe card images weren't updating
correctly when switching between languages.
- Add embedded translations schema to Recipe model with English support
- Create DeepL translation service with batch translation and change detection
- Build translation approval UI with side-by-side editing for all recipe fields
- Integrate translation workflow into add/edit pages with field comparison
- Create complete English recipe routes at /recipes/* mirroring German structure
- Add language switcher component with hreflang SEO tags
- Support image loading from German short_name for English recipes
- Add English API endpoints for all recipe filters (category, tag, icon, season)
- Include layout with English navigation header for all recipe subroutes
Use the card wrapper pattern with absolute positioned main link and elevated z-index for nested links.
This maintains proper HTML semantics (no nested <a> tags) while allowing category, icon, and tag links to be clickable.
- Replace outer <a> wrapper with <div>
- Add invisible overlay link for main card click area (z-index: 1)
- Elevate nested links (category, tags, icon) with z-index: 10
- Maintain all existing hover effects and accessibility
- Keep semantic HTML structure without nesting <a> tags
- Reduce icon z-index from 10 to 5 to prevent overlap with header
- Adjust favorite indicator position from -0.5em to 0.1em for better spacing
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add heart emoji indicators to recipe cards (top-left positioning)
- Show favorites across all recipe list pages (season, category, icon, tag)
- Create favorites utility functions for server-side data merging
- Convert client-side load files to server-side for session access
- Redesign favorite button with emoji hearts (🖤/❤️) and bottom-right positioning
- Fix randomizer array mutation issue causing card display glitches
- Implement consistent favorite indicators with drop shadows for visibility
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Restored icon to top-right position with absolute positioning
- Added proper circular background with nord0 color
- Set correct dimensions (50px × 50px) and border-radius for circular shape
- Added shadow and hover effects to match original design
- Fixed z-index to ensure icon appears above other elements
- Maintained shake animation on card hover for visual feedback
The icon now appears correctly in the top-right corner with a round
background instead of being positioned at bottom center with transparent
background.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
No longer do we have this weird shift of the description to the right of
the Card until some magical JS is loaded to fix it.
Not yet perfect: The now wrapping a-tag is for some reason still weirdly
sent to client until some js cleans it up. Currently results in a too
large gap which is fixed by local js.
Still TODO: do not blur images if no js present