diff --git a/package.json b/package.json index 2e53b20..2894e7b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "homepage", - "version": "1.27.0", + "version": "1.28.0", "private": true, "type": "module", "scripts": { diff --git a/src/lib/components/recipes/CreateIngredientList.svelte b/src/lib/components/recipes/CreateIngredientList.svelte index 25bbfce..0e4ac87 100644 --- a/src/lib/components/recipes/CreateIngredientList.svelte +++ b/src/lib/components/recipes/CreateIngredientList.svelte @@ -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() -portions.subscribe((p: any) => { - portions_local = p +let { + lang = 'de' as 'de' | 'en', + ingredients = $bindable(), + portions: portionsProp = $bindable(undefined), + useStore = true, +} = $props<{ + lang?: 'de' | 'en', + ingredients: any, + portions?: string, + useStore?: boolean, +}>(); + +let portions_local = $state(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> = { de: { diff --git a/src/lib/components/recipes/CreateStepList.svelte b/src/lib/components/recipes/CreateStepList.svelte index 8baddd9..180193b 100644 --- a/src/lib/components/recipes/CreateStepList.svelte +++ b/src/lib/components/recipes/CreateStepList.svelte @@ -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{
-
- -

{t[lang].preparation}

-

+
+
+

{t[lang].preparation}

+

- -

{t[lang].bulkFermentation}

-

+
+

{t[lang].bulkFermentation}

+

-

{t[lang].finalFermentation}

-

+
+

{t[lang].finalFermentation}

+

-

{t[lang].baking}

-

bei

°C

- -

{t[lang].cooking}

-

+
+

{t[lang].baking}

+
+ + bei + + °C + +
-

{t[lang].totalTime}

-

+
+

{t[lang].cooking}

+

+
+ +
+

{t[lang].totalTime}

+

diff --git a/src/lib/components/recipes/EditTitleImgParallax.svelte b/src/lib/components/recipes/EditTitleImgParallax.svelte new file mode 100644 index 0000000..da92603 --- /dev/null +++ b/src/lib/components/recipes/EditTitleImgParallax.svelte @@ -0,0 +1,557 @@ + + +
+
+ + {#if selected_image_file} + + {/if} + +
+ +
+
+ + + + + + +

+ + +
+ {#each card_data.tags ?? [] as tag (tag)} + + {/each} + +
+ + {#if titleExtras}{@render titleExtras()}{/if} +
+ + {#if children}{@render children()}{/if} +
+
+ + diff --git a/src/lib/components/recipes/TranslationApproval.svelte b/src/lib/components/recipes/TranslationApproval.svelte index 128a44d..988a634 100644 --- a/src/lib/components/recipes/TranslationApproval.svelte +++ b/src/lib/components/recipes/TranslationApproval.svelte @@ -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)'; - } - }
@@ -620,64 +713,73 @@ button:disabled {
{#if errorMessage} -
- Error: {errorMessage} +
+
+ Error + {errorMessage} +
{/if} {#if validationErrors.length > 0} -
- Please fix the following errors: -
    - {#each validationErrors as error} -
  • {error}
  • - {/each} -
+
+
+ Please fix the following errors: +
    + {#each validationErrors as error} +
  • {error}
  • + {/each} +
+
{/if} {#if isEditMode && changedFields.length > 0} -
- Changed fields: {changedFields.join(', ')} -
- Only these fields will be re-translated if you use auto-translate. +
+
+ Changed fields: {changedFields.join(', ')} + Only these fields will be re-translated if you use auto-translate. +
{/if} {#if checkingBaseRecipes} -
-

Checking if referenced base recipes are translated...

+
+
Checking if referenced base recipes are translated…
{/if} {#if untranslatedBaseRecipes.length > 0} -
-

⚠️ Base Recipes Need Translation

-

The following base recipes need to be translated to English before you can translate this recipe:

- -

- -

+
+
+ Base recipes need translation + The following base recipes need to be translated to English before you can translate this recipe: + +
+ +
+
{/if} {#if translationState === 'idle'} -
- Preview (Not yet translated) -

The structure below shows what will be translated. Click "Auto-translate" to generate English translation.

+
+
+ Preview — not yet translated + The structure below shows what will be translated. Click “Auto-translate” to generate the English translation. +
- {/if} {#if translationState === 'translating'} @@ -691,7 +793,7 @@ button:disabled { {#if translationState === 'idle' || translationState === 'preview' || translationState === 'approved'}
-

🇬🇧 English Translation

+

Side-by-side review

@@ -780,77 +882,59 @@ button:disabled {
{/if} - {#if editableEnglish?.portions !== undefined} -
- handleFieldChange(value, 'portions')} - /> -
- {/if} - {#if germanData.images && germanData.images.length > 0} -
-

🖼️ Images - English Alt Texts & Captions

+
+

Images — Alt texts & captions

{#each germanData.images as germanImage, i} {#if editableEnglish.images && editableEnglish.images[i]} -
-
+
+
{germanImage.alt -
-

Image {i + 1}: {germanImage.mediapath}

+
+

Image {i + 1}: {germanImage.mediapath}

-
-
- +
+
+
-
- +
+
-
-
- +
+
+
-
- +
+
@@ -863,7 +947,7 @@ button:disabled {
{/if} {/each} -
+
{/if} @@ -871,7 +955,12 @@ button:disabled {
{#if editableEnglish?.ingredients} - + {/if}
@@ -926,7 +1015,7 @@ button:disabled { Approve Translation {:else} - ✓ Translation Approved + Translation Approved {/if}
{/if} diff --git a/src/lib/components/recipes/TranslationFieldComparison.svelte b/src/lib/components/recipes/TranslationFieldComparison.svelte index 6d27145..3f6505f 100644 --- a/src/lib/components/recipes/TranslationFieldComparison.svelte +++ b/src/lib/components/recipes/TranslationFieldComparison.svelte @@ -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); }
{label}
- {#if readonly} -
- {germanValue || '(empty)'} +
+
+ Deutsch +
+ {germanValue || '—'} +
- {:else if multiline} - - {:else} - - {/if} +
+ English + {#if readonly} +
+ {englishValue || '—'} +
+ {:else if multiline} + + {:else} + + {/if} +
+
diff --git a/src/routes/[recipeLang=recipeLang]/edit/[name]/+page.svelte b/src/routes/[recipeLang=recipeLang]/edit/[name]/+page.svelte index 839ed04..df2db26 100644 --- a/src/routes/[recipeLang=recipeLang]/edit/[name]/+page.svelte +++ b/src/routes/[recipeLang=recipeLang]/edit/[name]/+page.svelte @@ -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 @@ @@ -861,8 +1026,6 @@ -

Rezept bearbeiten

- {#if form?.error}
Fehler: {form.error} @@ -908,16 +1071,6 @@ })} /> {/if} - - -

Kurzname (für URL):

- - @@ -926,72 +1079,89 @@ - -
- -
- - -
-

Backform (Standard):

-
- - - - -
- {#if defaultForm?.shape === 'round'} -
- -
- {:else if defaultForm?.shape === 'rectangular'} -
- - -
- {:else if defaultForm?.shape === 'gugelhupf'} -
- - -
- {/if} -
- - - + -
-
-

Eine etwas längere Beschreibung:

-

- - -
-

Saison:

+ + {#snippet titleExtras()} + +
-
-
+ + +

+ +
+ +
+ {/snippet} + +
+
+ +
+ +
+
+ +
+

Backform (Standard)

+
+ + + + +
+ {#if defaultForm?.shape === 'round'} +
+ +
+ {:else if defaultForm?.shape === 'rectangular'} +
+ + +
+ {:else if defaultForm?.shape === 'gugelhupf'} +
+ + +
+ {/if} +
@@ -1030,14 +1200,14 @@ {#each nutritionMappings as m, i (mappingKey(m))} {@const key = mappingKey(m)} - {i + 1} - + {i + 1} + {m.ingredientNameDe || m.ingredientName} {#if m.ingredientName && m.ingredientName !== m.ingredientNameDe} ({m.ingredientName}) {/if} - + {#if m.recipeRef} REF {:else if m.excluded} @@ -1049,7 +1219,7 @@ — {/if} - +
{#if m.recipeRef} {m.recipeRef} @@ -1096,8 +1266,8 @@ {/if}
- {m.recipeRef ? '—' : (m.matchConfidence ? (m.matchConfidence * 100).toFixed(0) + '%' : '—')} - + {m.recipeRef ? '—' : (m.matchConfidence ? (m.matchConfidence * 100).toFixed(0) + '%' : '—')} + {#if m.recipeRef} — {:else if m.manuallyEdited} @@ -1106,7 +1276,7 @@ {m.gramsPerUnit || '—'} {/if} - + {#if !m.recipeRef}
{/if} +
+ {#if translationData || showTranslationWorkflow}