Migliora header pagine e ridisegna Convertitore

MealPlanner:
- Titolo "Piano Pasti" (più breve e da app)
- Sottotitolo "Oggi, mercoledì 25 marzo" con data completa formattata

Converter:
- Layout calcolatrice: input sinistra | swap | risultato destra
- Card unificata con header alimento e footer fattore di resa
- Icona ricerca nell'input, stato iniziale con hint
- Titolo "Convertitore", sottotitolo descrittivo
- Tasto swap per invertire direzione invece del toggle

MealCard:
- Fix emoji pranzo 🌤️🍽️ (rendering universale)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-25 08:33:10 +01:00
parent 76135e404a
commit d14f56cced
3 changed files with 255 additions and 129 deletions

View File

@@ -56,7 +56,7 @@ const isOpen = ref(props.defaultOpen)
const slots = [
{ id: 'colazione', label: 'Colazione', icon: '☀️' },
{ id: 'pranzo', label: 'Pranzo', icon: '🌤' },
{ id: 'pranzo', label: 'Pranzo', icon: '🍽' },
{ id: 'cena', label: 'Cena', icon: '🌙' },
]

View File

@@ -1,67 +1,101 @@
<template>
<div class="page">
<div class="page-header">
<h1 class="page-title">Conversione</h1>
<p class="page-subtitle">crudo cotto</p>
<h1 class="page-title">Convertitore</h1>
<p class="page-subtitle">Calcola il peso cotto dal crudo e viceversa</p>
</div>
<div class="search-box">
<input
v-model="query"
type="text"
placeholder="Cerca alimento (es. pollo, riso...)"
@input="onSearch"
:disabled="!!selected"
/>
<!-- step 1: ricerca -->
<div class="search-wrapper">
<div class="search-field">
<svg class="search-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>
</svg>
<input
v-model="query"
type="text"
placeholder="Cerca alimento…"
@input="onSearch"
:disabled="!!selected"
/>
</div>
<ul v-if="results.length && !selected" class="results-list">
<li
v-for="r in results"
:key="r.key"
class="result-item"
@click="selectItem(r)"
>
<span class="result-food">{{ r.food }}</span>
<span class="result-method">{{ r.method }}</span>
</li>
</ul>
</div>
<ul v-if="results.length && !selected" class="results-list">
<li
v-for="r in results"
:key="r.key"
class="result-item"
@click="selectItem(r)"
>
<span class="result-food">{{ r.food }}</span>
<span class="result-method">{{ r.method }}</span>
</li>
</ul>
<div v-if="selected" class="converter-panel">
<div class="selected-chip">
<span class="chip-text">{{ selected.food }} · {{ selected.method }}</span>
<button class="btn-chip-reset" @click="reset" aria-label="Cambia alimento">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round">
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
</svg>
<!-- step 2: converter card -->
<div v-if="selected" class="converter-card">
<div class="card-top">
<div class="food-info">
<span class="food-name">{{ selected.food }}</span>
<span class="food-sep">·</span>
<span class="food-method">{{ selected.method }}</span>
</div>
<button class="btn-reset" @click="reset" aria-label="Cambia alimento">
Cambia
</button>
</div>
<div class="direction-toggle">
<button :class="['toggle-btn', { active: direction === 'rawToCooked' }]" @click="direction = 'rawToCooked'">crudo cotto</button>
<button :class="['toggle-btn', { active: direction === 'cookedToRaw' }]" @click="direction = 'cookedToRaw'">cotto crudo</button>
</div>
<div class="card-divider" />
<div class="input-group">
<label class="input-label">{{ direction === 'rawToCooked' ? 'Grammi crudi' : 'Grammi cotti' }}</label>
<div class="input-row">
<input v-model.number="grams" type="number" min="0" placeholder="0" />
<span class="unit">g</span>
<!-- riga input swap output -->
<div class="calc-row">
<div class="calc-side input-side">
<div class="calc-label">{{ direction === 'rawToCooked' ? 'crudo' : 'cotto' }}</div>
<div class="calc-input-wrap">
<input
v-model.number="grams"
type="number"
min="0"
placeholder="0"
class="calc-input"
/>
<span class="calc-unit">g</span>
</div>
</div>
<button class="btn-swap" @click="swapDirection" :title="direction === 'rawToCooked' ? 'Inverti direzione' : 'Inverti direzione'">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="17 1 21 5 17 9"/>
<path d="M3 11V9a4 4 0 014-4h14"/>
<polyline points="7 23 3 19 7 15"/>
<path d="M21 13v2a4 4 0 01-4 4H3"/>
</svg>
</button>
<div class="calc-side output-side" :class="{ 'has-result': result !== null }">
<div class="calc-label">{{ direction === 'rawToCooked' ? 'cotto' : 'crudo' }}</div>
<div class="calc-output">
<span v-if="result !== null" class="output-value">{{ result }}</span>
<span v-else class="output-placeholder"></span>
<span class="calc-unit" :class="{ visible: result !== null }">g</span>
</div>
</div>
</div>
<div v-if="result !== null" class="result-box">
<div class="result-formula">
{{ grams }}g × {{ yieldValue }} =
</div>
<div class="result-main">
<span class="result-value">{{ result }}</span>
<span class="result-unit">g</span>
</div>
<div class="result-desc">{{ direction === 'rawToCooked' ? 'da mangiare cotti' : 'peso crudo equivalente' }}</div>
<div v-if="result !== null" class="card-footer">
fattore di resa {{ yieldValue }} · {{ direction === 'rawToCooked' ? 'da mangiare cotti' : 'peso crudo equivalente' }}
</div>
</div>
<!-- stato iniziale -->
<div v-if="!selected && !results.length && !query" class="hint-state">
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>
</svg>
<p>Cerca un alimento per iniziare</p>
<p class="hint-sub">es. pollo, riso, zucchine</p>
</div>
</div>
</template>
@@ -106,7 +140,7 @@ function onSearch() {
function selectItem(r) {
selected.value = r
query.value = `${r.food}${r.method}`
query.value = ''
results.value = []
grams.value = null
}
@@ -116,19 +150,43 @@ function reset() {
query.value = ''
results.value = []
grams.value = null
direction.value = 'rawToCooked'
}
function swapDirection() {
direction.value = direction.value === 'rawToCooked' ? 'cookedToRaw' : 'rawToCooked'
grams.value = null
}
</script>
<style scoped>
.search-box { margin-bottom: 8px; }
/* ── ricerca ───────────────────────────────────────── */
.search-wrapper { margin-bottom: 16px; }
.search-field {
position: relative;
display: flex;
align-items: center;
}
.search-icon {
position: absolute;
left: 14px;
color: var(--color-muted);
pointer-events: none;
}
.search-field input {
padding-left: 42px;
}
.results-list {
list-style: none;
background: var(--color-surface);
border: 1.5px solid var(--color-border);
border-radius: var(--radius);
border-top: none;
border-radius: 0 0 var(--radius) var(--radius);
overflow: hidden;
margin-bottom: 16px;
box-shadow: var(--shadow-sm);
}
@@ -144,110 +202,178 @@ function reset() {
.result-item:last-child { border-bottom: none; }
.result-item:active { background: var(--color-bg); }
.result-food { font-weight: 600; text-transform: capitalize; }
.result-method { font-size: 0.85rem; color: var(--color-muted); text-transform: capitalize; }
.converter-panel { display: flex; flex-direction: column; gap: 16px; }
.selected-chip {
display: inline-flex;
align-items: center;
gap: 8px;
background: var(--color-primary-muted);
border-radius: var(--radius-full);
padding: 6px 12px 6px 14px;
align-self: flex-start;
/* ── converter card ───────────────────────────────── */
.converter-card {
background: var(--color-surface);
border-radius: var(--radius);
border: 1.5px solid var(--color-border);
box-shadow: var(--shadow-md);
overflow: hidden;
}
.chip-text {
font-size: 0.9rem;
font-weight: 600;
color: var(--color-primary);
.card-top {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 16px;
}
.food-info {
display: flex;
align-items: center;
gap: 6px;
text-transform: capitalize;
}
.btn-chip-reset {
.food-name { font-weight: 700; font-size: 1rem; }
.food-sep { color: var(--color-border); font-size: 1.1rem; }
.food-method { font-size: 0.9rem; color: var(--color-muted); }
.btn-reset {
background: none;
color: var(--color-primary);
font-size: 0.85rem;
font-weight: 600;
min-height: unset;
padding: 2px;
padding: 4px 10px;
border: 1.5px solid var(--color-primary-muted);
border-radius: var(--radius-full);
background: var(--color-primary-muted);
}
.card-divider {
height: 1px;
background: var(--color-border);
margin: 0 16px;
}
/* ── riga calcolatrice ───────────────────────────── */
.calc-row {
display: flex;
align-items: center;
opacity: 0.7;
padding: 20px 16px;
gap: 12px;
}
.direction-toggle { display: flex; gap: 8px; }
.toggle-btn {
.calc-side {
flex: 1;
background: var(--color-surface);
color: var(--color-muted);
border: 1.5px solid var(--color-border);
font-size: 0.875rem;
font-weight: 500;
border-radius: var(--radius-sm);
}
.toggle-btn.active {
background: var(--color-primary);
color: #fff;
border-color: var(--color-primary);
}
.input-group { display: flex; flex-direction: column; gap: 6px; }
.input-label {
font-size: 0.8rem;
font-weight: 600;
color: var(--color-muted);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.input-row { display: flex; align-items: center; gap: 10px; }
.unit { font-size: 1rem; color: var(--color-muted); font-weight: 500; }
.result-box {
background: var(--color-primary);
border-radius: var(--radius);
padding: 24px 20px;
text-align: center;
box-shadow: var(--shadow-md);
display: flex;
flex-direction: column;
gap: 4px;
gap: 6px;
}
.result-formula {
font-size: 0.8rem;
color: rgba(255,255,255,0.65);
.calc-label {
font-size: 0.72rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--color-muted);
}
.result-main {
.calc-input-wrap {
display: flex;
align-items: center;
gap: 6px;
}
/* override globale per input dentro la card */
.calc-input {
font-size: 1.5rem;
font-weight: 700;
letter-spacing: -0.02em;
border: none;
border-bottom: 2px solid var(--color-border);
border-radius: 0;
padding: 4px 0;
min-height: unset;
background: transparent;
width: 100%;
color: var(--color-text);
transition: border-color var(--transition);
}
.calc-input:focus {
outline: none;
box-shadow: none;
border-bottom-color: var(--color-primary);
}
.calc-unit {
font-size: 1rem;
font-weight: 600;
color: var(--color-muted);
}
/* bottone swap centrale */
.btn-swap {
background: var(--color-bg);
color: var(--color-primary);
min-height: 44px;
min-width: 44px;
padding: 0;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
border: 1.5px solid var(--color-border);
transition: background var(--transition), transform var(--transition);
}
.btn-swap:active {
background: var(--color-primary-muted);
transform: rotate(180deg);
opacity: 1;
}
/* colonna output */
.output-side .calc-output {
display: flex;
align-items: baseline;
justify-content: center;
gap: 4px;
min-height: 44px;
align-items: center;
}
.result-value {
font-size: 3rem;
.output-value {
font-size: 2rem;
font-weight: 800;
color: #fff;
letter-spacing: -0.02em;
line-height: 1;
letter-spacing: -0.03em;
color: var(--color-primary);
}
.result-unit {
font-size: 1.4rem;
color: rgba(255,255,255,0.8);
font-weight: 600;
.output-placeholder {
font-size: 2rem;
font-weight: 300;
color: var(--color-border);
}
.result-desc {
font-size: 0.85rem;
color: rgba(255,255,255,0.65);
margin-top: 2px;
/* nasconde "g" finché non c'è un risultato */
.calc-unit { visibility: hidden; }
.calc-input-wrap .calc-unit,
.has-result .calc-unit { visibility: visible; }
/* ── footer card ─────────────────────────────────── */
.card-footer {
padding: 10px 16px 14px;
font-size: 0.78rem;
color: var(--color-muted);
border-top: 1px solid var(--color-border);
text-align: center;
}
/* ── stato iniziale ──────────────────────────────── */
.hint-state {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
padding: 60px 20px;
color: var(--color-muted);
}
.hint-sub { font-size: 0.85rem; opacity: 0.6; }
</style>

View File

@@ -1,8 +1,8 @@
<template>
<div class="page">
<div class="page-header">
<h1 class="page-title">Pasti della settimana</h1>
<p class="page-subtitle">{{ todayLabel }}</p>
<h1 class="page-title">Piano Pasti</h1>
<p class="page-subtitle">{{ todayDate }}</p>
</div>
<MealCard
v-for="day in days"
@@ -33,7 +33,7 @@ const days = [
const todayMap = ['domenica', 'lunedi', 'martedi', 'mercoledi', 'giovedi', 'venerdi', 'sabato']
const todayId = todayMap[new Date().getDay()]
const todayLabel = days.find(d => d.id === todayId)?.label ?? ''
const todayDate = 'Oggi, ' + new Date().toLocaleDateString('it-IT', { weekday: 'long', day: 'numeric', month: 'long' })
const defaultMeals = () =>
Object.fromEntries(days.map(d => [d.id, { colazione: [], pranzo: [], cena: [] }]))