feat: guida utente in-app e fix grafici Convertitore
- DocsPanel: pannello documentazione slide-from-right con nav a pill, IntersectionObserver per sezione attiva, card con step numerati e callout tip - InfoPanel: bottone "Guida" emette open-docs invece di aprire link esterno - App: integra DocsPanel con v-model showDocs - Converter: fix capitalize food names (capFirst invece di text-transform), simmetria visiva input/output (stesso underline e font-size 1.6rem), rimosso CSS morto (doppio align-items, doppio background, visibility hack) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -14,7 +14,8 @@
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<InfoPanel v-model="showInfo" />
|
||||
<InfoPanel v-model="showInfo" @open-docs="showDocs = true" />
|
||||
<DocsPanel v-model="showDocs" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -22,12 +23,14 @@
|
||||
import { ref } from 'vue'
|
||||
import BottomNav from './components/BottomNav.vue'
|
||||
import InfoPanel from './components/InfoPanel.vue'
|
||||
import DocsPanel from './components/DocsPanel.vue'
|
||||
import MealPlanner from './pages/MealPlanner.vue'
|
||||
import Converter from './pages/Converter.vue'
|
||||
import ShoppingList from './pages/ShoppingList.vue'
|
||||
|
||||
const page = ref('meal')
|
||||
const page = ref('meal')
|
||||
const showInfo = ref(false)
|
||||
const showDocs = ref(false)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
505
src/components/DocsPanel.vue
Normal file
505
src/components/DocsPanel.vue
Normal file
@@ -0,0 +1,505 @@
|
||||
<template>
|
||||
<Transition name="slide-right">
|
||||
<div v-if="modelValue" class="docs-panel" role="dialog" aria-label="Guida utente">
|
||||
|
||||
<!-- Header sticky: back arrow + titolo centrato + spacer bilanciante -->
|
||||
<div class="docs-header">
|
||||
<button class="btn-back" @click="$emit('update:modelValue', false)" aria-label="Torna indietro">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="15 18 9 12 15 6"/>
|
||||
</svg>
|
||||
</button>
|
||||
<span class="docs-title">Guida</span>
|
||||
<!-- spacer identico al btn-back per centrare il titolo -->
|
||||
<div aria-hidden="true" class="header-spacer" />
|
||||
</div>
|
||||
|
||||
<!-- Pill nav scrollabile — IntersectionObserver aggiorna la pill attiva -->
|
||||
<div class="section-nav" role="tablist" aria-label="Sezioni">
|
||||
<button
|
||||
v-for="s in sections"
|
||||
:key="s.id"
|
||||
:class="['nav-pill', { active: activeSection === s.id }]"
|
||||
role="tab"
|
||||
:aria-selected="activeSection === s.id"
|
||||
@click="scrollToSection(s.id)"
|
||||
>{{ s.label }}</button>
|
||||
</div>
|
||||
|
||||
<!-- Contenuto scorrevole -->
|
||||
<div class="docs-scroll" ref="scrollEl">
|
||||
|
||||
<!-- ── Pasti ──────────────────────────────── -->
|
||||
<section data-section="pasti">
|
||||
<div class="section-head">
|
||||
<span class="section-icon" aria-hidden="true">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="3" y="4" width="18" height="18" rx="2"/>
|
||||
<line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/>
|
||||
<line x1="3" y1="10" x2="21" y2="10"/>
|
||||
</svg>
|
||||
</span>
|
||||
<h2 class="section-heading">Pasti</h2>
|
||||
</div>
|
||||
|
||||
<div class="doc-card">
|
||||
<h3 class="card-title">Aggiungere un alimento</h3>
|
||||
<ol class="steps">
|
||||
<li>Tocca il giorno per espandere la card</li>
|
||||
<li>Scegli il pasto: <strong>Colazione</strong>, <strong>Pranzo</strong> o <strong>Cena</strong></li>
|
||||
<li>Scrivi il nome nel campo di testo</li>
|
||||
<li>Premi <kbd>+</kbd> o Invio per aggiungerlo</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="doc-card tip">
|
||||
<p class="tip-label" aria-hidden="true">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12.01" y2="8"/><line x1="12" y1="12" x2="12" y2="16"/>
|
||||
</svg>
|
||||
Suggerimento
|
||||
</p>
|
||||
<p>Premi <strong>Genera lista della spesa</strong> in fondo alla pagina per importare automaticamente tutti gli alimenti della settimana, senza duplicati.</p>
|
||||
</div>
|
||||
|
||||
<div class="doc-card">
|
||||
<h3 class="card-title">Rimuovere un alimento</h3>
|
||||
<p>Tocca il pulsante <strong>×</strong> a destra dell'elemento.</p>
|
||||
</div>
|
||||
|
||||
<div class="doc-card">
|
||||
<h3 class="card-title">Salvataggio automatico</h3>
|
||||
<p>I dati vengono salvati automaticamente sul dispositivo. Non serve premere nessun tasto.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ── Converti ───────────────────────────── -->
|
||||
<section data-section="converti">
|
||||
<div class="section-head">
|
||||
<span class="section-icon" aria-hidden="true">
|
||||
<svg width="18" height="18" 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>
|
||||
</span>
|
||||
<h2 class="section-heading">Converti</h2>
|
||||
</div>
|
||||
|
||||
<div class="doc-card">
|
||||
<h3 class="card-title">Come usarlo</h3>
|
||||
<ol class="steps">
|
||||
<li>Cerca l'alimento nel campo (es. <em>pollo</em>, <em>riso</em>)</li>
|
||||
<li>Seleziona il metodo di cottura dall'elenco</li>
|
||||
<li>Inserisci il peso in grammi</li>
|
||||
<li>Il risultato appare in tempo reale</li>
|
||||
<li>Premi <strong>⇄</strong> per invertire crudo ↔ cotto</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="doc-card">
|
||||
<h3 class="card-title">Alimenti disponibili</h3>
|
||||
<div class="category-table" role="list">
|
||||
<div class="cat-row" v-for="cat in categories" :key="cat.label" role="listitem">
|
||||
<span class="cat-label">{{ cat.label }}</span>
|
||||
<span class="cat-items">{{ cat.items }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="methods-row" aria-label="Metodi di cottura disponibili">
|
||||
<span class="method-chip" v-for="m in cookingMethods" :key="m">{{ m }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ── Spesa ──────────────────────────────── -->
|
||||
<section data-section="spesa">
|
||||
<div class="section-head">
|
||||
<span class="section-icon" aria-hidden="true">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M6 2L3 6v14a2 2 0 002 2h14a2 2 0 002-2V6l-3-4z"/>
|
||||
<line x1="3" y1="6" x2="21" y2="6"/>
|
||||
<path d="M16 10a4 4 0 01-8 0"/>
|
||||
</svg>
|
||||
</span>
|
||||
<h2 class="section-heading">Spesa</h2>
|
||||
</div>
|
||||
|
||||
<div class="doc-card">
|
||||
<h3 class="card-title">Aggiungere un elemento</h3>
|
||||
<p>Scrivi il nome nel campo in alto e premi <kbd>+</kbd> o Invio.</p>
|
||||
</div>
|
||||
|
||||
<div class="doc-card tip">
|
||||
<p class="tip-label" aria-hidden="true">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12.01" y2="8"/><line x1="12" y1="12" x2="12" y2="16"/>
|
||||
</svg>
|
||||
Suggerimento
|
||||
</p>
|
||||
<p>Vai alla tab <strong>Pasti</strong> e premi <strong>Genera lista della spesa</strong> per importare automaticamente gli alimenti pianificati.</p>
|
||||
</div>
|
||||
|
||||
<div class="doc-card">
|
||||
<h3 class="card-title">Spuntare un elemento</h3>
|
||||
<p>Tocca la casella a sinistra. Gli elementi completati vengono spostati in una sezione separata con testo barrato.</p>
|
||||
</div>
|
||||
|
||||
<div class="doc-card">
|
||||
<h3 class="card-title">Rimuovere / svuotare</h3>
|
||||
<p>Tocca <strong>×</strong> per rimuovere un elemento singolo, oppure <strong>Svuota lista</strong> in fondo per eliminare tutto (richiede conferma).</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="docs-footer">BitePlan · v{{ version }} · Davide Grilli</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, nextTick, onUnmounted } from 'vue'
|
||||
import pkg from '../../package.json'
|
||||
|
||||
const props = defineProps({ modelValue: Boolean })
|
||||
defineEmits(['update:modelValue'])
|
||||
|
||||
const version = pkg.version
|
||||
const scrollEl = ref(null)
|
||||
const activeSection = ref('pasti')
|
||||
let observer = null
|
||||
|
||||
const sections = [
|
||||
{ id: 'pasti', label: 'Pasti' },
|
||||
{ id: 'converti', label: 'Converti' },
|
||||
{ id: 'spesa', label: 'Spesa' },
|
||||
]
|
||||
|
||||
const categories = [
|
||||
{ label: 'Cereali e pasta', items: 'Riso (4 varietà), pasta, farro, orzo, quinoa, cous cous' },
|
||||
{ label: 'Legumi secchi', items: 'Ceci, fagioli, lenticchie' },
|
||||
{ label: 'Verdure', items: 'Carote, zucchine, patate, spinaci, broccoli, asparagi e altri' },
|
||||
{ label: 'Carni', items: 'Pollo petto, tacchino fesa, hamburger, vitello' },
|
||||
{ label: 'Pesce', items: 'Tonno, merluzzo, spigola, sogliola' },
|
||||
{ label: 'Uova', items: 'Uovo al tegamino, frittata' },
|
||||
]
|
||||
|
||||
const cookingMethods = ['Bollitura', 'Padella', 'Forno', 'Friggitrice ad aria']
|
||||
|
||||
watch(() => props.modelValue, async (open) => {
|
||||
if (open) {
|
||||
await nextTick()
|
||||
setupObserver()
|
||||
activeSection.value = 'pasti'
|
||||
scrollEl.value?.scrollTo({ top: 0 })
|
||||
} else {
|
||||
observer?.disconnect()
|
||||
observer = null
|
||||
}
|
||||
})
|
||||
|
||||
function setupObserver() {
|
||||
if (!scrollEl.value) return
|
||||
observer?.disconnect()
|
||||
// rootMargin: entra in zona attiva quando la sezione raggiunge il 20% dall'alto del container
|
||||
observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
const visible = entries.filter(e => e.isIntersecting)
|
||||
if (visible.length) activeSection.value = visible[0].target.dataset.section
|
||||
},
|
||||
{ root: scrollEl.value, rootMargin: '0px 0px -65% 0px', threshold: 0 }
|
||||
)
|
||||
scrollEl.value.querySelectorAll('[data-section]').forEach(el => observer.observe(el))
|
||||
}
|
||||
|
||||
function scrollToSection(id) {
|
||||
const el = scrollEl.value?.querySelector(`[data-section="${id}"]`)
|
||||
if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||
}
|
||||
|
||||
onUnmounted(() => observer?.disconnect())
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* ── Panel container ─────────────────────────── */
|
||||
.docs-panel {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
max-width: 480px;
|
||||
margin: 0 auto;
|
||||
background: var(--color-bg);
|
||||
z-index: 300;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ── Animazione slide da destra ──────────────── */
|
||||
/* enter ease-out: risposta immediata, decelera all'arrivo (feel nativo) */
|
||||
.slide-right-enter-active { transition: transform 320ms cubic-bezier(0.25, 1, 0.5, 1); }
|
||||
/* leave ease-in: parte lentamente, accelera — chiusura decisa */
|
||||
.slide-right-leave-active { transition: transform 240ms cubic-bezier(0.55, 0, 1, 0.45); }
|
||||
.slide-right-enter-from,
|
||||
.slide-right-leave-to { transform: translateX(100%); }
|
||||
|
||||
/* ── Header ──────────────────────────────────── */
|
||||
.docs-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
/* safe-area-inset-top: evita sovrapposizione con status bar su iOS */
|
||||
padding: calc(env(safe-area-inset-top, 0px) + 10px) 8px 10px;
|
||||
background: var(--color-surface);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.btn-back {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 44px;
|
||||
min-width: 44px;
|
||||
background: none;
|
||||
color: var(--color-primary);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 0;
|
||||
transition: background var(--transition);
|
||||
}
|
||||
|
||||
.btn-back:active {
|
||||
background: var(--color-primary-muted);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.docs-title {
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
color: var(--color-text);
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
/* spacer uguale al btn-back per centrare il titolo otticamente */
|
||||
.header-spacer { width: 44px; }
|
||||
|
||||
/* ── Navigazione sezioni (pill) ──────────────── */
|
||||
.section-nav {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
padding: 10px 16px;
|
||||
overflow-x: auto;
|
||||
scrollbar-width: none;
|
||||
background: var(--color-surface);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.section-nav::-webkit-scrollbar { display: none; }
|
||||
|
||||
.nav-pill {
|
||||
flex-shrink: 0;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
padding: 0 14px;
|
||||
min-height: 32px;
|
||||
border-radius: var(--radius-full);
|
||||
background: var(--color-bg);
|
||||
color: var(--color-muted);
|
||||
border: 1px solid var(--color-border);
|
||||
transition: background var(--transition), color var(--transition), border-color var(--transition);
|
||||
}
|
||||
|
||||
.nav-pill.active {
|
||||
background: var(--color-primary);
|
||||
color: #fff;
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
/* ── Scroll container ────────────────────────── */
|
||||
.docs-scroll {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 40px);
|
||||
}
|
||||
|
||||
/* ── Section header ──────────────────────────── */
|
||||
section { padding-top: 4px; }
|
||||
section + section { margin-top: 8px; }
|
||||
|
||||
.section-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 24px 16px 10px;
|
||||
}
|
||||
|
||||
.section-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
/* quadratino con sfondo primario muted — identifica visivamente la sezione */
|
||||
background: var(--color-primary-muted);
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--color-primary);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.section-heading {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
/* ── Card contenuto ──────────────────────────── */
|
||||
.doc-card {
|
||||
margin: 0 16px 8px;
|
||||
background: var(--color-surface);
|
||||
border-radius: var(--radius);
|
||||
padding: 14px 16px;
|
||||
border: 1px solid var(--color-border);
|
||||
box-shadow: var(--shadow-sm);
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.55;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 0.82rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: var(--color-muted);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/* ── Card tip / suggerimento ─────────────────── */
|
||||
/* border-left accent: segnala contenuto accessorio senza competere con le card */
|
||||
.doc-card.tip {
|
||||
background: var(--color-primary-muted);
|
||||
border-color: transparent;
|
||||
border-left: 3px solid var(--color-primary);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.tip-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.07em;
|
||||
color: var(--color-primary);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
/* ── Lista numerata con badge ────────────────── */
|
||||
/* counter CSS invece di <span> inline: mantiene semantica <ol> accessibile */
|
||||
.steps {
|
||||
list-style: none;
|
||||
counter-reset: steps;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 9px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.steps li {
|
||||
counter-increment: steps;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.steps li::before {
|
||||
content: counter(steps);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 22px;
|
||||
height: 22px;
|
||||
background: var(--color-primary);
|
||||
color: #fff;
|
||||
border-radius: 50%;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 700;
|
||||
flex-shrink: 0;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
/* ── Kbd inline ──────────────────────────────── */
|
||||
kbd {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 1px 7px;
|
||||
background: var(--color-bg);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 5px;
|
||||
font-family: inherit;
|
||||
font-size: 0.85em;
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
/* ── Tabella categorie alimenti ──────────────── */
|
||||
.category-table {
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
overflow: hidden;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.cat-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
padding: 9px 12px;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
font-size: 0.86rem;
|
||||
}
|
||||
|
||||
.cat-row:last-child { border-bottom: none; }
|
||||
|
||||
.cat-label {
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
flex-shrink: 0;
|
||||
min-width: 110px;
|
||||
}
|
||||
|
||||
.cat-items { color: var(--color-muted); font-size: 0.82rem; }
|
||||
|
||||
/* ── Chip metodi cottura ─────────────────────── */
|
||||
.methods-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.method-chip {
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
padding: 3px 10px;
|
||||
background: var(--color-bg);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-full);
|
||||
color: var(--color-muted);
|
||||
}
|
||||
|
||||
/* ── Footer ──────────────────────────────────── */
|
||||
.docs-footer {
|
||||
margin: 24px 16px 0;
|
||||
text-align: center;
|
||||
font-size: 0.78rem;
|
||||
color: var(--color-muted);
|
||||
opacity: 0.6;
|
||||
}
|
||||
</style>
|
||||
@@ -32,16 +32,15 @@
|
||||
<span class="info-label">Licenza</span>
|
||||
<span class="info-value">EUPL v1.2</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">Documentazione</span>
|
||||
<a class="info-link" href="https://docs.biteplan.example.com" target="_blank" rel="noopener">
|
||||
<button class="info-row info-row--btn" @click="$emit('open-docs')" aria-label="Apri guida">
|
||||
<span class="info-label">Guida</span>
|
||||
<span class="info-link">
|
||||
Apri
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6"/>
|
||||
<polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<polyline points="9 18 15 12 9 6"/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -52,7 +51,7 @@
|
||||
import pkg from '../../package.json'
|
||||
import appIcon from '../../assets/icon-only.png'
|
||||
defineProps({ modelValue: Boolean })
|
||||
defineEmits(['update:modelValue'])
|
||||
defineEmits(['update:modelValue', 'open-docs'])
|
||||
const version = pkg.version
|
||||
</script>
|
||||
|
||||
@@ -150,6 +149,16 @@ const version = pkg.version
|
||||
|
||||
.info-row:last-child { border-bottom: none; }
|
||||
|
||||
/* riga cliccabile: reset button, mantiene layout identico alle righe statiche */
|
||||
.info-row--btn {
|
||||
width: 100%;
|
||||
min-height: unset;
|
||||
border-radius: 0;
|
||||
background: var(--color-surface);
|
||||
text-align: left;
|
||||
}
|
||||
.info-row--btn:active { background: var(--color-bg); opacity: 1; }
|
||||
|
||||
.info-label {
|
||||
font-size: 0.9rem;
|
||||
color: var(--color-text);
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
class="result-item"
|
||||
@click="selectItem(r)"
|
||||
>
|
||||
<span class="result-food">{{ r.food }}</span>
|
||||
<span class="result-food">{{ capFirst(r.food) }}</span>
|
||||
<span class="result-method">{{ capFirst(r.method) }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -37,7 +37,7 @@
|
||||
<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-name">{{ capFirst(selected.food) }}</span>
|
||||
<span class="food-sep">·</span>
|
||||
<span class="food-method">{{ capFirst(selected.method) }}</span>
|
||||
</div>
|
||||
@@ -64,7 +64,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn-swap" @click="swapDirection" :title="direction === 'rawToCooked' ? 'Inverti direzione' : 'Inverti direzione'">
|
||||
<button class="btn-swap" @click="swapDirection" aria-label="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"/>
|
||||
@@ -73,12 +73,12 @@
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div class="calc-side output-side" :class="{ 'has-result': result !== null }">
|
||||
<div class="calc-side output-side">
|
||||
<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>
|
||||
<span v-if="result !== null" class="calc-unit">g</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -206,7 +206,7 @@ function swapDirection() {
|
||||
|
||||
.result-item:last-child { border-bottom: none; }
|
||||
.result-item:active { background: var(--color-bg); }
|
||||
.result-food { font-weight: 600; text-transform: capitalize; }
|
||||
.result-food { font-weight: 600; }
|
||||
.result-method { font-size: 0.85rem; color: var(--color-muted); }
|
||||
|
||||
/* ── converter card ───────────────────────────────── */
|
||||
@@ -231,12 +231,11 @@ function swapDirection() {
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.food-name { font-weight: 700; font-size: 1rem; text-transform: capitalize; }
|
||||
.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;
|
||||
@@ -284,7 +283,7 @@ function swapDirection() {
|
||||
|
||||
/* override globale per input dentro la card */
|
||||
.calc-input {
|
||||
font-size: 1.5rem;
|
||||
font-size: 1.6rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.02em;
|
||||
border: none;
|
||||
@@ -308,6 +307,8 @@ function swapDirection() {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-muted);
|
||||
align-self: flex-end;
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
|
||||
/* bottone swap centrale */
|
||||
@@ -332,33 +333,31 @@ function swapDirection() {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* colonna output */
|
||||
.output-side .calc-output {
|
||||
/* colonna output: underline visivo per simmetria con il lato input */
|
||||
.calc-output {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
min-height: 44px;
|
||||
align-items: center;
|
||||
border-bottom: 2px solid var(--color-border);
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.output-value {
|
||||
font-size: 2rem;
|
||||
font-size: 1.6rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.03em;
|
||||
color: var(--color-primary);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.output-placeholder {
|
||||
font-size: 2rem;
|
||||
font-size: 1.6rem;
|
||||
font-weight: 300;
|
||||
color: var(--color-border);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
|
||||
Reference in New Issue
Block a user