feat(recipes): redesign cake-form and baking info with collapsible card pattern

Viewer: cake-form adjust now collapses into a summary trigger with live
factor badge; shape picker replaced with icon-only tiles that flex to
fill the row; numeric inputs gain inline cm suffix; restore-default link
appears when user deviates from the default. Editor: default-Backform
config mirrors the same card + tile pattern (adds "none" tile), plus
inline cm suffixes. Baking info row in instruction editor becomes a
click-to-reveal card with summary chips, mode presets, and editable
fields behind the chevron.
This commit is contained in:
2026-04-12 21:46:47 +02:00
parent f108e9ceaa
commit 4692bf9bf7
4 changed files with 851 additions and 148 deletions
@@ -571,40 +571,129 @@
.form-size-section {
max-width: 600px;
margin: 2rem auto;
text-align: center;
}
.form-size-section h3 {
margin-top: 0;
}
.form-size-controls {
display: flex;
gap: 1rem 1.25rem;
justify-content: center;
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;
gap: 1rem;
justify-content: center;
align-items: center;
flex-wrap: wrap;
}
.form-size-inputs input[type='number'] {
width: 4em;
padding: 0.3em 0.5em;
margin: 0 0.3em;
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
overflow: hidden;
}
.form-size-head {
padding: 0.75rem 1rem;
}
.form-size-title {
font-weight: 600;
font-size: var(--text-sm);
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--color-text-secondary);
}
.form-size-body {
padding: 0.25rem 1rem 1rem;
border-top: 1px solid var(--color-border);
display: flex;
flex-direction: column;
gap: 1rem;
}
.form-shape-row {
display: flex;
gap: 0.4rem;
margin-top: 0.75rem;
}
.form-shape-row .shape-tile {
flex: 1 1 0;
display: flex;
align-items: center;
justify-content: center;
height: 2.25rem;
padding: 0;
background: var(--color-bg-tertiary);
border: 1.5px solid var(--color-border);
border-radius: var(--radius-md);
cursor: pointer;
color: var(--color-text-secondary);
transition: all 150ms ease;
}
.form-shape-row .shape-tile:hover,
.form-shape-row .shape-tile:focus-visible {
border-color: color-mix(in srgb, var(--color-primary) 50%, var(--color-border));
color: var(--color-text-primary);
border-radius: var(--radius-sm);
outline: none;
}
.form-shape-row .shape-tile[aria-checked="true"] {
border-color: var(--color-primary);
background: color-mix(in srgb, var(--color-primary) 10%, var(--color-bg-tertiary));
color: var(--color-primary);
}
.form-shape-row .shape-tile svg {
width: 1.25rem;
height: 1.25rem;
}
.form-size-inputs {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
gap: 0.75rem;
}
.form-size-inputs .input-wrap {
display: flex;
flex-direction: column;
gap: 0.3rem;
}
.form-size-inputs .input-label {
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.04em;
color: var(--color-text-tertiary);
text-transform: uppercase;
}
.form-size-inputs .input-box {
position: relative;
display: flex;
align-items: center;
background: var(--color-bg-tertiary);
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
transition: border-color 150ms ease, box-shadow 150ms ease;
}
.form-size-inputs .input-box:focus-within {
border-color: var(--color-primary);
box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-primary) 25%, transparent);
}
.form-size-inputs .input-box input {
flex: 1;
width: 100%;
padding: 0.55rem 2.25rem 0.55rem 0.75rem;
border: none;
background: transparent;
color: var(--color-text-primary);
font: inherit;
font-size: 1rem;
outline: none;
}
.form-size-inputs .input-box input::-webkit-outer-spin-button,
.form-size-inputs .input-box input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
.form-size-inputs .input-box input[type="number"] {
-moz-appearance: textfield;
appearance: textfield;
}
.form-size-inputs .input-suffix {
position: absolute;
right: 0.75rem;
font-size: 0.8rem;
font-weight: 600;
color: var(--color-text-tertiary);
pointer-events: none;
letter-spacing: 0.02em;
}
@media (max-width: 560px) {
.form-size-head { padding: 0.65rem 0.75rem; }
.form-size-body { padding: 0.25rem 0.75rem 0.85rem; }
.form-shape-row .shape-tile { height: 2rem; }
.form-shape-row .shape-tile svg { width: 1.1rem; height: 1.1rem; }
.form-size-inputs { grid-template-columns: 1fr 1fr; }
}
.error-message {
@@ -1127,40 +1216,113 @@
</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 class="form-size-head">
<span class="form-size-title">Backform (Standard)</span>
</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 class="form-size-body">
<div class="form-shape-row" role="radiogroup" aria-label="Backform">
<button
type="button"
role="radio"
aria-checked={!defaultForm}
aria-label="Keine"
title="Keine"
class="shape-tile"
onclick={() => { defaultForm = null; }}
>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" aria-hidden="true">
<circle cx="12" cy="12" r="8.5"/>
<path d="m6 6 12 12"/>
</svg>
</button>
<button
type="button"
role="radio"
aria-checked={defaultForm?.shape === 'round'}
aria-label="Rund"
title="Rund"
class="shape-tile"
onclick={() => { defaultForm = { shape: 'round', diameter: defaultForm?.diameter || 26 }; }}
>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" aria-hidden="true">
<circle cx="12" cy="12" r="8.5"/>
</svg>
</button>
<button
type="button"
role="radio"
aria-checked={defaultForm?.shape === 'rectangular'}
aria-label="Rechteckig"
title="Rechteckig"
class="shape-tile"
onclick={() => { defaultForm = { shape: 'rectangular', width: defaultForm?.width || 20, length: defaultForm?.length || 30 }; }}
>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" aria-hidden="true">
<rect x="3" y="6" width="18" height="12" rx="1.5"/>
</svg>
</button>
<button
type="button"
role="radio"
aria-checked={defaultForm?.shape === 'gugelhupf'}
aria-label="Gugelhupf"
title="Gugelhupf"
class="shape-tile"
onclick={() => { defaultForm = { shape: 'gugelhupf', diameter: defaultForm?.diameter || 24, innerDiameter: defaultForm?.innerDiameter || 8 }; }}
>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" aria-hidden="true">
<circle cx="12" cy="12" r="8.5"/>
<circle cx="12" cy="12" r="3"/>
</svg>
</button>
</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}
{#if defaultForm?.shape === 'round'}
<div class="form-size-inputs">
<label class="input-wrap">
<span class="input-label">Durchmesser</span>
<span class="input-box">
<input type="number" min="1" step="1" bind:value={defaultForm.diameter} />
<span class="input-suffix">cm</span>
</span>
</label>
</div>
{:else if defaultForm?.shape === 'rectangular'}
<div class="form-size-inputs">
<label class="input-wrap">
<span class="input-label">Breite</span>
<span class="input-box">
<input type="number" min="1" step="1" bind:value={defaultForm.width} />
<span class="input-suffix">cm</span>
</span>
</label>
<label class="input-wrap">
<span class="input-label">Länge</span>
<span class="input-box">
<input type="number" min="1" step="1" bind:value={defaultForm.length} />
<span class="input-suffix">cm</span>
</span>
</label>
</div>
{:else if defaultForm?.shape === 'gugelhupf'}
<div class="form-size-inputs">
<label class="input-wrap">
<span class="input-label">Aussen-Ø</span>
<span class="input-box">
<input type="number" min="1" step="1" bind:value={defaultForm.diameter} />
<span class="input-suffix">cm</span>
</span>
</label>
<label class="input-wrap">
<span class="input-label">Innen-Ø</span>
<span class="input-box">
<input type="number" min="1" step="1" bind:value={defaultForm.innerDiameter} />
<span class="input-suffix">cm</span>
</span>
</label>
</div>
{/if}
</div>
</div>
<div class="list_wrapper">