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 = [ const slots = [
{ id: 'colazione', label: 'Colazione', icon: '☀️' }, { id: 'colazione', label: 'Colazione', icon: '☀️' },
{ id: 'pranzo', label: 'Pranzo', icon: '🌤' }, { id: 'pranzo', label: 'Pranzo', icon: '🍽' },
{ id: 'cena', label: 'Cena', icon: '🌙' }, { id: 'cena', label: 'Cena', icon: '🌙' },
] ]

View File

@@ -1,67 +1,101 @@
<template> <template>
<div class="page"> <div class="page">
<div class="page-header"> <div class="page-header">
<h1 class="page-title">Conversione</h1> <h1 class="page-title">Convertitore</h1>
<p class="page-subtitle">crudo cotto</p> <p class="page-subtitle">Calcola il peso cotto dal crudo e viceversa</p>
</div> </div>
<div class="search-box"> <!-- step 1: ricerca -->
<input <div class="search-wrapper">
v-model="query" <div class="search-field">
type="text" <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">
placeholder="Cerca alimento (es. pollo, riso...)" <circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>
@input="onSearch" </svg>
:disabled="!!selected" <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> </div>
<ul v-if="results.length && !selected" class="results-list"> <!-- step 2: converter card -->
<li <div v-if="selected" class="converter-card">
v-for="r in results" <div class="card-top">
:key="r.key" <div class="food-info">
class="result-item" <span class="food-name">{{ selected.food }}</span>
@click="selectItem(r)" <span class="food-sep">·</span>
> <span class="food-method">{{ selected.method }}</span>
<span class="result-food">{{ r.food }}</span> </div>
<span class="result-method">{{ r.method }}</span> <button class="btn-reset" @click="reset" aria-label="Cambia alimento">
</li> Cambia
</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>
</button> </button>
</div> </div>
<div class="direction-toggle"> <div class="card-divider" />
<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="input-group"> <!-- riga input swap output -->
<label class="input-label">{{ direction === 'rawToCooked' ? 'Grammi crudi' : 'Grammi cotti' }}</label> <div class="calc-row">
<div class="input-row"> <div class="calc-side input-side">
<input v-model.number="grams" type="number" min="0" placeholder="0" /> <div class="calc-label">{{ direction === 'rawToCooked' ? 'crudo' : 'cotto' }}</div>
<span class="unit">g</span> <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> </div>
<div v-if="result !== null" class="result-box"> <div v-if="result !== null" class="card-footer">
<div class="result-formula"> fattore di resa {{ yieldValue }} · {{ direction === 'rawToCooked' ? 'da mangiare cotti' : 'peso crudo equivalente' }}
{{ 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> </div>
</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> </div>
</template> </template>
@@ -106,7 +140,7 @@ function onSearch() {
function selectItem(r) { function selectItem(r) {
selected.value = r selected.value = r
query.value = `${r.food}${r.method}` query.value = ''
results.value = [] results.value = []
grams.value = null grams.value = null
} }
@@ -116,19 +150,43 @@ function reset() {
query.value = '' query.value = ''
results.value = [] results.value = []
grams.value = null grams.value = null
direction.value = 'rawToCooked'
}
function swapDirection() {
direction.value = direction.value === 'rawToCooked' ? 'cookedToRaw' : 'rawToCooked'
grams.value = null
} }
</script> </script>
<style scoped> <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 { .results-list {
list-style: none; list-style: none;
background: var(--color-surface); background: var(--color-surface);
border: 1.5px solid var(--color-border); border: 1.5px solid var(--color-border);
border-radius: var(--radius); border-top: none;
border-radius: 0 0 var(--radius) var(--radius);
overflow: hidden; overflow: hidden;
margin-bottom: 16px;
box-shadow: var(--shadow-sm); box-shadow: var(--shadow-sm);
} }
@@ -144,110 +202,178 @@ function reset() {
.result-item:last-child { border-bottom: none; } .result-item:last-child { border-bottom: none; }
.result-item:active { background: var(--color-bg); } .result-item:active { background: var(--color-bg); }
.result-food { font-weight: 600; text-transform: capitalize; } .result-food { font-weight: 600; text-transform: capitalize; }
.result-method { font-size: 0.85rem; color: var(--color-muted); 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; } /* ── converter card ───────────────────────────────── */
.converter-card {
.selected-chip { background: var(--color-surface);
display: inline-flex; border-radius: var(--radius);
align-items: center; border: 1.5px solid var(--color-border);
gap: 8px; box-shadow: var(--shadow-md);
background: var(--color-primary-muted); overflow: hidden;
border-radius: var(--radius-full);
padding: 6px 12px 6px 14px;
align-self: flex-start;
} }
.chip-text { .card-top {
font-size: 0.9rem; display: flex;
font-weight: 600; align-items: center;
color: var(--color-primary); justify-content: space-between;
padding: 14px 16px;
}
.food-info {
display: flex;
align-items: center;
gap: 6px;
text-transform: capitalize; 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; background: none;
color: var(--color-primary); color: var(--color-primary);
font-size: 0.85rem;
font-weight: 600;
min-height: unset; 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; display: flex;
align-items: center; align-items: center;
opacity: 0.7; padding: 20px 16px;
gap: 12px;
} }
.direction-toggle { display: flex; gap: 8px; } .calc-side {
.toggle-btn {
flex: 1; 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; display: flex;
flex-direction: column; flex-direction: column;
gap: 4px; gap: 6px;
} }
.result-formula { .calc-label {
font-size: 0.8rem; font-size: 0.72rem;
color: rgba(255,255,255,0.65); 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; display: flex;
align-items: baseline; align-items: baseline;
justify-content: center;
gap: 4px; gap: 4px;
min-height: 44px;
align-items: center;
} }
.result-value { .output-value {
font-size: 3rem; font-size: 2rem;
font-weight: 800; font-weight: 800;
color: #fff; letter-spacing: -0.03em;
letter-spacing: -0.02em; color: var(--color-primary);
line-height: 1;
} }
.result-unit { .output-placeholder {
font-size: 1.4rem; font-size: 2rem;
color: rgba(255,255,255,0.8); font-weight: 300;
font-weight: 600; color: var(--color-border);
} }
.result-desc { /* nasconde "g" finché non c'è un risultato */
font-size: 0.85rem; .calc-unit { visibility: hidden; }
color: rgba(255,255,255,0.65); .calc-input-wrap .calc-unit,
margin-top: 2px; .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> </style>

View File

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