chore: initial project setup with documentation and design assets

Add project foundation: CLAUDE.md, requirements tracking system,
technical architecture docs, Firestore setup guide, device testing guide,
and Stitch design mockups for Precision Vitality app.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-16 09:44:35 +02:00
commit 7fd03a99ba
19 changed files with 2765 additions and 0 deletions

41
.gitignore vendored Normal file
View File

@@ -0,0 +1,41 @@
# Dependencies
node_modules/
.npm
# Build
www/
dist/
# Capacitor native projects
android/
ios/
# Environment files (contain secrets)
src/environments/environment.ts
src/environments/environment.prod.ts
# Firebase
.firebase/
google-services.json
GoogleService-Info.plist
functions/node_modules/
functions/lib/
# IDE
.idea/
.vscode/
*.swp
*.swo
*~
# OS
.DS_Store
Thumbs.db
# Keystore (signing keys)
*.keystore
*.jks
# Misc
*.log
npm-debug.log*

96
CLAUDE.md Normal file
View File

@@ -0,0 +1,96 @@
# CalorieTracker (Precision Vitality) — CLAUDE.md
## Progetto
App mobile per il tracciamento calorico giornaliero con stima AI dei pasti.
Nome UI: **Precision Vitality** | Nome progetto: **CalorieTracker**
## Stack Tecnologico
- **Frontend**: Ionic 7+ / Angular 17+ / TypeScript
- **Capacitor**: 5+ (Android + iOS)
- **Database**: Firebase Firestore (Spark plan gratuito)
- **Auth**: Firebase Anonymous Auth (upgrade Google Sign-In futuro)
- **AI**: Anthropic Claude API (claude-haiku-4-5-20251001) via proxy sicuro
- **Chart**: Chart.js + ng2-charts
- **Storage locale**: @ionic/storage-angular
## Repository
- **Remote**: git.i-a.run (Gitea) — `emanuele/traccia-calorica`
- **Branch principale**: `main`
- **Lingua codice**: inglese (variabili, commenti tecnici)
- **Lingua UI**: italiano
## Design System — "Precision Vitality"
- Riferimento design: `stitch_calorietracker_design_brief_brief/`
- **Font**: Lexend (dati/numeri) + Plus Jakarta Sans (UI)
- **Colori primari**: Verde `#006b1b`, Accent arancione `#FF7043`, Errore `#F44336`
- **Background**: `#f6f6f6` (surface), `#f0f1f1` (container-low), `#ffffff` (cards)
- **Testo**: `#2d2f2f` (primario), `on-surface-variant` (secondario)
- **Regola NO-LINE**: niente bordi 1px — separazione solo tramite tonal shifts
- **Elevazione**: tonal layering, no drop shadows su card statiche
- **Icone**: Material Icons (Outlined/Two-Tone, mai Filled pesanti)
## Struttura Tab (da design)
1. **JOURNAL** (Home) — diario pasti + FAB arancione
2. **STATS** — statistiche giornaliere/settimanali
3. **PLANS** — (futuro) piani alimentari
4. **PROFILE** — (futuro) profilo utente
## Struttura Cartelle
```
src/
app/
core/ # Servizi singleton, guards, interceptors
services/
meal.service.ts
calorie-estimator.service.ts
auth.service.ts
shared/ # Componenti, pipe, direttive condivise
features/
home/ # Tab Journal/Home
modals/ # Manual input + LLM input modals
stats/ # Tab Statistiche (daily + weekly)
search/ # Ricerca pasti
profile/ # Tab Profile (futuro)
assets/
data/
food-dictionary.md
environments/
environment.ts
environment.prod.ts
```
## Convenzioni
- Lazy loading per ogni feature module
- Servizi in `core/services/` — singleton con `providedIn: 'root'`
- Componenti condivisi in `shared/`
- Ogni modal è un componente standalone
- Commit messages in inglese, descrittivi
- Branch naming: `feature/REQ-XXX-descrizione`, `fix/REQ-XXX-descrizione`
## Documenti di Progetto
- `docs/REQUIREMENTS.md` — requisiti funzionali completi
- `docs/TRACKING.md` — tracciamento stato requisiti sessione per sessione
- `docs/TECHNICAL.md` — architettura e decisioni tecniche
- `docs/FIRESTORE_SETUP.md` — guida configurazione Firebase/Firestore
- `docs/DEVICE_TESTING.md` — test su device e build APK senza Android Studio
## Decisioni Architetturali
- **Proxy API Claude**: Firebase Cloud Function (free tier) — API key mai nel bundle
- **Cancellazione pasti**: hard delete (semplice, niente soft delete per ora)
- **Obiettivo calorico**: salvato in @ionic/storage locale (default 2000 kcal)
- **Ricerca**: client-side su ultimi 30 giorni
## Comandi Utili
```bash
ionic serve # Dev server
ionic build # Build produzione
npx cap sync # Sync Capacitor
npx cap open android # Apri in Android Studio
```
## Note per Claude
- Leggere sempre il design in `stitch_calorietracker_design_brief_brief/` prima di implementare UI
- Seguire il DESIGN.md (Precision Vitality) per colori, tipografia e componenti
- Aggiornare `docs/TRACKING.md` dopo ogni sessione di lavoro
- Non esporre mai API keys nel codice client
- Verificare `docs/TRACKING.md` per sapere cosa è stato completato nelle sessioni precedenti

416
docs/DEVICE_TESTING.md Normal file
View File

@@ -0,0 +1,416 @@
# Test su Device e Creazione APK senza Android Studio
> Guida per testare l'app su dispositivo reale e generare APK usando solo CLI (senza Android Studio).
> Ultimo aggiornamento: 16/04/2026
---
## 1. Prerequisiti
### Software Necessario
| Software | Versione | Download | Note |
|----------|----------|----------|------|
| Node.js | 18+ | nodejs.org | Già installato |
| Java JDK | 17+ | adoptium.net | Per Gradle |
| Android SDK (cmdline-tools) | Latest | developer.android.com | Solo CLI, no IDE |
| Gradle | 8+ | Incluso nel progetto | Via wrapper |
### Installare Android SDK Command Line Tools (senza Android Studio)
#### Windows
```bash
# 1. Scaricare command line tools da:
# https://developer.android.com/studio#command-line-tools-only
# 2. Creare struttura directory
mkdir -p C:/Android/cmdline-tools/latest
# 3. Estrarre lo zip in C:/Android/cmdline-tools/latest/
# (i file bin/, lib/ etc. devono essere DENTRO latest/)
# 4. Configurare variabili d'ambiente (aggiungere al profilo bash o System env)
export ANDROID_HOME=C:/Android
export ANDROID_SDK_ROOT=C:/Android
export PATH=$PATH:$ANDROID_HOME/cmdline-tools/latest/bin
export PATH=$PATH:$ANDROID_HOME/platform-tools
```
#### Installare componenti SDK
```bash
# Accettare licenze
sdkmanager --licenses
# Installare componenti necessari
sdkmanager "platform-tools"
sdkmanager "platforms;android-34"
sdkmanager "build-tools;34.0.0"
# Verificare installazione
sdkmanager --list | head -20
```
### Configurare JAVA_HOME
```bash
# Windows — verificare path Java
# Il JDK 17+ deve essere installato
export JAVA_HOME="C:/Program Files/Eclipse Adoptium/jdk-17.x.x-hotspot"
# oppure
export JAVA_HOME="C:/Program Files/Java/jdk-17"
# Verificare
java -version
javac -version
```
---
## 2. Preparazione Progetto per Android
### Build dell'app Ionic
```bash
# Build di produzione
ionic build --prod
# Oppure build di sviluppo (più veloce, con source maps)
ionic build
```
### Aggiungere piattaforma Android (prima volta)
```bash
npx cap add android
```
### Sync dopo ogni build
```bash
npx cap sync android
```
### Copiare solo i file web (senza sync dipendenze native)
```bash
npx cap copy android
```
---
## 3. Build APK da Riga di Comando
### APK di Debug (per test)
```bash
# Navigare nella directory android
cd android
# Build con Gradle wrapper (non serve Gradle installato globalmente)
./gradlew assembleDebug
# L'APK si trova in:
# android/app/build/outputs/apk/debug/app-debug.apk
```
### APK di Release (per distribuzione)
```bash
# 1. Generare keystore (solo la prima volta)
keytool -genkey -v \
-keystore calorie-tracker-release.keystore \
-alias calorie-tracker \
-keyalg RSA \
-keysize 2048 \
-validity 10000
# 2. Configurare signing in android/app/build.gradle
# Aggiungere nella sezione android {}:
```
```groovy
// In android/app/build.gradle
android {
// ... configurazione esistente ...
signingConfigs {
release {
storeFile file('calorie-tracker-release.keystore')
storePassword 'LA_TUA_PASSWORD'
keyAlias 'calorie-tracker'
keyPassword 'LA_TUA_PASSWORD'
}
}
buildTypes {
release {
signingConfig signingConfigs.release
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
```
```bash
# 3. Build release APK
cd android
./gradlew assembleRelease
# L'APK si trova in:
# android/app/build/outputs/apk/release/app-release.apk
```
### AAB (Android App Bundle) per Google Play
```bash
cd android
./gradlew bundleRelease
# L'AAB si trova in:
# android/app/build/outputs/bundle/release/app-release.aab
```
---
## 4. Installare APK su Device
### Via USB (ADB)
```bash
# 1. Abilitare "Opzioni sviluppatore" sul telefono:
# Impostazioni → Info telefono → Tocca 7 volte "Numero build"
# 2. Abilitare "Debug USB" nelle opzioni sviluppatore
# 3. Collegare il telefono via USB
# 4. Verificare che il device sia rilevato
adb devices
# Deve mostrare il device con stato "device"
# 5. Installare l'APK
adb install android/app/build/outputs/apk/debug/app-debug.apk
# Per reinstallare (sovrascrivere)
adb install -r android/app/build/outputs/apk/debug/app-debug.apk
```
### Via WiFi (ADB wireless)
```bash
# 1. Collegare prima via USB
adb devices
# 2. Abilitare TCP/IP
adb tcpip 5555
# 3. Trovare IP del telefono (Impostazioni → WiFi → IP)
adb connect 192.168.1.XXX:5555
# 4. Scollegare USB — ora si può installare via WiFi
adb install android/app/build/outputs/apk/debug/app-debug.apk
```
### Senza ADB — Trasferimento diretto
1. Copiare l'APK sul telefono (via USB, email, cloud, etc.)
2. Sul telefono: abilitare "Installa da origini sconosciute"
3. Aprire il file APK → Installa
---
## 5. Test in Tempo Reale (Live Reload)
### Live Reload su Device via WiFi
Il modo più veloce per testare durante lo sviluppo:
```bash
# 1. Trovare l'IP della macchina di sviluppo
# Windows:
ipconfig | grep "IPv4"
# Es: 192.168.1.100
# 2. Avviare Ionic con external host
ionic serve --external --host=0.0.0.0
# 3. Modificare capacitor.config.ts per il live reload
```
```typescript
// capacitor.config.ts — SOLO PER SVILUPPO
const config: CapacitorConfig = {
appId: 'com.precisionvitality.app',
appName: 'Precision Vitality',
webDir: 'www',
server: {
// SOLO per live reload development!
url: 'http://192.168.1.100:8100', // IP della tua macchina
cleartext: true
}
};
```
```bash
# 4. Sync e reinstallare
npx cap sync android
cd android && ./gradlew assembleDebug && cd ..
adb install -r android/app/build/outputs/apk/debug/app-debug.apk
# 5. Aprire l'app sul device — le modifiche al codice si rifletteranno in real-time!
```
> **IMPORTANTE**: Rimuovere la sezione `server.url` da `capacitor.config.ts` prima del build di produzione!
---
## 6. Debug Remoto
### Chrome DevTools (per WebView)
1. Collegare device via USB con debug abilitato
2. Aprire Chrome sul PC → `chrome://inspect`
3. Il WebView dell'app apparirà nella lista
4. Cliccare "inspect" → si apre DevTools con console, network, elements
### Logcat (log nativi Android)
```bash
# Tutti i log
adb logcat
# Filtrare per l'app
adb logcat | grep -i "capacitor\|precision"
# Solo errori
adb logcat *:E
# Pulire e seguire
adb logcat -c && adb logcat
```
---
## 7. Script Automazione
### Script build + install completo
Creare `scripts/build-and-install.sh`:
```bash
#!/bin/bash
set -e
echo "=== Building Ionic app ==="
ionic build
echo "=== Syncing Capacitor ==="
npx cap sync android
echo "=== Building APK debug ==="
cd android
./gradlew assembleDebug
cd ..
APK_PATH="android/app/build/outputs/apk/debug/app-debug.apk"
echo "=== Installing on device ==="
adb install -r "$APK_PATH"
echo "=== Done! APK: $APK_PATH ==="
echo "=== App launched ==="
adb shell am start -n com.precisionvitality.app/.MainActivity
```
### Script build release
Creare `scripts/build-release.sh`:
```bash
#!/bin/bash
set -e
echo "=== Building Ionic production ==="
ionic build --prod
echo "=== Syncing Capacitor ==="
npx cap sync android
echo "=== Building Release APK ==="
cd android
./gradlew assembleRelease
cd ..
APK_PATH="android/app/build/outputs/apk/release/app-release.apk"
echo "=== Release APK: $APK_PATH ==="
ls -lh "$APK_PATH"
```
---
## 8. Troubleshooting
### Errore: "ANDROID_HOME not set"
```bash
# Verificare che le variabili siano settate
echo $ANDROID_HOME
echo $ANDROID_SDK_ROOT
# Se vuote, settarle nel profilo (~/.bashrc o ~/.bash_profile)
export ANDROID_HOME=C:/Android
export ANDROID_SDK_ROOT=C:/Android
```
### Errore: "SDK location not found"
```bash
# Creare/verificare android/local.properties
echo "sdk.dir=C:\\\\Android" > android/local.properties
```
### Errore: "No connected devices"
```bash
# Verificare driver USB (Windows)
# Installare Google USB Driver o driver OEM del produttore
# Verificare che il device sia in modalità debug
adb kill-server
adb start-server
adb devices
```
### Errore: "INSTALL_FAILED_UPDATE_INCOMPATIBLE"
```bash
# Disinstallare la versione precedente
adb uninstall com.precisionvitality.app
# Poi reinstallare
adb install android/app/build/outputs/apk/debug/app-debug.apk
```
### Build lento
```bash
# Aggiungere a android/gradle.properties:
org.gradle.jvmargs=-Xmx4096m
org.gradle.daemon=true
org.gradle.parallel=true
org.gradle.caching=true
```
---
## 9. Checklist Test su Device
### Prima del test
- [ ] Node.js e Ionic CLI installati
- [ ] Android SDK command line tools installati
- [ ] Java JDK 17+ installato
- [ ] ANDROID_HOME e JAVA_HOME configurati
- [ ] Device in modalità debug USB
### Test funzionali
- [ ] App si avvia correttamente
- [ ] Navigazione tra tab funzionante
- [ ] FAB visibile e cliccabile
- [ ] Modal input numerico funzionante
- [ ] Modal input LLM funzionante
- [ ] Tastiera nativa si apre correttamente
- [ ] Scroll liste fluido
- [ ] Grafico statistiche visibile
- [ ] Ricerca funzionante
- [ ] Connessione a Firestore (online)
- [ ] Funzionamento offline (attivare modalità aereo)
### Test performance
- [ ] Tempo avvio app < 3 secondi
- [ ] Scroll fluido (60fps)
- [ ] Risposta AI < 5 secondi
- [ ] Nessun crash in 10 minuti di utilizzo
### Test compatibilità
- [ ] Android 10+ (API 29+)
- [ ] Schermo piccolo (5") e grande (6.5"+)
- [ ] Orientamento portrait
- [ ] Tastiera non copre input

349
docs/FIRESTORE_SETUP.md Normal file
View File

@@ -0,0 +1,349 @@
# Guida Configurazione Firebase & Firestore
> Guida passo-passo per configurare Firebase, Firestore e Cloud Functions per CalorieTracker.
> Ultimo aggiornamento: 16/04/2026
---
## 1. Creazione Progetto Firebase
### Passo 1 — Console Firebase
1. Andare su [console.firebase.google.com](https://console.firebase.google.com)
2. Cliccare "Aggiungi progetto"
3. Nome progetto: `calorie-tracker` (o simile disponibile)
4. **Disabilitare** Google Analytics (non necessario per ora)
5. Cliccare "Crea progetto"
### Passo 2 — Aggiungere App Web
1. Nella dashboard progetto, cliccare l'icona **Web** (`</>`)
2. Nome app: `CalorieTracker Web`
3. **Non** abilitare Firebase Hosting (non necessario)
4. Copiare la configurazione Firebase:
```javascript
const firebaseConfig = {
apiKey: "AIza...",
authDomain: "calorie-tracker-XXXXX.firebaseapp.com",
projectId: "calorie-tracker-XXXXX",
storageBucket: "calorie-tracker-XXXXX.appspot.com",
messagingSenderId: "123456789",
appId: "1:123456789:web:abcdef"
};
```
5. Salvare questi valori in `environment.ts` e `environment.prod.ts`
---
## 2. Abilitare Firestore
### Passo 1 — Creare Database
1. Nel menu laterale Firebase: **Build → Firestore Database**
2. Cliccare "Crea database"
3. Selezionare località: **europe-west1 (Belgio)** — più vicino all'Italia
4. Selezionare "Inizia in modalità produzione"
5. Cliccare "Abilita"
### Passo 2 — Regole di Sicurezza
Nella tab **Regole** di Firestore, incollare:
```
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Nessun accesso di default
match /{document=**} {
allow read, write: if false;
}
// Accesso pasti: solo utente proprietario
match /users/{userId}/meals/{mealId} {
allow read, write: if request.auth != null
&& request.auth.uid == userId;
// Validazione scrittura
allow create: if request.auth != null
&& request.auth.uid == userId
&& request.resource.data.calories is number
&& request.resource.data.calories > 0
&& request.resource.data.calories <= 9999
&& request.resource.data.inputType in ['manual', 'llm']
&& request.resource.data.timestamp is timestamp
&& request.resource.data.createdAt is timestamp;
allow update: if request.auth != null
&& request.auth.uid == userId;
allow delete: if request.auth != null
&& request.auth.uid == userId;
}
}
}
```
### Passo 3 — Indici
Nella tab **Indici** di Firestore, creare indice composto:
| Collection | Campi | Query Scope |
|------------|-------|-------------|
| `users/{userId}/meals` | `timestamp` DESC | Collection |
> **Nota**: Firestore creerà automaticamente gli indici necessari quando l'app esegue le prime query. In alternativa, il file `firestore.indexes.json` nel progetto definisce gli indici programmaticamente.
---
## 3. Abilitare Authentication
### Passo 1 — Anonymous Auth
1. Nel menu laterale: **Build → Authentication**
2. Cliccare "Inizia"
3. Tab **Metodo di accesso**
4. Abilitare **Anonimo** → switch ON → Salva
### Passo 2 — (Futuro) Google Sign-In
> Per upgrade futuro da Anonymous a Google Sign-In:
> 1. Abilitare provider "Google" in Authentication
> 2. Configurare OAuth consent screen in Google Cloud Console
> 3. Implementare linking account (da anonimo a Google)
---
## 4. Configurazione App Android
### Passo 1 — Aggiungere App Android
1. Dashboard Firebase → "Aggiungi app" → icona Android
2. Package name: `com.precisionvitality.app` (deve corrispondere a `capacitor.config.ts`)
3. App nickname: `CalorieTracker Android`
4. SHA-1: (opzionale per ora, necessario per Google Sign-In)
### Passo 2 — Scaricare google-services.json
1. Scaricare `google-services.json`
2. Copiare in `android/app/google-services.json` (dopo `npx cap add android`)
---
## 5. Configurazione App iOS
### Passo 1 — Aggiungere App iOS
1. Dashboard Firebase → "Aggiungi app" → icona iOS
2. Bundle ID: `com.precisionvitality.app`
3. App nickname: `CalorieTracker iOS`
### Passo 2 — Scaricare GoogleService-Info.plist
1. Scaricare `GoogleService-Info.plist`
2. Copiare in `ios/App/App/GoogleService-Info.plist` (dopo `npx cap add ios`)
---
## 6. Setup Firebase Cloud Functions (Proxy AI)
### Passo 1 — Upgrade a Blaze Plan (Pay-as-you-go)
> **ATTENZIONE**: Le Cloud Functions richiedono il piano Blaze (pay-as-you-go).
> Il piano Blaze include comunque il tier gratuito di Firestore e Auth.
> Non ci saranno costi se il traffico resta nel free tier.
1. Console Firebase → icona ingranaggio → Utilizzo e fatturazione
2. Upgrade a Blaze
3. Impostare budget alert: $5/mese
### Alternativa gratuita: Se non si vuole il piano Blaze
- Usare un backend NestJS su VPS esistente come proxy
- Oppure usare Cloudflare Workers (free tier: 100k req/giorno)
### Passo 2 — Inizializzare Cloud Functions
```bash
# Nella root del progetto
npm install -g firebase-tools
firebase login
firebase init functions
# Selezionare:
# - Linguaggio: TypeScript
# - ESLint: Sì
# - Installare dipendenze: Sì
```
### Passo 3 — Configurare Secret per API Key
```bash
# Salvare API key come secret (non in codice!)
firebase functions:secrets:set ANTHROPIC_API_KEY
# Inserire: sk-ant-...
```
### Passo 4 — Implementare la Function
File `functions/src/index.ts`:
```typescript
import { onRequest } from 'firebase-functions/v2/https';
import { defineSecret } from 'firebase-functions/params';
import Anthropic from '@anthropic-ai/sdk';
const anthropicKey = defineSecret('ANTHROPIC_API_KEY');
export const estimateCalories = onRequest(
{
cors: true,
region: 'europe-west1',
secrets: [anthropicKey],
timeoutSeconds: 15,
memory: '256MiB',
},
async (req, res) => {
// Verificare metodo POST
if (req.method !== 'POST') {
res.status(405).send('Method Not Allowed');
return;
}
// Verificare auth token
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith('Bearer ')) {
res.status(401).send('Unauthorized');
return;
}
// Estrarre e validare input
const { description } = req.body;
if (!description || typeof description !== 'string' || description.length > 500) {
res.status(400).send('Invalid description');
return;
}
try {
const client = new Anthropic({ apiKey: anthropicKey.value() });
const message = await client.messages.create({
model: 'claude-haiku-4-5-20251001',
max_tokens: 256,
system: `Sei un nutrizionista. Stima le calorie del pasto descritto.
Rispondi SOLO con JSON: {"calories": number, "confidence": "high"|"medium"|"low", "explanation": "string"}`,
messages: [{ role: 'user', content: description }],
});
// Parsare risposta
const text = message.content[0].type === 'text' ? message.content[0].text : '';
const jsonStr = text.replace(/```json?\n?/g, '').replace(/```/g, '').trim();
const result = JSON.parse(jsonStr);
res.json(result);
} catch (error) {
console.error('Estimation error:', error);
res.status(500).json({ error: 'Estimation failed' });
}
}
);
```
### Passo 5 — Deploy
```bash
cd functions
npm install @anthropic-ai/sdk
firebase deploy --only functions
```
---
## 7. Integrazione Angular
### Installare dipendenze
```bash
npm install @angular/fire firebase
```
### Configurare in app.config.ts (Angular 17+ standalone)
```typescript
import { provideFirebaseApp, initializeApp } from '@angular/fire/app';
import { provideFirestore, getFirestore, enableIndexedDbPersistence } from '@angular/fire/firestore';
import { provideAuth, getAuth } from '@angular/fire/auth';
import { environment } from '../environments/environment';
export const appConfig: ApplicationConfig = {
providers: [
provideFirebaseApp(() => initializeApp(environment.firebase)),
provideFirestore(() => {
const firestore = getFirestore();
enableIndexedDbPersistence(firestore);
return firestore;
}),
provideAuth(() => getAuth()),
// ... altri providers
]
};
```
---
## 8. File di Configurazione Locale
### `firestore.rules` (nella root del progetto)
Copiare le regole dal Passo 2 della Sezione 2.
### `firestore.indexes.json`
```json
{
"indexes": [
{
"collectionGroup": "meals",
"queryScope": "COLLECTION",
"fields": [
{ "fieldPath": "timestamp", "order": "DESCENDING" }
]
}
],
"fieldOverrides": []
}
```
### `.firebaserc`
```json
{
"projects": {
"default": "calorie-tracker-XXXXX"
}
}
```
### `firebase.json`
```json
{
"firestore": {
"rules": "firestore.rules",
"indexes": "firestore.indexes.json"
},
"functions": {
"source": "functions",
"runtime": "nodejs18"
}
}
```
---
## 9. Limiti Free Tier Firebase (Spark / Blaze)
| Risorsa | Limite Gratuito | Note |
|---------|----------------|------|
| Firestore letture | 50.000/giorno | ~1.600 utenti × 30 letture/giorno |
| Firestore scritture | 20.000/giorno | ~2.000 utenti × 10 pasti/giorno |
| Firestore storage | 1 GiB | Sufficiente per ~5M documenti pasto |
| Auth utenti | Illimitati | Anonymous + email + social |
| Cloud Functions invocazioni | 2M/mese | ~66.000/giorno |
| Cloud Functions compute | 400.000 GB-secondi/mese | Sufficiente per proxy AI |
---
## 10. Checklist Configurazione
- [ ] Progetto Firebase creato
- [ ] Firestore abilitato (europe-west1)
- [ ] Regole sicurezza configurate
- [ ] Anonymous Auth abilitato
- [ ] App Web registrata + config copiata
- [ ] App Android registrata + google-services.json scaricato
- [ ] (Opzionale) App iOS registrata + GoogleService-Info.plist scaricato
- [ ] Cloud Functions inizializzate
- [ ] ANTHROPIC_API_KEY salvata come secret
- [ ] Cloud Function deployata
- [ ] @angular/fire installato e configurato
- [ ] Test lettura/scrittura funzionante
- [ ] Offline persistence verificata

312
docs/REQUIREMENTS.md Normal file
View File

@@ -0,0 +1,312 @@
# Requisiti — CalorieTracker (Precision Vitality)
> Documento di riferimento per tutti i requisiti funzionali e sistemistici del progetto.
> Ultimo aggiornamento: 16/04/2026
---
## Indice Requisiti
| ID | Tipo | Priorità | Titolo | Dipendenze |
|----|------|----------|--------|------------|
| REQ-001 | SISTEMISTICO | CRITICA | Scaffolding Ionic + Capacitor | — |
| REQ-002 | SISTEMISTICO | CRITICA | Setup Firebase Firestore | REQ-001 |
| REQ-003 | EVOLUTIVO | ALTA | Home screen + FAB | REQ-002 |
| REQ-004 | EVOLUTIVO | ALTA | Modal input numerico | REQ-003 |
| REQ-005 | EVOLUTIVO | ALTA | Modal input LLM | REQ-003, REQ-006 |
| REQ-006 | EVOLUTIVO | ALTA | Servizio Claude API | REQ-001 |
| REQ-007 | EVOLUTIVO | ALTA | CRUD pasti Firestore | REQ-002 |
| REQ-008 | EVOLUTIVO | MEDIA | Statistiche giornaliere | REQ-007 |
| REQ-009 | EVOLUTIVO | MEDIA | Statistiche settimanali | REQ-007 |
| REQ-010 | EVOLUTIVO | MEDIA | Ricerca pasti | REQ-007 |
| REQ-011 | REWORK | MEDIA | Design brief Google Stitch | — |
---
## REQ-001 — Scaffolding Ionic + Capacitor
**Tipo**: SISTEMISTICO | **Priorità**: CRITICA
**Obiettivo**: Scaffolding del progetto Ionic 7+ con Capacitor 5+. Configurazione build nativa Android/iOS. Setup ambiente con struttura cartelle, linting e variabili d'ambiente.
**Stato Target**:
- Progetto Ionic Angular inizializzato con routing e tab layout (Journal, Stats, Plans, Profile)
- Capacitor configurato per Android e iOS
- Variabili ambiente: `ANTHROPIC_API_KEY`, `FIREBASE_*`
- Struttura moduli: `core/`, `shared/`, `features/home`, `features/stats`, `features/search`
- Proxy sviluppo configurato per CORS Anthropic API
**Passi Implementativi**:
1. `ionic start calorie-tracker tabs --type=angular --capacitor`
2. Aggiungere `@capacitor/android` e `@capacitor/ios`
3. Configurare `capacitor.config.ts` con appId e server URL
4. Creare struttura moduli `features/` con lazy loading
5. Aggiungere `@ionic/storage-angular` per cache locale
6. Configurare `environment.ts` e `environment.prod.ts`
7. Verificare build `ionic build` + `npx cap sync`
**Criteri Accettazione**:
- [ ] `ionic serve` avvia l'app senza errori
- [ ] `ionic build` produce `www/` senza errori
- [ ] `npx cap sync` completa senza errori
- [ ] Struttura a 4 tab visibile nel browser (Journal, Stats, Plans, Profile)
- [ ] Variabili d'ambiente lette correttamente in `environment.ts`
---
## REQ-002 — Setup Firebase Firestore
**Tipo**: SISTEMISTICO | **Priorità**: CRITICA | **Dipende da**: REQ-001
**Obiettivo**: Setup Firebase con Firestore come database. Schema dati, regole sicurezza e SDK Angular.
**Schema Firestore**:
```
users/{userId}/
meals/{mealId}/
- id: string
- timestamp: Timestamp
- calories: number
- inputType: 'manual' | 'llm'
- description?: string
- llmConfidence?: 'high' | 'medium' | 'low'
- llmExplanation?: string
- createdAt: Timestamp
```
**Regole Sicurezza**:
- Lettura/scrittura solo all'utente autenticato proprietario
- Nessun accesso cross-user
**Auth**: Anonymous auth abilitato (upgrade Google Sign-In futuro)
**Passi Implementativi**:
1. Creare Firebase project su console
2. Abilitare Firestore in modalità produzione
3. Abilitare Anonymous Authentication
4. Scaricare `google-services.json` e `GoogleService-Info.plist`
5. Installare `@angular/fire` e configurare in `app.module.ts`
6. Definire regole Firestore in `firestore.rules`
7. Creare `FirestoreService` con CRUD per collection `meals`
8. Testare lettura/scrittura da `ionic serve`
**Criteri Accettazione**:
- [ ] Scrittura documento su Firestore funzionante
- [ ] Lettura documenti da Firestore funzionante
- [ ] Regole sicurezza bloccano accesso non autenticato
- [ ] `FirestoreService` espone: `addMeal()`, `getMealsByDate()`, `getMealsRange()`
- [ ] Offline persistence abilitata e testata
---
## REQ-003 — Home Screen + FAB
**Tipo**: EVOLUTIVO | **Priorità**: ALTA | **Dipende da**: REQ-002
**Obiettivo**: Home screen (tab Journal) con totale kcal giornaliero, lista pasti e FAB per aggiunta.
**Specifiche UI** (da design Precision Vitality):
- Header: data odierna centrata (es. "MERCOLEDÌ 16 APRILE")
- Titolo: "Il tuo progresso giornaliero"
- Numero kcal grande (56px, Lexend Bold, verde `#006b1b`)
- Label "calorie oggi"
- Barra progresso: kcal / obiettivo (default 2000)
- Sezione "Diario Alimentare": lista card pasti (ora, descrizione, kcal)
- Badge "AI" (arancione) o "M" (grigio) su ogni pasto
- FAB arancione `#FF7043` in basso a destra con icona "+"
- Empty state: "Nessun pasto registrato oggi. Inizia!"
**Criteri Accettazione**:
- [ ] Totale kcal odierno visibile e aggiornato real-time
- [ ] Lista pasti del giorno visibile e scrollabile
- [ ] FAB visibile e cliccabile su tutti i device
- [ ] Empty state quando nessun pasto registrato
- [ ] Aggiornamento immediato dopo salvataggio nuovo pasto
---
## REQ-004 — Modal Input Numerico
**Tipo**: EVOLUTIVO | **Priorità**: ALTA | **Dipende da**: REQ-003
**Obiettivo**: Modal per inserimento diretto kcal con tastiera numerica.
**Specifiche UI** (da design):
- Titolo: "Inserisci calorie"
- Icona fulmine verde
- Motivazionale: "Ogni dato conta per il tuo obiettivo."
- Campo numerico grande (font 40px), placeholder "0", label "KCAL"
- Campo "Descrizione (opzionale)"
- Bottone "Salva" verde a tutta larghezza
- Suggerimenti veloci: 150 (Spuntino), 450 (Pranzo), 600 (Cena)
**Validazione**: valore > 0 e ≤ 9999
**Criteri Accettazione**:
- [ ] Modal si apre dal FAB
- [ ] Tastiera numerica nativa su mobile
- [ ] Validazione blocca valori ≤ 0 e > 9999
- [ ] Salvataggio corretto su Firestore
- [ ] Toast conferma dopo salvataggio
- [ ] Modal si chiude e Home aggiorna totale
---
## REQ-005 — Modal Input LLM (Descrizione Pasto)
**Tipo**: EVOLUTIVO | **Priorità**: ALTA | **Dipende da**: REQ-003, REQ-006
**Obiettivo**: Modal per descrizione testuale del pasto con stima AI.
**Specifiche UI** (da design):
- Titolo: "Descrivi il pasto"
- Sottotitolo: "Usa il linguaggio naturale per stimare i tuoi nutrienti in pochi secondi."
- Textarea "IL TUO PASTO" con placeholder
- Bottone "Stima calorie" arancione
- Risultato: badge "ALTA PRECISIONE" / "MEDIA" / "BASSA"
- Kcal stimate in grande + breakdown (carbi, proteine, grassi)
- Campo kcal modificabile
- Bottone "Salva" verde
**Criteri Accettazione**:
- [ ] Textarea accetta input testuale
- [ ] Bottone "Stima calorie" chiama API (REQ-006)
- [ ] Loading spinner durante chiamata
- [ ] Risultato kcal e badge confidence corretti
- [ ] Campo kcal modificabile dopo stima
- [ ] Salvataggio su Firestore con `inputType: 'llm'`
- [ ] Gestione errore timeout (messaggio + retry)
---
## REQ-006 — Servizio Claude API
**Tipo**: EVOLUTIVO | **Priorità**: ALTA | **Dipende da**: REQ-001
**Obiettivo**: Servizio Angular per stima calorica via Claude API con proxy sicuro.
**Architettura**: Firebase Cloud Function come proxy (API key mai nel bundle)
**Specifiche**:
- Model: `claude-haiku-4-5-20251001`
- Max tokens: 256
- Food dictionary incluso nel system prompt
- Parser JSON robusto (strip markdown code blocks)
- Timeout: 10 secondi
- Risposta: `{ calories, confidence, explanation }`
**Criteri Accettazione**:
- [ ] Stima kcal corretta per descrizioni test
- [ ] Food dictionary nel system prompt
- [ ] API key non nel bundle app
- [ ] Parser gestisce JSON wrapped in markdown
- [ ] Timeout 10s con errore esplicito
---
## REQ-007 — CRUD Pasti Firestore
**Tipo**: EVOLUTIVO | **Priorità**: ALTA | **Dipende da**: REQ-002
**Obiettivo**: Servizio Angular per CRUD pasti su Firestore.
**Metodi**:
- `addMeal(data)` — scrittura con timestamp e userId
- `getMealsForDate(date)` — query filtrata per giorno (Observable)
- `getMealsInRange(from, to)` — query per range date
- `deleteMeal(mealId)` — hard delete
- `getTotalCaloriesForDate(date)` — aggregazione client-side
**Criteri Accettazione**:
- [ ] `addMeal()` scrive documento corretto
- [ ] `getMealsForDate()` restituisce solo pasti del giorno richiesto
- [ ] `getMealsInRange()` restituisce pasti nel range
- [ ] `deleteMeal()` rimuove documento
- [ ] Observable aggiorna componenti senza reload
- [ ] Funziona offline
---
## REQ-008 — Statistiche Giornaliere
**Tipo**: EVOLUTIVO | **Priorità**: MEDIA | **Dipende da**: REQ-007
**Obiettivo**: Vista giornaliera nel tab Stats con totale kcal, breakdown pasti e progresso obiettivo.
**Specifiche UI**:
- Tab selector: "Oggi" | "Settimana"
- Navigazione tra giorni (frecce)
- Totale kcal, barra progresso (verde/rosso)
- Lista pasti con swipe-to-delete
- Obiettivo calorico: salvato in @ionic/storage (default 2000)
**Criteri Accettazione**:
- [ ] Totale kcal giornaliero corretto
- [ ] Navigazione tra giorni funzionante
- [ ] Barra progresso aggiornata real-time
- [ ] Colore cambia se obiettivo superato
- [ ] Swipe-to-delete funzionante
---
## REQ-009 — Statistiche Settimanali
**Tipo**: EVOLUTIVO | **Priorità**: MEDIA | **Dipende da**: REQ-007
**Obiettivo**: Grafico a barre 7 giorni con media settimanale.
**Specifiche UI** (da design):
- Periodo corrente (es. "14-20 apr") con frecce navigazione
- Grafico barre: barre verdi ≤ obiettivo, rosse > obiettivo
- Asse X: LUN, MAR, MER... (giorno corrente evidenziato)
- Legenda: "In target" (verde), "Eccesso" (rosso)
- Card "RIEPILOGO PRESTAZIONI": media kcal/giorno
- Indicatori: variazione % vs settimana precedente, giorni in target
**Criteri Accettazione**:
- [ ] Grafico a barre con dati reali
- [ ] Colori corretti sopra/sotto obiettivo
- [ ] Media settimanale calcolata
- [ ] Navigazione settimane funzionante
- [ ] Tap su barra apre dettaglio giornaliero
- [ ] Grafico responsive
---
## REQ-010 — Ricerca Pasti
**Tipo**: EVOLUTIVO | **Priorità**: MEDIA | **Dipende da**: REQ-007
**Obiettivo**: Tab ricerca per trovare pasti passati per testo o intervallo date.
**Specifiche**:
- Searchbar con debounce 300ms
- Filtro date opzionale (da/a)
- Risultati: data, ora, kcal, descrizione, badge tipo
- Funzione "Ri-registra": crea nuovo pasto con stesse kcal e timestamp corrente
- Ordinamento: data DESC
- Range: ultimi 30 giorni (client-side)
**Criteri Accettazione**:
- [ ] Ricerca per testo funzionante con debounce
- [ ] Filtro date funzionante
- [ ] Badge tipo visibili
- [ ] "Ri-registra" crea nuovo documento
- [ ] Empty state "Nessun risultato"
---
## REQ-011 — Design Brief Google Stitch
**Tipo**: REWORK | **Priorità**: MEDIA
**Stato**: COMPLETATO — I mockup sono disponibili in `stitch_calorietracker_design_brief_brief/`
**Schermate generate**:
1. Home (Journal) — `home_calorietracker/`
2. Bottom sheet scelta input — `scelta_input_calorietracker/`
3. Modal input numerico — `inserisci_calorie_calorietracker/`
4. Modal descrizione LLM — `descrizione_ai_calorietracker/`
5. Statistiche settimanali — `statistiche_calorietracker/`
**Design System**: `vitality_pulse/DESIGN.md`

292
docs/TECHNICAL.md Normal file
View File

@@ -0,0 +1,292 @@
# Requisiti Tecnici — CalorieTracker (Precision Vitality)
> Documento di architettura e decisioni tecniche del progetto.
> Ultimo aggiornamento: 16/04/2026
---
## 1. Panoramica Architettura
```
┌─────────────────────────────────────────────┐
│ App Ionic/Angular │
│ ┌───────────┐ ┌───────────┐ ┌─────────┐ │
│ │ Journal │ │ Stats │ │ Search │ │
│ │ (Home) │ │ Day/Week │ │ │ │
│ └─────┬─────┘ └─────┬─────┘ └────┬────┘ │
│ │ │ │ │
│ ┌─────┴───────────────┴─────────────┴────┐ │
│ │ Core Services Layer │ │
│ │ MealService | CalorieEstimator | Auth │ │
│ └─────┬───────────────┬─────────────────┘ │
│ │ │ │
└────────┼───────────────┼────────────────────┘
│ │
┌────┴────┐ ┌──────┴──────┐
│Firestore│ │ Firebase CF │
│ (DB) │ │ (Proxy AI) │
└─────────┘ └──────┬──────┘
┌──────┴──────┐
│ Anthropic │
│ Claude API │
└─────────────┘
```
---
## 2. Stack Tecnologico
| Layer | Tecnologia | Versione | Motivazione |
|-------|------------|----------|-------------|
| Framework UI | Ionic | 7+ | Cross-platform, componenti nativi |
| Framework App | Angular | 17+ | Standalone components, signals |
| Mobile Runtime | Capacitor | 5+ | Build nativo Android/iOS |
| Database | Firebase Firestore | 10+ | Free tier, offline, real-time |
| Auth | Firebase Auth | 10+ | Anonymous + Google Sign-In |
| AI Proxy | Firebase Cloud Functions | Gen2 | Free tier, sicurezza API key |
| AI Model | Claude Haiku 4.5 | claude-haiku-4-5-20251001 | Economico, veloce, sufficiente per stime |
| Chart | Chart.js + ng2-charts | 4+ | Leggero (~200KB), well-maintained |
| Storage locale | @ionic/storage-angular | 4+ | Preferenze utente (obiettivo kcal) |
| Linguaggio | TypeScript | 5.3+ | Type safety |
---
## 3. Requisiti Ambiente di Sviluppo
| Requisito | Versione Minima | Note |
|-----------|-----------------|------|
| Node.js | 18+ | LTS raccomandato |
| npm | 9+ | Incluso con Node.js |
| Ionic CLI | 7+ | `npm i -g @ionic/cli` |
| Angular CLI | 17+ | Incluso come dipendenza |
| Java JDK | 17+ | Per build Android (Gradle) |
| Android SDK | API 33+ | Per build APK |
---
## 4. Configurazione Variabili d'Ambiente
### `environment.ts` (development)
```typescript
export const environment = {
production: false,
firebase: {
apiKey: '...',
authDomain: '...',
projectId: '...',
storageBucket: '...',
messagingSenderId: '...',
appId: '...'
},
cloudFunctionUrl: 'http://localhost:5001/PROJECT_ID/us-central1/estimateCalories',
defaultCalorieTarget: 2000
};
```
### `environment.prod.ts` (production)
```typescript
export const environment = {
production: true,
firebase: { /* config produzione */ },
cloudFunctionUrl: 'https://us-central1-PROJECT_ID.cloudfunctions.net/estimateCalories',
defaultCalorieTarget: 2000
};
```
**IMPORTANTE**: I file `environment.*.ts` sono nel `.gitignore`. Usare `environment.example.ts` come template.
---
## 5. Firebase Cloud Function — Proxy AI
### Architettura
```
Client → Cloud Function → Anthropic API → Cloud Function → Client
```
### Endpoint
- **URL**: `POST /estimateCalories`
- **Body**: `{ description: string }`
- **Response**: `{ calories: number, confidence: 'high'|'medium'|'low', explanation: string }`
- **Auth**: Firebase Auth token required (header `Authorization: Bearer <token>`)
### Implementazione Cloud Function
```typescript
// functions/src/index.ts
import { onRequest } from 'firebase-functions/v2/https';
import Anthropic from '@anthropic-ai/sdk';
export const estimateCalories = onRequest(
{ cors: true, region: 'europe-west1' },
async (req, res) => {
// 1. Verificare Firebase Auth token
// 2. Estrarre description dal body
// 3. Chiamare Claude con food dictionary nel system prompt
// 4. Parsare risposta JSON
// 5. Restituire risultato
}
);
```
### Sicurezza
- API key Anthropic in Secret Manager di Firebase
- Rate limiting: max 10 richieste/minuto per utente
- Validazione input: description max 500 caratteri
- Timeout: 10 secondi
---
## 6. Schema Dati Firestore
### Collection `users/{userId}/meals/{mealId}`
| Campo | Tipo | Obbligatorio | Descrizione |
|-------|------|-------------|-------------|
| id | string | Sì | Auto-generated document ID |
| timestamp | Timestamp | Sì | Data/ora del pasto |
| calories | number | Sì | Kcal totali |
| inputType | string | Sì | `'manual'` o `'llm'` |
| description | string | No | Descrizione testuale |
| llmConfidence | string | No | `'high'`, `'medium'`, `'low'` (solo se llm) |
| llmExplanation | string | No | Spiegazione AI (solo se llm) |
| createdAt | Timestamp | Sì | Data creazione documento |
### Indici Richiesti
```
Collection: users/{userId}/meals
- (timestamp DESC) — per query giornaliere e range
```
---
## 7. Servizi Angular Core
### `AuthService`
```typescript
- signInAnonymously(): Promise<User>
- getCurrentUser(): Observable<User | null>
- getUserId(): string
```
### `MealService`
```typescript
- addMeal(data: MealInput): Promise<string>
- getMealsForDate(date: Date): Observable<Meal[]>
- getMealsInRange(from: Date, to: Date): Observable<Meal[]>
- deleteMeal(mealId: string): Promise<void>
- getTotalCaloriesForDate(date: Date): Observable<number>
```
### `CalorieEstimatorService`
```typescript
- estimateCalories(description: string): Observable<CalorieEstimate>
```
### `StorageService`
```typescript
- getCalorieTarget(): Promise<number>
- setCalorieTarget(target: number): Promise<void>
```
---
## 8. Interfacce TypeScript
```typescript
interface Meal {
id: string;
timestamp: Date;
calories: number;
inputType: 'manual' | 'llm';
description?: string;
llmConfidence?: 'high' | 'medium' | 'low';
llmExplanation?: string;
createdAt: Date;
}
interface MealInput {
calories: number;
inputType: 'manual' | 'llm';
description?: string;
llmConfidence?: 'high' | 'medium' | 'low';
llmExplanation?: string;
}
interface CalorieEstimate {
calories: number;
confidence: 'high' | 'medium' | 'low';
explanation: string;
}
```
---
## 9. Design System — Mapping Tecnico
### Colori (CSS Custom Properties)
```css
:root {
--pv-primary: #006b1b;
--pv-primary-dim: #005d16;
--pv-accent: #FF7043;
--pv-error: #F44336;
--pv-surface: #f6f6f6;
--pv-surface-container-low: #f0f1f1;
--pv-surface-container-lowest: #ffffff;
--pv-on-surface: #2d2f2f;
--pv-on-surface-variant: #757575;
}
```
### Font (da importare)
```css
@import url('https://fonts.googleapis.com/css2?family=Lexend:wght@400;700&family=Plus+Jakarta+Sans:wght@400;500;600&display=swap');
:root {
--pv-font-data: 'Lexend', sans-serif;
--pv-font-ui: 'Plus Jakarta Sans', sans-serif;
}
```
### Regole Chiave
1. **No borders** — separazione solo via background shifts
2. **No drop shadows su card** — usare tonal layering
3. **Border radius**: `1.5rem` (card principali), `1rem` (elementi nested)
4. **Spacing**: `24px` vertical padding nelle card
5. **Progress bar**: `12px` altezza, full roundedness, gradient `primary-fixed → primary`
6. **FAB**: gradient `primary → primary-dim` a 135°, shadow `0px 12px 32px rgba(0,107,27,0.15)`
---
## 10. Performance e Limiti
### Firestore (Spark Plan — Gratuito)
- 50.000 letture/giorno
- 20.000 scritture/giorno
- 1 GiB storage
- 10 GiB trasferimento/mese
### Claude API (Haiku)
- ~$0.25 per 1M input tokens
- ~$1.25 per 1M output tokens
- Stima: ~$0.001 per richiesta singola
- Budget suggerito: $5/mese per uso moderato
### Ottimizzazioni
- Cache locale food dictionary (caricato una volta)
- Observable Firestore per real-time (no polling)
- Lazy loading moduli Angular
- Offline persistence Firestore per uso senza rete
---
## 11. Sicurezza
| Rischio | Mitigazione |
|---------|-------------|
| API key Anthropic esposta | Firebase Cloud Function come proxy |
| Accesso dati cross-user | Firestore Security Rules basate su userId |
| Input injection nell'LLM | Sanitizzazione input + max 500 caratteri |
| Firebase config nel bundle | Le Firebase config sono safe da esporre (solo project identifier) |
| Rate abuse | Rate limiting nella Cloud Function (10 req/min/user) |

99
docs/TRACKING.md Normal file
View File

@@ -0,0 +1,99 @@
# Tracciamento Requisiti — CalorieTracker
> Questo documento tiene traccia dello stato di avanzamento di ogni requisito sessione per sessione.
> Aggiornare questo file alla fine di ogni sessione di lavoro.
---
## Stato Requisiti
| ID | Titolo | Stato | Sessione | Note |
|----|--------|-------|----------|------|
| REQ-001 | Scaffolding Ionic + Capacitor | ⬜ DA FARE | — | Prerequisito bloccante |
| REQ-002 | Setup Firebase Firestore | ⬜ DA FARE | — | Richiede REQ-001 |
| REQ-003 | Home screen + FAB | ⬜ DA FARE | — | Richiede REQ-002 |
| REQ-004 | Modal input numerico | ⬜ DA FARE | — | Richiede REQ-003 |
| REQ-005 | Modal input LLM | ⬜ DA FARE | — | Richiede REQ-003 + REQ-006 |
| REQ-006 | Servizio Claude API | ⬜ DA FARE | — | Richiede REQ-001 |
| REQ-007 | CRUD pasti Firestore | ⬜ DA FARE | — | Richiede REQ-002 |
| REQ-008 | Statistiche giornaliere | ⬜ DA FARE | — | Richiede REQ-007 |
| REQ-009 | Statistiche settimanali | ⬜ DA FARE | — | Richiede REQ-007 |
| REQ-010 | Ricerca pasti | ⬜ DA FARE | — | Richiede REQ-007 |
| REQ-011 | Design brief Stitch | ✅ COMPLETATO | S0 | Mockup disponibili |
### Legenda Stati
- ⬜ DA FARE — Non ancora iniziato
- 🔄 IN CORSO — Lavoro iniziato
- ⏸️ IN PAUSA — Iniziato ma bloccato/sospeso
- ✅ COMPLETATO — Tutti i criteri accettazione soddisfatti
- ❌ BLOCCATO — Bloccato da dipendenza o problema
---
## Ordine di Implementazione Raccomandato
```
Fase 1 (Foundation):
REQ-001 → REQ-002 → REQ-007
Fase 2 (Core Features):
REQ-003 → REQ-004 (parallelo con REQ-006)
REQ-006 → REQ-005
Fase 3 (Analytics & Search):
REQ-008 → REQ-009
REQ-010
Fase 4 (Polish):
REQ-011 (già completato — usare come riferimento)
```
---
## Log Sessioni
### Sessione 0 — 16/04/2026
**Obiettivo**: Setup iniziale progetto
**Completato**:
- Inizializzazione repository Git
- Configurazione remote Gitea (git.i-a.run)
- Creazione CLAUDE.md
- Creazione documentazione progetto (REQUIREMENTS.md, TRACKING.md, TECHNICAL.md, FIRESTORE_SETUP.md, DEVICE_TESTING.md)
- Analisi mockup design Stitch (REQ-011 ✅)
**Prossima sessione**: Iniziare REQ-001 (scaffolding Ionic)
---
## Decisioni Prese
| Data | Decisione | Motivazione |
|------|-----------|-------------|
| 16/04/2026 | Proxy API: Firebase Cloud Function | API key non esposta nel bundle mobile |
| 16/04/2026 | Cancellazione pasti: hard delete | Semplicità, niente complessità soft delete |
| 16/04/2026 | Obiettivo calorico: @ionic/storage locale | Non serve persistenza cloud per preferenza utente |
| 16/04/2026 | Ricerca: client-side 30 giorni | Firestore non supporta full-text, sufficiente per uso tipico |
| 16/04/2026 | Nome UI: "Precision Vitality" | Da design brief Stitch approvato |
| 16/04/2026 | 4 tab: Journal, Stats, Plans, Profile | Da design (diverge dai 3 tab originali dei requisiti) |
---
## Problemi Aperti
| # | Problema | Impatto | Stato |
|---|----------|---------|-------|
| 1 | Deadline business non definita | Pianificazione | Aperto |
| 2 | Target platform prioritario (Android vs iOS) | Build priority | Aperto |
| 3 | Strategia Auth lungo termine | REQ-002 | Aperto — per ora Anonymous |
| 4 | Logo app | REQ-011 | Aperto |
| 5 | Dark mode in scope? | Design | Aperto |
---
## Come Usare Questo Documento
1. **Inizio sessione**: leggere lo stato attuale dei requisiti e il log dell'ultima sessione
2. **Durante la sessione**: aggiornare lo stato dei requisiti man mano
3. **Fine sessione**: aggiungere entry nel log sessioni con obiettivo, completato e prossimi passi
4. **Decisioni**: registrare ogni decisione architetturale con motivazione
5. **Problemi**: aggiungere problemi aperti che bloccano o impattano il lavoro

View File

@@ -0,0 +1,74 @@
# GOOGLE STITCH DESIGN BRIEF — CalorieTracker
**App name:** CalorieTracker
**Platform:** Mobile (iOS + Android), Ionic Capacitor
**Language:** Italiano
---
#### IDENTITÀ VISIVA
**Mood:** Energico ma pulito. Non medicale, non pesante. Motivante.
**Palette colori:**
- Primario: Verde salute `#4CAF50` (azioni positive, conferme, obiettivo raggiunto)
- Accent: Arancione energia `#FF7043` (FAB, CTA principale, avvisi morbidi)
- Errore/Eccesso: Rosso `#F44336` (obiettivo superato, validazioni)
- Background: Bianco sporco `#FAFAFA`
- Superfici card: Bianco `#FFFFFF` con ombra leggera
- Testo primario: `#212121`
- Testo secondario: `#757575`
**Tipografia:**
- Font: Roboto (già incluso in Ionic)
- Titolo totale kcal: 48px, bold, colore primario
- Label: 14px, medium
- Corpo lista: 16px, regular
**Iconografia:** Material Icons (set standard Ionic/Angular Material)
---
#### SCHERMATE DA GENERARE
**SCHERMATA 1 — Home**
- Header: data odierna (es. "Mercoledì 16 aprile") centrata, testo secondario
- Centro schermata: numero kcal totali giornalieri in grande (48px, verde), label "calorie oggi" sotto
- Barra progresso orizzontale: kcal consumate / 2000 kcal obiettivo, colore verde → rosso se >100%
- Lista pasti sotto la barra: card minimal con [ora | descrizione | kcal] su ogni riga
- Badge piccolo a destra: "AI" (arancione) se stima LLM, "M" (grigio) se manuale
- FAB in basso a destra: cerchio arancione `#FF7043`, icona "+" bianca, dimensione XL
- Empty state se nessun pasto: illustrazione semplice + "Nessun pasto registrato oggi. Inizia!"
**SCHERMATA 2 — Bottom Sheet scelta input**
- Modal bottom sheet (mezzo schermo)
- Titolo: "Come vuoi registrare?"
- Due opzioni a tutta larghezza, staccate, con icona:
- "Inserisci kcal" — icona tastiera numerica — sfondo bianco con bordo verde
- "Descrivi il pasto" — icona microfono/testo — sfondo bianco con bordo arancione
- Bottone X in alto a destra per chiudere
**SCHERMATA 3 — Modal input numerico**
- Titolo: "Inserisci calorie"
- Campo numerico grande al centro: font 40px, placeholder "0"
- Label sotto campo: "kcal"
- Campo testo sotto: "Descrizione (opzionale)", font normale, bordo leggero
- Bottone "Salva" verde a tutta larghezza in basso
**SCHERMATA 4 — Modal descrizione LLM**
- Titolo: "Descrivi il pasto"
- Textarea grande: placeholder "Es. piatto di pasta al pomodoro con 2 cucchiai di parmigiano"
- Bottone "Stima calorie" arancione a tutta larghezza
- Stato risultato (visibile dopo risposta LLM):
- Box risultato: kcal stimati in grande, badge confidence (verde/giallo/rosso)
- Testo spiegazione in corsivo sotto
- Campo kcal modificabile (pre-compilato con stima)
- Bottone "Salva" verde
**SCHERMATA 5 — Statistiche settimanali**
- Tab selector: "Oggi" | "Settimana"
- Vista settimana: grafico a barre 7 giorni
- Barre verdi se ≤ obiettivo, rosse se sopra
- Asse X: giorni abbreviati (Lun, Mar...)
- Asse Y: kcal (0, 500, 1000, 1500, 2000+)
- Sotto grafico: card "Media settimanale: X kcal/giorno"
- Navigazione settimana: frecce sinistra/destra con label "1420 apr"

View File

@@ -0,0 +1,193 @@
<!DOCTYPE html>
<html class="light" lang="it"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Descrivi il pasto - Precision Vitality</title>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link href="https://fonts.googleapis.com/css2?family=Lexend:wght@400;500;600;700;800&amp;family=Plus+Jakarta+Sans:wght@400;500;600;700&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
"colors": {
"inverse-on-surface": "#9c9d9d",
"on-tertiary-container": "#690002",
"on-primary-container": "#005e17",
"surface-variant": "#dbdddd",
"surface": "#f6f6f6",
"on-secondary-container": "#862400",
"error-container": "#f95630",
"on-surface": "#2d2f2f",
"primary-fixed-dim": "#83e881",
"on-secondary": "#ffefeb",
"on-surface-variant": "#5a5c5c",
"primary": "#006b1b",
"inverse-surface": "#0c0f0f",
"inverse-primary": "#91f78e",
"surface-tint": "#006b1b",
"surface-bright": "#f6f6f6",
"on-tertiary-fixed": "#3a0001",
"tertiary": "#b71211",
"tertiary-dim": "#a40007",
"primary-fixed": "#91f78e",
"secondary-dim": "#952800",
"surface-container-lowest": "#ffffff",
"error": "#b02500",
"on-background": "#2d2f2f",
"on-error-container": "#520c00",
"on-tertiary": "#ffefed",
"surface-container-low": "#f0f1f1",
"surface-container-highest": "#dbdddd",
"tertiary-fixed": "#ff9385",
"on-secondary-fixed-variant": "#972900",
"surface-dim": "#d3d5d5",
"on-secondary-fixed": "#651900",
"on-error": "#ffefec",
"secondary-container": "#ffc4b3",
"surface-container": "#e7e8e8",
"secondary": "#a83206",
"outline": "#767777",
"primary-container": "#91f78e",
"on-primary": "#d1ffc8",
"on-tertiary-fixed-variant": "#7a0003",
"outline-variant": "#acadad",
"background": "#f6f6f6",
"secondary-fixed": "#ffc4b3",
"on-primary-fixed-variant": "#00691a",
"primary-dim": "#005d16",
"surface-container-high": "#e1e3e3",
"tertiary-container": "#ff9385",
"tertiary-fixed-dim": "#ff7c6c",
"on-primary-fixed": "#00480f",
"secondary-fixed-dim": "#ffb19a",
"error-dim": "#b92902"
},
"borderRadius": {
"DEFAULT": "0.25rem",
"lg": "0.5rem",
"xl": "0.75rem",
"full": "9999px"
},
"fontFamily": {
"headline": ["Lexend"],
"body": ["Plus Jakarta Sans"],
"label": ["Plus Jakarta Sans"]
}
}
}
}
</script>
<style>
body { font-family: 'Plus Jakarta Sans', sans-serif; }
h1, h2, h3, .display-lg { font-family: 'Lexend', sans-serif; }
.material-symbols-outlined {
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
vertical-align: middle;
}
.kinetic-gradient {
background: linear-gradient(135deg, #006b1b 0%, #005d16 100%);
}
.energy-gradient {
background: linear-gradient(135deg, #a83206 0%, #952800 100%);
}
</style>
<style>
body {
min-height: max(884px, 100dvh);
}
</style>
</head>
<body class="bg-surface text-on-surface min-h-screen pb-24">
<!-- Top Navigation Shell -->
<nav class="fixed top-0 w-full flex justify-between items-center px-6 h-16 bg-white/80 dark:bg-zinc-900/80 backdrop-blur-xl z-50">
<div class="flex items-center gap-3">
<span class="material-symbols-outlined text-on-surface-variant cursor-pointer">close</span>
<span class="text-xl font-bold tracking-tight text-green-700 dark:text-green-500">Precision Vitality</span>
</div>
<div class="w-10 h-10 rounded-full overflow-hidden">
<img class="w-full h-full object-cover" data-alt="close up professional headshot of a smiling man with clean background" src="https://lh3.googleusercontent.com/aida-public/AB6AXuB3ZTuiToTbzi1xyLIep2NDhvydENIomB2p1AxfdYnIsJIhDe2rG5vNpXmRpuWz0sIpilOKsIzauEJD0WsIMcnNf3GTSF2hFHfgoYrA2bx30T0Smy-Q-A7IRn8uSr7tMM_ffx6nyUrcQoV4upparHsyoz2k6WDNSUxhl0erHoFjMYpfRH_5cXVasigJ7Qw58JAMao5lI3cNq3ajcuySHutW4o2x-ArQKLCPZdWC325u6vhI8qLPTXfXWIyNwpe0OICW-AkYDOJ35wc"/>
</div>
</nav>
<main class="pt-24 px-6 max-w-2xl mx-auto">
<!-- Header Section -->
<header class="mb-8">
<h1 class="text-3xl font-bold text-on-surface mb-2">Descrivi il pasto</h1>
<p class="text-on-surface-variant">Usa il linguaggio naturale per stimare i tuoi nutrienti in pochi secondi.</p>
</header>
<!-- Editor Card -->
<div class="bg-surface-container-lowest rounded-[2rem] p-6 mb-6 shadow-sm">
<div class="mb-6">
<label class="block text-xs font-bold uppercase tracking-widest text-on-surface-variant mb-3 px-1">Il tuo pasto</label>
<textarea class="w-full min-h-[160px] bg-surface-container-low border-0 rounded-xl p-4 text-on-surface focus:ring-2 focus:ring-primary/20 placeholder:text-outline resize-none text-lg leading-relaxed" placeholder="Esempio: Pizza margherita e una birra media...">Pizza margherita e una birra</textarea>
</div>
<!-- Kinetic Action Button -->
<button class="w-full energy-gradient text-on-primary py-4 px-6 rounded-full font-bold text-lg flex items-center justify-center gap-2 hover:opacity-90 transition-opacity active:scale-[0.98] duration-200">
<span class="material-symbols-outlined">psychology</span>
Stima calorie
</button>
</div>
<!-- AI Analysis Result (The Kinetic Editor Style) -->
<div class="bg-surface-container-low rounded-[2rem] p-8 relative overflow-hidden">
<!-- Background Accent -->
<div class="absolute -right-12 -top-12 w-48 h-48 bg-primary/5 rounded-full blur-3xl"></div>
<div class="relative z-10 flex flex-col items-center text-center">
<div class="bg-primary/10 text-primary-dim px-4 py-1.5 rounded-full text-xs font-extrabold uppercase tracking-widest mb-6 flex items-center gap-1.5">
<span class="material-symbols-outlined text-[16px]" style="font-variation-settings: 'FILL' 1;">verified</span>
Alta precisione
</div>
<div class="flex flex-col items-center mb-4">
<span class="display-lg text-[4rem] leading-none font-bold text-primary block">650</span>
<span class="text-xs font-bold uppercase tracking-[0.2em] text-on-surface-variant -mt-1">KCAL STIMATE</span>
</div>
<!-- Macro Breakdown - Minimalist Style -->
<div class="w-full grid grid-cols-3 gap-4 mt-4 mb-8">
<div class="flex flex-col items-center">
<span class="text-sm font-bold text-on-surface">85g</span>
<span class="text-[10px] font-bold uppercase text-outline">Carbi</span>
</div>
<div class="flex flex-col items-center border-x border-outline/10">
<span class="text-sm font-bold text-on-surface">22g</span>
<span class="text-[10px] font-bold uppercase text-outline">Proteine</span>
</div>
<div class="flex flex-col items-center">
<span class="text-sm font-bold text-on-surface">18g</span>
<span class="text-[10px] font-bold uppercase text-outline">Grassi</span>
</div>
</div>
<p class="text-on-surface-variant italic text-xs leading-relaxed max-w-[280px]">
Basato su ingredienti standard e porzioni medie per una pizza tradizionale e 33cl di birra.
</p>
</div>
</div>
<!-- Sticky Bottom Action -->
<div class="fixed bottom-24 left-0 w-full px-6 z-40 max-w-2xl mx-auto right-0">
<button class="w-full bg-primary text-on-primary py-5 rounded-2xl font-bold text-lg shadow-[0px_12px_32px_rgba(0,107,27,0.15)] flex items-center justify-center gap-2 active:scale-95 transition-transform">
<span class="material-symbols-outlined">check_circle</span>
Salva
</button>
</div>
</main>
<!-- Navigation Shell (Bottom) -->
<nav class="fixed bottom-0 left-0 w-full z-50 flex justify-around items-center px-4 pb-6 pt-2 bg-white/80 dark:bg-zinc-900/80 backdrop-blur-xl rounded-t-[2rem] shadow-[0px_-12px_32px_rgba(0,107,27,0.08)]">
<div class="flex flex-col items-center justify-center bg-green-100 dark:bg-green-900/30 text-green-800 dark:text-green-200 rounded-2xl px-5 py-2 scale-90 transition-transform">
<span class="material-symbols-outlined" data-icon="event_note">event_note</span>
<span class="font-headline text-[10px] font-bold uppercase tracking-widest mt-1">Journal</span>
</div>
<div class="flex flex-col items-center justify-center text-zinc-400 dark:text-zinc-500 px-5 py-2 hover:text-green-600 transition-opacity">
<span class="material-symbols-outlined" data-icon="bar_chart">bar_chart</span>
<span class="font-headline text-[10px] font-bold uppercase tracking-widest mt-1">Stats</span>
</div>
<div class="flex flex-col items-center justify-center text-zinc-400 dark:text-zinc-500 px-5 py-2 hover:text-green-600 transition-opacity">
<span class="material-symbols-outlined" data-icon="restaurant_menu">restaurant_menu</span>
<span class="font-headline text-[10px] font-bold uppercase tracking-widest mt-1">Plans</span>
</div>
<div class="flex flex-col items-center justify-center text-zinc-400 dark:text-zinc-500 px-5 py-2 hover:text-green-600 transition-opacity">
<span class="material-symbols-outlined" data-icon="person">person</span>
<span class="font-headline text-[10px] font-bold uppercase tracking-widest mt-1">Profile</span>
</div>
</nav>
</body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

View File

@@ -0,0 +1,209 @@
<!DOCTYPE html>
<html class="light" lang="it"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Precision Vitality - Journal</title>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link href="https://fonts.googleapis.com/css2?family=Lexend:wght@400;500;700;800&amp;family=Plus+Jakarta+Sans:wght@400;500;600&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
"colors": {
"inverse-on-surface": "#9c9d9d",
"on-tertiary-container": "#690002",
"on-primary-container": "#005e17",
"surface-variant": "#dbdddd",
"surface": "#f6f6f6",
"on-secondary-container": "#862400",
"error-container": "#f95630",
"on-surface": "#2d2f2f",
"primary-fixed-dim": "#83e881",
"on-secondary": "#ffefeb",
"on-surface-variant": "#5a5c5c",
"primary": "#006b1b",
"inverse-surface": "#0c0f0f",
"inverse-primary": "#91f78e",
"surface-tint": "#006b1b",
"surface-bright": "#f6f6f6",
"on-tertiary-fixed": "#3a0001",
"tertiary": "#b71211",
"tertiary-dim": "#a40007",
"primary-fixed": "#91f78e",
"secondary-dim": "#952800",
"surface-container-lowest": "#ffffff",
"error": "#b02500",
"on-background": "#2d2f2f",
"on-error-container": "#520c00",
"on-tertiary": "#ffefed",
"surface-container-low": "#f0f1f1",
"surface-container-highest": "#dbdddd",
"tertiary-fixed": "#ff9385",
"on-secondary-fixed-variant": "#972900",
"surface-dim": "#d3d5d5",
"on-secondary-fixed": "#651900",
"on-error": "#ffefec",
"secondary-container": "#ffc4b3",
"surface-container": "#e7e8e8",
"secondary": "#a83206",
"outline": "#767777",
"primary-container": "#91f78e",
"on-primary": "#d1ffc8",
"on-tertiary-fixed-variant": "#7a0003",
"outline-variant": "#acadad",
"background": "#f6f6f6",
"secondary-fixed": "#ffc4b3",
"on-primary-fixed-variant": "#00691a",
"primary-dim": "#005d16",
"surface-container-high": "#e1e3e3",
"tertiary-container": "#ff9385",
"tertiary-fixed-dim": "#ff7c6c",
"on-primary-fixed": "#00480f",
"secondary-fixed-dim": "#ffb19a",
"error-dim": "#b92902"
},
"borderRadius": {
"DEFAULT": "0.25rem",
"lg": "0.5rem",
"xl": "0.75rem",
"full": "9999px"
},
"fontFamily": {
"headline": ["Lexend"],
"body": ["Plus Jakarta Sans"],
"label": ["Plus Jakarta Sans"]
}
},
},
}
</script>
<style>
body { font-family: 'Plus Jakarta Sans', sans-serif; }
h1, h2, .font-headline { font-family: 'Lexend', sans-serif; }
.material-symbols-outlined { font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24; }
</style>
<style>
body {
min-height: max(884px, 100dvh);
}
</style>
</head>
<body class="bg-surface text-on-surface min-h-screen pb-32">
<!-- TopAppBar -->
<header class="fixed top-0 w-full flex justify-between items-center px-6 h-16 bg-white/80 dark:bg-zinc-900/80 backdrop-blur-xl z-50">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-full overflow-hidden bg-surface-container">
<img alt="User Profile" class="w-full h-full object-cover" data-alt="Professional studio portrait of a smiling young man with friendly eyes and soft natural lighting against a neutral background" src="https://lh3.googleusercontent.com/aida-public/AB6AXuCYLHnqZTkjUzITUdAwv_saSlGzWcd7dMu3wqqrMYBm-2xFL7A9D150Gi3nhfpkXlh6rav5vwDqe2QqM7s7SH8QECG-Qt18YbbOi4JGLinwhQhni6njNxJDhPYPSPiuRCvN49xs3728RN-eZXlmPHfUh8-vGZUnokRHlhf_M2sWhFA_Vw-4vjD9ecmVLe6487040GPmcAu7t4etj2W_KMUehn0_KEvL3rlOsJ8TQ29Z6AHbzWV3gklT05hhXAChvrq9K4TRqu_IboQ"/>
</div>
<span class="text-xl font-bold tracking-tight text-green-700 dark:text-green-500 font-headline">Precision Vitality</span>
</div>
<button class="text-zinc-500 hover:opacity-80 transition-opacity">
<span class="material-symbols-outlined text-2xl" data-icon="settings">settings</span>
</button>
</header>
<main class="pt-24 px-6 max-w-2xl mx-auto">
<!-- Date Header -->
<div class="text-center mb-10">
<p class="text-on-surface-variant font-medium text-sm font-label uppercase tracking-widest">Mercoledì 16 aprile</p>
<h2 class="text-on-surface font-headline font-bold text-lg">Il tuo progresso giornaliero</h2>
</div>
<!-- Main Score Hero -->
<div class="relative flex flex-col items-center mb-12">
<!-- Macro Donut Overlay (Kinetic Suite) -->
<div class="absolute -z-10 w-64 h-64 border-[4px] border-surface-container-highest rounded-full flex items-center justify-center opacity-40">
<div class="w-full h-full rounded-full border-[4px] border-primary border-t-transparent -rotate-45"></div>
</div>
<span class="text-[3.5rem] font-bold text-primary font-headline leading-none">1450</span>
<span class="text-on-surface-variant font-label font-medium text-sm mt-2">calorie oggi</span>
<div class="w-full max-w-xs mt-8">
<div class="flex justify-between text-[10px] font-bold font-headline uppercase tracking-wider text-on-surface-variant mb-2">
<span>Attuale: 1450</span>
<span>Obiettivo: 2000</span>
</div>
<div class="h-3 w-full bg-surface-container-highest rounded-full overflow-hidden">
<div class="h-full bg-gradient-to-r from-primary-fixed to-primary w-[72.5%] rounded-full shadow-[0px_4px_12px_rgba(0,107,27,0.15)]"></div>
</div>
</div>
</div>
<!-- Meal List Section -->
<div class="space-y-6">
<h3 class="font-headline font-bold text-xl mb-4 ml-2">Diario Alimentare</h3>
<!-- Meal Card 1 -->
<div class="bg-surface-container-lowest p-5 rounded-xl shadow-[0px_8px_24px_rgba(0,0,0,0.04)] flex items-center justify-between transition-transform duration-200 hover:scale-[1.02]">
<div class="flex items-center gap-4">
<div class="text-on-surface-variant font-medium text-sm w-12">08:30</div>
<div>
<p class="font-semibold text-on-surface">Colazione Proteica</p>
<div class="flex gap-2 mt-1">
<span class="bg-surface-container text-on-surface-variant text-[10px] font-bold px-2 py-0.5 rounded-full uppercase tracking-tighter">M</span>
</div>
</div>
</div>
<div class="text-right">
<span class="font-headline font-bold text-primary">350 kcal</span>
</div>
</div>
<!-- Meal Card 2 -->
<div class="bg-surface-container-lowest p-5 rounded-xl shadow-[0px_8px_24px_rgba(0,0,0,0.04)] flex items-center justify-between transition-transform duration-200 hover:scale-[1.02]">
<div class="flex items-center gap-4">
<div class="text-on-surface-variant font-medium text-sm w-12">13:00</div>
<div>
<p class="font-semibold text-on-surface">Pasta al pesto</p>
<div class="flex gap-2 mt-1">
<span class="bg-secondary-container text-on-secondary-container text-[10px] font-bold px-2 py-0.5 rounded-full uppercase tracking-tighter">AI</span>
</div>
</div>
</div>
<div class="text-right">
<span class="font-headline font-bold text-primary">650 kcal</span>
</div>
</div>
<!-- Meal Card 3 -->
<div class="bg-surface-container-lowest p-5 rounded-xl shadow-[0px_8px_24px_rgba(0,0,0,0.04)] flex items-center justify-between transition-transform duration-200 hover:scale-[1.02]">
<div class="flex items-center gap-4">
<div class="text-on-surface-variant font-medium text-sm w-12">16:45</div>
<div>
<p class="font-semibold text-on-surface">Frutta fresca e noci</p>
<div class="flex gap-2 mt-1">
<span class="bg-secondary-container text-on-secondary-container text-[10px] font-bold px-2 py-0.5 rounded-full uppercase tracking-tighter">AI</span>
</div>
</div>
</div>
<div class="text-right">
<span class="font-headline font-bold text-primary">450 kcal</span>
</div>
</div>
</div>
</main>
<!-- FAB XL -->
<button class="fixed bottom-28 right-6 w-16 h-16 bg-secondary text-on-secondary rounded-full flex items-center justify-center shadow-[0px_12px_32px_rgba(168,50,6,0.25)] hover:scale-95 transition-transform duration-200 ease-out z-40">
<span class="material-symbols-outlined text-3xl font-bold" data-icon="add">add</span>
</button>
<!-- BottomNavBar -->
<nav class="fixed bottom-0 left-0 w-full z-50 flex justify-around items-center px-4 pb-6 pt-2 bg-white/80 dark:bg-zinc-900/80 backdrop-blur-xl rounded-t-[2rem] shadow-[0px_-12px_32px_rgba(0,107,27,0.08)]">
<!-- Journal Active -->
<a class="flex flex-col items-center justify-center bg-green-100 dark:bg-green-900/30 text-green-800 dark:text-green-200 rounded-2xl px-5 py-2 scale-90 transition-transform duration-300" href="#">
<span class="material-symbols-outlined mb-1" data-icon="event_note" style="font-variation-settings: 'FILL' 1;">event_note</span>
<span class="font-headline text-[10px] font-bold uppercase tracking-widest">Journal</span>
</a>
<!-- Stats Inactive -->
<a class="flex flex-col items-center justify-center text-zinc-400 dark:text-zinc-500 px-5 py-2 hover:text-green-600 transition-opacity" href="#">
<span class="material-symbols-outlined mb-1" data-icon="bar_chart">bar_chart</span>
<span class="font-headline text-[10px] font-bold uppercase tracking-widest">Stats</span>
</a>
<!-- Plans Inactive -->
<a class="flex flex-col items-center justify-center text-zinc-400 dark:text-zinc-500 px-5 py-2 hover:text-green-600 transition-opacity" href="#">
<span class="material-symbols-outlined mb-1" data-icon="restaurant_menu">restaurant_menu</span>
<span class="font-headline text-[10px] font-bold uppercase tracking-widest">Plans</span>
</a>
<!-- Profile Inactive -->
<a class="flex flex-col items-center justify-center text-zinc-400 dark:text-zinc-500 px-5 py-2 hover:text-green-600 transition-opacity" href="#">
<span class="material-symbols-outlined mb-1" data-icon="person">person</span>
<span class="font-headline text-[10px] font-bold uppercase tracking-widest">Profile</span>
</a>
</nav>
</body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

View File

@@ -0,0 +1,175 @@
<!DOCTYPE html>
<html lang="it"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Precision Vitality — Inserisci calorie</title>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link href="https://fonts.googleapis.com/css2?family=Lexend:wght@400;500;700;800&amp;family=Plus+Jakarta+Sans:wght@400;500;600&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
"colors": {
"inverse-on-surface": "#9c9d9d",
"on-tertiary-container": "#690002",
"on-primary-container": "#005e17",
"surface-variant": "#dbdddd",
"surface": "#f6f6f6",
"on-secondary-container": "#862400",
"error-container": "#f95630",
"on-surface": "#2d2f2f",
"primary-fixed-dim": "#83e881",
"on-secondary": "#ffefeb",
"on-surface-variant": "#5a5c5c",
"primary": "#006b1b",
"inverse-surface": "#0c0f0f",
"inverse-primary": "#91f78e",
"surface-tint": "#006b1b",
"surface-bright": "#f6f6f6",
"on-tertiary-fixed": "#3a0001",
"tertiary": "#b71211",
"tertiary-dim": "#a40007",
"primary-fixed": "#91f78e",
"secondary-dim": "#952800",
"surface-container-lowest": "#ffffff",
"error": "#b02500",
"on-background": "#2d2f2f",
"on-error-container": "#520c00",
"on-tertiary": "#ffefed",
"surface-container-low": "#f0f1f1",
"surface-container-highest": "#dbdddd",
"tertiary-fixed": "#ff9385",
"on-secondary-fixed-variant": "#972900",
"surface-dim": "#d3d5d5",
"on-secondary-fixed": "#651900",
"on-error": "#ffefec",
"secondary-container": "#ffc4b3",
"surface-container": "#e7e8e8",
"secondary": "#a83206",
"outline": "#767777",
"primary-container": "#91f78e",
"on-primary": "#d1ffc8",
"on-tertiary-fixed-variant": "#7a0003",
"outline-variant": "#acadad",
"background": "#f6f6f6",
"secondary-fixed": "#ffc4b3",
"on-primary-fixed-variant": "#00691a",
"primary-dim": "#005d16",
"surface-container-high": "#e1e3e3",
"tertiary-container": "#ff9385",
"tertiary-fixed-dim": "#ff7c6c",
"on-primary-fixed": "#00480f",
"secondary-fixed-dim": "#ffb19a",
"error-dim": "#b92902"
},
"borderRadius": {
"DEFAULT": "0.25rem",
"lg": "0.5rem",
"xl": "0.75rem",
"full": "9999px"
},
"fontFamily": {
"headline": ["Lexend"],
"body": ["Plus Jakarta Sans"],
"label": ["Plus Jakarta Sans"]
}
},
},
}
</script>
<style>
body {
font-family: 'Plus Jakarta Sans', sans-serif;
background-color: #f6f6f6;
}
.material-symbols-outlined {
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
}
.input-no-spinner::-webkit-inner-spin-button,
.input-no-spinner::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
</style>
<style>
body {
min-height: max(884px, 100dvh);
}
</style>
</head>
<body class="bg-surface text-on-surface min-h-screen flex flex-col items-center justify-center p-4">
<!-- Modal Background -->
<div class="fixed inset-0 bg-inverse-surface/20 backdrop-blur-md z-0"></div>
<!-- Main Modal Canvas -->
<div class="relative w-full max-w-md bg-surface-container-lowest rounded-[2.5rem] overflow-hidden shadow-[0px_24px_48px_rgba(0,107,27,0.12)] z-10">
<!-- Top Navigation (Suppressing Shell as per Focus Rule) -->
<div class="flex justify-between items-center px-8 pt-8 pb-4">
<button class="w-10 h-10 flex items-center justify-center rounded-full bg-surface-container-low text-on-surface-variant hover:bg-surface-container-high transition-colors">
<span class="material-symbols-outlined">close</span>
</button>
<h2 class="font-headline text-lg font-bold tracking-tight text-on-surface">Inserisci calorie</h2>
<div class="w-10"></div> <!-- Spacer for symmetry -->
</div>
<!-- Content Body -->
<div class="px-8 pb-10 flex flex-col items-center">
<!-- Motivational Kinetic Element -->
<div class="mt-4 mb-8 flex flex-col items-center">
<div class="w-20 h-20 rounded-full bg-primary-container flex items-center justify-center mb-4">
<span class="material-symbols-outlined text-on-primary-container text-4xl" style="font-variation-settings: 'FILL' 1;">bolt</span>
</div>
<p class="font-body text-on-surface-variant text-sm font-medium">Ogni dato conta per il tuo obiettivo.</p>
</div>
<!-- Kinetic Large Input Section -->
<div class="w-full flex flex-col items-center mb-10">
<div class="relative flex flex-col items-center">
<input autofocus="" class="input-no-spinner w-full text-center bg-transparent border-none focus:ring-0 font-headline font-bold text-[80px] leading-tight text-primary placeholder:text-surface-container-highest" placeholder="0" style="font-size: 80px;" type="number"/>
<span class="font-headline text-xl font-bold text-on-surface-variant -mt-2 tracking-widest uppercase">kcal</span>
</div>
</div>
<!-- Description Field -->
<div class="w-full mb-12">
<label class="block text-[10px] font-headline font-bold uppercase tracking-[0.2em] text-on-surface-variant mb-3 ml-2">Dettagli pasto</label>
<div class="relative group">
<input class="w-full h-14 px-6 bg-surface-container-low rounded-2xl border-2 border-transparent focus:border-primary-container focus:bg-surface-container-lowest outline-none transition-all duration-300 font-body text-on-surface placeholder:text-outline-variant" placeholder="Descrizione (opzionale)" type="text"/>
<span class="absolute right-5 top-1/2 -translate-y-1/2 material-symbols-outlined text-outline-variant group-focus-within:text-primary transition-colors">edit_note</span>
</div>
</div>
<!-- Primary Action (Using specific hex requested #4CAF50 translated to closest branding logic) -->
<button class="w-full h-16 bg-[#4CAF50] hover:opacity-90 active:scale-[0.98] transition-all duration-300 rounded-full flex items-center justify-center shadow-[0px_12px_24px_rgba(76,175,80,0.3)]">
<span class="font-headline text-white font-bold text-lg tracking-wide">Salva</span>
<span class="material-symbols-outlined text-white ml-2">check_circle</span>
</button>
<!-- Quick Suggestions (The Kinetic Grid) -->
<div class="mt-8 w-full">
<div class="flex justify-between items-center mb-4">
<span class="text-[10px] font-headline font-bold uppercase tracking-[0.2em] text-on-surface-variant ml-2">Suggerimenti veloci</span>
</div>
<div class="grid grid-cols-3 gap-3">
<button class="py-3 px-2 bg-surface-container-low hover:bg-primary-container/30 rounded-xl transition-colors flex flex-col items-center">
<span class="font-headline font-bold text-on-surface text-sm">150</span>
<span class="text-[10px] font-body text-on-surface-variant">Spuntino</span>
</button>
<button class="py-3 px-2 bg-surface-container-low hover:bg-primary-container/30 rounded-xl transition-colors flex flex-col items-center">
<span class="font-headline font-bold text-on-surface text-sm">450</span>
<span class="text-[10px] font-body text-on-surface-variant">Pranzo</span>
</button>
<button class="py-3 px-2 bg-surface-container-low hover:bg-primary-container/30 rounded-xl transition-colors flex flex-col items-center">
<span class="font-headline font-bold text-on-surface text-sm">600</span>
<span class="text-[10px] font-body text-on-surface-variant">Cena</span>
</button>
</div>
</div>
</div>
<!-- Surface Decoration (Kinetic Editorial) -->
<div class="absolute -bottom-20 -left-20 w-48 h-48 bg-primary/5 rounded-full blur-3xl"></div>
<div class="absolute top-20 -right-20 w-48 h-48 bg-secondary-container/10 rounded-full blur-3xl"></div>
</div>
<!-- Background Image Layer (Contextual) -->
<div class="fixed inset-0 -z-10 overflow-hidden">
<img alt="Healthy lifestyle background" class="w-full h-full object-cover opacity-20 grayscale" data-alt="overhead shot of a healthy vibrant salad bowl with green vegetables and seeds in a professional editorial food photography style" src="https://lh3.googleusercontent.com/aida-public/AB6AXuCMR0GVR1_hzjXrt5maXBXQ2w6xTD2gcBIFGVQDuK--44EQbdfOr4DvTLYCi6rMFHX8l-TopvBArPMqRaSTu59JU-c6iIcVIS_cioe7To6B6m21-uPLhn4Of8vyRSoOkohJwIxUfG2jGJfBJ4HMitFMewEgCiW5MFG-X6Y5soOFk4daRMkkSz5S6pGTZp1igGcT_PNRuXv2OX-GJCy01Kd3hQY16vgmwVSToNrXLxd0_jbd0pOLA96Ei8T8gO94_8UOBejzcWK7z4k"/>
</div>
</body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

View File

@@ -0,0 +1,187 @@
<!DOCTYPE html>
<html lang="it"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link href="https://fonts.googleapis.com/css2?family=Lexend:wght@400;500;700;800&amp;family=Plus_Jakarta_Sans:wght@400;500;600&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
"colors": {
"inverse-on-surface": "#9c9d9d",
"on-tertiary-container": "#690002",
"on-primary-container": "#005e17",
"surface-variant": "#dbdddd",
"surface": "#f6f6f6",
"on-secondary-container": "#862400",
"error-container": "#f95630",
"on-surface": "#2d2f2f",
"primary-fixed-dim": "#83e881",
"on-secondary": "#ffefeb",
"on-surface-variant": "#5a5c5c",
"primary": "#006b1b",
"inverse-surface": "#0c0f0f",
"inverse-primary": "#91f78e",
"surface-tint": "#006b1b",
"surface-bright": "#f6f6f6",
"on-tertiary-fixed": "#3a0001",
"tertiary": "#b71211",
"tertiary-dim": "#a40007",
"primary-fixed": "#91f78e",
"secondary-dim": "#952800",
"surface-container-lowest": "#ffffff",
"error": "#b02500",
"on-background": "#2d2f2f",
"on-error-container": "#520c00",
"on-tertiary": "#ffefed",
"surface-container-low": "#f0f1f1",
"surface-container-highest": "#dbdddd",
"tertiary-fixed": "#ff9385",
"on-secondary-fixed-variant": "#972900",
"surface-dim": "#d3d5d5",
"on-secondary-fixed": "#651900",
"on-error": "#ffefec",
"secondary-container": "#ffc4b3",
"surface-container": "#e7e8e8",
"secondary": "#a83206",
"outline": "#767777",
"primary-container": "#91f78e",
"on-primary": "#d1ffc8",
"on-tertiary-fixed-variant": "#7a0003",
"outline-variant": "#acadad",
"background": "#f6f6f6",
"secondary-fixed": "#ffc4b3",
"on-primary-fixed-variant": "#00691a",
"primary-dim": "#005d16",
"surface-container-high": "#e1e3e3",
"tertiary-container": "#ff9385",
"tertiary-fixed-dim": "#ff7c6c",
"on-primary-fixed": "#00480f",
"secondary-fixed-dim": "#ffb19a",
"error-dim": "#b92902"
},
"borderRadius": {
"DEFAULT": "0.25rem",
"lg": "0.5rem",
"xl": "0.75rem",
"full": "9999px"
},
"fontFamily": {
"headline": ["Lexend"],
"body": ["Plus Jakarta Sans"],
"label": ["Plus Jakarta Sans"]
}
},
},
}
</script>
<style>
.material-symbols-outlined {
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
}
.editorial-shadow {
box-shadow: 0px 12px 32px rgba(0, 107, 27, 0.08);
}
</style>
<style>
body {
min-height: max(884px, 100dvh);
}
</style>
</head>
<body class="bg-surface font-body text-on-surface">
<!-- Background Content (Simulated Home Screen) -->
<div class="fixed inset-0 overflow-hidden opacity-40 grayscale pointer-events-none">
<!-- TopAppBar Placeholder -->
<header class="fixed top-0 w-full flex justify-between items-center px-6 h-16 bg-white/80 backdrop-blur-xl z-10">
<h1 class="text-xl font-headline font-bold tracking-tight text-primary">Precision Vitality</h1>
<div class="w-10 h-10 rounded-full bg-surface-container-high flex items-center justify-center">
<span class="material-symbols-outlined text-outline">person</span>
</div>
</header>
<!-- Main Content Placeholder -->
<main class="pt-20 px-6 space-y-8">
<div class="space-y-2">
<p class="text-label-md font-medium text-on-surface-variant">Oggi, 24 Maggio</p>
<h2 class="text-[3.5rem] font-headline font-bold leading-tight text-primary">1,842</h2>
<p class="text-on-surface-variant font-medium">kcal rimanenti</p>
</div>
<!-- Bento Grid Simulation -->
<div class="grid grid-cols-2 gap-4">
<div class="col-span-2 h-48 bg-surface-container-low rounded-xl p-6 flex flex-col justify-end">
<div class="w-full h-3 bg-surface-container-highest rounded-full overflow-hidden">
<div class="w-[65%] h-full bg-gradient-to-r from-primary-fixed to-primary"></div>
</div>
</div>
<div class="aspect-square bg-surface-container-low rounded-xl p-4"></div>
<div class="aspect-square bg-surface-container-low rounded-xl p-4"></div>
</div>
</main>
</div>
<!-- Modal Backdrop -->
<div class="fixed inset-0 bg-inverse-surface/40 backdrop-blur-sm z-[60]"></div>
<!-- Bottom Sheet -->
<div class="fixed bottom-0 left-0 w-full z-[70] transition-transform duration-500 ease-out">
<div class="bg-surface-container-lowest rounded-t-[2.5rem] p-8 editorial-shadow max-w-2xl mx-auto">
<!-- Handle -->
<div class="w-12 h-1.5 bg-surface-container-highest rounded-full mx-auto mb-8"></div>
<!-- Header -->
<div class="flex justify-between items-center mb-10">
<h2 class="text-2xl font-headline font-bold text-on-surface">Come vuoi registrare?</h2>
<button class="w-10 h-10 rounded-full bg-surface-container-low flex items-center justify-center hover:bg-surface-container hover:scale-95 transition-all">
<span class="material-symbols-outlined text-on-surface-variant">close</span>
</button>
</div>
<!-- Options Stack -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 pb-8">
<!-- Option 1: Manual Input -->
<button class="group relative flex flex-col items-center justify-center p-8 rounded-[2rem] bg-surface-container-lowest transition-all hover:bg-primary-container/10 border-2 border-primary-fixed-dim">
<div class="w-16 h-16 rounded-2xl bg-primary/10 flex items-center justify-center mb-4 transition-transform group-hover:scale-110">
<span class="material-symbols-outlined text-primary text-4xl" data-icon="keyboard">keyboard</span>
</div>
<span class="font-headline font-bold text-lg text-on-surface">Inserisci kcal</span>
<span class="text-sm text-on-surface-variant mt-2 font-medium">Input manuale veloce</span>
</button>
<!-- Option 2: Voice/AI Description -->
<button class="group relative flex flex-col items-center justify-center p-8 rounded-[2rem] bg-surface-container-lowest transition-all hover:bg-secondary-container/10 border-2 border-secondary-fixed">
<div class="w-16 h-16 rounded-2xl bg-secondary-container/30 flex items-center justify-center mb-4 transition-transform group-hover:scale-110">
<span class="material-symbols-outlined text-secondary text-4xl" data-icon="mic">mic</span>
</div>
<span class="font-headline font-bold text-lg text-on-surface">Descrivi il pasto</span>
<span class="text-sm text-on-surface-variant mt-2 font-medium">Usa l'intelligenza artificiale</span>
</button>
</div>
<!-- Contextual Tip -->
<div class="bg-surface-container-low rounded-2xl p-4 flex items-start gap-4 mb-4">
<span class="material-symbols-outlined text-primary" data-icon="lightbulb" style="font-variation-settings: 'FILL' 1;">lightbulb</span>
<p class="text-sm text-on-surface-variant leading-relaxed">
Puoi dire cose come <span class="font-bold text-on-surface italic">"Un'insalata russa e due bicchieri d'acqua"</span> per una stima precisa.
</p>
</div>
</div>
</div>
<!-- Bottom Navigation Shell (Consistent with Shared Components) -->
<nav class="fixed bottom-0 left-0 w-full z-50 flex justify-around items-center px-4 pb-6 pt-2 bg-white/80 dark:bg-zinc-900/80 backdrop-blur-xl rounded-t-[2rem] shadow-[0px_-12px_32px_rgba(0,107,27,0.08)]">
<div class="flex flex-col items-center justify-center text-zinc-400 dark:text-zinc-500 px-5 py-2 hover:text-green-600 transition-opacity">
<span class="material-symbols-outlined" data-icon="event_note">event_note</span>
<span class="font-headline text-[10px] font-bold uppercase tracking-widest mt-1">Journal</span>
</div>
<div class="flex flex-col items-center justify-center bg-green-100 dark:bg-green-900/30 text-green-800 dark:text-green-200 rounded-2xl px-5 py-2 scale-90">
<span class="material-symbols-outlined" data-icon="bar_chart">bar_chart</span>
<span class="font-headline text-[10px] font-bold uppercase tracking-widest mt-1">Stats</span>
</div>
<div class="flex flex-col items-center justify-center text-zinc-400 dark:text-zinc-500 px-5 py-2 hover:text-green-600 transition-opacity">
<span class="material-symbols-outlined" data-icon="restaurant_menu">restaurant_menu</span>
<span class="font-headline text-[10px] font-bold uppercase tracking-widest mt-1">Plans</span>
</div>
<div class="flex flex-col items-center justify-center text-zinc-400 dark:text-zinc-500 px-5 py-2 hover:text-green-600 transition-opacity">
<span class="material-symbols-outlined" data-icon="person">person</span>
<span class="font-headline text-[10px] font-bold uppercase tracking-widest mt-1">Profile</span>
</div>
</nav>
</body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

View File

@@ -0,0 +1,246 @@
<!DOCTYPE html>
<html lang="it"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<link href="https://fonts.googleapis.com/css2?family=Lexend:wght@400;500;600;700;800&amp;family=Plus+Jakarta+Sans:wght@400;500;600&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
"colors": {
"inverse-on-surface": "#9c9d9d",
"on-tertiary-container": "#690002",
"on-primary-container": "#005e17",
"surface-variant": "#dbdddd",
"surface": "#f6f6f6",
"on-secondary-container": "#862400",
"error-container": "#f95630",
"on-surface": "#2d2f2f",
"primary-fixed-dim": "#83e881",
"on-secondary": "#ffefeb",
"on-surface-variant": "#5a5c5c",
"primary": "#006b1b",
"inverse-surface": "#0c0f0f",
"inverse-primary": "#91f78e",
"surface-tint": "#006b1b",
"surface-bright": "#f6f6f6",
"on-tertiary-fixed": "#3a0001",
"tertiary": "#b71211",
"tertiary-dim": "#a40007",
"primary-fixed": "#91f78e",
"secondary-dim": "#952800",
"surface-container-lowest": "#ffffff",
"error": "#b02500",
"on-background": "#2d2f2f",
"on-error-container": "#520c00",
"on-tertiary": "#ffefed",
"surface-container-low": "#f0f1f1",
"surface-container-highest": "#dbdddd",
"tertiary-fixed": "#ff9385",
"on-secondary-fixed-variant": "#972900",
"surface-dim": "#d3d5d5",
"on-secondary-fixed": "#651900",
"on-error": "#ffefec",
"secondary-container": "#ffc4b3",
"surface-container": "#e7e8e8",
"secondary": "#a83206",
"outline": "#767777",
"primary-container": "#91f78e",
"on-primary": "#d1ffc8",
"on-tertiary-fixed-variant": "#7a0003",
"outline-variant": "#acadad",
"background": "#f6f6f6",
"secondary-fixed": "#ffc4b3",
"on-primary-fixed-variant": "#00691a",
"primary-dim": "#005d16",
"surface-container-high": "#e1e3e3",
"tertiary-container": "#ff9385",
"tertiary-fixed-dim": "#ff7c6c",
"on-primary-fixed": "#00480f",
"secondary-fixed-dim": "#ffb19a",
"error-dim": "#b92902"
},
"borderRadius": {
"DEFAULT": "0.25rem",
"lg": "0.5rem",
"xl": "0.75rem",
"full": "9999px"
},
"fontFamily": {
"headline": ["Lexend"],
"body": ["Plus Jakarta Sans"],
"label": ["Plus Jakarta Sans"]
}
}
}
}
</script>
<style>
.material-symbols-outlined {
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
vertical-align: middle;
}
body { font-family: 'Plus Jakarta Sans', sans-serif; background-color: #f6f6f6; }
.font-headline { font-family: 'Lexend', sans-serif; }
</style>
<style>
body {
min-height: max(884px, 100dvh);
}
</style>
</head>
<body class="bg-surface text-on-surface min-h-screen pb-32">
<!-- TopAppBar -->
<header class="fixed top-0 w-full flex justify-between items-center px-6 h-16 bg-white/80 dark:bg-zinc-900/80 backdrop-blur-xl z-50">
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-full overflow-hidden bg-surface-container">
<img alt="Profile" class="w-full h-full object-cover" data-alt="Close-up portrait of a professional man with friendly expression in soft daylight studio setting" src="https://lh3.googleusercontent.com/aida-public/AB6AXuCqYGyjHm0Gmv8NEA7LgUR8XQneI4uQXOH9v_daWUsV-pjPrK6L3DNSQOfM1vDqqm25brto_p4nxzx20T5Gc7Lq5fZDEEPQ-Ge4HJ9vPT5wjTwf_KNLUuSyROh1y_rc8z6dFins05ib88sww_vnWCwbQr7QodSemo-9Ay73AFQFO2LRhwVIx2pLUphWO1BrdlPg2HZv0em04YJUU-GyXSW4cwCls0GRh-UXeVAIN7ojXqFNom4TPKIFlTijPba0Zb21TcEXYnukhOg"/>
</div>
<span class="text-xl font-headline font-bold tracking-tight text-green-700 dark:text-green-500">Precision Vitality</span>
</div>
<button class="text-zinc-500 dark:text-zinc-400 hover:opacity-80 transition-opacity active:scale-95 duration-200 ease-out">
<span class="material-symbols-outlined" data-icon="settings">settings</span>
</button>
</header>
<main class="pt-24 px-6 max-w-2xl mx-auto">
<!-- Tab Selector: Kinetic Editorial Style -->
<div class="flex p-1 bg-surface-container-low rounded-full mb-8">
<button class="flex-1 py-3 px-6 rounded-full text-sm font-label font-medium transition-all text-on-surface-variant hover:text-on-surface">
Oggi
</button>
<button class="flex-1 py-3 px-6 rounded-full text-sm font-label font-bold transition-all bg-surface-container-lowest text-primary shadow-sm">
Settimana
</button>
</div>
<!-- Temporal Navigation -->
<div class="flex items-center justify-between mb-10 px-2">
<button class="w-10 h-10 flex items-center justify-center rounded-full bg-surface-container-low text-on-surface-variant hover:bg-surface-container-high transition-colors">
<span class="material-symbols-outlined">chevron_left</span>
</button>
<div class="text-center">
<p class="text-[10px] font-headline font-bold uppercase tracking-[0.2em] text-on-surface-variant mb-1">Periodo Corrente</p>
<h2 class="text-xl font-headline font-bold text-on-surface">1420 apr</h2>
</div>
<button class="w-10 h-10 flex items-center justify-center rounded-full bg-surface-container-low text-on-surface-variant hover:bg-surface-container-high transition-colors">
<span class="material-symbols-outlined">chevron_right</span>
</button>
</div>
<!-- Chart Section: The Kinetic Editor Grid -->
<div class="bg-surface-container-lowest rounded-[2rem] p-8 mb-8">
<div class="flex justify-between items-end h-64 gap-3 mb-6">
<!-- Monday (Green - Under Goal) -->
<div class="flex-1 flex flex-col items-center gap-3">
<div class="relative w-full bg-surface-container-low rounded-full flex flex-col justify-end h-full overflow-hidden">
<div class="w-full bg-gradient-to-t from-primary-dim to-primary rounded-full transition-all" style="height: 75%;"></div>
</div>
<span class="text-[10px] font-headline font-bold text-on-surface-variant">LUN</span>
</div>
<!-- Tuesday (Green - Under Goal) -->
<div class="flex-1 flex flex-col items-center gap-3">
<div class="relative w-full bg-surface-container-low rounded-full flex flex-col justify-end h-full overflow-hidden">
<div class="w-full bg-gradient-to-t from-primary-dim to-primary rounded-full transition-all" style="height: 85%;"></div>
</div>
<span class="text-[10px] font-headline font-bold text-on-surface-variant">MAR</span>
</div>
<!-- Wednesday (Red - Over Goal) -->
<div class="flex-1 flex flex-col items-center gap-3">
<div class="relative w-full bg-surface-container-low rounded-full flex flex-col justify-end h-full overflow-hidden">
<div class="w-full bg-gradient-to-t from-error-dim to-error rounded-full transition-all" style="height: 100%;"></div>
<div class="absolute top-2 w-full flex justify-center">
<span class="material-symbols-outlined text-white text-xs" style="font-size: 14px;">warning</span>
</div>
</div>
<span class="text-[10px] font-headline font-bold text-error">MER</span>
</div>
<!-- Thursday (Green - Under Goal) -->
<div class="flex-1 flex flex-col items-center gap-3">
<div class="relative w-full bg-surface-container-low rounded-full flex flex-col justify-end h-full overflow-hidden">
<div class="w-full bg-gradient-to-t from-primary-dim to-primary rounded-full transition-all" style="height: 60%;"></div>
</div>
<span class="text-[10px] font-headline font-bold text-on-surface-variant">GIO</span>
</div>
<!-- Friday (Green - Under Goal) -->
<div class="flex-1 flex flex-col items-center gap-3">
<div class="relative w-full bg-surface-container-low rounded-full flex flex-col justify-end h-full overflow-hidden">
<div class="w-full bg-gradient-to-t from-primary-dim to-primary rounded-full transition-all" style="height: 90%;"></div>
</div>
<span class="text-[10px] font-headline font-bold text-on-surface-variant">VEN</span>
</div>
<!-- Saturday (Red - Over Goal) -->
<div class="flex-1 flex flex-col items-center gap-3">
<div class="relative w-full bg-surface-container-low rounded-full flex flex-col justify-end h-full overflow-hidden">
<div class="w-full bg-gradient-to-t from-error-dim to-error rounded-full transition-all" style="height: 98%;"></div>
</div>
<span class="text-[10px] font-headline font-bold text-error">SAB</span>
</div>
<!-- Sunday (Green - Under Goal) -->
<div class="flex-1 flex flex-col items-center gap-3">
<div class="relative w-full bg-surface-container-low rounded-full flex flex-col justify-end h-full overflow-hidden">
<div class="w-full bg-gradient-to-t from-primary-dim to-primary rounded-full transition-all" style="height: 40%;"></div>
</div>
<span class="text-[10px] font-headline font-bold text-on-surface-variant">DOM</span>
</div>
</div>
<div class="flex items-center justify-between pt-6 border-t border-surface-container">
<div class="flex items-center gap-2">
<div class="w-3 h-3 rounded-full bg-primary"></div>
<span class="text-xs font-label text-on-surface-variant">In target</span>
</div>
<div class="flex items-center gap-2">
<div class="w-3 h-3 rounded-full bg-error"></div>
<span class="text-xs font-label text-on-surface-variant">Eccesso</span>
</div>
</div>
</div>
<!-- Weekly Summary Card: Editorial Style -->
<div class="relative overflow-hidden bg-surface-container-low rounded-[2rem] p-8">
<div class="absolute -right-12 -top-12 w-48 h-48 bg-primary/5 rounded-full blur-3xl"></div>
<div class="relative z-10">
<h3 class="text-[10px] font-headline font-bold uppercase tracking-[0.2em] text-on-surface-variant mb-4">Riepilogo Prestazioni</h3>
<div class="flex items-baseline gap-2 mb-2">
<span class="text-4xl font-headline font-extrabold text-primary">1850</span>
<span class="text-lg font-headline font-bold text-on-surface">kcal/giorno</span>
</div>
<p class="text-sm font-label text-on-surface-variant leading-relaxed max-w-[200px]">
Media settimanale: <span class="text-on-surface font-bold">1850 kcal/giorno</span>. Ottimo lavoro, sei costante!
</p>
<div class="mt-8 flex gap-4">
<div class="flex-1 p-4 bg-surface-container-lowest rounded-2xl">
<span class="material-symbols-outlined text-primary mb-2">trending_down</span>
<p class="text-[10px] font-headline font-bold uppercase tracking-widest text-on-surface-variant">-12%</p>
<p class="text-xs font-label text-on-surface">vs scorsa sett.</p>
</div>
<div class="flex-1 p-4 bg-surface-container-lowest rounded-2xl">
<span class="material-symbols-outlined text-secondary mb-2" style="font-variation-settings: 'FILL' 1;">bolt</span>
<p class="text-[10px] font-headline font-bold uppercase tracking-widest text-on-surface-variant">5/7</p>
<p class="text-xs font-label text-on-surface">Giorni in target</p>
</div>
</div>
</div>
</div>
</main>
<!-- BottomNavBar -->
<nav class="fixed bottom-0 left-0 w-full z-50 flex justify-around items-center px-4 pb-6 pt-2 bg-white/80 dark:bg-zinc-900/80 backdrop-blur-xl rounded-t-[2rem] shadow-[0px_-12px_32px_rgba(0,107,27,0.08)]">
<a class="flex flex-col items-center justify-center text-zinc-400 dark:text-zinc-500 px-5 py-2 hover:text-green-600 transition-opacity" href="#">
<span class="material-symbols-outlined mb-1" data-icon="event_note">event_note</span>
<span class="font-headline text-[10px] font-bold uppercase tracking-widest">Journal</span>
</a>
<a class="flex flex-col items-center justify-center bg-green-100 dark:bg-green-900/30 text-green-800 dark:text-green-200 rounded-2xl px-5 py-2 scale-90 transition-transform duration-300" href="#">
<span class="material-symbols-outlined mb-1" data-icon="bar_chart" style="font-variation-settings: 'FILL' 1;">bar_chart</span>
<span class="font-headline text-[10px] font-bold uppercase tracking-widest">Stats</span>
</a>
<a class="flex flex-col items-center justify-center text-zinc-400 dark:text-zinc-500 px-5 py-2 hover:text-green-600 transition-opacity" href="#">
<span class="material-symbols-outlined mb-1" data-icon="restaurant_menu">restaurant_menu</span>
<span class="font-headline text-[10px] font-bold uppercase tracking-widest">Plans</span>
</a>
<a class="flex flex-col items-center justify-center text-zinc-400 dark:text-zinc-500 px-5 py-2 hover:text-green-600 transition-opacity" href="#">
<span class="material-symbols-outlined mb-1" data-icon="person">person</span>
<span class="font-headline text-[10px] font-bold uppercase tracking-widest">Profile</span>
</a>
</nav>
</body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

View File

@@ -0,0 +1,76 @@
# Design System Document: Precision Vitality
## 1. Overview & Creative North Star
The "Creative North Star" for this design system is **The Kinetic Editor**.
While many fitness apps feel like static spreadsheets, this system treats health data as a living, breathing editorial story. We move beyond the "standard tracker" by utilizing high-contrast typography scales, intentional asymmetry, and a sophisticated layering system. The goal is to make the user feel like they are interacting with a premium digital magazine that reacts to their progress. By breaking the grid with overlapping elements and prioritizing tonal depth over rigid borders, we create an environment that feels energetic yet remarkably clean.
## 2. Color Strategy: Tonal Vitality
Our palette utilizes the Material Design convention to ensure a "Health Green" core that feels professional rather than radioactive.
### The "No-Line" Rule
**Borders are prohibited.** You must never use a 1px solid border to define sections. Boundaries are created through:
* **Background Shifts:** Placing a `surface-container-lowest` card against a `surface` background.
* **Tonal Transitions:** Using `surface-container-low` for secondary groupings to create natural separation.
### Surface Hierarchy & Nesting
Treat the UI as a series of physical layers—like stacked sheets of fine paper.
* **Level 0 (Base):** `surface` (#f6f6f6) The canvas.
* **Level 1 (Sections):** `surface-container-low` (#f0f1f1) For grouping related modules.
* **Level 2 (Active Cards):** `surface-container-lowest` (#ffffff) The highest priority interactive elements.
### The "Glass & Gradient" Rule
To inject "visual soul," use subtle gradients.
* **Primary Action:** Transition from `primary` (#006b1b) to `primary-dim` (#005d16) at a 135° angle.
* **Floating Elements:** Apply Glassmorphism to top navigation bars or floating action buttons using `surface` at 80% opacity with a `24px` backdrop-blur.
## 3. Typography: Editorial Impact
We pair the geometric precision of **Lexend** for data with the humanist readability of **Plus Jakarta Sans** for interface elements.
* **Main Kcal Display (`display-lg`):** Lexend, 3.5rem (56px), Bold. Use `primary` color. This is the "Hero" of the screen.
* **Section Headlines (`headline-sm`):** Lexend, 1.5rem. Use `on-surface`.
* **Primary Labels (`label-md`):** Plus Jakarta Sans, 0.75rem, Medium. Use `on-surface-variant` for metadata.
* **Body Copy (`body-lg`):** Plus Jakarta Sans, 1rem, Regular. Use `on-surface`.
The high contrast between the massive `display-lg` calorie counts and the tight `label-sm` metadata creates an "Editorial" hierarchy that guides the eye instantly to what matters.
## 4. Elevation & Depth: Tonal Layering
Depth is achieved through "stacking" rather than traditional drop shadows.
* **The Layering Principle:** Avoid shadows on static cards. Instead, place a `surface-container-lowest` card on a `surface-container` background. The subtle 2-3% shift in hex value provides a sophisticated, modern lift.
* **Ambient Shadows:** For floating elements (e.g., a "Log Food" button), use a 15% opacity `primary` color shadow: `0px 12px 32px rgba(0, 107, 27, 0.15)`. This mimics natural light reflecting off a colored surface.
* **The Ghost Border Fallback:** If accessibility requires a stroke, use `outline-variant` at 15% opacity. Never use 100% opaque lines.
## 5. Components: The Kinetic Suite
### Progress Bars (The Core Vital)
* **Style:** Horizontal, `12px` height, `full` roundedness.
* **Container:** `surface-container-highest`.
* **Fill:** A linear gradient from `primary-fixed` to `primary`.
* **Animation:** Always use a "Spring" ease-out (0.6s) to make progress feel energetic.
### Cards & Modules
* **Rule:** Forbid divider lines. Use `1.5rem (xl)` corner radius for main dashboard cards and `1rem (lg)` for nested items.
* **Spacing:** Use `24px` vertical padding to provide significant breathing room.
### Buttons
* **Primary:** `primary` background, `on-primary` text. No shadow. `full` roundedness.
* **Secondary (Energy):** `secondary-container` background, `on-secondary-container` text. Use for "Add Calories" or "High Energy" actions.
### Input Fields
* **Style:** Minimalist. No bottom line. Use a `surface-container-low` background with a `0.5rem (sm)` radius. On focus, transition background to `surface-container-highest`.
### Contextual Components: "The Macro-Donut"
* Use a large-scale, thin-stroke (4px) ring for Macronutrient breakdowns. Overlap the donut slightly with the `display-lg` calorie title to create a sense of depth and custom-fit design.
## 6. Do's and Don'ts
### Do:
* **Do** use asymmetrical margins. For example, give the "Main Kcal" title a larger left-hand gutter (32px) than the body text (16px) to create a rhythmic, magazine-style layout.
* **Do** use `secondary` (Energy Orange) sparingly for high-motivation moments only (e.g., reaching a streak).
* **Do** prioritize "Breathing Room." If a screen feels crowded, increase the `surface-container` spacing rather than adding lines.
### Don't:
* **Don't** use Material Icons in their "Filled" state if they feel too heavy. Use "Outlined" or "Two-Tone" with `outline` color to maintain the "Clean" mood.
* **Don't** ever use #000000 for text. Always use `on-background` or `on-surface` (#2d2f2f) to keep the look premium and soft.
* **Don't** use standard 4px "Card Shadows." If it doesn't look like it's floating in a real-world space, don't shadow it.