initial implementation of placeholder images, thumbnails and blurring between using sharp
@@ -10,17 +10,19 @@ if(icon_override){
 | 
			
		||||
	current_month = recipe.season[0]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
let isloaded = false
 | 
			
		||||
</script>
 | 
			
		||||
<style>
 | 
			
		||||
.card{
 | 
			
		||||
	flex-shrink: 0;
 | 
			
		||||
	--card-width: 300px;
 | 
			
		||||
	text-decoration: none;
 | 
			
		||||
	position: relative;
 | 
			
		||||
	box-sizing: border-box;
 | 
			
		||||
	font-family: sans-serif;
 | 
			
		||||
	cursor: pointer;
 | 
			
		||||
	height: calc(7/4 * var(--card-width)); /* otherwise card is not initialized at correct size and readjusts when populated*/
 | 
			
		||||
	width: var(--card-width);
 | 
			
		||||
	aspect-ratio: 4/7;
 | 
			
		||||
	border-radius: 20px;
 | 
			
		||||
	background-size: contain;
 | 
			
		||||
	display: flex;
 | 
			
		||||
@@ -28,7 +30,32 @@ if(icon_override){
 | 
			
		||||
	justify-content: end;
 | 
			
		||||
	background-color:  var(--blue);
 | 
			
		||||
	box-shadow: 0em 0em 2em 0.1em rgba(0, 0, 0, 0.3);
 | 
			
		||||
}
 | 
			
		||||
.card #image{
 | 
			
		||||
	width: var(--card-width);
 | 
			
		||||
	height: calc(var(--card-width)*0.85);
 | 
			
		||||
	object-fit: cover;
 | 
			
		||||
	transition: 200ms;
 | 
			
		||||
	backdrop-filter: blur(10px);
 | 
			
		||||
	filter: blur(10px);
 | 
			
		||||
}
 | 
			
		||||
.unblur{
 | 
			
		||||
	filter: blur(0px) !important;
 | 
			
		||||
}
 | 
			
		||||
div:has(#image){
 | 
			
		||||
	width: var(--card-width);
 | 
			
		||||
	background-repeat: no-repeat;
 | 
			
		||||
	background-size: cover;
 | 
			
		||||
	background-position: center;
 | 
			
		||||
	overflow: hidden;
 | 
			
		||||
	border-top-left-radius: inherit;
 | 
			
		||||
	border-top-right-radius: inherit;
 | 
			
		||||
}
 | 
			
		||||
div:has(div #image){
 | 
			
		||||
	height: 100%;
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	width: var(--card-width);
 | 
			
		||||
	top: 0;
 | 
			
		||||
}
 | 
			
		||||
.card:hover,
 | 
			
		||||
.card:focus-within{
 | 
			
		||||
@@ -39,12 +66,6 @@ if(icon_override){
 | 
			
		||||
.card:active{
 | 
			
		||||
	scale: 0.95 0.95;
 | 
			
		||||
}
 | 
			
		||||
.card img{
 | 
			
		||||
	height: 50%;
 | 
			
		||||
	object-fit: cover;
 | 
			
		||||
	border-top-left-radius: inherit;
 | 
			
		||||
	border-top-right-radius: inherit;
 | 
			
		||||
}
 | 
			
		||||
.card .title {
 | 
			
		||||
	position: relative;
 | 
			
		||||
	box-sizing: border-box;
 | 
			
		||||
@@ -129,14 +150,20 @@ if(icon_override){
 | 
			
		||||
.card:hover .icon,
 | 
			
		||||
.card:focus-visible .icon
 | 
			
		||||
{
 | 
			
		||||
	animation:  shake 0.6s
 | 
			
		||||
	animation: shake 0.6s;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
<a class="card {search}" href="/rezepte/{recipe.short_name}" data-tags=[{recipe.tags}]>
 | 
			
		||||
	<div>
 | 
			
		||||
	<div style="background-image:url({'https://new.bocken.org/static/rezepte/placeholder/' + recipe.short_name + '.webp'})">
 | 
			
		||||
	<img class:unblur={isloaded} id=image src={'https://new.bocken.org/static/rezepte/thumb/' + recipe.short_name + '.webp'} loading=lazy  alt="{recipe.alt}" on:load={() => isloaded=true}/>
 | 
			
		||||
	</div>
 | 
			
		||||
	</div>
 | 
			
		||||
	{#if icon_override || recipe.season.includes(current_month)}
 | 
			
		||||
	<a class=icon href="/rezepte/icon/{recipe.icon}">{recipe.icon}</a>
 | 
			
		||||
{/if}
 | 
			
		||||
	<img width=300px height=300px src="/images/{recipe.images[0].mediapath}" alt="{recipe.alt}" />
 | 
			
		||||
 | 
			
		||||
	<div class=title>
 | 
			
		||||
		<a class=category href="/rezepte/category/{recipe.category}" >{recipe.category}</a>
 | 
			
		||||
		<div>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										21
									
								
								src/lib/components/Icon.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,21 @@
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
	import '$lib/css/nordtheme.css';
 | 
			
		||||
	import "$lib/css/shake.css"
 | 
			
		||||
	export let icon : string;
 | 
			
		||||
</script>
 | 
			
		||||
<style>
 | 
			
		||||
	a{
 | 
			
		||||
		font-size: 2rem;
 | 
			
		||||
		text-decoration: none;
 | 
			
		||||
		padding: 0.5em;
 | 
			
		||||
		background-color: var(--nord4);
 | 
			
		||||
		border-radius: 1000px;
 | 
			
		||||
    		box-shadow: 0em 0em 0.5em 0.2em rgba(0, 0, 0, 0.2);
 | 
			
		||||
	}
 | 
			
		||||
	a:hover{
 | 
			
		||||
		--angle: 15deg;
 | 
			
		||||
		animation: shake 0.5s ease forwards;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
<a href="/rezepte/icon/{icon}" {...$$restProps} >{icon}</a>
 | 
			
		||||
@@ -27,7 +27,7 @@ h4{
 | 
			
		||||
{#if data.ingredients}
 | 
			
		||||
<div class=ingredients>
 | 
			
		||||
{#if data.portions}
 | 
			
		||||
	<h4>Portionen:</h4>
 | 
			
		||||
	<h3>Portionen:</h3>
 | 
			
		||||
	{data.portions}
 | 
			
		||||
{/if}
 | 
			
		||||
<h2>Zutaten</h2>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,7 @@
 | 
			
		||||
<script>
 | 
			
		||||
	export let src
 | 
			
		||||
	export let placeholder_src
 | 
			
		||||
	let isloaded=false
 | 
			
		||||
</script>
 | 
			
		||||
<style>
 | 
			
		||||
:root {
 | 
			
		||||
@@ -46,7 +48,7 @@
 | 
			
		||||
  z-index: -10;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.image-container img {
 | 
			
		||||
#image{
 | 
			
		||||
  display: block;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  top: 0;
 | 
			
		||||
@@ -54,8 +56,10 @@
 | 
			
		||||
  z-index: -1;
 | 
			
		||||
  height: max(60dvh,600px);
 | 
			
		||||
  object-fit: cover;
 | 
			
		||||
  /*object-position: top;*/
 | 
			
		||||
  object-position: 50% 20%;
 | 
			
		||||
  backdrop-filter: blur(20px);
 | 
			
		||||
  transition: 50ms;
 | 
			
		||||
  filter: blur(20px);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.image-container::after {
 | 
			
		||||
@@ -68,9 +72,33 @@
 | 
			
		||||
:global(h1){
 | 
			
		||||
	width: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.placeholder{
 | 
			
		||||
	background-repeat: no-repeat;
 | 
			
		||||
	background-size: cover;
 | 
			
		||||
	background-position: 50% 20%;
 | 
			
		||||
	position: absolute;
 | 
			
		||||
        width: min(1000px, 100dvw);
 | 
			
		||||
  	height: max(60dvh,600px);
 | 
			
		||||
	z-index: -2;
 | 
			
		||||
}
 | 
			
		||||
div:has(.placeholder){
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	top: 0;
 | 
			
		||||
        width: min(1000px, 100dvw);
 | 
			
		||||
  	height: max(60dvh,600px);
 | 
			
		||||
	overflow: hidden;
 | 
			
		||||
}
 | 
			
		||||
.unblur#image{
 | 
			
		||||
	filter: blur(0px) !important;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
<section class="section">
 | 
			
		||||
    <figure class="image-container"><img {src} alt=""/></figure>
 | 
			
		||||
    <figure class="image-container">
 | 
			
		||||
    	<div>
 | 
			
		||||
		<div class=placeholder style="background-image:url({placeholder_src})" >
 | 
			
		||||
			<img class:unblur={isloaded} id=image {src} on:load={() => isloaded=true} alt=""/>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
	</figure>
 | 
			
		||||
    <div class=content><slot></slot></div>
 | 
			
		||||
</section>
 | 
			
		||||
 
 | 
			
		||||
@@ -38,7 +38,7 @@ const RecipeSchema = new mongoose.Schema(
 | 
			
		||||
     		      steps: [String]}],
 | 
			
		||||
     preamble : String,
 | 
			
		||||
     addendum : String,
 | 
			
		||||
     },
 | 
			
		||||
     }, {timestamps: true}
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
export const Recipe = mongoose.model("Recipe", RecipeSchema);
 | 
			
		||||
 
 | 
			
		||||
@@ -3,18 +3,40 @@ import path from 'path'
 | 
			
		||||
import type { RequestHandler } from '@sveltejs/kit';
 | 
			
		||||
import { BEARER_TOKEN } from '$env/static/private'
 | 
			
		||||
import { error } from '@sveltejs/kit';
 | 
			
		||||
import { Image } from '../../../../models/Image';
 | 
			
		||||
import { IMAGE_DIR } from '$env/static/private'
 | 
			
		||||
import sharp from 'sharp';
 | 
			
		||||
 | 
			
		||||
export const POST =  (async ({ request })  => {
 | 
			
		||||
    const data = await request.json();
 | 
			
		||||
    const filePath = path.join(
 | 
			
		||||
            process.cwd(),
 | 
			
		||||
            "static",
 | 
			
		||||
            "images",
 | 
			
		||||
	    data.filename as string
 | 
			
		||||
        );
 | 
			
		||||
    const file = data.image;
 | 
			
		||||
    if(data.bearer === BEARER_TOKEN){
 | 
			
		||||
    	writeFileSync(filePath, file, 'base64');
 | 
			
		||||
	let full_res = new Buffer.from(data.image, 'base64')
 | 
			
		||||
	// reduce image size if over 500KB
 | 
			
		||||
	const MAX_SIZE_KB = 500
 | 
			
		||||
	//const metadata = await sharp(full_res).metadata()
 | 
			
		||||
	////reduce image size if larger than 500KB
 | 
			
		||||
	//if(metadata.size > MAX_SIZE_KB*1000){
 | 
			
		||||
	//	full_res = sharp(full_res).
 | 
			
		||||
	//		webp( { quality: 70})
 | 
			
		||||
	//		.toBuffer()
 | 
			
		||||
	//}
 | 
			
		||||
	await sharp(full_res)
 | 
			
		||||
		.toFormat('webp')
 | 
			
		||||
		.toFile(path.join(IMAGE_DIR,
 | 
			
		||||
				  "full",
 | 
			
		||||
				  data.name + ".webp"))
 | 
			
		||||
	await sharp(full_res)
 | 
			
		||||
		.resize({ width: 800})
 | 
			
		||||
		.toFormat('webp')
 | 
			
		||||
		.toFile(path.join(IMAGE_DIR,
 | 
			
		||||
			  "thumb",
 | 
			
		||||
			  data.name + ".webp"))
 | 
			
		||||
	await sharp(full_res)
 | 
			
		||||
		.resize({ width: 20})
 | 
			
		||||
		.toFormat('webp')
 | 
			
		||||
		.toFile(path.join(IMAGE_DIR,
 | 
			
		||||
			  "placeholder",
 | 
			
		||||
			  data.name + ".webp"))
 | 
			
		||||
	return new Response(JSON.stringify({msg: "Added image successfully"}),{
 | 
			
		||||
			    status: 200,
 | 
			
		||||
  	});
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								static/favicon.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.5 KiB  | 
| 
		 Before Width: | Height: | Size: 209 KiB  | 
| 
		 Before Width: | Height: | Size: 229 KiB  | 
| 
		 Before Width: | Height: | Size: 190 KiB  | 
| 
		 Before Width: | Height: | Size: 502 KiB  | 
| 
		 Before Width: | Height: | Size: 388 KiB  | 
| 
		 Before Width: | Height: | Size: 264 KiB  | 
| 
		 Before Width: | Height: | Size: 127 KiB  | 
| 
		 Before Width: | Height: | Size: 236 KiB  | 
| 
		 Before Width: | Height: | Size: 458 KiB  | 
| 
		 Before Width: | Height: | Size: 439 KiB  | 
| 
		 Before Width: | Height: | Size: 228 KiB  | 
| 
		 Before Width: | Height: | Size: 489 KiB  | 
| 
		 Before Width: | Height: | Size: 300 KiB  | 
| 
		 Before Width: | Height: | Size: 295 KiB  | 
| 
		 Before Width: | Height: | Size: 122 KiB  | 
| 
		 Before Width: | Height: | Size: 263 KiB  | 
| 
		 Before Width: | Height: | Size: 354 KiB  | 
| 
		 Before Width: | Height: | Size: 419 KiB  | 
| 
		 Before Width: | Height: | Size: 284 KiB  | 
| 
		 Before Width: | Height: | Size: 200 KiB  | 
| 
		 Before Width: | Height: | Size: 270 KiB  | 
| 
		 Before Width: | Height: | Size: 495 KiB  | 
| 
		 Before Width: | Height: | Size: 270 KiB  | 
| 
		 Before Width: | Height: | Size: 315 KiB  | 
| 
		 Before Width: | Height: | Size: 360 KiB  | 
| 
		 Before Width: | Height: | Size: 408 KiB  | 
| 
		 Before Width: | Height: | Size: 289 KiB  | 
| 
		 Before Width: | Height: | Size: 236 KiB  | 
| 
		 Before Width: | Height: | Size: 383 KiB  | 
| 
		 Before Width: | Height: | Size: 300 KiB  | 
| 
		 Before Width: | Height: | Size: 286 KiB  | 
| 
		 Before Width: | Height: | Size: 300 KiB  | 
| 
		 Before Width: | Height: | Size: 582 KiB  | 
| 
		 Before Width: | Height: | Size: 326 KiB  | 
| 
		 Before Width: | Height: | Size: 98 KiB  | 
| 
		 Before Width: | Height: | Size: 192 KiB  | 
| 
		 Before Width: | Height: | Size: 329 KiB  | 
| 
		 Before Width: | Height: | Size: 260 KiB  | 
| 
		 Before Width: | Height: | Size: 207 KiB  | 
| 
		 Before Width: | Height: | Size: 176 KiB  | 
| 
		 Before Width: | Height: | Size: 127 KiB  | 
| 
		 Before Width: | Height: | Size: 162 KiB  | 
| 
		 Before Width: | Height: | Size: 214 KiB  | 
| 
		 Before Width: | Height: | Size: 326 KiB  | 
| 
		 Before Width: | Height: | Size: 263 KiB  | 
| 
		 Before Width: | Height: | Size: 188 KiB  | 
| 
		 Before Width: | Height: | Size: 246 KiB  | 
| 
		 Before Width: | Height: | Size: 511 KiB  | 
| 
		 Before Width: | Height: | Size: 283 KiB  | 
| 
		 Before Width: | Height: | Size: 395 KiB  | 
| 
		 Before Width: | Height: | Size: 135 KiB  | 
| 
		 Before Width: | Height: | Size: 231 KiB  | 
| 
		 Before Width: | Height: | Size: 288 KiB  | 
| 
		 Before Width: | Height: | Size: 93 KiB  | 
| 
		 Before Width: | Height: | Size: 224 KiB  | 
| 
		 Before Width: | Height: | Size: 286 KiB  | 
| 
		 Before Width: | Height: | Size: 204 KiB  | 
| 
		 Before Width: | Height: | Size: 287 KiB  | 
| 
		 Before Width: | Height: | Size: 220 KiB  | 
| 
		 Before Width: | Height: | Size: 510 KiB  | 
| 
		 Before Width: | Height: | Size: 230 KiB  | 
| 
		 Before Width: | Height: | Size: 315 KiB  | 
| 
		 Before Width: | Height: | Size: 145 KiB  | 
| 
		 Before Width: | Height: | Size: 144 KiB  | 
| 
		 Before Width: | Height: | Size: 226 KiB  | 
| 
		 Before Width: | Height: | Size: 275 KiB  | 
| 
		 Before Width: | Height: | Size: 625 KiB  | 
| 
		 Before Width: | Height: | Size: 461 KiB  | 
| 
		 Before Width: | Height: | Size: 180 KiB  | 
| 
		 Before Width: | Height: | Size: 295 KiB  | 
| 
		 Before Width: | Height: | Size: 452 KiB  | 
| 
		 Before Width: | Height: | Size: 45 KiB  | 
| 
		 Before Width: | Height: | Size: 266 KiB  | 
| 
		 Before Width: | Height: | Size: 326 KiB  | 
| 
		 Before Width: | Height: | Size: 312 KiB  | 
| 
		 Before Width: | Height: | Size: 167 KiB  | 
| 
		 Before Width: | Height: | Size: 220 KiB  | 
| 
		 Before Width: | Height: | Size: 270 KiB  | 
| 
		 Before Width: | Height: | Size: 258 KiB  | 
| 
		 Before Width: | Height: | Size: 236 KiB  | 
| 
		 Before Width: | Height: | Size: 339 KiB  | 
| 
		 Before Width: | Height: | Size: 521 KiB  | 
| 
		 Before Width: | Height: | Size: 360 KiB  | 
| 
		 Before Width: | Height: | Size: 354 KiB  | 
| 
		 Before Width: | Height: | Size: 197 KiB  | 
| 
		 Before Width: | Height: | Size: 196 KiB  | 
| 
		 Before Width: | Height: | Size: 497 KiB  | 
| 
		 Before Width: | Height: | Size: 225 KiB  | 
| 
		 Before Width: | Height: | Size: 330 KiB  | 
| 
		 Before Width: | Height: | Size: 330 KiB  | 
| 
		 Before Width: | Height: | Size: 409 KiB  | 
| 
		 Before Width: | Height: | Size: 58 KiB  | 
| 
		 Before Width: | Height: | Size: 372 KiB  |