feat: align recipe edit page with viewer design
Replace CardAdd with EditTitleImgParallax so /rezepte/edit/[name] mirrors the hero-parallax layout of /rezepte/[name]. Add Lucide icons and a width-constrained info-card grid to CreateStepList's additional info section. Refactor the English translation view to use semantic theme vars, side-by-side German/English field comparison, and a card aesthetic matching the rest of the site. Fix portions binding bug where the shared portions store was being written by both language ingredient lists. CreateIngredientList now accepts a bindable portions prop; the English list uses it with useStore=false to stay isolated from the German value.
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "homepage",
|
||||
"version": "1.27.0",
|
||||
"version": "1.28.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -13,17 +13,39 @@ import { do_on_key } from '$lib/components/recipes/do_on_key.js'
|
||||
import { portions } from '$lib/js/portions_store.js'
|
||||
import BaseRecipeSelector from '$lib/components/recipes/BaseRecipeSelector.svelte'
|
||||
|
||||
let portions_local = $state<string | undefined>()
|
||||
portions.subscribe((p: any) => {
|
||||
portions_local = p
|
||||
let {
|
||||
lang = 'de' as 'de' | 'en',
|
||||
ingredients = $bindable(),
|
||||
portions: portionsProp = $bindable<string | undefined>(undefined),
|
||||
useStore = true,
|
||||
} = $props<{
|
||||
lang?: 'de' | 'en',
|
||||
ingredients: any,
|
||||
portions?: string,
|
||||
useStore?: boolean,
|
||||
}>();
|
||||
|
||||
let portions_local = $state<string | undefined>(portionsProp)
|
||||
|
||||
if (useStore) {
|
||||
portions.subscribe((p: any) => {
|
||||
portions_local = p
|
||||
});
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (!useStore) {
|
||||
portions_local = portionsProp ?? ''
|
||||
}
|
||||
});
|
||||
|
||||
export function set_portions(){
|
||||
portions.update((_p: any) => portions_local)
|
||||
if (useStore) {
|
||||
portions.update((_p: any) => portions_local)
|
||||
}
|
||||
portionsProp = portions_local
|
||||
}
|
||||
|
||||
let { lang = 'de' as 'de' | 'en', ingredients = $bindable() } = $props<{ lang?: 'de' | 'en', ingredients: any }>();
|
||||
|
||||
// Translation strings
|
||||
const t: Record<string, Record<string, string>> = {
|
||||
de: {
|
||||
|
||||
@@ -4,6 +4,7 @@ import Pen from '$lib/assets/icons/Pen.svelte'
|
||||
import Cross from '$lib/assets/icons/Cross.svelte'
|
||||
import Plus from '$lib/assets/icons/Plus.svelte'
|
||||
import Check from '$lib/assets/icons/Check.svelte'
|
||||
import { Timer, Wheat, Croissant, Flame, CookingPot, UtensilsCrossed } from '@lucide/svelte';
|
||||
|
||||
import "$lib/css/action_button.css"
|
||||
|
||||
@@ -592,7 +593,7 @@ ol li::marker{
|
||||
.instructions{
|
||||
flex-basis: 0;
|
||||
flex-grow: 2;
|
||||
background-color: var(--nord5);
|
||||
background-color: var(--color-bg-secondary);
|
||||
padding-block: 1rem;
|
||||
padding-inline: 2rem;
|
||||
}
|
||||
@@ -605,24 +606,83 @@ ol li::marker{
|
||||
}
|
||||
|
||||
.additional_info{
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
|
||||
gap: 0.75rem;
|
||||
margin-block: 1rem;
|
||||
}
|
||||
.info-card{
|
||||
padding: 0.75rem 1rem;
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--shadow-sm);
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
.info-card:focus-within{
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
.info-card-baking{
|
||||
grid-column: span 2;
|
||||
}
|
||||
.info-card h3{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
margin: 0 0 0.25rem 0;
|
||||
font-size: var(--text-sm);
|
||||
color: var(--color-text-secondary);
|
||||
cursor: default;
|
||||
user-select: auto;
|
||||
}
|
||||
.info-value{
|
||||
display: block;
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-primary);
|
||||
outline: none;
|
||||
min-height: 1.2em;
|
||||
border-bottom: 1px dashed transparent;
|
||||
transition: border-color 200ms ease;
|
||||
}
|
||||
.info-value:hover,
|
||||
.info-value:focus{
|
||||
border-bottom-color: var(--color-border);
|
||||
}
|
||||
.info-value:empty::before,
|
||||
.info-value span:empty::before{
|
||||
content: attr(data-placeholder);
|
||||
color: var(--color-text-tertiary);
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
}
|
||||
.baking-row{
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1em;
|
||||
align-items: baseline;
|
||||
gap: 0.35em;
|
||||
}
|
||||
.additional_info > *{
|
||||
flex-grow: 0;
|
||||
overflow: hidden;
|
||||
padding: 1em;
|
||||
background-color: #FAFAFE;
|
||||
box-shadow: 0.3em 0.3em 1em 0.2em rgba(0,0,0,0.3);
|
||||
/*max-width: 30%*/
|
||||
.baking-row > span[contenteditable]{
|
||||
outline: none;
|
||||
padding: 0 0.15em;
|
||||
border-bottom: 1px dashed transparent;
|
||||
transition: border-color 200ms ease;
|
||||
min-width: 2ch;
|
||||
}
|
||||
.additional_info > div > *:not(h4){
|
||||
line-height: 2em;
|
||||
.baking-row > span[contenteditable]:hover,
|
||||
.baking-row > span[contenteditable]:focus{
|
||||
border-bottom-color: var(--color-border);
|
||||
}
|
||||
h4{
|
||||
line-height: 1em;
|
||||
margin-block: 0;
|
||||
.baking-sep{
|
||||
color: var(--color-text-secondary);
|
||||
font-weight: 400;
|
||||
}
|
||||
@media (max-width: 560px){
|
||||
.info-card-baking{
|
||||
grid-column: span 1;
|
||||
}
|
||||
}
|
||||
.button_subtle{
|
||||
padding: 0em;
|
||||
@@ -641,21 +701,6 @@ h3{
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
.additional_info p[contenteditable]{
|
||||
display: inline;
|
||||
padding: 0.25em 1em;
|
||||
border: 2px solid grey;
|
||||
border-radius: var(--radius-pill);
|
||||
}
|
||||
.additional_info div:has(p[contenteditable]){
|
||||
transition: var(--transition-normal);
|
||||
display: inline;
|
||||
}
|
||||
.additional_info div:has(p[contenteditable]):hover,
|
||||
.additional_info div:has(p[contenteditable]):focus-within
|
||||
{
|
||||
transform: scale(1.1, 1.1);
|
||||
}
|
||||
@media screen and (max-width: 500px){
|
||||
dialog h2{
|
||||
margin-top: 2rem;
|
||||
@@ -664,20 +709,6 @@ h3{
|
||||
width: 80%;
|
||||
}
|
||||
}
|
||||
@media (prefers-color-scheme: dark){
|
||||
:global(:root:not([data-theme="light"])) .additional_info div {
|
||||
background-color: var(--accent-dark);
|
||||
}
|
||||
:global(:root:not([data-theme="light"])) .instructions {
|
||||
background-color: var(--nord6-dark);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .additional_info div {
|
||||
background-color: var(--accent-dark);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .instructions {
|
||||
background-color: var(--nord6-dark);
|
||||
}
|
||||
.button_arrow{
|
||||
fill: var(--nord1);
|
||||
}
|
||||
@@ -768,30 +799,41 @@ h3{
|
||||
</style>
|
||||
|
||||
<div class=instructions>
|
||||
<div class=additional_info>
|
||||
|
||||
<div><h4>{t[lang].preparation}</h4>
|
||||
<p contenteditable bind:innerText={add_info.preparation}></p>
|
||||
<div class="additional_info">
|
||||
<div class="info-card">
|
||||
<h3><Timer size={16} />{t[lang].preparation}</h3>
|
||||
<p class="info-value" contenteditable="plaintext-only" bind:innerText={add_info.preparation} data-placeholder="z.B. 30 min"></p>
|
||||
</div>
|
||||
|
||||
|
||||
<div><h4>{t[lang].bulkFermentation}</h4>
|
||||
<p contenteditable bind:innerText={add_info.fermentation.bulk}></p>
|
||||
<div class="info-card">
|
||||
<h3><Wheat size={16} />{t[lang].bulkFermentation}</h3>
|
||||
<p class="info-value" contenteditable="plaintext-only" bind:innerText={add_info.fermentation.bulk} data-placeholder="z.B. 4 h"></p>
|
||||
</div>
|
||||
|
||||
<div><h4>{t[lang].finalFermentation}</h4>
|
||||
<p contenteditable bind:innerText={add_info.fermentation.final}></p>
|
||||
<div class="info-card">
|
||||
<h3><Croissant size={16} />{t[lang].finalFermentation}</h3>
|
||||
<p class="info-value" contenteditable="plaintext-only" bind:innerText={add_info.fermentation.final} data-placeholder="z.B. 1 h"></p>
|
||||
</div>
|
||||
|
||||
<div><h4>{t[lang].baking}</h4>
|
||||
<div><p bind:innerText={add_info.baking.length} contenteditable placeholder="40 min..."></p></div> bei <div><p bind:innerText={add_info.baking.temperature} contenteditable placeholder=200...></p></div> °C <div><p bind:innerText={add_info.baking.mode} contenteditable placeholder="Ober-/Unterhitze..."></p></div></div>
|
||||
|
||||
<div><h4>{t[lang].cooking}</h4>
|
||||
<p contenteditable bind:innerText={add_info.cooking}></p>
|
||||
<div class="info-card info-card-baking">
|
||||
<h3><Flame size={16} />{t[lang].baking}</h3>
|
||||
<div class="info-value baking-row">
|
||||
<span contenteditable="plaintext-only" bind:innerText={add_info.baking.length} data-placeholder="40 min"></span>
|
||||
<span class="baking-sep">bei</span>
|
||||
<span contenteditable="plaintext-only" bind:innerText={add_info.baking.temperature} data-placeholder="200"></span>
|
||||
<span class="baking-sep">°C</span>
|
||||
<span contenteditable="plaintext-only" bind:innerText={add_info.baking.mode} data-placeholder="Ober-/Unterhitze"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div><h4>{t[lang].totalTime}</h4>
|
||||
<p contenteditable bind:innerText={add_info.total_time}></p>
|
||||
<div class="info-card">
|
||||
<h3><CookingPot size={16} />{t[lang].cooking}</h3>
|
||||
<p class="info-value" contenteditable="plaintext-only" bind:innerText={add_info.cooking} data-placeholder="z.B. 20 min"></p>
|
||||
</div>
|
||||
|
||||
<div class="info-card">
|
||||
<h3><UtensilsCrossed size={16} />{t[lang].totalTime}</h3>
|
||||
<p class="info-value" contenteditable="plaintext-only" bind:innerText={add_info.total_time} data-placeholder="z.B. 1 h"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,557 @@
|
||||
<script lang="ts">
|
||||
import Cross from '$lib/assets/icons/Cross.svelte';
|
||||
import { toast } from '$lib/js/toast.svelte';
|
||||
import { onMount, type Snippet } from 'svelte';
|
||||
|
||||
type CardData = {
|
||||
icon?: string;
|
||||
category?: string;
|
||||
name?: string;
|
||||
description?: string;
|
||||
tags?: string[];
|
||||
};
|
||||
|
||||
type Props = {
|
||||
card_data: CardData;
|
||||
image_preview_url: string;
|
||||
selected_image_file: File | null;
|
||||
color?: string;
|
||||
titleExtras?: Snippet;
|
||||
children?: Snippet;
|
||||
};
|
||||
|
||||
let {
|
||||
card_data = $bindable(),
|
||||
image_preview_url = $bindable(''),
|
||||
selected_image_file = $bindable<File | null>(null),
|
||||
color = '',
|
||||
titleExtras,
|
||||
children
|
||||
}: Props = $props();
|
||||
|
||||
const ALLOWED_MIME = ['image/webp', 'image/jpeg', 'image/jpg', 'image/png'];
|
||||
const MAX_SIZE = 5 * 1024 * 1024;
|
||||
|
||||
let fileInput: HTMLInputElement;
|
||||
let new_tag = $state('');
|
||||
|
||||
if (!card_data.tags) card_data.tags = [];
|
||||
|
||||
function handleFileSelect(event: Event) {
|
||||
const input = event.currentTarget as HTMLInputElement;
|
||||
const file = input.files?.[0];
|
||||
if (!file) return;
|
||||
if (!ALLOWED_MIME.includes(file.type)) {
|
||||
toast.error('Ungültiger Dateityp. Bitte JPEG, PNG oder WebP hochladen.');
|
||||
input.value = '';
|
||||
return;
|
||||
}
|
||||
if (file.size > MAX_SIZE) {
|
||||
toast.error(
|
||||
`Datei zu gross. Maximum 5 MB, deine Datei ${(file.size / 1024 / 1024).toFixed(2)} MB.`
|
||||
);
|
||||
input.value = '';
|
||||
return;
|
||||
}
|
||||
if (image_preview_url?.startsWith('blob:')) URL.revokeObjectURL(image_preview_url);
|
||||
image_preview_url = URL.createObjectURL(file);
|
||||
selected_image_file = file;
|
||||
}
|
||||
|
||||
function clearSelectedImage() {
|
||||
if (image_preview_url?.startsWith('blob:')) URL.revokeObjectURL(image_preview_url);
|
||||
image_preview_url = '';
|
||||
selected_image_file = null;
|
||||
if (fileInput) fileInput.value = '';
|
||||
}
|
||||
|
||||
function triggerFilePicker() {
|
||||
fileInput?.click();
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (image_preview_url && !image_preview_url.startsWith('blob:')) {
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
if (img.naturalWidth === 150 && img.naturalHeight === 150) image_preview_url = '';
|
||||
};
|
||||
img.onerror = () => {
|
||||
image_preview_url = '';
|
||||
};
|
||||
img.src = image_preview_url;
|
||||
}
|
||||
});
|
||||
|
||||
function addTag() {
|
||||
const t = new_tag.trim();
|
||||
if (t && !card_data.tags!.includes(t)) {
|
||||
card_data.tags = [...card_data.tags!, t];
|
||||
}
|
||||
new_tag = '';
|
||||
}
|
||||
function removeTag(tag: string) {
|
||||
card_data.tags = card_data.tags!.filter((x) => x !== tag);
|
||||
}
|
||||
function onTagKey(e: KeyboardEvent) {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
addTag();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<section class="section">
|
||||
<figure class="image-container">
|
||||
<button
|
||||
type="button"
|
||||
class="image-wrap"
|
||||
onclick={triggerFilePicker}
|
||||
style:background-color={color || 'var(--color-bg-elevated)'}
|
||||
aria-label={image_preview_url ? 'Bild ersetzen' : 'Bild hochladen'}
|
||||
>
|
||||
{#if image_preview_url}
|
||||
<img class="image" src={image_preview_url} alt="" />
|
||||
{/if}
|
||||
<div class="upload-overlay" class:empty={!image_preview_url}>
|
||||
<svg
|
||||
class="camera"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 512 512"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
d="M149.1 64.8L138.7 96H64C28.7 96 0 124.7 0 160V416c0 35.3 28.7 64 64 64H448c35.3 0 64-28.7 64-64V160c0-35.3-28.7-64-64-64H373.3L362.9 64.8C356.4 45.2 338.1 32 317.4 32H194.6c-20.7 0-39 13.2-45.5 32.8zM256 192a96 96 0 1 1 0 192 96 96 0 1 1 0-192z"
|
||||
/>
|
||||
</svg>
|
||||
<span class="upload-label">
|
||||
{image_preview_url ? 'Bild ersetzen' : 'Bild hochladen'}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
{#if selected_image_file}
|
||||
<button
|
||||
type="button"
|
||||
class="clear-img"
|
||||
onclick={clearSelectedImage}
|
||||
title="Auswahl verwerfen"
|
||||
aria-label="Auswahl verwerfen"
|
||||
>
|
||||
<Cross fill="white" width="1.25rem" height="1.25rem" />
|
||||
</button>
|
||||
{/if}
|
||||
<input
|
||||
bind:this={fileInput}
|
||||
type="file"
|
||||
accept="image/webp,image/jpeg,image/jpg,image/png"
|
||||
onchange={handleFileSelect}
|
||||
class="file-input"
|
||||
tabindex="-1"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</figure>
|
||||
|
||||
<div class="content">
|
||||
<div class="title" style="view-transition-name: recipe-title">
|
||||
<input
|
||||
class="category g-pill g-btn-dark"
|
||||
placeholder="Kategorie…"
|
||||
bind:value={card_data.category}
|
||||
aria-label="Kategorie"
|
||||
/>
|
||||
|
||||
<input
|
||||
class="icon g-icon-badge"
|
||||
placeholder="🥫"
|
||||
bind:value={card_data.icon}
|
||||
aria-label="Icon"
|
||||
maxlength="4"
|
||||
/>
|
||||
|
||||
<input
|
||||
class="name"
|
||||
placeholder="Rezeptname…"
|
||||
bind:value={card_data.name}
|
||||
aria-label="Rezeptname"
|
||||
/>
|
||||
|
||||
<p
|
||||
class="description"
|
||||
contenteditable="plaintext-only"
|
||||
bind:innerText={card_data.description}
|
||||
data-placeholder="Kurzbeschreibung…"
|
||||
aria-label="Kurzbeschreibung"
|
||||
></p>
|
||||
|
||||
<h2 class="section-label">Stichwörter</h2>
|
||||
<div class="tags center">
|
||||
{#each card_data.tags ?? [] as tag (tag)}
|
||||
<button
|
||||
type="button"
|
||||
class="g-tag tag-chip"
|
||||
onclick={() => removeTag(tag)}
|
||||
aria-label={`Stichwort ${tag} entfernen`}
|
||||
>
|
||||
<span>{tag}</span><span class="x" aria-hidden="true">×</span>
|
||||
</button>
|
||||
{/each}
|
||||
<label class="g-tag tag-add">
|
||||
<span aria-hidden="true">+</span>
|
||||
<input
|
||||
type="text"
|
||||
bind:value={new_tag}
|
||||
onkeydown={onTagKey}
|
||||
onblur={addTag}
|
||||
placeholder="neu…"
|
||||
size="1"
|
||||
aria-label="Neues Stichwort"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{#if titleExtras}{@render titleExtras()}{/if}
|
||||
</div>
|
||||
|
||||
{#if children}{@render children()}{/if}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.section {
|
||||
--scale: 0.3;
|
||||
margin-bottom: -20vh;
|
||||
margin-top: calc(-3.5rem - 12px - env(safe-area-inset-top, 0px));
|
||||
transform-origin: center top;
|
||||
transform: scaleY(calc(1 - var(--scale)));
|
||||
}
|
||||
@media (prefers-reduced-motion) {
|
||||
.section {
|
||||
--scale: 0;
|
||||
}
|
||||
}
|
||||
.section > :global(*) {
|
||||
transform-origin: center top;
|
||||
transform: scaleY(calc(1 / (1 - var(--scale))));
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
margin: 30vh auto 0;
|
||||
}
|
||||
|
||||
.image-container {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
height: max(55dvh, 540px);
|
||||
z-index: -10;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.image-wrap {
|
||||
all: unset;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: min(calc(1000px + 2rem), 100dvw);
|
||||
height: max(65dvh, 640px);
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.image {
|
||||
display: block;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
object-position: 50% 20%;
|
||||
}
|
||||
|
||||
.upload-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.75rem;
|
||||
color: white;
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(0, 0, 0, 0.15) 0%,
|
||||
rgba(0, 0, 0, 0.45) 100%
|
||||
);
|
||||
opacity: 0;
|
||||
transition: opacity 200ms ease;
|
||||
font-size: 1rem;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
}
|
||||
.image-wrap:hover .upload-overlay,
|
||||
.image-wrap:focus-visible .upload-overlay {
|
||||
opacity: 1;
|
||||
}
|
||||
.upload-overlay.empty {
|
||||
opacity: 1;
|
||||
background: color-mix(in srgb, var(--color-primary) 65%, transparent);
|
||||
}
|
||||
.camera {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
fill: white;
|
||||
filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.4));
|
||||
}
|
||||
.upload-label {
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.clear-img {
|
||||
position: absolute;
|
||||
top: calc(1rem + env(safe-area-inset-top, 0px));
|
||||
right: 1rem;
|
||||
background: rgba(0, 0, 0, 0.55);
|
||||
border: none;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
border-radius: 50%;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
cursor: pointer;
|
||||
z-index: 5;
|
||||
transition:
|
||||
transform 150ms ease,
|
||||
background 150ms ease;
|
||||
backdrop-filter: blur(6px);
|
||||
}
|
||||
.clear-img:hover,
|
||||
.clear-img:focus-visible {
|
||||
background: var(--red);
|
||||
transform: scale(1.08);
|
||||
}
|
||||
|
||||
.file-input {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.title {
|
||||
position: relative;
|
||||
width: min(800px, 80vw);
|
||||
margin-inline: auto;
|
||||
background-color: var(--color-bg-tertiary);
|
||||
padding: 1rem 2rem 1.5rem;
|
||||
translate: 0 1px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.category {
|
||||
--size: 1.75rem;
|
||||
position: absolute;
|
||||
top: calc(-1 * var(--size));
|
||||
left: calc(-1.5 * var(--size));
|
||||
font-size: var(--size);
|
||||
padding: calc(var(--size) * 2 / 3);
|
||||
border: none;
|
||||
outline: none;
|
||||
max-width: min(16rem, 70%);
|
||||
text-align: center;
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
.category::placeholder {
|
||||
color: var(--color-text-tertiary);
|
||||
font-style: italic;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.category:hover,
|
||||
.category:focus-visible {
|
||||
scale: 1.04;
|
||||
}
|
||||
|
||||
.icon {
|
||||
position: absolute;
|
||||
top: -1em;
|
||||
right: -0.75em;
|
||||
padding: 0.5em;
|
||||
font-size: 1.5rem;
|
||||
background-color: var(--color-bg-tertiary);
|
||||
text-align: center;
|
||||
border: none;
|
||||
outline: none;
|
||||
width: 2.6rem;
|
||||
height: 2.6rem;
|
||||
box-sizing: border-box;
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
.icon::placeholder {
|
||||
opacity: 0.5;
|
||||
}
|
||||
.icon:hover,
|
||||
.icon:focus-visible {
|
||||
scale: 1.15;
|
||||
}
|
||||
|
||||
.name {
|
||||
all: unset;
|
||||
display: block;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
padding-block: 0.5em;
|
||||
margin: 0;
|
||||
font-size: 3rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.1;
|
||||
color: var(--color-text-primary);
|
||||
overflow-wrap: break-word;
|
||||
text-wrap: balance;
|
||||
border-bottom: 1px dashed transparent;
|
||||
transition: border-color 200ms ease;
|
||||
}
|
||||
.name::placeholder {
|
||||
color: var(--color-text-tertiary);
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
}
|
||||
.name:hover,
|
||||
.name:focus-visible {
|
||||
border-bottom-color: var(--color-border);
|
||||
}
|
||||
|
||||
.description {
|
||||
text-align: center;
|
||||
margin: -0.25em 0 1.75em;
|
||||
padding: 0.25em 0.5em;
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 1.05rem;
|
||||
min-height: 1.2em;
|
||||
outline: none;
|
||||
border-bottom: 1px dashed transparent;
|
||||
transition: border-color 200ms ease;
|
||||
}
|
||||
.description:hover,
|
||||
.description:focus {
|
||||
border-bottom-color: var(--color-border);
|
||||
}
|
||||
.description:empty::before {
|
||||
content: attr(data-placeholder);
|
||||
color: var(--color-text-tertiary);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.section-label {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
margin-block: 1.25rem 0.5rem;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.5em;
|
||||
margin-block: 0.5rem 1rem;
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
.tags.center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.tag-chip {
|
||||
all: unset;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.35em;
|
||||
padding: 0.25em 0.9em;
|
||||
border-radius: var(--radius-pill);
|
||||
background: var(--color-bg-elevated);
|
||||
color: var(--color-text-primary);
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
.tag-chip .x {
|
||||
opacity: 0.55;
|
||||
font-size: 1.1em;
|
||||
line-height: 1;
|
||||
}
|
||||
.tag-chip:hover,
|
||||
.tag-chip:focus-visible {
|
||||
background: var(--red);
|
||||
color: white;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
.tag-chip:hover .x,
|
||||
.tag-chip:focus-visible .x {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tag-add {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.35em;
|
||||
background: transparent;
|
||||
border: 1px dashed var(--color-border);
|
||||
color: var(--color-text-secondary);
|
||||
padding: 0.15em 0.7em;
|
||||
border-radius: var(--radius-pill);
|
||||
}
|
||||
.tag-add input {
|
||||
all: unset;
|
||||
min-width: 6ch;
|
||||
color: var(--color-text-primary);
|
||||
font-size: inherit;
|
||||
}
|
||||
.tag-add input::placeholder {
|
||||
color: var(--color-text-tertiary);
|
||||
font-style: italic;
|
||||
}
|
||||
.tag-add:focus-within {
|
||||
border-style: solid;
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
.title {
|
||||
width: 100%;
|
||||
}
|
||||
.icon {
|
||||
right: 1rem;
|
||||
top: -1.75rem;
|
||||
}
|
||||
.category {
|
||||
left: 1rem;
|
||||
top: calc(var(--size) * -1.5);
|
||||
}
|
||||
.name {
|
||||
font-size: 2.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
:global(::view-transition-new(recipe-title)) {
|
||||
animation: slide-up 0.35s ease both;
|
||||
}
|
||||
:global(::view-transition-old(recipe-title)) {
|
||||
animation: slide-down 0.25s ease both;
|
||||
}
|
||||
@keyframes slide-up {
|
||||
from {
|
||||
transform: translateY(var(--title-slide, 100vh));
|
||||
}
|
||||
}
|
||||
@keyframes slide-down {
|
||||
to {
|
||||
transform: translateY(var(--title-slide, 100vh));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -358,76 +358,79 @@
|
||||
oncancelled?.();
|
||||
}
|
||||
|
||||
// Get status badge color
|
||||
function getStatusColor(status: string): string {
|
||||
switch (status) {
|
||||
case 'approved': return 'var(--nord14)';
|
||||
case 'pending': return 'var(--nord13)';
|
||||
case 'needs_update': return 'var(--nord12)';
|
||||
default: return 'var(--nord9)';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.translation-approval {
|
||||
margin: 2rem 0;
|
||||
padding: 1.5rem;
|
||||
border: 2px solid var(--nord9);
|
||||
border-radius: 8px;
|
||||
background: var(--nord1);
|
||||
margin: 3rem auto 2rem;
|
||||
padding: 2rem clamp(1rem, 3vw, 2.5rem);
|
||||
max-width: 1200px;
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-card);
|
||||
box-shadow: var(--shadow-md);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@media(prefers-color-scheme: light) {
|
||||
:global(:root:not([data-theme="dark"])) .translation-approval {
|
||||
background: var(--nord6);
|
||||
border-color: var(--nord4);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="light"]) .translation-approval {
|
||||
background: var(--nord6);
|
||||
border-color: var(--nord4);
|
||||
.translation-approval::before{
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 4px;
|
||||
background: linear-gradient(90deg, var(--color-primary), var(--blue), var(--green));
|
||||
border-radius: var(--radius-card) var(--radius-card) 0 0;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1.5rem;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 1.75rem;
|
||||
padding-bottom: 1rem;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.header h3 {
|
||||
margin: 0;
|
||||
color: var(--nord6);
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: var(--color-text-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
@media(prefers-color-scheme: light) {
|
||||
:global(:root:not([data-theme="dark"])) .header h3 {
|
||||
color: var(--nord0);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="light"]) .header h3 {
|
||||
color: var(--nord0);
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 16px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
color: var(--nord0);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
padding: 0.35rem 0.9rem;
|
||||
border-radius: var(--radius-pill);
|
||||
font-size: 0.8rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
}
|
||||
.status-badge::before{
|
||||
content: '';
|
||||
width: 0.45rem;
|
||||
height: 0.45rem;
|
||||
border-radius: 50%;
|
||||
background: currentColor;
|
||||
}
|
||||
|
||||
.status-pending {
|
||||
background: var(--nord13);
|
||||
background: color-mix(in srgb, var(--orange) 18%, transparent);
|
||||
color: var(--orange);
|
||||
}
|
||||
|
||||
.status-approved {
|
||||
background: var(--nord14);
|
||||
background: color-mix(in srgb, var(--green) 18%, transparent);
|
||||
color: var(--green);
|
||||
}
|
||||
|
||||
.status-needs_update {
|
||||
background: var(--nord12);
|
||||
background: color-mix(in srgb, var(--red) 18%, transparent);
|
||||
color: var(--red);
|
||||
}
|
||||
|
||||
.translation-preview {
|
||||
@@ -436,10 +439,9 @@
|
||||
}
|
||||
|
||||
.field-section {
|
||||
margin-bottom: 1.5rem;
|
||||
max-width: 800px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-bottom: 1.25rem;
|
||||
max-width: 900px;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.list-wrapper {
|
||||
@@ -451,160 +453,251 @@
|
||||
justify-content: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 700px) {
|
||||
.list-wrapper {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
/* Fix button icon visibility in dark mode */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .list-wrapper :global(svg) {
|
||||
fill: white !important;
|
||||
}
|
||||
:global(:root:not([data-theme="light"])) .list-wrapper :global(.button_arrow) {
|
||||
fill: var(--nord4) !important;
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .list-wrapper :global(svg) {
|
||||
fill: white !important;
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .list-wrapper :global(.button_arrow) {
|
||||
fill: var(--nord4) !important;
|
||||
}
|
||||
|
||||
.column-header {
|
||||
.preview-title{
|
||||
margin: 0 0 1.5rem;
|
||||
font-size: 1.15rem;
|
||||
font-weight: 700;
|
||||
font-size: 1.1rem;
|
||||
color: var(--nord8);
|
||||
margin-bottom: 1rem;
|
||||
color: var(--color-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 2px solid var(--nord9);
|
||||
}
|
||||
|
||||
.field-group {
|
||||
margin-bottom: 1.5rem;
|
||||
border-bottom: 2px solid var(--color-primary);
|
||||
max-width: 900px;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
gap: 0.75rem;
|
||||
justify-content: flex-end;
|
||||
margin-top: 1.5rem;
|
||||
margin-top: 2rem;
|
||||
padding-top: 1.5rem;
|
||||
border-top: 1px solid var(--color-border);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 0.75rem 1.5rem;
|
||||
padding: 0.65rem 1.25rem;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
border-radius: var(--radius-pill);
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
transition: var(--transition-fast);
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--nord14);
|
||||
color: var(--nord0);
|
||||
button:hover:not(:disabled){
|
||||
transform: scale(1.03);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: var(--nord15);
|
||||
button:active:not(:disabled){
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: var(--nord9);
|
||||
color: var(--nord6);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: var(--nord10);
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: var(--nord11);
|
||||
color: var(--nord6);
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background: var(--nord12);
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--color-primary);
|
||||
color: var(--color-text-on-primary);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
.btn-primary:hover:not(:disabled) {
|
||||
background: var(--color-primary-hover);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: var(--color-bg-elevated);
|
||||
color: var(--color-text-primary);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
.btn-secondary:hover:not(:disabled) {
|
||||
background: var(--color-bg-tertiary);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: transparent;
|
||||
color: var(--red);
|
||||
border: 1px solid color-mix(in srgb, var(--red) 40%, transparent);
|
||||
}
|
||||
.btn-danger:hover:not(:disabled) {
|
||||
background: var(--red);
|
||||
color: white;
|
||||
border-color: var(--red);
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 3px solid var(--nord4);
|
||||
border-top-color: var(--nord14);
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border: 2px solid var(--color-border);
|
||||
border-top-color: var(--color-primary);
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-right: 0.5rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.error-message {
|
||||
background: var(--nord11);
|
||||
color: var(--nord6);
|
||||
padding: 1rem;
|
||||
border-radius: 4px;
|
||||
margin: 1rem 0;
|
||||
.notice {
|
||||
padding: 1rem 1.25rem;
|
||||
border-radius: var(--radius-md);
|
||||
margin-bottom: 1.25rem;
|
||||
font-size: 0.95rem;
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
align-items: flex-start;
|
||||
border: 1px solid;
|
||||
}
|
||||
.notice-body{ flex: 1; min-width: 0; }
|
||||
.notice-body strong{ display: block; margin-bottom: 0.2rem; }
|
||||
|
||||
.notice-error {
|
||||
background: color-mix(in srgb, var(--red) 10%, var(--color-surface));
|
||||
color: var(--color-text-primary);
|
||||
border-color: color-mix(in srgb, var(--red) 40%, transparent);
|
||||
}
|
||||
.notice-warn {
|
||||
background: color-mix(in srgb, var(--orange) 10%, var(--color-surface));
|
||||
color: var(--color-text-primary);
|
||||
border-color: color-mix(in srgb, var(--orange) 40%, transparent);
|
||||
}
|
||||
.notice-info {
|
||||
background: color-mix(in srgb, var(--blue) 10%, var(--color-surface));
|
||||
color: var(--color-text-primary);
|
||||
border-color: color-mix(in srgb, var(--blue) 40%, transparent);
|
||||
}
|
||||
|
||||
.validation-errors {
|
||||
background: var(--nord12);
|
||||
color: var(--nord0);
|
||||
padding: 1rem;
|
||||
border-radius: 4px;
|
||||
margin: 1rem 0;
|
||||
.notice ul {
|
||||
margin: 0.35rem 0 0;
|
||||
padding-left: 1.25rem;
|
||||
}
|
||||
|
||||
.validation-errors ul {
|
||||
margin: 0.5rem 0 0 0;
|
||||
padding-left: 1.5rem;
|
||||
.notice ul li{
|
||||
margin: 0.2rem 0;
|
||||
}
|
||||
|
||||
.changed-fields {
|
||||
background: var(--nord13);
|
||||
color: var(--nord0);
|
||||
padding: 0.75rem;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.changed-fields strong {
|
||||
font-weight: 700;
|
||||
.notice a{
|
||||
color: var(--color-primary);
|
||||
text-decoration: underline;
|
||||
margin-left: 0.4rem;
|
||||
}
|
||||
|
||||
.idle-state {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
color: var(--nord4);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
@media(prefers-color-scheme: light) {
|
||||
:global(:root:not([data-theme="dark"])) .idle-state {
|
||||
color: var(--nord2);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="light"]) .idle-state {
|
||||
color: var(--nord2);
|
||||
}
|
||||
|
||||
.idle-state p {
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
|
||||
.approved-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: var(--radius-pill);
|
||||
background: color-mix(in srgb, var(--green) 18%, transparent);
|
||||
color: var(--green);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* Images section */
|
||||
.images-section {
|
||||
margin: 2rem auto;
|
||||
max-width: 900px;
|
||||
padding: 1.25rem;
|
||||
background: var(--color-bg-secondary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
}
|
||||
.images-section h4{
|
||||
margin: 0 0 1rem;
|
||||
font-size: 1rem;
|
||||
color: var(--color-text-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.image-card{
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
padding: 1rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
.image-card:last-child{ margin-bottom: 0; }
|
||||
.image-card-row{
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.image-card-row img{
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
object-fit: cover;
|
||||
border-radius: var(--radius-sm);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.image-card-body{ flex: 1; min-width: 0; }
|
||||
.image-path{
|
||||
margin: 0 0 0.75rem;
|
||||
font-size: 0.8rem;
|
||||
color: var(--color-text-tertiary);
|
||||
font-family: monospace;
|
||||
word-break: break-all;
|
||||
}
|
||||
.image-grid{
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
@media (max-width: 560px){
|
||||
.image-card-row{ flex-direction: column; }
|
||||
.image-card-row img{ width: 100%; height: 140px; }
|
||||
.image-grid{ grid-template-columns: 1fr; gap: 0.5rem; }
|
||||
}
|
||||
.image-field label{
|
||||
display: block;
|
||||
margin-bottom: 0.25rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
color: var(--color-text-secondary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
.image-field input{
|
||||
width: 100%;
|
||||
padding: 0.45rem 0.6rem;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--color-bg-tertiary);
|
||||
color: var(--color-text-primary);
|
||||
font-size: 0.9rem;
|
||||
box-sizing: border-box;
|
||||
font-family: inherit;
|
||||
transition: border-color 150ms ease;
|
||||
}
|
||||
.image-field input:focus{
|
||||
outline: none;
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
.image-field input:disabled{
|
||||
background: var(--color-bg-secondary);
|
||||
color: var(--color-text-tertiary);
|
||||
cursor: default;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="translation-approval">
|
||||
@@ -620,64 +713,73 @@ button:disabled {
|
||||
</div>
|
||||
|
||||
{#if errorMessage}
|
||||
<div class="error-message">
|
||||
<strong>Error:</strong> {errorMessage}
|
||||
<div class="notice notice-error">
|
||||
<div class="notice-body">
|
||||
<strong>Error</strong>
|
||||
{errorMessage}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if validationErrors.length > 0}
|
||||
<div class="validation-errors">
|
||||
<strong>Please fix the following errors:</strong>
|
||||
<ul>
|
||||
{#each validationErrors as error}
|
||||
<li>{error}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<div class="notice notice-warn">
|
||||
<div class="notice-body">
|
||||
<strong>Please fix the following errors:</strong>
|
||||
<ul>
|
||||
{#each validationErrors as error}
|
||||
<li>{error}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if isEditMode && changedFields.length > 0}
|
||||
<div class="changed-fields">
|
||||
<strong>Changed fields:</strong> {changedFields.join(', ')}
|
||||
<br>
|
||||
<small>Only these fields will be re-translated if you use auto-translate.</small>
|
||||
<div class="notice notice-info">
|
||||
<div class="notice-body">
|
||||
<strong>Changed fields: {changedFields.join(', ')}</strong>
|
||||
<small>Only these fields will be re-translated if you use auto-translate.</small>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if checkingBaseRecipes}
|
||||
<div style="background: var(--nord9); color: var(--nord6); padding: 1rem; border-radius: 4px; margin-bottom: 1.5rem; text-align: center;">
|
||||
<p>Checking if referenced base recipes are translated...</p>
|
||||
<div class="notice notice-info">
|
||||
<div class="notice-body"><span class="loading-spinner"></span>Checking if referenced base recipes are translated…</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if untranslatedBaseRecipes.length > 0}
|
||||
<div style="background: var(--nord12); color: var(--nord0); padding: 1.5rem; border-radius: 4px; margin-bottom: 1.5rem;">
|
||||
<h4 style="margin-top: 0;">⚠️ Base Recipes Need Translation</h4>
|
||||
<p>The following base recipes need to be translated to English before you can translate this recipe:</p>
|
||||
<ul style="margin: 1rem 0;">
|
||||
{#each untranslatedBaseRecipes as baseRecipe}
|
||||
<li>
|
||||
<strong>{baseRecipe.name}</strong>
|
||||
<a href="/de/edit/{baseRecipe.shortName}" target="_blank" rel="noopener noreferrer" style="margin-left: 0.5rem; color: var(--nord10);">
|
||||
Open in new tab →
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<p style="margin-bottom: 0;">
|
||||
<button class="btn-secondary" onclick={syncBaseRecipeReferences}>
|
||||
Re-check Base Recipes
|
||||
</button>
|
||||
</p>
|
||||
<div class="notice notice-warn">
|
||||
<div class="notice-body">
|
||||
<strong>Base recipes need translation</strong>
|
||||
The following base recipes need to be translated to English before you can translate this recipe:
|
||||
<ul>
|
||||
{#each untranslatedBaseRecipes as baseRecipe}
|
||||
<li>
|
||||
<strong>{baseRecipe.name}</strong>
|
||||
<a href="/de/edit/{baseRecipe.shortName}" target="_blank" rel="noopener noreferrer">
|
||||
Open in new tab →
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<div style="margin-top: 0.75rem;">
|
||||
<button class="btn-secondary" onclick={syncBaseRecipeReferences}>
|
||||
Re-check base recipes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if translationState === 'idle'}
|
||||
<div style="background: var(--nord13); color: var(--nord0); padding: 1rem; border-radius: 4px; margin-bottom: 1.5rem; text-align: center;">
|
||||
<strong>Preview (Not yet translated)</strong>
|
||||
<p style="margin: 0.5rem 0;">The structure below shows what will be translated. Click "Auto-translate" to generate English translation.</p>
|
||||
<div class="notice notice-info">
|
||||
<div class="notice-body">
|
||||
<strong>Preview — not yet translated</strong>
|
||||
The structure below shows what will be translated. Click “Auto-translate” to generate the English translation.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/if}
|
||||
|
||||
{#if translationState === 'translating'}
|
||||
@@ -691,7 +793,7 @@ button:disabled {
|
||||
|
||||
{#if translationState === 'idle' || translationState === 'preview' || translationState === 'approved'}
|
||||
<div class="translation-preview">
|
||||
<h3 style="margin-bottom: 1.5rem; color: var(--nord8);">🇬🇧 English Translation</h3>
|
||||
<h3 class="preview-title">Side-by-side review</h3>
|
||||
|
||||
<!-- Basic Fields -->
|
||||
<div class="field-section">
|
||||
@@ -780,77 +882,59 @@ button:disabled {
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if editableEnglish?.portions !== undefined}
|
||||
<div class="field-section">
|
||||
<TranslationFieldComparison
|
||||
label="Portions"
|
||||
germanValue={germanData.portions || ''}
|
||||
englishValue={editableEnglish.portions}
|
||||
fieldName="portions"
|
||||
readonly={false}
|
||||
onchange={(value) => handleFieldChange(value, 'portions')}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Images Section -->
|
||||
{#if germanData.images && germanData.images.length > 0}
|
||||
<div class="field-section" style="background-color: var(--nord13); padding: 1rem; border-radius: 5px; margin-top: 1.5rem;">
|
||||
<h4 style="margin-top: 0; color: var(--nord0);">🖼️ Images - English Alt Texts & Captions</h4>
|
||||
<section class="images-section">
|
||||
<h4>Images — Alt texts & captions</h4>
|
||||
{#each germanData.images as germanImage, i}
|
||||
{#if editableEnglish.images && editableEnglish.images[i]}
|
||||
<div style="background-color: white; padding: 1rem; margin-bottom: 1rem; border-radius: 5px; border: 2px solid var(--nord9);">
|
||||
<div style="display: flex; gap: 1rem; align-items: start;">
|
||||
<div class="image-card">
|
||||
<div class="image-card-row">
|
||||
<img
|
||||
src="https://bocken.org/static/rezepte/thumb/{germanImage.mediapath}"
|
||||
alt={germanImage.alt || 'Recipe image'}
|
||||
style="width: 100px; height: 100px; object-fit: cover; border-radius: 5px;"
|
||||
/>
|
||||
<div style="flex: 1;">
|
||||
<p style="margin: 0 0 0.5rem 0; font-size: 0.85rem; color: var(--nord3);"><strong>Image {i + 1}:</strong> {germanImage.mediapath}</p>
|
||||
<div class="image-card-body">
|
||||
<p class="image-path"><strong>Image {i + 1}:</strong> {germanImage.mediapath}</p>
|
||||
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-bottom: 0.75rem;">
|
||||
<div>
|
||||
<label for="german-alt-{i}" style="display: block; margin-bottom: 0.25rem; font-weight: bold; font-size: 0.85rem; color: var(--nord0);">🇩🇪 German Alt-Text:</label>
|
||||
<div class="image-grid">
|
||||
<div class="image-field">
|
||||
<label for="german-alt-{i}">German Alt-Text</label>
|
||||
<input
|
||||
id="german-alt-{i}"
|
||||
type="text"
|
||||
value={germanImage.alt || ''}
|
||||
disabled
|
||||
style="width: 100%; padding: 0.4rem; border: 1px solid var(--nord4); border-radius: 3px; background-color: var(--nord5); color: var(--nord2); font-size: 0.85rem;"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="english-alt-{i}" style="display: block; margin-bottom: 0.25rem; font-weight: bold; font-size: 0.85rem; color: var(--nord0);">🇬🇧 English Alt-Text:</label>
|
||||
<div class="image-field">
|
||||
<label for="english-alt-{i}">English Alt-Text</label>
|
||||
<input
|
||||
id="english-alt-{i}"
|
||||
type="text"
|
||||
bind:value={editableEnglish.images[i].alt}
|
||||
placeholder="English image description for screen readers"
|
||||
style="width: 100%; padding: 0.4rem; border: 1px solid var(--nord8); border-radius: 3px; font-size: 0.85rem;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1rem;">
|
||||
<div>
|
||||
<label for="german-caption-{i}" style="display: block; margin-bottom: 0.25rem; font-weight: bold; font-size: 0.85rem; color: var(--nord0);">🇩🇪 German Caption:</label>
|
||||
<div class="image-grid">
|
||||
<div class="image-field">
|
||||
<label for="german-caption-{i}">German Caption</label>
|
||||
<input
|
||||
id="german-caption-{i}"
|
||||
type="text"
|
||||
value={germanImage.caption || ''}
|
||||
disabled
|
||||
style="width: 100%; padding: 0.4rem; border: 1px solid var(--nord4); border-radius: 3px; background-color: var(--nord5); color: var(--nord2); font-size: 0.85rem;"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="english-caption-{i}" style="display: block; margin-bottom: 0.25rem; font-weight: bold; font-size: 0.85rem; color: var(--nord0);">🇬🇧 English Caption:</label>
|
||||
<div class="image-field">
|
||||
<label for="english-caption-{i}">English Caption</label>
|
||||
<input
|
||||
id="english-caption-{i}"
|
||||
type="text"
|
||||
bind:value={editableEnglish.images[i].caption}
|
||||
placeholder="English caption (optional)"
|
||||
style="width: 100%; padding: 0.4rem; border: 1px solid var(--nord8); border-radius: 3px; font-size: 0.85rem;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -863,7 +947,7 @@ button:disabled {
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
<!-- Ingredients and Instructions in two-column layout -->
|
||||
@@ -871,7 +955,12 @@ button:disabled {
|
||||
<div class="list-wrapper">
|
||||
<div>
|
||||
{#if editableEnglish?.ingredients}
|
||||
<CreateIngredientList bind:ingredients={editableEnglish.ingredients} lang="en" />
|
||||
<CreateIngredientList
|
||||
bind:ingredients={editableEnglish.ingredients}
|
||||
bind:portions={editableEnglish.portions}
|
||||
useStore={false}
|
||||
lang="en"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
<div>
|
||||
@@ -926,7 +1015,7 @@ button:disabled {
|
||||
Approve Translation
|
||||
</button>
|
||||
{:else}
|
||||
<span style="color: var(--nord14); font-weight: 700;">✓ Translation Approved</span>
|
||||
<span class="approved-pill" aria-live="polite">Translation Approved</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -31,66 +31,86 @@
|
||||
}
|
||||
|
||||
.field-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-weight: 600;
|
||||
color: var(--nord4);
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
color: var(--color-text-secondary);
|
||||
margin-bottom: 0.4rem;
|
||||
font-size: var(--text-sm);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
letter-spacing: 0.06em;
|
||||
}
|
||||
.field-label::before{
|
||||
content: '';
|
||||
width: 0.35rem;
|
||||
height: 0.35rem;
|
||||
border-radius: 50%;
|
||||
background: var(--color-primary);
|
||||
}
|
||||
|
||||
@media(prefers-color-scheme: light) {
|
||||
:global(:root:not([data-theme="dark"])) .field-label {
|
||||
color: var(--nord2);
|
||||
.pair {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 0.75rem;
|
||||
align-items: stretch;
|
||||
}
|
||||
@media (max-width: 640px) {
|
||||
.pair {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="light"]) .field-label {
|
||||
color: var(--nord2);
|
||||
}
|
||||
|
||||
.lang-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.35rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.lang-chip {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
color: var(--color-text-tertiary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
|
||||
.field-value {
|
||||
padding: 0.75rem;
|
||||
background: var(--nord0);
|
||||
border-radius: 4px;
|
||||
color: var(--nord6);
|
||||
border: 1px solid var(--nord3);
|
||||
min-height: 3rem;
|
||||
}
|
||||
|
||||
@media(prefers-color-scheme: light) {
|
||||
:global(:root:not([data-theme="dark"])) .field-value {
|
||||
background: var(--nord5);
|
||||
color: var(--nord0);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="light"]) .field-value {
|
||||
background: var(--nord5);
|
||||
color: var(--nord0);
|
||||
border-color: var(--nord3);
|
||||
padding: 0.6rem 0.75rem;
|
||||
background: var(--color-bg-tertiary);
|
||||
border-radius: var(--radius-md);
|
||||
color: var(--color-text-primary);
|
||||
border: 1px solid var(--color-border);
|
||||
min-height: 2.6rem;
|
||||
font-size: 0.95rem;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
font-family: inherit;
|
||||
transition: border-color 150ms ease, box-shadow 150ms ease;
|
||||
}
|
||||
|
||||
.field-value.readonly {
|
||||
opacity: 0.8;
|
||||
background: var(--color-bg-secondary);
|
||||
color: var(--color-text-secondary);
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
input.field-value,
|
||||
textarea.field-value {
|
||||
width: 100%;
|
||||
font-family: inherit;
|
||||
font-size: 1rem;
|
||||
box-sizing: border-box;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
input.field-value:focus,
|
||||
textarea.field-value:focus {
|
||||
outline: 2px solid var(--nord14);
|
||||
border-color: var(--nord14);
|
||||
outline: none;
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-primary) 25%, transparent);
|
||||
}
|
||||
|
||||
textarea.field-value {
|
||||
min-height: 6rem;
|
||||
min-height: 5rem;
|
||||
}
|
||||
|
||||
.readonly-text {
|
||||
@@ -100,63 +120,57 @@ textarea.field-value {
|
||||
|
||||
:global(.readonly-text strong) {
|
||||
display: block;
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--nord8);
|
||||
margin-top: 0.75rem;
|
||||
margin-bottom: 0.35rem;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
:global(.readonly-text strong:first-child) {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
:global(.readonly-text ul),
|
||||
:global(.readonly-text ol) {
|
||||
margin: 0.5rem 0;
|
||||
padding-left: 1.5rem;
|
||||
margin: 0.4rem 0;
|
||||
padding-left: 1.25rem;
|
||||
}
|
||||
|
||||
:global(.readonly-text li) {
|
||||
margin: 0.25rem 0;
|
||||
color: var(--nord4);
|
||||
}
|
||||
|
||||
@media(prefers-color-scheme: light) {
|
||||
:global(:root:not([data-theme="dark"]) .readonly-text strong) {
|
||||
color: var(--nord10);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="dark"]) .readonly-text li) {
|
||||
color: var(--nord2);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="light"]) :global(.readonly-text strong) {
|
||||
color: var(--nord10);
|
||||
}
|
||||
:global(:root[data-theme="light"]) :global(.readonly-text li) {
|
||||
color: var(--nord2);
|
||||
margin: 0.2rem 0;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="field-comparison">
|
||||
<div class="field-label">{label}</div>
|
||||
{#if readonly}
|
||||
<div class="field-value readonly readonly-text">
|
||||
{germanValue || '(empty)'}
|
||||
<div class="pair">
|
||||
<div class="lang-column">
|
||||
<span class="lang-chip">Deutsch</span>
|
||||
<div class="field-value readonly readonly-text">
|
||||
{germanValue || '—'}
|
||||
</div>
|
||||
</div>
|
||||
{:else if multiline}
|
||||
<textarea
|
||||
class="field-value"
|
||||
value={englishValue}
|
||||
oninput={handleInput}
|
||||
placeholder="Enter {label.toLowerCase()}..."
|
||||
></textarea>
|
||||
{:else}
|
||||
<input
|
||||
type="text"
|
||||
class="field-value"
|
||||
value={englishValue}
|
||||
oninput={handleInput}
|
||||
placeholder="Enter {label.toLowerCase()}..."
|
||||
/>
|
||||
{/if}
|
||||
<div class="lang-column">
|
||||
<span class="lang-chip">English</span>
|
||||
{#if readonly}
|
||||
<div class="field-value readonly readonly-text">
|
||||
{englishValue || '—'}
|
||||
</div>
|
||||
{:else if multiline}
|
||||
<textarea
|
||||
class="field-value"
|
||||
value={englishValue}
|
||||
oninput={handleInput}
|
||||
placeholder="Enter {label.toLowerCase()}…"
|
||||
></textarea>
|
||||
{:else}
|
||||
<input
|
||||
type="text"
|
||||
class="field-value"
|
||||
value={englishValue}
|
||||
oninput={handleInput}
|
||||
placeholder="Enter {label.toLowerCase()}…"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import TranslationApproval from '$lib/components/recipes/TranslationApproval.svelte';
|
||||
import GenerateAltTextButton from '$lib/components/recipes/GenerateAltTextButton.svelte';
|
||||
import EditRecipeNote from '$lib/components/recipes/EditRecipeNote.svelte';
|
||||
import CardAdd from '$lib/components/recipes/CardAdd.svelte';
|
||||
import EditTitleImgParallax from '$lib/components/recipes/EditTitleImgParallax.svelte';
|
||||
import CreateIngredientList from '$lib/components/recipes/CreateIngredientList.svelte';
|
||||
import CreateStepList from '$lib/components/recipes/CreateStepList.svelte';
|
||||
import Toggle from '$lib/components/Toggle.svelte';
|
||||
@@ -419,20 +419,116 @@
|
||||
</script>
|
||||
|
||||
<style>
|
||||
input {
|
||||
/* ===== Below-hero content wrapper mirrors viewer's .wrapper_wrapper trick:
|
||||
a full-width backdrop behind the editor content hides the sticky hero image. */
|
||||
.below-hero {
|
||||
--bg-color: var(--color-bg-primary);
|
||||
position: relative;
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem 1rem 4rem;
|
||||
}
|
||||
.below-hero::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 100vw;
|
||||
background-color: var(--bg-color);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
h3 {
|
||||
text-align: center;
|
||||
font-size: 1.15rem;
|
||||
letter-spacing: 0.02em;
|
||||
margin-block: 1.25rem 0.75rem;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
/* ===== Meta row under the hero: URL + base-recipe toggle ===== */
|
||||
.meta-row {
|
||||
display: flex;
|
||||
gap: 1.5rem 2rem;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
margin-block: 0.5rem 2rem;
|
||||
padding-bottom: 1.5rem;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
.url-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.35rem;
|
||||
font-size: 0.72rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
color: var(--color-text-secondary);
|
||||
font-weight: 700;
|
||||
}
|
||||
.url-field input {
|
||||
display: block;
|
||||
border: unset;
|
||||
margin: 1rem auto;
|
||||
padding: 0.5em 1em;
|
||||
border: 1px solid var(--color-border);
|
||||
margin: 0;
|
||||
padding: 0.55em 1.1em;
|
||||
border-radius: var(--radius-pill);
|
||||
background-color: var(--nord4);
|
||||
font-size: 1.1rem;
|
||||
background-color: var(--color-bg-tertiary);
|
||||
color: var(--color-text-primary);
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
letter-spacing: 0;
|
||||
text-transform: none;
|
||||
min-width: 16rem;
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
input:hover,
|
||||
input:focus-visible {
|
||||
scale: 1.05 1.05;
|
||||
.url-field input:hover,
|
||||
.url-field input:focus-visible {
|
||||
border-color: var(--color-primary);
|
||||
outline: none;
|
||||
}
|
||||
.toggle-field {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
/* ===== Title-card extras (inside hero card) ===== */
|
||||
.section-label {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
margin-block: 1.25rem 0.5rem;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
.season-wrapper {
|
||||
margin-block: 0.25rem 0.75rem;
|
||||
}
|
||||
.preamble {
|
||||
margin: 0.5rem 0 0.25rem;
|
||||
padding: 1em 1.25em;
|
||||
background: var(--color-bg-primary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
color: var(--color-text-primary);
|
||||
font-size: 1rem;
|
||||
min-height: 3em;
|
||||
outline: none;
|
||||
transition: border-color 200ms ease;
|
||||
}
|
||||
.preamble:focus,
|
||||
.preamble:hover {
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
.preamble:empty::before {
|
||||
content: attr(data-placeholder);
|
||||
color: var(--color-text-tertiary);
|
||||
font-style: italic;
|
||||
}
|
||||
.note-slot {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
/* ===== Ingredients + Instructions two-col ===== */
|
||||
.list_wrapper {
|
||||
margin-inline: auto;
|
||||
display: flex;
|
||||
@@ -440,79 +536,58 @@
|
||||
max-width: 1000px;
|
||||
gap: 2rem;
|
||||
justify-content: center;
|
||||
margin-block: 2.5rem;
|
||||
}
|
||||
@media screen and (max-width: 700px) {
|
||||
.list_wrapper {
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
}
|
||||
h1 {
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.title_container {
|
||||
max-width: 1000px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-inline: auto;
|
||||
}
|
||||
.title {
|
||||
position: relative;
|
||||
width: min(800px, 80vw);
|
||||
margin-block: 2rem;
|
||||
margin-inline: auto;
|
||||
background-color: var(--nord6);
|
||||
padding: 1rem 2rem;
|
||||
}
|
||||
.title p {
|
||||
border: 2px solid var(--nord1);
|
||||
border-radius: 10000px;
|
||||
padding: 0.5em 1em;
|
||||
font-size: 1.1rem;
|
||||
transition: var(--transition-normal);
|
||||
}
|
||||
.title p:hover,
|
||||
.title p:focus-within {
|
||||
scale: 1.02 1.02;
|
||||
}
|
||||
.addendum {
|
||||
font-size: 1.1rem;
|
||||
max-width: 90%;
|
||||
margin-inline: auto;
|
||||
border: 2px solid var(--nord1);
|
||||
border-radius: 45px;
|
||||
padding: 1em 1em;
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
.addendum:hover,
|
||||
.addendum:focus-within {
|
||||
scale: 1.02 1.02;
|
||||
}
|
||||
|
||||
/* ===== Addendum ===== */
|
||||
.addendum_wrapper {
|
||||
max-width: 1000px;
|
||||
margin: 2.5rem auto;
|
||||
}
|
||||
.addendum {
|
||||
font-size: 1.05rem;
|
||||
max-width: min(720px, 100%);
|
||||
margin-inline: auto;
|
||||
padding: 1em 1.25em;
|
||||
background: var(--color-bg-primary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
color: var(--color-text-primary);
|
||||
outline: none;
|
||||
transition: border-color 200ms ease;
|
||||
}
|
||||
h3 {
|
||||
text-align: center;
|
||||
.addendum:hover,
|
||||
.addendum:focus-visible {
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .title {
|
||||
background-color: var(--nord6-dark);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .title {
|
||||
background-color: var(--nord6-dark);
|
||||
}
|
||||
|
||||
/* ===== Form-size / Backform ===== */
|
||||
.form-size-section {
|
||||
max-width: 600px;
|
||||
margin: 1rem auto;
|
||||
margin: 2rem auto;
|
||||
text-align: center;
|
||||
}
|
||||
.form-size-section h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
.form-size-controls {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
gap: 1rem 1.25rem;
|
||||
justify-content: center;
|
||||
margin-bottom: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
.form-size-controls label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4em;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
.form-size-inputs {
|
||||
display: flex;
|
||||
@@ -521,29 +596,39 @@
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.form-size-inputs input[type="number"] {
|
||||
.form-size-inputs input[type='number'] {
|
||||
width: 4em;
|
||||
display: inline;
|
||||
padding: 0.3em 0.5em;
|
||||
margin: 0 0.3em;
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-bg-tertiary);
|
||||
color: var(--color-text-primary);
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
background: var(--nord11);
|
||||
color: var(--nord6);
|
||||
background: var(--red);
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
border-radius: 4px;
|
||||
border-radius: var(--radius-md);
|
||||
margin: 1rem auto;
|
||||
max-width: 800px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ===== Nutrition ===== */
|
||||
.nutrition-section {
|
||||
max-width: 1000px;
|
||||
margin: 1.5rem auto;
|
||||
margin: 2.5rem auto;
|
||||
}
|
||||
.nutrition-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0.75rem;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
.nutrition-header h3 {
|
||||
margin: 0;
|
||||
@@ -553,7 +638,7 @@
|
||||
color: var(--color-text-on-primary);
|
||||
border: none;
|
||||
border-radius: var(--radius-pill);
|
||||
padding: 0.4rem 1rem;
|
||||
padding: 0.45rem 1.1rem;
|
||||
font-size: 0.85rem;
|
||||
cursor: pointer;
|
||||
transition: opacity var(--transition-fast);
|
||||
@@ -566,57 +651,74 @@
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.nutrition-table-wrapper {
|
||||
background: var(--color-bg-secondary);
|
||||
border-radius: 12px;
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 1rem;
|
||||
overflow-x: auto;
|
||||
}
|
||||
.nutrition-result-summary {
|
||||
margin: 0 0 0.75rem;
|
||||
font-weight: 600;
|
||||
font-weight: 700;
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 0.9rem;
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
}
|
||||
.nutrition-result-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.85rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.nutrition-result-table th,
|
||||
.nutrition-result-table td {
|
||||
text-align: left;
|
||||
padding: 0.4rem 0.6rem;
|
||||
border-bottom: 1px solid var(--color-bg-elevated);
|
||||
padding: 0.55rem 0.6rem;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
vertical-align: top;
|
||||
}
|
||||
.nutrition-result-table th {
|
||||
color: var(--color-text-secondary);
|
||||
font-weight: 600;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 700;
|
||||
font-size: 0.7rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
.unmapped-row {
|
||||
opacity: 0.6;
|
||||
letter-spacing: 0.06em;
|
||||
}
|
||||
.unmapped-row { opacity: 0.55; }
|
||||
.manual-row { border-left: 3px solid var(--orange); }
|
||||
.recipe-ref-row { border-left: 3px solid var(--blue); }
|
||||
.excluded-row { opacity: 0.45; }
|
||||
.excluded-row .name-td,
|
||||
.excluded-row .src-td { text-decoration: line-through; }
|
||||
|
||||
.search-cell {
|
||||
position: relative;
|
||||
}
|
||||
.search-input {
|
||||
display: inline !important;
|
||||
display: block !important;
|
||||
width: 100%;
|
||||
padding: 0.3rem 0.5rem !important;
|
||||
padding: 0.4rem 0.6rem !important;
|
||||
margin: 0 !important;
|
||||
border: 1px solid var(--color-bg-elevated) !important;
|
||||
border-radius: 6px !important;
|
||||
border: 1px solid var(--color-border) !important;
|
||||
border-radius: var(--radius-sm) !important;
|
||||
background: var(--color-bg-primary) !important;
|
||||
color: var(--color-text-primary) !important;
|
||||
font-size: 0.85rem !important;
|
||||
font-size: 0.9rem !important;
|
||||
scale: 1 !important;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.search-input:hover,
|
||||
.search-input:focus-visible {
|
||||
scale: 1 !important;
|
||||
border-color: var(--color-primary) !important;
|
||||
outline: none;
|
||||
}
|
||||
.search-input.has-match {
|
||||
opacity: 0.55;
|
||||
font-size: 0.8rem !important;
|
||||
}
|
||||
.search-input.has-match:focus {
|
||||
opacity: 1;
|
||||
font-size: 0.9rem !important;
|
||||
}
|
||||
.search-dropdown {
|
||||
position: absolute;
|
||||
@@ -628,26 +730,26 @@
|
||||
margin: 2px 0 0;
|
||||
padding: 0;
|
||||
background: var(--color-bg-primary);
|
||||
border: 1px solid var(--color-bg-elevated);
|
||||
border-radius: 8px;
|
||||
max-height: 240px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
max-height: 260px;
|
||||
overflow-y: auto;
|
||||
min-width: 300px;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
||||
min-width: 260px;
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
.search-dropdown li button {
|
||||
display: block;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
padding: 0.4rem 0.6rem;
|
||||
padding: 0.55rem 0.75rem;
|
||||
border: none;
|
||||
background: none;
|
||||
color: var(--color-text-primary);
|
||||
font-size: 0.8rem;
|
||||
font-size: 0.85rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
.search-dropdown li button:hover {
|
||||
background: var(--color-bg-tertiary);
|
||||
background: var(--color-bg-elevated);
|
||||
}
|
||||
.search-cal {
|
||||
color: var(--color-text-secondary);
|
||||
@@ -658,54 +760,28 @@
|
||||
display: inline-block;
|
||||
font-size: 0.6rem;
|
||||
font-weight: 700;
|
||||
padding: 0.1rem 0.35rem;
|
||||
border-radius: 4px;
|
||||
padding: 0.15rem 0.45rem;
|
||||
border-radius: var(--radius-sm);
|
||||
margin-right: 0.3rem;
|
||||
background: var(--nord10);
|
||||
color: white;
|
||||
background: var(--color-primary);
|
||||
color: var(--color-text-on-primary);
|
||||
vertical-align: middle;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
.source-badge.bls {
|
||||
background: var(--nord14);
|
||||
color: var(--nord0);
|
||||
}
|
||||
.source-badge.skip {
|
||||
background: var(--nord11);
|
||||
color: white;
|
||||
}
|
||||
.source-badge.bls { background: var(--green); color: white; }
|
||||
.source-badge.skip { background: var(--red); color: white; }
|
||||
.source-badge.recipe-ref { background: var(--blue); color: white; }
|
||||
.manual-indicator {
|
||||
display: inline-block;
|
||||
font-size: 0.55rem;
|
||||
font-weight: 700;
|
||||
color: var(--nord13);
|
||||
color: var(--orange);
|
||||
margin-left: 0.2rem;
|
||||
vertical-align: super;
|
||||
}
|
||||
.excluded-row {
|
||||
opacity: 0.4;
|
||||
}
|
||||
.excluded-row td {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
.excluded-row td:last-child,
|
||||
.excluded-row td:nth-last-child(2),
|
||||
.excluded-row td:nth-last-child(3),
|
||||
.excluded-row td:nth-last-child(4) {
|
||||
text-decoration: none;
|
||||
}
|
||||
.manual-row {
|
||||
border-left: 2px solid var(--nord13);
|
||||
}
|
||||
.recipe-ref-row {
|
||||
border-left: 2px solid var(--nord8);
|
||||
}
|
||||
.source-badge.recipe-ref {
|
||||
background: var(--nord8);
|
||||
color: var(--nord0);
|
||||
}
|
||||
.recipe-ref-label {
|
||||
font-size: 0.85rem;
|
||||
color: var(--nord8);
|
||||
font-size: 0.88rem;
|
||||
color: var(--blue);
|
||||
font-weight: 600;
|
||||
}
|
||||
.ref-multiplier {
|
||||
@@ -716,13 +792,11 @@
|
||||
color: var(--color-text-secondary);
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
.ref-multiplier .gpu-input {
|
||||
width: 3.5rem;
|
||||
}
|
||||
.ref-multiplier .gpu-input { width: 3.5rem; }
|
||||
.excluded-label {
|
||||
font-style: italic;
|
||||
color: var(--nord11);
|
||||
font-size: 0.8rem;
|
||||
color: var(--red);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.en-name {
|
||||
color: var(--color-text-secondary);
|
||||
@@ -730,26 +804,19 @@
|
||||
}
|
||||
.current-match {
|
||||
display: block;
|
||||
font-size: 0.8rem;
|
||||
margin-bottom: 0.2rem;
|
||||
font-size: 0.85rem;
|
||||
margin-bottom: 0.3rem;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
.current-match.manual-match {
|
||||
color: var(--nord13);
|
||||
}
|
||||
.search-input.has-match {
|
||||
opacity: 0.5;
|
||||
font-size: 0.75rem !important;
|
||||
}
|
||||
.search-input.has-match:focus {
|
||||
opacity: 1;
|
||||
font-size: 0.85rem !important;
|
||||
color: var(--orange);
|
||||
}
|
||||
.row-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-top: 0.3rem;
|
||||
gap: 0.75rem;
|
||||
margin-top: 0.45rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.row-controls :global(.toggle-wrapper) {
|
||||
font-size: 0.75rem;
|
||||
@@ -758,30 +825,30 @@
|
||||
gap: 0.4rem;
|
||||
}
|
||||
.row-controls :global(.toggle-track),
|
||||
.row-controls :global(input[type="checkbox"]) {
|
||||
.row-controls :global(input[type='checkbox']) {
|
||||
width: 32px !important;
|
||||
height: 18px !important;
|
||||
}
|
||||
.row-controls :global(.toggle-track::before),
|
||||
.row-controls :global(input[type="checkbox"]::before) {
|
||||
.row-controls :global(input[type='checkbox']::before) {
|
||||
width: 14px !important;
|
||||
height: 14px !important;
|
||||
}
|
||||
.row-controls :global(.toggle-track.checked::before),
|
||||
.row-controls :global(input[type="checkbox"]:checked::before) {
|
||||
.row-controls :global(input[type='checkbox']:checked::before) {
|
||||
transform: translateX(14px) !important;
|
||||
}
|
||||
.revert-btn {
|
||||
background: none;
|
||||
border: 1px solid var(--color-bg-elevated);
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--color-text-secondary);
|
||||
cursor: pointer;
|
||||
padding: 0.15rem 0.4rem;
|
||||
font-size: 0.65rem;
|
||||
padding: 0.2rem 0.55rem;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.03em;
|
||||
letter-spacing: 0.05em;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
.revert-btn:hover {
|
||||
@@ -791,51 +858,148 @@
|
||||
}
|
||||
.skip-btn {
|
||||
background: none;
|
||||
border: 1px solid var(--color-bg-elevated);
|
||||
border-radius: 4px;
|
||||
color: var(--nord11);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--red);
|
||||
cursor: pointer;
|
||||
padding: 0.15rem 0.4rem;
|
||||
font-size: 0.8rem;
|
||||
padding: 0.3rem 0.55rem;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
.skip-btn:hover {
|
||||
background: var(--nord11);
|
||||
color: white;
|
||||
}
|
||||
.skip-btn:hover,
|
||||
.skip-btn.active {
|
||||
background: var(--nord11);
|
||||
background: var(--red);
|
||||
color: white;
|
||||
border-color: var(--red);
|
||||
}
|
||||
.skip-btn.active:hover {
|
||||
background: var(--nord14);
|
||||
color: var(--nord0);
|
||||
background: var(--green);
|
||||
color: white;
|
||||
border-color: var(--green);
|
||||
}
|
||||
.gpu-input {
|
||||
display: inline !important;
|
||||
width: 4em !important;
|
||||
padding: 0.2rem 0.3rem !important;
|
||||
padding: 0.25rem 0.4rem !important;
|
||||
margin: 0 !important;
|
||||
border: 1px solid var(--color-bg-elevated) !important;
|
||||
border-radius: 4px !important;
|
||||
border: 1px solid var(--color-border) !important;
|
||||
border-radius: var(--radius-sm) !important;
|
||||
background: var(--color-bg-primary) !important;
|
||||
color: var(--color-text-primary) !important;
|
||||
font-size: 0.8rem !important;
|
||||
font-size: 0.85rem !important;
|
||||
scale: 1 !important;
|
||||
text-align: right;
|
||||
}
|
||||
.gpu-input:hover, .gpu-input:focus-visible {
|
||||
.gpu-input:hover,
|
||||
.gpu-input:focus-visible {
|
||||
scale: 1 !important;
|
||||
}
|
||||
|
||||
/* ===== Mobile nutrition: table → card stack ===== */
|
||||
@media (max-width: 700px) {
|
||||
.nutrition-table-wrapper {
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
.nutrition-result-table,
|
||||
.nutrition-result-table tbody,
|
||||
.nutrition-result-table tr,
|
||||
.nutrition-result-table td {
|
||||
display: block;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.nutrition-result-table thead {
|
||||
display: none;
|
||||
}
|
||||
.nutrition-result-table tr {
|
||||
position: relative;
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
padding: 0.85rem 1rem 0.75rem;
|
||||
margin-bottom: 0.85rem;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
.nutrition-result-table td {
|
||||
padding: 0.45rem 0;
|
||||
border-bottom: 1px dashed var(--color-border);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 0.75rem;
|
||||
align-items: baseline;
|
||||
}
|
||||
.nutrition-result-table td:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.nutrition-result-table td::before {
|
||||
content: attr(data-label);
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 0.66rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
flex-shrink: 0;
|
||||
min-width: 5rem;
|
||||
align-self: center;
|
||||
}
|
||||
.nutrition-result-table td.num-td {
|
||||
position: absolute;
|
||||
top: 0.6rem;
|
||||
right: 0.9rem;
|
||||
padding: 0;
|
||||
border: none;
|
||||
width: auto;
|
||||
opacity: 0.5;
|
||||
font-size: 0.7rem;
|
||||
display: inline-block;
|
||||
}
|
||||
.nutrition-result-table td.num-td::before {
|
||||
display: none;
|
||||
}
|
||||
.nutrition-result-table td.name-td {
|
||||
font-size: 1.05rem;
|
||||
font-weight: 600;
|
||||
padding-right: 2rem;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 0.15rem;
|
||||
}
|
||||
.nutrition-result-table td.name-td::before {
|
||||
font-size: 0.62rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
.nutrition-result-table td.search-td {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
.nutrition-result-table td.action-td {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.nutrition-result-table td.action-td::before {
|
||||
display: none;
|
||||
}
|
||||
.search-input {
|
||||
font-size: 1rem !important;
|
||||
padding: 0.6rem 0.75rem !important;
|
||||
}
|
||||
.search-dropdown {
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.section-actions {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
justify-content: center;
|
||||
}
|
||||
.section-btn {
|
||||
padding: 0.75rem 1.5rem;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
border-radius: var(--radius-pill);
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
@@ -852,7 +1016,8 @@
|
||||
}
|
||||
.translation-section-trigger {
|
||||
max-width: 1000px;
|
||||
margin: 1.5rem auto;
|
||||
margin: 2.5rem auto;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -861,8 +1026,6 @@
|
||||
<meta name="description" content="Bearbeite das Rezept {data.recipe.name}" />
|
||||
</svelte:head>
|
||||
|
||||
<h1>Rezept bearbeiten</h1>
|
||||
|
||||
{#if form?.error}
|
||||
<div class="error-message">
|
||||
<strong>Fehler:</strong> {form.error}
|
||||
@@ -908,16 +1071,6 @@
|
||||
})} />
|
||||
{/if}
|
||||
|
||||
<CardAdd
|
||||
bind:card_data
|
||||
bind:image_preview_url
|
||||
bind:selected_image_file
|
||||
short_name={short_name}
|
||||
/>
|
||||
|
||||
<h3>Kurzname (für URL):</h3>
|
||||
<input name="short_name" bind:value={short_name} placeholder="Kurzname" required />
|
||||
|
||||
<!-- Hidden inputs for card data -->
|
||||
<input type="hidden" name="name" value={card_data.name} />
|
||||
<input type="hidden" name="description" value={card_data.description} />
|
||||
@@ -926,72 +1079,89 @@
|
||||
<input type="hidden" name="portions" value={portions_local} />
|
||||
<input type="hidden" name="isBaseRecipe" value={isBaseRecipe ? "true" : "false"} />
|
||||
<input type="hidden" name="defaultForm_json" value={defaultForm ? JSON.stringify(defaultForm) : ''} />
|
||||
|
||||
<div style="text-align: center; margin: 1rem;">
|
||||
<Toggle
|
||||
bind:checked={isBaseRecipe}
|
||||
label="Als Basisrezept markieren (kann von anderen Rezepten referenziert werden)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Default Form (Cake Pan) -->
|
||||
<div class="form-size-section">
|
||||
<h3>Backform (Standard):</h3>
|
||||
<div class="form-size-controls">
|
||||
<label>
|
||||
<input type="radio" name="formShape" value="none" checked={!defaultForm} onchange={() => { defaultForm = null; }
|
||||
} />
|
||||
Keine
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="formShape" value="round" checked={defaultForm?.shape === 'round'} onchange={() => { defaultForm = { shape: 'round', diameter: defaultForm?.diameter || 26 }; }
|
||||
} />
|
||||
Rund
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="formShape" value="rectangular" checked={defaultForm?.shape === 'rectangular'} onchange={() => { defaultForm = { shape: 'rectangular', width: defaultForm?.width || 20, length: defaultForm?.length || 30 }; }
|
||||
} />
|
||||
Rechteckig
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="formShape" value="gugelhupf" checked={defaultForm?.shape === 'gugelhupf'} onchange={() => { defaultForm = { shape: 'gugelhupf', diameter: defaultForm?.diameter || 24, innerDiameter: defaultForm?.innerDiameter || 8 }; }
|
||||
} />
|
||||
Gugelhupf
|
||||
</label>
|
||||
</div>
|
||||
{#if defaultForm?.shape === 'round'}
|
||||
<div class="form-size-inputs">
|
||||
<label>Durchmesser: <input type="number" min="1" step="1" bind:value={defaultForm.diameter} /> cm</label>
|
||||
</div>
|
||||
{:else if defaultForm?.shape === 'rectangular'}
|
||||
<div class="form-size-inputs">
|
||||
<label>Breite: <input type="number" min="1" step="1" bind:value={defaultForm.width} /> cm</label>
|
||||
<label>Länge: <input type="number" min="1" step="1" bind:value={defaultForm.length} /> cm</label>
|
||||
</div>
|
||||
{:else if defaultForm?.shape === 'gugelhupf'}
|
||||
<div class="form-size-inputs">
|
||||
<label>Aussen-Ø: <input type="number" min="1" step="1" bind:value={defaultForm.diameter} /> cm</label>
|
||||
<label>Innen-Ø: <input type="number" min="1" step="1" bind:value={defaultForm.innerDiameter} /> cm</label>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Recipe Note Component -->
|
||||
<EditRecipeNote bind:note />
|
||||
<input type="hidden" name="preamble" value={preamble} />
|
||||
<input type="hidden" name="note" value={note} />
|
||||
|
||||
<div class="title_container">
|
||||
<div class="title">
|
||||
<h4>Eine etwas längere Beschreibung:</h4>
|
||||
<p bind:innerText={preamble} contenteditable></p>
|
||||
<input type="hidden" name="preamble" value={preamble} />
|
||||
|
||||
<div class="tags">
|
||||
<h4>Saison:</h4>
|
||||
<EditTitleImgParallax
|
||||
bind:card_data
|
||||
bind:image_preview_url
|
||||
bind:selected_image_file
|
||||
>
|
||||
{#snippet titleExtras()}
|
||||
<h2 class="section-label">Saison</h2>
|
||||
<div class="season-wrapper">
|
||||
<SeasonSelect />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class="section-label">Einleitung</h2>
|
||||
<p
|
||||
class="preamble"
|
||||
contenteditable="plaintext-only"
|
||||
bind:innerText={preamble}
|
||||
data-placeholder="Eine etwas längere Einleitung für dieses Rezept…"
|
||||
aria-label="Einleitung"
|
||||
></p>
|
||||
|
||||
<div class="note-slot">
|
||||
<EditRecipeNote bind:note />
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
<div class="below-hero">
|
||||
<div class="meta-row">
|
||||
<label class="url-field">
|
||||
<span>URL-Kurzname</span>
|
||||
<input
|
||||
name="short_name"
|
||||
bind:value={short_name}
|
||||
placeholder="Kurzname"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<div class="toggle-field">
|
||||
<Toggle
|
||||
bind:checked={isBaseRecipe}
|
||||
label="Als Basisrezept markieren"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-size-section">
|
||||
<h3>Backform (Standard)</h3>
|
||||
<div class="form-size-controls">
|
||||
<label>
|
||||
<input type="radio" name="formShape" value="none" checked={!defaultForm} onchange={() => { defaultForm = null; }} />
|
||||
Keine
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="formShape" value="round" checked={defaultForm?.shape === 'round'} onchange={() => { defaultForm = { shape: 'round', diameter: defaultForm?.diameter || 26 }; }} />
|
||||
Rund
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="formShape" value="rectangular" checked={defaultForm?.shape === 'rectangular'} onchange={() => { defaultForm = { shape: 'rectangular', width: defaultForm?.width || 20, length: defaultForm?.length || 30 }; }} />
|
||||
Rechteckig
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="formShape" value="gugelhupf" checked={defaultForm?.shape === 'gugelhupf'} onchange={() => { defaultForm = { shape: 'gugelhupf', diameter: defaultForm?.diameter || 24, innerDiameter: defaultForm?.innerDiameter || 8 }; }} />
|
||||
Gugelhupf
|
||||
</label>
|
||||
</div>
|
||||
{#if defaultForm?.shape === 'round'}
|
||||
<div class="form-size-inputs">
|
||||
<label>Durchmesser: <input type="number" min="1" step="1" bind:value={defaultForm.diameter} /> cm</label>
|
||||
</div>
|
||||
{:else if defaultForm?.shape === 'rectangular'}
|
||||
<div class="form-size-inputs">
|
||||
<label>Breite: <input type="number" min="1" step="1" bind:value={defaultForm.width} /> cm</label>
|
||||
<label>Länge: <input type="number" min="1" step="1" bind:value={defaultForm.length} /> cm</label>
|
||||
</div>
|
||||
{:else if defaultForm?.shape === 'gugelhupf'}
|
||||
<div class="form-size-inputs">
|
||||
<label>Aussen-Ø: <input type="number" min="1" step="1" bind:value={defaultForm.diameter} /> cm</label>
|
||||
<label>Innen-Ø: <input type="number" min="1" step="1" bind:value={defaultForm.innerDiameter} /> cm</label>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="list_wrapper">
|
||||
<div>
|
||||
@@ -1030,14 +1200,14 @@
|
||||
{#each nutritionMappings as m, i (mappingKey(m))}
|
||||
{@const key = mappingKey(m)}
|
||||
<tr class:unmapped-row={m.matchMethod === 'none' && !m.excluded && !m.recipeRef} class:excluded-row={m.excluded && !m.recipeRef} class:manual-row={m.manuallyEdited && !m.excluded} class:recipe-ref-row={!!m.recipeRef}>
|
||||
<td>{i + 1}</td>
|
||||
<td>
|
||||
<td data-label="#" class="num-td">{i + 1}</td>
|
||||
<td data-label="Zutat" class="name-td">
|
||||
{m.ingredientNameDe || m.ingredientName}
|
||||
{#if m.ingredientName && m.ingredientName !== m.ingredientNameDe}
|
||||
<span class="en-name">({m.ingredientName})</span>
|
||||
{/if}
|
||||
</td>
|
||||
<td>
|
||||
<td data-label="Quelle" class="src-td">
|
||||
{#if m.recipeRef}
|
||||
<span class="source-badge recipe-ref">REF</span>
|
||||
{:else if m.excluded}
|
||||
@@ -1049,7 +1219,7 @@
|
||||
—
|
||||
{/if}
|
||||
</td>
|
||||
<td>
|
||||
<td data-label="Treffer / Suche" class="search-td">
|
||||
<div class="search-cell">
|
||||
{#if m.recipeRef}
|
||||
<span class="recipe-ref-label">{m.recipeRef}</span>
|
||||
@@ -1096,8 +1266,8 @@
|
||||
{/if}
|
||||
</div>
|
||||
</td>
|
||||
<td>{m.recipeRef ? '—' : (m.matchConfidence ? (m.matchConfidence * 100).toFixed(0) + '%' : '—')}</td>
|
||||
<td>
|
||||
<td data-label="Konf." class="conf-td">{m.recipeRef ? '—' : (m.matchConfidence ? (m.matchConfidence * 100).toFixed(0) + '%' : '—')}</td>
|
||||
<td data-label="g/u" class="unit-td">
|
||||
{#if m.recipeRef}
|
||||
—
|
||||
{:else if m.manuallyEdited}
|
||||
@@ -1106,7 +1276,7 @@
|
||||
{m.gramsPerUnit || '—'}
|
||||
{/if}
|
||||
</td>
|
||||
<td>
|
||||
<td data-label="" class="action-td">
|
||||
{#if !m.recipeRef}
|
||||
<button
|
||||
type="button"
|
||||
@@ -1146,6 +1316,8 @@
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</EditTitleImgParallax>
|
||||
</form>
|
||||
|
||||
{#if translationData || showTranslationWorkflow}
|
||||
|
||||
Reference in New Issue
Block a user