162 lines
7 KiB
HTML
162 lines
7 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>Calorie AI</title>
|
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
|
<link rel="stylesheet" href="/styles.css">
|
|
</head>
|
|
<body>
|
|
<div class="app-frame">
|
|
<aside class="sidebar" id="sidebar">
|
|
<div class="brand-block">
|
|
<span class="brand-mark">CA</span>
|
|
<div>
|
|
<strong>Calorie AI</strong>
|
|
<small>Private food diary</small>
|
|
</div>
|
|
</div>
|
|
<nav class="main-nav" aria-label="Main menu">
|
|
<button class="nav-item active" type="button" data-nav="dashboard">Dashboard</button>
|
|
<button class="nav-item" type="button" data-nav="log">Log meal</button>
|
|
<button class="nav-item" type="button" data-nav="analytics">Analytics</button>
|
|
<button class="nav-item" type="button" data-nav="diary">Diary</button>
|
|
<button class="nav-item" type="button" data-nav="settings">AI settings</button>
|
|
</nav>
|
|
<div class="sidebar-footer">
|
|
<small>Protected by local web auth</small>
|
|
<button id="logout" class="ghost" type="button">Sign out</button>
|
|
</div>
|
|
</aside>
|
|
|
|
<div class="content-shell">
|
|
<header class="topbar">
|
|
<button id="menuToggle" class="menu-toggle" type="button" aria-label="Open menu">Menu</button>
|
|
<div>
|
|
<p class="eyebrow">AI nutrition cockpit</p>
|
|
<h1>Private food tracking with image-backed nutrition estimates</h1>
|
|
</div>
|
|
<button class="primary-action" type="button" data-nav="log">Add meal</button>
|
|
</header>
|
|
|
|
<main class="content">
|
|
<section class="section-page" data-section="dashboard">
|
|
<div class="hero-grid">
|
|
<article class="hero-card">
|
|
<p class="eyebrow">Today</p>
|
|
<strong id="todayCalories">0 kcal</strong>
|
|
<span id="todayMacroText">P 0g | C 0g | F 0g</span>
|
|
<span id="todayProduceText">Fruit 0.0 | Veg 0.0</span>
|
|
</article>
|
|
<article class="metric-card">
|
|
<small>Meals logged</small>
|
|
<strong id="mealCountText">0</strong>
|
|
<span>Entries saved locally</span>
|
|
</article>
|
|
<article class="metric-card warm">
|
|
<small>7-day average</small>
|
|
<strong id="weekAverageText">0 kcal</strong>
|
|
<span>Calories per day</span>
|
|
</article>
|
|
<article class="metric-card cool">
|
|
<small>Produce today</small>
|
|
<strong id="produceGoalText">0.0 / 5</strong>
|
|
<span>Fruit + vegetable servings</span>
|
|
</article>
|
|
</div>
|
|
|
|
<section class="panel split-panel">
|
|
<div>
|
|
<p class="eyebrow">Quick log</p>
|
|
<h2>Capture the meal while it is still in front of you.</h2>
|
|
<p>Upload a plate photo, add a portion note, and let the vision model hand context to the tasking model for clean nutrition JSON.</p>
|
|
<button type="button" data-nav="log">Open meal logger</button>
|
|
</div>
|
|
<canvas id="macroChart" width="420" height="260"></canvas>
|
|
</section>
|
|
</section>
|
|
|
|
<section class="section-page" data-section="log" hidden>
|
|
<form id="mealForm" class="panel meal-panel">
|
|
<div class="panel-heading">
|
|
<p class="eyebrow">Meal intake</p>
|
|
<h2>Add meal</h2>
|
|
<p>Text-only works, but images give the vision model more evidence for portion and produce estimates.</p>
|
|
</div>
|
|
<div class="form-grid">
|
|
<label class="span-2">
|
|
Description
|
|
<textarea id="description" rows="5" placeholder="Example: grilled salmon, rice, broccoli, berries"></textarea>
|
|
</label>
|
|
<label>
|
|
Portion or measure
|
|
<input id="measure" placeholder="Example: one plate, 450g, 2 cups">
|
|
</label>
|
|
<label>
|
|
Meal image
|
|
<input id="image" type="file" accept="image/*">
|
|
</label>
|
|
</div>
|
|
<img id="preview" class="preview" alt="Selected meal preview" hidden>
|
|
<div class="form-actions">
|
|
<button type="submit">Analyze and save</button>
|
|
<p id="status" class="status"></p>
|
|
</div>
|
|
</form>
|
|
</section>
|
|
|
|
<section class="section-page" data-section="analytics" hidden>
|
|
<div class="grid two analytics-grid">
|
|
<section class="panel chart-panel wide">
|
|
<div class="panel-heading compact">
|
|
<p class="eyebrow">Trend</p>
|
|
<h2>Calories, last 7 days</h2>
|
|
</div>
|
|
<canvas id="calorieChart" width="720" height="260"></canvas>
|
|
</section>
|
|
<section class="panel chart-panel wide">
|
|
<div class="panel-heading compact">
|
|
<p class="eyebrow">Produce</p>
|
|
<h2>Fruit and vegetables</h2>
|
|
</div>
|
|
<canvas id="produceChart" width="720" height="240"></canvas>
|
|
</section>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="section-page" data-section="diary" hidden>
|
|
<section class="panel">
|
|
<div class="row-title">
|
|
<div>
|
|
<p class="eyebrow">Local history</p>
|
|
<h2>Food diary</h2>
|
|
</div>
|
|
<button id="clearData" class="secondary" type="button">Clear local diary</button>
|
|
</div>
|
|
<div id="entries" class="entries"></div>
|
|
</section>
|
|
</section>
|
|
|
|
<section class="section-page" data-section="settings" hidden>
|
|
<section class="panel settings-panel">
|
|
<div class="panel-heading">
|
|
<p class="eyebrow">Admin</p>
|
|
<h2>AI settings</h2>
|
|
<p>These settings stay in browser local storage. The API key is sent only to this server-side proxy, not directly to the model endpoint from the browser.</p>
|
|
</div>
|
|
<div class="form-grid">
|
|
<label class="span-2">API base URL<input id="baseUrl" placeholder="https://api.openai.com/v1"></label>
|
|
<label class="span-2">API key<input id="apiKey" type="password" placeholder="Optional for local endpoints"></label>
|
|
<label>Vision model<input id="visionModel" placeholder="gpt-4o-mini"></label>
|
|
<label>Tasking model<input id="taskModel" placeholder="gpt-4o-mini"></label>
|
|
</div>
|
|
<button id="saveSettings" type="button">Save settings</button>
|
|
</section>
|
|
</section>
|
|
</main>
|
|
</div>
|
|
</div>
|
|
<script src="/app.js"></script>
|
|
</body>
|
|
</html>
|