recipes: Swissmilk-inspired hero redesign with parallax and card refresh

- Full-bleed hero image with CSS parallax (scaleY technique matching TitleImgParallax)
- Hero picks random seasonal recipe with hashed image on each visit
- Left-aligned title, subheading, and featured recipe link over the hero
- Category chips with ellipsis collapse on small screens (<600px)
- Search bar anchored to hero/grid boundary regardless of chip count
- CompactCard redesign: 3/2 aspect ratio, rounded corners, subtle hover zoom
- Search component margin adjusted to sit flush at hero boundary
This commit is contained in:
2026-02-16 13:53:48 +01:00
parent c5e33d5573
commit 747e4b5cc6
3 changed files with 562 additions and 78 deletions
@@ -0,0 +1,134 @@
<script lang="ts">
import "$lib/css/shake.css";
let {
recipe,
current_month = 0,
isFavorite = false,
showFavoriteIndicator = false,
loading_strat = "lazy",
routePrefix = '/rezepte'
} = $props();
const img_name = $derived(
recipe.images?.[0]?.mediapath ||
`${recipe.germanShortName || recipe.short_name}.webp`
);
const img_alt = $derived(
recipe.images?.[0]?.alt || recipe.name
);
const isInSeason = $derived(recipe.season?.includes(current_month));
</script>
<style>
.compact-card {
position: relative;
display: flex;
flex-direction: column;
border-radius: var(--radius-card);
overflow: hidden;
background: var(--color-surface);
box-shadow: 0 1px 4px rgba(0,0,0,0.08);
transition: transform var(--transition-normal), box-shadow var(--transition-normal);
cursor: pointer;
}
.compact-card:hover,
.compact-card:focus-within {
transform: translateY(-5px);
box-shadow: 0 8px 24px rgba(0,0,0,0.12);
}
.compact-card:hover .img-wrap img {
transform: scale(1.05);
}
.compact-card:hover .icon,
.compact-card:focus-within .icon {
animation: shake 0.6s;
}
.card-link {
position: absolute;
inset: 0;
z-index: 1;
}
.img-wrap {
aspect-ratio: 3 / 2;
overflow: hidden;
}
.img-wrap img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.4s ease;
}
.info {
position: relative;
padding: 0.8em 0.9em 0.7em;
flex: 1;
}
.name {
font-size: 0.95rem;
font-weight: 600;
line-height: 1.3;
margin: 0;
}
.tags {
display: flex;
flex-wrap: wrap;
gap: 0.3em;
margin-top: 0.5em;
position: relative;
z-index: 2;
}
.tag {
font-size: 0.7rem;
padding: 0.15em 0.6em;
}
.icon {
position: absolute;
top: -0.75em;
right: 0.6em;
width: 2em;
height: 2em;
font-size: 1rem;
background-color: var(--color-primary);
color: white;
z-index: 3;
}
.favorite {
position: absolute;
top: 0.5em;
left: 0.5em;
font-size: 1.1rem;
filter: drop-shadow(0 0 3px rgba(0,0,0,0.8));
z-index: 2;
pointer-events: none;
}
</style>
<div class="compact-card">
<a href="{routePrefix}/{recipe.short_name}" class="card-link" aria-label={recipe.name}></a>
{#if showFavoriteIndicator && isFavorite}
<span class="favorite">❤️</span>
{/if}
<div class="img-wrap">
<img
src="https://bocken.org/static/rezepte/thumb/{img_name}"
alt={img_alt}
loading={loading_strat}
/>
</div>
<div class="info">
{#if isInSeason}
<span class="icon g-icon-badge">{recipe.icon}</span>
{/if}
<p class="name">{@html recipe.name}</p>
{#if recipe.tags?.length}
<div class="tags">
{#each recipe.tags as tag (tag)}
<a href="{routePrefix}/tag/{tag}" class="tag g-tag">{tag}</a>
{/each}
</div>
{/if}
</div>
</div>
+4 -4
View File
@@ -319,15 +319,15 @@ input::placeholder{
}
.search {
width: 500px;
max-width: 85vw;
width: 560px;
max-width: 88vw;
position: relative;
margin: 2.5rem auto 1.2rem;
margin: 0 auto;
font-size: 1.6rem;
display: flex;
align-items: center;
transition: var(--transition-fast);
filter: drop-shadow(0.4em 0.5em 0.4em rgba(0,0,0,0.4))
filter: drop-shadow(0 4px 12px rgba(0,0,0,0.25));
}
.search:hover,