49 Commits

Author SHA1 Message Date
rmcampos f63e51ca1f feat: add workflow to run against PRs
Build & Deploy to Pages / build-deploy (push) Has been cancelled
2026-07-10 22:25:20 +02:00
rmcampos 8a4435e28d Merge pull request 'Update dependency typescript to v7' (#3) from renovate/typescript-7.x into main
Build & Deploy to Pages / build-deploy (push) Successful in 56s
Reviewed-on: #3
Reviewed-by: Ricardo Campos <ricardompcampos@gmail.com>
2026-07-10 20:21:49 +00:00
rmcampos 761fd3dfc0 Merge branch 'main' into renovate/typescript-7.x
Build & Deploy to Pages / build-deploy (pull_request) Successful in 4m13s
2026-07-10 22:16:51 +02:00
rmcampos b51a54c6db chore: bump deps
Build & Deploy to Pages / build-deploy (push) Successful in 3m59s
2026-07-10 22:04:13 +02:00
renovate-bot adcc6b5f74 Update dependency typescript to v7
renovate/artifacts Artifact file update failure
Build & Deploy to Pages / build-deploy (pull_request) Failing after 20s
2026-07-10 16:59:18 +00:00
renovate-bot aa97b04baa Configure Renovate (#2)
Build & Deploy to Pages / build-deploy (push) Successful in 55s
Welcome to [Renovate](https://github.com/renovatebot/renovate)! This is an onboarding PR to help you understand and configure settings before regular Pull Requests begin.

🚦 To activate Renovate, merge this Pull Request. To disable Renovate, simply close this Pull Request unmerged.

📚 See our [Reading List](https://docs.renovatebot.com/reading-list/) for relevant documentation you may be interested in reading.

🔡 Do you want to change how Renovate upgrades your dependencies? Add your custom config to `renovate.json` in this branch. Renovate will update the Pull Request description the next time it runs.

---
### Detected Package Files

 * `package.json` (npm)

### Configuration Summary

Based on the default config's presets, Renovate will:

  - Start dependency updates only once this onboarding PR is merged
  - Enable Renovate Dependency Dashboard creation.
  - Use semantic commit type `fix` for dependencies and `chore` for all others if semantic commits are in use.
  - Ignore `node_modules`, `bower_components`, `vendor` and various test/tests (except for nuget) directories.
  - Group known monorepo packages together.
  - Use curated list of recommended non-monorepo package groupings.
  - Show only the Age and Confidence Merge Confidence badges for pull requests.
  - Apply crowd-sourced package replacement rules.
  - Apply crowd-sourced workarounds for known problems with packages.
  - Ensure that every dependency pinned by digest and sourced from Forgejo contains a link to the commit-to-commit diff
  - Ensure that every dependency pinned by digest and sourced from Gitea contains a link to the commit-to-commit diff
  - Ensure that every dependency pinned by digest and sourced from GitHub.com and Github enterprise contains a link to the commit-to-commit diff
  - Ensure that every dependency pinned by digest and sourced from GitLab.com contains a link to the commit-to-commit diff
  - Correctly link to the source code for golang.org/x packages
  - Link to pkg.go.dev/... for golang.org/x packages' title
  - Provide a link to octochangelog's improved breakdown for Renovate's changelogs

---

### What to Expect

It looks like your repository dependencies are already up-to-date and no Pull Requests will be necessary right away.

---

 Got questions? Check out Renovate's [Docs](https://docs.renovatebot.com/), particularly the Getting Started section.
If you need any further assistance then you can also [request help here](https://github.com/renovatebot/renovate/discussions).

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).

<!--renovate-config-hash:e80b4e42a3043bc12fa0640db4bac392d2bf770acf841360d7c8ceeeac2ec1a9-->

Reviewed-on: #2
Co-authored-by: Renovate Bot <renovate-bot@lightroasted.vps-kinghost.net>
Co-committed-by: Renovate Bot <renovate-bot@lightroasted.vps-kinghost.net>
2026-07-05 21:06:39 +00:00
rmcampos 2a1a492523 hotfix: vite config with base path
Build & Deploy to Pages / build-deploy (push) Successful in 57s
2026-07-05 00:00:45 +02:00
rmcampos 70d2d22055 Update .github/workflows/deploy.yml
Build & Deploy to Pages / build-deploy (push) Successful in 4m10s
2026-07-04 21:55:24 +00:00
rmcampos 0682649a9f Merge pull request 'feat: remove old files and update workflows for gitea' (#1) from feat/updates-for-gitea into main
Reviewed-on: #1
2026-07-04 21:49:48 +00:00
rmcampos 1eba605146 feat: remove old files and update workflows for gitea 2026-07-04 23:45:30 +02:00
rmcampos 0746088203 Add protein bar entry to food database 2026-05-19 13:14:57 -03:00
Copilot fe2f324bfa Make similar food suggestions clickable to populate food entry form (#12) 2026-04-28 23:00:36 -03:00
rmcampos 88f55e4f7a Update foodDatabase.ts 2026-04-28 22:48:00 -03:00
rmcampos 2a18a78a76 Update foodDatabase.ts 2026-04-28 22:35:21 -03:00
rmcampos 1a0bd0007f chore: update gitignore to ignore idea files 2026-04-28 13:14:10 -03:00
rmcampos 8f327c191b feat: update packages dependencies 2026-04-28 13:12:33 -03:00
Copilot 6837e4f139 feat: Add "Show other options" to surface nutritionally equivalent food alternatives (#10)
* Initial plan

* feat: Add 'Show other options' feature to find similar foods by category and bestFor

Agent-Logs-Url: https://github.com/RMCampos/calories-tracker/sessions/00437e19-3c33-48b4-8d15-f29ac843a20f

Co-authored-by: RMCampos <2219519+RMCampos@users.noreply.github.com>

* fix: Address code review issues - string type, dedup logic, HTML escaping

Agent-Logs-Url: https://github.com/RMCampos/calories-tracker/sessions/00437e19-3c33-48b4-8d15-f29ac843a20f

Co-authored-by: RMCampos <2219519+RMCampos@users.noreply.github.com>

* fix: Add full HTML escaping including quotes in alternatives display

Agent-Logs-Url: https://github.com/RMCampos/calories-tracker/sessions/00437e19-3c33-48b4-8d15-f29ac843a20f

Co-authored-by: RMCampos <2219519+RMCampos@users.noreply.github.com>

* feat: improve food options by food type

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: RMCampos <2219519+RMCampos@users.noreply.github.com>
Co-authored-by: Ricardo Campos <ricardompcampos@gmail.com>
2026-04-28 11:07:34 -03:00
rmcampos 44847d561f feat: add matching columsn for food types 2026-04-27 23:31:33 +02:00
rmcampos df70762505 Update types.ts 2026-04-27 17:53:50 -03:00
rmcampos 4e9e4edc54 Add 'bestFor' property to food items in database
Updated food database to include 'bestFor' property for various food items, specifying meal recommendations.
2026-04-27 17:52:45 -03:00
rmcampos fa7571e8aa feat: update foodlist to include new column bestFor 2026-04-27 17:12:27 -03:00
rmcampos 7119d21b6c Merge pull request #8 from RMCampos/copilot/feat-enable-updating-time-food-entry
feat: enable updating time from a food entry
2026-04-27 16:23:07 -03:00
copilot-swe-agent[bot] e4fa1f9c1c feat: enable updating time from a food entry
- Add visible time input (foodEntryTime) in index.html, hidden by default
- Show time input only when editing a food entry (setFoodToEdit)
- Use foodEntryTime value when saving an edited entry (updateFood)
- Hide and reset time input on clearEditing (cancel/save)
- input type=time ensures HH:MM format is preserved

Agent-Logs-Url: https://github.com/RMCampos/calories-tracker/sessions/085efbe7-b9c8-4a81-a58f-5dbdf91f2942

Co-authored-by: RMCampos <2219519+RMCampos@users.noreply.github.com>
2026-04-27 19:20:15 +00:00
copilot-swe-agent[bot] b07861f88f Initial plan 2026-04-27 19:16:28 +00:00
rmcampos 372a06eb6b chore: bump dependencies 2026-04-15 19:54:43 +02:00
rmcampos d173329e19 Add new food items to food database 2026-02-27 16:44:37 -03:00
rmcampos a5329af7b3 feat: manage current date with datetime api. Closes #6 2026-02-13 14:02:41 -03:00
rmcampos 6826a4256d chore: add and update food source list 2026-02-13 13:26:01 -03:00
rmcampos 8a187fbe15 feat: add multiple goals. by Claude 2026-02-12 19:45:29 -03:00
rmcampos 3f89eca586 chore: fix project errors 2026-02-12 18:48:18 -03:00
rmcampos 5c856300cd chore: update dependencies versions 2026-02-12 18:30:13 -03:00
rmcampos 78f709bee7 feat: improve alkaline indicator to a shorter option 2026-02-12 11:16:09 -03:00
rmcampos 2fbba3b6b2 feat: add weight and height features 2026-02-12 11:13:51 -03:00
rmcampos 78220fdbf0 Add Cashew Butter entry to food database 2026-01-09 09:51:37 -03:00
rmcampos 1ca0706923 fix: pre-workout group not showing up 2025-12-15 05:17:05 -03:00
rmcampos a3b1747bda chore: add leite vegetal 2025-12-13 16:28:09 -03:00
rmcampos 228c7e4661 feat: add grouping meal 2025-12-13 16:00:31 -03:00
rmcampos 915cfba809 fix: missing iphone icon when installed 2025-12-10 13:14:07 -03:00
rmcampos 6b36bf0e19 feat: make it PWA 2025-12-10 12:56:58 -03:00
rmcampos 39ce9e4684 feat: makes the AI nutrition search optional 2025-12-10 12:27:08 -03:00
rmcampos 37e3d4268b feat: add cache to localstore and clear options 2025-12-10 12:21:17 -03:00
rmcampos 4117c54d24 fix: food amount 2025-12-10 12:01:08 -03:00
rmcampos 0499e4dc4e feat: remove quant from prompt 2025-12-10 11:57:41 -03:00
rmcampos 761c19d394 chore: bump dependencies version 2025-12-10 11:48:04 -03:00
rmcampos 5948d976a8 feat: add AI info 2025-12-03 17:38:29 -03:00
rmcampos 63312b1bc0 feat: improve landing page and login 2025-12-03 16:42:52 -03:00
rmcampos ae7d5aa5b5 feat: enable english seach terms 2025-12-01 18:15:09 -03:00
rmcampos c0489dd5e8 Add sunflower seed entry to food database 2025-12-01 17:01:28 -03:00
rmcampos 531c5642d6 Add 'Leite Caju+côco A Tal da Castanha' to food database 2025-11-30 07:08:42 -03:00
27 changed files with 8938 additions and 935 deletions
+42
View File
@@ -0,0 +1,42 @@
name: Build Only
on:
pull_request:
branches: [ main ]
# Allow only one concurrent deployment
concurrency:
group: "prs"
cancel-in-progress: false
jobs:
build-deploy:
runs-on: easynode-debian
env:
VITE_APPWRITE_PROJECT_ID: ${{ secrets.VITE_APPWRITE_PROJECT_ID }}
VITE_APPWRITE_ENDPOINT: ${{ secrets.VITE_APPWRITE_ENDPOINT }}
VITE_APPWRITE_DBID: ${{ secrets.VITE_APPWRITE_DBID }}
VITE_APPWRITE_FOODENTRIESID: ${{ secrets.VITE_APPWRITE_FOODENTRIESID }}
VITE_APPWRITE_USERSETTINGSID: ${{ secrets.VITE_APPWRITE_USERSETTINGSID }}
VITE_APPWRITE_MONTLYCALORIESID: ${{ secrets.VITE_APPWRITE_MONTLYCALORIESID }}
VITE_APPWRITE_SHAREDDAYSID: ${{ secrets.VITE_APPWRITE_SHAREDDAYSID }}
VITE_CLAUDE_API_KEY: ${{ secrets.VITE_CLAUDE_API_KEY }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Cache npm dependencies
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
+32 -26
View File
@@ -1,6 +1,7 @@
name: Deploy to GitHub Pages name: Build & Deploy to Pages
on: on:
workflow_dispatch:
push: push:
branches: [ main ] branches: [ main ]
pull_request: pull_request:
@@ -11,9 +12,12 @@ concurrency:
group: "pages" group: "pages"
cancel-in-progress: false cancel-in-progress: false
env:
APP_FOLDER: "calories-tracker"
jobs: jobs:
build: build-deploy:
runs-on: ubuntu-latest runs-on: easynode-debian
env: env:
VITE_APPWRITE_PROJECT_ID: ${{ secrets.VITE_APPWRITE_PROJECT_ID }} VITE_APPWRITE_PROJECT_ID: ${{ secrets.VITE_APPWRITE_PROJECT_ID }}
VITE_APPWRITE_ENDPOINT: ${{ secrets.VITE_APPWRITE_ENDPOINT }} VITE_APPWRITE_ENDPOINT: ${{ secrets.VITE_APPWRITE_ENDPOINT }}
@@ -22,16 +26,19 @@ jobs:
VITE_APPWRITE_USERSETTINGSID: ${{ secrets.VITE_APPWRITE_USERSETTINGSID }} VITE_APPWRITE_USERSETTINGSID: ${{ secrets.VITE_APPWRITE_USERSETTINGSID }}
VITE_APPWRITE_MONTLYCALORIESID: ${{ secrets.VITE_APPWRITE_MONTLYCALORIESID }} VITE_APPWRITE_MONTLYCALORIESID: ${{ secrets.VITE_APPWRITE_MONTLYCALORIESID }}
VITE_APPWRITE_SHAREDDAYSID: ${{ secrets.VITE_APPWRITE_SHAREDDAYSID }} VITE_APPWRITE_SHAREDDAYSID: ${{ secrets.VITE_APPWRITE_SHAREDDAYSID }}
VITE_CLAUDE_API_KEY: ${{ secrets.VITE_CLAUDE_API_KEY }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Setup Node.js - name: Cache npm dependencies
uses: actions/setup-node@v4 uses: actions/cache@v3
with: with:
node-version: '22' path: ~/.npm
cache: 'npm' key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci
@@ -39,25 +46,24 @@ jobs:
- name: Build - name: Build
run: npm run build run: npm run build
- name: Upload artifact - name: Install rsync + ssh client
uses: actions/upload-pages-artifact@v3 run: apt-get update -qq && apt-get install -y -qq rsync openssh-client
with:
path: ./dist
deploy: - name: Configure SSH
environment: run: |
name: github-pages mkdir -p ~/.ssh
url: ${{ steps.deployment.outputs.page_url }} echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/deploy_key
runs-on: ubuntu-latest chmod 600 ~/.ssh/deploy_key
needs: build ssh-keyscan -H "${{ secrets.DEPLOY_HOST }}" >> ~/.ssh/known_hosts
steps: - name: Ensure destination folder exists
- name: Deploy to GitHub Pages run: |
id: deployment ssh -i ~/.ssh/deploy_key deploy@${{ secrets.DEPLOY_HOST }} \
uses: actions/deploy-pages@v4 "mkdir -p /srv/sites/${{ env.APP_FOLDER }} && chmod 755 /srv/sites/${{ env.APP_FOLDER }}"
- name: Upload site
run: |
rsync -avz --delete \
-e "ssh -i ~/.ssh/deploy_key" \
./dist/ deploy@${{ secrets.DEPLOY_HOST }}:/srv/sites/${{ env.APP_FOLDER }}/
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write
+8
View File
@@ -3,3 +3,11 @@ dist/
.env .env
senha.txt senha.txt
.vscode/ .vscode/
# PWA
dist/sw.js
dist/workbox-*.js
dev-dist/
# IDE
.idea/
-75
View File
@@ -1,75 +0,0 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Development Commands
- `npm run dev` - Start development server (Vite)
- `npm run build` - Build for production
- `npm run preview` - Preview production build
## Environment Setup
Requires environment variables in `.env`:
- `VITE_APPWRITE_ENDPOINT` - Appwrite server endpoint
- `VITE_APPWRITE_DBID` - Database ID
- `VITE_APPWRITE_FOODENTRIESID` - Food entries collection ID
- `VITE_APPWRITE_USERSETTINGSID` - User settings collection ID
- `VITE_APPWRITE_MONTLYCALORIESID` - Monthly calories collection ID
## Architecture Overview
This is a TypeScript/Vite-based calories tracking application with Appwrite backend integration. Key architectural patterns:
### Core Structure
- **Entry Point**: `src/index.ts` - Main application logic and DOM manipulation
- **State Management**: `src/state.ts` - Centralized application state
- **Backend Integration**: `src/appwrite.ts` - Appwrite client and database operations
- **Type Definitions**: `src/types.ts` - TypeScript interfaces for data models
### Data Flow
- User authentication managed through `AppwriteAuth` class
- Food entries stored via `AppwriteDB` class with real-time Appwrite sync
- Local food database in `src/foodDatabase.ts` for searching
- Calendar view tracks daily calorie totals separately from individual entries
### Key Components
- **Authentication**: Login/register forms with session management
- **Food Search**: Real-time search with keyboard navigation and debouncing
- **Food Entry**: Add/edit/delete food items with nutritional calculations
- **Calendar**: Monthly calendar view with daily calorie totals
- **Settings**: User-configurable nutritional goals
### State Management Patterns
- Global app state in `appState` object
- Date-based data loading (selectedDate/currentViewDate)
- Monthly calorie aggregation separate from individual food entries
- Real-time UI updates after database operations
### DOM Manipulation
- Utility functions in `src/DomUtils.ts` for type-safe DOM access
- Event listeners centralized in `setupEventListeners()`
- Dynamic card creation for food entries with collapsible details
### Backend Integration
- All data operations use Appwrite SDK
- User-scoped queries with proper authentication
- Concurrent operations for bulk deletions
- Timezone-aware date handling for cross-timezone consistency
## Code Patterns
### Data Calculations
- All nutrition values calculated from 100g base values in food database
- Proportional scaling based on user-entered grams
- Real-time preview updates during food entry
### Error Handling
- Try-catch blocks with user-friendly SweetAlert notifications
- Loading states during async operations
- Graceful fallbacks for missing data
### Event Management
- Event listener cleanup and re-attachment for dynamic content
- Keyboard navigation support (arrows, enter, escape)
- Click-outside handlers for dropdowns and modals
+6 -2
View File
@@ -1,8 +1,12 @@
![Calories Tracker](https://img.shields.io/badge/status-active-brightgreen) ![Version](https://img.shields.io/badge/version-1.0.0-blue) ![License](https://img.shields.io/badge/license-ISC-orange)
# 🍽️ Calories Tracker # 🍽️ Calories Tracker
A modern, responsive web application for tracking daily food intake, calories, and nutritional information. Built with TypeScript, Vite, and Appwrite for a seamless food logging experience. A modern, responsive web application for tracking daily food intake, calories, and nutritional information. Built with TypeScript, Vite, and Appwrite for a seamless food logging experience.
![Calories Tracker](https://img.shields.io/badge/status-active-brightgreen) ![Version](https://img.shields.io/badge/version-1.0.0-blue) ![License](https://img.shields.io/badge/license-ISC-orange) ## Live
https://calories-tracker.lightroasted.vps-kinghost.net
## ✨ Features ## ✨ Features
@@ -72,7 +76,7 @@ VITE_APPWRITE_USERSETTINGSID=your_user_settings_collection_id
1. **Clone the repository** 1. **Clone the repository**
```bash ```bash
git clone https://github.com/yourusername/calories-tracker.git git clone https://lightroasted.vps-kinghost.net/rmcampos/calories-tracker.git
cd calories-tracker cd calories-tracker
``` ```
Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

-41
View File
@@ -1,41 +0,0 @@
{
"name": "App",
"icons": [
{
"src": "/assets/android-icon-36x36.png",
"sizes": "36x36",
"type": "image/png",
"density": "0.75"
},
{
"src": "/assets/android-icon-48x48.png",
"sizes": "48x48",
"type": "image/png",
"density": "1.0"
},
{
"src": "/assets/android-icon-72x72.png",
"sizes": "72x72",
"type": "image/png",
"density": "1.5"
},
{
"src": "/assets/android-icon-96x96.png",
"sizes": "96x96",
"type": "image/png",
"density": "2.0"
},
{
"src": "/assets/android-icon-144x144.png",
"sizes": "144x144",
"type": "image/png",
"density": "3.0"
},
{
"src": "/assets/android-icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"density": "4.0"
}
]
}
+335 -9
View File
@@ -19,6 +19,13 @@
min-height: 50px; min-height: 50px;
} }
.logo h1 {
margin: 0;
font-size: 2rem;
font-weight: 700;
letter-spacing: -0.5px;
}
.logo h2 { .logo h2 {
margin: 0; margin: 0;
font-size: 1.3rem; font-size: 1.3rem;
@@ -30,20 +37,108 @@
align-items: center; align-items: center;
} }
.auth-form { .auth-buttons {
background: rgba(40, 40, 40, 0.9); display: flex;
backdrop-filter: blur(10px); align-items: center;
border-radius: 6px; gap: 12px;
padding: 25px; }
min-width: 300px;
.auth-buttons .header-btn.primary {
background: linear-gradient(135deg, #6B8DD6 0%, #5A7BC6 100%);
border: 1px solid rgba(107, 141, 214, 0.6);
}
.auth-buttons .header-btn.primary:hover {
box-shadow: 0 4px 15px rgba(107, 141, 214, 0.4);
}
/* Authentication Modal */
.auth-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.85);
display: flex;
justify-content: center;
align-items: center;
z-index: 10000;
backdrop-filter: blur(8px);
animation: fadeIn 0.3s ease-out;
}
.auth-modal-content {
background: rgba(40, 40, 40, 0.95);
backdrop-filter: blur(20px);
border-radius: 12px;
padding: 40px;
max-width: 450px;
width: 90%;
border: 1px solid rgba(100, 100, 100, 0.3); border: 1px solid rgba(100, 100, 100, 0.3);
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
position: relative;
animation: slideUp 0.3s ease-out;
}
.close-modal-btn {
position: absolute;
top: 15px;
right: 15px;
background: transparent;
border: none;
color: rgba(255, 255, 255, 0.6);
font-size: 2rem;
cursor: pointer;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
transition: all 0.2s ease;
line-height: 1;
}
.close-modal-btn:hover {
background: rgba(255, 255, 255, 0.1);
color: white;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.auth-form {
background: transparent;
border-radius: 0;
padding: 0;
min-width: auto;
border: none;
} }
.auth-form h3 { .auth-form h3 {
margin: 0 0 20px 0; margin: 0 0 25px 0;
text-align: center; text-align: center;
font-size: 1.3rem; font-size: 1.6rem;
font-weight: 500; font-weight: 600;
color: white;
} }
.auth-form .form-group { .auth-form .form-group {
@@ -308,6 +403,10 @@
order: 1; order: 1;
} }
.logo h1 {
font-size: 1.4rem;
}
.logo h2 { .logo h2 {
font-size: 1.1rem; font-size: 1.1rem;
} }
@@ -318,6 +417,16 @@
justify-content: center; justify-content: center;
} }
.auth-buttons {
order: 2;
gap: 8px;
}
.auth-buttons .header-btn {
font-size: 0.85rem;
padding: 8px 14px;
}
.user-info { .user-info {
order: 2; order: 2;
position: relative; position: relative;
@@ -356,6 +465,163 @@
} }
} }
/* Landing Page Styles */
.landing-page {
background: linear-gradient(180deg, #1a1a2e 0%, #16213e 100%);
min-height: calc(100vh - 80px);
padding: 60px 20px;
}
.landing-container {
max-width: 1200px;
margin: 0 auto;
}
.landing-hero {
text-align: center;
margin-bottom: 60px;
animation: fadeInUp 0.8s ease-out;
}
.landing-title {
font-size: 3rem;
font-weight: 700;
color: white;
margin: 0 0 20px 0;
line-height: 1.2;
background: linear-gradient(135deg, #6B8DD6 0%, #9B7BC6 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.landing-subtitle {
font-size: 1.3rem;
color: rgba(255, 255, 255, 0.85);
max-width: 800px;
margin: 0 auto;
line-height: 1.6;
}
.landing-features {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 30px;
margin-bottom: 60px;
animation: fadeInUp 0.8s ease-out 0.2s backwards;
}
.feature-card {
background: rgba(40, 40, 40, 0.6);
backdrop-filter: blur(10px);
border: 1px solid rgba(100, 100, 100, 0.3);
border-radius: 12px;
padding: 30px;
text-align: center;
transition: all 0.3s ease;
}
.feature-card:hover {
transform: translateY(-5px);
border-color: rgba(107, 141, 214, 0.5);
box-shadow: 0 8px 30px rgba(107, 141, 214, 0.2);
}
.feature-icon {
font-size: 3rem;
margin-bottom: 15px;
display: block;
}
.feature-card h3 {
color: white;
font-size: 1.3rem;
margin: 0 0 12px 0;
font-weight: 600;
}
.feature-card p {
color: rgba(255, 255, 255, 0.75);
font-size: 1rem;
line-height: 1.5;
margin: 0;
}
.landing-cta {
text-align: center;
background: rgba(74, 93, 138, 0.4);
backdrop-filter: blur(10px);
border: 1px solid rgba(100, 100, 100, 0.3);
border-radius: 12px;
padding: 50px 30px;
animation: fadeInUp 0.8s ease-out 0.4s backwards;
}
.landing-cta h2 {
color: white;
font-size: 2rem;
margin: 0 0 15px 0;
font-weight: 600;
}
.landing-cta p {
color: rgba(255, 255, 255, 0.85);
font-size: 1.1rem;
margin: 0 0 30px 0;
}
.cta-buttons {
display: flex;
gap: 15px;
justify-content: center;
flex-wrap: wrap;
}
.cta-btn {
padding: 15px 40px;
border-radius: 8px;
font-size: 1.1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
border: none;
min-width: 220px;
}
.cta-btn.primary {
background: linear-gradient(135deg, #6B8DD6 0%, #5A7BC6 100%);
color: white;
box-shadow: 0 4px 15px rgba(107, 141, 214, 0.4);
}
.cta-btn.primary:hover {
transform: translateY(-2px);
box-shadow: 0 6px 25px rgba(107, 141, 214, 0.5);
}
.cta-btn.secondary {
background: rgba(40, 40, 40, 0.8);
color: white;
border: 1px solid rgba(107, 141, 214, 0.5);
}
.cta-btn.secondary:hover {
background: rgba(60, 60, 60, 0.9);
border-color: rgba(107, 141, 214, 0.8);
transform: translateY(-2px);
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* For very small screens */ /* For very small screens */
@media (max-width: 480px) { @media (max-width: 480px) {
.auth-container { .auth-container {
@@ -376,7 +642,67 @@
right: -10px; right: -10px;
} }
.logo h1 {
font-size: 1.3rem;
}
.logo h2 { .logo h2 {
font-size: 1rem; font-size: 1rem;
} }
.auth-buttons {
gap: 8px;
}
.auth-buttons .header-btn {
padding: 6px 10px;
font-size: 0.8rem;
min-width: auto;
}
.auth-modal-content {
padding: 30px 20px;
}
}
/* Landing Page Responsive */
@media (max-width: 768px) {
.landing-page {
padding: 40px 15px;
}
.landing-title {
font-size: 2rem;
}
.landing-subtitle {
font-size: 1.1rem;
}
.landing-features {
grid-template-columns: 1fr;
gap: 20px;
}
.feature-card {
padding: 25px;
}
.landing-cta {
padding: 35px 20px;
}
.landing-cta h2 {
font-size: 1.5rem;
}
.cta-buttons {
flex-direction: column;
align-items: center;
}
.cta-btn {
width: 100%;
max-width: 300px;
}
} }
+308 -53
View File
@@ -20,59 +20,27 @@
<link rel="icon" type="image/png" sizes="32x32" href="/assets/favicon-32x32.png"> <link rel="icon" type="image/png" sizes="32x32" href="/assets/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="/assets/favicon-96x96.png"> <link rel="icon" type="image/png" sizes="96x96" href="/assets/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="/assets/favicon-16x16.png"> <link rel="icon" type="image/png" sizes="16x16" href="/assets/favicon-16x16.png">
<link rel="manifest" href="/assets/manifest.json">
<meta name="msapplication-TileColor" content="#ffffff"> <meta name="msapplication-TileColor" content="#ffffff">
<meta name="msapplication-TileImage" content="/assets/ms-icon-144x144.png"> <meta name="msapplication-TileImage" content="/assets/ms-icon-144x144.png">
<meta name="theme-color" content="#121212"> <meta name="theme-color" content="#121212">
<!-- PWA Meta Tags -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="Food Tracker">
<meta name="mobile-web-app-capable" content="yes">
</head> </head>
<body> <body>
<!-- Authentication Header --> <!-- Authentication Header -->
<header class="auth-header"> <header class="auth-header">
<div class="auth-container"> <div class="auth-container">
<div class="logo"> <div class="logo">
<h2>🍎 Food Tracker</h2> <h1>🍎 Food Tracker</h1>
</div> </div>
<!-- Authentication Forms --> <!-- Authentication Buttons (shown when not logged in) -->
<div id="auth-section" class="auth-section"> <div id="auth-buttons" class="auth-buttons">
<!-- Login Form --> <button id="open-login-btn" class="header-btn">Login</button>
<div id="login-form" class="auth-form"> <button id="open-register-btn" class="header-btn primary">Get Started</button>
<h3>Login</h3>
<form id="loginForm">
<div class="form-group">
<input type="email" id="loginEmail" placeholder="Email" required>
</div>
<div class="form-group">
<input type="password" id="loginPassword" placeholder="Password" required>
</div>
<button type="submit" class="auth-btn">Login</button>
<p class="auth-switch">
Don't have an account?
<a href="#" id="showRegister">Register here</a>
</p>
</form>
</div>
<!-- Register Form -->
<div id="register-form" class="auth-form hidden">
<h3>Register</h3>
<form id="registerForm">
<div class="form-group">
<input type="text" id="registerName" placeholder="Name" required>
</div>
<div class="form-group">
<input type="email" id="registerEmail" placeholder="Email" required>
</div>
<div class="form-group">
<input type="password" id="registerPassword" placeholder="Password" required minlength="8">
</div>
<button type="submit" class="auth-btn">Register</button>
<p class="auth-switch">
Already have an account?
<a href="#" id="showLogin">Login here</a>
</p>
</form>
</div>
</div> </div>
<!-- User Info (shown when logged in) --> <!-- User Info (shown when logged in) -->
@@ -99,6 +67,109 @@
</div> </div>
</header> </header>
<!-- Authentication Modal -->
<div id="auth-modal" class="auth-modal hidden">
<div class="auth-modal-content">
<button id="close-auth-modal" class="close-modal-btn">×</button>
<!-- Login Form -->
<div id="login-form" class="auth-form">
<h3>Welcome Back</h3>
<form id="loginForm">
<div class="form-group">
<input type="email" id="loginEmail" placeholder="Email" required>
</div>
<div class="form-group">
<input type="password" id="loginPassword" placeholder="Password" required>
</div>
<button type="submit" class="auth-btn">Login</button>
<p class="auth-switch">
Don't have an account?
<a href="#" id="showRegister">Register here</a>
</p>
</form>
</div>
<!-- Register Form -->
<div id="register-form" class="auth-form hidden">
<h3>Create Your Account</h3>
<form id="registerForm">
<div class="form-group">
<input type="text" id="registerName" placeholder="Name" required>
</div>
<div class="form-group">
<input type="email" id="registerEmail" placeholder="Email" required>
</div>
<div class="form-group">
<input type="password" id="registerPassword" placeholder="Password" required minlength="8">
</div>
<button type="submit" class="auth-btn">Register</button>
<p class="auth-switch">
Already have an account?
<a href="#" id="showLogin">Login here</a>
</p>
</form>
</div>
</div>
</div>
<!-- Landing Page Content (shown when not logged in) -->
<section id="landing-page" class="landing-page hidden">
<div class="landing-container">
<div class="landing-hero">
<h1 class="landing-title">Track Your Nutrition, Transform Your Health</h1>
<p class="landing-subtitle">Take control of your diet with our comprehensive calorie and nutrition tracker. Perfect for anyone who wants to maintain a balanced diet and achieve their health goals.</p>
</div>
<div class="landing-features">
<div class="feature-card">
<div class="feature-icon">📊</div>
<h3>Detailed Tracking</h3>
<p>Monitor calories, protein, fat, carbs, and fiber for every meal. Get a complete picture of your daily nutrition.</p>
</div>
<div class="feature-card">
<div class="feature-icon">🎯</div>
<h3>Set Goals</h3>
<p>Define your daily macros targets and track your progress. Stay motivated and on track with your health journey.</p>
</div>
<div class="feature-card">
<div class="feature-icon">📅</div>
<h3>Calendar View</h3>
<p>Visualize your nutrition history with an intuitive calendar. See patterns and maintain consistency over time.</p>
</div>
<div class="feature-card">
<div class="feature-icon">🥗</div>
<h3>Alkaline Balance</h3>
<p>Track your alkaline food intake to maintain a healthy pH balance and support overall wellness.</p>
</div>
<div class="feature-card">
<div class="feature-icon">🔍</div>
<h3>Smart Search</h3>
<p>Quickly find and log foods from our comprehensive database. Make tracking effortless and accurate.</p>
</div>
<div class="feature-card">
<div class="feature-icon">🔗</div>
<h3>Share Progress</h3>
<p>Share your daily food log with friends, trainers, or nutritionists. Get support and accountability.</p>
</div>
</div>
<div class="landing-cta">
<h2>Ready to Start Your Health Journey?</h2>
<p>Join today and take the first step towards a healthier, more balanced lifestyle.</p>
<div class="cta-buttons">
<button id="cta-register-btn" class="cta-btn primary">Get Started Free</button>
<button id="cta-login-btn" class="cta-btn secondary">Already Have an Account?</button>
</div>
</div>
</div>
</section>
<!-- Loading Overlay --> <!-- Loading Overlay -->
<div id="loading-overlay" class="loading-overlay hidden"> <div id="loading-overlay" class="loading-overlay hidden">
<div class="loading-spinner"></div> <div class="loading-spinner"></div>
@@ -196,6 +267,10 @@
<label for="gramAmount">Amount (grams)</label> <label for="gramAmount">Amount (grams)</label>
<input type="number" id="gramAmount" min="1" step="1" value="100"> <input type="number" id="gramAmount" min="1" step="1" value="100">
</div> </div>
<div class="form-group hidden" id="foodTimeGroup">
<label for="foodEntryTime">Time</label>
<input type="time" id="foodEntryTime">
</div>
<button class="add-btn" id="add-food-btn">Add Food</button> <button class="add-btn" id="add-food-btn">Add Food</button>
<button class="cancel-btn" id="cancel-food-btn">Cancel</button> <button class="cancel-btn" id="cancel-food-btn">Cancel</button>
</div> </div>
@@ -223,6 +298,75 @@
</div> </div>
</div> </div>
</div> </div>
<div class="form-group checkbox-group">
<label class="checkbox-label">
<input type="checkbox" id="enableAlternatives" class="ai-checkbox">
<span>Show other options</span>
</label>
</div>
<!-- Alternatives Container -->
<div class="display-none" id="alternatives-container">
<div class="alternatives-list-container">
<h3 class="alternatives-title">Similar Food Options</h3>
<div id="alternatives-list"></div>
</div>
</div>
<div class="form-group checkbox-group">
<label class="checkbox-label">
<input type="checkbox" id="enableAINutrition" class="ai-checkbox">
<span>Show AI nutrition information</span>
</label>
</div>
<!-- AI Nutrition Info Card -->
<div class="display-none" id="ai-nutrition-card">
<div class="ai-nutrition-container">
<h3 class="ai-nutrition-title">
<span class="ai-badge">AI</span>
Detailed Nutrition Information
</h3>
<!-- Loading State -->
<div id="ai-nutrition-loading" class="ai-loading">
<div class="ai-spinner"></div>
<p>Analyzing nutritional content...</p>
</div>
<!-- Error State -->
<div id="ai-nutrition-error" class="ai-error hidden">
<p>Unable to load detailed nutrition info. Please try again.</p>
</div>
<!-- Content -->
<div id="ai-nutrition-content" class="ai-nutrition-content hidden">
<!-- Vitamins Section -->
<div class="ai-nutrition-section">
<h4 class="ai-section-title">Vitamins</h4>
<ul id="ai-vitamins-list" class="ai-list"></ul>
</div>
<!-- Minerals Section -->
<div class="ai-nutrition-section">
<h4 class="ai-section-title">Minerals</h4>
<ul id="ai-minerals-list" class="ai-list"></ul>
</div>
<!-- Benefits Section -->
<div class="ai-nutrition-section">
<h4 class="ai-section-title">Health Benefits</h4>
<ul id="ai-benefits-list" class="ai-list"></ul>
</div>
<!-- Notes Section -->
<div class="ai-nutrition-section" id="ai-notes-section">
<h4 class="ai-section-title">Notes</h4>
<p id="ai-notes-text" class="ai-notes"></p>
</div>
</div>
</div>
</div>
</div> </div>
<div class="section-title">Food Log</div> <div class="section-title">Food Log</div>
@@ -249,31 +393,142 @@
<div class="container"> <div class="container">
<h1>⚙️ Settings</h1> <h1>⚙️ Settings</h1>
<div class="date-display">Define your daily macros goal</div> <div class="date-display">Body Metrics</div>
<form id="settingsForm" class="settings-form"> <form id="settingsForm" class="settings-form">
<div class="form-group"> <div class="form-group">
<label for="caloriesGoal">Daily calorie goal</label> <label for="bodyWeight">Body Weight (kg)</label>
<input type="number" id="caloriesGoal" placeholder="Calories" required> <input type="text" id="bodyWeight" placeholder="Body Weight">
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="proteinGoal">Daily protein goal</label> <label for="height">Height (cm)</label>
<input type="number" id="proteinGoal" placeholder="Proteins in grams" required> <input type="number" id="height" placeholder="Height">
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="fatGoal">Daily fat goal</label> <label for="bmi">Body Mass Index (BMI)</label>
<input type="number" id="fatGoal" placeholder="Fat in grams" required> <input type="number" id="bmi" placeholder="BMI" readonly>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="carboGoal">Daily carbs goal</label> <label for="bmiResult">Body Mass Index (BMI) Result</label>
<input type="number" id="carboGoal" placeholder="Carbo in grams" required> <input type="text" id="bmiResult" placeholder="BMI Result" readonly>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="fiberGoal">Daily fiber goal</label> <label for="timezone">Timezone</label>
<input type="number" id="fiberGoal" placeholder="Fiber in grams" required> <select id="timezone">
<option value="">— Use browser time —</option>
<optgroup label="UTC">
<option value="UTC">UTC</option>
</optgroup>
<optgroup label="Americas">
<option value="America/New_York">America/New_York</option>
<option value="America/Chicago">America/Chicago</option>
<option value="America/Denver">America/Denver</option>
<option value="America/Los_Angeles">America/Los_Angeles</option>
<option value="America/Toronto">America/Toronto</option>
<option value="America/Vancouver">America/Vancouver</option>
<option value="America/Mexico_City">America/Mexico_City</option>
<option value="America/Bogota">America/Bogota</option>
<option value="America/Lima">America/Lima</option>
<option value="America/Caracas">America/Caracas</option>
<option value="America/La_Paz">America/La_Paz</option>
<option value="America/Santiago">America/Santiago</option>
<option value="America/Sao_Paulo">America/Sao_Paulo</option>
<option value="America/Argentina/Buenos_Aires">America/Argentina/Buenos_Aires</option>
</optgroup>
<optgroup label="Europe">
<option value="Europe/London">Europe/London</option>
<option value="Europe/Lisbon">Europe/Lisbon</option>
<option value="Europe/Amsterdam">Europe/Amsterdam</option>
<option value="Europe/Berlin">Europe/Berlin</option>
<option value="Europe/Madrid">Europe/Madrid</option>
<option value="Europe/Paris">Europe/Paris</option>
<option value="Europe/Rome">Europe/Rome</option>
<option value="Europe/Helsinki">Europe/Helsinki</option>
<option value="Europe/Moscow">Europe/Moscow</option>
<option value="Europe/Istanbul">Europe/Istanbul</option>
</optgroup>
<optgroup label="Africa">
<option value="Africa/Cairo">Africa/Cairo</option>
<option value="Africa/Lagos">Africa/Lagos</option>
<option value="Africa/Nairobi">Africa/Nairobi</option>
<option value="Africa/Johannesburg">Africa/Johannesburg</option>
</optgroup>
<optgroup label="Asia">
<option value="Asia/Karachi">Asia/Karachi</option>
<option value="Asia/Kolkata">Asia/Kolkata</option>
<option value="Asia/Dhaka">Asia/Dhaka</option>
<option value="Asia/Bangkok">Asia/Bangkok</option>
<option value="Asia/Jakarta">Asia/Jakarta</option>
<option value="Asia/Singapore">Asia/Singapore</option>
<option value="Asia/Shanghai">Asia/Shanghai</option>
<option value="Asia/Seoul">Asia/Seoul</option>
<option value="Asia/Tokyo">Asia/Tokyo</option>
<option value="Asia/Dubai">Asia/Dubai</option>
</optgroup>
<optgroup label="Oceania">
<option value="Australia/Sydney">Australia/Sydney</option>
<option value="Pacific/Auckland">Pacific/Auckland</option>
</optgroup>
</select>
</div> </div>
<button type="submit" class="settings-btn">Save</button> <button type="submit" class="settings-btn">Save</button>
</form> </form>
<div class="date-display" style="margin-top: 40px;">Nutrition Goals</div>
<div id="goalsContainer">
<!-- Dynamically rendered list of goals -->
</div>
<button type="button" id="addGoalBtn" class="settings-btn" style="margin-top: 15px;">+ Add Goal</button>
<div id="goalFormContainer" class="hidden">
<form id="goalForm" class="settings-form" style="margin-top: 15px;">
<div class="form-group">
<label for="goalNameInput">Goal Name</label>
<input type="text" id="goalNameInput" placeholder="e.g. Workout Day" required>
</div>
<div class="form-group">
<label for="goalCaloriesInput">Daily calorie goal</label>
<input type="number" id="goalCaloriesInput" placeholder="Calories">
</div>
<div class="form-group">
<label for="goalProteinInput">Daily protein goal (g)</label>
<input type="number" id="goalProteinInput" placeholder="Proteins in grams">
</div>
<div class="form-group">
<label for="goalFatInput">Daily fat goal (g)</label>
<input type="number" id="goalFatInput" placeholder="Fat in grams">
</div>
<div class="form-group">
<label for="goalCarboInput">Daily carbs goal (g)</label>
<input type="number" id="goalCarboInput" placeholder="Carbs in grams">
</div>
<div class="form-group">
<label for="goalFiberInput">Daily fiber goal (g)</label>
<input type="number" id="goalFiberInput" placeholder="Fiber in grams">
</div>
<input type="hidden" id="editingGoalId" value="">
<button type="submit" class="settings-btn">Save Goal</button>
<button type="button" id="cancelGoalBtn" class="settings-btn" style="margin-top: 10px; background: #555; border-color: #555;">Cancel</button>
</form>
</div>
<div class="date-display" style="margin-top: 40px;">AI Nutrition Cache</div>
<div class="cache-info-section">
<div class="cache-stats">
<div class="cache-stat-item">
<span class="cache-stat-label">Cached Items:</span>
<span class="cache-stat-value" id="cacheItemCount">0</span>
</div>
<div class="cache-stat-item">
<span class="cache-stat-label">Cache Size:</span>
<span class="cache-stat-value" id="cacheSize">0 KB</span>
</div>
</div>
<button type="button" id="clearCacheBtn" class="settings-btn clear-cache-btn">Clear Cache</button>
</div>
</div> </div>
</section> </section>
+6025 -433
View File
File diff suppressed because it is too large Load Diff
+6 -3
View File
@@ -12,11 +12,14 @@
"license": "ISC", "license": "ISC",
"description": "", "description": "",
"devDependencies": { "devDependencies": {
"typescript": "^5.9.3", "typescript": "^7.0.0",
"vite": "^7.1.12" "vite": "^8.1.4",
"vite-plugin-pwa": "^1.3.0",
"workbox-window": "^7.4.1"
}, },
"dependencies": { "dependencies": {
"appwrite": "^21.3.0", "@anthropic-ai/sdk": "^0.111.0",
"appwrite": "^26.1.0",
"sweetalert": "^2.1.2" "sweetalert": "^2.1.2"
} }
} }
+6
View File
@@ -0,0 +1,6 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended"
]
}
+171
View File
@@ -32,6 +32,35 @@
border-color: #6B8DD6; border-color: #6B8DD6;
} }
.settings-form select {
width: 100%;
padding: 12px;
padding-right: 36px;
border-radius: 6px;
font-size: 1rem;
box-sizing: border-box;
border: 1px solid #404040;
background-color: #2a2a2a;
color: #e0e0e0;
appearance: none;
-webkit-appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%23a0a0a0' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 12px center;
cursor: pointer;
}
.settings-form select:focus {
outline: none;
border-color: #6B8DD6;
}
.settings-form select option,
.settings-form select optgroup {
background-color: #2a2a2a;
color: #e0e0e0;
}
.settings-btn { .settings-btn {
width: 100%; width: 100%;
padding: 12px; padding: 12px;
@@ -51,3 +80,145 @@
border: 1px solid #5A7BC4; border: 1px solid #5A7BC4;
transform: translateY(-1px); transform: translateY(-1px);
} }
.cache-info-section {
backdrop-filter: blur(10px);
border-radius: 10px;
padding: 25px;
min-width: 300px;
background: rgba(40, 40, 40, 0.9);
border: 1px solid rgba(100, 100, 100, 0.3);
margin-top: 20px;
}
.cache-stats {
display: flex;
flex-direction: column;
gap: 15px;
margin-bottom: 20px;
}
.cache-stat-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
background: rgba(60, 60, 60, 0.5);
border-radius: 6px;
}
.cache-stat-label {
color: #b0b0b0;
font-size: 0.95rem;
}
.cache-stat-value {
color: #e0e0e0;
font-size: 1.1rem;
font-weight: 600;
}
.clear-cache-btn {
background: #d66b6b;
border: 1px solid #d66b6b;
}
.clear-cache-btn:hover {
background: #c45a5a;
border: 1px solid #c45a5a;
}
.no-goals-msg {
color: #888;
padding: 15px 0;
font-size: 0.95rem;
}
.goal-card {
backdrop-filter: blur(10px);
border-radius: 10px;
padding: 15px 20px;
background: rgba(40, 40, 40, 0.9);
border: 1px solid rgba(100, 100, 100, 0.3);
margin-bottom: 12px;
}
.goal-card-active {
border-color: #6B8DD6;
}
.goal-card-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 8px;
}
.goal-name {
font-size: 1.05rem;
font-weight: 600;
color: #e0e0e0;
}
.goal-active-badge {
font-size: 0.75rem;
font-weight: 600;
color: #6B8DD6;
background: rgba(107, 141, 214, 0.15);
border: 1px solid #6B8DD6;
border-radius: 4px;
padding: 2px 7px;
}
.goal-summary {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 12px;
color: #b0b0b0;
font-size: 0.88rem;
}
.goal-summary span {
background: rgba(60, 60, 60, 0.5);
border-radius: 4px;
padding: 2px 8px;
}
.goal-actions {
display: flex;
gap: 8px;
}
.goal-actions .btn {
padding: 6px 14px;
border-radius: 5px;
font-size: 0.85rem;
cursor: pointer;
border: 1px solid #555;
background: #333;
color: #e0e0e0;
transition: background 0.2s;
}
.goal-actions .btn:hover {
background: #444;
}
.goal-actions .goal-delete-btn {
border-color: #d66b6b;
color: #d66b6b;
}
.goal-actions .goal-delete-btn:hover {
background: rgba(214, 107, 107, 0.15);
}
.goal-actions .goal-select-btn {
border-color: #6B8DD6;
color: #6B8DD6;
}
.goal-actions .goal-select-btn:hover {
background: rgba(107, 141, 214, 0.15);
}
+3 -3
View File
@@ -95,8 +95,7 @@ export function delay(seconds: number) {
return new Promise(resolve => setTimeout(resolve, seconds)); return new Promise(resolve => setTimeout(resolve, seconds));
} }
export function getIcon(category: string) { const icons = {
const icons = {
'fats': '🥑 🍳 🍟', 'fats': '🥑 🍳 🍟',
'proteins': '🫘 🥩 🥚', 'proteins': '🫘 🥩 🥚',
'carbs': '🍞 🥔 🍠', 'carbs': '🍞 🥔 🍠',
@@ -104,7 +103,8 @@ export function getIcon(category: string) {
'fruits': '🍊 🍇 🍎', 'fruits': '🍊 🍇 🍎',
'low carb': '🥦 🍅 🍓', 'low carb': '🥦 🍅 🍓',
'dairy': '🧀 🧈 🥛' 'dairy': '🧀 🧈 🥛'
}; };
export function getIcon(category: keyof typeof icons) {
return icons[category]; return icons[category];
} }
+92 -1
View File
@@ -1,5 +1,5 @@
import { Client, Account, Databases, Query, ID } from 'appwrite'; import { Client, Account, Databases, Query, ID } from 'appwrite';
import { DailyTotalCalories, FoodStorage, UserSettings, SharedDay } from './types'; import { FoodStorage, UserSettings } from './types';
// Initialize Appwrite client // Initialize Appwrite client
const client = new Client(); const client = new Client();
@@ -264,6 +264,97 @@ export class AppwriteDB {
} }
} }
static async updateUserSettings(documentId: string, settings: Partial<UserSettings>) {
try {
const response = await databases.updateDocument(
DATABASE_ID,
USER_SETTINGS_COLLECTION_ID,
documentId,
{
...settings,
userId: (await account.get()).$id,
}
);
console.debug('User settings updated:', response);
return response;
} catch (error) {
console.error('Update user settings error:', error);
throw error;
}
}
static async saveUserGoal(goal: UserSettings) {
try {
const response = await databases.createDocument(
DATABASE_ID,
USER_SETTINGS_COLLECTION_ID,
'unique()',
{
...goal,
userId: (await account.get()).$id,
}
);
console.debug('User goal saved:', response);
return response;
} catch (error) {
console.error('Save user goal error:', error);
throw error;
}
}
static async updateUserGoal(documentId: string, goal: UserSettings) {
try {
const response = await databases.updateDocument(
DATABASE_ID,
USER_SETTINGS_COLLECTION_ID,
documentId,
{
...goal,
userId: (await account.get()).$id,
}
);
console.debug('User goal updated:', response);
return response;
} catch (error) {
console.error('Update user goal error:', error);
throw error;
}
}
static async deleteUserGoal(documentId: string) {
try {
await databases.deleteDocument(
DATABASE_ID,
USER_SETTINGS_COLLECTION_ID,
documentId
);
console.debug('User goal deleted:', documentId);
} catch (error) {
console.error('Delete user goal error:', error);
throw error;
}
}
static async setActiveGoal(goalId: string, allGoals: UserSettings[]) {
try {
const updates = allGoals
.filter(goal => goal.id)
.map(goal =>
databases.updateDocument(
DATABASE_ID,
USER_SETTINGS_COLLECTION_ID,
goal.id!,
{ isActive: goal.id === goalId }
)
);
await Promise.all(updates);
console.debug('Active goal set:', goalId);
} catch (error) {
console.error('Set active goal error:', error);
throw error;
}
}
// Delete food entry // Delete food entry
static async deleteFoodEntry(documentId: string) { static async deleteFoodEntry(documentId: string) {
try { try {
+36 -1
View File
@@ -1,9 +1,24 @@
import { getDivById } from "./DomUtils"; import { getDivById } from "./DomUtils";
export function showAuthForms() { export function showAuthForms() {
getDivById('auth-section').classList.remove('hidden'); getDivById('auth-buttons').classList.remove('hidden');
getDivById('user-info').classList.add('hidden'); getDivById('user-info').classList.add('hidden');
getDivById('app-content').classList.add('hidden'); getDivById('app-content').classList.add('hidden');
getDivById('landing-page').classList.remove('hidden');
}
export function hideAuthForms() {
getDivById('auth-buttons').classList.add('hidden');
getDivById('auth-modal').classList.add('hidden');
getDivById('landing-page').classList.add('hidden');
}
export function openAuthModal() {
getDivById('auth-modal').classList.remove('hidden');
}
export function closeAuthModal() {
getDivById('auth-modal').classList.add('hidden');
} }
export function toggleAuthForms() { export function toggleAuthForms() {
@@ -16,3 +31,23 @@ export function toggleAuthForms() {
const resetForm = document.getElementById('registerForm') as HTMLFormElement; const resetForm = document.getElementById('registerForm') as HTMLFormElement;
resetForm.reset(); resetForm.reset();
} }
export function showRegisterForm() {
document.getElementById('login-form')?.classList.add('hidden');
document.getElementById('register-form')?.classList.remove('hidden');
const resetForm = document.getElementById('registerForm') as HTMLFormElement;
resetForm.reset();
openAuthModal();
}
export function showLoginForm() {
document.getElementById('login-form')?.classList.remove('hidden');
document.getElementById('register-form')?.classList.add('hidden');
const logForm = document.getElementById('loginForm') as HTMLFormElement;
logForm.reset();
openAuthModal();
}
+179
View File
@@ -0,0 +1,179 @@
import Anthropic from '@anthropic-ai/sdk';
const API_KEY = import.meta.env.VITE_CLAUDE_API_KEY;
// Initialize Claude client
const anthropic = new Anthropic({
apiKey: API_KEY,
dangerouslyAllowBrowser: true // Allow usage in browser (for development/demo purposes)
});
export interface NutritionInfo {
vitamins: string[];
minerals: string[];
benefits: string[];
notes: string;
}
// localStorage-based cache for nutrition info
// Key format: "nutrition_cache_foodName_grams" (e.g., "nutrition_cache_apple_100")
const CACHE_PREFIX = 'nutrition_cache_';
/**
* Generate cache key from food name and grams
*/
function getCacheKey(foodName: string, grams: number): string {
return `${CACHE_PREFIX}${foodName.toLowerCase().trim()}_${grams}`;
}
/**
* Get cached nutrition info if available
*/
function getCachedNutrition(foodName: string, grams: number): NutritionInfo | null {
try {
const key = getCacheKey(foodName, grams);
const cached = localStorage.getItem(key);
if (cached) {
return JSON.parse(cached) as NutritionInfo;
}
return null;
} catch (error) {
console.error('Error reading from cache:', error);
return null;
}
}
/**
* Store nutrition info in cache
*/
function cacheNutrition(foodName: string, grams: number, info: NutritionInfo): void {
try {
const key = getCacheKey(foodName, grams);
localStorage.setItem(key, JSON.stringify(info));
const stats = getCacheStats();
console.log(`Cached nutrition info for: ${key} (total cached: ${stats.size})`);
} catch (error) {
console.error('Error writing to cache:', error);
// If localStorage is full, try to clear some space
if (error instanceof Error && error.name === 'QuotaExceededError') {
console.warn('localStorage quota exceeded, clearing old cache entries');
clearNutritionCache();
}
}
}
/**
* Get detailed nutritional information for a food item using Claude AI
* Uses localStorage cache to avoid repeated API calls for the same food/amount
* @param foodName - The name of the food item
* @returns Promise with nutritional information
*/
export async function getNutritionInfo(foodName: string): Promise<NutritionInfo> {
// Check cache first
const cached = getCachedNutrition(foodName, 100);
if (cached) {
console.log(`Using cached nutrition info for: ${foodName} (100g)`);
return cached;
}
console.log(`Fetching nutrition info from AI for: ${foodName} (100g)`);
try {
const message = await anthropic.messages.create({
model: 'claude-haiku-4-5-20251001',
max_tokens: 1024,
messages: [
{
role: 'user',
content: `Provide detailed nutritional information for 100g of ${foodName}.
IMPORTANT: Respond in Brazilian Portuguese (pt-BR).
Focus on:
1. Key vitamins (e.g., A, B complex, C, D, E, K)
2. Important minerals (e.g., iron, calcium, magnesium, zinc, potassium)
3. Main health benefits
4. Any important notes or considerations
Format your response as JSON with this structure:
{
"vitamins": ["Vitamina A: descrição", "Vitamina C: descrição", ...],
"minerals": ["Ferro: descrição", "Cálcio: descrição", ...],
"benefits": ["benefício 1", "benefício 2", ...],
"notes": "Informações adicionais importantes"
}
Be concise but informative. Only include significant amounts of vitamins and minerals. All text must be in Brazilian Portuguese.`
}
]
});
// Extract the text content from Claude's response
const textContent = message.content.find(block => block.type === 'text');
if (!textContent || textContent.type !== 'text') {
throw new Error('No text content in response');
}
// Parse the JSON response
const responseText = textContent.text;
// Extract JSON from the response (handle cases where Claude adds markdown formatting)
const jsonMatch = responseText.match(/\{[\s\S]*\}/);
if (!jsonMatch) {
throw new Error('Could not parse JSON from response');
}
const nutritionInfo: NutritionInfo = JSON.parse(jsonMatch[0]);
// Cache the result for future use
cacheNutrition(foodName, 100, nutritionInfo);
return nutritionInfo;
} catch (error) {
console.error('Error getting nutrition info from Claude:', error);
throw new Error('Failed to get nutrition information. Please try again.');
}
}
/**
* Clear the nutrition info cache
* Removes all cached nutrition data from localStorage
*/
export function clearNutritionCache(): void {
try {
const keys = Object.keys(localStorage).filter(key => key.startsWith(CACHE_PREFIX));
keys.forEach(key => localStorage.removeItem(key));
console.log(`Cleared nutrition cache (${keys.length} entries removed)`);
} catch (error) {
console.error('Error clearing cache:', error);
}
}
/**
* Get cache statistics including size and storage usage
*/
export function getCacheStats(): { size: number; keys: string[]; sizeInBytes: number; sizeInKB: number } {
try {
const keys = Object.keys(localStorage).filter(key => key.startsWith(CACHE_PREFIX));
// Calculate total size in bytes
let totalBytes = 0;
keys.forEach(key => {
const value = localStorage.getItem(key);
if (value) {
// Each character in localStorage is stored as UTF-16, which is 2 bytes per character
totalBytes += (key.length + value.length) * 2;
}
});
return {
size: keys.length,
keys: keys.map(k => k.replace(CACHE_PREFIX, '')),
sizeInBytes: totalBytes,
sizeInKB: Math.round(totalBytes / 1024 * 100) / 100
};
} catch (error) {
console.error('Error getting cache stats:', error);
return { size: 0, keys: [], sizeInBytes: 0, sizeInKB: 0 };
}
}
+36
View File
@@ -0,0 +1,36 @@
export interface CurrentDateResult {
date: string; // "2026-02-13"
dateTime: Date; // new Date(year, month-1, day) — local-midnight Date
timeString: string; // "HH:MM"
}
export async function getCurrentDate(timezone: string): Promise<CurrentDateResult> {
if (timezone) {
try {
const response = await fetch(
`https://timeapi.io/api/v1/time/current/zone?timezone=${encodeURIComponent(timezone)}`
);
if (response.ok) {
const data = await response.json();
// data.date is "YYYY-MM-DD", data.time is "HH:MM:SS.mmm" or "HH:MM"
const [year, month, day] = data.date.split('-').map(Number);
const dateTime = new Date(year, month - 1, day);
const timeParts = data.time.split(':');
const timeString = `${timeParts[0].padStart(2, '0')}:${timeParts[1].padStart(2, '0')}`;
return { date: data.date, dateTime, timeString };
}
} catch (e) {
console.warn('timeapi.io request failed, falling back to browser time:', e);
}
}
// Fallback: browser local time
const now = new Date();
const year = now.getFullYear();
const month = now.getMonth() + 1;
const day = now.getDate();
const date = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
const dateTime = new Date(year, now.getMonth(), day);
const timeString = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
return { date, dateTime, timeString };
}
+122 -111
View File
@@ -2,127 +2,138 @@ import { FoodItem } from "./types";
export const foodDatabase: FoodItem[] = [ export const foodDatabase: FoodItem[] = [
// Gorduras - Fats // Gorduras - Fats
{ name: 'Abacate', info: { calories: 96, protein: 1.2, fat: 8.4, carbs: 6, fiber: 6.3, category: 'fats', alkaline: true } }, { name: 'Abacate', nameEn: 'Avocado', info: { calories: 96, protein: 1.2, fat: 8.4, carbs: 6, fiber: 6.3, category: 'fats', alkaline: true, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Amêndoas', info: { calories: 581, protein: 18.6, fat: 47.3, carbs: 29.5, fiber: 11.6, category: 'fats', alkaline: false } }, { name: 'Amêndoas', nameEn: 'Almonds', info: { calories: 581, protein: 18.6, fat: 47.3, carbs: 29.5, fiber: 11.6, category: 'fats', alkaline: false, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Amendoin natural', info: { calories: 611, protein: 26, fat: 49, carbs: 16, fiber: 8.5, category: 'fats', alkaline: true } }, { name: 'Amendoin natural', nameEn: 'Natural Peanut', info: { calories: 611, protein: 26, fat: 49, carbs: 16, fiber: 8.5, category: 'fats', alkaline: false, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Azeite de Oliva EV', info: { calories: 844, protein: 0, fat: 100, carbs: 0, fiber: 0, category: 'fats', alkaline: false } }, { name: 'Azeite de Oliva EV', nameEn: 'Extra Virgin Olive Oil', info: { calories: 844, protein: 0, fat: 100, carbs: 0, fiber: 0, category: 'fats', alkaline: false, bestFor: ['lunch', 'dinner'] } },
{ name: 'Castanha de Caju', info: { calories: 570, protein: 18.5, fat: 46.3, carbs: 29.1, fiber: 3.7, category: 'fats', alkaline: false } }, { name: 'Castanha de Caju', nameEn: 'Cashew Nut', info: { calories: 570, protein: 18.5, fat: 46.3, carbs: 29.1, fiber: 3.7, category: 'fats', alkaline: false, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Castanha do Pará', info: { calories: 643, protein: 14.5, fat: 63.5, carbs: 15.1, fiber: 7.9, category: 'fats', alkaline: false } }, { name: 'Castanha do Pará', nameEn: 'Brazil Nut', info: { calories: 643, protein: 14.5, fat: 63.5, carbs: 15.1, fiber: 7.9, category: 'fats', alkaline: false, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Semente de Chia', info: { calories: 490, protein: 15.6, fat: 30.7, carbs: 43.8, fiber: 37.7, category: 'fats', alkaline: true } }, { name: 'Semente de Chia', nameEn: 'Chia Seed', info: { calories: 490, protein: 15.6, fat: 30.7, carbs: 43.8, fiber: 37.7, category: 'fats', alkaline: true, bestFor: ['breakfast', 'brunch'] } },
{ name: 'Semente de Linhaça Dourada', info: { calories: 515, protein: 20, fat: 35, carbs: 30, fiber: 26, category: 'fats', alkaline: true } }, { name: 'Semente de Linhaça Dourada', nameEn: 'Golden Flaxseed', info: { calories: 515, protein: 20, fat: 35, carbs: 30, fiber: 26, category: 'fats', alkaline: true, bestFor: ['breakfast', 'brunch'] } },
{ name: 'Semente de Gergelim', info: { calories: 584, protein: 21.2, fat: 50.4, carbs: 21.6, fiber: 11.9, category: 'fats', alkaline: true } }, { name: 'Semente de Gergelim', nameEn: 'Sesame Seed', info: { calories: 584, protein: 21.2, fat: 50.4, carbs: 21.6, fiber: 11.9, category: 'fats', alkaline: true, bestFor: ['breakfast', 'brunch'] } },
{ name: 'Pasta de Amendoim Growth', info: { calories: 544, protein: 27, fat: 33, carbs: 20, fiber: 8, category: 'fats', alkaline: false } }, { name: 'Pasta de Amendoim DaColônia', nameEn: 'DaColônia Peanut Butter', info: { calories: 608, protein: 30, fat: 51, carbs: 7.3, fiber: 7.3, category: 'fats', alkaline: false, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Semente de Abóbora', info: { calories: 446, protein: 18.55, fat: 19.4, carbs: 53.75, fiber: 6, category: 'fats', alkaline: true } }, { name: 'Semente de Abóbora', nameEn: 'Pumpkin Seed', info: { calories: 446, protein: 18.55, fat: 19.4, carbs: 53.75, fiber: 6, category: 'fats', alkaline: true, bestFor: ['lunch', 'dinner'] } },
{ name: 'Semente de Girassol', nameEn: 'Sunflower Seed', info: { calories: 570, protein: 22.8, fat: 49.6, carbs: 18.7, fiber: 10.6, category: 'fats', alkaline: true, bestFor: ['lunch', 'dinner'] } },
{ name: 'Pasta de Castanha (A Tal da Castanha)', nameEn: 'Cashew Butter (A Tal da Castanha)', info: { calories: 564, protein: 17, fat: 40, carbs: 34, fiber: 4.7, category: 'fats', alkaline: false, bestFor: ['breakfast', 'brunch', 'snacks'] } },
// Proteínas - Proteins // Proteínas - Proteins
{ name: 'Atum, conserva em óleo', info: { calories: 166, protein: 26.2, fat: 6, carbs: 0, fiber: 0, category: 'proteins', alkaline: false } }, { name: 'Atum, conserva em óleo', nameEn: 'Tuna, canned in oil', info: { calories: 166, protein: 26.2, fat: 6, carbs: 0, fiber: 0, category: 'proteins', alkaline: false, bestFor: ['breakfast', 'brunch', 'snacks', 'lunch', 'dinner'] } },
{ name: 'Filé de Tilápia, cozido', info: { calories: 128, protein: 26.1, fat: 2.6, carbs: 0, fiber: 0, category: 'proteins', alkaline: false } }, { name: 'Filé de Tilápia, cozido', nameEn: 'Tilapia Fillet, cooked', info: { calories: 128, protein: 26.1, fat: 2.6, carbs: 0, fiber: 0, category: 'proteins', alkaline: false, bestFor: ['lunch', 'dinner'] } },
{ name: 'Ovo, de galinha, inteiro, cozido', info: { calories: 146, protein: 13.3, fat: 9.5, carbs: 0.6, fiber: 0, category: 'proteins', alkaline: false } }, { name: 'Ovo, de galinha, inteiro, cozido', nameEn: 'Egg, whole, boiled', info: { calories: 146, protein: 13.3, fat: 9.5, carbs: 0.6, fiber: 0, category: 'proteins', alkaline: false, bestFor: ['breakfast', 'brunch', 'snacks', 'lunch', 'dinner'] } },
{ name: 'Ovo, de galinha, gema, cozida', info: { calories: 353, protein: 15.9, fat: 30.8, carbs: 1.6, fiber: 0, category: 'proteins', alkaline: false } }, { name: 'Ovo, de galinha, gema, cozida', nameEn: 'Egg, yolk, boiled', info: { calories: 353, protein: 15.9, fat: 30.8, carbs: 1.6, fiber: 0, category: 'proteins', alkaline: false, bestFor: ['breakfast', 'brunch', 'snacks', 'lunch', 'dinner'] } },
{ name: 'Ovo, de galinha, clara, cozida', info: { calories: 59, protein: 13.4, fat: 0.1, carbs: 0, fiber: 0, category: 'proteins', alkaline: false } }, { name: 'Ovo, de galinha, clara, cozida', nameEn: 'Egg, white, boiled', info: { calories: 59, protein: 13.4, fat: 0.1, carbs: 0, fiber: 0, category: 'proteins', alkaline: false, bestFor: ['breakfast', 'brunch', 'snacks', 'lunch', 'dinner'] } },
{ name: 'PTS, crua', info: { calories: 288, protein: 52, fat: 0, carbs: 20, fiber: 14, category: 'proteins', alkaline: false } }, { name: 'PTS, crua', nameEn: 'Textured Vegetable Protein, raw', info: { calories: 288, protein: 52, fat: 0, carbs: 20, fiber: 14, category: 'proteins', alkaline: false, bestFor: ['lunch', 'dinner'] } },
{ name: 'Carne, bovina, patinho, sem gordura, grelhado', info: { calories: 219, protein: 35.9, fat: 7.3, carbs: 0, fiber: 0, category: 'proteins', alkaline: false } }, { name: 'Carne, bovina, patinho, sem gordura, grelhado', nameEn: 'Beef, round, lean, grilled', info: { calories: 219, protein: 35.9, fat: 7.3, carbs: 0, fiber: 0, category: 'proteins', alkaline: false, bestFor: ['lunch', 'dinner'] } },
{ name: 'Soja em Pó Growth', info: { calories: 400, protein: 86.7, fat: 5, carbs: 3.3, fiber: 0, category: 'proteins', alkaline: false } }, { name: 'Soja em Pó Growth', nameEn: 'Growth Soy Powder', info: { calories: 400, protein: 86.7, fat: 5, carbs: 3.3, fiber: 0, category: 'proteins', alkaline: false, bestFor: ['breakfast', 'brunch'] } },
{ name: 'Sobrecoxa s/pele, cozida', info: { calories: 245, protein: 24.9, fat: 15.4, carbs: 0, fiber: 0, category: 'proteins', alkaline: false } }, { name: 'Sobrecoxa s/pele, cozida', nameEn: 'Chicken Thigh, skinless, cooked', info: { calories: 245, protein: 24.9, fat: 15.4, carbs: 0, fiber: 0, category: 'proteins', alkaline: false, bestFor: ['lunch', 'dinner'] } },
{ name: 'Soja, queijo (tofu)', info: { calories: 64, protein: 6.5, fat: 2, carbs: 4, fiber: 0.8, category: 'proteins', alkaline: false } }, { name: 'Soja, queijo (tofu)', nameEn: 'Soy, cheese (tofu)', info: { calories: 64, protein: 6.5, fat: 2, carbs: 4, fiber: 0.8, category: 'proteins', alkaline: false, bestFor: ['breakfast', 'brunch', 'snacks', 'lunch', 'dinner'] } },
{ name: 'Feijão preto, cozido', info: { calories: 77, protein: 4.5, fat: 0.5, carbs: 14, fiber: 8.4, category: 'proteins', alkaline: false } }, { name: 'Feijão preto, cozido', nameEn: 'Black Beans, cooked', info: { calories: 77, protein: 4.5, fat: 0.5, carbs: 14, fiber: 8.4, category: 'proteins', alkaline: false, bestFor: ['lunch', 'dinner'] } },
{ name: 'Lentilha, cozida', info: { calories: 116, protein: 9.4, fat: 0.2, carbs: 19.4, fiber: 5.5, category: 'proteins', alkaline: false } }, { name: 'Lentilha, cozida', nameEn: 'Lentils, cooked', info: { calories: 116, protein: 9.4, fat: 0.2, carbs: 19.4, fiber: 5.5, category: 'proteins', alkaline: false, bestFor: ['lunch', 'dinner'] } },
{ name: 'Frango, peito, sem pele, cozido', info: { calories: 163, protein: 31.5, fat: 3.2, carbs: 0, fiber: 0, category: 'proteins', alkaline: false } }, { name: 'Frango, peito, sem pele, cozido', nameEn: 'Chicken Breast, skinless, cooked', info: { calories: 163, protein: 31.5, fat: 3.2, carbs: 0, fiber: 0, category: 'proteins', alkaline: false, bestFor: ['lunch', 'dinner'] } },
{ name: 'Frango, peito, sem pele, grelhado', info: { calories: 159, protein: 32, fat: 2.5, carbs: 0, fiber: 0, category: 'proteins', alkaline: false } }, { name: 'Frango, peito, sem pele, grelhado', nameEn: 'Chicken Breast, skinless, grilled', info: { calories: 159, protein: 32, fat: 2.5, carbs: 0, fiber: 0, category: 'proteins', alkaline: false, bestFor: ['lunch', 'dinner'] } },
{ name: 'Carne, bovina, acém, moído, cozido', info: { calories: 212, protein: 26.7, fat: 10.9, carbs: 0, fiber: 0, category: 'proteins', alkaline: false } }, { name: 'Carne, bovina, acém, moído, cozido', nameEn: 'Beef, chuck, ground, cooked', info: { calories: 212, protein: 26.7, fat: 10.9, carbs: 0, fiber: 0, category: 'proteins', alkaline: false, bestFor: ['lunch', 'dinner'] } },
{ name: 'Carne, bovina, contra-filé, sem gordura, grelhado', info: { calories: 194, protein: 35.9, fat: 4.5, carbs: 0, fiber: 0, category: 'proteins', alkaline: false } }, { name: 'Carne, bovina, contra-filé, sem gordura, grelhado', nameEn: 'Beef, sirloin, lean, grilled', info: { calories: 194, protein: 35.9, fat: 4.5, carbs: 0, fiber: 0, category: 'proteins', alkaline: false, bestFor: ['lunch', 'dinner'] } },
{ name: 'Carne, bovina, costela, assada', info: { calories: 373, protein: 28.8, fat: 27.7, carbs: 0, fiber: 0, category: 'proteins', alkaline: false } }, { name: 'Carne, bovina, costela, assada', nameEn: 'Beef, rib, roasted', info: { calories: 373, protein: 28.8, fat: 27.7, carbs: 0, fiber: 0, category: 'proteins', alkaline: false, bestFor: ['lunch', 'dinner'] } },
{ name: 'Carne, bovina, cupim, assado', info: { calories: 330, protein: 28.6, fat: 23, carbs: 0, fiber: 0, category: 'proteins', alkaline: false } }, { name: 'Carne, bovina, cupim, assado', nameEn: 'Beef, hump, roasted', info: { calories: 330, protein: 28.6, fat: 23, carbs: 0, fiber: 0, category: 'proteins', alkaline: false, bestFor: ['lunch', 'dinner'] } },
{ name: 'Carne, bovina, fígado, grelhado', info: { calories: 225, protein: 29.9, fat: 9, carbs: 0, fiber: 0, category: 'proteins', alkaline: false } }, { name: 'Carne, bovina, fígado, grelhado', nameEn: 'Beef, liver, grilled', info: { calories: 225, protein: 29.9, fat: 9, carbs: 0, fiber: 0, category: 'proteins', alkaline: false, bestFor: ['lunch', 'dinner'] } },
{ name: 'Carne, bovina, picanha, sem gordura, grelhada', info: { calories: 238, protein: 31.9, fat: 11.3, carbs: 0, fiber: 0, category: 'proteins', alkaline: false } }, { name: 'Carne, bovina, picanha, sem gordura, grelhada', nameEn: 'Beef, rump cap, lean, grilled', info: { calories: 238, protein: 31.9, fat: 11.3, carbs: 0, fiber: 0, category: 'proteins', alkaline: false, bestFor: ['lunch', 'dinner'] } },
{ name: 'Apresuntado', info: { calories: 129, protein: 13.5, fat: 6.7, carbs: 2.9, fiber: 0, category: 'proteins', alkaline: false } }, { name: 'Apresuntado', nameEn: 'Cooked Ham', info: { calories: 129, protein: 13.5, fat: 6.7, carbs: 2.9, fiber: 0, category: 'proteins', alkaline: false, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Presunto, com capa de gordura', info: { calories: 128, protein: 14.4, fat: 6.8, carbs: 1.4, fiber: 0, category: 'proteins', alkaline: false } }, { name: 'Presunto, com capa de gordura', nameEn: 'Ham, with fat layer', info: { calories: 128, protein: 14.4, fat: 6.8, carbs: 1.4, fiber: 0, category: 'proteins', alkaline: false, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Presunto, sem capa de gordura', info: { calories: 94, protein: 14.3, fat: 2.7, carbs: 2.1, fiber: 0, category: 'proteins', alkaline: false } }, { name: 'Presunto, sem capa de gordura', nameEn: 'Ham, without fat layer', info: { calories: 94, protein: 14.3, fat: 2.7, carbs: 2.1, fiber: 0, category: 'proteins', alkaline: false, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Proteína da Ervilha em pó, Growth', nameEn: 'Pea Protein Powder, Growth', info: { calories: 437, protein: 83.3, fat: 9.7, carbs: 2.3, fiber: 2.7, category: 'proteins', alkaline: false, bestFor: ['breakfast', 'brunch'] } },
// Carboidratos, amiláceos - Carbs // Carboidratos, amiláceos - Carbs
{ name: 'Grão de Bico, cozido', info: { calories: 180, protein: 9.5, fat: 3, carbs: 30, fiber: 8.6, category: 'carbs', alkaline: false } }, { name: 'Grão de Bico, cozido', nameEn: 'Chickpeas, cooked', info: { calories: 180, protein: 9.5, fat: 3, carbs: 30, fiber: 8.6, category: 'carbs', alkaline: false, bestFor: ['lunch', 'dinner'] } },
{ name: 'Banana Nanica', info: { calories: 92, protein: 1.4, fat: 0.1, carbs: 23.8, fiber: 1.9, category: 'carbs', alkaline: true } }, { name: 'Batata doce, assada', nameEn: 'Sweet Potato, baked', info: { calories: 90, protein: 2.0, fat: 0.1, carbs: 20.7, fiber: 3.3, category: 'carbs', alkaline: true, bestFor: ['lunch', 'dinner'] } },
{ name: 'Manga Palmer', info: { calories: 72, protein: 0.4, fat: 0.2, carbs: 19.4, fiber: 1.6, category: 'carbs', alkaline: true } }, { name: 'Batata doce, cozida', nameEn: 'Sweet Potato, boiled', info: { calories: 77, protein: 0.6, fat: 0.1, carbs: 18.4, fiber: 2.2, category: 'carbs', alkaline: true, bestFor: ['lunch', 'dinner'] } },
{ name: 'Mamão', info: { calories: 40, protein: 0.5, fat: 0.1, carbs: 10.4, fiber: 1, category: 'carbs', alkaline: true } }, { name: 'Batata inglesa, assada', nameEn: 'Potato, baked', info: { calories: 94, protein: 2.1, fat: 0.1, carbs: 21.8, fiber: 2.1, category: 'carbs', alkaline: true, bestFor: ['lunch', 'dinner'] } },
{ name: 'Batata doce, assada', info: { calories: 90, protein: 2.0, fat: 0.1, carbs: 20.7, fiber: 3.3, category: 'carbs', alkaline: true } }, { name: 'Batata inglesa, cozida', nameEn: 'Potato, boiled', info: { calories: 52, protein: 1.2, fat: 0, carbs: 11.9, fiber: 1.3, category: 'carbs', alkaline: true, bestFor: ['lunch', 'dinner'] } },
{ name: 'Batata doce, cozida', info: { calories: 77, protein: 0.6, fat: 0.1, carbs: 18.4, fiber: 2.2, category: 'carbs', alkaline: true } }, { name: 'Mandioca, cozida', nameEn: 'Cassava, boiled', info: { calories: 125, protein: 0.6, fat: 0.3, carbs: 30.1, fiber: 1.6, category: 'carbs', alkaline: false, bestFor: ['lunch', 'dinner'] } },
{ name: 'Batata inglesa, assada', info: { calories: 94, protein: 2.1, fat: 0.1, carbs: 21.8, fiber: 2.1, category: 'carbs', alkaline: true } }, { name: 'Pão, trigo, francês', nameEn: 'Bread, wheat, French', info: { calories: 300, protein: 8, fat: 3.1, carbs: 58.6, fiber: 2.3, category: 'carbs', alkaline: false, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Batata inglesa, cozida', info: { calories: 52, protein: 1.2, fat: 0, carbs: 11.9, fiber: 1.3, category: 'carbs', alkaline: true } }, { name: 'Polenta, cozida', nameEn: 'Polenta, cooked', info: { calories: 192, protein: 4.8, fat: 1.2, carbs: 40, fiber: 3.8, category: 'carbs', alkaline: false, bestFor: ['lunch', 'dinner'] } },
{ name: 'Mandioca, cozida', info: { calories: 125, protein: 0.6, fat: 0.3, carbs: 30.1, fiber: 1.6, category: 'carbs', alkaline: false } }, { name: 'Arroz, tipo 1 (branco), cozido', nameEn: 'Rice, type 1 (white), cooked', info: { calories: 128, protein: 2.5, fat: 0.2, carbs: 28.1, fiber: 1.6, category: 'carbs', alkaline: false, bestFor: ['lunch', 'dinner'] } },
{ name: 'Pão, trigo, francês', info: { calories: 300, protein: 8, fat: 3.1, carbs: 58.6, fiber: 2.3, category: 'carbs', alkaline: false } }, { name: 'Arroz, integral, cozido', nameEn: 'Brown Rice, cooked', info: { calories: 124, protein: 2.6, fat: 1.0, carbs: 25.8, fiber: 2.7, category: 'carbs', alkaline: false, bestFor: ['lunch', 'dinner'] } },
{ name: 'Polenta, cozida', info: { calories: 192, protein: 4.8, fat: 1.2, carbs: 40, fiber: 3.8, category: 'carbs', alkaline: false } }, { name: 'Aveia em flocos finos s/glúten (Vitalin)', nameEn: 'Gluten-free fine oat flakes (Vitalin)', info: { calories: 338, protein: 15, fat: 6, carbs: 56, fiber: 10, category: 'carbs', alkaline: false, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Arroz, tipo 1 (branco), cozido', info: { calories: 128, protein: 2.5, fat: 0.2, carbs: 28.1, fiber: 1.6, category: 'carbs', alkaline: false } }, { name: 'Ervilha seca, cozida', nameEn: 'Dried peas, cooked', info: { calories: 88, protein: 7.5, fat: 0.5, carbs: 14.2, fiber: 9.7, category: 'proteins', alkaline: true, bestFor: ['lunch', 'dinner'] } },
{ name: 'Arroz, integral, cozido', info: { calories: 124, protein: 2.6, fat: 1.0, carbs: 25.8, fiber: 2.7, category: 'carbs', alkaline: false } }, { name: 'Ervilha congelada, cozida', nameEn: 'Frozen peas, cooked', info: { calories: 57, protein: 5, fat: 1, carbs: 7, fiber: 8, category: 'proteins', alkaline: true, bestFor: ['lunch', 'dinner'] } },
{ name: 'Aveia em flocos finos s/glúten (Vitalin)', info: { calories: 338, protein: 15, fat: 6, carbs: 56, fiber: 10, category: 'carbs', alkaline: false } }, { name: 'Pão, trigo, forma, integral', nameEn: 'Bread, wheat, sliced, whole', info: { calories: 253, protein: 9.4, fat: 3.7, carbs: 49.9, fiber: 6.9, category: 'carbs', alkaline: false, bestFor: ['breakfast', 'brunch'] } },
{ name: 'Ervilha seca, cozida', info: { calories: 88, protein: 7.5, fat: 0.5, carbs: 14.2, fiber: 9.7, category: 'carbs', alkaline: true } }, { name: 'Pipoca, com óleo de soja, sem sal', nameEn: 'Popcorn, with soybean oil, unsalted', info: { calories: 448, protein: 9.9, fat: 15.9, carbs: 70.3, fiber: 14.3, category: 'carbs', alkaline: false, bestFor: ['brunch', 'snacks'] } },
{ name: 'Ervilha congelada, cozida', info: { calories: 57, protein: 5, fat: 1, carbs: 7, fiber: 8, category: 'carbs', alkaline: true } }, { name: 'Pipoca, sem óleo, sem sal', nameEn: 'Popcorn, without oil, unsalted', info: { calories: 178, protein: 3.3, fat: 7.2, carbs: 25, fiber: 4.3, category: 'carbs', alkaline: false, bestFor: ['brunch', 'snacks'] } },
{ name: 'Pão, trigo, forma, integral', info: { calories: 253, protein: 9.4, fat: 3.7, carbs: 49.9, fiber: 6.9, category: 'carbs', alkaline: false } }, { name: 'Quinoa, cozida', nameEn: 'Quinoa, cooked', info: { calories: 158, protein: 5.55, fat: 2.5, carbs: 29, fiber: 2.5, category: 'carbs', alkaline: true, bestFor: ['lunch', 'dinner'] } },
{ name: 'Pipoca, com óleo de soja, sem sal', info: { calories: 448, protein: 9.9, fat: 15.9, carbs: 70.3, fiber: 14.3, category: 'carbs', alkaline: false } }, { name: '70% Nibs Garoto', nameEn: '70% Garoto Nibs', info: { calories: 544, protein: 8.4, fat: 40, carbs: 34, fiber: 0, category: 'carbs', alkaline: false, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Pipoca, sem óleo, sem sal', info: { calories: 178, protein: 3.3, fat: 7.2, carbs: 25, fiber: 4.3, category: 'carbs', alkaline: false } }, { name: 'Barra de Proteína Vegana Growth', nameEn: 'Growth Vegan Protein Bar', info: { calories: 400, protein: 25, fat: 22, carbs: 25, fiber: 8, category: 'carbs', alkaline: false, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Quinoa, cozida', info: { calories: 158, protein: 5.55, fat: 2.5, carbs: 29, fiber: 2.5, category: 'carbs', alkaline: true } }, { name: 'Goiabada cascão', nameEn: 'Goiabada cascão', info: { calories: 286, protein: 0.4, fat: 0.1, carbs: 78.7, fiber: 4.4, category: 'carbs', alkaline: false, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: '70% Nibs Garoto', info: { calories: 544, protein: 8.4, fat: 40, carbs: 34, fiber: 0, category: 'carbs', alkaline: false } }, { name: 'Goma de Tapioca', nameEn: 'Tapioca Starch', info: { calories: 226, protein: 0, fat: 0, carbs: 57, fiber: 0, category: 'carbs', alkaline: false, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Barra de Proteína Vegana Growth', info: { calories: 400, protein: 25, fat: 22, carbs: 25, fiber: 8, category: 'carbs', alkaline: false } }, { name: 'Mel Silvestre', nameEn: 'Wild Honey', info: { calories: 230, protein: 0, fat: 0, carbs: 65, fiber: 0, category: 'carbs', alkaline: false, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Goiabada cascão', info: { calories: 286, protein: 0.4, fat: 0.1, carbs: 78.7, fiber: 4.4, category: 'carbs', alkaline: false } }, { name: 'Melado', nameEn: 'Molasses', info: { calories: 297, protein: 0, fat: 0, carbs: 76.6, fiber: 0, category: 'carbs', alkaline: false, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Goma de Tapioca', info: { calories: 226, protein: 0, fat: 0, carbs: 57, fiber: 0, category: 'carbs', alkaline: false } }, { name: 'Tâmara desidratada', nameEn: 'Dried Date', info: { calories: 230, protein: 0.3, fat: 33, carbs: 63.3, fiber: 7, category: 'carbs', alkaline: false, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Mel Silvestre', info: { calories: 230, protein: 0, fat: 0, carbs: 65, fiber: 0, category: 'carbs', alkaline: false } }, { name: 'Pão, de queijo, assado', nameEn: 'Baked Cheese Bread', info: { calories: 363, protein: 5.1, fat: 24.6, carbs: 34.2, fiber: 0.6, category: 'carbs', alkaline: false, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Melado', info: { calories: 297, protein: 0, fat: 0, carbs: 76.6, fiber: 0, category: 'carbs', alkaline: false } }, { name: 'Cuscuz, de milho , cozido com sal', nameEn: 'Corn Couscous, cooked with salt', info: { calories: 113, protein: 2.2, fat: 0.7, carbs: 25.3, fiber: 2.1, category: 'carbs', alkaline: false, bestFor: ['breakfast', 'brunch'] } },
{ name: 'Tâmara desidratada', info: { calories: 230, protein: 0.3, fat: 33, carbs: 63.3, fiber: 7, category: 'carbs', alkaline: false } }, { name: 'Paçoca, amendoim', nameEn: 'Peanut Paçoca', info: { calories: 487, protein: 16, fat: 26.1, carbs: 52.4, fiber: 7.3, category: 'carbs', alkaline: false, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Pão, de queijo, assado', info: { calories: 363, protein: 5.1, fat: 24.6, carbs: 34.2, fiber: 0.6, category: 'carbs', alkaline: false } }, { name: 'Tortelete de mousse chocolate zero Quiero Café', nameEn: 'Zero Chocolate Mousse Tartlet Quiero Café', info: { calories: 250, protein: 2.3, fat: 8.9, carbs: 40, fiber: 5, category: 'carbs', alkaline: false, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Cuscuz, de milho , cozido com sal', info: { calories: 113, protein: 2.2, fat: 0.7, carbs: 25.3, fiber: 2.1, category: 'carbs', alkaline: false } }, { name: 'Pão de beijo com chia e pequi Empório Veggie', nameEn: 'Kiss Bread with Chia and Pequi Empório Veggie', info: { calories: 316, protein: 0.6, fat: 14, carbs: 47, fiber: 1, category: 'carbs', alkaline: false, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Paçoca, amendoim', info: { calories: 487, protein: 16, fat: 26.1, carbs: 52.4, fiber: 7.3, category: 'carbs', alkaline: false } }, { name: 'Bananinha Paraibuna zero', nameEn: 'Zero Paraibuna Banana', info: { calories: 296, protein: 4.8, fat: 0, carbs: 70, fiber: 4.3, category: 'carbs', alkaline: true, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Tortelete de mousse chocolate zero Quiero Café', info: { calories: 250, protein: 2.3, fat: 8.9, carbs: 40, fiber: 5, category: 'carbs', alkaline: false } }, { name: 'Farinha de castanha de caju (Zaya)', nameEn: 'Cashew Nut Flour (Zaya)', info: { calories: 500, protein: 20, fat: 53.3, carbs: 20, fiber: 13.3, category: 'carbs', alkaline: false, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Pão de beijo com chia e pequi Empório Veggie', info: { calories: 316, protein: 0.6, fat: 14, carbs: 47, fiber: 1, category: 'carbs', alkaline: false } }, { name: 'Batata baroa, cozida', nameEn: 'Cooked Baroa Potato', info: { calories: 80, protein: 0.9, fat: 0.2, carbs: 18.9, fiber: 1.8, category: 'carbs', alkaline: true, bestFor: ['lunch', 'dinner'] } },
{ name: 'Bananinha Paraibuna zero', info: { calories: 296, protein: 4.8, fat: 0, carbs: 70, fiber: 4.3, category: 'carbs', alkaline: true } }, { name: 'Paçoquita original Santa Helena', nameEn: 'Original peanut candy Santa Helena', info: { calories: 532, protein: 18, fat: 28, carbs: 50, fiber: 4, category: 'carbs', alkaline: false, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Farinha de castanha de caju (Zaya)', info: { calories: 500, protein: 20, fat: 53.3, carbs: 20, fiber: 13.3, category: 'carbs', alkaline: false } }, { name: 'Barra de castanhas, amendoim e frutas Nuts', nameEn: 'Nuts, peanuts and fruits bar', info: { calories: 470, protein: 16, fat: 30, carbs: 34, fiber: 12, category: 'carbs', alkaline: false, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Batata baroa, cozida', info: { calories: 80, protein: 0.9, fat: 0.2, carbs: 18.9, fiber: 1.8, category: 'carbs', alkaline: true } }, { name: 'Barra de caramelo e chocolate Bold', nameEn: 'Caramelo and chocolate bar Bold', info: { calories: 348, protein: 30, fat: 16, carbs: 28, fiber: 10, category: 'carbs', alkaline: false, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Brownie proteico double chocolate Belive', nameEn: 'Protein Brownie double chocolate Belive', info: { calories: 305, protein: 15, fat: 18, carbs: 33, fiber: 13, category: 'carbs', alkaline: false, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Proto bar Paçoca Amendo Whey, Nutrata', nameEn: 'Protein bar peanuts Whey, Nutrata', info: { calories: 467, protein: 31.4, fat: 22.8, carbs: 31.4, fiber: 5, category: 'carbs', alkaline: false, bestFor: ['breakfast', 'brunch', 'snacks'] } },
// Folhas e verduras - Leaves // Folhas e verduras - Leaves
{ name: 'Agrião', info: { calories: 17, protein: 2.7, fat: 0.2, carbs: 2.3, fiber: 2.1, category: 'leaves', alkaline: true } }, { name: 'Agrião', nameEn: 'Watercress', info: { calories: 17, protein: 2.7, fat: 0.2, carbs: 2.3, fiber: 2.1, category: 'leaves', alkaline: true, bestFor: ['lunch', 'dinner'] } },
{ name: 'Espinafre', info: { calories: 16, protein: 2, fat: 0.2, carbs: 2.6, fiber: 2.1, category: 'leaves', alkaline: true } }, { name: 'Espinafre', nameEn: 'Spinach', info: { calories: 16, protein: 2, fat: 0.2, carbs: 2.6, fiber: 2.1, category: 'leaves', alkaline: true, bestFor: ['lunch', 'dinner'] } },
{ name: 'Rúcula', info: { calories: 13, protein: 1.8, fat: 0.1, carbs: 2.2, fiber: 1.7, category: 'leaves', alkaline: true } }, { name: 'Rúcula', nameEn: 'Arugula', info: { calories: 13, protein: 1.8, fat: 0.1, carbs: 2.2, fiber: 1.7, category: 'leaves', alkaline: true, bestFor: ['lunch', 'dinner'] } },
{ name: 'Spirulina', info: { calories: 290, protein: 57.5, fat: 23.9, carbs: 7.7, fiber: 0, category: 'leaves', alkaline: true } }, { name: 'Spirulina', nameEn: 'Spirulina', info: { calories: 290, protein: 57.5, fat: 23.9, carbs: 7.7, fiber: 0, category: 'leaves', alkaline: true, bestFor: ['breakfast', 'brunch', 'snacks'] } },
// Frutas - Fruits // Frutas - Fruits
{ name: 'Ameixa, crua', info: { calories: 53, protein: 0.8, fat: 0, carbs: 13.9, fiber: 2.4, category: 'fruits', alkaline: true } }, { name: 'Ameixa, crua', nameEn: 'Raw Plum', info: { calories: 53, protein: 0.8, fat: 0, carbs: 13.9, fiber: 2.4, category: 'fruits', alkaline: true, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Abacaxi', info: { calories: 48, protein: 0.9, fat: 0.1, carbs: 12.3, fiber: 1, category: 'fruits', alkaline: true } }, { name: 'Abacaxi', nameEn: 'Pineapple', info: { calories: 48, protein: 0.9, fat: 0.1, carbs: 12.3, fiber: 1, category: 'fruits', alkaline: true, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Laranja', info: { calories: 37, protein: 1, fat: 0.1, carbs: 8.9, fiber: 0.8, category: 'fruits', alkaline: false } }, { name: 'Banana Nanica', nameEn: 'Banana Nanica', info: { calories: 92, protein: 1.4, fat: 0.1, carbs: 23.8, fiber: 1.9, category: 'fruits', alkaline: true, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Maçã', info: { calories: 56, protein: 0.3, fat: 0, carbs: 15.2, fiber: 1.3, category: 'fruits', alkaline: true } }, { name: 'Laranja', nameEn: 'Orange', info: { calories: 37, protein: 1, fat: 0.1, carbs: 8.9, fiber: 0.8, category: 'fruits', alkaline: false, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Melancia', info: { calories: 33, protein: 0.9, fat: 0, carbs: 8.1, fiber: 0.1, category: 'fruits', alkaline: true } }, { name: 'Manga Palmer', nameEn: 'Mango Palmer', info: { calories: 72, protein: 0.4, fat: 0.2, carbs: 19.4, fiber: 1.6, category: 'fruits', alkaline: true, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Melão', info: { calories: 29, protein: 0.7, fat: 0, carbs: 7.5, fiber: 0.3, category: 'fruits', alkaline: true } }, { name: 'Mamão', nameEn: 'Papaya', info: { calories: 40, protein: 0.5, fat: 0.1, carbs: 10.4, fiber: 1, category: 'fruits', alkaline: true, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Uva', info: { calories: 53, protein: 0.7, fat: 0.2, carbs: 13.6, fiber: 0.9, category: 'fruits', alkaline: true } }, { name: 'Maçã', nameEn: 'Apple', info: { calories: 56, protein: 0.3, fat: 0, carbs: 15.2, fiber: 1.3, category: 'fruits', alkaline: true, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Goiaba, branca, com casca, crua', info: { calories: 52, protein: 0.9, fat: 0.5, carbs: 12.4, fiber: 6.3, category: 'fruits', alkaline: true } }, { name: 'Melancia', nameEn: 'Watermelon', info: { calories: 33, protein: 0.9, fat: 0, carbs: 8.1, fiber: 0.1, category: 'fruits', alkaline: true, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Morango, cru', info: { calories: 30, protein: 0.9, fat: 0.3, carbs: 6.8, fiber: 1.7, category: 'fruits', alkaline: true } }, { name: 'Melão', nameEn: 'Melon', info: { calories: 29, protein: 0.7, fat: 0, carbs: 7.5, fiber: 0.3, category: 'fruits', alkaline: true, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Uva', nameEn: 'Grape', info: { calories: 53, protein: 0.7, fat: 0.2, carbs: 13.6, fiber: 0.9, category: 'fruits', alkaline: true, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Goiaba, branca, com casca, crua', nameEn: 'White Guava, with skin, raw', info: { calories: 52, protein: 0.9, fat: 0.5, carbs: 12.4, fiber: 6.3, category: 'fruits', alkaline: true, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Morango, cru', nameEn: 'Strawberry, raw', info: { calories: 30, protein: 0.9, fat: 0.3, carbs: 6.8, fiber: 1.7, category: 'fruits', alkaline: true, bestFor: ['breakfast', 'brunch', 'snacks'] } },
// Dairy // Dairy
{ name: 'Iogurte, natural, desnatado', info: { calories: 41, protein: 3.8, fat: 0.3, carbs: 5.8, fiber: 0, category: 'dairy', alkaline: true } }, { name: 'Iogurte, natural, desnatado', nameEn: 'Natural Yogurt, skimmed', info: { calories: 41, protein: 3.8, fat: 0.3, carbs: 5.8, fiber: 0, category: 'dairy', alkaline: true, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Queijo, mozarela, mussarela, muçarela', info: { calories: 330, protein: 22.6, fat: 25.2, carbs: 3, fiber: 0, category: 'dairy', alkaline: true } }, { name: 'Queijo, mozarela, mussarela, muçarela', nameEn: 'Mozzarella Cheese', info: { calories: 330, protein: 22.6, fat: 25.2, carbs: 3, fiber: 0, category: 'dairy', alkaline: true, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Queijo, parmesão', info: { calories: 453, protein: 35.6, fat: 33.5, carbs: 1.7, fiber: 0, category: 'dairy', alkaline: true } }, { name: 'Queijo, parmesão', nameEn: 'Parmesan Cheese', info: { calories: 453, protein: 35.6, fat: 33.5, carbs: 1.7, fiber: 0, category: 'dairy', alkaline: true, bestFor: ['lunch', 'dinner'] } },
{ name: 'Queijo, prato', info: { calories: 360, protein: 22.7, fat: 29.1, carbs: 1.9, fiber: 0, category: 'dairy', alkaline: true } }, { name: 'Queijo, prato', nameEn: 'Prato Cheese', info: { calories: 360, protein: 22.7, fat: 29.1, carbs: 1.9, fiber: 0, category: 'dairy', alkaline: true, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Queijo, requeijão, cremoso', info: { calories: 257, protein: 9.6, fat: 23.4, carbs: 2.4, fiber: 0, category: 'dairy', alkaline: true } }, { name: 'Queijo, requeijão, cremoso', nameEn: 'Creamy Requeijão Cheese', info: { calories: 257, protein: 9.6, fat: 23.4, carbs: 2.4, fiber: 0, category: 'dairy', alkaline: true, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Requeijão light (Tirol)', info: { calories: 177, protein: 11, fat: 13.3, carbs: 3.3, fiber: 0, category: 'dairy', alkaline: true } }, { name: 'Requeijão light (Tirol)', nameEn: 'Light Requeijão (Tirol)', info: { calories: 177, protein: 11, fat: 13.3, carbs: 3.3, fiber: 0, category: 'dairy', alkaline: true, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Queijo, ricota', info: { calories: 140, protein: 12.6, fat: 8.1, carbs: 3.8, fiber: 0, category: 'dairy', alkaline: true } }, { name: 'Queijo, ricota', nameEn: 'Ricotta Cheese', info: { calories: 140, protein: 12.6, fat: 8.1, carbs: 3.8, fiber: 0, category: 'dairy', alkaline: true, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Creme de leite', info: { calories: 221, protein: 1.5, fat: 22.5, carbs: 4.6, fiber: 0, category: 'dairy', alkaline: true } }, { name: 'Creme de leite', nameEn: 'Cream', info: { calories: 221, protein: 1.5, fat: 22.5, carbs: 4.6, fiber: 0, category: 'dairy', alkaline: true, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Leite, condensado', info: { calories: 313, protein: 7.7, fat: 6.7, carbs: 57, fiber: 0, category: 'dairy', alkaline: true } }, { name: 'Leite, condensado', nameEn: 'Condensed Milk', info: { calories: 313, protein: 7.7, fat: 6.7, carbs: 57, fiber: 0, category: 'dairy', alkaline: true, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Leite, de vaca, desnatado, pó', info: { calories: 362, protein: 34.7, fat: 0.9, carbs: 53, fiber: 0, category: 'dairy', alkaline: true } }, { name: 'Leite, de vaca, desnatado, pó', nameEn: 'Skimmed Cow Milk, Powdered', info: { calories: 362, protein: 34.7, fat: 0.9, carbs: 53, fiber: 0, category: 'dairy', alkaline: true, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Leite, de vaca, desnatado, UTH (Tirol)', info: { calories: 30.5, protein: 3.15, fat: 0, carbs: 4.5, fiber: 0, category: 'dairy', alkaline: true } }, { name: 'Leite, de vaca, desnatado, UTH (Tirol)', nameEn: 'Skimmed Cow Milk, UHT (Tirol)', info: { calories: 30.5, protein: 3.15, fat: 0, carbs: 4.5, fiber: 0, category: 'dairy', alkaline: true, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Leite, de vaca, integral, UTH (Tirol)', info: { calories: 57.5, protein: 3.15, fat: 3, carbs: 4.5, fiber: 0, category: 'dairy', alkaline: true } }, { name: 'Leite, de vaca, integral, UTH (Tirol)', nameEn: 'Whole Cow Milk, UHT (Tirol)', info: { calories: 57.5, protein: 3.15, fat: 3, carbs: 4.5, fiber: 0, category: 'dairy', alkaline: true, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Leite, de vaca, integral, pó', info: { calories: 496, protein: 26, fat: 26.4, carbs: 38.4, fiber: 0, category: 'dairy', alkaline: true } }, { name: 'Leite, de vaca, integral, pó', nameEn: 'Whole Cow Milk, Powdered', info: { calories: 496, protein: 26, fat: 26.4, carbs: 38.4, fiber: 0, category: 'dairy', alkaline: true, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Leite Caju+côco A Tal da Castanha', nameEn: 'Cashew+Coconut Milk A Tal da Castanha', info: { calories: 51, protein: 2, fat: 3.4, carbs: 2.6, fiber: 0, category: 'dairy', alkaline: false, bestFor: ['breakfast', 'brunch', 'snacks'] } },
{ name: 'Leite Caju Original A Tal da Castanha', nameEn: 'Original Cashew Milk A Tal da Castanha', info: { calories: 48, protein: 1.5, fat: 4, carbs: 1.5, fiber: 0.7, category: 'dairy', alkaline: false, bestFor: ['breakfast', 'brunch', 'snacks'] } },
// Low carb // Low carb
{ name: 'Abóbrinha, italinaa, cozida', info: { calories: 15, protein: 1.1, fat: 0.2, carbs: 3.0, fiber: 1.6, category: 'low carb', alkaline: true } }, { name: 'Abóbrinha, italinaa, cozida', nameEn: 'Italian Zucchini, cooked', info: { calories: 15, protein: 1.1, fat: 0.2, carbs: 3.0, fiber: 1.6, category: 'low carb', alkaline: true, bestFor: ['lunch', 'dinner'] } },
{ name: 'Abóbora Cabotian, crua', info: { calories: 39, protein: 1.7, fat: 0.5, carbs: 8.4, fiber: 2.2, category: 'low carb', alkaline: true } }, { name: 'Abóbora Cabotian, crua', nameEn: 'Cabotian Pumpkin, raw', info: { calories: 39, protein: 1.7, fat: 0.5, carbs: 8.4, fiber: 2.2, category: 'low carb', alkaline: true, bestFor: ['lunch', 'dinner'] } },
{ name: 'Abóbora Cabotian, cozida', info: { calories: 48, protein: 1.4, fat: 0.7, carbs: 10.8, fiber: 2.5, category: 'low carb', alkaline: true } }, { name: 'Abóbora Cabotian, cozida', nameEn: 'Cabotian Pumpkin, cooked', info: { calories: 48, protein: 1.4, fat: 0.7, carbs: 10.8, fiber: 2.5, category: 'low carb', alkaline: true, bestFor: ['lunch', 'dinner'] } },
{ name: 'Abóbora Cabotian, assada', info: { calories: 50, protein: 2, fat: 1, carbs: 11, fiber: 2.5, category: 'low carb', alkaline: true } }, { name: 'Abóbora Cabotian, assada', nameEn: 'Cabotian Pumpkin, roasted', info: { calories: 50, protein: 2, fat: 1, carbs: 11, fiber: 2.5, category: 'low carb', alkaline: true, bestFor: ['lunch', 'dinner'] } },
{ name: 'Cenoura, cozida', info: { calories: 30, protein: 0.8, fat: 0.2, carbs: 6.7, fiber: 2.6, category: 'low carb', alkaline: true } }, { name: 'Cenoura, cozida', nameEn: 'Carrot, cooked', info: { calories: 30, protein: 0.8, fat: 0.2, carbs: 6.7, fiber: 2.6, category: 'low carb', alkaline: true, bestFor: ['lunch', 'dinner'] } },
{ name: 'Tomate', info: { calories: 15, protein: 1.1, fat: 0.2, carbs: 3.1, fiber: 1.2, category: 'low carb', alkaline: true } }, { name: 'Tomate', nameEn: 'Tomato', info: { calories: 15, protein: 1.1, fat: 0.2, carbs: 3.1, fiber: 1.2, category: 'low carb', alkaline: true, bestFor: ['lunch', 'dinner'] } },
{ name: 'Beterraba, cozida', info: { calories: 32, protein: 1.3, fat: 0.1, carbs: 7.2, fiber: 1.9, category: 'low carb', alkaline: true } }, { name: 'Beterraba, cozida', nameEn: 'Beetroot, cooked', info: { calories: 32, protein: 1.3, fat: 0.1, carbs: 7.2, fiber: 1.9, category: 'low carb', alkaline: true, bestFor: ['lunch', 'dinner'] } },
{ name: 'Beterraba, crua', info: { calories: 49, protein: 1.9, fat: 0.1, carbs: 11.1, fiber: 3.4, category: 'low carb', alkaline: true } }, { name: 'Beterraba, crua', nameEn: 'Beetroot, raw', info: { calories: 49, protein: 1.9, fat: 0.1, carbs: 11.1, fiber: 3.4, category: 'low carb', alkaline: true, bestFor: ['lunch', 'dinner'] } },
{ name: 'Brócolis, cozido', info: { calories: 25, protein: 2.1, fat: 0.5, carbs: 4.4, fiber: 3.4, category: 'low carb', alkaline: true } }, { name: 'Brócolis, cozido', nameEn: 'Broccoli, cooked', info: { calories: 25, protein: 2.1, fat: 0.5, carbs: 4.4, fiber: 3.4, category: 'low carb', alkaline: true, bestFor: ['lunch', 'dinner'] } },
{ name: 'Brócolis, cru', info: { calories: 25, protein: 3.6, fat: 0.3, carbs: 4, fiber: 2.9, category: 'low carb', alkaline: true } }, { name: 'Brócolis, cru', nameEn: 'Broccoli, raw', info: { calories: 25, protein: 3.6, fat: 0.3, carbs: 4, fiber: 2.9, category: 'low carb', alkaline: true, bestFor: ['lunch', 'dinner'] } },
{ name: 'Couve flor, cozida', info: { calories: 19, protein: 1.2, fat: 0.3, carbs: 3.9, fiber: 2.1, category: 'low carb', alkaline: true } }, { name: 'Couve flor, cozida', nameEn: 'Cauliflower, cooked', info: { calories: 19, protein: 1.2, fat: 0.3, carbs: 3.9, fiber: 2.1, category: 'low carb', alkaline: true, bestFor: ['lunch', 'dinner'] } },
{ name: 'Pepino', info: { calories: 10, protein: 0.9, fat: 0, carbs: 2, fiber: 1.1, category: 'low carb', alkaline: true } }, { name: 'Pepino', nameEn: 'Cucumber', info: { calories: 10, protein: 0.9, fat: 0, carbs: 2, fiber: 1.1, category: 'low carb', alkaline: true, bestFor: ['lunch', 'dinner'] } },
{ name: 'Chuchu, cozido', info: { calories: 19, protein: 0.4, fat: 0, carbs: 4.9, fiber: 1.0, category: 'low carb', alkaline: true } }, { name: 'Chuchu, cozido', nameEn: 'Chayote, cooked', info: { calories: 19, protein: 0.4, fat: 0, carbs: 4.9, fiber: 1.0, category: 'low carb', alkaline: true, bestFor: ['lunch', 'dinner'] } },
{ name: 'Cogumelo Paris, in natura', info: { calories: 17, protein: 4.9, fat: 0, carbs: 1, fiber: 1.7, category: 'low carb', alkaline: true } }, { name: 'Cogumelo Paris, in natura', nameEn: 'Paris Mushroom, raw', info: { calories: 17, protein: 4.9, fat: 0, carbs: 1, fiber: 1.7, category: 'low carb', alkaline: true, bestFor: ['lunch', 'dinner'] } },
{ name: 'Milho, verde, enlatado, drenado', info: { calories: 98, protein: 3.2, fat: 2.4, carbs: 17.1, fiber: 4.6, category: 'low carb', alkaline: true } }, { name: 'Milho, verde, enlatado, drenado', nameEn: 'Green Corn, canned, drained', info: { calories: 98, protein: 3.2, fat: 2.4, carbs: 17.1, fiber: 4.6, category: 'low carb', alkaline: true, bestFor: ['lunch', 'dinner'] } },
{ name: 'Broto de Feijão', info: { calories: 23, protein: 2.8, fat: 0, carbs: 2.8, fiber: 0.7, category: 'low carb', alkaline: true } }, { name: 'Broto de Feijão', nameEn: 'Bean Sprout', info: { calories: 23, protein: 2.8, fat: 0, carbs: 2.8, fiber: 0.7, category: 'low carb', alkaline: true, bestFor: ['lunch', 'dinner'] } },
]; ];
+1021 -125
View File
File diff suppressed because it is too large Load Diff
+5 -1
View File
@@ -7,6 +7,8 @@ interface AppState {
calendarMonthlyCalories: DailyTotalCalories[]; calendarMonthlyCalories: DailyTotalCalories[];
isSharedView: boolean; isSharedView: boolean;
sharedData: SharedDay | null; sharedData: SharedDay | null;
userTimezone: string;
todayDateString: string;
} }
export const appState: AppState = { export const appState: AppState = {
@@ -15,5 +17,7 @@ export const appState: AppState = {
searchResults: [], searchResults: [],
calendarMonthlyCalories: [], calendarMonthlyCalories: [],
isSharedView: false, isSharedView: false,
sharedData: null sharedData: null,
userTimezone: '',
todayDateString: ''
}; };
+26 -5
View File
@@ -2,7 +2,8 @@ type FoodCategory = 'fats' | 'proteins' | 'carbs' | 'leaves' | 'fruits' | 'low c
export type FoodItem = { export type FoodItem = {
name: string; name: string;
info: { calories: number, protein: number, fat: number, carbs: number, fiber: number, category: FoodCategory, alkaline: boolean } nameEn: string;
info: { calories: number, protein: number, fat: number, carbs: number, fiber: number, category: FoodCategory, alkaline: boolean, bestFor: Array<String> }
} }
export type FoodStorage = { export type FoodStorage = {
@@ -22,10 +23,17 @@ export type FoodStorage = {
export type UserSettings = { export type UserSettings = {
id?: string; id?: string;
caloriesGoal?: number; caloriesGoal?: number;
proteinGoal: number; proteinGoal?: number;
fatGoal: number; fatGoal?: number;
carboGoal: number; carboGoal?: number;
fiberGoal: number; fiberGoal?: number;
bodyWeight?: number;
height?: number;
bmi?: number;
bmiResult?: string;
goalName?: string;
isActive?: boolean;
timezone?: string;
}; };
export type DailyTotalCalories = { export type DailyTotalCalories = {
@@ -43,3 +51,16 @@ export type SharedDay = {
foodEntries: string; // JSON stringified FoodStorage[] foodEntries: string; // JSON stringified FoodStorage[]
createdAt: string; createdAt: string;
}; };
export type MealPeriod = 'pre-workout' | 'breakfast' | 'second-breakfast' | 'lunch' | 'snacks' | 'dinner' | 'night-snacks';
export type MealGroup = {
period: MealPeriod;
label: string;
entries: FoodStorage[];
totalCalories: number;
totalProtein: number;
totalFat: number;
totalCarbs: number;
isExpanded: boolean;
};
+3
View File
@@ -1,3 +1,6 @@
/// <reference types="vite/client" />
/// <reference types="vite-plugin-pwa/client" />
interface ImportMetaEnv { interface ImportMetaEnv {
readonly VITE_APPWRITE_PROJECT_ID: string; readonly VITE_APPWRITE_PROJECT_ID: string;
readonly VITE_APPWRITE_ENDPOINT: string; readonly VITE_APPWRITE_ENDPOINT: string;
+360
View File
@@ -349,6 +349,133 @@ h1 {
background: #1dd1a1; background: #1dd1a1;
} }
/* Meal Group Styles */
.meal-group {
margin-bottom: 20px;
}
.meal-group-header {
background: linear-gradient(135deg, #3A4D6A 0%, #4A2E5A 100%);
padding: 16px 20px;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
.meal-group-header:hover {
background: linear-gradient(135deg, #2E3F58 0%, #3E2448 100%);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
}
.meal-group-header.collapsed {
border-radius: 8px;
margin-bottom: 0;
}
.meal-group-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
}
.meal-group-label {
font-size: 18px;
font-weight: 700;
color: #ffffff;
letter-spacing: 0.5px;
}
.meal-group-summary {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
font-size: 14px;
color: rgba(255, 255, 255, 0.85);
}
.meal-summary-item {
font-weight: 500;
}
.meal-summary-divider {
opacity: 0.5;
}
.meal-group-toggle {
font-size: 18px;
transition: transform 0.3s ease;
color: rgba(255, 255, 255, 0.8);
margin-left: 12px;
}
.meal-group-header .meal-group-toggle.rotated {
transform: rotate(180deg);
}
.meal-group-items {
display: flex;
flex-direction: column;
gap: 12px;
margin-top: 12px;
padding-left: 8px;
border-left: 3px solid rgba(107, 141, 214, 0.3);
transition: all 0.3s ease;
max-height: 5000px;
overflow: hidden;
}
.meal-group-items.collapsed {
max-height: 0;
margin-top: 0;
opacity: 0;
padding-top: 0;
padding-bottom: 0;
}
/* Adjust food card styles within meal groups */
.meal-group-items .food-card {
margin-left: 12px;
}
/* Mobile responsive adjustments */
@media (max-width: 768px) {
.meal-group-header {
padding: 14px 16px;
}
.meal-group-label {
font-size: 16px;
}
.meal-group-summary {
font-size: 13px;
gap: 6px;
}
.meal-summary-item {
font-size: 12px;
}
}
/* Stack summary items on very small screens */
@media (max-width: 480px) {
.meal-group-summary {
flex-direction: column;
align-items: flex-start;
gap: 4px;
}
.meal-summary-divider {
display: none;
}
}
.empty-state { .empty-state {
text-align: center; text-align: center;
padding: 60px 20px; padding: 60px 20px;
@@ -461,6 +588,36 @@ label {
color: #666; color: #666;
} }
.checkbox-group {
margin: 10px 0;
}
.checkbox-label {
display: flex;
align-items: center;
cursor: pointer;
color: #e0e0e0;
font-weight: 400;
user-select: none;
}
.ai-checkbox {
width: 18px;
height: 18px;
margin-right: 10px;
cursor: pointer;
accent-color: #6B8DD6;
}
.checkbox-label span {
font-size: 0.95rem;
color: #b0b0b0;
}
.checkbox-label:hover span {
color: #e0e0e0;
}
.add-btn { .add-btn {
padding: 17px 25px; padding: 17px 25px;
background: linear-gradient(135deg, #4CAF50, #45a049); background: linear-gradient(135deg, #4CAF50, #45a049);
@@ -490,6 +647,7 @@ label {
font-weight: 600; font-weight: 600;
transition: all 0.3s ease; transition: all 0.3s ease;
height: fit-content; height: fit-content;
display: none; /* Hidden by default, shown when editing */
} }
.cancel-btn:hover { .cancel-btn:hover {
@@ -901,3 +1059,205 @@ label {
pointer-events: none; pointer-events: none;
cursor: not-allowed !important; cursor: not-allowed !important;
} }
/* AI Nutrition Info Card */
#ai-nutrition-card {
margin-top: 20px;
animation: fadeIn 0.3s ease-in;
}
.ai-nutrition-container {
background: rgba(40, 40, 40, 0.6);
backdrop-filter: blur(10px);
border: 1px solid rgba(107, 141, 214, 0.3);
border-radius: 12px;
padding: 25px;
}
.ai-nutrition-title {
font-size: 1.2rem;
font-weight: 600;
color: white;
margin: 0 0 20px 0;
display: flex;
align-items: center;
gap: 10px;
}
.ai-badge {
background: linear-gradient(135deg, #6B8DD6 0%, #9B7BC6 100%);
color: white;
padding: 4px 10px;
border-radius: 6px;
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.5px;
}
/* Loading State */
.ai-loading {
text-align: center;
padding: 30px 20px;
}
.ai-spinner {
width: 40px;
height: 40px;
margin: 0 auto 15px;
border: 3px solid rgba(107, 141, 214, 0.3);
border-top: 3px solid #6B8DD6;
border-radius: 50%;
animation: spin 1s linear infinite;
}
.ai-loading p {
color: rgba(255, 255, 255, 0.7);
font-size: 0.95rem;
}
/* Error State */
.ai-error {
text-align: center;
padding: 20px;
color: rgba(255, 107, 107, 0.9);
font-size: 0.95rem;
}
/* Content */
.ai-nutrition-content {
display: flex;
flex-direction: column;
gap: 20px;
}
.ai-nutrition-section {
background: rgba(30, 30, 30, 0.4);
border-radius: 8px;
padding: 15px;
}
.ai-section-title {
font-size: 1rem;
font-weight: 600;
color: #6B8DD6;
margin: 0 0 12px 0;
padding-bottom: 8px;
border-bottom: 1px solid rgba(107, 141, 214, 0.2);
}
.ai-list {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 8px;
}
.ai-list li {
color: rgba(255, 255, 255, 0.85);
font-size: 0.9rem;
line-height: 1.5;
padding-left: 20px;
position: relative;
}
.ai-list li:before {
content: "•";
position: absolute;
left: 8px;
color: #6B8DD6;
font-weight: bold;
}
.ai-notes {
color: rgba(255, 255, 255, 0.85);
font-size: 0.9rem;
line-height: 1.6;
margin: 0;
}
/* Responsive adjustments */
@media (max-width: 768px) {
.ai-nutrition-container {
padding: 20px;
}
.ai-nutrition-title {
font-size: 1.1rem;
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
.ai-list li {
font-size: 0.85rem;
}
.ai-notes {
font-size: 0.85rem;
}
}
/* Alternatives Card */
#alternatives-container {
margin-top: 20px;
animation: fadeIn 0.3s ease-in;
}
.alternatives-list-container {
background: rgba(40, 40, 40, 0.6);
backdrop-filter: blur(10px);
border: 1px solid rgba(107, 141, 214, 0.3);
border-radius: 12px;
padding: 20px 25px;
}
.alternatives-title {
font-size: 1rem;
font-weight: 600;
color: white;
margin: 0 0 15px 0;
}
.alternative-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
cursor: pointer;
transition: background-color 0.2s ease;
border-radius: 4px;
padding-left: 6px;
padding-right: 6px;
}
.alternative-item:hover {
background-color: rgba(107, 141, 214, 0.15);
}
.alternative-item:last-child {
border-bottom: none;
}
.alternative-name {
color: #e0e0e0;
font-size: 0.9rem;
}
.alternative-quantity {
color: #6B8DD6;
font-weight: 600;
font-size: 0.9rem;
white-space: nowrap;
margin-left: 12px;
}
.no-alternatives {
color: #b0b0b0;
font-size: 0.9rem;
text-align: center;
padding: 10px 0;
font-style: italic;
}
-9
View File
@@ -1,9 +0,0 @@
# TODO
- [x] Allow search without special characters
- [x] Get the calories from the last 7 days and display in the calendar
- [x] Add food type: Alkaline or Acid
- [x] Display the percentage of Alkaline daily
- [x] Remove the table and create a list-like component
- [x] Add logo
- [ ] Group food added by time and consider a meal
+13
View File
@@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"noEmit": true,
"skipLibCheck": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"types": ["vite/client"]
},
"include": ["src"]
}
+68 -2
View File
@@ -1,6 +1,72 @@
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import { VitePWA } from 'vite-plugin-pwa'
export default defineConfig({ export default defineConfig({
base: '/calories-tracker/', include: ['src', 'vite-end.d.ts'],
include: ['src', 'vite-end.d.ts']
plugins: [
VitePWA({
registerType: 'prompt',
includeAssets: ['assets/*.png', 'assets/*.ico', 'assets/*.xml'],
manifest: {
name: 'Food Tracker - Daily Calorie Tracker',
short_name: 'Food Tracker',
description: 'Track your nutrition, transform your health. Monitor calories, protein, fat, carbs, and fiber for every meal.',
theme_color: '#121212',
background_color: '#121212',
display: 'standalone', // CRITICAL: Enables fullscreen
orientation: 'portrait',
scope: '/',
start_url: '/',
icons: [
{
src: '/assets/apple-icon-180x180.png',
sizes: '180x180',
type: 'image/png',
purpose: 'any'
},
{
src: '/assets/android-icon-192x192.png',
sizes: '192x192',
type: 'image/png',
purpose: 'any maskable'
},
{
src: '/assets/icon-512x512.png',
sizes: '512x512',
type: 'image/png',
purpose: 'any maskable'
}
]
},
workbox: {
runtimeCaching: [
{
urlPattern: /^https:\/\/fonts\.googleapis\.com\/.*/i,
handler: 'CacheFirst',
options: {
cacheName: 'google-fonts-cache',
expiration: {
maxEntries: 10,
maxAgeSeconds: 60 * 60 * 24 * 365
},
cacheableResponse: {
statuses: [0, 200]
}
}
}
],
cleanupOutdatedCaches: true,
globPatterns: ['**/*.{js,css,html,ico,png,svg}']
},
devOptions: {
enabled: false, // Disable in dev to avoid warnings; test with preview mode
type: 'module'
}
})
]
}) })